Compare commits
59 Commits
1.0-RC3
...
0.9-conn-f
| 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.
|
||||||
179
README.rst
179
README.rst
@@ -1,179 +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.
|
|
||||||
|
|
||||||
**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>`_
|
|
||||||
|
|
||||||
**Develop Releases**
|
|
||||||
- `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_
|
|
||||||
|
|
||||||
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 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>`_)
|
|
||||||
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 |
70
docs/_static/pygments.css
vendored
70
docs/_static/pygments.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 */
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
35
docs/_templates/defindex.html
vendored
35
docs/_templates/defindex.html
vendored
@@ -1,35 +0,0 @@
|
|||||||
{#
|
|
||||||
basic/defindex.html
|
|
||||||
~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Default template for the "index" page.
|
|
||||||
|
|
||||||
:copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
|
|
||||||
:license: BSD, see LICENSE for details.
|
|
||||||
#}
|
|
||||||
{% extends "layout.html" %}
|
|
||||||
{% set title = _('Overview') %}
|
|
||||||
{% block body %}
|
|
||||||
<h1>{{ docstitle|e }}</h1>
|
|
||||||
<p>
|
|
||||||
Welcome! This is
|
|
||||||
{% block description %}the documentation for {{ project|e }}
|
|
||||||
{{ release|e }}{% if last_updated %}, last updated {{ last_updated|e }}{% endif %}{% endblock %}.
|
|
||||||
</p>
|
|
||||||
{% block tables %}
|
|
||||||
<p><strong>{{ _('Indices and tables:') }}</strong></p>
|
|
||||||
<table class="contentstable" align="center"><tr>
|
|
||||||
<td width="50%">
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("contents") }}">{{ _('Complete Table of Contents') }}</a><br>
|
|
||||||
<span class="linkdescr">{{ _('lists all sections and subsections') }}</span></p>
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("search") }}">{{ _('Search Page') }}</a><br>
|
|
||||||
<span class="linkdescr">{{ _('search this documentation') }}</span></p>
|
|
||||||
</td><td width="50%">
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("modindex") }}">{{ _('Global Module Index') }}</a><br>
|
|
||||||
<span class="linkdescr">{{ _('quick access to all modules') }}</span></p>
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("genindex") }}">{{ _('General Index') }}</a><br>
|
|
||||||
<span class="linkdescr">{{ _('all functions, classes, terms') }}</span></p>
|
|
||||||
</td></tr>
|
|
||||||
</table>
|
|
||||||
{% endblock %}
|
|
||||||
{% endblock %}
|
|
||||||
61
docs/_templates/indexcontent.html
vendored
61
docs/_templates/indexcontent.html
vendored
@@ -1,61 +0,0 @@
|
|||||||
{% extends "defindex.html" %}
|
|
||||||
{% block tables %}
|
|
||||||
<p><strong>Parts of the documentation:</strong></p>
|
|
||||||
<table class="contentstable" align="center"><tr>
|
|
||||||
<td width="50%">
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("whatsnew/" + version) }}">What's new in Python {{ version }}?</a><br/>
|
|
||||||
<span class="linkdescr">or <a href="{{ pathto("whatsnew/index") }}">all "What's new" documents</a> since 2.0</span></p>
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("tutorial/index") }}">Tutorial</a><br/>
|
|
||||||
<span class="linkdescr">start here</span></p>
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("library/index") }}">Library Reference</a><br/>
|
|
||||||
<span class="linkdescr">keep this under your pillow</span></p>
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("reference/index") }}">Language Reference</a><br/>
|
|
||||||
<span class="linkdescr">describes syntax and language elements</span></p>
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("using/index") }}">Python Setup and Usage</a><br/>
|
|
||||||
<span class="linkdescr">how to use Python on different platforms</span></p>
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("howto/index") }}">Python HOWTOs</a><br/>
|
|
||||||
<span class="linkdescr">in-depth documents on specific topics</span></p>
|
|
||||||
</td><td width="50%">
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("extending/index") }}">Extending and Embedding</a><br/>
|
|
||||||
<span class="linkdescr">tutorial for C/C++ programmers</span></p>
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("c-api/index") }}">Python/C API</a><br/>
|
|
||||||
<span class="linkdescr">reference for C/C++ programmers</span></p>
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("install/index") }}">Installing Python Modules</a><br/>
|
|
||||||
<span class="linkdescr">information for installers & sys-admins</span></p>
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("distutils/index") }}">Distributing Python Modules</a><br/>
|
|
||||||
<span class="linkdescr">sharing modules with others</span></p>
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("documenting/index") }}">Documenting Python</a><br/>
|
|
||||||
<span class="linkdescr">guide for documentation authors</span></p>
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("faq/index") }}">FAQs</a><br/>
|
|
||||||
<span class="linkdescr">frequently asked questions (with answers!)</span></p>
|
|
||||||
</td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<p><strong>Indices and tables:</strong></p>
|
|
||||||
<table class="contentstable" align="center"><tr>
|
|
||||||
<td width="50%">
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("py-modindex") }}">Global Module Index</a><br/>
|
|
||||||
<span class="linkdescr">quick access to all modules</span></p>
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("genindex") }}">General Index</a><br/>
|
|
||||||
<span class="linkdescr">all functions, classes, terms</span></p>
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("glossary") }}">Glossary</a><br/>
|
|
||||||
<span class="linkdescr">the most important terms explained</span></p>
|
|
||||||
</td><td width="50%">
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("search") }}">Search page</a><br/>
|
|
||||||
<span class="linkdescr">search this documentation</span></p>
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("contents") }}">Complete Table of Contents</a><br/>
|
|
||||||
<span class="linkdescr">lists all sections and subsections</span></p>
|
|
||||||
</td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<p><strong>Meta information:</strong></p>
|
|
||||||
<table class="contentstable" align="center"><tr>
|
|
||||||
<td width="50%">
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("bugs") }}">Reporting bugs</a></p>
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("about") }}">About the documentation</a></p>
|
|
||||||
</td><td width="50%">
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("license") }}">History and License of Python</a></p>
|
|
||||||
<p class="biglink"><a class="biglink" href="{{ pathto("copyright") }}">Copyright</a></p>
|
|
||||||
</td></tr>
|
|
||||||
</table>
|
|
||||||
{% endblock %}
|
|
||||||
69
docs/_templates/layout.html
vendored
69
docs/_templates/layout.html
vendored
@@ -1,69 +0,0 @@
|
|||||||
{#
|
|
||||||
haiku/layout.html
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Sphinx layout template for the haiku theme.
|
|
||||||
|
|
||||||
:copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
|
|
||||||
:license: BSD, see LICENSE for details.
|
|
||||||
#}
|
|
||||||
{% extends "basic/layout.html" %}
|
|
||||||
{% set script_files = script_files + ['_static/theme_extras.js'] %}
|
|
||||||
{% set css_files = css_files + ['_static/print.css'] %}
|
|
||||||
|
|
||||||
{# do not display relbars #}
|
|
||||||
{% block relbar1 %}{% endblock %}
|
|
||||||
{% block relbar2 %}{% endblock %}
|
|
||||||
|
|
||||||
{% macro nav() %}
|
|
||||||
<p>
|
|
||||||
{%- block haikurel1 %}
|
|
||||||
{%- endblock %}
|
|
||||||
{%- if prev %}
|
|
||||||
«  <a href="{{ prev.link|e }}">{{ prev.title }}</a>
|
|
||||||
  ::  
|
|
||||||
{%- endif %}
|
|
||||||
<a class="uplink" href="{{ pathto(master_doc) }}">{{ _('Contents') }}</a>
|
|
||||||
{%- if next %}
|
|
||||||
  ::  
|
|
||||||
<a href="{{ next.link|e }}">{{ next.title }}</a>  »
|
|
||||||
{%- endif %}
|
|
||||||
{%- block haikurel2 %}
|
|
||||||
{%- endblock %}
|
|
||||||
</p>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="header">
|
|
||||||
{%- block haikuheader %}
|
|
||||||
{%- if theme_full_logo != "false" %}
|
|
||||||
<a href="{{ pathto('index') }}">
|
|
||||||
<img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
|
|
||||||
</a>
|
|
||||||
{%- else %}
|
|
||||||
{%- if logo -%}
|
|
||||||
<img class="rightlogo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
|
|
||||||
{%- endif -%}
|
|
||||||
<h1 class="heading">
|
|
||||||
<a href="{{ pathto('index') }}"><span>{{ project|e }}</span></a>
|
|
||||||
</h1>
|
|
||||||
<h2 class="heading"><span>{{ shorttitle|e }}</span></h2>
|
|
||||||
{%- endif %}
|
|
||||||
{%- endblock %}
|
|
||||||
</div>
|
|
||||||
<div class="topnav">
|
|
||||||
{{ nav() }}
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
{#{%- if display_toc %}
|
|
||||||
<div id="toc">
|
|
||||||
<h3>Table Of Contents</h3>
|
|
||||||
{{ toc }}
|
|
||||||
</div>
|
|
||||||
{%- endif %}#}
|
|
||||||
{% block body %}{% endblock %}
|
|
||||||
</div>
|
|
||||||
<div class="bottomnav">
|
|
||||||
{{ nav() }}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
========
|
|
||||||
basexmpp
|
|
||||||
========
|
|
||||||
|
|
||||||
.. module:: sleekxmpp.basexmpp
|
|
||||||
|
|
||||||
.. autoclass:: BaseXMPP
|
|
||||||
:members:
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
==========
|
|
||||||
clientxmpp
|
|
||||||
==========
|
|
||||||
|
|
||||||
.. module:: sleekxmpp.clientxmpp
|
|
||||||
|
|
||||||
.. autoclass:: ClientXMPP
|
|
||||||
|
|
||||||
.. automethod:: connect
|
|
||||||
|
|
||||||
.. automethod:: register_feature
|
|
||||||
|
|
||||||
.. automethod:: get_roster
|
|
||||||
|
|
||||||
.. automethod:: update_roster
|
|
||||||
|
|
||||||
.. automethod:: del_roster_item
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
=========
|
|
||||||
xmlstream
|
|
||||||
=========
|
|
||||||
|
|
||||||
.. module:: sleekxmpp.xmlstream
|
|
||||||
|
|
||||||
.. autoclass:: XMLStream
|
|
||||||
:members:
|
|
||||||
@@ -1,269 +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
|
|
||||||
-------------------------
|
|
||||||
``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
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
``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 ``Message`` :term:`stanza object`
|
|
||||||
because the namespaced element name ``{jabber:client}message`` is
|
|
||||||
associated with the 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 ``Message`` object is thus paired with the message stanza handler
|
|
||||||
``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
|
|
||||||
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
|
|
||||||
``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 ``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 ``XMLStream`` attempts to shy away from anything too XMPP specific,
|
|
||||||
``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
|
|
||||||
``BaseXMPP``.
|
|
||||||
|
|
||||||
.. index:: ClientXMPP, BaseXMPP
|
|
||||||
|
|
||||||
ClientXMPP
|
|
||||||
----------
|
|
||||||
``ClientXMPP`` extends ``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
|
|
||||||
-------------
|
|
||||||
``ComponentXMPP`` is only a thin layer on top of ``BaseXMPP`` that
|
|
||||||
implements the component handshake protocol.
|
|
||||||
|
|
||||||
.. index::
|
|
||||||
double: object; stanza
|
|
||||||
|
|
||||||
Stanza Objects: A Brief Look
|
|
||||||
----------------------------
|
|
||||||
.. seealso::
|
|
||||||
See :ref:`api-stanza-objects` for a more detailed overview.
|
|
||||||
|
|
||||||
Almost worthy of their own standalone library, :term:`stanza objects <stanza object>`
|
|
||||||
are wrappers for XML objects which expose dictionary like interfaces
|
|
||||||
for manipulating their XML content. For example, consider the XML:
|
|
||||||
|
|
||||||
.. code-block:: xml
|
|
||||||
|
|
||||||
<message />
|
|
||||||
|
|
||||||
A very plain element to start with, but we can create a :term:`stanza object`
|
|
||||||
using ``sleekxmpp.stanza.Message`` as so::
|
|
||||||
|
|
||||||
msg = Message(xml=ET.fromstring("<message />"))
|
|
||||||
|
|
||||||
The ``Message`` stanza class defines interfaces such as ``'body'`` and
|
|
||||||
``'to'``, so we can assign values to those interfaces to include new XML
|
|
||||||
content::
|
|
||||||
|
|
||||||
msg['body'] = "Following so far?"
|
|
||||||
msg['to'] = 'user@example.com'
|
|
||||||
|
|
||||||
Dumping the XML content of ``msg`` (using ``msg.xml``), we find:
|
|
||||||
|
|
||||||
.. code-block:: xml
|
|
||||||
|
|
||||||
<message to="user@example.com">
|
|
||||||
<body>Following so far?</body>
|
|
||||||
</message>
|
|
||||||
|
|
||||||
The process is similar for reading from interfaces and deleting interface
|
|
||||||
contents. A :term:`stanza object` behaves very similarly to a regular
|
|
||||||
``dict`` object: you may assign to keys, read from keys, and ``del`` keys.
|
|
||||||
|
|
||||||
Stanza interfaces come with built-in behaviours such as adding/removing
|
|
||||||
attribute and sub element values. However, a lot of the time more custom
|
|
||||||
logic is needed. This can be provided by defining methods of the form
|
|
||||||
``get_*``, ``set_*``, and ``del_*`` for any interface which requires custom
|
|
||||||
behaviour.
|
|
||||||
|
|
||||||
Stanza Plugins
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
Since it is generally possible to embed one XML element inside another,
|
|
||||||
:term:`stanza objects <stanza object>` may be nested. Nested
|
|
||||||
:term:`stanza objects <stanza object>` are referred to as :term:`stanza plugins <stanza plugin>`
|
|
||||||
or :term:`substanzas <substanza>`.
|
|
||||||
|
|
||||||
A :term:`stanza plugin` exposes its own interfaces by adding a new
|
|
||||||
interface to its parent stanza. To demonstrate, consider these two stanza
|
|
||||||
class definitions using ``sleekxmpp.xmlstream.ElementBase``:
|
|
||||||
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
class Parent(ElementBase):
|
|
||||||
name = "the-parent-xml-element-name"
|
|
||||||
namespace = "the-parent-namespace"
|
|
||||||
interfaces = set(('foo', 'bar'))
|
|
||||||
|
|
||||||
class Child(ElementBase):
|
|
||||||
name = "the-child-xml-element-name"
|
|
||||||
namespace = "the-child-namespace"
|
|
||||||
plugin_attrib = 'child'
|
|
||||||
interfaces = set(('baz',))
|
|
||||||
|
|
||||||
|
|
||||||
If we register the ``Child`` stanza as a plugin of the ``Parent`` stanza as
|
|
||||||
so, using ``sleekxmpp.xmlstream.register_stanza_plugin``::
|
|
||||||
|
|
||||||
register_stanza_plugin(Parent, Child)
|
|
||||||
|
|
||||||
Then we can access content in the child stanza through the parent.
|
|
||||||
Note that the interface used to access the child stanza is the same as
|
|
||||||
``Child.plugin_attrib``::
|
|
||||||
|
|
||||||
parent = Parent()
|
|
||||||
parent['foo'] = 'a'
|
|
||||||
parent['child']['baz'] = 'b'
|
|
||||||
|
|
||||||
The above code would produce:
|
|
||||||
|
|
||||||
.. code-block:: xml
|
|
||||||
|
|
||||||
<the-parent-xml-element xmlns="the-parent-namespace" foo="a">
|
|
||||||
<the-child-xml-element xmlsn="the-child-namespace" baz="b" />
|
|
||||||
</the-parent-xml-element>
|
|
||||||
|
|
||||||
It is also possible to allow a :term:`substanza` to appear multiple times
|
|
||||||
by using ``iterable=True`` in the ``register_stanza_plugin`` call. All
|
|
||||||
iterable :term:`substanzas <substanza>` can be accessed using a standard
|
|
||||||
``substanzas`` interface.
|
|
||||||
220
docs/conf.py
220
docs/conf.py
@@ -1,220 +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']
|
|
||||||
|
|
||||||
# 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.0RC1'
|
|
||||||
|
|
||||||
# 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 = 'default'
|
|
||||||
|
|
||||||
# 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 = 'haiku'
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
]
|
|
||||||
@@ -1,677 +0,0 @@
|
|||||||
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,2 +0,0 @@
|
|||||||
Create and Run a Server Component
|
|
||||||
=================================
|
|
||||||
@@ -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,2 +0,0 @@
|
|||||||
Send/Receive IQ Stanzas
|
|
||||||
=======================
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
Mulit-User Chat (MUC) Bot
|
|
||||||
=========================
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
Manage Presence Subscriptions
|
|
||||||
=============================
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
Enable HTTP Proxy Support
|
|
||||||
=========================
|
|
||||||
@@ -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,2 +0,0 @@
|
|||||||
Using Stream Handlers and Matchers
|
|
||||||
==================================
|
|
||||||
155
docs/index.rst
155
docs/index.rst
@@ -1,155 +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.
|
|
||||||
|
|
||||||
**Stable Releases**
|
|
||||||
- `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>`_
|
|
||||||
|
|
||||||
**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
|
|
||||||
|
|
||||||
xeps
|
|
||||||
xmpp_tdg
|
|
||||||
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/basexmpp
|
|
||||||
api/xmlstream
|
|
||||||
|
|
||||||
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
|
|
||||||
===================
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
How SASL Authentication Works
|
|
||||||
=============================
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
Supported XEPS
|
|
||||||
==============
|
|
||||||
@@ -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,205 +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 time
|
|
||||||
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 intialize
|
|
||||||
# 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 intial
|
|
||||||
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 intial 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 pydns 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(threaded=False)
|
|
||||||
print("Done")
|
|
||||||
else:
|
|
||||||
print("Unable to connect.")
|
|
||||||
@@ -1,211 +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 time
|
|
||||||
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 intialize
|
|
||||||
# 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 intial
|
|
||||||
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 pydns 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(threaded=False)
|
|
||||||
print("Done")
|
|
||||||
else:
|
|
||||||
print("Unable to connect.")
|
|
||||||
@@ -1,200 +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 time
|
|
||||||
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 intialize
|
|
||||||
# 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 intial
|
|
||||||
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 pydns 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(threaded=False)
|
|
||||||
else:
|
|
||||||
print("Unable to connect.")
|
|
||||||
@@ -1,145 +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 time
|
|
||||||
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 intialize
|
|
||||||
# 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 intial
|
|
||||||
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 pydns 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(threaded=False)
|
|
||||||
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 time
|
|
||||||
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(threaded=False)
|
|
||||||
print("Done")
|
|
||||||
else:
|
|
||||||
print("Unable to connect.")
|
|
||||||
188
examples/muc.py
188
examples/muc.py
@@ -1,188 +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 time
|
|
||||||
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 intialize
|
|
||||||
# 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 intial
|
|
||||||
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 None in [opts.jid, opts.password, opts.room, opts.nick]:
|
|
||||||
optp.print_help()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# 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 pydns 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(threaded=False)
|
|
||||||
print("Done")
|
|
||||||
else:
|
|
||||||
print("Unable to connect.")
|
|
||||||
142
examples/ping.py
142
examples/ping.py
@@ -1,142 +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 time
|
|
||||||
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 intialize
|
|
||||||
# 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 intial
|
|
||||||
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 pydns 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(threaded=False)
|
|
||||||
print("Done")
|
|
||||||
else:
|
|
||||||
print("Unable to connect.")
|
|
||||||
@@ -1,169 +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 time
|
|
||||||
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 intialize
|
|
||||||
# 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 intial
|
|
||||||
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 pydns 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(threaded=False)
|
|
||||||
print("Done")
|
|
||||||
else:
|
|
||||||
print("Unable to connect.")
|
|
||||||
@@ -1,173 +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 time
|
|
||||||
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 intialize
|
|
||||||
# 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 intial
|
|
||||||
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 pydns 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(threaded=False)
|
|
||||||
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,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 time
|
|
||||||
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 intialize
|
|
||||||
# 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 intial
|
|
||||||
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 pydns 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(threaded=False)
|
|
||||||
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,310 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2010 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
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:
|
|
||||||
xmpp = ClientXMPP('user@server.tld/resource', 'password')
|
|
||||||
xmpp.process(block=False) // when block is True, it blocks the current
|
|
||||||
// thread. False by default.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
|
|
||||||
Methods:
|
|
||||||
connect -- Overrides XMLStream.connect.
|
|
||||||
del_roster_item -- Delete a roster item.
|
|
||||||
get_roster -- Retrieve the roster from the server.
|
|
||||||
register_feature -- Register a stream feature.
|
|
||||||
update_roster -- Update a roster item.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, jid, password, ssl=False, plugin_config={},
|
|
||||||
plugin_whitelist=[], escape_quotes=True, sasl_mech=None):
|
|
||||||
"""
|
|
||||||
Create a new SleekXMPP client.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
jid -- The JID of the XMPP user account.
|
|
||||||
password -- The password for the XMPP user account.
|
|
||||||
ssl -- Deprecated.
|
|
||||||
plugin_config -- A dictionary of plugin configurations.
|
|
||||||
plugin_whitelist -- A list of approved plugins that will be loaded
|
|
||||||
when calling register_plugins.
|
|
||||||
escape_quotes -- Deprecated.
|
|
||||||
"""
|
|
||||||
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.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
address -- A tuple containing the server's host and port.
|
|
||||||
reattempt -- If True, reattempt the connection if an
|
|
||||||
error occurs. Defaults to True.
|
|
||||||
use_tls -- Indicates if TLS should be used for the
|
|
||||||
connection. Defaults to True.
|
|
||||||
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.
|
|
||||||
Overriddes XMLStream.get_dns_records to use SRV.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
domain -- The domain in question.
|
|
||||||
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.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
name -- The name of the stream feature.
|
|
||||||
handler -- The function to execute if the feature is received.
|
|
||||||
restart -- Indicates if feature processing should halt with
|
|
||||||
this feature. Defaults to False.
|
|
||||||
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.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
jid -- The JID of the entry to modify.
|
|
||||||
name -- The user's nickname for this JID.
|
|
||||||
subscription -- The subscription status. May be one of
|
|
||||||
'to', 'from', 'both', or 'none'. If set
|
|
||||||
to 'remove', the entry will be deleted.
|
|
||||||
groups -- The roster groups that contain this item.
|
|
||||||
block -- Specify if the roster request will block
|
|
||||||
until a response is received, or a timeout
|
|
||||||
occurs. Defaults to True.
|
|
||||||
timeout -- The length of time (in seconds) to wait
|
|
||||||
for a response before continuing if blocking
|
|
||||||
is used. Defaults to self.response_timeout.
|
|
||||||
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 by setting its subscription
|
|
||||||
status to 'remove'.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
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.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
block -- Specify if the roster request will block until a
|
|
||||||
response is received, or a timeout occurs.
|
|
||||||
Defaults to True.
|
|
||||||
timeout -- The length of time (in seconds) to wait for a response
|
|
||||||
before continuing if blocking is used.
|
|
||||||
Defaults to self.response_timeout.
|
|
||||||
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.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
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.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
iq -- The roster stanza.
|
|
||||||
request -- Indicates if this stanza is a response
|
|
||||||
to a request for the roster.
|
|
||||||
"""
|
|
||||||
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.")
|
||||||
173
sleekxmpp/componentxmpp.py
Normal file → Executable file
173
sleekxmpp/componentxmpp.py
Normal file → Executable file
@@ -1,151 +1,88 @@
|
|||||||
|
#!/usr/bin/python2.6
|
||||||
|
|
||||||
"""
|
"""
|
||||||
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
|
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.
|
|
||||||
|
|
||||||
Methods:
|
|
||||||
connect -- Overrides XMLStream.connect.
|
|
||||||
incoming_filter -- Overrides XMLStream.incoming_filter.
|
|
||||||
start_stream_handler -- Overrides XMLStream.start_stream_handler.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, jid, secret, host, port,
|
|
||||||
plugin_config={}, plugin_whitelist=[], use_jc_ns=False):
|
|
||||||
"""
|
|
||||||
Arguments:
|
|
||||||
jid -- The JID of the component.
|
|
||||||
secret -- The secret or password for the component.
|
|
||||||
host -- The server accepting the component.
|
|
||||||
port -- The port used to connect to the server.
|
|
||||||
plugin_config -- A dictionary of plugin configurations.
|
|
||||||
plugin_whitelist -- A list of desired plugins to load
|
|
||||||
when using register_plugins.
|
|
||||||
use_js_ns -- Indicates if the 'jabber:client' namespace
|
|
||||||
should be used instead of the standard
|
|
||||||
'jabber:component:accept' namespace.
|
|
||||||
Defaults to 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):
|
def incoming_filter(self, xmlobj):
|
||||||
"""
|
if xmlobj.tag.startswith('{jabber:client}'):
|
||||||
Connect to the server.
|
xmlobj.tag = xmlobj.tag.replace('jabber:client', self.default_ns)
|
||||||
|
for sub in xmlobj:
|
||||||
Overrides XMLStream.connect.
|
|
||||||
"""
|
|
||||||
log.debug("Connecting to %s:%s" % (self.server_host,
|
|
||||||
self.server_port))
|
|
||||||
return XMLStream.connect(self, self.server_host,
|
|
||||||
self.server_port)
|
|
||||||
|
|
||||||
def incoming_filter(self, xml):
|
|
||||||
"""
|
|
||||||
Pre-process incoming XML stanzas by converting any 'jabber:client'
|
|
||||||
namespaced elements to the component's default namespace.
|
|
||||||
|
|
||||||
Overrides XMLStream.incoming_filter.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
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.
|
|
||||||
|
|
||||||
Overrides BaseXMPP.start_stream_handler.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
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.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
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)
|
|
||||||
|
|||||||
@@ -3,84 +3,14 @@
|
|||||||
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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
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 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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, condition='undefined-condition', text=None,
|
|
||||||
etype='cancel', extension=None, extension_ns=None,
|
|
||||||
extension_args=None, clear=True):
|
|
||||||
"""
|
|
||||||
Create a new XMPPError exception.
|
|
||||||
|
|
||||||
Extension information can be included to add additional XML elements
|
|
||||||
to the generated error stanza.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
condition -- The XMPP defined error condition.
|
|
||||||
Defaults to 'undefined-condition'.
|
|
||||||
text -- Human readable text describing the error.
|
|
||||||
etype -- The XMPP error type, such as cancel or modify.
|
|
||||||
Defaults to 'cancel'.
|
|
||||||
extension -- Tag name of the extension's XML content.
|
|
||||||
extension_ns -- XML namespace of the extensions' XML content.
|
|
||||||
extension_args -- Content and attributes for the extension
|
|
||||||
element. Same as the additional arguments to
|
|
||||||
the ET.Element constructor.
|
|
||||||
clear -- Indicates if the stanza's contents should be
|
|
||||||
removed before replying with an error.
|
|
||||||
Defaults to 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')
|
|
||||||
|
|
||||||
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'])
|
|
||||||
|
|
||||||
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
|
|
||||||
@@ -1,56 +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.xmlstream import register_stanza_plugin
|
|
||||||
from sleekxmpp.xmlstream.matcher import *
|
|
||||||
from sleekxmpp.xmlstream.handler import *
|
|
||||||
from sleekxmpp.plugins.base import base_plugin
|
|
||||||
|
|
||||||
from sleekxmpp.features.feature_session import stanza
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class feature_session(base_plugin):
|
|
||||||
|
|
||||||
def plugin_init(self):
|
|
||||||
self.name = 'Start Session'
|
|
||||||
self.rfc = '3920'
|
|
||||||
self.description = 'Start Session Stream Feature'
|
|
||||||
self.stanza = stanza
|
|
||||||
|
|
||||||
self.xmpp.register_feature('session',
|
|
||||||
self._handle_start_session,
|
|
||||||
restart=False,
|
|
||||||
order=10001)
|
|
||||||
|
|
||||||
register_stanza_plugin(Iq, stanza.Session)
|
|
||||||
register_stanza_plugin(StreamFeatures, stanza.Session)
|
|
||||||
|
|
||||||
def _handle_start_session(self, features):
|
|
||||||
"""
|
|
||||||
Handle the start of the session.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
feature -- The stream features element.
|
|
||||||
"""
|
|
||||||
iq = self.xmpp.Iq()
|
|
||||||
iq['type'] = 'set'
|
|
||||||
iq.enable('session')
|
|
||||||
response = iq.send(now=True)
|
|
||||||
|
|
||||||
self.xmpp.features.add('session')
|
|
||||||
|
|
||||||
log.debug("Established Session")
|
|
||||||
self.xmpp.sessionstarted = True
|
|
||||||
self.xmpp.session_started_event.set()
|
|
||||||
self.xmpp.event("session_start")
|
|
||||||
@@ -1,21 +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 Session(ElementBase):
|
|
||||||
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = 'session'
|
|
||||||
namespace = 'urn:ietf:params:xml:ns:xmpp-session'
|
|
||||||
interfaces = set()
|
|
||||||
plugin_attrib = 'session'
|
|
||||||
@@ -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_starttls.starttls import feature_starttls
|
|
||||||
from sleekxmpp.features.feature_starttls.stanza import *
|
|
||||||
@@ -1,47 +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 StanzaBase, ElementBase
|
|
||||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
|
||||||
|
|
||||||
|
|
||||||
class STARTTLS(ElementBase):
|
|
||||||
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = 'starttls'
|
|
||||||
namespace = 'urn:ietf:params:xml:ns:xmpp-tls'
|
|
||||||
interfaces = set(('required',))
|
|
||||||
plugin_attrib = name
|
|
||||||
|
|
||||||
def get_required(self):
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class Proceed(StanzaBase):
|
|
||||||
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = 'proceed'
|
|
||||||
namespace = 'urn:ietf:params:xml:ns:xmpp-tls'
|
|
||||||
interfaces = set()
|
|
||||||
|
|
||||||
|
|
||||||
class Failure(StanzaBase):
|
|
||||||
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = 'failure'
|
|
||||||
namespace = 'urn:ietf:params:xml:ns:xmpp-tls'
|
|
||||||
interfaces = set()
|
|
||||||
@@ -1,70 +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 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_starttls import stanza
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class feature_starttls(base_plugin):
|
|
||||||
|
|
||||||
def plugin_init(self):
|
|
||||||
self.name = "STARTTLS"
|
|
||||||
self.rfc = '6120'
|
|
||||||
self.description = "STARTTLS Stream Feature"
|
|
||||||
self.stanza = stanza
|
|
||||||
|
|
||||||
self.xmpp.register_handler(
|
|
||||||
Callback('STARTTLS Proceed',
|
|
||||||
MatchXPath(stanza.Proceed.tag_name()),
|
|
||||||
self._handle_starttls_proceed,
|
|
||||||
instream=True))
|
|
||||||
self.xmpp.register_feature('starttls',
|
|
||||||
self._handle_starttls,
|
|
||||||
restart=True,
|
|
||||||
order=self.config.get('order', 0))
|
|
||||||
|
|
||||||
self.xmpp.register_stanza(stanza.Proceed)
|
|
||||||
self.xmpp.register_stanza(stanza.Failure)
|
|
||||||
register_stanza_plugin(StreamFeatures, stanza.STARTTLS)
|
|
||||||
|
|
||||||
def _handle_starttls(self, features):
|
|
||||||
"""
|
|
||||||
Handle notification that the server supports TLS.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
features -- The stream:features element.
|
|
||||||
"""
|
|
||||||
if 'starttls' in self.xmpp.features:
|
|
||||||
# We have already negotiated TLS, but the server is
|
|
||||||
# offering it again, against spec.
|
|
||||||
return False
|
|
||||||
elif not self.xmpp.use_tls:
|
|
||||||
return False
|
|
||||||
elif self.xmpp.ssl_support:
|
|
||||||
self.xmpp.send(features['starttls'], now=True)
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
log.warning("The module tlslite is required to log in" +\
|
|
||||||
" to some servers, and has not been found.")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _handle_starttls_proceed(self, proceed):
|
|
||||||
"""Restart the XML stream when TLS is accepted."""
|
|
||||||
log.debug("Starting TLS")
|
|
||||||
if self.xmpp.start_tls():
|
|
||||||
self.xmpp.features.add('starttls')
|
|
||||||
raise RestartStream()
|
|
||||||
@@ -1,13 +1,20 @@
|
|||||||
"""
|
"""
|
||||||
SleekXMPP: The Sleek XMPP Library
|
SleekXMPP: The Sleek XMPP Library
|
||||||
Copyright (C) 2010 Nathanael C. Fritz
|
Copyright (C) 2007 Nathanael C. Fritz
|
||||||
This file is part of SleekXMPP.
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
SleekXMPP is free software; you can redistribute it and/or modify
|
||||||
"""
|
it under the terms of the GNU General Public License as published by
|
||||||
__all__ = ['xep_0004', 'xep_0009', 'xep_0012', 'xep_0030', 'xep_0033',
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
'xep_0045', 'xep_0050', 'xep_0060', 'xep_0066', 'xep_0082',
|
(at your option) any later version.
|
||||||
'xep_0085', 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199',
|
|
||||||
'xep_0203', 'xep_0224', 'xep_0249', 'gmail_notify']
|
|
||||||
|
|
||||||
# Don't automatically load xep_0078
|
SleekXMPP is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with SleekXMPP; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
"""
|
||||||
|
__all__ = ['xep_0004', 'xep_0030', 'xep_0045', 'xep_0050', 'xep_0078', 'xep_0092', 'xep_0199', 'gmail_notify', 'xep_0060']
|
||||||
|
|||||||
@@ -1,73 +1,26 @@
|
|||||||
"""
|
"""
|
||||||
SleekXMPP: The Sleek XMPP Library
|
SleekXMPP: The Sleek XMPP Library
|
||||||
Copyright (C) 2010 Nathanael C. Fritz
|
Copyright (C) 2007 Nathanael C. Fritz
|
||||||
This file is part of SleekXMPP.
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
SleekXMPP is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
SleekXMPP is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with SleekXMPP; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class base_plugin(object):
|
class base_plugin(object):
|
||||||
|
|
||||||
"""
|
def __init__(self, xmpp, config):
|
||||||
The base_plugin class serves as a base for user created plugins
|
self.xep = 'base'
|
||||||
that provide support for existing or experimental XEPS.
|
|
||||||
|
|
||||||
Each plugin has a dictionary for configuration options, as well
|
|
||||||
as a name and description.
|
|
||||||
|
|
||||||
The lifecycle of a plugin is:
|
|
||||||
1. The plugin is instantiated during registration.
|
|
||||||
2. Once the XML stream begins processing, the method
|
|
||||||
plugin_init() is called (if the plugin is configured
|
|
||||||
as enabled with {'enable': True}).
|
|
||||||
3. After all plugins have been initialized, the
|
|
||||||
method post_init() is called.
|
|
||||||
|
|
||||||
Recommended event handlers:
|
|
||||||
session_start -- Plugins which require the use of the current
|
|
||||||
bound JID SHOULD wait for the session_start
|
|
||||||
event to perform any initialization (or
|
|
||||||
resetting). This is a transitive recommendation,
|
|
||||||
plugins that use other plugins which use the
|
|
||||||
bound JID should also wait for session_start
|
|
||||||
before making such calls.
|
|
||||||
session_end -- If the plugin keeps any per-session state,
|
|
||||||
such as joined MUC rooms, such state SHOULD
|
|
||||||
be cleared when the session_end event is raised.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
xep -- The XEP number the plugin implements, if any.
|
|
||||||
description -- A short description of the plugin, typically
|
|
||||||
the long name of the implemented XEP.
|
|
||||||
xmpp -- The main SleekXMPP instance.
|
|
||||||
config -- A dictionary of custom configuration values.
|
|
||||||
The value 'enable' is special and controls
|
|
||||||
whether or not the plugin is initialized
|
|
||||||
after registration.
|
|
||||||
post_initted -- Executed after all plugins have been initialized
|
|
||||||
to handle any cross-plugin interactions, such as
|
|
||||||
registering service discovery items.
|
|
||||||
enable -- Indicates that the plugin is enabled for use and
|
|
||||||
will be initialized after registration.
|
|
||||||
|
|
||||||
Methods:
|
|
||||||
plugin_init -- Initialize the plugin state.
|
|
||||||
post_init -- Handle any cross-plugin concerns.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, xmpp, config=None):
|
|
||||||
"""
|
|
||||||
Instantiate a new plugin and store the given configuration.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
xmpp -- The main SleekXMPP instance.
|
|
||||||
config -- A dictionary of configuration values.
|
|
||||||
"""
|
|
||||||
if config is None:
|
|
||||||
config = {}
|
|
||||||
self.xep = None
|
|
||||||
self.rfc = None
|
|
||||||
self.description = 'Base Plugin'
|
self.description = 'Base Plugin'
|
||||||
self.xmpp = xmpp
|
self.xmpp = xmpp
|
||||||
self.config = config
|
self.config = config
|
||||||
@@ -77,15 +30,7 @@ class base_plugin(object):
|
|||||||
self.plugin_init()
|
self.plugin_init()
|
||||||
|
|
||||||
def plugin_init(self):
|
def plugin_init(self):
|
||||||
"""
|
|
||||||
Initialize plugin state, such as registering any stream or
|
|
||||||
event handlers, or new stanza types.
|
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def post_init(self):
|
def post_init(self):
|
||||||
"""
|
|
||||||
Perform any cross-plugin interactions, such as registering
|
|
||||||
service discovery identities or items.
|
|
||||||
"""
|
|
||||||
self.post_inited = True
|
self.post_inited = True
|
||||||
|
|||||||
@@ -1,149 +1,57 @@
|
|||||||
"""
|
"""
|
||||||
SleekXMPP: The Sleek XMPP Library
|
SleekXMPP: The Sleek XMPP Library
|
||||||
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
|
Copyright (C) 2007 Nathanael C. Fritz
|
||||||
This file is part of SleekXMPP.
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
SleekXMPP is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
SleekXMPP is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with SleekXMPP; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
"""
|
"""
|
||||||
|
from __future__ import with_statement
|
||||||
import logging
|
|
||||||
from . import base
|
from . import base
|
||||||
from .. xmlstream.handler.callback import Callback
|
import logging
|
||||||
from .. xmlstream.matcher.xpath import MatchXPath
|
from xml.etree import cElementTree as ET
|
||||||
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
|
import traceback
|
||||||
from .. stanza.iq import Iq
|
import time
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class GmailQuery(ElementBase):
|
|
||||||
namespace = 'google:mail:notify'
|
|
||||||
name = 'query'
|
|
||||||
plugin_attrib = 'gmail'
|
|
||||||
interfaces = set(('newer-than-time', 'newer-than-tid', 'q', 'search'))
|
|
||||||
|
|
||||||
def getSearch(self):
|
|
||||||
return self['q']
|
|
||||||
|
|
||||||
def setSearch(self, search):
|
|
||||||
self['q'] = search
|
|
||||||
|
|
||||||
def delSearch(self):
|
|
||||||
del self['q']
|
|
||||||
|
|
||||||
|
|
||||||
class MailBox(ElementBase):
|
|
||||||
namespace = 'google:mail:notify'
|
|
||||||
name = 'mailbox'
|
|
||||||
plugin_attrib = 'mailbox'
|
|
||||||
interfaces = set(('result-time', 'total-matched', 'total-estimate',
|
|
||||||
'url', 'threads', 'matched', 'estimate'))
|
|
||||||
|
|
||||||
def getThreads(self):
|
|
||||||
threads = []
|
|
||||||
for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace,
|
|
||||||
MailThread.name)):
|
|
||||||
threads.append(MailThread(xml=threadXML, parent=None))
|
|
||||||
return threads
|
|
||||||
|
|
||||||
def getMatched(self):
|
|
||||||
return self['total-matched']
|
|
||||||
|
|
||||||
def getEstimate(self):
|
|
||||||
return self['total-estimate'] == '1'
|
|
||||||
|
|
||||||
|
|
||||||
class MailThread(ElementBase):
|
|
||||||
namespace = 'google:mail:notify'
|
|
||||||
name = 'mail-thread-info'
|
|
||||||
plugin_attrib = 'thread'
|
|
||||||
interfaces = set(('tid', 'participation', 'messages', 'date',
|
|
||||||
'senders', 'url', 'labels', 'subject', 'snippet'))
|
|
||||||
sub_interfaces = set(('labels', 'subject', 'snippet'))
|
|
||||||
|
|
||||||
def getSenders(self):
|
|
||||||
senders = []
|
|
||||||
sendersXML = self.xml.find('{%s}senders' % self.namespace)
|
|
||||||
if sendersXML is not None:
|
|
||||||
for senderXML in sendersXML.findall('{%s}sender' % self.namespace):
|
|
||||||
senders.append(MailSender(xml=senderXML, parent=None))
|
|
||||||
return senders
|
|
||||||
|
|
||||||
|
|
||||||
class MailSender(ElementBase):
|
|
||||||
namespace = 'google:mail:notify'
|
|
||||||
name = 'sender'
|
|
||||||
plugin_attrib = 'sender'
|
|
||||||
interfaces = set(('address', 'name', 'originator', 'unread'))
|
|
||||||
|
|
||||||
def getOriginator(self):
|
|
||||||
return self.xml.attrib.get('originator', '0') == '1'
|
|
||||||
|
|
||||||
def getUnread(self):
|
|
||||||
return self.xml.attrib.get('unread', '0') == '1'
|
|
||||||
|
|
||||||
|
|
||||||
class NewMail(ElementBase):
|
|
||||||
namespace = 'google:mail:notify'
|
|
||||||
name = 'new-mail'
|
|
||||||
plugin_attrib = 'new-mail'
|
|
||||||
|
|
||||||
|
|
||||||
class gmail_notify(base.base_plugin):
|
class gmail_notify(base.base_plugin):
|
||||||
"""
|
|
||||||
Google Talk: Gmail Notifications
|
|
||||||
"""
|
|
||||||
|
|
||||||
def plugin_init(self):
|
def plugin_init(self):
|
||||||
self.description = 'Google Talk: Gmail Notifications'
|
self.description = 'Google Talk Gmail Notification'
|
||||||
|
self.xmpp.add_event_handler('sent_presence', self.handler_gmailcheck, threaded=True)
|
||||||
|
self.emails = []
|
||||||
|
|
||||||
self.xmpp.registerHandler(
|
def handler_gmailcheck(self, payload):
|
||||||
Callback('Gmail Result',
|
#TODO XEP 30 should cache results and have getFeature
|
||||||
MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns,
|
result = self.xmpp['xep_0030'].getInfo(self.xmpp.server)
|
||||||
MailBox.namespace,
|
features = []
|
||||||
MailBox.name)),
|
for feature in result.findall('{http://jabber.org/protocol/disco#info}query/{http://jabber.org/protocol/disco#info}feature'):
|
||||||
self.handle_gmail))
|
features.append(feature.get('var'))
|
||||||
|
if 'google:mail:notify' in features:
|
||||||
|
logging.debug("Server supports Gmail Notify")
|
||||||
|
self.xmpp.add_handler("<iq type='set' xmlns='%s'><new-mail xmlns='google:mail:notify' /></iq>" % self.xmpp.default_ns, self.handler_notify)
|
||||||
|
self.getEmail()
|
||||||
|
|
||||||
self.xmpp.registerHandler(
|
def handler_notify(self, xml):
|
||||||
Callback('Gmail New Mail',
|
logging.info("New Gmail recieved!")
|
||||||
MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns,
|
|
||||||
NewMail.namespace,
|
|
||||||
NewMail.name)),
|
|
||||||
self.handle_new_mail))
|
|
||||||
|
|
||||||
registerStanzaPlugin(Iq, GmailQuery)
|
|
||||||
registerStanzaPlugin(Iq, MailBox)
|
|
||||||
registerStanzaPlugin(Iq, NewMail)
|
|
||||||
|
|
||||||
self.last_result_time = None
|
|
||||||
|
|
||||||
def handle_gmail(self, iq):
|
|
||||||
mailbox = iq['mailbox']
|
|
||||||
approx = ' approximately' if mailbox['estimated'] else ''
|
|
||||||
log.info('Gmail: Received%s %s emails' % (approx, mailbox['total-matched']))
|
|
||||||
self.last_result_time = mailbox['result-time']
|
|
||||||
self.xmpp.event('gmail_messages', iq)
|
|
||||||
|
|
||||||
def handle_new_mail(self, iq):
|
|
||||||
log.info("Gmail: New emails received!")
|
|
||||||
self.xmpp.event('gmail_notify')
|
self.xmpp.event('gmail_notify')
|
||||||
self.checkEmail()
|
|
||||||
|
|
||||||
def getEmail(self, query=None):
|
def getEmail(self):
|
||||||
return self.search(query)
|
iq = self.xmpp.makeIqGet()
|
||||||
|
iq.attrib['from'] = self.xmpp.fulljid
|
||||||
def checkEmail(self):
|
iq.attrib['to'] = self.xmpp.jid
|
||||||
return self.search(newer=self.last_result_time)
|
self.xmpp.makeIqQuery(iq, 'google:mail:notify')
|
||||||
|
emails = iq.send()
|
||||||
def search(self, query=None, newer=None):
|
mailbox = emails.find('{google:mail:notify}mailbox')
|
||||||
if query is None:
|
total = int(mailbox.get('total-matched', 0))
|
||||||
log.info("Gmail: Checking for new emails")
|
logging.info("%s New Gmail Messages" % total)
|
||||||
else:
|
|
||||||
log.info('Gmail: Searching for emails matching: "%s"' % query)
|
|
||||||
iq = self.xmpp.Iq()
|
|
||||||
iq['type'] = 'get'
|
|
||||||
iq['to'] = self.xmpp.boundjid.bare
|
|
||||||
iq['gmail']['q'] = query
|
|
||||||
iq['gmail']['newer-than-time'] = newer
|
|
||||||
return iq.send()
|
|
||||||
|
|||||||
@@ -2,10 +2,6 @@ from . import base
|
|||||||
import logging
|
import logging
|
||||||
from xml.etree import cElementTree as ET
|
from xml.etree import cElementTree as ET
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class jobs(base.base_plugin):
|
class jobs(base.base_plugin):
|
||||||
def plugin_init(self):
|
def plugin_init(self):
|
||||||
self.xep = 'pubsubjob'
|
self.xep = 'pubsubjob'
|
||||||
@@ -24,7 +20,7 @@ class jobs(base.base_plugin):
|
|||||||
def claimJob(self, host, node, jobid, ifrom=None):
|
def claimJob(self, host, node, jobid, ifrom=None):
|
||||||
return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}claimed'))
|
return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}claimed'))
|
||||||
|
|
||||||
def unclaimJob(self, host, node, jobid):
|
def unclaimJob(self, jobid):
|
||||||
return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}unclaimed'))
|
return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}unclaimed'))
|
||||||
|
|
||||||
def finishJob(self, host, node, jobid, payload=None):
|
def finishJob(self, host, node, jobid, payload=None):
|
||||||
@@ -42,8 +38,7 @@ class jobs(base.base_plugin):
|
|||||||
iq['psstate']['item'] = jobid
|
iq['psstate']['item'] = jobid
|
||||||
iq['psstate']['payload'] = state
|
iq['psstate']['payload'] = state
|
||||||
result = iq.send()
|
result = iq.send()
|
||||||
if result is None or type(result) == bool or result['type'] != 'result':
|
if result is None or result['type'] != 'result':
|
||||||
log.error("Unable to change %s:%s to %s" % (node, jobid, state))
|
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user