Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8bdfa77024 | |||
| 15ac3e9fba | |||
| e8d37b409c | |||
| 898f96f265 | |||
| 4fccd77685 | |||
| bf2bf29fc6 | |||
| 34dc236126 | |||
| 9464736551 | |||
| 47f1fb1690 | |||
| 66cf0c2021 | |||
| e7c37c4ec5 | |||
| 919c8c5633 | |||
| f54501a346 | |||
| d20cd6b3e6 | |||
| da6e1e47dc | |||
| 2f0f18a8c6 | |||
| 1c32668e18 | |||
| 77bff9cce7 | |||
| 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 | |||
| 3f41fdd231 | |||
| 8e95ae2948 | |||
| 341c110b6a | |||
| 7522839141 | |||
| 4c410dd48a | |||
| a92075a659 | |||
| 7552efee5c | |||
| 6bc6ebb95d | |||
| e0c32b6d9b |
+3
-5
@@ -1,8 +1,6 @@
|
||||
*.pyc
|
||||
.project
|
||||
build/
|
||||
dist/
|
||||
MANIFEST
|
||||
docs/_build/
|
||||
*.swp
|
||||
.tox/
|
||||
.coverage
|
||||
.pydevproject
|
||||
.settings
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
Pre-requisites:
|
||||
- Python 3.1 or 2.6
|
||||
Python 3.1 or 2.6
|
||||
|
||||
Install:
|
||||
> python3 setup.py install
|
||||
python3 setup.py install
|
||||
|
||||
Root install:
|
||||
> sudo python3 setup.py install
|
||||
|
||||
To test:
|
||||
> cd examples
|
||||
> python echo_client.py -v -j [USER@example.com] -p [PASSWORD]
|
||||
sudo python3 setup.py install
|
||||
|
||||
@@ -1,125 +1,4 @@
|
||||
Copyright (c) 2010 Nathanael C. Fritz
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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."
|
||||
Vendored
-452
@@ -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;
|
||||
}
|
||||
Vendored
-532
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
-256
@@ -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
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
-406
@@ -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;
|
||||
}
|
||||
|
||||
Vendored
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
Vendored
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 27 KiB |
Vendored
-70
@@ -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 */
|
||||
Vendored
-245
@@ -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;
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB |
Vendored
-70
@@ -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 */
|
||||
Vendored
-339
@@ -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;
|
||||
}
|
||||
Vendored
-35
@@ -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 %}
|
||||
Vendored
-61
@@ -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 %}
|
||||
Vendored
-69
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.")
|
||||
@@ -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.")
|
||||
@@ -1,101 +1,65 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2007-2011 Nathanael C. Fritz
|
||||
# All Rights Reserved
|
||||
#
|
||||
# This software is licensed as described in the README.rst and LICENSE
|
||||
# file, 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 testall import TestCommand
|
||||
from sleekxmpp.version import __version__
|
||||
# if 'cygwin' in sys.platform.lower():
|
||||
# min_version = '0.6c6'
|
||||
# else:
|
||||
# min_version = '0.6a9'
|
||||
#
|
||||
# try:
|
||||
# use_setuptools(min_version=min_version)
|
||||
# except TypeError:
|
||||
# # locally installed ez_setup won't have min_version
|
||||
# use_setuptools()
|
||||
#
|
||||
# from setuptools import setup, find_packages, Extension, Feature
|
||||
|
||||
VERSION = __version__
|
||||
DESCRIPTION = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).'
|
||||
with open('README.rst') as readme:
|
||||
LONG_DESCRIPTION = ''.join(readme)
|
||||
|
||||
CLASSIFIERS = [ 'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'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',
|
||||
]
|
||||
|
||||
packages = [ 'sleekxmpp',
|
||||
'sleekxmpp/stanza',
|
||||
'sleekxmpp/test',
|
||||
'sleekxmpp/roster',
|
||||
'sleekxmpp/xmlstream',
|
||||
'sleekxmpp/xmlstream/matcher',
|
||||
'sleekxmpp/xmlstream/handler',
|
||||
'sleekxmpp/plugins',
|
||||
'sleekxmpp/plugins/xep_0004',
|
||||
'sleekxmpp/plugins/xep_0004/stanza',
|
||||
'sleekxmpp/plugins/xep_0009',
|
||||
'sleekxmpp/plugins/xep_0009/stanza',
|
||||
'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(
|
||||
name = "sleekxmpp",
|
||||
version = VERSION,
|
||||
description = DESCRIPTION,
|
||||
long_description = LONG_DESCRIPTION,
|
||||
author = 'Nathanael Fritz',
|
||||
author_email = 'fritzy [at] netflint.net',
|
||||
url = 'http://github.com/fritzy/SleekXMPP',
|
||||
license = 'MIT',
|
||||
platforms = [ 'any' ],
|
||||
packages = packages,
|
||||
requires = [ 'dnspython' ],
|
||||
classifiers = CLASSIFIERS,
|
||||
cmdclass = {'test': TestCommand}
|
||||
)
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2007-2008 Nathanael C. Fritz
|
||||
# All Rights Reserved
|
||||
#
|
||||
# This software is licensed as described in the README file,
|
||||
# which you should have received as part of this distribution.
|
||||
#
|
||||
|
||||
# from ez_setup import use_setuptools
|
||||
from distutils.core import setup
|
||||
import sys
|
||||
|
||||
# if 'cygwin' in sys.platform.lower():
|
||||
# min_version = '0.6c6'
|
||||
# else:
|
||||
# min_version = '0.6a9'
|
||||
#
|
||||
# try:
|
||||
# use_setuptools(min_version=min_version)
|
||||
# except TypeError:
|
||||
# # locally installed ez_setup won't have min_version
|
||||
# use_setuptools()
|
||||
#
|
||||
# from setuptools import setup, find_packages, Extension, Feature
|
||||
|
||||
VERSION = '0.2.3.1'
|
||||
DESCRIPTION = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).'
|
||||
LONG_DESCRIPTION = """
|
||||
SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).
|
||||
"""
|
||||
|
||||
CLASSIFIERS = [ 'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
]
|
||||
|
||||
packages = [ 'sleekxmpp',
|
||||
'sleekxmpp/plugins',
|
||||
'sleekxmpp/stanza',
|
||||
'sleekxmpp/xmlstream',
|
||||
'sleekxmpp/xmlstream/matcher',
|
||||
'sleekxmpp/xmlstream/handler' ]
|
||||
|
||||
if sys.version_info < (3, 0):
|
||||
packages.append('sleekxmpp/xmlstream/tostring26')
|
||||
else:
|
||||
packages.append('sleekxmpp/xmlstream/tostring')
|
||||
|
||||
setup(
|
||||
name = "sleekxmpp",
|
||||
version = VERSION,
|
||||
description = DESCRIPTION,
|
||||
long_description = LONG_DESCRIPTION,
|
||||
author = 'Nathanael Fritz',
|
||||
author_email = 'fritzy [at] netflint.net',
|
||||
url = 'http://code.google.com/p/sleekxmpp',
|
||||
license = 'MIT',
|
||||
platforms = [ 'any' ],
|
||||
packages = packages,
|
||||
requires = [ 'tlslite', 'pythondns' ],
|
||||
)
|
||||
|
||||
|
||||
+270
-13
@@ -1,18 +1,275 @@
|
||||
#!/usr/bin/python2.5
|
||||
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
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)
|
||||
|
||||
+312
-815
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
|
||||
@@ -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.")
|
||||
Regular → Executable
+72
-135
@@ -1,151 +1,88 @@
|
||||
#!/usr/bin/python2.6
|
||||
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
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 . 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 base64
|
||||
import sys
|
||||
import random
|
||||
import copy
|
||||
from . import plugins
|
||||
from . import stanza
|
||||
import hashlib
|
||||
|
||||
from sleekxmpp import plugins
|
||||
from sleekxmpp import stanza
|
||||
from sleekxmpp.basexmpp import BaseXMPP
|
||||
from sleekxmpp.xmlstream import XMLStream, RestartStream
|
||||
from sleekxmpp.xmlstream import StanzaBase, ET
|
||||
from sleekxmpp.xmlstream.matcher import *
|
||||
from sleekxmpp.xmlstream.handler import *
|
||||
srvsupport = True
|
||||
try:
|
||||
import dns.resolver
|
||||
except ImportError:
|
||||
srvsupport = False
|
||||
|
||||
|
||||
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):
|
||||
XMLStream.__init__(self)
|
||||
if use_jc_ns:
|
||||
self.default_ns = 'jabber:client'
|
||||
else:
|
||||
self.default_ns = 'jabber:component:accept'
|
||||
basexmpp.__init__(self)
|
||||
self.auto_authorize = None
|
||||
self.stream_header = "<stream:stream xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams' to='%s'>" % jid
|
||||
self.stream_footer = "</stream:stream>"
|
||||
self.server_host = host
|
||||
self.server_port = port
|
||||
self.set_jid(jid)
|
||||
self.secret = secret
|
||||
self.registerHandler(Callback('Handshake', MatchXPath('{jabber:component:accept}handshake'), self._handleHandshake))
|
||||
|
||||
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 incoming_filter(self, xmlobj):
|
||||
if xmlobj.tag.startswith('{jabber:client}'):
|
||||
xmlobj.tag = xmlobj.tag.replace('jabber:client', self.default_ns)
|
||||
for sub in xmlobj:
|
||||
self.incoming_filter(sub)
|
||||
return xmlobj
|
||||
|
||||
class ComponentXMPP(BaseXMPP):
|
||||
|
||||
"""
|
||||
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:
|
||||
default_ns = 'jabber:client'
|
||||
else:
|
||||
default_ns = 'jabber:component:accept'
|
||||
BaseXMPP.__init__(self, jid, default_ns)
|
||||
|
||||
self.auto_authorize = None
|
||||
self.stream_header = "<stream:stream %s %s to='%s'>" % (
|
||||
'xmlns="jabber:component:accept"',
|
||||
'xmlns:stream="%s"' % self.stream_ns,
|
||||
jid)
|
||||
self.stream_footer = "</stream:stream>"
|
||||
self.server_host = host
|
||||
self.server_port = port
|
||||
self.secret = secret
|
||||
|
||||
self.plugin_config = plugin_config
|
||||
self.plugin_whitelist = plugin_whitelist
|
||||
self.is_component = True
|
||||
|
||||
self.register_handler(
|
||||
Callback('Handshake',
|
||||
MatchXPath('{jabber:component:accept}handshake'),
|
||||
self._handle_handshake))
|
||||
self.add_event_handler('presence_probe',
|
||||
self._handle_probe)
|
||||
|
||||
def connect(self):
|
||||
"""
|
||||
Connect to the server.
|
||||
|
||||
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)
|
||||
|
||||
return 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', '')
|
||||
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.text = hashlib.sha1(pre_hash).hexdigest().lower()
|
||||
self.send_xml(handshake, now=True)
|
||||
|
||||
def _handle_handshake(self, xml):
|
||||
"""
|
||||
The handshake has been accepted.
|
||||
|
||||
Arguments:
|
||||
xml -- The reply handshake stanza.
|
||||
"""
|
||||
self.session_started_event.set()
|
||||
self.event("session_start")
|
||||
|
||||
def _handle_probe(self, presence):
|
||||
pto = presence['to'].bare
|
||||
pfrom = presence['from'].bare
|
||||
self.roster[pto][pfrom].handle_probe(presence)
|
||||
def start_stream_handler(self, xml):
|
||||
sid = xml.get('id', '')
|
||||
handshake = ET.Element('{jabber:component:accept}handshake')
|
||||
if sys.version_info < (3,0):
|
||||
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 _handleHandshake(self, xml):
|
||||
self.event("session_start")
|
||||
|
||||
def connect(self):
|
||||
logging.debug("Connecting to %s:%s" % (self.server_host, self.server_port))
|
||||
return xmlstreammod.XMLStream.connect(self, self.server_host, self.server_port)
|
||||
|
||||
+8
-78
@@ -3,84 +3,14 @@
|
||||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
See the file license.txt for copying permission.
|
||||
"""
|
||||
|
||||
|
||||
class XMPPError(Exception):
|
||||
|
||||
"""
|
||||
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.text = text
|
||||
self.etype = etype
|
||||
self.clear = clear
|
||||
self.extension = extension
|
||||
self.extension_ns = extension_ns
|
||||
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
|
||||
def __init__(self, condition='undefined-condition', text=None, etype=None, extension=None, extension_ns=None, extension_args=None):
|
||||
self.condition = condition
|
||||
self.text = text
|
||||
self.etype = etype
|
||||
self.extension = extension
|
||||
self.extension_ns = extension_ns
|
||||
self.extension_args = extension_args
|
||||
|
||||
@@ -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
|
||||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
Copyright (C) 2007 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
__all__ = ['xep_0004', 'xep_0009', 'xep_0012', 'xep_0030', 'xep_0033',
|
||||
'xep_0045', 'xep_0050', 'xep_0060', 'xep_0066', 'xep_0082',
|
||||
'xep_0085', 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199',
|
||||
'xep_0203', 'xep_0224', 'xep_0249', 'gmail_notify']
|
||||
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.
|
||||
|
||||
# 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']
|
||||
|
||||
+32
-87
@@ -1,91 +1,36 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2007 Nathanael C. Fritz
|
||||
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):
|
||||
|
||||
"""
|
||||
The base_plugin class serves as a base for user created plugins
|
||||
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.xmpp = xmpp
|
||||
self.config = config
|
||||
self.post_inited = False
|
||||
self.enable = config.get('enable', True)
|
||||
if self.enable:
|
||||
self.plugin_init()
|
||||
|
||||
def plugin_init(self):
|
||||
"""
|
||||
Initialize plugin state, such as registering any stream or
|
||||
event handlers, or new stanza types.
|
||||
"""
|
||||
pass
|
||||
|
||||
def post_init(self):
|
||||
"""
|
||||
Perform any cross-plugin interactions, such as registering
|
||||
service discovery identities or items.
|
||||
"""
|
||||
self.post_inited = True
|
||||
|
||||
def __init__(self, xmpp, config):
|
||||
self.xep = 'base'
|
||||
self.description = 'Base Plugin'
|
||||
self.xmpp = xmpp
|
||||
self.config = config
|
||||
self.post_inited = False
|
||||
self.enable = config.get('enable', True)
|
||||
if self.enable:
|
||||
self.plugin_init()
|
||||
|
||||
def plugin_init(self):
|
||||
pass
|
||||
|
||||
def post_init(self):
|
||||
self.post_inited = True
|
||||
|
||||
@@ -1,149 +1,57 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2007 Nathanael C. Fritz
|
||||
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
|
||||
"""
|
||||
|
||||
import logging
|
||||
from __future__ import with_statement
|
||||
from . import base
|
||||
from .. xmlstream.handler.callback import Callback
|
||||
from .. xmlstream.matcher.xpath import MatchXPath
|
||||
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
|
||||
from .. stanza.iq import Iq
|
||||
|
||||
|
||||
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'
|
||||
|
||||
import logging
|
||||
from xml.etree import cElementTree as ET
|
||||
import traceback
|
||||
import time
|
||||
|
||||
class gmail_notify(base.base_plugin):
|
||||
"""
|
||||
Google Talk: Gmail Notifications
|
||||
"""
|
||||
|
||||
def plugin_init(self):
|
||||
self.description = 'Google Talk: Gmail Notifications'
|
||||
|
||||
self.xmpp.registerHandler(
|
||||
Callback('Gmail Result',
|
||||
MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns,
|
||||
MailBox.namespace,
|
||||
MailBox.name)),
|
||||
self.handle_gmail))
|
||||
|
||||
self.xmpp.registerHandler(
|
||||
Callback('Gmail New Mail',
|
||||
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.checkEmail()
|
||||
|
||||
def getEmail(self, query=None):
|
||||
return self.search(query)
|
||||
|
||||
def checkEmail(self):
|
||||
return self.search(newer=self.last_result_time)
|
||||
|
||||
def search(self, query=None, newer=None):
|
||||
if query is None:
|
||||
log.info("Gmail: Checking for new emails")
|
||||
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()
|
||||
|
||||
def plugin_init(self):
|
||||
self.description = 'Google Talk Gmail Notification'
|
||||
self.xmpp.add_event_handler('sent_presence', self.handler_gmailcheck, threaded=True)
|
||||
self.emails = []
|
||||
|
||||
def handler_gmailcheck(self, payload):
|
||||
#TODO XEP 30 should cache results and have getFeature
|
||||
result = self.xmpp['xep_0030'].getInfo(self.xmpp.server)
|
||||
features = []
|
||||
for feature in result.findall('{http://jabber.org/protocol/disco#info}query/{http://jabber.org/protocol/disco#info}feature'):
|
||||
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()
|
||||
|
||||
def handler_notify(self, xml):
|
||||
logging.info("New Gmail recieved!")
|
||||
self.xmpp.event('gmail_notify')
|
||||
|
||||
def getEmail(self):
|
||||
iq = self.xmpp.makeIqGet()
|
||||
iq.attrib['from'] = self.xmpp.fulljid
|
||||
iq.attrib['to'] = self.xmpp.jid
|
||||
self.xmpp.makeIqQuery(iq, 'google:mail:notify')
|
||||
emails = iq.send()
|
||||
mailbox = emails.find('{google:mail:notify}mailbox')
|
||||
total = int(mailbox.get('total-matched', 0))
|
||||
logging.info("%s New Gmail Messages" % total)
|
||||
|
||||
@@ -2,19 +2,15 @@ from . import base
|
||||
import logging
|
||||
from xml.etree import cElementTree as ET
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class jobs(base.base_plugin):
|
||||
def plugin_init(self):
|
||||
self.xep = 'pubsubjob'
|
||||
self.description = "Job distribution over Pubsub"
|
||||
|
||||
|
||||
def post_init(self):
|
||||
pass
|
||||
#TODO add event
|
||||
|
||||
|
||||
def createJobNode(self, host, jid, node, config=None):
|
||||
pass
|
||||
|
||||
@@ -24,7 +20,7 @@ class jobs(base.base_plugin):
|
||||
def claimJob(self, host, node, jobid, ifrom=None):
|
||||
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'))
|
||||
|
||||
def finishJob(self, host, node, jobid, payload=None):
|
||||
@@ -42,8 +38,7 @@ class jobs(base.base_plugin):
|
||||
iq['psstate']['item'] = jobid
|
||||
iq['psstate']['payload'] = state
|
||||
result = iq.send()
|
||||
if result is None or type(result) == bool or result['type'] != 'result':
|
||||
log.error("Unable to change %s:%s to %s" % (node, jobid, state))
|
||||
if result is None or result['type'] != 'result':
|
||||
return False
|
||||
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