Compare commits
59 Commits
sleek-1.0
...
sleek-0.9-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf2bf29fc6 | ||
|
|
34dc236126 | ||
|
|
9464736551 | ||
|
|
47f1fb1690 | ||
|
|
66cf0c2021 | ||
|
|
e7c37c4ec5 | ||
|
|
919c8c5633 | ||
|
|
f54501a346 | ||
|
|
d20cd6b3e6 | ||
|
|
da6e1e47dc | ||
|
|
2f0f18a8c6 | ||
|
|
1c32668e18 | ||
|
|
1f3cfb98f1 | ||
|
|
4295a66c70 | ||
|
|
8227affd7f | ||
|
|
3a2f989c5e | ||
|
|
85a2715c7d | ||
|
|
b03e6168a8 | ||
|
|
2a43f59a58 | ||
|
|
184f7cb8a4 | ||
|
|
e1aa4d0b93 | ||
|
|
7930ed22f2 | ||
|
|
060b4c3938 | ||
|
|
49f5767aea | ||
|
|
4eb210bff5 | ||
|
|
1780ca900a | ||
|
|
e6c2fde283 | ||
|
|
ecf902bf16 | ||
|
|
d76c0931ef | ||
|
|
e18793152f | ||
|
|
e388680269 | ||
|
|
bee42e4a2f | ||
|
|
8e3227ae5e | ||
|
|
257bcadd96 | ||
|
|
3e5cdc8664 | ||
|
|
194e6bcb51 | ||
|
|
2e7024419a | ||
|
|
5235313aab | ||
|
|
a2719b0bb0 | ||
|
|
71ad715caa | ||
|
|
d452085049 | ||
|
|
8b3b8aca9e | ||
|
|
e00dea7c0c | ||
|
|
520bf72e11 | ||
|
|
040f426f1a | ||
|
|
226b0e4297 | ||
|
|
0b2cd176b1 | ||
|
|
56b5cbe5b1 | ||
|
|
3e83b16a58 | ||
|
|
de4d611d30 | ||
|
|
e8d0fc37dc | ||
|
|
8e95ae2948 | ||
|
|
341c110b6a | ||
|
|
7522839141 | ||
|
|
4c410dd48a | ||
|
|
a92075a659 | ||
|
|
7552efee5c | ||
|
|
6bc6ebb95d | ||
|
|
e0c32b6d9b |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,8 +1,6 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
|
.project
|
||||||
build/
|
build/
|
||||||
dist/
|
|
||||||
MANIFEST
|
|
||||||
docs/_build/
|
|
||||||
*.swp
|
*.swp
|
||||||
.tox/
|
.pydevproject
|
||||||
.coverage
|
.settings
|
||||||
|
|||||||
10
INSTALL
10
INSTALL
@@ -1,12 +1,8 @@
|
|||||||
Pre-requisites:
|
Pre-requisites:
|
||||||
- Python 3.1 or 2.6
|
Python 3.1 or 2.6
|
||||||
|
|
||||||
Install:
|
Install:
|
||||||
> python3 setup.py install
|
python3 setup.py install
|
||||||
|
|
||||||
Root install:
|
Root install:
|
||||||
> sudo python3 setup.py install
|
sudo python3 setup.py install
|
||||||
|
|
||||||
To test:
|
|
||||||
> cd examples
|
|
||||||
> python echo_client.py -v -j [USER@example.com] -p [PASSWORD]
|
|
||||||
|
|||||||
123
LICENSE
123
LICENSE
@@ -1,125 +1,4 @@
|
|||||||
Copyright (c) 2010 Nathanael C. Fritz
|
Copyright (c) 2010 ICRL
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Licenses of Bundled Third Party Code
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
dateutil - Extensions to the standard python 2.3+ datetime module.
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Copyright (c) 2003-2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
|
|
||||||
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
* Neither the name of the copyright holder nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
||||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
||||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
|
|
||||||
fixed_datetime
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Copyright (c) 2008, Red Innovation Ltd., Finland
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
* Neither the name of Red Innovation nor the names of its contributors
|
|
||||||
may be used to endorse or promote products derived from this software
|
|
||||||
without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY
|
|
||||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL RED INNOVATION BE LIABLE FOR ANY
|
|
||||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
OrderedDict - A port of the Python 2.7+ OrderedDict to Python 2.6
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Copyright (c) 2009 Raymond Hettinger
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
||||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
||||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
||||||
OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
SUELTA – A PURE-PYTHON SASL CLIENT LIBRARY
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This software is subject to "The MIT License"
|
|
||||||
|
|
||||||
Copyright 2007-2010 David Alan Cridland
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
39
MANIFEST
Normal file
39
MANIFEST
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
setup.py
|
||||||
|
sleekxmpp/__init__.py
|
||||||
|
sleekxmpp/basexmpp.py
|
||||||
|
sleekxmpp/clientxmpp.py
|
||||||
|
sleekxmpp/example.py
|
||||||
|
sleekxmpp/plugins/__init__.py
|
||||||
|
sleekxmpp/plugins/base.py
|
||||||
|
sleekxmpp/plugins/gmail_notify.py
|
||||||
|
sleekxmpp/plugins/xep_0004.py
|
||||||
|
sleekxmpp/plugins/xep_0009.py
|
||||||
|
sleekxmpp/plugins/xep_0030.py
|
||||||
|
sleekxmpp/plugins/xep_0045.py
|
||||||
|
sleekxmpp/plugins/xep_0050.py
|
||||||
|
sleekxmpp/plugins/xep_0060.py
|
||||||
|
sleekxmpp/plugins/xep_0078.py
|
||||||
|
sleekxmpp/plugins/xep_0086.py
|
||||||
|
sleekxmpp/plugins/xep_0092.py
|
||||||
|
sleekxmpp/plugins/xep_0199.py
|
||||||
|
sleekxmpp/stanza/__init__.py
|
||||||
|
sleekxmpp/stanza/iq.py
|
||||||
|
sleekxmpp/stanza/message.py
|
||||||
|
sleekxmpp/stanza/presence.py
|
||||||
|
sleekxmpp/xmlstream/__init__.py
|
||||||
|
sleekxmpp/xmlstream/stanzabase.py
|
||||||
|
sleekxmpp/xmlstream/statemachine.py
|
||||||
|
sleekxmpp/xmlstream/test.py
|
||||||
|
sleekxmpp/xmlstream/testclient.py
|
||||||
|
sleekxmpp/xmlstream/xmlstream.py
|
||||||
|
sleekxmpp/xmlstream/handler/__init__.py
|
||||||
|
sleekxmpp/xmlstream/handler/base.py
|
||||||
|
sleekxmpp/xmlstream/handler/callback.py
|
||||||
|
sleekxmpp/xmlstream/handler/waiter.py
|
||||||
|
sleekxmpp/xmlstream/handler/xmlcallback.py
|
||||||
|
sleekxmpp/xmlstream/handler/xmlwaiter.py
|
||||||
|
sleekxmpp/xmlstream/matcher/__init__.py
|
||||||
|
sleekxmpp/xmlstream/matcher/base.py
|
||||||
|
sleekxmpp/xmlstream/matcher/many.py
|
||||||
|
sleekxmpp/xmlstream/matcher/xmlmask.py
|
||||||
|
sleekxmpp/xmlstream/matcher/xpath.py
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
include README.rst
|
|
||||||
include LICENSE
|
|
||||||
include testall.py
|
|
||||||
recursive-include docs Makefile *.bat *.py *.rst *.css *.ttf *.png
|
|
||||||
recursive-include examples *.py
|
|
||||||
recursive-include tests *.py
|
|
||||||
42
README
Normal file
42
README
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
SleekXMPP is an XMPP library written for Python 3.1+ (with 2.6 compatibility).
|
||||||
|
Hosted at http://wiki.github.com/fritzy/SleekXMPP/
|
||||||
|
|
||||||
|
Featured in examples in XMPP: The Definitive Guide by Kevin Smith, Remko Tronçon, and Peter Saint-Andre
|
||||||
|
If you're coming here from The Definitive Guide, please read http://wiki.github.com/fritzy/SleekXMPP/xmpp-the-definitive-guide
|
||||||
|
|
||||||
|
SleekXMPP has several design goals/philosophies:
|
||||||
|
- Low number of dependencies.
|
||||||
|
- Every XEP as a plugin.
|
||||||
|
- Rewarding to work with.
|
||||||
|
|
||||||
|
The goals for 1.0 include (and we're getting close):
|
||||||
|
- Nearly Full test coverage of stanzas.
|
||||||
|
- Wide range of functional tests.
|
||||||
|
- Stanza objects for all interaction with the stream
|
||||||
|
- Documentation on using and extending SleekXMPP.
|
||||||
|
- Complete documentation on all implemented stanza objects
|
||||||
|
- Documentation on all examples used in XMPP: The Definitive Guide
|
||||||
|
|
||||||
|
1.1 will include:
|
||||||
|
- More functional and unit tests
|
||||||
|
- PEP-8 compliance
|
||||||
|
- XEP-225 support
|
||||||
|
|
||||||
|
Since 0.2, here's the Changelog:
|
||||||
|
- MANY bugfixes
|
||||||
|
- Re-implementation of handlers/threading to greatly simplify and remove bugs (no more spawning threads in handlers)
|
||||||
|
- Stanza objects for jabber:client and all implemented XEPs
|
||||||
|
- Raising XMPPError for jabber:client and extended errors in handlers
|
||||||
|
- Robust error handling and better insurance of iq responses
|
||||||
|
- Stanza objects have made life a lot easier!
|
||||||
|
- Massive audit/cleanup.
|
||||||
|
|
||||||
|
Credits
|
||||||
|
----------------
|
||||||
|
Main Author: Nathan Fritz fritz@netflint.net
|
||||||
|
Contributors: Kevin Smith & Lance Stout
|
||||||
|
Patches: Remko Tronçon
|
||||||
|
|
||||||
|
Feel free to add fritzy@netflint.net to your roster for direct support and comments.
|
||||||
|
Join sleekxmpp-discussion@googlegroups.com / http://groups.google.com/group/sleekxmpp-discussion for email discussion.
|
||||||
|
Join sleek@conference.jabber.org for groupchat discussion.
|
||||||
182
README.rst
182
README.rst
@@ -1,182 +0,0 @@
|
|||||||
SleekXMPP
|
|
||||||
#########
|
|
||||||
|
|
||||||
SleekXMPP is an MIT licensed XMPP library for Python 2.6/3.1+,
|
|
||||||
and is featured in examples in
|
|
||||||
`XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271>`_
|
|
||||||
by Kevin Smith, Remko Tronçon, and Peter Saint-Andre. If you've arrived
|
|
||||||
here from reading the Definitive Guide, please see the notes on updating
|
|
||||||
the examples to the latest version of SleekXMPP.
|
|
||||||
|
|
||||||
SleekXMPP's design goals and philosphy are:
|
|
||||||
|
|
||||||
**Low number of dependencies**
|
|
||||||
Installing and using SleekXMPP should be as simple as possible, without
|
|
||||||
having to deal with long dependency chains.
|
|
||||||
|
|
||||||
As part of reducing the number of dependencies, some third party
|
|
||||||
modules are included with SleekXMPP in the ``thirdparty`` directory.
|
|
||||||
Imports from this module first try to import an existing installed
|
|
||||||
version before loading the packaged version, when possible.
|
|
||||||
|
|
||||||
**Every XEP as a plugin**
|
|
||||||
Following Python's "batteries included" approach, the goal is to
|
|
||||||
provide support for all currently active XEPs (final and draft). Since
|
|
||||||
adding XEP support is done through easy to create plugins, the hope is
|
|
||||||
to also provide a solid base for implementing and creating experimental
|
|
||||||
XEPs.
|
|
||||||
|
|
||||||
**Rewarding to work with**
|
|
||||||
As much as possible, SleekXMPP should allow things to "just work" using
|
|
||||||
sensible defaults and appropriate abstractions. XML can be ugly to work
|
|
||||||
with, but it doesn't have to be that way.
|
|
||||||
|
|
||||||
|
|
||||||
Get the Code
|
|
||||||
------------
|
|
||||||
|
|
||||||
Get the latest stable version from PyPI::
|
|
||||||
|
|
||||||
pip install sleekxmpp
|
|
||||||
|
|
||||||
The latest source code for SleekXMPP may be found on `Github
|
|
||||||
<http://github.com/fritzy/SleekXMPP>`_. Releases can be found in the
|
|
||||||
``master`` branch, while the latest development version is in the
|
|
||||||
``develop`` branch.
|
|
||||||
|
|
||||||
**Latest Release**
|
|
||||||
- `1.0 <http://github.com/fritzy/SleekXMPP/zipball/1.0>`_
|
|
||||||
|
|
||||||
**Develop Releases**
|
|
||||||
- `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_
|
|
||||||
|
|
||||||
**Older Stable Releases**
|
|
||||||
- `1.0 RC3 <http://github.com/fritzy/SleekXMPP/zipball/1.0-RC3>`_
|
|
||||||
- `1.0 RC2 <http://github.com/fritzy/SleekXMPP/zipball/1.0-RC2>`_
|
|
||||||
- `1.0 RC1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-RC1>`_
|
|
||||||
- `1.0 Beta6.1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta6.1>`_
|
|
||||||
- `1.0 Beta5 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta5>`_
|
|
||||||
- `1.0 Beta4 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta4>`_
|
|
||||||
- `1.0 Beta3 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta3>`_
|
|
||||||
- `1.0 Beta2 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta2>`_
|
|
||||||
- `1.0 Beta1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta1>`_
|
|
||||||
|
|
||||||
Installing DNSPython
|
|
||||||
---------------------
|
|
||||||
If you are using Python3 and wish to use dnspython, you will have to checkout and
|
|
||||||
install the ``python3`` branch::
|
|
||||||
|
|
||||||
git clone http://github.com/rthalley/dnspython
|
|
||||||
cd dnspython
|
|
||||||
git checkout python3
|
|
||||||
python3 setup.py install
|
|
||||||
|
|
||||||
Discussion
|
|
||||||
----------
|
|
||||||
A mailing list and XMPP chat room are available for discussing and getting
|
|
||||||
help with SleekXMPP.
|
|
||||||
|
|
||||||
**Mailing List**
|
|
||||||
`SleekXMPP Discussion on Google Groups <http://groups.google.com/group/sleekxmpp-discussion>`_
|
|
||||||
|
|
||||||
**Chat**
|
|
||||||
`sleek@conference.jabber.org <xmpp:sleek@conference.jabber.org?join>`_
|
|
||||||
|
|
||||||
Documentation and Testing
|
|
||||||
-------------------------
|
|
||||||
Documentation can be found both inline in the code, and as a Sphinx project in ``/docs``.
|
|
||||||
To generate the Sphinx documentation, follow the commands below. The HTML output will
|
|
||||||
be in ``docs/_build/html``::
|
|
||||||
|
|
||||||
cd docs
|
|
||||||
make html
|
|
||||||
open _build/html/index.html
|
|
||||||
|
|
||||||
To run the test suite for SleekXMPP::
|
|
||||||
|
|
||||||
python testall.py
|
|
||||||
|
|
||||||
|
|
||||||
The SleekXMPP Boilerplate
|
|
||||||
-------------------------
|
|
||||||
Projects using SleekXMPP tend to follow a basic pattern for setting up client/component
|
|
||||||
connections and configuration. Here is the gist of the boilerplate needed for a SleekXMPP
|
|
||||||
based project. See the documetation or examples directory for more detailed archetypes for
|
|
||||||
SleekXMPP projects::
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from sleekxmpp import ClientXMPP
|
|
||||||
from sleekxmpp.exceptions import IqError, IqTimeout
|
|
||||||
|
|
||||||
|
|
||||||
class EchoBot(ClientXMPP):
|
|
||||||
|
|
||||||
def __init__(self, jid, password):
|
|
||||||
ClientXMPP.__init__(self, jid, password)
|
|
||||||
|
|
||||||
self.add_event_handler("session_start", self.session_start)
|
|
||||||
self.add_event_handler("message", self.message)
|
|
||||||
|
|
||||||
self.register_plugin('xep_0030') # Service Discovery
|
|
||||||
self.register_plugin('xep_0199') # XMPP Ping
|
|
||||||
|
|
||||||
# If you are working with an OpenFire server, you will
|
|
||||||
# need to use a different SSL version:
|
|
||||||
# import ssl
|
|
||||||
# self.ssl_version = ssl.PROTOCOL_SSLv3
|
|
||||||
|
|
||||||
def session_start(self, event):
|
|
||||||
self.send_presence()
|
|
||||||
|
|
||||||
# Most get_*/set_* methods from plugins use Iq stanzas, which
|
|
||||||
# can generate IqError and IqTimeout exceptions
|
|
||||||
try:
|
|
||||||
self.get_roster()
|
|
||||||
except IqError as err:
|
|
||||||
logging.error('There was an error getting the roster')
|
|
||||||
logging.error(err.iq['error']['condition'])
|
|
||||||
self.disconnect()
|
|
||||||
except IqTimeout:
|
|
||||||
logging.error('Server is taking too long to respond')
|
|
||||||
self.disconnect()
|
|
||||||
|
|
||||||
def message(self, msg):
|
|
||||||
if msg['type'] in ('chat', 'normal'):
|
|
||||||
msg.reply("Thanks for sending\n%(body)s" % msg).send()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Ideally use optparse or argparse to get JID,
|
|
||||||
# password, and log level.
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG,
|
|
||||||
format='%(levelname)-8s %(message)s')
|
|
||||||
|
|
||||||
xmpp = EchoBot('somejid@example.com', 'use_getpass')
|
|
||||||
xmpp.connect()
|
|
||||||
xmpp.process(block=True)
|
|
||||||
|
|
||||||
|
|
||||||
Credits
|
|
||||||
-------
|
|
||||||
**Main Author:** Nathan Fritz
|
|
||||||
`fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_,
|
|
||||||
`@fritzy <http://twitter.com/fritzy>`_
|
|
||||||
|
|
||||||
Nathan is also the author of XMPPHP and `Seesmic-AS3-XMPP
|
|
||||||
<http://code.google.com/p/seesmic-as3-xmpp/>`_, and a former member of
|
|
||||||
the XMPP Council.
|
|
||||||
|
|
||||||
**Co-Author:** Lance Stout
|
|
||||||
`lancestout@gmail.com <xmpp:lancestout@gmail.com?message>`_,
|
|
||||||
`@lancestout <http://twitter.com/lancestout>`_
|
|
||||||
|
|
||||||
**Contributors:**
|
|
||||||
- Brian Beggs (`macdiesel <http://github.com/macdiesel>`_)
|
|
||||||
- Dann Martens (`dannmartens <http://github.com/dannmartens>`_)
|
|
||||||
- Florent Le Coz (`louiz <http://github.com/louiz>`_)
|
|
||||||
- Kevin Smith (`Kev <http://github.com/Kev>`_, http://kismith.co.uk)
|
|
||||||
- Remko Tronçon (`remko <http://github.com/remko>`_, http://el-tramo.be)
|
|
||||||
- Te-jé Rogers (`te-je <http://github.com/te-je>`_)
|
|
||||||
- Thom Nichols (`tomstrummer <http://github.com/tomstrummer>`_)
|
|
||||||
@@ -122,14 +122,14 @@ if __name__ == '__main__':
|
|||||||
logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
|
logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
#load xml config
|
#load xml config
|
||||||
logging.info("Loading config file: %s" , opts.configfile)
|
logging.info("Loading config file: %s" % opts.configfile)
|
||||||
config = configparser.RawConfigParser()
|
config = configparser.RawConfigParser()
|
||||||
config.read(opts.configfile)
|
config.read(opts.configfile)
|
||||||
|
|
||||||
#init
|
#init
|
||||||
logging.info("Account 1 is %s" , config.get('account1', 'jid'))
|
logging.info("Account 1 is %s" % config.get('account1', 'jid'))
|
||||||
xmpp1 = TestClient(config.get('account1','jid'), config.get('account1','pass'))
|
xmpp1 = TestClient(config.get('account1','jid'), config.get('account1','pass'))
|
||||||
logging.info("Account 2 is %s" , config.get('account2', 'jid'))
|
logging.info("Account 2 is %s" % config.get('account2', 'jid'))
|
||||||
xmpp2 = TestClient(config.get('account2','jid'), config.get('account2','pass'))
|
xmpp2 = TestClient(config.get('account2','jid'), config.get('account2','pass'))
|
||||||
|
|
||||||
xmpp1.registerPlugin('xep_0004')
|
xmpp1.registerPlugin('xep_0004')
|
||||||
|
|||||||
@@ -186,14 +186,14 @@ if __name__ == '__main__':
|
|||||||
logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
|
logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
#load xml config
|
#load xml config
|
||||||
logging.info("Loading config file: %s" , opts.configfile)
|
logging.info("Loading config file: %s" % opts.configfile)
|
||||||
config = configparser.RawConfigParser()
|
config = configparser.RawConfigParser()
|
||||||
config.read(opts.configfile)
|
config.read(opts.configfile)
|
||||||
|
|
||||||
#init
|
#init
|
||||||
logging.info("Account 1 is %s" , config.get('account1', 'jid'))
|
logging.info("Account 1 is %s" % config.get('account1', 'jid'))
|
||||||
xmpp1 = TestClient(config.get('account1','jid'), config.get('account1','pass'))
|
xmpp1 = TestClient(config.get('account1','jid'), config.get('account1','pass'))
|
||||||
logging.info("Account 2 is %s" , config.get('account2', 'jid'))
|
logging.info("Account 2 is %s" % config.get('account2', 'jid'))
|
||||||
xmpp2 = TestClient(config.get('account2','jid'), config.get('account2','pass'))
|
xmpp2 = TestClient(config.get('account2','jid'), config.get('account2','pass'))
|
||||||
|
|
||||||
xmpp1.registerPlugin('xep_0004')
|
xmpp1.registerPlugin('xep_0004')
|
||||||
|
|||||||
130
docs/Makefile
130
docs/Makefile
@@ -1,130 +0,0 @@
|
|||||||
# Makefile for Sphinx documentation
|
|
||||||
#
|
|
||||||
|
|
||||||
# You can set these variables from the command line.
|
|
||||||
SPHINXOPTS =
|
|
||||||
SPHINXBUILD = sphinx-build
|
|
||||||
PAPER =
|
|
||||||
BUILDDIR = _build
|
|
||||||
|
|
||||||
# Internal variables.
|
|
||||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
|
||||||
PAPEROPT_letter = -D latex_paper_size=letter
|
|
||||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
|
||||||
|
|
||||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
|
|
||||||
|
|
||||||
help:
|
|
||||||
@echo "Please use \`make <target>' where <target> is one of"
|
|
||||||
@echo " html to make standalone HTML files"
|
|
||||||
@echo " dirhtml to make HTML files named index.html in directories"
|
|
||||||
@echo " singlehtml to make a single large HTML file"
|
|
||||||
@echo " pickle to make pickle files"
|
|
||||||
@echo " json to make JSON files"
|
|
||||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
|
||||||
@echo " qthelp to make HTML files and a qthelp project"
|
|
||||||
@echo " devhelp to make HTML files and a Devhelp project"
|
|
||||||
@echo " epub to make an epub"
|
|
||||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
|
||||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
|
||||||
@echo " text to make text files"
|
|
||||||
@echo " man to make manual pages"
|
|
||||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
|
||||||
@echo " linkcheck to check all external links for integrity"
|
|
||||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
|
||||||
|
|
||||||
clean:
|
|
||||||
-rm -rf $(BUILDDIR)/*
|
|
||||||
|
|
||||||
html:
|
|
||||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
|
||||||
|
|
||||||
dirhtml:
|
|
||||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
|
||||||
|
|
||||||
singlehtml:
|
|
||||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
|
||||||
|
|
||||||
pickle:
|
|
||||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can process the pickle files."
|
|
||||||
|
|
||||||
json:
|
|
||||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can process the JSON files."
|
|
||||||
|
|
||||||
htmlhelp:
|
|
||||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
|
||||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
|
||||||
|
|
||||||
qthelp:
|
|
||||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
|
||||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
|
||||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/SleekXMPP.qhcp"
|
|
||||||
@echo "To view the help file:"
|
|
||||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/SleekXMPP.qhc"
|
|
||||||
|
|
||||||
devhelp:
|
|
||||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished."
|
|
||||||
@echo "To view the help file:"
|
|
||||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/SleekXMPP"
|
|
||||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/SleekXMPP"
|
|
||||||
@echo "# devhelp"
|
|
||||||
|
|
||||||
epub:
|
|
||||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
|
||||||
|
|
||||||
latex:
|
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
|
||||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
|
||||||
"(use \`make latexpdf' here to do that automatically)."
|
|
||||||
|
|
||||||
latexpdf:
|
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
|
||||||
@echo "Running LaTeX files through pdflatex..."
|
|
||||||
make -C $(BUILDDIR)/latex all-pdf
|
|
||||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
|
||||||
|
|
||||||
text:
|
|
||||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
|
||||||
|
|
||||||
man:
|
|
||||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
|
||||||
|
|
||||||
changes:
|
|
||||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
|
||||||
@echo
|
|
||||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
|
||||||
|
|
||||||
linkcheck:
|
|
||||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
|
||||||
@echo
|
|
||||||
@echo "Link check complete; look for any errors in the above output " \
|
|
||||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
|
||||||
|
|
||||||
doctest:
|
|
||||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
|
||||||
@echo "Testing of doctests in the sources finished, look at the " \
|
|
||||||
"results in $(BUILDDIR)/doctest/output.txt."
|
|
||||||
452
docs/_static/agogo.css
vendored
452
docs/_static/agogo.css
vendored
@@ -1,452 +0,0 @@
|
|||||||
/*
|
|
||||||
* agogo.css_t
|
|
||||||
* ~~~~~~~~~~~
|
|
||||||
*
|
|
||||||
* Sphinx stylesheet -- agogo theme.
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
|
|
||||||
* :license: BSD, see LICENSE for details.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
* {
|
|
||||||
margin: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: "Verdana", Arial, sans-serif;
|
|
||||||
line-height: 1.4em;
|
|
||||||
color: black;
|
|
||||||
background-color: #eeeeec;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Page layout */
|
|
||||||
|
|
||||||
div.header, div.content, div.footer {
|
|
||||||
width: 70em;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.header-wrapper {
|
|
||||||
background: url(bgtop.png) top left repeat-x;
|
|
||||||
border-bottom: 3px solid #2e3436;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Default body styles */
|
|
||||||
a {
|
|
||||||
color: #ce5c00;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.bodywrapper a, div.footer a {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clearer {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.line-block {
|
|
||||||
display: block;
|
|
||||||
margin-top: 1em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.line-block .line-block {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
margin-left: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2, h3, h4 {
|
|
||||||
font-family: "Georgia", "Times New Roman", serif;
|
|
||||||
font-weight: normal;
|
|
||||||
color: #3465a4;
|
|
||||||
margin-bottom: .8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
color: #204a87;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
padding-bottom: .5em;
|
|
||||||
border-bottom: 1px solid #3465a4;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.headerlink {
|
|
||||||
visibility: hidden;
|
|
||||||
color: #dddddd;
|
|
||||||
padding-left: .3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1:hover > a.headerlink,
|
|
||||||
h2:hover > a.headerlink,
|
|
||||||
h3:hover > a.headerlink,
|
|
||||||
h4:hover > a.headerlink,
|
|
||||||
h5:hover > a.headerlink,
|
|
||||||
h6:hover > a.headerlink,
|
|
||||||
dt:hover > a.headerlink {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition {
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding: 2px 7px 1px 7px;
|
|
||||||
border-left: 0.2em solid black;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.admonition-title {
|
|
||||||
margin: 0px 10px 5px 0px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
dt:target, .highlighted {
|
|
||||||
background-color: #fbe54e;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Header */
|
|
||||||
|
|
||||||
div.header {
|
|
||||||
padding-top: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.header h1 {
|
|
||||||
font-family: "Georgia", "Times New Roman", serif;
|
|
||||||
font-weight: normal;
|
|
||||||
font-size: 180%;
|
|
||||||
letter-spacing: .08em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.header h1 a {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.header div.rel {
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.header div.rel a {
|
|
||||||
color: #fcaf3e;
|
|
||||||
letter-spacing: .1em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.logo {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.logo {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Content */
|
|
||||||
div.content-wrapper {
|
|
||||||
background-color: white;
|
|
||||||
padding-top: 20px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document {
|
|
||||||
width: 50em;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body {
|
|
||||||
padding-right: 2em;
|
|
||||||
text-align: justify;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document ul {
|
|
||||||
margin: 1.5em;
|
|
||||||
list-style-type: square;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document dd {
|
|
||||||
margin-left: 1.2em;
|
|
||||||
margin-top: .4em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document .section {
|
|
||||||
margin-top: 1.7em;
|
|
||||||
}
|
|
||||||
div.document .section:first-child {
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document div.highlight {
|
|
||||||
padding: 3px;
|
|
||||||
background-color: #eeeeec;
|
|
||||||
border-top: 2px solid #dddddd;
|
|
||||||
border-bottom: 2px solid #dddddd;
|
|
||||||
margin-top: .8em;
|
|
||||||
margin-bottom: .8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document h2 {
|
|
||||||
margin-top: .7em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document p {
|
|
||||||
margin-bottom: .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document li.toctree-l1 {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document .descname {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document .docutils.literal {
|
|
||||||
background-color: #eeeeec;
|
|
||||||
padding: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document .docutils.xref.literal {
|
|
||||||
background-color: transparent;
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document blockquote {
|
|
||||||
margin: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document ol {
|
|
||||||
margin: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Sidebar */
|
|
||||||
|
|
||||||
div.sidebar {
|
|
||||||
width: 20em;
|
|
||||||
float: right;
|
|
||||||
font-size: .9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sidebar a, div.header a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sidebar a:hover, div.header a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sidebar h3 {
|
|
||||||
color: #2e3436;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 130%;
|
|
||||||
letter-spacing: .1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sidebar ul {
|
|
||||||
list-style-type: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sidebar li.toctree-l1 a {
|
|
||||||
display: block;
|
|
||||||
padding: 1px;
|
|
||||||
border: 1px solid #dddddd;
|
|
||||||
background-color: #eeeeec;
|
|
||||||
margin-bottom: .4em;
|
|
||||||
padding-left: 3px;
|
|
||||||
color: #2e3436;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sidebar li.toctree-l2 a {
|
|
||||||
background-color: transparent;
|
|
||||||
border: none;
|
|
||||||
margin-left: 1em;
|
|
||||||
border-bottom: 1px solid #dddddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sidebar li.toctree-l3 a {
|
|
||||||
background-color: transparent;
|
|
||||||
border: none;
|
|
||||||
margin-left: 2em;
|
|
||||||
border-bottom: 1px solid #dddddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sidebar li.toctree-l2:last-child a {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sidebar li.toctree-l1.current a {
|
|
||||||
border-right: 5px solid #fcaf3e;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sidebar li.toctree-l1.current li.toctree-l2 a {
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Footer */
|
|
||||||
|
|
||||||
div.footer-wrapper {
|
|
||||||
background: url(bgfooter.png) top left repeat-x;
|
|
||||||
border-top: 4px solid #babdb6;
|
|
||||||
padding-top: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
min-height: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer, div.footer a {
|
|
||||||
color: #888a85;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer .right {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer .left {
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Styles copied from basic theme */
|
|
||||||
|
|
||||||
img.align-left, .figure.align-left, object.align-left {
|
|
||||||
clear: left;
|
|
||||||
float: left;
|
|
||||||
margin-right: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.align-right, .figure.align-right, object.align-right {
|
|
||||||
clear: right;
|
|
||||||
float: right;
|
|
||||||
margin-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.align-center, .figure.align-center, object.align-center {
|
|
||||||
display: block;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align-left {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align-center {
|
|
||||||
clear: both;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align-right {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- search page ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
ul.search {
|
|
||||||
margin: 10px 0 0 20px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.search li {
|
|
||||||
padding: 5px 0 5px 20px;
|
|
||||||
background-image: url(file.png);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: 0 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.search li a {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.search li div.context {
|
|
||||||
color: #888;
|
|
||||||
margin: 2px 0 0 30px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.keywordmatches li.goodmatch a {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- index page ------------------------------------------------------------ */
|
|
||||||
|
|
||||||
table.contentstable {
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.contentstable p.biglink {
|
|
||||||
line-height: 150%;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.biglink {
|
|
||||||
font-size: 1.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.linkdescr {
|
|
||||||
font-style: italic;
|
|
||||||
padding-top: 5px;
|
|
||||||
font-size: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- general index --------------------------------------------------------- */
|
|
||||||
|
|
||||||
table.indextable td {
|
|
||||||
text-align: left;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.indextable dl, table.indextable dd {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.indextable tr.pcap {
|
|
||||||
height: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.indextable tr.cap {
|
|
||||||
margin-top: 10px;
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.toggler {
|
|
||||||
margin-right: 3px;
|
|
||||||
margin-top: 3px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- viewcode extension ---------------------------------------------------- */
|
|
||||||
|
|
||||||
.viewcode-link {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewcode-back {
|
|
||||||
float: right;
|
|
||||||
font-family:: "Verdana", Arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.viewcode-block:target {
|
|
||||||
margin: -1px -3px;
|
|
||||||
padding: 0 3px;
|
|
||||||
background-color: #f4debf;
|
|
||||||
border-top: 1px solid #ac9;
|
|
||||||
border-bottom: 1px solid #ac9;
|
|
||||||
}
|
|
||||||
532
docs/_static/basic.css
vendored
532
docs/_static/basic.css
vendored
@@ -1,532 +0,0 @@
|
|||||||
/*
|
|
||||||
* basic.css
|
|
||||||
* ~~~~~~~~~
|
|
||||||
*
|
|
||||||
* Sphinx stylesheet -- basic theme.
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
|
|
||||||
* :license: BSD, see LICENSE for details.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* -- main layout ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.clearer {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- relbar ---------------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.related {
|
|
||||||
width: 100%;
|
|
||||||
font-size: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related h3 {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related ul {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 0 0 10px;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related li {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related li.right {
|
|
||||||
float: right;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- sidebar --------------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.sphinxsidebarwrapper {
|
|
||||||
padding: 10px 5px 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar {
|
|
||||||
float: left;
|
|
||||||
width: 230px;
|
|
||||||
margin-left: -100%;
|
|
||||||
font-size: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul ul,
|
|
||||||
div.sphinxsidebar ul.want-points {
|
|
||||||
margin-left: 20px;
|
|
||||||
list-style: square;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul ul {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar form {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar input {
|
|
||||||
border: 1px solid #98dbcc;
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- search page ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
ul.search {
|
|
||||||
margin: 10px 0 0 20px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.search li {
|
|
||||||
padding: 5px 0 5px 20px;
|
|
||||||
background-image: url(file.png);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: 0 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.search li a {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.search li div.context {
|
|
||||||
color: #888;
|
|
||||||
margin: 2px 0 0 30px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.keywordmatches li.goodmatch a {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- index page ------------------------------------------------------------ */
|
|
||||||
|
|
||||||
table.contentstable {
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.contentstable p.biglink {
|
|
||||||
line-height: 150%;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.biglink {
|
|
||||||
font-size: 1.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.linkdescr {
|
|
||||||
font-style: italic;
|
|
||||||
padding-top: 5px;
|
|
||||||
font-size: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- general index --------------------------------------------------------- */
|
|
||||||
|
|
||||||
table.indextable {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.indextable td {
|
|
||||||
text-align: left;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.indextable dl, table.indextable dd {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.indextable tr.pcap {
|
|
||||||
height: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.indextable tr.cap {
|
|
||||||
margin-top: 10px;
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.toggler {
|
|
||||||
margin-right: 3px;
|
|
||||||
margin-top: 3px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.modindex-jumpbox {
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
margin: 1em 0 1em 0;
|
|
||||||
padding: 0.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.genindex-jumpbox {
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
margin: 1em 0 1em 0;
|
|
||||||
padding: 0.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- general body styles --------------------------------------------------- */
|
|
||||||
|
|
||||||
a.headerlink {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1:hover > a.headerlink,
|
|
||||||
h2:hover > a.headerlink,
|
|
||||||
h3:hover > a.headerlink,
|
|
||||||
h4:hover > a.headerlink,
|
|
||||||
h5:hover > a.headerlink,
|
|
||||||
h6:hover > a.headerlink,
|
|
||||||
dt:hover > a.headerlink {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body p.caption {
|
|
||||||
text-align: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body td {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-list ul {
|
|
||||||
padding-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.first {
|
|
||||||
margin-top: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.rubric {
|
|
||||||
margin-top: 30px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.align-left, .figure.align-left, object.align-left {
|
|
||||||
clear: left;
|
|
||||||
float: left;
|
|
||||||
margin-right: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.align-right, .figure.align-right, object.align-right {
|
|
||||||
clear: right;
|
|
||||||
float: right;
|
|
||||||
margin-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.align-center, .figure.align-center, object.align-center {
|
|
||||||
display: block;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align-left {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align-center {
|
|
||||||
clear: both;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align-right {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- sidebars -------------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.sidebar {
|
|
||||||
margin: 0 0 0.5em 1em;
|
|
||||||
border: 1px solid #ddb;
|
|
||||||
padding: 7px 7px 0 7px;
|
|
||||||
background-color: #efefef;
|
|
||||||
width: 40%;
|
|
||||||
float: right;
|
|
||||||
-mox-border-radius: 4px;
|
|
||||||
-webkit-border-radius: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.sidebar-title {
|
|
||||||
font-weight: bold;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- topics ---------------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.topic {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
padding: 7px 7px 0 7px;
|
|
||||||
margin: 10px 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.topic-title {
|
|
||||||
font-size: 1.1em;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- admonitions ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.admonition {
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition dt {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition dl {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.admonition-title {
|
|
||||||
margin: 0px 10px 5px 0px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body p.centered {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- tables ---------------------------------------------------------------- */
|
|
||||||
|
|
||||||
table.docutils {
|
|
||||||
border: 0;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.docutils td, table.docutils th {
|
|
||||||
padding: 1px 8px 1px 5px;
|
|
||||||
border-top: 0;
|
|
||||||
border-left: 0;
|
|
||||||
border-right: 0;
|
|
||||||
border-bottom: 1px solid #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.field-list td, table.field-list th {
|
|
||||||
border: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.footnote td, table.footnote th {
|
|
||||||
border: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
text-align: left;
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.citation {
|
|
||||||
border-left: solid 1px gray;
|
|
||||||
margin-left: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.citation td {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- other body styles ----------------------------------------------------- */
|
|
||||||
|
|
||||||
ol.arabic {
|
|
||||||
list-style: decimal;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol.loweralpha {
|
|
||||||
list-style: lower-alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol.upperalpha {
|
|
||||||
list-style: upper-alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol.lowerroman {
|
|
||||||
list-style: lower-roman;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol.upperroman {
|
|
||||||
list-style: upper-roman;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd p {
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd ul, dd table {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd {
|
|
||||||
margin-top: 3px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
margin-left: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dt:target, .highlighted {
|
|
||||||
}
|
|
||||||
|
|
||||||
dl.glossary dt {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-list ul {
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-list p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refcount {
|
|
||||||
color: #060;
|
|
||||||
}
|
|
||||||
|
|
||||||
.optional {
|
|
||||||
font-size: 1.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.versionmodified {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.system-message {
|
|
||||||
background-color: #fda;
|
|
||||||
padding: 5px;
|
|
||||||
border: 3px solid red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footnote:target {
|
|
||||||
background-color: #ffa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.line-block {
|
|
||||||
display: block;
|
|
||||||
margin-top: 1em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.line-block .line-block {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
margin-left: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guilabel, .menuselection {
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.accelerator {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.classifier {
|
|
||||||
font-style: oblique;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- code displays --------------------------------------------------------- */
|
|
||||||
|
|
||||||
pre {
|
|
||||||
overflow: auto;
|
|
||||||
overflow-y: hidden; /* fixes display issues on Chrome browsers */
|
|
||||||
}
|
|
||||||
|
|
||||||
td.linenos pre {
|
|
||||||
padding: 5px 0px;
|
|
||||||
border: 0;
|
|
||||||
background-color: transparent;
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.highlighttable {
|
|
||||||
margin-left: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.highlighttable td {
|
|
||||||
padding: 0 0.5em 0 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
tt.descname {
|
|
||||||
background-color: transparent;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
tt.descclassname {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
tt.xref, a tt {
|
|
||||||
background-color: transparent;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewcode-link {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewcode-back {
|
|
||||||
float: right;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.viewcode-block:target {
|
|
||||||
margin: -1px -10px;
|
|
||||||
padding: 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- math display ---------------------------------------------------------- */
|
|
||||||
|
|
||||||
img.math {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body div.math p {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.eqno {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- printout stylesheet --------------------------------------------------- */
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
div.document,
|
|
||||||
div.documentwrapper,
|
|
||||||
div.bodywrapper {
|
|
||||||
margin: 0 !important;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar,
|
|
||||||
div.related,
|
|
||||||
div.footer,
|
|
||||||
#top-link {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
256
docs/_static/default.css
vendored
256
docs/_static/default.css
vendored
@@ -1,256 +0,0 @@
|
|||||||
/*
|
|
||||||
* default.css_t
|
|
||||||
* ~~~~~~~~~~~~~
|
|
||||||
*
|
|
||||||
* Sphinx stylesheet -- default theme.
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
|
|
||||||
* :license: BSD, see LICENSE for details.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
@import url("basic.css");
|
|
||||||
|
|
||||||
/* -- page layout ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 100%;
|
|
||||||
background-color: #11303d;
|
|
||||||
color: #000;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document {
|
|
||||||
background-color: #1c4e63;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.documentwrapper {
|
|
||||||
float: left;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.bodywrapper {
|
|
||||||
margin: 0 0 0 230px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body {
|
|
||||||
background-color: #ffffff;
|
|
||||||
color: #000000;
|
|
||||||
padding: 0 20px 30px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer {
|
|
||||||
color: #ffffff;
|
|
||||||
width: 100%;
|
|
||||||
padding: 9px 0 9px 0;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 75%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer a {
|
|
||||||
color: #ffffff;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related {
|
|
||||||
background-color: #133f52;
|
|
||||||
line-height: 30px;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related a {
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar {
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h3 {
|
|
||||||
font-family: 'Trebuchet MS', sans-serif;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 1.4em;
|
|
||||||
font-weight: normal;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h3 a {
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h4 {
|
|
||||||
font-family: 'Trebuchet MS', sans-serif;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 1.3em;
|
|
||||||
font-weight: normal;
|
|
||||||
margin: 5px 0 0 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar p {
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar p.topless {
|
|
||||||
margin: 5px 10px 10px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul {
|
|
||||||
margin: 10px;
|
|
||||||
padding: 0;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar a {
|
|
||||||
color: #98dbcc;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar input {
|
|
||||||
border: 1px solid #98dbcc;
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* -- hyperlink styles ------------------------------------------------------ */
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #355f7c;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:visited {
|
|
||||||
color: #355f7c;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* -- body styles ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.body h1,
|
|
||||||
div.body h2,
|
|
||||||
div.body h3,
|
|
||||||
div.body h4,
|
|
||||||
div.body h5,
|
|
||||||
div.body h6 {
|
|
||||||
font-family: 'Trebuchet MS', sans-serif;
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
font-weight: normal;
|
|
||||||
color: #20435c;
|
|
||||||
border-bottom: 1px solid #ccc;
|
|
||||||
margin: 20px -20px 10px -20px;
|
|
||||||
padding: 3px 0 3px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body h1 { margin-top: 0; font-size: 200%; }
|
|
||||||
div.body h2 { font-size: 160%; }
|
|
||||||
div.body h3 { font-size: 140%; }
|
|
||||||
div.body h4 { font-size: 120%; }
|
|
||||||
div.body h5 { font-size: 110%; }
|
|
||||||
div.body h6 { font-size: 100%; }
|
|
||||||
|
|
||||||
a.headerlink {
|
|
||||||
color: #c60f0f;
|
|
||||||
font-size: 0.8em;
|
|
||||||
padding: 0 4px 0 4px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.headerlink:hover {
|
|
||||||
background-color: #c60f0f;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body p, div.body dd, div.body li {
|
|
||||||
text-align: justify;
|
|
||||||
line-height: 130%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition p.admonition-title + p {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition p {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition pre {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition ul, div.admonition ol {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.note {
|
|
||||||
background-color: #eee;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.seealso {
|
|
||||||
background-color: #ffc;
|
|
||||||
border: 1px solid #ff6;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.topic {
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.warning {
|
|
||||||
background-color: #ffe4e4;
|
|
||||||
border: 1px solid #f66;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.admonition-title {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.admonition-title:after {
|
|
||||||
content: ":";
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
padding: 5px;
|
|
||||||
background-color: #eeffcc;
|
|
||||||
color: #333333;
|
|
||||||
line-height: 120%;
|
|
||||||
border: 1px solid #ac9;
|
|
||||||
border-left: none;
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
tt {
|
|
||||||
background-color: #ecf0f3;
|
|
||||||
padding: 0 1px 0 1px;
|
|
||||||
font-size: 0.95em;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
background-color: #ede;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning tt {
|
|
||||||
background: #efc2c2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note tt {
|
|
||||||
background: #d6d6d6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewcode-back {
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.viewcode-block:target {
|
|
||||||
background-color: #f4debf;
|
|
||||||
border-top: 1px solid #ac9;
|
|
||||||
border-bottom: 1px solid #ac9;
|
|
||||||
}
|
|
||||||
BIN
docs/_static/fonts/Museo_Slab_500.otf
vendored
BIN
docs/_static/fonts/Museo_Slab_500.otf
vendored
Binary file not shown.
BIN
docs/_static/fonts/Museo_Slab_500italic.otf
vendored
BIN
docs/_static/fonts/Museo_Slab_500italic.otf
vendored
Binary file not shown.
BIN
docs/_static/fonts/OFLGoudyStMTT-Italic.ttf
vendored
BIN
docs/_static/fonts/OFLGoudyStMTT-Italic.ttf
vendored
Binary file not shown.
BIN
docs/_static/fonts/OFLGoudyStMTT.ttf
vendored
BIN
docs/_static/fonts/OFLGoudyStMTT.ttf
vendored
Binary file not shown.
BIN
docs/_static/fonts/YanoneKaffeesatz-Bold.ttf
vendored
BIN
docs/_static/fonts/YanoneKaffeesatz-Bold.ttf
vendored
Binary file not shown.
BIN
docs/_static/fonts/YanoneKaffeesatz-Light.ttf
vendored
BIN
docs/_static/fonts/YanoneKaffeesatz-Light.ttf
vendored
Binary file not shown.
BIN
docs/_static/fonts/YanoneKaffeesatz-Regular.ttf
vendored
BIN
docs/_static/fonts/YanoneKaffeesatz-Regular.ttf
vendored
Binary file not shown.
BIN
docs/_static/fonts/YanoneKaffeesatz-Thin.ttf
vendored
BIN
docs/_static/fonts/YanoneKaffeesatz-Thin.ttf
vendored
Binary file not shown.
406
docs/_static/haiku.css
vendored
406
docs/_static/haiku.css
vendored
@@ -1,406 +0,0 @@
|
|||||||
/*
|
|
||||||
* haiku.css_t
|
|
||||||
* ~~~~~~~~~~~
|
|
||||||
*
|
|
||||||
* Sphinx stylesheet -- haiku theme.
|
|
||||||
*
|
|
||||||
* Adapted from http://haiku-os.org/docs/Haiku-doc.css.
|
|
||||||
* Original copyright message:
|
|
||||||
*
|
|
||||||
* Copyright 2008-2009, Haiku. All rights reserved.
|
|
||||||
* Distributed under the terms of the MIT License.
|
|
||||||
*
|
|
||||||
* Authors:
|
|
||||||
* Francois Revol <revol@free.fr>
|
|
||||||
* Stephan Assmus <superstippi@gmx.de>
|
|
||||||
* Braden Ewing <brewin@gmail.com>
|
|
||||||
* Humdinger <humdingerb@gmail.com>
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
|
|
||||||
* :license: BSD, see LICENSE for details.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
@import url("basic.css");
|
|
||||||
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Museo Slab";
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
src: local("Museo Slab"),
|
|
||||||
url("fonts/Museo_Slab_500.otf") format("opentype");
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Yanone Kaffeesatz";
|
|
||||||
font-weight: bold;
|
|
||||||
font-style: normal;
|
|
||||||
src: local("Yanone Kaffeesatz"),
|
|
||||||
url("fonts/YanoneKaffeesatz-Bold.ttf") format("truetype");
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Yanone Kaffeesatz";
|
|
||||||
font-weight: lighter;
|
|
||||||
font-style: normal;
|
|
||||||
src: local("Yanone Kaffeesatz"),
|
|
||||||
url("fonts/YanoneKaffeesatz-Regular.ttf") format("truetype");
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
margin: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
background: #FFF url(header.png) top left repeat-x;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
line-height: 1.5;
|
|
||||||
margin: auto;
|
|
||||||
padding: 0px;
|
|
||||||
font-family: "Helvetica Neueu", Helvetica, sans-serif;
|
|
||||||
min-width: 59em;
|
|
||||||
max-width: 70em;
|
|
||||||
color: #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer {
|
|
||||||
padding: 8px;
|
|
||||||
font-size: 11px;
|
|
||||||
text-align: center;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* link colors and text decoration */
|
|
||||||
|
|
||||||
a:link {
|
|
||||||
font-weight: bold;
|
|
||||||
text-decoration: none;
|
|
||||||
color: #00ADEE;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:visited {
|
|
||||||
font-weight: bold;
|
|
||||||
text-decoration: none;
|
|
||||||
color: #00ADEE;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover, a:active {
|
|
||||||
text-decoration: underline;
|
|
||||||
color: #F46DBA;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Some headers act as anchors, don't give them a hover effect */
|
|
||||||
|
|
||||||
h1 a:hover, a:active {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #CFCFCF;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 a:hover, a:active {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #CFCFCF;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 a:hover, a:active {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #CFCFCF;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 a:hover, a:active {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #CFCFCF;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.headerlink {
|
|
||||||
color: #a7ce38;
|
|
||||||
padding-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.headerlink:hover {
|
|
||||||
color: #a7ce38;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* basic text elements */
|
|
||||||
|
|
||||||
div.content {
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-left: 40px;
|
|
||||||
margin-right: 40px;
|
|
||||||
margin-bottom: 50px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* heading and navigation */
|
|
||||||
|
|
||||||
div.header {
|
|
||||||
position: relative;
|
|
||||||
margin-top: 125px;
|
|
||||||
height: 85px;
|
|
||||||
padding: 0 40px;
|
|
||||||
font-family: "Yanone Kaffeesatz";
|
|
||||||
}
|
|
||||||
div.header h1 {
|
|
||||||
font-size: 2.6em;
|
|
||||||
font-weight: normal;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
color: #CFCFCF;
|
|
||||||
border: 0;
|
|
||||||
margin: 0;
|
|
||||||
padding-top: 15px;
|
|
||||||
font-family: "Yanone Kaffeesatz";
|
|
||||||
text-shadow: 1px 1px 1px rgba(175, 175, 175, .8);
|
|
||||||
font-variant: small-caps;
|
|
||||||
}
|
|
||||||
div.header h1 a {
|
|
||||||
font-weight: normal;
|
|
||||||
color: #00ADEE;
|
|
||||||
}
|
|
||||||
div.header h2 {
|
|
||||||
font-size: 1.3em;
|
|
||||||
font-weight: normal;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: #aaa;
|
|
||||||
border: 0;
|
|
||||||
margin-top: -3px;
|
|
||||||
padding: 0;
|
|
||||||
font-family: "Yanone Kaffeesatz";
|
|
||||||
}
|
|
||||||
|
|
||||||
div.header img.rightlogo {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
div.title {
|
|
||||||
font-size: 1.3em;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #CFCFCF;
|
|
||||||
border-bottom: dotted thin #e0e0e0;
|
|
||||||
margin-bottom: 25px;
|
|
||||||
}
|
|
||||||
div.topnav {
|
|
||||||
position: relative;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
div.topnav p {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-left: 40px;
|
|
||||||
margin-right: 40px;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
text-align: right;
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
div.bottomnav {
|
|
||||||
background: #eeeeee;
|
|
||||||
}
|
|
||||||
div.bottomnav p {
|
|
||||||
margin-right: 40px;
|
|
||||||
text-align: right;
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.uplink {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* contents box */
|
|
||||||
|
|
||||||
table.index {
|
|
||||||
margin: 0px 0px 30px 30px;
|
|
||||||
padding: 1px;
|
|
||||||
border-width: 1px;
|
|
||||||
border-style: dotted;
|
|
||||||
border-color: #e0e0e0;
|
|
||||||
}
|
|
||||||
table.index tr.heading {
|
|
||||||
background-color: #e0e0e0;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
table.index tr.index {
|
|
||||||
background-color: #eeeeee;
|
|
||||||
}
|
|
||||||
table.index td {
|
|
||||||
padding: 5px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.index a:link, table.index a:visited {
|
|
||||||
font-weight: normal;
|
|
||||||
text-decoration: none;
|
|
||||||
color: #4A7389;
|
|
||||||
}
|
|
||||||
table.index a:hover, table.index a:active {
|
|
||||||
text-decoration: underline;
|
|
||||||
color: #ff4500;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Haiku User Guide styles and layout */
|
|
||||||
|
|
||||||
/* Rounded corner boxes */
|
|
||||||
/* Common declarations */
|
|
||||||
div.admonition {
|
|
||||||
-webkit-border-radius: 10px;
|
|
||||||
-khtml-border-radius: 10px;
|
|
||||||
-moz-border-radius: 10px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border-style: dotted;
|
|
||||||
border-width: thin;
|
|
||||||
border-color: #dcdcdc;
|
|
||||||
padding: 10px 15px 10px 15px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
div.note {
|
|
||||||
padding: 10px 15px 10px 15px;
|
|
||||||
background-color: #e4ffde;
|
|
||||||
/*background: #e4ffde url(alert_info_32.png) 15px 15px no-repeat;*/
|
|
||||||
min-height: 42px;
|
|
||||||
}
|
|
||||||
div.warning {
|
|
||||||
padding: 10px 15px 10px 15px;
|
|
||||||
background-color: #fffbc6;
|
|
||||||
/*background: #fffbc6 url(alert_warning_32.png) 15px 15px no-repeat;*/
|
|
||||||
min-height: 42px;
|
|
||||||
}
|
|
||||||
div.seealso {
|
|
||||||
background: #e4ffde;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* More layout and styles */
|
|
||||||
h1 {
|
|
||||||
font-size: 1.6em;
|
|
||||||
color: #aaa;
|
|
||||||
border-bottom: dotted thin #e0e0e0;
|
|
||||||
margin-top: 30px;
|
|
||||||
font-family: "Museo Slab";
|
|
||||||
text-shadow: 1px 1px 1px rgba(175, 175, 175, .25);
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 1.5em;
|
|
||||||
font-weight: normal;
|
|
||||||
color: #aaa;
|
|
||||||
border-bottom: dotted thin #e0e0e0;
|
|
||||||
margin-top: 30px;
|
|
||||||
font-family: "Museo Slab";
|
|
||||||
text-shadow: 1px 1px 1px rgba(175, 175, 175, .25);
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.4em;
|
|
||||||
font-weight: normal;
|
|
||||||
color: #aaa;
|
|
||||||
margin-top: 30px;
|
|
||||||
font-family: "Museo Slab";
|
|
||||||
text-shadow: 1px 1px 1px rgba(175, 175, 175, .25);
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
font-size: 1.3em;
|
|
||||||
font-weight: normal;
|
|
||||||
color: #CFCFCF;
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
text-align: justify;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.last {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol {
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
padding-left: 5px;
|
|
||||||
margin-top: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.content ul > li {
|
|
||||||
-moz-background-clip:border;
|
|
||||||
-moz-background-inline-policy:continuous;
|
|
||||||
-moz-background-origin:padding;
|
|
||||||
background: transparent url(bullet_orange.png) no-repeat scroll left 0.45em;
|
|
||||||
list-style-image: none;
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0 0 0 1.666em;
|
|
||||||
margin-bottom: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
tt {
|
|
||||||
background-color: #e2e2e2;
|
|
||||||
font-size: 1.0em;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
font-size: 1.1em;
|
|
||||||
margin: 0 0 12px 0;
|
|
||||||
padding: 0.8em;
|
|
||||||
background-image: url(noise_dk.png);
|
|
||||||
background-color: #222;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
border-top: 1px solid #ccc;
|
|
||||||
border-bottom: 0;
|
|
||||||
border-right: 0;
|
|
||||||
border-left: 0;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* printer only pretty stuff */
|
|
||||||
@media print {
|
|
||||||
.noprint {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
/* for acronyms we want their definitions inlined at print time */
|
|
||||||
acronym[title]:after {
|
|
||||||
font-size: small;
|
|
||||||
content: " (" attr(title) ")";
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
/* and not have mozilla dotted underline */
|
|
||||||
acronym {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
div.topnav, div.bottomnav, div.header, table.index {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
div.content {
|
|
||||||
margin: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
html {
|
|
||||||
background: #FFF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewcode-back {
|
|
||||||
font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.viewcode-block:target {
|
|
||||||
border-top: 1px solid #ac9;
|
|
||||||
border-bottom: 1px solid #ac9;
|
|
||||||
margin: -1px -12px;
|
|
||||||
padding: 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
BIN
docs/_static/header.png
vendored
BIN
docs/_static/header.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
BIN
docs/_static/images/arch_layers.png
vendored
BIN
docs/_static/images/arch_layers.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 27 KiB |
70
docs/_static/ir_black.css
vendored
70
docs/_static/ir_black.css
vendored
@@ -1,70 +0,0 @@
|
|||||||
.highlight .hll { background-color: #ffffcc }
|
|
||||||
.highlight { background: #000000; color: #f6f3e8; }
|
|
||||||
.highlight .c { color: #7C7C7C; } /* Comment */
|
|
||||||
.highlight .err { color: #f6f3e8; } /* Error */
|
|
||||||
.highlight .g { color: #f6f3e8; } /* Generic */
|
|
||||||
.highlight .k { color: #00ADEE; } /* Keyword */
|
|
||||||
.highlight .l { color: #f6f3e8; } /* Literal */
|
|
||||||
.highlight .n { color: #f6f3e8; } /* Name */
|
|
||||||
.highlight .o { color: #f6f3e8; } /* Operator */
|
|
||||||
.highlight .x { color: #f6f3e8; } /* Other */
|
|
||||||
.highlight .p { color: #f6f3e8; } /* Punctuation */
|
|
||||||
.highlight .cm { color: #7C7C7C; } /* Comment.Multiline */
|
|
||||||
.highlight .cp { color: #96CBFE; } /* Comment.Preproc */
|
|
||||||
.highlight .c1 { color: #7C7C7C; } /* Comment.Single */
|
|
||||||
.highlight .cs { color: #7C7C7C; } /* Comment.Special */
|
|
||||||
.highlight .gd { color: #f6f3e8; } /* Generic.Deleted */
|
|
||||||
.highlight .ge { color: #f6f3e8; } /* Generic.Emph */
|
|
||||||
.highlight .gr { color: #ffffff; background-color: #ff0000 } /* Generic.Error */
|
|
||||||
.highlight .gh { color: #f6f3e8; font-weight: bold; } /* Generic.Heading */
|
|
||||||
.highlight .gi { color: #f6f3e8; } /* Generic.Inserted */
|
|
||||||
.highlight .go { color: #070707; } /* Generic.Output */
|
|
||||||
.highlight .gp { color: #f6f3e8; } /* Generic.Prompt */
|
|
||||||
.highlight .gs { color: #f6f3e8; } /* Generic.Strong */
|
|
||||||
.highlight .gu { color: #f6f3e8; font-weight: bold; } /* Generic.Subheading */
|
|
||||||
.highlight .gt { color: #ffffff; font-weight: bold; background-color: #FF6C60 } /* Generic.Traceback */
|
|
||||||
.highlight .kc { color: #6699CC; } /* Keyword.Constant */
|
|
||||||
.highlight .kd { color: #6699CC; } /* Keyword.Declaration */
|
|
||||||
.highlight .kn { color: #6699CC; } /* Keyword.Namespace */
|
|
||||||
.highlight .kp { color: #6699CC; } /* Keyword.Pseudo */
|
|
||||||
.highlight .kr { color: #6699CC; } /* Keyword.Reserved */
|
|
||||||
.highlight .kt { color: #FFFFB6; } /* Keyword.Type */
|
|
||||||
.highlight .ld { color: #f6f3e8; } /* Literal.Date */
|
|
||||||
.highlight .m { color: #FF73FD; } /* Literal.Number */
|
|
||||||
.highlight .s { color: #F46DBA;/*#A8FF60;*/ } /* Literal.String */
|
|
||||||
.highlight .na { color: #f6f3e8; } /* Name.Attribute */
|
|
||||||
.highlight .nb { color: #f6f3e8; } /* Name.Builtin */
|
|
||||||
.highlight .nc { color: #f6f3e8; } /* Name.Class */
|
|
||||||
.highlight .no { color: #99CC99; } /* Name.Constant */
|
|
||||||
.highlight .nd { color: #f6f3e8; } /* Name.Decorator */
|
|
||||||
.highlight .ni { color: #E18964; } /* Name.Entity */
|
|
||||||
.highlight .ne { color: #f6f3e8; } /* Name.Exception */
|
|
||||||
.highlight .nf { color: #F64DBA; } /* Name.Function */
|
|
||||||
.highlight .nl { color: #f6f3e8; } /* Name.Label */
|
|
||||||
.highlight .nn { color: #f6f3e8; } /* Name.Namespace */
|
|
||||||
.highlight .nx { color: #f6f3e8; } /* Name.Other */
|
|
||||||
.highlight .py { color: #f6f3e8; } /* Name.Property */
|
|
||||||
.highlight .nt { color: #00ADEE; } /* Name.Tag */
|
|
||||||
.highlight .nv { color: #C6C5FE; } /* Name.Variable */
|
|
||||||
.highlight .ow { color: #ffffff; } /* Operator.Word */
|
|
||||||
.highlight .w { color: #f6f3e8; } /* Text.Whitespace */
|
|
||||||
.highlight .mf { color: #FF73FD; } /* Literal.Number.Float */
|
|
||||||
.highlight .mh { color: #FF73FD; } /* Literal.Number.Hex */
|
|
||||||
.highlight .mi { color: #FF73FD; } /* Literal.Number.Integer */
|
|
||||||
.highlight .mo { color: #FF73FD; } /* Literal.Number.Oct */
|
|
||||||
.highlight .sb { color: #A8FF60; } /* Literal.String.Backtick */
|
|
||||||
.highlight .sc { color: #A8FF60; } /* Literal.String.Char */
|
|
||||||
.highlight .sd { color: #A8FF60; } /* Literal.String.Doc */
|
|
||||||
.highlight .s2 { color: #A8FF60; } /* Literal.String.Double */
|
|
||||||
.highlight .se { color: #A8FF60; } /* Literal.String.Escape */
|
|
||||||
.highlight .sh { color: #A8FF60; } /* Literal.String.Heredoc */
|
|
||||||
.highlight .si { color: #A8FF60; } /* Literal.String.Interpol */
|
|
||||||
.highlight .sx { color: #A8FF60; } /* Literal.String.Other */
|
|
||||||
.highlight .sr { color: #A8FF60; } /* Literal.String.Regex */
|
|
||||||
.highlight .s1 { color: #A8FF60; } /* Literal.String.Single */
|
|
||||||
.highlight .ss { color: #A8FF60; } /* Literal.String.Symbol */
|
|
||||||
.highlight .bp { color: #f6f3e8; } /* Name.Builtin.Pseudo */
|
|
||||||
.highlight .vc { color: #C6C5FE; } /* Name.Variable.Class */
|
|
||||||
.highlight .vg { color: #C6C5FE; } /* Name.Variable.Global */
|
|
||||||
.highlight .vi { color: #C6C5FE; } /* Name.Variable.Instance */
|
|
||||||
.highlight .il { color: #FF73FD; } /* Literal.Number.Integer.Long */
|
|
||||||
245
docs/_static/nature.css
vendored
245
docs/_static/nature.css
vendored
@@ -1,245 +0,0 @@
|
|||||||
/*
|
|
||||||
* nature.css_t
|
|
||||||
* ~~~~~~~~~~~~
|
|
||||||
*
|
|
||||||
* Sphinx stylesheet -- nature theme.
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
|
|
||||||
* :license: BSD, see LICENSE for details.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
@import url("basic.css");
|
|
||||||
|
|
||||||
/* -- page layout ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
font-size: 100%;
|
|
||||||
background-color: #111;
|
|
||||||
color: #555;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.documentwrapper {
|
|
||||||
float: left;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.bodywrapper {
|
|
||||||
margin: 0 0 0 230px;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
border: 1px solid #B1B4B6;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document {
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body {
|
|
||||||
background-color: #ffffff;
|
|
||||||
color: #3E4349;
|
|
||||||
padding: 0 30px 30px 30px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer {
|
|
||||||
color: #555;
|
|
||||||
width: 100%;
|
|
||||||
padding: 13px 0;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 75%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer a {
|
|
||||||
color: #444;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related {
|
|
||||||
background-color: #6BA81E;
|
|
||||||
line-height: 32px;
|
|
||||||
color: #fff;
|
|
||||||
text-shadow: 0px 1px 0 #444;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related a {
|
|
||||||
color: #E2F3CC;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar {
|
|
||||||
font-size: 0.75em;
|
|
||||||
line-height: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebarwrapper{
|
|
||||||
padding: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h3,
|
|
||||||
div.sphinxsidebar h4 {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
color: #222;
|
|
||||||
font-size: 1.2em;
|
|
||||||
font-weight: normal;
|
|
||||||
margin: 0;
|
|
||||||
padding: 5px 10px;
|
|
||||||
background-color: #ddd;
|
|
||||||
text-shadow: 1px 1px 0 white
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h4{
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h3 a {
|
|
||||||
color: #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
div.sphinxsidebar p {
|
|
||||||
color: #888;
|
|
||||||
padding: 5px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar p.topless {
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul {
|
|
||||||
margin: 10px 20px;
|
|
||||||
padding: 0;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar a {
|
|
||||||
color: #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar input {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar input[type=text]{
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- body styles ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #005B81;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #E32E00;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body h1,
|
|
||||||
div.body h2,
|
|
||||||
div.body h3,
|
|
||||||
div.body h4,
|
|
||||||
div.body h5,
|
|
||||||
div.body h6 {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
background-color: #BED4EB;
|
|
||||||
font-weight: normal;
|
|
||||||
color: #212224;
|
|
||||||
margin: 30px 0px 10px 0px;
|
|
||||||
padding: 5px 0 5px 10px;
|
|
||||||
text-shadow: 0px 1px 0 white
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; }
|
|
||||||
div.body h2 { font-size: 150%; background-color: #C8D5E3; }
|
|
||||||
div.body h3 { font-size: 120%; background-color: #D8DEE3; }
|
|
||||||
div.body h4 { font-size: 110%; background-color: #D8DEE3; }
|
|
||||||
div.body h5 { font-size: 100%; background-color: #D8DEE3; }
|
|
||||||
div.body h6 { font-size: 100%; background-color: #D8DEE3; }
|
|
||||||
|
|
||||||
a.headerlink {
|
|
||||||
color: #c60f0f;
|
|
||||||
font-size: 0.8em;
|
|
||||||
padding: 0 4px 0 4px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.headerlink:hover {
|
|
||||||
background-color: #c60f0f;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body p, div.body dd, div.body li {
|
|
||||||
line-height: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition p.admonition-title + p {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.highlight{
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.note {
|
|
||||||
background-color: #eee;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.seealso {
|
|
||||||
background-color: #ffc;
|
|
||||||
border: 1px solid #ff6;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.topic {
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.warning {
|
|
||||||
background-color: #ffe4e4;
|
|
||||||
border: 1px solid #f66;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.admonition-title {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.admonition-title:after {
|
|
||||||
content: ":";
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
padding: 10px;
|
|
||||||
background-color: White;
|
|
||||||
color: #222;
|
|
||||||
line-height: 1.2em;
|
|
||||||
border: 1px solid #C6C9CB;
|
|
||||||
font-size: 1.1em;
|
|
||||||
margin: 1.5em 0 1.5em 0;
|
|
||||||
-webkit-box-shadow: 1px 1px 1px #d8d8d8;
|
|
||||||
-moz-box-shadow: 1px 1px 1px #d8d8d8;
|
|
||||||
}
|
|
||||||
|
|
||||||
tt {
|
|
||||||
background-color: #ecf0f3;
|
|
||||||
color: #222;
|
|
||||||
/* padding: 1px 2px; */
|
|
||||||
font-size: 1.1em;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewcode-back {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.viewcode-block:target {
|
|
||||||
background-color: #f4debf;
|
|
||||||
border-top: 1px solid #ac9;
|
|
||||||
border-bottom: 1px solid #ac9;
|
|
||||||
}
|
|
||||||
BIN
docs/_static/noise_dk.png
vendored
BIN
docs/_static/noise_dk.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB |
339
docs/_static/sphinxdoc.css
vendored
339
docs/_static/sphinxdoc.css
vendored
@@ -1,339 +0,0 @@
|
|||||||
/*
|
|
||||||
* sphinxdoc.css_t
|
|
||||||
* ~~~~~~~~~~~~~~~
|
|
||||||
*
|
|
||||||
* Sphinx stylesheet -- sphinxdoc theme. Originally created by
|
|
||||||
* Armin Ronacher for Werkzeug.
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
|
|
||||||
* :license: BSD, see LICENSE for details.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
@import url("basic.css");
|
|
||||||
|
|
||||||
/* -- page layout ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
|
|
||||||
'Verdana', sans-serif;
|
|
||||||
font-size: 14px;
|
|
||||||
letter-spacing: -0.01em;
|
|
||||||
line-height: 150%;
|
|
||||||
text-align: center;
|
|
||||||
background-color: #BFD1D4;
|
|
||||||
color: black;
|
|
||||||
padding: 0;
|
|
||||||
border: 1px solid #aaa;
|
|
||||||
|
|
||||||
margin: 0px 80px 0px 80px;
|
|
||||||
min-width: 740px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document {
|
|
||||||
background-color: white;
|
|
||||||
text-align: left;
|
|
||||||
background-image: url(contents.png);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.bodywrapper {
|
|
||||||
margin: 0 240px 0 0;
|
|
||||||
border-right: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0.5em 20px 20px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related ul {
|
|
||||||
background-image: url(navigation.png);
|
|
||||||
height: 2em;
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related ul li {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
height: 2em;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related ul li.right {
|
|
||||||
float: right;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related ul li a {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 5px 0 5px;
|
|
||||||
line-height: 1.75em;
|
|
||||||
color: #EE9816;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related ul li a:hover {
|
|
||||||
color: #3CA8E7;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebarwrapper {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0.5em 15px 15px 0;
|
|
||||||
width: 210px;
|
|
||||||
float: right;
|
|
||||||
font-size: 1em;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h3, div.sphinxsidebar h4 {
|
|
||||||
margin: 1em 0 0.5em 0;
|
|
||||||
font-size: 1em;
|
|
||||||
padding: 0.1em 0 0.1em 0.5em;
|
|
||||||
color: white;
|
|
||||||
border: 1px solid #86989B;
|
|
||||||
background-color: #AFC1C4;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h3 a {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul {
|
|
||||||
padding-left: 1.5em;
|
|
||||||
margin-top: 7px;
|
|
||||||
padding: 0;
|
|
||||||
line-height: 130%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul ul {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer {
|
|
||||||
background-color: #E3EFF1;
|
|
||||||
color: #86989B;
|
|
||||||
padding: 3px 8px 3px 0;
|
|
||||||
clear: both;
|
|
||||||
font-size: 0.8em;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer a {
|
|
||||||
color: #86989B;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- body styles ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0.8em 0 0.5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #CA7900;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #2491CF;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body a {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0.7em 0 0.3em 0;
|
|
||||||
font-size: 1.5em;
|
|
||||||
color: #11557C;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
margin: 1.3em 0 0.2em 0;
|
|
||||||
font-size: 1.35em;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 1em 0 -0.3em 0;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a {
|
|
||||||
color: black!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor {
|
|
||||||
display: none;
|
|
||||||
margin: 0 0 0 0.3em;
|
|
||||||
padding: 0 0.2em 0 0.2em;
|
|
||||||
color: #aaa!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor,
|
|
||||||
h5:hover a.anchor, h6:hover a.anchor {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover,
|
|
||||||
h5 a.anchor:hover, h6 a.anchor:hover {
|
|
||||||
color: #777;
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.headerlink {
|
|
||||||
color: #c60f0f!important;
|
|
||||||
font-size: 1em;
|
|
||||||
margin-left: 6px;
|
|
||||||
padding: 0 4px 0 4px;
|
|
||||||
text-decoration: none!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.headerlink:hover {
|
|
||||||
background-color: #ccc;
|
|
||||||
color: white!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
cite, code, tt {
|
|
||||||
font-family: 'Consolas', 'Deja Vu Sans Mono',
|
|
||||||
'Bitstream Vera Sans Mono', monospace;
|
|
||||||
font-size: 0.95em;
|
|
||||||
letter-spacing: 0.01em;
|
|
||||||
}
|
|
||||||
|
|
||||||
tt {
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
tt.descname, tt.descclassname, tt.xref {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
border: 1px solid #abc;
|
|
||||||
margin: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
a tt {
|
|
||||||
border: 0;
|
|
||||||
color: #CA7900;
|
|
||||||
}
|
|
||||||
|
|
||||||
a tt:hover {
|
|
||||||
color: #2491CF;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
font-family: 'Consolas', 'Deja Vu Sans Mono',
|
|
||||||
'Bitstream Vera Sans Mono', monospace;
|
|
||||||
font-size: 0.95em;
|
|
||||||
letter-spacing: 0.015em;
|
|
||||||
line-height: 120%;
|
|
||||||
padding: 0.5em;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
background-color: #f8f8f8;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre a {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
td.linenos pre {
|
|
||||||
padding: 0.5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.quotebar {
|
|
||||||
background-color: #f8f8f8;
|
|
||||||
max-width: 250px;
|
|
||||||
float: right;
|
|
||||||
padding: 2px 7px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.topic {
|
|
||||||
background-color: #f8f8f8;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin: 0 -0.5em 0 -0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table td, table th {
|
|
||||||
padding: 0.2em 0.5em 0.2em 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition, div.warning {
|
|
||||||
font-size: 0.9em;
|
|
||||||
margin: 1em 0 1em 0;
|
|
||||||
border: 1px solid #86989B;
|
|
||||||
background-color: #f7f7f7;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition p, div.warning p {
|
|
||||||
margin: 0.5em 1em 0.5em 1em;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition pre, div.warning pre {
|
|
||||||
margin: 0.4em 1em 0.4em 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition p.admonition-title,
|
|
||||||
div.warning p.admonition-title {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0.1em 0 0.1em 0.5em;
|
|
||||||
color: white;
|
|
||||||
border-bottom: 1px solid #86989B;
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: #AFC1C4;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.warning {
|
|
||||||
border: 1px solid #940000;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.warning p.admonition-title {
|
|
||||||
background-color: #CF0000;
|
|
||||||
border-bottom-color: #940000;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition ul, div.admonition ol,
|
|
||||||
div.warning ul, div.warning ol {
|
|
||||||
margin: 0.1em 0.5em 0.5em 3em;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.versioninfo {
|
|
||||||
margin: 1em 0 0 0;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
background-color: #DDEAF0;
|
|
||||||
padding: 8px;
|
|
||||||
line-height: 1.3em;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewcode-back {
|
|
||||||
font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
|
|
||||||
'Verdana', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.viewcode-block:target {
|
|
||||||
background-color: #f4debf;
|
|
||||||
border-top: 1px solid #ac9;
|
|
||||||
border-bottom: 1px solid #ac9;
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
========
|
|
||||||
BaseXMPP
|
|
||||||
========
|
|
||||||
|
|
||||||
.. module:: sleekxmpp.basexmpp
|
|
||||||
|
|
||||||
.. autoclass:: BaseXMPP
|
|
||||||
:members:
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
==========
|
|
||||||
ClientXMPP
|
|
||||||
==========
|
|
||||||
|
|
||||||
.. module:: sleekxmpp.clientxmpp
|
|
||||||
|
|
||||||
.. autoclass:: ClientXMPP
|
|
||||||
:members:
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
=============
|
|
||||||
ComponentXMPP
|
|
||||||
=============
|
|
||||||
|
|
||||||
.. module:: sleekxmpp.componentxmpp
|
|
||||||
|
|
||||||
.. autoclass:: ComponentXMPP
|
|
||||||
:members:
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
Exceptions
|
|
||||||
==========
|
|
||||||
|
|
||||||
.. module:: sleekxmpp.exceptions
|
|
||||||
|
|
||||||
|
|
||||||
.. autoexception:: XMPPError
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. autoexception:: IqError
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. autoexception:: IqTimeout
|
|
||||||
:members:
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
.. module:: sleekxmpp.xmlstream.filesocket
|
|
||||||
|
|
||||||
.. _filesocket:
|
|
||||||
|
|
||||||
Python 2.6 File Socket Shims
|
|
||||||
============================
|
|
||||||
|
|
||||||
.. autoclass:: FileSocket
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. autoclass:: Socket26
|
|
||||||
:members:
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
Stanza Handlers
|
|
||||||
===============
|
|
||||||
|
|
||||||
The Basic Handler
|
|
||||||
-----------------
|
|
||||||
.. module:: sleekxmpp.xmlstream.handler.base
|
|
||||||
|
|
||||||
.. autoclass:: BaseHandler
|
|
||||||
:members:
|
|
||||||
|
|
||||||
Callback
|
|
||||||
--------
|
|
||||||
.. module:: sleekxmpp.xmlstream.handler.callback
|
|
||||||
|
|
||||||
.. autoclass:: Callback
|
|
||||||
:members:
|
|
||||||
|
|
||||||
|
|
||||||
Waiter
|
|
||||||
------
|
|
||||||
.. module:: sleekxmpp.xmlstream.handler.waiter
|
|
||||||
|
|
||||||
.. autoclass:: Waiter
|
|
||||||
:members:
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
Jabber IDs (JID)
|
|
||||||
=================
|
|
||||||
|
|
||||||
.. module:: sleekxmpp.xmlstream.jid
|
|
||||||
|
|
||||||
.. autoclass:: JID
|
|
||||||
:members:
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
Stanza Matchers
|
|
||||||
===============
|
|
||||||
|
|
||||||
The Basic Matcher
|
|
||||||
-----------------
|
|
||||||
.. module:: sleekxmpp.xmlstream.matcher.base
|
|
||||||
|
|
||||||
.. autoclass:: MatcherBase
|
|
||||||
:members:
|
|
||||||
|
|
||||||
|
|
||||||
ID Matching
|
|
||||||
-----------
|
|
||||||
.. module:: sleekxmpp.xmlstream.matcher.id
|
|
||||||
|
|
||||||
.. autoclass:: MatcherId
|
|
||||||
:members:
|
|
||||||
|
|
||||||
|
|
||||||
Stanza Path Matching
|
|
||||||
--------------------
|
|
||||||
.. module:: sleekxmpp.xmlstream.matcher.stanzapath
|
|
||||||
|
|
||||||
.. autoclass:: StanzaPath
|
|
||||||
:members:
|
|
||||||
|
|
||||||
|
|
||||||
XPath
|
|
||||||
-----
|
|
||||||
.. module:: sleekxmpp.xmlstream.matcher.xpath
|
|
||||||
|
|
||||||
.. autoclass:: MatchXPath
|
|
||||||
:members:
|
|
||||||
|
|
||||||
|
|
||||||
XMLMask
|
|
||||||
-------
|
|
||||||
.. module:: sleekxmpp.xmlstream.matcher.xmlmask
|
|
||||||
|
|
||||||
.. autoclass:: MatchXMLMask
|
|
||||||
:members:
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
=========
|
|
||||||
Scheduler
|
|
||||||
=========
|
|
||||||
|
|
||||||
.. module:: sleekxmpp.xmlstream.scheduler
|
|
||||||
|
|
||||||
.. autoclass:: Task
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. autoclass:: Scheduler
|
|
||||||
:members:
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
.. _stanzabase:
|
|
||||||
|
|
||||||
==============
|
|
||||||
Stanza Objects
|
|
||||||
==============
|
|
||||||
|
|
||||||
.. module:: sleekxmpp.xmlstream.stanzabase
|
|
||||||
|
|
||||||
The :mod:`~sleekmxpp.xmlstream.stanzabase` module provides a wrapper for the
|
|
||||||
standard :mod:`~xml.etree.ElementTree` module that makes working with XML
|
|
||||||
less painful. Instead of having to manually move up and down an element
|
|
||||||
tree and insert subelements and attributes, you can interact with an object
|
|
||||||
that behaves like a normal dictionary or JSON object, which silently maps
|
|
||||||
keys to XML attributes and elements behind the scenes.
|
|
||||||
|
|
||||||
Overview
|
|
||||||
--------
|
|
||||||
|
|
||||||
The usefulness of this layer grows as the XML you have to work with
|
|
||||||
becomes nested. The base unit here, :class:`ElementBase`, can map to a
|
|
||||||
single XML element, or several depending on how advanced of a mapping
|
|
||||||
is desired from interface keys to XML structures. For example, a single
|
|
||||||
:class:`ElementBase` derived class could easily describe:
|
|
||||||
|
|
||||||
.. code-block:: xml
|
|
||||||
|
|
||||||
<message to="user@example.com" from="friend@example.com">
|
|
||||||
<body>Hi!</body>
|
|
||||||
<x:extra>
|
|
||||||
<x:item>Custom item 1</x:item>
|
|
||||||
<x:item>Custom item 2</x:item>
|
|
||||||
<x:item>Custom item 3</x:item>
|
|
||||||
</x:extra>
|
|
||||||
</message>
|
|
||||||
|
|
||||||
If that chunk of XML were put in the :class:`ElementBase` instance
|
|
||||||
``msg``, we could extract the data from the XML using::
|
|
||||||
|
|
||||||
>>> msg['extra']
|
|
||||||
['Custom item 1', 'Custom item 2', 'Custom item 3']
|
|
||||||
|
|
||||||
Provided we set up the handler for the ``'extra'`` interface to load the
|
|
||||||
``<x:item>`` element content into a list.
|
|
||||||
|
|
||||||
The key concept is that given an XML structure that will be repeatedly
|
|
||||||
used, we can define a set of :term:`interfaces` which when we read from,
|
|
||||||
write to, or delete, will automatically manipulate the underlying XML
|
|
||||||
as needed. In addition, some of these interfaces may in turn reference
|
|
||||||
child objects which expose interfaces for particularly complex child
|
|
||||||
elements of the original XML chunk.
|
|
||||||
|
|
||||||
.. seealso::
|
|
||||||
:ref:`create-stanza-interfaces`.
|
|
||||||
|
|
||||||
Because the :mod:`~sleekxmpp.xmlstream.stanzabase` module was developed
|
|
||||||
as part of an `XMPP <http://xmpp.org>`_ library, these chunks of XML are
|
|
||||||
referred to as :term:`stanzas <stanza>`, and in SleekXMPP we refer to a
|
|
||||||
subclass of :class:`ElementBase` which defines the interfaces needed for
|
|
||||||
interacting with a given :term:`stanza` a :term:`stanza object`.
|
|
||||||
|
|
||||||
To make dealing with more complicated and nested :term:`stanzas <stanza>`
|
|
||||||
or XML chunks easier, :term:`stanza objects <stanza object>` can be
|
|
||||||
composed in two ways: as iterable child objects or as plugins. Iterable
|
|
||||||
child stanzas, or :term:`substanzas`, are accessible through a special
|
|
||||||
``'substanzas'`` interface. This option is useful for stanzas which
|
|
||||||
may contain more than one of the same kind of element. When there is
|
|
||||||
only one child element, the plugin method is more useful. For plugins,
|
|
||||||
a parent stanza object delegates one of its XML child elements to the
|
|
||||||
plugin stanza object. Here is an example:
|
|
||||||
|
|
||||||
.. code-block:: xml
|
|
||||||
|
|
||||||
<iq type="result">
|
|
||||||
<query xmlns="http://jabber.org/protocol/disco#info">
|
|
||||||
<identity category="client" type="bot" name="SleekXMPP Bot" />
|
|
||||||
</query>
|
|
||||||
</iq>
|
|
||||||
|
|
||||||
We can can arrange this stanza into two objects: an outer, wrapper object for
|
|
||||||
dealing with the ``<iq />`` element and its attributes, and a plugin object to
|
|
||||||
control the ``<query />`` payload element. If we give the plugin object the
|
|
||||||
name ``'disco_info'`` (using its :attr:`ElementBase.plugin_attrib` value), then
|
|
||||||
we can access the plugin as so::
|
|
||||||
|
|
||||||
>>> iq['disco_info']
|
|
||||||
'<query xmlns="http://jabber.org/protocol/disco#info">
|
|
||||||
<identity category="client" type="bot" name="SleekXMPP Bot" />
|
|
||||||
</query>'
|
|
||||||
|
|
||||||
We can then drill down through the plugin object's interfaces as desired::
|
|
||||||
|
|
||||||
>>> iq['disco_info']['identities']
|
|
||||||
[('client', 'bot', 'SleekXMPP Bot')]
|
|
||||||
|
|
||||||
Plugins may also add new interfaces to the parent stanza object as if they
|
|
||||||
had been defined by the parent directly, and can also override the behaviour
|
|
||||||
of an interface defined by the parent.
|
|
||||||
|
|
||||||
.. seealso::
|
|
||||||
|
|
||||||
- :ref:`create-stanza-plugins`
|
|
||||||
- :ref:`create-extension-plugins`
|
|
||||||
- :ref:`override-parent-interfaces`
|
|
||||||
|
|
||||||
|
|
||||||
Registering Stanza Plugins
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
.. autofunction:: register_stanza_plugin
|
|
||||||
|
|
||||||
ElementBase
|
|
||||||
-----------
|
|
||||||
|
|
||||||
.. autoclass:: ElementBase
|
|
||||||
:members:
|
|
||||||
:private-members:
|
|
||||||
:special-members:
|
|
||||||
|
|
||||||
StanzaBase
|
|
||||||
----------
|
|
||||||
|
|
||||||
.. autoclass:: StanzaBase
|
|
||||||
:members:
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
.. module:: sleekxmpp.xmlstream.tostring
|
|
||||||
|
|
||||||
.. _tostring:
|
|
||||||
|
|
||||||
XML Serialization
|
|
||||||
=================
|
|
||||||
|
|
||||||
Since the XML layer of SleekXMPP is based on :mod:`~xml.etree.ElementTree`,
|
|
||||||
why not just use the built-in :func:`~xml.etree.ElementTree.tostring`
|
|
||||||
method? The answer is that using that method produces ugly results when
|
|
||||||
using namespaces. The :func:`tostring()` method used here intelligently
|
|
||||||
hides namespaces when able and does not introduce excessive namespace
|
|
||||||
prefixes::
|
|
||||||
|
|
||||||
>>> from sleekxmpp.xmlstream.tostring import tostring
|
|
||||||
>>> from xml.etree import cElementTree as ET
|
|
||||||
>>> xml = ET.fromstring('<foo xmlns="bar"><baz /></foo>')
|
|
||||||
>>> ET.tostring(xml)
|
|
||||||
'<ns0:foo xmlns:ns0="bar"><ns0:baz /></foo>'
|
|
||||||
>>> tostring(xml)
|
|
||||||
'<foo xmlns="bar"><baz /></foo>'
|
|
||||||
|
|
||||||
As a side effect of this namespace hiding, using :func:`tostring()` may
|
|
||||||
produce unexpected results depending on how the :func:`tostring()` method
|
|
||||||
is invoked. For example, when sending XML on the wire, the main XMPP
|
|
||||||
stanzas with their namespace of ``jabber:client`` will not include the
|
|
||||||
namespace because that is already declared by the stream header. But, if
|
|
||||||
you create a :class:`~sleekxmpp.stanza.message.Message` instance and dump
|
|
||||||
it to the terminal, the ``jabber:client`` namespace will appear.
|
|
||||||
|
|
||||||
.. autofunction:: tostring
|
|
||||||
|
|
||||||
Escaping Special Characters
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
In order to prevent errors when sending arbitrary text as the textual
|
|
||||||
content of an XML element, certain characters must be escaped. These
|
|
||||||
are: ``&``, ``<``, ``>``, ``"``, and ``'``. The default escaping
|
|
||||||
mechanism is to replace those characters with their equivalent escape
|
|
||||||
entities: ``&``, ``<``, ``>``, ``'``, and ``"``.
|
|
||||||
|
|
||||||
In the future, the use of CDATA sections may be allowed to reduce the
|
|
||||||
size of escaped text or for when other XMPP processing agents do not
|
|
||||||
undertand these entities.
|
|
||||||
|
|
||||||
.. autofunction:: xml_escape
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
==========
|
|
||||||
XML Stream
|
|
||||||
==========
|
|
||||||
|
|
||||||
.. module:: sleekxmpp.xmlstream.xmlstream
|
|
||||||
|
|
||||||
.. autoexception:: RestartStream
|
|
||||||
|
|
||||||
.. autoclass:: XMLStream
|
|
||||||
:members:
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
.. index:: XMLStream, BaseXMPP, ClientXMPP, ComponentXMPP
|
|
||||||
|
|
||||||
SleekXMPP Architecture
|
|
||||||
======================
|
|
||||||
|
|
||||||
The core of SleekXMPP is contained in four classes: ``XMLStream``,
|
|
||||||
``BaseXMPP``, ``ClientXMPP``, and ``ComponentXMPP``. Along side this
|
|
||||||
stack is a library for working with XML objects that eliminates most
|
|
||||||
of the tedium of creating/manipulating XML.
|
|
||||||
|
|
||||||
.. image:: _static/images/arch_layers.png
|
|
||||||
:height: 300px
|
|
||||||
:align: center
|
|
||||||
|
|
||||||
|
|
||||||
.. index:: XMLStream
|
|
||||||
|
|
||||||
The Foundation: XMLStream
|
|
||||||
-------------------------
|
|
||||||
:class:`~sleekxmpp.xmlstream.xmlstream.XMLStream` is a mostly XMPP-agnostic
|
|
||||||
class whose purpose is to read and write from a bi-directional XML stream.
|
|
||||||
It also allows for callback functions to execute when XML matching given
|
|
||||||
patterns is received; these callbacks are also referred to as :term:`stream
|
|
||||||
handlers <stream handler>`. The class also provides a basic eventing system
|
|
||||||
which can be triggered either manually or on a timed schedule.
|
|
||||||
|
|
||||||
The Main Threads
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
:class:`~sleekxmpp.xmlstream.xmlstream.XMLStream` instances run using at
|
|
||||||
least three background threads: the send thread, the read thread, and the
|
|
||||||
scheduler thread. The send thread is in charge of monitoring the send queue
|
|
||||||
and writing text to the outgoing XML stream. The read thread pulls text off
|
|
||||||
of the incoming XML stream and stores the results in an event queue. The
|
|
||||||
scheduler thread is used to emit events after a given period of time.
|
|
||||||
|
|
||||||
Additionally, the main event processing loop may be executed in its
|
|
||||||
own thread if SleekXMPP is being used in the background for another
|
|
||||||
application.
|
|
||||||
|
|
||||||
Short-lived threads may also be spawned as requested for threaded
|
|
||||||
:term:`event handlers <event handler>`.
|
|
||||||
|
|
||||||
How XML Text is Turned into Action
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
To demonstrate the flow of information, let's consider what happens
|
|
||||||
when this bit of XML is received (with an assumed namespace of
|
|
||||||
``jabber:client``):
|
|
||||||
|
|
||||||
.. code-block:: xml
|
|
||||||
|
|
||||||
<message to="user@example.com" from="friend@example.net">
|
|
||||||
<body>Hej!</body>
|
|
||||||
</message>
|
|
||||||
|
|
||||||
|
|
||||||
1. **Convert XML strings into objects.**
|
|
||||||
|
|
||||||
Incoming text is parsed and converted into XML objects (using
|
|
||||||
ElementTree) which are then wrapped into what are referred to as
|
|
||||||
:term:`Stanza objects <stanza object>`. The appropriate class for the
|
|
||||||
new object is determined using a map of namespaced element names to
|
|
||||||
classes.
|
|
||||||
|
|
||||||
Our incoming XML is thus turned into a :class:`~sleekxmpp.stanza.Message`
|
|
||||||
:term:`stanza object` because the namespaced element name
|
|
||||||
``{jabber:client}message`` is associated with the class
|
|
||||||
:class:`~sleekxmpp.stanza.Message`.
|
|
||||||
|
|
||||||
2. **Match stanza objects to callbacks.**
|
|
||||||
|
|
||||||
These objects are then compared against the stored patterns associated
|
|
||||||
with the registered callback handlers. For each match, a copy of the
|
|
||||||
:term:`stanza object` is paired with a reference to the handler and
|
|
||||||
placed into the event queue.
|
|
||||||
|
|
||||||
Our :class:`~sleekxmpp.stanza.Message` object is thus paired with the message stanza handler
|
|
||||||
:meth:`BaseXMPP._handle_message` to create the tuple::
|
|
||||||
|
|
||||||
('stanza', stanza_obj, handler)
|
|
||||||
|
|
||||||
3. **Process the event queue.**
|
|
||||||
|
|
||||||
The event queue is the heart of SleekXMPP. Nearly every action that
|
|
||||||
takes place is first inserted into this queue, whether that be received
|
|
||||||
stanzas, custom events, or scheduled events.
|
|
||||||
|
|
||||||
When the stanza is pulled out of the event queue with an associated
|
|
||||||
callback, the callback function is executed with the stanza as its only
|
|
||||||
parameter.
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
The callback, aka :term:`stream handler`, is executed in the main event
|
|
||||||
processing thread. If the handler blocks, event processing will also
|
|
||||||
block.
|
|
||||||
|
|
||||||
4. **Raise Custom Events**
|
|
||||||
|
|
||||||
Since a :term:`stream handler` shouldn't block, if extensive processing
|
|
||||||
for a stanza is required (such as needing to send and receive an
|
|
||||||
:class:`~sleekxmpp.stanza.Iq` stanza), then custom events must be used.
|
|
||||||
These events are not explicitly tied to the incoming XML stream and may
|
|
||||||
be raised at any time. Importantly, these events may be handled in their
|
|
||||||
own thread.
|
|
||||||
|
|
||||||
When the event is raised, a copy of the stanza is created for each
|
|
||||||
handler registered for the event. In contrast to :term:`stream handlers
|
|
||||||
<stream handler>`, these functions are referred to as :term:`event
|
|
||||||
handlers <event handler>`. Each stanza/handler pair is then put into the
|
|
||||||
event queue.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
It is possible to skip the event queue and process an event immediately
|
|
||||||
by using ``direct=True`` when raising the event.
|
|
||||||
|
|
||||||
The code for :meth:`BaseXMPP._handle_message` follows this pattern, and
|
|
||||||
raises a ``'message'`` event::
|
|
||||||
|
|
||||||
self.event('message', msg)
|
|
||||||
|
|
||||||
The event call then places the message object back into the event queue
|
|
||||||
paired with an :term:`event handler`::
|
|
||||||
|
|
||||||
('event', 'message', msg_copy1, custom_event_handler_1)
|
|
||||||
('event', 'message', msg_copy2, custom_evetn_handler_2)
|
|
||||||
|
|
||||||
5. **Process Custom Events**
|
|
||||||
|
|
||||||
The stanza and :term:`event handler` are then pulled from the event
|
|
||||||
queue, and the handler is executed, passing the stanza as its only
|
|
||||||
argument. If the handler was registered as threaded, then a new thread
|
|
||||||
will be spawned for it.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
Events may be raised without needing :term:`stanza objects <stanza object>`.
|
|
||||||
For example, you could use ``self.event('custom', {'a': 'b'})``.
|
|
||||||
You don't even need any arguments: ``self.event('no_parameters')``.
|
|
||||||
However, every event handler MUST accept at least one argument.
|
|
||||||
|
|
||||||
Finally, after a long trek, our message is handed off to the user's
|
|
||||||
custom handler in order to do awesome stuff::
|
|
||||||
|
|
||||||
msg.reply()
|
|
||||||
msg['body'] = "Hey! This is awesome!"
|
|
||||||
msg.send()
|
|
||||||
|
|
||||||
|
|
||||||
.. index:: BaseXMPP, XMLStream
|
|
||||||
|
|
||||||
Raising XMPP Awareness: BaseXMPP
|
|
||||||
--------------------------------
|
|
||||||
While :class:`~sleekxmpp.xmlstream.xmlstream.XMLStream` attempts to shy away
|
|
||||||
from anything too XMPP specific, :class:`~sleekxmpp.basexmpp.BaseXMPP`'s
|
|
||||||
sole purpose is to provide foundational support for sending and receiving
|
|
||||||
XMPP stanzas. This support includes registering the basic message,
|
|
||||||
presence, and iq stanzas, methods for creating and sending stanzas, and
|
|
||||||
default handlers for incoming messages and keeping track of presence
|
|
||||||
notifications.
|
|
||||||
|
|
||||||
The plugin system for adding new XEP support is also maintained by
|
|
||||||
:class:`~sleekxmpp.basexmpp.BaseXMPP`.
|
|
||||||
|
|
||||||
.. index:: ClientXMPP, BaseXMPP
|
|
||||||
|
|
||||||
ClientXMPP
|
|
||||||
----------
|
|
||||||
:class:`~sleekxmpp.clientxmpp.ClientXMPP` extends
|
|
||||||
:class:`~sleekxmpp.clientxmpp.BaseXMPP` with additional logic for connecting
|
|
||||||
to an XMPP server by performing DNS lookups. It also adds support for stream
|
|
||||||
features such as STARTTLS and SASL.
|
|
||||||
|
|
||||||
.. index:: ComponentXMPP, BaseXMPP
|
|
||||||
|
|
||||||
ComponentXMPP
|
|
||||||
-------------
|
|
||||||
:class:`~sleekxmpp.componentxmpp.ComponentXMPP` is only a thin layer on top of
|
|
||||||
:class:`~sleekxmpp.basexmpp.BaseXMPP` that implements the component handshake
|
|
||||||
protocol.
|
|
||||||
222
docs/conf.py
222
docs/conf.py
@@ -1,222 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# SleekXMPP documentation build configuration file, created by
|
|
||||||
# sphinx-quickstart on Tue Aug 9 22:27:06 2011.
|
|
||||||
#
|
|
||||||
# This file is execfile()d with the current directory set to its containing dir.
|
|
||||||
#
|
|
||||||
# Note that not all possible configuration values are present in this
|
|
||||||
# autogenerated file.
|
|
||||||
#
|
|
||||||
# All configuration values have a default; values that are commented out
|
|
||||||
# serve to show the default.
|
|
||||||
|
|
||||||
import sys, os
|
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
|
||||||
sys.path.insert(0, os.path.abspath('..'))
|
|
||||||
|
|
||||||
# -- General configuration -----------------------------------------------------
|
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
|
||||||
#needs_sphinx = '1.0'
|
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
|
||||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
|
||||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx']
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
|
||||||
templates_path = ['_templates']
|
|
||||||
|
|
||||||
# The suffix of source filenames.
|
|
||||||
source_suffix = '.rst'
|
|
||||||
|
|
||||||
# The encoding of source files.
|
|
||||||
#source_encoding = 'utf-8-sig'
|
|
||||||
|
|
||||||
# The master toctree document.
|
|
||||||
master_doc = 'index'
|
|
||||||
|
|
||||||
# General information about the project.
|
|
||||||
project = u'SleekXMPP'
|
|
||||||
copyright = u'2011, Nathan Fritz, Lance Stout'
|
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
|
||||||
# |version| and |release|, also used in various other places throughout the
|
|
||||||
# built documents.
|
|
||||||
#
|
|
||||||
# The short X.Y version.
|
|
||||||
version = '1.0'
|
|
||||||
# The full version, including alpha/beta/rc tags.
|
|
||||||
release = '1.0RC3'
|
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
|
||||||
# for a list of supported languages.
|
|
||||||
#language = None
|
|
||||||
|
|
||||||
# There are two options for replacing |today|: either, you set today to some
|
|
||||||
# non-false value, then it is used:
|
|
||||||
#today = ''
|
|
||||||
# Else, today_fmt is used as the format for a strftime call.
|
|
||||||
#today_fmt = '%B %d, %Y'
|
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
|
||||||
# directories to ignore when looking for source files.
|
|
||||||
exclude_patterns = ['_build']
|
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
|
||||||
#default_role = None
|
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
|
||||||
#add_function_parentheses = True
|
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
|
||||||
# unit titles (such as .. function::).
|
|
||||||
#add_module_names = True
|
|
||||||
|
|
||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
|
||||||
# output. They are ignored by default.
|
|
||||||
#show_authors = False
|
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
|
||||||
pygments_style = 'tango'
|
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
|
||||||
#modindex_common_prefix = []
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output ---------------------------------------------------
|
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
|
||||||
# a list of builtin themes.
|
|
||||||
html_theme = 'nature'
|
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
|
||||||
# further. For a list of options available for each theme, see the
|
|
||||||
# documentation.
|
|
||||||
#html_theme_options = {'headingcolor': '#CFCFCF', 'linkcolor': '#4A7389'}
|
|
||||||
|
|
||||||
# 00ADEE
|
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
|
||||||
#html_theme_path = []
|
|
||||||
|
|
||||||
# The name for this set of Sphinx documents. If None, it defaults to
|
|
||||||
# "<project> v<release> documentation".
|
|
||||||
html_title = 'SleekXMPP'
|
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
|
||||||
html_short_title = '%s Documentation' % release
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
|
||||||
# of the sidebar.
|
|
||||||
#html_logo = None
|
|
||||||
|
|
||||||
# The name of an image file (within the static path) to use as favicon of the
|
|
||||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
|
||||||
# pixels large.
|
|
||||||
#html_favicon = None
|
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
|
||||||
html_static_path = ['_static']
|
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
|
||||||
# using the given strftime format.
|
|
||||||
#html_last_updated_fmt = '%b %d, %Y'
|
|
||||||
|
|
||||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
|
||||||
# typographically correct entities.
|
|
||||||
#html_use_smartypants = True
|
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
|
||||||
#html_sidebars = {}
|
|
||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names to
|
|
||||||
# template names.
|
|
||||||
html_additional_pages = {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#html_domain_indices = True
|
|
||||||
|
|
||||||
# If false, no index is generated.
|
|
||||||
#html_use_index = True
|
|
||||||
|
|
||||||
# If true, the index is split into individual pages for each letter.
|
|
||||||
#html_split_index = False
|
|
||||||
|
|
||||||
# If true, links to the reST sources are added to the pages.
|
|
||||||
#html_show_sourcelink = True
|
|
||||||
|
|
||||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
|
||||||
#html_show_sphinx = True
|
|
||||||
|
|
||||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
|
||||||
#html_show_copyright = True
|
|
||||||
|
|
||||||
# If true, an OpenSearch description file will be output, and all pages will
|
|
||||||
# contain a <link> tag referring to it. The value of this option must be the
|
|
||||||
# base URL from which the finished HTML is served.
|
|
||||||
#html_use_opensearch = ''
|
|
||||||
|
|
||||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
|
||||||
#html_file_suffix = None
|
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
|
||||||
htmlhelp_basename = 'SleekXMPPdoc'
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output --------------------------------------------------
|
|
||||||
|
|
||||||
# The paper size ('letter' or 'a4').
|
|
||||||
#latex_paper_size = 'letter'
|
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
|
||||||
#latex_font_size = '10pt'
|
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
|
||||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
|
||||||
latex_documents = [
|
|
||||||
('index', 'SleekXMPP.tex', u'SleekXMPP Documentation',
|
|
||||||
u'Nathan Fritz, Lance Stout', 'manual'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
|
||||||
# the title page.
|
|
||||||
#latex_logo = None
|
|
||||||
|
|
||||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
|
||||||
# not chapters.
|
|
||||||
#latex_use_parts = False
|
|
||||||
|
|
||||||
# If true, show page references after internal links.
|
|
||||||
#latex_show_pagerefs = False
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
|
||||||
#latex_show_urls = False
|
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
|
||||||
#latex_preamble = ''
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
#latex_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#latex_domain_indices = True
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for manual page output --------------------------------------------
|
|
||||||
|
|
||||||
# One entry per manual page. List of tuples
|
|
||||||
# (source start file, name, description, authors, manual section).
|
|
||||||
man_pages = [
|
|
||||||
('index', 'sleekxmpp', u'SleekXMPP Documentation',
|
|
||||||
[u'Nathan Fritz, Lance Stout'], 1)
|
|
||||||
]
|
|
||||||
|
|
||||||
intersphinx_mapping = {'python': ('http://docs.python.org/3.2', 'python-objects.inv')}
|
|
||||||
@@ -1,679 +0,0 @@
|
|||||||
.. _create-plugin:
|
|
||||||
|
|
||||||
Creating a SleekXMPP Plugin
|
|
||||||
===========================
|
|
||||||
|
|
||||||
One of the goals of SleekXMPP is to provide support for every draft or final
|
|
||||||
XMPP extension (`XEP <http://xmpp.org/extensions/>`_). To do this, SleekXMPP has a
|
|
||||||
plugin mechanism for adding the functionalities required by each XEP. But even
|
|
||||||
though plugins were made to quickly implement and prototype the official XMPP
|
|
||||||
extensions, there is no reason you can't create your own plugin to implement
|
|
||||||
your own custom XMPP-based protocol.
|
|
||||||
|
|
||||||
This guide will help walk you through the steps to
|
|
||||||
implement a rudimentary version of `XEP-0077 In-band
|
|
||||||
Registration <http://xmpp.org/extensions/xep-0077.html>`_. In-band registration
|
|
||||||
was implemented in example 14-6 (page 223) of `XMPP: The Definitive
|
|
||||||
Guide <http://oreilly.com/catalog/9780596521271>`_ because there was no SleekXMPP
|
|
||||||
plugin for XEP-0077 at the time of writing. We will partially fix that issue
|
|
||||||
here by turning the example implementation from *XMPP: The Definitive Guide*
|
|
||||||
into a plugin. Again, note that this will not a complete implementation, and a
|
|
||||||
different, more robust, official plugin for XEP-0077 may be added to SleekXMPP
|
|
||||||
in the future.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
The example plugin created in this guide is for the server side of the
|
|
||||||
registration process only. It will **NOT** be able to register new accounts
|
|
||||||
on an XMPP server.
|
|
||||||
|
|
||||||
First Steps
|
|
||||||
-----------
|
|
||||||
Every plugin inherits from the class :mod:`base_plugin <sleekxmpp.plugins.base.base_plugin>`,
|
|
||||||
and must include a ``plugin_init`` method. While the
|
|
||||||
plugins distributed with SleekXMPP must be placed in the plugins directory
|
|
||||||
``sleekxmpp/plugins`` to be loaded, custom plugins may be loaded from any
|
|
||||||
module. To do so, use the following form when registering the plugin:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
self.register_plugin('myplugin', module=mod_containing_my_plugin)
|
|
||||||
|
|
||||||
The plugin name must be the same as the plugin's class name.
|
|
||||||
|
|
||||||
Now, we can open our favorite text editors and create ``xep_0077.py`` in
|
|
||||||
``SleekXMPP/sleekxmpp/plugins``. We want to do some basic house-keeping and
|
|
||||||
declare the name and description of the XEP we are implementing. If you
|
|
||||||
are creating your own custom plugin, you don't need to include the ``xep``
|
|
||||||
attribute.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
"""
|
|
||||||
Creating a SleekXMPP Plugin
|
|
||||||
|
|
||||||
This is a minimal implementation of XEP-0077 to serve
|
|
||||||
as a tutorial for creating SleekXMPP plugins.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from sleekxmpp.plugins.base import base_plugin
|
|
||||||
|
|
||||||
class xep_0077(base_plugin):
|
|
||||||
"""
|
|
||||||
XEP-0077 In-Band Registration
|
|
||||||
"""
|
|
||||||
|
|
||||||
def plugin_init(self):
|
|
||||||
self.description = "In-Band Registration"
|
|
||||||
self.xep = "0077"
|
|
||||||
|
|
||||||
Now that we have a basic plugin, we need to edit
|
|
||||||
``sleekxmpp/plugins/__init__.py`` to include our new plugin by adding
|
|
||||||
``'xep_0077'`` to the ``__all__`` declaration.
|
|
||||||
|
|
||||||
Interacting with Other Plugins
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
In-band registration is a feature that should be advertised through `Service
|
|
||||||
Discovery <http://xmpp.org/extensions/xep-0030.html>`_. To do that, we tell the
|
|
||||||
``xep_0030`` plugin to add the ``"jabber:iq:register"`` feature. We put this
|
|
||||||
call in a method named ``post_init`` which will be called once the plugin has
|
|
||||||
been loaded; by doing so we advertise that we can do registrations only after we
|
|
||||||
finish activating the plugin.
|
|
||||||
|
|
||||||
The ``post_init`` method needs to call ``base_plugin.post_init(self)``
|
|
||||||
which will mark that ``post_init`` has been called for the plugin. Once the
|
|
||||||
SleekXMPP object begins processing, ``post_init`` will be called on any plugins
|
|
||||||
that have not already run ``post_init``. This allows you to register plugins and
|
|
||||||
their dependencies without needing to worry about the order in which you do so.
|
|
||||||
|
|
||||||
**Note:** by adding this call we have introduced a dependency on the XEP-0030
|
|
||||||
plugin. Be sure to register ``'xep_0030'`` as well as ``'xep_0077'``. SleekXMPP
|
|
||||||
does not automatically load plugin dependencies for you.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def post_init(self):
|
|
||||||
base_plugin.post_init(self)
|
|
||||||
self.xmpp['xep_0030'].add_feature("jabber:iq:register")
|
|
||||||
|
|
||||||
Creating Custom Stanza Objects
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
Now, the IQ stanzas needed to implement our version of XEP-0077 are not very
|
|
||||||
complex, and we could just interact with the XML objects directly just like
|
|
||||||
in the *XMPP: The Definitive Guide* example. However, creating custom stanza
|
|
||||||
objects is good practice.
|
|
||||||
|
|
||||||
We will create a new ``Registration`` stanza. Following the *XMPP: The
|
|
||||||
Definitive Guide* example, we will add support for a username and password
|
|
||||||
field. We also need two flags: ``registered`` and ``remove``. The ``registered``
|
|
||||||
flag is sent when an already registered user attempts to register, along with
|
|
||||||
their registration data. The ``remove`` flag is a request to unregister a user's
|
|
||||||
account.
|
|
||||||
|
|
||||||
Adding additional `fields specified in
|
|
||||||
XEP-0077 <http://xmpp.org/extensions/xep-0077.html#registrar-formtypes-register>`_
|
|
||||||
will not be difficult and is left as an exercise for the reader.
|
|
||||||
|
|
||||||
Our ``Registration`` class needs to start with a few descriptions of its
|
|
||||||
behaviour:
|
|
||||||
|
|
||||||
* ``namespace``
|
|
||||||
The namespace our stanza object lives in. In this case,
|
|
||||||
``"jabber:iq:register"``.
|
|
||||||
|
|
||||||
* ``name``
|
|
||||||
The name of the root XML element. In this case, the ``query`` element.
|
|
||||||
|
|
||||||
* ``plugin_attrib``
|
|
||||||
The name to access this type of stanza. In particular, given a
|
|
||||||
registration stanza, the ``Registration`` object can be found using:
|
|
||||||
``iq_object['register']``.
|
|
||||||
|
|
||||||
* ``interfaces``
|
|
||||||
A list of dictionary-like keys that can be used with the stanza object.
|
|
||||||
When using ``"key"``, if there exists a method of the form ``getKey``,
|
|
||||||
``setKey``, or``delKey`` (depending on context) then the result of calling
|
|
||||||
that method will be returned. Otherwise, the value of the attribute ``key``
|
|
||||||
of the main stanza element is returned if one exists.
|
|
||||||
|
|
||||||
**Note:** The accessor methods currently use title case, and not camel case.
|
|
||||||
Thus if you need to access an item named ``"methodName"`` you will need to
|
|
||||||
use ``getMethodname``. This naming convention might change to full camel
|
|
||||||
case in a future version of SleekXMPP.
|
|
||||||
|
|
||||||
* ``sub_interfaces``
|
|
||||||
A subset of ``interfaces``, but these keys map to the text of any
|
|
||||||
subelements that are direct children of the main stanza element. Thus,
|
|
||||||
referencing ``iq_object['register']['username']`` will either execute
|
|
||||||
``getUsername`` or return the value in the ``username`` element of the
|
|
||||||
query.
|
|
||||||
|
|
||||||
If you need to access an element, say ``elem``, that is not a direct child
|
|
||||||
of the main stanza element, you will need to add ``getElem``, ``setElem``,
|
|
||||||
and ``delElem``. See the note above about naming conventions.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from sleekxmpp.xmlstream import ElementBase, ET, JID, register_stanza_plugin
|
|
||||||
from sleekxmpp import Iq
|
|
||||||
|
|
||||||
class Registration(ElementBase):
|
|
||||||
namespace = 'jabber:iq:register'
|
|
||||||
name = 'query'
|
|
||||||
plugin_attrib = 'register'
|
|
||||||
interfaces = set(('username', 'password', 'registered', 'remove'))
|
|
||||||
sub_interfaces = interfaces
|
|
||||||
|
|
||||||
def getRegistered(self):
|
|
||||||
present = self.xml.find('{%s}registered' % self.namespace)
|
|
||||||
return present is not None
|
|
||||||
|
|
||||||
def getRemove(self):
|
|
||||||
present = self.xml.find('{%s}remove' % self.namespace)
|
|
||||||
return present is not None
|
|
||||||
|
|
||||||
def setRegistered(self, registered):
|
|
||||||
if registered:
|
|
||||||
self.addField('registered')
|
|
||||||
else:
|
|
||||||
del self['registered']
|
|
||||||
|
|
||||||
def setRemove(self, remove):
|
|
||||||
if remove:
|
|
||||||
self.addField('remove')
|
|
||||||
else:
|
|
||||||
del self['remove']
|
|
||||||
|
|
||||||
def addField(self, name):
|
|
||||||
itemXML = ET.Element('{%s}%s' % (self.namespace, name))
|
|
||||||
self.xml.append(itemXML)
|
|
||||||
|
|
||||||
Setting a ``sub_interface`` attribute to ``""`` will remove that subelement.
|
|
||||||
Since we want to include empty registration fields in our form, we need the
|
|
||||||
``addField`` method to add the empty elements.
|
|
||||||
|
|
||||||
Since the ``registered`` and ``remove`` elements are just flags, we need to add
|
|
||||||
custom logic to enforce the binary behavior.
|
|
||||||
|
|
||||||
Extracting Stanzas from the XML Stream
|
|
||||||
--------------------------------------
|
|
||||||
|
|
||||||
Now that we have a custom stanza object, we need to be able to detect when we
|
|
||||||
receive one. To do this, we register a stream handler that will pattern match
|
|
||||||
stanzas off of the XML stream against our stanza object's element name and
|
|
||||||
namespace. To do so, we need to create a ``Callback`` object which contains
|
|
||||||
an XML fragment that can identify our stanza type. We can add this handler
|
|
||||||
registration to our ``plugin_init`` method.
|
|
||||||
|
|
||||||
Also, we need to associate our ``Registration`` class with IQ stanzas;
|
|
||||||
that requires the use of the ``register_stanza_plugin`` function (in
|
|
||||||
``sleekxmpp.xmlstream.stanzabase``) which takes the class of a parent stanza
|
|
||||||
type followed by the substanza type. In our case, the parent stanza is an IQ
|
|
||||||
stanza, and the substanza is our registration query.
|
|
||||||
|
|
||||||
The ``__handleRegistration`` method referenced in the callback will be our
|
|
||||||
handler function to process registration requests.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def plugin_init(self):
|
|
||||||
self.description = "In-Band Registration"
|
|
||||||
self.xep = "0077"
|
|
||||||
|
|
||||||
self.xmpp.registerHandler(
|
|
||||||
Callback('In-Band Registration',
|
|
||||||
MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns),
|
|
||||||
self.__handleRegistration))
|
|
||||||
register_stanza_plugin(Iq, Registration)
|
|
||||||
|
|
||||||
Handling Incoming Stanzas and Triggering Events
|
|
||||||
-----------------------------------------------
|
|
||||||
There are six situations that we need to handle to finish our implementation of
|
|
||||||
XEP-0077.
|
|
||||||
|
|
||||||
**Registration Form Request from a New User:**
|
|
||||||
|
|
||||||
.. code-block:: xml
|
|
||||||
|
|
||||||
<iq type="result">
|
|
||||||
<query xmlns="jabber:iq:register">
|
|
||||||
<username />
|
|
||||||
<password />
|
|
||||||
</query>
|
|
||||||
</iq>
|
|
||||||
|
|
||||||
**Registration Form Request from an Existing User:**
|
|
||||||
|
|
||||||
.. code-block:: xml
|
|
||||||
|
|
||||||
<iq type="result">
|
|
||||||
<query xmlns="jabber:iq:register">
|
|
||||||
<registered />
|
|
||||||
<username>Foo</username>
|
|
||||||
<password>hunter2</password>
|
|
||||||
</query>
|
|
||||||
</iq>
|
|
||||||
|
|
||||||
**Unregister Account:**
|
|
||||||
|
|
||||||
.. code-block:: xml
|
|
||||||
|
|
||||||
<iq type="result">
|
|
||||||
<query xmlns="jabber:iq:register" />
|
|
||||||
</iq>
|
|
||||||
|
|
||||||
**Incomplete Registration:**
|
|
||||||
|
|
||||||
.. code-block:: xml
|
|
||||||
|
|
||||||
<iq type="error">
|
|
||||||
<query xmlns="jabber:iq:register">
|
|
||||||
<username>Foo</username>
|
|
||||||
</query>
|
|
||||||
<error code="406" type="modify">
|
|
||||||
<not-acceptable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
|
|
||||||
</error>
|
|
||||||
</iq>
|
|
||||||
|
|
||||||
**Conflicting Registrations:**
|
|
||||||
|
|
||||||
.. code-block:: xml
|
|
||||||
|
|
||||||
<iq type="error">
|
|
||||||
<query xmlns="jabber:iq:register">
|
|
||||||
<username>Foo</username>
|
|
||||||
<password>hunter2</password>
|
|
||||||
</query>
|
|
||||||
<error code="409" type="cancel">
|
|
||||||
<conflict xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
|
|
||||||
</error>
|
|
||||||
</iq>
|
|
||||||
|
|
||||||
**Successful Registration:**
|
|
||||||
|
|
||||||
.. code-block:: xml
|
|
||||||
|
|
||||||
<iq type="result">
|
|
||||||
<query xmlns="jabber:iq:register" />
|
|
||||||
</iq>
|
|
||||||
|
|
||||||
Cases 1 and 2: Registration Requests
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
Responding to registration requests depends on if the requesting user already
|
|
||||||
has an account. If there is an account, the response should include the
|
|
||||||
``registered`` flag and the user's current registration information. Otherwise,
|
|
||||||
we just send the fields for our registration form.
|
|
||||||
|
|
||||||
We will handle both cases by creating a ``sendRegistrationForm`` method that
|
|
||||||
will create either an empty of full form depending on if we provide it with
|
|
||||||
user data. Since we need to know which form fields to include (especially if we
|
|
||||||
add support for the other fields specified in XEP-0077), we will also create a
|
|
||||||
method ``setForm`` which will take the names of the fields we wish to include.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def plugin_init(self):
|
|
||||||
self.description = "In-Band Registration"
|
|
||||||
self.xep = "0077"
|
|
||||||
self.form_fields = ('username', 'password')
|
|
||||||
... remainder of plugin_init
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
def __handleRegistration(self, iq):
|
|
||||||
if iq['type'] == 'get':
|
|
||||||
# Registration form requested
|
|
||||||
userData = self.backend[iq['from'].bare]
|
|
||||||
self.sendRegistrationForm(iq, userData)
|
|
||||||
|
|
||||||
def setForm(self, *fields):
|
|
||||||
self.form_fields = fields
|
|
||||||
|
|
||||||
def sendRegistrationForm(self, iq, userData=None):
|
|
||||||
reg = iq['register']
|
|
||||||
if userData is None:
|
|
||||||
userData = {}
|
|
||||||
else:
|
|
||||||
reg['registered'] = True
|
|
||||||
|
|
||||||
for field in self.form_fields:
|
|
||||||
data = userData.get(field, '')
|
|
||||||
if data:
|
|
||||||
# Add field with existing data
|
|
||||||
reg[field] = data
|
|
||||||
else:
|
|
||||||
# Add a blank field
|
|
||||||
reg.addField(field)
|
|
||||||
|
|
||||||
iq.reply().setPayload(reg.xml)
|
|
||||||
iq.send()
|
|
||||||
|
|
||||||
Note how we are able to access our ``Registration`` stanza object with
|
|
||||||
``iq['register']``.
|
|
||||||
|
|
||||||
A User Backend
|
|
||||||
++++++++++++++
|
|
||||||
You might have noticed the reference to ``self.backend``, which is an object
|
|
||||||
that abstracts away storing and retrieving user information. Since it is not
|
|
||||||
much more than a dictionary, we will leave the implementation details to the
|
|
||||||
final, full source code example.
|
|
||||||
|
|
||||||
Case 3: Unregister an Account
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
The next simplest case to consider is responding to a request to remove
|
|
||||||
an account. If we receive a ``remove`` flag, we instruct the backend to
|
|
||||||
remove the user's account. Since your application may need to know about
|
|
||||||
when users are registered or unregistered, we trigger an event using
|
|
||||||
``self.xmpp.event('unregister_user', iq)``. See the component examples below for
|
|
||||||
how to respond to that event.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def __handleRegistration(self, iq):
|
|
||||||
if iq['type'] == 'get':
|
|
||||||
# Registration form requested
|
|
||||||
userData = self.backend[iq['from'].bare]
|
|
||||||
self.sendRegistrationForm(iq, userData)
|
|
||||||
elif iq['type'] == 'set':
|
|
||||||
# Remove an account
|
|
||||||
if iq['register']['remove']:
|
|
||||||
self.backend.unregister(iq['from'].bare)
|
|
||||||
self.xmpp.event('unregistered_user', iq)
|
|
||||||
iq.reply().send()
|
|
||||||
return
|
|
||||||
|
|
||||||
Case 4: Incomplete Registration
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
For the next case we need to check the user's registration to ensure it has all
|
|
||||||
of the fields we wanted. The simple option that we will use is to loop over the
|
|
||||||
field names and check each one; however, this means that all fields we send to
|
|
||||||
the user are required. Adding optional fields is left to the reader.
|
|
||||||
|
|
||||||
Since we have received an incomplete form, we need to send an error message back
|
|
||||||
to the user. We have to send a few different types of errors, so we will also
|
|
||||||
create a ``_sendError`` method that will add the appropriate ``error`` element
|
|
||||||
to the IQ reply.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def __handleRegistration(self, iq):
|
|
||||||
if iq['type'] == 'get':
|
|
||||||
# Registration form requested
|
|
||||||
userData = self.backend[iq['from'].bare]
|
|
||||||
self.sendRegistrationForm(iq, userData)
|
|
||||||
elif iq['type'] == 'set':
|
|
||||||
if iq['register']['remove']:
|
|
||||||
# Remove an account
|
|
||||||
self.backend.unregister(iq['from'].bare)
|
|
||||||
self.xmpp.event('unregistered_user', iq)
|
|
||||||
iq.reply().send()
|
|
||||||
return
|
|
||||||
|
|
||||||
for field in self.form_fields:
|
|
||||||
if not iq['register'][field]:
|
|
||||||
# Incomplete Registration
|
|
||||||
self._sendError(iq, '406', 'modify', 'not-acceptable'
|
|
||||||
"Please fill in all fields.")
|
|
||||||
return
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
def _sendError(self, iq, code, error_type, name, text=''):
|
|
||||||
iq.reply().setPayload(iq['register'].xml)
|
|
||||||
iq.error()
|
|
||||||
iq['error']['code'] = code
|
|
||||||
iq['error']['type'] = error_type
|
|
||||||
iq['error']['condition'] = name
|
|
||||||
iq['error']['text'] = text
|
|
||||||
iq.send()
|
|
||||||
|
|
||||||
Cases 5 and 6: Conflicting and Successful Registration
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
We are down to the final decision on if we have a successful registration. We
|
|
||||||
send the user's data to the backend with the ``self.backend.register`` method.
|
|
||||||
If it returns ``True``, then registration has been successful. Otherwise,
|
|
||||||
there has been a conflict with usernames and registration has failed. Like
|
|
||||||
with unregistering an account, we trigger an event indicating that a user has
|
|
||||||
been registered by using ``self.xmpp.event('registered_user', iq)``. See the
|
|
||||||
component examples below for how to respond to this event.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def __handleRegistration(self, iq):
|
|
||||||
if iq['type'] == 'get':
|
|
||||||
# Registration form requested
|
|
||||||
userData = self.backend[iq['from'].bare]
|
|
||||||
self.sendRegistrationForm(iq, userData)
|
|
||||||
elif iq['type'] == 'set':
|
|
||||||
if iq['register']['remove']:
|
|
||||||
# Remove an account
|
|
||||||
self.backend.unregister(iq['from'].bare)
|
|
||||||
self.xmpp.event('unregistered_user', iq)
|
|
||||||
iq.reply().send()
|
|
||||||
return
|
|
||||||
|
|
||||||
for field in self.form_fields:
|
|
||||||
if not iq['register'][field]:
|
|
||||||
# Incomplete Registration
|
|
||||||
self._sendError(iq, '406', 'modify', 'not-acceptable',
|
|
||||||
"Please fill in all fields.")
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.backend.register(iq['from'].bare, iq['register']):
|
|
||||||
# Successful registration
|
|
||||||
self.xmpp.event('registered_user', iq)
|
|
||||||
iq.reply().setPayload(iq['register'].xml)
|
|
||||||
iq.send()
|
|
||||||
else:
|
|
||||||
# Conflicting registration
|
|
||||||
self._sendError(iq, '409', 'cancel', 'conflict',
|
|
||||||
"That username is already taken.")
|
|
||||||
|
|
||||||
Example Component Using the XEP-0077 Plugin
|
|
||||||
-------------------------------------------
|
|
||||||
Alright, the moment we've been working towards - actually using our plugin to
|
|
||||||
simplify our other applications. Here is a basic component that simply manages
|
|
||||||
user registrations and sends the user a welcoming message when they register,
|
|
||||||
and a farewell message when they delete their account.
|
|
||||||
|
|
||||||
Note that we have to register the ``'xep_0030'`` plugin first,
|
|
||||||
and that we specified the form fields we wish to use with
|
|
||||||
``self.xmpp.plugin['xep_0077'].setForm('username', 'password')``.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
import sleekxmpp.componentxmpp
|
|
||||||
|
|
||||||
class Example(sleekxmpp.componentxmpp.ComponentXMPP):
|
|
||||||
|
|
||||||
def __init__(self, jid, password):
|
|
||||||
sleekxmpp.componentxmpp.ComponentXMPP.__init__(self, jid, password, 'localhost', 8888)
|
|
||||||
|
|
||||||
self.registerPlugin('xep_0030')
|
|
||||||
self.registerPlugin('xep_0077')
|
|
||||||
self.plugin['xep_0077'].setForm('username', 'password')
|
|
||||||
|
|
||||||
self.add_event_handler("registered_user", self.reg)
|
|
||||||
self.add_event_handler("unregistered_user", self.unreg)
|
|
||||||
|
|
||||||
def reg(self, iq):
|
|
||||||
msg = "Welcome! %s" % iq['register']['username']
|
|
||||||
self.sendMessage(iq['from'], msg, mfrom=self.fulljid)
|
|
||||||
|
|
||||||
def unreg(self, iq):
|
|
||||||
msg = "Bye! %s" % iq['register']['username']
|
|
||||||
self.sendMessage(iq['from'], msg, mfrom=self.fulljid)
|
|
||||||
|
|
||||||
**Congratulations!** We now have a basic, functioning implementation of
|
|
||||||
XEP-0077.
|
|
||||||
|
|
||||||
Complete Source Code for XEP-0077 Plugin
|
|
||||||
----------------------------------------
|
|
||||||
Here is a copy of a more complete implementation of the plugin we created, but
|
|
||||||
with some additional registration fields implemented.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
"""
|
|
||||||
Creating a SleekXMPP Plugin
|
|
||||||
|
|
||||||
This is a minimal implementation of XEP-0077 to serve
|
|
||||||
as a tutorial for creating SleekXMPP plugins.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from sleekxmpp.plugins.base import base_plugin
|
|
||||||
from sleekxmpp.xmlstream.handler.callback import Callback
|
|
||||||
from sleekxmpp.xmlstream.matcher.xpath import MatchXPath
|
|
||||||
from sleekxmpp.xmlstream import ElementBase, ET, JID, register_stanza_plugin
|
|
||||||
from sleekxmpp import Iq
|
|
||||||
import copy
|
|
||||||
|
|
||||||
|
|
||||||
class Registration(ElementBase):
|
|
||||||
namespace = 'jabber:iq:register'
|
|
||||||
name = 'query'
|
|
||||||
plugin_attrib = 'register'
|
|
||||||
interfaces = set(('username', 'password', 'email', 'nick', 'name',
|
|
||||||
'first', 'last', 'address', 'city', 'state', 'zip',
|
|
||||||
'phone', 'url', 'date', 'misc', 'text', 'key',
|
|
||||||
'registered', 'remove', 'instructions'))
|
|
||||||
sub_interfaces = interfaces
|
|
||||||
|
|
||||||
def getRegistered(self):
|
|
||||||
present = self.xml.find('{%s}registered' % self.namespace)
|
|
||||||
return present is not None
|
|
||||||
|
|
||||||
def getRemove(self):
|
|
||||||
present = self.xml.find('{%s}remove' % self.namespace)
|
|
||||||
return present is not None
|
|
||||||
|
|
||||||
def setRegistered(self, registered):
|
|
||||||
if registered:
|
|
||||||
self.addField('registered')
|
|
||||||
else:
|
|
||||||
del self['registered']
|
|
||||||
|
|
||||||
def setRemove(self, remove):
|
|
||||||
if remove:
|
|
||||||
self.addField('remove')
|
|
||||||
else:
|
|
||||||
del self['remove']
|
|
||||||
|
|
||||||
def addField(self, name):
|
|
||||||
itemXML = ET.Element('{%s}%s' % (self.namespace, name))
|
|
||||||
self.xml.append(itemXML)
|
|
||||||
|
|
||||||
|
|
||||||
class UserStore(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.users = {}
|
|
||||||
|
|
||||||
def __getitem__(self, jid):
|
|
||||||
return self.users.get(jid, None)
|
|
||||||
|
|
||||||
def register(self, jid, registration):
|
|
||||||
username = registration['username']
|
|
||||||
|
|
||||||
def filter_usernames(user):
|
|
||||||
return user != jid and self.users[user]['username'] == username
|
|
||||||
|
|
||||||
conflicts = filter(filter_usernames, self.users.keys())
|
|
||||||
if conflicts:
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.users[jid] = registration
|
|
||||||
return True
|
|
||||||
|
|
||||||
def unregister(self, jid):
|
|
||||||
del self.users[jid]
|
|
||||||
|
|
||||||
class xep_0077(base_plugin):
|
|
||||||
"""
|
|
||||||
XEP-0077 In-Band Registration
|
|
||||||
"""
|
|
||||||
|
|
||||||
def plugin_init(self):
|
|
||||||
self.description = "In-Band Registration"
|
|
||||||
self.xep = "0077"
|
|
||||||
self.form_fields = ('username', 'password')
|
|
||||||
self.form_instructions = ""
|
|
||||||
self.backend = UserStore()
|
|
||||||
|
|
||||||
self.xmpp.registerHandler(
|
|
||||||
Callback('In-Band Registration',
|
|
||||||
MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns),
|
|
||||||
self.__handleRegistration))
|
|
||||||
register_stanza_plugin(Iq, Registration)
|
|
||||||
|
|
||||||
def post_init(self):
|
|
||||||
base_plugin.post_init(self)
|
|
||||||
self.xmpp['xep_0030'].add_feature("jabber:iq:register")
|
|
||||||
|
|
||||||
def __handleRegistration(self, iq):
|
|
||||||
if iq['type'] == 'get':
|
|
||||||
# Registration form requested
|
|
||||||
userData = self.backend[iq['from'].bare]
|
|
||||||
self.sendRegistrationForm(iq, userData)
|
|
||||||
elif iq['type'] == 'set':
|
|
||||||
if iq['register']['remove']:
|
|
||||||
# Remove an account
|
|
||||||
self.backend.unregister(iq['from'].bare)
|
|
||||||
self.xmpp.event('unregistered_user', iq)
|
|
||||||
iq.reply().send()
|
|
||||||
return
|
|
||||||
|
|
||||||
for field in self.form_fields:
|
|
||||||
if not iq['register'][field]:
|
|
||||||
# Incomplete Registration
|
|
||||||
self._sendError(iq, '406', 'modify', 'not-acceptable',
|
|
||||||
"Please fill in all fields.")
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.backend.register(iq['from'].bare, iq['register']):
|
|
||||||
# Successful registration
|
|
||||||
self.xmpp.event('registered_user', iq)
|
|
||||||
iq.reply().setPayload(iq['register'].xml)
|
|
||||||
iq.send()
|
|
||||||
else:
|
|
||||||
# Conflicting registration
|
|
||||||
self._sendError(iq, '409', 'cancel', 'conflict',
|
|
||||||
"That username is already taken.")
|
|
||||||
|
|
||||||
def setForm(self, *fields):
|
|
||||||
self.form_fields = fields
|
|
||||||
|
|
||||||
def setInstructions(self, instructions):
|
|
||||||
self.form_instructions = instructions
|
|
||||||
|
|
||||||
def sendRegistrationForm(self, iq, userData=None):
|
|
||||||
reg = iq['register']
|
|
||||||
if userData is None:
|
|
||||||
userData = {}
|
|
||||||
else:
|
|
||||||
reg['registered'] = True
|
|
||||||
|
|
||||||
if self.form_instructions:
|
|
||||||
reg['instructions'] = self.form_instructions
|
|
||||||
|
|
||||||
for field in self.form_fields:
|
|
||||||
data = userData.get(field, '')
|
|
||||||
if data:
|
|
||||||
# Add field with existing data
|
|
||||||
reg[field] = data
|
|
||||||
else:
|
|
||||||
# Add a blank field
|
|
||||||
reg.addField(field)
|
|
||||||
|
|
||||||
iq.reply().setPayload(reg.xml)
|
|
||||||
iq.send()
|
|
||||||
|
|
||||||
def _sendError(self, iq, code, error_type, name, text=''):
|
|
||||||
iq.reply().setPayload(iq['register'].xml)
|
|
||||||
iq.error()
|
|
||||||
iq['error']['code'] = code
|
|
||||||
iq['error']['type'] = error_type
|
|
||||||
iq['error']['condition'] = name
|
|
||||||
iq['error']['text'] = text
|
|
||||||
iq.send()
|
|
||||||
@@ -1,271 +0,0 @@
|
|||||||
Event Index
|
|
||||||
===========
|
|
||||||
|
|
||||||
.. glossary::
|
|
||||||
:sorted:
|
|
||||||
|
|
||||||
connected
|
|
||||||
- **Data:** ``{}``
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.clientxmpp.ClientXMPP`
|
|
||||||
|
|
||||||
Signal that a connection has been made with the XMPP server, but a session
|
|
||||||
has not yet been established.
|
|
||||||
|
|
||||||
changed_status
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.Presence`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
|
|
||||||
|
|
||||||
Triggered when a presence stanza is received from a JID with a show type
|
|
||||||
different than the last presence stanza from the same JID.
|
|
||||||
|
|
||||||
changed_subscription
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.Presence`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
|
|
||||||
|
|
||||||
Triggered whenever a presence stanza with a type of ``subscribe``,
|
|
||||||
``subscribed``, ``unsubscribe``, or ``unsubscribed`` is received.
|
|
||||||
|
|
||||||
Note that if the values ``xmpp.auto_authorize`` and ``xmpp.auto_subscribe``
|
|
||||||
are set to ``True`` or ``False``, and not ``None``, then SleekXMPP will
|
|
||||||
either accept or reject all subscription requests before your event handlers
|
|
||||||
are called. Set these values to ``None`` if you wish to make more complex
|
|
||||||
subscription decisions.
|
|
||||||
|
|
||||||
chatstate_active
|
|
||||||
- **Data:**
|
|
||||||
- **Source:**
|
|
||||||
|
|
||||||
chatstate_composing
|
|
||||||
- **Data:**
|
|
||||||
- **Source:**
|
|
||||||
|
|
||||||
chatstate_gone
|
|
||||||
- **Data:**
|
|
||||||
- **Source:**
|
|
||||||
|
|
||||||
chatstate_inactive
|
|
||||||
- **Data:**
|
|
||||||
- **Source:**
|
|
||||||
|
|
||||||
chatstate_paused
|
|
||||||
- **Data:**
|
|
||||||
- **Source:**
|
|
||||||
|
|
||||||
disco_info
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0030.stanza.DiscoInfo`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0030.disco.xep_0030`
|
|
||||||
|
|
||||||
Triggered whenever a ``disco#info`` result stanza is received.
|
|
||||||
|
|
||||||
disco_items
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0030.stanza.DiscoItems`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0030.disco.xep_0030`
|
|
||||||
|
|
||||||
Triggered whenever a ``disco#items`` result stanza is received.
|
|
||||||
|
|
||||||
disconnected
|
|
||||||
- **Data:** ``{}``
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.ClientXMPP`
|
|
||||||
|
|
||||||
Signal that the connection with the XMPP server has been lost.
|
|
||||||
|
|
||||||
entity_time
|
|
||||||
- **Data:**
|
|
||||||
- **Source:**
|
|
||||||
|
|
||||||
failed_auth
|
|
||||||
- **Data:** ``{}``
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.ClientXMPP`, :py:class:`~sleekxmpp.plugins.xep_0078.xep_0078`
|
|
||||||
|
|
||||||
Signal that the server has rejected the provided login credentials.
|
|
||||||
|
|
||||||
gmail_notify
|
|
||||||
- **Data:** ``{}``
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.plugins.gmail_notify.gmail_notify`
|
|
||||||
|
|
||||||
Signal that there are unread emails for the Gmail account associated with the current XMPP account.
|
|
||||||
|
|
||||||
gmail_messages
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.Iq`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.plugins.gmail_notify.gmail_notify`
|
|
||||||
|
|
||||||
Signal that there are unread emails for the Gmail account associated with the current XMPP account.
|
|
||||||
|
|
||||||
got_online
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.Presence`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
|
|
||||||
|
|
||||||
If a presence stanza is received from a JID which was previously marked as
|
|
||||||
offline, and the presence has a show type of '``chat``', '``dnd``', '``away``',
|
|
||||||
or '``xa``', then this event is triggered as well.
|
|
||||||
|
|
||||||
got_offline
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.Presence`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
|
|
||||||
|
|
||||||
Signal that an unavailable presence stanza has been received from a JID.
|
|
||||||
|
|
||||||
groupchat_invite
|
|
||||||
- **Data:**
|
|
||||||
- **Source:**
|
|
||||||
|
|
||||||
groupchat_direct_invite
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.Message`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0249.direct`
|
|
||||||
|
|
||||||
groupchat_message
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.Message`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0045.xep_0045`
|
|
||||||
|
|
||||||
Triggered whenever a message is received from a multi-user chat room.
|
|
||||||
|
|
||||||
groupchat_presence
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.Presence`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0045.xep_0045`
|
|
||||||
|
|
||||||
Triggered whenever a presence stanza is received from a user in a multi-user chat room.
|
|
||||||
|
|
||||||
groupchat_subject
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.Message`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0045.xep_0045`
|
|
||||||
|
|
||||||
Triggered whenever the subject of a multi-user chat room is changed, or announced when joining a room.
|
|
||||||
|
|
||||||
killed
|
|
||||||
- **Data:**
|
|
||||||
- **Source:**
|
|
||||||
|
|
||||||
last_activity
|
|
||||||
- **Data:**
|
|
||||||
- **Source:**
|
|
||||||
|
|
||||||
message
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.Message`
|
|
||||||
- **Source:** :py:class:`BaseXMPP <sleekxmpp.BaseXMPP>`
|
|
||||||
|
|
||||||
Makes the contents of message stanzas available whenever one is received. Be
|
|
||||||
sure to check the message type in order to handle error messages.
|
|
||||||
|
|
||||||
message_form
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
|
|
||||||
|
|
||||||
Currently the same as :term:`message_xform`.
|
|
||||||
|
|
||||||
message_xform
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
|
|
||||||
|
|
||||||
Triggered whenever a data form is received inside a message.
|
|
||||||
|
|
||||||
mucc::[room]::got_offline
|
|
||||||
- **Data:**
|
|
||||||
- **Source:**
|
|
||||||
|
|
||||||
muc::[room]::got_online
|
|
||||||
- **Data:**
|
|
||||||
- **Source:**
|
|
||||||
|
|
||||||
muc::[room]::message
|
|
||||||
- **Data:**
|
|
||||||
- **Source:**
|
|
||||||
|
|
||||||
muc::[room]::presence
|
|
||||||
- **Data:**
|
|
||||||
- **Source:**
|
|
||||||
|
|
||||||
presence_available
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.Presence`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
|
|
||||||
|
|
||||||
A presence stanza with a type of '``available``' is received.
|
|
||||||
|
|
||||||
presence_error
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.Presence`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
|
|
||||||
|
|
||||||
A presence stanza with a type of '``error``' is received.
|
|
||||||
|
|
||||||
presence_form
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
|
|
||||||
|
|
||||||
This event is present in the XEP-0004 plugin code, but is currently not used.
|
|
||||||
|
|
||||||
presence_probe
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.Presence`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
|
|
||||||
|
|
||||||
A presence stanza with a type of '``probe``' is received.
|
|
||||||
|
|
||||||
presence_subscribe
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.Presence`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
|
|
||||||
|
|
||||||
A presence stanza with a type of '``subscribe``' is received.
|
|
||||||
|
|
||||||
presence_subscribed
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.Presence`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
|
|
||||||
|
|
||||||
A presence stanza with a type of '``subscribed``' is received.
|
|
||||||
|
|
||||||
presence_unavailable
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.Presence`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
|
|
||||||
|
|
||||||
A presence stanza with a type of '``unavailable``' is received.
|
|
||||||
|
|
||||||
presence_unsubscribe
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.Presence`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
|
|
||||||
|
|
||||||
A presence stanza with a type of '``unsubscribe``' is received.
|
|
||||||
|
|
||||||
presence_unsubscribed
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.Presence`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
|
|
||||||
|
|
||||||
A presence stanza with a type of '``unsubscribed``' is received.
|
|
||||||
|
|
||||||
roster_update
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.stanza.Roster`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.ClientXMPP`
|
|
||||||
|
|
||||||
An IQ result containing roster entries is received.
|
|
||||||
|
|
||||||
sent_presence
|
|
||||||
- **Data:** ``{}``
|
|
||||||
- **Source:** :py:class:`BaseXMPP <sleekxmpp.BaseXMPP>`
|
|
||||||
|
|
||||||
Signal that an initial presence stanza has been written to the XML stream.
|
|
||||||
|
|
||||||
session_end
|
|
||||||
- **Data:** ``{}``
|
|
||||||
- **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`,
|
|
||||||
:py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>`
|
|
||||||
:py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>`
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Plugins that maintain session-based state should clear themselves when
|
|
||||||
this event is fired.
|
|
||||||
|
|
||||||
session_start
|
|
||||||
- **Data:** ``{}``
|
|
||||||
- **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`,
|
|
||||||
:py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>`
|
|
||||||
:py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>`
|
|
||||||
|
|
||||||
Signal that a connection to the XMPP server has been made and a session has been established.
|
|
||||||
|
|
||||||
socket_error
|
|
||||||
- **Data:** ``Socket`` exception object
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
|
|
||||||
|
|
||||||
stream_error
|
|
||||||
- **Data:** :py:class:`~sleekxmpp.stanza.StreamError`
|
|
||||||
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
How to Use Stream Features
|
|
||||||
==========================
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
.. _echocomponent:
|
|
||||||
|
|
||||||
=================================
|
|
||||||
Create and Run a Server Component
|
|
||||||
=================================
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
If you have any issues working through this quickstart guide
|
|
||||||
or the other tutorials here, please either send a message to the
|
|
||||||
`mailing list <http://groups.google.com/group/sleekxmpp-discussion>`_
|
|
||||||
or join the chat room at `sleek@conference.jabber.org
|
|
||||||
<xmpp:sleek@conference.jabber.org?join>`_.
|
|
||||||
|
|
||||||
If you have not yet installed SleekXMPP, do so now by either checking out a version
|
|
||||||
from `Github <http://github.com/fritzy/SleekXMPP>`_, or installing it using ``pip``
|
|
||||||
or ``easy_install``.
|
|
||||||
|
|
||||||
.. code-block:: sh
|
|
||||||
|
|
||||||
pip install sleekxmpp # Or: easy_install sleekxmpp
|
|
||||||
|
|
||||||
|
|
||||||
Many XMPP applications eventually graduate to requiring to run as a server
|
|
||||||
component in order to meet scalability requirements. To demonstrate how to
|
|
||||||
turn an XMPP client bot into a component, we'll turn the echobot example
|
|
||||||
(:ref:`echobot`) into a component version.
|
|
||||||
|
|
||||||
The first difference is that we will add an additional import statement:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from sleekxmpp.componentxmpp import ComponentXMPP
|
|
||||||
|
|
||||||
Likewise, we will change the bot's class definition to match:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
class EchoComponent(ComponentXMPP):
|
|
||||||
|
|
||||||
def __init__(self, jid, secret, server, port):
|
|
||||||
ComponentXMPP.__init__(self, jid, secret, server, port)
|
|
||||||
|
|
||||||
A component instance requires two extra parameters compared to a client
|
|
||||||
instance: ``server`` and ``port``. These specifiy the name and port of
|
|
||||||
the XMPP server that will be accepting the component. For example, for
|
|
||||||
a MUC component, the following could be used:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
muc = ComponentXMPP('muc.sleekxmpp.com', '******', 'sleekxmpp.com', 5555)
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
The ``server`` value is **NOT** derived from the provided JID for the
|
|
||||||
component, unlike with client connections.
|
|
||||||
|
|
||||||
One difference with the component version is that we do not have
|
|
||||||
to handle the :term:`session_start` event if we don't wish to deal
|
|
||||||
with presence.
|
|
||||||
|
|
||||||
The other, main difference with components is that the
|
|
||||||
``'from'`` value for every stanza must be explicitly set, since
|
|
||||||
components may send stanzas from multiple JIDs. To do so,
|
|
||||||
the :meth:`~sleekxmpp.basexmpp.BaseXMPP.send_message()` and
|
|
||||||
:meth:`~sleekxmpp.basexmpp.BaseXMPP.send_presence()` accept the parameters
|
|
||||||
``mfrom`` and ``pfrom``, respectively. For any method that uses
|
|
||||||
:class:`~sleekxmpp.stanza.iq.Iq` stanzas, ``ifrom`` may be used.
|
|
||||||
|
|
||||||
|
|
||||||
Final Product
|
|
||||||
-------------
|
|
||||||
|
|
||||||
.. include:: ../../examples/echo_component.py
|
|
||||||
:literal:
|
|
||||||
@@ -1,390 +0,0 @@
|
|||||||
.. _echobot:
|
|
||||||
|
|
||||||
===============================
|
|
||||||
SleekXMPP Quickstart - Echo Bot
|
|
||||||
===============================
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
If you have any issues working through this quickstart guide
|
|
||||||
or the other tutorials here, please either send a message to the
|
|
||||||
`mailing list <http://groups.google.com/group/sleekxmpp-discussion>`_
|
|
||||||
or join the chat room at `sleek@conference.jabber.org
|
|
||||||
<xmpp:sleek@conference.jabber.org?join>`_.
|
|
||||||
|
|
||||||
If you have not yet installed SleekXMPP, do so now by either checking out a version
|
|
||||||
from `Github <http://github.com/fritzy/SleekXMPP>`_, or installing it using ``pip``
|
|
||||||
or ``easy_install``.
|
|
||||||
|
|
||||||
.. code-block:: sh
|
|
||||||
|
|
||||||
pip install sleekxmpp # Or: easy_install sleekxmpp
|
|
||||||
|
|
||||||
|
|
||||||
As a basic starting project, we will create an echo bot which will reply to any
|
|
||||||
messages sent to it. We will also go through adding some basic command line configuration
|
|
||||||
for enabling or disabling debug log outputs and setting the username and password
|
|
||||||
for the bot.
|
|
||||||
|
|
||||||
For the command line options processing, we will use the built-in ``optparse``
|
|
||||||
module and the ``getpass`` module for reading in passwords.
|
|
||||||
|
|
||||||
TL;DR Just Give Me the Code
|
|
||||||
---------------------------
|
|
||||||
As you wish: :ref:`the completed example <echobot_complete>`.
|
|
||||||
|
|
||||||
Overview
|
|
||||||
--------
|
|
||||||
|
|
||||||
To get started, here is a brief outline of the structure that the final project will have:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import getpass
|
|
||||||
from optparse import OptionParser
|
|
||||||
|
|
||||||
import sleekxmpp
|
|
||||||
|
|
||||||
'''Here we will create out echo bot class'''
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
'''Here we will configure and read command line options'''
|
|
||||||
|
|
||||||
'''Here we will instantiate our echo bot'''
|
|
||||||
|
|
||||||
'''Finally, we connect the bot and start listening for messages'''
|
|
||||||
|
|
||||||
Default Encoding
|
|
||||||
----------------
|
|
||||||
XMPP requires support for UTF-8 and so SleekXMPP must use UTF-8 as well. In
|
|
||||||
Python3 this is simple because Unicode is the default string type. For Python2.6+
|
|
||||||
the situation is not as easy because standard strings are simply byte arrays and
|
|
||||||
use ASCII. We can get Python to use UTF-8 as the default encoding by including:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
if sys.version_info < (3, 0):
|
|
||||||
reload(sys)
|
|
||||||
sys.setdefaultencoding('utf8')
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
Until we are able to ensure that SleekXMPP will always use Unicode in Python2.6+, this
|
|
||||||
may cause issues embedding SleekXMPP into other applications which assume ASCII encoding.
|
|
||||||
|
|
||||||
Creating the EchoBot Class
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
There are three main types of entities within XMPP — servers, components, and
|
|
||||||
clients. Since our echo bot will only be responding to a few people, and won't need
|
|
||||||
to remember thousands of users, we will use a client connection. A client connection
|
|
||||||
is the same type that you use with your standard IM client such as Pidgin or Psi.
|
|
||||||
|
|
||||||
SleekXMPP comes with a :class:`ClientXMPP <sleekxmpp.clientxmpp.ClientXMPP>` class
|
|
||||||
which we can extend to add our message echoing feature. :class:`ClientXMPP <sleekxmpp.clientxmpp.ClientXMPP>`
|
|
||||||
requires the parameters ``jid`` and ``password``, so we will let our ``EchoBot`` class accept those
|
|
||||||
as well.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
class EchoBot(sleekxmpp.ClientXMPP):
|
|
||||||
|
|
||||||
def __init__(self, jid, password):
|
|
||||||
super(EchoBot, self).__init__(jid, password)
|
|
||||||
|
|
||||||
Handling Session Start
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
The XMPP spec requires clients to broadcast its presence and retrieve its roster (buddy list) once
|
|
||||||
it connects and establishes a session with the XMPP server. Until these two tasks are completed,
|
|
||||||
some servers may not deliver or send messages or presence notifications to the client. So we now
|
|
||||||
need to be sure that we retrieve our roster and send an initial presence once the session has
|
|
||||||
started. To do that, we will register an event handler for the :term:`session_start` event.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def __init__(self, jid, password):
|
|
||||||
super(EchoBot, self).__init__(jid, password)
|
|
||||||
|
|
||||||
self.add_event_handler('session_start', self.start)
|
|
||||||
|
|
||||||
|
|
||||||
Since we want the method ``self.start`` to execute when the :term:`session_start` event is triggered,
|
|
||||||
we also need to define the ``self.start`` handler.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def start(self, event):
|
|
||||||
self.send_presence()
|
|
||||||
self.get_roster()
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
Not sending an initial presence and retrieving the roster when using a client instance can
|
|
||||||
prevent your program from receiving presence notifications or messages depending on the
|
|
||||||
XMPP server you have chosen.
|
|
||||||
|
|
||||||
Our event handler, like every event handler, accepts a single parameter which typically is the stanza
|
|
||||||
that was received that caused the event. In this case, ``event`` will just be an empty dictionary since
|
|
||||||
there is no associated data.
|
|
||||||
|
|
||||||
Our first task of sending an initial presence is done using :meth:`send_presence <sleekxmpp.basexmpp.BaseXMPP.send_presence>`.
|
|
||||||
Calling :meth:`send_presence <sleekxmpp.basexmpp.BaseXMPP.send_presence>` without any arguments will send the simplest
|
|
||||||
stanza allowed in XMPP:
|
|
||||||
|
|
||||||
.. code-block:: xml
|
|
||||||
|
|
||||||
<presence />
|
|
||||||
|
|
||||||
|
|
||||||
The second requirement is fulfilled using :meth:`get_roster <sleekxmpp.clientxmpp.ClientXMPP.get_roster>`, which
|
|
||||||
will send an IQ stanza requesting the roster to the server and then wait for the response. You may be wondering
|
|
||||||
what :meth:`get_roster <sleekxmpp.clientxmpp.ClientXMPP.get_roster>` returns since we are not saving any return
|
|
||||||
value. The roster data is saved by an internal handler to ``self.roster``, and in the case of a :class:`ClientXMPP
|
|
||||||
<sleekxmpp.clientxmpp.ClientXMPP>` instance to ``self.client_roster``. (The difference between ``self.roster`` and
|
|
||||||
``self.client_roster`` is that ``self.roster`` supports storing roster information for multiple JIDs, which is useful
|
|
||||||
for components, whereas ``self.client_roster`` stores roster data for just the client's JID.)
|
|
||||||
|
|
||||||
It is possible for a timeout to occur while waiting for the server to respond, which can happen if the
|
|
||||||
network is excessively slow or the server is no longer responding. In that case, an :class:`IQTimeout
|
|
||||||
<sleekxmpp.exceptions.IQTimeout>` is raised. Similarly, an :class:`IQError <sleekxmpp.exceptions.IQError>` exception can
|
|
||||||
be raised if the request contained bad data or requested the roster for the wrong user. In either case, you can wrap the
|
|
||||||
``get_roster()`` call in a ``try``/``except`` block to retry the roster retrieval process.
|
|
||||||
|
|
||||||
The XMPP stanzas from the roster retrieval process could look like this:
|
|
||||||
|
|
||||||
.. code-block:: xml
|
|
||||||
|
|
||||||
<iq type="get">
|
|
||||||
<query xmlns="jabber:iq:roster" />
|
|
||||||
</iq>
|
|
||||||
|
|
||||||
<iq type="result" to="echobot@example.com" from="example.com">
|
|
||||||
<query xmlns="jabber:iq:roster">
|
|
||||||
<item jid="friend@example.com" subscription="both" />
|
|
||||||
</query>
|
|
||||||
</iq>
|
|
||||||
|
|
||||||
Responding to Messages
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
Now that an ``EchoBot`` instance handles :term:`session_start`, we can begin receiving and
|
|
||||||
responding to messages. Now we can register a handler for the :term:`message` event that is raised
|
|
||||||
whenever a messsage is received.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def __init__(self, jid, password):
|
|
||||||
super(EchoBot, self).__init__(jid, password)
|
|
||||||
|
|
||||||
self.add_event_handler('session_start', self.start)
|
|
||||||
self.add_event_handler('message', self.message)
|
|
||||||
|
|
||||||
|
|
||||||
The :term:`message` event is fired whenever a ``<message />`` stanza is received, including for
|
|
||||||
group chat messages, errors, etc. Properly responding to messages thus requires checking the
|
|
||||||
``'type'`` interface of the message :term:`stanza object`. For responding to only messages
|
|
||||||
addressed to our bot (and not from a chat room), we check that the type is either ``normal``
|
|
||||||
or ``chat``. (Other potential types are ``error``, ``headline``, and ``groupchat``.)
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def message(self, msg):
|
|
||||||
if msg['type'] in ('normal', 'chat'):
|
|
||||||
msg.reply("Thanks for sending:\n%s" % msg['body']).send()
|
|
||||||
|
|
||||||
Let's take a closer look at the ``.reply()`` method used above. For message stanzas,
|
|
||||||
``.reply()`` accepts the parameter ``body`` (also as the first positional argument),
|
|
||||||
which is then used as the value of the ``<body />`` element of the message.
|
|
||||||
Setting the appropriate ``to`` JID is also handled by ``.reply()``.
|
|
||||||
|
|
||||||
Another way to have sent the reply message would be to use :meth:`send_message <sleekxmpp.basexmpp.BaseXMPP.send_message>`,
|
|
||||||
which is a convenience method for generating and sending a message based on the values passed to it. If we were to use
|
|
||||||
this method, the above code would look as so:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def message(self, msg):
|
|
||||||
if msg['type'] in ('normal', 'chat'):
|
|
||||||
self.send_message(mto=msg['from'],
|
|
||||||
mbody='Thanks for sending:\n%s' % msg['body'])
|
|
||||||
|
|
||||||
Whichever method you choose to use, the results in action will look like this:
|
|
||||||
|
|
||||||
.. code-block:: xml
|
|
||||||
|
|
||||||
<message to="echobot@example.com" from="someuser@example.net" type="chat">
|
|
||||||
<body>Hej!</body>
|
|
||||||
</message>
|
|
||||||
|
|
||||||
<message to="someuser@example.net" type="chat">
|
|
||||||
<body>Thanks for sending:
|
|
||||||
Hej!</body>
|
|
||||||
</message>
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
XMPP does not require stanzas sent by a client to include a ``from`` attribute, and
|
|
||||||
leaves that responsibility to the XMPP server. However, if a sent stanza does
|
|
||||||
include a ``from`` attribute, it must match the full JID of the client or some
|
|
||||||
servers will reject it. SleekXMPP thus leaves out the ``from`` attribute when replying
|
|
||||||
using a client connection.
|
|
||||||
|
|
||||||
Command Line Arguments and Logging
|
|
||||||
----------------------------------
|
|
||||||
|
|
||||||
While this isn't part of SleekXMPP itself, we do want our echo bot program to be able
|
|
||||||
to accept a JID and password from the command line instead of hard coding them. We will
|
|
||||||
use the ``optparse`` module for this, though there are several alternative methods, including
|
|
||||||
the newer ``argparse`` module.
|
|
||||||
|
|
||||||
We want to accept three parameters: the JID for the echo bot, its password, and a flag for
|
|
||||||
displaying the debugging logs. We also want these to be optional parameters, since passing
|
|
||||||
a password directly through the command line can be a security risk.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
optp = OptionParser()
|
|
||||||
|
|
||||||
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=logging.DEBUG, default=logging.INFO)
|
|
||||||
optp.add_option("-j", "--jid", dest="jid",
|
|
||||||
help="JID to use")
|
|
||||||
optp.add_option("-p", "--password", dest="password",
|
|
||||||
help="password to use")
|
|
||||||
|
|
||||||
opts, args = optp.parse_args()
|
|
||||||
|
|
||||||
if opts.jid is None:
|
|
||||||
opts.jid = raw_input("Username: ")
|
|
||||||
if opts.password is None:
|
|
||||||
opts.password = getpass.getpass("Password: ")
|
|
||||||
|
|
||||||
Since we included a flag for enabling debugging logs, we need to configure the
|
|
||||||
``logging`` module to behave accordingly.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
|
||||||
# .. option parsing from above ..
|
|
||||||
|
|
||||||
logging.basicConfig(level=opts.loglevel,
|
|
||||||
format='%(levelname)-8s %(message)s')
|
|
||||||
|
|
||||||
|
|
||||||
Connecting to the Server and Processing
|
|
||||||
---------------------------------------
|
|
||||||
There are three steps remaining until our echo bot is complete:
|
|
||||||
1. We need to instantiate the bot.
|
|
||||||
2. The bot needs to connect to an XMPP server.
|
|
||||||
3. We have to instruct the bot to start running and processing messages.
|
|
||||||
|
|
||||||
Creating the bot is straightforward, but we can also perform some configuration
|
|
||||||
at this stage. For example, let's say we want our bot to support `service discovery
|
|
||||||
<http://xmpp.org/extensions/xep-0030.html>`_ and `pings <http://xmpp.org/extensions/xep-0199.html>`_:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
|
||||||
# .. option parsing and logging steps from above
|
|
||||||
|
|
||||||
xmpp = EchoBot(opts.jid, opts.password)
|
|
||||||
xmpp.register_plugin('xep_0030') # Service Discovery
|
|
||||||
xmpp.register_plugin('xep_0199') # Ping
|
|
||||||
|
|
||||||
If the ``EchoBot`` class had a hard dependency on a plugin, we could register that plugin in
|
|
||||||
the ``EchoBot.__init__`` method instead.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
If you are using the OpenFire server, you will need to include an additional
|
|
||||||
configuration step. OpenFire supports a different version of SSL than what
|
|
||||||
most servers and SleekXMPP support.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
import ssl
|
|
||||||
xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
|
||||||
|
|
||||||
Now we're ready to connect and begin echoing messages. If you have the package
|
|
||||||
``dnspython`` installed, then the :meth:`sleekxmpp.clientxmpp.ClientXMPP` method
|
|
||||||
will perform a DNS query to find the appropriate server to connect to for the
|
|
||||||
given JID. If you do not have ``dnspython``, then SleekXMPP will attempt to
|
|
||||||
connect to the hostname used by the JID, unless an address tuple is supplied
|
|
||||||
to :meth:`sleekxmpp.clientxmpp.ClientXMPP`.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
|
||||||
# .. option parsing & echo bot configuration
|
|
||||||
|
|
||||||
if xmpp.connect():
|
|
||||||
xmpp.process(block=True)
|
|
||||||
else:
|
|
||||||
print('Unable to connect')
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
For Google Talk users withouth ``dnspython`` installed, the above code
|
|
||||||
should look like:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
|
||||||
# .. option parsing & echo bot configuration
|
|
||||||
|
|
||||||
if xmpp.connect(('talk.google.com', 5222)):
|
|
||||||
xmpp.process(block=True)
|
|
||||||
else:
|
|
||||||
print('Unable to connect')
|
|
||||||
|
|
||||||
To begin responding to messages, you'll see we called :meth:`sleekxmpp.basexmpp.BaseXMPP.process`
|
|
||||||
which will start the event handling, send queue, and XML reader threads. It will also call
|
|
||||||
the :meth:`sleekxmpp.plugins.base.base_plugin.post_init` method on all registered plugins. By
|
|
||||||
passing ``block=True`` to :meth:`sleekxmpp.basexmpp.BaseXMPP.process` we are running the
|
|
||||||
main processing loop in the main thread of execution. The :meth:`sleekxmpp.basexmpp.BaseXMPP.process`
|
|
||||||
call will not return until after SleekXMPP disconnects. If you need to run the client in the background
|
|
||||||
for another program, use ``block=False`` to spawn the processing loop in its own thread.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Before 1.0, controlling the blocking behaviour of :meth:`sleekxmpp.basexmpp.BaseXMPP.process` was
|
|
||||||
done via the ``threaded`` argument. This arrangement was a source of confusion because some users
|
|
||||||
interpreted that as controlling whether or not SleekXMPP used threads at all, instead of how
|
|
||||||
the processing loop itself was spawned.
|
|
||||||
|
|
||||||
The statements ``xmpp.process(threaded=False)`` and ``xmpp.process(block=True)`` are equivalent.
|
|
||||||
|
|
||||||
|
|
||||||
.. _echobot_complete:
|
|
||||||
|
|
||||||
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 SleekXMPP `examples directory <http://github.com/fritzy/SleekXMPP/tree/master/examples>`_.
|
|
||||||
|
|
||||||
.. compound::
|
|
||||||
|
|
||||||
You can run the code using:
|
|
||||||
|
|
||||||
.. code-block:: sh
|
|
||||||
|
|
||||||
python echobot.py -d -j echobot@example.com
|
|
||||||
|
|
||||||
which will prompt for the password and then begin echoing messages. To test, open
|
|
||||||
your regular IM client and start a chat with the echo bot. Messages you send to it should
|
|
||||||
be mirrored back to you. Be careful if you are using the same JID for the echo bot that
|
|
||||||
you also have logged in with another IM client. Messages could be routed to your IM client instead
|
|
||||||
of the bot.
|
|
||||||
|
|
||||||
.. include:: ../../examples/echo_client.py
|
|
||||||
:literal:
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
Send/Receive IQ Stanzas
|
|
||||||
=======================
|
|
||||||
|
|
||||||
Unlike :class:`~sleekxmpp.stanza.message.Message` and
|
|
||||||
:class:`~sleekxmpp.stanza.presence.Presence` stanzas which only use
|
|
||||||
text data for basic usage, :class:`~sleekxmpp.stanza.iq.Iq` stanzas
|
|
||||||
require using XML payloads, and generally entail creating a new
|
|
||||||
SleekXMPP plugin to provide the necessary convenience methods to
|
|
||||||
make working with them easier.
|
|
||||||
|
|
||||||
Basic Use
|
|
||||||
---------
|
|
||||||
|
|
||||||
XMPP's use of :class:`~sleekxmpp.stanza.iq.Iq` stanzas is built around
|
|
||||||
namespaced ``<query />`` elements. For clients, just sending the
|
|
||||||
empty ``<query />`` element will suffice for retrieving information. For
|
|
||||||
example, a very basic implementation of service discovery would just
|
|
||||||
need to be able to send:
|
|
||||||
|
|
||||||
.. code-block:: xml
|
|
||||||
|
|
||||||
<iq to="user@example.com" type="get" id="1">
|
|
||||||
<query xmlns="http://jabber.org/protocol/disco#info" />
|
|
||||||
</iq>
|
|
||||||
|
|
||||||
Creating Iq Stanzas
|
|
||||||
~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
SleekXMPP provides built-in support for creating basic :class:`~sleekxmpp.stanza.iq.Iq`
|
|
||||||
stanzas this way. The relevant methods are:
|
|
||||||
|
|
||||||
* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq`
|
|
||||||
* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_get`
|
|
||||||
* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_set`
|
|
||||||
* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_result`
|
|
||||||
* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_error`
|
|
||||||
* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_query`
|
|
||||||
|
|
||||||
These methods all follow the same pattern: create or modify an existing
|
|
||||||
:class:`~sleekxmpp.stanza.iq.Iq` stanza, set the ``'type'`` value based
|
|
||||||
on the method name, and finally add a ``<query />`` element with the given
|
|
||||||
namespace. For example, to produce the query above, you would use:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
self.make_iq_get(queryxmlns='http://jabber.org/protocol/disco#info',
|
|
||||||
ito='user@example.com')
|
|
||||||
|
|
||||||
|
|
||||||
Sending Iq Stanzas
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Once an :class:`~sleekxmpp.stanza.iq.Iq` stanza is created, sending it
|
|
||||||
over the wire is done using its :meth:`~sleekxmpp.stanza.iq.Iq.send()`
|
|
||||||
method, like any other stanza object. However, there are a few extra
|
|
||||||
options to control how to wait for the query's response.
|
|
||||||
|
|
||||||
These options are:
|
|
||||||
|
|
||||||
* ``block``: The default behaviour is that :meth:`~sleekxmpp.stanza.iq.Iq.send()`
|
|
||||||
will block until a response is received and the response stanza will be the
|
|
||||||
return value. Setting ``block`` to ``False`` will cause the call to return
|
|
||||||
immediately. In which case, you will need to arrange some way to capture
|
|
||||||
the response stanza if you need it.
|
|
||||||
|
|
||||||
* ``timeout``: When using the blocking behaviour, the call will eventually
|
|
||||||
timeout with an error. The default timeout is 30 seconds, but this may
|
|
||||||
be overidden two ways. To change the timeout globally, set:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
self.response_timeout = 10
|
|
||||||
|
|
||||||
To change the timeout for a single call, the ``timeout`` parameter works:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
iq.send(timeout=60)
|
|
||||||
|
|
||||||
* ``callback``: When not using a blocking call, using the ``callback``
|
|
||||||
argument is a simple way to register a handler that will execute
|
|
||||||
whenever a response is finally received. Using this method, there
|
|
||||||
is no timeout limit. In case you need to remove the callback, the
|
|
||||||
name of the newly created callback is returned.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
cb_name = iq.send(callback=self.a_callback)
|
|
||||||
|
|
||||||
# ... later if we need to cancel
|
|
||||||
self.remove_handler(cb_name)
|
|
||||||
|
|
||||||
Properly working with :class:`~sleekxmpp.stanza.iq.Iq` stanzas requires
|
|
||||||
handling the intended, normal flow, error responses, and timed out
|
|
||||||
requests. To make this easier, two exceptions may be thrown by
|
|
||||||
:meth:`~sleekxmpp.stanza.iq.Iq.send()`: :exc:`~sleekxmpp.exceptions.IqError`
|
|
||||||
and :exc:`~sleekxmpp.exceptions.IqTimeout`. These exceptions only
|
|
||||||
apply to the default, blocking calls.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
try:
|
|
||||||
resp = iq.send()
|
|
||||||
# ... do stuff with expected Iq result
|
|
||||||
except IqError as e:
|
|
||||||
err_resp = e.iq
|
|
||||||
# ... handle error case
|
|
||||||
except IqTimeout:
|
|
||||||
# ... no response received in time
|
|
||||||
pass
|
|
||||||
|
|
||||||
If you do not care to distinguish between errors and timeouts, then you
|
|
||||||
can combine both cases with a generic :exc:`~sleekxmpp.exceptions.XMPPError`
|
|
||||||
exception:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
try:
|
|
||||||
resp = iq.send()
|
|
||||||
except XMPPError:
|
|
||||||
# ... Don't care about the response
|
|
||||||
pass
|
|
||||||
|
|
||||||
Advanced Use
|
|
||||||
------------
|
|
||||||
|
|
||||||
Going beyond the basics provided by SleekXMPP requires building at least a
|
|
||||||
rudimentary SleekXMPP plugin to create a :term:`stanza object` for
|
|
||||||
interfacting with the :class:`~sleekxmpp.stanza.iq.Iq` payload.
|
|
||||||
|
|
||||||
.. seealso::
|
|
||||||
|
|
||||||
* :ref:`create-plugin`
|
|
||||||
* :ref:`work-with-stanzas`
|
|
||||||
* :ref:`using-handlers-matchers`
|
|
||||||
|
|
||||||
|
|
||||||
The typical way to respond to :class:`~sleekxmpp.stanza.iq.Iq` requests is
|
|
||||||
to register stream handlers. As an example, suppose we create a stanza class
|
|
||||||
named ``CustomXEP`` which uses the XML element ``<query xmlns="custom-xep" />``,
|
|
||||||
and has a :attr:`~sleekxmpp.xmlstream.stanzabase.ElementBase.plugin_attrib` value
|
|
||||||
of ``custom_xep``.
|
|
||||||
|
|
||||||
There are two types of incoming :class:`~sleekxmpp.stanza.iq.Iq` requests:
|
|
||||||
``get`` and ``set``. You can register a handler that will accept both and then
|
|
||||||
filter by type as needed, as so:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
self.register_handler(Callback(
|
|
||||||
'CustomXEP Handler',
|
|
||||||
StanzaPath('iq/custom_xep'),
|
|
||||||
self._handle_custom_iq))
|
|
||||||
|
|
||||||
# ...
|
|
||||||
|
|
||||||
def _handle_custom_iq(self, iq):
|
|
||||||
if iq['type'] == 'get':
|
|
||||||
# ...
|
|
||||||
pass
|
|
||||||
elif iq['type'] == 'set':
|
|
||||||
# ...
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# ... This will capture error responses too
|
|
||||||
pass
|
|
||||||
|
|
||||||
If you want to filter out query types beforehand, you can adjust the matching
|
|
||||||
filter by using ``@type=get`` or ``@type=set`` if you are using the recommended
|
|
||||||
:class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath` matcher.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
self.register_handler(Callback(
|
|
||||||
'CustomXEP Handler',
|
|
||||||
StanzaPath('iq@type=get/custom_xep'),
|
|
||||||
self._handle_custom_iq_get))
|
|
||||||
|
|
||||||
# ...
|
|
||||||
|
|
||||||
def _handle_custom_iq_get(self, iq):
|
|
||||||
assert(iq['type'] == 'get')
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
Mulit-User Chat (MUC) Bot
|
|
||||||
=========================
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
Manage Presence Subscriptions
|
|
||||||
=============================
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
.. _proxy:
|
|
||||||
|
|
||||||
=========================
|
|
||||||
Enable HTTP Proxy Support
|
|
||||||
=========================
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
If you have any issues working through this quickstart guide
|
|
||||||
or the other tutorials here, please either send a message to the
|
|
||||||
`mailing list <http://groups.google.com/group/sleekxmpp-discussion>`_
|
|
||||||
or join the chat room at `sleek@conference.jabber.org
|
|
||||||
<xmpp:sleek@conference.jabber.org?join>`_.
|
|
||||||
|
|
||||||
In some instances, you may wish to route XMPP traffic through
|
|
||||||
an HTTP proxy, probably to get around restrictive firewalls.
|
|
||||||
SleekXMPP provides support for basic HTTP proxying with DIGEST
|
|
||||||
authentication.
|
|
||||||
|
|
||||||
Enabling proxy support is done in two steps. The first is to instruct SleekXMPP
|
|
||||||
to use a proxy, and the second is to configure the proxy details:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
xmpp = ClientXMPP(...)
|
|
||||||
xmpp.use_proxy = True
|
|
||||||
xmpp.proxy_config = {
|
|
||||||
'host': 'proxy.example.com',
|
|
||||||
'port': 5555,
|
|
||||||
'username': 'example_user',
|
|
||||||
'password': '******'
|
|
||||||
}
|
|
||||||
|
|
||||||
The ``'username'`` and ``'password'`` fields are optional if the proxy does not
|
|
||||||
require authentication.
|
|
||||||
|
|
||||||
|
|
||||||
The Final Product
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
.. include:: ../../examples/proxy_echo_client.py
|
|
||||||
:literal:
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
Send a Message Every 5 Minutes
|
|
||||||
==============================
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
Sign in, Send a Message, and Disconnect
|
|
||||||
=======================================
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
If you have any issues working through this quickstart guide
|
|
||||||
or the other tutorials here, please either send a message to the
|
|
||||||
`mailing list <http://groups.google.com/group/sleekxmpp-discussion>`_
|
|
||||||
or join the chat room at `sleek@conference.jabber.org
|
|
||||||
<xmpp:sleek@conference.jabber.org?join>`_.
|
|
||||||
|
|
||||||
A common use case for SleekXMPP is to send one-off messages from
|
|
||||||
time to time. For example, one use case could be sending out a notice when
|
|
||||||
a shell script finishes a task.
|
|
||||||
|
|
||||||
We will create our one-shot bot based on the pattern explained in :ref:`echobot`. To
|
|
||||||
start, we create a client class based on :class:`ClientXMPP <sleekxmpp.clientxmpp.ClientXMPP>` and
|
|
||||||
register a handler for the :term:`session_start` event. We will also accept parameters
|
|
||||||
for the JID that will receive our message, and the string content of the message.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
import sleekxmpp
|
|
||||||
|
|
||||||
|
|
||||||
class SendMsgBot(sleekxmpp.ClientXMPP):
|
|
||||||
|
|
||||||
def __init__(self, jid, password, recipient, msg):
|
|
||||||
super(SendMsgBot, self).__init__(jid, password)
|
|
||||||
|
|
||||||
self.recipient = recipient
|
|
||||||
self.msg = msg
|
|
||||||
|
|
||||||
self.add_event_handler('session_start', self.start)
|
|
||||||
|
|
||||||
def start(self, event):
|
|
||||||
self.send_presence()
|
|
||||||
self.get_roster()
|
|
||||||
|
|
||||||
Note that as in :ref:`echobot`, we need to include send an initial presence and request
|
|
||||||
the roster. Next, we want to send our message, and to do that we will use :meth:`send_message <sleekxmpp.basexmpp.BaseXMPP.send_message>`.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def start(self, event):
|
|
||||||
self.send_presence()
|
|
||||||
self.get_roster()
|
|
||||||
|
|
||||||
self.send_message(mto=self.recipient, mbody=self.msg)
|
|
||||||
|
|
||||||
Finally, we need to disconnect the client using :meth:`disconnect <sleekxmpp.xmlstream.XMLStream.disconnect>`.
|
|
||||||
Now, sent stanzas are placed in a queue to pass them to the send thread. If we were to call
|
|
||||||
:meth:`disconnect <sleekxmpp.xmlstream.XMLStream.disconnect>` without any parameters, then it is possible
|
|
||||||
for the client to disconnect before the send queue is processed and the message is actually
|
|
||||||
sent on the wire. To ensure that our message is processed, we use
|
|
||||||
:meth:`disconnect(wait=True) <sleekxmpp.xmlstream.XMLStream.disconnect>`.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def start(self, event):
|
|
||||||
self.send_presence()
|
|
||||||
self.get_roster()
|
|
||||||
|
|
||||||
self.send_message(mto=self.recipient, mbody=self.msg)
|
|
||||||
|
|
||||||
self.disconnect(wait=True)
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
If you happen to be adding stanzas to the send queue faster than the send thread
|
|
||||||
can process them, then :meth:`disconnect(wait=True) <sleekxmpp.xmlstream.XMLStream.disconnect>`
|
|
||||||
will block and not disconnect.
|
|
||||||
|
|
||||||
Final Product
|
|
||||||
-------------
|
|
||||||
|
|
||||||
.. compound::
|
|
||||||
|
|
||||||
The final step is to create a small runner script for initialising our ``SendMsgBot`` class and adding some
|
|
||||||
basic configuration options. By following the basic boilerplate pattern in :ref:`echobot`, we arrive
|
|
||||||
at the code below. To experiment with this example, you can use:
|
|
||||||
|
|
||||||
.. code-block:: sh
|
|
||||||
|
|
||||||
python send_client.py -d -j oneshot@example.com -t someone@example.net -m "This is a message"
|
|
||||||
|
|
||||||
which will prompt for the password and then log in, send your message, and then disconnect. To test, open
|
|
||||||
your regular IM client with the account you wish to send messages to. When you run the ``send_client.py``
|
|
||||||
example and instruct it to send your IM client account a message, you should receive the message you
|
|
||||||
gave. If the two JIDs you use also have a mutual presence subscription (they're on each other's buddy lists)
|
|
||||||
then you will also see the ``SendMsgBot`` client come online and then go offline.
|
|
||||||
|
|
||||||
.. include:: ../../examples/send_client.py
|
|
||||||
:literal:
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
.. _glossary:
|
|
||||||
|
|
||||||
Glossary
|
|
||||||
========
|
|
||||||
|
|
||||||
.. glossary::
|
|
||||||
:sorted:
|
|
||||||
|
|
||||||
stream handler
|
|
||||||
A callback function that accepts stanza objects pulled directly
|
|
||||||
from the XML stream. A stream handler is encapsulated in a
|
|
||||||
object that includes a :term:`Matcher` object, and which provides
|
|
||||||
additional semantics. For example, the ``Waiter`` handler wrapper
|
|
||||||
blocks thread execution until a matching stanza is received.
|
|
||||||
|
|
||||||
event handler
|
|
||||||
A callback function that responds to events raised by
|
|
||||||
``XMLStream.event``. An event handler may be marked as
|
|
||||||
threaded, allowing it to execute outside of the main processing
|
|
||||||
loop.
|
|
||||||
|
|
||||||
stanza object
|
|
||||||
Informally may refer both to classes which extend ``ElementBase``
|
|
||||||
or ``StanzaBase``, and to objects of such classes.
|
|
||||||
|
|
||||||
A stanza object is a wrapper for an XML object which exposes ``dict``
|
|
||||||
like interfaces which may be assigned to, read from, or deleted.
|
|
||||||
|
|
||||||
stanza plugin
|
|
||||||
A :term:`stanza object` which has been registered as a potential child
|
|
||||||
of another stanza object. The plugin stanza may accessed through the
|
|
||||||
parent stanza using the plugin's ``plugin_attrib`` as an interface.
|
|
||||||
|
|
||||||
substanza
|
|
||||||
See :term:`stanza plugin`
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
XEP-0030: Working with Service Discovery
|
|
||||||
========================================
|
|
||||||
|
|
||||||
XMPP networks can be composed of many individual clients, components,
|
|
||||||
and servers. Determining the JIDs for these entities and the various
|
|
||||||
features they may support is the role of `XEP-0030, Service
|
|
||||||
Discovery <http://xmpp.org/extensions/xep-0030.html>`_, or "disco" for short.
|
|
||||||
|
|
||||||
Every XMPP entity may possess what are called nodes. A node is just a name for
|
|
||||||
some aspect of an XMPP entity. For example, if an XMPP entity provides `Ad-Hoc
|
|
||||||
Commands <http://xmpp.org/extensions/xep-0050.html>`_, then it will have a node
|
|
||||||
named ``http://jabber.org/protocol/commands`` which will contain information
|
|
||||||
about the commands provided. Other agents using these ad-hoc commands will
|
|
||||||
interact with the information provided by this node. Note that the node name is
|
|
||||||
just an identifier; there is no inherent meaning.
|
|
||||||
|
|
||||||
Working with service discovery is about creating and querying these nodes.
|
|
||||||
According to XEP-0030, a node may contain three types of information:
|
|
||||||
identities, features, and items. (Further, extensible, information types are
|
|
||||||
defined in `XEP-0128 <http://xmpp.org/extensions/xep-0128.html>`_, but they are
|
|
||||||
not yet implemented by SleekXMPP.) SleekXMPP provides methods to configure each
|
|
||||||
of these node attributes.
|
|
||||||
|
|
||||||
Configuring Service Discovery
|
|
||||||
-----------------------------
|
|
||||||
The design focus for the XEP-0030 plug-in is handling info and items requests
|
|
||||||
in a dynamic fashion, allowing for complex policy decisions of who may receive
|
|
||||||
information and how much, or use alternate backend storage mechanisms for all
|
|
||||||
of the disco data. To do this, each action that the XEP-0030 plug-in performs
|
|
||||||
is handed off to what is called a "node handler," which is just a callback
|
|
||||||
function. These handlers are arranged in a hierarchy that allows for a single
|
|
||||||
handler to manage an entire domain of JIDs (say for a component), while allowing
|
|
||||||
other handler functions to override that global behaviour for certain JIDs, or
|
|
||||||
even further limited to only certain JID and node combinations.
|
|
||||||
|
|
||||||
The Dynamic Handler Hierarchy
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
* ``global``: (JID is None, node is None)
|
|
||||||
|
|
||||||
Handlers assigned at this level for an action (such as ``add_feature``) provide a global default
|
|
||||||
behaviour when the action is performed.
|
|
||||||
|
|
||||||
* ``jid``: (JID assigned, node is None)
|
|
||||||
|
|
||||||
At this level, handlers provide a default behaviour for actions affecting any node owned by the
|
|
||||||
JID in question. This level is most useful for component connections; there is effectively no
|
|
||||||
difference between this and the global level when using a client connection.
|
|
||||||
|
|
||||||
* ``node``: (JID assigned, node assigned)
|
|
||||||
|
|
||||||
A handler for this level is responsible for carrying out an action for only one node, and is the
|
|
||||||
most specific handler type available. These types of handlers will be most useful for "special"
|
|
||||||
nodes that require special processing different than others provided by the JID, such as using
|
|
||||||
access control lists, or consolidating data from other nodes.
|
|
||||||
|
|
||||||
Default Static Handlers
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
The XEP-0030 plug-in provides a default set of handlers that work using in-memory
|
|
||||||
disco stanzas. Each handler simply performs the appropriate lookup or storage
|
|
||||||
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()``.
|
|
||||||
You may also elect to only convert a given set of actions instead.
|
|
||||||
|
|
||||||
Creating a Node Handler
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
Every node handler receives three arguments: the JID, the node, and a data
|
|
||||||
parameter that will contain the relevant information for carrying out the
|
|
||||||
handler's action, typically a dictionary.
|
|
||||||
|
|
||||||
The JID will always have a value, defaulting to ``xmpp.boundjid.full`` for
|
|
||||||
components or ``xmpp.boundjid.bare`` for clients. The node value may be None or
|
|
||||||
a string.
|
|
||||||
|
|
||||||
Only handlers for the actions ``get_info`` and ``get_items`` need to have return
|
|
||||||
values. For these actions, DiscoInfo or DiscoItems stanzas are exepected as
|
|
||||||
output. It is also acceptable for handlers for these actions to generate an
|
|
||||||
XMPPError exception when necessary.
|
|
||||||
|
|
||||||
Example Node Handler:
|
|
||||||
+++++++++++++++++++++
|
|
||||||
Here is one of the built-in default handlers as an example:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def add_identity(self, jid, node, data):
|
|
||||||
"""
|
|
||||||
Add a new identity to the JID/node combination.
|
|
||||||
|
|
||||||
The data parameter may provide:
|
|
||||||
category -- The general category to which the agent belongs.
|
|
||||||
itype -- A more specific designation with the category.
|
|
||||||
name -- Optional human readable name for this identity.
|
|
||||||
lang -- Optional standard xml:lang value.
|
|
||||||
"""
|
|
||||||
self.add_node(jid, node)
|
|
||||||
self.nodes[(jid, node)]['info'].add_identity(
|
|
||||||
data.get('category', ''),
|
|
||||||
data.get('itype', ''),
|
|
||||||
data.get('name', None),
|
|
||||||
data.get('lang', None))
|
|
||||||
|
|
||||||
Adding Identities, Features, and Items
|
|
||||||
--------------------------------------
|
|
||||||
In order to maintain some backwards compatibility, the methods ``add_identity``,
|
|
||||||
``add_feature``, and ``add_item`` do not follow the method signature pattern of
|
|
||||||
the other API methods (i.e. jid, node, then other options), but rather retain
|
|
||||||
the parameter orders from previous plug-in versions.
|
|
||||||
|
|
||||||
Adding an Identity
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
Adding an identity may be done using either the older positional notation, or
|
|
||||||
with keyword parameters. The example below uses the keyword arguments, but in
|
|
||||||
the same order as expected using positional arguments.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
xmpp['xep_0030'].add_identity(category='client',
|
|
||||||
itype='bot',
|
|
||||||
name='Sleek',
|
|
||||||
node='foo',
|
|
||||||
jid=xmpp.boundjid.full,
|
|
||||||
lang='no')
|
|
||||||
|
|
||||||
The JID and node values determine which handler will be used to perform the
|
|
||||||
``add_identity`` action.
|
|
||||||
|
|
||||||
The ``lang`` parameter allows for adding localized versions of identities using
|
|
||||||
the ``xml:lang`` attribute.
|
|
||||||
|
|
||||||
Adding a Feature
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
The position ordering for ``add_feature()`` is to include the feature, then
|
|
||||||
specify the node and then the JID. The JID and node values determine which
|
|
||||||
handler will be used to perform the ``add_feature`` action.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
xmpp['xep_0030'].add_feature(feature='jabber:x:data',
|
|
||||||
node='foo',
|
|
||||||
jid=xmpp.boundjid.full)
|
|
||||||
|
|
||||||
Adding an Item
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
The parameters to ``add_item()`` are potentially confusing due to the fact that
|
|
||||||
adding an item requires two JID and node combinations: the JID and node of the
|
|
||||||
item itself, and the JID and node that will own the item.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
xmpp['xep_0030'].add_item(jid='myitemjid@example.com',
|
|
||||||
name='An Item!',
|
|
||||||
node='owner_node',
|
|
||||||
subnode='item_node',
|
|
||||||
ijid=xmpp.boundjid.full)
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
In this case, the owning JID and node are provided with the
|
|
||||||
parameters ``ijid`` and ``node``.
|
|
||||||
|
|
||||||
Peforming 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()``
|
|
||||||
method. The ``get_items()`` method may also accept the boolean parameter
|
|
||||||
``iterator``, which when set to ``True`` will return an iterator object using
|
|
||||||
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)
|
|
||||||
|
|
||||||
items = self['xep_0030'].get_info(jid='foo@example.com',
|
|
||||||
node='bar',
|
|
||||||
iterator=True)
|
|
||||||
|
|
||||||
For more examples on how to use basic disco queries, check the ``disco_browser.py``
|
|
||||||
example in the ``examples`` directory.
|
|
||||||
|
|
||||||
Local Queries
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
In some cases, it may be necessary to query the contents of a node owned by the
|
|
||||||
client itself, or one of a component's many JIDs. The same method is used as for
|
|
||||||
normal queries, with two differences. First, the parameter ``local=True`` must
|
|
||||||
be used. Second, the return value will be a DiscoInfo or DiscoItems stanza, not
|
|
||||||
a full Iq stanza.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
info = self['xep_0030'].get_info(node='foo', local=True)
|
|
||||||
items = self['xep_0030'].get_items(jid='somejid@mycomponent.example.com',
|
|
||||||
node='bar',
|
|
||||||
local=True)
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
.. _using-handlers-matchers:
|
|
||||||
|
|
||||||
Using Stream Handlers and Matchers
|
|
||||||
==================================
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
.. _work-with-stanzas:
|
|
||||||
|
|
||||||
How to Work with Stanza Objects
|
|
||||||
===============================
|
|
||||||
|
|
||||||
|
|
||||||
.. _create-stanza-interfaces:
|
|
||||||
|
|
||||||
Defining Stanza Interfaces
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
|
|
||||||
.. _create-stanza-plugins:
|
|
||||||
|
|
||||||
Creating Stanza Plugins
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. _create-extension-plugins:
|
|
||||||
|
|
||||||
Creating a Stanza Extension
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. _override-parent-interfaces:
|
|
||||||
|
|
||||||
Overriding a Parent Stanza
|
|
||||||
--------------------------
|
|
||||||
179
docs/index.rst
179
docs/index.rst
@@ -1,179 +0,0 @@
|
|||||||
SleekXMPP
|
|
||||||
#########
|
|
||||||
|
|
||||||
.. sidebar:: Get the Code
|
|
||||||
|
|
||||||
.. code-block:: sh
|
|
||||||
|
|
||||||
pip install sleekxmpp
|
|
||||||
|
|
||||||
The latest source code for SleekXMPP may be found on `Github
|
|
||||||
<http://github.com/fritzy/SleekXMPP>`_. Releases can be found in the
|
|
||||||
``master`` branch, while the latest development version is in the
|
|
||||||
``develop`` branch.
|
|
||||||
|
|
||||||
**Latest Stable Release**
|
|
||||||
- `1.0 RC3 <http://github.com/fritzy/SleekXMPP/zipball/1.0-RC3>`_
|
|
||||||
|
|
||||||
**Develop Releases**
|
|
||||||
- `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_
|
|
||||||
|
|
||||||
|
|
||||||
A mailing list and XMPP chat room are available for discussing and getting
|
|
||||||
help with SleekXMPP.
|
|
||||||
|
|
||||||
**Mailing List**
|
|
||||||
`SleekXMPP Discussion on Google Groups <http://groups.google.com/group/sleekxmpp-discussion>`_
|
|
||||||
|
|
||||||
**Chat**
|
|
||||||
`sleek@conference.jabber.org <xmpp:sleek@conference.jabber.org?join>`_
|
|
||||||
|
|
||||||
|
|
||||||
SleekXMPP is an :ref:`MIT licensed <license>` XMPP library for Python 2.6/3.1+,
|
|
||||||
and is featured in examples in
|
|
||||||
`XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271>`_
|
|
||||||
by Kevin Smith, Remko Tronçon, and Peter Saint-Andre. If you've arrived
|
|
||||||
here from reading the Definitive Guide, please see the notes on updating
|
|
||||||
the examples to the latest version of SleekXMPP.
|
|
||||||
|
|
||||||
SleekXMPP's design goals and philosphy are:
|
|
||||||
|
|
||||||
**Low number of dependencies**
|
|
||||||
Installing and using SleekXMPP should be as simple as possible, without
|
|
||||||
having to deal with long dependency chains.
|
|
||||||
|
|
||||||
As part of reducing the number of dependencies, some third party
|
|
||||||
modules are included with SleekXMPP in the ``thirdparty`` directory.
|
|
||||||
Imports from this module first try to import an existing installed
|
|
||||||
version before loading the packaged version, when possible.
|
|
||||||
|
|
||||||
**Every XEP as a plugin**
|
|
||||||
Following Python's "batteries included" approach, the goal is to
|
|
||||||
provide support for all currently active XEPs (final and draft). Since
|
|
||||||
adding XEP support is done through easy to create plugins, the hope is
|
|
||||||
to also provide a solid base for implementing and creating experimental
|
|
||||||
XEPs.
|
|
||||||
|
|
||||||
**Rewarding to work with**
|
|
||||||
As much as possible, SleekXMPP should allow things to "just work" using
|
|
||||||
sensible defaults and appropriate abstractions. XML can be ugly to work
|
|
||||||
with, but it doesn't have to be that way.
|
|
||||||
|
|
||||||
Getting Started (with Examples)
|
|
||||||
-------------------------------
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
getting_started/echobot
|
|
||||||
getting_started/sendlogout
|
|
||||||
getting_started/component
|
|
||||||
getting_started/presence
|
|
||||||
getting_started/muc
|
|
||||||
getting_started/proxy
|
|
||||||
getting_started/scheduler
|
|
||||||
getting_started/iq
|
|
||||||
|
|
||||||
|
|
||||||
Tutorials, FAQs, and How To Guides
|
|
||||||
----------------------------------
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
faq
|
|
||||||
xeps
|
|
||||||
xmpp_tdg
|
|
||||||
howto/stanzas
|
|
||||||
create_plugin
|
|
||||||
features
|
|
||||||
sasl
|
|
||||||
handlersmatchers
|
|
||||||
|
|
||||||
Plugin Guides
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
guide_xep_0030
|
|
||||||
|
|
||||||
SleekXMPP Architecture and Design
|
|
||||||
---------------------------------
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 3
|
|
||||||
|
|
||||||
architecture
|
|
||||||
plugin_arch
|
|
||||||
|
|
||||||
API Reference
|
|
||||||
-------------
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
event_index
|
|
||||||
api/clientxmpp
|
|
||||||
api/componentxmpp
|
|
||||||
api/basexmpp
|
|
||||||
api/exceptions
|
|
||||||
api/xmlstream/jid
|
|
||||||
api/xmlstream/stanzabase
|
|
||||||
api/xmlstream/handler
|
|
||||||
api/xmlstream/matcher
|
|
||||||
api/xmlstream/xmlstream
|
|
||||||
api/xmlstream/scheduler
|
|
||||||
api/xmlstream/tostring
|
|
||||||
api/xmlstream/filesocket
|
|
||||||
|
|
||||||
Core Stanzas
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
api/stanza/rootstanza
|
|
||||||
api/stanza/message
|
|
||||||
api/stanza/presence
|
|
||||||
api/stanza/iq
|
|
||||||
api/stanza/error
|
|
||||||
api/stanza/stream_error
|
|
||||||
|
|
||||||
Plugins
|
|
||||||
~~~~~~~
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
|
|
||||||
Additional Info
|
|
||||||
---------------
|
|
||||||
.. toctree::
|
|
||||||
:hidden:
|
|
||||||
|
|
||||||
glossary
|
|
||||||
license
|
|
||||||
|
|
||||||
* :ref:`license`
|
|
||||||
* :ref:`glossary`
|
|
||||||
* :ref:`genindex`
|
|
||||||
* :ref:`modindex`
|
|
||||||
* :ref:`search`
|
|
||||||
|
|
||||||
Credits
|
|
||||||
-------
|
|
||||||
**Main Author:** Nathan Fritz
|
|
||||||
`fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_,
|
|
||||||
`@fritzy <http://twitter.com/fritzy>`_
|
|
||||||
|
|
||||||
Nathan is also the author of XMPPHP and `Seesmic-AS3-XMPP
|
|
||||||
<http://code.google.com/p/seesmic-as3-xmpp/>`_, and a member of the XMPP
|
|
||||||
Council.
|
|
||||||
|
|
||||||
**Co-Author:** Lance Stout
|
|
||||||
`lancestout@gmail.com <xmpp:lancestout@gmail.com?message>`_,
|
|
||||||
`@lancestout <http://twitter.com/lancestout>`_
|
|
||||||
|
|
||||||
**Contributors:**
|
|
||||||
- Brian Beggs (`macdiesel <http://github.com/macdiesel>`_)
|
|
||||||
- Dann Martens (`dannmartens <http://github.com/dannmartens>`_)
|
|
||||||
- Florent Le Coz (`louiz <http://github.com/louiz>`_)
|
|
||||||
- Kevin Smith (`Kev <http://github.com/Kev>`_, http://kismith.co.uk)
|
|
||||||
- Remko Tronçon (`remko <http://github.com/remko>`_, http://el-tramo.be)
|
|
||||||
- Te-jé Rogers (`te-je <http://github.com/te-je>`_)
|
|
||||||
- Thom Nichols (`tomstrummer <http://github.com/tomstrummer>`_)
|
|
||||||
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
.. _license:
|
|
||||||
|
|
||||||
License (MIT)
|
|
||||||
=============
|
|
||||||
.. include:: ../LICENSE
|
|
||||||
170
docs/make.bat
170
docs/make.bat
@@ -1,170 +0,0 @@
|
|||||||
@ECHO OFF
|
|
||||||
|
|
||||||
REM Command file for Sphinx documentation
|
|
||||||
|
|
||||||
if "%SPHINXBUILD%" == "" (
|
|
||||||
set SPHINXBUILD=sphinx-build
|
|
||||||
)
|
|
||||||
set BUILDDIR=_build
|
|
||||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
|
||||||
if NOT "%PAPER%" == "" (
|
|
||||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "" goto help
|
|
||||||
|
|
||||||
if "%1" == "help" (
|
|
||||||
:help
|
|
||||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
|
||||||
echo. html to make standalone HTML files
|
|
||||||
echo. dirhtml to make HTML files named index.html in directories
|
|
||||||
echo. singlehtml to make a single large HTML file
|
|
||||||
echo. pickle to make pickle files
|
|
||||||
echo. json to make JSON files
|
|
||||||
echo. htmlhelp to make HTML files and a HTML help project
|
|
||||||
echo. qthelp to make HTML files and a qthelp project
|
|
||||||
echo. devhelp to make HTML files and a Devhelp project
|
|
||||||
echo. epub to make an epub
|
|
||||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
|
||||||
echo. text to make text files
|
|
||||||
echo. man to make manual pages
|
|
||||||
echo. changes to make an overview over all changed/added/deprecated items
|
|
||||||
echo. linkcheck to check all external links for integrity
|
|
||||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "clean" (
|
|
||||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
|
||||||
del /q /s %BUILDDIR%\*
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "html" (
|
|
||||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "dirhtml" (
|
|
||||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "singlehtml" (
|
|
||||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "pickle" (
|
|
||||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished; now you can process the pickle files.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "json" (
|
|
||||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished; now you can process the JSON files.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "htmlhelp" (
|
|
||||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
|
||||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "qthelp" (
|
|
||||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
|
||||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
|
||||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\SleekXMPP.qhcp
|
|
||||||
echo.To view the help file:
|
|
||||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\SleekXMPP.ghc
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "devhelp" (
|
|
||||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "epub" (
|
|
||||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "latex" (
|
|
||||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "text" (
|
|
||||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "man" (
|
|
||||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "changes" (
|
|
||||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.The overview file is in %BUILDDIR%/changes.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "linkcheck" (
|
|
||||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Link check complete; look for any errors in the above output ^
|
|
||||||
or in %BUILDDIR%/linkcheck/output.txt.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "doctest" (
|
|
||||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Testing of doctests in the sources finished, look at the ^
|
|
||||||
results in %BUILDDIR%/doctest/output.txt.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
:end
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
Plugin Architecture
|
|
||||||
===================
|
|
||||||
Binary file not shown.
@@ -1,2 +0,0 @@
|
|||||||
How SASL Authentication Works
|
|
||||||
=============================
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
Supported XEPS
|
|
||||||
==============
|
|
||||||
|
|
||||||
======= ============================= ================
|
|
||||||
XEP Description Notes
|
|
||||||
======= ============================= ================
|
|
||||||
`0004`_ Data forms
|
|
||||||
`0009`_ Jabber RPC
|
|
||||||
`0012`_ Last Activity
|
|
||||||
`0030`_ Service Discovery
|
|
||||||
`0033`_ Extended Stanza Addressing
|
|
||||||
`0045`_ Multi-User Chat (MUC) Client-side only
|
|
||||||
`0050`_ Ad-hoc Commands
|
|
||||||
`0059`_ Result Set Management
|
|
||||||
`0060`_ Publish/Subscribe (PubSub) Client-side only
|
|
||||||
`0066`_ Out-of-band Data
|
|
||||||
`0078`_ Non-SASL Authentication
|
|
||||||
`0082`_ XMPP Date and Time Profiles
|
|
||||||
`0085`_ Chat-State Notifications
|
|
||||||
`0086`_ Error Condition Mappings
|
|
||||||
`0092`_ Software Version
|
|
||||||
`0128`_ Service Discovery Extensions
|
|
||||||
`0202`_ Entity Time
|
|
||||||
`0203`_ Delayed Delivery
|
|
||||||
`0224`_ Attention
|
|
||||||
`0249`_ Direct MUC Invitations
|
|
||||||
======= ============================= ================
|
|
||||||
|
|
||||||
|
|
||||||
.. _0004: http://xmpp.org/extensions/xep-0004.html
|
|
||||||
.. _0009: http://xmpp.org/extensions/xep-0009.html
|
|
||||||
.. _0012: http://xmpp.org/extensions/xep-0012.html
|
|
||||||
.. _0030: http://xmpp.org/extensions/xep-0030.html
|
|
||||||
.. _0033: http://xmpp.org/extensions/xep-0033.html
|
|
||||||
.. _0045: http://xmpp.org/extensions/xep-0045.html
|
|
||||||
.. _0050: http://xmpp.org/extensions/xep-0050.html
|
|
||||||
.. _0059: http://xmpp.org/extensions/xep-0059.html
|
|
||||||
.. _0060: http://xmpp.org/extensions/xep-0060.html
|
|
||||||
.. _0066: http://xmpp.org/extensions/xep-0066.html
|
|
||||||
.. _0078: http://xmpp.org/extensions/xep-0078.html
|
|
||||||
.. _0082: http://xmpp.org/extensions/xep-0082.html
|
|
||||||
.. _0085: http://xmpp.org/extensions/xep-0085.html
|
|
||||||
.. _0086: http://xmpp.org/extensions/xep-0086.html
|
|
||||||
.. _0092: http://xmpp.org/extensions/xep-0092.html
|
|
||||||
.. _0128: http://xmpp.org/extensions/xep-0128.html
|
|
||||||
.. _0199: http://xmpp.org/extensions/xep-0199.html
|
|
||||||
.. _0202: http://xmpp.org/extensions/xep-0202.html
|
|
||||||
.. _0203: http://xmpp.org/extensions/xep-0203.html
|
|
||||||
.. _0224: http://xmpp.org/extensions/xep-0224.html
|
|
||||||
.. _0249: http://xmpp.org/extensions/xep-0249.html
|
|
||||||
@@ -1,249 +0,0 @@
|
|||||||
Following *XMPP: The Definitive Guide*
|
|
||||||
======================================
|
|
||||||
|
|
||||||
SleekXMPP was featured in the first edition of the O'Reilly book
|
|
||||||
`XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271/>`_
|
|
||||||
by Peter Saint-Andre, Kevin Smith, and Remko Tronçon. The original source code
|
|
||||||
for the book's examples can be found at http://github.com/remko/xmpp-tdg. An
|
|
||||||
updated version of the source code, maintained to stay current with the latest
|
|
||||||
SleekXMPP release, is available at http://github.com/legastero/xmpp-tdg.
|
|
||||||
|
|
||||||
However, since publication, SleekXMPP has advanced from version 0.2.1 to version
|
|
||||||
1.0 and there have been several major API changes. The most notable is the
|
|
||||||
introduction of :term:`stanza objects <stanza object>` which have simplified and
|
|
||||||
standardized interactions with the XMPP XML stream.
|
|
||||||
|
|
||||||
What follows is a walk-through of *The Definitive Guide* highlighting the
|
|
||||||
changes needed to make the code examples work with version 1.0 of SleekXMPP.
|
|
||||||
These changes have been kept to a minimum to preserve the correlation with
|
|
||||||
the book's explanations, so be aware that some code may not use current best
|
|
||||||
practices.
|
|
||||||
|
|
||||||
Example 2-2. (Page 26)
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
**Implementation of a basic bot that echoes all incoming messages back to its sender.**
|
|
||||||
|
|
||||||
The echo bot example requires a change to the ``handleIncomingMessage`` method
|
|
||||||
to reflect the use of the ``Message`` :term:`stanza object`. The
|
|
||||||
``"jid"`` field of the message object should now be ``"from"`` to match the
|
|
||||||
``from`` attribute of the actual XML message stanza. Likewise, ``"message"``
|
|
||||||
changes to ``"body"`` to match the ``body`` element of the message stanza.
|
|
||||||
|
|
||||||
Updated Code
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def handleIncomingMessage(self, message):
|
|
||||||
self.xmpp.sendMessage(message["from"], message["body"])
|
|
||||||
|
|
||||||
`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/EchoBot/EchoBot.py>`_ |
|
|
||||||
`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/EchoBot/EchoBot.py>`_
|
|
||||||
|
|
||||||
Example 14-1. (Page 215)
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
**CheshiR IM bot implementation.**
|
|
||||||
|
|
||||||
The main event handling method in the Bot class is meant to process both message
|
|
||||||
events and presence update events. With the new changes in SleekXMPP 1.0,
|
|
||||||
extracting a CheshiR status "message" from both types of stanzas
|
|
||||||
requires accessing different attributes. In the case of a message stanza, the
|
|
||||||
``"body"`` attribute would contain the CheshiR message. For a presence event,
|
|
||||||
the information is stored in the ``"status"`` attribute. To handle both cases,
|
|
||||||
we can test the type of the given event object and look up the proper attribute
|
|
||||||
based on the type.
|
|
||||||
|
|
||||||
Like in the EchoBot example, the expression ``event["jid"]`` needs to change
|
|
||||||
to ``event["from"]`` in order to get a JID object for the stanza's sender.
|
|
||||||
Because other functions in CheshiR assume that the JID is a string, the ``jid``
|
|
||||||
attribute is used to access the string version of the JID. A check is also added
|
|
||||||
in case ``user`` is ``None``, but the check could (and probably should) be
|
|
||||||
placed in ``addMessageFromUser``.
|
|
||||||
|
|
||||||
Another change is needed in ``handleMessageAddedToBackend`` where
|
|
||||||
an HTML-IM response is created. The HTML content should be enclosed in a single
|
|
||||||
element, such as a ``<p>`` tag.
|
|
||||||
|
|
||||||
Updated Code
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def handleIncomingXMPPEvent(self, event):
|
|
||||||
msgLocations = {sleekxmpp.stanza.presence.Presence: "status",
|
|
||||||
sleekxmpp.stanza.message.Message: "body"}
|
|
||||||
|
|
||||||
message = event[msgLocations[type(event)]]
|
|
||||||
user = self.backend.getUserFromJID(event["from"].jid)
|
|
||||||
if user is not None:
|
|
||||||
self.backend.addMessageFromUser(message, user)
|
|
||||||
|
|
||||||
def handleMessageAddedToBackend(self, message) :
|
|
||||||
body = message.user + ": " + message.text
|
|
||||||
htmlBody = "<p><a href='%(uri)s'>%(user)s</a>: %(message)s</p>" % {
|
|
||||||
"uri": self.url + "/" + message.user,
|
|
||||||
"user" : message.user, "message" : message.text }
|
|
||||||
for subscriberJID in self.backend.getSubscriberJIDs(message.user) :
|
|
||||||
self.xmpp.sendMessage(subscriberJID, body, mhtml=htmlBody)
|
|
||||||
|
|
||||||
`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/Bot.py>`_ |
|
|
||||||
`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/Bot.py>`_
|
|
||||||
|
|
||||||
|
|
||||||
Example 14-3. (Page 217)
|
|
||||||
------------------------
|
|
||||||
**Configurable CheshiR IM bot implementation.**
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
Since the CheshiR examples build on each other, see previous sections for
|
|
||||||
corrections to code that is not marked as new in the book example.
|
|
||||||
|
|
||||||
The main difference for the configurable IM bot is the handling for the
|
|
||||||
data form in ``handleConfigurationCommand``. The test for equality
|
|
||||||
with the string ``"1"`` is no longer required; SleekXMPP converts
|
|
||||||
boolean data form fields to the values ``True`` and ``False``
|
|
||||||
automatically.
|
|
||||||
|
|
||||||
For the method ``handleIncomingXMPPPresence``, the attribute
|
|
||||||
``"jid"`` is again converted to ``"from"`` to get a JID
|
|
||||||
object for the presence stanza's sender, and the ``jid`` attribute is
|
|
||||||
used to access the string version of that JID object. A check is also added in
|
|
||||||
case ``user`` is ``None``, but the check could (and probably
|
|
||||||
should) be placed in ``getShouldMonitorPresenceFromUser``.
|
|
||||||
|
|
||||||
Updated Code
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def handleConfigurationCommand(self, form, sessionId):
|
|
||||||
values = form.getValues()
|
|
||||||
monitorPresence =values["monitorPresence"]
|
|
||||||
jid = self.xmpp.plugin["xep_0050"].sessions[sessionId]["jid"]
|
|
||||||
user = self.backend.getUserFromJID(jid)
|
|
||||||
self.backend.setShouldMonitorPresenceFromUser(user, monitorPresence)
|
|
||||||
|
|
||||||
def handleIncomingXMPPPresence(self, event):
|
|
||||||
user = self.backend.getUserFromJID(event["from"].jid)
|
|
||||||
if user is not None:
|
|
||||||
if self.backend.getShouldMonitorPresenceFromUser(user):
|
|
||||||
self.handleIncomingXMPPEvent(event)
|
|
||||||
|
|
||||||
`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/ConfigurableBot.py>`_ |
|
|
||||||
`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/ConfigurableBot.py>`_
|
|
||||||
|
|
||||||
|
|
||||||
Example 14-4. (Page 220)
|
|
||||||
------------------------
|
|
||||||
**CheshiR IM server component implementation.**
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
Since the CheshiR examples build on each other, see previous sections for
|
|
||||||
corrections to code that is not marked as new in the book example.
|
|
||||||
|
|
||||||
Like several previous examples, a needed change is to replace
|
|
||||||
``subscription["from"]`` with ``subscription["from"].jid`` because the
|
|
||||||
``BaseXMPP`` method ``makePresence`` requires the JID to be a string.
|
|
||||||
|
|
||||||
A correction needs to be made in ``handleXMPPPresenceProbe`` because a line was
|
|
||||||
left out of the original implementation; the variable ``user`` is undefined. The
|
|
||||||
JID of the user can be extracted from the presence stanza's ``from`` attribute.
|
|
||||||
|
|
||||||
Since this implementation of CheshiR uses an XMPP component, it must
|
|
||||||
include a ``from`` attribute in all messages that it sends. Adding the
|
|
||||||
``from`` attribute is done by including ``mfrom=self.xmpp.jid`` in calls to
|
|
||||||
``self.xmpp.sendMessage``.
|
|
||||||
|
|
||||||
Updated Code
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def handleXMPPPresenceProbe(self, event) :
|
|
||||||
self.xmpp.sendPresence(pto = event["from"])
|
|
||||||
|
|
||||||
def handleXMPPPresenceSubscription(self, subscription) :
|
|
||||||
if subscription["type"] == "subscribe" :
|
|
||||||
userJID = subscription["from"].jid
|
|
||||||
self.xmpp.sendPresenceSubscription(pto=userJID, ptype="subscribed")
|
|
||||||
self.xmpp.sendPresence(pto = userJID)
|
|
||||||
self.xmpp.sendPresenceSubscription(pto=userJID, ptype="subscribe")
|
|
||||||
|
|
||||||
def handleMessageAddedToBackend(self, message) :
|
|
||||||
body = message.user + ": " + message.text
|
|
||||||
for subscriberJID in self.backend.getSubscriberJIDs(message.user) :
|
|
||||||
self.xmpp.sendMessage(subscriberJID, body, mfrom=self.xmpp.jid)
|
|
||||||
|
|
||||||
`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/SimpleComponent.py>`_ |
|
|
||||||
`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/SimpleComponent.py>`_
|
|
||||||
|
|
||||||
|
|
||||||
Example 14-6. (Page 223)
|
|
||||||
------------------------
|
|
||||||
**CheshiR IM server component with in-band registration support.**
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
Since the CheshiR examples build on each other, see previous sections for
|
|
||||||
corrections to code that is not marked as new in the book example.
|
|
||||||
|
|
||||||
After applying the changes from Example 14-4 above, the registrable component
|
|
||||||
implementation should work correctly.
|
|
||||||
|
|
||||||
.. tip::
|
|
||||||
To see how to implement in-band registration as a SleekXMPP plugin,
|
|
||||||
see the tutorial :ref:`tutorial-create-plugin`.
|
|
||||||
|
|
||||||
`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/RegistrableComponent.py>`_ |
|
|
||||||
`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/RegistrableComponent.py>`_
|
|
||||||
|
|
||||||
Example 14-7. (Page 225)
|
|
||||||
------------------------
|
|
||||||
**Extended CheshiR IM server component implementation.**
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
Since the CheshiR examples build on each other, see previous
|
|
||||||
sections for corrections to code that is not marked as new in the book
|
|
||||||
example.
|
|
||||||
|
|
||||||
While the final code example can look daunting with all of the changes
|
|
||||||
made, it requires very few modifications to work with the latest version of
|
|
||||||
SleekXMPP. Most differences are the result of CheshiR's backend functions
|
|
||||||
expecting JIDs to be strings so that they can be stripped to bare JIDs. To
|
|
||||||
resolve these, use the ``jid`` attribute of the JID objects. Also,
|
|
||||||
references to ``"message"`` and ``"jid"`` attributes need to
|
|
||||||
be changed to either ``"body"`` or ``"status"``, and either
|
|
||||||
``"from"`` or ``"to"`` depending on if the object is a message
|
|
||||||
or presence stanza and which of the JIDs from the stanza is needed.
|
|
||||||
|
|
||||||
Updated Code
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def handleIncomingXMPPMessage(self, event) :
|
|
||||||
message = self.addRecipientToMessage(event["body"], event["to"].jid)
|
|
||||||
user = self.backend.getUserFromJID(event["from"].jid)
|
|
||||||
self.backend.addMessageFromUser(message, user)
|
|
||||||
|
|
||||||
def handleIncomingXMPPPresence(self, event) :
|
|
||||||
if event["to"].jid == self.componentDomain :
|
|
||||||
user = self.backend.getUserFromJID(event["from"].jid)
|
|
||||||
self.backend.addMessageFromUser(event["status"], user)
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
def handleXMPPPresenceSubscription(self, subscription) :
|
|
||||||
if subscription["type"] == "subscribe" :
|
|
||||||
userJID = subscription["from"].jid
|
|
||||||
user = self.backend.getUserFromJID(userJID)
|
|
||||||
contactJID = subscription["to"]
|
|
||||||
self.xmpp.sendPresenceSubscription(
|
|
||||||
pfrom=contactJID, pto=userJID, ptype="subscribed", pnick=user)
|
|
||||||
self.sendPresenceOfContactToUser(contactJID=contactJID, userJID=userJID)
|
|
||||||
if contactJID == self.componentDomain :
|
|
||||||
self.sendAllContactSubscriptionRequestsToUser(userJID)
|
|
||||||
|
|
||||||
`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/Component.py>`_ |
|
|
||||||
`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/Component.py>`_
|
|
||||||
48
example.py
Normal file
48
example.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# coding=utf8
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
import logging
|
||||||
|
from optparse import OptionParser
|
||||||
|
import time
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if sys.version_info < (3,0):
|
||||||
|
reload(sys)
|
||||||
|
sys.setdefaultencoding('utf8')
|
||||||
|
|
||||||
|
|
||||||
|
class Example(sleekxmpp.ClientXMPP):
|
||||||
|
|
||||||
|
def __init__(self, jid, password):
|
||||||
|
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||||
|
self.add_event_handler("session_start", self.start)
|
||||||
|
self.add_event_handler("message", self.message)
|
||||||
|
|
||||||
|
def start(self, event):
|
||||||
|
self.getRoster()
|
||||||
|
self.sendPresence()
|
||||||
|
|
||||||
|
def message(self, msg):
|
||||||
|
msg.reply("Thanks for sending\n%(body)s" % msg).send()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
#parse command line arguements
|
||||||
|
optp = OptionParser()
|
||||||
|
optp.add_option('-q','--quiet', help='set logging to ERROR', action='store_const', dest='loglevel', const=logging.ERROR, default=logging.INFO)
|
||||||
|
optp.add_option('-d','--debug', help='set logging to DEBUG', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO)
|
||||||
|
optp.add_option('-v','--verbose', help='set logging to COMM', action='store_const', dest='loglevel', const=5, default=logging.INFO)
|
||||||
|
optp.add_option("-c","--config", dest="configfile", default="config.xml", help="set config file to use")
|
||||||
|
opts,args = optp.parse_args()
|
||||||
|
|
||||||
|
logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
|
||||||
|
xmpp = Example('user@gmail.com/sleekxmpp', 'password')
|
||||||
|
xmpp.registerPlugin('xep_0030')
|
||||||
|
xmpp.registerPlugin('xep_0004')
|
||||||
|
xmpp.registerPlugin('xep_0060')
|
||||||
|
xmpp.registerPlugin('xep_0199')
|
||||||
|
if xmpp.connect(('talk.google.com', 5222)):
|
||||||
|
xmpp.process(threaded=False)
|
||||||
|
print("done")
|
||||||
|
else:
|
||||||
|
print("Unable to connect.")
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2010 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import getpass
|
|
||||||
from optparse import OptionParser
|
|
||||||
|
|
||||||
import sleekxmpp
|
|
||||||
|
|
||||||
# Python versions before 3.0 do not use UTF-8 encoding
|
|
||||||
# by default. To ensure that Unicode is handled properly
|
|
||||||
# throughout SleekXMPP, we will set the default encoding
|
|
||||||
# ourselves to UTF-8.
|
|
||||||
if sys.version_info < (3, 0):
|
|
||||||
reload(sys)
|
|
||||||
sys.setdefaultencoding('utf8')
|
|
||||||
else:
|
|
||||||
raw_input = input
|
|
||||||
|
|
||||||
|
|
||||||
class CommandBot(sleekxmpp.ClientXMPP):
|
|
||||||
|
|
||||||
"""
|
|
||||||
A simple SleekXMPP bot that provides a basic
|
|
||||||
adhoc command.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, jid, password):
|
|
||||||
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
|
||||||
|
|
||||||
# The session_start event will be triggered when
|
|
||||||
# the bot establishes its connection with the server
|
|
||||||
# and the XML streams are ready for use. We want to
|
|
||||||
# listen for this event so that we we can initialize
|
|
||||||
# our roster.
|
|
||||||
self.add_event_handler("session_start", self.start)
|
|
||||||
|
|
||||||
def start(self, event):
|
|
||||||
"""
|
|
||||||
Process the session_start event.
|
|
||||||
|
|
||||||
Typical actions for the session_start event are
|
|
||||||
requesting the roster and broadcasting an initial
|
|
||||||
presence stanza.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
event -- An empty dictionary. The session_start
|
|
||||||
event does not provide any additional
|
|
||||||
data.
|
|
||||||
"""
|
|
||||||
self.send_presence()
|
|
||||||
self.get_roster()
|
|
||||||
|
|
||||||
# We add the command after session_start has fired
|
|
||||||
# to ensure that the correct full JID is used.
|
|
||||||
|
|
||||||
# If using a component, may also pass jid keyword parameter.
|
|
||||||
|
|
||||||
self['xep_0050'].add_command(node='greeting',
|
|
||||||
name='Greeting',
|
|
||||||
handler=self._handle_command)
|
|
||||||
|
|
||||||
def _handle_command(self, iq, session):
|
|
||||||
"""
|
|
||||||
Respond to the initial request for a command.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
iq -- The iq stanza containing the command request.
|
|
||||||
session -- A dictionary of data relevant to the command
|
|
||||||
session. Additional, custom data may be saved
|
|
||||||
here to persist across handler callbacks.
|
|
||||||
"""
|
|
||||||
form = self['xep_0004'].makeForm('form', 'Greeting')
|
|
||||||
form['instructions'] = 'Send a custom greeting to a JID'
|
|
||||||
form.addField(var='greeting',
|
|
||||||
ftype='text-single',
|
|
||||||
label='Your greeting')
|
|
||||||
|
|
||||||
session['payload'] = form
|
|
||||||
session['next'] = self._handle_command_complete
|
|
||||||
session['has_next'] = False
|
|
||||||
|
|
||||||
# Other useful session values:
|
|
||||||
# session['to'] -- The JID that received the
|
|
||||||
# command request.
|
|
||||||
# session['from'] -- The JID that sent the
|
|
||||||
# command request.
|
|
||||||
# session['has_next'] = True -- There are more steps to complete
|
|
||||||
# session['allow_complete'] = True -- Allow user to finish immediately
|
|
||||||
# and possibly skip steps
|
|
||||||
# session['cancel'] = handler -- Assign a handler for if the user
|
|
||||||
# cancels the command.
|
|
||||||
# session['notes'] = [ -- Add informative notes about the
|
|
||||||
# ('info', 'Info message'), command's results.
|
|
||||||
# ('warning', 'Warning message'),
|
|
||||||
# ('error', 'Error message')]
|
|
||||||
|
|
||||||
return session
|
|
||||||
|
|
||||||
def _handle_command_complete(self, payload, session):
|
|
||||||
"""
|
|
||||||
Process a command result from the user.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
payload -- Either a single item, such as a form, or a list
|
|
||||||
of items or forms if more than one form was
|
|
||||||
provided to the user. The payload may be any
|
|
||||||
stanza, such as jabber:x:oob for out of band
|
|
||||||
data, or jabber:x:data for typical data forms.
|
|
||||||
session -- A dictionary of data relevant to the command
|
|
||||||
session. Additional, custom data may be saved
|
|
||||||
here to persist across handler callbacks.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# In this case (as is typical), the payload is a form
|
|
||||||
form = payload
|
|
||||||
|
|
||||||
greeting = form['values']['greeting']
|
|
||||||
|
|
||||||
self.send_message(mto=session['from'],
|
|
||||||
mbody="%s, World!" % greeting,
|
|
||||||
mtype='chat')
|
|
||||||
|
|
||||||
# Having no return statement is the same as unsetting the 'payload'
|
|
||||||
# and 'next' session values and returning the session.
|
|
||||||
|
|
||||||
# Unless it is the final step, always return the session dictionary.
|
|
||||||
|
|
||||||
session['payload'] = None
|
|
||||||
session['next'] = None
|
|
||||||
|
|
||||||
return session
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Setup the command line arguments.
|
|
||||||
optp = OptionParser()
|
|
||||||
|
|
||||||
# Output verbosity options.
|
|
||||||
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=logging.ERROR, default=logging.INFO)
|
|
||||||
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=logging.DEBUG, default=logging.INFO)
|
|
||||||
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=5, default=logging.INFO)
|
|
||||||
|
|
||||||
# JID and password options.
|
|
||||||
optp.add_option("-j", "--jid", dest="jid",
|
|
||||||
help="JID to use")
|
|
||||||
optp.add_option("-p", "--password", dest="password",
|
|
||||||
help="password to use")
|
|
||||||
|
|
||||||
opts, args = optp.parse_args()
|
|
||||||
|
|
||||||
# Setup logging.
|
|
||||||
logging.basicConfig(level=opts.loglevel,
|
|
||||||
format='%(levelname)-8s %(message)s')
|
|
||||||
|
|
||||||
if opts.jid is None:
|
|
||||||
opts.jid = raw_input("Username: ")
|
|
||||||
if opts.password is None:
|
|
||||||
opts.password = getpass.getpass("Password: ")
|
|
||||||
|
|
||||||
# Setup the CommandBot and register plugins. Note that while plugins may
|
|
||||||
# have interdependencies, the order in which you register them does
|
|
||||||
# not matter.
|
|
||||||
xmpp = CommandBot(opts.jid, opts.password)
|
|
||||||
xmpp.register_plugin('xep_0030') # Service Discovery
|
|
||||||
xmpp.register_plugin('xep_0004') # Data Forms
|
|
||||||
xmpp.register_plugin('xep_0050') # Adhoc Commands
|
|
||||||
xmpp.register_plugin('xep_0199', {'keepalive': True, 'frequency':15})
|
|
||||||
|
|
||||||
# If you are working with an OpenFire server, you may need
|
|
||||||
# to adjust the SSL version used:
|
|
||||||
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
|
||||||
|
|
||||||
# If you want to verify the SSL certificates offered by a server:
|
|
||||||
# xmpp.ca_certs = "path/to/ca/cert"
|
|
||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
|
||||||
if xmpp.connect():
|
|
||||||
# If you do not have the dnspython library installed, you will need
|
|
||||||
# to manually specify the name of the server if it does not match
|
|
||||||
# the one in the JID. For example, to use Google Talk you would
|
|
||||||
# need to use:
|
|
||||||
#
|
|
||||||
# if xmpp.connect(('talk.google.com', 5222)):
|
|
||||||
# ...
|
|
||||||
xmpp.process(block=True)
|
|
||||||
print("Done")
|
|
||||||
else:
|
|
||||||
print("Unable to connect.")
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2010 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import getpass
|
|
||||||
from optparse import OptionParser
|
|
||||||
|
|
||||||
import sleekxmpp
|
|
||||||
|
|
||||||
# Python versions before 3.0 do not use UTF-8 encoding
|
|
||||||
# by default. To ensure that Unicode is handled properly
|
|
||||||
# throughout SleekXMPP, we will set the default encoding
|
|
||||||
# ourselves to UTF-8.
|
|
||||||
if sys.version_info < (3, 0):
|
|
||||||
reload(sys)
|
|
||||||
sys.setdefaultencoding('utf8')
|
|
||||||
else:
|
|
||||||
raw_input = input
|
|
||||||
|
|
||||||
|
|
||||||
class CommandUserBot(sleekxmpp.ClientXMPP):
|
|
||||||
|
|
||||||
"""
|
|
||||||
A simple SleekXMPP bot that uses the adhoc command
|
|
||||||
provided by the adhoc_provider.py example.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, jid, password, other, greeting):
|
|
||||||
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
|
||||||
|
|
||||||
self.command_provider = other
|
|
||||||
self.greeting = greeting
|
|
||||||
|
|
||||||
# The session_start event will be triggered when
|
|
||||||
# the bot establishes its connection with the server
|
|
||||||
# and the XML streams are ready for use. We want to
|
|
||||||
# listen for this event so that we we can initialize
|
|
||||||
# our roster.
|
|
||||||
self.add_event_handler("session_start", self.start)
|
|
||||||
self.add_event_handler("message", self.message)
|
|
||||||
|
|
||||||
def start(self, event):
|
|
||||||
"""
|
|
||||||
Process the session_start event.
|
|
||||||
|
|
||||||
Typical actions for the session_start event are
|
|
||||||
requesting the roster and broadcasting an initial
|
|
||||||
presence stanza.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
event -- An empty dictionary. The session_start
|
|
||||||
event does not provide any additional
|
|
||||||
data.
|
|
||||||
"""
|
|
||||||
self.send_presence()
|
|
||||||
self.get_roster()
|
|
||||||
|
|
||||||
# We first create a session dictionary containing:
|
|
||||||
# 'next' -- the handler to execute on a successful response
|
|
||||||
# 'error' -- the handler to execute if an error occurs
|
|
||||||
|
|
||||||
# The session may also contain custom data.
|
|
||||||
|
|
||||||
session = {'greeting': self.greeting,
|
|
||||||
'next': self._command_start,
|
|
||||||
'error': self._command_error}
|
|
||||||
|
|
||||||
self['xep_0050'].start_command(jid=self.command_provider,
|
|
||||||
node='greeting',
|
|
||||||
session=session)
|
|
||||||
|
|
||||||
def message(self, msg):
|
|
||||||
"""
|
|
||||||
Process incoming message stanzas.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
msg -- The received message stanza.
|
|
||||||
"""
|
|
||||||
logging.info(msg['body'])
|
|
||||||
|
|
||||||
def _command_start(self, iq, session):
|
|
||||||
"""
|
|
||||||
Process the initial command result.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
iq -- The iq stanza containing the command result.
|
|
||||||
session -- A dictionary of data relevant to the command
|
|
||||||
session. Additional, custom data may be saved
|
|
||||||
here to persist across handler callbacks.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# The greeting command provides a form with a single field:
|
|
||||||
# <x xmlns="jabber:x:data" type="form">
|
|
||||||
# <field var="greeting"
|
|
||||||
# type="text-single"
|
|
||||||
# label="Your greeting" />
|
|
||||||
# </x>
|
|
||||||
|
|
||||||
form = self['xep_0004'].makeForm(ftype='submit')
|
|
||||||
form.addField(var='greeting',
|
|
||||||
value=session['greeting'])
|
|
||||||
|
|
||||||
session['payload'] = form
|
|
||||||
|
|
||||||
# We don't need to process the next result.
|
|
||||||
session['next'] = None
|
|
||||||
|
|
||||||
# Other options include using:
|
|
||||||
# continue_command() -- Continue to the next step in the workflow
|
|
||||||
# cancel_command() -- Stop command execution.
|
|
||||||
|
|
||||||
self['xep_0050'].complete_command(session)
|
|
||||||
|
|
||||||
def _command_error(self, iq, session):
|
|
||||||
"""
|
|
||||||
Process an error that occurs during command execution.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
iq -- The iq stanza containing the error.
|
|
||||||
session -- A dictionary of data relevant to the command
|
|
||||||
session. Additional, custom data may be saved
|
|
||||||
here to persist across handler callbacks.
|
|
||||||
"""
|
|
||||||
logging.error("COMMAND: %s %s" % (iq['error']['condition'],
|
|
||||||
iq['error']['text']))
|
|
||||||
|
|
||||||
# Terminate the command's execution and clear its session.
|
|
||||||
# The session will automatically be cleared if no error
|
|
||||||
# handler is provided.
|
|
||||||
self['xep_0050'].terminate_command(session)
|
|
||||||
self.disconnect()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Setup the command line arguments.
|
|
||||||
optp = OptionParser()
|
|
||||||
|
|
||||||
# Output verbosity options.
|
|
||||||
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=logging.ERROR, default=logging.INFO)
|
|
||||||
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=logging.DEBUG, default=logging.INFO)
|
|
||||||
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=5, default=logging.INFO)
|
|
||||||
|
|
||||||
# JID and password options.
|
|
||||||
optp.add_option("-j", "--jid", dest="jid",
|
|
||||||
help="JID to use")
|
|
||||||
optp.add_option("-p", "--password", dest="password",
|
|
||||||
help="password to use")
|
|
||||||
optp.add_option("-o", "--other", dest="other",
|
|
||||||
help="JID providing commands")
|
|
||||||
optp.add_option("-g", "--greeting", dest="greeting",
|
|
||||||
help="Greeting")
|
|
||||||
|
|
||||||
opts, args = optp.parse_args()
|
|
||||||
|
|
||||||
# Setup logging.
|
|
||||||
logging.basicConfig(level=opts.loglevel,
|
|
||||||
format='%(levelname)-8s %(message)s')
|
|
||||||
|
|
||||||
if opts.jid is None:
|
|
||||||
opts.jid = raw_input("Username: ")
|
|
||||||
if opts.password is None:
|
|
||||||
opts.password = getpass.getpass("Password: ")
|
|
||||||
if opts.other is None:
|
|
||||||
opts.other = raw_input("JID Providing Commands: ")
|
|
||||||
if opts.greeting is None:
|
|
||||||
opts.greeting = raw_input("Greeting: ")
|
|
||||||
|
|
||||||
# Setup the CommandBot and register plugins. Note that while plugins may
|
|
||||||
# have interdependencies, the order in which you register them does
|
|
||||||
# not matter.
|
|
||||||
xmpp = CommandUserBot(opts.jid, opts.password, opts.other, opts.greeting)
|
|
||||||
xmpp.register_plugin('xep_0030') # Service Discovery
|
|
||||||
xmpp.register_plugin('xep_0004') # Data Forms
|
|
||||||
xmpp.register_plugin('xep_0050') # Adhoc Commands
|
|
||||||
|
|
||||||
# If you are working with an OpenFire server, you may need
|
|
||||||
# to adjust the SSL version used:
|
|
||||||
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
|
||||||
|
|
||||||
# If you want to verify the SSL certificates offered by a server:
|
|
||||||
# xmpp.ca_certs = "path/to/ca/cert"
|
|
||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
|
||||||
if xmpp.connect():
|
|
||||||
# If you do not have the dnspython library installed, you will need
|
|
||||||
# to manually specify the name of the server if it does not match
|
|
||||||
# the one in the JID. For example, to use Google Talk you would
|
|
||||||
# need to use:
|
|
||||||
#
|
|
||||||
# if xmpp.connect(('talk.google.com', 5222)):
|
|
||||||
# ...
|
|
||||||
xmpp.process(block=True)
|
|
||||||
print("Done")
|
|
||||||
else:
|
|
||||||
print("Unable to connect.")
|
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2010 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import getpass
|
|
||||||
from optparse import OptionParser
|
|
||||||
|
|
||||||
import sleekxmpp
|
|
||||||
|
|
||||||
|
|
||||||
# Python versions before 3.0 do not use UTF-8 encoding
|
|
||||||
# by default. To ensure that Unicode is handled properly
|
|
||||||
# throughout SleekXMPP, we will set the default encoding
|
|
||||||
# ourselves to UTF-8.
|
|
||||||
if sys.version_info < (3, 0):
|
|
||||||
reload(sys)
|
|
||||||
sys.setdefaultencoding('utf8')
|
|
||||||
else:
|
|
||||||
raw_input = input
|
|
||||||
|
|
||||||
|
|
||||||
class Disco(sleekxmpp.ClientXMPP):
|
|
||||||
|
|
||||||
"""
|
|
||||||
A demonstration for using basic service discovery.
|
|
||||||
|
|
||||||
Send a disco#info and disco#items request to a JID/node combination,
|
|
||||||
and print out the results.
|
|
||||||
|
|
||||||
May also request only particular info categories such as just features,
|
|
||||||
or just items.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, jid, password, target_jid, target_node='', get=''):
|
|
||||||
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
|
||||||
|
|
||||||
# Using service discovery requires the XEP-0030 plugin.
|
|
||||||
self.register_plugin('xep_0030')
|
|
||||||
|
|
||||||
self.get = get
|
|
||||||
self.target_jid = target_jid
|
|
||||||
self.target_node = target_node
|
|
||||||
|
|
||||||
# Values to control which disco entities are reported
|
|
||||||
self.info_types = ['', 'all', 'info', 'identities', 'features']
|
|
||||||
self.identity_types = ['', 'all', 'info', 'identities']
|
|
||||||
self.feature_types = ['', 'all', 'info', 'features']
|
|
||||||
self.items_types = ['', 'all', 'items']
|
|
||||||
|
|
||||||
|
|
||||||
# The session_start event will be triggered when
|
|
||||||
# the bot establishes its connection with the server
|
|
||||||
# and the XML streams are ready for use. We want to
|
|
||||||
# listen for this event so that we we can initialize
|
|
||||||
# our roster.
|
|
||||||
self.add_event_handler("session_start", self.start)
|
|
||||||
|
|
||||||
def start(self, event):
|
|
||||||
"""
|
|
||||||
Process the session_start event.
|
|
||||||
|
|
||||||
Typical actions for the session_start event are
|
|
||||||
requesting the roster and broadcasting an initial
|
|
||||||
presence stanza.
|
|
||||||
|
|
||||||
In this case, we send disco#info and disco#items
|
|
||||||
stanzas to the requested JID and print the results.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
event -- An empty dictionary. The session_start
|
|
||||||
event does not provide any additional
|
|
||||||
data.
|
|
||||||
"""
|
|
||||||
self.get_roster()
|
|
||||||
self.send_presence()
|
|
||||||
|
|
||||||
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 = self['xep_0030'].get_info(jid=self.target_jid,
|
|
||||||
node=self.target_node,
|
|
||||||
block=True)
|
|
||||||
if self.get in self.items_types:
|
|
||||||
# The same applies from above. Listen for the
|
|
||||||
# disco_items event or pass a callback function
|
|
||||||
# if you need to process a non-blocking request.
|
|
||||||
items = self['xep_0030'].get_items(jid=self.target_jid,
|
|
||||||
node=self.target_node,
|
|
||||||
block=True)
|
|
||||||
else:
|
|
||||||
logging.error("Invalid disco request type.")
|
|
||||||
self.disconnect()
|
|
||||||
return
|
|
||||||
|
|
||||||
header = 'XMPP Service Discovery: %s' % self.target_jid
|
|
||||||
print(header)
|
|
||||||
print('-' * len(header))
|
|
||||||
if self.target_node != '':
|
|
||||||
print('Node: %s' % self.target_node)
|
|
||||||
print('-' * len(header))
|
|
||||||
|
|
||||||
if self.get in self.identity_types:
|
|
||||||
print('Identities:')
|
|
||||||
for identity in info['disco_info']['identities']:
|
|
||||||
print(' - %s' % str(identity))
|
|
||||||
|
|
||||||
if self.get in self.feature_types:
|
|
||||||
print('Features:')
|
|
||||||
for feature in info['disco_info']['features']:
|
|
||||||
print(' - %s' % feature)
|
|
||||||
|
|
||||||
if self.get in self.items_types:
|
|
||||||
print('Items:')
|
|
||||||
for item in items['disco_items']['items']:
|
|
||||||
print(' - %s' % str(item))
|
|
||||||
|
|
||||||
self.disconnect()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Setup the command line arguments.
|
|
||||||
optp = OptionParser()
|
|
||||||
optp.version = '%%prog 0.1'
|
|
||||||
optp.usage = "Usage: %%prog [options] %s <jid> [<node>]" % \
|
|
||||||
'all|info|items|identities|features'
|
|
||||||
|
|
||||||
optp.add_option('-q','--quiet', help='set logging to ERROR',
|
|
||||||
action='store_const',
|
|
||||||
dest='loglevel',
|
|
||||||
const=logging.ERROR,
|
|
||||||
default=logging.ERROR)
|
|
||||||
optp.add_option('-d','--debug', help='set logging to DEBUG',
|
|
||||||
action='store_const',
|
|
||||||
dest='loglevel',
|
|
||||||
const=logging.DEBUG,
|
|
||||||
default=logging.ERROR)
|
|
||||||
optp.add_option('-v','--verbose', help='set logging to COMM',
|
|
||||||
action='store_const',
|
|
||||||
dest='loglevel',
|
|
||||||
const=5,
|
|
||||||
default=logging.ERROR)
|
|
||||||
|
|
||||||
# JID and password options.
|
|
||||||
optp.add_option("-j", "--jid", dest="jid",
|
|
||||||
help="JID to use")
|
|
||||||
optp.add_option("-p", "--password", dest="password",
|
|
||||||
help="password to use")
|
|
||||||
opts,args = optp.parse_args()
|
|
||||||
|
|
||||||
# Setup logging.
|
|
||||||
logging.basicConfig(level=opts.loglevel,
|
|
||||||
format='%(levelname)-8s %(message)s')
|
|
||||||
|
|
||||||
if len(args) < 2:
|
|
||||||
optp.print_help()
|
|
||||||
exit()
|
|
||||||
|
|
||||||
if len(args) == 2:
|
|
||||||
args = (args[0], args[1], '')
|
|
||||||
|
|
||||||
if opts.jid is None:
|
|
||||||
opts.jid = raw_input("Username: ")
|
|
||||||
if opts.password is None:
|
|
||||||
opts.password = getpass.getpass("Password: ")
|
|
||||||
|
|
||||||
# Setup the Disco browser.
|
|
||||||
xmpp = Disco(opts.jid, opts.password, args[1], args[2], args[0])
|
|
||||||
|
|
||||||
# If you are working with an OpenFire server, you may need
|
|
||||||
# to adjust the SSL version used:
|
|
||||||
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
|
||||||
|
|
||||||
# If you want to verify the SSL certificates offered by a server:
|
|
||||||
# xmpp.ca_certs = "path/to/ca/cert"
|
|
||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
|
||||||
if xmpp.connect():
|
|
||||||
# If you do not have the dnspython library installed, you will need
|
|
||||||
# to manually specify the name of the server if it does not match
|
|
||||||
# the one in the JID. For example, to use Google Talk you would
|
|
||||||
# need to use:
|
|
||||||
#
|
|
||||||
# if xmpp.connect(('talk.google.com', 5222)):
|
|
||||||
# ...
|
|
||||||
xmpp.process(block=True)
|
|
||||||
else:
|
|
||||||
print("Unable to connect.")
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2010 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import getpass
|
|
||||||
from optparse import OptionParser
|
|
||||||
|
|
||||||
import sleekxmpp
|
|
||||||
|
|
||||||
# Python versions before 3.0 do not use UTF-8 encoding
|
|
||||||
# by default. To ensure that Unicode is handled properly
|
|
||||||
# throughout SleekXMPP, we will set the default encoding
|
|
||||||
# ourselves to UTF-8.
|
|
||||||
if sys.version_info < (3, 0):
|
|
||||||
reload(sys)
|
|
||||||
sys.setdefaultencoding('utf8')
|
|
||||||
else:
|
|
||||||
raw_input = input
|
|
||||||
|
|
||||||
|
|
||||||
class EchoBot(sleekxmpp.ClientXMPP):
|
|
||||||
|
|
||||||
"""
|
|
||||||
A simple SleekXMPP bot that will echo messages it
|
|
||||||
receives, along with a short thank you message.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, jid, password):
|
|
||||||
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
|
||||||
|
|
||||||
# The session_start event will be triggered when
|
|
||||||
# the bot establishes its connection with the server
|
|
||||||
# and the XML streams are ready for use. We want to
|
|
||||||
# listen for this event so that we we can initialize
|
|
||||||
# our roster.
|
|
||||||
self.add_event_handler("session_start", self.start)
|
|
||||||
|
|
||||||
# The message event is triggered whenever a message
|
|
||||||
# stanza is received. Be aware that that includes
|
|
||||||
# MUC messages and error messages.
|
|
||||||
self.add_event_handler("message", self.message)
|
|
||||||
|
|
||||||
def start(self, event):
|
|
||||||
"""
|
|
||||||
Process the session_start event.
|
|
||||||
|
|
||||||
Typical actions for the session_start event are
|
|
||||||
requesting the roster and broadcasting an initial
|
|
||||||
presence stanza.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
event -- An empty dictionary. The session_start
|
|
||||||
event does not provide any additional
|
|
||||||
data.
|
|
||||||
"""
|
|
||||||
self.send_presence()
|
|
||||||
self.get_roster()
|
|
||||||
|
|
||||||
def message(self, msg):
|
|
||||||
"""
|
|
||||||
Process incoming message stanzas. Be aware that this also
|
|
||||||
includes MUC messages and error messages. It is usually
|
|
||||||
a good idea to check the messages's type before processing
|
|
||||||
or sending replies.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
msg -- The received message stanza. See the documentation
|
|
||||||
for stanza objects and the Message stanza to see
|
|
||||||
how it may be used.
|
|
||||||
"""
|
|
||||||
if msg['type'] in ('chat', 'normal'):
|
|
||||||
msg.reply("Thanks for sending\n%(body)s" % msg).send()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Setup the command line arguments.
|
|
||||||
optp = OptionParser()
|
|
||||||
|
|
||||||
# Output verbosity options.
|
|
||||||
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=logging.ERROR, default=logging.INFO)
|
|
||||||
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=logging.DEBUG, default=logging.INFO)
|
|
||||||
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=5, default=logging.INFO)
|
|
||||||
|
|
||||||
# JID and password options.
|
|
||||||
optp.add_option("-j", "--jid", dest="jid",
|
|
||||||
help="JID to use")
|
|
||||||
optp.add_option("-p", "--password", dest="password",
|
|
||||||
help="password to use")
|
|
||||||
|
|
||||||
opts, args = optp.parse_args()
|
|
||||||
|
|
||||||
# Setup logging.
|
|
||||||
logging.basicConfig(level=opts.loglevel,
|
|
||||||
format='%(levelname)-8s %(message)s')
|
|
||||||
|
|
||||||
if opts.jid is None:
|
|
||||||
opts.jid = raw_input("Username: ")
|
|
||||||
if opts.password is None:
|
|
||||||
opts.password = getpass.getpass("Password: ")
|
|
||||||
|
|
||||||
# Setup the EchoBot and register plugins. Note that while plugins may
|
|
||||||
# have interdependencies, the order in which you register them does
|
|
||||||
# not matter.
|
|
||||||
xmpp = EchoBot(opts.jid, opts.password)
|
|
||||||
xmpp.register_plugin('xep_0030') # Service Discovery
|
|
||||||
xmpp.register_plugin('xep_0004') # Data Forms
|
|
||||||
xmpp.register_plugin('xep_0060') # PubSub
|
|
||||||
xmpp.register_plugin('xep_0199') # XMPP Ping
|
|
||||||
|
|
||||||
# If you are working with an OpenFire server, you may need
|
|
||||||
# to adjust the SSL version used:
|
|
||||||
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
|
||||||
|
|
||||||
# If you want to verify the SSL certificates offered by a server:
|
|
||||||
# xmpp.ca_certs = "path/to/ca/cert"
|
|
||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
|
||||||
if xmpp.connect():
|
|
||||||
# If you do not have the dnspython library installed, you will need
|
|
||||||
# to manually specify the name of the server if it does not match
|
|
||||||
# the one in the JID. For example, to use Google Talk you would
|
|
||||||
# need to use:
|
|
||||||
#
|
|
||||||
# if xmpp.connect(('talk.google.com', 5222)):
|
|
||||||
# ...
|
|
||||||
xmpp.process(block=True)
|
|
||||||
print("Done")
|
|
||||||
else:
|
|
||||||
print("Unable to connect.")
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2010 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import getpass
|
|
||||||
from optparse import OptionParser
|
|
||||||
|
|
||||||
import sleekxmpp
|
|
||||||
from sleekxmpp.componentxmpp import ComponentXMPP
|
|
||||||
|
|
||||||
# Python versions before 3.0 do not use UTF-8 encoding
|
|
||||||
# by default. To ensure that Unicode is handled properly
|
|
||||||
# throughout SleekXMPP, we will set the default encoding
|
|
||||||
# ourselves to UTF-8.
|
|
||||||
if sys.version_info < (3, 0):
|
|
||||||
reload(sys)
|
|
||||||
sys.setdefaultencoding('utf8')
|
|
||||||
else:
|
|
||||||
raw_input = input
|
|
||||||
|
|
||||||
|
|
||||||
class EchoComponent(ComponentXMPP):
|
|
||||||
|
|
||||||
"""
|
|
||||||
A simple SleekXMPP component that echoes messages.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, jid, secret, server, port):
|
|
||||||
ComponentXMPP.__init__(self, jid, secret, server, port)
|
|
||||||
|
|
||||||
# You don't need a session_start handler, but that is
|
|
||||||
# where you would broadcast initial presence.
|
|
||||||
|
|
||||||
# The message event is triggered whenever a message
|
|
||||||
# stanza is received. Be aware that that includes
|
|
||||||
# MUC messages and error messages.
|
|
||||||
self.add_event_handler("message", self.message)
|
|
||||||
|
|
||||||
def message(self, msg):
|
|
||||||
"""
|
|
||||||
Process incoming message stanzas. Be aware that this also
|
|
||||||
includes MUC messages and error messages. It is usually
|
|
||||||
a good idea to check the messages's type before processing
|
|
||||||
or sending replies.
|
|
||||||
|
|
||||||
Since a component may send messages from any number of JIDs,
|
|
||||||
it is best to always include a from JID.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
msg -- The received message stanza. See the documentation
|
|
||||||
for stanza objects and the Message stanza to see
|
|
||||||
how it may be used.
|
|
||||||
"""
|
|
||||||
# The reply method will use the messages 'to' JID as the
|
|
||||||
# outgoing reply's 'from' JID.
|
|
||||||
msg.reply("Thanks for sending\n%(body)s" % msg).send()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Setup the command line arguments.
|
|
||||||
optp = OptionParser()
|
|
||||||
|
|
||||||
# Output verbosity options.
|
|
||||||
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=logging.ERROR, default=logging.INFO)
|
|
||||||
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=logging.DEBUG, default=logging.INFO)
|
|
||||||
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=5, default=logging.INFO)
|
|
||||||
|
|
||||||
# JID and password options.
|
|
||||||
optp.add_option("-j", "--jid", dest="jid",
|
|
||||||
help="JID to use")
|
|
||||||
optp.add_option("-p", "--password", dest="password",
|
|
||||||
help="password to use")
|
|
||||||
optp.add_option("-s", "--server", dest="server",
|
|
||||||
help="server to connect to")
|
|
||||||
optp.add_option("-P", "--port", dest="port",
|
|
||||||
help="port to connect to")
|
|
||||||
|
|
||||||
opts, args = optp.parse_args()
|
|
||||||
|
|
||||||
if opts.jid is None:
|
|
||||||
opts.jid = raw_input("Component JID: ")
|
|
||||||
if opts.password is None:
|
|
||||||
opts.password = getpass.getpass("Password: ")
|
|
||||||
if opts.server is None:
|
|
||||||
opts.server = raw_input("Server: ")
|
|
||||||
if opts.port is None:
|
|
||||||
opts.port = int(raw_input("Port: "))
|
|
||||||
|
|
||||||
# Setup logging.
|
|
||||||
logging.basicConfig(level=opts.loglevel,
|
|
||||||
format='%(levelname)-8s %(message)s')
|
|
||||||
|
|
||||||
# Setup the EchoComponent and register plugins. Note that while plugins
|
|
||||||
# may have interdependencies, the order in which you register them does
|
|
||||||
# not matter.
|
|
||||||
xmpp = EchoComponent(opts.jid, opts.password, opts.server, opts.port)
|
|
||||||
xmpp.registerPlugin('xep_0030') # Service Discovery
|
|
||||||
xmpp.registerPlugin('xep_0004') # Data Forms
|
|
||||||
xmpp.registerPlugin('xep_0060') # PubSub
|
|
||||||
xmpp.registerPlugin('xep_0199') # XMPP Ping
|
|
||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
|
||||||
if xmpp.connect():
|
|
||||||
xmpp.process(block=True)
|
|
||||||
print("Done")
|
|
||||||
else:
|
|
||||||
print("Unable to connect.")
|
|
||||||
193
examples/muc.py
193
examples/muc.py
@@ -1,193 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2010 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import getpass
|
|
||||||
from optparse import OptionParser
|
|
||||||
|
|
||||||
import sleekxmpp
|
|
||||||
|
|
||||||
# Python versions before 3.0 do not use UTF-8 encoding
|
|
||||||
# by default. To ensure that Unicode is handled properly
|
|
||||||
# throughout SleekXMPP, we will set the default encoding
|
|
||||||
# ourselves to UTF-8.
|
|
||||||
if sys.version_info < (3, 0):
|
|
||||||
reload(sys)
|
|
||||||
sys.setdefaultencoding('utf8')
|
|
||||||
else:
|
|
||||||
raw_input = input
|
|
||||||
|
|
||||||
|
|
||||||
class MUCBot(sleekxmpp.ClientXMPP):
|
|
||||||
|
|
||||||
"""
|
|
||||||
A simple SleekXMPP bot that will greets those
|
|
||||||
who enter the room, and acknowledge any messages
|
|
||||||
that mentions the bot's nickname.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, jid, password, room, nick):
|
|
||||||
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
|
||||||
|
|
||||||
self.room = room
|
|
||||||
self.nick = nick
|
|
||||||
|
|
||||||
# The session_start event will be triggered when
|
|
||||||
# the bot establishes its connection with the server
|
|
||||||
# and the XML streams are ready for use. We want to
|
|
||||||
# listen for this event so that we we can initialize
|
|
||||||
# our roster.
|
|
||||||
self.add_event_handler("session_start", self.start)
|
|
||||||
|
|
||||||
# The groupchat_message event is triggered whenever a message
|
|
||||||
# stanza is received from any chat room. If you also also
|
|
||||||
# register a handler for the 'message' event, MUC messages
|
|
||||||
# will be processed by both handlers.
|
|
||||||
self.add_event_handler("groupchat_message", self.muc_message)
|
|
||||||
|
|
||||||
# The groupchat_presence event is triggered whenever a
|
|
||||||
# presence stanza is received from any chat room, including
|
|
||||||
# any presences you send yourself. To limit event handling
|
|
||||||
# to a single room, use the events muc::room@server::presence,
|
|
||||||
# muc::room@server::got_online, or muc::room@server::got_offline.
|
|
||||||
self.add_event_handler("muc::%s::got_online" % self.room,
|
|
||||||
self.muc_online)
|
|
||||||
|
|
||||||
|
|
||||||
def start(self, event):
|
|
||||||
"""
|
|
||||||
Process the session_start event.
|
|
||||||
|
|
||||||
Typical actions for the session_start event are
|
|
||||||
requesting the roster and broadcasting an initial
|
|
||||||
presence stanza.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
event -- An empty dictionary. The session_start
|
|
||||||
event does not provide any additional
|
|
||||||
data.
|
|
||||||
"""
|
|
||||||
self.getRoster()
|
|
||||||
self.sendPresence()
|
|
||||||
self.plugin['xep_0045'].joinMUC(self.room,
|
|
||||||
self.nick,
|
|
||||||
# If a room password is needed, use:
|
|
||||||
# password=the_room_password,
|
|
||||||
wait=True)
|
|
||||||
|
|
||||||
def muc_message(self, msg):
|
|
||||||
"""
|
|
||||||
Process incoming message stanzas from any chat room. Be aware
|
|
||||||
that if you also have any handlers for the 'message' event,
|
|
||||||
message stanzas may be processed by both handlers, so check
|
|
||||||
the 'type' attribute when using a 'message' event handler.
|
|
||||||
|
|
||||||
Whenever the bot's nickname is mentioned, respond to
|
|
||||||
the message.
|
|
||||||
|
|
||||||
IMPORTANT: Always check that a message is not from yourself,
|
|
||||||
otherwise you will create an infinite loop responding
|
|
||||||
to your own messages.
|
|
||||||
|
|
||||||
This handler will reply to messages that mention
|
|
||||||
the bot's nickname.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
msg -- The received message stanza. See the documentation
|
|
||||||
for stanza objects and the Message stanza to see
|
|
||||||
how it may be used.
|
|
||||||
"""
|
|
||||||
if msg['mucnick'] != self.nick and self.nick in msg['body']:
|
|
||||||
self.send_message(mto=msg['from'].bare,
|
|
||||||
mbody="I heard that, %s." % msg['mucnick'],
|
|
||||||
mtype='groupchat')
|
|
||||||
|
|
||||||
def muc_online(self, presence):
|
|
||||||
"""
|
|
||||||
Process a presence stanza from a chat room. In this case,
|
|
||||||
presences from users that have just come online are
|
|
||||||
handled by sending a welcome message that includes
|
|
||||||
the user's nickname and role in the room.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
presence -- The received presence stanza. See the
|
|
||||||
documentation for the Presence stanza
|
|
||||||
to see how else it may be used.
|
|
||||||
"""
|
|
||||||
if presence['muc']['nick'] != self.nick:
|
|
||||||
self.send_message(mto=presence['from'].bare,
|
|
||||||
mbody="Hello, %s %s" % (presence['muc']['role'],
|
|
||||||
presence['muc']['nick']),
|
|
||||||
mtype='groupchat')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Setup the command line arguments.
|
|
||||||
optp = OptionParser()
|
|
||||||
|
|
||||||
# Output verbosity options.
|
|
||||||
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=logging.ERROR, default=logging.INFO)
|
|
||||||
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=logging.DEBUG, default=logging.INFO)
|
|
||||||
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=5, default=logging.INFO)
|
|
||||||
|
|
||||||
# JID and password options.
|
|
||||||
optp.add_option("-j", "--jid", dest="jid",
|
|
||||||
help="JID to use")
|
|
||||||
optp.add_option("-p", "--password", dest="password",
|
|
||||||
help="password to use")
|
|
||||||
optp.add_option("-r", "--room", dest="room",
|
|
||||||
help="MUC room to join")
|
|
||||||
optp.add_option("-n", "--nick", dest="nick",
|
|
||||||
help="MUC nickname")
|
|
||||||
|
|
||||||
opts, args = optp.parse_args()
|
|
||||||
|
|
||||||
# Setup logging.
|
|
||||||
logging.basicConfig(level=opts.loglevel,
|
|
||||||
format='%(levelname)-8s %(message)s')
|
|
||||||
|
|
||||||
if opts.jid is None:
|
|
||||||
opts.jid = raw_input("Username: ")
|
|
||||||
if opts.password is None:
|
|
||||||
opts.password = getpass.getpass("Password: ")
|
|
||||||
if opts.room is None:
|
|
||||||
opts.room = raw_input("MUC room: ")
|
|
||||||
if opts.nick is None:
|
|
||||||
opts.nick = raw_input("MUC nickname: ")
|
|
||||||
|
|
||||||
# Setup the MUCBot and register plugins. Note that while plugins may
|
|
||||||
# have interdependencies, the order in which you register them does
|
|
||||||
# not matter.
|
|
||||||
xmpp = MUCBot(opts.jid, opts.password, opts.room, opts.nick)
|
|
||||||
xmpp.register_plugin('xep_0030') # Service Discovery
|
|
||||||
xmpp.register_plugin('xep_0045') # Multi-User Chat
|
|
||||||
xmpp.register_plugin('xep_0199') # XMPP Ping
|
|
||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
|
||||||
if xmpp.connect():
|
|
||||||
# If you do not have the dnspython library installed, you will need
|
|
||||||
# to manually specify the name of the server if it does not match
|
|
||||||
# the one in the JID. For example, to use Google Talk you would
|
|
||||||
# need to use:
|
|
||||||
#
|
|
||||||
# if xmpp.connect(('talk.google.com', 5222)):
|
|
||||||
# ...
|
|
||||||
xmpp.process(block=True)
|
|
||||||
print("Done")
|
|
||||||
else:
|
|
||||||
print("Unable to connect.")
|
|
||||||
141
examples/ping.py
141
examples/ping.py
@@ -1,141 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2010 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import getpass
|
|
||||||
from optparse import OptionParser
|
|
||||||
|
|
||||||
import sleekxmpp
|
|
||||||
|
|
||||||
# Python versions before 3.0 do not use UTF-8 encoding
|
|
||||||
# by default. To ensure that Unicode is handled properly
|
|
||||||
# throughout SleekXMPP, we will set the default encoding
|
|
||||||
# ourselves to UTF-8.
|
|
||||||
if sys.version_info < (3, 0):
|
|
||||||
reload(sys)
|
|
||||||
sys.setdefaultencoding('utf8')
|
|
||||||
else:
|
|
||||||
raw_input = input
|
|
||||||
|
|
||||||
|
|
||||||
class PingTest(sleekxmpp.ClientXMPP):
|
|
||||||
|
|
||||||
"""
|
|
||||||
A simple SleekXMPP bot that will send a ping request
|
|
||||||
to a given JID.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, jid, password, pingjid):
|
|
||||||
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
|
||||||
if pingjid is None:
|
|
||||||
pingjid = self.jid
|
|
||||||
self.pingjid = pingjid
|
|
||||||
|
|
||||||
# The session_start event will be triggered when
|
|
||||||
# the bot establishes its connection with the server
|
|
||||||
# and the XML streams are ready for use. We want to
|
|
||||||
# listen for this event so that we we can initialize
|
|
||||||
# our roster.
|
|
||||||
self.add_event_handler("session_start", self.start)
|
|
||||||
|
|
||||||
def start(self, event):
|
|
||||||
"""
|
|
||||||
Process the session_start event.
|
|
||||||
|
|
||||||
Typical actions for the session_start event are
|
|
||||||
requesting the roster and broadcasting an initial
|
|
||||||
presence stanza.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
event -- An empty dictionary. The session_start
|
|
||||||
event does not provide any additional
|
|
||||||
data.
|
|
||||||
"""
|
|
||||||
self.send_presence()
|
|
||||||
self.get_roster()
|
|
||||||
result = self['xep_0199'].send_ping(self.pingjid,
|
|
||||||
timeout=10,
|
|
||||||
errorfalse=True)
|
|
||||||
logging.info("Pinging...")
|
|
||||||
if result is False:
|
|
||||||
logging.info("Couldn't ping.")
|
|
||||||
self.disconnect()
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
logging.info("Success! RTT: %s", str(result))
|
|
||||||
self.disconnect()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Setup the command line arguments.
|
|
||||||
optp = OptionParser()
|
|
||||||
|
|
||||||
# Output verbosity options.
|
|
||||||
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=logging.ERROR, default=logging.INFO)
|
|
||||||
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=logging.DEBUG, default=logging.INFO)
|
|
||||||
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=5, default=logging.INFO)
|
|
||||||
optp.add_option('-t', '--pingto', help='set jid to ping',
|
|
||||||
action='store', type='string', dest='pingjid',
|
|
||||||
default=None)
|
|
||||||
|
|
||||||
# JID and password options.
|
|
||||||
optp.add_option("-j", "--jid", dest="jid",
|
|
||||||
help="JID to use")
|
|
||||||
optp.add_option("-p", "--password", dest="password",
|
|
||||||
help="password to use")
|
|
||||||
|
|
||||||
opts, args = optp.parse_args()
|
|
||||||
|
|
||||||
# Setup logging.
|
|
||||||
logging.basicConfig(level=opts.loglevel,
|
|
||||||
format='%(levelname)-8s %(message)s')
|
|
||||||
|
|
||||||
if opts.jid is None:
|
|
||||||
opts.jid = raw_input("Username: ")
|
|
||||||
if opts.password is None:
|
|
||||||
opts.password = getpass.getpass("Password: ")
|
|
||||||
|
|
||||||
# Setup the PingTest and register plugins. Note that while plugins may
|
|
||||||
# have interdependencies, the order in which you register them does
|
|
||||||
# not matter.
|
|
||||||
xmpp = PingTest(opts.jid, opts.password, opts.pingjid)
|
|
||||||
xmpp.register_plugin('xep_0030') # Service Discovery
|
|
||||||
xmpp.register_plugin('xep_0004') # Data Forms
|
|
||||||
xmpp.register_plugin('xep_0060') # PubSub
|
|
||||||
xmpp.register_plugin('xep_0199') # XMPP Ping
|
|
||||||
|
|
||||||
# If you are working with an OpenFire server, you may need
|
|
||||||
# to adjust the SSL version used:
|
|
||||||
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
|
||||||
|
|
||||||
# If you want to verify the SSL certificates offered by a server:
|
|
||||||
# xmpp.ca_certs = "path/to/ca/cert"
|
|
||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
|
||||||
if xmpp.connect():
|
|
||||||
# If you do not have the dnspython library installed, you will need
|
|
||||||
# to manually specify the name of the server if it does not match
|
|
||||||
# the one in the JID. For example, to use Google Talk you would
|
|
||||||
# need to use:
|
|
||||||
#
|
|
||||||
# if xmpp.connect(('talk.google.com', 5222)):
|
|
||||||
# ...
|
|
||||||
xmpp.process(block=True)
|
|
||||||
print("Done")
|
|
||||||
else:
|
|
||||||
print("Unable to connect.")
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2010 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import getpass
|
|
||||||
from optparse import OptionParser
|
|
||||||
|
|
||||||
import sleekxmpp
|
|
||||||
|
|
||||||
# Python versions before 3.0 do not use UTF-8 encoding
|
|
||||||
# by default. To ensure that Unicode is handled properly
|
|
||||||
# throughout SleekXMPP, we will set the default encoding
|
|
||||||
# ourselves to UTF-8.
|
|
||||||
if sys.version_info < (3, 0):
|
|
||||||
reload(sys)
|
|
||||||
sys.setdefaultencoding('utf8')
|
|
||||||
else:
|
|
||||||
raw_input = input
|
|
||||||
|
|
||||||
|
|
||||||
class EchoBot(sleekxmpp.ClientXMPP):
|
|
||||||
|
|
||||||
"""
|
|
||||||
A simple SleekXMPP bot that will echo messages it
|
|
||||||
receives, along with a short thank you message.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, jid, password):
|
|
||||||
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
|
||||||
|
|
||||||
# The session_start event will be triggered when
|
|
||||||
# the bot establishes its connection with the server
|
|
||||||
# and the XML streams are ready for use. We want to
|
|
||||||
# listen for this event so that we we can initialize
|
|
||||||
# our roster.
|
|
||||||
self.add_event_handler("session_start", self.start)
|
|
||||||
|
|
||||||
# The message event is triggered whenever a message
|
|
||||||
# stanza is received. Be aware that that includes
|
|
||||||
# MUC messages and error messages.
|
|
||||||
self.add_event_handler("message", self.message)
|
|
||||||
|
|
||||||
def start(self, event):
|
|
||||||
"""
|
|
||||||
Process the session_start event.
|
|
||||||
|
|
||||||
Typical actions for the session_start event are
|
|
||||||
requesting the roster and broadcasting an initial
|
|
||||||
presence stanza.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
event -- An empty dictionary. The session_start
|
|
||||||
event does not provide any additional
|
|
||||||
data.
|
|
||||||
"""
|
|
||||||
self.send_presence()
|
|
||||||
self.get_roster()
|
|
||||||
|
|
||||||
def message(self, msg):
|
|
||||||
"""
|
|
||||||
Process incoming message stanzas. Be aware that this also
|
|
||||||
includes MUC messages and error messages. It is usually
|
|
||||||
a good idea to check the messages's type before processing
|
|
||||||
or sending replies.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
msg -- The received message stanza. See the documentation
|
|
||||||
for stanza objects and the Message stanza to see
|
|
||||||
how it may be used.
|
|
||||||
"""
|
|
||||||
msg.reply("Thanks for sending\n%(body)s" % msg).send()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Setup the command line arguments.
|
|
||||||
optp = OptionParser()
|
|
||||||
|
|
||||||
# Output verbosity options.
|
|
||||||
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=logging.ERROR, default=logging.INFO)
|
|
||||||
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=logging.DEBUG, default=logging.INFO)
|
|
||||||
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=5, default=logging.INFO)
|
|
||||||
|
|
||||||
# JID and password options.
|
|
||||||
optp.add_option("-j", "--jid", dest="jid",
|
|
||||||
help="JID to use")
|
|
||||||
optp.add_option("-p", "--password", dest="password",
|
|
||||||
help="password to use")
|
|
||||||
optp.add_option("--phost", dest="proxy_host",
|
|
||||||
help="Proxy hostname")
|
|
||||||
optp.add_option("--pport", dest="proxy_port",
|
|
||||||
help="Proxy port")
|
|
||||||
optp.add_option("--puser", dest="proxy_user",
|
|
||||||
help="Proxy username")
|
|
||||||
optp.add_option("--ppass", dest="proxy_pass",
|
|
||||||
help="Proxy password")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
opts, args = optp.parse_args()
|
|
||||||
|
|
||||||
# Setup logging.
|
|
||||||
logging.basicConfig(level=opts.loglevel,
|
|
||||||
format='%(levelname)-8s %(message)s')
|
|
||||||
|
|
||||||
if opts.jid is None:
|
|
||||||
opts.jid = raw_input("Username: ")
|
|
||||||
if opts.password is None:
|
|
||||||
opts.password = getpass.getpass("Password: ")
|
|
||||||
if opts.proxy_host is None:
|
|
||||||
opts.proxy_host = raw_input("Proxy host: ")
|
|
||||||
if opts.proxy_port is None:
|
|
||||||
opts.proxy_port = raw_input("Proxy port: ")
|
|
||||||
if opts.proxy_user is None:
|
|
||||||
opts.proxy_user = raw_input("Proxy username: ")
|
|
||||||
if opts.proxy_pass is None and opts.proxy_user:
|
|
||||||
opts.proxy_pass = getpass.getpass("Proxy password: ")
|
|
||||||
|
|
||||||
# Setup the EchoBot and register plugins. Note that while plugins may
|
|
||||||
# have interdependencies, the order in which you register them does
|
|
||||||
# not matter.
|
|
||||||
xmpp = EchoBot(opts.jid, opts.password)
|
|
||||||
xmpp.register_plugin('xep_0030') # Service Discovery
|
|
||||||
xmpp.register_plugin('xep_0004') # Data Forms
|
|
||||||
xmpp.register_plugin('xep_0060') # PubSub
|
|
||||||
xmpp.register_plugin('xep_0199') # XMPP Ping
|
|
||||||
|
|
||||||
# If you are working with an OpenFire server, you may need
|
|
||||||
# to adjust the SSL version used:
|
|
||||||
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
|
||||||
|
|
||||||
# If you want to verify the SSL certificates offered by a server:
|
|
||||||
# xmpp.ca_certs = "path/to/ca/cert"
|
|
||||||
|
|
||||||
xmpp.use_proxy = True
|
|
||||||
xmpp.proxy_config = {
|
|
||||||
'host': opts.proxy_host,
|
|
||||||
'port': int(opts.proxy_port),
|
|
||||||
'username': opts.proxy_user,
|
|
||||||
'password': opts.proxy_pass}
|
|
||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
|
||||||
if xmpp.connect():
|
|
||||||
# If you do not have the dnspython library installed, you will need
|
|
||||||
# to manually specify the name of the server if it does not match
|
|
||||||
# the one in the JID. For example, to use Google Talk you would
|
|
||||||
# need to use:
|
|
||||||
#
|
|
||||||
# if xmpp.connect(('talk.google.com', 5222)):
|
|
||||||
# ...
|
|
||||||
xmpp.process(block=True)
|
|
||||||
print("Done")
|
|
||||||
else:
|
|
||||||
print("Unable to connect.")
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2011 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import getpass
|
|
||||||
import threading
|
|
||||||
from optparse import OptionParser
|
|
||||||
|
|
||||||
import sleekxmpp
|
|
||||||
from sleekxmpp.exceptions import IqError, IqTimeout
|
|
||||||
|
|
||||||
|
|
||||||
# Python versions before 3.0 do not use UTF-8 encoding
|
|
||||||
# by default. To ensure that Unicode is handled properly
|
|
||||||
# throughout SleekXMPP, we will set the default encoding
|
|
||||||
# ourselves to UTF-8.
|
|
||||||
if sys.version_info < (3, 0):
|
|
||||||
reload(sys)
|
|
||||||
sys.setdefaultencoding('utf8')
|
|
||||||
else:
|
|
||||||
raw_input = input
|
|
||||||
|
|
||||||
|
|
||||||
class RosterBrowser(sleekxmpp.ClientXMPP):
|
|
||||||
|
|
||||||
"""
|
|
||||||
A basic script for dumping a client's roster to
|
|
||||||
the command line.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, jid, password):
|
|
||||||
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
|
||||||
# The session_start event will be triggered when
|
|
||||||
# the bot establishes its connection with the server
|
|
||||||
# and the XML streams are ready for use. We want to
|
|
||||||
# listen for this event so that we we can initialize
|
|
||||||
# our roster. We need threaded=True so that the
|
|
||||||
# session_start handler doesn't block event processing
|
|
||||||
# while we wait for presence stanzas to arrive.
|
|
||||||
self.add_event_handler("session_start", self.start, threaded=True)
|
|
||||||
self.add_event_handler("changed_status", self.wait_for_presences)
|
|
||||||
|
|
||||||
self.received = set()
|
|
||||||
self.presences_received = threading.Event()
|
|
||||||
|
|
||||||
def start(self, event):
|
|
||||||
"""
|
|
||||||
Process the session_start event.
|
|
||||||
|
|
||||||
Typical actions for the session_start event are
|
|
||||||
requesting the roster and broadcasting an initial
|
|
||||||
presence stanza.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
event -- An empty dictionary. The session_start
|
|
||||||
event does not provide any additional
|
|
||||||
data.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
self.get_roster()
|
|
||||||
except IqError as err:
|
|
||||||
print('Error: %' % err.iq['error']['condition'])
|
|
||||||
except IqTimeout:
|
|
||||||
print('Error: Request timed out')
|
|
||||||
self.send_presence()
|
|
||||||
|
|
||||||
|
|
||||||
print('Waiting for presence updates...\n')
|
|
||||||
self.presences_received.wait(5)
|
|
||||||
|
|
||||||
print('Roster for %s' % self.boundjid.bare)
|
|
||||||
groups = self.client_roster.groups()
|
|
||||||
for group in groups:
|
|
||||||
print('\n%s' % group)
|
|
||||||
print('-' * 72)
|
|
||||||
for jid in groups[group]:
|
|
||||||
sub = self.client_roster[jid]['subscription']
|
|
||||||
name = self.client_roster[jid]['name']
|
|
||||||
if self.client_roster[jid]['name']:
|
|
||||||
print(' %s (%s) [%s]' % (name, jid, sub))
|
|
||||||
else:
|
|
||||||
print(' %s [%s]' % (jid, sub))
|
|
||||||
|
|
||||||
connections = self.client_roster.presence(jid)
|
|
||||||
for res, pres in connections.items():
|
|
||||||
show = 'available'
|
|
||||||
if pres['show']:
|
|
||||||
show = pres['show']
|
|
||||||
print(' - %s (%s)' % (res, show))
|
|
||||||
if pres['status']:
|
|
||||||
print(' %s' % pres['status'])
|
|
||||||
|
|
||||||
self.disconnect()
|
|
||||||
|
|
||||||
def wait_for_presences(self, pres):
|
|
||||||
"""
|
|
||||||
Track how many roster entries have received presence updates.
|
|
||||||
"""
|
|
||||||
self.received.add(pres['from'].bare)
|
|
||||||
if len(self.received) >= len(self.client_roster.keys()):
|
|
||||||
self.presences_received.set()
|
|
||||||
else:
|
|
||||||
self.presences_received.clear()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Setup the command line arguments.
|
|
||||||
optp = OptionParser()
|
|
||||||
optp.add_option('-q','--quiet', help='set logging to ERROR',
|
|
||||||
action='store_const',
|
|
||||||
dest='loglevel',
|
|
||||||
const=logging.ERROR,
|
|
||||||
default=logging.ERROR)
|
|
||||||
optp.add_option('-d','--debug', help='set logging to DEBUG',
|
|
||||||
action='store_const',
|
|
||||||
dest='loglevel',
|
|
||||||
const=logging.DEBUG,
|
|
||||||
default=logging.ERROR)
|
|
||||||
optp.add_option('-v','--verbose', help='set logging to COMM',
|
|
||||||
action='store_const',
|
|
||||||
dest='loglevel',
|
|
||||||
const=5,
|
|
||||||
default=logging.ERROR)
|
|
||||||
|
|
||||||
# JID and password options.
|
|
||||||
optp.add_option("-j", "--jid", dest="jid",
|
|
||||||
help="JID to use")
|
|
||||||
optp.add_option("-p", "--password", dest="password",
|
|
||||||
help="password to use")
|
|
||||||
opts,args = optp.parse_args()
|
|
||||||
|
|
||||||
# Setup logging.
|
|
||||||
logging.basicConfig(level=opts.loglevel,
|
|
||||||
format='%(levelname)-8s %(message)s')
|
|
||||||
|
|
||||||
if opts.jid is None:
|
|
||||||
opts.jid = raw_input("Username: ")
|
|
||||||
if opts.password is None:
|
|
||||||
opts.password = getpass.getpass("Password: ")
|
|
||||||
|
|
||||||
xmpp = RosterBrowser(opts.jid, opts.password)
|
|
||||||
|
|
||||||
# If you are working with an OpenFire server, you may need
|
|
||||||
# to adjust the SSL version used:
|
|
||||||
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
|
||||||
|
|
||||||
# If you want to verify the SSL certificates offered by a server:
|
|
||||||
# xmpp.ca_certs = "path/to/ca/cert"
|
|
||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
|
||||||
if xmpp.connect():
|
|
||||||
# If you do not have the dnspython library installed, you will need
|
|
||||||
# to manually specify the name of the server if it does not match
|
|
||||||
# the one in the JID. For example, to use Google Talk you would
|
|
||||||
# need to use:
|
|
||||||
#
|
|
||||||
# if xmpp.connect(('talk.google.com', 5222)):
|
|
||||||
# ...
|
|
||||||
xmpp.process(block=True)
|
|
||||||
else:
|
|
||||||
print("Unable to connect.")
|
|
||||||
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2011 Dann Martens
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
|
|
||||||
ANY_ALL, Future
|
|
||||||
import time
|
|
||||||
|
|
||||||
class Boomerang(Endpoint):
|
|
||||||
|
|
||||||
def FQN(self):
|
|
||||||
return 'boomerang'
|
|
||||||
|
|
||||||
@remote
|
|
||||||
def throw(self):
|
|
||||||
print "Duck!"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
session = Remote.new_session('kangaroo@xmpp.org/rpc', '*****')
|
|
||||||
|
|
||||||
session.new_handler(ANY_ALL, Boomerang)
|
|
||||||
|
|
||||||
boomerang = session.new_proxy('kangaroo@xmpp.org/rpc', Boomerang)
|
|
||||||
|
|
||||||
callback = Future()
|
|
||||||
|
|
||||||
boomerang.async(callback).throw()
|
|
||||||
|
|
||||||
time.sleep(10)
|
|
||||||
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2011 Dann Martens
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
|
|
||||||
ANY_ALL
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
|
|
||||||
class Thermostat(Endpoint):
|
|
||||||
|
|
||||||
def FQN(self):
|
|
||||||
return 'thermostat'
|
|
||||||
|
|
||||||
def __init(self, initial_temperature):
|
|
||||||
self._temperature = initial_temperature
|
|
||||||
self._event = threading.Event()
|
|
||||||
|
|
||||||
@remote
|
|
||||||
def set_temperature(self, temperature):
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
@remote
|
|
||||||
def get_temperature(self):
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
@remote(False)
|
|
||||||
def release(self):
|
|
||||||
return NotImplemented
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
session = Remote.new_session('operator@xmpp.org/rpc', '*****')
|
|
||||||
|
|
||||||
thermostat = session.new_proxy('thermostat@xmpp.org/rpc', Thermostat)
|
|
||||||
|
|
||||||
print("Current temperature is %s" % thermostat.get_temperature())
|
|
||||||
|
|
||||||
thermostat.set_temperature(20)
|
|
||||||
|
|
||||||
time.sleep(10)
|
|
||||||
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2011 Dann Martens
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
|
|
||||||
ANY_ALL
|
|
||||||
import threading
|
|
||||||
|
|
||||||
class Thermostat(Endpoint):
|
|
||||||
|
|
||||||
def FQN(self):
|
|
||||||
return 'thermostat'
|
|
||||||
|
|
||||||
def __init(self, initial_temperature):
|
|
||||||
self._temperature = initial_temperature
|
|
||||||
self._event = threading.Event()
|
|
||||||
|
|
||||||
@remote
|
|
||||||
def set_temperature(self, temperature):
|
|
||||||
print("Setting temperature to %s" % temperature)
|
|
||||||
self._temperature = temperature
|
|
||||||
|
|
||||||
@remote
|
|
||||||
def get_temperature(self):
|
|
||||||
return self._temperature
|
|
||||||
|
|
||||||
@remote(False)
|
|
||||||
def release(self):
|
|
||||||
self._event.set()
|
|
||||||
|
|
||||||
def wait_for_release(self):
|
|
||||||
self._event.wait()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
session = Remote.new_session('sleek@xmpp.org/rpc', '*****')
|
|
||||||
|
|
||||||
thermostat = session.new_handler(ANY_ALL, Thermostat, 18)
|
|
||||||
|
|
||||||
thermostat.wait_for_release()
|
|
||||||
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2010 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import getpass
|
|
||||||
from optparse import OptionParser
|
|
||||||
|
|
||||||
import sleekxmpp
|
|
||||||
|
|
||||||
# Python versions before 3.0 do not use UTF-8 encoding
|
|
||||||
# by default. To ensure that Unicode is handled properly
|
|
||||||
# throughout SleekXMPP, we will set the default encoding
|
|
||||||
# ourselves to UTF-8.
|
|
||||||
if sys.version_info < (3, 0):
|
|
||||||
reload(sys)
|
|
||||||
sys.setdefaultencoding('utf8')
|
|
||||||
else:
|
|
||||||
raw_input = input
|
|
||||||
|
|
||||||
|
|
||||||
class SendMsgBot(sleekxmpp.ClientXMPP):
|
|
||||||
|
|
||||||
"""
|
|
||||||
A basic SleekXMPP bot that will log in, send a message,
|
|
||||||
and then log out.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, jid, password, recipient, message):
|
|
||||||
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
|
||||||
|
|
||||||
# The message we wish to send, and the JID that
|
|
||||||
# will receive it.
|
|
||||||
self.recipient = recipient
|
|
||||||
self.msg = message
|
|
||||||
|
|
||||||
# The session_start event will be triggered when
|
|
||||||
# the bot establishes its connection with the server
|
|
||||||
# and the XML streams are ready for use. We want to
|
|
||||||
# listen for this event so that we we can initialize
|
|
||||||
# our roster.
|
|
||||||
self.add_event_handler("session_start", self.start)
|
|
||||||
|
|
||||||
def start(self, event):
|
|
||||||
"""
|
|
||||||
Process the session_start event.
|
|
||||||
|
|
||||||
Typical actions for the session_start event are
|
|
||||||
requesting the roster and broadcasting an initial
|
|
||||||
presence stanza.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
event -- An empty dictionary. The session_start
|
|
||||||
event does not provide any additional
|
|
||||||
data.
|
|
||||||
"""
|
|
||||||
self.send_presence()
|
|
||||||
self.get_roster()
|
|
||||||
|
|
||||||
self.send_message(mto=self.recipient,
|
|
||||||
mbody=self.msg,
|
|
||||||
mtype='chat')
|
|
||||||
|
|
||||||
# Using wait=True ensures that the send queue will be
|
|
||||||
# emptied before ending the session.
|
|
||||||
self.disconnect(wait=True)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Setup the command line arguments.
|
|
||||||
optp = OptionParser()
|
|
||||||
|
|
||||||
# Output verbosity options.
|
|
||||||
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=logging.ERROR, default=logging.INFO)
|
|
||||||
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=logging.DEBUG, default=logging.INFO)
|
|
||||||
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
|
||||||
action='store_const', dest='loglevel',
|
|
||||||
const=5, default=logging.INFO)
|
|
||||||
|
|
||||||
# JID and password options.
|
|
||||||
optp.add_option("-j", "--jid", dest="jid",
|
|
||||||
help="JID to use")
|
|
||||||
optp.add_option("-p", "--password", dest="password",
|
|
||||||
help="password to use")
|
|
||||||
optp.add_option("-t", "--to", dest="to",
|
|
||||||
help="JID to send the message to")
|
|
||||||
optp.add_option("-m", "--message", dest="message",
|
|
||||||
help="message to send")
|
|
||||||
|
|
||||||
opts, args = optp.parse_args()
|
|
||||||
|
|
||||||
# Setup logging.
|
|
||||||
logging.basicConfig(level=opts.loglevel,
|
|
||||||
format='%(levelname)-8s %(message)s')
|
|
||||||
|
|
||||||
if opts.jid is None:
|
|
||||||
opts.jid = raw_input("Username: ")
|
|
||||||
if opts.password is None:
|
|
||||||
opts.password = getpass.getpass("Password: ")
|
|
||||||
if opts.to is None:
|
|
||||||
opts.to = raw_input("Send To: ")
|
|
||||||
if opts.message is None:
|
|
||||||
opts.message = raw_input("Message: ")
|
|
||||||
|
|
||||||
# Setup the EchoBot and register plugins. Note that while plugins may
|
|
||||||
# have interdependencies, the order in which you register them does
|
|
||||||
# not matter.
|
|
||||||
xmpp = SendMsgBot(opts.jid, opts.password, opts.to, opts.message)
|
|
||||||
xmpp.register_plugin('xep_0030') # Service Discovery
|
|
||||||
xmpp.register_plugin('xep_0199') # XMPP Ping
|
|
||||||
|
|
||||||
# If you are working with an OpenFire server, you may need
|
|
||||||
# to adjust the SSL version used:
|
|
||||||
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
|
||||||
|
|
||||||
# If you want to verify the SSL certificates offered by a server:
|
|
||||||
# xmpp.ca_certs = "path/to/ca/cert"
|
|
||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
|
||||||
if xmpp.connect():
|
|
||||||
# If you do not have the dnspython library installed, you will need
|
|
||||||
# to manually specify the name of the server if it does not match
|
|
||||||
# the one in the JID. For example, to use Google Talk you would
|
|
||||||
# need to use:
|
|
||||||
#
|
|
||||||
# if xmpp.connect(('talk.google.com', 5222)):
|
|
||||||
# ...
|
|
||||||
xmpp.process(block=True)
|
|
||||||
print("Done")
|
|
||||||
else:
|
|
||||||
print("Unable to connect.")
|
|
||||||
78
setup.py
Executable file → Normal file
78
setup.py
Executable file → Normal file
@@ -1,21 +1,17 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# Copyright (C) 2007-2011 Nathanael C. Fritz
|
# Copyright (C) 2007-2008 Nathanael C. Fritz
|
||||||
# All Rights Reserved
|
# All Rights Reserved
|
||||||
#
|
#
|
||||||
# This software is licensed as described in the README.rst and LICENSE
|
# This software is licensed as described in the README file,
|
||||||
# file, which you should have received as part of this distribution.
|
# which you should have received as part of this distribution.
|
||||||
|
#
|
||||||
|
|
||||||
import sys
|
|
||||||
try:
|
|
||||||
from setuptools import setup, Command
|
|
||||||
except ImportError:
|
|
||||||
from distutils.core import setup, Command
|
|
||||||
# from ez_setup import use_setuptools
|
# from ez_setup import use_setuptools
|
||||||
|
from distutils.core import setup
|
||||||
|
import sys
|
||||||
|
|
||||||
from testall import TestCommand
|
|
||||||
from sleekxmpp.version import __version__
|
|
||||||
# if 'cygwin' in sys.platform.lower():
|
# if 'cygwin' in sys.platform.lower():
|
||||||
# min_version = '0.6c6'
|
# min_version = '0.6c6'
|
||||||
# else:
|
# else:
|
||||||
@@ -29,60 +25,29 @@ from sleekxmpp.version import __version__
|
|||||||
#
|
#
|
||||||
# from setuptools import setup, find_packages, Extension, Feature
|
# from setuptools import setup, find_packages, Extension, Feature
|
||||||
|
|
||||||
VERSION = __version__
|
VERSION = '0.2.3.1'
|
||||||
DESCRIPTION = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).'
|
DESCRIPTION = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).'
|
||||||
with open('README.rst') as readme:
|
LONG_DESCRIPTION = """
|
||||||
LONG_DESCRIPTION = ''.join(readme)
|
SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).
|
||||||
|
"""
|
||||||
|
|
||||||
CLASSIFIERS = [ 'Intended Audience :: Developers',
|
CLASSIFIERS = [ 'Intended Audience :: Developers',
|
||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT',
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
'Programming Language :: Python :: 2.6',
|
|
||||||
'Programming Language :: Python :: 2.7',
|
|
||||||
'Programming Language :: Python :: 3.1',
|
|
||||||
'Programming Language :: Python :: 3.2',
|
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||||
]
|
]
|
||||||
|
|
||||||
packages = [ 'sleekxmpp',
|
packages = [ 'sleekxmpp',
|
||||||
|
'sleekxmpp/plugins',
|
||||||
'sleekxmpp/stanza',
|
'sleekxmpp/stanza',
|
||||||
'sleekxmpp/test',
|
|
||||||
'sleekxmpp/roster',
|
|
||||||
'sleekxmpp/xmlstream',
|
'sleekxmpp/xmlstream',
|
||||||
'sleekxmpp/xmlstream/matcher',
|
'sleekxmpp/xmlstream/matcher',
|
||||||
'sleekxmpp/xmlstream/handler',
|
'sleekxmpp/xmlstream/handler' ]
|
||||||
'sleekxmpp/plugins',
|
|
||||||
'sleekxmpp/plugins/xep_0004',
|
if sys.version_info < (3, 0):
|
||||||
'sleekxmpp/plugins/xep_0004/stanza',
|
packages.append('sleekxmpp/xmlstream/tostring26')
|
||||||
'sleekxmpp/plugins/xep_0009',
|
else:
|
||||||
'sleekxmpp/plugins/xep_0009/stanza',
|
packages.append('sleekxmpp/xmlstream/tostring')
|
||||||
'sleekxmpp/plugins/xep_0030',
|
|
||||||
'sleekxmpp/plugins/xep_0030/stanza',
|
|
||||||
'sleekxmpp/plugins/xep_0050',
|
|
||||||
'sleekxmpp/plugins/xep_0059',
|
|
||||||
'sleekxmpp/plugins/xep_0060',
|
|
||||||
'sleekxmpp/plugins/xep_0060/stanza',
|
|
||||||
'sleekxmpp/plugins/xep_0066',
|
|
||||||
'sleekxmpp/plugins/xep_0078',
|
|
||||||
'sleekxmpp/plugins/xep_0085',
|
|
||||||
'sleekxmpp/plugins/xep_0086',
|
|
||||||
'sleekxmpp/plugins/xep_0092',
|
|
||||||
'sleekxmpp/plugins/xep_0128',
|
|
||||||
'sleekxmpp/plugins/xep_0199',
|
|
||||||
'sleekxmpp/plugins/xep_0202',
|
|
||||||
'sleekxmpp/plugins/xep_0203',
|
|
||||||
'sleekxmpp/plugins/xep_0224',
|
|
||||||
'sleekxmpp/plugins/xep_0249',
|
|
||||||
'sleekxmpp/features',
|
|
||||||
'sleekxmpp/features/feature_mechanisms',
|
|
||||||
'sleekxmpp/features/feature_mechanisms/stanza',
|
|
||||||
'sleekxmpp/features/feature_starttls',
|
|
||||||
'sleekxmpp/features/feature_bind',
|
|
||||||
'sleekxmpp/features/feature_session',
|
|
||||||
'sleekxmpp/thirdparty',
|
|
||||||
'sleekxmpp/thirdparty/suelta',
|
|
||||||
'sleekxmpp/thirdparty/suelta/mechanisms',
|
|
||||||
]
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name = "sleekxmpp",
|
name = "sleekxmpp",
|
||||||
@@ -91,11 +56,10 @@ setup(
|
|||||||
long_description = LONG_DESCRIPTION,
|
long_description = LONG_DESCRIPTION,
|
||||||
author = 'Nathanael Fritz',
|
author = 'Nathanael Fritz',
|
||||||
author_email = 'fritzy [at] netflint.net',
|
author_email = 'fritzy [at] netflint.net',
|
||||||
url = 'http://github.com/fritzy/SleekXMPP',
|
url = 'http://code.google.com/p/sleekxmpp',
|
||||||
license = 'MIT',
|
license = 'MIT',
|
||||||
platforms = [ 'any' ],
|
platforms = [ 'any' ],
|
||||||
packages = packages,
|
packages = packages,
|
||||||
requires = [ 'dnspython' ],
|
requires = [ 'tlslite', 'pythondns' ],
|
||||||
classifiers = CLASSIFIERS,
|
|
||||||
cmdclass = {'test': TestCommand}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,275 @@
|
|||||||
|
#!/usr/bin/python2.5
|
||||||
|
|
||||||
"""
|
"""
|
||||||
SleekXMPP: The Sleek XMPP Library
|
SleekXMPP: The Sleek XMPP Library
|
||||||
Copyright (C) 2010 Nathanael C. Fritz
|
Copyright (C) 2010 Nathanael C. Fritz
|
||||||
This file is part of SleekXMPP.
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
See the file license.txt for copying permission.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
from . basexmpp import basexmpp
|
||||||
|
from xml.etree import cElementTree as ET
|
||||||
|
from . xmlstream.xmlstream import XMLStream
|
||||||
|
from . xmlstream.xmlstream import RestartStream
|
||||||
|
from . xmlstream.matcher.xmlmask import MatchXMLMask
|
||||||
|
from . xmlstream.matcher.xpath import MatchXPath
|
||||||
|
from . xmlstream.matcher.many import MatchMany
|
||||||
|
from . xmlstream.handler.callback import Callback
|
||||||
|
from . xmlstream.stanzabase import StanzaBase
|
||||||
|
from . xmlstream import xmlstream as xmlstreammod
|
||||||
|
from . stanza.message import Message
|
||||||
|
from . stanza.iq import Iq
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import base64
|
||||||
|
import sys
|
||||||
|
import random
|
||||||
|
import copy
|
||||||
|
from . import plugins
|
||||||
|
#from . import stanza
|
||||||
|
srvsupport = True
|
||||||
|
try:
|
||||||
|
import dns.resolver
|
||||||
|
import dns.rdatatype
|
||||||
|
except ImportError:
|
||||||
|
srvsupport = False
|
||||||
|
|
||||||
from sleekxmpp.basexmpp import BaseXMPP
|
|
||||||
from sleekxmpp.clientxmpp import ClientXMPP
|
|
||||||
from sleekxmpp.componentxmpp import ComponentXMPP
|
|
||||||
from sleekxmpp.stanza import Message, Presence, Iq
|
|
||||||
from sleekxmpp.xmlstream.handler import *
|
|
||||||
from sleekxmpp.xmlstream import XMLStream, RestartStream
|
|
||||||
from sleekxmpp.xmlstream.matcher import *
|
|
||||||
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
|
|
||||||
|
|
||||||
from sleekxmpp.version import __version__, __version_info__
|
|
||||||
|
#class PresenceStanzaType(object):
|
||||||
|
#
|
||||||
|
# def fromXML(self, xml):
|
||||||
|
# self.ptype = xml.get('type')
|
||||||
|
|
||||||
|
|
||||||
|
class ClientXMPP(basexmpp, XMLStream):
|
||||||
|
"""SleekXMPP's client class. Use only for good, not evil."""
|
||||||
|
|
||||||
|
def __init__(self, jid, password, ssl=False, plugin_config = {}, plugin_whitelist=[], escape_quotes=True):
|
||||||
|
global srvsupport
|
||||||
|
XMLStream.__init__(self)
|
||||||
|
self.default_ns = 'jabber:client'
|
||||||
|
basexmpp.__init__(self)
|
||||||
|
self.plugin_config = plugin_config
|
||||||
|
self.escape_quotes = escape_quotes
|
||||||
|
self.set_jid(jid)
|
||||||
|
self.server = None
|
||||||
|
self.port = 5222 # not used if DNS SRV is used
|
||||||
|
self.plugin_whitelist = plugin_whitelist
|
||||||
|
self.auto_reconnect = True
|
||||||
|
self.srvsupport = srvsupport
|
||||||
|
self.password = password
|
||||||
|
self.registered_features = []
|
||||||
|
self.stream_header = """<stream:stream to='%s' xmlns:stream='http://etherx.jabber.org/streams' xmlns='%s' version='1.0'>""" % (self.domain,self.default_ns)
|
||||||
|
self.stream_footer = "</stream:stream>"
|
||||||
|
#self.map_namespace('http://etherx.jabber.org/streams', 'stream')
|
||||||
|
#self.map_namespace('jabber:client', '')
|
||||||
|
self.features = []
|
||||||
|
#TODO: Use stream state here
|
||||||
|
self.authenticated = False
|
||||||
|
self.sessionstarted = False
|
||||||
|
self.bound = False
|
||||||
|
self.bindfail = False
|
||||||
|
self.registerHandler(Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True))
|
||||||
|
self.registerHandler(Callback('Roster Update', MatchXPath('{%s}iq/{jabber:iq:roster}query' % self.default_ns), self._handleRoster, thread=True))
|
||||||
|
#self.registerHandler(Callback('Roster Update', MatchXMLMask("<presence xmlns='%s' type='subscribe' />" % self.default_ns), self._handlePresenceSubscribe, thread=True))
|
||||||
|
self.registerFeature("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_starttls, True)
|
||||||
|
self.registerFeature("<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_sasl_auth, True)
|
||||||
|
self.registerFeature("<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' />", self.handler_bind_resource)
|
||||||
|
self.registerFeature("<session xmlns='urn:ietf:params:xml:ns:xmpp-session' />", self.handler_start_session)
|
||||||
|
|
||||||
|
#self.registerStanzaExtension('PresenceStanza', PresenceStanzaType)
|
||||||
|
#self.register_plugins()
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if key in self.plugin:
|
||||||
|
return self.plugin[key]
|
||||||
|
else:
|
||||||
|
logging.warning("""Plugin "%s" is not loaded.""" % key)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get(self, key, default):
|
||||||
|
return self.plugin.get(key, default)
|
||||||
|
|
||||||
|
def connect(self, host=None, port=None):
|
||||||
|
"""Connect to the Jabber Server. Attempts SRV lookup, and if it fails, uses
|
||||||
|
the JID server."""
|
||||||
|
|
||||||
|
if self.state['connected']: return True
|
||||||
|
|
||||||
|
if host:
|
||||||
|
self.server = host
|
||||||
|
if port is None: port = self.port
|
||||||
|
else:
|
||||||
|
if not self.srvsupport:
|
||||||
|
logging.debug("Did not supply (address, port) to connect to and no SRV support is installed (http://www.dnspython.org). Continuing to attempt connection, using domain from JID.")
|
||||||
|
else:
|
||||||
|
logging.debug("Since no address is supplied, attempting SRV lookup.")
|
||||||
|
try:
|
||||||
|
answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.domain,
|
||||||
|
dns.rdatatype.SRV )
|
||||||
|
except dns.resolver.NXDOMAIN:
|
||||||
|
logging.debug("No appropriate SRV record found. Using JID server name.")
|
||||||
|
else:
|
||||||
|
# pick a random answer, weighted by priority
|
||||||
|
# there are less verbose ways of doing this (random.choice() with answer * priority), but I chose this way anyway
|
||||||
|
# suggestions are welcome
|
||||||
|
addresses = {}
|
||||||
|
intmax = 0
|
||||||
|
priorities = []
|
||||||
|
for answer in answers:
|
||||||
|
intmax += answer.priority
|
||||||
|
addresses[intmax] = (answer.target.to_text()[:-1], answer.port)
|
||||||
|
priorities.append(intmax) # sure, I could just do priorities = addresses.keys()\n priorities.sort()
|
||||||
|
picked = random.randint(0, intmax)
|
||||||
|
for priority in priorities:
|
||||||
|
if picked <= priority:
|
||||||
|
(host,port) = addresses[priority]
|
||||||
|
break
|
||||||
|
# if SRV lookup was successful, we aren't using a particular server.
|
||||||
|
self.server = None
|
||||||
|
|
||||||
|
if not host:
|
||||||
|
# if all else fails take server from JID.
|
||||||
|
(host,port) = (self.domain, self.port)
|
||||||
|
self.server = None
|
||||||
|
|
||||||
|
logging.debug('Attempting connection to %s:%d', host, port )
|
||||||
|
#TODO option to not use TLS?
|
||||||
|
result = XMLStream.connect(self, host, port, use_tls=True)
|
||||||
|
if result:
|
||||||
|
self.event("connected")
|
||||||
|
else:
|
||||||
|
logging.warning("Failed to connect")
|
||||||
|
self.event("disconnected")
|
||||||
|
return result
|
||||||
|
|
||||||
|
# overriding reconnect and disconnect so that we can get some events
|
||||||
|
# should events be part of or required by xmlstream? Maybe that would be cleaner
|
||||||
|
def reconnect(self):
|
||||||
|
self.disconnect(reconnect=True)
|
||||||
|
|
||||||
|
def disconnect(self, reconnect=False):
|
||||||
|
self.event("disconnected")
|
||||||
|
self.authenticated = False
|
||||||
|
self.sessionstarted = False
|
||||||
|
XMLStream.disconnect(self, reconnect)
|
||||||
|
|
||||||
|
def registerFeature(self, mask, pointer, breaker = False):
|
||||||
|
"""Register a stream feature."""
|
||||||
|
self.registered_features.append((MatchXMLMask(mask), pointer, breaker))
|
||||||
|
|
||||||
|
def updateRoster(self, jid, name=None, subscription=None, groups=[]):
|
||||||
|
"""Add or change a roster item."""
|
||||||
|
iq = self.Iq().setValues({'type': 'set'})
|
||||||
|
iq['roster'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}}
|
||||||
|
#self.send(iq, self.Iq().setValues({'id': iq['id']}))
|
||||||
|
r = iq.send()
|
||||||
|
return r['type'] == 'result'
|
||||||
|
|
||||||
|
def getRoster(self):
|
||||||
|
"""Request the roster be sent."""
|
||||||
|
iq = self.Iq().setValues({'type': 'get'}).enable('roster').send()
|
||||||
|
self._handleRoster(iq, request=True)
|
||||||
|
|
||||||
|
def _handleStreamFeatures(self, features):
|
||||||
|
logging.debug('handling stream features')
|
||||||
|
self.features = []
|
||||||
|
for sub in features.xml:
|
||||||
|
self.features.append(sub.tag)
|
||||||
|
for subelement in features.xml:
|
||||||
|
for feature in self.registered_features:
|
||||||
|
if feature[0].match(subelement):
|
||||||
|
#if self.maskcmp(subelement, feature[0], True):
|
||||||
|
# This calls the feature handler & optionally breaks
|
||||||
|
if feature[1](subelement) and feature[2]: #if breaker, don't continue
|
||||||
|
return True
|
||||||
|
|
||||||
|
def handler_starttls(self, xml):
|
||||||
|
logging.debug( 'TLS start handler; SSL support: %s', self.ssl_support )
|
||||||
|
if not self.authenticated and self.ssl_support:
|
||||||
|
_stanza = "<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls' />"
|
||||||
|
if not self.event_handlers.get(_stanza,None): # don't add handler > once
|
||||||
|
self.add_handler( _stanza, self.handler_tls_start, instream=True )
|
||||||
|
self.sendXML(xml)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logging.warning("The module tlslite is required in to some servers, and has not been found.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def handler_tls_start(self, xml):
|
||||||
|
logging.debug("Starting TLS")
|
||||||
|
if self.startTLS():
|
||||||
|
raise RestartStream()
|
||||||
|
|
||||||
|
def handler_sasl_auth(self, xml):
|
||||||
|
if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features:
|
||||||
|
return False
|
||||||
|
logging.debug("Starting SASL Auth")
|
||||||
|
self.add_handler("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_success, instream=True)
|
||||||
|
self.add_handler("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_fail, instream=True)
|
||||||
|
sasl_mechs = xml.findall('{urn:ietf:params:xml:ns:xmpp-sasl}mechanism')
|
||||||
|
if len(sasl_mechs):
|
||||||
|
for sasl_mech in sasl_mechs:
|
||||||
|
self.features.append("sasl:%s" % sasl_mech.text)
|
||||||
|
if 'sasl:PLAIN' in self.features:
|
||||||
|
if sys.version_info < (3,0):
|
||||||
|
self.send("""<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>""" % base64.b64encode(b'\x00' + bytes(self.username) + b'\x00' + bytes(self.password)).decode('utf-8'))
|
||||||
|
else:
|
||||||
|
self.send("""<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>""" % base64.b64encode(b'\x00' + bytes(self.username, 'utf-8') + b'\x00' + bytes(self.password, 'utf-8')).decode('utf-8'))
|
||||||
|
else:
|
||||||
|
logging.error("No appropriate login method.")
|
||||||
|
self.disconnect()
|
||||||
|
#if 'sasl:DIGEST-MD5' in self.features:
|
||||||
|
# self._auth_digestmd5()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def handler_auth_success(self, xml):
|
||||||
|
logging.debug("Authentication successful.")
|
||||||
|
self.authenticated = True
|
||||||
|
self.features = []
|
||||||
|
raise RestartStream()
|
||||||
|
|
||||||
|
def handler_auth_fail(self, xml):
|
||||||
|
logging.warning("Authentication failed.")
|
||||||
|
self.disconnect()
|
||||||
|
self.event("failed_auth")
|
||||||
|
|
||||||
|
def handler_bind_resource(self, xml):
|
||||||
|
logging.debug("Requesting resource: %s" % self.resource)
|
||||||
|
iq = self.Iq(stype='set')
|
||||||
|
res = ET.Element('resource')
|
||||||
|
res.text = self.resource
|
||||||
|
xml.append(res)
|
||||||
|
iq.append(xml)
|
||||||
|
response = iq.send()
|
||||||
|
#response = self.send(iq, self.Iq(sid=iq['id']))
|
||||||
|
self.set_jid(response.xml.find('{urn:ietf:params:xml:ns:xmpp-bind}bind/{urn:ietf:params:xml:ns:xmpp-bind}jid').text)
|
||||||
|
self.bound = True
|
||||||
|
logging.info("Node set to: %s" % self.fulljid)
|
||||||
|
if "{urn:ietf:params:xml:ns:xmpp-session}session" not in self.features or self.bindfail:
|
||||||
|
logging.debug("Established Session")
|
||||||
|
self.sessionstarted = True
|
||||||
|
self.event("session_start")
|
||||||
|
|
||||||
|
def handler_start_session(self, xml):
|
||||||
|
if self.authenticated and self.bound:
|
||||||
|
iq = self.makeIqSet(xml)
|
||||||
|
response = iq.send()
|
||||||
|
logging.debug("Established Session")
|
||||||
|
self.sessionstarted = True
|
||||||
|
self.event("session_start")
|
||||||
|
else:
|
||||||
|
#bind probably hasn't happened yet
|
||||||
|
self.bindfail = True
|
||||||
|
|
||||||
|
def _handleRoster(self, iq, request=False):
|
||||||
|
if iq['type'] == 'set' or (iq['type'] == 'result' and request):
|
||||||
|
for jid in iq['roster']['items']:
|
||||||
|
if not jid in self.roster:
|
||||||
|
self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': True}
|
||||||
|
self.roster[jid].update(iq['roster']['items'][jid])
|
||||||
|
if iq['type'] == 'set':
|
||||||
|
self.send(self.Iq().setValues({'type': 'result', 'id': iq['id']}).enable('roster'))
|
||||||
|
self.event("roster_update", iq)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,297 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
sleekxmpp.clientxmpp
|
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This module provides XMPP functionality that
|
|
||||||
is specific to client connections.
|
|
||||||
|
|
||||||
Part of SleekXMPP: The Sleek XMPP Library
|
|
||||||
|
|
||||||
:copyright: (c) 2011 Nathanael C. Fritz
|
|
||||||
:license: MIT, see LICENSE for more details
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import base64
|
|
||||||
import sys
|
|
||||||
import hashlib
|
|
||||||
import random
|
|
||||||
import threading
|
|
||||||
|
|
||||||
import sleekxmpp
|
|
||||||
from sleekxmpp import plugins
|
|
||||||
from sleekxmpp import stanza
|
|
||||||
from sleekxmpp import features
|
|
||||||
from sleekxmpp.basexmpp import BaseXMPP
|
|
||||||
from sleekxmpp.stanza import *
|
|
||||||
from sleekxmpp.xmlstream import XMLStream, RestartStream
|
|
||||||
from sleekxmpp.xmlstream import StanzaBase, ET, register_stanza_plugin
|
|
||||||
from sleekxmpp.xmlstream.matcher import *
|
|
||||||
from sleekxmpp.xmlstream.handler import *
|
|
||||||
|
|
||||||
# Flag indicating if DNS SRV records are available for use.
|
|
||||||
try:
|
|
||||||
import dns.resolver
|
|
||||||
except ImportError:
|
|
||||||
DNSPYTHON = False
|
|
||||||
else:
|
|
||||||
DNSPYTHON = True
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class ClientXMPP(BaseXMPP):
|
|
||||||
|
|
||||||
"""
|
|
||||||
SleekXMPP's client class. (Use only for good, not for evil.)
|
|
||||||
|
|
||||||
Typical use pattern:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
xmpp = ClientXMPP('user@server.tld/resource', 'password')
|
|
||||||
# ... Register plugins and event handlers ...
|
|
||||||
xmpp.connect()
|
|
||||||
xmpp.process(block=False) # block=True will block the current
|
|
||||||
# thread. By default, block=False
|
|
||||||
|
|
||||||
: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
|
|
||||||
:meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
|
|
||||||
:param escape_quotes: **Deprecated.**
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, jid, password, ssl=False, plugin_config={},
|
|
||||||
plugin_whitelist=[], escape_quotes=True, sasl_mech=None):
|
|
||||||
BaseXMPP.__init__(self, jid, 'jabber:client')
|
|
||||||
|
|
||||||
self.set_jid(jid)
|
|
||||||
self.password = password
|
|
||||||
self.escape_quotes = escape_quotes
|
|
||||||
self.plugin_config = plugin_config
|
|
||||||
self.plugin_whitelist = plugin_whitelist
|
|
||||||
self.default_port = 5222
|
|
||||||
|
|
||||||
self.stream_header = "<stream:stream to='%s' %s %s version='1.0'>" % (
|
|
||||||
self.boundjid.host,
|
|
||||||
"xmlns:stream='%s'" % self.stream_ns,
|
|
||||||
"xmlns='%s'" % self.default_ns)
|
|
||||||
self.stream_footer = "</stream:stream>"
|
|
||||||
|
|
||||||
self.features = set()
|
|
||||||
self._stream_feature_handlers = {}
|
|
||||||
self._stream_feature_order = []
|
|
||||||
|
|
||||||
#TODO: Use stream state here
|
|
||||||
self.authenticated = False
|
|
||||||
self.sessionstarted = False
|
|
||||||
self.bound = False
|
|
||||||
self.bindfail = False
|
|
||||||
|
|
||||||
self.add_event_handler('connected', self._handle_connected)
|
|
||||||
|
|
||||||
self.register_stanza(StreamFeatures)
|
|
||||||
|
|
||||||
self.register_handler(
|
|
||||||
Callback('Stream Features',
|
|
||||||
MatchXPath('{%s}features' % self.stream_ns),
|
|
||||||
self._handle_stream_features))
|
|
||||||
self.register_handler(
|
|
||||||
Callback('Roster Update',
|
|
||||||
MatchXPath('{%s}iq/{%s}query' % (
|
|
||||||
self.default_ns,
|
|
||||||
'jabber:iq:roster')),
|
|
||||||
self._handle_roster))
|
|
||||||
|
|
||||||
# Setup default stream features
|
|
||||||
self.register_plugin('feature_starttls')
|
|
||||||
self.register_plugin('feature_bind')
|
|
||||||
self.register_plugin('feature_session')
|
|
||||||
self.register_plugin('feature_mechanisms',
|
|
||||||
pconfig={'use_mech': sasl_mech} if sasl_mech else None)
|
|
||||||
|
|
||||||
def connect(self, address=tuple(), reattempt=True,
|
|
||||||
use_tls=True, use_ssl=False):
|
|
||||||
"""Connect to the XMPP server.
|
|
||||||
|
|
||||||
When no address is given, a SRV lookup for the server will
|
|
||||||
be attempted. If that fails, the server user in the JID
|
|
||||||
will be used.
|
|
||||||
|
|
||||||
:param address -- A tuple containing the server's host and port.
|
|
||||||
:param reattempt: If ``True``, repeat attempting to connect if an
|
|
||||||
error occurs. Defaults to ``True``.
|
|
||||||
:param use_tls: Indicates if TLS should be used for the
|
|
||||||
connection. Defaults to ``True``.
|
|
||||||
:param use_ssl: Indicates if the older SSL connection method
|
|
||||||
should be used. Defaults to ``False``.
|
|
||||||
"""
|
|
||||||
self.session_started_event.clear()
|
|
||||||
if not address:
|
|
||||||
address = (self.boundjid.host, 5222)
|
|
||||||
|
|
||||||
return XMLStream.connect(self, address[0], address[1],
|
|
||||||
use_tls=use_tls, use_ssl=use_ssl,
|
|
||||||
reattempt=reattempt)
|
|
||||||
|
|
||||||
def get_dns_records(self, domain, port=None):
|
|
||||||
"""Get the DNS records for a domain, including SRV records.
|
|
||||||
|
|
||||||
:param domain: The domain in question.
|
|
||||||
:param port: If the results don't include a port, use this one.
|
|
||||||
"""
|
|
||||||
if port is None:
|
|
||||||
port = self.default_port
|
|
||||||
if DNSPYTHON:
|
|
||||||
try:
|
|
||||||
record = "_xmpp-client._tcp.%s" % domain
|
|
||||||
answers = []
|
|
||||||
for answer in dns.resolver.query(record, dns.rdatatype.SRV):
|
|
||||||
address = (answer.target.to_text()[:-1], answer.port)
|
|
||||||
answers.append((address, answer.priority, answer.weight))
|
|
||||||
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
|
|
||||||
log.warning("No SRV records for %s", domain)
|
|
||||||
answers = super(ClientXMPP, self).get_dns_records(domain, port)
|
|
||||||
except dns.exception.Timeout:
|
|
||||||
log.warning("DNS resolution timed out " + \
|
|
||||||
"for SRV record of %s", domain)
|
|
||||||
answers = super(ClientXMPP, self).get_dns_records(domain, port)
|
|
||||||
return answers
|
|
||||||
else:
|
|
||||||
log.warning("dnspython is not installed -- " + \
|
|
||||||
"relying on OS A record resolution")
|
|
||||||
return [((domain, port), 0, 0)]
|
|
||||||
|
|
||||||
def register_feature(self, name, handler, restart=False, order=5000):
|
|
||||||
"""Register a stream feature handler.
|
|
||||||
|
|
||||||
:param name: The name of the stream feature.
|
|
||||||
:param handler: The function to execute if the feature is received.
|
|
||||||
:param restart: Indicates if feature processing should halt with
|
|
||||||
this feature. Defaults to ``False``.
|
|
||||||
:param order: The relative ordering in which the feature should
|
|
||||||
be negotiated. Lower values will be attempted
|
|
||||||
earlier when available.
|
|
||||||
"""
|
|
||||||
self._stream_feature_handlers[name] = (handler, restart)
|
|
||||||
self._stream_feature_order.append((order, name))
|
|
||||||
self._stream_feature_order.sort()
|
|
||||||
|
|
||||||
def update_roster(self, jid, name=None, subscription=None, groups=[],
|
|
||||||
block=True, timeout=None, callback=None):
|
|
||||||
"""Add or change a roster item.
|
|
||||||
|
|
||||||
:param jid: The JID of the entry to modify.
|
|
||||||
:param name: The user's nickname for this JID.
|
|
||||||
:param subscription: The subscription status. May be one of
|
|
||||||
``'to'``, ``'from'``, ``'both'``, or
|
|
||||||
``'none'``. If set to ``'remove'``,
|
|
||||||
the entry will be deleted.
|
|
||||||
:param groups: The roster groups that contain this item.
|
|
||||||
:param block: Specify if the roster request will block
|
|
||||||
until a response is received, or a timeout
|
|
||||||
occurs. Defaults to ``True``.
|
|
||||||
:param timeout: The length of time (in seconds) to wait
|
|
||||||
for a response before continuing if blocking
|
|
||||||
is used. Defaults to
|
|
||||||
:attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`.
|
|
||||||
:param callback: Optional reference to a stream handler function.
|
|
||||||
Will be executed when the roster is received.
|
|
||||||
Implies ``block=False``.
|
|
||||||
"""
|
|
||||||
return self.client_roster.update(jid, name, subscription, groups,
|
|
||||||
block, timeout, callback)
|
|
||||||
|
|
||||||
def del_roster_item(self, jid):
|
|
||||||
"""Remove an item from the roster.
|
|
||||||
|
|
||||||
This is done by setting its subscription status to ``'remove'``.
|
|
||||||
|
|
||||||
:param jid: The JID of the item to remove.
|
|
||||||
"""
|
|
||||||
return self.client_roster.remove(jid)
|
|
||||||
|
|
||||||
def get_roster(self, block=True, timeout=None, callback=None):
|
|
||||||
"""Request the roster from the server.
|
|
||||||
|
|
||||||
:param block: Specify if the roster request will block until a
|
|
||||||
response is received, or a timeout occurs.
|
|
||||||
Defaults to ``True``.
|
|
||||||
:param timeout: The length of time (in seconds) to wait for a response
|
|
||||||
before continuing if blocking is used.
|
|
||||||
Defaults to
|
|
||||||
:attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`.
|
|
||||||
:param callback: Optional reference to a stream handler function. Will
|
|
||||||
be executed when the roster is received.
|
|
||||||
Implies ``block=False``.
|
|
||||||
"""
|
|
||||||
iq = self.Iq()
|
|
||||||
iq['type'] = 'get'
|
|
||||||
iq.enable('roster')
|
|
||||||
response = iq.send(block, timeout, callback)
|
|
||||||
|
|
||||||
if callback is None:
|
|
||||||
return self._handle_roster(response, request=True)
|
|
||||||
|
|
||||||
def _handle_connected(self, event=None):
|
|
||||||
#TODO: Use stream state here
|
|
||||||
self.authenticated = False
|
|
||||||
self.sessionstarted = False
|
|
||||||
self.bound = False
|
|
||||||
self.bindfail = False
|
|
||||||
self.features = set()
|
|
||||||
|
|
||||||
def _handle_stream_features(self, features):
|
|
||||||
"""Process the received stream features.
|
|
||||||
|
|
||||||
:param features: The features stanza.
|
|
||||||
"""
|
|
||||||
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:
|
|
||||||
# Don't continue if the feature requires
|
|
||||||
# restarting the XML stream.
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _handle_roster(self, iq, request=False):
|
|
||||||
"""Update the roster after receiving a roster stanza.
|
|
||||||
|
|
||||||
:param iq: The roster stanza.
|
|
||||||
:param request: Indicates if this stanza is a response
|
|
||||||
to a request for the roster, and not an
|
|
||||||
empty acknowledgement from the server.
|
|
||||||
"""
|
|
||||||
if iq['type'] == 'set' or (iq['type'] == 'result' and request):
|
|
||||||
for jid in iq['roster']['items']:
|
|
||||||
item = iq['roster']['items'][jid]
|
|
||||||
roster = self.roster[iq['to'].bare]
|
|
||||||
roster[jid]['name'] = item['name']
|
|
||||||
roster[jid]['groups'] = item['groups']
|
|
||||||
roster[jid]['from'] = item['subscription'] in ['from', 'both']
|
|
||||||
roster[jid]['to'] = item['subscription'] in ['to', 'both']
|
|
||||||
roster[jid]['pending_out'] = (item['ask'] == 'subscribe')
|
|
||||||
self.event('roster_received', iq)
|
|
||||||
|
|
||||||
self.event("roster_update", iq)
|
|
||||||
if iq['type'] == 'set':
|
|
||||||
iq.reply()
|
|
||||||
iq.enable('roster')
|
|
||||||
iq.send()
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
# To comply with PEP8, method names now use underscores.
|
|
||||||
# Deprecated method names are re-mapped for backwards compatibility.
|
|
||||||
ClientXMPP.updateRoster = ClientXMPP.update_roster
|
|
||||||
ClientXMPP.delRosterItem = ClientXMPP.del_roster_item
|
|
||||||
ClientXMPP.getRoster = ClientXMPP.get_roster
|
|
||||||
ClientXMPP.registerFeature = ClientXMPP.register_feature
|
|
||||||
41
sleekxmpp/component_example.py
Normal file
41
sleekxmpp/component_example.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import sleekxmpp.componentxmpp
|
||||||
|
import logging
|
||||||
|
from optparse import OptionParser
|
||||||
|
import time
|
||||||
|
|
||||||
|
class Example(sleekxmpp.componentxmpp.ComponentXMPP):
|
||||||
|
|
||||||
|
def __init__(self, jid, password):
|
||||||
|
sleekxmpp.componentxmpp.ComponentXMPP.__init__(self, jid, password, 'vm1', 5230)
|
||||||
|
self.add_event_handler("session_start", self.start)
|
||||||
|
self.add_event_handler("message", self.message)
|
||||||
|
|
||||||
|
def start(self, event):
|
||||||
|
#self.getRoster()
|
||||||
|
#self.sendPresence(pto='admin@tigase.netflint.net/sarkozy')
|
||||||
|
#self.sendPresence(pto='tigase.netflint.net')
|
||||||
|
pass
|
||||||
|
|
||||||
|
def message(self, event):
|
||||||
|
self.sendMessage("%s/%s" % (event['jid'], event['resource']), "Thanks for sending me, \"%s\"." % event['message'], mtype=event['type'])
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
#parse command line arguements
|
||||||
|
optp = OptionParser()
|
||||||
|
optp.add_option('-q','--quiet', help='set logging to ERROR', action='store_const', dest='loglevel', const=logging.ERROR, default=logging.INFO)
|
||||||
|
optp.add_option('-d','--debug', help='set logging to DEBUG', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO)
|
||||||
|
optp.add_option('-v','--verbose', help='set logging to COMM', action='store_const', dest='loglevel', const=5, default=logging.INFO)
|
||||||
|
optp.add_option("-c","--config", dest="configfile", default="config.xml", help="set config file to use")
|
||||||
|
opts,args = optp.parse_args()
|
||||||
|
|
||||||
|
logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
|
||||||
|
xmpp = Example('component.vm1', 'secreteating')
|
||||||
|
xmpp.registerPlugin('xep_0004')
|
||||||
|
xmpp.registerPlugin('xep_0030')
|
||||||
|
xmpp.registerPlugin('xep_0060')
|
||||||
|
xmpp.registerPlugin('xep_0199')
|
||||||
|
if xmpp.connect():
|
||||||
|
xmpp.process(threaded=False)
|
||||||
|
print("done")
|
||||||
|
else:
|
||||||
|
print("Unable to connect.")
|
||||||
190
sleekxmpp/componentxmpp.py
Normal file → Executable file
190
sleekxmpp/componentxmpp.py
Normal file → Executable file
@@ -1,162 +1,88 @@
|
|||||||
# -*- coding: utf-8 -*-
|
#!/usr/bin/python2.6
|
||||||
|
|
||||||
"""
|
"""
|
||||||
sleekxmpp.clientxmpp
|
SleekXMPP: The Sleek XMPP Library
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
Copyright (C) 2010 Nathanael C. Fritz
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
This module provides XMPP functionality that
|
See the file license.txt for copying permission.
|
||||||
is specific to external server component connections.
|
|
||||||
|
|
||||||
Part of SleekXMPP: The Sleek XMPP Library
|
|
||||||
|
|
||||||
:copyright: (c) 2011 Nathanael C. Fritz
|
|
||||||
:license: MIT, see LICENSE for more details
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
from . basexmpp import basexmpp
|
||||||
|
from xml.etree import cElementTree as ET
|
||||||
|
|
||||||
|
from . xmlstream.xmlstream import XMLStream
|
||||||
|
from . xmlstream.xmlstream import RestartStream
|
||||||
|
from . xmlstream.matcher.xmlmask import MatchXMLMask
|
||||||
|
from . xmlstream.matcher.xpath import MatchXPath
|
||||||
|
from . xmlstream.matcher.many import MatchMany
|
||||||
|
from . xmlstream.handler.callback import Callback
|
||||||
|
from . xmlstream.stanzabase import StanzaBase
|
||||||
|
from . xmlstream import xmlstream as xmlstreammod
|
||||||
|
import time
|
||||||
import logging
|
import logging
|
||||||
import base64
|
import base64
|
||||||
import sys
|
import sys
|
||||||
|
import random
|
||||||
|
import copy
|
||||||
|
from . import plugins
|
||||||
|
from . import stanza
|
||||||
import hashlib
|
import hashlib
|
||||||
|
srvsupport = True
|
||||||
from sleekxmpp import plugins
|
try:
|
||||||
from sleekxmpp import stanza
|
import dns.resolver
|
||||||
from sleekxmpp.basexmpp import BaseXMPP
|
except ImportError:
|
||||||
from sleekxmpp.xmlstream import XMLStream, RestartStream
|
srvsupport = False
|
||||||
from sleekxmpp.xmlstream import StanzaBase, ET
|
|
||||||
from sleekxmpp.xmlstream.matcher import *
|
|
||||||
from sleekxmpp.xmlstream.handler import *
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
class ComponentXMPP(basexmpp, XMLStream):
|
||||||
|
"""SleekXMPP's client class. Use only for good, not evil."""
|
||||||
|
|
||||||
|
def __init__(self, jid, secret, host, port, plugin_config = {}, plugin_whitelist=[], use_jc_ns=False):
|
||||||
class ComponentXMPP(BaseXMPP):
|
XMLStream.__init__(self)
|
||||||
|
|
||||||
"""
|
|
||||||
SleekXMPP's basic XMPP server component.
|
|
||||||
|
|
||||||
Use only for good, not for evil.
|
|
||||||
|
|
||||||
:param jid: The JID of the component.
|
|
||||||
:param secret: The secret or password for the component.
|
|
||||||
:param host: The server accepting the component.
|
|
||||||
:param port: The port used to connect to the server.
|
|
||||||
:param plugin_config: A dictionary of plugin configurations.
|
|
||||||
:param plugin_whitelist: A list of approved plugins that
|
|
||||||
will be loaded when calling
|
|
||||||
:meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
|
|
||||||
:param use_jc_ns: Indicates if the ``'jabber:client'`` namespace
|
|
||||||
should be used instead of the standard
|
|
||||||
``'jabber:component:accept'`` namespace.
|
|
||||||
Defaults to ``False``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, jid, secret, host=None, port=None,
|
|
||||||
plugin_config={}, plugin_whitelist=[], use_jc_ns=False):
|
|
||||||
if use_jc_ns:
|
if use_jc_ns:
|
||||||
default_ns = 'jabber:client'
|
self.default_ns = 'jabber:client'
|
||||||
else:
|
else:
|
||||||
default_ns = 'jabber:component:accept'
|
self.default_ns = 'jabber:component:accept'
|
||||||
BaseXMPP.__init__(self, jid, default_ns)
|
basexmpp.__init__(self)
|
||||||
|
|
||||||
self.auto_authorize = None
|
self.auto_authorize = None
|
||||||
self.stream_header = "<stream:stream %s %s to='%s'>" % (
|
self.stream_header = "<stream:stream xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams' to='%s'>" % jid
|
||||||
'xmlns="jabber:component:accept"',
|
|
||||||
'xmlns:stream="%s"' % self.stream_ns,
|
|
||||||
jid)
|
|
||||||
self.stream_footer = "</stream:stream>"
|
self.stream_footer = "</stream:stream>"
|
||||||
self.server_host = host
|
self.server_host = host
|
||||||
self.server_port = port
|
self.server_port = port
|
||||||
|
self.set_jid(jid)
|
||||||
self.secret = secret
|
self.secret = secret
|
||||||
|
self.registerHandler(Callback('Handshake', MatchXPath('{jabber:component:accept}handshake'), self._handleHandshake))
|
||||||
|
|
||||||
self.plugin_config = plugin_config
|
def __getitem__(self, key):
|
||||||
self.plugin_whitelist = plugin_whitelist
|
if key in self.plugin:
|
||||||
self.is_component = True
|
return self.plugin[key]
|
||||||
|
else:
|
||||||
|
logging.warning("""Plugin "%s" is not loaded.""" % key)
|
||||||
|
return False
|
||||||
|
|
||||||
self.register_handler(
|
def get(self, key, default):
|
||||||
Callback('Handshake',
|
return self.plugin.get(key, default)
|
||||||
MatchXPath('{jabber:component:accept}handshake'),
|
|
||||||
self._handle_handshake))
|
|
||||||
self.add_event_handler('presence_probe',
|
|
||||||
self._handle_probe)
|
|
||||||
|
|
||||||
def connect(self, host=None, port=None, use_ssl=False,
|
def incoming_filter(self, xmlobj):
|
||||||
use_tls=True, reattempt=True):
|
if xmlobj.tag.startswith('{jabber:client}'):
|
||||||
"""Connect to the server.
|
xmlobj.tag = xmlobj.tag.replace('jabber:client', self.default_ns)
|
||||||
|
for sub in xmlobj:
|
||||||
Setting ``reattempt`` to ``True`` will cause connection attempts to
|
|
||||||
be made every second until a successful connection is established.
|
|
||||||
|
|
||||||
:param host: The name of the desired server for the connection.
|
|
||||||
Defaults to :attr:`server_host`.
|
|
||||||
:param port: Port to connect to on the server.
|
|
||||||
Defauts to :attr:`server_port`.
|
|
||||||
:param use_ssl: Flag indicating if SSL should be used by connecting
|
|
||||||
directly to a port using SSL.
|
|
||||||
:param use_tls: Flag indicating if TLS should be used, allowing for
|
|
||||||
connecting to a port without using SSL immediately and
|
|
||||||
later upgrading the connection.
|
|
||||||
:param reattempt: Flag indicating if the socket should reconnect
|
|
||||||
after disconnections.
|
|
||||||
"""
|
|
||||||
if host is None:
|
|
||||||
host = self.server_host
|
|
||||||
if port is None:
|
|
||||||
port = self.server_port
|
|
||||||
log.debug("Connecting to %s:%s", host, port)
|
|
||||||
return XMLStream.connect(self, host=host, port=port,
|
|
||||||
use_ssl=use_ssl,
|
|
||||||
use_tls=use_tls,
|
|
||||||
reattempt=reattempt)
|
|
||||||
|
|
||||||
def incoming_filter(self, xml):
|
|
||||||
"""
|
|
||||||
Pre-process incoming XML stanzas by converting any
|
|
||||||
``'jabber:client'`` namespaced elements to the component's
|
|
||||||
default namespace.
|
|
||||||
|
|
||||||
:param xml: The XML stanza to pre-process.
|
|
||||||
"""
|
|
||||||
if xml.tag.startswith('{jabber:client}'):
|
|
||||||
xml.tag = xml.tag.replace('jabber:client', self.default_ns)
|
|
||||||
|
|
||||||
# The incoming_filter call is only made on top level stanza
|
|
||||||
# elements. So we manually continue filtering on sub-elements.
|
|
||||||
for sub in xml:
|
|
||||||
self.incoming_filter(sub)
|
self.incoming_filter(sub)
|
||||||
|
return xmlobj
|
||||||
return xml
|
|
||||||
|
|
||||||
def start_stream_handler(self, xml):
|
def start_stream_handler(self, xml):
|
||||||
"""
|
|
||||||
Once the streams are established, attempt to handshake
|
|
||||||
with the server to be accepted as a component.
|
|
||||||
|
|
||||||
:param xml: The incoming stream's root element.
|
|
||||||
"""
|
|
||||||
BaseXMPP.start_stream_handler(self, xml)
|
|
||||||
|
|
||||||
# Construct a hash of the stream ID and the component secret.
|
|
||||||
sid = xml.get('id', '')
|
sid = xml.get('id', '')
|
||||||
pre_hash = '%s%s' % (sid, self.secret)
|
|
||||||
if sys.version_info >= (3, 0):
|
|
||||||
# Handle Unicode byte encoding in Python 3.
|
|
||||||
pre_hash = bytes(pre_hash, 'utf-8')
|
|
||||||
|
|
||||||
handshake = ET.Element('{jabber:component:accept}handshake')
|
handshake = ET.Element('{jabber:component:accept}handshake')
|
||||||
handshake.text = hashlib.sha1(pre_hash).hexdigest().lower()
|
if sys.version_info < (3,0):
|
||||||
self.send_xml(handshake, now=True)
|
handshake.text = hashlib.sha1("%s%s" % (sid, self.secret)).hexdigest().lower()
|
||||||
|
else:
|
||||||
|
handshake.text = hashlib.sha1(bytes("%s%s" % (sid, self.secret), 'utf-8')).hexdigest().lower()
|
||||||
|
self.sendXML(handshake)
|
||||||
|
|
||||||
def _handle_handshake(self, xml):
|
def _handleHandshake(self, xml):
|
||||||
"""The handshake has been accepted.
|
|
||||||
|
|
||||||
:param xml: The reply handshake stanza.
|
|
||||||
"""
|
|
||||||
self.session_started_event.set()
|
|
||||||
self.event("session_start")
|
self.event("session_start")
|
||||||
|
|
||||||
def _handle_probe(self, presence):
|
def connect(self):
|
||||||
pto = presence['to'].bare
|
logging.debug("Connecting to %s:%s" % (self.server_host, self.server_port))
|
||||||
pfrom = presence['from'].bare
|
return xmlstreammod.XMLStream.connect(self, self.server_host, self.server_port)
|
||||||
self.roster[pto][pfrom].handle_probe(presence)
|
|
||||||
|
|||||||
@@ -1,90 +1,16 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
sleekxmpp.exceptions
|
SleekXMPP: The Sleek XMPP Library
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
Copyright (C) 2010 Nathanael C. Fritz
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
Part of SleekXMPP: The Sleek XMPP Library
|
See the file license.txt for copying permission.
|
||||||
|
|
||||||
:copyright: (c) 2011 Nathanael C. Fritz
|
|
||||||
:license: MIT, see LICENSE for more details
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class XMPPError(Exception):
|
class XMPPError(Exception):
|
||||||
|
def __init__(self, condition='undefined-condition', text=None, etype=None, extension=None, extension_ns=None, extension_args=None):
|
||||||
"""
|
|
||||||
A generic exception that may be raised while processing an XMPP stanza
|
|
||||||
to indicate that an error response stanza should be sent.
|
|
||||||
|
|
||||||
The exception method for stanza objects extending
|
|
||||||
:class:`~sleekxmpp.stanza.rootstanza.RootStanza` will create an error
|
|
||||||
stanza and initialize any additional substanzas using the extension
|
|
||||||
information included in the exception.
|
|
||||||
|
|
||||||
Meant for use in SleekXMPP plugins and applications using SleekXMPP.
|
|
||||||
|
|
||||||
Extension information can be included to add additional XML elements
|
|
||||||
to the generated error stanza.
|
|
||||||
|
|
||||||
:param condition: The XMPP defined error condition.
|
|
||||||
Defaults to ``'undefined-condition'``.
|
|
||||||
:param text: Human readable text describing the error.
|
|
||||||
:param etype: The XMPP error type, such as ``'cancel'`` or ``'modify'``.
|
|
||||||
Defaults to ``'cancel'``.
|
|
||||||
:param extension: Tag name of the extension's XML content.
|
|
||||||
:param extension_ns: XML namespace of the extensions' XML content.
|
|
||||||
:param extension_args: Content and attributes for the extension
|
|
||||||
element. Same as the additional arguments to
|
|
||||||
the :class:`~xml.etree.ElementTree.Element`
|
|
||||||
constructor.
|
|
||||||
:param clear: Indicates if the stanza's contents should be
|
|
||||||
removed before replying with an error.
|
|
||||||
Defaults to ``True``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, condition='undefined-condition', text=None,
|
|
||||||
etype='cancel', extension=None, extension_ns=None,
|
|
||||||
extension_args=None, clear=True):
|
|
||||||
if extension_args is None:
|
|
||||||
extension_args = {}
|
|
||||||
|
|
||||||
self.condition = condition
|
self.condition = condition
|
||||||
self.text = text
|
self.text = text
|
||||||
self.etype = etype
|
self.etype = etype
|
||||||
self.clear = clear
|
|
||||||
self.extension = extension
|
self.extension = extension
|
||||||
self.extension_ns = extension_ns
|
self.extension_ns = extension_ns
|
||||||
self.extension_args = extension_args
|
self.extension_args = extension_args
|
||||||
|
|
||||||
|
|
||||||
class IqTimeout(XMPPError):
|
|
||||||
|
|
||||||
"""
|
|
||||||
An exception which indicates that an IQ request response has not been
|
|
||||||
received within the alloted time window.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, iq):
|
|
||||||
super(IqTimeout, self).__init__(
|
|
||||||
condition='remote-server-timeout',
|
|
||||||
etype='cancel')
|
|
||||||
|
|
||||||
#: The :class:`~sleekxmpp.stanza.iq.Iq` stanza whose response
|
|
||||||
#: did not arrive before the timeout expired.
|
|
||||||
self.iq = iq
|
|
||||||
|
|
||||||
class IqError(XMPPError):
|
|
||||||
|
|
||||||
"""
|
|
||||||
An exception raised when an Iq stanza of type 'error' is received
|
|
||||||
after making a blocking send call.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, iq):
|
|
||||||
super(IqError, self).__init__(
|
|
||||||
condition=iq['error']['condition'],
|
|
||||||
text=iq['error']['text'],
|
|
||||||
etype=iq['error']['type'])
|
|
||||||
|
|
||||||
#: The :class:`~sleekxmpp.stanza.iq.Iq` error result stanza.
|
|
||||||
self.iq = iq
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2011 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = ['feature_starttls', 'feature_mechanisms', 'feature_bind']
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2011 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from sleekxmpp.features.feature_bind.bind import feature_bind
|
|
||||||
from sleekxmpp.features.feature_bind.stanza import Bind
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2011 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from sleekxmpp.stanza import Iq, StreamFeatures
|
|
||||||
from sleekxmpp.features.feature_bind import stanza
|
|
||||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
|
||||||
from sleekxmpp.xmlstream.matcher import *
|
|
||||||
from sleekxmpp.xmlstream.handler import *
|
|
||||||
from sleekxmpp.plugins.base import base_plugin
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class feature_bind(base_plugin):
|
|
||||||
|
|
||||||
def plugin_init(self):
|
|
||||||
self.name = 'Bind Resource'
|
|
||||||
self.rfc = '6120'
|
|
||||||
self.description = 'Resource Binding Stream Feature'
|
|
||||||
self.stanza = stanza
|
|
||||||
|
|
||||||
self.xmpp.register_feature('bind',
|
|
||||||
self._handle_bind_resource,
|
|
||||||
restart=False,
|
|
||||||
order=10000)
|
|
||||||
|
|
||||||
register_stanza_plugin(Iq, stanza.Bind)
|
|
||||||
register_stanza_plugin(StreamFeatures, stanza.Bind)
|
|
||||||
|
|
||||||
def _handle_bind_resource(self, features):
|
|
||||||
"""
|
|
||||||
Handle requesting a specific resource.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
features -- The stream features stanza.
|
|
||||||
"""
|
|
||||||
log.debug("Requesting resource: %s", self.xmpp.boundjid.resource)
|
|
||||||
iq = self.xmpp.Iq()
|
|
||||||
iq['type'] = 'set'
|
|
||||||
iq.enable('bind')
|
|
||||||
if self.xmpp.boundjid.resource:
|
|
||||||
iq['bind']['resource'] = self.xmpp.boundjid.resource
|
|
||||||
response = iq.send(now=True)
|
|
||||||
|
|
||||||
self.xmpp.set_jid(response['bind']['jid'])
|
|
||||||
self.xmpp.bound = True
|
|
||||||
|
|
||||||
self.xmpp.features.add('bind')
|
|
||||||
|
|
||||||
log.info("Node set to: %s", self.xmpp.boundjid.full)
|
|
||||||
|
|
||||||
if 'session' not in features['features']:
|
|
||||||
log.debug("Established Session")
|
|
||||||
self.xmpp.sessionstarted = True
|
|
||||||
self.xmpp.session_started_event.set()
|
|
||||||
self.xmpp.event("session_start")
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2011 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from sleekxmpp.stanza import Iq, StreamFeatures
|
|
||||||
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
|
|
||||||
|
|
||||||
|
|
||||||
class Bind(ElementBase):
|
|
||||||
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = 'bind'
|
|
||||||
namespace = 'urn:ietf:params:xml:ns:xmpp-bind'
|
|
||||||
interfaces = set(('resource', 'jid'))
|
|
||||||
sub_interfaces = interfaces
|
|
||||||
plugin_attrib = 'bind'
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2011 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from sleekxmpp.features.feature_mechanisms.mechanisms import feature_mechanisms
|
|
||||||
from sleekxmpp.features.feature_mechanisms.stanza import Mechanisms
|
|
||||||
from sleekxmpp.features.feature_mechanisms.stanza import Auth
|
|
||||||
from sleekxmpp.features.feature_mechanisms.stanza import Success
|
|
||||||
from sleekxmpp.features.feature_mechanisms.stanza import Failure
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2011 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from sleekxmpp.thirdparty import suelta
|
|
||||||
|
|
||||||
from sleekxmpp.stanza import StreamFeatures
|
|
||||||
from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin
|
|
||||||
from sleekxmpp.xmlstream.matcher import *
|
|
||||||
from sleekxmpp.xmlstream.handler import *
|
|
||||||
from sleekxmpp.plugins.base import base_plugin
|
|
||||||
from sleekxmpp.features.feature_mechanisms import stanza
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class feature_mechanisms(base_plugin):
|
|
||||||
|
|
||||||
def plugin_init(self):
|
|
||||||
self.name = 'SASL Mechanisms'
|
|
||||||
self.rfc = '6120'
|
|
||||||
self.description = "SASL Stream Feature"
|
|
||||||
self.stanza = stanza
|
|
||||||
|
|
||||||
self.use_mech = self.config.get('use_mech', None)
|
|
||||||
|
|
||||||
def tls_active():
|
|
||||||
return 'starttls' in self.xmpp.features
|
|
||||||
|
|
||||||
def basic_callback(mech, values):
|
|
||||||
if 'username' in values:
|
|
||||||
values['username'] = self.xmpp.boundjid.user
|
|
||||||
if 'password' in values:
|
|
||||||
values['password'] = self.xmpp.password
|
|
||||||
mech.fulfill(values)
|
|
||||||
|
|
||||||
sasl_callback = self.config.get('sasl_callback', None)
|
|
||||||
if sasl_callback is None:
|
|
||||||
sasl_callback = basic_callback
|
|
||||||
|
|
||||||
self.mech = None
|
|
||||||
self.sasl = suelta.SASL(self.xmpp.boundjid.domain, 'xmpp',
|
|
||||||
username=self.xmpp.boundjid.user,
|
|
||||||
sec_query=suelta.sec_query_allow,
|
|
||||||
request_values=sasl_callback,
|
|
||||||
tls_active=tls_active,
|
|
||||||
mech=self.use_mech)
|
|
||||||
|
|
||||||
register_stanza_plugin(StreamFeatures, stanza.Mechanisms)
|
|
||||||
|
|
||||||
self.xmpp.register_stanza(stanza.Success)
|
|
||||||
self.xmpp.register_stanza(stanza.Failure)
|
|
||||||
self.xmpp.register_stanza(stanza.Auth)
|
|
||||||
self.xmpp.register_stanza(stanza.Challenge)
|
|
||||||
self.xmpp.register_stanza(stanza.Response)
|
|
||||||
|
|
||||||
self.xmpp.register_handler(
|
|
||||||
Callback('SASL Success',
|
|
||||||
MatchXPath(stanza.Success.tag_name()),
|
|
||||||
self._handle_success,
|
|
||||||
instream=True,
|
|
||||||
once=True))
|
|
||||||
self.xmpp.register_handler(
|
|
||||||
Callback('SASL Failure',
|
|
||||||
MatchXPath(stanza.Failure.tag_name()),
|
|
||||||
self._handle_fail,
|
|
||||||
instream=True,
|
|
||||||
once=True))
|
|
||||||
self.xmpp.register_handler(
|
|
||||||
Callback('SASL Challenge',
|
|
||||||
MatchXPath(stanza.Challenge.tag_name()),
|
|
||||||
self._handle_challenge))
|
|
||||||
|
|
||||||
self.xmpp.register_feature('mechanisms',
|
|
||||||
self._handle_sasl_auth,
|
|
||||||
restart=True,
|
|
||||||
order=self.config.get('order', 100))
|
|
||||||
|
|
||||||
def _handle_sasl_auth(self, features):
|
|
||||||
"""
|
|
||||||
Handle authenticating using SASL.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
features -- The stream features stanza.
|
|
||||||
"""
|
|
||||||
if 'mechanisms' in self.xmpp.features:
|
|
||||||
# SASL authentication has already succeeded, but the
|
|
||||||
# server has incorrectly offered it again.
|
|
||||||
return False
|
|
||||||
|
|
||||||
mech_list = features['mechanisms']
|
|
||||||
self.mech = self.sasl.choose_mechanism(mech_list)
|
|
||||||
|
|
||||||
if self.mech is not None:
|
|
||||||
resp = stanza.Auth(self.xmpp)
|
|
||||||
resp['mechanism'] = self.mech.name
|
|
||||||
resp['value'] = self.mech.process()
|
|
||||||
resp.send(now=True)
|
|
||||||
else:
|
|
||||||
log.error("No appropriate login method.")
|
|
||||||
self.xmpp.event("no_auth", direct=True)
|
|
||||||
self.xmpp.disconnect()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _handle_challenge(self, stanza):
|
|
||||||
"""SASL challenge received. Process and send response."""
|
|
||||||
resp = self.stanza.Response(self.xmpp)
|
|
||||||
resp['value'] = self.mech.process(stanza['value'])
|
|
||||||
resp.send(now=True)
|
|
||||||
|
|
||||||
def _handle_success(self, stanza):
|
|
||||||
"""SASL authentication succeeded. Restart the stream."""
|
|
||||||
self.xmpp.authenticated = True
|
|
||||||
self.xmpp.features.add('mechanisms')
|
|
||||||
raise RestartStream()
|
|
||||||
|
|
||||||
def _handle_fail(self, stanza):
|
|
||||||
"""SASL authentication failed. Disconnect and shutdown."""
|
|
||||||
log.info("Authentication failed: %s", stanza['condition'])
|
|
||||||
self.xmpp.event("failed_auth", stanza, direct=True)
|
|
||||||
self.xmpp.disconnect()
|
|
||||||
return True
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2011 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from sleekxmpp.features.feature_mechanisms.stanza.mechanisms import Mechanisms
|
|
||||||
from sleekxmpp.features.feature_mechanisms.stanza.auth import Auth
|
|
||||||
from sleekxmpp.features.feature_mechanisms.stanza.success import Success
|
|
||||||
from sleekxmpp.features.feature_mechanisms.stanza.failure import Failure
|
|
||||||
from sleekxmpp.features.feature_mechanisms.stanza.challenge import Challenge
|
|
||||||
from sleekxmpp.features.feature_mechanisms.stanza.response import Response
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2011 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import base64
|
|
||||||
|
|
||||||
from sleekxmpp.thirdparty.suelta.util import bytes
|
|
||||||
|
|
||||||
from sleekxmpp.stanza import StreamFeatures
|
|
||||||
from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET
|
|
||||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
|
||||||
|
|
||||||
|
|
||||||
class Auth(StanzaBase):
|
|
||||||
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = 'auth'
|
|
||||||
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
|
||||||
interfaces = set(('mechanism', 'value'))
|
|
||||||
plugin_attrib = name
|
|
||||||
|
|
||||||
def setup(self, xml):
|
|
||||||
StanzaBase.setup(self, xml)
|
|
||||||
self.xml.tag = self.tag_name()
|
|
||||||
|
|
||||||
def get_value(self):
|
|
||||||
return base64.b64decode(bytes(self.xml.text))
|
|
||||||
|
|
||||||
def set_value(self, values):
|
|
||||||
self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
|
|
||||||
|
|
||||||
def del_value(self):
|
|
||||||
self.xml.text = ''
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2011 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import base64
|
|
||||||
|
|
||||||
from sleekxmpp.thirdparty.suelta.util import bytes
|
|
||||||
|
|
||||||
from sleekxmpp.stanza import StreamFeatures
|
|
||||||
from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET
|
|
||||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
|
||||||
|
|
||||||
|
|
||||||
class Challenge(StanzaBase):
|
|
||||||
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = 'challenge'
|
|
||||||
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
|
||||||
interfaces = set(('value',))
|
|
||||||
plugin_attrib = name
|
|
||||||
|
|
||||||
def setup(self, xml):
|
|
||||||
StanzaBase.setup(self, xml)
|
|
||||||
self.xml.tag = self.tag_name()
|
|
||||||
|
|
||||||
def get_value(self):
|
|
||||||
return base64.b64decode(bytes(self.xml.text))
|
|
||||||
|
|
||||||
def set_value(self, values):
|
|
||||||
self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
|
|
||||||
|
|
||||||
def del_value(self):
|
|
||||||
self.xml.text = ''
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2011 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from sleekxmpp.stanza import StreamFeatures
|
|
||||||
from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET
|
|
||||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
|
||||||
|
|
||||||
|
|
||||||
class Failure(StanzaBase):
|
|
||||||
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = 'failure'
|
|
||||||
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
|
||||||
interfaces = set(('condition', 'text'))
|
|
||||||
plugin_attrib = name
|
|
||||||
sub_interfaces = set(('text',))
|
|
||||||
conditions = set(('aborted', 'account-disabled', 'credentials-expired',
|
|
||||||
'encryption-required', 'incorrect-encoding', 'invalid-authzid',
|
|
||||||
'invalid-mechanism', 'malformed-request', 'mechansism-too-weak',
|
|
||||||
'not-authorized', 'temporary-auth-failure'))
|
|
||||||
|
|
||||||
def setup(self, xml=None):
|
|
||||||
"""
|
|
||||||
Populate the stanza object using an optional XML object.
|
|
||||||
|
|
||||||
Overrides ElementBase.setup.
|
|
||||||
|
|
||||||
Sets a default error type and condition, and changes the
|
|
||||||
parent stanza's type to 'error'.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
xml -- Use an existing XML object for the stanza's values.
|
|
||||||
"""
|
|
||||||
# StanzaBase overrides self.namespace
|
|
||||||
self.namespace = Failure.namespace
|
|
||||||
|
|
||||||
if StanzaBase.setup(self, xml):
|
|
||||||
#If we had to generate XML then set default values.
|
|
||||||
self['condition'] = 'not-authorized'
|
|
||||||
|
|
||||||
self.xml.tag = self.tag_name()
|
|
||||||
|
|
||||||
def get_condition(self):
|
|
||||||
"""Return the condition element's name."""
|
|
||||||
for child in self.xml.getchildren():
|
|
||||||
if "{%s}" % self.namespace in child.tag:
|
|
||||||
cond = child.tag.split('}', 1)[-1]
|
|
||||||
if cond in self.conditions:
|
|
||||||
return cond
|
|
||||||
return 'not-authorized'
|
|
||||||
|
|
||||||
def set_condition(self, value):
|
|
||||||
"""
|
|
||||||
Set the tag name of the condition element.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
value -- The tag name of the condition element.
|
|
||||||
"""
|
|
||||||
if value in self.conditions:
|
|
||||||
del self['condition']
|
|
||||||
self.xml.append(ET.Element("{%s}%s" % (self.namespace, value)))
|
|
||||||
return self
|
|
||||||
|
|
||||||
def del_condition(self):
|
|
||||||
"""Remove the condition element."""
|
|
||||||
for child in self.xml.getchildren():
|
|
||||||
if "{%s}" % self.condition_ns in child.tag:
|
|
||||||
tag = child.tag.split('}', 1)[-1]
|
|
||||||
if tag in self.conditions:
|
|
||||||
self.xml.remove(child)
|
|
||||||
return self
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2011 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from sleekxmpp.stanza import StreamFeatures
|
|
||||||
from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET
|
|
||||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
|
||||||
|
|
||||||
|
|
||||||
class Mechanisms(ElementBase):
|
|
||||||
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = 'mechanisms'
|
|
||||||
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
|
||||||
interfaces = set(('mechanisms', 'required'))
|
|
||||||
plugin_attrib = name
|
|
||||||
is_extension = True
|
|
||||||
|
|
||||||
def get_required(self):
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_mechanisms(self):
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
results = []
|
|
||||||
mechs = self.findall('{%s}mechanism' % self.namespace)
|
|
||||||
if mechs:
|
|
||||||
for mech in mechs:
|
|
||||||
results.append(mech.text)
|
|
||||||
return results
|
|
||||||
|
|
||||||
def set_mechanisms(self, values):
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
self.del_mechanisms()
|
|
||||||
for val in values:
|
|
||||||
mech = ET.Element('{%s}mechanism' % self.namespace)
|
|
||||||
mech.text = val
|
|
||||||
self.append(mech)
|
|
||||||
|
|
||||||
def del_mechanisms(self):
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
mechs = self.findall('{%s}mechanism' % self.namespace)
|
|
||||||
if mechs:
|
|
||||||
for mech in mechs:
|
|
||||||
self.xml.remove(mech)
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2011 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import base64
|
|
||||||
|
|
||||||
from sleekxmpp.thirdparty.suelta.util import bytes
|
|
||||||
|
|
||||||
from sleekxmpp.stanza import StreamFeatures
|
|
||||||
from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET
|
|
||||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
|
||||||
|
|
||||||
|
|
||||||
class Response(StanzaBase):
|
|
||||||
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = 'response'
|
|
||||||
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
|
||||||
interfaces = set(('value',))
|
|
||||||
plugin_attrib = name
|
|
||||||
|
|
||||||
def setup(self, xml):
|
|
||||||
StanzaBase.setup(self, xml)
|
|
||||||
self.xml.tag = self.tag_name()
|
|
||||||
|
|
||||||
def get_value(self):
|
|
||||||
return base64.b64decode(bytes(self.xml.text))
|
|
||||||
|
|
||||||
def set_value(self, values):
|
|
||||||
self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
|
|
||||||
|
|
||||||
def del_value(self):
|
|
||||||
self.xml.text = ''
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2011 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from sleekxmpp.stanza import StreamFeatures
|
|
||||||
from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET
|
|
||||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
|
||||||
|
|
||||||
|
|
||||||
class Success(StanzaBase):
|
|
||||||
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = 'success'
|
|
||||||
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
|
||||||
interfaces = set()
|
|
||||||
plugin_attrib = name
|
|
||||||
|
|
||||||
def setup(self, xml):
|
|
||||||
StanzaBase.setup(self, xml)
|
|
||||||
self.xml.tag = self.tag_name()
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2011 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from sleekxmpp.features.feature_session.session import feature_session
|
|
||||||
from sleekxmpp.features.feature_session.stanza import Session
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user