pax_global_header00006660000000000000000000000064134245764420014525gustar00rootroot0000000000000052 comment=9d378c611cacf2c5e425cdebc00a7a20a530d67c slixmpp-slix-1.4.2/000077500000000000000000000000001342457644200142025ustar00rootroot00000000000000slixmpp-slix-1.4.2/.gitignore000066400000000000000000000002041342457644200161660ustar00rootroot00000000000000*.py[co] build/ dist/ MANIFEST docs/_build/ *.swp .tox/ .coverage slixmpp.egg-info/ .ropeproject/ 4913 *~ .baboon/ .DS_STORE .idea/ slixmpp-slix-1.4.2/.gitlab-ci.yml000066400000000000000000000006351342457644200166420ustar00rootroot00000000000000stages: - test - trigger test: stage: test tags: - docker image: ubuntu:latest script: - apt update - apt install -y python3 cython3 gpg - ./run_tests.py trigger_poezio: stage: trigger tags: - docker image: appropriate/curl:latest script: - curl --request POST -F token="$SLIXMPP_TRIGGER_TOKEN" -F ref=master https://lab.louiz.org/api/v4/projects/18/trigger/pipeline slixmpp-slix-1.4.2/.travis.yml000066400000000000000000000001651342457644200163150ustar00rootroot00000000000000language: python python: - "3.4" - "3.5" - "3.6" - "3.7-dev" install: - "pip install ." script: testall.py slixmpp-slix-1.4.2/CONTRIBUTING.rst000066400000000000000000000011701342457644200166420ustar00rootroot00000000000000Contributing to the Slixmpp project =================================== To contribute, the preferred way is to commit your changes on some publicly-available git repository (on a fork `on github `_ or on your own repository) and to notify the developers with either: - a ticket `on the bug tracker `_ - a pull request on github - a simple message on `the XMPP MUC `_ Even though Slixmpp’s github repository is just a read-only mirror, we can still be notified of the pull requests and fetch your mirror manually to integrate your changes. slixmpp-slix-1.4.2/INSTALL000066400000000000000000000004731342457644200152370ustar00rootroot00000000000000Pre-requisites: - Python 3.5+ - Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module) - GnuPG, for testing Install: > python3 setup.py install Root install: > sudo python3 setup.py install To test: > cd examples > python3 echo_client.py -d -j [USER@example.com] -p [PASSWORD] slixmpp-slix-1.4.2/LICENSE000066400000000000000000000232351342457644200152140ustar00rootroot00000000000000Copyright (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 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 2004-2013 David Alan Cridland 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. python-gnupg: A Python wrapper for the GNU Privacy Guard ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Copyright (c) 2008-2012 by Vinay Sajip. 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. * The name(s) of the copyright holder(s) may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) "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 HOLDER(S) 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. socksipy: A Python SOCKS client module. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Copyright 2006 Dan-Haim. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of Dan Haim nor the names of his contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. slixmpp-slix-1.4.2/MANIFEST.in000066400000000000000000000003271342457644200157420ustar00rootroot00000000000000include README.rst include LICENSE include run_tests.py include slixmpp/stringprep.pyx recursive-include docs Makefile *.bat *.py *.rst *.css *.ttf *.png recursive-include examples *.py recursive-include tests *.py slixmpp-slix-1.4.2/README.rst000066400000000000000000000115261342457644200156760ustar00rootroot00000000000000Slixmpp ######### Slixmpp is an MIT licensed XMPP library for Python 3.5+. It is a fork of SleekXMPP. Slixmpp's goals is to only rewrite the core of the library (the low level socket handling, the timers, the events dispatching) in order to remove all threads. Building -------- Slixmpp can make use of cython to improve performance on critical modules. To do that, **cython3** is necessary along with **libidn** headers. Otherwise, no compilation is needed. Building is done by running setup.py:: python3 setup.py build_ext --inplace Documentation and Testing ------------------------- 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 Slixmpp:: python run_tests.py The Slixmpp Boilerplate ------------------------- Projects using Slixmpp tend to follow a basic pattern for setting up client/component connections and configuration. Here is the gist of the boilerplate needed for a Slixmpp based project. See the documentation or examples directory for more detailed archetypes for Slixmpp projects:: import logging from slixmpp import ClientXMPP from slixmpp.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) # If you wanted more functionality, here's how to register plugins: # self.register_plugin('xep_0030') # Service Discovery # self.register_plugin('xep_0199') # XMPP Ping # Here's how to access plugins once you've registered them: # self['xep_0030'].add_feature('echo_demo') # 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() self.get_roster() # 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(forever=True) Slixmpp Credits --------------- **Maintainers:** - Florent Le Coz (`louiz@louiz.org `_), - Mathieu Pasquet (`mathieui@mathieui.net `_), **Contributors:** - Emmanuel Gil Peyrot (`Link mauve `_) - Sam Whited (`Sam Whited `_) - Dan Sully (`Dan Sully `_) - Gasper Zejn (`Gasper Zejn `_) - Krzysztof Kotlenga (`Krzysztof Kotlenga `_) - Tsukasa Hiiragi (`Tsukasa Hiiragi `_) Credits (SleekXMPP) ------------------- **Main Author:** Nathan Fritz `fritzy@netflint.net `_, `@fritzy `_ Nathan is also the author of XMPPHP and `Seesmic-AS3-XMPP `_, and a former member of the XMPP Council. **Co-Author:** Lance Stout `lancestout@gmail.com `_, `@lancestout `_ **Contributors:** - Brian Beggs (`macdiesel `_) - Dann Martens (`dannmartens `_) - Florent Le Coz (`louiz `_) - Kevin Smith (`Kev `_, http://kismith.co.uk) - Remko Tronçon (`remko `_, http://el-tramo.be) - Te-jé Rogers (`te-je `_) - Thom Nichols (`tomstrummer `_) slixmpp-slix-1.4.2/docs/000077500000000000000000000000001342457644200151325ustar00rootroot00000000000000slixmpp-slix-1.4.2/docs/.gitignore000066400000000000000000000000111342457644200171120ustar00rootroot00000000000000_build/* slixmpp-slix-1.4.2/docs/Makefile000066400000000000000000000107621342457644200166000ustar00rootroot00000000000000# 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 ' where 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/Slixmpp.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Slixmpp.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/Slixmpp" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Slixmpp" @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." slixmpp-slix-1.4.2/docs/_static/000077500000000000000000000000001342457644200165605ustar00rootroot00000000000000slixmpp-slix-1.4.2/docs/_static/agogo.css000066400000000000000000000153021342457644200203670ustar00rootroot00000000000000/* * 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; }slixmpp-slix-1.4.2/docs/_static/basic.css000066400000000000000000000202471342457644200203600ustar00rootroot00000000000000/* * 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; } } slixmpp-slix-1.4.2/docs/_static/default.css000066400000000000000000000077101342457644200207230ustar00rootroot00000000000000/* * 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; }slixmpp-slix-1.4.2/docs/_static/fonts/000077500000000000000000000000001342457644200177115ustar00rootroot00000000000000slixmpp-slix-1.4.2/docs/_static/fonts/Museo_Slab_500.otf000066400000000000000000001713441342457644200230520ustar00rootroot00000000000000OTTO 0CFF Y GPOS*K-pGSUBm=OS/2xT `cmaphead]6hheal$hmtx)L maxpPnameDdpost2 I*_<DD}@j}}@P72@Kpyrs@   n8 8B!Ef t *      8B p h  B    T \ \ :t . .  Copyright (c) 2009 by Jos Buivenga. All rights reserved.Museo Slab500JosBuivenga: Museo Slab 500: 2009Museo Slab 500Version 1.000MuseoSlab-500Museo Slab is a trademark of Jos Buivenga.Jos BuivengaSpaced and kerned with iKern.http://www.exljbris.comCopyright (c) 2009 by Jos Buivenga. All rights reserved.Museo Slab 500RegularJosBuivenga: Museo Slab 500: 2009MuseoSlab-500Version 1.000Museo Slab is a trademark of Jos Buivenga.Jos BuivengaSpaced and kerned with iKern.http://www.exljbris.comMuseo Slab500   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_UlabdWrjf]pea_mbccqgv}`iF`hwX~MNRSOP>z\Z[efVsQTYktKZ@~  01P    " & 0 : D p y !""""H"`"e   12Q    & 0 9 D p t !""""H"`"d&}/:7652)!621;LAdJ8:,Ky2MuseoSlab-5000 u * ")/5@KU_ekqw $/:>BHNU\bhov &147>EKQ^kqw $)6CJQ\gr} !)1;DMU_ir})4?Rcr}%0;EOWcjt|'6GWgv"(AmacronamacronAbreveabreveAogonekaogonekCacutecacuteCcircumflexccircumflexCdotaccentcdotaccentCcaronccaronDcarondcaroncommaaccentDcroatdcroatEmacronemacronEbreveebreveEdotaccentedotaccentEogonekeogonekEcaronecaronGcircumflexgcircumflexGbrevegbreveGdotaccentgdotaccentGcommaaccentgcommaaccentHcircumflexhcircumflexHbarhbarItildeitildeImacronimacronIbreveibreveIogonekiogonekIdotaccentIJijJcircumflexjcircumflexKcommaaccentkcommaaccentkgreenlandicLacutelacuteLcommaaccentlcommaaccentLcaronlcaronLdotldotNacutenacuteNcommaaccentncommaaccentNcaronncaronnapostropheEngengOmacronomacronObreveobreveOhungarumlautohungarumlautRacuteracuteRcommaaccentrcommaaccentRcaronrcaronSacutesacuteScircumflexscircumflexScedillascedillaTcommaaccenttcommaaccentTcarontcaronTbartbarUtildeutildeUmacronumacronUbreveubreveUringuringUhungarumlautuhungarumlautUogonekuogonekWcircumflexwcircumflexYcircumflexycircumflexZacutezacuteZdotaccentzdotaccentlongsScommaaccentscommaaccentuni021Auni021Buni1E9EEuroinfinityapproxequalnotequallessequalgreaterequalf_ff_f_if_f_lapplef_jzero.tnumone.tnumtwo.tnumthree.tnumfour.tnumfive.tnumsix.tnumseven.tnumeight.tnumnine.tnumhyphen.caseparenleft.caseparenright.casebracketleft.casebracketright.casebraceleft.casebraceright.caseat.caseguillemotleft.caseguillemotright.caseguilsinglleft.caseguilsinglright.caseendash.caseemdash.caseperiodcentered.casequestiondown.caseexclamdown.casebullet.casezero.p_osfone.p_osftwo.p_osfthree.p_osffour.p_osffive.p_osfsix.p_osfseven.p_osfeight.p_osfnine.p_osfzero.t_osfone.t_osftwo.t_osfthree.t_osffour.t_osffive.t_osfsix.t_osfseven.t_osfeight.t_osfnine.t_osfdollar.osfcent.osfsterling.osfyen.osfflorin.osfEuro.osfzero.numeratorone.numeratortwo.numeratorthree.numeratorfour.numeratorfive.numeratorsix.numeratorseven.numeratoreight.numeratornine.numeratorzero.denominatorone.denominatortwo.denominatorthree.denominatorfour.denominatorfive.denominatorsix.denominatorseven.denominatoreight.denominatornine.denominatorthinspaceuni00A0uni00ADCopyright (c) 2009 by Jos Buivenga. All rights reserved.Spaced and kerned with iKern.Museo Slab 500Normalh 7|B dfj}srxGc{~3  eoAuiwvptykm :FEHJIKJLKML N KN{uHl >0<eq ] ` 4 S  G 5X/x5BN"?+{j.0Kuy+C / ` ! !'!v!!!"*"p"#-#p#$$%%%&q&'*'c'(j()))*P*++?+o++,<,-,-..//7/0+01 1y12,2334>456667_78-899A9^99::y;;e;<&<<=+=>>}>?g?@@@A,AB;BhCCD4DDEtEFHF[FG'GJGH:HHIYIJ6JK KKLQLM-MNNO0OPPQRRS,SSTBTTU UWWXgXYKYYZZjZ[[B[}[\\j\]O]^:^_1_``p`aEab/bTbc8cdfdeneffgmghghijjkkklEllm mnHnoop/pqEqr0rsstKtu9uvvfvw wrwxxy yyz=zhzz{{){B{Z{~{{{|&|H|n}-}N}n}~R~f~~1c ḱb҄RVk̆MgO1Tv-Nċb_ o'.TPœG{Ɣ'oϕ/FΗOFw™#eŚMΛ)hߜ?;jJMPdB$P> >$> >>w]V'V'h`h? hh?vS<\w S/iSS<\9h\/\:h\Bm<A</m<c%UU=~  h`(þNPD{@@.nJSdG%=׋;bvg)w((V:G77HH:9Iw:V-(fakibwakkag9IG86HH:ibajkbak9H߀wDY 0./1.*)*vιƲף]a*J k:q\?;»//MY-*Qhh>,M,?f))!6A a$5. ,1, .5$aA 6!)e)@#VwY5  ӿ2oY"0"p7f2vtss9ty@y% ۋ#3aa5P6`_M5PQN5_`6Qk 0..0j #wc*.3=8Mvu`0$PrD4-=w-4K[ֿ]BBlzyx~cY{}@cW\7=IS<&DًWD+%' 3OpCvQwإQQQ+I[ms~xCQ0#')&Ndu=T1@hvP4;EK2%PӋA 0ILpcl ĸsU:r(iOZ.4ȾL64YM9-Jcvx@tC7[ 1!'`Qؘ/(C0-J_jw}-J¶׾bEEYT~--q˹Yt9FGYA>FDu&˥ |G Q^l7nskMZ-'* $7RKY=-U##%%%P y%vfd++fd+qqqqqvggdgd2++%)(V E9+IKFͻ`S>u,&` mb25s=016\S:3(&3CAAs/8aϊͼ'GZ1wOԖ?11@=GuuGrn=k>!AuԤb?v§bSheX>3PvkzhbKc_K(2uջ[BVq_bzxsw5F8b-7hE.TJyY. ;4+tMTL6`1.cX=>!lú ?@3\_P>3lP}i0P0,j}ge]=}w>!Eu;46}wv98>3v~>!j+v~j:;}>3KIQ?e,2mJZz=.FxL:! 769nPW'p*.e~!2~~2=3>>3]!2>3olY+Q+@>:+XiS=l*r!H4wd}jr}qwwK>.r0r>3="xX393 (yA6:iw66gu:6=3^\IBnuIB\>^3!0:lw >;uhO>3d26a`36d_31`a21_. $# .3#$3ыO>!2O#ܧ\Bh``>3PvaRNq^^wurkb17aئ87˪a3+ac01`0 &( .LvPk]$RN$hbNyW&2 c>!4cmUavr~'ByZAdbY>3P yeT8XY>>9hA&Z$#]dzn[nSqHGTny>3v Ghs >2erm2e>>n 1 lr2 >cro'6(rocAL~{ LA=HHK>M|~ M>GJI΋N2qZ>r||r>Zq2X4wwT|}}w,?,9;?94aa5,?vA::B?Vwc#$\_Lq==7k$oLAz|!#Z|~wjuX}vG AOF̼ɹo;xk7g33v?V35QjC|/ 515 ^3~J@DIJ<*\m7&&Ġf}yQX5*:xdh@+ /C|1|zjuX}I@4u{b-#53.DU@7Po@*3 ыcLR31<@$$#5ǽ]2dۈw.ʔxpOw6r7@<fC -kArz;a{xk~oEI!:%& ջ{{c#Cc3?L;L0TeKaE;6ܚSDD<T &IHZuI@;'%>V;CI@<b'%<=J@O&D~f~7 zG7y?dkq5'w{&z@& AI@;$ nӢ{_I@54;.ٕNID<2טSDE;V8ESLozY:9BLOrv`@}<4;4ܙSDE;WP+pw_@|<.++.-++-@"! N?f{ t"jBkͷ\t@uu{@ELvYczjm``nSKC06:Oww70D~|y) 3{qN:ܧȔ}[{27#65%3K3K6)uA362[}AȂ+$(o:s$+Nr\֡`ז* <a|WW?!5VVGv*],&ЌV4n~D1B~xѫ3Rr`aK'X̵`SiI3D7y; %$ yX4HHǷȽbI&09GE5v[w[7[[66%: pu\vTvu p9$L[f7(M܂v)xGϺ³iU4 EA*|{|vX E< z|~E\hPTa75\&5o#_26Y[36__36[Y26_;35;;53;!- NjLI:`и̭LȬ[ ?,"3K̦]]_8?^fZ{\U Lijy֎an\^Du/c8a8a8b*^/8a8b*8bQ Qqo4 #_26Y[36__36[Y26_;35;;53;4O&&C[D%Gudcssf7T:)(W:G77HH:9IbkibbjjaAvtss9t7=f;Tc|rLX1b'S6.\C^of(j}4e1' G,es\_ck`GiaXIPN7$k$GvPRvw\!lzyF*'N0#;}*Y2X &%A%7mqvpN*spotrxpV£ɶh_X; ;% H Z~y$`Y>,.>E,)ET_ƷaPN_aU/Lt/b8a8b*8ba8a8b*8b ,-f Ez9 1wOݗ?11@=GuuGrnL$1wOږ?11@=GuuGrn5L1wOږ?11@=GuuGrnL;3"7;3"1wʏIYIՀ@?11@=GuuGrnMHـϭCnhkb}CGi`77ʏYpHϭCnhkb}CpGi`71wde܀?11@=GuuGrn`f5\&51wַĖ݀?11@=GuuGrn'<][[_lWYmu}|sv}|t8wַ2w][[_lWYmu}|sv}|t=}:z>]Ik@@?H5k@=988}wk98@>wL[@zz5mqv8*spptqx~Vãɶg`8.TJyY. ;4+tMTL6`1.cT/Rw=}w>ؼ!Ev;46}ww98>39$=}w>ؼ!Ev;46}ww98>3=}w>ؼ!EvN;3"q;46}ww98>3=}w>!QEu5\&5v ;46}wv@98 >3]ؼ!2>39$]ؼ!2>39]ؼ!2>39;3"]2>3WS5\&5`=zz>!vl"ts"l>@9~3vPz//z0,CE#,1غʏ!Yә0:lw >;uhO>3]:HϭCnhkb}CGi`7d26a`36d_31`a21_. $# .3#$3x!$*d26a`36d_31`a21_. $# .3#$3;3"d26a`36d_31`a21_. $# .3#$3ʏYݻd26a`36d_31`a21_. $# .3#$3"HϭCnhkb}CGi`7 h5\&5pd26a`36d_31`a21_. $# .3#$3vwTawawdzdzTaxaxTTczEvwlihx`36dZ<lZ^KULF_31_(GZm.$~tlH\D<3 %Y\djؼ  GP^Jeϻ>]dzn[nSqHGTny>$jؼ GP^Jeϻ>]dzn[nSqHGTny>jؼ ;3"vGP^Jeϻ>]dzn[nSqHGTny>j \5\&5GP^Jeϻ>]dzn[nSqHGTny>΋ؼN2qZ>r||r>Zq29΋}!1: 6;>3O<}'߼d@?Z`6>wzDůtʫddB'k8 Gl:Xcv_AS5nݽGt< J[;oLAz~!#Z~~wjuX}vG AOF̼ɹo;xk7g33v?V35QjB$oLAz~!#Z~~wjuX}vG AOF̼ɹo;xk7g33v?V35QjBoLA\z{!#Z{~wjuX}vG AOF̼ɹo;xk7g33v?V35QjBw{;3"oLAʏeX,v!#Zv~wjuX}vG AOF̼ɹo;xk7g33v?V35QjueCHz@ϭCnhkb}CuGi`7oLA^!#Zz~wjuX}vG AOF̼ɹo;xk7g33v?V35Qj{Q\5\&5oLA۸ַď@!@#Z@~wjuX}vG AOF̼ɹo;xk7g3@3v?V35Qj2][[_lWYmu}|sv}|t?D>{o!)T<[ ӋcLP50B=>MmPbs\. BOwF˼˺p5zpo!V3=;º]46y?U48NjmmqvPvNHҩ*spptrwwVãɶg`¡f}yQX5*:nxdh@+ /&+yo@*3 ыcLR31<@$$#5ǽ]2*$o@~Y*3 ыcLR31<@$$#5ǽ]2o@\.;3"]Y*3 ыcLR31<@$$#5ǽ]2o@r5\&5Y*3 ыcLR31<@$$#5ǽ]2 BCA@A$ BCA@A 9VBCA@A\ ؿ;3"FVBCA@ABCA@A55A&5w,J,&N6궁OK=Epc7 ʳAamyomnA}>coLAP iv@!@#Zv@~wjuX}vG AOF̼ɹo;xk7g3@3v?V35Qju>ʳAyamyomnu@A}u>c1w ?vw_ßGuuG=?11@=*iqj`YFrnoLAz1z ?vx_ß|X}vG AOF̼ɹo;xk7g3#Z|~wivziqi`Y3v?V35Qj5F8b-7hE.TJyY. ;4+tMTL6`1.cmZ7&&Ġf}yQX5*:xdh@+ /5F8;3"b-7hE.TJyY. ;4+tMTL6`1.cm\/ܿ;3"_Z7&&Ġf}yQX5*:xdh@+ /5]"8b-7hE.TJyY. ;4+tMTL6`1.c?08``0m7&&Ġf}yQX5*:xdh@+ /f05F84V:W4eb-7hE.TJyY. ;4+tMTL6`1.c7Y4V:W4mȹ]4V:W4Z7&&Ġf}yQX5*:xdh@+ /X=>ؼ!n4V:W4?ú ?@3\_P>3nP}i0P0,j}ge]C~Vvzzm|qtui Y1~zjuX}I@4u{b-#53.DU@7Pu; Qvzzm|qtui`=zz>!vl"ts"l>@9~3vPz//z0,CE#,1CL!n1nzjuX}hv?n*v=*nL*+vzb-#53.DU@7P=}w>!aEv@;46}wv98@>3u@&Vo@*3 ыcLR31<@$$#5ǽ]2=}w>ظ![ ՞Ev;46}wv98>3u5ʳAamyomnA}>co@P }*3 ыcLR31<@$$#5ǽ]2 ʳAamyomnA}>c=}w>!Ew@;46}ww98@>3wV0o@*3 ыcLR31<@$$#5ǽ]280=}w>!C{EtvCiqi`Y?vx_àt;46}wu98>3o@)<mprda?vw`̸cLR31<@$$#5ǽ]2=}w>ؼ!Ev4V:W4;46}ww98>3o@ȹ]4V:W4Y*3 ыcLR31<@$$#5ǽ]2KIQ?;3"e,2mJZz=.FxL:! 769nPW'p*.efC\3;3"kArz;a{xk~oEI!:%& ջ{{c#Cc3?L;L0TeKaEKI ?e,2mJZz=.FxL:! 769nPW'p*.e ʳAamyomnA}>cfCP Ձ@ -kArz;a{xk@~oEI!:%& ջ{{c#Cc3@?L;L0T@e@KaE瀗yʳAamyomn@A}>cKIc'?e,2mJZz=.FxL:! 769nPW'p*.eA0fC -kArz;a{xk~oEI!:%& ջ{{c#Cc3?L;L0TeKaEi0KIp%?Qvzzm|qtui!1e,2mJZz=.FxL:! 769nPW'p*.efCRwa^tl_X{y|1kArz;a{xk~oEI!:%& ջ{{c#Cc3?L;L0TeKaE~ؼ!;3"2~~2=3>>3ּ;3"@;6ܚSDD<T &IHZuI@;z!v(3=2zz2=3(8>1>19J6"M!ܫA8ܚSDD<T &IHZu ..+3k:HϭCnhkb}CGi`7ʏӽܺBCA@A[HϭCnhkb}CGi`7]!2>3VDBCA@A]ظՓ2>35ʳAamyomnA}>cPՖԺBCA@AʳAamyomnܘA}>c]!Giqj`Y?vw_á2>3'63iqi`Y?vw_ß;CI@<%]!,2>3V0+Ov !?>3xӍ8Z >(;uQb'Z'%>V;CI@< %<=J@O&D~f~oؼl;3"A+Q+@>:+XiS=l*b]ؿ:3"'<=J@O&D~f~r!ۗQvzzm|qtui4wd}jr}qwwK>.r0r>37 [zQvzzm|qtuiG7y?dkq5'w{&z@& AI@;7xG7y?dkq6%wz&x@)?SI@<=ݼ"|HX393ּ.v nӢ{_I@="|Qvzzm|qtuiX393_Qvzzm|qtui^ nӢ{_I@="|Qvzzm|qtuiX393Uvzzm|qtui nӢ{_I@=T"zX393%i[$ nӢ{_I@I%=!xX7__!b96^3Z3ܹL nԡ|^z0WI@k:^ؼ!30:lw >;uhO>34V;4ܙSDE;WP+pw_@|<!,Qvzzm|qtui10:lw >;uhO>34Qvzzm|qtui;4ܙSDE;WP+pw_@|<ؼ!4V:W4{0:lw >;uhO>34Թ]4V:W4DV;4ܙSDE;WP+pw_@|<4zevzzm|qtui2e;4ܙSDE;WP+pw_@|<a !0:lw7IjG5J>;uhQ>3e4ܻ;2ܙSD(Df~:=WP+p܋w_@|<d26a`36d_31`a21_. $# .3#$3N&=.++.-++-@"!cP յ߳.++.-++-@"!cD$w2d26a`36d_31`a21_. $# .3#$37$w2$w2dX.++.-++-@"!4#(ַֻAZ ӋcLQ43=?#*D^@c_@&'!4ǽZ5dUE;E--< c>ؼ!4cmUavr~'ByZAdbY>3P yeT8XY>4:LV@~=PQAtv_@|? c>!Qvzzm|qtui4cmUavr~'ByZAdbY>3P yeT8XY>4:<Qvzzm|qtui;@~=PQAtv_@|? c>ؼ!4V:W4"4cmUavr~'ByZAdbY>3P yeT8XY>4:Թ]T4V:W4UV@~=PQAtv_@|?>9v2A&Z$#9&;3"I2A&Z$#Ү9}}*spptrw{@Vãɶg` $#9~4V:W4.2A&Z$#]dzn[nSqHGTny>HϭCnhkb}CGi`7?ʏVYnlJO覕l~zugwV}.J@2'L*&:}J@)jHuϭCnhkb}CGi`7j ￰H GP^Jeϻ>]dzn[nSqHGTny>,/?BҙzJO覕z~zugwV}.~J@2'L*&:}J@~Ejظ պ GP^Jeϻ>]dzn[nSqHGTny>ʳAamyomnA}>c?P{ ՕlJO覕l~zugwV}.J@2'L*&:}J@kj$ʳAsamyomnjA}k>cjجַ #w][[_lWYmu}|sv}|tGP^Jeϻ>]dzn[nSqHGTny>?ķַĻ][[_lWYmu}|sv}|tj=O覕}~zugwV}.J@2'L*&:}J@jؼ  GP^Jeϻ>]dzn[nSqHGTny>$w2?|JO覕|~zugwV}.J@2'L*&:}J@($w2j $ B+9npqe`?vw_η> DKD>?4tk ?vw_ßxV}.J@2(N)$:}J@O覕x~zuhvtiqj`Y>ؼe;3"H1 lr2 >cro'6(roc\vvܿ;3"CrtCv@4vq3vq5v΋ؼN2qZ>r||r>Zq29;3"f\{ t"jBkͷ\t@uu{@ELvYczjm``n@ܿ;3"΋EEN2qZ>r||r>Zq2YS5\&5ݼX4wwT|}}wSeKC06:Oww70D~|y X4wwT|}}wvb0SKC06:Oww70D~|y$b0ݼ4V:W4X4wwT|}}wSԹ]m4V:W4:KC06:Oww70D~|y܈w4^̕xoMw6r<@A? b߄wĀayv7; Ϙ]eKv;|%2=t)G~fpd>9Qvzzm|qtuiA&Z$# (?H\w=? qqH\-\ > H >bF56FF55FQ22&"%"&bvg)w'(پ'^:G77HH:9IW:V-(fbk ibWakka9IG77HH:9IG77GH:Eibakkabkibajkbakt/c8a8b*8bt/8a8b*8bA9w,?ʼxb}X4l]T9 &bIMobtTaILhh||iLE#zys#zEL -H-/;;:''9;&'9 ׅ׶ׅXhҬEðbnpaqbXDj-NSfgiҬEðbnpaqbXDj-NSfgi+q i4X9=;6=Afd++fd1=eAffdfd2++x=eۈw.;@.ʔxoO w6rʔxpOw6r7@<Bۈw(.;@FDu&˥ |G Q^l7nskMZ-'* $7RKY=-U JMJ@e))!6A a$5. J2J .5$aA 6!)e)@J?J9;?9J?A::B?G 3{rN:ܧȔ}[{27#65%GA362[}AȂ+$(o:s$+NrN| m`27s>2/ /]R:2('2CHU`58bό˻&G[v/c-8a8b*8bea8a8b*8bu/b8a8b*8ba8a8b*8bs/c8a8b*8bs/8a8b*8bjjj&+Fw)a'BDˋUX`MH[>,,d Dz9vw'V4@wbZF65FF56E&"":; #$@#:9@##&''`&'(`ig5LGkp,kE}dw6 : ;*MnE.&AIYe1# 6Le+3F|}:WXb;?JZ?)OӋ,,&EW^msJ7Rf6%Pex%K73gw_42>]=-R̋7P +3MIsbmŷuV6n+d@d˸R;;[Q:+KJk_3ytH9S 8%`Qؒ1%E62Mblsz0M+ԿeMGUW2/oзbqAGJ^@@F7uk<pnd@P +-/-ͣ}FR^Bq0Vۺ)7[J^;&"":; #$@#:9@##&''`&'(`g5LGkp,E}dw6 : ;*MnE.&AXf1" 7Kd*3E|}9VXb;?JZ@)OӋ,,&EW^ms7Rf6%Pex%K73gw_42>]=-R̋7P +3MIsbmŷuV6n+d@d˸R;;[Q:+Kk _3ytH9S 8%`Qؒ1%E62Mblsz0M+ԿeMGUW2/oзbqAGJ^@@F7uk<pnd@P +-/-ͣ}FR^Bq0Vۺ)7[J^;0%M9MvvHIN.F}%RU_MZ$E)/~A_+\[\lM$EыRp:<+w8ы^WK3(ELPB6w6*2V2mmǽcF#+93MG2<`KpRu9K9 p <<+|#LvcuYuv#L |-eof7; Ϙ\fJv;|"2=v)E~fpe~ cT09ǹye}Z$IneϨ`V@(-h@QqotJL7\ e e'' KyʝE31yELL17\1 e e'' KyʝE31yEL w!% H Z~y$fT|rLX1b'S6.\C^of(j}4d' G,es\_ck`GiaXIPNSSًwyM:][d*rw&j:V?M0ktVsp|苪g\[bhYQeaYMk1:V?1M0ktVsp|苪g\[bhYQeaYM\:ыw3`R2pi~yyЗ{ܤcVN<Wgg[^rf]\1:3`1R2pi~yyЗ{cVN<Wgg[^rf]x5Zy5;ZTMww4UJYҾ׼og֔CEDEd=apvw}XcO1]XXwecqlgO`apz\dbqeT1UJY1Ҿ׼ogؔCEDEd=apvw}XcO1]XXwecqlgO`apz\dbqe\:3(E,6NM57N߬F`SOhkrKurVmLcdisa\1:3(1E,6NM57N߬F`SOhkrKurVmLcdisaL;7\; e e'' KyʝE31yEL1 1% H Z~y$e1T |rLX1b'S6.\C^of(j}4e<' G:,es\_ck`GiaXIPNS[Sy1M:][d*rw&k;:V?;M0ktVsp|苪g\[bhYQeaYM\;:3`;R2pi~yyЗ{cVN<Wgg[^rf]yo52ZT:UJY:Ҿ׼ogؔCEDEd=apvw}XcO1]XXwecqlgO`apz\dbqe\;:3(;E,6NM57N߬F`SOhkrKurVmLcdisaL7\ e e'' KyʝE31yEL % H Z~y$eT୳|rLX1b'S6.\C^of(j}4d' G,es\_ck`GiaXIPNS#CSI8L8MR^2WXZ C.ILM.^CMCM    1 0=>#CTI8L8MS^2 WXZ C4ILM3^ KQW$. 2 7JKQUVWXYZ[! - @#FC; (I !"#%'(+,-./13456789:=BDEFGHJKLMPRSTUVWXYZ[ls+ELP^d  ";=="B[#hh=ww>~?BCEV]uEGJLTZ[dh jjuu} !"#$%&'*+,-./051234556+789:;<=>?@ """"&(******.,....11111A5666666;;;;?+?***,,,,--.....0000551111223444455555666.888 9 9 9 9!:!:!:";";";";";";$=&?&'@'@'@B 9!:)  /14142  "#834:!$% & 0'(0)** + *,- 517 6 '''''2* ----%        00'''''(0)))))*****    ***!!!!,,,------111&!, ##7&&&&&&./9/9.. 2latn  caseDdnomJfracPliganlnumtnumrzonumpnumsinfsupstnum  4<DLT\dlt| ,6BP`r2dLvwu|xyz{}~$*0gGJhGMdGeJjKfMrZppppj@VVVVVP$::::::4  klmnopqrstabcF\|^klmnopqrst@(((unoyunoyunoyunoy>( z4 !<>\^`hsw}MNWZ[Gz zzac FF\\ktkt  kt IX@**+1@:E:,)e*\L)l<.J1:*W4*U8(X6U-*a++e:eDeX$+ 51 5x5A51P5~5+5\6&F5605915B5  x67a*81eE- +!a)(u*+)w+r+P+(K%++r(}a*)(hE X <%N 0Q!_Q"aCE*)f+z*-j"-J1+R1"-z,e*u!vSGT#YP@072}+.0   m O   1x5x5x5x5~$~5~3~05F56060606060e.50 5n +!+!+!+!+!+!_")(+)+)+)+)C C-C-CC#N++r(r(r(r(r(e6r)N `N  +! u+! +!1)(1)(1)(1m)( 5u*Of5u*x5+)x5+)x5+)x5+)x5+)1r+1r+1r+1r+P5P5 ~C~5C*~5C$~5P+~55^++(5KZ+\6%\6%\6%\6r[5%F5+F5+F5+F5+60r(60r(60Ur(1(5)5)5)B5(B5(B5(B5(ihhX  N  x60x60x60N+B5(h%^^/2$y/r2$*04LP3g+g7W,e0@e9eCe:eY+++++j*+>%>]>+>,> >/>,>7>)>!W;E;-9a:3Q!Q")2+18h+h8ZZV+c3U:&'5FG4%G2A%>.>[>@>5>>3>/>A>.>*H+P#=:,**Q u!w qp''c b&&!!*Pv"vp'b&!*Pv"w p'a&!\slixmpp-slix-1.4.2/docs/_static/fonts/Museo_Slab_500italic.otf000066400000000000000000002015101342457644200242250ustar00rootroot00000000000000OTTO 0CFF [ GPOS>I}Ƽ.GSUBm=tOS/2  `cmap(headV6hhea!Z$hmtx/( maxpPnamewpost2 (_<DkDkB0jB+0P82@Kxljb   n8 8 B(Lt *      8 B p    P & ! & T;   : . .  #Copyright (c) 2009 by Jos Buivenga. All rights reserved.Museo Slab500 ItalicJosBuivenga: Museo Slab 500 Italic: 2009Museo Slab 500 ItalicVersion 1.000MuseoSlab-500ItalicMuseo Slab is a trademark of Jos Buivenga.Jos BuivengaSpaced and kerned with iKern.http://www.exljbris.comCopyright (c) 2009 by Jos Buivenga. All rights reserved.Museo Slab 500ItalicJosBuivenga: Museo Slab 500 Italic: 2009MuseoSlab-500ItalicVersion 1.000Museo Slab is a trademark of Jos Buivenga.Jos BuivengaSpaced and kerned with iKern.http://www.exljbris.comMuseo Slab500 Italic   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_UlabdWrjf]pea_mbccqgv}`iF`hwX~MNRSOP>z\Z[efVsQTYktKZ@~  01P    " & 0 : D p y !""""H"`"e   12Q    & 0 9 D p t !""""H"`"d'}/:7652)!621;LAdJ8:,Ky2MuseoSlab-500Italic0 Ru G ")/5@KU_ekqw $/:>BHNU\bhov &147>EKQ^kqw $)6CJQ\gr} !)1;DMU_ir})4?Rcr}%0;EOWcjt|'6GWgv)7=AmacronamacronAbreveabreveAogonekaogonekCacutecacuteCcircumflexccircumflexCdotaccentcdotaccentCcaronccaronDcarondcaroncommaaccentDcroatdcroatEmacronemacronEbreveebreveEdotaccentedotaccentEogonekeogonekEcaronecaronGcircumflexgcircumflexGbrevegbreveGdotaccentgdotaccentGcommaaccentgcommaaccentHcircumflexhcircumflexHbarhbarItildeitildeImacronimacronIbreveibreveIogonekiogonekIdotaccentIJijJcircumflexjcircumflexKcommaaccentkcommaaccentkgreenlandicLacutelacuteLcommaaccentlcommaaccentLcaronlcaronLdotldotNacutenacuteNcommaaccentncommaaccentNcaronncaronnapostropheEngengOmacronomacronObreveobreveOhungarumlautohungarumlautRacuteracuteRcommaaccentrcommaaccentRcaronrcaronSacutesacuteScircumflexscircumflexScedillascedillaTcommaaccenttcommaaccentTcarontcaronTbartbarUtildeutildeUmacronumacronUbreveubreveUringuringUhungarumlautuhungarumlautUogonekuogonekWcircumflexwcircumflexYcircumflexycircumflexZacutezacuteZdotaccentzdotaccentlongsScommaaccentscommaaccentuni021Auni021Buni1E9EEuroinfinityapproxequalnotequallessequalgreaterequalf_ff_f_if_f_lapplef_jzero.tnumone.tnumtwo.tnumthree.tnumfour.tnumfive.tnumsix.tnumseven.tnumeight.tnumnine.tnumhyphen.caseparenleft.caseparenright.casebracketleft.casebracketright.casebraceleft.casebraceright.caseat.caseguillemotleft.caseguillemotright.caseguilsinglleft.caseguilsinglright.caseendash.caseemdash.caseperiodcentered.casequestiondown.caseexclamdown.casebullet.casezero.p_osfone.p_osftwo.p_osfthree.p_osffour.p_osffive.p_osfsix.p_osfseven.p_osfeight.p_osfnine.p_osfzero.t_osfone.t_osftwo.t_osfthree.t_osffour.t_osffive.t_osfsix.t_osfseven.t_osfeight.t_osfnine.t_osfdollar.osfcent.osfsterling.osfyen.osfflorin.osfEuro.osfzero.numeratorone.numeratortwo.numeratorthree.numeratorfour.numeratorfive.numeratorsix.numeratorseven.numeratoreight.numeratornine.numeratorzero.denominatorone.denominatortwo.denominatorthree.denominatorfour.denominatorfive.denominatorsix.denominatorseven.denominatoreight.denominatornine.denominatorthinspaceuni00ADuni00A0Copyright (c) 2009 by Jos Buivenga. All rights reserved.Spaced and kerned with iKern.Museo Slab 500 ItalicMuseo Slab 500Normalh 7|B dfj}srxGc{~3  eoAuiwvptykm :FEHJIKJLKML N MPu|Q%aZL.Rw5   m  p E  H$ ^i"DXq_.m&n,qci-<n6\  ! !""F"^"##L#}#$ $9$$$%*%d%%&&&'v(.(y()\))**+@+,,-!--.Z.//G//0D01D12233D34845656$667y889w::;<'>f>??r?@@A;ABByBC#CD5DE^EFBFG3GHVHINIJJKNKL/LLMM|MN NO=OfOPoPQ;QRLRSSSTjTUjVVWWXY'YZdZ[l[\Q\]]O]^"^k^__z``d`aeabcbcc`cdddeehef,fg=ghYhijjjk=kl>>$> >>׋wV&V'vhh?Jhh?$vS<\wJS/ESS<\8A\/\8A\pBL<qA</L<l%UUOA|wMyͻTY!Dw+; PF;_?)MЋRbvg)w,t+@{:U:27:7=Vw@q:0V'Kfdr@qhw@[`c^# =VU:17:7ڍqh[ac]ds9HB="#>ԛ.*w)0xԼԧ`W ;,q(pH12z/uIX1=^h h=,,l{aQ>28HOg)/=,Fm,9Ofwl962/`CQ?0Vw!<  ׿I($Y"0v"`9gvcss9[t}=syRmyy*Ћ##assa+/q,,IV///"'Vfnaq@ bA9ʹjU:;D{~WƦbqA75X^ %#)#%a%%Q y!vfd++fd+qqqqqvffdfd2++&V hF< (FtخlTrѪkVkd\{> 3rk|sl?RVDh3tʱhNVn\er{rjh;IS3/UO!sD;?!3IyJ~RO6w[Q}c=>hǕ&49Q#\[K|> 3hP|nM,E "Uu]ZOȋ=}w>t;3{6}w{98{> 3v~>,v~j{:;}{> 3XIJ[6Zz{=x.H~ vG);=7RtJ~W<TUz~2~]~2|=2|>]|> 3R22|> 3v/X)F|>8?.zNkUNqê+~ab ?r{x4t\X`xxe<_{}|>Yr5r|> 3=pX4t{9 4(yby6:iw}guY:6|=3_B{nu*BY|>*41?nw |>7@ujL{> 3GKpdY}CKrbUyJ2(51N5*24ߋO>2O2_Nlge{> 2{j^FfPUrspmGQק8J CPlbUyJ1(616gt5cj-Y_tzfT"{h[h^]l{> 2t }|pb.AM8b% u%e( oR!Ϸ|[Z8!Cg(?IV.G?ɗ/29vTeT: 3wW?#͝˷ӝ|>A Sr_flmf[|U,Tۣ|>Gsln3v Up {>ermGe|>FtKlr |>\croa6rowc|>ooC'L}{LA|=F{>)O}{9O|>Iً3q|>Cr||4r|>`q2ȋX4twwaeTy}yw_,?,::?9,aK{a&9,?rv}A96:|B?Vwc#$\_L{=L[|=@N^=zJC{X7_|Ft}rv-=- 5V@ӻ̦m\{ir_g33wAxD09TsXvEfxNl SF HIgXgyI|@C ҶZ:%'55;izf1(֋oCI&-R ybi BG#$8AW:Ϻ|Gt}rvI|@l7yxtNL&T%ڭQE< !D`y@`1(͋p@Q4.XٞM>&< ѼųiG{zsvCۈwCΓ؜yqPx:ju8|@fC,bE|m ;bwwk؈}U0@,:! 6нxe u=d0;T˿q8%R ?bЉ,Rwr[Y`9[5͂}rw賷:U3 ?3m|G}@;%2̃}rvI|@Qz|b*@%f(xBf}{=>J|@|7 yYcvhT ~}7,v}@ KJ}@2|qwI|@,{z{~04RvÛnbzwwP{śn_[`=\5͂}rwȳ;V57?UFdۆb:,9=^s|c|@~4IRwr[Y`9[5͂}rwȳ:U5@/l||d|@~g08/D+9.BQ7 ,"7QGvPB\yPn SF ~uuUw|@LmYcrdm``s_G07{:Govܘ1l02uywv})Nԫ|pRРP᠟ӔA}l2y1w#5z5ͅAz1xz|3K3K5}8Lb*Յ2ᒋH֓К#W|AćG~x(?s5vw$t x(Ek\֡`ז* <a}WW?"5UVGvr,&V4~DB3Y~-$8T*':d)vМD{70~ 3< =wX4tGHǖ˳cQ (;!wn#G}E\5Ǡv[wa[56%s 4ql\mU~~?p $L[f7L7!M݄vMMxۊĬlW/?&  U >;]O7Rj@5q&4$_36X\36__36\X36_;36;;63;!-NjLH;_йͭLȬ[ ?,"'*^˰‹[UeCAWiXve{Y0 Rom}⑳\cU]2B/]mf$c`Dc]\f%S`g Qq3 $_36X\36__36\X36_;36;;63;3O&&B\D&Gudctse@H:),{:R;56<4@Tdrqg\`c^Avtss9t7=W;;LX3{/S?7OGZuldZ1k1Tcu} gweY]dZIiZZI@(GvPRvwtPtѼssP*^z(x8('Qx*VcY2Y # 6 A &@mqvWp*xmgtrzpVҥγmfȌ\;;% HY{w\$&`]O3%*69R\jǺj\CMWPRL/tT`c/]5fd`c/]`}Z`@#I\5j|=98{8}w{98{>MZz^z;mqvNvKw*xmgurywVҥγmf3/UO!sD;?!3IyJ~RO6w[Q}o6 2uȋ=}w>ؼv;3{6}w{98{> 3 9=ȋ=}w>ؼv(y;3{6}w{98{> 3ȋ=}w>ؼv;L";3{6}w{98{> 3ȋ=}w>vr5q&4;3{6}w{98{> 3Rؼ22|> 39=Rؼe2|> 3b9(Rؼ82|> 39;L"RY2|> 3S5q&4h=zz>t@VC W|>]?{9]~3tQz..zET)Pغʏ؈1?nw |>7@ujL{> 3:HŷܙBp~fncFPa]:{GKpdY}CKrbUyJ2(51N5*24[=K(GKpdY}CKrbUyJ2(51N5*24;L"2GKpdY}CKrbUyJ2(51N5*24ʏGKpdY}CKrbUyJ2(51N5*24I\HŷܙBp~fncFPa]:{5q&4GKpdY}CKrbUyJ2(51N5*24vwT`wawczczTax`xTTbz̗ffvjdY}mX^MGXKBlbUy7BUn{I1&6~un#Fg[TL5*2Y`hwؼW?#͝˷ӝ|>A Sr_flmf[|U,Tۣ|>Gsln=wؼ=(SC?#͝˷ӝ|>A Sr_flmf[|U,Tۣ|>Gslnwؼ;L"C?#͝˷ӝ|>A Sr_flmf[|U,Tۣ|>Gslnw5q&4C?#͝˷ӝ|>A Sr_flmf[|U,Tۣ|>Gslnًؼ3q|>Cr||4r|>`q2e9(ۋ}1?D7{> 3o<}(ӵnQ.EV0Svx֚ʯålh?!j&4 h?PRsa@Zxz7n F%M uzJC{X7_~Ft}rv-=- 5V@ӻ̦m\{ir_g33wAxD09TsSS=zJC{X7_~Ft}rv-=- 5V@ӻ̦m\{ir_g33wAxD09Ts3S(zJC\{X7_{Ft}rv-=- 5V@ӻ̦m\{ir_g33wAxD09TsS;L"zJCʏ{X7_wFt}rv-=- 5V@ӻ̦m\{ir_g33wAxD09TsTHŷܙBp~fncFPa]:{zJC{X7_~Ft}rv-=- 5V@ӻ̦m\{ir_g33wAxD09Tsm5q&4zJC۸յ"­SY@7_Ft}rv -=- 5V@ӻ̦m\{ir@_g33wAxD09TssCf^\UhT`ryzpxxr=C>o 6[?Z͋oBQ41YߞKDHoRYx]4$6Vw@λ˫pVp]o Vp4_Oʺ¶jG}yDyA{H49Tq{mqvRvLw=*xmhtrywVҥγlg֋oBI',R ybi AF$$8o:wy@`1(͋p@Q4.XٞM>&< ѼųiG{z*=y@(1(͋p@Q4.XٞM>&< ѼųiG{zy@\b;L"1(͋p@Q4.XٞM>&< ѼųiG{zy@05q&4^1(͋p@Q4.XٞM>&< ѼųiG{z2̃}rvI|@Qz|x=2̃}rvI|@Qz|*(a2̃}rvI|@Qz|\;L"2̃}rvI|@Qz|2̃}rvI|@Qz|P5V&5wS-"ktcO/MXGHcCxv"QUVٷhZQc8D!#ַaN1@=g4ʏIRwr[Y`9[5͂}rwʳ:U5@/l||d|@~?HŷܙBp~fncFPa]:{g08/D+9.BQ7 ,"7Qg=(*08/D+9.BQ7 ,"7Q\u;L"08/D+9.BQ7 ,"7Qʏܨg08/D+9.BQ7 ,"7QHŷܙBp~fncFPa]:{C5q&4r08/D+9.BQ7 ,"7QG-bb-VvHxiv~1/DthdUQdYU2.BxLUcy}svnm ,"qpw= ,eп|?h|rwI|@m/+y9#&RxI|@\tux^==(,eп|?h|rwI|@m/+y9#&RxI|@\tux=\q;L",eпv?h|rwI|@m/+y9#&RxI|@\tux=@5q&4B,eп|?h|rwI|@m/+y9#&RxI|@\tuxf!vLO=| ^Ejβw|@>~uuUw|@LmYcrdm``sB(GvPg\yPnSF @Oc\jG}@A ҶZ:%(54=hf!vLO=| ^Ejβw|@>~uuUw|@LmYcrdm``s@\5q&4)1w]^]?11@|=F%GrnizJC{X7_~Ft}rv-=- 5V@ӻ̦m\{ir_g33wAxD09Tsp)1w]^]?11@|=F%GrnHˮݚA`iymmrB}C|^@NˮݚA`iymmrB}C|^zJCQ{X7_wFt}rv-=- 5V@ӻ̦m\{ir_g33wAxD09TsxOˮݚA`iymmrB}C|^)1wddMwzeˠF%G|=?11@|=$[k`]SXUrnzLWvU`X>Mw{eŦ]}rv-=- 5V@ӻ̦m\{ir_g37_Zn>Yk^]QU3wAxD09Ts;=(IS3/UO!sD;?!3IyJ~RO6w[Q}z(1(֋oCI&-R ybi BG#$8;;L"#IS3/UO!sD;?!3IyJ~RO6w[Q}z\d;L"1(֋oCI&-R ybi BG#$8;IS3/UO!sD;?!3IyJ~RO6w[Q}|/A""/zf1(֋oCI&-R ybi BG#$8"/;J0F:g8IS3/UO!sD;?!3IyJ~RO6w[Q}@0F:g8zȹ]0F:g8i1(֋oCI&-R ybi BG#$8c=>ؼl(0F:g8Ǖ&49Q#\[K|> 3lP|nM,E "Uu]ZOAI4Vcxkhpgǧk:Ϻ~Gt}rvI|@l7yxtNL&T%ڭQE< !D`u;Qcxkhpgǧh=zz>t@VC W|>]?{9]~3tQz..zET)PAL!W:ϺnGt}rv5?*|=+L+x*yxtNL&T%ڭQE< !D`ȋ=}w>v;3{6}w{98{> 3Vy@`1(͋p@Q4.XٞM>&< ѼųiG{zPȋ=}w>ظv;3{6}w{98{> 305ˮݚA`iymmrB}C|^y@Q`1(͋p@Q4.XٞM>&< ѼųiG{zPˮݚA`iymmrB}C|^ȋ=}w>v;3{6}w{98{> 3V/y@`1(͋p@Q4.XٞM>&< ѼųiG{z!/ȋ=}w>tA\k`]SMw{dˠ;3{6}w{98{> 3y@-`2$blicXMwzeοŢp@Q4.XٞM>&< ѼųiG{zȋ=}w>ؼv0F:g8;3{6}w{98{> 3y@ȹ]0F:g8e1(͋p@Q4.XٞM>&< ѼųiG{zXI;L"-J[6Zz{=x.H~ vG);=7RtJ~W<TUzfC\g;L"bE|m ;bwwkڈ}U0@,:! 6нxe u=d0;T˿q8%R ?bXIJ[6Zz{=x.H~ vG);=7RtJ~W<TUzT\ˮݚA`iymmrB}C|^fCQ,bE|m ;bwwkֈ}U0@,:! 6нxe u=d0;T˿q8%R ?b{ˮݚA`iymmr捘B}C|^XIJ[6Zz{=x.H~ vG);=7RtJ~W<TUz%}/fC,bE|m ;bwwk܈}U0@,:! 6нxe u=d0;T˿q8%R ?bL/XIQcxkhpgǧJ[6Zz{=x.H~ vG);=7RtJ~W<TUzfCQw=hOo`UJ3bE|m ;bwwkވ}U0@,:! 6нxe u=d0;T˿q8%R ?b~ؼ;L"{2~]~2|=2|>]|> 3Љּ,R;L"7wr[Y`9[5͂}rw:U3 ?3m|G}@z3z^z2|=2(8|>z1{>z18R=(3w"ωM!+Rԧwr[Y`9[5͂}rwԳ:U3 ?3m| --Ԛ*|<D~MRغʏj؇2|> 3:HŷܙBp~fncFPa]:{ʏ2̃}rvI|@Qz|tyHŷܙBp~fncFPa]:{RA2|> 3'V2̃}rvI|@Qz|RظN2|> 35ˮݚA`iymmrB}C|^Q2̃}rvI|@Qz|'tˮݚA`iymmr؍B}C|^RG[k`]SMw{eˠ2|> 3WvC[xMw{dǣ|rwJ}@Rz|PsxZk]\Qy%R22|> 3yV/5Ov pψ c |>=(r7uQ;?{> 3b;%2̃}rvI|@Qz|&%f(xBf}{=>J|@vؼ;L"NkX)F|>8?.zNkUNqê+~abb]V:M"f(xBf}{=>J|@ ?rxQcxkhpgǧ4t\X`xxe<_{}|>Yr5r|> 3|7 HEQcxkhpgǧ~yYcvhT ~}7,v}@ KJ}@6 yYcvhT z7-q|@ HUI|@=ݼxm(X4t{9 4ּ3(2|qwI|@,{z{~=zpwQcxkhpgǧX4t{9 45Qcxkhpgǧ+02|qwI|@,{z{~=zpwQcxkhpgǧX4t{9 4JUcxkhpgǧ2|qwI|@,{z{~=TxX4t{9 4&a[2|qwI|@,{z{~ry&=pX8ttt!b{9X-^z3eZ3"sykvɗ{￝&WI|@ak2^{2ؼW(1?nw |>7@ujL{> 34IR(2Vwr[Y`9[5͂}rwԳ:U5@/l||d|@~Qcxkhpgǧ"1?nw |>7@ujL{> 34RQcxkhpgǧwr[Y`9[5͂}rwԳ:U5@/l||d|@~ؼd0F:g8)1?nw |>7@ujL{> 34Թ]IR0F:g8Vwr[Y`9[5͂}rwʳ:U5@/l||d|@~4yR ecxkhpgǧ)ewr[Y`9[5͂}rwԳ:U5@/l||d|@~a 1?nw(t@uIz5a|>7@ujM{> 3e+vA4twr[vnkR'xBf}{=>U5@/l|ؓ|d|@~GKpdY}CKrbUyJ2(51N5*24|h08/D+9.BQ7 ,"7QGKpdY}CKrbUyJ2(51N5*24WˮݚA`iymmrB}C|^Qܨg08/D+9.BQ7 ,"7QˮݚA`iymmr܍B}C|^ ,&GKpdY}CKrbUyJ2(51N5*24@= , ,08/D+9.BQ7 ,"7Q(@}x@CRt 98{8}x{98abQq]UzN*-B#jc9@?>g0!>]͋o@P5-XןM.<[CYhD+6,BмĴiG{{s/Q7 ,"7Q?c>ؼv(5cj-Y_vzfT"{h[h^]n{> 2v }|pb.AM89v5: c(VP~;GLCi|d{@?c>`vQcxkhpgǧ5cj-Y_vzfT"{h[h^]n{> 2v }|pb.AM89v5:ߋQcxkhpgǧ>P~;GLCi|d{@?c>ؼv 0F:g85cj-Y_vzfT"{h[h^]n{> 2v }|pb.AM89v5:Թ]o0F:g8'VP~;GLCi|d{@b(3u u%e( oR!Ϸ|[Z8!Cg(?IV.G?ɗ/3 '(0yi+k‚cg՚37 W$.+]XoKc;–6b~;L"u u%e( oR!Ϸ|[Z8!Cg(?IV.G?ɗ/3 \'3;L"0yi+k‚cg՚37 W$.+]XoKc;–6mqvJv5*xmguryoVҥγmf# voR!Ϸ|[Z8!Cg(?IV.G?ɗ/}Eox$b2mqvSvV*xmhtryoVӥγlfv+k‚cg՚37 W$.+]XoKc;–6Yo|A Sr_flmf[|U,Tۣ|>GslnoHŷܙBp~fncFPa]:{=ʏ ,eпn?h|rwI|@m/+y9#&RxI|@\tux_HŷܙBp~fncFPa]:{wW?#͝˷ӝ|>A Sr_flmf[|U,Tۣ|>Gsln= ,eп|?h|rwI|@m/+y9#&RxI|@\tux>{wظW?#͝˷ӝ|>A Sr_flmf[|U,Tۣ|>GslnˮݚA`iymmrB}C|^=Q ,eпn?h|rwI|@m/+y9#&RxI|@\tuxZˮݚA`iymmrB}C|^wجյ^~wf^\UhT`ryzpxxr?#͝˷ӝ|>A Sr_flmf[|U,Tۣ|>Gsln=ĵյ`f^\UhT`ryzpxxrx,eп~?h|rwI|@m/+y9#&RxI|@\tuxwؼW?#͝˷ӝ|>A Sr_flmf[|U,Tۣ|>Gsln% ,= ,eп|?h|rwI|@m/+y9#&RxI|@\tux^ ,w[/QbkibYMwzeоۢ |>A u:K-T|>C wxy?WvChdMw{d<ȥZ|rwI|@m/+y9#&RxI|@\tux,eпTdZj^]QFؼtS;L" lr |>\croa6rowc|> A\ԯ;L"V@rtjCz}@$vqe2Lvqns|@ًؼ3q|>Cr||4r|>`q29;L"f!vL\O=| ^Ejβw|@>~uuUw|@LmYcrdm``srB;L"ً3q|>Cr||4r|>`q2S5q&4ȋݼ(fX4twwaeTy}yw__(VG07{:Govܘ1l02uywvȋX4twwaeTy}yw_b/_G07{:Govܘ1l02uywv/ȋݼ0F:g8X4twwaeTy}yw__Թ]0F:g8iVG07{:Govܘ1l02uywvvDۈwϒ؜yqPx:ju7|AQ b߃w11Buz93 И؜W`L x4^k2|=@)oC~glkq(HQcxkhpgǧ_ u%e( oR!Ϸ|[Z8!Cg(?IV.G?ɗ/3 gQcxkhpgǧg?0yi+k‚cg՚37 W$.+]XoKc;–6qnQcxkhpgǧ<29vTeT: 3mIQcxkhpgǧ^ oٛ{e]Е-p4|@]xz|@Mw{eˠKQjJWF*;pxPϘ~~|0QPaM8GAhj{4O =8>]'5sp983!yjknPn@nOn)nfPfn(Rn~n@nn@Wnn)nn)=nf[2nn2nn)A\wYs\.d\ |> ?H\w'YqH\-e\ |> gH }= /cF55FF55F\%"&"%bvg)w,s+ں+p:V:.9:7=VWp:/V'Kfdr qhW[`c^ =VV:.9:7=VV:/8:7Gqh[ac]erqh[ac]erp/<]\f%S`m/S`c/]A91 "9Ƿ`}YJ~ZJ7%"(HMMohnTaahh||iLE#zys#zEL -H-@;;;&'9;&'9 ׅ׶ׅXhҬEðbnpaqbXDj-NSfgiҬEðbnpaqbXDj-NSfgi+q i3 X:=<7=Agd++gd1=eAffdfd2++x=evCۈwJCx8CCΓ؜yrOx:juyΓ؜yqPx:ju8|@ɉBۈwClQz|2̃}rvΓ̜ԋyqPx:ju7|@D%ψSvC  xCР¨+z{~2{rv;# g6jy7}@BۈwaCx8CClQz|2̓}rvΓ̜ԋyrOx:juyΓ̜ԋyqPx:ju8|@3D%SvCHwfjCx8CCР¨+z{~2{rw;$ g6jyyΓyqPx:ju8|@v9wwzճXC9Ծϸ!D!,+"D"+DC  K IK I`4kQj u'j Pk(QuOX-JQ{fABw+9bFb`b(vCBۈwCo5S(xBf}{=>ΓyqPx:ju8|@D'q,,IV///"'Vfnaq@ bA9ʹjU:;D{~WƦbqA75X!JJl{aQ>28HOg)/=JLsJ9Ofwl962/`CQ?J?J::?9J?z}A86:|B?|HN}pRРPᡟӔA|l1y0w$5z5ͅAz2xz||?Mw|A2ᒋH֓К$V|AŇG~x(?s4vx$s y(DlZ|LM'4E/@Md]#,(#2A CH,a\y԰]&Og/]mf$c`Dc]\f%S`/S`c/]5fc`c/]o/G]\f%S`l/T`c/]ykj &!Fw&(Nŋ`S`IPfW5,ds hnEvw5&V42wlZF56FF56E&a ()MS4 O'L"U4, ٫L@F) >j $g5bCkpP,x DwdvTF -.HsU&1"hWed2"+ )Jsh+l33xyyYSXdP36O7(Zϋttn,,%E!WdnWV8hf6",2Uc{&I{73(ܿwĪeD#)V72Ű7x*14@ NKtbdówT6Bܶ`K,DB08^Yk9al3v~{t[U: D)k`Ԝ=(2',\gqoyATA:ĽʹmYj$g5bCkpP, %DwdvTF -.HsU&1"he2"* )Krg*l32xyyXRXdP37O7(Zϋnnn,,%E!WdnW8f6",2Uc{&I{73(ܿwĪeD#)V72Ű7x*14@ NKtbdówT6Bܶ`K,DB08^k9al3v~{t[U: D)k`Ԝ=(2',\gqoyATA:ĽʹmYw2V~~2mmǘż׸fN$C rz3L~Gl2<`Kp :p p ;;+L| LoclY~}GL|-KRu9eB&Sfz9 3 ИW`L x5]l1|=I)oC~glk nT/>c}Z!S~fв_H=+YEQqspI@Ia)Xi'+ZY4P`{&㷚jZ0h%4>1I1)Xi'+ZY4P`{&㷚jZ0h%4} w% HY{w\$WLX3{/S?7OGZuldZMTcu} gweY]dZIiZZIHSًwtN9[c *rwo&\:FX;juVIp|n`V]bQRb]ZL\1:d1X;juVIp|n`V]bQRb]ZLN91eZClj~yvФ}gP-/M(O_spfXhYUN1911ZClj~yvФ}gP-/M(O_spfXhYUkvm;vFMww2{`\ݱwoľU@@6_3kvyoxH[D@ba`|j_ae|EMfhr|`_Xm`F1{`z1ݱwoԡľU@@6_3kvyoxH[D@ba`|j_ae|EMfhr|`_Xm`M92+,MS53;C&DZӪFrWSLdqiOts`s`_`_h\M192I1,MS53;C&DZӪFrWSLdqiOts`s`_`_h\>;I;)Xi'+ZY4P`{&㷚jZ0h%41YY1% HY{w\$W1s[1LX3{/S?7OGZuldZ<Y<Tcu} gweY]dZIiZZIH[S^!1N9[c *rwo&];:[;X;juVIp|n`V]bQRb]ZLN;9{1;ZClj~yvФ}gP-/M(O_spfXhYUlojj2vF:p{` :ݱwoԡľU@@6_3kvyoxH[D@ba`|j_ae|EMfhr|`_Xm`M;92;,MS53;C&DZӪFrWSLdqiOts`s`_`_h\>I.)Xi'+ZY4P`{&㷚jZ0h%4ww% HY{w\$WyLX3{/S?7OGZuldZxTcu} gweY]dZIiZZIHd"\jt(>\r8b*@Fhrx2Pz77W78:P7QW QRYL s~QRW .79L]  !KQRWYL  KQRW!'7P4578:+4578:;4578:4578:; +7 8: ].5789:;4578:.4578:;578:4578:s.5789:; .45789:;Q+.789:;578:;578:4578: +.789:;+.789:;578:+.789:;578:-M .79LJKQUVWXZCILMMJKMQWXYZK ZK  1 ,=>CMIILLMO^+CILM CM   1 -=>CNIJLMMP^, WXZJKQUVWXZ  C5I1L3M5^ QG$/Q.3/K" 7JKQUVWXYZ[- #E: ,L !"#%&'()*+,-./13456789:;=BDEFGHJKLMPRSTUVWXYZ[ls+EL^d  !;==#B[$hh>ww?~@CDFWwDGJLTZ[dh jjuu} !"#$%&'(+,-./0162345667,89:;<=>?@A ####')B++++++/-////22222C6777777<<<<@,@+++----../////111166222233455556666D777/ 9 9 9!:!:!:!:";";";#<#<#<#<#<#<%>'@'(A(A(A!:";* 025253  7539!"#$%&'%())* )+,40-6 1"""""""&&&&&2),,,,#"""%%&&&&&'%((((())))))))+++,,,,,,000$+   -6$$$$$$./8/8.. 2latn  caseDdnomJfracPliganlnumtnumrzonumpnumsinfsupstnum  4<DLT\dlt| ,6BP`r2dLvwu|xyz{}~$*0gGJhGMdGeJjKfMrZppppj@VVVVVP$::::::4  klmnopqrstabcF\|^klmnopqrst@(((unoyunoyunoyunoy>( z4 !<>\^`hsw}MNWZ[Gz zzac FF\\ktkt  kt \i i?AAXe  Qs<J<[8[6%)^"\$-)3e1e@eaZ9 {F8R)[H8878LF!`LPK{<<e .g-d-,_8s?3*/'188se7e*,G$\%3PU L~UW;52hzR+/=D3(xG/=?^e({@xBJXYDMSP?J42DB,3%f9{{{{H8888888888e,98!`!`!`!`KV------_.,,,,?3?3?3?3?3S8ssssse2t,,,,PfP-A--9-9-9-9F- O {,{,{,{,{,8s8s8s8sRR?2?3?3@)Y3)*/J8['1[' ['1[q1Z&H8H8H88H888s88s88 s877LLLLFg,Fe*Fe!`,!`,!`,!`,!`,!`,P\%KPK{{{*8LFe*fTTW[W{[E4`b(e\% ge,Ne5e@e0ea8|8888jc8>!>+>>>> >'>5>>WAG@<=V'V0436c3fZZT/+>Z0+  I Q/ KP>">9>>>>>%>>>KR2<<GeU%{#x$&v"v@Cag3eP7U6TP{xufP{xue Q slixmpp-slix-1.4.2/docs/_static/fonts/OFLGoudyStMTT-Italic.ttf000066400000000000000000004727441342457644200242010ustar00rootroot00000000000000FFTMU#ۂuGDEF]d4GPOSGSUB<L OS/2XVcmap5 cvt = !(Ffpgm/ $eglyfAJEwDhead |_6hheaT$hmtxw#loca:*pmaxp*x nameDpost^PprepBt_<nn"^"Zq^d 1oBPfEd  !Z2MD$;;B'$ ? ;*'1 5 u * *F*Be5,d'wI'EM$/'@''};, K 7 M ,  .. `M+    4N |D N;z,Q_'}G%G '-hGI#_'jIjIjb46'wwwwEEEE$/'/'/'/'/'O/';,;,;,;,)                          ' ' ' ' w w w w w ''''EEEEEM.M.M.T.H$$$$/' /' /' ' }`}`}`MMMM+++;, ;, ;, ;, ;, ;,  KKKh.M+rQyMd''I$$II$$I?D;;1'f q' ''''C>H<HCzHFGGGIG%GGG.GG?GG ""GGGIG%GGG.GG?GG ""GGIG%GGG.GG?GG GGIG%GGG.GG?GG ''''LI*?!;!__''~m: ~7I7 '  " & / : > D ` " 9L7 &  & / 9 > D _ "( sQPOJH8`^[SJGB(x   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a~rdeixpkvjsgwl|cn?m}bstz{vw7yx|HOqKLMzPNI,KPXJvY#?+X=YKPX}Y ԰.-, ڰ +-,KRXE#Y!-,i @PX!@Y-,+X!#!zXYKRXXY#!+XFvYXYYY-, \Z-,"PX \\Y-,$PX@\\Y-, 9/- , }+XY %I# &JPXea PX8!!Ya RX8!!YY- ,+X!!Y- , Ұ +- , /+\X G#Faj X db8!!Y!Y- , 9/ GFa# #JPX#RX@8!Y#PX@e8!YY-,+X=!! ֊KRX #I UX8!!Y!!YY-,# /+\X# XKS!YX&I## I#a8!!!!Y!!!!!Y-, ڰ+-, Ұ+-, /+\X G#Faj G#F#aj` X db8!!Y!!Y-, %Jd# PX<Y-,@@BBKcKc UX RX#b #Bb #BY @RX CcB CcB ce!Y!!Y-,Cc#Cc#-KPXYF+X!YKRX!Y EW+D E+D E+D E+D E k+D+\X E+D E+D E +Fv+D E $+Fv+DY+NB,DDUK)!@2G:BP#&N.64I8=zdl & v @ b rz Bjf,f !"@#0#%$%&p'"')*+,--../ /*/012*23R4D525>5J6789\9:;|;<==>\?8?@tA ABBCCCE(FFGdII^JJJTKKLLJLRMbMlMN.N6N>NHOPVPPPPQJQZQjQzRRRR(R4R@RLT\ThTtTTTTTTTUUUV VV"V.W0XxXXXXXY[\[h[t[[[[\\\\\\\\\\]]^ ^^"^.^:^F______aXadapa|aabccccccccccdde~eeeeeeghhh h,h8hDhPh\hhhthhhhjlDlPl\lhltllmnn$n0nnnnnnnnnoooo&o2o>pr(r4r@rLrXrdrpr|rrrrrrsttttttu uu"u.u:uFuRu^ujuvuuuvwwwwxxxx(x4x@xLyDz4^>Ԕ@,( ¢ʢ֢*6BNإ&ȦPابrhzRt$&bܲƳгڳ  *4>HR\fpzʴԴ޴$."*2:BJ¸4 .DZz2&P+ ++N 2+6 + +Q/ִ4 + %+' +/ +48+ +K B+K/R+' 994 )9998$9/,2@BN$9KE901!%27>54'.#"326323254&54>7>54&#"2    ,'4)1)     N0%@^5ː      +-MIVA  $  4C-D/+ +0/ֲ222% &2+%+ +/+ +++ + +/ +1+6?Hn+ .&.)&+>+ )')&+()&+')& #9(9)&'(.......)'(...@99.99 + 9%#9017467>32#"'.747<>32#"D  A  $   2    ! VA~u$Q{;&ewd/ET33 >h22d +@dM +\2/7s33 $222 +@ ++2x/^ְ_2[ +[+ + 2"+ +/" +!2f"+ +/ 3f +f +@ +22a `3  3O+P2K +RKO+Q3K)+- +.20-)+/3y+6?)+ _.Y  ?`+ P.'J.__+ _+?a+ _+ _+_+Y!Y +J/J.+?f<+ :J.+HJ.+PQP'+_`_+?_+ PkP'+lP'+mP'+nP'+oP'+pP'+YvY + _ #9vY 9kP'9l9m9n9o9p9HJ.9:9@':HJYkv !./PQ_`lmnop..........................@':HJYkv lmnop..............@a^c99[\9 99"9ROM9)K>Fq999089-+2990170546;25>54+"&54;256767632;2767632;2+";2+"#"54654+""54654+"7;27>54+"; R _ f bEQ_b  B$F_ b D/ dC    #3D  /. `Zj|X+E3 9+m2X +@XP ++ /f 9+62f' "+}/ִ[ %+[+ Q+/ Q+[S+N +NH V +V/H +|2HN+2<+;2+ +d  r+B ~+6?+ |.8 b b+ b+ b+?+ |9|8+:|8+;|8+?+ z|8+{|8+ b #99{|89z9:999@ 8bz9:;{|............@ 8bz9:{.........@[Y9S9V X99NP9dE`fm$9<x9 #699r'(?999B2+99f @ +/B[`dkrx$9'(9#901746323267>54.54672>323#".'.#"#"#"&54654#.327654#"32>54.#" 7 !..!tK  (7  8  '77'{d   >eo9  #/g *5%!#[ B( %- ;N !<&X\%% "  # ->`' %K4\n(  c D, % 3"F.0 %;;GS_Q/W 9+9/? "+]/K 9+,/ "+E/ "+E +@ +`/ֱ< <B+1 421H+T TZ+N a+<9B 9$919H/9T,99Z *KQ&$9N99WQ99 HNTZ$9,K4?632#"&5476?>54#"#"."#"&732654&#"4632#"&732654&#";sT 1,.%/ ޑ 5k*%9 6X#KH 0:8?nX66nK;>G -@8?XQ#"%  O( + M:9H-PAO7>M(4qUD7WQF7@K(9u'/}+9 + 4+k+] "+]k +@]e + +@ +1+$ L+$'  "+U+ME} +M "+EM +EH +,} + 9+/ֱ5 5 +" +  %+"P+B %+BP +BJ +*BP+ %+/* %+B+  + +<+h2x %+` %+`< +`c +xZ+n ++ 59"/199$9,}'999P99*9B9<?Us999`Vu99Zx]k99,E554.54>32#"54654&#"32632#".#"32654&#"32632#"&54>7>54&#"#"&54632#"&32654&#"';& +CE %A #MR 0(,*&* %#3pL^0*#2 !',7)NON+#$//H),@.AA.C;}Wy""7k   4R.$F m= (6&'M*8hJ|O*8+% @, #L4.)$+;D\O0$ *`B;5wA !$w?*W'/ִ + + + ++0174>32#".?1GM8 )>A8 +3+ $-(_f3 )E`c0"XQ= (Nc*!/ִ + + ++014>7654.54632#"7)>A8 +3+ $-(1GM8  )E`c0"XR= (McE_g3;ufA#*/?3B/7ְ22 /222C+014654&546323254&546322632#"&#"#"54654#"#"&;ll N   Q mm N Q  02 5 7   . 6 20 5 ) 4 5,O"+*/3 2* +@ +-/%ְ2 +2% +@ +% +@% +.+0174>;2=4>2;2+"#"&=4&+"&           *Zv1/ +/ִ Q+ + + ++014>7654.54632#"* %- &Y )2 9<:2t' /33 +22/+ ++6 + .=+ ++Ð+ +++ #999.....@ .........@017467>32#"'$8  ,     1q)+ +/ִ + ++01747632#".'.1!    .   Ir+/+6<+   +  + #9 9  ......  ......@01477>32#"1  77  $   P + f++ d+/ִ C++ 4++ 99990174632#"&732654&#" rTv-KZ/Sr;QLtfqY=d>"e^FLp^IQr!43+.3 9+')22+3 }+25/ ִ% +"$22% +@%, +@% + % +@ +@  +6+6?J}+ $.   + +  +$!$ +"$ +?jZ+ #$ + #99 9#$ 9!9@ " !#$.........  !#.......@% 0993,9017432>7654'"&5432232632#"2#"&"#" 8  < "&%F.D  "&:-  ^%   84+! 2+!4 +!+ ++ 9+  +  +9/ֱ  +  + + -2 +@ +:+ 4$9)+/3$9!499 990174>7654&#"#"54>32;2>7632+"#"+Z)J\D(L0@`N\N-B   A[ 5"wE)+. ( # #(66$ZB:  !)Gh@p>+ > + +#+ P+># + "+>#+A/ ֱ9  ' B+ 999 9'6999 90143232654&#"#"54>7654&#"#"54632#"&2HoN. $ 0r>/ 7g/8. !1:1*JzF,tq4-A   @C$0&,"$   4'!OK3 C>WT!+:3 M+B2! + +6+>+2+ +X/ֱ? ? +@? +?7+62( 4+Y+6?)+ 5P..+.+.+.+-.+5K5P+L5P+M5P+N5P+O5P+K5P #9L9M9N9O9-.99999@ O-.5KLMN.............@ O-.5KLMN.............@?=974:G999(*2S999!2(99 S90174>763232>32#".#"#"&4654&#"#"7326;2>7654#"  /      !  )@e^( 8 .  $2 J58:! '  #  G   -ECQ@+ +3/ +/ +" +D/ ִ< Q+ < +@  +E+/<$90143232654&#".54?>32327>32+*&#"#"'&#-?udg  4'f   *9J%^U sqYE[ LR      !+Ao\S  'j+ 9++% P+ + / "+(/ֱ "+ )+" $999%$90174>3232>32#"732654&#" M4L!<%_e{hG_>7654#"+"#"#"547>;2;2632+", NP    ?@ T25#3 3  tL!*!2@+% 9+./8 L+>/ "+A/ִ" C+" +3 %+3)+ 2; %+;/B+)3@ %.8>$9.%998$9> 990174>4.54632#"&7327654'.#"32654&#"*#22# !l_Mn()))jZo;JH95EWN H*42A1VJB=V+I+$4?rLI$: (?%a{`W=\$/ZL1+?E":T:-OB 3)h+ ++( 9+ / 9+*/ֱ %+ ++ 999%999 9(990174>32#"&5432>54"#"&632>54&#" 3nFWyN@n6pTrmq]5 KQcҔy56U}F']++" +(/ִ +   +/ +)+99 "$9"9017467>32#"'.4632#".'&F    "<,= 2   B53 3 BZ#V+$/ִ C+ + + ++ +/ +%+!999014>7654.54632#"4632#".'&B &- &Y"@.<  *1 9<:2t;: 5 )}74>767>32#"%.җ$ **6 k L8  op ZJ&b)}747%654'%&5432#"& 6**0 k A po 8L et I+E/$ E$ +E< +EH +J/>ִ7 +77+ +/ +7B+' K+>!H$97 <$92E999B$1@999'.9E 90174>?>32".'.4632#"54>54&#"#"e     ,@%0N     AB4'2     2-C4  $  A$tl-, EU+1 g+C/7 P+7C +7; + /+ +I  6+S/ 9+S +@" +V/ִ4 Q+4+F F.+ +W+.F@  17=CP$9S+@ &.4FP$991$9014>32#"."#"&54>323263232654&#"32>32#"&7326767654&#",A_xa*[ &$;/.?n:3 )Lbtѱ?f4 ,9Q+#6 (,1k`W9m$5,9] (ax! ;sl{ɡ! i"/Qa +&JP333 9+*C222+_ +^28W& +8 b/ִA +A +AE +c+6%+ ^..+^/^.+0^.+1^.+]^.+ #9]^.9/919091./0]........@ 1./0]^.........@A9 (998A99_WR9901747> 7>2#"&#"#"54>54.'&'&#"#"&#""#".#""3:632>54#" $8?]3  w '8 N&L2*""& .(!]UW   8@gqY',M,   ?H 1U' : 8<SfP5+8;33@ 9+2 +a P++ "+WH; +W "+g/ ְ 2T C+ T +@ +@  +TC+\2/ #2h+6?/+  . KS  + +SMSK+OSK+PSK+QSK+RSK+ #9 9RSK9Q9P9O9M9@ KMOP QRS...........@ KMOP QRS..........@T =9C +5@)Wa$9@59H+/I$9W)9a #99017432>?654'.5463232632632#"&#"#"732654&'&+"32>54'.#"   * 0PJYr*)**K40P#] K2HbcQTF  ,*\'<,??   NNQ!<' <)!JIQn9?[= "C F  0.:%Ro';U7+% P+%7 +%+ ++ g+322632#".'.'&#"327>32#".'0]`=t!    [aZ?p}[   zTQ)8JtE&+<+ !>eVg +" L8_v(A +- 9+&+ 9++3; 2B/5ֱ C+6?s+ A>?A>+@A>+@A> #9?9>?@A......>?@A......@ 9;- 990174>767654&'&54322632#"&#"#"&7;267>54.#"D.;2Y*NS^R/#= X,w)(9 (>iC>$ $  {~x#O9 A*%%a^@ =C1 'imh+\ h+ "+h +@hf +h +@a ++0 }++3 "+0 +@0$ +8Sf +8 S8 +@SH +8S +@8? +n/ ִ5 %+ 5 +@ +@  +5J+F +>FJ+A +F&+" +o+6?+  X 3 + +  +XUX3+VX3+WX3+ #99 9WX39V9U9@  3UVWX..........@  3UVWX..........@5 Y99J08S\$9F-f99AC9&a9"$c999S\Ec998C90 A999999017476767467654.543232632#"&4&'.#"&#"3267>32#"54'&#*&"#"32>32#"&#"#"$ $ AqB  2&V, =   N% &0NUw+ T H<# #Pb /  # $   0   K 8 0 w\ %j ;fe+^3 9++3. P++2+3 9+. +@.$ +8Qe +8 Q8 +@QG +8Q +@8@ +g/IִE +>EI+B +E'+" +h+6?'+  V1 + +  +  +  +V3V1+SV1+TV1+UV1+ #99 9 9 9UV19T9S939@  13S TUV.............@  13S TUV.............@>IG9BE@9"'9e\9QEZ99.8B99999017467>767>54.543232632#".'&'&+";27>32#"=4.#"#"&#""   " IyB I8")=J   663# "("B"; 7+, Xp 5$  '  ^*4 oR 'WU+* 9++! P+48U +?34 9+D2X/ֱ' '/+I Q+I/ +@IA +/I +@/6 +/I+ +Y+/'!8U$9I;O99 99D994*HO998'A999! 99014>763232632#".'.#"32>=4&+"543232>32#"&'0aBC?,U:-  &Q#BI5#}.>:& ! B ( *(6g%WdW"<)9<,EvL+[F4  7   +dj333 "+`m222+!J33 "+%M22J+D3 9+4|d +4 /vִU %+T2Uv +@UL +@Ub +vU +@vl ++6?"+  )?e+ T.Qs;  + + + + + + + +*)++)+s8s;+9s;+:s;+TRTQ+STQ+sts;+)+  #9 9 9 99999)9+9*9ts;98999:9STQ9R9@+8Qst )*9:;RST.........................@+8Qst )*9:;RS........................@Uv7>7>54.'&543232632323267>54'.54323263223#"&#"#"47>?654&#"+"3#"&#"#"   "3 < ?  2s  ( B7(   !>6    B: 7 "   .(C8R  0w-\K0  \pi     4  2<;+5833 "+4+/ "++3 "+2=/ִ +>+6?{+  $! +  +$"$!+#$!+ #9 9#$!9"9! "#$........! "#$........@199/;9,9+990174>7676654.'&543232632#"&#"#"  %"3 < ?    I4 #   ^qk++) M+) + ++3 9+,/-+6?~+   $!  + +$"$!+#$!+ #9 9"$!9#9 !$ "#........ !$ "#........@901463232674654'&43232632#"&  '+*D 4"!> ! l.' Bxf'   &"hE<;L|P+Xu{333L "+q22+ "+26+0339 9+&`P +& 9+'2}/ְ2$ %+#2$ +@$ +$ +@ +@ +~+6?=+ .l%3+ '.)b>գ+ X.^IF?|+ +l l+!l+"l+#l+&m+ '(')+b?b>+@b>+Ab>+Bb>+Cb>+ ՠ+ FGFI+HFI+^Y^X+Z^X+\^X+?5+ lil+jl+kl+ #9jl9k9i9"9!9 9(') #9Bb>9C9A9@9?9GFI #9H9\^X9Y9Z9@ )>?FZ\^bl!"#(@ABCGHIYijk.............................@ )>?FZ\^bl!"'(@ABCGHIXYijk.............................@$enx$9LP99&+996  .990174>764&'&5432326323267654&543232632#"&#*#"&'&'&'&#"#"&#"#" 6''5Q50K9 B/  :/B.#N  I=+J *(O +0   F  ?*  '"  ?5; !WI3W   6 H?;+7>33) ;+ 9+; +@0 ++3 9+@/A+6?q+ %$+ + + + + + #9 9 9 9 9 9 $ ........ $ ........@)"2990174>7>7654'"&54323263232>32+"&#"#"  = 4"8G%HCe5$  - "//J   59c505   "K  {r+3Y+_333U "+2_+\3d "+ +:3 9+?2/hֱH P2Hh +@H< +hH +@ha ++6>+  }{  + + +}|}{+  #999|}{9 {|}........ {|}........@Hh26\999U_Wap$9dS999@ '+3@Phjotx~$9017<>7>767>54&54'.+"54;263232>7>;2632#"&#"#"54>764#"#"&.#"#"&#"#" ! G*(&`A :   ,   F 3  = 6+ F-  AS d3* *d^׈2q4  + .@Na 5,*"   2s87<# /( " E" v6R+t3u+o3 "+o+r3+>3 9+A+;3w/ִe +d2e +em +x+6?M+ d.b  + + + + + + +dcdb+  #9 9 9 9999cdb9@ b cd............@ b c...........@e"999om99,14MNU`j$9+7H9990174>7>7>54.'.5432326323254.543232632#".'.'&#"#"&#"#"      .C p;9'  *$ %G) ;   *' *5)1"Ne32#"&732>54'.#"'5b[UP*/[^VpWA[GtF&:JtF9`xCGuGn[X5KZAftMF+C "+L+ "++0 "++30 9+ 2'F + "+N/*ֱ * +*! +O+6?A+  ?9 +?:?9+;?9+?9+ #9>?99=9;9<9:9@ 9=:;<>?.........@ 9=:;<>?.........@CLD99'!90 *9990174763676754'"543232632#"&5432232654.'&#""#"&#"#" &EETdD;'C) 9l&7    ..BF .(04 UQ@s jV!4 X`ID '8AQ3+* +*3 +*. ++O "+E3+R/ ְ2B ; +; +@;> +BJ+ 02S+; 9B@999J"*38&$9.9*3@9E"6;>$9O 8$9014>54.54>32332>32#".#"#"32>54'.#"'9W\%  "Gz]#'Q,9{. )UEoC$&xK~(%5"+R0/eu[=   `35 LJYJ8!1 ؂DoCfA3QiObe-+) 9+D2N+H3 P+K++3\ 9+2R:- +R P+c/Yֱ Y +@+ +d+6?+  @_ + +  +  +  +@=@_+>@_+?@_+`@_+a@_+b@_+ #99 9 9 9?@_9=9>9a9b9`9@ =>_ ?@`ab...............@ =>_ ?@`ab...............@Y-9)K+F999:2A99R$"99\90174>7>767>54'&546323263232#"&'.#"#"&#"#"&#"#";27>54&#"     >  : a+.DPQN g#/F;.5 ( !"8KY".KD-, -|x[bC1 V;:g"0 %C<3"6  <;4N&,_:HD+ P+++1 9+I/ִ ++6 6 +? ,? +' +J+9 61:D$9,-99?*99'# 991 *?$901743232>54&/&54>763232632#".'&#"#"'.YH*= "%u_(>#k   1F(',X!1bCj& 4H&!":%taP'8 #B .0#E.R"'C'69$ZT]+K M+2K +K +2/83. "+B2U/ִ +V+.205:999KS999014>7>3232?2#".'.+"#"&#"#"54>3676754&#"#"qfYZ /^-   K=   E-`:  = +  "H  3 [}     ,'G:+ +$)333+ "+!.F222H/@ֱ @ +@@ +@C  ++, +, +" +I+C@9 9:99,29 2@99",990143032632327654.54323262#"&'.5454.,##, : %P:A3+,( I !'>:aWf"%! 4.19#8F3 05=#"#FA D[5+ ++ "+&2+3C 9++E/F+5:9C,999 9 9901432326323254.5463232632#"'&'&'.'.$ 7 9 $   V"  Lky$2(+/ֱb c2+6N+ d.7+++++7c7d+ #99999@ 7c.........@ 7d.........@b.059$9e9$V$9(w9014323263232>54/.'.54323263232654.543232632"3254.543232632#"./&#"#"&'&''&'&'.> :   CA+     4 : - ["!6%      LH 5"  7>7654.'.'&#""&54>322?654.54323263232632#".'&'&#"2#"&#"#" .&8 H  A %+ 6#.)AK' AH*7%7[4!U$2 >$9? !!]4< ` <  ,!G[=Gc4 % D*%6 DQ'JW_  3uB) ?! */JO6!|G \<+C3$+$3( 9+Z22]/Oִ2 %+2O +@29 +O2 +@OE +2+& +& + +^+6=o+ WR++WSWR+TWR+UWR+VWR+ #99UWR9V9T9S9@ WRSTUV..........@ WRSTUV..........@2O@9,=99&)9(< )99014323263232>54&&543232632#"&#"#"54>7>7654.'&'.!N E2,+\\' >!   Z1   )2& )L^9|   *4   -*O&b 7WS+PV336 +:2&+ +)+ +2& + +>CP& +> +X/ִ + + +<+F +3<99CF99 9)090174>754#"#"&#"#"&54767>3232632;23254.54632#"&#"#"$e!P :#(1   +A<: M0+8uK;  ^w: .  5 1&)  #A074=2B+ 0/' 3/ֲ222% %+$2% +%, +% +4+6?+ . $ + +?+  + + + + +  +  +$$+!$+"$+#$+ #99999 9 9#$9"9!99@ ! "#$.................@  ! "#.............@'0,9901465767>7>;26232#"+" 32632+"&77 (  H) GS =Hj,M* -9/6 I +/+01432#"&'& 3 !    ?$4+=+ +*/ 2,/ְ 2 %+222 + + +-+6?+  .% + +  +  +  +%%+?+ %+%+ %+!%+"%+#%+$%+ #99 9 9 9#%9$9"9!99 99@ !%  "#$................@ !%  "#$..............@01430;26767654+"543632+"3 a) T^ 7 (= ,:/6k  =H&j,M* A% 1.!/ L+! +@! +/22/ִ +3+014767>32+".'."+" *)   K%G!!E&      "  %+ +/ + ++014>;2+"&  &  Q -Aa++1 7+"+ +" + ++; "+; +@ ++B/ֱ. C+;%'.8$901747676323263232632#"."#"&73267>54&#" 87>7654#"".54>7>3232>32#"&732654&#",  @*P359n1IBMBt9)%4 GD5W*)H +./J5m+5DX*< /(#_: #T"+ 4+" + ++ }+ + +$/ֱ  + +%+90174632#".#"32>32#" g *0 %' v$;! *Z2k # #N) --=LK;+13A 4+;+( +(; +@(, ++ + "++I "++ I+M/ֱ> >F+E2& +& +2 B+  +  +N+6?d1+ .D $DD+?Y+ $!$ +"$ +#$ +DED+"$ #9#9!9$ !"#DE........$ !"#D.......@F>4;999&199 (9I(.68>$9901746323254&#"+"54>7>23232>32#".#"#"&7327>54&#"[ /" %    # U+#>"=;F*+<)+,R0}s    =Uo+ c#*##*#IX/84 2Tv s,v+ 2+ + ++( 9+" +" "+-/ֱ  + +%+ .+% 9999("9017467632"32>32#"&732654&#" <58S$3$ :cHH1 s;YER Gl#%-!?|15* -):C HTP0/JLJH+ H + +!+) L+)! +)& ++.3 52K/ֱ+? C+? +@?3 +? +@ +L+H99?99?9399)901463232>754+"&546332767632#"&#"32632#"&#"#"&+  ( = 2?9E/ 2!B 93 8% 70/G   ZP%+)  >w!+4 =>MY<+B "++! 5++W "+$!+ "+Q-< +Q "+Z/ִ? 4+?+N Q+4N+ +/4 +4 +@ +NT+* C+D 9 [+K99N0GH999T54.54>54.5463232632#"&#"#"&#"#"&73254&'./"32654&#"SBJ 2 $ Q>.P_P[SVw7[9pGi  &U'#7)"5I&  :05k   $8t  &5)<0=I58A)+ 079OA9>QD%2+C3' +'2 +'- ++ + "+ +9 L+E/7ֱ" 4 % %4 +%/ +F+6?J|+ @+++++@=@+>@+?@+ #99999>@9?9=9@ @=>?............@ @=>?............@9'"4$9 90174>76654#"#"547>3232>32327>32#"54654#"#" &  &/6 <!7Y    `"19"C ! AV  6 q."T]N(/I &8&DXE+W3= +=E +=@ ++ + "+-++P&E- +P 9+Y/ְ2T RS22T0+Z+6?[+ .U.+ IG49?Q+ +++++?Q+ +UU+U+U+ U+!U+=+ 4549+649+749+849+IHIG+URU+SU+TU+ #99999 U9!9999549 #9697989HIG9@45TU !6789GHIRS..........................@45U !6789GHI.......................@T 9990 =E$9&PN9-J9 90174>?>7654#"&#"543>323267>3232632#".#"#"  /C   zMM2  . F1'' ! @Ya(-  *:E `$ 3D/Z 8PESE  .3-/+# +#/ +#( +++ "++4/ֱ22 + * +  + 5+6?a+ .++++++++ #99999999@ ............@ ...........@  99 9# 9990174>5>7654#"#"547>3232>32#"..  (F     25  VVN"  UWsU:*f3+EZ33% +%3 +%. ++33; O2+b +b +@be +g/\ֱW WG+C C9+ # 6 6/# #6 +#0 +h+W\_b999G O999CJ996;99999%36CGV\$9b@  89J_$9;99901463232>763232>7632326?>32#"&5464&#"#"54654.#"#"54654&#"#"E +; @<%   b"#8 '$  $! #- " BV '  J  >* #   K%H)9/ P 8 H3- "+:G!+:3 +! + + +* O++C +C +@CF +H/=ֱ7 7'+  $ $/ $ + +I+7=@C999$*999' 9!$5=999C&'@$9*90146322>7632326?>32#"&5464.#"#"&54654&#"#"F I1T   c!#"    ,% AT54  9}( $  P%+%,A/;  +8  J + 9++ 9+/ֱ + +  99990174632#"&72>54&#" kGK3lFBWM1L;1%&FfeLAxS\VHL6QQ"CDC:F<8Xmj>+A3] "+W+W+M "+/++-33f "++ P++/ / "+n/`ֱ1 o+6?iS+ Ik+++?R+ +++IDIk+FIk+GIk+HIk+lIk+ #99999HIk9G9F9D9l9@DFklGHI..............@DFklGHI...............@1`79>M9 ]17`999f990146767754"#"=47>2>7>763232>32#"&#"#"&#"+"732654.#"#")6 (    &'    ;I' *)E % Gr'4!   "5   , ZSB  *  #  I'9Mo87GU5+; "+& "+2+D "++H/ֱ8 I+5&*99D;+1/$9017467632326326#"&#"#"54>7>54#"#"&73267>54&#"D-Lb"' (  .:%  -L.#;D0&0e* 3c6t32RN &C&f!  *nB''AJ&8_If!b~V4r&++ ++- +- +- +@-3 +5/ִ0 +0(+# 6+#(+-$9-& 999 99014>3232>32#".#"+"54654#"#" $=!   ( +"  ( 4#B34  x5! *D%07r5+ 9++$ 9+$ +@$! +8/ִ& C+& +0 Q+9+&5999 $)$90!9$0$901743232>54.'.54632#"&#"#"&  $  M;])56 B-(G6$$  2?485$2 (-=u5+, +,5 +,0 +@, ++ 2+>/8ֱ( (8 +(2 +8( +@8 +?+(8;999,8;9999014>7>3232632#"&#"32>32#"&54654&+    ! >   ( h+&*;    =  fKH*46 F9++3 ++3@ +@ +@E +G/;ֱ > ;+ Q+> +> + -+ - + ( + -+ H+>;99  9-69 13999 9-9@@ "&(136;$9901463232>76763232>32#"54>54#"#"54654#"#"C.%    G-!=!^>  mS"-  &&`" -$   V %%c6H2 !@<# $3+ ++32 +4/ִ% +% +.2 Q+0  + %+ + +5+ %")2999 099 92  )$999017463232654&54632#"54.7326765654" W$5c !3@7A  2Gj0!;%X/ 6#(c*$1!! hM AO;+13 +2+)33M +P/ִB +BK+ C+ = Q+=/ Q++ 3  4+&++ Q+B@9=?FM999 K;993 7999919&9+#9M @ #357=F$9&+$90174632326767>3232>54&54632#"'4#"#"54.73267654#" V# !F  #J+ !-q$9#+8="!  *Ep2'4&\2?x 4.1^m40 -))VZ(89( -! &+#AO>+6 M+ +6> +@6 ++ ++2%+. + +@ +9M+P/ִ + +1 %+Q+ K9991 I9996>;99 "1DIK$9.(9017432326?4.'&#"#"5467>3232>32#"&#"32632#".'.#"#"&# ;   ! *#9 R83% -P*) M'&3A # -1<12<2m }-(   ;Z[Hz,++ 3++ + ++#3 +  +  +-/ִ ++& %+& +! +.+ +$9 999!&9901463232654#"#"54>3232654&5462#" ,'U$ '!K4( "+(\qS} !#1 %&71YjY|20 *eIG+4+B+#++ +  +  +;/2 "+2; +25 +J/ִ + + +&+K+&D999G20A99  (-D$9#'9901747654'.#"#".546323263232632#"./&#"#"& #;  0\ - #% (  <)  =*      #1/; ' ?'6# G1 4F//.+'3+3+ 3+0/1+6?G+   +  +  +  +++ #9 9 9 999@  ..........@  ..........@.+901454&54676763232632#"#".#"#"4+' $ &H)" % p DC ! z  N/!-"/!ִ %+2 %+22#+! 90145<&54632#"&5N   WKh,L+  'F)OtJa2&)F0/+'3+33+3 +1/ִ +2 + +2+6?S+ .'  + +  +  +''+#'+%'+&'+ #99 9 9%'9&9#99@  #% &...........@  #% &'...........@9 /+90147>5>4#"&543232632#"&#"#"#% % >% & /%  !  xn   l G_ D/ 6+ + ++ 7+ + +/+ ++0174>3232>32#"&"#"4G#:A( +.-),.D/+*/ +0/ְ2 2+ ++# +1+6?Hn+ .>+ +++ #999............@9-9  *99#(99* 90174>767>32#"47632#".'&D     $6  !VA{w{ w   HXE+O !+E+( d+E( +@E< +3+@++V P+V +@ ++Y/ֱI I>+?2: +: + +2 +@. +2Z+6?+ ?..?: ?+ ?? +? + ? +::+?+ ":+#:+6:+8:+?@? +?+ Q? +R? +Q? #9R999 98:969#9"9@ "#68:QR?.............@ "#68QR@...........@>IDE99:CO99 &4V$9 (999O(&9V ,.$9017467>2>?432#".'&#"32>32"##"54654&*&'&732754&#" \L 2  #!:" H+   eI# %KV $ ( R    %+ ;&5# <mbmZ+N +`+f "+NZ +NS +\NZ+ M+#+0 P+0# +0) +lZ / P+:BZ# +3: 2n/ִc +c +5 5 +@  + 5 +H H +@H= +5,+& &o+ c`fl$9 \^i$9H5:DL999,#0;BNZ$9f`^9N\ci999lL9B H99:90 9901746323>54'&'.#"#"54;254.54632#".'.#";2+"32>32#"'"#"&73267.#"##!  0 7}U;W"   1$) q9F,E  $B(Ng*# "$%& *-c !5nw6*$'  =+.b  0'R$ % /8(,  /8)/3 7+)3 +@)- +$27/ L+7 +@ +29/ִ1 4+1 +@ + 215+ C+5 +@" +2:+6-O+ +ұ+ & -O+ +ұ+ &+ +-O+ ++++ұ+  +'&+-O+ +*++ #99*+99 #99&9'9@ &'*+................@ &'*+................@0174?&547'&4632627632#"/"'#"&264&#"D'&C E45E E))E E43E hRrSS9:, E3DB3EE''E F8>=7FE'&DvQStUm+t32+,$36 9+'22t2 +U3 ]2t2 +H3 D22/ֱS S +@SF +@SZ +@Sj + c 鲀c +@ +@ +@v +S%+4 +%4 +%* ++6+ ++++++++++ #9999999999@..............@..............@cq9S`9%;B^hn$94:9Z96 7990143232632;:>7>54&&54323263232+"32632+"#"&#"#"54>7>7654&#"&#"54;267654654'&'&+"&546;254./&'.!N E2'+@E' >! 6L] V   ` ~    Z1   >3   _  D M2&  '$Q%  Zt   Kp  @    _  [    b N/ =!/ְ2 %+ %+2 + /"+ $90146465432#"&4632#"&N     *g p+"&[D &;KTkO+ )+#+7 P+l/ִV Q+ V+: V]+@ 4++2L@]+ 4+/L 4+@2 +2/m+ 9:V9@ #7=OZej$9]FH99L3&99@2)0997  .0LZj$9)&+9901743232654.54>54.5463232632#".'&#"#"&'.32654.#&#";!4!DY@[[@!!##qU,E  -7>=VV=wi*<' U-7  4  (2G0.#*I1#;# 1$Jn _ &)3F$ - 'H3#<% JpnV ;H&    zoX, N + I/7 P+7I +7= +// 9+/ +@/+ +/ O/ִ + +2 2 +2) +2;+? +?+ +P+;2 I$9/7 !$9014632#"&7 654&#"463232632#"'&#"327>32#".,ϕΗ*N}p&M- ;nK`)T9P8    '$&?_4 OӒϕf" k\K#CD*O :  "/?/H.?2+, g++  + +.+;/ "+@/ִ/ %+/9+ +9 +@ +A+9/,$99999;$/9$990146323263232632#".5#"&732>7>54#"dH &   6   '.* ,0'9  >K ' :   *)l @T8]*P/%3 +2+/ֱ +  + + +  + +" +,+01747632#"'&'&74762#"'&'&cK >"{ dJ +*"z ge?$z gh-,#(3/  + +/ ִ +  + ++01743!2#".=4#!".     o ' Q Xh + 9+4+DJ33, "+/@N222 / 9+9_4 +9 "+e4 +W3 "+i/ִ + Q+= +Y2=Q +@=B +Q= +@QL +=b+! +!+ +j+Q IW$9=G9b@ %8$D$9!&)99,14999,4-1BL$99 &8R$9_%$99e!U999014632#"&732654&#"74;2632;632#"'.'#"#"&#""54676=4654#"3232654&#"zVX||XVzjHJkkJHj@ ,%2    8$0  N ! "X{{XV{{VHiiHJjj& !  _  #87!}3R`U H+ / /ִ ++ ++ 9999014632#".732>54#";+02K,*!9&c#CG$/7&"=#I 5b / 3/%3 23 +@3+ +3 +@ +6/.ְ2) +2). +@) +!2.) +@. +27+0174>3!2#!"54;2=432;2+"#"&=4&+"&  V     Z        %^"[YR 9L=+/3 !+'2= +* +K+ +3M/ִE +E +EH +E+ C+ 1+$ $1 +$, +N+E=99 99189$4699=KE99A9 168$9014>767>3232>7>23232632#"54654#"#".#"#" '   %2"_H > & T5(2!F -^(U_2 $pA    H$ZHx#J$!,'6='G5Ka+& 9+& +&! ++F "+W2280 +8 "+b/ִ6 +6*+R+ +D +D/C3T+c+6?+ C.(Y](*(C+?u+ +(C+<(C+=(C+>(C+?(C+@(C+A(C+B(C+]Z]Y+[]Y+\]Y++(C #9<9=9>9?9@9A9B9\]Y9Z9[9@(*+@ABCZ[................@(+@ABZ[..............@6&999*08J999D-FLN$90&N999F899014>326326;2#"'&5463232657654&#".32>7>7>54#"32>754&#"'G],P%  $ tP% D /9&!  '8  ! Is5 3eev6 ;OL3B }N+W1. Xxx h ( / + + /ֱ  +014632#"&h% $ L,&'-9_I_ N+ "+/ "+/ִ %+ + %++  9999014632#"&7327654#"RI00PF-83 48#.?c>/?e95+,:*0O9:'TK]-P/+3 +#2./ֱ  + + ++( ( + + +/+0174654&54632#"&74654&54632#"&'dJ +  *34{cJ Zfhi- !#+, gj ]z ID'#'GIE'#'GD'#'G4C(D[&+ & + +! +E/ֱ + +F+9 &)*C$9.>9990174>7>54&543232632#"&4>762#".'4     BB5'@%0N   #  h! $  A$tl--B     'Q$'R$'S$'T$'X$'Y$'}j+^ g++k3 9+s2 +@h +^j +@^c ++ .122+ "+ +@% +{h +3{ 9Uh +9 }+U9 +@UJ +9U +@9A +/ִ + + +L+H +?HL+C +H(+" +(" +@( ++62wأ+   ?+ vZ42/+  + +?+ ZWZ4+XZ4+YZ4+vv+?I+ v+v+v+3;٤+ + #9 99v #999YZ49X9W9@ 4Wv XYZ.................@ 4Wv XYZ..................@9L19^kp$9?J.h999CHAE99(c9"e999^9{e99HL99UG99E9C999901747>?654&54;232632#"&4&'.#"&#"3267>32#"54'&#*&"#"32>32#"&#"#"5467>7&#"#"&#"#"&#"#"3:637>54#" $CMH'=l5  2%X,>    O% %/OUw+ U H=# % H$G. &:3 5+5.fH :  &0[h]2  " $   0     L 8 0 w\ $ j  G1U'  :0K'9'_X&i'Q[(i'R[(i'S[(i'X[(2'Q,>'R,4'S,T'X,9f*7+7+ 9+1+> 9+'+"3L 2U`7' + 3U 2g/Fֱ* h+6?+ fP+ ++fQfP+RfP+SfP+cfP+dfP+efP+ #9 99efP9d9c9S9R9Q9@  RScdPQef.............@  RScdPQef.............@19U`Z99L*F9990174>7>?4&+"54632326?654&'&54322632#"&#"#"&7;267>54.#"32632#"+" 2 4.;2Y*NS^R/#= X,w)(9 (>iC>$   CJ   }89   {~x#O9 A*%%a^@ =C1 ' t55 <={'T1''Q2''R2''S2''T2''X2O#.{'/ +' +@', +"2' +@ +2//ֱ  +@ +2 +@ + 20+6-Aҿ+ * ҿҿ+ $ -Aҿ+ +ҿҿ+ $+  +-Aҿ+ +**+ҿҿ+  +%$+-T+ *)*+ #99)*99  #99$9%9@ $%)*................@ $%)*................@0174?64/&54622?62"/&#"#"&Ox | t }  y| { w=y |s } y|| x'?Se{(+X P+(X +@(3 +0++O 9+O +@ + +f/ֱ@ @\+# g+67v+ 81 8 8+7v+ 11+1+1+1+01+7s + 898+:8+H8+I8+1b1+d1+98 #9:9H9I9d19b99999@189:HIbd..............@ 0189:HIbd................@@69\(33232?632#".#"#"&54?4.7327654.#"32>54'&#"'5b[-  # & ##/[^6% 0 3 ,-V , $?GtF&'F"WAPi:JtF> B 1;R.GuG SV*[ +! "+!> +@!$ +]/ֱ 2 +@ + +@ +@ +8+' 8' +8/ +^+6?{+ D+DD+?hQ+ AD+BD+CD+ #9CD9B9A9ABCD........ABCD.......@K998$>QU$9[K95,/9>'9 90174>754.'&54323263232632#"&543232654.'&#"#"&#"#"##"3 < ? HTcD;)A) 9l&7'     I4 #  ^TR@r jV!4 (XPCe1+; 9+d+ d + ++N 61+f/ֱF+# 4+#@+K2- Q+2@- +@4 +g+6?p+  _Z>x+ [Y=(#+ _ZZX?_ +  + + + + + + + + ++ZYZX+_Z[Z[Y+[Y_[_Z+?&+ \_Z+]_Z+^_Z+  #9 9 99999^_Z9]9\9@X\ YZ[]^_....................@X\ YZ[]^_....................@FV9@#!'DIN$96;4@99N-D9990146322>7>7>32#"&543232>54.'&54>54&#"#"+    E31.8.%+B-(H! $M-6-,      8%$ 7  20N$,7h*A+$+8+)L1;80  (-#$,P7 D3N*)4 #f? K+4 'QD 'RD 'SD 'TD 'XD 'YD :JW-+$ 2+8+> 7+$- +@$( ++H "++S 9+ +M8 +M "+X/ֱ; ;E+ C+K2E +@ +P+ Y+E;38999!99P $-$9(*99>-69$*35;$9HMEKP$90174>32326322>32"32>32#".#"#"&73267>54&#"%32654&#" E~E0 #9"$3+%:dHH1 s;.@&-G"@+F-!.E (F~:Gl#%- r:k1E * !9):C H##&/&EB$,?rZP0/J 9'_F s'QH 'RH 'SH 'XH'QZ'RZ'SZ'XZ AP#?+E 9+!++L 9+Q/ֱB BI+; ;I +@;0 +R+6+ -22+-+&-+'-+)-+*-+,-+32+- #9&9'9)9*9,92939@ )*,-23&'............@ )*,-23&'............@IB ?$9;9LE;999! %59990174632&'&#""&54654.'.54322>?6?2#"&72>54&#" k5% <9  ! #I  54.#"32>54." k 3kF     M  &F( &;f 4 -4AxS 00 4,C:F6QQ"''QX'RX'SX'XXHz'R\<g|M+P3l "+f+f+\ "+++#+' "+0+2+:+u "++++++ M:+}/ֳ$28 8 +@8/ +8o+@ ~+6?gC+ 0.X++++?d0+ +X1X0+2X0+?b#+ 3X0+4X0+5X0+6X0+SX0+UX0+VX0+WX0+zX0+{X0+ #99WX09V9U9S93919{9z9694959@6SUz{1345VWX..................@6SUz{012345VWX...................@8(P_h$9o+>M]lx$9@F9M\9 l@Fo999+'/9014674#"#"=467>465>54#"#"&547>3232>32#"&#"#"&#"+"732654.#"#")E( &  $7&'    ;I' *)E % Gr'4!       (wM   0~ SB  *  #  I'9MoHz'X\h'U$ `'UD{'V$ s'VD=]mb+V3 9+O220+( g+(0 +(+ ++k +j2Dc +D n/ִM +M +MQ +M2+% C+%2 +%- +%9+:2 +o+6+ j.:+j;j:+ 7>2#".#"32632#"547>54.'&'&#"#"&#""#".#""3:632>54#" $8?]3 w  @Z 8K;& N&L2*""& .(!]UW   8@gqY ":5M,  9$$ +B3-!>G 1U' :  =<O;+@ 7+0+*+" g+"* +"% ++J "+ +P/ֱ= =,+ C+, +' +Q+,=;@997J999;",/999@19J@ 357$90164>32326323263232632#"54>54&/"#"73267>54&#" I~A5 )/ H F, 9J% #+8@-!.E , F"!6xk 1 0 0&A *= %'$ +B/ .##$,?v\,%8Y''R& 'RF''S& 'SF''W& 'WF''[& '[F'[w'P'] Gap_+U3e 4+_+L +L_ +@LP +.+ #+' "+3++m "++ m' +93 A2m.+q/ֱb bj+i2J +J +7 7 +@7? +@72 +7 +@ +@% +r+6??3+ hH4hh+?Z+ h+h+h+?Y+ H5H4+DH4+FH4+GH4+hih+h #99GH49F9D959@ 45DFGHhi............@ 45DFGHh...........@jb X_$9J ()U$97*-BL$9mLRZ\b$9 ?9017463232654+"5432;267657654&#"+"54>7>23232632+"32>32#".#"#"&7327>54&#"[ / G,  %    % =$# U+#>"=;F*+<)+,R0}s r  /   S+ c#*##*#IX/84 2Tvih'U[( `'UHi{'V[( s'VHi'W[( s'WH=i}+\ 鲃+3 "+u+m g+mu +mp ++0 }++3 "+0 +@0$ +8S} +8 S8 +@SH +8S +@8? +/ ִ5 %+ 5 +@ +@  +5w+j C+jw +@jc +jr +jJ+F +>FJ+A +F&+" ++6?+  X 3 + +  +XUX3+VX3+WX3+ #99 9WX39V9U9@  3UVWX..........@  3UVWX..........@5 Y99w08NS\}~$9j9Jh{99F-9AC9&a9"$99}mhw99S\Eac9998C90 A999999017476767467654.543232632#"&4&'.#"&#"3267>32#"54'&#*&"#"32>3232632#"54>54'.#"#"$ $ AqB  2&V, =   N% &0NUw+ ' >$ 8K><# #Pb /  # $   0   K 8 0 w\ %A )'$ +B1  =s4A*+" g+"* +"% ++= 9+7* +7 "+B/ֱ ,+ C+, +@ +' +:+ C+,5799919:/0=$99",99=79017467632"32>3232632#"54>4.732654&#" <58S$3$ :cHH1 0?A 8K!./!R Gl#%-!?|15* -):C %#$3$ +B1  EP0/Ji'[[( '[H''S}*='SJ'{'V}*=s'VJ''W}*='WJ'9'^W*='\J'S}+'SKD+333 "+{222*+0T33& "+4W22T+N3$ 9+* + * +e33 <`22/"ֳ!$29 829" +@92 +"9 +@" +@" +9F+B2\ \F +@\c +@\} +F\ +@FL +p\F+ %+/p %+p +@ ++6?+ !.7!+!+ !+!+!+!+!+?+ !+ !+87+?a+ 7+˜7+! #99 999 9799@  !78................@   7...........@9"-$9@ 0?JN$9F99\pQky999}999n99m9kl99c9$9F\999&5X99T(2LV$90174>7>76764&+"54;2>465>54.'&54323263232632674>54'.543232632;2+"23#"&#"#"47>?654&#"+"3#"&#"#"32326?4&#"+"    / :"2 < ? 04 ( B7( . 0     !>6    B: 7 "#$ AD  >`    Q  0  BUR     4   J6eS+d3H +HS +HN +$++! "+'+A+Z L+6+Z! + 36 2f/ְ2) %+(2) +@)0 +@)& +'2:)+ +/: +: +@ +@ +)X+C U F FU +FP +g+6?M+ 'a++++??6+ +++a(a'+?E]+ 8a'+^a'+_a'+`a'+ #999999_a'9`9^989@8a(^_`...............@ 8a^_`............@:"9)<9U$+56>AZ$9FX.299ZH:<>CU$9+6 0999$!&90174>?4+"543232>7>54#"#"547>3232632#"+"32>32327>32#"54654#"#"  %+    &/6 0W9>!7Y    `"19"C ! 7v;4   t, q."T]N(/I d'T,"'TZ7h'U,`'UZJ{'V,s'VZ=2QP+23 "+D+< g+754.'&543232632#"&#"32632#"&54>54#"#"##"3 < ?    > 9%$  #  ^qk #.$ #' 5!=;G9+2 g+29 +25 +++ "+E/? +H/ִ/ C+/ +/6 +/+  + + + < +B '2I+/9 ,99 "?E999B%9299014>54&54654&#"&#"54;2632326323262#"&4632#"&" -   A %  !>% #9$'& $ !-$"(   N4 ( !" $ &&-&',2'W,$e+ + + +++# "+%/ֱ  +  + + + + &+# 99014;263232632"54654&#"&#" A %  7J-   N4 ( ;S( '-,8'ML'S-8'SID9'^-.9'^NH'R/.'Ru*O9H'^/.9'^>OH'] /.I'] OH'y'/.'yOC]Y+G 9+\+U3 9+\ +@N +#+*3 9+^/ִ4 %+4 +@4, +4 +@! +_+6I+ 8 >?+ C1 + + +8+ >+8+?ms+ +C2C1+I+ 78+?>+@>+?p+ CBC1+8 #979@>99?9  #9 9 99BC1929@ 1278?@B>.................@ 1278?@B>.................@4'DY999Y9G/6P$90174>7>7674574#"#"54?67654'"&5432326323?63232>32+"&#"#"  2 H = 4" 6  %HCe5$  - "//J  ,\8$,5   xxP  R"K  W1H+< +54#"#"547>3232>?632#32>32#".54>54#"#" > (F $ 3    25  = K-\  EH  F:*!w_'R1'RQ9'^919'^Q'[1'[Q$'Qw'h'U2 `'UR'{'V2 s'VR''Z2 'ZR'`},Z+N g+^+d P+ZN +@ZX +NZ +@NS ++ 3" }++y 9+" +@" +*E^ +* }+E* +@E: +*E +@*1 +~/ֱa at+' %+'<+8 +08<+3 +8+ ++ta[^999'GK99<"*EN$98X99359S9 U99Ndh9E8Uip$9*5a99"3t999y 9014>3232632#"&4&'.#"&#"3267>32#"54'&#*&"#"32>32#"&#"#"&732>7>7654.#"'5b[  3&V-=   N% %*TUw+ U H;N4Vp $*<0GtF&:JtF " $   /#    M :/ w\$ j   - ]/ Aft 3BO%+ 2+1+7 9+% +@ ++3> 9+K2E1 +E "+P/ֱ4 4;+C CH+ Q+;4199C +999H %$9 "99"+4$9>E ; CH$9017463232>32"32>32#"./#"&72>54&#"%32654&#" k1%H,$2+%:cHH1 s;$7#6AYM1L;1%&F0Gl#%- f$* !9):C H_VHL6QQ"CDC:F)P0/Ji'R"5V'RU9i'^59V'^MUi'["5V'[U'R6F'RV'S6<'SV9'_690'_aV'[6H'[V9'_79'_DW'[Q72']Wu$I+M "+>2+l M+2l +l +-7I +X3- _2v/ִ +w+6?{+ Rg:#%:#+&:#+':#+(:#+):#+RTRg+eRg+fRg+TRg #9e9f9(:#9)9'9&9%9@ #%:RTg&'()ef............@ #%:RTg&'()ef............@MIAK997V9-0]99lt999014>7>3232?2#".'.+";2#"&"#"#"&#"#"546676767654#"#"54326;2>7654&#"#"qfYZ /^-  :R!  K=  2n /\ -`:  = +  "H  I=+      D       Y=N+E +EN +EI ++( %2+XN +;3 42X +@ +Z/QֱA AQ +AK +QA +@Q +@Q + AQ+2 %+2 +@29 +[+6>I+ S? ,-?,+.?,+0?,+STS+TS #90?,9-9.90?,-.ST........0?,-.ST........@ QW999A992=99XEQ999( 299 "9901746;267654&54>7>3232632#"&#"32632+"32>32#"&54>54+"#;+    ! >   < `( h+&   O     =  -  V H*4<',''T8'TX,'h'U8`'UX,'{'V8s'VX,''Y8'YX,''Z8'ZX,='a9+ P+Q+K+C g+CK +CF ++$)333+ "+!.`222b/Zֱ Z +@Z +Z]  +M+@ C+@M +@H +@+, +, +" +c+]Z9M 999@ S99Q999,299CM9 2Z99",990143032632327654.54323262"32632#"54>54.'.5454.,##, : %P:A3+,( I !'>:?  9K,3:%! 4.19#8F3 05=#  $ #B- FA =^Q+ +A+;+4 g+4; +47 ++3X +X +X] +_/Sֱ V S+ Q+V +V + =+1 C+1= +18 +1E+" "E +@"* +"E+ `+VSQ9  91=@N99"E/BIK$9$99Q4/=99 E9X@ "$(*IKNS$9901463232>7>763232>323262#"54>54&54>54#"#"54654#"#"C.%    0!=!)! F, 9K  mS"-  &&`" -$   %`F8 %%; %'$ +B+H2 !@<# 'S: 'ShZ'SJ<Hz'S\'XJ<7'R'='R]7'W'='W]7'['='[]h?3K1+ 1 + ++ 9+ + +4/5+6?+  ,$>+  %#?Z+   +  +   +   +%$%#+%#,%,$+?|+ &,$+',$+(,$+),$+*,$++,$+  #9 9+,$9*9)9'9&9(9@ '( #$%&)*+,................@ '( #$%&)*+,................@0146323265>7>32#".'&#"#"& &W $W54J '/   #^.  EQoJ&.-  +:9=+/UiE?>LI9'^690'^aV9'^79'^DW82-+ .+ +++ "+3/ ְ 2 2  +  +4+6?s+  ."  +  +""+"+"+ "+!"+  #9 9!"9 9999@   !"...........@   !".........@.01999014323267>54+"5432632632#"&'.I (  /    *P7[  g=9J"    \K ]rOS[[esVW3YQ TyZK ' / + /ֱ  + +01462#"&* jz 4G  / +/+ +014>762#"! $ 9$ +/ + + +2/+ +0147>;2"&#"#" %Sa$  B$UT @/ M+ + ++ M+ + +/+ +01463232>32#"&#"#"PH OJS0 O03`( /  /+ + 990143232#"&#"MB%E s5/ L+ +@ + 2/ִ + + +014;232>;2#"&  ( * 5 /6`$- ?_ $ / + + /ֱ  014632#"&& $ !Q-'(, 1/3 +2 +/ֱ  + 014632#"&74632#"&#&$'M ' &" ' &"| H / 9+/ 9+/ִ + + +  9999014632#"&732654#"5 >&! $D"4-&-*$, /3 +2/ִ + 99014>762#"74>762#"" $ " $ 9$ 9$ !2/ + + + 2"/ִ +9014;2327>32+"'.S/    %U) $  B%U-/ +/ִ %+ +  +@ +014632#".?"1*3  ! KV/+ +/ִ + +@ + +01632#"54>54."> *3  9W/ + +/ִ %+ +@ + +01632#"54>54."?O! *4  9W^' '   / L+ L+/+015463!2#!"& @     / L+ L+ /+015463!2#!"& G    / L+ L+ /+015463!2#!"& S  tI3+ +/ִ C+ +  +  ++014>32#".I46  +  1W* ! 0&$3+ +/ִ C+ + + ++014>7654.4632"$  +  46 1&1W*$1/ +/ִ C+ + + ++014>7654.4632#"$  +  )& / 1&%G,I3+ +/ִ %+ + + ++014>32".I  0 P#: #;I+<;I #9=9>925'93949@ 5;..........<234=>......@5DF9G9LN99,(%/$99NR9 99999014>3232654&543232>32#'.#"+"57>54.54>74574#"#"? %F* ;6"M4 F,       (K+ "( )d@T%< /'5 )Of` EED)8K5;3 yiu+e3 9+X2ux+a3 +\2+1+(e1 +G3( "+92+C3$ +=2z/ ְ 2V +VVk+o +o/k + s h +V+K +KKO+ +/O +/+3 ++ 7 +I27+ +@73 +{+ou9sm99 9h9V/9(Xe$9KkQ97P9O19G999359S^99 KQ$9 99$(+?991/599014>32325654&4>74654#"##"54>3232654.543232>32#".#"32>32#'.#"#"57>54#"#"; 5J  F/ (F'  :7#M3 (I* %L2 G$ =9  #,%)5r!cP% "(!;$+Q.% );&)9tE'? D<# U /;h./ + +/ִ + + +01462";X|YX| |XX|Y1iq''T]./ +/ֱ +  + ++01747632#"'&'& dK >"z gf ?$'T]./ +/ֱ  + + ++0174654&54632#"&'cJ Zfgf]z 4>74>32#"oOr~  ׍H{ë   E% !2\)/ //30 2/3 2/ 3/1ִ %+4+1.99/%'99 990173>32.#"!!!!32>?3#".'#735B_Z*:Z-"O.;rU;v:$?,K]#FH<R7#/F\ $*2 $FM/ 0HK ==*O50' /  /+0174>3!2#!"& V   H_+Y3 g+c2 +  +^2,+8 8, +85 +#+ )++D33 P+KNqt$2/+6?/+   {?@+ fnRO  + +RPRO+QRO+fgfn+hfn+ifn+jfn+kfn+lfn+mfn+|{+}{+~{+{+{+{+  #99{999|9}9~9gfn9h9i9j9k9l9m9PRO9Q9@ Rin OPQfghjklm{|}~.........................@ Rin OPQfghjklm{|}~.........................@ o99I999!=998'901463232676?4#*"#"5463327>3232>32#"&#"32632#"&#"#"5463232>7>?4#"&+"#"32327>54.#"'     2Q!. )    96  *$       2(( Q ER  &)H$7 YG8     &<1&(  F14)#  / 'C?K :^1! !)18\ C[$`oHnW+I +IW +IL +l+ l + +*+3 3* +3/ +$+9?33!+;=33 9+_b2228+o/ִ +Y+F FY +FN +\FY+B \B +@\ +p+6?z+  g7  + + + + +g8g7+?+ eg7+  #9 9999eg79@ eg 7..........@ eg 78...........@l9Y%;b999\3_99BF/1>*$9IDY99!B993$%90143232>76754+"#"54>32;27>7>32#".#"32>232632#"54654&'"&#"#"5(    % &  4'!R&A #9  BV  & +"& E) P>  -!4" >*#/& +7LU5 y$0  S4  0%56Kgl>+5 +5> +59 + +f+ f + +!+H +P33 9+W2h/Aִ1 Q+1A +1; +1F+* i+6?r+ b]+ +?r+ + ++?+ b^b]+_b]+`b]+ #9 9 9`b]9^9_9@ `b ]^_..........@ `b ]^_............@1ADH99F!9*$-9951D99U99H-F99!*90146322>754#"#"5463;2>7>323263232>32#"&=4754#"32632#"&#"#")  ( ( 1_<    $# c* #*5%>" 62 /J!6251?#  2g2/R ,% !" b#"M $IdR  ĥ!dL`_+W +W_ +WZ ++u3 9+2+ +|21+@ f+@1 +@; +)+ /++$N333K+G$3g 9+22/aֱU Ua +U\ +dUa+P dP +@d ++6?+ $. ?mu+ .k p  $+ $+ $+ $+pmpk+npk+++++++++++++ $ #9999999999999999npk9m9@ km np........................@ $km np..........................@daK@99UL9P>1N999 y99gWRad999KP99%9@-9014>323267676754#"#"546;2232767>3232>32#".#"32>3>3232632#"54654&+"#".5463232>7>?4+"+"32327>4.#"'    (   Dj),$>#& $"9 (>*!+ \&( _%    -       J FR %&A&" rt:/ vP":    HbQ N*3 SO( F7:T,5 41&CQM><>;+=>;+?:+ MNMQ+OMQ+PMQ+nZnY+?V+ lnY+|}|+~|+?v+ |+|+|+|+}| #99999lnY9NMQ9O9P9<>;9@=MNln767654#"#"5463327>322>32326322>2#"=4?>54&#"32632+"&#"#"546322>754&#"&#"#";2>7>54&#"'     ( -  (29!" aHvS6 ;gK   || ./53@  $18\ ")2  `p g~+;3t 6+~+2 +2~ +@26 +x2 +M3% BE22+\/ 9+2  V /ֱq q +qz +q +_ +_=+. .= +@." +.8 +=. +@=G +.S+ 鱁+ qn9_9= \fl$9.@EMNVY$9S&+$9%28=filqz$9 @dn999V _99\9017467>4.546323263232632632+"32>32#"54654#"&#"5<>;267654&#"#"&#"#".#"32>32#" UC, RB- * !" `( h+=-   5&1"  '6P=?$9  (X2N# 3C     H^8 A#  5  M>R ,+4^lw+3 !+d  L+v+ 9+l+?+ 5+{+3 +{ + +++$+/+9+^/R +R^ +RW +h/o 9+3{ + g+'?+!,? +! M+/:ֱ m:+ /m +/ +/ +) +/+H +H+ 鳹+ 鲹 +x ++ 鲖 + ++ Q++@ 3?dhosv$9/N999^9R9999ohf9ds9Nm99v99993$999{H$9,!):9990114>323267>54.#"32654&#"#"54632#".54>3232>32#".#"#".732>54&#"463232>767632327>32#"54>54#"#"54654#"#" 7%F`,=K4Jq\@$?#&3'9*4,&;jC5R%$&67&:- .L)HuJ &N:/:$HZ!*  /x, 1c)Dl;& _VFg1) 4C,")A)%SP4@b?754#"#"546332>7>323263232>32327>32#"54654#"#"54>7>54#"32632#"&#"#")  ( ( 3_<  3"7Y    _#18"D  $5%>" 6 2  /J!6251?#  2g2/R  q.#S]N(/I  AV%IdR 8hdHW+h3L +LW +LR ++++3 g+2 +  +22+u )+ :+"+~33 9+222E+^ L++ 3 9+/rֱ: :\+G Y J JY +JT ++6?{+ yy+++?v+ +++ž+ #99999y9@ y..........@ y...........@:r5>B999YE^99W 9Ldj999^@ >BG@Ylo$9"99E9%767654#"#"5463327>322>323263232>32327>32#"54654#"#"54>7>54&#"32632+"&+"#"546322>754&+"#";2>7>54&#"'     ( -  B Q +*)I$7  #=8Z&v2  JuA!   q.#S]N(/K  AV ;gK  7pu ./53@  # 18\ ")2  `pK\+]q33T +T\ +TW + ++  + +!+| A++3 9+222/sְt2n lm22nD++6?r+ ?L+ t.xo+I+ ].`PJ?r+ + +?r+ + ++?W+ o,o++.o++/o++1o++2o++3o++4o++5o++ȱ+ JKJP+LJP+MJP+NJP+OJP+`^`]+_`]+olo++mo++no++?F`+ tutx+vtx+wtx++++ #9 9 9999utx9v9w94o+959392919,9/9.9KJP #9L9M9N9O9_`]9^9@& /12Jnowx +,.345KLMNOP^_`lmtuv......................................@% /12Jowx +,.345KLMNOP]^_`uv.....................................@ns!|999D$*HT\z$9T:H99D990146322>754#"#"5463;2>7>32326323267>3232632#".##"54>7654#"32632#"&#"#")  ( ( 1_<    yMM1  . F2&'   <5%>" 8 1 /J!6251?#  2g2/R  l*"! `$ 4D0X  8PESE   @Y2%IdR   ѣ"dHPg+y3_ +_g +_b ++++3 g+2 +  +22+ )+ :+"+33 9++ 3 9+2O+rHgO +r 9+/{ֱv vR++6?mu+ .+ kiV[WV[+XV[+YV[+ZV[+kjki+?N+ +++?v+ +++Ϭ+Ь+ #9999999WV[ #9X9Y9Z9jki9@VWXYZ[ijk....................@VWXYZ[ijk.....................@v{2$9R5F:_g$9_gw{999rv999Hp9Fl$9"R999O9%999.=>,$901463232>767654#"#"5463327>322>32326323267>3232632#".#"#"54>?>54&#"32632+"#"546322>767654&+"#";2>7>54&#"'     ( -  B Q +*)I$7  #=8Z&v2  JuA!   (P4 `$ 3D0Z 8PESE   @YKJ ;gK  Z ./53@(  !,o18\ ")2  `pKiy8+l "+ +h+ h + +!+F Q+3U 9+222+r L+TX22z/;ְ<2j y2jo+5 {+6?r+ d^?4+ <.Cy*+ +?r+ + ++?T+ y+y*+<=9?9@9A9B9xy*9w9+9@ @bd *+<=>?ABC^_`awxy........................@ @bd *+=>?ABC^_`awx........................@j;F9o!)28D$9Ul.5;o$9Q0990146322>754#"#"5463;2>7>323263232>32#"&54>7>54#"32632#"&#"#"32654&#")  ( ( 1_<  4*P359n1I 5%>" 30 /J!MBt9)%4 6251?#  2g2/R ./J5m+)D5W/y%IdR    d DX*< /(#_:HI+ "+q+++u3 g+|2 +  +z22+W )+ ;+"+`33 9+g2C+ L+2c+ 3f 9+/ִm 4+m +@me +mL+M2 鰷2+F 鱹+6?{+ ~p[?5+ M.S;?c*+ <;+MNMS+OMS+PMS+QMS+RMS+pkp[+mp[+?m+ op[+~~+~+?o+ ~+~+~+;+;+~ #9999op[9k9NMS9O9P9Q9R9;99<9@Pkmp767654#"#"5463327>322>32326322>32#"&54>7>54&#"32632+"#"546322>754&+"#";2>7>54&#"32654&#"'     ( -  D Q +*)I$MBs8)%4 7  #=8Z&v2  JuA!   #J5m+)Tm#u ;gK   > is ./53@ 'i18\ 0)2  `pDY*< /(#_:'>c%_+& 9+Y+N+W "+X2WN +WT ++ P+39_ +3 9+E "+d/ֱ# #+ +e+6?w+ X.-JGX,X-+JHJG+IJG+XYX-+,X- #9IJG9H9,-GHIJ......,-GHIJXY........@# 7763232632#".'.#"32>76?654'".543232>32"#"&5463232654"#".''H/VXFyB  &Qw3N . B (  "+4 !,N-cQ&g3LEG,$#"<)9<# 6U.   ^SK  ;T =k~ x+T 7+% +7+2 "+%7 +@%( +"27%+ g+  "++@ @ "+iax +i L+ai +@ad +7 + /ִ + +@ ++}2 + +@N +F +Q ++ +^+l +^l +^f +l+: %++99 9F$9Q@K$9x9=99^+.4T$9l!(7999aTQl99if}99@FKNH$9999%:$9 +$99.9015467>232>32327>54&/"&#"#"&54654&+"54632#"&#"32632327>7>54&#"#"54632#".5463235>7>4.'.54&#";2#"&732654&#" A<- >+1 + "  C8#D) Q ( !  d6Ym +U#!''  6%/.V<-K ,#8;  0?QX5)-*;{ZY+ d+Y + +4+:3$ "+>2$4 +$- ++F3 g+M2[/2ֱ' '2 +'* +'"+!2D %+C2D" +@DK +@D< +"D +@" +\+6?Q+ !. BT  !+ !+ !+ !+ !+ !+ !+TCTB+?(+ PTB+QTB+RTB+STB+ ! #9 9 9 999 9QTB9R9S9P9@ PT !BCQRS................@ PT BQRS..............@'2Y999"49D7N994$?6&#"#"&5432326?>54#"#".5432326323232+"#""!+  *$  R()  ;;   5 5C;4 >4Fh8==  ((V,   Q y%.{Cf4\5+/39 9+*2+B +B  5+ ++M+Z 6+MZ +MR +\+]/ִH +HP+U U + %+  +  +^+PHM9URZ99 7>$995-799Z&9M$%>H$9B!9014>32;254&54632#"&#"2#"&#"#"54>754&+"326&632#"&53 +޼  8&v &  KBE).18 5   !:Lf/      PS    =>(  PY|<+<+A 9++33R 9+T22#+ 333Z/ִ + + +[+ 9?BR$9A<?9R /5$9#(901432326322>54&&543232632#"#"&54>767>54'.'"&".!L/709&QP" >  F 5  ( !4&(    'StM 07 I  !;  Y&L'9'^"'>'S}'>{'V}'>'W};{'S9'^9'_'[e'R<'X<'S<aXgyV+D3 9+j2V +@VO + V +@  ++c 9+52c +@ +c& 9+!+z/ִY %+YT+G +TG +@T +@TQ +G+2; +:2;+ +o+A {+6?/+  _y7  _+ _+ _+?^+ y8y7+9y7+:y7+?^+ wy7+xy7+ _ #99xy79w99989@ 7_w 89:xy............@ 7_w 89xy..........@YW99T V999GO9h9;D]cj$9u9"599o&'>999A1*99c @ *.AY]ahou$9&'90174632327>54.54672>323#".'.#"#"#"54654#.327654#"32>54.#" 7  !..!sK  (7 7  '77'ze  >eo,:  $/h *5% #a C) &-;P "<'Z]&% "  % .>d& &M5_o* e D2' 5#H/0 $C J + + 2+/ֱ + C++  9999017432#"32>54&#"CjOQ;wM?IA3N&EB6P$xsΉJwoXqf21++.33 g+!2+ / P+3/ ְ2 + +@) +  +  +4+6?+ .+ #9.........@ !99 9 990174;27>54#"#"4>7>3222#"&#"#" - *%: #< K$J  ( %  '>>@b;+) +); +)1 ++ +  +  +A/ֱ  + +B+/38999 )9901754>7654&#"#"54>323267>7632#"&#"#"&>5B:1* 3 (F))," $ "U?  . =a Z  ?fZO6? % ,7) &>(0(1!,!% fH;p9+ }+(+ f+( +# +9( + "+7>54&#"#"54632#"&H.$H[;; $*-*2 d49N&.&%$a4V*xI7I  N+&7" +27> +@7/ +>7 +> +O/ֱ< <E+FG22 2E +@ + 2+ 1 1/23+ P+6?E+ 2.H* * +&* +?+ '* +(* +2E2H+F2H+G2H+(* #9'9@ (*G '2EF.........(*&'....@<99E1/4A999+K99%99 >K999017547>7>32;2>2#*&"#"#"54654+""&7;:62646554#"<2ez ' 7   ! uEC"'O :M /   &OM% %M  3HFzD+ g+&+ +0 +)+D +;D +; +G/ ֱ> > +@>( +H+> #99 >99;9 0901743232>54&#"#"&54>7>;232632#"&#"232#"&H4% !:.#I?$% ! Y  9;. ]OlPFR?+C!-h+% P++++ ++./ֱ" "(+ /+("999 99+%99 90147>3232>32#".732654&#"C3~1-M(#01IYrZ+E) VB9*=G137˞71  )(#\2Ks'32;2?2#".54>7654&#"&#"#"&#"#"z HG2 !k?3  >L[  R %. (@#IH4A 9~'J  H"3B +& "++@ 9+C/ֱ# #+4 C+#=+ 4+ ) )/ D+4# 99)@  &0:@$9@& 0:$90174>54.54632#"&732654.'"32654&#"H)(`TRW&&$%x_NkBJ4>O 5$*&2$(!E:6?;+H*<#UuX3*C# )A%cw^^NQX@'"&UN2" \0+ULF,o+ 9++* }+ " + "+ " +@  +-/ֱ '+ .+999'999* 990147>54#"#"&54632#"3267654&#"FAg6$ $9KORPh1Pdf/YC61 J2*> BGJ)Vge^nO$Yx(Nc^!9M[jY+,3 "+Y +@ +K/;3 "+^2K +@KC +K +@ +f/R +k/ִ ++N +NE+A +A= I +I/= +j2=A3+22+W2 +/ +b+8 %+l+6?X+ W.j.W/"j " W+j0j/+1j/+2j/+?_ + T W+V W+jhj/+ij/+ W #9T9V9ij/9h91909@ /TVWh012ij............ TVh01i........@NL99IEK9AC993=;RY^f$9/9,99b98(6!$9KL9f8\b$9R63999Y!%999990146232?4.54632632#".'.#"#"#"54>54#.276754#"32>54.#" %"F0  "    &.&U= $9L  -5 %&$ ) - (1F.   &92"8F  #& g ) 0$(T. I / 9+/ /ִ %+ + %++  9999014632#"732654&#"hE35[Jp3+&,7)&/6}\InEbQAXuI_5+ "++4/.3 "+$2/6/ְ2" " +@", +@" +" +@ +7+6?+ .   +  +  #9 9   ......  .....@" 1999419014;2>5>?4#"#"4>7>32:#"&#"#"I * + 88l @@   *% %^"2d+. 4+. +& + /  +  +3/ֱ (2 + +4+#+99.9 990154>7654&#"#"54>3232>7632#"&#"#"% N'%$7"'@6A6$-.%;5d&b>:0$ +-17![G?   [[3r+# P+# + +$+2/ 9+/ "+4/ ְ2/ %2 / + +5+29 /999 9+90143232654&#"#"54>7654&#"#"5462#"  /=** "5 4BR7++]B$p F) * !2!,&%2 &9`G!0=/+3 7+32 +@$ + +@ +>/&ִ 4+ ) %+)/ %++ +?+)&$+99!9;999 9999014656632;262+""#"54654#"#"&7;267654#" !  % { 5 ^    ;   ! U : .[#*<*+ +:/ "+: + +/ /2 !+=/ִ0 +0 +5 4+5 +@5" +>+0:$9  '299959 599290143232>54&#"#"5474>;23262#"&#"32#"&. *+# "$<  "+ 036 B,2z 4  B &D18;'Z"*j/" "+(/ "+( +@ + / "++/ֱ %+ Q+,+%999 999("990147>32#32>32#".732654&#"b"S!"A) 09L<$4 A("%,!zb!+92  O.F]#64 1L71;A<?V-/p%+ +% +@%. +0/ִ+ +++ "+ +1++999" 9 9%990167>32;2?2#".54>54+"#"? /.  $ 7C7 n" ' #4 &s_R  W"!.;/% "+,/3  "+9/ "+3.5462#"&732654.'"732654&#"?d6 N<1F7(","+&"$ & &2F6 ( /=J<54#"#"&54632#"3267654&#" *B#%12T75E1LU(B)  ,b '*, P4>]_>GtE%lhG .;K"4'/ִ + +  +  ++014>32#"."+0# -5- 9oO= 0AR4\1%.;R4'/ִ + + + ++01'4>54.54632#"-5- +0# /CR4\1%.;R)9oO=r8/ + +/ִ + + + ++014>54.54632#"  < )+ &&VRx#+ +/ֱ +014632#".'&/    ,   "2(&/ 9+/ 9+)/ִ +2 +" + +*+6?V+ .+++++++ #9999999@ ...........@ ..........@&"90174?6767>;2+"32632+"&"# & 3 * 6 %2!9#* $!ٜ  2*1'/ 9+/ 9++/ ְ2 +!2 +@ + + 2 + +@ +,+6?V+ .#   +?+  +  +##+#+#+ #+!#+?+ "#+  #9 9"#9999@   !"#............. "#........@ %99901'4326;2767>54+"543632+".3 . # '$ ٜ  %2:- @!v=.M=IL=%"M=N=!T=.#g="W=?-N="U= )S=""q="q=3r=x="#o=$o=._OI^O%"_O`O!fO.#yO"iO?-`O"gO )eOErOxO.~I~%"~~!~.#~"~?-~"~ )~tr?~x1~|E+?3I 9+:2+g +g  5+  +  +z+n 6+nz +nr +)3Er +R3) Y2}/ִk +kp+u ua+' %+'a +@', +@'= +a' +@aW +@aG +' + %+  +  +~+6?}+ L_6$%6$+LNL_+NL_ #9%6$9%6LN_$......%6LN_$......@pkn9uUYgrz$9aBR\d$9':9 3;?$9IE=BG9993P9),W99nk99g"a99014>32;254&54632#"&#";2#"&"#"2#"&#"#"54>767654#"#"54326;2764&+"&#"26&632#"&!.2  8&v 9Q! KC 1n  /[). 1 '4  <2*=      L     D    %7%'  J'/33 +22/+ ++6 + .=+ ++|+ +++ #999.....@ .........@01467>32#"'$8  -.    '''3hrz3hsz3htz3htzI`a:!/ִ + + ++014>32#".I1GM8 )>A8 +3+ $-(_f3 )E`c0"XR= (Nc`:!/ִ + + ++014>7654.54632#"-)>A8 +2+ $,(1FM8  )E`c'+XR= (NcE_f3?jF1,F*/! /3 -/ֲ222 %+2 +& + +.+6?+ .  + +?+  + + + + +  +  ++++++ #99999 9 999999@  .................@  ..............@!*&901465767>7>;2+" 32632+"&?7 (=V ) GS }=Hj,M)  ,i7 j1.M-/ 2/ //ְ2 %+ + + +0+6?+ . (++ + + + + ++(!( +"( +#( +$( +%( +&( +'( + #99 9 9 9 9 99&( 9'9%9$9"9#9!9@ !$( "#%&'..................@ !$( "#%&'..................@90146;267>7654#"#"543632+"%`) G^ 6 (>  #17i   =H&j,M) ;|6&/+ 2+2 +@ +0/1+6?Q+   +  +  +  +++ #9 9 9 999@  ..........@  ..........@901454&54676763232632#"#".#"#";+' $ &H)" % zq DC ! z  |&0 + +21/ִ +2 + +2+6??+ .'  + +  +  +''+#'+%'+&'+ #99 9 9%'9&9#99@  #% &'............@  #% &'...........@9 90147>5>4#"&543232632#"&#"#"$ % @% & /%  " x ym  l  A8mz'K}zz'zZ74>3!2#!"& V r  ~ 4632#"&~& $ !Q-&',m 4632#"&m$  O ("(*<"i 462#"4632"""0 " q4>32#".  0 P#: #?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghjikmlnoqprsutvwxzy{}|~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~     uni00A0uni00ADuni00B2uni00B3uni00B5uni00B9AmacronamacronAbreveabreveAogonekaogonek Ccircumflex ccircumflex Cdotaccent cdotaccentDcarondcaronDcroatEmacronemacronEbreveebreve Edotaccent edotaccentEogonekeogonekEcaronecaron Gcircumflex gcircumflex Gdotaccent gdotaccent Gcommaaccent gcommaaccent Hcircumflex hcircumflexHbarhbarItildeitildeImacronimacronIbreveibreveIogonekiogonekIJij Jcircumflex jcircumflex Kcommaaccent kcommaaccentLacutelacute Lcommaaccent lcommaaccentLcaronlcaronLdotldotNacutenacute Ncommaaccent ncommaaccentNcaronncaron napostropheOmacronomacronObreveobreve Ohungarumlaut ohungarumlautRacuteracute Rcommaaccent rcommaaccentRcaronrcaronSacutesacute Scircumflex scircumflexuni0162uni0163TcarontcaronTbartbarUtildeutildeUmacronumacronUbreveubreveUringuring Uhungarumlaut uhungarumlautUogonekuogonek Wcircumflex wcircumflex Ycircumflex ycircumflexZacutezacute Zdotaccent zdotaccentlongsuni0218uni0219uni021Auni021Buni0237uni02BB afii57929uni02C0uni02C8 gravecomb acutecombuni0302 tildecombuni0304uni0306uni0307uni0308uni030Auni030Buni030Cuni0312uni0315uni0326uni0327uni2000uni2001uni2002uni2003uni2004uni2005uni2006uni2007uni2008uni2009uni200Auni200B afii61664afii301afii299afii300uni2010uni2011 figuredash afii00208 quotereverseduni201Funi202Funi203Euni205Funi2060EurouniFEFFf_ff_if_lf_f_if_f_lc_tQ_uf_hf_f_hf_kf_f_kf_bf_f_bG.001 bullet.001J.001T.001Y.001i.TRKGcommaaccent.001Gcircumflex.001 Gbreve.001Gdotaccent.001Jcircumflex.001 uni021A.001 uni0162.001 Tcaron.001 Yacute.001 Ydieresis.001Ycircumflex.001 dollar.lining zero.lining one.lining two.lining three.lining four.lining five.lining six.lining seven.lining eight.lining nine.liningdollar.lining.supzero.lining.supone.lining.suptwo.lining.supthree.lining.supfour.lining.supfive.lining.supsix.lining.supseven.lining.supeight.lining.supnine.lining.sup parenleft.supparenright.sup comma.sup period.supbracketleft.supbracketright.supdollar.lining.subzero.lining.subone.lining.subtwo.lining.subthree.lining.subfour.lining.subfive.lining.subsix.lining.subseven.lining.subeight.lining.subnine.lining.sub parenleft.subparenright.sub comma.sub period.subbracketleft.subbracketright.subzero.lining.numerone.lining.numertwo.lining.numerthree.lining.numerfour.lining.numerfive.lining.numersix.lining.numerseven.lining.numereight.lining.numernine.lining.numer comma.numer period.numerzero.lining.denomone.lining.denomtwo.lining.denomthree.lining.denomfour.lining.denomfive.lining.denomsix.lining.denomseven.lining.denomeight.lining.denomnine.lining.denom comma.denom period.denomTbar.001hyphen.uppercaseuni00AD.uppercaseuni2010.uppercaseuni2011.uppercasefiguredash.uppercaseendash.uppercaseemdash.uppercaseafii00208.uppercaseparenleft.uppercaseparenright.uppercasebracketleft.uppercasebracketright.uppercasebraceleft.uppercasebraceright.uppercaseguillemotleft.uppercaseguillemotright.uppercaseguilsinglleft.uppercaseguilsinglright.uppercase equal.ref1i.ref1j.ref1 divide.ref1 uni201F.ref1$, @DFLTlatn0  AZE 4CRT 4TRK 4   aaltVaalt\casebdligjdnompfracxhistligalnumloclnumrsaltsubssups     $,4<DLT\dlt|TX2x4> ".26:>FNRVZ^bfjnrvz~>> *-7<>@V^`mo}!#%57Bpqrstu? $048<@HPTX\`dhlptx|>? *-7<>@LV^`mo}!#%57BpqrstuRL     ( >@( >@ rr V( >@^`mo}pqstu(*-7<!#%57Bl  (08@FLRX^IEINIKIOILKNEOLII XW4F F`DFLTlatnAZE CRT TRK cpspkern0 ( "$%&'()*+,-./0123456789:;<=c  !#%')+-/13578:<@BZ7x~$:Xn.8FLRd~$>Pbpv(2<  @` >a *x0@`  ) 0 @1BS`&w>|0 @ `p>k>'%?@`p )@`[ @-`">x  0/@>`5ew ># ?p )0@/`$>z] @`w`? ? ,? ' ? ?+7'` ".??>>6? ( ?? ?#2> .? ?>8>B?( 7C%? ?? )0`%7 6)01`7(K ='@L`A>gpH)Biw7 ")49>?T^egprx|>SFQ#B?66,6SS?" ; ;;""?&&&&& &&  && & &&&&&&&&&&-------------------- L6N@<"2D.:)),,NND;NNNNN;6N,(+N;1,( '' ' ';""""",,,,,,,,;,,,,N;;;N;;;;,,,,,,,,,,,,,,,,,mh<<SFQ#B?66,6SS?" ; ;;""?    $K23"2222 2222 2   2222222222      LAY@<=O.E44+)YYO;YYYYYEA Y 36YE<73('2 2!;-- ---##''4;Y;E;Y< <;; 9@G,,QQG QQQQQ=9Q+.Q=4+, ** %%%%% ,@@,Q=Q44 (((,""" DN :111NN:666:N ///:;S?;I-?..*SSI:SSSSS?;S -0S?-&&,,:'''''&&.:S:?:S: ::: SFQ#wwmB?66,6SS?" ; ;;""? %13==3 =====)%==)  11=)= 'l##5+??5?????+'??+" ##?+?"" 5*38BB8BBBBB.*BB.%  33B.B%%  DD;}nuQQQ`Qxxxxxxxssxxxx; ;;\333_3wwwww{{{mm0^e 3hhK^KKKcc^^^h; ;];]]SLLLVLnnnn^PTTT|aF+7>)).whqhhh)AFFFFhFFhhhhhwhhhh|hhz3KNNN(',!!!v-vCvbvU@@@dMdNdNdNd$dPw|~5~X~S0vb@y"luR G GWD8 8888%%%%%%%%%%%%%%%%%yyyyyyyygYZYZqq1$6^Pc$ 'Yo2lBlA8Uem99ooo1R<3JG=4JEE:GG) JELE5], [gh~~S|*wO# pp~IhT0ontt_j(l1~6fR&dolZo%z&1<<<<<<;J====EEECG JJJJJoJE+hhhhhhh~~~~~xkp~~~o~nnnnjHu<h<h<hJ~J~J~J~GG=~=~=~=~=~J}J}J|J|E*E">E EDrEqEw:G#G G G F GM p p pXJ~J~J~J~ETETEV505.505.]o]o`onnnnnntj,(,(,(S50]oNllllll\$i\\$i\{BdlQosvvy~-vyvyvyJbwJJJJbbbWWWWWWWWWWWWWWWWWa{{{{{{{{K7@?@/![![BDa?ci]kpdrujwyn{}qDtp8JLMOMgnslixmpp-slix-1.4.2/docs/_static/fonts/OFLGoudyStMTT.ttf000066400000000000000000005227201342457644200227640ustar00rootroot00000000000000FFTMU#]GDEFdP4GPOS!GSUB޿NC:OS/2X|Vcmap &cvt (fpgm/eglyf‹5xhead|?6hheaT$hmtx<' locaBmaxpVx name({postB AprepV̶<a =I_<nnJRJZfRlr1oBPfEd@  !ZMD8;@;;8FIF ;6T,6'T' V,'';;))^K5,,v',\*2,H"[U,4G-T;/'V3NMm0# '5''Y'K$!;O#'+/'w7V@ $z(*N |;'-N;g,.hT,qQ*Q( ,MQRkh'RR(^$,,,,,,',\\\\HHHHU,U,U,U,U,XU,////80000000'''''$ #'O#'#'#'#'#'#'@@@@ ,0,0,0','','','','5'5'\'\'\'\'\'2,'2,'2,'2,'KKHHH H$H$j0$"![[[[[OOO98U,#'U,#'U,#',['wwwT;7T;7T;7T;7VVV/@/@/@/@/@/@V3z(3z(3z(=T;7VkkjHpMdT,T,I88II88J-?-;;6'sf_$(' DG`f>B D 'OU'FOJ'I''T@uK'@*B<*A/M39$FO' ''D'J'TD ''OJ''T'D'OJ'TD'J'TDTD 'U'J''TuD'J'T ''' 'OO''D'J'TJ'TOOU'D'TQ"QQRQ*Q(QQ/QQ8Q$QQ"QQRQ*Q(QQ/QQ8Q$Q'}'}'}'}QQRQ*Q(QQ/QQ8Q$QQQRQ*Q(QQ/QQ8Q$QT,T,T,T,FIF N* &hh''J't < ~7I7 '  " & / : > D ` " 9L7 &  & / 9 > D _ "( srPONIG7_]ZRIFA'w   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a}rdeixpkvjsgwl|cn?m}brsyzuv7~yw{HNqKLMzOI,KPXJvY#?+X=YKPX}Y ԰.-, ڰ +-,KRXE#Y!-,i @PX!@Y-,+X!#!zXYKRXXY#!+XFvYXYYY-, \Z-,"PX \\Y-,$PX@\\Y-, 9/- , }+XY %I# &JPXea PX8!!Ya RX8!!YY- ,+X!!Y- , Ұ +- , /+\X G#Faj X db8!!Y!Y- , 9/ GFa# #JPX#RX@8!Y#PX@e8!YY-,+X=!! ֊KRX #I UX8!!Y!!YY-,# /+\X# XKS!YX&I## I#a8!!!!Y!!!!!Y-, ڰ+-, Ұ+-, /+\X G#Faj G#F#aj` X db8!!Y!!Y-, %Jd# PX<Y-,@@BBKcKc UX RX#b #Bb #BY @RX CcB CcB ce!Y!!Y-,Cc#Cc#-KPXYF+X!YKRX!Y+\X E+D E+D E+Fv+D E >+Fv+DY+B,GGQ*I2^K7E (,&NV2p  V l l F TR< "0jRf$ !N""#$%&2&&'6'''))**+X,- .../0Z12234j556L6789:F:;V;< ?A8ABfBpC,CCDD"E8EBEFF FFFGGGGHTJUUUUUUVVVVVVWYpY|YYYYYYYYYYZZZ[[[[(\]T]`]l]x]]]]]]]]]_b```````abnbzbbcccc*c6cBcNcZcfcrc~ccdexeeeeeeeeeeeffgmJmVmbmnmzmmmn2oo$o0o–DV\z8v^P$ȣLԥ^親\Ҩbxȩԩ(4@LXdp|ĪЪܪ $0+"9%:99 &(7$9 *-3$9/901!326323254&54>54&#"2>7>54&/.#"N $@\,4,P6(@     $@ )'$8  #XDT!6<,     D2Q++3/ִ" +,"+)+4+,$9)$9" 99901747>32"&'.432"5.'&D      1  ?      T& "w gy~8z;&ewd/ET33>h22d +@dM +\2/7s33$222 +@ ++2x/^ְ_2[+[++ 2"++/"+!2f"++/ 3f+f +@ +22a `3  3O+P2K+RKO+Q3K)+-+.20-)+/3y+6?hM+ _.Y  ?`+ P.'J ._ _+?a+ _+ _+_+Y!Y +J/J.+?f<+ :J.+HJ.+PQP'+_`_+?_+ PkP'+lP'+mP'+nP'+oP'+pP'+YvY + _ #9vY 9kP'9l9m9n9o9p9HJ.9:9@':HJYkv !./PQ_`lmnop.........................@':HJYkv lmnop..............@a^c99[\9 99"9ROM9)K>Fq999089-+2990170546;25>54+"&54;256767632;2767632;2+";2+"#"54654+""54654+"7;27>54+"; R _ f bEQ_b  B$F_ b D/ dC    #3D  /. @j[h{ W+E3 }+k2W +@WP + W +  +5/f3(|/ִ\2+\a+2:+72:y +/ U33y+Ki22y +@ +a&+&a +@&. +:q+@2+}+\99 RWf$9a9yP99:H9&Gkw999q)15=>$9@095 .0@^w$9(90174>323254654'.467>54&54323"'.#"#"54654#"&'&3254654#"32>54.#"@ B $;KbC 6:  ZW!87  /f Ri  O)"#&`% - *@9 Jd    1 %=T\ `P2P0?E - T,n* = >)25;;GS_Q/W9+9/?"+]/K9+,/"+E/"+E +@ +`/ֱ<<B+1N+421H+TTZ+Na+<9B 9$919H/9T,99Z *KQ&$9N99WQ99 HNTZ$9,K4?632#"&5476?>54#"#"."#"&732654&#"4632#"&732654&#";sT 1,.%/ ޑ 5k*%9 6X#KH 0:8?nX66nK;>G -@8?XQ#"%  O( + M:9H-PAO7>M(4qUD7WQF7@K(9u;je+p+Y+Q+!P+_+e +_+=Ae +="+/ֱkk +$$ +$ +$/+H$+H/ +@H? +LH/+t "+t/L "++$ 999t57ep$9/+:_a$9QYV9_pa9+Ltv999A/Ek$9=59! $~$999017467>54.546322632#".#"32>54.'.546323232632#".#"#".732>54.'.#";=%tH#> "9/.794K   3M  w= 9('H2&&6Z6 @YC/B"AtH+S*4x> !L"I ;&BC  $+$1O<4Kx(%-   , K   6fe#LI/'f> T8vI*<!/ִ+ + ++014>32#".I*54.54632#" )::)(9:( 1?:'*;2=4>2;2+"#"&=4&+".          6Z}./ +/ֱ + + + +0174>7>32#"54>54.'.6  "Y 6  >'?g %  ,|( /33+2/+ ++6#+ . o+ ++++++++ #9999999@ .........@ ............@0175467>32#",&5R #7    "    65++++/ִ + ++01647>2#"&'6!   + '   #Ir+/+6<+  +  + #9 9  ......  ......@01477>32#"1  77  $ ' P +8++O+/ִ 2+ +$++  99990174632#"&732654&#"'[]tgqw8jFDbkFE`erqlZXQW`OJXW@=y<+<+9+22+9++"+>/ֱ.. +@.4 +@. +.?+.9:999<49 99990174>76=4654&'"543232632#"&"#"" 56<(9 ;6 [.Q  61P ">   " Q 6{2+"+.+53+ 9+  +  +7/ֱ   +  + + 8+  "2$909".9 ')$90174>7>54&#"#"&54>32327>32".#"#" "phC!; -"H0;KO2=.d*# 2RGAE C5+4. *  "'84+f)2 - _'4f;7+f+7 + +&+& +! +7& +9+7>54&#"#"54632#"545'.!;PRB    07% ) P,3O#)#&H[U a60@  B(1 5+2;& $HS$  FFY=+@3M +I22$+<>33+ +Z/ִG !+GP+;2*26 6/PR R +@ +[+GC96=@MV$9PT9R49 9M= 999G999 RTX$901746767>3232>32#"&#"#"54>54#"#"&#".7;23265654" /w    (  f#C ]&% 7  8 C( 3     K D6E 9; I/3BHvD++4+4 +@) +++I/ִ:$+: +@@ +@+ +J+:99 ;94@$9.990143232654&'.54>7632;:>7>32#"#"'./ 1#6Q'/7    /N .K M/9 I<  ! # C)UM/,2m+%9++- /"+3/ֱ (+4+(  $9 999-%$99  901467>3232>32#"'&732654.#",[%W ,>*,A9%Wfk[QG;Q./,J$D+"+r? $%n@vc_I>Df6RKDF0)D,8+ 3+ +@( +-/ֱ.+990144+"#"546563232>32#"&#"u=N$-=# +'9H  ! #  M 2v- '"1=+&9+,/52+;/ 9+>/ִ#+ 22+#)+N+8 +?+82 &,$9,&995$9; 990174>4.54632#"'.732654&#"32654&#"'''#*#gNW^*2*heO+-2,_HJJ!,%"!KI@1Q(>"  @&Md[H!6 #I-R]:K`Y5@u:<4ZK<(YB'3*m +9++(P+  +"+ +@ ++/ֱ%+,+ 9% $9( 990174632#"54327>54#"#"&732>54&#"'p]_lZY<^32#".'&47>2".'.;#         7    &  %     ;X8Z++9/4ֱ*4* +40 +*  +/3 +:+4$999 990147>32".'.4>32#"&54>54&'&;      \ V  &       ,?i +!))}74>767>32#"%.)җ$ **6 k L8  op ZJ&kb))}747%654'%&5432#"&) 7**0 k A po 8L K98h4+$+/ + + +9/ִ +2 + :+ "$4$9 (/3$9,9014632#"&54654&#"#"47>32"&'.K@(6P,4,\@$ $     d,<6!TDX#  8$')      , EU7+CP+7C +7; +S+9+S +@" ++3+ +7" + I 6+1/g+V/ִ42+4+FF.+ +W+.F@  17=CP$9S+@ &.4FP$91$9014>32#"."#"&54>323263232654&#"32>32#"&7326767654&#",A_xa*[ &$;/.?n:3 )Lbtѱ?f4 ,9Q+#6 (,1k`W9m$5,9] (ax! ;sl{ɡ! i"/JW+!CI3339+ +U +2NI +2L+X/)ִ +) +)$ +Y+)9$@9992')>$9UN99 9901747>767>32#"&#"#"&54>54&'&#"+"#"&#"#";7254&#" 0_bK    * @<2 N&5-   97 !_'b ` SԠ6O"#       9# V  Y;Xj +>'++"++ 3:"+g2aJ +a9+k/5ֱY T99a99:90143232632632#"&#"+"5432632>76=4.#";27>54.#"&"&#"3;2654&#" wF[z"0/"2",L6( 9  'b6 /=$.?# )  @??iaWP3   $G0+b  -#A<R91G$!>>%.28B48i%B,5K0+  0 + $ ++0+6/ִ +7+ 99 99014>322632#"'&#"327632#".,Tx=|%%  a}%KXb  6=^1WY<Dmk$ A*G|g< %- *D[]/Sn+3;P+2+3E,2T/#ֱ0 #0 +@# +@# +0@+ U+0#99@99E; )#9990146;2632#"&#"#"54>7>54654&54&'.;267654.#*"W$EtH,,&r<*#<!  $ #&D`,J?fv<!  *ONFgK/ * '8)$A#    '*GPM) #<Hs4[+L^+X3d9+d^ +@dS ++ +3M++3r9+ +@ +,+"BX, +"B" +@B7 +t/oֱ DG22o +@o +@ob +9+5+5/ *+*//+5++ +@ +u+o[99BL$95*,7X999 9d[`9BL2Ui999"j9, /99m99 r 99014323232632#"54&'&+";26767632#"'&'&#*&"#"32>32#"&#"#"5<>7>=4654'.'"; "Z04*<(  K% )Kq9&  V HT 0 C ,.H_2  N"*W(H+X  Fp|r @ u"[+UP+a+e"+a+++ 3t9+ +@ +1+"I[1 +"I" +@I< +v/oֱ KN22o +@W +o +@o +@oc +>+9+329/+//9+ ++w+oQ^99>)U[$99/16<999 9e[Wc99IU9fg999"6k991 3999t 99014>;232632#"54'&#"2326;267>2#"'4.'.+"32#"&#"#"5467>74546=4'."& kVGn. .-<'  H W)'! E E " /  -B "6q1   87  W(,  Y5s :B,TP+ +}+/)P +/9+62U/ִ +%+= B22=% +@=8 +%= +@%- += +/+V+%/P$9 2EH$9=9:9) :H999/258999  $9014>3232632"'.#"3267654'&'&'&5432326;2#".,W|0T6(#W}DZM}  V .3" "%mX*Imk@* Li\jF-f'    +E.  ;evw+FL}333r"+2w+A"+/+)3332"+ &222g} +/ֱm2m +@m +@mu +m +@ +@ +m"+X25 ;?225" +@5C +@51 +"5 +@"Q ++mz99"')Lw$95,I99AwQu999grT?99989925999/ '9990143232632#"326323276=4.43232632#"2#"&#"#*.54>7654654654&#""&#"32#"&#"#"54>7>=4654'.#"& H#"# JX&W8' B 0T  33 '66,n # '@/ /  R+%=f6'."!+c%3 A   $% $R  2P++* N9 IL+ 59+$39+(2+89++ :/3ְ.23 +@ +@ +3 +@3 +@3& +;+3!99&9 /98 9901432326323232#"&#"#"54>7>=46=4'.'& H#".  3B- / fT%  /9 I.,@1x0+3+0 + ++9+!+%2/ְ 2' ' +@'# +' +@ +@ +3+'9%-99!#901463232765<5'4.54'.'&43232632#"-7 / G ".{T(("ND5 <"u@#" <.<$ rD+f3>9+]2l+q"+l++#3"+ 22++/9+Ql +QO+/vֱY 2Yv +@Y +@Yc +vY +@v +@vo ++Yvi99qlBJc999>?9QLYr999/7z{$9 9014323263232>54.'&543232372+".'&'&#"3232#"&#"#"=4>7>=4&=4'.'& H&    +| " , hKc /KR%)a  0 3B$-  h6dw    W@S /JE  #     eo I.3GA)+,+&31"+1, +@1! ++ 3?9+2B/6ֱ 6 +@6 +@6. +6 6 +@ +C+6)991).9?#699 901432323263232>32#"&#"#"546327>=4654'.'"& 7FKq9&  V HI 0   ))'(H+X   Kk|r @ }+=C333w"+5G222+$39+'+3,9+/pֱL+22L +@2) +@28 +2/ R R/P3/ R/ +@RE ++6#+ f  #+ ff   +  +  +[2+  + + +gf+hf+jf+f+ #9f9h9g9j9 999@ hj fg..............@ hj fg..............@RpCHz}$9L9/ $@999w}8Ebd$9,Ta9999' !)$901432326322>7> 323232"#"&#"#"54>7654./4#"#".''&#"2#"&#"#".54>7654&'&+4 >%:$' " !"  3 :<    "  P6'A P|MN%  -H,   ,>d7  , ZUK+  18& :t(N"-% 0IdLgB+T+P9+]2++3f9+'2h/cִI+Ic +@IQ +cI +@c +@c\ +I +;$+; +@;4 + ; +@ ) +i+Ic LW$9 .EOT$9;D9PT?@99f87654&'&     T; !   $ 13 8& 06   " >uC\)"0  _&L,D_ -/  $F>VU,)"D ++ #/ֱ + $+ 99  99014632#".732>54&#",ʹVP6jqwQX2GmA"KJ:%nS2I^O"Uh(]cO3,?[5ɔR3+83/ +$ +Q9+3 +9+S/Nֱ' 'N +@'1 +N' +@N +'!+ ! +! +T+'N599! 399/3.1A999*C999$'KN$90143263232632#"&543232>54&#"6"&#"#"54>7>7654&54&#"- ~^$3?1 B 48$l_) !.t ; "'_Y1M,  ;'Cs%A;X    H>i5->:J%++% + ++H)@% +)"+)@ +@)/ +K/ִ; !+;- 2+2/-+-;E+ L+;2/9E-@ )579@H$9 %9)-2999@ 9H5 99901463232>32#".#"#"&5467654'&7327>54&#"-SM40eAG12.& " lB?., ([J'6FJ*SnS6OeT%Gs    #5ff@*,N]ۈf7E&SݔHY+1733P+-2+Vg++H9+"N +"g+Z/DֱY %2YD +@Y/ +DY +@D +@D9 +YS+ S +@  +[+YD49S1$999"*<>$9N9VA D999014;263232#"'&'&'&+"#"&#"#"54>54654&54.;2654&#"<Z'/'&62/'2w0= &/&P % UIab%[M3M" !/   N30> )-  (%=>g%'g1 N=>f'G;>9+ ++?/ֱ..++/+. +6%6 +!+@+9.9  &29$9%9+ !#6$99901743232654.'&5463232632#"'.'&#"#"&'.;  &,E)K[>P{'[g&i    6M>T1RZe!(cET, $ #3!O@/?-6]Yk!y$ 0<3-: 2#+@i{V8+>3+$M++ 3#5+L2$ +@$U +W/ֱH+( .2(H +@(5 +H( +@H@ +(+X+H >99(;9938999$8C999#901474>323272632#"'.+"#"&#"#"54>7676'4&#"#" X   ()    R I% #" 6FG   D(?  F -1++    X"   WJ+:+3 9+:+433X/Oֱ O +@ +O +@O +'+D$+D' +@D< +'D +@'2 +Y+O9' 7J999 *>DSV$9:0<$9014>23263232>54&54.'.543232632#"'.=4654&'& F !)#.>& $?%/ B6+  AA5ZF70  /(/ =#3$  +>`=ej#(0   -+]@3'^ 7)Dn6++#3+C9+-2"+"+E/ִ+ ++ + +F++09C6>$9 9 +999014323263232654.5463232632#"'&'.'.&C& -  9Tif(-   M&> %  88}= 68 EASB4 C+31+Ll333-"+HUg2221+,9+2/!ֱ^^F+U +FU +FJ +Ud+t +dt +di ++^!9?C$9UFVa$9d9t}9,@ =CFVadw$9-;91 /9Jt$901432326323267>54'."&#.54323263232654.5432;27323254.5463232632#".'.#" #".'&'.A'8 +8 8 x  B&2@0 ' &  z  (-  M_L ! s D#:A," 8<}>?(-|7    F:'  3DF    ֭:;N689M07a+Hg33]"+Kl22A+f3+$39+2+!"+,2/{ֱ3 鱈+3{UW999]A=Dj999.;MU$9! 99 9901463232632327>54.543232632#"&#"#"54654.'&#"32#"&#"".54>7>7>4.'&'".& _6Q  (!*8#("$/+\&!U- 3 ; S*6  K <   !*4P8  N{  9-1"  .3C>  0-V##oD   )/7 %H'v mO+U3F9+[22+3'9+l2n/aֱ> >a +@>L +a> +@aW +>"+4 +"4 +"* +o+>aR99":7>54'.543232323232#"&#"#"54>7654>54.'.#"'+ ;2* "I   "  A1  )   M#: *?2   &6b6"    0: U!&    0XR*'\,/1 DX@+++@ ++2 ++ E/ִ# +F+#!6=999+@9 &$9#90174>7664#"#"546547>32326323267>32#"#"#"(p+# /  7b9?M(+'eZ$   8W(M<"2#(*     2 N42^/+%+ 3/ְ2$+!22 +@ ++24+9%/+999 990145<&546;26232#"+"32632+"&5N+  N M Z RKh,L+  'F)OyJa2 )I+/+01432#"&'& 3 !    ?$4,[*+2+-/ֲ 222$$+2$ + +2.+$%9*%9!#9901430;254&45454'4+"&543632+"& \ Z d +B  'F)OJ`2 )=K'h,L+ A% 11!+L+! +@! +/22/+ +3+014767>32+".'."+" *)   K%G!!E&      "  %+ +/ + ++014>;2+"&  &  mP0?V3+<+C3+09+"+9+" + +W/ְ2@ 22@I+ 2+(2+I +@+1 +X+60+  RM +  RMRO +  + +  + + +   + O+  +RNRM+PRO+QRO+ROQRM+ #9 9NRM9 999PRO9@ O MNPQR...............@ O MNPQR...............@@9I"8<$9+6799C(68L$90174>7>7>54654&#"#".54632#"."#"'&732>=4#"#0=B/ BHi?+<?%)@N(%%  , U$1 #93< 'G)HS%+    <")!H   3A+&37P+++=8+2= +f+B/.ֱA 2A+ +/+A +@+ +@+( +A:+C+.+&99A#999:99=7+#$99929 9 9014>3>3232>32#".#"#"546546=4+"32654&#" , B1Yc1*:Q)F#  - OCEI_Q/!    'r$|S/{ ,@ C!=[P]CO|'(I&+M+& + ++}+ + +)/ֱ*+90174632#".'.#"32>32#"&'g#Q  K$cH#'Ns r'!?M=+13CM+++K"++#+C=+N/ֱ@@G+32$G$ +@G +$  /$ +@$ +@$, +O+G@=$9 9$/1999C=*6:999K%8$9"901746323254654.+"&=4>7>32#"54&54#"#"&7326574'&#"'rU  =     M*  9!]hH]I-JJ-AJa$L/ $ 2gD.   {L.!=,_' .~+!+ + ++,"+#  +#"+&2//ֱ !2)+ !+20+) $9  99 9# 90174632+"32632#"&732632654&#"'qZ*E& )eD*W (32#".#"327>32#"#"32#".#"#"54>54.54&#"#" 0GH + 6* !  (1 $   I{I(  *GI)    :N`   AS '1AS_ >+E"+ ++]"+#+N6> +N+W,> +W"+`/ֱT B2+3T+ + /3+TZ+) H)Z+9@+)"+a+B 993999T 9Z@ ,/67>EMQ$9H9)'9"9 9NE9B9996Q999, 399W /99]')$9 "9014>54.54>54.54>32327>32#"&#";2#".732654.+"/"32654&#"'$$ #I2'8+   `E/>H.vN-N?:J6>c& .- $)9+-080%4a% ,5$/3 *7  @X "X1:4/,-3( (b763232>32#"&#"#"546265464654&#"#'.#"#".547>554'&#"#'"' ? %A%QZ 6C AJ2/2  58    &Z}+%%`e    H9=Y'?l-7   #"U1$5@~+9+ +>/9+A/.ֱ. +@ +. +@. +@.& +..6+< +B+. !9>$9< 9&99 (9014>7>32#"&#"&#"#"54727676=4.'&74632#"&$    *(   9#& } " "  X" +) '*8&lD{k+Ge33o"+B2 ++{P++&+&+,+1"+Re, +"+|/uֱUX22u +@c +u +@um +!+. +.! +@.D +!. +!$ +}+u h999!:<=PRe$9.7GJ999okc9RPar9991!=99&$9{,9x90147>763232>7654&54323>322#"&#".'&#"#"&#"#"547>54654#"#1     -  ,]  & / [A7\$3$4 CL % /  %5Y5""*p     $o9 9+@  0   "1 K:}+$3"+*2 ++9"+;/.ֱ1522. +@ +@ +. +@. +@.' +<+. !999999  9014>7>3232#"&#"#"&54>7>54&=454.#" " '# K/     'E(8/A J%?E%'a+/5[333f9+(22+2^33"+V2 ++3wJ2  /"+/ֱ|2| +@| +|o+RNP22Ro +@RY +oR +@oc +R<+% %< +@%- +<% +@<7 ++| 999o a$9R^99<5J[$9%29a-7c$9f@ $9FNt$9w99 9014>7>3232>3232>322#"&#"#"54676=4&4&4.'&#"#"&#"#"5467>5<6454&4&54&#"#"&#"#"54>7654&54&&0   /%6#A$IO  !1 / *  12+ 2 N 60*B 74    ))V@;D -(  #"3+  9-U.+    /%5@,$& |R;`J+)3E"+%422J+T"+++< /_"+a/ZֱAAZ +@AH +ZA +@Z +@ZR +Z A8+""8 +@"' +8" +@81 +b+AZO9 M98/7>;232>7632#"&#"#"54>?6=4&#"2#"&#""54>76=4&+" ) -*(@$(2 ; L5+@ $  ++       %0( >1&  763232>32#".#"#"&#"#"&54>=46=4&#"&32654&#"4' !2Y^ub.9 -"4.)  //AEUH3(  #uT]n   +)vS7  #2 _EOx 7'5"@R=+EL+5+!+P+!+."+++N"+S/ֱAAK+32K +@ +@ +K +@K, +T+KA*8=$9 %999.!99=399E69N :8$90174>322632#"&"#"#"54>7>54654#"#"'&732>=4&#"'.JT,    ) ?'I0IF;F ,8Y./4"Af; @ %TCQV ) 2=.(=@Z"'(=&NiM1+73-"++++ + +L +L9+N/@ֱ! 2!@ +@!/ +@! +@@ +@@; +O+!@99-1=9E99LI99 9014>763232>32#"&"#"&#"#".547>54&4&54.#"&#"I  0)6/  : D   | () ,h$-  '@AT  7cC7+ P++%9+D/ִ+ (N+( +( + +3E+7>32;2>32#".#'"32632#"=4>54&#"#"  !  #    !53 E(    - &  /  A3 +u'pQ +F5+<+M+*22"++3+3EP+2G/>ֱ 2> +@> + +72'' +@ +H+ >99<9'59992/:99E #8>$9!99014>326323265'4&'&543263232632#"#"&#"#"54654&+" 0D+@:X  Q $C(  $a;XI6D2#  0454&'.543232632#32654.54;2#"#"#"&'&'.'. 8$   D ;  Us ! ;( ^z&"  $~;E   . %8"   !1UAcS1Z+}33T"+w2>+3D"+>+/ְ2t +t +@ty +t +HH3+A +A3 +@AX +3A +36 ++t$9  !$}$9H-jm9993dg99AD[^999TZdy999$-3Hgm$9D9>!6990174>7>7654.'.'.54323263232767654&543232632#"&#"+"54654.'&#"#"&#"#"%&%..&3 ! F. ) - $s "/  ( & "#0  " $, &*7)   Y  s  &9     (: (  AKY>++$3"+I22L/ִ + +@ +M+>@J$9>.999,90143232632+"32654&&543232632#*#"54>54&'.;  +Q1 1#&Y  -.r*   _  @  g=&& 8R"O(]JB+/!+/B +/8 ++ !+!+3+  +@  +K/6+:+L+:6!#=999/B99+99  9&990174>754#"##"54>56323263232632>7632+"+"#"(  c 0` *i/1 "$   r3  &) AG  7 *F2S+3 3+3/0ֲ-222$+0 + +"2 /4+ 99014654543232632#"#".#"#"54654&*% ( (I' "!& # ?J  22+?   C7N/!-"/!ִ$+2 $+22#+! 90145<&54632#"&5N   WKh,L+  'F)OtJa2&) F2Q+33+3+3/ ִ%$+22 % + +2 " 4+90147>54654.54#"&543232632#"&#"#" " 'I( ( %# &! ?+22  J?7C _ D/6+ + ++7+ + +/+ ++0174>3232>32#"&"#"4G#:A( +.-),.;5d4+(+/+6/ִ1 +*1+&+&/*+7+&$9*4$91  $9014>7>2#"'.4>767632#";        .g        "w gy'L_G+S +G+/d+/@+/@ +/3 ++Z9+Z +@ ++&G +`/ֱMMB+ U22>+(22>B +@>5 +a+BMGZ$9> 9S/,9&MW$9017467>264=4322#".'&#"32>32#"=4.".'.7325474&#"'SF      " #(  #>fr`+S+e+j"+S` +SX +bS`+M+%+2P+2% +2+ +q` /P+:C`% +3:2s/ְ2g+g"+55 +I@+I +@I> +I(+t+g995"@ ejnq$9 b99I:CEQ$9(%2;BS`$9bj9Sgn99qQ99C I99:92"9901646323>54'."#"#"54;254.54632#".'.#";2+"32>32#"'"#"732>7.#"!#%03 :xRBe 9 0C0 z  >M/G !8"Ru>&  (,# -0r +E bc<,"' 99@m  '<  %  ",&,&7   /8+7L+7 +@ +2 ++)/37+)3 +@)- +$29/ִ12+1 +@ + 215+2+5 +@" +2:+6-O+ +ұ+ & -O+ +ұ+ &+ +-O+ ++++ұ+  +'&+-O+ +*++ #99*+99 #99&9'9@ &'*+................@ &'*+................@0174?&547'&4632627632#"/"'#"&264&#"D'&C E45E E))E E43E hRrSS9:, E3DB3EE''E F8>=7FE'&DvQStUp+v3l9+i2+,39+'2v +O3Z2v +E3A22/ְ2` L2` +@`V +C2@`n +` +@ +2@x +`%+4 +%4 +%* ++`s9%?HO]p$947AEZ$9lp{99V97?$9 *499901463232632;2>7>54.5432323232#"#"326;2#"#"3232#"&#"#"54>7657654&+"&#"54;2654&54&'"&+"&54>3254/.#"'+ ;2*  G     A1  .Y pJ>  } Z  @ W * ?4 c  d [b0   &2a!"  %/ 1/    !'   1%$   ) ,.N/ =!/ְ2$+$+2 + /"+ $90146465432#"&4632#"&N     *g p+"&[D &;K[k+!P+>/Rl/ְD2\2+I+ $@+\U+e2;2+,2+m+I FH99\ J99$9U@ (35>RXbi$9799!R ;DFbi$999014>54.5463232632#".'&#"#"&'.543232654.732654'&#"; nR0M  (!"29;/YJJ+  |_-B-  %?\6Q^Q6<:\N0pa F 5 3!I_ )3@") +E- .  !Xf  ;6,&)HO0" $A#F,%8gfW, N +I/7P+7I +7= +//9+/ +@/+ +/O/ִ + +22 +2) +2;+?+?++P+;2 I$9/7 !$9014632#"&7 654&#"463232632#"'&#"327>32#".,ϕΗ*N}p&M- ;nK`)T9P8    '$&?_4 OӒϕf" k\K#CD*O :  "/?/ 9K<+&)3389+-21+ /"+  +  +L/ְ22+:22@+2#$+ 2M+9@ 48=4#"#>B '+  E+#. N;. ! 1 "#  + ,4#  J+( TA]&m(3/ + +/ ִ+  + ++01743!2#".=4#!"&     o ,|( Q Xh+9+,+/@N3334"+DJ22+ + /9+9_4 +9"+e4 +W3"+i/ִ + Q+=+Y2=Q +@=B +Q= +@QL +=b+!+!++j+Q IW$9=G9b@ %8$D$9!&)99,14999,41BGL$99 &8R$9_%$99e!U999014632#"&732654&#"74;2632;632#"'.'#"#"&#""54676=4654#"3232654&#"zVX||XVzjHJkkJHj@ ,%2    8$0  N ! "X{{XV{{VHiiHJjj& !  _  #87!q3E`T H+ //ִ++++ 9999014632#".732>54#";+02K,*!9&c#CG$/7&"=#I 5b /3/%323 +@3+ +3 +@ +6/.ְ2)+2). +@) +!2.) +@. +27+0164>3!2#!"'4;2=432;2+"#"&=4&+"&  U      O        */>()8 IQ 9L=+/3!+'2= +* +K+ +3M/ִE+E +EH +E+ 2+ 1+$$1 +$, +N+E=99 99189$4699=KE99A9 168$9014>767>3232>7>23232632#"54654#"#".#"#" '   %2"_H > & T5(2!F -^(U_2 $pA    H$ZHx#J$!,'6=,G4Ib+E"+_22/%9+% +% +//7"+c/ִ5 +5'+:>C222S+Ja22SY++Y +@ +d+5#999'%/999Y9/%OUY$9E7JM\$90146326326;2#"'&546323254654&54#".7326=4&54>54#"32>5454&#",~IV ( iV(  B#6D,0 c \v 2eew21%G. #J.; )/]dcst(! *X1oa^ M ./++ /ִ + + +014632#"&M"& M *) '+7^R?N L /"+/"+/ִ 2+ +2++  9999014632#"&732654&#"RH=Z]?FO;B*"/9418SI?AIPL=H204Q0'TT]0J/.3 + 21/ֱ + +2+(( + +22+0174654&54632#"&74654&54632#"&'_] *4004* ^] 3&65# fij %.) )-$ fk - !.. R'L@'Q?Ro'J@'Q?('L@'QA$7o+ + + +0/%+8/ֱ +2 +9+9!$06$9  %.$9 90174>54&543232632#"&4>2#"./.$,5,[@$?(6QH  #  i!TDX#  8$') ,<(     'Pq$'Qq$'Rq$'Sq$'Wq$'Xq$e+Wh+b333n9+2h+nh +@n] ++&鰣2+#8++ 9+& +@& +8+yb8 +8+-Mb8 +-M- +@MC +/ִ + + ++s2+ R2+ +@l ++A+;26+6/A ++ +@ ++9 hnv$9+Oe996(EMW$9A8>Cb$9 9nej$9W9y_99EAO999M>9998-+;99#999& 9 9901747676767654'&543232632#"54&'&+";267>32#"&'&#*&"#"32>32#"&#"#"5<>7>=4"&+"&#"#"&#"#";26=4.#"|TJ4b #Z/4+;(    L% )Y|6  W HU  ;2'  ##:7 [4  {  C ,-6q2   N"AW(H+X  Ep  +  S  7,7'^&H'P(H'Q(H'R(H'W(5'Pq,5'Qq,5'Rq,5'Wq,=t+3IP+2+3S:21) +j31b2u/4ְ#2_ >2_4 +@_f +4_ +@4 +@4 +_N+ v+_499NDn$9)I#91N9S 7990146;2632#"&#"#"54>7>54654&+"5432632654&54&'.;267654.#*;2#"+""W$EtH,,&r<*#< A  !  # #&D`,J?fv<!  j  *ONFgK/ * '.!  9#    '*GPM) # g ('Si1,)'P~2,)'Q~2,)'R~2,)'S~2,)'W~2X#/h /+  +@ % +2 +@ + 20/,ֱ, +@ +2, +@,' +21+6-T+ ) #Ҭ+ .+-T+ ) ) +##+ҿҿ+ +..+-Aҿ+ #"#+)*) +Ҭ+ .-.+*) 9 9"#99 #99-.99@ "#)*-.................@ "#)*-.................@01463232?632#"/&#""&54?64/&Xt } y|{ x x |fs } y| | xy |,)(6F\L +J J +@ ) +&++DD +@ ++]/ֱ7 7P+ ^+69+ - '- - +''+&'+9+ -.- +>- +?- +'W'+X'+Y'+['+.- #9>9?9 9['9Y9W9X9@ '-.>?Y[WX............@ &'-.>?Y[WX..............@P7 +3G$9J 09D354.#",ʹ!6     E[jq!8  ,.EIX%(  _(nZ-"KJ:%*-3^XS> B se,U] b 1.^F9 . ,?[5CuF3*m˻ 'P8'Q8'R8'W8'QR<\>+D3:g++[9++ &> +9+.> +P+2]/Vֱ 12V +@ +@< +V +@V +@VF +++ + ++! +^+VA99+:>$9:>9L99M9&!9.#2P$9[ 99014323263232632#"&543232>54&#""6#"&#"#"54>767654>=4'.'& H#".F^$3@1 B  58$m_  t < /;`Y1M,  ;'Cs(CF,8  &M] .,ph+&o33d"+@22$+B"++T"+q/ְ 2aZ]22a +@af +a +@ +@ +a1+K29+$+9Q+@+E+$+r+al91j99+T99I99QHN999$B99E9Bh9d,+99T 8E5$90174>54&54654632#".'.=463232654.54>54&#"#".#"#"!!oY)+&-&@L@/$     .*<<*!"-!" @$  3%)J,59+J, ' W     ''/&);"0,.@$15 "8cA2GG=F(  0'PD0'QD0'RD0'SD0'WD0'XD0RbpD+9!+O+V9D +9< ++)39+n2 + +e3D +e"+h2q/ְ2S 22S +\2c 62ck+/ !+>2r+S9 O999c#%IK$9k39D)$9/1<9939IKS_$9e/9 #%ck$90174>767>54.#"#".5463232>32+"32632#"."#"'&732>=4#732632654&#"0"+J"+L"+4( #959 9>"9L9J999@ 45>JL (...........@"45>JL )..............@/-98$,A$9H99D 99A,9;2H$990174632327>32#".#""&54654'.727>54&#"32654.#"'}o,  ia,' 46O!44Qg49Ng&\ 1 599YS$AB$999IE9999  9014>7>3232>32#".#"#"&#"#"54>=46=454.#"32654&#" " $G,Y_ub.9 - "4.)# //ADTH4(   'E(8g ##uT]n   +)vD+%  #2 _EOx 6A'W\h'Tq$0`'TD~'Uq$0v'UD4[h+TZ339+4+.+&&. +&) + +f +@`Z +@L+i/ִL +L +LQ +L1+#N+#6+ +j+L91B\f$9#9767>32#"&#"32632#"&54>54&'4'&'"#""#"&#"#";7254&#"+RiX    * @ **;-*")"3B!U   97 !_'b ` I6O"# .#+ ,;#6) 9# V  04UlR+Y1+I+@+9g+9@ +9< +"+9+" + +m/ְ2V 22V_+ 2+(26+_+C2+C/62+6C +6= +G+_+1 n+60+  hc +  hche +  + +  + + +   + O+  +hdhc+fhe+ghe+heghc+ #9 9dhc9 999fhe9@ e cdfgh...............@ e cdfgh...............@V9C"RY$9_N96LM99GJ91+&399R96C99Y09(/LNb$90174>7>7>54654&#"#".546323262#"&546?4'."#"'&732>=4#"#0=B/ BHi?+<& M 9%(: )@N(%%  , U$1 #93< 'G)HS%+  7$ ,2#$4   <")!H  ,'Q&''QF,'R&''RF,'V&''VF,'Z&''ZF'ZR''v'\ G'!ZhX+L3^M+,++f"+>+^X+ f, +43 92i/ֱ[[b+N2?12b? +@b +@b# +?/3? +@?7 +@?. +j+b[ X$9'9?*JL999^XEQU999f@S$9 =9,19017463232=4.+"&54>326;2654&+"&=4>7>32;2+"#"54&54#"#"&7326574'&#"'rU Z ! =    -   M*  9!]hH]I-JJ-AJa$H 2.   gD.   {L.!=,_Hh'T('`'THH~'U('v'UHH'V(''VH4HW~+L鲁+9+^+p+hhp +hk +++ 39+ +@ +-+"B- +"B" +@B8 +/ֱ DG22 +@ +@ +s+eN+es +@eU +em +e6+02+++/6++ +@ ++~9s=BL|$9e:{99+y96-38999^9 9hs9~9BL3SU$9"9- 09999 99014323232632#"54&'&+";267>32#"&'&#*&"#"32>3232632#"&54>?4#.#"#"5<>7>=4654'.'&; "Z04*<(   K% )Kq9&   " # 2%,0  8T 0 C +.H_2   N"AW(H)  %'&7#"  Fp|r @ '4;I3+936+3 +@ ++)+!!) +!$ ++G"+> 9 +>"+A2J/ֱ <2-+2+D+ !+2K+- >G$919999DA9  !&)$93!-99 99> 90174632+"3263232632#"'&546?4+#"&732632654&#"'qZ*E& )eD*W [ /%-  X{T 8406d+0^l- L . & 7- lPOH'Z(''ZH,'R*'1'RJ,~'U*'1v'UJ,'V*'1'VJ,7']*'1'[J'Rc+7'RK+OU333{"+2+J"+&+ 333)"+ 222j + +:33222/ֱ22u22 +@ +@~ ++a22/ >DH222/ +@/6 +@/L +@/( +/ +@Z ++99 U$9/#R99JZ~999j{]H999A99>99969&) 9990143232632#"326232654.43232632#";2+"2#"&#"#*.54>7654654654&#""&#"32#"&#"#"54>7>=4654+"54;25&'.#"&;22>54=4&#"#"&#* H#"# `a ' B 0  + ) T  33  EE,n # '@/  ; < / 6 -j :v  O-'.   Ic%3 A   $% $R  2P++* N9 @ &  0 07y=+Cah333:9+=+]"++2+R%R +3%v2z/ְq2(,22 +@" +@ +XX +@X_ +X +@ +2@l +O+55O +@5< +O5 +@OE +{+Xe99O%.2CR]$95@9]=E_n999:G\H999R,5.Jo$9209%)99014>;254&#"#'"54>7632;2+"32>32#"&#"#"546265464654&#"#'.#"#".547>554&+"& +  ' ?  %A%QZ 6C AJ2/2  58   )- ="   ;1  1A%%`e    H9=Y'?l-7   #"UP  5'Sq,'SX5h'Tq,`'TX5~'Uq, v'UX45R+:=339+A20+(g+(0 +(+ ++Q9++ S/LְG2L +@ +@ +L +@L +@L? +L3 %T+L3;E99%6899099(%399?9 H9Q 9901432326323232#".#"32632#"&54>54##"54>7>=46=4'.'& H#".  .&. - / fT% B,  6#, /9 I.,$4MX+79339+-+%g+%- +%( + +V/Q+Y/FֱF +@ +F +@F +@F> +"F+02+0/"2+FFN+T +Z+N099 @9014>7>32#".#"32632#"&54>54+"#"54727676=4.'&74632#"&$  !.%- (   9#& } " " ',  :#*  X" +) '*5'Vq,$3Z+9+ +4/,ֱ, +@ +, +@, +@,$ +5+, 999$9 &9014>7>32#"&#"&#"#"54727676=4.'&$  *(   } " "   X"@W'-H,$8'ML@'Rn-8'RgD7']L.7']NG'Q}/'Q]*O7G'] /7']aOG'\p /C'\ OG'y:/'yOGd7<+-?+93D"+D? +@D4 ++ 3b9+2e/IְZ2) &22)I +@) +I) +@I +@IU +f+6Ĥ+ WP #WW+W+W+W+P$P#+OP#+WXW+XW #99999$P#9O9@ #$OPWX...........@ #$OPWX...........@)I<99D<A9b-6L999 901432323263232>?63232>32#"&#"#"546327>=4+##"&54?654654'.'"& 7F Kq9&  V HI . < 0   ))RzW  `(H+X   Kk6   r @ Z=+C38"+I2++"+*+[/Mֲ PT2224!24M +@4 +@4; +M4 +@M +2@MF +\+4M@999*8%'999 !"999990147>7>5654.#"54>7>323263232#"&#"#"&54>7>54&=4654"#"&    " . 0 '# K/  +P   I%  'E(89    J O'Qi1;'Q Q7']w17;']Q'Zi1;'Z Q8%'Qv,)h'T~2'`'TR,)~'U~2'v'UR,)'Y~2''YR,`zZ+L]+gZL +@ZW ++ +8++x +@ +++ @]+ + @ +@@6 +{/ֱa am+H A2 B2H4+.2)+)/4++ +@ +|+ma]x999HZ9)8@L$94+16W$9 9@L1RTm$9 n99+.a999qs999x 901463232632#"54&'&+";267>32#"&'&#*&"#"32>32#"&#"#".732>76=465<.'&#",ʹ #Z04+<(    L% )Y|6  W H/y-wQX2GmA%,  ;AnSC ,-6q2   N"AWH+X h(]cO3 $|r 2*'4:FT*+!+8+>9+>8 +@>" ++3D"+R2I* +I"+L2U/ֱ; ;A+ G2O+ !+$2V+A;899 02$9O*$9"99$02;A$9I9D GO$9017463237>7632+"32632#"'.##"&732654&#"32632654&#"'}orC-;*E& )eD*X (57i']U'Z5i'ZU;'Q 67c'QV;'R 67c'RV;7'^677c'^V;'Z 67c'ZV7'^977F'^W'Z;7F'\WwG+M3+$M++ 3#5+m2$ +@$v +e[G +93e02x/ֱW+h2< '2323272632#"'.+";2"#"#"&#"#"54>767674&+'"#"54326326='4&#"#" X   ()   $]    R I% #" DE#S6FG   D(?  F -1M  +    = $   FbW+Od+OW +OR +$+ M+.522$+6$6 +@$ +)+.+aW +>3aF2c/Yֲ ]222L9;22LY +@LB +YL +@Y +d+LY9aO]9B9  999$6990174;25457654&#"#"54>7>32;2>32#".#'";2#"#"32632#"=4654&+"      !  #     v ?) !53 E( "   - &  /  D   !A3 +u - 'S8+'SXh'T8+`'TX~'U8+v'UX'X8+'XX'Y8+'YX4qb+K+Y+Qg+QY +QT +:+3 9+:+433r/iֱ i +@ +i +@i +\+NN'+D$+D' +@D< +'D +@'2 +s+i9\ 99N`b999'7JVY$9bQN\99 *>Dmp$9:0<$9014>23263232>54&54.'.54323263232632#"&546?4".'.=4654&'& F !)#.>& $?%/ B6+  AA) /&. 7=D70  /(/ =#3$  +>`=ej#(0   -+]",  6#-  3'^ 7)4+a W+M+*229+O+I+Ag+AI +AD ++3+3`P+2b/Yֱ 2Y +@Y + +P2'' +@ +>'+L2+L/>2+>+> +@>/ +c+ Y99LW99SU999'>;AI$9WA>L992/PU999` #STY$9!99014>326323265'4&'&543263232632#"32632#"&54>54."#"54654&+" 0D+@:X     .%-$C(  $a;XI6D2#  054.54&547>54>32#".#"32#".#"#"& 0GH   (0 /   2|e I{I(  0T8! MG"+*M+*" +*' ++/362F/F + +H/ֱ+>2+?2> +@ +I+6?z+ . ?A  + + + +A@A?+  #9 9 99@A?9@  ?@A......... @A.......@F99>99<9499*901463232>74#"#"&546332767632#"&#"32632#"&#"#"+  ( #  2?9E/ 2!B 93  8%$ 70/G   [P$+)  ͹+4 ;7']677c']V7']977F']W8%[!+f+! + ++!+"+&/ֱ +@ +'+99990143232654.#"54>7632#"54& (  /, ?1'+C^0    "7bR[J\kMRkMZjWvUV'XH nSpYK' / +/ֱ + +014632#"'&*   b| "8  g% / +/+  + +014>7632#"' )a H6 f+/ + + +2/+ +0147>;2#"&#"#"  , ]Y -  C&UT @/4+ + ++4+ + +/+ +01463232632#"&#"#"CL) BO Y08 T03`(//+ +990143232#"&#"MC%   M v+/3+ +@ + 2/+ +014;232>;2"& +$* ?p>s##(VSW */++ /ִ + +014632#"&#' N +) '* 7/3+ 2+/ִ + + +014632#"64632#"## A%# %'%# %p H /}+/}+/ִ + ++  9999014632#".732>54#". %%9% &%O36$)! )Q /3 +2/ִ ++ / +@ + 9 9014>7632#"74>7632#"' )a ' )a H6 f H6 f+/+ + +2/+ +0143232632+"'. \Z   ,UT-  C&W-/ +/ִ $+ +  + +014632#".?  1+4    JY- / +/ִ $+ + + +0147>32#"54>54. @   +5  7Y\7Y\,|( ,|(   /L+L+ /+015463!2#!"& ,    /L+L+ /+015463!2#!"& G    /L+L+ /+015463!2#!"& S  sI0++/ֱ  +  +  ++014>32#".I'!   *H' &% 780++/ֱ  + + ++0147>32#"&54>54.8  &! (*H' &%8./+/ֱ  + + ++01747>32#"&54>54.8  &! (*H( &%I0++/ֱ + + ++014>32#".I    $)Y( %-)EI&ou8&pv8&qwJ9R+!3+52:/ֱ + + ++-- +-' +-2 +;+014>32#".74>32#".J   !'   !'Y%7    & 'H*%7    & 'H?)O+N+3+2JN+"3"+2%+G+P/ֱ9H22+$222B+ 3* +2Q+B;J9999*"99JN$H999999 99014323254.543232>32#".#"+"&54.54>54#"#"?+N- <=$P5&P5      ,M-=$M54Q%<=)@$  $Khk ^eO+  #D,;3lh+X39+L2hk+T3+P2`+(++83+42+<3 "+02+?+m/fֲ"222Z+.>J222ZZ*+& +&/ b33* +D2^ +n+f& h$9Z(`99*,099 699".99(&,990174323254.54>54#"#"54323254.543232>32#".#"32>32#'.#"#"57>54#"#";;R  #M5-O* <=%P4-Q-  'P3O'@?  ?=rF +R4<=+N-.Q,<=2P-*8S,@?  -M+S' /;h./++/ִ + + +01462";X|YX| |XX|Y6''T]+/+/ֱ   + +2+0164767632#"'&')23 ]] 32) #+, gg -,#'T]+/ +/ֱ + +2+0174654&54632#"&'^] 3&)23 ffh - !#+, sh/ִ ++014>74>32#"Ovy  ޑH|   O( !2\)///302/32/3/1ִ$+4+1.99/%'99 990173>32.#"!!!!32>?3#".'#735B_Z*:Z-"O.;rU;v:$?,K]#FH<R7#/F\ $*2 $FM/ 0HK ==*O50' //+0174>3!2#!". U   |+MQ33"+C2|+Y9++"2+" +" ++++)$3b522/+/ְ 2l鰐2l +@ls +l +@ +@~ +l_+[22<'2<_ +@<1 +@32326546763232>32#"&#";2>32#"#"2#".#"#"54>54>54&#"#"#".#"#"54>3654&454&+"73232654>54&#"  6/CO2# +u+Mk]i99D 32#".'&#";2632#"&#"#"54>=4&#'"#".#"#"54>546454&#"&#"&!/LU+#  +&"  K   46" &$, @  JvE$ :  (mA %f/-  +;Hc <]//(  BS  u]+Y"+2g22'+-d33++:f++A3tF+K4+q2v/kֱ o22S?N22Sk +@SH +@S[ +kS +@k +@kf +S5+5 +@$ +5 +@50 +w+Ska95-:_$9*999Y'$9t"5Nho$9:F8?$99901463232654>3232632#"&#"#"&54>54654#"3272632+"#".#"#"5467654&454#"#"0HJ"   F' I!#D"Z  % N#  IyE&$B(8/A "   ZG;" 'Ie"'  ']222+7b333"++M+ + + +++ 33yP+J22y +@' +)+/ְ2鰞2 +@ + +@ +@ +u+2U2Uu +@U_ +uU +@uj +UC+--C +@-2 +C- +@C< ++9u h$9Ue99C $:Jb$9-'7$9lh99?o99yBXp9999 990146;26546322>32#".'&#";2632632#"&#"#"547676=4654&#"#"#"#"&#"#"54?>54.#"#"#".#"#"54>54&+7;254>54&#"  ^5&C$M %%x)&M )  ,6  .!= )  (!X <(0 *  ) OT FAR{ r!""5  52#$   c(+^+-%    uG8$   4 4O(0db+/333^9+2b+9"+++Ad+ +++H33{g+SVx~$2{ +N +/ְ2鰤2 +@ + +@ +@ +p+2ZF2Zp +@ZP +@Za +pZ +@pk +Z?+? +@, +? +@?7 ++9p i~$9Zf99? 5d$929999b,`k$9^mn999{:<&Zu$9#99 ?F$9A 999 90146;265463232>763232632#"&#"#"54>54&54#"32632632#"&#"#".#"#"54>=4&54&#"#"#"#"&"#"#"5472>54+"7;2>54>54&#"  f&L?5   '  J(D"!5 # +! .  &/ @ = , A*2 (   **zL"G  g(1$A(84&o     Z0A ' _  U(t%u0 88   8 1Q-,E';wu+;3kd+32ku +k6 +ko ++*F2+a* +# +M+U+Q/9+x/ִfN+f +fq +f + 2T+T +@TY +T=+A200= +@0% +0 O+O/+2O +@OK +y+ fa99=TFMQ^$9*kAIY[^f$9aK9W9Q N99901746763225'&54>32;2632#&#"32632#"=4654.#"#"54>4&#"#".'&#"32>32#"&'=.5H>./7" I2!62 C( 00-"5K3W ))J/";" 3Y(^q6e#) ' 2  +C4 +u*  %40,&$ # , D0@<' &&p8mR+[+_"+M2++.+8f+8. +85 + +L+ + ++3D9+Ahk222#+3n/ֱae22FHK222 +@P + +@ +@] +<+*o+Vc99< .3T$9*#999_R]9D*)99&99014323254>32#".'&#";2632#"54&543232>54.#"&#"#".#"#".54>546454&#"&#"&!/LU+#  +&"." 32#".#";2632632#"54&5432326546=4&#"#"#"&#"#"54>=4.#"#"#".#"#"54>54&+73263254>54&#"  ^5&C$M +x)&M ) ?1' ' A!W& (!X <(0 2  )_) FAR{ r!""1 97bR+K~g'v'(+^+-%  "10  $   4 4O(0{(*+13P+b+i3b+^"+l2+ +>f++E3wg+P2"+8+w +K +/pֱt22XCS22Xp +@XM +@X` +pX +@p +@pk +X<+|26 6/6 +@63 ++%鱋+Xpf96 >d999<19.999"*999w%69.Um$9999>"323263232>32#".#"#"54654654654#"32632632+"#".#"#"5467654&454&#"#"32654&#"0HJ" ?+`a1*:Q)F#  I!# %Z  % N#  OCEI_Q/!  IyE& !;\~Q/{ ,@ C!=+G;" 'Ie"'  '76323263232>32#".#"#"546546=4&54#"32632632#"&#"#".#"#"54>=4&54&+"#"#"&"#"#"5472>54+"&7;2654>54&#"32654&#"  f&L?5   B1Yc1*:Q)F#  D"!5 # +! .  &/ @ b , A*2 '    L"G OCEI`Q/!  g(1"$|S/{ ,@ C!=H#0A ' _  U(s%u0 88   3 1R.,EEP]CO|La.+(LR$3H"+2.+%9++ +df++k3g+v2+= +q +\+/ֱ22~iy22~ +@~s +@~ +~ +@ +@ +~\+B2\ +@J +\ +@\V +\:+!!: +@!' +:! +@:0 ++~9\R 999O9999:.=HM$9!+9H.0JX$9%2G3$9@ !5:Y[{$9=99di9 90146323254>323263232>32#"&#"#"546265464654&#"#'&#"#".547>54654&54#"32632632+"#".#"#"5467654&454&#"#"0HJ" %@%QZ5C AJ2/2  439  I!# %Z  % N#   IyE&%%`e&   H9=Y'>m.6   #"U  XG;" 'Ie"'  '76323263232>32#"&#"#"546265464654&#"#'.#"#".547>554'&#"32632632#"&#"#".#"#"54>=4&54&#"#"#"#"&"#"#".5472>54+"7;2>54>54&#"  f&L?5    %A%QZ 5B AJ2$P 58  B"!5( +! .  &/ @ = , B*2 #   **zL"G  g(1/%%`e    H9=Y1#o.6   #"$M0A ( _  U(t%u0 88   8 1Q-,E,}+=A^d$3"+92>+3+ +tf+(++%{33}+2+4+(+-"+K> +K"+/ֱ22y22 +@ +@ + +@ +@ +n+N2n +@nf +nn +@\ + +* +* +@*; + * + # ++9nd t$9a999T99 34EKW^$9*->A999>\f999KEZhn$9 34$9-9#9toy99 90146;2654>323263232>7654&54326322#"&#".'&#"#"&#"#"5467654654654#"3272632+"23#".#"#"5467654&454#"#" 0HI"  - ( ' T XE7\% :B "/G!#D"Z % N#   IxF%+*p    @ l< "+$  0  *  A;" 'Ie"-+  'NQ999xrj$9YW$9%+=D$989.93$9 999 90146;265463232>76323263232>7654&54326322#"&#".'&#"#"&#"#"54>32676554&#"32632632#"&#"#".#"#"54>=4&54&#"#"#"#"&"#"#"5472>54+"7;2>54>54&#"  f&L?5    - )f & / [A7\$3$4 %L " *"!5 # +! .  &/ @ = , A*2 $  **zL"G  g(1+mT""*p     $o9 9+@ !    Vi[,A ' _  U(t%u0 88   8 1Q-,E x+T7+%+7+2"+%7 +@%( +"27%+g+ "+aix7 +aL+ai +@ad +=x7 +"+鴟x7 +/ִ+ +@ ++}2+ +@N +F +Q +++^+l+^l +^f +l+:$++99 9F$9Q@K$9x9=99^+.4T$9l!(7999aTQl99if}99=FKN$9999%:$9 +$99.9015467>232>32327>54&/"&#"#"&54654&+"54632#"&#"32632327>7>54&#"#"54632#".5463235>7>4.'.54&#";2#"&732654&#" A<- >+1 + "  C8#D) Q ( !  d6Ym +U#!''  6%/.V<-K ,#8;  0?QX5)-*0DS!+=3%"+92!+9+!+ +-J! +-g+T/ִ7 +7 +7; +U+79%!999-(799J99  Q9990174>7>767632#"&#"#"5432654&'&#*"#"#"&#"#"73263254/&#" a   V=8H D %1?  ''67  Z#> '02 Ł   Y4   $"F9FV+>9+(2!++3SP+72JC +J"+W/2ֱG:22G +@2 +@2& +GN++A+ N  X+G2!99N>C$9A9>!&9C/99J99S 9014332632#"&#"#"54>7>4546=4.#"&32654#"7;2654.#"P T 76 (L2Z2    %*.=&<% 4%" / &  / ' '. Lp=%m.r $) #"'2d.+!L+!. +!& ++9+3/ֱ  +( ++4+9! 999 99017463232632#"'.#"32>32#".'p<)  !\34$,%M2)B!!-H)Ed3c . 1 DE"EC;H3 # ,HK(/?v+32P+2+ +3399+)2@/"ֱ0"0 +@" +@" +06+ A+0"996 9992 9901430232632#"&#"#"54>765464654'"&".327654&+".#sy$$W0q= /  Gw.7{a$  og;P*#  Xn SH-5KexbQ+D9+T+X"+QD +@QN ++ 3+a9+ +@ +%;N +%P+;% +@;5 +%; +@%, +c/[ֱ@=22[@ +@[ +@[V +@(+.+32.++d+@[Q9(%7D$9.015N$9 9XQV9DY9;3IK[$9%0199.^999a9 99014323232632#".'&#"32674>32#"&'&+"32>32#"&#"#"54>54654&'" *c k/ >%  $#6P 8&n2@,  + ) 6/3 7#H  c" ^K+Q3H"+K++}++^9+ +@ +$@K +$9+@$ +@@6 +$@ +@$, +_/Zֱ!B2!Z +@!I +Z! +@Z +@ZS +!9+4+.24+ +`+!ZN99'K9994,99HKS9@3UW999$19 .Z999^\9 990143232632#"'.'&+"326327>32#"./.#"#"&#"#"54>54654. nG-J &  !    "$! S 0  ,   68 NA   O)(<:++9+'+: +'"+=/ֱ $+44$ +@41 +$4 +@$) +4$++/+>+$+:$9,999 9'4799+19 $9 9017463232632"'.#"327>54&#"54;2632#"&o)h^5?$&cE5*) ]* --d~e#  , >?"%FCXx '  C H|Ah+I3c"+?N22l+F3q9+0+ *33369+ &~222Xl +d+/yֱ]2y +@ +@f +y +@y +@yo +T+2<P99X`t9969vy$90 (-$901463232632326;2765'4=4'.5432326322"&+"54>7>=4&#"#"32+"#"&543265464654'.#"./,7 &6G"?C  !3 Q F  g>%,Z7   )   "$- &    #IZ:%:%)X M " *I9N6p+$3"++ 339+27/,ֱ, +@ +@ +, +@, +@,' +8+,!99)99390146323263232#"&#"#"&547>54654'&#"./,7  #I .  1    6  ,EBl";5u3+6+3 + ++9++'"+6/ ְ2.. +@.$ + . +@  +7+. 9+.99'9014763232>54&54&54'&#".543232632#"&   5 0,7 H#  (!B3*CBl& * mF,LAr_+Y"+e+=3i9+e+e+'+"33,P++!&(333q"+2s/mֱV2m +@ +@] +m +@m +@mg +t+mb99Y_:g99i89,2NPVm$9q9' )$9014323263232>54&54332632#"&#"#"./.#";2#"&#"#"5467>54'&#"J%`a5 "#-'F-J^2 & 1)  *  .4 2  5   < Q P]   >+^k   7 W   YD!7#+9+&+*"+&* +@& +++79+8/3ֱ3 +@ +3 +@3 +@3( +9+3#99*#(9+97 -$9 9014323263232>32#"&#"#"54>54&54654. 9' &7M 57n&5#  (_ =  .-! ^%++@MR$3!"+.2+33^9+_/[ִH+H[ +@HL +HY+Y/YH +@Y +@YT +H+`+H[OP\999(M$9!%>KT999^@  9=BEHW$9999014303232767>3232#"&#"#"4>54./4#"#"/&#"#"&"#"54>7>4&"e$18 !#> 0 /%  T# && )9  <Fbp% <\;  .41X9G<AE<)!   .3;c|>+P+M"+Z2P+V3@++%+33b9+022d/`ִH+H` +@HN +H+5+8;225 +@5. +5 +@# +e+6Ϸ+ @.D  + + + + + + +DAD@+BD@+CD@+  #9 999999BD@9C9A9@ AD BC.............@@AD BC..............@H`S99 %P9995(9MPX9b18999 9014323263232=4.'.543232632#"/.'&#"#"&#"#"54>767654#" /  |   Q&   W(|&#!$5 T   ( ! $,)   #k i0%>)   <3n'# G ++P+/ֱ + +  99990174632#"&732>54&#"'k\heUrH+? uBKXtwfR",?'V{^P9+.9+D2 +3#9+O29 +"+Q/LְI2((L +@(7 +L( +@L +@LA +( + +  +R+(L<99  999.+9I99# (L999014>3232632#"&543232>54&+"2#"&#"#"54>767654654&#" Wq C\ *B  '>6  Z4  *>=!5*#B Z'&  =A"$'?8C%+<"++++A9+D/0ִ(+92( /(>+ E+(069>"%4$999-9%"+0$9A<4999017463232632#".'&#"#"&546?4.73254&#"'ke '' -U!&$ 6'#&0 %: $SrFqJIWfsk*I2(6   %m<' 92)RG]SZPb@+E"+42 +@+"++33_9+O2T)  +T"+c/MֱQ,2QM +@Q8 +MQ +@M +@MB +QZ+ d+QM=9Z$:$9 99EF99)$1999T9_ Z999014323263232632#"'.'"'#"#"&#"#"54>7676=4#"3232654.#" :XJb"!'  A5 / aC  :  <- 5$ =B+  <4 f[ !G  #04n($1 !'f<5+ P++)"+=/ְ2,2+, +@, ++, +22+>+,9  )/5$92%99) #2$99901643232654.546322632#".'&#"#".'&'&'  C#.05LL5UD.  (-"05KK5gE   "3.")9*:F +8.&,% :*GL\;+53@9+12 +3O2O +@O[ +@O +]/ֱG+K2++G +@+3 +G+ +@G= +^+G;99+ 899@;39O"999 9014763232>2#".'"&#"#"&#"#"546762>76=4&5'4&+"#"ZFD^    )#  T K    &. _6!;  )0       )F?n   AM?++,2333L9+ 722N/EֱE +@ +E +@E ++:+: +@:4 +: +@* +O+E9,?999:/9L:E999 %499901432326323267654654.'.543232632#".546=4&'&?1 A1WL ,/  .=0S%H4 <u-4z.++"+35/ִ + +  + +$ +$ +@ +6+ 19+0999$*99.+9"*099901462;23254&5463232632#".'. /"F,(  / sN 5+  # <_#7 \ #n   EBVoi+U3+72E++330g+E+K"+p/ִ + +  + :+H +:H +:= +q+ l9:Rk99HP9iR[e9990 :OP`kl$9EK&.=$901462;232>7654/.5462;2254&543232632#"./&#"#".'. ."D.  3   &2 ("G,( /wL9&  "H"$   &5*>_)3   *m%$: F  \ 7m  GMI  RB:gQ? "8j?+Q3C"+L[228+W3?+49+?+ +3$9+2+3ig++k/cֱ**+" +" +@"6 +" + +l+*cG99(=AD$9"'8;999C8694\9i &DJ`$9$ "999014323263232654.543232632#"&#""#"54654&#"#"&#"#"54>7>7654'&'.#.W*?] 79: 9,>!$  G+,fbT5 %2 4Z2 %  %Xq  ;-I)$   w    %8 6m=ZA+G3<"+O2+3Xg+2 +"33 9+[/Sֱ77S +@7? +S7 +@SJ +7+( +( + +\+7S D999.99(*7654.5432326322#"&#"#"&54>27>=4'.#"&.F &D,   ()8M J ?   !d  p/,   ;_    24*G. 'z42+%9+2% +@2/ +%2 +@%* ++3 }+  +@  +5/ְ2 +# !+# +#, + + +6+ 99#999 9%29 #,$9 90174>754+"#"&546546323263232>32#"&+"' 1*2 -!0e"03I ;!S o &6)! t  O ~YkT+]L+Df+G2 +9++P&T +P+2.T +2"+2l/ִZ2+Z +"N+"(+A+A( +@A9 +Aa "+a/03(m+" gi$9a RT]c$9(&.3P$9A49D]I9&Aac999.;>Z$920999 "gi$90174>?4&546322632#".'&#"3254.'.546;263232632#".#"#".732>54.'.#" G0N   '4"{  < ( \)@#'="G/-H7):X#.^+  %0)!,1& ''f>  &(  $GT;q  K-& +@h`v\+ 9+y2 \ +  +K+7/nq33/ִa2+aZ+ 22O+w22OW+W/WO +@W +O< i+i/3<+92"+"i +@"* +O+G$++a99W \999Zdq999ign99OU99"<Jy999%-7?@$97 *-Ggo$90174>23254654'&5467654&54632#".'.#"#"54654#"&'&23254654#'"#"32>54.#"@   B $`E  !6  % /# gJ ;[ P65 !-##'_"&-@9 ,F`   (3  Tm "'8"Mr   * F '15,{* /?*2 5* O++2+/ִ2++$++9999901462#".732654.#"*kvl1M0 <+V6EL'T9JJCƘ&>Y^yXovVg2+#9+2 +/ +3/(ֱ( +@ +@ +& &/& +@&! +( ( +@( +4+(99 9/#99  ,990147>7>322+"#"54;256=4&#"#".6 ##!vQ  3 0c )~; %) (U B;u6+"+"6 +"+ ++ +  +  +7654&#"#"54>323:6267>32#"&#"#"&B:r3,9/!8 )A%=^L\LJ13"+ # AV [  BobX*6# JC>n  0 <<<o:+g+: + +(+3+( +# +=/ ֱ7 +7 + /+ + +% +>+ 39+79901743232>54&#"#"54>7654&#"#"54632#"&<6'.I(AL   ?5':(r4;V)@g8d/+BA?T 1E'8 $J<9";3Rw,*HW +%/A3+K2% +@%6 +% +@ +X/ֱI I?+O2'2'9 !+9/?S  Y+IE99?9U999S69' 99%9 IOU$901746>3232>32#"&#"#"&54>54+"#".7;2654>54#"*D   ,   I X d    ( S#/" "6b?E ZpADB+P+2++8  +B2+)/+) +@ +E/ִ0$+0 +0 +0+= F+09 8B$99=992 9)80999901743232654&#"#"54>7636767>32#"&#"32>32#"&A>/TjLJ&0    % 2#=  -P4/LY./]~YAW  H:  Lj!:HFrE$//f+%++ +0/ֱ (+1+(999 999+%$99 90174>3232>32#"'&732654&#"/EdsW$W.AKC.MhzXC.nS#<%97654&#"&#"#"&#"#"MQM5 5A5 0Gf   X 3) 2U  051! "6E%@ "3&5D$+*g++Bg+;+3+E/ִ'2+ '+6'>+2+- F+>6$*3$93*99;$9B 990174>54.54632#"&732654&'.#"32654.#"3&%$qO3J%##**-Z=Zw9hC8L(] G&*:+ "<=,1I)E( ='Vd33':)F*#EF+j}XiQK8"*U%8 @=7189)n +"++'!  +*/ֱ$+ ++ 999$$99!9'$9014632#"54767>54#"#"&732654&#"9v^a|_Y7x#aa!9"9\fJU=A>TD<=eqgD*0 d#wS\FNV$L7A'] 7']7;']7']7']'7f']7']'7'^'7f'^7'^0'P)'P)'Pg)'#'P)A'P)0'Q)''Q)'Q)'Qg)'Qk);'Q)'#'Q)'Q)'f'Q)A'Q)'Q)'z'Q)0'S)'Sg);'S)'#'S)A'S)0'W)'W)'Wg)'#'W)A'W)'W)0'X)A'X)0'R)''R)'R)'R)A'R)'Rg);'Re)'#'R)'f'R)A'R)V'R)'R)0'U)'U)'U)'Ug)'#'U)A'U)''V)'V)'V)'z'V)'Vg)''Z)('Z)'Z);'Z)'Z)'f'Z)'Z)'z'Z)'\" 0'T)'T)'Tg)'#'T)A'T)'#'Y)A'Y)R?+?+D9+42+O9+ + "+$? +"+-? +9+2S/Kֱ02K +@ +@7 +K +@K +@KA +)+ ) +) +T+K<99) 59$9D?79$H99-9K9O9 90146323263232632#"&43232>54&+"#"&#"#"54>7654654'&#"./,7P C]!1*C  '?6"X51   >=)< *#C0eS  -H}"(;_+3>P+2+ +33E9+52-( +W3-d+O2`/"ְ02<I2<" +@B99- 901430232632#"&#"#"54>76=4.+"&54;254654'"&".327654&+"32632#"#".#sy$$W0q= $  * /  Gw.7{a$ D  C og;P*#  qSH-5KexK (A1i+J3d"+@O22m+G3r9++9+ 22&+ 33Ym +d+m +6|33O+122/yְ2^22y +@ +@g +y +@y +@yp +U+22=.:22=U +@=) +@=C +U= +@U +@UM ++yj99U Ki$9=#J99dmCp99r?Q99Yau99w:999+99& #)$901463232632;254'.543232632";2+"2"&+"54>7>=4&#"#"32+"#"&54326546454&+"54;254&#".326;276=4&+"/,7  ""?C --3 Q F  g>%,Z7   ;7 *  &6F     TG:%)X M " *I9I ! }N+H3S9+D2 +3p2p +@p| +@p +&2H +`3&e2~/ֱi+Z2#52#i +@#- +@#F +i# +@ic +@iP ++iNU]$9# K99SNF9&2-b99p99 9014763232>2#".'"&#"3:6;2#"#"#"&#"#"546762>76=4&"&#"&46;2654&454&+"#"ZFD^    )# 3 :   T K   ? 2# &. _6!;  V         )F  &2   n^+3Q9++`33"+d{22^Q +@^[ ++g+&2+3 "+  +@ +n[ +n2H[ +2P+H2 +@HB +2H +@29 +/ִx +x +x} +xh+22M)J22hM +@hc +M5+;+@2;#+++x9h `$9M^95&2DQ$9;=>B[$9#9^c}999Qfz99nMVXx$9@9HDJ$92=>99 ;$9 90174>7>?654&543263232632#".'&#"32674>32#"&'&+"32>32#"&#""&54>=4&#*"#"#"&#"#"732632654654&"%!|9>*c k/>&  $#6P 8&n2@ ,=  48  Z  p #8  + )  6/3 7#H  C    &r 'Wh R+E9+U+[RE +@RO + + 3+fP+ +@ +&22A)+/+42/++j+^XUf999AR99)& 8E$9/126O$9 932#"&'&+"32>32#"&#"#"&732654654'.#"'k [&9c k/ >%  %#6P 8&n2.ZeUrH&,?(KXt + ) 6/3 7#H R"c^U 2+53%9+2% +@2/ +++U9+V/<ְN2"2"< +@" +@" +<" +@< +@<7 +W+67+ JC JJ+CC+BC+JKJ+KJ #99BC99BCJK........BCJK........@"<299%27:99U *,32#"&#"#"54>54&54##"&54?>54654. 9' &i |7M 57n&50 : #  (X+ 2g=  0   N ! 40^mW+3J"+2]+"+W+0+((0 +(+ ++>d] +>g+n/ִH +H +HU +H3+%N+o+H 93@_ak$9%>df$9](%3997P999J9>99d  999 k9990174>767>767632#"&#"32632#"&54>54&'&#*"#"2#"&#"#"73263254/&#"  a  Q?8 / A6;-* %1?  ,67  Z#> '02   =.+ ,;.Y4(   $"F4{Wj+m3Dj+q"+]+UU] +UX ++ 3+z9+ +@ +%;m +%P+;% +@;5 +%; +@%, +|/tֱ@=22t@ +@t +@to +@`+RR` +RZ +R(+.+32(d+K +.++}+@tj9`%D$9Rgh99d79.(015P$9 9KI9jUP`99Dqr9;3IKt$9%0199.w999z9 99014323232632#".'&#"32674>32#"&'&+"32>3232632#"&54>54.'&#"#"54>54654&'" *c k/ >%  $#6P  ("!2%,1#" <4@,  + ) 6/3 7#!&$"&2"5  c"4O!+=3"+1+)g+)1 +), ++ 3L9+2P/EֱE +@ +@ +E +@E +@E@ +E4 &Q+E4C9&8:99919!)49B99L90146323263232#"&#"32632#"&54>54#"#"&547>54654'&#"./,7  #" /&. 1    6  4)  5"!4 ,EBl"4AdN+Fg+FN +FI ++,2333c9+ 722N2+e/\ֱ\ +@ +\ +@\ +Q+CC+:+: +@:4 +: +@* +f+\9QX99CUW999,@KN$9:/9F@QX999c:\999 %499901432326323267654654.'.54323263232632#"&54>54'&'.546=4&'&?1 A1I$ kC&56>WL ,/  .z&  B+  6#* QY <u"07bVgx +j3R"+ R +  +4/a3%"+2%4 +@% +y/ִW+WM+ P22H+h2MH +@M +H9 \+\/_3339+#2Hp+?+z+W99M R999\Z999HK999v9p &.23254654'&5467654&54323#".'&#"#"54654#"&'&254654+"32>54.#""  (W?-      9"D/  &<;A_ '    'c"K*<       3@  =.F & *  6 H   &( a79 K+ 9+//ִ++++ 9999014632#".732654&#"Y@?EME'8-?0).:2,.Njw\`!7>323#"&#"#"54326765<6454#"#".R.    # ( 3  dd#N    ,t)4 $ */>5k+15+1 +@) + /O+  +  +6/ִN+ + +7+"$.99919 +9990154>7654&#"#"54>32;:6267>32#"&#"#"*#C!&*'7654&#"#"54632#"&( !47(/   %5I%5  /_@#? N'%0  ' "8$## 2HaWB8;J/23L+>2 +@( + +@ ++K/*ֱ#2#A$+A/03 2+ A +@  +A +@A +L+A*(2H$9#'9999<99901467>3232632#"&#""&54>54#*"#"#"7;2=7>54#"1     %1 AQp A dn   35   <%:A/x"H?&++7++6 = +65+0d+@/ִ.+. +. +.+:@+A+.9 6=$9 :99909&6.99990143232654&#"#"54>763>7>32#"&#"372>32#"&/$0=+*  ]   F   7!`=9 J3&2/R%   9 &$,U_~6A-c#+9+)/9+./ִ@+&+$+/+&999 999)#$99014>3232>32#"'&732654&#"-AJ8 6).*0CP:+H>0$"$2*/";e?- #TJ;@Y6R547)1M8W-,&+7+& +@&+ ++-/ִ)+)+ + +++.+)9%&99#9 9&99901467632;2?2#"&54>54&++"#"8  1. _ ! =>  7  +3)  0$-8)6/!"+'/.f+4/ "+7/ִ+ +*$+$+$+1 +8+*91 !'$9$9'!99.$94 99014>4.54632#"&732654&#"732654&#"$ !D0:2//?H8J+=% +\)'! !!"')*4=;&$ 8(1R?J1<-+*:0% &#1 |:8)o /"+/!P+'/9+*/ִ$+$+++ 999$$99!9'$9014632#"54767>54#"#"&732654&#"L>?R=:$O ==#%:A73%'%2)$%=X_C?{)  =G QT0Q4(-O2"m7=7v=R|=*/{=()u =Bu!=/""=6~#=8j$=$-u%=:u&=Q},4=,5=}1/  +/ִ $+ + + ++014>7>32#"54654&'&  = %  //M , ./++/ִ + ++0147>32#"&'       ' ]!/ִ + +  ++014>32#".'&( %-%%,% '$45dC4 '6tGAq;*8E_ {]$/ִ+ + + 2+014>54.5432#"%-%%+% '$&( '6tGAq;+ 7F_/5dC4'X]/9+/9+ /ִ + 2+ +!+9 9999 90146;26232+"32632+"&' 7 . <8   UX=/9+2 /9+/ִ+ +/3 +01430;254#"#"&546;2+"7 / <,#   'J6=J{7='P8=PU9=7ORO*/O() OB!O/""O6#O8|$O$-%O:&O7~R ~* /~() ~B!~/""~6#~8$~$-%~:&~c}>4O>5O}m4~m5~,(z,(V,(V,(V3hqz3hrz3hsz3hszIe<? ; e?!/ִ+ + ++014>54.54632#" )::)(9:( 1?:'*J  22+?   C7 +2E/+23/ ִ%$+22 % + +2 " 4+90147>5454.54#"&543232632#"&#"#" " 'I( ( %# &!q ?+22  J>7C & ./++ /ִ + + +0174632#"&&"&  *( '*'d)Amz'T}zz'z'#5DU +H H +@ ) +&+/++BP+B +@ + +V/ֱ6 6?+N+ W+6:(+ ,', ,+ ,+ ,+9+ ,+ ,+''+'+&'+9+ ,.,+/,+9+ <,+=,+'E'+T'+U'+., #9<9=9 9 9 9 9E'9U9T999@ ',.<=ETU ................@ &',./<=ETU ...................@?6 )2H$9R999N9H 19B2:R$99017463232>?632#"&/"#"&56?64.'&7327654&#"32>54.#"'l &&h'  1U!} 3 D_n6+? ##yr   *G)#4 - 3W'R,#  U ",?'+P'*Z74>3!2#!". U r  t 4632#"t# A%# %TA]747672#"'&'& _] *40y hi %.)<+i4632"4632"&""0 "  4>32#". &!   *H' &% (47>32#"&54>54.  '! 7%*H' &%747>32#"&54>54.  '! 7%*H( &%:v 2w A M t .  d .   % O 4LCopyright (c) 2009 Barry Schwartz (http://crudfactory.com)Copyright (c) 2009 Barry Schwartz (http://crudfactory.com)OFL Sorts Mill Goudy TTOFL Sorts Mill Goudy TTRegularRegularFontForge 2.0 : OFL Sorts Mill Goudy TT : 9-1-2010FontForge 2.0 : OFL Sorts Mill Goudy TT : 9-1-2010OFL Sorts Mill Goudy TTOFL Sorts Mill Goudy TTVersion 003.000 Version 003.000 OFLGoudyStMTTOFLGoudyStMTTCopyright (c) 2009, Barry Schwartz (http://crudfactory.com), with Reserved Font Name OFL Sorts Mill Goudy. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: http://scripts.sil.org/OFL Copyright (c) 2009, Barry Schwartz (http://crudfactory.com), with Reserved Font Name OFL Sorts Mill Goudy. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: http://scripts.sil.org/OFL http://scripts.sil.org/OFLhttp://scripts.sil.org/OFL1r  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghjikmlnoqprsutvwxzy{}|~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~uni00A0uni00ADuni00B2uni00B3uni00B5uni00B9AmacronamacronAbreveabreveAogonekaogonek Ccircumflex ccircumflex Cdotaccent cdotaccentDcarondcaronDcroatEmacronemacronEbreveebreve Edotaccent edotaccentEogonekeogonekEcaronecaron Gcircumflex gcircumflex Gdotaccent gdotaccent Gcommaaccent gcommaaccent Hcircumflex hcircumflexHbarhbarItildeitildeImacronimacronIbreveibreveIogonekiogonekIJij Jcircumflex jcircumflex Kcommaaccent kcommaaccentLacutelacute Lcommaaccent lcommaaccentLcaronlcaronLdotldotNacutenacute Ncommaaccent ncommaaccentNcaronncaron napostropheOmacronomacronObreveobreve Ohungarumlaut ohungarumlautRacuteracute Rcommaaccent rcommaaccentRcaronrcaronSacutesacute Scircumflex scircumflexuni0162uni0163TcarontcaronTbartbarUtildeutildeUmacronumacronUbreveubreveUringuring Uhungarumlaut uhungarumlautUogonekuogonek Wcircumflex wcircumflex Ycircumflex ycircumflexZacutezacute Zdotaccent zdotaccentlongsuni0218uni0219uni021Auni021Buni0237uni02BB afii57929uni02C0uni02C8 gravecomb acutecombuni0302 tildecombuni0304uni0306uni0307uni0308uni030Auni030Buni030Cuni0312uni0315uni0326uni0327uni2000uni2001uni2002uni2003uni2004uni2005uni2006uni2007uni2008uni2009uni200Auni200B afii61664afii301afii299afii300uni2010uni2011 figuredash afii00208 quotereverseduni201Funi202Funi203Euni205Funi2060EurouniFEFFf_ff_if_lf_f_if_f_lc_tf_jf_f_jf_bf_f_bf_hf_f_hf_kf_f_k bullet.001a.scb.scc.scd.sce.scf.scg.sch.sci.scj.sck.scl.scm.scn.sco.scp.scq.scr.scs.sct.scu.scv.scw.scx.scy.scz.sc ampersand.sc dollar.lining zero.lining one.lining two.lining three.lining four.lining five.lining six.lining seven.lining eight.lining nine.liningi.TRKkcommaaccent.sclcommaaccent.scncommaaccent.scrcommaaccent.scgcommaaccent.sc uni0219.sc uni021B.sc ccedilla.sc scedilla.sc uni0163.sc agrave.sc egrave.sc igrave.sc ograve.sc ugrave.sc aacute.sc cacute.sc eacute.sc iacute.sc lacute.sc nacute.sc oacute.sc racute.sc sacute.sc uacute.sc yacute.sc zacute.sc atilde.sc itilde.sc ntilde.sc otilde.sc utilde.sc adieresis.sc edieresis.sc idieresis.sc odieresis.sc udieresis.sc ydieresis.scaring.scuring.scacircumflex.scccircumflex.scecircumflex.scgcircumflex.schcircumflex.scicircumflex.scjcircumflex.scocircumflex.scscircumflex.scucircumflex.scwcircumflex.scycircumflex.sc abreve.sc ebreve.sc gbreve.sc ibreve.sc obreve.sc ubreve.sc cdotaccent.sc edotaccent.sc gdotaccent.sc zdotaccent.sci.TRK.sc ccaron.sc dcaron.sc ecaron.sc ncaron.sc rcaron.sc scaron.sc tcaron.sc zcaron.sc lcaron.sc amacron.sc emacron.sc imacron.sc omacron.sc umacron.scohungarumlaut.scuhungarumlaut.scthorn.sceth.sc dcroat.schbar.sctbar.scae.scoe.sc lslash.sc aogonek.sc eogonek.sc iogonek.sc uogonek.scdollar.lining.supzero.lining.supone.lining.suptwo.lining.supthree.lining.supfour.lining.supfive.lining.supsix.lining.supseven.lining.supeight.lining.supnine.lining.supdollar.lining.subzero.lining.subone.lining.subtwo.lining.subthree.lining.subfour.lining.subfive.lining.subsix.lining.subseven.lining.subeight.lining.subnine.lining.sub comma.sub period.sub comma.sup period.sup parenleft.supparenright.supbracketleft.supbracketright.sup parenleft.subparenright.subbracketleft.subbracketright.subzero.lining.numerone.lining.numertwo.lining.numerthree.lining.numerfour.lining.numerfive.lining.numersix.lining.numerseven.lining.numereight.lining.numernine.lining.numerzero.lining.denomone.lining.denomtwo.lining.denomthree.lining.denomfour.lining.denomfive.lining.denomsix.lining.denomseven.lining.denomeight.lining.denomnine.lining.denom comma.numer period.numer comma.denom period.denomhyphen.uppercaseuni00AD.uppercaseuni2010.uppercaseuni2011.uppercasefiguredash.uppercaseendash.uppercaseemdash.uppercaseafii00208.uppercaseparenleft.uppercaseparenright.uppercasebracketleft.uppercasebracketright.uppercasebraceleft.uppercasebraceright.uppercaseperiodcentered.scldot.scguillemotleft.uppercaseguillemotright.uppercaseguilsinglleft.uppercaseguilsinglright.uppercase oslash.sc equal.ref1j.ref1guillemotleft.ref1 divide.ref1quotedblleft.ref1quotedblright.ref1quotedblbase.ref1$,q dDFLTlatn4 AZE 8CRT 8TRK 8  aaltbaalthc2scncasetdlig|dnomfrachistligalnumloclnumrsaltsmcpsubssups      (08@HPX`hpxJ*V`jLxZ<@ >FJRZdhrv "&*.26:>BFJNRV^fjnrvz~  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx| '^:6_;724TRV35US(H>)I?*J@+ KA,!LB-"MC.#ND/$OE0%PF1&QG`<8a=9>bcfWdgjj      ee    XYZ[\]hiHIJKLMNOPQTU  $>@@-D^.``ImmJooKyyL}}MNel  =@Cot  >GRS">FJRZdhrv "&*.26:>BFJNRV^fjnrvz~ "&*.26:>BFJNRVZ^bfjnrvz~ "&*.26:>BFJNRVZ^bfjnrvz~ "&*.26:>BFJNRVZ^bfjnrvz~ '^:6_;724TRV35US(H>)I?*J@+ KA,!LB-"MC.#ND/$OE0%PF1&QG`<8a=9>bcfWdgjj      ee    XYZ[\]hiHIJKLMNOPQTU  $>@@-D^.``ImmJooKyyL}}MNel  =@Cot  >GRStL RS>?@ABCDEFG TUHIJKLMNOPQ RS TU">GRS HQTU  HIJKLMNOPQTU>GRS (6745 !"#$%&89 >@(':;23()*+,-./01<= >@ Zqq xdj   e  xDEFGHIJKLMNOPQRSTUVWXYZ[\]y   "$&(*,.02469;=ACxdj   e  x $%&'()*+,-./0123456789:;<=y  !#%')+-/13578:<@BV(^_V`abcfWgXY[\]hi >@^`mo}oprst~ $,4<DLRX^djpINIKIEIMIOILKMNEOLIIWF F`DFLTlatnAZE CRT TRK cpspkernv  | "$%&'()*+,-./0123456789:;<=c  !#%')+-/13578:<@BH2`v.4>D HRhz $28>\flr$6LV`nx^9? [^ 9??[: ) =4S[wx?%" [_ ^9r?Ep^9?Mw?H_9?pE [E_  ;%9?  ? 9? 5;=") % 99 )EfM s+Xy{`gI  =:w ?5 9?.46"9?9? % EQM^CdfKR4=9?yd+e 9? 9? "9[9? 9 ?E|~bGhjOV8 =    Ei|N 9_9pE!#_ ?9gpw .4 SLiFwi?9?pp  #+)  *20   9p5w?0 ? Ep| y+,_dRe) 9 9H 349?PTY[ip| %9=>? ^_egiĸtkkDkkt:aM'D:MMDMaD'$ &A?D$ &$ &$"'$ &       #!&   $ &$ &$ &ZIIII]5II,,IIII,,5IIIIIIIII"III?I"III$ &$ &$ &$ &$ &GdPddUZdddZdPdiiPPZZ$"'$ &A?D$ &$ &rm__ sn`__  #!&$ & $ &   $ &$ &XXXXlDXX;;XXXX;;DXXXXXXXXX 1XXXX1XXX$ &@@@T@@#@@@@@@@1@@@@@@@@@@,##,@@@,@@@@@1@@@@#$"'$ &$ &$ &$ &rZ$ &$ &$ &$ &UUUUiUUU8UUUUUUUFUUUUUUUUUU.A88$AUUUAUUUUUF..UUUU8A?D $ &sZ rrUrr$ &$ & Urr Aum?DmA?DA?D$ & J A?Dkoz$ & 2 LimxKIr }imximxFJiiFJiPiZsvnwUYiw78UAUUKUUUKUAUZZAAKKgaJ}1S0C{//)YkC6IOQHPR!I#d%&b4qpK[@*]Fz3Uj?gqe IG$T.QH!IIIIIIbh@:@<=@`X^iV@;>QQQQHZHt**** \A"_F9W,!!!III32ddc&jbgb4 4'45d(J8a8TL + -FFnwyD)|MvmNxu~~Els]MmyNxNxuMmNulsMuuluyD)Nu~lyuysMmNxsuuBxuTTTTTTTTr7qqKoKfg m 7AmD):z,z=@9J@y=>::: S S S  S T !$l&'+- *(UF:zzz38W ::z?zY:Ka : (z z"H SQ:z#Q: X P: S SSSSS:Sllll- 2.::10::z}zzR4c:zzzzz:zaaaa<;:SzSzSzSz z z z ~ z z |SSSS      : 8C     : : :nSzSzSzSz Y Y V!:!;!:!5$K$K%Mlalalalalala'-- : : 62/!:$KZzzzzzzImEGImEG [,:z`:33333z33333333 ^z^wgzdoN]j_hzd{vbfLYB 8]jhvzbfzbfwtzzwojhzvbYuhzwrzzwzdpOzbwzizzwzYoz^whvbfYjwqzzs^^dfzjwk  [[[[[[[[\ ( (xeUUzBDa?ci]kpdrujwyn{}qDto8JLM=OVjMgnslixmpp-slix-1.4.2/docs/_static/fonts/YanoneKaffeesatz-Bold.ttf000066400000000000000000003550101342457644200245470ustar00rootroot00000000000000GPOS ]zJGSUB@Q'(OS/2UP``cmap C cvt  fpgmY7 sgaspg ]glyfϔITheadc6hheaf`T$hmtxrfkernJLlocaߢ@ maxpx name*H#post9[prep8_< |%`"7" 89dr sm`@ KYN @ 89  " #";" p !$& (fCr,'q~7/]oX| px^Wh ^r Rj#$nZ ~ i!!q!!!i!_l!p!> iJ{T,  WI   /8FC,,,,4pRRRRRR$ZZZZ!i_____iiiiTT!$#X>px,  r/ee`2    R]H rkM  r/_\k|v]Z:!&%   WCZEKn+r" 4F"&  ]##+l!]!n&   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYjkmov{[`^a]l}\eZ_dfizbhpgqnsturxyw~cHr@2";>]^z~1BSax7    " & 0 : D t !"!&"""""""+"H"`"e% #<?^_{1AR`x}7    & 0 9 D t !"!&"""""""+"H"`"d%qQE/+``F++y`ߝޫބޙ,rtvtzYZ[\]^_`abcdHr@2";>]^z~1BSax7    " & 0 : D t !"!&"""""""+"H"`"e% #<?^_{1AR`x}7    & 0 9 D t !"!&"""""""+"H"`"d%qQE/+``F++y`ߝޫބޙ,rtvtzYZ[\]^_`abcd,K PXYD _^-, EiD`-,*!-, F%FRX#Y Id F had%F hadRX#eY/ SXi TX!@Yi TX!@eYY:-, F%FRX#Y F jad%F jadRX#Y/-,K &PXQXD@DY!! EPXD!YY-, EiD` E}iD`-,*-,K &SX@Y &SX#!#Y &SX#!#Y &SX#!#Y &SX#!@#Y &SX%EPX#!#!%E#!#!Y!YD- ,KSXED!!Y-+++2) +.% +)" +8,!+8,!++ E}iDK`RXY`ipz B    k+// /EX/ >YEX / >Y ++ и ии!и%и'и,и.01%#7##7#46;7#46;7>337>33+3#'37#G.2J.2.7'.2J.2-6JJ5I5I2$T2$3G3G2$T2$VT#e9p5+5и/9/EX/ >YEX/>YEX5/5>Y&+ и/01.54>732654&/.54675463.#"#41 5!&p><427+ 4&+ v ."42   !|79D R{ . :%3+!Q#3GY,>+4$+++EX/>YEXN/N>YEX///>YEXH/H>Y/9014632#".732>54.#"4632#".732>54.#"&'.5IE"2!GF"3!k    IE"2!GF"3!i     ~  5D#S^X)E5dY(I;"()!''^X)E5dY(I8"()!'' =`w>S_`//и/1`GиG/ZEX/>YEX / >YEX#/# >YEXYEXB/B>Y  +B]012.#"6454.'>232+#"&'#".54>7.54>.'326EV5(3  %%  * $E/6H,' )(@d3!!%0  ANW)  '9," $K&%! 1A%80'32-!  G5+$A=.;?"" +/EX/>Y01#".54>32$15*)50/1CLpT'.6yÍu8*F?"" +/EX/>Y014.'>32#"&'>t $04**52-5C&67'6.' $ Z v $ N   Z ( #% |  kw?+ и//+и 017#5#46;54633#.2k.2k2.k2.+ /+0174632#"&'>7.$G  D&I 8+  J +01746;#2.2. $+EX / >Y0174632#"&%")%"*@,(!*% f //017#>3/+/+  |X!P"//"и/EX / >Y+ 014632#".732>54&#"T_`U)G67E'z #(VU+*OqC9M/9\DQX6N|X#> + /EX/>Y и/и 01&54>32326767#!&5463<$--)  8   9F " '  * %% }|X1N )+EX/>Y,+,9и/'901.54>32326767#!.54>7>54&#"5 3@"UD&:I"J3  F:&  "%# /  HE-PR[8#-  7[LC !qX/7++* ++и/01#"&546732654.'>54&#".546325C$>S.61 163*8?=#B `NJA:+HFD_<'K?%DJ%/ !F3@X_&-L'+и'#/EX/>Y+и'01%26767+#5#.54>7>327?  -*#++& ! s "  *F f< :NYN;  ~@IGsX/K'+"+/ +,+ и/ и/01#"&'&'>32#".546732>54.#"s 4 ?6#;W7* 7 /  2),,X'| (MACmL*  '/9%2 <~$7{8/5/8и/+и/5 +(и(/EX/>YEX/>Y%+001">32#".54>32."32>54& 0!5,2$bZJ' %04>$X/+и/01".54>7#".50L4#- "4=,V6 XlD :/$~'6EF/4/и/Fи/.и/#и#/4CиC/EX/>YEX/>Y7+791@19012#".5467.'.54>.'32654&">54&(C1#  ,A,4G,5,! .>P  (+*() # &>.!5+#  B&$D6 5F&>^  0+B-I  6'//,*#"Z! 6#(|a-Y./"/и/. и /" +и/+(++0172>7#"&54632#"&5467726754.#"t,9"5+ B+ иEX/>Y +014632#"&4632#"&%")%"*%")%"*,(!*%,(!*% 7+ и /и/// +014632#"&4632#"&'>7.%")%"*$G  ,(!*%&I 8+  w ++01463!#463!#JJN2.2.2.2.  =].++&и&//!/EX / >YEX / >Y 10174632#"&2#"&'&'.5467>54&#".54><%")%"*U$5"$&  $  '/@,(!*%%2/C4/  "2(&+  (A`o +)+UD+i8+D@и@/UYиY/Daиa/EX/>YEX./.>YEX3/3>Y$+RF+=f+3l014.#"3267#".54>32#"&'#".54>3254656#".5>7>3232>"'"&#"32674)H751?&  # * /B2!=V &7' ) sEX / >YEX/>YEX/>YEX / >YEX/>YEX/>Y+01%##"&'632#*'3'.'  () " [ X ](e00e(\$/o0/)/0и/)$%EX/>YEX/>Y/+#%0146;2+32654.+32654.+ b=0!)"-G3r    "=1&:( 7,2VA%4+!E>"(=&C"+EX/>YEX/>Y 012.#"327#".54> &  ( 1 A9*?*%:G  "  YEX/>Y012+4634.+32>2J0Ze     F{`ȹ CP* >s"U+EX / >YEX/>Y+ 01%+47>;+3+  yfd#("' '"K+ EX/>YEX/>Y+ 01347>;+3+# yf"& 'X)s*//и/и*%и%/EX/>YEX / >Y+  012.#"32675#54;#".54>67  (&0 .G+9I+8R"" AeHM\3HlKeyS_//и/ ииEX / >YEX/>YEX/>YEX/>Y+01##4>3354>3#`-+-+`-+-+C  X /+EX/>YEX / >Y014>3#.*/* X5+EX/>YEX / >Y014>3#".546732>5.+DR$, ,  UU   $~"{+EX/>YEX/>YEX / >YEX/>YEX/>YEX"/">Y014>3>32#"'4.'&'#.+m&; r75&*.+ 3Æ .XPC 5+EX / >YEX/>Y0173+4>3 .)iU  9{://:и/ и /.EX/>YEX/>YEX/>YEX9/9>Y0146;>?>;#7>767'+'.'# UO  D ^/+  0./  ,'*54* X=k*0)73)  )38(0)k>%&//&и/ и/!%и%/EX/>YEX!/!>YEX/>YEX$/$>Y01.'#4;.=4>3+ -*Yh .)W 5;;f3 X'](//(и/ EX/>YEX/>Y#014>32#".732>54.#"0H01D*/G11E+   bfS" PgiU$QV;T6<^BA\9>fSy // и/и/ и/EX/>YEX/>Y+0146;2+#4+32> ^/I1!6D#-*,  /P=DW2_ 5r"6k7/-/7и/- #EX/>YEX/>Y+(2014>323267#"&'.732>54.#"0H01D*-/ ! fc%01/&/1 и /и/& #и#/*EX/>YEX/>YEX/>YEX / >Y,+)012#"'.'##&7634&+32>/I1'  %  .) /P=KY1E\: :ZA,  61 1I12/&/и2/и// /и/ #и#/EX/>YEX/>Y ##9) 9012.#"#"&546732654&/.546L< 2%*`)G6D? 2*[ Y!'{)''&F6!),*#-#z'$ DU rA+EX / >YEX/>Y и01##543!#.)zT TTTT }`//и/ EX / >YEX/>YEX/>Y01732674>3#".54>3' .+Z@;G% /,1.>`  7I*#GEX/>YEX/>YEX/>YEX/>Y01&6;>7>;#"&'.'&^2  + c   %OKBBKO%(D"Tci3wCiEX/>YEX/>YEX!/!>YEX&/&>YEX5/5>YEX:/:>Y01&6;3>7>;3>7>;"#"&/.'"#"&'.'&^1  + O3 * aw   K2E< K2ADD (D!l@DDDD@$Vai2u$GEX/>YEX$/$>YEX/>YEX/>Y01+'.'+&>3>?>3||&  & al81 .+ut?>fgBi<:WlL+и/EX/>YEX/>YEX/>Y01&>3>?>3#}61  .+y-+463374n jp/EX/>YEX/>Y01463!3267#!.5467#"% ,"       Y  W8V52+EX / >Y+ 01+3+4>3V m # 5#)'+  _ //01#&>3\/+/+  862+EX/>Y +014>;#4>;# m # "*,'+"  h +01463!# J2.2.,/EX / >Y01".'>32&-+   !() ,! #) 7(9:/ /:и/и/  и//и/ )и 5и5/EX/ >YEX/>Y,+#201467>32#".54>3254&#"..#"3267>< N43A$  E $ 0$$&#Y#~$//$и/и/EX / >YEX/ >YEX/>Y !014>3>32#"&'732>54&#"# ,#+* $8E (<p  0N8k}BL+N?E6%C +EX#/# >YEX/>Y#01.#"3267#".54>32 # $ =0'8$ 3@%.  #D9+:$ 6aNZk9K%a&//и&и/EX"/" >YEX/>Y" 01&#"3274>3#".54>32     ,#G1 :-%2 "E>-:# K 5 2]PMlC F"01/,/1и/& и /и/,)и)/EX/ >YEX/>Y& +#012#3267#".54>"267>54.YEX/ >YEX&/&>YEX / >Y и 014>;54>32.#"3++  ! 2>/5$ &-H3\!%9:G' ! *E!%} <y7EUV/F/и/Fи/V'и'/NEX/ >YEX/ >YEX"/">YQJܸ8>0123#"&'#*'#".5467.7>7.54>"32>54&4.'32>w  KW  2$@D.H33@# % ,?"  "!" ,QR  6;%A0 (#9.K05H+O+;5' +"0$/   % !L"}#//#и/EX / >YEX/ >YEX/>YEX"/">Y 014>3>32#4.#"#! +#5$.) -* !0 Z  !&&%EX / >YEX/>Y01;&+!]-6y+'и34/EX / >YEX/ >YEX/>YEX-/->Y4# 0014>3>32#>54&#4#">! +#6 !  ' 3/ .+     0& d<""3:C d/! /+EX / >YEX/>Y017#4>3-* ," !,-/ и /AO]A`]A]  AO ]A` ]A ]и/ &EX/ >YEX/ >YEX / >YEX / >YEX,/,>Y$и$/014&#"#>32>32#4&#"#  -)P-4?&-.( -+w #3 s !Lh//и/ EX/ >YEX/>YEX/>Y01>32#4&#"#! %+,&6#/* .)  2%Z' Q%]&//&и/EX/ >YEX / >Y!0174632#".732>54.#"VP(;&)>();'{      {:bLHe?7gU;C" #C:6@" "@!;V%~&//&и/ и /%EX/ >YEX/>YEX / >Y#01>32#"'#"&5732>54.#"!O($;*#0+ p    .O=e|D ,TG$- :O)*//*и/! и /EX/ >YEX/>YEX/>YEX / >Y &01#"&'5#".54>32.#"3267O 6-"#9K)"0r      "1XGYrBL,K:)3 !9+EX/ >YEX/>Y 01>32.#"#!G*)   .+ $223/'/и/3.и./ 'и/.и/ *и*/EX/ >YEX/>Y 9$* 9012.#"#".546732654&/.54>*1 +/ J [M+ -W&?   KDL  !  \8.! (p +и  /EX/ >YEX/ >YEX#/#>Yии#01#4>;54>33+327#".5E< % +"R < !) &  &  $5!H\//и/EX/ >YEX/ >YEX/>Y01%#".54>332674>3HO--:# /,  -+'8#T } CGEX/ >YEX/ >YEX/>YEX/>Y01&>3367>?>3#"&' 40/+d0  S%+,,+%S z   MEX/ >YEX(/( >YEX///>YEX1/1>YEX4/4>YEX?/?>YEXA/A>YEXD/D>Y01&>33>?>;3>?>3##"&/.'#"&'.'&2, J"*&   # " %S#)* "(*1>?=v n/ZF+BN''No=FJ#Ss'GEX/ >YEX'/' >YEX/>YEX/>Y01"#'.'#'&>3>?>3m63    .&uf3/  .* 0A@7=;$?O*<EX/ >YEX/ >YEX/>Y'01&>34>?>3#".54673267 /, /*p !,# % B9)(9C 1B'  #  "1 "&UEX/ >YEX/>Y 9  9 013267+&'.5467#".54637") Q&    #"  8W8/ "+ /EX/>Y01#4>3.+.+  ; =+/EX / >YEX/>Y 01#"&54632#>3%")%"* ">4 *!,(!*%  IMc1M(+-и-/1/"/EX/>YEX#/#>Y01.#"3267#5.54>75463# #* '42!  *42  "C:3:+ Lz6T?C^?$B~60+ии/0*EX/>YEX#/#>YEX6/6>Y+ + $и(и+и/017#46;5'#46;'&>3>?>33+3+#{Ri#N61.+ORE|S-+2*82**,)*++n 2*02*6   +и 014632#"&74632#"& # ## #s%"# %"#  ?%1CI2&+ +,<+7/+)?++#+01.#"3267#".54>324632#"&732>54&#")  ! !oghbjjjbR ..#8A/#   +$*2 mpibtlvos)9#"9+EI!7!C%EX/ >YEX/>Y0174>7672#&'.74>7672#&'. (  ! (  '  '  &,2; @ %('#? :2,& #'-4 :!#!8 3-'" ? =Gk +#+%>++%+и+/>1и1/#7и#A ++D6+014632#"&732>54&#"7476;2#"&'.'+"#74+;26 oghbjjjbR ..#8A/#D%O   J  pibtlvos)9#"9+EI!7HI +,((OG//EX / >Y01"&'&'>32+  ()    +-&/ )# !/U + +014632#"&/%")%"* ,(!*%""++01732#"&'&'67>7632654#"7^ /+      ;!&&8  p!C%EX/ >YEX/>Y01%&'.546767&'.546767&'.5467672.546767  '    '   ' &! '  &,2: ?#'(% @;2,& $'-3 8 (7;5.'#< =Y+.+&и&/EX / >YEX / >Y  101#"&54632".54>76.7>32326767%")%"*U$5"$&  $  '/,(!*%g%2/C4/  "2(&+  & >c& a& W& Dy& ]Z& q<#)#+#и%EX/>YEX/>YEX / >YEX/>Y%+!+01%+5##"&'>3!+3+'35 / (   yf] !d#(  "' 'B9~1"=HGD+>и>/4%+ +9++?012.#"32732#"&'&'67>7632654#"7.54> &  ( 1 1,/+      $4#%:G  "  1)&$as.&$%2y&$](&(>&(a!&(y&(]&-E&.>X&.a&.L&.9y&.]O4\7&'7'>76'B $#RR#$ BB $#SS#$ H #ZZ# II #ZZ# ,5BC/B/C и /B$и/ 5EX/>YEX/>YEX/>YEX)/)>Y0)=01".'&>7&54>32>7#"&'7&#"732>5T  /G0(=   /G1);Fa v    %&OfR# %.1#V5iU$,?fIT)TOD 1cW}&4>T}&4a}&4H}y&4]Kl&8aA3A+ (+  #и#/EX/>YEX/>YEX7/7>YEX:/:>Y 0014632#"&546732654.54>54&#"#"&'&'67>5$ZL7O2)1)MG?:  (0(!*&'* &OL(5"%1?S6BT" !=?D'"*6;X("  #7&?>17&?as7&?%A&?7&?](7&??DSaøb/]/b2и2/и/]]и/"и"/2K7и7/]ZиZ/EX/ >YEX / >YEX'/'>YEX-/->Y>-P>TиT/01467>32>32#"&'3267#"&'#".54>3254&#"..#"3267"7>54. ME@5 YEX/>YEX;/;>Y0!+5+E01.#"326732#"&'&'67>7632654#"7.54>32 # $ ,# /+       . 3@%.  #D9+:$ 1!&&8  f8]GZk9F&C>CF&CaF&C7F&C]:&>!&a&&&]M&LQ&M>9Q&Ma{Q&M-Q&MQ&M]0;wL+иEX/ >Y ++017463!#4632#"&4632#"&J #!# #!#2.2.q%"$!Z%"$! +4>?/1/? и /1# и / >2и2/16и6/EX/ >YEX/ >YEX(/(>Y,901.54>7.54632>7#"&'72>5'7.#"?  VP#5    )>("3l FF    H/{  $8 He? @ #C:  "@5H&S><H&Sa~H&S0H&S]3?O&Wax?O&W]-! /+EX / >YEX/>Y017#4>3/* ,# (89//9и/и(и)EX/>YEX/>Y!&+,и,/4и4/01%+5#".54>32>;+3+'32654.#" (?1E+/G0:%  yf&% d#(QldT$ "' 'uoxA^=Bh-AOP/K/Pи/K и/.KHиH/EX/ >YEX / >YEX$/$>YEX)/)>YE+)3=B0174632>32#3267#"&'#".732>54.#"7"267>54.VP"4: YEX/>Y +014632.#"#"&'&'67>5[[3C '(%LOG!% 4- !  0 ////01632#&'.'#"&'r     $ *     '/ //01".'>32>767| "&#    '%'"%     )* 5r  +014632#"& #+ ))-"%  ++014632#"&732654#"9341k33M 72+Y+01>3232>32#".#"#"&'&H  +   p,'      |J +017463!#J2.2.J +017463!#2.2.:+ //01#"54>32$G  {&I9+  ; +/ /014632#"&'>7.$G  &I 8+  q+ //0174632#"&'>7.$G  0&I 8+  :H)G*//*и/ и /"и"/ ////01#"54>32#"54>32$G  $G  {&I9+  &I9+  ;H )K*// и /*и/"и"/// //014632#"&'>7.'4632#"&'>7.$G  $G  &I 8+  &I 8+  Hq)K*// и /*и/"и"/ ////0174632#"&'>7.'4632#"&'>7.$G  $G  0&I 8+  &I 8+  z + +014632#"&/*3&.+4%%72*%4/&'u!5EYi}ֻjZ+bt+F6+>P+"+,+P и /EX/>YEX/>YEX/>YEXA/A>YEXe/e>Y9U+1AK9]иKoиUy01&'.54632#".732>54.#"4632#".732>54.#"4632#".732>54.#"   5D#SIE"2!GF"3!k    IE"2!GF"3!k    IE"2!GF"3!k      =`x>a^X)E5dY(I;"()!''^X)E5dY(I;"()!''^X)E5dY(I;"()!''!%EX/ >YEX/>Y0174>7672#&'. (  ! ( &,2; @ %('#? :2,&!%EX/ >YEX/>Y017&'.546767&'.546767  '    '  &,2: ?#'(% @;2,&  %EX / >YEX/>Y017#"&'>32-)    _C6+и/61и1/6:и:/EX,/,>Y +++,#1и5и:и>012.#"3+3+3267#".'#46;5465#46;>!(" uZh; ( >5$:*U)S4 )38_ (  #2*  2*! #"+K<2*  2*YEX/>YEX/>YEX?/?>YEXA/A>Y4ܸ;и<01476;46?>;"#7>767#+'.'##"##5476;#&$*   $Z Ephl)1 =@ 2(d&&wJ +017463!#J2.2. 7CD/6/Dи/и/и60'и02и2/8и69EX/ >YEX(/( >YEX8/8 >YEX/>YEX5/5>Y$+8и ии/ .и/и$>и>/01+#4>;54>32>32.#"3++'3547&#"\< !/80:7-$ &-H3\NN  }!%@7C$    *E!%}]9/   5ʸ6//6и/ии/ и /&-и&0и0/4 /EX / >YEX/ >YEX&/& >YEX/>YEX3/3>Y ,и-01%#4>34>;54>32.#"3++y/* ,# !"6E$?  4\ ]!%9:G' ? & =!%} /0//0и/и/ 'и *и*/.EX/ >YEX / >YEX/>YEX-/->Y +&и'014>;54>32+.#"3++  !!1:,P\  :%\!%9:G' \O!%}C8DRQ+(.+D?+(иQ0и7и.EEX/ >YEX / >YEXD/D >YEXQ/Q >YEX-/->YEX6/6>YEX>/>>Y+ и /&и'и/и0иKиK/014>;54>32>32.#"3++#+#4>3%467.#"3 !/81B#?  4\N\/* ,#  N!%@7C$   ? & =!%}}} 9& O<?L++ &+и&.и@и JEX/ >YEX-/- >YEXK/K >YEX/>YEX/>YEX%/%>Y: +ии'и(и:4и4/ EиE/01%#.#"3++#+#4>;54>32>32347.#"3<-*  3\N\< !/8076K   N{I!%}}!%@7C$  . O >&RX8Q!01/"/1и/и/" и /"и/"*EX/>YEX/>Y%+-01"&54732>=#".54>32.#"3265?B1" ' #- 8K,7^   )""6 9[ARmA PKh(C1YE"( 'l+и&EX/ >YEX/ >YEX%/%>Y +и014>;54>32&#"3++ !!+1H3\!%m'2  ! c!%}AM+++!и!//A/.+'(+'$и$/=и=/017.5467.54675463.#"26732>7#LD<(JA42-7++,# .<.%%!  !3"42; NH=G *"?T Bk6$&h4%#  AfUct+PV+/F+и)иP>и>//^EX$/$>YKY+4C+$0154>36767326767#".=#32>7#"&54>32'4&#"7>*  $   + ( 20)[  $!*9"PJ(B00&  6   $+{!)      !8,%+   pAiI(!2"!Z$ .2{LM/E/Mи/и/ и /Eи/$иE1EX%/% >YEX/>YEX/>Y+@6+%01#"&'&'67>54632.#"!#".546732>54&'.'7%[[+  '8%1J1( ($U4(  OD    + # $,0-S>% ,*'+   {>,2%+EX/ >Y +01<>3!#".546732>54&'.'7  8%1J1( ($U!% # $,0-S>% ,*'+   {9:/2/:и/ии2!/EX / >YEX/ >YEX/>Y/&+и01##4>;54>3!#"&546732654&'.'7.)< % +"8%,F2.)  )$U  &  # $,0-S>%, 79'+  ^()//) и /и #EX/>YEX / >Y +и !и #012+#46;4634.+3+2>2J0.H3"   2  Cv\i`.;2. CP* 2.>sM+и//EX/>YEX / >Y0173+.?4>3[ \ .)0/U'0/ G +и  //EX/ >YEX / >Y017#.?4>3! "-*  ,"0/'0/$ !FA+EX / >YEX/>Y+01.5467>323#!.546;5*++% KF;    C<T3+EX/>Y0$+8+и/ 85и5/014632654&#".547>32#"&'.546732654'&"#.\   , K9>:1@#)2  - &,    /*50(8$   !P(2 #+EX/>Y+&01.54>323#!&5467>54&#"5 09%1 '-#4)+6 &!<5. 9 "4,$h&]  + и  и / / /EX/>YEX/>Y+013+#"'5#.547>7>3270/: "$2  d ( B ?;3 &)]-VW/Q/Wи/Q;AиA/EX / >YEX/>YEX*/*>YEX#/#>YEXF/F>Y+6T+иF@01.5467>323#!.546;5#"&'>32.54>323#!&5467>54&#"*++% KF-)? 09%1 '-#4)+;      t &!<5. 9 "4,$D&'aA&'S:: \+ и///EX/>YEX/>YEX / >Y01#"&'4632#"&'4632% #% #itv!Np //017#"/.5467%   Xi" |)&?u //017'>32.'47   Xi" }(%// /017#"&'7'u %~Dk9;$+ и$7++/*+01#".54654&'.547>54&546   &3  W%(#X.   '.-Q#-&!$22S2&!  % 3f'=:k9;4 + и4'++*/+01.5467>54&5467.54654&'&54632&  %3   W&($W./' (.-P$,&"$2-+.&! 3f'<; P# ++01>3232676#".#"'. %+  $+        % %3E,+&и&/EX/>YEX)/)>Y -+01&>32'76'.7&5467#"&'>32  $ "   q r a'!    ! "T'! ! ~  C ?//и/  ++014632#"&732654#"C9341k33H $72+Y!+$+ и$01+.5#46;>32&#"3#}/2RH$ *m3L& 3EHH!2.vj/@2.+HXY/N/и/Nи/и/Y=и=/V%и%/=+и+/EX/>YEX%/%>Y %/012&#"#"&'.54732654&'.'.5467'.54>>54&'&'1. &2    ,92.%    +9     $  %5'/  %   %6 (/ }  &4{++'-+и/EX/>YEX/>YEX*/*>YEX-/->Y01%#"&'5#".54>32.#"3267#"&'4632Y# #4'4I*&Dq  % #  %4WBTpD HYa0< w!YEX/ >YEX/>Y+ +и ии 017#"'7#46;7#46;76323+3#$*AL" !,FQ"]N$@2.h2.[$S2.h2.g0(9:/2/:и/2 2и//+ 8+ ии/и/и/8&и&/85и5/0174632>32"#"&'#"&73267.'&#"732654&#"AH0==*YEX/ >YEX/ >YEX / >YEX / >YEX / >YEX/>Y01%#"'#"&'463232674632PQ0 &    (" "  &a+,z-//-и/и/&EX/ >YEX/ >YEX/>Y!(012.'>32#".5464&'.#"32>&>0. BI %A4.C,S < @[- >ԣ>mQ/?fLz5 2>3H"B>9EX/>YEX/>Y 01!+3#" hbe!(} 6>p / / и/ EX/>YEX/>YEX/>Y и ии01#"&'##"'#.5463!#g  B*# V S    %! / / и/ EX/ >YEX/>YEX / >YEX/>YEX/>Y и ии01#"&'##"'#.5463!#g  B*# V    %! ;'2+EX/>Y+$014>32.#"#".54673265 2>!'$ &- 2>!($ &-0:F&   *:F&   *  -f.//.и/и/!и'EX/>Y* +$+01>32#".5463254&#".54.#"3267, <449I&)5/ -66 1*D:!   !+!P"//"и/EX/>Y +014632#".732>54.#"KB!2 GE!1!]     od/P>ng0Q9$- -##- 2~34//4и/ и/и/'0и0/EX"/">YEX/>YEX/>Y",и-013>54&#"#.54>;.546323#  #  Xg.?%  8$KS^7q-QCvH 5I]8$MvR;`I3 $"fhB+/+01%#"&=#&5463!h   $oQ&;/EX/>YEX/>Y017>323+#"&'& $)H  2S     ;3z+и.EX/ >YEX/// >YEX/>Y ++/и01#".54673265#46;54>32.#"3# 2>!($ &-kW 2>!'$ &-Y:F&   *2.<:F&   *H2.l!C*7++01>3232676#".#"'&>3232>#".#".B-  "(B(  #+  M(      S%     ! +EX/>YEX / >Y 01>32!73# w|={ Ri%EX/>YEX/>Y01'7LLLde %2?LWC;+EX/>YEX8/8>Y@+&+014632#"'&>32'76'.7&5467#"&'7>.546776'&47   $ "   q r ^  "   q r   ! "T'! !   ! !S'! ! !'d%EX/>YEX / >Y012#".54>">54&#"&2#"&#"3263232654#"#"&=463232654&#"#"&#"3262654&#"#"&#";2/.#"#"&#"&#"#"&#";27'32654&#"#"&#"32632#"&#"3263232654#"#"=7632326732632#"&#"3263232654#"#"&=326=463R^33^RR^33^\#&,-'$+     k    F      !    9  9       3^RR^33^RR^3 &"$$"& ,,' p     p         b   " 8   ^"  $[ q     a) v 7 +01.546;# 7 !(/+0132>76#".5466 E8.$    3> (+] // //01>32#"&/>32#"&' *z  Y.]   (&}  = EX/>Y 01723267#".54>m    4  &    #2&#r"//EX/>Y01#"/.5467%463!#   8/Xi" |)2.2.#r"//EX/>Y01'>32.'47463!#   6=Xi" }(2.2.+*<=/+/= и /6и/+&и&/+.и./EX/ >YEX / >YEX/>Y1801#".54632&'.?.'>3274&'.#"32>H %A4.C,S@ Z 1  . ; M < g=i>mQ/?fLz-##.-   .-6 2>3H!8V+,/$/,и/и$$ и /EX/ >YEX/ >YEX/ >YEX/>YEX / >Y)01>32#"'#"&54>332>54.#"  $;*&/+) ,"    -O=e|D E  -VG(-S'(//(и/и/ и/ии/и!и!/EX/>YEX/>Y#++0146;32+#4#"#32>/$/I1!6D#-(,   x/P=DW2_ 5 9+/EX/>YEX / >Y0174632#"&4>3#%")%"* ">4 *!?,(!*% c ;5+EX/ >YEX/>Y01#"&546732654>3 6*:.$ ,"$ :, , G%_&//&и/и/ и/ /// / /// /01&'.67>32&'.67>32-!  G!  G5+$A=.5+$A=.L*RnXR: @ j T * (  8|".@68t$~r"BN~V"x 0 !Z!!"6""##$$$%&%&$&0&<&H&T&`&l&'x'''''''''''((( (F(())))*)))))***++++++++++, ,,",.,:,-2->-J-V-b-n-z-.<./ //"/./:///00>000011B122l22344J4x55567d7899:b:;T<<=0=>@>>?2?@@pA@APA`AABB4BCCRCDDzE@EF*FGGHHVHI:IJJ^JKK\KLPLLLMTNNOO@O|OPPQ4QQR>Rj` 2|  s "&)t.03 568;(?ABCDEKLMNOPQRSTUVWX[efghijklLmpqt/uvwxyz{}~7'?{    " & . 0 9 : ; </ ? A B C Hq K L M O P R S T U V W X [ _ f g h i j k l m t? w x y z { }              ) A B C E M O f g h i j k l " & . 0 3 4 5 6 8 ;{ B C D H' M O R S T U W m w x y z { } ~    $)2356789;DRTUVWXfghijkn"&.034568;{BCDMORSTUWmwxyz{}~  TTi "&).079:<=s?ABCDEGHKLMNOPQRSTUVWXZ[_bdefghijkl\mpqt'vwyz{|}'?TTT)35678;<= = ==  )7={?BCEfghijkpqvyz{==33H( )fghijkpqvyz{)3578H     " & . 3 5 6 8 ; B C D M O R S T U W m w x y z { } !! !! !)!3!5!7!8!=!R!T!V!W!f!g!h!i!j!k!l!p!q!v!y!z!{!!!!!!!!!!!!!!!!" " """""""&"."0"?"A"B"C"D"M"O"R"S"T"U"V"W"_"m"w"x"y"z"{"}""""""""""""""""""""""""""""""""" " "" " """""""""" ## # ## #)#3#5#6#8#=#?#f#g#h#i#j#k#l#p#q#v#y#z#{##################################$ $$?$B$C$M$O$P$S$U$W$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ $ $ $ % % %%%%%% %)%=%?%A%B%C%E%M%O%P%S%T%U%V%W%X%_%d%f%g%h%i%j%k%l%p%q%v%y%z%{%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % %% % %%%% &8&T&W&&&&) ) )))=)V)f)g)h)i)j)k)l)p)q)v)y)z){))))* **"*)*.*0*?*A*B*C*D*M*O*R*S*T*U*W*m*w*x*y*z*{*}**************************************++ + ++++"+&+.+0+3+5+6+8+?+A+B+C+D+M+O+P+R+S+T+U+W+_+m+w+x+y+z+{+}++++++++++++++++++++++++++++++++++++++++++++++.. . .. .).3.5.6.7.8.=.V.f.g.h.i.j.k.l.p.q.v.y.z.{............../ / c//c// /)/7/8/9/=L/?/A/B/C/M/O/_/b/f/g/h/i/j/k/l|/p/q/v/y/z/{////////////////////////////////////c/c/c//00 00)0307080='0V0l000000000011"1.13151617181B1C1M1W1l1m1w1x1y1z1{1}1111111111111111122)2=2D2T2U2V2W2l22222222222223 3 33333333 3"3&3)3.303=3?3A3B3C3D3E3F3G3H3K3L3M3N3O3P3Q3R3S3T3U3V3W3X3_3d3f3g3h3i3j3k3l3m3p3q3v3w3x3y3z3{3}33333333333333333333333333333333333333333 3 33 3 33333333333333 55 5555555 5"5&5)5.5=5?5A5B5C5D5E5G5H5K5L5M5N5O5P5Q5R5S5T5U5V5W5X5_5d5f5g5h5i5j5k5l5m5p5q5v5w5x5y5z5{5}5555555555555555555555555555555555555555555555555555555566 6666666 6"6&6)6.696=6?6A6B6C6E6K6L6M6N6O6P6Q6S6T6U6V6W6X6_6d6f6g6h6i6j6k6l6m6p6q6v6w6x6y6z6{6}66666666666666676666666666666666666666666666666667 7777"7&7.707?7A7B7C7D7M7N7R7S7T7U7W7_7l7m7w7x7y7z7{7}777777777777777777777777777777777 7 7 7 77777777 88 8888888 8"8&8)8.80828=8?8A8B8C8D8E8G8H8K8L8M8N8O8P8Q8R8S8T8U8V8W8X8_8d8f8g8h8i8j8k8m8p8q8v8w8x8y8z8{8}888888888888888888888888888888888888888888888888888888899 999?9A9B9C9D9E9M9N9O9P9R9S9U9V9W999999999999999999999999999999999999999999: '::;{; ; ; ; ';f';g';h';i';j';k';p';q';v';y';z';{';';';';';{;{; ;{;{; ; ;{< =7======="=&=.=0=3=5=6=8=A=B=C=D=E=Hu=M=O=R=S=T=U=W?? ?D?R?V?W??????????????@@ @ @@@=@U@@@@@@@@AA AAAAABACAMAOAR AUAWA_AAAAAAAAAAAAAAAAAAAAAABBBCC CCCUCVCCCCCCCCCD7DuD mD "D DDDDnD;VD<OD=DADBDCDEDKDMDNDODPDSDUD^>D_D`?DDDDDDDDDDDDDDDDDDDuDuDDuDuDDDFDDVDDDDDDDDD^DuEEAEBECEHLEMEOEVEW EEEEEEEEEEEEE E EEEEEEFFFWFFFFFFFGHHH2IIIAICIMINIOIUIWIIIIIIIIIIIIIIIIIIIIKK KKVKWKKKKKKKLL LLLLLLMM M M MMM=MDMRMTMUMVMWMXMMMMMMMMMMMMMMMMMNN N N NN=N?NDNHNRNTNUNVNWNXNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNOOOOOOOPP P PPP=PAPBPCPEPMPOPVP_PPPPPPPPPPPPPPPPPPPPPPPPQQ QQVQQQQQQQRR RR RRARBRCRERMRORRRSR_RRRRRRRRRRRRRRRRRRRR RRR R RRRSS SSSSSSTT T TTTTTT=T?TATBTCTETITKTMTNTOTPTQTSTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTUU U UUUU=U?UAUBUCUEUIUKUMUNUOUPUQUSUXUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUVV VVVV?VAVBVCVEVIVMVNVOVPVQVRVUVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVWW W WWWW=W?WAWBWCWEWIWKWMWNWOWPWQWSWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWXX XXAXBXCXEXMXOXSXVXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZZH][[ [[[[[\^_3_5_6_8`d d)d3d5d6d7d8dXeeH^eeeeeff f ffff"f&f.f3f5f6f8f;fBfCfDfMfOfRfSfTfUfWfmfwfxfyfzf{f}ffffffffffffffffffffffffffffffffgg g ggg"g&g.g3g5g6g8g;gBgCgDgMgOgRgSgTgUgWgmgwgxgygzg{g}gggggggggggggggggggggggggggggggghh h hhhh"h&h.h3h5h6h8h;hBhChDhMhOhRhShThUhWhmhwhxhyhzh{h}hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhii i iiii"i&i.i3i5i6i8i;iBiCiDiMiOiRiSiTiUiWimiwixiyizi{i}iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiijj j jjj"j&j.j3j5j6j8j;jBjCjDjMjOjRjSjTjUjWjmjwjxjyjzj{j}jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjkk k kkkk"k&k.k3k5k6k8k;kBkCkDkMkOkRkSkTkUkWkmkwkxkykzk{k}kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkl ll?lAlBlClElMlOlPlSlUlWllllllllllllllllllllllllllllllll l l l l m m mmm"m&m.m0m?mAmBmCmDmMmOmRmSmTmUmVmWmmmwmxmymzm{m}mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm m mm m mmmmmmmmm n nn?nBnCnMnOnPnSnUnWnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn n n n n o oo?oBoCoMoOoPoSoUoWooooooooooooooooooooooooooooooo o o o o p pppp"p&p.p3p5p6p8p;p?pBpCpDpMpOpPpRpSpTpUpWpmpwpxpypzp{p}pppppppppppppppppppppppppppppppppp p p p ppppppp q qq'qq"q&q.q3q5q6q8q;q?qBqCqDqMqOqPqRqSqTqUqWqmqwqxqyqzq{q}qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq q q q qqqqqqq t7t Bt7t7t7t7t7uuuuuuvv'vv"v&v.v3v5v6v8v;vBvCvDvMvOvRvSvTvUvWvmvwvxvyvzv{v}vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvww w ww w)w3w5w6w7w8wVwfwgwhwiwjwkwlwpwqwvwywzw{wwwwwwwwwwwwwwxx x xx x)x3x5x6x7x8xVxfxgxhxixjxkxlxpxqxvxyxzx{xxxxxxxxxxxxxxyy y yyyy y"y&y)y.y3y5y6y7y8y;yByCyDyMyOyRySyTyUyVyWyfygyhyiyjykylymypyqyvywyxyyyzy{y}yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzz z zzzz z"z&z)z.z3z5z6z7z8z;zBzCzDzMzOzRzSzTzUzVzWzfzgzhzizjzkzlzmzpzqzvzwzxzyzzz{z}zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz{{ { {{{{ {"{&{){.{3{5{6{7{8{;{B{C{D{M{O{R{S{T{U{V{W{f{g{h{i{j{k{l{m{p{q{v{w{x{y{z{{{}{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}} }}}} })}3}5}6}7}8}?}A}B}C}E}F}I}N}O}P}Q}R}S}U}V}W}X}f}g}h}i}j}k}l}p}q}v}y}z}{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~"&.3568;BCDMORSTUWmwxyz{}"&.3568;BCDMORSTUWmwxyz{}  "&).02?ABCDEGHKLMNOPQRSTUVWXfghijkmpqvwxyz{}  DRVWDRVW DRVWDVWDRVWDRVW DRVW ABCMOR UW CCUVCUVWCUVW  F 'FVW'''''? FVW????? VW DIRTUVWX DIRTUVWX DIRTUVWX DIRTUVWX DIRTUVWX DERU W   VWVW ?ABCEIKMNOPQS ?ABCEIKMNOPQSVW  )35678?ABCEMOPSUVWfghijklpqvyz{   DIVWX"&).3568;BCDMORSTUVWlmwxyz{}VW  "&).02?ABCDEGHKLMNOPQRSTUVWXfghijkmpqvwxyz{}'"&.3568;?ABCDEMNOPRSTUVWmwxyz{}?ABCEIKMNOSVW $)2356789;DRTUVWXfghijknopqvyz{ $)2356789;DRTUVWXfghijknopqvyz{ s "&)t.03 568;(?ABCDEKLMNOPQRSTUVWX[efghijklLmpqt/uvwxyz{}~7'?{ s "&)t.03 568;(?ABCDEKLMNOPQRSTUVWX[efghijklLmpqt/uvwxyz{}~7'?{"&.034568;{BCDH>MORSTUWmwxyz{}~ s "&)t.03 568;(?ABCDEKLMNOPQRSTUVWX[efghijklLmpqt/uvwxyz{}~7'?{ s "&)t.03 568;(?ABCDEKLMNOPQRSTUVWX[efghijklLmpqt/uvwxyz{}~7'?{"&.034568;{BCDH>MORSTUWmwxyz{}~"&.034568;{BCDMORSTUWmwxyz{}~358 )35678DRVW 7u m  G;V<OABCDEKMNOPSU^>`?uuuuFV^uVW7u m  G;V<OABCDEKMNOPSU^>`?uuuuFV^u   s "&)t.03 568;(?ABCDEKLMNOPQRSTUVWX[efghijklLmpqt/uvwxyz{}~7'?{! *7*Lv | u   "/Qk  ,A m >{ ,  * T)  }   8 8 D 43 "g Copyright (c) 2010, Yanone (http://yanone.de/typedesign/). All rights reserved. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: http://scripts.sil.org/OFLYanone KaffeesatzBoldYN: YanoneKaffeesatz-Bold: 2010Yanone Kaffeesatz BoldVersion 1.002YanoneKaffeesatz-BoldYanone Kaffeesatz is a trademark of YanoneYanoneYanoneYanone Kaffeesatz was first published in 2004 and is Yanone's first ever finished typeface. Its Bold is reminiscent of 1920s coffee house typography, while the rather thin fonts bridge the gap to present times. Lacking self confidence and knowledge about the type scene Yanone decided to publish the family for free under a Creative Commons License. A decision that should turn out one of the best he ever made. It has been downloaded over 100,000 times to date from this website alone, and you can witness Kaffeesatz use on German fresh-water gyms, Dubai mall promos and New Zealand McDonald's ads. And of course on coffee and foodstuff packaging and cafe design around the globe. In 2009 he reworked much of the typeface and it got published in FontShop's FontFont Library under the new name FF Kava. You can read more about it in an extensive article by Yves Peters on the FontFeed.http://yanone.de/typedesign/http://yanone.de/typedesign/SIL Open Font License, Version 1.1http://scripts.sil.org/OFLYanone Kaffeesatz BoldCopyright (c) 2010, Yanone (http://yanone.de/typedesign/). All rights reserved. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: http://scripts.sil.org/OFLYanone Kaffeesatz BoldRegularYN: YanoneKaffeesatz-Bold: 2010Yanone Kaffeesatz BoldVersion 1.002YanoneKaffeesatz-BoldYanone Kaffeesatz is a trademark of YanoneYanoneYanoneYanone Kaffeesatz was first published in 2004 and is Yanone's first ever finished typeface. Its Bold is reminiscent of 1920s coffee house typography, while the rather thin fonts bridge the gap to present times. Lacking self confidence and knowledge about the type scene Yanone decided to publish the family for free under a Creative Commons License. A decision that should turn out one of the best he ever made. It has been downloaded over 100,000 times to date from this website alone, and you can witness Kaffeesatz use on German fresh-water gyms, Dubai mall promos and New Zealand McDonald's ads. And of course on coffee and foodstuff packaging and cafe design around the globe. In 2009 he reworked much of the typeface and it got published in FontShop's FontFont Library under the new name FF Kava. You can read more about it in an extensive article by Yves Peters on the FontFeed.http://yanone.de/typedesign/http://yanone.de/typedesign/SIL Open Font License, Version 1.1http://scripts.sil.org/OFLYanone KaffeesatzBold`  "#$%&'()*+,-./0123456789:;<=>?@BCDEFGHIJKLMNOPQRSTUVWXYZ[\]_bcdefghjikmlnoqprsutvwxzy{}|~     !A^`a.nullperiodcenteredlongsEuroffffifflt_zg.ss01f.ss02ampersand.ss03ampersand.ss04germandbls.ss05z.ss05t_z.ss05uni2074Deltauni00A0macronuni0237` :latncpspkernHVQh`vs tH.@N`rx~$*< F T 8 J,V<vfJT6v4N`8J(:@ | !Z""#X$$%>& &'<((() )**+t,b-P.:.H.V./01181j1122B22223 323D3^3x334.4|455b5t5556h77 8 8\99$:Z;<;<==>@D@B8CD,DDDE`?uuFV^O O =# = =O O - =IKOP, =IKOP'IOP+ =IKOP#O* ;Omwxyz{}* ;Omwxyz{}* ;Omwxyz{}* ;Omwxyz{}* ;Omwxyz{}* ;Omwxyz{}$ OP 8 0Omwxyz{} # OP # OP 6 ;OPmwxyz{} 6 ';OPmwxyz{} 777)';Omwxyz{} =fghijklpqvyz{ =fghijklpqvyz{; ;=Ofghijklmpqvwxyz{}; ;=Ofghijklmpqvwxyz{}; ;=Ofghijklmpqvwxyz{}: =IOPfghijklpqvyz{);Omwxyz{}(;Omwxyz{}M 0=KOPfghijkmpqvwxyz{}      O  '''??? =I =I =I =I =I = + =IKOP, =IKOP: OPfghijklpqvyz{  I*;=Olmwxyz{}M 0=KOPfghijkmpqvwxyz{}8';OPmwxyz{}&IKO/7u " n;V<O=KOP^>`?uuFV^ ;fghijknopqvyz{ ;fghijknopqvyz{Us0;(KOP[efghijklLmpqt/uvwxyz{}~7'?{Us0;(KOP[efghijklLmpqt/uvwxyz{}~7'?{'0;{Omwxyz{}~Us0;(KOP[efghijklLmpqt/uvwxyz{}~7'?{Us0;(KOP[efghijklLmpqt/uvwxyz{}~7'?{'0;{Omwxyz{}~'0;{Omwxyz{}~;fghijkn,7u  G;V<OKOP^>`?uuFV^07u  G;V<O=KOP^>`?uuFV^Us0;(KOP[efghijklLmpqt/uvwxyz{}~7'?{,`(Z.80^lz,V\Rd$V"`*   "&)t.3 568:<?ABCDELMNQRSTUVWX )<ABCEM <"&.34568BCDH'MRSTUW* T "&).79?ABCDEGHLMNQRSTUVWX_dxT )35678  )7<?BCEOl3H( )l )3578H )378V '  '<l'{{ { <1"&.3568ABCDEHuMRSTUWmwxyz{}W <VWH] <H^ "&.03568<BCDMRSTUW "&.03568<BCDMRSTUW "&.03568<BCDMRSTUW "&.03568<BCDMRSTUW "&.03568<BCDMRSTUW "&.03568<BCDMRSTUW?ABCEMSUW  "&.?ABCDMRSTUVW  ?BCMSUW  ?BCMSUW  "&.03568?BCDMRSTUW  "&.03568?BCDMRSTUW   B<B777#@\H0>L6Tz,B`~6p*H^ X  T @ < BL"&.03568BCDMRSTUW  )35678<V  )35678<V  "&).035678<BCDMRSTUVW  "&).035678<BCDMRSTUVW  "&).035678<BCDMRSTUVW )35678?ABCEFNQRSUVWX"&.03568BCDMRSTUW"&.03568BCDMRSTUW$ "&).2?ABCDEGHLMNQRSTUVWXl < <DRVWDRVW <DRVWDVWDRVWDRVW <DRVW <ABCMR UW <CCUVCUVWCUVW < <F I FIVW''' <FIVW??? <VW <DRTUVWX <DRTUVWX <DRTUVWX DRTUVWX <DRTUVWX <DERU W  < <VWVW?ABCEMNQS <?ABCEMNQSVW )35678?ABCEMSUVW  <DVWX"&).03568BCDMRSTUVWVW$ "&).2?ABCDEGHLMNQRSTUVWXl"&.03568?ABCDEMNRSTUVW?ABCEMNSVW !#$%')*+-/12356789<DRTUVWXl !#$%')*+-/12356789<DRTUVWXl  "&)t.3 568:<?ABCDELMNQRSTUVWX  "&)t.3 568:<?ABCDELMNQRSTUVWX"&.34568BCDH>MRSTUW"&.34568BCDMRSTUW358 )35678<DRVW < mABCDEMNSUuuuVW mABCDEMNSUuuu <40  cc   uuum L2 t t t>q 9f{}0678 &)+.35=$?I-KX8Z\F^^I``JeqKt{X}`f|( 0:;<=FKOZ[efghijklmnopqtu v{}#*16:;=>  &)+./135:<<?ACF!HI%KN'PX+__4dd5fq6w{B}}GHIU[bjrsuvx)23+*+11 23 !"#$%&'/0 """"&& '**,-.,-.+/0*()-$$..........+/#-()*(%%&%'  !".....////,))))% )+'#!,"  @ latn  aalt>dligFhistLligaRss01Xss02^ss03dss04jss05pss06v   (08@HPX`hHFPb`^\Z^|Y|Xp GJ\PVPP&YH&QRDEXDEQXslixmpp-slix-1.4.2/docs/_static/fonts/YanoneKaffeesatz-Light.ttf000066400000000000000000003741601342457644200247450ustar00rootroot00000000000000GPOS~qGSUB@Q'OS/2S``cmap C cvt &fpgmY7 sgaspg qglyfr4headc6hheaNT$hmtx" kern $#locaB~ maxpx namelZ\2post,nprepS8o&k_< |%`"18 89Zj s~,2@ KYN @ 89  " &B=$#&E&*-"+13 $$$1&'&4($1**'57a'8Q9G9$76.6I777&r7&{7q!o7 i/ o$:K()w&!6699P979(7p'*:d)rlb6[%&&;}+.a'Q9Q9Q9Q9)7&&&&&W/7777i1o$o$o$o$o$o$l$K(w&w&w&w&-7*::::rr9s$%q!p'il4A+%&&*%*&(&3-=+"*  gq- .+4h I==44=m=j=U,094 %sao/"+o4*:.:""O1\*.!7$%31(433.9r73#n&   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYjkmov{[`^a]l}\eZ_dfizbhpgqnsturxyw~cHr@2";>]^z~1BSax7    " & 0 : D t !"!&"""""""+"H"`"e% #<?^_{1AR`x}7    & 0 9 D t !"!&"""""""+"H"`"d%qQE/+``F++y`ߝޫބޙ,rtvtzYZ[\]^_`abcdHr@2";>]^z~1BSax7    " & 0 : D t !"!&"""""""+"H"`"e% #<?^_{1AR`x}7    & 0 9 D t !"!&"""""""+"H"`"d%qQE/+``F++y`ߝޫބޙ,rtvtzYZ[\]^_`abcd,K PXYD _^-, EiD`-,*!-, F%FRX#Y Id F had%F hadRX#eY/ SXi TX!@Yi TX!@eYY:-, F%FRX#Y F jad%F jadRX#Y/-,K &PXQXD@DY!! EPXD!YY-, EiD` E}iD`-,*-,K &SX@Y &SX#!#Y &SX#!#Y &SX#!#Y &SX#!@#Y &SX%EPX#!#!%E#!#!Y!YD- ,KSXED!!Y-+++dZF2$+^M<++nZF2$++ E}iDK`RXY27; B    &k+// /EX/ >YEX / >Y ++ и ии!и%и'и,и.01%#7##7#46;7#46;7>337>33+3#'37#:q@ 5I >qF :O qqO \O \|H UH U|.|Bo;d7+;/EX/>YEX/>YEX7/7>Y(+ и/01.54>732654&/.54675463.#"# -4-<6&p F?92 7&3<{$2%   ;1 6$i82>a y!&*r#@)5*^ =/?Q(8+0 +++EX/ >YEXF/F >YEXH/H >YEX+/+>YEX@/@>YEXB/B>Y+5014632#".732654.#"4632#".732654.#"&'.5=KB!2!HB 3#5 (/ (0KB!2!HB 3#4 '0 )0    $;L'\ aW*G5^[ (H7*5 BK*6 >WaW*G5^[ (H6*5 BK*6 > =a}@$M[ϸ\//и/$и$/)9и9/\AиA/TEX/ >YEX/ >YEX/ >YEX/ >YEX6/6>YEXY#$и<Y012&#">54&'>3:#'#"&'#".54>7.54>.'326AO2L) ,ET) 1#8+H  , +L98G) )$.-='T 0%-: "% boq0$DPm ,=* %*.) & ( 3B!9/% 23L I'47B?0E:%"+/EX/>Y01#".54>32} .2 &*#$+' 3,D`{S:XsVRlO5Om:%"+/EX/>Y014.'>32#"&'>,3 &+$#)& 3- DVoQ4PkSStX<Sy &//4>&7/&>&67'6.'    L  p a o0  2      *k?+ и//+и 01#5#46;54633#    -T+ /+0174632#"&'>7&7  +3+ * +3 +0146;#+  1U $+EX / >Y0174632#"&1( ) //01"#>3H  $X P//и/ EX / >Y+ 014632#"&732654&#"$VcaNQebP;8?E80CH9(rxv$Y%, + /EX/>Y 01.54>323:767#!&454>3N$,(  X  (64 )   $X-V &+EX/>Y+++9и/и/$901.54>32326767#!&5467>54.#"A.@'+8! 7R]% " "XO6$4S  "1:ece:  7a]]3 1X:7+ +1&++и/01#"&546732>54./>54.#".54>324'!;S2962$&=*-<AK#5J-?)*5 !,/#7':]B$6I*$- %$T/   !*4-&&\%,n&+ и&"/EX/>Y +и/и/ и&и"'01%:727+#5#.54>7>329   F !+24 : 63)   :JRNDyCKW]+'X.O%+и/ +. +*+ и/ и/01#*&#"'>36#"&546732>54.#"~ !(0;!"JA+7X?<9%G1C)!3>(9X  (M@ClM*  "%@V00YEX/>Y$++01"632#".54>32."32654& #>/2j!7(]a8C$ $?T/'-JX5 /%AE:#JrO[/J3|,QpEp_*   c7ZA#iTOL4X/+ и /01"&5>7""#"&5-SF8:CF /FX0 XJc b-(&7LM/5/и/Mи/"и"//5HиH/EX/ >YEX/>Y8*892C29012#".54>7.'.54>.'32654&">54.=3 #+#$WW1F-#-! />h#.#BA<@$W-" $+ "* #?3!5* D3Ti3E)!<3'  >!+@*k  *4>NK>.6u +1  $+"+ $b /Q0/$/и/0 и /$ -+*+!+012>7#".54632#"&546726754.#"2I2J6!8)]a5B% }~"2$U)G.'BG?3)NoG,/1J2~#KsP  306ZB%`bOK1 B+ иEX/>Y +014632#"&4632#"&1* 7+ и /и/// +014632#"&4632#"&'>7&2  3+ * * ++01463!#463!#* Q  Q X 9y:/+/:и/+и/#и#///EX / >YEX / >Y 00174632#"&2'&'&5467>54.#"&54>W83'%(  !  &!" 1!*(!7(,D90# ,127! &  5Cev !+++[J+n=+JEиE/JGиG/Jfиf/EX/>YEX2/2>YEX8/8>Y&+XM+Bi+2as014.#"3267#".54>32#"&'#".7>3265<76&#".547>3232>'.#"3267;]CDnL)(Ie=Vm' "@Z8.UK=,3]NKqJ%$#  7,/")6,%"2 ;09: %#  ' .XNoF &XjS!  $  0   3C#/ ?s 0$"*!lEX / >YEX/>YEX/>YEX/>YEX/>Y+ 01%##*'632#*/3'.'B.     0   P;4**4;7q$1o2/+/2и/+$%EX/ >YEX/>Y1+#%0146;2+32654.+32>54.+7 d<0")%'9Dz7K+<&GC.$"0C !;0,<& 8.K]4;J", *G5+1'I)C%+EX/ >YEX / >Y  012.#"3267#".54>1,4)!+#8 !-YEX / >Y 012+4634.+32>0I05I*y "3"EC2&!IxWvV  Hb=H95f+EX/ >YEX / >YEX/>Y+ 01%+47>;+3+4  3#" 95m+ EX/ >YEX/ >YEX/>YEX/>Y + 01347>;+3+#9  " $g*k+//и/+&и&/ EX/ >YEX!/!>Y+!012.#"3267#54;#".54>.;*'=)#0$)b!.,F08N Gz^cv>  "  LnsQ7y//и/ ииEX / >YEX/ >YEX/>YEX/>Y+01#"#4>2334>3"#B    cQ6n /+EX/ >YEX / >Y014>3"#6   Q5+EX/ >YEX / >Y014>3#"&546732>5 1*31+ !@2" '46z${+EX/ >YEX/ >YEX / >YEX/>YEX/>YEX$/$>Y014>23>32#*'.'.'"#6  ! ?h$  %%&   .) EhN7 75 5+EX / >YEX/>Y0173+4>3o 4' 72{3//3и/'и/EX/ >YEX/ >YEX/>YEX2/2>Y0146;67>7>;"#+'.'"#7%m  i*  K:8   (&T Q! 7KRKTL712/(/2и/и/и/( и /(-(1и1/EX/ >YEX-/- >YEX/>YEX0/0>Y01.'"#4;.4&4=4>23+  'x  &'8BA;=GJ>,*:CA9;HLB/Q &'](//(и/ EX/ >YEX/>Y#014>32#".732>54.#"&0H01D*.H10E+= /!"1 /!!1caT&$SdeW&"TYPnF!JvUSqF!Ky7[e//и/ EX/ >YEX/>Y +014;2+"#4.+327 h^Q0B';  * D?sfX9Q42$&z#78/./8и/. и/$EX/ >YEX/>YEX/>Y+)3014>32327#"&'.732>54.#"&0H01D*#5$%-5-?)= /!"1 /!!1caT&$SdXW0 0# :B&TVPnF!JvUSqF!Ky7[&01/'/1и/и/' и/'и- EX/ >YEX/ >YEX/>YEX/>YEX&/&>Y/+,0147>;2*'.'"+"#4.+327h^Q70    ;  * D?sfXU`3I`< DfH, 2%!S0}1/%/1.и./ %.и/EX/ >YEX/>Y""9*9012.#"#".546732654./.546C; 7(4>w**B/#1!5+A7l""W .&2{-N1'C1  D6%$%q&B&;LhA+EX / >YEX/>Y и01"##543!#  T &' 7`//и/EX/ >YEX/ >YEX/>Y01732674>23#".54>23o2*"? ]7>G&  #?. p] %=R-GEX/ >YEX/ >YEX/>YEX/>Y01&6;>7>;#"&'.'& ,oe -    J=i..i= >!Wel3y=iEX/ >YEX!/! >YEX#/#>YEX&/&>YEX1/1>YEX4/4>Y01&6;3>7>;3>7>;#"&'.'#"&'.'& ,\ M 'X  R -  K N    I3.+4]1 n3-+(e4 >D .78  Xdk3x )XEX/ >YEX)/) >YEX/>YEX/>YEX/>Y01+'.'+&>23>?>23 9D  'D 0 3  0 k 1+$ [2kGy2-% $-3od@+EX/ >YEX/ >YEX/>Y01&>23>?>23"# 0  ,  GA22BFbpMEX/ >YEX/>Y 990143!3267#!&547#"2*   +  Y:52 +EX / >Y+ 01+3+463 5!k  ' //01#&63$%'   8 62 +EX/>Y +0146;#4>;#  !  n +01463!# P ^G //01".'>32)3, !G&"   $A&;YEX/>Y*+"40147>32#".54>3254.#"&.#"3267>$W7<<B54(*6- 0L%(#* 5DYEX/>YEX/>Y!01463>32#"'732>54.#":!;*1"&:DM&7 -' $2-I6nD& 7hW-<$(0$C +EX"/" >YEX/>Y"01.#"3267#".54>320 (,$(0 ?3"7'!5@#1  /YK=M+ 5bQZk9)Y$i%//%и/и/EX"/" >YEX/>Y" 01.#"3267463#".54>32!,"(*!H-!;,-="3 4T=BP* 5 3aSHhB &W$45/2/5 и / и/ *и*/2/и//EX/ >YEX/>Y* +%012#3267#".54>":>7>5.B< FA8+"?%2$<+!3@(!/65SK ?7C$    2\K`r<2 *OB!<;T(+"и'EX/ >YEX/ >YEX&/&>YEX / >Y +!и"и(01<>;54>32.#"3++=/:-)#(!hZ ) ?:F%  2*A I !;=M`a/^/#и/#и/a-и-/TEX/ >YEX/ >YEX(/(>Y (YNܹ>F0123#"&'#"'#".5467.7467.54>"32>54&.'32>54& | '>+#'51$0I22>" 2 $%%-)!& 4+ -*""2!: =.'C1 !   '6*&%B 0R0/D-+AH0$(7 >8@:  )6_!}"//"и/ EX/ >YEX/>YEX/>YEX!/!>Y01463>32"#4.#""#6!C$,  ; "2l"k6w&6%EX / >YEX/>Y01=~&=+EX/ >YEX/>Y 019i2=y+,и89/EX/ >YEX/>YEX/>YEX2/2>Y9*501463>32"#>54'."#4#">9!O 1'"# '"  !#   3=#>.0("5'$7&/ &3# &/9p /+EX / >YEX/>Y017"#463p ! 91 +*+#+EX/ >YEX/ >YEX/>YEX#/#>YEX-/->YEX1/1>Y'и'/01.#""#>32>32"#4&#"#"#, $K%%. D %, *. V%6#p1+ \ 7bh//и/ EX/ >YEX/>YEX/>Y01>32"#4.#""#7#U*(5   < "4#f' Wa#]$//$и/EX/ >YEX / >Y0174632#".732>54.#"YN':'VP$:); $& $'z;dM}7fQAQ-3Q;AQ.2S9;n&'//'и/&EX/ >YEX/>YEX/>YEX / >Y$01632#"&'#"&532>54.#"9AO&=+!29 /  7()%(1%(0R=fzA 3bT4C'(:_%&//&и/иEX/ >YEX/>YEX/>YEX / >Y "01#"#".54>32.#"367_  $L2*6J,#38 #>D$551[LMnF $ qYEX/>Y 01>32.#""#7 E!%# 1%  `'K+,/"/и/,)и)/ "и/)и/ &и&/EX/ >YEX/>Y 9% 9012.#"#"&54732654&/.546#1 3 .;l#!WM:D 3#2:i)W !  M9&BE %(-L25=%t +и  /EX/ >YEX / >YEX / >Y иии 01#46;54633+3267#".5eJ =#ym $!!& )9":e\//и/EX/ >YEX/ >YEX/>Y01%#".54>2332674>23e!Q&,9!  &4 ,C0=#/ LGEX/ >YEX/ >YEX/>YEX/>Y01&>3367>?>23#"&' +    '  -^&-**-&_-QEX/ >YEX(/( >YEX///>YEX1/1>YEX4/4>YEXC/C>YEXE/E>YEXH/H>Y01&>233>?>;3>?>231#"#"&/.'#"#"&'.'& (  # $+  "         0X#)%3JR)y?HK$T/GEX/ >YEX/// >YEX/>YEX/>Y01"#'.'"#'&>3>?>23 4  -  3  -  P*(! !'+DP#!!$D?W(<EX/ >YEX/ >YEX/>Y%01&>34>7>3#"&54673267! K  C  "($% #/3,+5.>&8IN${EX/ >YEX/ >YEX/>YEX/>Y  к 9 9013267#!&'&547#"&4543Ht08 y%: o  8/ "+ /EX/>Y01"#4>3  6: I+и//EX / >YEX/>Y 01#"&54632#>3 * f  [tc~1m(+"/1/EX/ >YEX-/- >YEX/>YEX#/#>Y 01.#"3267#5.54>75463, -&+-"-(3  1\N@J%   j 2YGOh?j %60+и0*EX/ >YEX#/# >YEX6/6>Y+ + $и(и+и/017#46;5'#46;&>234>?>233+3+"#qsKo 0  . ob\o :90E>..>F5>`  +и 014632#"&74632#"&&@%5GI6&+ +.@+;1+)C++#+01.#"3267#".54>324632#".732>54&#"#    "  !k]/G/e^.H2,&6#"9)KE#:)-&"/)1: zsi3T>lq2U>2C(-C/bN+Ev%K%EX/ >YEX/>Y0174>7672#&'.34>7672#&'. !*   $%   ,! !)   #$   +! $+3> !&-58.&!  @5-&")09 #)21(#  80("&@!?Gi+D"+*@++*/и//@5и5/D; ++F:+014632#".732>54&#"7476;2"&#.'##74&+32&k]/G/e^.H2,&6#"9)KE#:)Z1-&)   !i3si3T>lq2U>2C(-C/bN+ES)7 4/'faB //01"&'&'>32.( ).B  +! ";: + +014632#"&; ?}$9EX / >YEX/>Y+ 017272632#"'&'67>7632654&#"7G , ?)$  a+%KGEX/ >YEX/ >YEX/>YEX / >Y01%&'.54>767&'.546767&'.54>7672.546767 "+   &%   )! !+   %!  )! &-5@ !'.85-&! >3+$ #(08 #(1)0-# 90)"= ;uYEX / >Y  101#"&54632".54>7>54&7>326783'%(   &"" 0!+[!7(+E90# ,027! '  & >f& a& _|& Jz& ]m& g%+иEX/ >YEX/>YEX / >YEX / >Y++01%+5##*'>;+3+'3f W  H  3#" 676'?IMI+EX/ >YEX,/,>YEX./.>YEX / >YEXD/D>Y&>+  ,8012.#"3267272632#"'&'67>7632654&#"7.54>1,4)!+#8 ( , 4($8E#  HqVi9 2)$  TF~hR95&$>O9;&$a98&$G95z&$]U&(>)&(a)&(z&(]7|&-]&&.>u&&.a&&.m&|&.X&z&.]{WX7&'7'>76'R]^ST_^]ik ^^ ki/):FG/*/G и /и/*и/*!и/ ;)и)/EX/ >YEX/ >YEX/>YEX&/&>Y6B01&'4>7.54>327#"&'4&'32>'.#"Y /H0&9   .G1&; !$%"2!1! &!1   *caT&5 *[eW&BJj#U`dXG#FyIHh#!Ky7&4>x7&4a7&4p7z&4]~d&8a1Fy9F+,+%+EX/ >YEX/>YEX=/=>YEX?/?>Y"6014632#"&546732654.54>54.#"#"'&'67>59VG6J."'.'QE:< ,#+:'.'(1( 2#82 %TG*2! !$.:%FK  -00)$&)"*!"!>J $A&?>F$A&?a$A&??$G&?*$A&?]M$A&?_$?J[lS7+.[+j+7и/. и /&и&/[?иjgиg/EX/ >YEX/ >YEX+/+>YEX2/2>Y+ E XиE\01467>32>32#"&'3267#"&'#".54>3254.#"..#"3267"3267>5.(K>05 $'B<.0M)$@%2&>@54(*5-  +E%("/ %!K(   #,PK B =G$     =55A$ T 1)$)  %E9 $<8(?0H A+EXF/F >YEX$/$>YEX&/&>YEX/>YEXY6+F$001.#"3267272632#"'&'67>7632654&#"7.54>320 (,$(0 5, , 0"!5@#1  /YK=M+ 3)$  U6_KZk9&W&C>^&W&Ca&W&CW&W&C]e&>-&a-&&]7b&LEa&M>Ra&Maa&MJa&M5a&M]X*c;+и +++01463!#4632#"&4632#"&* Q u) )9E~F/*/F и /и/*!и/ :EX/ >YEX/ >YEX$/$>Y5A01.54>7.54632>7#"&'4&'32>'.#"<  WN$7    UL(< 12, && %'  P9z#  R8}&(<SUJ3Q4#8!2S:e&S>`:e&Sa:e&SX:e&S]f?W&Wa?W&W]S9r /+EX / >YEX/>Y017"#463r ! $X';YEX/ >YEX/>Y"%+-и-/01%+5#".54>32547>;+3+32>54.#"W C/0E,/H0-> /!"/ ,!!1 3#A'##SiaU'#)" PoG "KwUSrG "Lz%`0DT1+J;+R+Jи и /ROиO/EX/ >YEX / >YEX%/%>YEX,/,>YJ+%6и@E0174632>32#327#".'#".732>54.#"%":>7>5.%XN0A "')B< FA8)L0%2*$ )W&<); $& $'}(!056z$/!PK B:D" "   N7fQAQ-3Q;AQ.1S )PD$<8!S&2Q'K&QSdz&8]Lp&9_N&XD4T9+EX/>YEX/>Y +014632.#"#"&'&'67>5;WN4@ 2#94 KTC 6E  C// //01632#&'.'#"&'o "D///01".'>32>767{&)# &$D )$ ! (+"oA +01432#"&! ./>  ++014632#"&732654&#"2,,*0-).+0+'3.-&2L%#+й и /01>3232>32#".#"#"&'&4"   p)   +3 +01463!#+ Q 3 +01463!#  %Cy+ //01#"&54>32v l92! > &>z+ //014632#"&'>7&) 92! > &zd+ //0174632#"&'>7&) ;91" = %C%G&//&и/и/!и!/ ////01#"&54>32#"&54>32v  l92! > 92! > &>%K&//и/&и/!и!/ ////014632#"&'>7&'4632#"&'>7&  92! > 92! > &d%K&//и/&и/!и!/ ////0174632#"&'>7&'4632#"&'>7&  ;91" = 91" = 3[ + +014632#"&3  " '%%$-U&'5=!1AQaqbR+Zj+B2+:J+"+*+"и/EX/ >YEX/ >YEX/ >YEX/>YEX/>YEX=/=>YEX]/]>Y5O+/=G5UиGgиOo01&'.54632#".732654.#"4632#".732654.#"4632#".732654.#"    $;L'\KB!2!HB 3#5 (/ (0KB!2!GB!3#5 (0 )0KC!2!HB 3#5 '0 )0  =b|AWaW*G5^[ (H7*5 BK*6 >V`X*G6^[ (I7*5 BK*6 >M`X*G6^[ (I7*5 BK*6 >%%EX/ >YEX/>Y0174>7672#&'. !*   $%   ,! %,4? !'.76-&!  >4,&+%GEX/ >YEX/ >YEX/>YEX / >Y017&'.54>767&'.546767 !+   &%   )! &,4>  &-77.'! ?4,%L %EX/ >YEX/>Y01##"&'632D   ^C7+и/и/72и2/7:и:/EX-/->Y +++-"2и6и:и>012.#"3+3+3267#".'#46;547#46;>/$ *)% n!(6 ) :-[B\J (29^   =4&-8B"   .WI-&CR,"1C36+-1++и/-(и(//1/6/EX/ >YEX/ >YEX/ >YEX;/; >YEX=/= >Y27и801476;67>?>;#74>5+/###5476;#20  !# m ]  r"$ 0V[J "$g*3 +01463!#* Q )5CD/4/Dи/и/и4/&и/1и1/6и47EX / >YEX'/' >YEX6/6 >YEX/>YEX/>YEX1/1>YEX3/3>Y!+6иии/-и.и5и!>и>/01+#<>;54>32>32.#"3++'35467.#")L="066?V$)!h[) $ !I J9@  # 2*A I0? 2 -' f 12//2и/#*и#-и-/0/EX / >YEX/ >YEX#/# >YEX/>YEX-/->YEX///>Y )и*и101%"#463<>;54>32&#"3++U !<2;06)5)!TF) 0 ?=F$ 3,> I ^-Ѹ.//.и/и/&и)и)/,EX/ >YEX/ >YEX/>YEX/>YEX)/)>YEX+/+>Y +%и&и-01<>;54>32+.#"3++ <-4":)"dV* ?=F#  G1,E I>6CMB+(-+DI+(иB/и5и-7EX/ >YEX / >YEXB/B >YEXM/M >YEX*/*>YEX,/,>YEX2/2>YEX4/4>YEXI/I>Y+ и /&и'и.и/и6и<и;54>32>32&#"3++#+747.#"3"#463<"156A 05)5)!TF)*% "   J9@  3,> IIo?& -'L7=J++$+и$,и>иHEX/ >YEX+/+ >YEXI/I >YEX/>YEX/>YEX/>YEX!/!>YEX#/#>Y8 +ии%и&и82и2/ CиC/01%"#.#"3++#+#<>;54>32>32347.#"37  #TF)*L<"1549*8$ " 0*G II J9@    >' -'Lx&RX*-8a$56/%/и/6и/%-EX/ >YEX/>YEX/>Y (201"&546732>=#".54>32.#"3267)9%2ZG:K+"# )+"и(EX/ >YEX/ >YEX%/%>YEX'/'>Y +!и"и)01<>;54>32.#"3++ <'#   hZ) y%. s I.CW-+/C/2++'(+и/'$и$/?и?/017.5467.54675463.#"326732>7#\PA/"]I#5#G1Yd+KZ+;/014&#"26767>>7675463>76732767#".5#32>7#".54>32#& (#; ,*     (/# ?>6x1(($6$&<*+F2*"A1/RAOd !+    ">3AH# 4^KHlI% !9- 4yHI/A/Iи/и и / и /!иA.EX"/" >YEX/>YEX/>Y+>3+"01#"&'&'67>54632.#"!#".546732654&'.'7r XP)2#:3J8,2H0 * '$@K-$2\ T@ 9I8 ':'0R<"   ZM46 yM)2"+EX/ >Y+01463!#".546732654&'.'78+1I/ + '#AK.#2! ':'0R<"   ZM46 y78/0/8и/ ии0/EX / >YEX/ >YEX/>Y-"+и01##46;5463!#".546732654&'.'7 _ R"C8+/G0' % @F.#2Q! ':'0R<"   ZM46  w()//) и /и #EX/ >YEX / >Y +и !и #012+#46;4634.+3+32>0I05I*y,  "3"Eh ZC2& JvUxW X" Hb=H5b +и //EX/ >YEX / >YEX / >Y0173+.?4>3of g) ) !"'G  , G +и  //EX/>YEX / >Y017"#.?463p3 3 2 2!>R=![A +EX / >YEX / >Y+01&547>323#!&546;G-+# _  qp    4=a9T!1+EX/ >Y.$+7+и/74и4/014632>54.#".7>32#"&'&54732654&'.#. "$; E6". 8#&`M+9 1'5?&- &    =, 3"FE  ,'$4!^+2 $+EX/ >Y+)01.54>323#!.5467>54.#"L*5%/ )=F"I=')6b   (&D?=   %>;9  4v$i +и и/ / /EX/ >YEX/ >Y+ ии/013+#"'5#&547>7>327+F :  &04   3*)"   <L  KKC 3>D=S&RS/K/Sи/ K4?и?/EX / >YEX / >YEX$/$ >YEX/>YEX/>YEX?/?>Y+/P+и?901&547>323#!&546;#"&'632.54>323#!.5467>54.#"G-+# _  qb r*5%/ )=F"I=')6p    4x     (&D?=   %>;9  =0&'g=-&'mU; q+и////EX/>YEX/>YEX / >YEX / >Y01#"&'4632#"&'4632    ]  k  ,nb ///01#"&/&547%t   hn  0ge// /01'4632.547   hn  9r/ //017#"&'7'i     i4j 7;!+и!3+++'+01#"&54654&'&547>54&54> ,4   &KB! )9$1&_<% '->`,"(=I4`;)"   9d0!, j=;6 + и6)+!+,1+01.547>54&54>7.54654.'.54632 -4   ' KB! )9$1&`<$ (->_-"'  >H125)"    9d0 -  E! + и/01>3232676#".#"& '!' '$        % )+и/%EX/ >YEX/ >YEX / >YEX / >YEX/ >YEX"/">Y 01&632'76'.7&547#"&'>   ~    >  -  D a2 ?//и/ ++014632#".732654&#"a2,,*0-! )0+'3-.#ZS+h +и/ и&EX / >Y!+'+  и'01+.5#46;4>32.#"3#%I <*:#% %) 9p6 MBOg=   -RA/@DVW/K/и/и/Kи/W;и;/%и%/;(и(/;T?EX/ >YEX"/">Y "-012.#"#"&'&454732654&'.'.5467'.546>54&'.'08 - */ !$"  PB09 - +. ## " P// !  + 7 &1;!  +8 &1;z% *"% +!+#0++$*+и/EX/ >YEX/>YEX/>YEX'/'>YEX*/*>Y01%#"'5#".54632.#"367#"&'4632e  4 =/i]'67"AF$-.    5YE $ ws:J*  v!YEX/ >YEX/>Y+ +и ии 017#"'7#46;7#46;76323+3#0-f l. 1/l p- |||4w; 3DkE/,/Eи/,4)+ 1+ ии/)9и9/1@и@/014>32>32&'.'#".%32654&'&3267'.'&4 4') ;19D@9  #5>+!0? $. &. +  2)!$6HFIH1##7'4O !.0"+A!5$ 1 0* I+ и//++и 01#5#46;54633#463!#   Q   :=g!"//" и /EX/ >YEX/ >YEX/ >YEX!/! >YEX / >YEX / >YEX/>Y01%#"'#"&'463232674632gQ*C   5*0  $ 0(  .'z(// (и/и/ EX/ >YEX/>YEX/>Y#012.'>32#"&5464&'&#"32>E)TF `[ %E7ZZZ*E?AA;(0-Ox5  I֞;mR1{}/Fobpc+F\:BR9EX/ >YEX/>Y  01!+3#:   G "?//и/ EX/ >YEX/>YEX/>YEX / >YEX/>Yи ии01#"&'##"&'#&5463!#Y    4 [  D D   "//и/ EX/ >YEX/>YEX/>YEX / >YEX/>Yиии01#"'##"&'#&5463!#Y    4 [ Q Q    <&2+EX/>Y +!014>32.#"#"&546732>5/:++#(!/:,+$)!8Y2 +(+01>32#".54>3254.#"..#"3267<;0&C)) +&  4 !  %   %0(%0 N ! *"2P // и/EX/ >Y +014632#".732654.#"*L>0FB/!0 */ *1pd0Q?kf.Q<0=# MV0>$I.56//6!и!/!и/!и/)/и//)2и2/EX$/$ >YEX/>YEX/>Y$ .и/01!>54.#"#.546;.546323#". 1!" A ^c0A( R J\j9TrGCpS9m`M  ;Rg>$P}XBkR:  !ke)'+//+01%#"&=!.5463!e   0o v  7?@EX/ >YEX/>YEX/>Y017>323+#"&'7 Of Un   <2z+и-EX/ >YEX./. >YEX/>Y (+.и01#"&546732>5#46;54>32.#"3# /:,+$)! {/:++#(! $=D! /*D3232676#".#"&>3232676#".#"&% ( 7)"%!- '"' A   # q       <EX/ >YEX/ >YEX/>Y01>32!7!#  x@  H48$%EX/ >YEX/>Y01'7Ȍcc%&0;Eû &+&и/ 'и'/&,EX / >YEX/ >YEX/ >YEX/ >YEX"/" >YEX*/*>Y<1+и<7и7/1AиA/0146'&632'76'.7&547#"'7>&546776'.7    ~    =  ~   ~   -  G ,  1)f%EX/ >YEX / >Y012#".54>">54&#".2#"&#"3263232654#"#"&=463232654&#"#"&#"3262654&#"#"&#";2/.#"#"&#"&#"#"&#";27'32654&#"#"&#"32632#"&#"3263232654&#"#"=7632326732632#"&#"3263232654#"#"&=326=463R^33^RR^33^[#&-.&$" #     k    F       "   :   :      3^RR^33^RR^3!&"%%"&!' p     p         b   " 7   ^"   $[ q    a) v g +01.5>;# g   D/+0132>7>#".546  ),  ' #% 47/// ///01>32632#"'#"&'  p   =EX/>Y 0176327#"&54>Z   #*     &++#3n& ///EX/>Y01#"/&547%463!#{     & gn  z3i&///EX/>Y01'>32.547463!#!    &hn  .&67/'/7 и /'#и'&и&/'*и*/ /EX/ >YEX/>YEX/>Y,201#"&54632&'.?.'>3274&'&#"32>.20 %E7ZZZQE)7`  B   M  *E?AA;(0Bs;mR1{}-ZF&  /Fobpc+F\98n)*/"/*и/и"EX/ >YEX/>YEX/>YEX/>YEX / >Y'01632#"&'#"&546332>54.#"p'2&=+$5: *  ! $+'(3% 1S?dw@ vn4bR5C&7['y(//(и/ и EX/ >YEX/>Y&++ и /0146;32+"#4.#"#:2327?/C*0B';  * s 1G,9P42$3 E+и//EX/ >YEX / >Y0174632#"&463#3 * ( =w5+EX/ >YEX/>Y 01#"&54732>5463w 0&67 / !%":* ,#H)w*/ /* и /и/  и /  и/ и/ % // /%/////01"+.547>23"+.547>23L q I'47B?0'47B?0 2p.Fn(~fL  N x   p L V 4bPl2d&n.~FP h@lbBV f !.!!""# #T#$^$%%@%&&&&&&&&''~(.(:(F(R(^(j(v((((((((())))))*******+,x,,,,,,,,,,,--- -n.."...:.F.R.^./,/0000&020~001121|11122.223L3l3|445B5l66678l9::;;<<=l>">z???@.@z@APABBBCC>ChCCDlDEBEEFGPGHLHI"IIJRJKKKLLMMMN2N2NZO,PPPQQVQQRrRSrSSTp%` B    "&).035 6 8;"?ABCDEKLMNOPQRSTUVWX[efghijkl/mpqt%u vwxyz{}2+-    " & . 0 9 : ; <9 ? A B C H K L M O P R S T U V W X [ _ f g h i j k l m t7 u w x y z { }             ){ . 3 7 9 A B C E M O f g h i j k l    " & . 0 3 4 5 6 8 ; A B C D H5 M O R S T U W m w x y z { } ~    )2356789;DRTUVWXdfghijkl "&.034568;ABCDMORSTUWmwxyz{}~& ) \\  !"&).0356789:;<=s?ABCDEGHKLMNOPQRSTUVWXZ[_bdefghijkllmpqtuvwyz{|}1+&6O&&OO&)35678;<=  = ==  )7=C?BCEfghijkpqvyz{==353HU  )fghijkpqvyz{ )3578HCV   $  " & . 0 3 5 6 8 ; @ A B C D M O R S T U W _ m w x y z { } !! ! !! !)!3!5!7!8!=!R!T!U!V!W!f!g!h!i!j!k!l!p!q!v!y!z!{!!!!!!!!!!!!!!!!!!!!!!!"" " """"""""&"."0"2"?"A"B"C"D"M"O"R"S"T"U"V"W"X"_"m"w"x"y"z"{"}"""""""""""""""""""""""""""""""""""""""""""""""""## # ## #)#3#5#6#8#=#?#M#O#V#f#g#h#i#j#k#l#p#q#v#y#z#{#####################################$ $$ $$?$A$B$C$M$O$P$S$U$V$W$X$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ $ $ $ % % y%%y%%%% %)%=l%?%A%B%C%E%M%O%P%R%S%T%U%V%W%X%_%d%f%g%h%i%j%k%l%p%q%v%y%z%{%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % %y% % %y%y%%% & & &8&T&V&W&&&&' () ) )) ) )))=)V)f)g)h)i)j)k)l)p)q)v)y)z){)))))))** ***"*)*.*0*?*A*B*C*D*M*O*R*S*T*U*W*_*m*w*x*y*z*{*}********************************************+n+ + l+++++"+&+.+0+2+3+5+6+8+?+A+B+C+D+M+O+P+R+S+T+U+W+_+m+w+x+y+z+{+}++++++++++++++++++++++++++++++++++++n+n+n+n++++++++n.. . . .. .)...3.5.6.7.8.=.A.V.f.g.h.i.j.k.l.p.q.v.w.x.y.z.{.}................/ / {//{/// /)/7/8/9/=3/?/A/B/C/M/O/V/_/b/f/g/h/i/j/k/lc/p/q/v/y/z/{////////////////////////////////////{/{/{//00 0 00 0)030507080=0V0f0g0h0i0j0k0l0p0q0v0y0z0{0000000000000011"1.13151617181B1C1M1O1V1W1l1m1w1x1y1z1{1}1111111111111111122)2=2D2T2U2V2W2l222222222222233 3 33333333 3"3&3)3.303=3?3A3B3C3D3E3F3G3H3K3L3M3N3O3P3Q3R3S3T3U3V3W3X3_3d3f3g3h3i3j3k3ly3m3p3q3v3w3x3y3z3{3}333333333333333 333333333333333333333333333333333333333333335 5 5555555 5"5&5)5.505=5?5A5B5C5D5E5G5H5K5L5M5N5O5P5Q5R5S5T5U5V5W5X5_5d5f5g5h5i5j5k5l5m5p5q5v5w5x5y5z5{5}55555555555555555555555555555555555555555 5 55 5 55555555555 66 6666666 6"6&6)6.60696=6?6A6B6C6E6K6L6M6N6O6P6Q6S6T6U6V6W6X6_6d6f6g6h6i6j6k6l6m6p6q6v6w6x6y6z6{6}666666666666666A6666666666666666666666666666666667 7 7777"7&7.707?7A7B7C7D7M7N7O7R7S7T7U7W7_7l7m7w7x7y7z7{7}777777777777777777777777777777777 7 7 7 77777777 88 8888888 8"8&8)8.80828=8?8A8B8C8D8E8G8H8K8L8M8N8O8P8Q8R8S8T8U8V8W8X8_8d8f8g8h8i8j8k8m8p8q8v8w8x8y8z8{8}888888888888888888888888888888888888888888888888888888899 999?9A9B9C9D9E9M9N9O9P9R9S9U9V9W9_9999999999999999999999999999999999999999999: 1:::H8;; ; %;%;; ,;f,;g,;h,;i,;j,;k,;p,;q,;v,;y,;z,;{,;,;,;,;,;;;%;;;%;%;< =-==o====="=&=.=0=3=5=6=8=A=B=C=D=E=H)=M=O=R=S=T=U=W?? ?D?R?V?W??????????????@@ @ @@@=@R@U@X@@@@@@@@@AA AAAAABACAMAOARAUAWA_AAAAAAAAAAAAAAAAAAAAAAB BBBCC C CCCRCUCVCCCCCCCCCDDD rD D DDD DsD;oD<D=D@DADBDCDEDFDKDMDNDODPDSDU D^?D_D`>DDDDDDDDDDDDDDDDDDDDDDDDDDDDeDDDDDDDDDNDEEEAEBECEHlEMEOEVEWEXEEEEEEEEEEEEEEEEEEEEEFFFRFUFWFFFFFFFFGHHH?II IIIAICIMINIOIUIWIIIIIIIIIIIIIIIIIIIIKK KKDKUKVKWKKKKKKKKKKKKKLL LLVLLLLLMM M M MMM=MDMRMTMUMVMWMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNN N N NN NN=N?NDNHNRNTNUNVNWNXNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNOOOOOOOPP P PPPP=PAPBPCPEPMPNPOPPPVP_PPPPPPPPPPPPPPPPPPPPPPPPQQ QQQVQWQQQQQQQQQRR R RRRRRARBRCRERMRORRRSR_RRRRRRRRRRRRRRRRRRRRRRRRRRRSS SSSSSSTT T TTTTTTT=T?TATBTCTETITKTMTNTOTPTQTRTSTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTUU U UUUUUU=U?UAUBUCUEUIUKUMUNUOUPUQURUSUVUXUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUVV VVVVV?VAVBVCVDVEVIVKVMVNVOVPVQVRVSVUVVVWVXVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVWW W WWWWW=W?WAWBWCWEWIWKWMWNWOWPWQWRWSWVWXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWXX XXAXBXCXEXMXNXOXRXSXUXVXWXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZ"ZH^[[ [[[[[\^_3_5_6_8`d dd)d3d5d6d7d8d9dTdUdVdWdXddee e eHwefegeheiejekepeqeveyeze{eeeeeeeeeff f ffff"f&f.f0f3f5f6f8f;f@fAfBfCfDfMfOfQfRfSfTfUfWf_fmfwfxfyfzf{f}ffffffffffffffffffffffffffffffffffffffffgg g gggg"g&g.g0g3g5g6g8g;g@gAgBgCgDgMgOgQgRgSgTgUgWg_gmgwgxgygzg{g}gggggggggggggggggggggggggggggggggggggggghh h hhhh"h&h.h0h3h5h6h8h;h@hAhBhChDhMhOhQhRhShThUhWh_hmhwhxhyhzh{h}hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhii i iiii"i&i.i0i3i5i6i8i;i@iAiBiCiDiMiOiQiRiSiTiUiWi_imiwixiyizi{i}iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiijj j jjjj"j&j.j0j3j5j6j8j;j@jAjBjCjDjMjOjQjRjSjTjUjWj_jmjwjxjyjzj{j}jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjkk k kkkk"k&k.k0k3k5k6k8k;k@kAkBkCkDkMkOkQkRkSkTkUkWk_kmkwkxkykzk{k}kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkl ll?lAlBlClElMlOlPlSlUlVlWlXllllllllllllllllllllllllllllllll l l l l mm mmm"m&m.m0m2m?mAmBmCmDmMmOmRmSmTmUmVmWmXmmmwmxmymzm{m}mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmn nn?nAnBnCnMnOnPnSnUnVnWnXnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn n n n n o oo?oAoBoCoMoOoPoSoUoVoWoXoooooooooooooooooooooooooooooooo o o o o p pppp"p&p.p0p3p5p6p8p;p?p@pApBpCpDpMpOpPpQpRpSpTpUpVpWpXp_pmpwpxpypzp{p}ppppppppppppppppppppppppppppppppppp p p p pppppppppppp q qq"qq"q&q.q0q3q5q6q8q;q?q@qAqBqCqDqMqOqPqQqRqSqTqUqVqWqXq_qmqwqxqyqzq{q}qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq q q q qqqqqqqqqqqq s t1t 8t1t1t1t1t1u u u u u u u vv"vv"v&v.v0v3v5v6v8v;v@vAvBvCvDvMvOvQvRvSvTvUvWv_vmvwvxvyvzv{v}vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvww w ww w)w.w3w5w6w7w8wAwVwfwgwhwiwjwkwlwpwqwvwwwxwywzw{w}wwwwwwwwwwwwwwwwxx x xx x)x.x3x5x6x7x8xAxVxfxgxhxixjxkxlxpxqxvxwxxxyxzx{x}xxxxxxxxxxxxxxxxyy y yyyy y"y&y)y.y0y3y5y6y7y8y;y@yAyByCyDyMyOyQyRySyTyUyVyWy_yfygyhyiyjykylymypyqyvywyxyyyzy{y}yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzz z zzzz z"z&z)z.z0z3z5z6z7z8z;z@zAzBzCzDzMzOzQzRzSzTzUzVzWz_zfzgzhzizjzkzlzmzpzqzvzwzxzyzzz{z}zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz{{ { {{{{ {"{&{){.{0{3{5{6{7{8{;{@{A{B{C{D{M{O{Q{R{S{T{U{V{W{_{f{g{h{i{j{k{l{m{p{q{v{w{x{y{z{{{}{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{|}} } }}}} })}.}3}5}6}7}8}?}A}B}C}E}F}I}N}O}P}Q}R}S}U}V}W}X}f}g}h}i}j}k}l}p}q}v}w}x}y}z}{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}"&.03568;@ABCDMOQRSTUW_mwxyz{}"&.03568;@ABCDMOQRSTUW_mwxyz{}  "&).02?ABCDEGHKLMNOPQRSTUVWXfghijkmpqvwxyz{}  DRVW DRVW DRVW DVW DRVWDRVW DRVWX ABCMORUW C CRUV CRUVW CRUVW ) F))))),FVW,,,,,5 F VW55555 VW ?DIRTUVWX ?DIRTUVWX ?DIRTUVWX ?DIRTUVWX ?DIRTUVWX ?DERUVW   VW VW ?ABCEIKMNOPQRSVX ?ABCEIKMNOPQRSVWX  ).35678?ABCEMOPSUVWXfghijklpqvwxyz{}   ?DIVWX"&).03568;@ABCDMOQRSTUVW_lmwxyz{}VW  "&).02?ABCDEGHKLMNOPQRSTUVWXfghijkmpqvwxyz{}"&.03568;?@ABCDEMNOPQRSTUVW_mwxyz{} ?ABCEIKMNORSUVW )2356789;DRTUVWXdfghijklpqvyz{ )2356789;DRTUVWXdfghijklpqvyz{   "&).035 6 8;"?ABCDEKLMNOPQRSTUVWX[efghijkl/mpqt%u vwxyz{}2+-   "&).035 6 8;"?ABCDEKLMNOPQRSTUVWX[efghijkl/mpqt%u vwxyz{}2+-"&.034568;ABCDHgMORSTUWmwxyz{}~   "&).035 6 8;"?ABCDEKLMNOPQRSTUVWX[efghijkl/mpqt%u vwxyz{}2+-   "&).035 6 8;"?ABCDEKLMNOPQRSTUVWX[efghijkl/mpqt%u vwxyz{}2+-"&.034568;ABCDHbMORSTUWmwxyz{}~"&.034568;ABCDMORSTUWmwxyz{}~358 )35678DRUVWXfghijkpqvyz{  r   [;o<@ABCDEFKMNOPSU ^?`>eNVW r   [;o<@ABCDEFKMNOPSU ^?`>eN       "&).035 6 8;"?ABCDEKLMNOPQRSTUVWX[efghijkl/mpqt%u vwxyz{}2+-!  -:*Pz  u   "3Uo  .F t @ .  ,  T6     8 8 D 4@ "t  Copyright (c) 2010, Yanone (http://yanone.de/typedesign/). All rights reserved. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: http://scripts.sil.org/OFLYanone KaffeesatzLightYN: YanoneKaffeesatz-Light: 2010Yanone Kaffeesatz LightVersion 1.002YanoneKaffeesatz-LightYanone Kaffeesatz is a trademark of YanoneYanoneYanoneYanone Kaffeesatz was first published in 2004 and is Yanone's first ever finished typeface. Its Bold is reminiscent of 1920s coffee house typography, while the rather thin fonts bridge the gap to present times. Lacking self confidence and knowledge about the type scene Yanone decided to publish the family for free under a Creative Commons License. A decision that should turn out one of the best he ever made. It has been downloaded over 100,000 times to date from this website alone, and you can witness Kaffeesatz use on German fresh-water gyms, Dubai mall promos and New Zealand McDonald's ads. And of course on coffee and foodstuff packaging and cafe design around the globe. In 2009 he reworked much of the typeface and it got published in FontShop's FontFont Library under the new name FF Kava. You can read more about it in an extensive article by Yves Peters on the FontFeed.http://yanone.de/typedesign/http://yanone.de/typedesign/SIL Open Font License, Version 1.1http://scripts.sil.org/OFLYanone Kaffeesatz LightCopyright (c) 2010, Yanone (http://yanone.de/typedesign/). All rights reserved. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: http://scripts.sil.org/OFLYanone Kaffeesatz LightRegularYN: YanoneKaffeesatz-Light: 2010Yanone Kaffeesatz LightVersion 1.002YanoneKaffeesatz-LightYanone Kaffeesatz is a trademark of YanoneYanoneYanoneYanone Kaffeesatz was first published in 2004 and is Yanone's first ever finished typeface. Its Bold is reminiscent of 1920s coffee house typography, while the rather thin fonts bridge the gap to present times. Lacking self confidence and knowledge about the type scene Yanone decided to publish the family for free under a Creative Commons License. A decision that should turn out one of the best he ever made. It has been downloaded over 100,000 times to date from this website alone, and you can witness Kaffeesatz use on German fresh-water gyms, Dubai mall promos and New Zealand McDonald's ads. And of course on coffee and foodstuff packaging and cafe design around the globe. In 2009 he reworked much of the typeface and it got published in FontShop's FontFont Library under the new name FF Kava. You can read more about it in an extensive article by Yves Peters on the FontFeed.http://yanone.de/typedesign/http://yanone.de/typedesign/SIL Open Font License, Version 1.1http://scripts.sil.org/OFLYanone KaffeesatzLight2  "#$%&'()*+,-./0123456789:;<=>?@BCDEFGHIJKLMNOPQRSTUVWXYZ[\]_bcdefghjikmlnoqprsutvwxzy{}|~     !A^`a.nullperiodcenteredlongsEuroffffifflt_zg.ss01f.ss02ampersand.ss03ampersand.ss04germandbls.ss05z.ss05t_z.ss05uni2074Deltauni00A0macronuni0237` :latncpspkernPFPk~ ~\&*~  4 ~ H X.X8L$dvV$Vh* `!!"#$#*#8#>#D#J#T#$l%6&&'(^()*+,- ---(-:../01(2:2@3>4466686j667727|7788@8j8|88889r::;4;<\>"?$?@@ABCtD2DE.FGH|IK(KLpL~LLM,MMMNNNU 0;"KOP[efghijkl/mpqt%u vwxyz{}2+-H 0:;<9KOP[fghijklmt7uwxyz{}  Ofghijkl) 0;Omwxyz{}~;fghijkl) 0;Omwxyz{}~k& \ !0:;<=sKOPZ[befghijkllmpqtuvwyz{|}1+&6OO;<=  = ==)=Cfghijkpqvyz{== fghijkpqvyz{ 2 $0;Omwxyz{}# =fghijklpqvyz{< 0Omwxyz{}2 =Ofghijklpqvyz{&  OP > y=lOPfghijklpqvyz{ yy    =fghijklpqvyz{5 0Omwxyz{};n l0OPmwxyz{}nn  =fghijklpqvwxyz{}9 {=3Obfghijklcpqvyz{{{ =fghijklpqvyz{Olmwxyz{} =lS 0=KOPfghijklympqvwxyz{} O 0=KOPfghijklmpqvwxyz{} G 0=KOPfghijklmpqvwxyz{}A7 0Olmwxyz{} M 0=KOPfghijkmpqvwxyz{},OPI 0:;<9KOP[fghijklmt7uwxyz{}  %f,g,h,i,j,k,p,q,v,y,z,{,,,,,%% -o0O   =O  /  s;o<=KOP^?`>eNOO $ =%  = =OP O- =IKOP- =IKOP1IKOP, =IKOP%O"fghijkpqvyz{2 0;Omwxyz{}2 0;Omwxyz{}2 0;Omwxyz{}2 0;Omwxyz{}2 0;Omwxyz{}2 0;Omwxyz{}&  OP < 0Omwxyz{}&  OP &  OP = 0;OPmwxyz{} = "0;OPmwxyz{} 111  1"0;Omwxyz{}  =fghijklpqvwxyz{}  =fghijklpqvwxyz{}D 0;=Ofghijklmpqvwxyz{}D 0;=Ofghijklmpqvwxyz{}D 0;=Ofghijklmpqvwxyz{}? =IOPfghijklpqvwxyz{}10;Omwxyz{}10;Omwxyz{}M 0=KOPfghijkmpqvwxyz{}       O  ))),,,555% =I% =I% =I% =I% =I$ =, =IKOP- =IKOP@  OPfghijklpqvwxyz{} %  I30;=Olmwxyz{}M 0=KOPfghijkmpqvwxyz{}=0;OPmwxyz{}'IKO/  s;o<=KOP^?`>eN;fghijklpqvyz{;fghijklpqvyz{U 0;"KOP[efghijkl/mpqt%u vwxyz{}2+-U 0;"KOP[efghijkl/mpqt%u vwxyz{}2+-(0;Omwxyz{}~U 0;"KOP[efghijkl/mpqt%u vwxyz{}2+-U 0;"KOP[efghijkl/mpqt%u vwxyz{}2+-(0;Omwxyz{}~) 0;Omwxyz{}~fghijkpqvyz{;fghijkl,   [;o<KOP^?`>eN0   [;o<=KOP^?`>eNU 0;"KOP[efghijkl/mpqt%u vwxyz{}2+-.  "&).35 6 8:<?ABCDELMNQRSTUVWX ){<.fiRhl$.Hz@,J 4 ( 2 H ^  X d l 8 Fh2X$B:x`,^TvF0~.0379ABCEMwxyz{} <"&.34568ABCDH5MRSTUW, )\ "&).356789?ABCDEGHLMNQRSTUVWX_dx&&O& )35678  )7<?BCEOl3HU )l )3578HCV  )3578<V 1H8 % ,<l,% <1"&.3568ABCDEH)MRSTUWmwxyz{}RUW <DUVWH^ <  <Hwl "&.3568<@ABCDMQRSTUW_ "&.3568<@ABCDMQRSTUW_ "&.3568<@ABCDMQRSTUW_ "&.3568<@ABCDMQRSTUW_ "&.3568<@ABCDMQRSTUW_ "&.3568<@ABCDMQRSTUW_?ABCEMSUVWX  "&.2?ABCDMRSTUVWX?ABCMSUVWX  ?ABCMSUVWX  "&.3568?@ABCDMQRSTUVWX_  "&.3568?@ABCDMQRSTUVWX_   < 8<8111 <  "&.3568@ABCDMQRSTUW_  ).035678<AV  ).035678<AV!  "&).35678<@ABCDMQRSTUVW_!  "&).35678<@ABCDMQRSTUVW_!  "&).35678<@ABCDMQRSTUVW_  ).035678<?ABCEFNQRSUVWX"&.3568@ABCDMQRSTUW_"&.3568@ABCDMQRSTUW_$ "&).2?ABCDEGHLMNQRSTUVWXl < <DRVW <DRVW <DRVW <DVW <DRVWDRVW <DRVWX <ABCMRUW <C <CRUV <CRUVW <CRUVW < <FI)))FIVW,,, <F I VW555 <VW <?DRTUVWX <?DRTUVWX <?DRTUVWX <?DRTUVWX <?DRTUVWX <?DERUVW < < <VW <VW?ABCEMNQRSVX <?ABCEMNQRSVWX ).035678?ABCEMSUVWX  <?DVWX"&).3568@ABCDMQRSTUVW_VW$ "&).2?ABCDEGHLMNQRSTUVWXl"&.3568?@ABCDEMNQRSTUVW_ <?ABCEMNRSUVW )2356789<DRTUVWXd )2356789<DRTUVWXd  "&).35 6 8:<?ABCDELMNQRSTUVWX  "&).35 6 8:<?ABCDELMNQRSTUVWX"&.34568ABCDHgMRSTUW"&.34568ABCDMRSTUW358 )35678<DRUVWXl < r@ABCDEFIMNSU VW r@ABCDEFIMNSU  < < < <||40 yynnn{{   rl?   b  9f{}0678+.3$5=*?I3KX>Z\L^^O``Pd}Qk      00:=FFKKOOZ[eqs{&}}/0JQX]abdg  &)+./135:<<?ACF!HI%KN'PX+__4dd5fq6w{B}}GHIU[bjrsuvx)23+*+11 23 !"#$%&'/0 """"&& '**,-.,-.+/0*().&& /%.!#*+,*''(')  "$////-!!!!!!!++++' + )%"-$ @ latn  aalt>dligFhistLligaRss01Xss02^ss03dss04jss05pss06v   (08@HPX`hHFPb`^\Z^|Y|Xp GJ\PVPP&YH&QRDEXDEQXslixmpp-slix-1.4.2/docs/_static/fonts/YanoneKaffeesatz-Regular.ttf000066400000000000000000003730641342457644200253010ustar00rootroot00000000000000GPOSAVn\GSUB@Q'TOS/2T#``cmap C cvt sfpgmY7 sgaspg nLglyf~headc6hheaYqT$hmtx|ukern42zloca maxpx nameggY0Ppost1kprep}k.8u!_< |%`"+2 89^n svG@ KYN @ 89  " 4,$A$"&%%O *",%""'/x+S,@,8,,+2*=+++h+u+fu+ lyJ  y b/9 i |-.}..3.~-p.-Y#,XdNCy,S##5 2gS@,@,@,@,&+G$++++l'bbbbbbE9iiii*~-ppppp",,,,dd.OWfYlN&X&%"""E"E"B") $, "   ^ h r% &!&\=},,((,e,b,I'+0) fSn\#!^(",,/ B(R##"/  ( @,,,,.h+*n&   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYjkmov{[`^a]l}\eZ_dfizbhpgqnsturxyw~cHr@2";>]^z~1BSax7    " & 0 : D t !"!&"""""""+"H"`"e% #<?^_{1AR`x}7    & 0 9 D t !"!&"""""""+"H"`"d%qQE/+``F++y`ߝޫބޙ,rtvtzYZ[\]^_`abcdHr@2";>]^z~1BSax7    " & 0 : D t !"!&"""""""+"H"`"e% #<?^_{1AR`x}7    & 0 9 D t !"!&"""""""+"H"`"d%qQE/+``F++y`ߝޫބޙ,rtvtzYZ[\]^_`abcd,K PXYD _^-, EiD`-,*!-, F%FRX#Y Id F had%F hadRX#eY/ SXi TX!@Yi TX!@eYY:-, F%FRX#Y F jad%F jadRX#Y/-,K &PXQXD@DY!! EPXD!YY-, EiD` E}iD`-,*-,K &SX@Y &SX#!#Y &SX#!#Y &SX#!#Y &SX#!@#Y &SX%EPX#!#!%E#!#!Y!YD- ,KSXED!!Y-+++D8,+?4(+N@2$+N@.$++ E}iDK`RXYGMQX B    k+// /EX/ >YEX / >Y ++ и ии!и%и'и,и.01%#7##7#46;7#46;7>337>33+3#'37#?!&_!&7 *@ 3!&_!&: .C __C SC S&j&? O? O&j&@j4k9d5+9/EX/ >YEX/>YEX5/5>Y&+ и/01.54>732654&/.54675463.#"#:54'/.pB=&$9. 5#-4y 0$$&  ,)- r76AY y " $y>'4*W ,#3EW,<+4$+++KиK/<QиQ/EX/ >YEXL/L >YEX///>YEXF/F>Y/9014632#".732>54.#"4632#".732654.#"&'.5,JD!2!GD!3#N   JD!2!GD!3#M$   "9H%X _X*F5aZ(I9&0 /$%/ /_X*F5aZ(I7&0 4G%/ 0  =az?M[\//и/-8и8/\CиC/TEX/ >YEX/ >YEX!/! >YEX8/8>YEX>/>>Y '>Y012.#">54&'>32+#"&'#".54>7.546.'326CS 9"!"8F$# !  %1  + 'H47H*)#+X!E # %$1# Tbf./O`  $2#R*&  "#2A#80'32Z  J+1+A>/A=#" +/EX/>Y01#".54>32%+49-.:3,"CWuT <~‡}w9Qk=#" +/EX/>Y014.'>32#"&'>"-3:.-94,$CJmV7uB Sr )2/&632&67/4>&67'6.'     S s X  f  &,     "k?+ и//+и 017#5#46;54633#!& {!& { &!{ &!&k+ /+0174632#"&'>7&+7  7<6+  %= +01746;#%  &!&!%m $+EX / >Y0174632#"&% 3# ! E //013#>3d"" XP//и/EX / >Y+ 014632#".732654&#"UaaQ+F44D)W #3-%49*(Q|R*(MrD>V5ygh~X#2 +EX/>Y и01.54>323:767#!.54>3E2A>  J   *3 &  *&  X.V )+EX/>Y,+,9и/и/'901.54>32326767#!.54>7>54&#";0A$+9"/GT$*  !PE/#*'R!  #2!4\Z`9.&  7^VP* %*yX67+ +-#++и/01#"&546732654.#'>54.#".546320$"BG,9@E)F]P(5 '-$6%>_?  aK (4P*  " "!+5-%^%,n&+и&"/EX/>Y+и/и/и&и"'01%:767+#5#.54>7632<  3! "+..T -*# h ( ;KVN@| FQR""{X0O'+и/"+0 +,+ и/ и/01#"&'&'>32#".546732>54.#"y  @!(-E;'9W<, "?):%)9#3X (LAClM* "8I&+7 '"0k1/./1и/%и. EX/ >YEX/>Y#+)01">32#".54>32."32>54& !8(B0@Q`^:C# #=S0'/GB/02'-q>bE$$Zh|,SuHp\' Jug,<#BF,X/+ и /01"&5>7#"&5.O>, .% /YEX/>Y7+791?19012#".5467.'.54>.'32654&">54."A1' ,A,2F-B0" .>] +<81.6A);' &7!%?0!4*"  C-'F34F(Ab 7+A+[  G26@<6),h+/2  ?*" a.Q//#/и// и /# ,+)+ +012>7#".54632#"&546726754.#"0A*@06'`^6B% w%5"l 9$3<0%D\8##3K1z IxW "&8U9TUBF% B+ иEX/>Y +014632#"&4632#"&%  # !# !" 7+ и /и/// +014632#"&4632#"&'>7&# 7  # !<6+  " ++01463!#463!#" M  M S&!&!&!&! ;iYEX / >Y 10174632#"&2"'&'.5467>54.#".54>J F?L#&   #!) $,3# !EF-D7/  % $1-/  /Bcr +)+YH+l;+HCиC/HEиE/Hdиd/EX/>YEX0/0>YEX6/6>Y$+VK+@g+6o014.#"3267#".54>32#"&'#".5>32474656&#".567>3232>.#"3267a3S>@bB"YEX/>YEX/>Y+ 01%##"&'632#*/3'.'1'   Ɗ"  S 43..25+g$1o2/+/2и/+$%EX/ >YEX/>Y1+#%0146;2+32654.+32>54.++ c<0")$4E'~S1 ) 0-!". "=0);' 8-?Z:7<&'<+'-B'C#+EX/ >YEX/>Y 012.#"3267#.54>$ "-$%1D8$<-%9F   @ycLa7  +FmS,lY// и /EX/ >YEX / >Y 012+4634.+32>1J12I.}  &.-%Hy[nZ& EZ4C},,U+EX / >YEX/>Y+ 01%+47>;+3+)I,,K+ EX/ >YEX/>Y+ 01347>;+3+#,  .`)k*//и/*%и%/EX/ >YEX / >Y+  012.#"32675#54;#".54>38 )&7#'IC42H-7O CqTYj94LiuR,m//и/ ииEX / >YEX/ >YEX/>YEX/>Y+01#"#4>334>3"#!!!!T T+} /+EX/ >YEX / >Y014>3"#+""T5+EX/ >YEX / >Y014>3#".546732>5" 5)) + %@.   -*|!{+EX/ >YEX/ >YEX / >YEX/>YEX/>YEX!/!>Y014>3>32#"'.'.'"#*! - YEX/>Y0173+4>3} !L<  +<{=//=и/и/0EX/ >YEX/ >YEX/>YEXY0146;4>7>;"#7>7+'.'"#+ ;_  XB"5#4! $"!$  T$\WD GA3 3BH55-e(+*+/!/+и/ и/!&!*и*/EX/ >YEX&/& >YEX/>YEX)/)>Y01.'"#4;5.=4>3+ !>p !=GSSX\OGRQV[PT'](//(и/ EX/ >YEX/>Y#014>32#".732>54.#"0H01D*.H10E+[ #% #% bdS$"QegV% SXFb?DjMKgAEo+We//и/ EX/ >YEX/>Y +014;2+"#4&+326+c_Y3C%#!#,2,*+]i>S35AEw$8s9///9и// и/%EX/ >YEX / >Y+ *4014>323267#"&'.732>54.#"0H01D*-! 4>+<&[ #% #% bdS$"QeRzV4 (!   =D%SRFb?DjMKgAEo+^&/0/'/0и/и/' ' и / и/+ EX/ >YEX/>YEX/>YEX&/&>Y-+*0147>;2#"&'.'"+"#4&+326+c_Y0'     #!"-2,*+]iQ]2F^; ?`E- 9BCO/0/&/и/0-и-/ -и/EX/ >YEX/>Y ##9) 9012.#"#"&546732654&/.546H; 4#-4l)E3D? 3$6,!e"W  % ,{)))&D4' 8.":&u*C#?QmA+EX / >YEX/>Y и01"##543!#!Tpp;<+`//и/EX/ >YEX/ >YEX/>Y01732674>3#".54>3% 1 "[<YEX/ >YEX/>YEX/>Y01&6;>7>;#"&'.'&BS  J G  }C00Bk A Wck3x?qEX/ >YEX/ >YEX/>YEX$/$>YEX3/3>YEX6/6>Y-01&6;3>7>;3>7>;#"&/.'#"&'.'&CH  = :F  @ E  3 7  }Cn0>>9"Vcj3v #GEX/ >YEX#/# >YEX/>YEX/>Y01+'.'+&>23>?>23 Y6 6 F}($(&" p(OO)} jDq+QQ,dh@+EX/ >YEX/ >YEX/>Y01&>23>?>23"#'#(  #"!?=2 2=?zepMEX/ >YEX/>Y 9  90143!3267#!.547#",+   @  /y9x5?+EX / >YEX/ >Y 01+3+4>3x   5  @ //01#&>3>""   86C+EX/>YEX/ >Y 014>;#4>;#    g  k +01463!#  M m&!&!;EX / >Y01".'>32(0+ $,* ;#!  '( =(=>/ />и/и/  и/ )и1 9и9/EX/ >YEX/>Y,+#601467>32#".54>3254&#"..#"3267>4 R9A;F14(%0) &H   6D5+WQH  =34@$ O# '!"  ',./e#r$//$и/EX / >YEX/ >YEX/>Y !014>3>32#"'732>54&#"/ 4#.%9DO&Q ""'.K7lC71\LP>&%C +EX#/# >YEX/>Y#01.#"3267#".54>32& &# !* >2$7&!4@#0  )OC5D( 6bOZk9 R&a'//и'и/EX$/$ >YEX/>Y$ 01.#"32674>3#".54>32   G/!;-*8 -+N=8F' l5 2`QJjCO#34/1/4и/ и/ )и)/1.и./EX/ >YEX/>Y) +$012#3267#".54>":>7>5.:92 ":&2#;*1@$%# !F(4  3\I^q<G'A3 6- V(+"и'EX/ >YEX/ >YEX&/&>YEX / >Y !и"014>;54>32.#"3++ 01;./$$ZI A <:G&   ' C a;:H]^/Y/ и/^*и*/O3и3/EX/ >YEX/ >YEX%/%>YTIܹܸ;C0123#"'#"'#".5467.7>7.54>"32654&"&'32>54.  xKW  +0#/I22?# , !"#,=" -($%< !& ,6%P_   ( ;- &%=/O02F+<7B*BB7/Q0  -V#}$//$и/EX / >YEX/ >YEX/>YEX#/#>Y 014>3>32"#4.#""#- < )" (!!2c y.&/%EX / >YEX/>Y01<&4+EX/ >YEX/>Y 01.c4=y+.и:./EX / >YEX/ >YEX / >YEX4/4>Y 7.;014>3>32"#>54&'."#4#">. D   $9$" 1"%+4D3'TE  <1)|?. /+EX / >YEX/>Y017"#4>3! . - +'+ +EX/ >YEX/ >YEX / >YEX / >YEX-/->Y$и$/01.#""#>32>32"#4&#""##!!!L* ,B%-!!!j$4"r$  m-Xh//и/ EX/ >YEX/>YEX/>Y01>32"#4&#""#-&*+O>"&)  =I`0' kY#]$//$и/EX/ >YEX / >Y0174632#".732>54.#"WO';&TP':(X   {;cL{7gR>J( ,J;YEX/>YEX / >Y$01632#"&'#"&532>54.#".?S%<+,5(  Q   "(/Q=e{B  0[N-8! :W'v(//(и/иEX/ >YEX/>YEX / >Y $01#"'5#".54>32&#"3267W A0' 8K*#1R"  # ,1YJSpD64T=3>" -9+EX/ >YEX/>Y 01>32&#""#-F%& #" w@*+/!/и/+(и(/ (и/EX/ >YEX/>Y 9$ 9012&#"#"&546732654&/.546'1 +7#,] XM6D+9&+a"O K 7&CH  &S54B+p +и  /EX/ >YEX/ >YEX&/&>Yии&01#4>;54>33+3267#".5VD2 fV &    '7",W\//и/EX/ >YEX/ >YEX/>Y01%#".54>332674>3WO)-:" " )%!*>*G1' H6EX/ >YEX/ >YEX/>Y01&>3367>?>3#"&'%!  !s $  'Y&,+,,&X( QGEX/ >YEX(/( >YEX4/4>YEXE/E>Y01&>33>?>;3>?>3#"#"&/.'"#"&'.'&$   5'           *V#)' +35 ? ;BBo/[F+CDB<=BCt>FK$T/GEX/ >YEX/// >YEX/>YEX/>Y01"#'.'"#'&>23>?>23(%%  "y%"%  "  A%$! !$%1D!  6?T(<EX/ >YEX/ >YEX/>Y%01&4>34>?>3#"&54673267" 6  ,"{ !+-$ $'  :2$#2= 0?'  .>:$UEX/ >YEX/>Y 9  9013267+&'&547#".5431X*0 f.  h#y8/ "+ /EX/>Y01#4>3!" ,; I+и//EX / >YEX/>Y 01#"&54632#>3 ,## "v  S{Yq1m(+"/1/EX/ >YEX-/- >YEX/>YEX#/#>Y 01.#"3267#5.54>75463# !% #,0 %%($.&$   )QE:B!   \ 4WDId?!W 60+и0*EX/ >YEX#/# >YEX6/6>Y+ + $и(и+и/017#46;5'#46;&>23>?>233+3+"# bn 7_'#'  %"_Y Q b!%':%96, ,69z%5,%L  +и 014632#"&74632#"&#@%5GI6&+ +.@+;1+)C++#+01.#"3267#".54>324632#".732>54&#"&       !mb1I0gc1K2>"3 3'BC 5&'! -&.6 tri3S&'?-UK&>%G%EX/ >YEX/>Y0174>7672#&'.74>7672#&'.  )   !"   )!  ( $ $ )! &+3<  $)00)#  <4,&"'.7  > &**$< 6.("#@!?Hc+69+%@++%+и+/@1и1/6D ++F4+014632#".732>54&#"72#*'.'##547>34&+326#mb1I0gc1K2>"3 3'BC 5&*'    Uri3S&'?-UK&>S&#! 0-1,\FV9 //01"&'&'>32 ,( $+*9 *"   5G + +014632#"&5  # !2 ++01732#"&'&'67>763254&#"7R -#    <#'- "g %I%EX/ >YEX/>Y01%&'.54>767&'.546767&'.5467672.546767 !)   "!   (  !) $  (  &,4<  #)00)$ <3+&#(/5 ;$)#,*# 7/(#= ;iYEX / >Y  101#"&54632"&54>7>54&7>3267 F?L#&   # !*$-# !aEF-D7/  % $1./  & >e& a& [& Gz& ]d& xS"*"+"и$EX/ >YEX/>YEX / >YEX/>Y$+ +01%+5##"&'63!+3+'3OD%׆ Ij:;82BGWC+EX/>YEX>/>>Y3&+ +8+012.#"326732#"&'&'67>763254&#"7.54>$ "-$%17/ -#    5&%9F   CycLa7  (.#'- "YJ~eS,,&$>A,3&$a,4&$8,1z&$]@&(>&&(a&&(z&(]+&-R&.>g&.a&.^&.Jz&.]gGZ7&'7'>76'K YY KLYY Sbd TT cc$.9HI/:/I и /:$ /2и2/EX/ >YEX/ >YEX/>YEX)/)>Y5)D01".'&>7.54>32>7#"&'&#"4&'32>W   /G1&;   .H1'; 1&% **% "'  *zVdT$ !$OgV%Z1MEp<3N0xufg+&4a+&4^+z&4]gh&8a'@y3@+ (+#+EX/ >YEX/>YEX7/7>YEX9/9>Y 0014632#"&546732654.54>54&#"#"'&'67>5/XI7L0")"#(#PF<;) #*0:0#)#571$ &QJ)4"( "'0;%EN$%'?<>&"* +:C4 =&?>==&?a=&?3E&?=&?]<=&?P BScK1+)S+a+1и/!и!/S9EX/ >YEX / >YEX&/&>YEX,/,>Y=,P=TиT/01467>32>32#"&'3267#"&'#".54>3254&#"..#"3267"327>5.KB(0 ?$/*&>  %<&21=04($/*&&?   $4  #"G4=  ,   @84B&H#  +!$m ":,"6+2&Eh >+EXC/C >YEX/>YEX9/9>Y.!+3+C01.#"326732#"&'&'67>763254&#"7.54>32& &# !* 0( -#    /!!4@#0  )OC5D( 1#'- "\8^IZk9O&C>RO&CaO&CHO&C]Q&>*&a*&&]-Y&L3Y&M>FY&MaY&M=Y&M)Y&M]F"P;+и +++017463!#4632#"&4632#"&" M &!&!s? *9EF/+/F и /+ +-и-/ :=и=/EX/ >YEX/ >YEX%/%>Y5A01.54>7.54632>7#"&'4'32>'7.#"> ! WO#7   )='%9 ##  ~  M5{  8VFc@ + ?>8,J7#*K,W&S>O,W&Sa,W&SF,W&S]O?T&Wa?T&W]B. /+EX / >YEX/>Y017"#4>3! ;,@A//Aи/ии/,#и-EX/ >YEX/>Y%*+"2и2/"<и32547>;+3+32>54.#"7*O0E+/G1$8 $$ #% I(1!SkbT&Fc@EkMKiC Hq:,@PQ/N/Qи/N и/-NKиK/EX/ >YEX / >YEX#/#>YEX(/(>YF+(2<A0174632>32#3267#"&'#".732>54.#"%":>7>5.WN);E%:92 $<&2(@'L'<(X   ?$%# z %"G-5 , !67gR>J( ,J;YEX/>Y +014632.#"#"&'&'67>5.XU3B ,1( LQE   59 :// //01632#&'.'#"&'q $   &    7///01".'>32>767|$(#  '$7%%#  (*!TX  +01>32#"&! "2  ++014632#"&732654#"5/0-30.0:%#3/)811)7))>&%(EX/ >Y+01>3232>32#".#"#"&'&<  &  p*!      %= +017463!#% N &!&!= +017463!#  &!&!"?+ //01#"54>326 s;:. -"<+ //014632#"&'>7&$7 ;:. ."j+ //0174632#"&'>7&$7 6;9/ -"?##G$//$и/ и /и/ ////01#"54>32#"54>326 7 s;:. -#;:. -"<$#K$// и /$и/и/ ////014632#"&'>7&'4632#"&'>7&7 7 ;:. .#;:. ."$j#K$// и /$и/и/ ////0174632#"&'>7&'4632#"&'>7&7 7 6;9/ -#;9/ -)i + +014632#"&)&#)"(#) .*# ,)$m&'R,!5EYi{ʻjZ+br+F6+>P+"+,+EX/ >YEX/ >YEX/>YEXA/A>YEXe/e>Y9U+1AK9]иKoиUw01&'.54632#".732>54.#"4632#".732>54.#"4632#".732654.#"  "9H%XJD!2!GD!3#N   JD!2!GE!2#N   JC!2!GD!3"M#   =a{?[_X*F5aZ(I9&0 /$%/ /`W)F5aZ(H9&/ /$%/ 0"`W)F5aZ(H9&/ 4G%/ 0%%EX/ >YEX/>Y0174>7672#&'.  )   !"   )! %,3=   $*00(#  <3,% %%EX/ >YEX/>Y017&'.54>767&'.546767 !)   "!   (!  %,3<  #(00*$  =3,%7 %EX / >YEX/>Y01'#"&'>32 O    ^E8+и/83и3/8<иY +++.#3и7и<и@012.#"3+3+3267#".'#46;5<7#46;>(& %"  xy V /+":+X 6X ? )29^  1(% $*3  ,QC% !%@N+~1E38+-1++и/-*и*//1/8/EX/ >YEX/ >YEX/ >YEX=/= >YEX?/? >Y29и:01476;67>?>;"#74>7+/#"##5476;#,(  eR~ wo " PJNT( f"= +017463!#" M &!&!  7DE/6/Eи/и/и61(и13и3/8и69EX / >YEX)/) >YEX8/8 >YEX/>YEX5/5>Y#+8иии//и0и7и#?01+#4>;54>32>32.#"3++'3547.#" AE0!063=1+#$YH @zz  a E8B"    ' C aE<6%# v 56//6и/'.и'1и1/4!/EX / >YEX/ >YEX'/' >YEX/>YEX3/3>Y -и.01%"#4>34>;54>32.#"3++e! 0 4@ 8;8%F5 AE < a n-.//.и/и/&и)и)/,EX/ >YEX/ >YEX/>YEX+/+>Y +%и&014>;54>32+.#"3++ 0 /7&D A  P? @ <YEX!/! >YEXC/C >YEXO/O >YEX-/->YEX5/5>YEX=/=>Y+ и /'и(и/и0иI014>;54>32>32.#"3++#+"#4>3%47.#"3 0 073B"7<8%F5 Ay @!   y E8B"     )#> aaa<7$#N :;H-2++G+-$иG4и:и2<EX/ >YEX%/% >YEXG/G >YEX/>YEX1/1>YEX9/9>Y+ и /+и,и3и4иAиA/014>;54>32>32"#.#"3++#+747.#"3 0 0728/B!E4 Ay @  y E8B"    O& H aa7$#N]&RX#%8Y#45/$/ и /5и/$,EX/ >YEX/>YEX/>Y'101"&54732>=#".54>32.#"3267>C2)  3#!0!"8J)8->5! &#"C7]ESl@(:&u .N=4D)!& 'l+ и&EX/ >YEX/ >YEX%/%>Y +и 014>;54>32.#"3++ 0)*  ZH A t&0  &k a&?K++/?/.++'(+'$и$/;и;/017.5467.54675463.#"26732>7#UJ?,!UE%% 1! ?-4>+)% FH76,$ #7&$&9RG>I +!DU Q r&$0*##MA-(+ )O !lkx+g+]+)и>иDиD/gSиS/EX9/9>Yb+IX+9-014#"26767>>76754>3>76732767#".=#32>7#".54>323&1$) * &   *7% :70j* &&8#';(*C2-$ U]XPB3&  !YEX/>YEX/>Y+=2+#01#"&'&'67>54632.#"!#".546732654&'.'7 YV*,4%5a#  ID.1 zF)2"+EX/ >Y+01<>3!#".546732654&'.'7a#  ID.1 z78/0/8и/ии0/EX / >YEX/ >YEX/>Y-$+и01##4>;54>3!#"&546732654&'.'7!N< +a#  ID.1 l()//) и /и #EX/ >YEX / >Y +и !и #012+#46;4634.+3+2>1J12I.}(   &.O ?-%FvXq\&J&" EZ4&"C})M +и //EX/ >YEX / >Y0173+.?4>3}a  b "  "!%!<8 $" $ G +и  //EX/ >YEX / >Y017"#.?4>3+  ,!(  (  %! 3 %! =,!QA+EX / >YEX / >Y+01&5467>323#!.546;59  ,+#W  ]W  ,S7H/+EX/ >Y,!+5+и/ 014632>54&#".7>32#"&'&546732654&'.#.v &5 H7A86 $/@%*6 /#+3' * 4&:.1%6$    (!W(2 #+EX/ >Y+&01.54>323#!&5467>54&#"B -6$0 #39@4!( 1M  '$@:7 ) #:4/(o 'i + и!и!/ //EX/ >YEX / >Y+и!и!/013+#"&'5#&5467>7>327-< )+  ")-#I#! 3H ED<  +36,W+TU/O/Uи/$и$/O9EX / >YEX / >YEX(/( >YEX!/!>YEX/>YEXD/D>Y+4R+иD>01&5467>323#!.546;5#"&'>32.54>323#!&5467>54&#"9  ,+#W  ] O  Y -6$0 #39@4!( 1W      '$@:7 ) #:4/,9&'d,6&'aI: q+и////EX / >YEX / >YEX/>YEX/>Y01#"&'4632#"&'4632      b }v '_h //017#"/&5467%  `&l  +Um// /01'>32.'47  `%l  0|/ //017#"&'7'n  W)j;;!+и!7++/)+01#"&54>54&'.5467>54&54>$%  K?   )9  &'%\61* (-6Y(#  DC//1(! 6d--j9;2 + и2&++)-+01.547>54&5467.54654.'&54632!#%  K>  )9  &'%[62+ (-6Y($ EB//1(!  6d- ,  M!# ++и/01>3232676#".#"& ") %  )!         ,i&+&и/ и /EX/ >YEX#/#>Y +и/ и/01&632'76'.7&5467#"&'>  x xN     ?  _  S( ?//и/  ++014632#"&732654&#"S5/0-30.07(3.(820'9,QS*\+и&EX / >Y!+&+ и&01+.'#46;>32.#"3#> .*9$'<!# 6_/ EB&!E^; $@3&!#7J\y]/Q/и/и/]>и>/+и+/>ZEX/ >YEX%/%>Y %0012.#"#"&'.54732654&'.'.5467&'.54>>54&'.'14&#%   )8 14&#%  (8    '8 &+  (7'+| !   ! !&3++'-+EX/ >YEX/>YEX/>YEX*/*>YEX-/->Y01%#"&'5#".54>32.#"3267#"&'4632`-9,4I,&=R24" #   5XDMnF! 4jj6C% w!YEX/ >YEX/>Y+ +и ии 017#"'7#46;7#46;76323+3#%!T \( (&[ d( tj`&!s&!rl&!s&!(p6+>k?/7/?и/74+ <+ ии/и/4 и /014>32>32&'.'#"&73267'.'&732654&'&( 4&3<<.:G?4  %;>)ABQ) # 6)1"!2JHJE+" /OA*&#'  %##"V+ и/EX/ >Y++и 01#5#46;54633#463!#!& {!&  M !{ &!{ &!&!&!,=]"#//# и /EX/ >YEX/ >YEX/ >YEX"/" >YEX / >YEX / >YEX/>Y01%#"&'#"&'463232674632]R-"   % #   #,,z-// -и/и/%EX/ >YEX/ >YEX/>Y (012.'>32#".5464&'.#"32>6"I<  RS %C6-D-W'% 1-$Hi1 DԡYEX/>Y  01!+3#/ y|`(?//и/ EX/ >YEX/>YEX/>YEX / >YEX/>Yи ии01#"&'##"'#.5463!#` l- Yo * *   // и/ EX/ >YEX/>YEX/>YEX / >YEX/>Yи ии01#"&'##"&'#.5463!#` l - Yr r    <'2+EX/>Y+"014>32.#"#"&546732>51;6'#$1<5(#$4Y. +&+01>32#".54>3254&#".6.#"32674<2/;F')(# &1  "/7 1)$/ 9% &#!/!P"//"и/EX/ >Y +014632#".732654.#"#L?!0 GC1!E $ pc/Q>mg/Q:*6 AN*6 "7#56//6!и!/!и/!и/)/и//)2и2/EX$/$ >YEX/>YEX/>Y$ .и/013>54.#"#.546;.546323#  $%   3 Ze/@' F JXd8LiA9aL9m`P 8Mc;$NzU?fN6 "hf5+//+01%#"&=#&5463!f  *o e/=@EX/ >YEX/>YEX/>Y017>323+#"&'/ =X Fa"   <3z+и.EX/ >YEX/// >YEX/>Y )+/и01#"&546732>5#46;54>32.#"3#1<5(#${ j1;6'#$ <;E#   %!&!@3232676#".#"&>3232>76#".#".>.# >* >*  !)%  F*    &  q(        +EX/ >YEX / >Y 01>32!73# \d MM%EX/ >YEX/>Y01'7nnocd'4AL $+$и/ (и(/$0EX / >YEX-/->YB5++и/#и#/B=и=/5HиH/0146'&632'76'.7&5467#"&'7>.546776'.7   x xL    x y    ?  a     ?  ('d%EX/ >YEX / >Y012#".54>">54&#"&2#"&#"3263232654#"#"&=463232654&#"#"&#"3262654&#"#"&#";2/.#"#"&#"&#"#"&#";27'32654&#"#"&#"32632#"&#"3263232654#"#"=7632326732632#"&#"3263232654#"#"&=326=463R^33^RR^33^[#&-.&$+     k    F       "   :   :      3^RR^33^RR^3 &"$$"& ,,' p     p         b   " 8   ^"  $[ q    a) v Q  +01.546;#Q  7/+0132>762#".546* !+-"    (!',2/ / //01632632#"'#"&'| v     =EX/>Y 0176327#"&54>c     #/# $  %0.%,o&///EX/>Y01#"&/&5467%463!#   . #a%l  |&!&!,m&// /EX/>Y01'>32.'47463!#    ) 1`%l  &!&!,*=>/+/и/+> и /6и/+.и./EX/ >YEX / >YEX/>Y1901#".54632.'.?.'>3274&'.#"32><'( %C6-D-WI6" ^ :  !  E ''% 1-$w@nYEX/ >YEX/ >YEX/>YEX/>YEX / >Y *01>32#"&'#"&54>332>54.#" %<+.5$      !/Q>dzB  `y 1\M/9 +W$m%//%и/ иEX/ >YEX/>Y"++0146;32+"#4&#"22326+"#_Y3C%#!#,  *+}]i>S35@E* E+и//EX/ >YEX / >Y0174632#"&4>3#* ,#2# " :<5+EX/ >YEX/>Y 01#"&546732654>3 4(82"2&$!:,  -5G-W./#/. и /#) ///#/&/)///////01"#4.47>32"#.47>32Z  x  J+1+A>/+1+A>/ T.VrjV2 4 ^ :  n  bj$zt\zjd:\ lV4 8 ! !l!"x"#4#$H$p$$%P%%%&& &&$&'8'D'P'\'h't'''''''''((((((()))))))*+F+R+^+j+v++++++++++,<,,,--- -,-Z......//L///00N0f0~0001V12202@3P33445v56b7789X9d9:`:;>f>?(?|?@@@AJArAAB2BBCfCDDEfEFXFG6GGHnHI/?/A/B/C/M/O/V/_/b/f/g/h/i/j/k/ln/p/q/v/y/z/{////////////////////////////////////p/p/p//00 0 00 0)030507080=0V0f0g0h0i0j0k0l0p0q0v0y0z0{0000000000000011"1.13151617181B1C1M1O1V1W1l1m1w1x1y1z1{1}1111111111111111122)2=2D2T2U2V2W2l22222222222223 3 3 33333333 3"3&3)3.303=3?3A3B3C3D3E3F3G3H3K3L3M3N3O3P3Q3R3S3T3U3V3W3X3_3d3f3g3h3i3j3k3l~3m3p3q3v3w3x3y3z3{3}33333333333333333333333333333333333333333 3 33 3 33333333333333 55 5555555 5"5&5)5.505=5?5A5B5C5D5E5G5H5K5L5M5N5O5P5Q5R5S5T5U5V5W5X5_5d5f5g5h5i5j5k5l5m5p5q5v5w5x5y5z5{5}5555555555555555555555555555555555555555555555555555555566 6666666 6"6&6)6.60696=6?6A6B6C6E6K6L6M6N6O6P6Q6S6T6U6V6W6X6_6d6f6g6h6i6j6k6l6m6p6q6v6w6x6y6z6{6}666666666666666<6666666666666666666666666666666667 7 7777"7&7.707?7A7B7C7D7M7N7O7R7S7T7U7W7_7l7m7w7x7y7z7{7}777777777777777777777777777777777 7 7 7 77777777 88 8888888 8"8&8)8.80828=8?8A8B8C8D8E8G8H8K8L8M8N8O8P8Q8R8S8T8U8V8W8X8_8d8f8g8h8i8j8k8m8p8q8v8w8x8y8z8{8}88888888888888888888888888888888888888888888888888888889 9 999?9A9B9C9D9E9M9N9O9P9R9S9U9V9W9_99999999999999999999999999999999 9 9 9 99999999 : ,:::H;; ; ";";; *;f*;g*;h*;i*;j*;k*;p*;q*;v*;y*;z*;{*;*;*;*;*;;;";;;";";< =1==x= ===="=&=.=0=3=5=6=8=A=B=C=D=E =HL=M=O=R=S=T=U=W?? ?D?R?V?W??????????????@@ @ @@@=@R@U@X@@@@@@@@@AA AAAAABACAMAOARAUAWA_AAAAAAAAAAAAAAAAAAAAAABB B B CC CCCCRCUCVCCCCCCCCCD$D{D pD !D DDDDpD;dD<3D=D@DADBDCDEDFDKDMDNDODPDSD^>D_D`?DDDDDDDDDDDDDDDDDDD{D{DD{D{DDD-DD^DDDDDDDDDVD{EEEAEBECEH]EMEOEVEWEXEEEEEEEEEEEEEEEEEEEEEFFFRFUFWFFFFFFFFGHHH9II IIIAICIMINIOIUIWIIIIIIIIIIIIIIIIIIIIKK KKDKUKVKWKKKKKKKKKKKKKLL LLVLLLLLMM M M MMM=MDMRMTMUMVMWMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNN N N NNNN=N?NDNHNRNTNUNVNWNXNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNOOOOOOOPP P PPPP=PAPBPCPEPMPNPOPPPVP_PPPPPPPPPPPPPPPPPPPPPPPPQQ QQQVQWQQQQQQQQQRR R RRRRRARBRCRERMRORRRSR_RRRRRRRRRRRRRRRRRRRRRRRRRRRSS SSSSSSTT T TTTTTTT=T?TATBTCTETITKTMTNTOTPTQTRTSTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTUU U UUUUUU=U?UAUBUCUEUIUKUMUNUOUPUQURUSUVUXUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUVV VVVVV?VAVBVCVDVEVIVKVMVNVOVPVQVRVSVUVVVWVXVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVWW W WWWWW=W?WAWBWCWEWIWKWMWNWOWPWQWRWSWVWXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWXX XXAXBXCXEXMXNXOXRXSXUXVXWXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZZH][[ [[[[[\^_3_5_6_8`d d d)d3d5d6d7d8d9dTdUdVdWdXd d ee e eHlefegeheiejekepeqeveyeze{eeeeeeeeeff f ffff"f&f.f0f3f5f6f8f;f@fAfBfCfDfMfOfQfRfSfTfUfWf_fmfwfxfyfzf{f}ffffffffffffffffffffffffffffffffffffffffgg g gg gg"g&g.g0g3g5g6g8g;g@gAgBgCgDgMgOgQgRgSgTgUgWg_gmgwgxgygzg{g}gggggggggggggggggggggggggggggggggggggggghh h hhhh"h&h.h0h3h5h6h8h;h@hAhBhChDhMhOhQhRhShThUhWh_hmhwhxhyhzh{h}hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhii i iiii"i&i.i0i3i5i6i8i;i@iAiBiCiDiMiOiQiRiSiTiUiWi_imiwixiyizi{i}iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiijj j jj jj"j&j.j0j3j5j6j8j;j@jAjBjCjDjMjOjQjRjSjTjUjWj_jmjwjxjyjzj{j}jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjkk k kkkk"k&k.k0k3k5k6k8k;k@kAkBkCkDkMkOkQkRkSkTkUkWk_kmkwkxkykzk{k}kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkl ll?lAlBlClElMlOlPlSlUlVlWlXllllllllllllllllllllllllllllllll l l l l mm mmm"m&m.m0m2m?mAmBmCmDmMmOmRmSmTmUmVmWmXmmmwmxmymzm{m}mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmn nn?nAnBnCnMnOnPnSnUnVnWnXnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn n n n n o oo?oAoBoCoMoOoPoSoUoVoWoXoooooooooooooooooooooooooooooooo o o o o p pppp"p&p.p0p3p5p6p8p;p?p@pApBpCpDpMpOpPpQpRpSpTpUpVpWpXp_pmpwpxpypzp{p}ppppppppppppppppppppppppppppppppppp p p p pppppppppppp q qq$qq"q&q.q0q3q5q6q8q;q?q@qAqBqCqDqMqOqPqQqRqSqTqUqVqWqXq_qmqwqxqyqzq{q}qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq q q q qqqqqqqqqqqq s t4t <t4t4t4t4t4uu uuuuuvv$vv"v&v.v0v3v5v6v8v;v@vAvBvCvDvMvOvQvRvSvTvUvWv_vmvwvxvyvzv{v}vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvww w ww w)w.w3w5w6w7w8wAwVwfwgwhwiwjwkwlwpwqwvwwwxwywzw{w}wwwwwwwwwwwwwwwwxx x xx x)x.x3x5x6x7x8xAxVxfxgxhxixjxkxlxpxqxvxwxxxyxzx{x}xxxxxxxxxxxxxxxxyy y yyyy y"y&y)y.y0y3y5y6y7y8y;y@yAyByCyDyMyOyQyRySyTyUyVyWy_yfygyhyiyjykylymypyqyvywyxyyyzy{y}yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzz z zzzz z"z&z)z.z0z3z5z6z7z8z;z@zAzBzCzDzMzOzQzRzSzTzUzVzWz_zfzgzhzizjzkzlzmzpzqzvzwzxzyzzz{z}zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz{{ { {{{{ {"{&{){.{0{3{5{6{7{8{;{@{A{B{C{D{M{O{Q{R{S{T{U{V{W{_{f{g{h{i{j{k{l{m{p{q{v{w{x{y{z{{{}{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{|}} } }}}} })}.}3}5}6}7}8}?}A}B}C}E}F}I}N}O}P}Q}R}S}U}V}W}X}f}g}h}i}j}k}l}p}q}v}w}x}y}z}{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~"&.03568;@ABCDMOQRSTUW_mwxyz{} "&.03568;@ABCDMOQRSTUW_mwxyz{}  "&).02?ABCDEGHKLMNOPQRSTUVWXfghijkmpqvwxyz{}  DRVW DRVW DRVW DVW DRVWDRVW DRVWX ABCMORUW  C  CRUV  CRUVW  CRUVW % F%%%%%*F VW*****9 FVW99999 VW ?DIRTUVWX ?DIRTUVWX ?DIRTUVWX ?DIRTUVWX ?DIRTUVWX ?D ER UVW    VW VW ?ABCEIKMNOPQRSVX ?ABCEIKMNOPQRSVWX  ).35678?ABCEMOPSUVWXfghijklpqvwxyz{}   ?DIVWX   "&).03568;@ABCDMOQRSTUVW_lmwxyz{}  VW  "&).02?ABCDEGHKLMNOPQRSTUVWXfghijkmpqvwxyz{}"&.03568;?@ABCDEMNOPQRSTUVW_mwxyz{} ?ABCEIKMNORSUVW $)2356789;DRTUVWXdfghijklnopqvyz{ $)2356789;DRTUVWXdfghijklnopqvyz{   "&)|.03 568;%?ABCDEKLMNOPQRSTUVWX[efghijkl<mpqt)uvwxyz{}~4)6   "&)|.03 568;%?ABCDEKLMNOPQRSTUVWX[efghijkl<mpqt)uvwxyz{}~4)6"&.034568;ABCDHTMORSTUWmwxyz{}~   "&)|.03 568;%?ABCDEKLMNOPQRSTUVWX[efghijkl<mpqt)uvwxyz{}~4)6   "&)|.03 568;%?ABCDEKLMNOPQRSTUVWX[efghijkl<mpqt)uvwxyz{}~4)6"&.034568;ABCDHQMORSTUWmwxyz{}~"&.034568;ABCDMORSTUWmwxyz{}~358 )35678DRUVWXfghijkpqvyz{ ${ p  R;d<3@ABCDEFKMNOPS^>`?{{{{-^V{VW${ p  R;d<3@ABCDEFKMNOPS^>`?{{{{-^V{      ~ "&)|.03 568;%?ABCDEKLMNOPQRSTUVWX[efghijkl<mpqt)uvwxyz{}~4)6!" 3@*X  u   ";]w  2P  D 2  0  TP     8 8 D 4Z " Copyright (c) 2010, Yanone (http://yanone.de/typedesign/). All rights reserved. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: http://scripts.sil.org/OFLYanone KaffeesatzRegularYN: YanoneKaffeesatz-Regular: 2010Yanone Kaffeesatz RegularVersion 1.002YanoneKaffeesatz-RegularYanone Kaffeesatz is a trademark of YanoneYanoneYanoneYanone Kaffeesatz was first published in 2004 and is Yanone's first ever finished typeface. Its Bold is reminiscent of 1920s coffee house typography, while the rather thin fonts bridge the gap to present times. Lacking self confidence and knowledge about the type scene Yanone decided to publish the family for free under a Creative Commons License. A decision that should turn out one of the best he ever made. It has been downloaded over 100,000 times to date from this website alone, and you can witness Kaffeesatz use on German fresh-water gyms, Dubai mall promos and New Zealand McDonald's ads. And of course on coffee and foodstuff packaging and cafe design around the globe. In 2009 he reworked much of the typeface and it got published in FontShop's FontFont Library under the new name FF Kava. You can read more about it in an extensive article by Yves Peters on the FontFeed.http://yanone.de/typedesign/http://yanone.de/typedesign/SIL Open Font License, Version 1.1http://scripts.sil.org/OFLYanone Kaffeesatz RegularCopyright (c) 2010, Yanone (http://yanone.de/typedesign/). All rights reserved. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: http://scripts.sil.org/OFLYanone Kaffeesatz RegularRegularYN: YanoneKaffeesatz-Regular: 2010Yanone Kaffeesatz RegularVersion 1.002YanoneKaffeesatz-RegularYanone Kaffeesatz is a trademark of YanoneYanoneYanoneYanone Kaffeesatz was first published in 2004 and is Yanone's first ever finished typeface. Its Bold is reminiscent of 1920s coffee house typography, while the rather thin fonts bridge the gap to present times. Lacking self confidence and knowledge about the type scene Yanone decided to publish the family for free under a Creative Commons License. A decision that should turn out one of the best he ever made. It has been downloaded over 100,000 times to date from this website alone, and you can witness Kaffeesatz use on German fresh-water gyms, Dubai mall promos and New Zealand McDonald's ads. And of course on coffee and foodstuff packaging and cafe design around the globe. In 2009 he reworked much of the typeface and it got published in FontShop's FontFont Library under the new name FF Kava. You can read more about it in an extensive article by Yves Peters on the FontFeed.http://yanone.de/typedesign/http://yanone.de/typedesign/SIL Open Font License, Version 1.1http://scripts.sil.org/OFLYanone KaffeesatzRegularG  "#$%&'()*+,-./0123456789:;<=>?@BCDEFGHIJKLMNOPQRSTUVWXYZ[\]_bcdefghjikmlnoqprsutvwxzy{}|~     !A^`a.nullperiodcenteredlongsEuroffffifflt_zg.ss01f.ss02ampersand.ss03ampersand.ss04germandbls.ss05z.ss05t_z.ss05uni2074Deltauni00A0macronuni0237` :latncpspkernPPk ` .6  * @ T  d:(d&D"X 0pb0bt6 l!"!"#0#6#D#J#P#V#`#$x%B& &'(j))*+*, ---"-4-F. ./0"142F2L3J3X3f4,46(6.6`6667(7Z7788>8h8888899:0:;\;<<<<<=>D>J?L?@@BBCDZDEfFHHJKpLLLMMMxN*N8NFOOO$V 0;%KOP[efghijkl<mpqt)uvwxyz{}~4)6H 0:;<5KOP[fghijklmt:u wxyz{}  Ofghijkl)0;Omwxyz{}~;fghijkln)0;Omwxyz{}~k X{!0:;< =sKOPZ[befghijkldmpqtuvwyz{|}- 4 (RR;<= = ==)=]fghijkpqvyz{== fghijkpqvyz{2 0;Omwxyz{}# =fghijklpqvyz{< 0Omwxyz{}2 =Ofghijklpqvyz{& OP > ={OPfghijklpqvyz{   =fghijklpqvyz{5 0Omwxyz{};x {0OPmwxyz{}xx  =fghijklpqvwxyz{}9 p=>Obfghijklnpqvyz{pp =fghijklpqvyz{Olmwxyz{} =lS 0=KOPfghijkl~mpqvwxyz{} O 0=KOPfghijklmpqvwxyz{}G 0=KOPfghijklmpqvwxyz{}<7 0Olmwxyz{} M 0=KOPfghijkmpqvwxyz{}, OP I 0:;<5KOP[fghijklmt:u wxyz{}  "f*g*h*i*j*k*p*q*v*y*z*{*****"" 1x 0O   =O /${ ! p;d<3=KOP^>`?{{-^VOO $ =% = =OP O- =IKOP- =IKOP1IKOP, =IKOP%O fghijkpqvyz{2 0;Omwxyz{}2  0;Omwxyz{}2 0;Omwxyz{}2 0;Omwxyz{}2  0;Omwxyz{}2 0;Omwxyz{}& OP < 0Omwxyz{}& OP & OP = 0;OPmwxyz{} = $0;OPmwxyz{}   4 44 1$0;Omwxyz{}  =fghijklpqvwxyz{}  =fghijklpqvwxyz{}D 0;=Ofghijklmpqvwxyz{}D 0;=Ofghijklmpqvwxyz{}D 0;=Ofghijklmpqvwxyz{}? =IOPfghijklpqvwxyz{}10;Omwxyz{}1 0;Omwxyz{}M 0=KOPfghijkmpqvwxyz{}      O    %%%***999% =I% =I% =I% =I% =I$ = , =IKOP- =IKOP@ OPfghijklpqvwxyz{} % I3  0;=Olmwxyz{} M 0=KOPfghijkmpqvwxyz{}=0;OPmwxyz{}'IKO/${ ! p;d<3=KOP^>`?{{-^V!;fghijklnopqvyz{!;fghijklnopqvyz{V 0;%KOP[efghijkl<mpqt)uvwxyz{}~4)6V 0;%KOP[efghijkl<mpqt)uvwxyz{}~4)6(0;Omwxyz{}~V 0;%KOP[efghijkl<mpqt)uvwxyz{}~4)6V 0;%KOP[efghijkl<mpqt)uvwxyz{}~4)6(0;Omwxyz{}~)0;Omwxyz{}~fghijkpqvyz{ ;fghijkln,${  R;d<3KOP^>`?{{-^V0${  R;d<3=KOP^>`?{{-^VV ~0;%KOP[efghijkl<mpqt)uvwxyz{}~4)6/   "&)|:<?ABCDELMNQRSTU/flR`j * 0>DZ|hT@ . p d n J  & *",Rx.p8V|T.Lj,JZ&<J`",v .3 568VWX! ).0379<ABCEMwxyz{} <"&.34568ABCDH.MRSTUW, $X "&).35 6789?ABCDEGHLMNQRSTUVWX_dx R )35678  )7<?BCEOl3H@ )l )3578H2V  )3578<V ,H " *<l*" <1"&.3568ABCDE HLMRSTUWmwxyz{}RUW <DUVWH] <  <Hll "&.3568<@ABCDMQRSTUW_ "&.3568<@ABCDMQRSTUW_ "&.3568<@ABCDMQRSTUW_ "&.3568<@ABCDMQRSTUW_ "&.3568<@ABCDMQRSTUW_ "&.3568<@ABCDMQRSTUW_?ABCEMSUVWX  "&.2?ABCDMRSTUVWX?ABCMSUVWX  ?ABCMSUVWX  "&.3568?@ABCDMQRSTUVWX_  "&.3568?@ABCDMQRSTUVWX_   < <<<444 < "&.3568@ABCDMQRSTUW_  ).035678<AV  ).035678<AV!  "&).35678<@ABCDMQRSTUVW_!  "&).35678<@ABCDMQRSTUVW_!  "&).35678<@ABCDMQRSTUVW_  ).035678<?ABCEFNQRSUVWX"&.3568@ABCDMQRSTUW_"&.3568@ABCDMQRSTUW_$ "&).2?ABCDEGHLMNQRSTUVWXl < <DRVW <DRVW <DRVW <DVW <DRVWDRVW <DRVWX <ABCMRUW <C <CRUV <CRUVW <CRUVW < < FI%%%F I VW*** < FIVW999 <VW <?DRTUVWX <?DRTUVWX <?DRTUVWX <?DRTUVWX <?DRTUVWX <?D ER UVW < < <VW <VW?ABCEMNQRSVX <?ABCEMNQRSVWX ).035678?ABCEMSUVWX  <?DVWX "&).3568@ABCDMQRSTUVW_  VW$ "&).2?ABCDEGHLMNQRSTUVWXl"&.3568?@ABCDEMNQRSTUVW_ <?ABCEMNRSUVW !#$%')*+-/12356789<DRTUVWXd !#$%')*+-/12356789<DRTUVWXd  "&)|.3 568:<?ABCDELMNQRSTUVWX  "&)|.3 568:<?ABCDELMNQRSTUVWX"&.34568ABCDHTMRSTUW"&.34568ABCDMRSTUW358 )35678<DRUVWXl < p@ABCDEFIMNS{{{VW p@ABCDEFIMNS{{{ < < < <$41 xxxpp   {{{p]9 | | |Q z 9f{}0678+.3$5=*?I3KX>Z\L^^O``PdQ    00:=FFKKOOZ[eqs{'}0MT[`degj  &)+./135:<<?ACF!HI%KN'PX+__4dd5fq6w{B}}GHIU[bjrsuvx)23+*+11 23 !"#$%&'/0 """"&& '**,-.,-.+/0*().&&////////// 0%.!#*+,*''(')  "$/////0000-!!!!!!!++++' + )%"-$ @ latn  aalt>dligFhistLligaRss01Xss02^ss03dss04jss05pss06v   (08@HPX`hHFPb`^\Z^|Y|Xp GJ\PVPP&YH&QRDEXDEQXslixmpp-slix-1.4.2/docs/_static/fonts/YanoneKaffeesatz-Thin.ttf000066400000000000000000003740441342457644200246010ustar00rootroot00000000000000GPOSq|GSUB@Q'DOS/2S``cmap C cvt hfpgmY7 sgaspg qlglyf]_sheadc6hheaET$hmtx'kern loca+- maxpx namet1[\|#post'nprepƝ8]sH _< |%`"z8 89zUi s!@ KYN @ 89  "  .MJ ,t''I'03,0; ***4/+-:.*:10(: @l-B_CSC-@@*@S@@@-z@-@{'j@z f  z*BZ//-)?;BBgB@%B.@//!En3}%{<b+)%)?s3*      l-_C_C_C_C-@-----e0@@@@f:z*z*z*z*z*z**Z/----0@%%%%%0&EEEE}}B-.{'/f%?.0))))))<3J % 3z'&0  py!343?sS JJ==JsJpJ`03A%=% +~l8'3}=0E/C&&Y9e06!= )+B9t99/Bz@<'n&   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYjkmov{[`^a]l}\eZ_dfizbhpgqnsturxyw~cHr@2";>]^z~1BSax7    " & 0 : D t !"!&"""""""+"H"`"e% #<?^_{1AR`x}7    & 0 9 D t !"!&"""""""+"H"`"d%qQE/+``F++y`ߝޫބޙ,rtvtzYZ[\]^_`abcdHr@2";>]^z~1BSax7    " & 0 : D t !"!&"""""""+"H"`"e% #<?^_{1AR`x}7    & 0 9 D t !"!&"""""""+"H"`"d%qQE/+``F++y`ߝޫބޙ,rtvtzYZ[\]^_`abcd,K PXYD _^-, EiD`-,*!-, F%FRX#Y Id F had%F hadRX#eY/ SXi TX!@Yi TX!@eYY:-, F%FRX#Y F jad%F jadRX#Y/-,K &PXQXD@DY!! EPXD!YY-, EiD` E}iD`-,*-,K &SX@Y &SX#!#Y &SX#!#Y &SX#!#Y &SX#!@#Y &SX%EPX#!#!%E#!#!Y!YD- ,KSXED!!Y-+++kQ7+qQ7++ E}iDK`RXY! B    .k+// /EX/ >YEX / >Y ++ и ии!и%и'и,и.01%#7##7#46;7#46;7>337>33+3#'37#4  G=PF  OFXXcXc  Q\Q\  Ms;d7+;/EX/>YEX/>YEX7/7>Y*+ и/01".546732>54&/.54675463.#"#"0 30$3"*p %GA *  8+7C"}*>K   !/#>'b8!/<f w '-1m&B*@Vc J/?P(8+0 +++EX/ >YEXE/E >YEXG/G >YEX+/+>YEX@/@>YEXB/B>Y+5014632#".732654.#"4632#".732654.#""'&50JLB 2"HA 3$! &3:%2;LB 2"HA 3$! &3:%2; %>N)` aX*G6\[ 'H6-:" NN.<"HQaX*G6\[ 'H6-:" NN.<"H=c}B,Qa˸b//!и!//4и4/bEиE/ZMиM/EX/ >YEX/ >YEX!/! >YEX$/$ >YEX:/:>YEX@/@>Y $)*и@_012&#">54.'>3:#'#"&'#".54>7.54>.'3264%2Q0&4P_, &-2\ ,  - N<9G(!*%0.<+b*$ 9-5C  !,#%myy1,' (G;,   3H. %,1+)#!,!4B 9/% Y01#".54>32k&58 9@3&.(93#DhS F}QkP5 Lq9&" +/EX/>Y014.'>32#"'>$399B63@885'D`qM Y0174632#"&'>'.A  "2* 00 * +0146;#0   ;|A "+EX / >Y0174632#"&;  //01#>30   *X P//и/EX / >Y+ 014632#"&732654.#"*We1B(Sc^T"GHSC 5(TG($KqL|CdB!*Y*Y%+ /EX/>YEX/>Yи/$ &01.50>3233:727#!4&<50>2623"T%+& e $+&.F   *X+R $+EX/>Y)+)9ии/"901&54>32;27#!.5467>54.#"E-?),7 >Ze'0 #_W=(!?T  !/?mii:  7ccg;"4X:7+ +1&++и/01#"&546732>54./>54.#".54>327*;S4:82(*E1/= AQ(!?M,@)+7 $.0#7)6]D' #=R/(. (X2    *3-'/["V+и /EX / >Y+ ии01%3+#5#&54>7>327_V'@T- $=:/    fvv)x`O\f3+X*C!+и/+*+ &+и/01+"'2636#"&54732>54.#"7F%*ND/5YB;<8)7K-)YEX/>Y'+/01">32#".54>32."32654&$D5"Y9":*\c6B$ %@S/(  54V 6-NQE)TW680J3w,NmAqc, A<3]G*raYR:X//+01"&5>7*"#"5+UNCCIK!1Pg9 Xи>/EX/ >YEX/>Y9)993D39012#"&54>7.'.54>.'32654&">54.;3"&.#'VX^\&3!# 0?q*5(&9'GI)j3(& )1%)1 "@4!5*  C9YdiT">3(  C$+?(x #0:!:*WF1> 3# 8  '.+4*b,e-/"/и/- и /" *и/*и/+'++01267#".54632#"&54672674.#"jnR;!;+\d5B$ {1#S0R7.NQMG38/I3}&KpJ  ?94`I,klYQ:{ >+ иEX/>Y +014632#"&4632#"&:1~ 5+ и /и/// +014632#"&4632#"&'>5&= 2* 00| ++01463!#463!#0SS[     :[,++$и$///EX / >YEX / >Yܸ 10174632#"&2"'&'.5467>54.#".54>a-2(')   (%"% @+ ( 8,*F:1, 205>&'-!  :Di| %+-+^L+r?+LGиG/LIиI/Ljиj/EX/>YEX4/4>YEX:/:>Y*+[Q+Dm+4ew014.#"32>7#".54>32#"&'#.7>3265<76.#"&547>3232>'.#"32>5AfHGvU/'8DN+.H8+"?Y6.VL?-4_N%$! 8./#-<#  $#8 9.9< +,!"1&'^PtK#)]n`]8   ;djuc,SuM,  ;00=$  %   4D$+ @$ 3('0   EX / >YEX / >YEX / >YEX/>YEX/>YEX/>YEX/>YEX/>Y+ 01%##*'432#*'%3'.'O6   =    M !?5( (5?!@y&3o4/-/4и/-&'EX/ >YEX/>Y3+%'0146;2+32>54.+32>54.+@ d;1 #*&-?Bw!a.#&1YU9-,:"T !;0.=$ 9/T`0 !9,%1 .O>.5-P3C-+EX/ >YEX&/&>Y &012.#"32>7#".54>" &# '/$"-&%!&*.   1RxS^p<    )GlNZX5 BY// и /EX/ >YEX / >Y 012+4634&+32>0H1!8I'v OUXU f+EX/ >YEX / >YEX/>Y+  01%+476;+3+>    C=m+ EX/ >YEX/ >YEX/>YEX/>Y + 013476;+3+#C  -m,g-//-(и(/ EX/ >YEX#/#>Y+#012.#"32>7#54;#".54>,<, 'A0*8#u 0"(D17L JflC  LqpQ@}/ /ии/ и EX / >YEX/ >YEX/>YEX/>Y +01!#463!463#b   p!O@a/+EX/ >YEX/>Y01463#@  O5+EX/ >YEX / >Y01463#"&54732>5/*,4 *#@5" -:@y{+EX/ >YEX/ >YEX/ >YEX/>YEX/>YEX/>Y014636232#*'.'.'#@  B|2 1.* -$NnL4 @? 5+EX / >YEX/>Y0173+463c  @;YEX/ >YEX/>YEX;/;>Y014;67>7>;#<6465+'4.'#@ x   w =<   y)"!' OG /9:66<;0 -9;6SQ?@&'//'и/и/и/#&и&/EX/ >YEX"/" >YEX/>YEX%/%>Y01.'#4;463+!   ~"  '=KJAFQRD, +@MJ?cO -'](//(и/ EX/ >YEX/>Y#014>32#".732>54.#"-0H10D*-H10D-##8'(;%#6&(;'c`U'%TbcW($U[XyL"#P]ZzK #P@^]// и / EX/ >YEX / >Y+012+#434.+326/@'\RO 4'TOHD2C%jh 7*`-~#7|8/./8и/. $EX/ >YEX/>YEX/>Y+)3014>32327#"&'.732>54.#"-0H10D*'<* + '. .C+##8'(;%#6&(;'c`U'%Tb\X.7&  6B&UYXyL"#P]ZzK #P@^#.//$//и/!и!/*EX/ >YEX!/! >YEX/>YEX/>Y,+)*012#.'"+#47634.+326/@'=8 !  ! O 4'TOHD2C%Vd 2Ld>HlK,8*`'W1}2/&/2/и// &/и/EX/ >YEX/>Y##9+9012.#"#".54732654./.546?; 9-9E$*A-#1!52IAr"'V  5,"6{)*,'B/  N>*((m#B'9Hd A+EX / >YEX/>Y и01##543!# T n  @`//и/ EX / >YEX/ >YEX/>Y0173267463#".5463a ;3(L  #-3>I% 'I8![ &AT.GEX/ >YEX/ >YEX/>YEX/>Y01&4;>7>;#"&'.'& {   !0,+-U7< Yel4yv=iEX/ >YEX!/! >YEX#/#>YEX&/&>YEX1/1>YEX4/4>Y01&4;3>7>733>7>;#"&''#"&'.'&l  Ze  b  ` a    +&&/M-D*&%#U/<11&;  Yel3y %zEX/ >YEX/ >YEX"/" >YEX%/% >YEX/>YEX / >YEX/>Y01+/+.623>?>3 !OTP<8 k #.6nH92& &1:yab+EX/ >YEX/ >YEX/ >YEX/ >YEX/>Y01&634>?>3# 8  4 !QG11FQ"ap5EX/ >YEX/>Y0143!3267#!&547!7 ) &    {;52 +EX / >Y+ 01+3+463 5 B   //01#&63   8 62 +EX/>Y +0146;#46;#    q +01463!#SQ  R //010.54>30%#R *E(;YEX/>Y,+$60147>32#.54>3254.#"&.#"3267>*X69=!+4).:/#6P-2&!* 3  5D@V< :35@" _% 5.*/ &vBx&r'//'и/ EX/ >YEX/>YEX/>Y#01463>32#"'732>54.#"BA02$'+,M'! (#70 +$@+H5JkJ,YEX/>Y$01.#"3267#".54>329 *3*!.%4+ 7("5@  2aRDT.  5bRZk9 /^%i&//&и/и/EX"/" >YEX/>Y" 01.#"3267463#".54>32<#7(#24I+!<,1A$, ;Y=KW- 53dTFfB -](:;/6/и/;$и$/и/.и63и3/EX/ >YEX/>Y.+)012+3267#".54>":>7>5."/B>7$2$C$0$=-"4?1* 7CG #):#:BO(   0[Mcs; -ZO" 3#S#p+и"EX/ >YEX/ >YEX!/!>Y +ии#0146;54>32.#"3++ G/7-%#-%uj  B:E$  :2@ 6 ):ASij/e/#и/#иj-и-/[EX/ >YEX/ >YEX/ >YEX(/(>Y (`TܹܸBL0123#"&'#"'#".54>7.54>7.54>"32>54&&'32>54. ~&!''>+'#->$0%0I12=#   '  ',;!/#1*!/B2& 1((<()C3&F5 )   &1(&%#   T1-C-%9&5)/;C@1  " &?f}//и/ EX/ >YEX/>YEX/>YEX/>Y01463>32#4.#"#? !I"&0 &I$2r(a;j&<%EX/ >YEX/>Y01>q&C+EX/ >YEX/>Y 01Bn8C+4и>4/,/EX / >YEX/>YEX!/!>YEX8/8>Y, ;4?01463>326"#>54&'.#"#4#">B )*(0)(* +*"     ?!M%+L9" -,$7( 6) !- =#+ #,6Bc/+EX/>YEX/>Y017#463c  B+,  +'++EX/ >YEX/ >YEX / >YEX/>YEX+/+>Y$01.#"#632>32#4.#"+( 7 OC*/ F $, 9   F!&8$o* O@kh/ /и/  EX/ >YEX / >YEX/>Y01>32#4.#"#@&R,)5 (!$# $5"j, H%g!]"//"и/EX/ >YEX / >Y0174632#".732654.#"%ZM&:'VP#:)" .YEX/>YEX/>YEX / >Y#01>32#"&'#"532>54.#"B!D*'>+&7=$5 !33.!"2! 52S=fx@ 5hY:L,.:e"v#//# и /и EX/ >YEX/>YEX/>Y01+5#".54632.#"367e @+4.m[#5! -MS&,C  0\N ~DN) "*@9+EX/ >YEX/>Y 01>32.#"#@"D#( ;+    !N/U23/%/и/и/3.и./.и/.и/EX/ >YEX/>Y ""9( 9012.#"#"&546732654&/.54>!0  7$.##z%%UM>C5*;G"#p,;   $M;&BA   +0#5E)!"t +и  /EX/ >YEX / >YEX/>Y иии01#46;54633+3267#".5rQG  /!  /"  ,:#Ep\//и/EX/ >YEX/ >YEX/>Y01%#".54>233267463p%Q#,8!  /$A -I45,;# PGEX/ >YEX/ >YEX/>YEX/>Y01&63367>?>3"#"&'  5  /   2b'.**.'b3EiEX/ >YEX%/% >YEX'/'>YEX)/)>YEX9/9>YEX;/;>Y01&633>?6;3>?>23+"&/.'+"&'.'& 0   & /   * <<       3Z#($:CH 0U"("8EK$E1]N==O]1|@IL$T!zEX/ >YEX/ >YEX/ >YEX!/! >YEX/>YEX / >YEX/>Y01#'.'#'&63>?>23 @e9 =Q 8 ].( T Y{"(Q?["<EX/ >YEX/ >YEX/>Y01&6367>7>3#"&54673267*]  U C,% " 7 12G YF  @R%_!EX/ >YEX/ >YEX/>YEX/>Y к 9 и /9013267#!&'&547#"&4543Z6= -B  L  8/"+/EX/>Y01#463   <:} ;+/EX / >YEX/>Y 01#"&54632#>3}Z bnm5m,+&/5/EX/ >YEX1/1 >YEX!/!>YEX'/'>Y !0132.#"3267+#5.54>754635+"1/% 1#+6   6fVEO(   v 1ZJTk?{ +40+и0*EX / >YEX/ >YEX/ >YEX#/# >YEX4/4>Y+ + $и(и+и/017#46;5'#46;&>23>?>233+3+#ˍ }w \~7  4 }j d y I7 E!ME11DN! 5K p  +и 014632#"74632#" " )A%5GI6&+ +.>+;1+)C++#+01.#"3267#".54>324632#".732654.#"!    # !hZ-D.cX,F2'9%KZ'9#&>+2*!& 1*4< ui3U?lo0U@6G*``7I,/L%o$I%EX/ >YEX/>Y014>7672#&'.4>7672#&'.% !+  '(  -" !+  &'  -" $+4?!(0;>2*" B6-%"(0<&-77,% ;0(")A!<Ei+47+%=++%*и*/=-и-/4A ++C2+014632#".732654.#"72+.'##47634&+326)hZ-D.cX,F2'9%KZ'9#&>+0$/ $) q%!ui3U?lo0U@6G*``7I,/L]+; :0+o Ik J //01"&'&'>12 0((2J*! &?0 + +014632#"&?Js%++017272632#"'&'6767632654&#"7>     @   [3z$HGEX/ >YEX/ >YEX/>YEX / >Y01&'&54>767&'.546767&'&54>?&'.546767z",  ('  *! ", H'  *! %-6B !*2?;0(!?4+$ "(0; %d7-&<0(">  :W+,+$и$/EX / >YEX / >Y ܸ 101#"&54632".54>7>54&762327-2(')   (%"& >, )V 8,*F:1, 205>&'-   & >h & a & b v& L {& ]t & x$+иEX/ >YEX/ >YEX/>YEX/>YEX / >YEX / >Y++01%+5##*'6;+3+'3xh e      144-JPVhP+EX/ >YEX&/&>YEXK/K>Y?4+,E+ &012.#"32>7272632#"'&'6767632654&#"7.54>" &# '/$ +     4+&*.   1RxS^p<    3   OD~iZX5 C>&$>ZCC&$aC>&$UC>{&$]g{&(>-&(a-&({&(]@v&-f-&.>-&.a-&.z-v&.d-{&.]eW&'7'>76'Y bb Z[ dc fpqggqp0+<GH/,/H и /,и/,! =EX/ >YEX/ >YEX/ >YEX/>YEX/>YEX&/&>Y8C01"&'4>7.54>3272#"&'4&'32>%.#"[ /H0$8 .G1&:% %,/(!=(;'#.';' *m`U'4*icW("u[{%^pvfL*"NOI #P@&4>@&4a@&4@{&4]a&8a:NyA+2+++EX/ >YEX/>YEXE/E>YEXG/G>Y&>014>32#".546732>54.54>54.#"#"'&'67>5A(8#6H-!%!+2+SE+ /&+!+2+!%!$:)?= %+;%+1" #,8&HG   * 2)#$'!'!AP  *E&?>O*E&?a*E&?I*J&?3*E&?][*E&?k*XIZlR7+.Z+j+7и/. и /.и/&и&/Z?и.`и`/jgиg/EX/ >YEX/ >YEX+/+>YEX2/2>Y`J++ E WиE[иJeиe/Jgиg/01467>32>32#"&'3267#"&'#.54>3254.#"&.#"3267"327>5..J<6: #(,C:-:a$1%C%0/H C84).:/%/I-2&!*9 .) %-3#%  ,9"(RF?"DM(  ,&;35@# ^% 70(-  'OE'AD/J:Kh B+EXG/G >YEX/>YEX=/=>Y1&+7+G01.#"3267272632#"'&'6767632654&#"7.54>329 *3*!.%49/     1$"5@  2aRDT. 3   O6_MZk9 -]&C>i-]&Ca-]&Cc-]&C]u~&>0&a0&&]@k&LT%g&M>[%g&Ma%g&MU%g&M@%g&M]h0r7+и +++01463!#4632#"&4632#"&0S   u& )9DE/*/E и /и/*и/и/*и/ :%и%/EX/ >YEX/ >YEX/>YEX"/">Y7@01"&50>7.546327#"&'4&'326'.#";  XM$8& TK*? " $),&0 / on]0N8ZEp&S>mEp&SaEp&ShEp&S]z?[&Wa?[&W]bBe/+EX/ >YEX/>Y017#463e  -q&:;//; и /ии/&и '1и1/EX/ >YEX/ >YEX/>Y!$+01%+5#".54>325476;+3+32>54.#"qH80D,/H04D$7')8" 4&';'  `91$Uh`V',2I  6XyL"#P]ZzK!$O.4GY5+M>+U+и/Mи#и#/URиR/EX/ >YEX / >YEX(/(>YEX0/0>YM+(:иCH0174632>32+3267#".'#".7326754.#"%":>7>5..XM6H %+.D9B>7$1%C$00) F1&<*" .;F.1"0* 7CG $z2B).RF?DN(  +#756fOCV1ir EX38Z,[R' 2"'W&2R/U&Q[a{&8]Rp&9b%_&XO?X!9+EX/>YEX/>Y+014>32.#"#"'&'7>5F);$+ 5( .  K+:#   3'   K- //EX/ >YEX/ >Y01632#&'.'#"&'n0  0  O ///01".5>32>767{  ! O & .EX/ >Y01632#&!" G  ++014632#"&732654&#"0()'-*%,-)&0*+%. W$/++ и /!и!/01>3232>32#".#"#"'&,  p'   0 * +01463!#0S    * +01463!#   )Gi+ //01#"&54>32d    f:5$ K)?j +/ /014632#"&'>5..   95$"#! )j_+ //0174632#"&'>5..   ?:4% "#! )G'G(//(и/и/"и"/ ////01#"&54>32#"&54>32d        f:5$ K :5$ K)? +K,//и/,и/$и$/// /!/014632#"&'>5.'4632#"&'>5.      95$"#!  95$"#! )_+K,//и/,и/$и$/ /!///0174632#"&'>5.'4632#"&'>5.      ?:4% "#!  :4% "#! <O + +014632#"&<  3A&'J 0@RbtcS+[k+A1+9I+!+)+EX/ >YEX/ >YEX/ >YEX/>YEX/>YEXYEX^/^>Y4N+.<F4VиFhиNp01"'&504632#".732654.#"4632#".732654.#"4632#".732654.#" &=O)`LB 2"HA 3$! &3:%2;KB 2"HA 2$ &3:%)KB 2"HA 2$ &3:%) =c~BRaX*G6\[ 'H6-:" NN.<"HPaX*G6\[ 'H6-:" NN.<"&='aX*G6\[ 'H6-:" NN.<"&=%$%EX/ >YEX/>Y0174>7672#&'.% !+  '(  -" $,5A")1==0(! @5-$3$GEX/ >YEX/ >YEX/>YEX / >Y017&'.54>?&'.546767", H'  *! $-5@ !(n=1)"A5,$z\ GEX/ >YEX/ >YEX/>YEX/>Y01#"&'632h   ]G:+и/ и /:5и5/:>и>/:CиC/EX0/0>Y +++0%5и9и>иB012.#"3+3+3267#".'#46;5467#46;>4# /)!   '/ : *:0 ] K` R '29]   %H= 1 DO(  0[N 0 FT,&4F69+04++ии/0+и+//4/9/EX/ >YEX/ >YEX/ >YEX>/> >YEX@/@ >Y5:и;01476;7>?46;#5464651+'.'###5476;#8  6 %' ug   t%( `e/ %'i""0 * +01463!#0S   12?@/1/@и/и/ и1,%и,.и./3и14EX / >YEX&/& >YEX3/3 >YEX/>YEX/>YEX./.>YEX0/0>Y +3иии/*и+и2и :и:/01+#46;54>32>32.#"3++'3547.#"S G#15 8B)(#-$uj, '$6 M9@   :2@ 6BD'4. X,ٸ-//- и / %и (и(/ +EX/ >YEX/ >YEX/ >YEX / >YEX / >YEX/>YEX(/(>YEX*/*>Y $и%и,01%#46346;54>32&#"3++G  F/9)1'/-%aV  B=F#  :3? 6 P(Ѹ)//)и/ и/!и$и$/'EX/ >YEX/ >YEX/>YEX/>YEX$/$>YEX&/&>Y + и!и(0146;54>32+.#"3++  F,1:+!'"ti B=F!?:5A 6=2@H?+$)+AD+$и?+и1и)3EX/ >YEX/ >YEX?/? >YEXH/H >YEX&/&>YEX(/(>YEX./.>YEX0/0>YEXD/D>Y+ и /"и#и*и+и2и9и9/0146;54>32>32&#"3++#+7467.#"3#463G#248B*0'/-%aV +($  M9@   :3? 66a$54.K65C ',++B+' иB.и4и,6EX/ >YEX!/! >YEXB/B >YEX/>YEX)/)>YEX+/+>YEX1/1>YEX3/3>Y+ и /%и&и-и.и5и<и32>32#.#"3++#+7467.#"3G#246:%2!' aV +($ M9@ >73F 66a%5 4.K!&RX/38g%67//7и/!&и.EX/ >YEX/>YEX/>Y )301"&54732>=#".54>32.#"3267YEX/ >YEX / >YEX"/">Y +ии$0146;54>32.#"3++G$ !  uj ~%, "y 64Kc5+/K/:+"+/0+0и/и//,и,/GиG/017".5467".54>75463.#"326732>7#1E,D1$0@& &7$ L3!9*!,+6L0$9'&9)  %<- 6*:"AL -%<)l } #& 1!% #(4-#f 3vmx+i+ a++иBи HиH/iWиW/EX=/=>Yd+M\+=1014.#"26767>>7675463>76732767#".5#32>7#".54632=  /- &O &$/ ,( " &'! DD:8%1*!5%$=,Tg'! *5 /^SP (2  "@6  LS(  2^O !<0!?xJK/C/K и / и/ и/$иC0EX%/% >YEX/>YEX/>Y+@5+%01#"'&'7>54>32.#"!#".546732654&'.#7g *=&) 5( . Z ?4"2H.!+ ))NR4)77   +9"    5(C %=-1RY+01463!#".546732654&'.#7 ?5"1I.!+ ))NS5)8  %=-1RYEX/ >YEX/>Y,+и01##46;5463!#".546732654&'.#7lc W ?5"1H. +  NQ5)8=  %=-1RYEX / >Y +и и !012+#46;4634&+3+32>0H1!8I'v0% OUX|qU YEX / >YEX / >Y0173+.?463ckk.. ) )U 5 G +и  //EX/>YEX / >Y017#.?463c::::w I bJ!cA+EX / >YEX / >Y+01.547>3:3#!&54;R., g    }`Jl:H"2+EX/ >Y/%+8+и/014632>54.#"&67>32#"&'&54732654&'.#.)##*@ D4#/ $*\O-:  0,Y+(01.54>323#!.5467>54.#"U'4$. /EO! $QD-#/;r '(GDB#  &BAA% "={"e +и  / /EX/ >YEX/ >Y+ ии/013+#"'5#&547>7632)OI +4;  00(   DQ QQI YEX / >YEX"/" >YEX$/$ >YEX/>YEX/>YEX=/=>Y+.N+и=801.547>3:3#!&54;#"&'632&54>323#!.5467>54.#"R., g  (   '4$. /EO! $QD-#/;  }`\  '(GDB#  &BAA% "J(&'iJ%&'w`; q+ и////EX/>YEX / >YEX/>YEX/>Y01#*'43:#*'432    X  b 0{\ ///01#"'%&547%\  n p   3w_// /01%>32.5473   np  Aiy // /017#"&'7'd v=i;;$+ и$7++/*+01##".54654&'.547>54&54>  + %# .%6$$ *9 #'cA62 '-Df/'+ 3&6e?*"   ;c4", i:;3 + и3'++*.+01&543>54&5467.54654.'.5432 + %# .IG% *9 #'cA62 (,Df/'+9L348 *"   ;c4!-  >! ++01>3232676#".#"& &"!+ %&     + (+и/$EX/ >YEX / >YEX / >YEX/ >YEX/ >YEX!/!>Y и/ии/014632'6'.7&547#"&'46    0       .l: ?//и/ ++014632#".73254&#"l0)((.) :,*&0*+!: `S,\+и'EX / >Y"+(+ и(01&+.5#46;54>32.#"3# QF*9!*9( .!<}<SA Rh; 2[I 8IDXY/K/и/и/Y:и:/%и%/:'и'/:VEX/ >YEX"/">Y ",012.#"#"&'&54732654&'.'.5467&'.546>54&'.'/= 2%27 $)& "# LC0< 2%27 $)&  $K@"@  ' !!.!8 %-8  ' !!.!8%-8y2 3%2 %3#/++$)+EX/ >YEX/>YEX/>YEX&/&>YEX)/)>Y01%#"'5#".54632.#"367#*'43:j  ;!A3g`&3! * NT+68    5[F  z>O.  v!jEX/ >YEX/>YEX/>Y+ +и ии 017#"'7#46;7#46;76323+3#: 8uw29 7{}2      =}?$7LgM/0/Mи/08-+5+и и /-=и=/5F014>32>32&'.'#".%32654&'&"32>75.'"= 4)* '7BB;  "0  &!/4 $+7!"" "$ 4( 0)"#FEHI 6#$'3U !'8:&4 N')'; 0 I+ и//++и 01#5#46;54633#463!#  S    E>p !//! и /EX/ >YEX/ >YEX/ >YEX / >YEX / >YEX / >YEX/>Y01%#"'#"&'43:3267463:pQ(S" A3%;   -:0 /*+//+и/и/!EX/ >YEX/>YEX / >YEX/>Y&012.'632#"&5464'&#"32>(B\Q 6M3 %F9Y\\1VHO'6#09 T8&[nN:lS2~~~1-Uop>Y80NdCBZ9EX/ >YEX/>Y 01!+3#C̝  2&@//и/ EX/ >YEX/>YEX/>YEX / >YEX/>Yиии01#"'##"'#.543!#T   ;]YY&//и/ EX/ >YEX/>YEX/>YEX / >YEX/>Yиии01#"'##"'#.543!#T   ;]55=%2+EX/>Y + 014>32.#"#"&54732>5/8"/#-$.9#.#-$;=D  73>C  829! $5^6//6и/%и-EX/ >Y2 +(+01>32#".54>3254.#"..#"3267B<.% A*)"-*#8'&!-  $/('._&!" 0"5#P$//$и/ EX/ >Y +014>32#".732654.#"0$2/FA.! #18 "'9P30R?jf.Q<4C'Z[5E(,G656//6!и!/!и!и/+1и1/+3и3/EX&/& >YEX/>YEX/>Y& 0и101!>54.#"#.546;.54>323#*"#7&';' 'L0H10C)! \J^o:ZzJ !K{Y8o`J >TkAZS&%P[DpV< !lc +//+01%#"&=!.5463!c 4o  =@@EX/ >YEX/>YEX/>Y 017>3+#"&'=]rdx * =1z+и,EX/ >YEX-/- >YEX/>Y'+-и01#"&54732>5#46;54>32.#"3#.9#.#-$/8"/#-$>C  82 G=D  73D z"B-8++01>3232676#".#"&>3232676#".#"&%!!+1)%%""-%% +<      p        <EX/ >YEX/ >YEX/>Y01>32!7!# q)> E d)%EX/ >YEX/>Y01 ȥbb" +$.8B +и/ и/ %и%/*EX / >YEX/ >YEX/ >YEX / >YEX!/! >YEX(/(>Y4/+и/и$и$/49и/>и>//?и?/0146'4632'6'.7&547#"'746'&54772'.7     0         1    9 )f%EX/ >YEX / >Y012#".54>">54&#".2#"&#"3263232654#"#"&=463232654&#"#"&#"3262654&#"#"&#";2/.#"#"&#"&#"#"&#";27'32654&#"#"&#"32632#"&#"3263232654&#"#"=7632326732632#"&#"3263232654#"#"&=326=463R^33^RR^33^[#&-.&$" #     k    F       "   :   :      3^RR^33^RR^3!&"%%"&!' p     p         b   " 7   ^"   $[ q    a) v z +01&456;#  zO/+0132>76#".546 (+  ! & %$ < W////EX/>YEX/>YEX / >YEX/>Y01632#"&'7632#"&'v p  w    =tEX/>Y 017623267#"&54>S  #%  " ''("9l& ///EX/>Y01#"'%&547%463!#f  mp  x  9e&// /EX/>Y01%>32.547463!#9   np    /&78/'/8 и /')и)/ .EX/ >YEX/>YEX / >YEX/>Y+301#"&54632.'.?.'63274'&#"32>"<7 %F9Y\\X(A /%_H# $R1VHO'6#09 Ev:lS2~~~6_*& ! 1-Uop>Y80NdB8w.//&//и/и&EX/ >YEX/>YEX/>YEX/>YEX/>Y!+01>32#"&'"#"&546332>54.#"c5 '>+')'$- -51#"0"6 3T@B`B)d6gV:L,@^"y#//#и/ иEX/ >YEX/>Y+ + и/0146;32+#4.+:2126@ V/@'\RO  4'THD2C%jh7*`<| 7+/EX/ >YEX / >Y0174632#"&&63#< >j5+EX/ >YEX/>Y 01#"&54732>5463j -%5:2&! %#:) #2!'G.//%// и /и/  и / %и/%"и"/%* //%/*///////01#"'.54676:3"#"'.54676:3A  k I!,0 &,,$!,0B@1 6n*Bj@vX4 8 b j R X ,V6P.H2J~ $Ff,V:Z r !P!r!"4"##$$$$%*%&:&F&R&^&j&v&'''''''((((&(2(>(J(V(b()H)T)`)l)x)*(*4*@*L*X*d*p+,",.,:,F,R,^,j,v,,,,,,,-----....D.//////0H00011\1t11122t23@3`3p44525l66678~9 ::;;< <=v>,>???@.@x@AFABBBC C2C^CCD^DE6E|EFG6GH>HIIIJFJK KKL|LLMlMNNN:OPPPQ&QdQQRSS~SST~` B    "&).035 6 8; ?ABCDEKLMNOPQRSTUVWX[efghijkl#mpqt!uvwxyz{}0,'    " & . 0 9 : ;% <= ? A B C H K L M O P R S T U V W X [ _ f g h i j k l m t3 u w x y z { }              )x . 3 7 9 A B C E M O f g h i j k l~    " & . 0 3 4 5 6 8 ; A B C D H: M O R S T U W m w x y z { } ~    )2356789;DRTUVWXdfghijkl"&.034568;ABCDMORSTUWmwxyz{}~, - __  !"&).03*56*78#9:;$<=s?ABCDEGHKLMNOPQRSTUVWXZ[_bdefghijklrmpqtuvwyz{|}#5#%,AN,,NN,)35678;<= = ==  )7=.?BCEfghijkpqvyz{==353Hg )fghijkpqvyz{)3578HPV   ,  " & . 0 3 5 6 8 ; @ A B C D M O R S T U W _ m w x y z { } !! ! !! !)!3!5!7!8!=!R!T!U!V!W!f!g!h!i!j!k!l!p!q!v!y!z!{!!!!!!!!!!!!!!!!!!!!!!!"" " """"""""&"."0"2"?"A"B"C"D"M"O"R"S"T"U"V"W"X"_"m"w"x"y"z"{"}"""""""""""""""""""""""""""""""""""""""""""""""""## # ## #)#3#5#6#8#=#?#M#O#V#f#g#h#i#j#k#l#p#q#v#y#z#{#####################################$ $$$$?$A$B$C$M$O$P$S$U$V$W$X$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $ $ $ $ % % p%%p%%%% %)%=a%?%A%B%C%E%M%O%P%R%S%T%U%V%W%X%_%d%f%g%h%i%j%k%l%p%q%v%y%z%{%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % %p% % %p%p%%% & &&8&T&V&W&&&&'() ) ))) )))=)V)f)g)h)i)j)k)l)p)q)v)y)z){)))))))* * **#*"*)*.*0*?*A*B*C*D*M*O*R*S*T*U*W*_*m*w*x*y*z*{*}********************************* * * * ******** +f+ + a+++++"+&+.+0+2+3+5+6+8+?+A+B+C+D+M+O+P+R+S+T+U+W+_+m+w+x+y+z+{+}++++++++++++++++++++++++++++++++++++f+f+f+f++++++++f.. . . .. .)...3.5.6.7.8.=.A.V.f.g.h.i.j.k.l.p.q.v.w.x.y.z.{.}................/ / ///// /)/7/8/9/=)/?/A/B/C/M/O/V/_/b/f/g/h/i/j/k/lY/p/q/v/y/z/{////////////////////////////////////////00 0 00 0)030507080=0V0f0g0h0i0j0k0l0p0q0v0y0z0{0000000000000011"1.13151617181B1C1M1O1V1W1l1m1w1x1y1z1{1}1111111111111111122)2=2D2T2U2V2W2l222222222222233 3 33333333 3"3&3)3.303=3?3A3B3C3D3E3F3G3H3K3L3M3N3O3P3Q3R3S3T3U3V3W3X3_3d3f3g3h3i3j3k3lu3m3p3q3v3w3x3y3z3{3}3333333333333335333333333333333333333333333333333333333333335 5 5555555 5"5&5)5.505=5?5A5B5C5D5E5G5H5K5L5M5N5O5P5Q5R5S5T5U5V5W5X5_5d5f5g5h5i5j5k5l5m5p5q5v5w5x5y5z5{5}55555555555555555555555555555555555555555 5 55 5 55555555555 66 6666666 6"6&6)6.60696=6?6A6B6C6E6K6L6M6N6O6P6Q6S6T6U6V6W6X6_6d6f6g6h6i6j6k6l6m6p6q6v6w6x6y6z6{6}666666666666666D6666666666666666666666666666666667 7 7777"7&7.707?7A7B7C7D7M7N7O7R7S7T7U7W7_7l7m7w7x7y7z7{7}777777777777777777777777777777777 7 7 7 77777777 88 8888888 8"8&8)8.80828=8?8A8B8C8D8E8G8H8K8L8M8N8O8P8Q8R8S8T8U8V8W8X8_8d8f8g8h8i8j8k8m8p8q8v8w8x8y8z8{8}888888888888888888888888888888888888888888888888888888899 999?9A9B9C9D9E9M9N9O9P9R9S9U9V9W9_9999999999999999999999999999999999999999999: 4:::HM;; ; &;&;#; .;f.;g.;h.;i.;j.;k.;p.;q.;v.;y.;z.;{.;.;.;.;.;;;&;;;&;&;< =)==g====="=&=.=0=3=5=6=8=A=B=C=D=E=H =M=O=R=S=T=U=W?? ?D?R?V?W??????????????@@ @ @@@=@R@U@X@@@@@@@@@AA AAAAABACAMAOARA_AAAAAAAAAAAAAAAAAAAABBBBCC C CCCRCUCVCCCCCCCCCDDD tD D DDDDtD;yD<D=D@DADBDCDEDF DKDMDNDODPDSDUD^?D_D`>DDDDDDDDDDDDDDDDDDDDDDDDDDDDkDDDDDDDDDHDEEEAEBECEHxEMEOEVEWEX EEEEEEEEEEEEEEEE EEEEEFFFRFUFWFFFFFFFFGHHHDII II#IAICIMINIOIUIWIIIIIIIIIIIIIIIIIIIIKK KKDKUKVKWKKKKKKKKKKKKKLL LLVLLLLLMM M M MMM=MDMRMTMUMVMWMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNN N N NNNN=N?NDNHNRNTNUNVNWNXNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNOOOOOOOPP P PPPP=PAPBPCPEPMPNPOPPPVP_PPPPPPPPPPPPPPPPPPPPPPPPQQ QQQVQWQQQQQQQQQRR R RRR#RRARBRCRERMRORRRSR_RRRRRRRRRRRRRRRRRRRRRRRRRRRSS SSSSSSTT T TTTTTTT=T?TATBTCTETITKTMTNTOTPTQTRTSTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTUU U UUUUUU=U?UAUBUCUEUIUKUMUNUOUPUQURUSUVUXUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUVV VVVVV?VAVBVCVDVEVIVKVMVNVOVPVQVRVSVUVVVWVXVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVWW W WWWWW=W?WAWBWCWEWIWKWMWNWOWPWQWRWSWVWXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWXX XXAXBXCXEXMXNXOXRXSXUXVXWXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZ&ZH^[[ [[[[[\^_3_5_6_8`d dd)d3d5d6d7d8d9dTdUdVdWdXddee e eHefegeheiejekepeqeveyeze{eeeeeeeeeff f ffff"f&f.f0f3f5f6f8f;f@fAfBfCfDfMfOfQfRfSfTfUfWf_fmfwfxfyfzf{f}ffffffffffffffffffffffffffffffffffffffffgg g gggg"g&g.g0g3g5g6g8g;g@gAgBgCgDgMgOgQgRgSgTgUgWg_gmgwgxgygzg{g}gggggggggggggggggggggggggggggggggggggggghh h hhhh"h&h.h0h3h5h6h8h;h@hAhBhChDhMhOhQhRhShThUhWh_hmhwhxhyhzh{h}hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhii i iiii"i&i.i0i3i5i6i8i;i@iAiBiCiDiMiOiQiRiSiTiUiWi_imiwixiyizi{i}iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiijj j jjjj"j&j.j0j3j5j6j8j;j@jAjBjCjDjMjOjQjRjSjTjUjWj_jmjwjxjyjzj{j}jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjkk k kkkk"k&k.k0k3k5k6k8k;k@kAkBkCkDkMkOkQkRkSkTkUkWk_kmkwkxkykzk{k}kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkl ll?lAlBlClElMlOlPlSlUlVlWlXllllllllllllllllllllllllllllllll l l l l mm mmm"m&m.m0m2m?mAmBmCmDmMmOmRmSmTmUmVmWmXmmmwmxmymzm{m}mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmn nn?nAnBnCnMnOnPnSnUnVnWnXnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn n n n n o oo?oAoBoCoMoOoPoSoUoVoWoXoooooooooooooooooooooooooooooooo o o o o p pppp"p&p.p0p3p5p6p8p;p?p@pApBpCpDpMpOpPpQpRpSpTpUpVpWpXp_pmpwpxpypzp{p}ppppppppppppppppppppppppppppppppppp p p p pppppppppppp q qq qq"q&q.q0q3q5q6q8q;q?q@qAqBqCqDqMqOqPqQqRqSqTqUqVqWqXq_qmqwqxqyqzq{q}qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq q q q qqqqqqqqqqqq s t/t 4t/t/t/t/t/uu uuuuuvv vv"v&v.v0v3v5v6v8v;v@vAvBvCvDvMvOvQvRvSvTvUvWv_vmvwvxvyvzv{v}vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvww w ww w)w.w3w5w6w7w8wAwVwfwgwhwiwjwkwlwpwqwvwwwxwywzw{w}wwwwwwwwwwwwwwwwxx x xx x)x.x3x5x6x7x8xAxVxfxgxhxixjxkxlxpxqxvxwxxxyxzx{x}xxxxxxxxxxxxxxxxyy y yyyy y"y&y)y.y0y3y5y6y7y8y;y@yAyByCyDyMyOyQyRySyTyUyVyWy_yfygyhyiyjykylymypyqyvywyxyyyzy{y}yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzz z zzzz z"z&z)z.z0z3z5z6z7z8z;z@zAzBzCzDzMzOzQzRzSzTzUzVzWz_zfzgzhzizjzkzlzmzpzqzvzwzxzyzzz{z}zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz{{ { {{{{ {"{&{){.{0{3{5{6{7{8{;{@{A{B{C{D{M{O{Q{R{S{T{U{V{W{_{f{g{h{i{j{k{l{m{p{q{v{w{x{y{z{{{}{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{|}} } }}}} })}.}3}5}6}7}8}?}A}B}C}E}F}I}N}O}P}Q}R}S}U}V}W}X}f}g}h}i}j}k}l}p}q}v}w}x}y}z}{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}"&.03568;@ABCDMOQRSTUW_mwxyz{}"&.03568;@ABCDMOQRSTUW_mwxyz{}  "&).02?ABCDEGHKLMNOPQRSTUVWXfghijkmpqvwxyz{}  DRVW DRVW DRVW DVW DRVWDRVW DRVWX ABCMOR C CRUV CRUVW CRUVW - F-----.FVW.....1 FVW11111 VW ?DIRTUVWX ?DIRTUVWX ?DIRTUVWX ?DIRTUVWX ?DIRTUVWX ?DERUVW   VW VW ?ABCEIKMNOPQRSVX ?ABCEIKMNOPQRSVWX  ).35678?ABCEMOPSUVWXfghijklpqvwxyz{}   ?DIVWX"&).03568;@ABCDMOQRSTUVW_lmwxyz{}VW  "&).02?ABCDEGHKLMNOPQRSTUVWXfghijkmpqvwxyz{} "&.03568;?@ABCDEMNOPQRSTUVW_mwxyz{} ?ABCEIKMNORSUVW )2356789;DRTUVWXdfghijklpqvyz{ )2356789;DRTUVWXdfghijklpqvyz{   "&).035 6 8; ?ABCDEKLMNOPQRSTUVWX[efghijkl#mpqt!uvwxyz{}0,'   "&).035 6 8; ?ABCDEKLMNOPQRSTUVWX[efghijkl#mpqt!uvwxyz{}0,'"&.034568;ABCDHwMORSTUWmwxyz{}~   "&).035 6 8; ?ABCDEKLMNOPQRSTUVWX[efghijkl#mpqt!uvwxyz{}0,'   "&).035 6 8; ?ABCDEKLMNOPQRSTUVWX[efghijkl#mpqt!uvwxyz{}0,'"&.034568;ABCDHoMORSTUWmwxyz{}~"&.034568;ABCDMORSTUWmwxyz{}~358 )35678DRUVWXfghijkpqvyz{  t  c;y<@ABCDEF KMNOPSU^?`>kHVW t  c;y<@ABCDEF KMNOPSU^?`>kH       "&).035 6 8; ?ABCDEKLMNOPQRSTUVWX[efghijkl#mpqt!uvwxyz{}0,'! *7*Lv | u   "/Qk  ,A m >{ ,  * T)  }   8 8 D 43 "g Copyright (c) 2010, Yanone (http://yanone.de/typedesign/). All rights reserved. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: http://scripts.sil.org/OFLYanone KaffeesatzThinYN: YanoneKaffeesatz-Thin: 2010Yanone Kaffeesatz ThinVersion 1.002YanoneKaffeesatz-ThinYanone Kaffeesatz is a trademark of YanoneYanoneYanoneYanone Kaffeesatz was first published in 2004 and is Yanone's first ever finished typeface. Its Bold is reminiscent of 1920s coffee house typography, while the rather thin fonts bridge the gap to present times. Lacking self confidence and knowledge about the type scene Yanone decided to publish the family for free under a Creative Commons License. A decision that should turn out one of the best he ever made. It has been downloaded over 100,000 times to date from this website alone, and you can witness Kaffeesatz use on German fresh-water gyms, Dubai mall promos and New Zealand McDonald's ads. And of course on coffee and foodstuff packaging and cafe design around the globe. In 2009 he reworked much of the typeface and it got published in FontShop's FontFont Library under the new name FF Kava. You can read more about it in an extensive article by Yves Peters on the FontFeed.http://yanone.de/typedesign/http://yanone.de/typedesign/SIL Open Font License, Version 1.1http://scripts.sil.org/OFLYanone Kaffeesatz ThinCopyright (c) 2010, Yanone (http://yanone.de/typedesign/). All rights reserved. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is available with a FAQ at: http://scripts.sil.org/OFLYanone Kaffeesatz ThinRegularYN: YanoneKaffeesatz-Thin: 2010Yanone Kaffeesatz ThinVersion 1.002YanoneKaffeesatz-ThinYanone Kaffeesatz is a trademark of YanoneYanoneYanoneYanone Kaffeesatz was first published in 2004 and is Yanone's first ever finished typeface. Its Bold is reminiscent of 1920s coffee house typography, while the rather thin fonts bridge the gap to present times. Lacking self confidence and knowledge about the type scene Yanone decided to publish the family for free under a Creative Commons License. A decision that should turn out one of the best he ever made. It has been downloaded over 100,000 times to date from this website alone, and you can witness Kaffeesatz use on German fresh-water gyms, Dubai mall promos and New Zealand McDonald's ads. And of course on coffee and foodstuff packaging and cafe design around the globe. In 2009 he reworked much of the typeface and it got published in FontShop's FontFont Library under the new name FF Kava. You can read more about it in an extensive article by Yves Peters on the FontFeed.http://yanone.de/typedesign/http://yanone.de/typedesign/SIL Open Font License, Version 1.1http://scripts.sil.org/OFLYanone KaffeesatzThin   "#$%&'()*+,-./0123456789:;<=>?@BCDEFGHIJKLMNOPQRSTUVWXYZ[\]_bcdefghjikmlnoqprsutvwxzy{}|~     !A^`a.nullperiodcenteredlongsEuroffffifflt_zg.ss01f.ss02ampersand.ss03ampersand.ss04germandbls.ss05z.ss05t_z.ss05uni2074Deltauni00A0macronuni0237` :latncpspkernP6Pk~ ~\&*~  4 ~ H X.X8L$\nNN`" X!!"##"#0#6#<#B#L#$d%.%&'(V()*|+, ---- -2-.z.01 2228363455606b6667*7t778808Z8l8~8889b9:;$; >??@x@ABCdD"DEFtGHlIKKL`LnLLMMMMNNNU 0; KOP[efghijkl#mpqt!uvwxyz{}0,'H 0:;%<=KOP[fghijklmt3uwxyz{} Ofghijkl~)0;Omwxyz{}~;fghijkl)0;Omwxyz{}~k, _ !0:;$<=sKOPZ[befghijklrmpqtuvwyz{|}#5#%,ANN;<= = ==)=.fghijkpqvyz{==fghijkpqvyz{2 ,0;Omwxyz{}# =fghijklpqvyz{< 0Omwxyz{}2 =Ofghijklpqvyz{& OP > p=aOPfghijklpqvyz{ pp =fghijklpqvyz{5 #0Omwxyz{} ;f a0OPmwxyz{}ff  =fghijklpqvwxyz{}9 =)ObfghijklYpqvyz{ =fghijklpqvyz{Olmwxyz{} =lS 0=KOPfghijklumpqvwxyz{}5O 0=KOPfghijklmpqvwxyz{} G 0=KOPfghijklmpqvwxyz{}D7 0Olmwxyz{} M 0=KOPfghijkmpqvwxyz{},OPI 0:;%<=KOP[fghijklmt3uwxyz{} &#f.g.h.i.j.k.p.q.v.y.z.{.....&& )g0O   =O /  t;y<=KOP^?`>kHO #O $ =% = =OP #O- =IKOP- =IKOP1IKOP, =IKOP%O&fghijkpqvyz{2 0;Omwxyz{}2 0;Omwxyz{}2 0;Omwxyz{}2 0;Omwxyz{}2 0;Omwxyz{}2 0;Omwxyz{}& OP < 0Omwxyz{}& OP & OP = 0;OPmwxyz{} =  0;OPmwxyz{} ///1 0;Omwxyz{}  =fghijklpqvwxyz{}  =fghijklpqvwxyz{}D 0;=Ofghijklmpqvwxyz{}D 0;=Ofghijklmpqvwxyz{}D 0;=Ofghijklmpqvwxyz{}? =IOPfghijklpqvwxyz{}10;Omwxyz{}10;Omwxyz{}M 0=KOPfghijkmpqvwxyz{}       O  ---...111% =I% =I% =I% =I% =I$ =, =IKOP- =IKOP@ OPfghijklpqvwxyz{} %  I30;=Olmwxyz{}M 0=KOPfghijkmpqvwxyz{}= 0;OPmwxyz{}'IKO/  t;y<=KOP^?`>kH;fghijklpqvyz{;fghijklpqvyz{U 0; KOP[efghijkl#mpqt!uvwxyz{}0,'U 0; KOP[efghijkl#mpqt!uvwxyz{}0,'(0;Omwxyz{}~U 0; KOP[efghijkl#mpqt!uvwxyz{}0,'U 0; KOP[efghijkl#mpqt!uvwxyz{}0,'(0;Omwxyz{}~)0;Omwxyz{}~fghijkpqvyz{;fghijkl,  c;y<KOP^?`>kH0  c;y<=KOP^?`>kHU 0; KOP[efghijkl#mpqt!uvwxyz{}0,'.  "&).35 6 8:<?ABCDELMNQRSTUVWX )x.0<wxyz{}.:i.DHv $V~j&    $ : 4 @ H "Dj,VTL~42X(zJhvR\379ABCEM <"&.34568ABCDH:MRSTUW, -_ "&).3*56*78#9?ABCDEGHLMNQRSTUVWX_dx,,N, )35678  )7<?BCEOl3Hg )l )3578HPV  )3578<V 4HM & .<l.& <1"&.3568ABCDEH MRSTUWmwxyz{}RUW <DUVWH^ < < Hl "&.3568<@ABCDMQRSTUW_ "&.3568<@ABCDMQRSTUW_ "&.3568<@ABCDMQRSTUW_ "&.3568<@ABCDMQRSTUW_ "&.3568<@ABCDMQRSTUW_ "&.3568<@ABCDMQRSTUW_?ABCEMSUVWX  "&.2?ABCDMRSTUVWX?ABCMSUVWX  ?ABCMSUVWX  "&.3568?@ABCDMQRSTUVWX_  "&.3568?@ABCDMQRSTUVWX_   <  4<4/// <"&.3568@ABCDMQRSTUW_  ).035678<AV  ).035678<AV!  "&).35678<@ABCDMQRSTUVW_!  "&).35678<@ABCDMQRSTUVW_!  "&).35678<@ABCDMQRSTUVW_  ).035678<?ABCEFNQRSUVWX"&.3568@ABCDMQRSTUW_"&.3568@ABCDMQRSTUW_$ "&).2?ABCDEGHLMNQRSTUVWXl < <DRVW <DRVW <DRVW <DVW <DRVWDRVW <DRVWX <ABCMR <C <CRUV <CRUVW <CRUVW < <FI---FIVW... <FIVW111 <VW <?DRTUVWX <?DRTUVWX <?DRTUVWX <?DRTUVWX <?DRTUVWX <?DERUVW < < <VW <VW?ABCEMNQRSVX <?ABCEMNQRSVWX ).035678?ABCEMSUVWX  <?DVWX"&).3568@ABCDMQRSTUVW_VW$ "&).2?ABCDEGHLMNQRSTUVWXl"&.3568?@ABCDEMNQRSTUVW_ <?ABCEMNRSUVW )2356789<DRTUVWXd )2356789<DRTUVWXd  "&).35 6 8:<?ABCDELMNQRSTUVWX  "&).35 6 8:<?ABCDELMNQRSTUVWX"&.34568ABCDHwMRSTUW"&.34568ABCDMRSTUW358 )35678<DRUVWXl < t@ABCDEF I MNSUVW t@ABCDEF I MNSU < < < <||40 pp fff  t  xD   o  9f{}0678+.3$5=*?I3KX>Z\L^^O``Pd}Qk      00:=FFKKOOZ[eqs{&}}/0JQX]abdg  &)+./135:<<?ACF!HI%KN'PX+__4dd5fq6w{B}}GHIU[bjrsuvx)23+*+11 23 !"#$%&'/0 """"&& '**,-.,-.+/0*().&& /%.!#*+,*''(')  "$////-!!!!!!!++++' + )%"-$ @ latn  aalt>dligFhistLligaRss01Xss02^ss03dss04jss05pss06v   (08@HPX`hHFPb`^\Z^|Y|Xp GJ\PVPP&YH&QRDEXDEQXslixmpp-slix-1.4.2/docs/_static/haiku.css000066400000000000000000000200131342457644200203670ustar00rootroot00000000000000/* * 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 * Stephan Assmus * Braden Ewing * Humdinger * * :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: 30em; max-width: 70em; color: #444; text-align: center; } 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: auto; margin-top: 20px; margin-bottom: 50px; font-size: 0.9em; width: 700px; text-align: left; } /* heading and navigation */ div.header { position: relative; margin: auto; margin-top: 125px; height: 85px; padding: 0 40px; font-family: "Yanone Kaffeesatz"; text-align: left; width: 750px; } 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: auto; margin-top: 0; margin-bottom: 0px; text-align: right; font-size: 0.8em; width: 750px; } 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; } #from_andyet { -webkit-box-shadow: #CCC 0px 0px 3px; background: rgba(255, 255, 255, 1); bottom: 0px; right: 17px; padding: 3px 10px; position: fixed; } #from_andyet h2 { background-image: url("images/from_&yet.png"); background-repeat: no-repeat; height: 29px; line-height: 0; text-indent: -9999em; width: 79px; margin-top: 0; margin: 0px; padding: 0px; } slixmpp-slix-1.4.2/docs/_static/header.png000066400000000000000000000403141342457644200205200ustar00rootroot00000000000000PNG  IHDRgIDtEXtSoftwareAdobe ImageReadyqe<PLTE@:F?IDATx]ks9>_SSB+d0² Y6ɛIӗþ䂍"ktO4M;SHq%?M8wBc)撷BnrYrE+ZޑJ8¯83 Ap>^Nc# [WcIjէhJ͸¡-N~>!4'Dmp ุQT+<[3M%NdMR qq~p1Ĕ -+I-P()ʎjMHD kK)lW$6Z+׋ZN+*!a5i,I,CN% B$IN2ɱ6G$iyM{N׼i9q_⎂S—iCG3*[ڻjîsY(Lk %Fȡ{gI"+2Iaq#H8Ygen֌I؍#^+?_ "鷦!]@ j\h%w!jK_!*$@s}1ζ+g]__;m>;u}a6ST*qIkJ]ȕr&k{Zy|,3"6o|R򊭃6ڟ$imi(dkڈ:1k|ޭ+*H[*`jI$Jl_X;Y+czKԜu+`Ls j?<!ݟ(w)3$*8md_{ f k'!=dmVuu 3ƈߎZĎ5.έ5dMk2_  (\Rc;|H  |%^V|E v|V&K()Ҙ X)b TTKh^4d!|QR.!El'@هYe`KqK!'m%;p8*;Pѻ_n1?rم-)Fe<,]~E1xZ2U} U사;f:,`hVҦsLu;!1a~OЦx0ăC*+4%c8張Y EOڔsZC /-^ȸ#nju6Հy/Hbr;l7%^mZ،i^XLc`uy>X}g(EjgQ#=/K",ʀM i O%:눑;rDl%y_ 2Er:+t&k##k!cfUg226pc Nm&{;#}!92h<~-zI{,T!>ZDآ ;v2| HϪ ӛĎthG3ldպV:w=@H`o8zY题1[l]@`#TT\Hi->,f` Xٺ Vgm! fe=?]CWvø6p;qY/aaۄ, lTY?ek_kT`r1S;B)CN?gdGӣz $K|CRYw̺5-܅rɽ?f۹ Uȏ]3ec&LX ݾ'D盚.1 :Y HifjΪ#ꊲ d$ c]ܕLYg䂽a6uHEZ,4շUg!qSk]R6C (>>΂n.?g >ċHKK.N9fG)F?ٴác1BFaF1fpڔs).ë7Aop4ЦBp*u; }v1wL}8;zh!ogOݜ‌Y%S^i\b1g\V#Xm^췼m;$ ,`v 7ۀ 8WCZʈlRλg[8 ً撨I,IjHqBemO!^.O9E޷OBБ@%W هx @ɄU 2Zz +&HF*0Wj^=r'}|}i;^fL=.0`,P_nځ˵ ZFU*Ie"%4\<3SH3,hsm)FFZq=jK)nG4A(=1DGCshP*?D gaba~f+2)ŅwNY]{9LƬT1ږC Y;@:_Di짊b3<-2~rJ9$1u hॲ|^N p¦xZ׭>" Ġ$&rٓ{ PfVZ2}`c4c³' 5<QH)Y-B 1Tk9LbUk;I>$V }re-98v|r9Wc04Rj61%K1ʫ1TJ$5R1Z˶5v1AYlXUѪD $d`.\Ruoy w_*fYj̓&M/Ҳ #Kz,Ttr~mSz䜼9oP`Y~Mk [uߓ[aV;h2;w';{^jwNiJ/FӽQ*î!EۏsQ@n^>~vzBهe?G1񐌭FC!NCʹ)6ErSl9MZ1cexxvt|Bh%83dSX`5gο]\oBY_}xE?{&1lB`*U/V{޻MNs,]<֭HijiNj2p4o[BXwEx*l1`,O~ e~rY_6/MS1,1՘ƚwW 2λ΂.^9CK)I]jLه`vz)EN- s 'b-K*Lα^ MXύxVar8_G8n>ETGrЋwAG#Q=8Kr}>;yEVL$c<i lcQ| gAQk.Fgd=nM-`!w[Q`oa*#mn><ɲv7u_mtƎ(Cpٶyʕy12։vOk Ƨ4` d]'* Q uBԝj~Pd* oxZ5T77mptQO9Ʊ72 "ہ8M%}lX&9\, [[ L"/}gk{m3{CaQڼuXђKA=<]_!\/)W3{n}l/5wTy{096m_q1hc.6Dok% LM;xY:Jw'CZ.rpUv~s\`Z-r?L<6td6EgXjq;2+i)ƍ j`d2Sk/-e n=(]^$T2;8 {nu|zCŦ\[1n;E՟^;,X(k]r .%7[sm륛"t|"ˡβBCHDK-!_[~:z0N^x6%gNJzcxGU\Kʰzg|!p8jk3ΦpXZ6dZ.#&^׏$v#^}-eamfmF`B@az?΂NN=#= _/@OpݴA^L!%XwF_Z)]?Z1g/ D+ʹʹʹ>S8Q`fm I,[HXTL%5rV1BVܑ8>ݓ B w1 KTx&?rWQi{>U2=8f1+Ɋ \88.@.(/~{Fa7*ZMQ:~Bc9/sHm,e@-S3zzO1ȔPi)X*+V!S]XK׭SkV9])"&ofyp6O h)DfMqʖ^[B b)#R_:\8?wфmhSI5ժi1ό<ЂN1&78VPC̟Z,Ƭ*};z5@vh帛1vG;zcm=QM9`]87p."2l9!"R4)E; OH_z‹G@ m֛۷}ry{DN-"O7d&ky@#]ӵn!&cz,jDg| 65H IZ&/O_Y:ZF2ˌ>̪15gf*/'A!bm٭!X^[696Y2#I^8M acA dkUI 23Q#oDEFa]MΒkng y%O jhޛzU$D\ʃWw̞ԗm})y3byo*l ;$BV4ŤR) jl~Oıdme3v$bKs #F) "KJvb}ÎRJy<8;ߙVD6m$Lmci# wL>X.?GDZL2FwUbj~" /Gy]vu*6{}xYz2*uE:>H)VAm1vE.οQl>GbAOF3dA[=f'-D?@!o9geQ6G{K+ ggj@LrۢeŠE9¶'`V+̰0l$Ͱ.-Hi1Fˣ[ӗ}|Mh~kejիNn~nC1SWzWD=wTw{<VlEuҲ&P#ȣ}ZٕRz`[0qneUX S2;񩌤w(% ڛֶH$cqvhWih> g!'C@&Lx,wJxP'j;Qd#81S%d.qtoTj5}JyosE1Q#< :36nչs= ngv^bуicaD/!DVxe&;!"5: `uU,1/> v,ِ^\Fj@'RRql>*6z]76{1}nq? rv $}s U=B!QA. ֮!q^cmCWf!5$_EJu4p|Sg)o |H{H,2.#5[ Lr/ψTfMwuI`8Kf=/h~^ Co44 )ª)_v2Onin#&D`F"${smCltW,⻆mcbzn/6T%yK$?˳Ql~ W4Rm:[F'v7.]y%3doݼ^ (C8}IQEkW<)5>[x 3Q{EHJ#Ջ<gϽ mSӴi۟&z8 ~<'RRbK>}=4V  fEfkho{g{6 6jr*؈_<- !Yi [nX1oeֆ -ѹO8q6s7O7q十>_„|97{ \k'cÑX"89ϫh;Yc&gjHr;rrSE][C q!I.Iq'6S׃Ǟn_JB{x9]D·l2<vWpl2v &>ˌ`xGb "mIKkN%eaR}a]ɓq6+l-!4g;ܚUt1_-JbofyS#"#}+źtrA;_emqj.MH̞jY`4+-#Ag_c̈³M Ċ#yǷ0]N(N)`@/fCۃ[ܨj& lpMAcC퓉OgږD/GcǫyӷS6P gd~T?:!\e#xgBh 6}1t6'X0/9.%,<7ϟЎgc4RV1/E!`I r=z\AK"^4g`K7f2ժMAXTW1UEЭqc}}]g&&p²}bA$R{j/#󠢱e],a=}7E}HbU&5b._;f^`{ۑngpK#Bpd=Y4cm*ۢ_[8LQW+L;3 rLBZ}Oޱe S4Co(ܲ6|.c&0[dz_61ΫYvY}׿ >n0P/³k 2P,R4+`04Ћ43$ J-SXXg+F៻R繥MضRFnԏ.q[+yoCSPB̳gL|A1F̌+1]3>B&GC۞ JbDiz3[W)[8dMpbviv腐֕E!Ow]p&_UblSFmTKlڕB&>q>ig,Q;W,km 4%^RQ4@k )<{/fPkzВA± ٘bXZ,N/5& ~)枥;99օĺI}܎!:^R(*L{cU߿HSW%ISx~,dL)yL~ƴe2K'Ĕ|`3ش澉 k8kz[/'^˕G&FjaNW뾞Y`_ b0l*g]y-7~(YDsS߶~7 2(T+{z˟I3 MAfcCSSe Yg[bʷdYsj+CP*zX1Q4~T72L;ZgGW1i{a.}Ԩ}$_kgcVd^O³_"d&gAp3-&⪒{CncOk\uLdϋn\qx'DKtW5 [\!ʕrmm\P^W P ǿ[;a@S3oZZNWJY*`ӵ\$%*g.NމO2mYm8R8&ʗ9ED[;fғMlX :%Z/]UzN;FqSx0)dh̄HH@hA7~mT-1)(0,) 9Os6"N1U,  D$B0[`$a_K[/1jrZI@גO4GO6[j[ce;gE*k+i[}85, ~ۨ 0iEDH ^  :M$m q pS"C|u]l$#$ڎuW3ն)k(Ǫ=Jkp>7)h{k}gYJ#) i^َr$* /X(Sbq؞5k+oX[W9̇t-yX~"@ K˹4s(ջt=YاFx5k ~sQ&G&Wt6M/&N@ V=좞ev#A][ErP2قW|*HCɲ,Khj:/Ϧ_&g㣽7%5YCʵʵkN_F2kwAK^K?DRM( X.+i.ېK{Xco/rwR[7pf?]N{#f0 c–UIf 6Hl}jÉyPqtF=NƇoeB% :#1so |x6A-]'=WOK="TK(3"dĪ2OƏQmj³= /w>]ߡ[0*GɴwvgMA/) Qr@\>eF&>smx^(5{\#n@N\6h Zu%jC$bW}O7lz=<$km2Ya6<|^g uR-7³߃3~sX>]LO5i%[Q|DET:눮5֥*3gSr$zՊ?m8tdtr:@fߗMƲ!$tz#DajVډBvl}5fD6?"Gi! ׋ſ>,7X,旧v{R[̞5I[x"³؏d嗣ĢV'Myw")(h\o1'6cgG2 V-"M62Cgd9WS~N "fKuX0hn'® ^Ձ cEjkQ1K{³`q6B@"K"k`7A|9"*㡱CXx,g*K\ }WjC$ldos&2"r-%V<(Eׁ`L0~֮8SEQiYnNc lv7?AY$A dCv$Z˵xlWm:5/w&l!rf*r&Y$ư>8O?&^r3E$F⚠Q ղ'>tz>?B>mȾzae*rY"eQr vzTg^T2 MҒT]2^st;ׄP=:UWxD!s>PލKsy~w"[³]tKD;LXy:{ ,X+Ah%Ve\p⥴$]D 9kXQEaעʘjy8/|N+s\$gٻfbMVI>olU|ʍԇyHkбHR)eUe4105'ddr^31'%Lgep:wy C9}an΢"$ܚy&@Dzu:vEY.O]CGfv!t#"R%fRFnq}׬-Q+Ekǐ6_YC>c9\saAV曺%X ֲ% b2[αw"2Py*S-+i.<{l>bU: cO9/?CR$qHLFuxO쭋A^W?u!>jNֱO(hjC)P[=VQa APh*LB{EG %B|Y{۫q$+|x{۶jgHu5",Nd65Ue=|w>)IVQn{& b ǎW„@|;[kb#}." F{lmMdЅ٤LC2xآ9))`ǽ㓓pc*B2"UNV;8( Eƽ@YEez vK$ەA#cFY,EM@]ݘ|Q KE|Kq&(w^^ޜr5;}9%0vӝ%zM| j0T Ɗ= "MU629nWYK75wˑK8Jw;% лc78~/GTgW=Ziʺ57wQد&v<-[ HCۭFDc5]QgX@s( cAi/J#U45}/EnzZiĆf y!cUgWKkF ߌEcEy&+-;OՋ%\s0F׺ţ(5AbYM1U0h%Oʙ.6l(7ex*~˽8]ӿaf`raz3:J4[=\`1lnk،^{SLݥO* w&:(ʵ0&,9G.zc{Uv).㖾wlb(^QTa(+o_$"tfQ]5U@Dw4{"'S(362UϏVY .H}uۜIrĜnEtEP%!SI;'qD!ްi[Ud1P~Ghd{i;Eޢ1[)%Ɗ,(<=V4$B Y T&N_9P[>Pb q,pp{>&B0>x~?ή(}9 ?ETSq#S)ɖUE:ON >0bXZއ`YN|"&)[#tݔqr i[ٕ?=%2g#qKcDç(?sѮ.>" cX,YoiP &|?lWQh0bVchmILx;!4:L6NQ?:X Hl\K!:1(ʷ+5Ypz1=9^k6w.G<3M' zK(m^ >Bqib?v;eUg͑5MP9xK[WЁvfEy\XCd F +"KLvl<5~?8+ ֜W jTڢtRvi['ǝ$zCrbJ&"y(xVĜ"g͐OD.lv|ILG^̄DmUgl-E<9DfҚ(&CT{F̔<<Դ7 -γwAZf#BxiUgcodo;%n1=V(EQ7+`y׷zˀ2mQZ_:Lt޶OTE3yQ۟(fZZe&`v4"`f cxޟ4d~D"XN3Y#RqUk,ȯb5܌oYuͬmUg*l9J83!( d9ӍXr߿ 3 7fdmp>vWD/P}(2$Ce6P,#x7mZ'wP=+1lXZEQnջmX O6,\?ۣY۪>q6/+2QUѲvvmo#p޴#Xv>z1!xf2(ewovհiUg) m.8j2x*<^n{fbTg@l >%|EQb6oUgˆr :jEQEuvI@bI+v ծS(kV0R-:!02 .?>Dyos@g9i?"[@gRwQ#|]@gsD1T3Plc(DNC) m>i;:_Yoޓ(tj^@g{r5k ruk : : : ,,,: : ,,dbAIENDB`slixmpp-slix-1.4.2/docs/_static/images/000077500000000000000000000000001342457644200200255ustar00rootroot00000000000000slixmpp-slix-1.4.2/docs/_static/images/arch_layers.png000066400000000000000000000657751342457644200230530ustar00rootroot00000000000000PNG  IHDR Jv pHYs   IDATx |ǟA: qq-uTQTUuEJݥn& "3ɻ6Molb|>}gwn3K                           C@-l"¥&izMq|nyN-[OO%ö )" 7^C]E Ek]mS+C7;L#`UPi:fbTi]\XT]>ʜR{;t]r]>CC XOaNjmAA@싀UBFu-5%5׊Ei=싈VA'/?vu֠=J   ` - 5M_MѮʝ:N5 ֨ ʈi9t^7?w.JMVs9Eo\FkA@ 6<-L?7LjQ%xA&Jw*M}%tb=4ٶB~x_y=n~ O3W)*t!C@FOBO,ZD 6X4݂~Z6Et;۠XرyTj  `%bJ%j1;8jsuB(ܺW-}K4WbQ3H,Dz1oѵ`?RBq]#"OQS[!`Xx:}Qa&~}()(},\ٜrBQXɠ'} I {Ύ^0&-9-2@/vRfgQςFC=өѫ`3NMEd pSȝ K2wm  0z*`#ggAl 7<= s}{%IP:(xJ{Wi)Ʀq[8ObAR LaNCbi.5LʔL]zMտ'ݼv-?J-(8Z-GN;/AV9 :92  "@1(ςXD$f+=?}^/P| E8Jaa Td.?#}Ѹ2{2W/QMrT˓& C_t4wXjSF]Ge:i v@^ ^  3IGO9<ONuYFYޫ,kÕxjYn2ʅ?oDϢ^'0kZ6ooUCv@gޮ *̓J(sx9sǛ[y6*Oq.?Cc|'*׬yi*+̖B,'~meY?:v!uۻs~ê%rΊ#WiHK:1d,BUA@-Vу{(Utx}jsiϢ\H>o\i1-W9޳s>'Ԡf:ź8f*Λ_ѥ'-3?5اJ.:Ys jД|C~ F;%;p6G ?TSД#y;qfUfn̈́0->dN?mC7lsXtlrOv[L%PBh*WZ .fH$8D" q}n)8>fWQ 7 6{%{r7A,CFTFPҙGhh﮴p92",NbQa>F :l=D(YH ӠY+a}nM-#SI|L=h4}E'C~쀀@ς]1, 0Dp$ϘFLC<qpnL6=yT`,M΋J)f- ¹ǣ%;},MA|֞VnO_c/>@^ >u9e.e9#x"&IRty6^J__vpPz)A|M/PPѧ,RY@ I@,$ fߨܑ/;|ٓ5U LʣYC;3|ЕxEnTv1yٚL!ŒG>3~)R0V:-%f;gK$88eA@ aZ$(<`:7=rS[(0R|e[Y)Sl29Lوgkoy!T A@բو_‚g.!^!{c8NvGos#WbV6ĂZbK?mK|@JCk-@nٸ;:cKίhkz˺!L i!an0b!^D0pFO< жЭ(, e͖C9ԧI9B;# V8vlQ6Z}0u h]\]oǺ%4p| -"‡  @Uh={M m8=} 'b-U'1Ă %-X1   `JVЋi@S㸎CcHWl\H(B,|@85K =p4jcA@@!@,8eD#@@@ `"آd$!f K[ᵘ>!U(|%P;}ky!l ~  < ~NoĴ@/B^Sȫr10Gn|C:܍{22qO>OM)Gb j ͘osEތ?{AaoފPq~i#onܲb 1[A4%2 ] %?.9  6D@?yB<Ϳ%+O$RD|P>OTn|󌼁9Pp= 7r'5)\/~qV !?1Q[ǴrNd@ >N  XuOe:+O o, !j wsӱ?%}NOb_p-i;绛vgvg=g&Wm?͝_n~s~H+i/^9.oJ7:/#n.^G"^G&׈x}D8|H"+DQ OOA_t 鋗l_7'PN-HJL⒋T)RPc=/"=ܽ: =x"#丿2ŏWp tGvOz mcK"j GbYט=ŏqw`~VKR !DhqDE, 'cqs$sJȒA+ yS=[YWYAOKeFcbn_ ֒_>zNoEc €qͱ @  袛"2Mn& sk\\5zMN6 f]nIO|8Nfnv2ND='͆7$O&z<<"{<8{JrOҐO'4S {7B&z#Z8c?,mQyr77rev_y#mc$VR(HX1O7yrB;Vbb@X`m/N J )+v)ƍ]u.ZWBrCѸ Q" iH/G}dD&qqx?RR~E@>>wVCO7{޾{p5| | 'Ƨ[wCtk3Б.V{o,Zbxƚ"AOA,X,ci5q&e%.!QbO@L  ! nߨ :b| @@Zf-(@@Ă^X4 @@EbZ$Q8(,8MfԬ.B_4z/?_DY yN z hg\9ֺh  Ob:E =Z]{tZ#&!@BCi({ꙡ&&o `5 VC4]H1Q>{>Ȕ9URG)mԲώIEaaat#q͟ΜB.+iiڐ֥ u&[;HbZչx)Ky6>2ed$N U\wjFŲQ7mڈ&B4tu܈PCjh9gƦEDI3#qhʌ]}쥩գWZ| UY;x߄`i/m@=A @,@u bx8}sCrUˈ6NEQ=wQV'C16^mTlĂ_ =VQ5[4}@飐@4nV&xȰ<vyĂs_xDp`߱U16Lz o?+YC1{b&puQ5i 6}yr=B@z'><7XaA"ESXp#y^GSٲs=G3W_J+ * `s l| ʐU[EǍ}J MFdѻ/N"A @,8%Ė>o"hԴhޅʏ *@,娦rBKkz;j3.'&$VCqѺPtT޻2}*!Y!#v@z:WXt-+w`7=yLoB4aۨ=B⳺0r$ʞ='IE=؉CPȕ3 <ސC:p/y o3{Tp&<=Dz4k+DXq^/v wP:td/e˚]>=p.K_ϟw?t ":a|mrٹk!YM..4uϔ:{9s qQ16}ڵ*O/B|!4LZ{8   3d. 3m\ԫG?lweݸy/{K^ֹJBg9{RTT3Z|\ZK#D9r@\> `7 R~c[ʚ#dyܡ]W ԡt7g3RS Ta99{"E+C˾nE^':91=9Eh},ի֊3Z8 C"IT)-E?}S[Ky!;r̤ͪOxXȓ;b'E(R=yD Ρ+拧x~L'Ǹ ,!<7zeʘYJcFN}M,&!Ga/eJ<&4}jzKҐ(S|{) 1o}r.ry/!mq/r^ŨTzMid@._{w_|yh>#Yr= uUϛ=m?j׬Q{Jp0⟠,뜐@s/pzVB( =)íHi$ ֥햠W PC!OWIV/&^*p0RjYU>]`|@*Xxqt]_0j@vOo<j3aeREŋxEEUvط@峅,4ERN۲ƻF%(wJo%>wҕhǚK84Cz["\Ym։`_.^!SĔ8=%i=34]":MџJy{렩A tl3*.y7G<} @> xR )C挦&8#u(-8N`Ԅc5 ZpX=aa {a)RQ}w)xM6nZ  j@! {qZtiK2D E/bnE{|umxWAb!8HpMxgVXڑ p9GJg"%g6*XӸMVGM!Ђ-cp #c^a*@‘ӷuZM5P Ђ-C)șa]D4 $7{AcB@ςU0p\ {m2 `D!    ڢe   ` VB@@@q ER{m2"}.uz:s|I&u-ZQZ46TSʅdb|3mؾ x=kOiH{)ݺs>o)4f2SY^.yT`w\vص\Wn\#] eN1/pѺGtTٻ =_АG!cg!t EA=iI ~iqTq-^炒TfJ4t(FL-mȤR(ə& |UǏCs'Z)~ =ЃG̙ĈEP'Ըg1"eM]DCA@Ǡh=k6ze wغz}Ս=N{ع k뗭_mew՛֓ ͛#N-o9i7m6<?B9}dmvṛ|^[Py<Xs<-o-Q@"G@,$2` v'{2eH;Vng7)\iP߄&4yA%b+)Gb!gz%T,ܸ,*KLJqnY{&YD'<cK~B/_GЖ GĞn\8GmN\~ݿ}G#nttns"uOoLC%],ϳuyK}BómK.=d9<D s/BlҴwqTt!)X[W+AW-+UFV(M~M{xPE!3{(08>GzX¨Ϩ!ҾVqSkgaVJXH ≖N'- MuyhY=(iҐQTPJ"ukI´(R4|+B8< ~*~گ]y*xyӔacdrԇg)ۭ]'Y^w]TɓӢɠp9sW3/"8BX JƟЎM^V0g!x%PtYy#n8 9y 5F"l^Z>+:ly4h5Zb_qP,b\P̥q/!XjG7t Vl ~\ђ%Z褺J?o&R挑xQi#I;EL2(C3Γ>*W0*w1 a e &ϒkb@_M59 ܍ 'F0Bݍ{?sL԰}h-QKҝvM'P6/Jʡ[9ޛ%J=0~ j`"i%WrfHڻfKsŗZ,d8])Fv;x֮!.偀]N}߄fjÂ瀻ydSp_Ѷke59UE\] 1•ϓs[Mncz=X\LOT N<ݔ {&{bر]7lq++3'/jeR<{o_Rej{e!of?E5?2DzmKP-{fhDu73kL1"6OQ-:6! +ÜyPi:9glKwk_v,8Ŵ 䨧ALSp C )DB4 D$phUkʨ)$p7!&[Oe0g%W>Z҅bqDw$Î.BRPsm'MB0BLz .ā2*8PДD$p\ߝeg յ?7Еe0y pf |ڮiv{w#%ce9KucPy >t[<&Q:ڜbQ'H'/A1B.5LPIKby}xKrIוb# 2]N<_K @,XJ "pIJ?'h5x]Q(k+ڪqq"QLA Nk_A,O / nQia@{#3{F(.n"( 8(!ݏ1 `ކKGԴbA -'d4a5iBZh/WqMK _:j{6g/8gT-Nfb!@(1SJMTr䝿2}Z3=gzppO į+Rߵ ? GOPCcmG1%;?=tv5̞HZxHWN_ WDLj3$W'_Qȋwo޼K5oVP欙g޴g>BRW!wɖEC:Sh|TaUR͋gHF~]'eY 5+Q(|6<.]C&Kh4aX)}oS< dmfM_AYe$|9(]4qT20{ݾuW CzڤG7l Iz?`{SE)GY  t"yF3v ͋'#=͟$d6&siԨ'y,$(!&韹$x|yj|X?ћUѯgW߷.[jqV?!""UKn$N@n /Qq[ݟ6ؼXȔ9#ݿH<6 3 9RR&e_s.Zd ]>^ E Yk\~қRtoˁiIwLIҀ5*2hs2e(_|?cb=~ZrW9N'5bXP,-rj:sqWj q@y ZMH8E|B>ixQԾWYS&.[%OfQgNMsϏf{/>-!_cA)i_&@;yX "rʲx9DOr> GY +l~6gZt҂Sf_ZvK;;rPz}xi'9ENӒ d[?y xҮc[}pƟgQT)]B6!5A@@|Cjca#ɣԱtqZh5-۰2e1Kxq+peڲ~t!|   `kB,(Jx'~h?cb' ~ebm#޾X0 b4J*R9=tQ"2hڰzK?i5 9"`b˳~CYM.b#"3x y:עE RŨiOcc!bر?CN=l;fHj'rh@@lMߧekAb 'r&V#M8%$~۾2dzk3p*qќ Pn to-2f@sͤ2Lq$H]-I&y ST?IE T.j5tUtn{~fE+ZZavpd?\b0_;R.}dzuV+"#Cbq i?7[yvR(pO] .絤naֻzcr5j.gP >B(x-BbG  .Fj:m rwCzPhܥbcڌSs 5܄NIu֢±qC4%.*5*Fz;Tlv: >o%rD>ٴ%«!/%c]zsQzˑ"eW(@O"޺E:CO5C{ CR|Cc%lZ,px1*>iXTܙ q&#ذY}Խaq̆p<]ěuTx!m'u;m jiIP絤nٸ)Elk{R_lAԯq8 GYNJ-߰6Eo޾RWB_ާ˓Z{fJ*,H i|6DA/ѐ)EOm WӖ) N  }(cԌ! @@{'LL鯦|Zѥʨ60}up~7G h V"@@Cȗ;'͟0x+<銼J||4!uXHAOO]Orq}60"=Óo詉{QŻ rlC~-al /rVu  eD)A+z޽AC,X"4CE]֩%dh8JDȖڶC,%b KmMk۴#m'СE3@J- >ˈYSo@@wji}̿}1f۴Xػs};Z܇(CUդo:{mN,%?K+'b'{ Z׵|Re@@@%^d"2"_ZMMPs%y?슖Wgd&6 PɣAO!h:^{xoM66ʔ1c0_3D#tOiqI0 iҺS>=J(kS:o5im};HcqcľP ^@!|(cz{6   @DNXaw9HKkwJ{~)Bd5$7$>c#!]ؒ  \޽YUg͉~^)_f/ N=:ʴu+# _uDeV#/Rl,pc~:Rew9qJwi`TH Ģ%mYMI3a|NG3'͖/Dm n4"U")p椟?{KZ? G쮉R0uI릆xӝicf9K oS票cǯҰ  Dz(̕#NN=s{bLeŀ3۶#WvWJ/yy [XKO7l6b (FO0dQmy͆alX<(i&.-: ֆO/ߐŚ[Cy   lr":Zo#/^XbCp5+*R  8X`HujBX0|@@@$Tq@"{zmq!RŊQk'}G;D ) x%}޸!OM[1Ӧ˴R%z+ũAfb}/~2ں^H7]?0kŅҥ RB < Xz Ł#7'Oҷ:ҩcG%|cG9r1b)rMFK{޷I|fEˣ̛#-x>zVAOcӗ-jL>|!q…gM[( )4mQb $`M( @@WI͘)}?hxϐ!wʔ//KVw@y9.)UUV?{FFK;u^ ժE@R.OO޻K/iԔiY,?p[:@3|^@<ǥshOJqb8Mtab!3 t[JUYƑ۾ eWjyς?7n̙x o0x  ` W>жӧmJU!t?423*|%/}* -V/["+"BI2fN:}mصJovr##ʕoic?]bfGqr}91~,lF(M<$ qtY`S4>wۦSO}D<@~N,~](nCȁ#:Y2^y:svQF[(fW+/PO[ґ! /yXtCaZFB򘖁c,X"00?r M&ؿgRz 5/g۲7ꄈ0}nn>mhjՠ.-;ބnߒ쯠jWʭ'4r%O̍?|(ݿVjC< %[6*Oi;ϕ<A= ֠2@HN7PhXhᲷgԪW_ nX/F9 6*5jʞk~#}Ii/H/ 10D,3jԩ+PN8NCכNNT썷^+P);NI>(!!yԞ ` ,XB 6 EEb!#M=  M `#ϫ%WKLC)/g2O?+3PBUڕ+͹@T jydɤY vPOP=:|!y5IE N"ׁ^PiĄIFe(B8(S*͘" !R! `bsK,rOVҧ~}t8?p"E8t 2"?ƶ*V{~+)Ν>Em426!v`3dtCocvfC<\pX Uan%GY/151E@g. F5ʑgA,]QNe6 5F)LJ(% ^6QhO40wK^xebH] NLePb4N ^{5;l<~ݾMG0MB)EL /0ja6m۬\GC7=%iS{W_"VӨaTx1d8k7 7Q6 = 8)'h6XJbRR'%K @,XJ v    £   `)KI@@Ă^x4@@,%`))؁Xp f ,%;pR NzlĂ`  NJbI/<   X@@@I @,8GA@@R 8)'6\4@@Ă-I( h80g/^DNXM3!rT[>0(Q[vOEq5ڛܴh/A:U? si ۷ %{.Չ#4bA -}}|?C~:9꠭D@@ :t8%ܴ6RDD$$|m!9j)@,%/hWy~Exm% I`/0h˓/[<-E@@,^. ..xPs!ͽ(ii}R">QBu{q#"//@'/~wKM+;Lw#M AA_ji2x,I)fʙLIgW]D?x@/)( /^ǏS?z7r/)"V8e" 9< ?ރN&cFԼ9Unْ׏l|8<G+^LG,J 9 gY($K>ٓmB #^p @(T#!Nק {hx.  营:*W}uL,@@l~6l}p WM<EQKkPt2X(FS4ͽT:{R-E@@qV ފB*=  ZKDN(RW~ ǏGo H\},E0d={B,=KBZ4-SItb!$ZNKI9])h"Z j@@Ă]p4@@XPK   d 삣   ĂZb'#d =8'h.%A@@@,8GsA@@@-`  NFb.8   j @,%{p2 Nv\PKbA-1؃Xp Z j@@Ă]p4@@XPK   d 삣   ĂZb'#d =8'h.%A@@@,8GsA@@@-`  NFb.8   j @,%{p2 Nv\PKUm؃BavN*'YR)I+ܮԮI+7h@^EP…eT@Ce_ѹW͛tmv%˕!Nw~Z:vRd(\ԨF m[J&!kd  EH0ѱs4xt9d~SJ7G6`!APy@Ə7);,Cm;@ ˗JF~B׭[ŋ)[LԦo_zY*B b@  7h E͛ŅKR(3{v##thioi3kh 98m +VcWSqӟB% "u3GB@<\  `!^^ԿsgiSCE OJ4[R-q~N]-{*Bo8~,bɸqT`AJ*J-KKEB< Mb!n>H" ?}J<ca~K IV ϒ!qCw ؈}"e3vmjҳOTY3P21?Lo0iشg5уPJ(m^LsqJɓ)s m1쓥Z5){%E@"XHD(n <ܰ ቅG,_bbѣL؟} v*nح},}| 7/o۳?}29eC-υhhDp|B,s~UPvObZ8x6;,VnB\/H  IA' 7gNXliҥ)I+dHJ WУ'O݇0sZbH#mZyc/E׬){_/o|cF7>"#!bXq%fW|ʗ(A~8)nu6if _  e7m;W)> ˰ca7XԗCo1d" .c@԰Q`V%`U( ?bjѻkvEz9!e",0h%g^ش._N~K0 'R޹WQqwo(Ҩ'{b3ZקwSnԮ *DLXde ԅ iQeV}Ζ+.cs<)bf'6gS;*O}T *}U4̺rot4Nv4a҇@ٿ_.Y"Fxŋq Cn bP~m$ok1M3wNnc''G{}Иx_ΞEPc6?v?y_C NjB @g z0Kg ljkA/h @2iv6뼁kٯw{dQmjHzP#}ؖ--o -b"X*=ӺR[" IDAT Vq`m[qkn,_M/ ,|Opk\_>z쭷2k͢O~Kz8Dom\orYrɧ@D|Rlb!kA DT(SmA%c7DSE._D#aQi"^묆L3̦Lm_E] &2J]`S xYeo6@`B@P x wrNz}5[u@c+9)k!i5_ !E uꦙ53O_]R|Dw{hfq7E,jДII-d$x>1sC7/i:eefbõ eEBg>al6-`/zR5fK; 1J$mkeh2Yl{ Kup%W{lhla<:֭pikL(iyfAv+nj:5 R]M5S^k6%Eu/yȒY",L/F:LTrqQg @=>jce#J ?@@@ Z JJ ?@@@ Z JJ ?@@@ Z JJ ?@@@ Z JJ ?@@@ Z JJ ?@@@ Z JJ ?@@@ Z JJ ?@@@ Z JJ ?@@@ Z JJ ?@@@ Z JJ ?@@@ Z JJ ?@@@ Z JJ ?@@@ Z JJ ?@@@ Z JJ ?@@@ Z JJ*~55(%E X b%փh Z֒Y聕J D8ZZ_\Nb!,!/Ѳ#.iDD,%u]@@y}9$RuNb69a%m|u9 IIcYRP`7vc C|[~a,Er Hx%7{cI8!z E" cNJrMC@ p?\JDb9^!YmNUi'}XRExqѣRp 9d_\NbanB!(5m  Z+rPISYع܌\8@ݘwr(Nbdm7gm M̖6$ʶ_\vzX C`]4c-XHטlVwn]y63M(lz\9@݌(:{%g+8/.‰>IH`=>;Hj:1hJahq@ UV|i%5v ^]:š ݹ׼ZRsޙ=qB@7%`O=^?Q&+bLb`mI^X{X(o/4.纘 i6EY$ryYz@l"x n V-XO>XQ#ϱ9$aƾ}(I%-;~'5607Օ_"ZQvvA2d( 8!gA뚝 LX*ec %'ʒFVW9; ɐs aC,%G˴"ȉ =tAG6I3ϔk2C@)k.iiv"%+1Y¤}u6^d'9eyr~%pv|shf$q𭇿<ުUT0\]`V jIHhտLׂB+͓>@1}Oym^@OʍG^ͪ"2fX cbT{r=\滘^0lS``&ʠdI:C@y=YCrpk ~B@lԔs"yZSuKg/l[\L\ZvqXI+t}BbxH \ uUWĔ[ Jƍb /z"93Dm ؈&²B kz5?7܊h08dW;zk^ԭ(6q:oH7"* aMy7 '-{@D!m=Vv*|Ps"o.b2k{-ԁ ]5"ǫ/VƎLCuqi{9E^/ٟo,/pm|ךc&EBo{3$Z .g%%IBo @]TUVzBٟZ%lE݀۾nbʰPp>}݁@mm|wg*3 v¾:Vk^5 ܹ x%J Wo$v4v ~'9:z qkLϳu-`ÈSޯT+č'rw/7##1AV v΃,[.J 2f9BcpZ8VNC @ @l=$IENDB`slixmpp-slix-1.4.2/docs/_static/images/from_&yet.png000066400000000000000000000053741342457644200224360ustar00rootroot00000000000000PNG  IHDROr1ztEXtSoftwareAdobe ImageReadyqe< IDATx TTe޹b̀D@@E(US1Z [RVY ߪD4$-U:@ A!m 1:`Re<kWf}ごxl=+**,z{:ۼxQ6@5>>To="SK$܋& Hu-Pug'tNߞ7Xk?Z9ԠX\1\Vlyfs/NYeh.Uaa. D0vg)+H'a$,NQN(prB&E<r5[9t;rZnmwI;#χszƣn/&[L˅R5U*C{Rf_=0o@_ӥ/=c8!`kn=le c䇼DMU@PG'Mݶ1QǏL֑V|CطHvdazl7@zn0-_ܐ}Իw:7(O[{B~ 6貸a3_ۦzAGhp㽯D/i ťg/NW,dA ɰ+~9-_jz乚||%BJ?x-8n}%*}9}EC}y r?ɤ±}i:EnΝa)|5%V2 P<׽sf+{J;f4iQz~ü!S(iVE[k_j6hVކ揯|[&S +0FSa]ahx8C g+}ס*lf͏)YTz7q( RIfWE~*]U@}oN^(Y 'A,KJؓ sUhuRYcY|_ҩ5 /ܻpݼ\Ƹ*$P LEuKƖn[;rK[Lp37Ϋ(.3`hhEOn}NCBq9Xzuhy"q>90*'#$iIمxj#Y(>QtjbB3U-m]]ŰZp0/j4jQa{y=9oGd:^-st6im2q6BGfamtx,&+|THl⚌P1L;=Q䇟x:?= :;t x\4rN'|F+c+w j"xqIr푻۾ U[YaQsa;;)lEiiTS ni<ϑmY!$T٨gxa7rʗpzIHNÓ~J|oɋp=漻wS('WtƐ-:6/Z9]-n(2sW1˸-sM_Y91\L/~Nծ^vz)j8rsT¯]1rڗ Fe@Gm2K2ĈPloV.?jXp~dhM9OaUwy$rTuK t. Cjh缆E*MY = >,qGN@ܣ;ozȑyؖeC63̘nCt}4uqVrvIr07pa(yCa'Pj +8Wb}~ahqz22Ѝ:)Q?kr2 ǻB<q'i+Q쿷T_ V5L8Zr,mk]Z\4 5oDcMб2 G`=]ɂi|yjZjxݑ=ꔃjJw< Ζ” 5xq`/2|Xy}m1"|?.w}uY~!Ur$ @az LY\dx"3,=ޑ) O!?x~=7ﮓ 0Xw9ҢIENDB`slixmpp-slix-1.4.2/docs/_static/ir_black.css000066400000000000000000000077611342457644200210530ustar00rootroot00000000000000.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 */ slixmpp-slix-1.4.2/docs/_static/nature.css000066400000000000000000000100551342457644200205710ustar00rootroot00000000000000/* * 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; }slixmpp-slix-1.4.2/docs/_static/noise_dk.png000066400000000000000000000543531342457644200210730ustar00rootroot00000000000000PNG  IHDR<qiCCPICC ProfilexTkA6n"Zkx"IYhE6bk Ed3In6&*Ezd/JZE(ޫ(b-nL~7}ov r4 Ril|Bj A4%UN$As{z[V{wwҶ@G*q Y<ߡ)t9Nyx+=Y"|@5-MS%@H8qR>׋infObN~N>! ?F?aĆ=5`5_M'Tq. VJp8dasZHOLn}&wVQygE0  HPEaP@<14r?#{2u$jtbDA{6=Q<("qCA*Oy\V;噹sM^|vWGyz?W15s-_̗)UKuZ17ߟl;=..s7VgjHUO^gc)1&v!.K `m)m$``/]?[xF QT*d4o(/lșmSqens}nk~8X<R5 vz)Ӗ9R,bRPCRR%eKUbvؙn9BħJeRR~NցoEx pHYs   IDATxl [o&4LyʄΧoT.T{KKKK:&p~?G}ŝׇO>dqgy0cf~>gf@J/zS  B@dVL1N0R [L֐bkrU[S!gjxt!Ќ[hQ?b0q~mūmp7 )^==Skv8[wgz#/fo7y0ۉ dDvW;/V"`A-z,FW^g;\z1r Rmw+-r =MEu/O,=l9L>]=x5,(ǽF-jUl~}M2AD1[c181wg .X5{pa9Ӱ3M .;!F>,x‡qV񩋇5ы0v Fn}Q gsw8Nu<=þ}@pUnFs+C5P1"ݑ1A>{Ml/ ΰs 6k/'O~xU\񸛅'u۹p꟯ _|pS.9tᔫ<͍~{Z_noNxՐǼ(4qü`;㾽X&JXw SHCH 4"1{R[oEߞ<:y"IP rր x +ڋCmLan 8]?k0~1ҬVuq_X~jm]b-Ϲ^gf^nZpX89|rp": 4҆>YPYx)v~& 9C!)!D!6@B]<Q.n zt3>uY/Vp˳ދϷE'^qE-XK 7>;nO<J sáGV[\Z!Q~fR_LԘ9z(L"{/U"kPb(g G@vw1BkLn&h3Ca5HN@;<_KXZa-KƞZӄ\:wp2u7 8,g'O3=;_}qk`c=)Xar0B""T[(L>\Ay0iic ;~83 K>řs񛃘z`r+t%/}6kz,O/sWbAϷz4coB'#AXi T388:b1RA3jNZiv䉧_Xgsp3uӬFO?\_ҕ0348ӭ.f~zlj^SswSWpFD@*bgvpRݪ5%ǹzY⤅o`jhېGC'4nH8Ի85m7,? &f6p gҗ)_.0zqWO]bxiKC^N6/5N"pog1g^-pÕ۟棯.6 O}5 ^v\!N_`h6up\38ۃo/Goq"ЇWY/T|x3EӇWԳ]O7Id$vz)'ivy;N{uB5ۥmյI%7q6(1/gܴ:ߏ-y8Ԣ2:c\~&GM}O͊fX9Wo<5h>IenjC" %0Xq3{9θrocj˧! FzaabrՋ$RՇs/>^.N19zf˟xhfpj:Kc&8{~p_ϕ?L5cV_'DF8 G,9hp55_xVNM8>8{r rX>w5i`ՏXuE0n6g~qe6jv篗bx3}!{<8l̩=F^8XL"B B)o` A) "WW3p7:q%>w'3?|x|pWX$1 :Y`08|S0(8RAհ]nprc7/gװz~J#sҫriQ_o0& Lq;.>3t—_5I~y©]~j'Fό^}&Et&]uSҿ%@ Txx{B|rwC'/ V\.-/ x/8|+>;==4[\M/\3c}h -ah%y✾/90Xϐb$G=Z Yu}F  zAiH1>MLB`X\vyx7DwV?Sc? }ʭ/Z|i͏ѹo;xXobU[oĿɁa~g5pѩb7O(.flh=Z~yxf$`{K@ 7/_~Da3w$ƙP[M;=rݝipg~3Xl=(×gp8èK8xx Kx.0tKA'N~ܖ<=a⨎ziF{#>? mra -ot!*v>` Et.&N}OdcO\b-aq1[ H879b]~aØݾ%mθҢ/p؛3#ng/hwzNꔏ; ^F'_;f{ա`+E:WfS(A WT|CCS5"լaw8 n\M{{ rҟ]_'W99:yYeVVyaP8쬦\yx`?w$40ŝY YAxg98 hWNu5ߠq2g5ph=ρf@iUFnf셀>Յi G=lUptvWt͑O]y!< (Fb8g,]H Ϯw+' È3w| y@5ć[nx\V%GzPr~twMCv|b$>hbա~-zMWqxqYͻ~ <ꃮxՍlIpae@#rgbL\E!YуIx͉d0ՇQSo zgo/Ǚ^9ǯ]L33.C߹8ݙ3tG,_=LЫ{3FKիK*ҠRHsc 2Xg z8|Yx>b[|j$nɟF {Uܸ{Qĭ=鯞8]|j屴W?tk|zXwύ:u ]铯Fu"!pG3r-~ Ë1DseƁӷx\v@ġ@Ԫ7Xzq7J=o`rs@ [_=.+\zѦ@rZaՃ|s>g6/>0}Hp{ҍ>+(`%tWpj(@<~V<8|5@e5_{R iǻr`kŃ5B"/m^ik>|vFOaCrgE..Zv ]>q ؆X3jZ& h8sk!0KH7:DMܽp4Cm5/X? ^^K|N[옾Ԗ =¨ϝ_pq6?zr%vtF#DatC<!'N|P[48>BrWbsQ1\0R/$jguc5 Ǫ_n\<_."^Ά G[3j&zHmF} FW4ORk['K+>69f^:iRG^ܪ}但{0%*;bI؝Ɔ%RO3p^Ν_=XyL<}A`4+@>^N1/,~u{jۙ3Nu%jɳfOS.άT\,*pWSz>|qM|~yOq`5_{/aE̽b<4YU\Xo8p&-uzhd84|t2^^X~1 z%Tf&^<>r3LZ LKWq{gw?2q 5riC"r Ao$>@#S1gq<POK¨'_ "7sog̗^uӃCj`p𷇓Wn?jFZңEVoo&L_wN~, 3_q>qйyc1.AHv"{}G ?nװB=o}m cVjWY2;xev>\6^&GC:=c^/Xafϙ>)_.]Xy|pͻwH|:hIdl>(M`9|k]5^38azy J^k FE ^XYi,̧^{Woj9M|w\A͋\k: P3>Ĵ 5 ;:\X3^~|=|bt}Яs OT]x9:ʥyX~}J߂K~W%b_rpm:";pwD0Ng;"| 'ƒSܰa{0p=j2maɧ&ipIEAgW6˷/OOŧF=+FL?4<~ɥ7690g7>~˭..Z\᪏(nח~ Y$5nF`+@|w J0K.4qiKG^p'݃a^p߱˅aՀߖ0;8fᇓtb8=3>/8yz\Y_θYza~p6\ +&YR;7XQOęj;YY>bogt0䚅:W>,뽞X.LIiow<"N{x7Yuf4oBf'QP;pgra5n 7=8NKS'p;QgCoJfq4p7vܬ~3>|̎vY~}wG}kg9땯so&[5K~H|giU Xq5ӧFfAy1`85*>]~7WLqzJp,xpދ9az0xp[<ԑ'և!r`|> #ytg˥6wƹ\3p;xIl4`=qKnbp+kp5D ^:iËӀcv=ŭ>3^bOww [ ٢!-̍9wW]~zB.4'pʇsM"ܑ; 7|,d ق/n_ϟ>~w*\qX||j--gdv|0w1gKa~QC-.™3>wg:K3=biRc@ ֒gS%YtႯ?eDWSlՠZ5 ǹ!!P>G.7%tl IDAT_׏ڰx[ nwaFw/h!]Of8:Ԑy9]:?f~CC,AI\!b$H.ЂIXq' +N37$1a0{{vw<g[-|/ O&,ȸ쌋!%T,YIx0곞3= ¥Xs-fWOɢ7]ܙWaA|\58˟PA֙?٠!v>j/.8Ѵ8gr@8S'/7rz}WKNjoы= C?dH%'907++Y<C\,L_qVMgzU7nċ Msq 4QЈ^J<x; G,XLcX> >å]=BKRAŁ9XxĄ&Y [vMQNyb: `Q?a+=Ւw>}oocEЁ Yfq󻗺9+oχ_ 7HCN"#WX!VqȻhw|j#'^s A;iSg_`Q]fzQSw"ȅ :vaͨC x3isu!&5^|rZ~83JbR@x{ 9˷<A>j/J:r~ ㌛.a-.6i5'~,/r(Ϝ!-LZj˒0/YN0>ݢƙnS_8NVoEX yjUW+K~}!ĩ7J]4Ի3mȯ_ش8#ZAF3K+$* Ol#P28g8>4Ƶqb԰YLoE̝mN8:[zC9TWնUgNӇzU}577>\g~wh$ߒ cx(܂q&&v{pc(8Ii>rԱn7ߚiҁ[vr5K8}A_o 3L>X|t8շ7b3ugufgj^\O3n:v30_p(.Y$֘TU b ǟx\10kex:rêϿ{4E4m-xzhn^<^;^Xzi2 z '/ >s`艻guo@rpȟyp +n&X"pdg j|8!h 6X~x1hBCg8We 94_ ֭S, bPqV2zXs9B\|aqЯ>PG5ic|;01" `E#!QC(ǯQ݃g {4i%S+G8g:|8{F-G" k|;>1&O7vwxZ4,}:(.ʙνdV׽zvklKLr,jaEy8>8;^8hk.0\|.3.sw+oIB+$"^#B\.'f F/GA!fլNb]oiS yՔP;*` >\ӋV\Sgo4KfC=sWS($H1I3xʫX|p>$8D]3j5PC4$Q/_-v9᪙zꛅVxo`t0ڙ^VtfwX=Z0:lY'^mqˤ>;>dX'=ԧ^-61/l1@܃w2˹̏'<LX= r^皃qO'F?L-w|8-;߿1.<{hh^~ 30rn9v<ѡ]L?_neLJ|>yȋi7p((рDbv1{Eq%@q1X/Agaԗg:Yݒiwn<|7gN o%9bꈻ˃ciW 'b4.30PE\_|7Maew#>w&]=0|?|a#4#9҆!_`+nhBI90{qw4/Fs&uOqqoX|zd0sA*.֢˄&zw9zL=oC?vȈV'!05p0јs[4~yzXz,9|ڝ頛61z8ózN/|gv}aq?yGPC;#08"3q!T3,&QQ*zʷi#ѓ{ Wxp[b0}S\Zp]/a`zb=+8?Ih=Z>y jPa83D|Pa581Jp-Tq|b9|8"ȣ/O{>+԰`haf_}2>K.wA/4 `GAX0'ڎO^bcd[VCMꤓOu!X_rruW߽bLo͋զ{˃a8rè/Kuqf}{`3twC cDHH8\cD֐%O|eˇu1;t|nuуW]?:`;_/Z\C.jZbv~?K|1_LNOU-g9+Y\Clo,Y#sGn .v,0,s>Nar uSL-+fgiƏ|i (n\ӫ,&ˇP`c>ZMgp +oíj,ŇI<".?==s(+CgOxqd{(8劋KK x[ۯޮâFm'6+)_ tK8p/^/WGs >-}Ś\d7X|8Ě%m|jnj"@dm *2B5D3 ey@SO^p(_aWCN<hê6i\j>hgՆ թ}kᨿf^`|ͭyipbDt6>9C1|WXs|' LB55v+5.c˅pw_wM8ᰊ>hY׻x3ӽrX_gсG߸hHʓcy|g~Cgyo iDJ+AWfvb<\||D4D]-41 sk0zp q`fb1^43p)~؝:ś63q7瞍~f. so5{G{Z丫WN9 " 4)hkm[]`ՁO~qӬ f|:mJSƛf;^\bf<g0Hv@?5A`?՘s i| 1b$ë%n|媛Nu< Yxo7`-߼ŋɧC^T^~90Sݢ9qVoׁu#Gl,9iq&GLvYu10}8-:; N$ +(ƏH,fx+g9 RK|>~ZM:5[zjݫLJc/ÀxC|-qE-,KOx13?gx{0b\h^|x+p5PS`, ÃCsa{|r!9+54"l֤=hjɍOU_pߝ&:ͤxsij"WOjfԩ_-f~qzaĘ{\,Ց+YX/'_M$y " [&: Y|qw*Wm >=壩!wά>pZIݽ)wS\o?_pgK̯s={<8zvr֫zvqۚjr _"!#zx>L C-530Mt Ǽ Fm Y.G#$X- |H#}08ӷূav|SZz L?ppvy4Y9{ȷ3<}{iԦ`7?xic>G맟~\G%z鳗'V|^xˆ/n S{zS%N aW"@ôUsN N-ZR/|錗>8E+brQ~X>/2Ћ;8p_߮gLY2r8YтG&CE>^5+~XV|t3z+^]\i9rڷLL\5kxl0guiͯf3Ϫ ~~< k,ow庫kyI O?n=<o'Z`‰#\É[i/hGhWigfĚ߾!06>bOodQ̋#X/f/\1-q&-vimp>恣%MqW{}⫆T6|8{hlg㦱 O>}4,$0~&_=$سÜ`P4b%P7 -Ca ïr{y*뙯efѝi61= .m^b}[掏W. )`P)nؙPyۻǗ1< BC7^XUCMgu$$$`MCm cW͢5IDAT`,x1{jHsK ` Z^AhpL/o4[6|AZtn^x1N}әj6McoNiUCm >;?U3"1db ӝXj^\MUC^56w5߄^jv;fi1?{vtú[4ȃWonC .wrN/O2qѭg>H;.w"!a0o{ V1w$C)ਆ!qe`:m>vjtpwXCT' N27y0b>Xry5?qWx>X߆6Wx|?0 oj?ECP+5I\ j}"Xjdrp8Mj%̏ o'~pcsҐ-xLqlL`,:~z₧B/Zpx]`sTU~V yx7DܪA4`j@\,kPD0pbծ~z.i5}n}ݞ.t)|~ugV`iKAW/o zկ;>3sࣟSϬգ¯~yp9v,^KۼX@V$GHF5z C.;N[=lC\x/Z k0L}~xO| M,Zװ8`,<ū-O̞n~zf%C3>P+ .Oue[fFq~tXeÉH" W.OI\X%V̎UF| ql1H~Fv .aԭ1 7|/f[W<^->5y4sy8;ղg" c11sGSώ׼z1`[bvzˁI~p Yd%2Bq~x\Q\~h_ θ҂2ÇH<XKj6N;~wu'Vq7zQ<.SW4Κ3hܗBO ewxפdg8'OAd*{` D!_DOģKRYbr3xjn1za-gj|-Z=ǧ4n1yΰrUa^ qlp0|4g>/io'`+x~+< w5{Dj:.ZgͧCtO<5S1W9Q顾p/'zetӤpCS8;입z(^M=tN/_xw|45$19;0xz(8vu6jw|˝nGga˙ee~p!7FC9V &N>m Osb˓>a%!kLCL=~!8nѣ`m>lI',O:⌷":՛8v].N3v^K>L9(_.k=H;E.a?l}4?mnԇe8ę*3^+Ǟzœ뉐 6O8+(~ "^̎S}m\srh![]wy}I] \gnx@mj\j~zԔYӏй`-&W-zap[`l,,<w9pcot)Yio,B_vKC O!^ϰapy`≦U RddiiҦ QafW?'~pv 7Ng/.gKܞL%'ro0w߯{ܫI>B `~@;#{&>΄0\r W.ѸaٙO>1;Ʉ /&N}jۭzLO.G^\j#Q?h҃ ۼ_Xp3nՇ+&nvLճR&C{c'9)nH=|>DHaf=`0|D-WL04YZLm8xgx(4W%;.L-=Z0u-_a8{a%3KK93/' W+._'̞^wb:#[Ȁ%'LLQb4hi;N+&Lsw7|yh<_-Ϗ.~;?<.G? /.|L OrX4ygrh cnb0,<=$ ӏK= Nsp뉏08O>z^?!0! Ha3_o9kL3qt/^C#G3nq1| ^z= ؆h~SϸS QbK3\/rG.o]b;/su//-}yrf<_G\@`DxSb 1vy|an@>rW=!qWXwyx,q| y+q]‘&+"K 'mjAOp=#Woa:|_<`k'.fIJN22bS0aH5rp 1X|0a_!w0q殞{x; ^?,b;Q/i·ٛI-Cn_߬7-^rWO}|{Hԙ [ [ͧp<+Nsè<pv 呶^^&Y5k0 Q. M6NXxY3SxGsq3X9訮{ZWOw/ϫm}#G;O+φ_brtIENDB`slixmpp-slix-1.4.2/docs/_static/pygments.css000066400000000000000000000077611342457644200211530ustar00rootroot00000000000000.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 */ slixmpp-slix-1.4.2/docs/_static/sphinxdoc.css000066400000000000000000000137031342457644200212750ustar00rootroot00000000000000/* * 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; } slixmpp-slix-1.4.2/docs/_templates/000077500000000000000000000000001342457644200172675ustar00rootroot00000000000000slixmpp-slix-1.4.2/docs/_templates/layout.html000066400000000000000000000041041342457644200214710ustar00rootroot00000000000000{# haiku/layout.html ~~~~~~~~~~~~~~~~~ Sphinx layout template for the haiku theme. :copyright: Copyright 2007-2010 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() %}

{%- block haikurel1 %} {%- endblock %} {%- if prev %} «  {{ prev.title }}   ::   {%- endif %} {{ _('Contents') }} {%- if next %}   ::   {{ next.title }}  » {%- endif %} {%- block haikurel2 %} {%- endblock %}

{% endmacro %} {% block content %}
{%- block haikuheader %} {%- if theme_full_logo != "false" %} {%- else %} {%- if logo -%} {%- endif -%}

{{ title|striptags }}

{{ shorttitle|e }}

{%- endif %} {%- endblock %}
{{ nav() }}
{#{%- if display_toc %}

Table Of Contents

{{ toc }}
{%- endif %}#} {% block body %}{% endblock %}
{{ nav() }}

From &yet

{% endblock %} slixmpp-slix-1.4.2/docs/api/000077500000000000000000000000001342457644200157035ustar00rootroot00000000000000slixmpp-slix-1.4.2/docs/api/basexmpp.rst000066400000000000000000000001401342457644200202470ustar00rootroot00000000000000======== BaseXMPP ======== .. module:: slixmpp.basexmpp .. autoclass:: BaseXMPP :members: slixmpp-slix-1.4.2/docs/api/clientxmpp.rst000066400000000000000000000001521342457644200206160ustar00rootroot00000000000000========== ClientXMPP ========== .. module:: slixmpp.clientxmpp .. autoclass:: ClientXMPP :members: slixmpp-slix-1.4.2/docs/api/componentxmpp.rst000066400000000000000000000001711342457644200213430ustar00rootroot00000000000000============= ComponentXMPP ============= .. module:: slixmpp.componentxmpp .. autoclass:: ComponentXMPP :members: slixmpp-slix-1.4.2/docs/api/exceptions.rst000066400000000000000000000002711342457644200206160ustar00rootroot00000000000000Exceptions ========== .. module:: slixmpp.exceptions .. autoexception:: XMPPError :members: .. autoexception:: IqError :members: .. autoexception:: IqTimeout :members: slixmpp-slix-1.4.2/docs/api/stanza/000077500000000000000000000000001342457644200172035ustar00rootroot00000000000000slixmpp-slix-1.4.2/docs/api/stanza/iq.rst000066400000000000000000000001221342457644200203410ustar00rootroot00000000000000IQ Stanza ========= .. module:: slixmpp.stanza .. autoclass:: Iq :members: slixmpp-slix-1.4.2/docs/api/stanza/message.rst000066400000000000000000000001401342457644200213540ustar00rootroot00000000000000Message Stanza ============== .. module:: slixmpp.stanza .. autoclass:: Message :members: slixmpp-slix-1.4.2/docs/api/stanza/presence.rst000066400000000000000000000001441342457644200215400ustar00rootroot00000000000000Presence Stanza =============== .. module:: slixmpp.stanza .. autoclass:: Presence :members: slixmpp-slix-1.4.2/docs/api/stanza/rootstanza.rst000066400000000000000000000001511342457644200221360ustar00rootroot00000000000000Root Stanza =========== .. module:: slixmpp.stanza.rootstanza .. autoclass:: RootStanza :members: slixmpp-slix-1.4.2/docs/api/xmlstream/000077500000000000000000000000001342457644200177175ustar00rootroot00000000000000slixmpp-slix-1.4.2/docs/api/xmlstream/handler.rst000066400000000000000000000006031342457644200220650ustar00rootroot00000000000000Stanza Handlers =============== The Basic Handler ----------------- .. module:: slixmpp.xmlstream.handler.base .. autoclass:: BaseHandler :members: Callback -------- .. module:: slixmpp.xmlstream.handler .. autoclass:: Callback :members: CoroutineCallback ----------------- .. autoclass:: CoroutineCallback :members: Waiter ------ .. autoclass:: Waiter :members: slixmpp-slix-1.4.2/docs/api/xmlstream/jid.rst000066400000000000000000000001361342457644200212170ustar00rootroot00000000000000Jabber IDs (JID) ================= .. module:: slixmpp.jid .. autoclass:: JID :members: slixmpp-slix-1.4.2/docs/api/xmlstream/matcher.rst000066400000000000000000000011311342457644200220700ustar00rootroot00000000000000Stanza Matchers =============== The Basic Matcher ----------------- .. module:: slixmpp.xmlstream.matcher.base .. autoclass:: MatcherBase :members: ID Matching ----------- .. module:: slixmpp.xmlstream.matcher.id .. autoclass:: MatcherId :members: Stanza Path Matching -------------------- .. module:: slixmpp.xmlstream.matcher.stanzapath .. autoclass:: StanzaPath :members: XPath ----- .. module:: slixmpp.xmlstream.matcher.xpath .. autoclass:: MatchXPath :members: XMLMask ------- .. module:: slixmpp.xmlstream.matcher.xmlmask .. autoclass:: MatchXMLMask :members: slixmpp-slix-1.4.2/docs/api/xmlstream/stanzabase.rst000066400000000000000000000104001342457644200225770ustar00rootroot00000000000000.. _stanzabase: ============== Stanza Objects ============== .. module:: slixmpp.xmlstream.stanzabase The :mod:`~slixmpp.xmlstream.stanzabase` module provides a wrapper for the standard :mod:`~xml.etree.ElementTree` module that makes working with XML less painful. Instead of having to manually move up and down an element tree and insert subelements and attributes, you can interact with an object that behaves like a normal dictionary or JSON object, which silently maps keys to XML attributes and elements behind the scenes. Overview -------- The usefulness of this layer grows as the XML you have to work with becomes nested. The base unit here, :class:`ElementBase`, can map to a single XML element, or several depending on how advanced of a mapping is desired from interface keys to XML structures. For example, a single :class:`ElementBase` derived class could easily describe: .. code-block:: xml Hi! Custom item 1 Custom item 2 Custom item 3 If that chunk of XML were put in the :class:`ElementBase` instance ``msg``, we could extract the data from the XML using:: >>> msg['extra'] ['Custom item 1', 'Custom item 2', 'Custom item 3'] Provided we set up the handler for the ``'extra'`` interface to load the ```` element content into a list. The key concept is that given an XML structure that will be repeatedly used, we can define a set of :term:`interfaces` which when we read from, write to, or delete, will automatically manipulate the underlying XML as needed. In addition, some of these interfaces may in turn reference child objects which expose interfaces for particularly complex child elements of the original XML chunk. .. seealso:: :ref:`create-stanza-interfaces`. Because the :mod:`~slixmpp.xmlstream.stanzabase` module was developed as part of an `XMPP `_ library, these chunks of XML are referred to as :term:`stanzas `, and in Slixmpp we refer to a subclass of :class:`ElementBase` which defines the interfaces needed for interacting with a given :term:`stanza` a :term:`stanza object`. To make dealing with more complicated and nested :term:`stanzas ` or XML chunks easier, :term:`stanza objects ` can be composed in two ways: as iterable child objects or as plugins. Iterable child stanzas, or :term:`substanzas `, are accessible through a special ``'substanzas'`` interface. This option is useful for stanzas which may contain more than one of the same kind of element. When there is only one child element, the plugin method is more useful. For plugins, a parent stanza object delegates one of its XML child elements to the plugin stanza object. Here is an example: .. code-block:: xml We can can arrange this stanza into two objects: an outer, wrapper object for dealing with the ```` element and its attributes, and a plugin object to control the ```` payload element. If we give the plugin object the name ``'disco_info'`` (using its :attr:`ElementBase.plugin_attrib` value), then we can access the plugin as so:: >>> iq['disco_info'] ' ' We can then drill down through the plugin object's interfaces as desired:: >>> iq['disco_info']['identities'] [('client', 'bot', 'Slixmpp Bot')] Plugins may also add new interfaces to the parent stanza object as if they had been defined by the parent directly, and can also override the behaviour of an interface defined by the parent. .. seealso:: - :ref:`create-stanza-plugins` - :ref:`create-extension-plugins` - :ref:`override-parent-interfaces` Registering Stanza Plugins -------------------------- .. autofunction:: register_stanza_plugin ElementBase ----------- .. autoclass:: ElementBase :members: :private-members: :special-members: StanzaBase ---------- .. autoclass:: StanzaBase :members: slixmpp-slix-1.4.2/docs/api/xmlstream/tostring.rst000066400000000000000000000035621342457644200223300ustar00rootroot00000000000000.. module:: slixmpp.xmlstream.tostring .. _tostring: XML Serialization ================= Since the XML layer of Slixmpp is based on :mod:`~xml.etree.ElementTree`, why not just use the built-in :func:`~xml.etree.ElementTree.tostring` method? The answer is that using that method produces ugly results when using namespaces. The :func:`tostring()` method used here intelligently hides namespaces when able and does not introduce excessive namespace prefixes:: >>> from slixmpp.xmlstream.tostring import tostring >>> from xml.etree import cElementTree as ET >>> xml = ET.fromstring('') >>> ET.tostring(xml) '' >>> tostring(xml) '' As a side effect of this namespace hiding, using :func:`tostring()` may produce unexpected results depending on how the :func:`tostring()` method is invoked. For example, when sending XML on the wire, the main XMPP stanzas with their namespace of ``jabber:client`` will not include the namespace because that is already declared by the stream header. But, if you create a :class:`~slixmpp.stanza.message.Message` instance and dump it to the terminal, the ``jabber:client`` namespace will appear. .. autofunction:: slixmpp.xmlstream.tostring Escaping Special Characters --------------------------- In order to prevent errors when sending arbitrary text as the textual content of an XML element, certain characters must be escaped. These are: ``&``, ``<``, ``>``, ``"``, and ``'``. The default escaping mechanism is to replace those characters with their equivalent escape entities: ``&``, ``<``, ``>``, ``'``, and ``"``. In the future, the use of CDATA sections may be allowed to reduce the size of escaped text or for when other XMPP processing agents do not undertand these entities. .. autofunction:: xml_escape slixmpp-slix-1.4.2/docs/api/xmlstream/xmlstream.rst000066400000000000000000000001621342457644200224640ustar00rootroot00000000000000========== XML Stream ========== .. module:: slixmpp.xmlstream.xmlstream .. autoclass:: XMLStream :members: slixmpp-slix-1.4.2/docs/architecture.rst000066400000000000000000000126121342457644200203500ustar00rootroot00000000000000.. index:: XMLStream, BaseXMPP, ClientXMPP, ComponentXMPP Slixmpp Architecture ====================== The core of Slixmpp is contained in four classes: ``XMLStream``, ``BaseXMPP``, ``ClientXMPP``, and ``ComponentXMPP``. Along side this stack is a library for working with XML objects that eliminates most of the tedium of creating/manipulating XML. .. image:: _static/images/arch_layers.png :height: 300px :align: center .. index:: XMLStream The Foundation: XMLStream ------------------------- :class:`~slixmpp.xmlstream.xmlstream.XMLStream` is a mostly XMPP-agnostic class whose purpose is to read and write from a bi-directional XML stream. It also allows for callback functions to execute when XML matching given patterns is received; these callbacks are also referred to as :term:`stream handlers `. The class also provides a basic eventing system which can be triggered either manually or on a timed schedule. The event loop ~~~~~~~~~~~~~~ :class:`~slixmpp.xmlstream.xmlstream.XMLStream` instances inherit the :class:`asyncio.BaseProtocol` class, and therefore do not have to handle reads and writes directly, but receive data through :meth:`~slixmpp.xmlstream.xmlstream.XMLStream.data_received` and write data in the socket transport. Upon receiving data, :term:`stream handlers ` are run immediately, except if they are coroutines, in which case they are scheduled using :meth:`asyncio.async`. :term:`Event handlers ` (which are called inside :term:`stream handlers `) work the same way. 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 Hej! #. **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 `. The appropriate class for the new object is determined using a map of namespaced element names to classes. Our incoming XML is thus turned into a :class:`~slixmpp.stanza.Message` :term:`stanza object` because the namespaced element name ``{jabber:client}message`` is associated with the class :class:`~slixmpp.stanza.Message`. #. **Match stanza objects to callbacks.** These objects are then compared against the stored patterns associated with the registered callback handlers. Each handler matching our :term:`stanza object` is then added to a list. #. **Processing callbacks** Every handler in the list is then called with the :term:`stanza object` as a parameter; if the handler is a :class:`~slixmpp.xmlstream.handler.CoroutineCallback` then it will be scheduled in the event loop using :meth:`asyncio.async` instead of run. #. **Raise Custom Events** Since a :term:`stream handler` shouldn't block, if extensive processing for a stanza is required (such as needing to send and receive an :class:`~slixmpp.stanza.Iq` stanza), then custom events must be used. These events are not explicitly tied to the incoming XML stream and may be raised at any time. In contrast to :term:`stream handlers `, these functions are referred to as :term:`event handlers `. The code for :meth:`BaseXMPP._handle_message` follows this pattern, and raises a ``'message'`` event .. code-block:: python self.event('message', msg) #. **Process Custom Events** The :term:`event handlers ` are then executed, passing the stanza as the only argument. .. note:: Events may be raised without needing :term:`stanza objects `. 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:: reply = msg.reply() reply['body'] = "Hey! This is awesome!" reply.send() .. index:: BaseXMPP, XMLStream Raising XMPP Awareness: BaseXMPP -------------------------------- While :class:`~slixmpp.xmlstream.xmlstream.XMLStream` attempts to shy away from anything too XMPP specific, :class:`~slixmpp.basexmpp.BaseXMPP`'s sole purpose is to provide foundational support for sending and receiving XMPP stanzas. This support includes registering the basic message, presence, and iq stanzas, methods for creating and sending stanzas, and default handlers for incoming messages and keeping track of presence notifications. The plugin system for adding new XEP support is also maintained by :class:`~slixmpp.basexmpp.BaseXMPP`. .. index:: ClientXMPP, BaseXMPP ClientXMPP ---------- :class:`~slixmpp.clientxmpp.ClientXMPP` extends :class:`~slixmpp.clientxmpp.BaseXMPP` with additional logic for connecting to an XMPP server by performing DNS lookups. It also adds support for stream features such as STARTTLS and SASL. .. index:: ComponentXMPP, BaseXMPP ComponentXMPP ------------- :class:`~slixmpp.componentxmpp.ComponentXMPP` is only a thin layer on top of :class:`~slixmpp.basexmpp.BaseXMPP` that implements the component handshake protocol. slixmpp-slix-1.4.2/docs/conf.py000066400000000000000000000165021342457644200164350ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Slixmpp 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 import datetime # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) # get version automagically from source tree from slixmpp.version import __version__ as version release = ".".join(version.split(".")[0:2]) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Slixmpp' year = datetime.datetime.now().year copyright = u'{}, Nathan Fritz, Lance Stout'.format(year) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # auto imported from code! # The short X.Y version. # version = '1.4' # The full version, including alpha/beta/rc tags. # release = '1.4.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'tango' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = '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 # " v documentation". html_title = 'slixmpp' # 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 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 = 'Slixmppdoc' # -- 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', 'Slixmpp.tex', u'Slixmpp 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', 'slixmpp', u'Slixmpp Documentation', [u'Nathan Fritz, Lance Stout'], 1) ] intersphinx_mapping = {'python': ('http://docs.python.org/3.4', 'python-objects.inv')} slixmpp-slix-1.4.2/docs/create_plugin.rst000066400000000000000000000615021342457644200205110ustar00rootroot00000000000000.. _create-plugin: Creating a Slixmpp Plugin =========================== One of the goals of Slixmpp is to provide support for every draft or final XMPP extension (`XEP `_). To do this, Slixmpp 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 `_. In-band registration was implemented in example 14-6 (page 223) of `XMPP: The Definitive Guide `_ because there was no Slixmpp 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 Slixmpp 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:`BasePlugin `_. 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 ``BasePlugin.post_init(self)`` which will mark that ``post_init`` has been called for the plugin. Once the Slixmpp 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'``. Slixmpp does not automatically load plugin dependencies for you. .. code-block:: python def post_init(self): BasePlugin.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 `_ 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 Slixmpp. * ``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 slixmpp.xmlstream import ElementBase, ET, JID, register_stanza_plugin from slixmpp import Iq class Registration(ElementBase): namespace = 'jabber:iq:register' name = 'query' plugin_attrib = 'register' interfaces = {'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 ``slixmpp.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.register_handler( 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 **Registration Form Request from an Existing User:** .. code-block:: xml Foo hunter2 **Unregister Account:** .. code-block:: xml **Incomplete Registration:** .. code-block:: xml Foo **Conflicting Registrations:** .. code-block:: xml Foo hunter2 **Successful Registration:** .. code-block:: xml 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().set_payload(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().set_payload(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().set_payload(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 slixmpp.componentxmpp class Example(slixmpp.componentxmpp.ComponentXMPP): def __init__(self, jid, password): slixmpp.componentxmpp.ComponentXMPP.__init__(self, jid, password, 'localhost', 8888) self.register_plugin('xep_0030') self.register_plugin('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.send_message(iq['from'], msg, mfrom=self.fulljid) def unreg(self, iq): msg = "Bye! %s" % iq['register']['username'] self.send_message(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 Slixmpp Plugin This is a minimal implementation of XEP-0077 to serve as a tutorial for creating Slixmpp plugins. """ from slixmpp.plugins.base import BasePlugin from slixmpp.xmlstream.handler.callback import Callback from slixmpp.xmlstream.matcher.xpath import MatchXPath from slixmpp.xmlstream import ElementBase, ET, JID, register_stanza_plugin from slixmpp import Iq import copy class Registration(ElementBase): namespace = 'jabber:iq:register' name = 'query' plugin_attrib = 'register' interfaces = {'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(BasePlugin): """ 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.register_handler( 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): BasePlugin.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) reply = iq.reply() reply.set_payload(iq['register'].xml) reply.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) reply = iq.reply() reply.set_payload(reg.xml) reply.send() def _sendError(self, iq, code, error_type, name, text=''): reply = iq.reply() reply.set_payload(iq['register'].xml) reply.error() reply['error']['code'] = code reply['error']['type'] = error_type reply['error']['condition'] = name reply['error']['text'] = text reply.send() slixmpp-slix-1.4.2/docs/differences.rst000066400000000000000000000032031342457644200201370ustar00rootroot00000000000000.. _differences: Differences from SleekXMPP ========================== **Python 3.5+ only** slixmpp will only work on python 3.5 and above. **Stanza copies** The same stanza object is given through all the handlers; a handler that edits the stanza object should make its own copy. **Replies** Because stanzas are not copied anymore, :meth:`Stanza.reply() <.StanzaBase.reply>` calls (for :class:`IQs <.Iq>`, :class:`Messages <.Message>`, etc) now return a new object instead of editing the stanza object in-place. **Block and threaded arguments** All the functions that had a ``threaded=`` or ``block=`` argument do not have it anymore. Also, :meth:`.Iq.send` **does not block anymore**. **Coroutine facilities** **See** :ref:`using_asyncio` If an event handler is a coroutine, it will be called asynchronously in the event loop instead of inside the event caller. A CoroutineCallback class has been added to create coroutine stream handlers, which will be also handled in the event loop. The :class:`~.slixmpp.stanza.Iq` object’s :meth:`~.slixmpp.stanza.Iq.send` method now **always** return a :class:`~.asyncio.Future` which result will be set to the IQ reply when it is received, or to ``None`` if the IQ is not of type ``get`` or ``set``. Many plugins (WIP) calls which retrieve information also return the same future. **Architectural differences** slixmpp does not have an event queue anymore, and instead processes handlers directly after receiving the XML stanza. .. note:: If you find something that doesn’t work but should, please report it. slixmpp-slix-1.4.2/docs/event_index.rst000066400000000000000000000222301342457644200201730ustar00rootroot00000000000000Event Index =========== .. glossary:: :sorted: connected - **Data:** ``{}`` - **Source:** :py:class:`~slixmpp.xmlstream.XMLstream` Signal that a connection has been made with the XMPP server, but a session has not yet been established. connection_failed - **Data:** ``{}`` or ``Failure Stanza`` if available - **Source:** :py:class:`~slixmpp.xmlstream.XMLstream` Signal that a connection can not be established after number of attempts. changed_status - **Data:** :py:class:`~slixmpp.Presence` - **Source:** :py:class:`~slixmpp.roster.item.RosterItem` 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:`~slixmpp.Presence` - **Source:** :py:class:`~slixmpp.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 Slixmpp 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:`~slixmpp.plugins.xep_0030.stanza.DiscoInfo` - **Source:** :py:class:`~slixmpp.plugins.xep_0030.disco.xep_0030` Triggered whenever a ``disco#info`` result stanza is received. disco_items - **Data:** :py:class:`~slixmpp.plugins.xep_0030.stanza.DiscoItems` - **Source:** :py:class:`~slixmpp.plugins.xep_0030.disco.xep_0030` Triggered whenever a ``disco#items`` result stanza is received. disconnected - **Data:** ``{}`` - **Source:** :py:class:`~slixmpp.xmlstream.XMLstream` Signal that the connection with the XMPP server has been lost. entity_time - **Data:** - **Source:** failed_auth - **Data:** ``{}`` - **Source:** :py:class:`~slixmpp.ClientXMPP`, :py:class:`~slixmpp.plugins.xep_0078.xep_0078` Signal that the server has rejected the provided login credentials. gmail_notify - **Data:** ``{}`` - **Source:** :py:class:`~slixmpp.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:`~slixmpp.Iq` - **Source:** :py:class:`~slixmpp.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:`~slixmpp.Presence` - **Source:** :py:class:`~slixmpp.roster.item.RosterItem` 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:`~slixmpp.Presence` - **Source:** :py:class:`~slixmpp.roster.item.RosterItem` Signal that an unavailable presence stanza has been received from a JID. groupchat_invite - **Data:** - **Source:** groupchat_direct_invite - **Data:** :py:class:`~slixmpp.Message` - **Source:** :py:class:`~slixmpp.plugins.xep_0249.direct` groupchat_message - **Data:** :py:class:`~slixmpp.Message` - **Source:** :py:class:`~slixmpp.plugins.xep_0045.xep_0045` Triggered whenever a message is received from a multi-user chat room. groupchat_presence - **Data:** :py:class:`~slixmpp.Presence` - **Source:** :py:class:`~slixmpp.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:`~slixmpp.Message` - **Source:** :py:class:`~slixmpp.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:`~slixmpp.Message` - **Source:** :py:class:`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_error - **Data:** :py:class:`~slixmpp.Message` - **Source:** :py:class:`BaseXMPP ` Makes the contents of message stanzas available whenever one is received. Only handler messages with an ``error`` type. message_form - **Data:** :py:class:`~slixmpp.plugins.xep_0004.Form` - **Source:** :py:class:`~slixmpp.plugins.xep_0004.xep_0004` Currently the same as :term:`message_xform`. message_xform - **Data:** :py:class:`~slixmpp.plugins.xep_0004.Form` - **Source:** :py:class:`~slixmpp.plugins.xep_0004.xep_0004` Triggered whenever a data form is received inside a message. muc::[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:`~slixmpp.Presence` - **Source:** :py:class:`~slixmpp.BaseXMPP` A presence stanza with a type of '``available``' is received. presence_error - **Data:** :py:class:`~slixmpp.Presence` - **Source:** :py:class:`~slixmpp.BaseXMPP` A presence stanza with a type of '``error``' is received. presence_form - **Data:** :py:class:`~slixmpp.plugins.xep_0004.Form` - **Source:** :py:class:`~slixmpp.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:`~slixmpp.Presence` - **Source:** :py:class:`~slixmpp.BaseXMPP` A presence stanza with a type of '``probe``' is received. presence_subscribe - **Data:** :py:class:`~slixmpp.Presence` - **Source:** :py:class:`~slixmpp.BaseXMPP` A presence stanza with a type of '``subscribe``' is received. presence_subscribed - **Data:** :py:class:`~slixmpp.Presence` - **Source:** :py:class:`~slixmpp.BaseXMPP` A presence stanza with a type of '``subscribed``' is received. presence_unavailable - **Data:** :py:class:`~slixmpp.Presence` - **Source:** :py:class:`~slixmpp.BaseXMPP` A presence stanza with a type of '``unavailable``' is received. presence_unsubscribe - **Data:** :py:class:`~slixmpp.Presence` - **Source:** :py:class:`~slixmpp.BaseXMPP` A presence stanza with a type of '``unsubscribe``' is received. presence_unsubscribed - **Data:** :py:class:`~slixmpp.Presence` - **Source:** :py:class:`~slixmpp.BaseXMPP` A presence stanza with a type of '``unsubscribed``' is received. roster_update - **Data:** :py:class:`~slixmpp.stanza.Roster` - **Source:** :py:class:`~slixmpp.ClientXMPP` An IQ result containing roster entries is received. sent_presence - **Data:** ``{}`` - **Source:** :py:class:`~slixmpp.roster.multi.Roster` Signal that an initial presence stanza has been written to the XML stream. session_end - **Data:** ``{}`` - **Source:** :py:class:`~slixmpp.xmlstream.XMLstream` Signal that a connection to the XMPP server has been lost and the current stream session has ended. Currently equivalent to :term:`disconnected`, but implementations of `XEP-0198: Stream Management `_ distinguish between the two events. Plugins that maintain session-based state should clear themselves when this event is fired. session_start - **Data:** ``{}`` - **Source:** :py:class:`ClientXMPP `, :py:class:`ComponentXMPP ` :py:class:`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:`~slixmpp.xmlstream.XMLstream` stream_error - **Data:** :py:class:`~slixmpp.stanza.StreamError` - **Source:** :py:class:`~slixmpp.BaseXMPP` slixmpp-slix-1.4.2/docs/features.rst000066400000000000000000000000661342457644200175040ustar00rootroot00000000000000How to Use Stream Features ========================== slixmpp-slix-1.4.2/docs/getting_started/000077500000000000000000000000001342457644200203215ustar00rootroot00000000000000slixmpp-slix-1.4.2/docs/getting_started/component.rst000066400000000000000000000043101342457644200230530ustar00rootroot00000000000000.. _echocomponent: ================================= Create and Run a Server Component ================================= .. note:: If you have any issues working through this quickstart guide join the chat room at `slixmpp@muc.poez.io `_. If you have not yet installed Slixmpp, do so now by either checking out a version with `Git `_. Many XMPP applications eventually graduate to requiring to run as a server component in order to meet scalability requirements. To demonstrate how to turn an XMPP client bot into a component, we'll turn the echobot example (:ref:`echobot`) into a component version. The first difference is that we will add an additional import statement: .. code-block:: python from slixmpp.componentxmpp import ComponentXMPP Likewise, we will change the bot's class definition to match: .. code-block:: python class EchoComponent(ComponentXMPP): def __init__(self, jid, secret, server, port): ComponentXMPP.__init__(self, jid, secret, server, port) A component instance requires two extra parameters compared to a client instance: ``server`` and ``port``. These specifiy the name and port of the XMPP server that will be accepting the component. For example, for a MUC component, the following could be used: .. code-block:: python muc = ComponentXMPP('muc.slixmpp.com', '******', 'slixmpp.com', 5555) .. note:: The ``server`` value is **NOT** derived from the provided JID for the component, unlike with client connections. One difference with the component version is that we do not have to handle the :term:`session_start` event if we don't wish to deal with presence. The other, main difference with components is that the ``'from'`` value for every stanza must be explicitly set, since components may send stanzas from multiple JIDs. To do so, the :meth:`~slixmpp.basexmpp.BaseXMPP.send_message()` and :meth:`~slixmpp.basexmpp.BaseXMPP.send_presence()` accept the parameters ``mfrom`` and ``pfrom``, respectively. For any method that uses :class:`~slixmpp.stanza.iq.Iq` stanzas, ``ifrom`` may be used. Final Product ------------- .. include:: ../../examples/echo_component.py :literal: slixmpp-slix-1.4.2/docs/getting_started/echobot.rst000066400000000000000000000332301342457644200224770ustar00rootroot00000000000000.. _echobot: =============================== Slixmpp Quickstart - Echo Bot =============================== .. note:: If you have any issues working through this quickstart guide join the chat room at `slixmpp@muc.poez.io `_. If you have not yet installed Slixmpp, do so now by either checking out a version with `Git `_. 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 `. 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 asyncio import logging import getpass from optparse import OptionParser import slixmpp '''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''' 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. Slixmpp comes with a :class:`ClientXMPP ` class which we can extend to add our message echoing feature. :class:`ClientXMPP ` requires the parameters ``jid`` and ``password``, so we will let our ``EchoBot`` class accept those as well. .. code-block:: python class EchoBot(slixmpp.ClientXMPP): def __init__(self, jid, password): super().__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().__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 `. Calling :meth:`send_presence ` without any arguments will send the simplest stanza allowed in XMPP: .. code-block:: xml The second requirement is fulfilled using :meth:`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 ` 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 ` 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 ` is raised. Similarly, an :class:`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 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().__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 ```` 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 ```` 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 `, 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 Hej! Thanks for sending: Hej! .. 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. Slixmpp thus leaves out the ``from`` attribute when replying using a client connection. Command Line Arguments and Logging ---------------------------------- While this isn't part of Slixmpp 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 `_ and `pings `_: .. 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 Slixmpp 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 ``aiodns`` installed, then the :meth:`slixmpp.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 ``aiodns``, then Slixmpp will attempt to connect to the hostname used by the JID, unless an address tuple is supplied to :meth:`slixmpp.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') To begin responding to messages, you'll see we called :meth:`slixmpp.basexmpp.BaseXMPP.process` which will start the event handling, send queue, and XML reader threads. It will also call the :meth:`slixmpp.plugins.base.BasePlugin.post_init` method on all registered plugins. By passing ``block=True`` to :meth:`slixmpp.basexmpp.BaseXMPP.process` we are running the main processing loop in the main thread of execution. The :meth:`slixmpp.basexmpp.BaseXMPP.process` call will not return until after Slixmpp 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:`slixmpp.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 Slixmpp 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 Slixmpp `examples directory `_. .. 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: slixmpp-slix-1.4.2/docs/getting_started/iq.rst000066400000000000000000000136201342457644200214660ustar00rootroot00000000000000Send/Receive IQ Stanzas ======================= Unlike :class:`~slixmpp.stanza.message.Message` and :class:`~slixmpp.stanza.presence.Presence` stanzas which only use text data for basic usage, :class:`~slixmpp.stanza.iq.Iq` stanzas require using XML payloads, and generally entail creating a new Slixmpp plugin to provide the necessary convenience methods to make working with them easier. Basic Use --------- XMPP's use of :class:`~slixmpp.stanza.iq.Iq` stanzas is built around namespaced ```` elements. For clients, just sending the empty ```` element will suffice for retrieving information. For example, a very basic implementation of service discovery would just need to be able to send: .. code-block:: xml Creating Iq Stanzas ~~~~~~~~~~~~~~~~~~~ Slixmpp provides built-in support for creating basic :class:`~slixmpp.stanza.iq.Iq` stanzas this way. The relevant methods are: * :meth:`~slixmpp.basexmpp.BaseXMPP.make_iq` * :meth:`~slixmpp.basexmpp.BaseXMPP.make_iq_get` * :meth:`~slixmpp.basexmpp.BaseXMPP.make_iq_set` * :meth:`~slixmpp.basexmpp.BaseXMPP.make_iq_result` * :meth:`~slixmpp.basexmpp.BaseXMPP.make_iq_error` * :meth:`~slixmpp.basexmpp.BaseXMPP.make_iq_query` These methods all follow the same pattern: create or modify an existing :class:`~slixmpp.stanza.iq.Iq` stanza, set the ``'type'`` value based on the method name, and finally add a ```` element with the given namespace. For example, to produce the query above, you would use: .. code-block:: python self.make_iq_get(queryxmlns='http://jabber.org/protocol/disco#info', ito='user@example.com') Sending Iq Stanzas ~~~~~~~~~~~~~~~~~~ Once an :class:`~slixmpp.stanza.iq.Iq` stanza is created, sending it over the wire is done using its :meth:`~slixmpp.stanza.iq.Iq.send()` method, like any other stanza object. However, there are a few extra options to control how to wait for the query's response. These options are: * ``block``: The default behaviour is that :meth:`~slixmpp.stanza.iq.Iq.send()` will block until a response is received and the response stanza will be the return value. Setting ``block`` to ``False`` will cause the call to return immediately. In which case, you will need to arrange some way to capture the response stanza if you need it. * ``timeout``: When using the blocking behaviour, the call will eventually timeout with an error. The default timeout is 30 seconds, but this may be overidden two ways. To change the timeout globally, set: .. code-block:: python self.response_timeout = 10 To change the timeout for a single call, the ``timeout`` parameter works: .. code-block:: python iq.send(timeout=60) * ``callback``: When not using a blocking call, using the ``callback`` argument is a simple way to register a handler that will execute whenever a response is finally received. Using this method, there is no timeout limit. In case you need to remove the callback, the name of the newly created callback is returned. .. code-block:: python cb_name = iq.send(callback=self.a_callback) # ... later if we need to cancel self.remove_handler(cb_name) Properly working with :class:`~slixmpp.stanza.iq.Iq` stanzas requires handling the intended, normal flow, error responses, and timed out requests. To make this easier, two exceptions may be thrown by :meth:`~slixmpp.stanza.iq.Iq.send()`: :exc:`~slixmpp.exceptions.IqError` and :exc:`~slixmpp.exceptions.IqTimeout`. These exceptions only apply to the default, blocking calls. .. code-block:: python try: resp = iq.send() # ... do stuff with expected Iq result except IqError as e: err_resp = e.iq # ... handle error case except IqTimeout: # ... no response received in time pass If you do not care to distinguish between errors and timeouts, then you can combine both cases with a generic :exc:`~slixmpp.exceptions.XMPPError` exception: .. code-block:: python try: resp = iq.send() except XMPPError: # ... Don't care about the response pass Advanced Use ------------ Going beyond the basics provided by Slixmpp requires building at least a rudimentary Slixmpp plugin to create a :term:`stanza object` for interfacting with the :class:`~slixmpp.stanza.iq.Iq` payload. .. seealso:: * :ref:`create-plugin` * :ref:`work-with-stanzas` * :ref:`using-handlers-matchers` The typical way to respond to :class:`~slixmpp.stanza.iq.Iq` requests is to register stream handlers. As an example, suppose we create a stanza class named ``CustomXEP`` which uses the XML element ````, and has a :attr:`~slixmpp.xmlstream.stanzabase.ElementBase.plugin_attrib` value of ``custom_xep``. There are two types of incoming :class:`~slixmpp.stanza.iq.Iq` requests: ``get`` and ``set``. You can register a handler that will accept both and then filter by type as needed, as so: .. code-block:: python self.register_handler(Callback( 'CustomXEP Handler', StanzaPath('iq/custom_xep'), self._handle_custom_iq)) # ... def _handle_custom_iq(self, iq): if iq['type'] == 'get': # ... pass elif iq['type'] == 'set': # ... pass else: # ... This will capture error responses too pass If you want to filter out query types beforehand, you can adjust the matching filter by using ``@type=get`` or ``@type=set`` if you are using the recommended :class:`~slixmpp.xmlstream.matcher.stanzapath.StanzaPath` matcher. .. code-block:: python self.register_handler(Callback( 'CustomXEP Handler', StanzaPath('iq@type=get/custom_xep'), self._handle_custom_iq_get)) # ... def _handle_custom_iq_get(self, iq): assert(iq['type'] == 'get') slixmpp-slix-1.4.2/docs/getting_started/muc.rst000066400000000000000000000152761342457644200216520ustar00rootroot00000000000000.. _mucbot: ========================= Multi-User Chat (MUC) Bot ========================= .. note:: If you have any issues working through this quickstart guide join the chat room at `slixmpp@muc.poez.io `_. If you have not yet installed Slixmpp, do so now by either checking out a version from `Git `_. Now that you've got the basic gist of using Slixmpp by following the echobot example (:ref:`echobot`), we can use one of the bundled plugins to create a very popular XMPP starter project: a `Multi-User Chat`_ (MUC) bot. Our bot will login to an XMPP server, join an MUC chat room and "lurk" indefinitely, responding with a generic message to anyone that mentions its nickname. It will also greet members as they join the chat room. .. _`multi-user chat`: http://xmpp.org/extensions/xep-0045.html Joining The Room ---------------- As usual, our code will be based on the pattern explained in :ref:`echobot`. To start, we create an ``MUCBot`` class based on :class:`ClientXMPP ` and which accepts parameters for the JID of the MUC room to join, and the nick that the bot will use inside the chat room. We also register an :term:`event handler` for the :term:`session_start` event. .. code-block:: python import slixmpp class MUCBot(slixmpp.ClientXMPP): def __init__(self, jid, password, room, nick): slixmpp.ClientXMPP.__init__(self, jid, password) self.room = room self.nick = nick self.add_event_handler("session_start", self.start) After initialization, we also need to register the MUC (XEP-0045) plugin so that we can make use of the group chat plugin's methods and events. .. code-block:: python xmpp.register_plugin('xep_0045') Finally, we can make our bot join the chat room once an XMPP session has been established: .. code-block:: python def start(self, event): self.get_roster() self.send_presence() self.plugin['xep_0045'].join_muc(self.room, self.nick, wait=True) Note that as in :ref:`echobot`, we need to include send an initial presence and request the roster. Next, we want to join the group chat, so we call the ``join_muc`` method of the MUC plugin. .. note:: The :attr:`plugin ` attribute is dictionary that maps to instances of plugins that we have previously registered, by their names. Adding Functionality -------------------- Currently, our bot just sits dormantly inside the chat room, but we would like it to respond to two distinct events by issuing a generic message in each case to the chat room. In particular, when a member mentions the bot's nickname inside the chat room, and when a member joins the chat room. Responding to Mentions ~~~~~~~~~~~~~~~~~~~~~~ Whenever a user mentions our bot's nickname in chat, our bot will respond with a generic message resembling *"I heard that, user."* We do this by examining all of the messages sent inside the chat and looking for the ones which contain the nickname string. First, we register an event handler for the :term:`groupchat_message` event inside the bot's ``__init__`` function. .. note:: We do not register a handler for the :term:`message` event in this bot, but if we did, the group chat message would have been sent to both handlers. .. code-block:: python def __init__(self, jid, password, room, nick): slixmpp.ClientXMPP.__init__(self, jid, password) self.room = room self.nick = nick self.add_event_handler("session_start", self.start) self.add_event_handler("groupchat_message", self.muc_message) Then, we can send our generic message whenever the bot's nickname gets mentioned. .. warning:: Always check that a message is not from yourself, otherwise you will create an infinite loop responding to your own messages. .. code-block:: python def muc_message(self, msg): 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') Greeting Members ~~~~~~~~~~~~~~~~ Now we want to greet member whenever they join the group chat. To do this we will use the dynamic ``muc::room@server::got_online`` [1]_ event so it's a good idea to register an event handler for it. .. note:: 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``. .. code-block:: python def __init__(self, jid, password, room, nick): slixmpp.ClientXMPP.__init__(self, jid, password) self.room = room self.nick = nick self.add_event_handler("session_start", self.start) self.add_event_handler("groupchat_message", self.muc_message) self.add_event_handler("muc::%s::got_online" % self.room, self.muc_online) Now all that's left to do is to greet them: .. code-block:: python def muc_online(self, presence): 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') .. [1] this is similar to the :term:`got_online` event and is sent by the xep_0045 plugin whenever a member joins the referenced MUC chat room. Final Product ------------- .. compound:: The final step is to create a small runner script for initialising our ``MUCBot`` 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 muc.py -d -j jid@example.com -r room@muc.example.net -n lurkbot which will prompt for the password, log in, and join the group chat. To test, open your regular IM client and join the same group chat that you sent the bot to. You will see ``lurkbot`` as one of the members in the group chat, and that it greeted you upon entry. Send a message with the string "lurkbot" inside the body text, and you will also see that it responds with our pre-programmed customized message. .. include:: ../../examples/muc.py :literal: slixmpp-slix-1.4.2/docs/getting_started/presence.rst000066400000000000000000000000741342457644200226600ustar00rootroot00000000000000Manage Presence Subscriptions ============================= slixmpp-slix-1.4.2/docs/getting_started/proxy.rst000066400000000000000000000020201342457644200222260ustar00rootroot00000000000000.. _proxy: ========================= Enable HTTP Proxy Support ========================= .. note:: If you have any issues working through this quickstart guide join the chat room at `slixmpp@muc.poez.io `_. In some instances, you may wish to route XMPP traffic through an HTTP proxy, probably to get around restrictive firewalls. Slixmpp provides support for basic HTTP proxying with DIGEST authentication. Enabling proxy support is done in two steps. The first is to instruct Slixmpp to use a proxy, and the second is to configure the proxy details: .. code-block:: python xmpp = ClientXMPP(...) xmpp.use_proxy = True xmpp.proxy_config = { 'host': 'proxy.example.com', 'port': 5555, 'username': 'example_user', 'password': '******' } The ``'username'`` and ``'password'`` fields are optional if the proxy does not require authentication. The Final Product ----------------- .. include:: ../../examples/proxy_echo_client.py :literal: slixmpp-slix-1.4.2/docs/getting_started/scheduler.rst000066400000000000000000000000761342457644200230340ustar00rootroot00000000000000Send a Message Every 5 Minutes ============================== slixmpp-slix-1.4.2/docs/getting_started/sendlogout.rst000066400000000000000000000070241342457644200232410ustar00rootroot00000000000000Sign in, Send a Message, and Disconnect ======================================= .. note:: If you have any issues working through this quickstart guide join the chat room at `slixmpp@muc.poez.io `_. A common use case for Slixmpp 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 ` 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 slixmpp class SendMsgBot(slixmpp.ClientXMPP): def __init__(self, jid, password, recipient, msg): super().__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 `. .. 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 `. Now, sent stanzas are placed in a queue to pass them to the send thread. If we were to call :meth:`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) `. .. 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) ` 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: slixmpp-slix-1.4.2/docs/glossary.rst000066400000000000000000000022631342457644200175320ustar00rootroot00000000000000.. _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 :class:`Matcher <.MatcherBase>` object, and which provides additional semantics. For example, the :class:`.Waiter` handler wrapper blocks thread execution until a matching stanza is received. event handler A callback function that responds to events raised by :meth:`.XMLStream.event`. stanza object Informally may refer both to classes which extend :class:`.ElementBase` or :class:`.StanzaBase`, and to objects of such classes. A stanza object is a wrapper for an XML object which exposes :class:`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` slixmpp-slix-1.4.2/docs/guide_xep_0030.rst000066400000000000000000000214371342457644200203060ustar00rootroot00000000000000XEP-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 `_, 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 `_, 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 `_, but they are not yet implemented by Slixmpp.) Slixmpp 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 ``restore_defaults()``. 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='Slixmpp', 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``. Performing Disco Queries ------------------------ The methods ``get_info()`` and ``get_items()`` are used to query remote JIDs and their nodes for disco information. Since these methods are wrappers for sending Iq stanzas, they also accept all of the parameters of the ``Iq.send()`` 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 `_ plug-in. .. code-block:: python info = yield from self['xep_0030'].get_info(jid='foo@example.com', node='bar', ifrom='baz@mycomponent.example.com', timeout=30) items = self['xep_0030'].get_info(jid='foo@example.com', node='bar', 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) slixmpp-slix-1.4.2/docs/handlersmatchers.rst000066400000000000000000000001441342457644200212120ustar00rootroot00000000000000.. _using-handlers-matchers: Using Stream Handlers and Matchers ================================== slixmpp-slix-1.4.2/docs/howto/000077500000000000000000000000001342457644200162725ustar00rootroot00000000000000slixmpp-slix-1.4.2/docs/howto/stanzas.rst000066400000000000000000000006611342457644200205120ustar00rootroot00000000000000.. _work-with-stanzas: How to Work with Stanza Objects =============================== .. _create-stanza-interfaces: Defining Stanza Interfaces -------------------------- .. _create-stanza-plugins: Creating Stanza Plugins ----------------------- .. _create-extension-plugins: Creating a Stanza Extension --------------------------- .. _override-parent-interfaces: Overriding a Parent Stanza -------------------------- slixmpp-slix-1.4.2/docs/index.rst000066400000000000000000000143021342457644200167730ustar00rootroot00000000000000Slixmpp ######### .. sidebar:: Get the Code The latest source code for Slixmpp may be found on the `Git repo `_. :: git clone git://git.poez.io/slixmpp An XMPP chat room is available for discussing and getting help with slixmpp. **Chat** `slixmpp@muc.poez.io `_ **Reporting bugs** You can report bugs at http://dev.louiz.org/projects/slixmpp/issues. .. note:: slixmpp is a friendly fork of `SleekXMPP `_ which goal is to use asyncio instead of threads to handle networking. See :ref:`differences`. Slixmpp is an :ref:`MIT licensed ` XMPP library for Python 3.5+, Slixmpp's design goals and philosphy are: **Low number of dependencies** Installing and using Slixmpp 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 Slixmpp 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, Slixmpp 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. Here's your first Slixmpp Bot: -------------------------------- .. code-block:: python import asyncio import logging from slixmpp import ClientXMPP 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) # If you wanted more functionality, here's how to register plugins: # self.register_plugin('xep_0030') # Service Discovery # self.register_plugin('xep_0199') # XMPP Ping # Here's how to access plugins once you've registered them: # self['xep_0030'].add_feature('echo_demo') def session_start(self, event): self.send_presence() self.get_roster() # Most get_*/set_* methods from plugins use Iq stanzas, which # are sent asynchronously. You can almost always provide a # callback that will be executed when the reply is received. 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() To read if you come from SleekXMPP ---------------------------------- .. toctree:: :maxdepth: 1 differences using_asyncio 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 howto/stanzas create_plugin features sasl handlersmatchers Plugin Guides ~~~~~~~~~~~~~ .. toctree:: :maxdepth: 1 guide_xep_0030 Slixmpp Architecture and Design --------------------------------- .. toctree:: :maxdepth: 3 architecture plugin_arch API Reference ------------- .. toctree:: :maxdepth: 2 event_index api/clientxmpp api/componentxmpp api/basexmpp api/exceptions api/xmlstream/jid api/xmlstream/stanzabase api/xmlstream/handler api/xmlstream/matcher api/xmlstream/xmlstream api/xmlstream/tostring Core Stanzas ~~~~~~~~~~~~ .. toctree:: :maxdepth: 2 api/stanza/rootstanza api/stanza/message api/stanza/presence api/stanza/iq Plugins ~~~~~~~ .. toctree:: :maxdepth: 2 Additional Info --------------- .. toctree:: :hidden: glossary license * :ref:`license` * :ref:`glossary` * :ref:`genindex` * :ref:`modindex` * :ref:`search` SleekXMPP Credits ----------------- .. note:: Those people made SleekXMPP, so you should not bother them if you have an issue with slixmpp. But it’s still fair to credit them for their work. **Main Author:** `Nathan Fritz `_ `fritzy@netflint.net `_, `@fritzy `_ Nathan is also the author of XMPPHP and `Seesmic-AS3-XMPP `_, and a former member of the XMPP Council. **Co-Author:** `Lance Stout `_ `lancestout@gmail.com `_, `@lancestout `_ Both Fritzy and Lance work for `&yet `_, which specializes in realtime web and XMPP applications. - `contact@andyet.net `_ - `XMPP Consulting `_ **Contributors:** - Brian Beggs (`macdiesel `_) - Dann Martens (`dannmartens `_) - Florent Le Coz (`louiz `_) - Kevin Smith (`Kev `_, http://kismith.co.uk) - Remko Tronçon (`remko `_, http://el-tramo.be) - Te-jé Rogers (`te-je `_) - Thom Nichols (`tomstrummer `_) slixmpp-slix-1.4.2/docs/license.rst000066400000000000000000000001021342457644200172770ustar00rootroot00000000000000.. _license: License (MIT) ============= .. include:: ../LICENSE slixmpp-slix-1.4.2/docs/make.bat000066400000000000000000000106411342457644200165410ustar00rootroot00000000000000@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 ^` where ^ 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\Slixmpp.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Slixmpp.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 slixmpp-slix-1.4.2/docs/plugin_arch.rst000066400000000000000000000000501342457644200201520ustar00rootroot00000000000000Plugin Architecture =================== slixmpp-slix-1.4.2/docs/python-objects.inv000066400000000000000000003165461342457644200206370ustar00rootroot00000000000000# Sphinx inventory version 2 # Project: Python # Version: 3.2 # The remainder of this file is compressed using zlib. xڬYH6zϯ7vc$Yt. U h-\ן%IRLWBBYO]ewG.df<,ma5eGIhtLN\Q9 }!c 5pǸ?a,O|I~9r-쨹Y%牳Z̏Tm)~ MK~i{}{<ocz)!};N<)j>k: C6[8sUp80SG uO/ŪFgt md> gl(}E )quoP"nH?833Sߢ>K{+xf*">~gq&Cz8Jv˘fg[ؤy.J")ab"b[+O q:4_Ez-s>O~BFW_pJy_nQHaj؞ӼLf `|Nա8dq21|{3';xevAߡzܟ on<YGQBnc'E-]Ҭȃuwn:̢Ml7Boo]1_%fY~tL 5%lTƩ_|xDxwtٶxtGR;XOp5^&=-֑sld!r$k8'a*ΖŅ⦳[n-8[x(3;鏦?wb/x(0|ӫ{&bV9?X .nqu'N! DA, S{o8FXgs<4+| ^xy\)blŞ!>rK' ǭod9S1ѥPPWӀbx5 2KVũJf]<, UzKyJ1hS߶yxO\C }8"T"E|MFdoY`ޝ"#|~^DŽQ>wS|'Chtݍx8(I`GyP8D/$o`.dZo!/o4 's-BQt1a{j< ?:'ϟSp;9:~Y&aFK7T? &iE;v_!>[OkjVHy!?x+RQ|tKZ%1 .F_FP*:BmO}M_FUm'lPt%Uw*/.U"Yjxo/m† /=aºK|>oMrX-S%1PܱL7YغO PaA ,,O ʞݍBjs4}* M$Ҙfd'D, ˆ1G ՀQSwu"x!.&DϐǎϺ;Ty<ބvdG$}t<%o\88O.' aPWW?XZ~JpmIp6xow?Ļ;7&7K :jٶ ښ/唉*⥗ >C8"cU{w\v @`iB/n-<,/RhU;3Hap/yXFwUP&Sb Cze6$'Vp̏E*qΜ 1zΠ"لRsF/1H&d}^Ts(-h#Ҽ{m#Z|#68_ 3pȏovz7XCY)\W/-dwY҅sW9:E P3}#2}>B†_0"NVxHS?05B "dnof_'b1n/,:)or*ErgY~R{E{XÈ\z@G9@X^YBZ;Z BO:_ 11 oCJHu͸?m>nx3xPq_Ei}/G ?hSr'E##xI79~[ݻwmًl[jֆ8\ 줏MOF7q߾Ko} Xqx^9~V[7qрK>+|ekKTԥrm37HEg%ˀ,> ɧM"$J A˃F!F)n[~| |4 PyE;(;--*VO/^^YF72Z Po|{G➂6{?UG4?2(J)4Dpp*,pXMUas\!qC_ך'ز$i"y穊JhI)b=C7nY xyJE R#&f烫a.ැq'XK[Q#x^rvԇ..SAfU1~M{89`R4/1",ᘝ'*xcQ!q>â^VA0)`2SNE֊5VB/xN d&B}zdvYtJ \m R7aR5J6 7Ȗ'QI0^0DeBov? 4QD (,}]D~u=qvBf?}0p9s{h77gq\lث #!PZa߿A=<ê>;-ހO)}~'1ɮ/mۑFݪ T1w<=(D7)Up,M=VOyObk@⪫T]WmNuo6=7WW,d Q\R)ۢpU~-ľu]Edsq}&+1aE bzfc0_8vph\w{H6i*q]n*R}?aA OkKS,KaXC )5a!eA5Ѥ =~eyMvTrLM\(Il$^`.MA*kqqӛ/eOJBK_Κ^-4 RoBCN2+D8J_ƟIZiC!D_LL$n? {Ţ#&{n;XVS"rw)^|-Xf^* tQ{IH/FΞ]PtpgEzAH8{ uК}-\U3VQ湽ji-–ڬ6Kl )y)5q|W&G~R,6Da&)l&wSiyXxyPmX -wueF7ΑyI]*YTiN*[Ϲ C'v;'4Q~Ux\û .;fIٿ\ qʁ2[vcenR*GR8W@ Pg(N>Z&qoYB oU~>˨Q'tC塶+SE޻ح~H.wYS<^j`} p]kҖ _'6>F@Av(8 3Dv5G!g۔E:լq?P T,WxFk09n`wfUrhU$>φ$Kwl' et;?'(k<w?әXREw~t7w<_lK_Ҩ*8 OWL+$f](!(*r SvwU _(.AEu$j[Ast,a,ʁf>Q B9X/4N6~wg6?*`+8ךVRU)p@oB_XFI\ 3 Fgk80 ^[+BQ+cm_o6уi!F+2RPiP24݂5UbJ/_Io~ s+ ] OU|nUy5 ضk}1Ф}}q{6k#ؽ0x `?Qz@ )ź'܋I) d  tbC E gZϘEUA')nK-7D7u<x_@ݙ$Ê_qR>~v/BL'1sv_`pAW xP~q._K7PR;Ѫiv*\M_oͶD'pq"6TH*Uz9USz>ȻL2kj1}l8)RTϨqV6bG(]+JQE[Xml@d)̭;' )l.|okQWw9i=q̣RWo_;aVu'[Hd7A`׳rCp2:75X?6O 1~sthl>=2J֪δ$_V>>C ۔/*jJ^{fBn>fh>؎4p4<(繰vn {4~8R.M&A1>o /dfSTxbuWPGfs8joSglO,>c0ERZ #AZ ]ûkgNy|g7p$Y*Z04jHXaHNI!]#A1NgN`"9N^E-_)D>XNȞmhXqI# ӓT,m[n6^a޾tkexp]C4%ȌQH1Iv.6߀i]2iN{oPHeC5*.*@ WXT.MZ8_zd~h}64j2LPB=(M/C82Sv"}mW~ GJOU.p%{<쵫9&^EQR.A)q%x6}?vswuEOԤyDpeBZPh W\"r`^9L=b;ޱN?0q$Q<nj]rI0ez)(Q/7RXJt}g"JUkɠLKr RD5Ss*e$ J!fV8TwsGSe!ψ`9m 5+ʍ%C*bJ `v%nsC"{ &]`u(O[04⟇B>IH ]FPhHjx ++IH Pc"]PLPi?|^,U_- I^D`oY|_u}O:̴,1|'F9kQڅ )a\y99$ x%4EU`=HR](k΁ڡ ox׀eH{*E{yF# $ ƚu5}0N#_*vo ӑ/7tv|^|Jw]e]KD4?$ | DXE OF>T`{|x5H ۂE4TJŕJ՚ވOQPGTwjAq!lc(tT˚lN ,X*[|;`{7[ %+َGgeugq@R nA ߣD 7Yub +1Fy¼]Ō9+z7r ER~Z4LtV9CVz3Y僲,z{P#"T!cvi;|L[|ktP~ƷմH@(&䟔5ߛU!MA9;=/ EC.x4Fp8QD }U*# JM(x0ӦZ[mf6QAh;OHBb|yïgG\Ȥ< a;.Q񳗡sVxNO@l+ف~\J'Z` ?y0YQ.vm,>Y_rXVW4^U-ҳJJ TqR cMyeG-J XB/q,RK;+I|(I F i}(,m)`{K7u (RL1fS*71|[2n pN\C,'I?uq3t>)@FWuBVf4bBrcUnܥj-u<( pKtIk’AIĖ5BWM*_&AVjdm.lM FX_0s`_Lmj*| umHb8BscY]QXAס~ˇA|80C&C\@i,}w:| iٯ"?6~lB!x((X%8-Vni&ȮD{?4q_9O"t.MH< pH_@ɨ2\c\oB^ 5[?5M2?fL%7*YeZ)ުңRs0*zEkLl<8D^ œ*O(\_O*U e; ٭s5S.ͧLdAX*KrSrLcN~2i EYVn3/jEtU801~jݩePB(籅9L?ɛvgqB*BŎ4>kf!^#P4~63 qA@@U|]'={~5 eJ O!u:(XWG^ t C{{}Aqݿ.6\I|Mij&b)K[cxdܿ3[LNa5qi,-[ .̃:.]$!`RĚwQοE`4Gh#_0 $ xޠy.mܰ23BcQ} *#~aZUe6לr`J1&#( 80LbYn1fh NBNHƒ\GV%j p{[*a}Qd!q!zDŻwaG9_xIDJ^GOnIo%k󜯣e,𫦔Fb u3"A=}JG 'iK Mfb  e ^))|O {`/͑PGY=.6lD.PP{i!;I(3iX po2 e=zU BE)Sz5addYdDpoGPYr=?ֲBC)YX|^'*A8}Nh 5WWVv2g$]zFB԰',vx{]nSl4{MTK=6gۘNɇ٥dB΄It@-{vkG^q[%.1MHt$ 2;R F";;zh4R2^sί_jPڅBAؠ4y)5QuHd%P<;@hn%!iGWܺ~FXV#}KߨSjءjxPlYLofTُ虀}{ͽn惓"^'@ě7ʐ'*? . [')JJv3^(.޴&Z)c' %HMpFp+YŃ"g"ruCd™2a0SEYސAWMNUG=q0fidȇX-^9g#tptU'Y(BHANuȻ-X iّx"1NmT3=7maE `@hC͋:tͬUWuxcsuVŇ?V&N  q]/EVVͺljj!'CNM4%x &7XB[;Wmv2%3IKYρXXu%Daa,r$g1bד ZƄ/Q:2$+i5Oi8~8~8vOZ|']U>)iX6c*x$&׼BDmH}b!YU{ U{ϳn_1]Ah37喡:'$ ]eWi7~.0`S?Y1u3Bs.Kw܍{ (A8!pz H})X5Qhit|9AI*.ŋk*K aݽ r3`ȯbo={vrJ}IQ8U v#H@zvK\bFMAsNE⪉aLB$(%ZΫ6GZsvޜJI.svɯ]6bQxA? mY(fR٬3QɧzE槟$"ܒaY-Wl9ILhH"(_9*h\Y\McxF o: 6@ĬTԧTS)^B %(ߊ7 (cAH4]񳏦EBui*(c19"F$a~҉0ñPF/_%'ǯzT:f쩭aKGӇS*+۠ ~5()'Vͤ_4CВ<'tS<%Lk0{Vd0",?{[Nty9BK=.M IfwGǣCJ)pb䅰rCl"l)N!ѶǡhoWJⅽyR1HkńdzR>(ߋ{kǃ -4 * 1KLЅќrR*rA:M)&EwmUrf] ؒ2r0Bƴa%Jrqs~RhCxTboř31@ - ~'` //:| m'_Ǜt¾ j٬z5Fֲ@x0Pa>G ,:u.RRL| N3 CBG5uTȐY+Gn"IkqZoJyM2T iE,VA!kIºuDk{Žhhw;8K4F[(I/' ׽ <č!R+a?!E֟Uϝ0 bu(tV`Ra]hJJ<$ UMۧy!uie$bP],AW 77dmxXUI v@/` P0bjuAʜVήa r ~My_1ZQ&4:dy[FJc"* 4b5j_ M4i4փa/Epq[0Q]aB2'%?●Ϩ&aҸSz뙒I.1p )3ۊɋ7 hm\2i.|0%1nWOQB훌杦 ^U#QDK J s<3[Ugwlny{8$>uߟFTGl.|.8HVp!T:i:aٹ2.4#K,<ҕ6>5!g;nPU?!5SCXy(-RѴn]tZ.BԖiy0׸w T2bT=`WRb {k̶>&Ӱ b:ZzU05V`;쒗@l4"o4ѡ{椖g|ڰjw')" ô@-Pi)1@<HoFB^;nrRIZē".aJJ3(^0=GJ뙌pHQb~&tz?AMRg]%.1a57Rlٷ<\Ϻ,\I<"915y.n8 <NvC PT |+5?ۻ2c,ͽO4gR*孬~5뤿<՝@谋J QRݥNJ(1wa 'ʠk* , qYn噃! ƊTKl-xYY`Lq4&ϱetvVrraI w9!jLކj ۪+%d I 4U $GMzdU/t2CjMJ\JZH׳.,_ֈ(/7 UHػm ͧU$\m|^º(}v#v_ᐵi O;Ke G&VqH_ݟu,Un ŀۆ_p~zMjG 5Zfm^eVe~wɂc]2綍#Nݖ4Ri BJfMDŬ-3B{/wtנۭ _o4؆XxN2^ kB]cbFm&B)=;T@`n*N|gnn3*m:{w:W@Bb|/K:v']y&mj/5ISP+{u&B ,R@jM$a[&$5LU˚A4i; jpH%#4Vq&X*~T C~$u*%TZfVрrjsVF-髈Y;тM:1~JٵXduVɅKjRK*uՆߪb5(' QRk166Ak[ˢ,SkZ &l4tЈR#lxRRP}M3|1GD&Ve'H oi_ `V6:15}b\ Oq9MeC6_ʲ*_aJgp|t>% w~Vr@__zZbM;8a:+L/iRvDy%qV_">>G/7F?Sm8`mb6* Ă2K]Wڨ`PI",Iw| D'߉pغ%{. S79T9CzmήWQP;)ܕֆOZp0%8V["gaQ\֢;ݒ!Ȭ T|In[gC5k3˥g- 1x#[$Ae6\Z/$aSΤη՟oIuǪ `lw _>%a2Cg)m{h8/4HZckij&CR;2a籍窎x*p+Ky>d7Vl^JT :Cgq1ɮ5 +IeZ#qL 94!$OC'33xU e&$F#LRg618K+A|Ģ%y|ݑNXOG,܍,lpQq gw2V1(,A3Xc0lb\g%,$|^^ Sx%jZ6!W@R+ؽ>ے0O@:25.@.ySu:; 5YjE2PL1/U6ƶ6v6ln;by](IY}\C'2>JGJ(ƿ0vcoe܂"F4Űb;a$v!y2ALy봕hJAeϾ:5)(]4/q-_aP TTS/JMe@TF]6}4 [م>2o cNu$ P7Z5n o %)!ADZG~nGh'}-uӯ@)OhM I:b~nWhdch9Xc⠪2Ŧ\1G,'dI^~VMvSKnsq9A|#EKan㲭0A_UGCDbapHƹs)?#մ2# ;t-dɡuX=Z=%D̥"hNȺ8^-;["b'5^l@/SZV4 rt_͛fiޚ/~k~+S*/CA ƦWqa)#01mwaOeh~RMGr*9]Þ;j0u;9f,FE k@<$ }BwғB%\6]md5?lFT Z8UpĈ (+٪ZsYXu,}u@MASyQWtȐ̋S Ype)ՏJzpΝQjGft>ƻ`Ksjo6FlY\c^щQ#np"󖶠d{ա {,4+NJê,Ƈ-YkUJGIq'٣{>Kb䎽1ؔC:xOr1×JDzXm.fUaM㲀qPb8?Ϯ 16vP'V(r 9C+r_$ߏ$@Ju|,Fݬ*E] ."/$f3|wGCn7nƗ}V3o s#QV,jvnZ<`RC9!E-h0Utx[r4Ƹ ?PU5pn;Q6_kE" JU!]+{  ]sA}/fk[>`FE5'5,Z'(xFԘ&rT:!W57|߸Ǚ4e|,'XkCUT4a'kWImTJ@J6BrM&%GKpGyE]*Y66rj@2+BE{om(,+,$NEݞa!fq[*ZUq&_p#AW 8.)LWrɈ0Kw:߲ɔ3Fhh5ycxɓ^o[rPm,+@o l~'bZM%C[ gE_dcnJbO8|w^V!nBRPG%k7Y1Av^ykX;Ins jyVkOµb!A^?-2kFÈiP,%ga.\~B^M<>R[GgR g B ,= , 5(9Y*e5S,m0{-jzYj',%sR*Mo*a$ms8FQ_e?"ըeYǍϺdYxFWWBLmJPyF!r?u{a E(be[9 RX{p`#n+"- `>6~GQ^./x3aآW7%TPn_܍/b4t.L>` 2\=)~^BT*e\Rq<(cTN)Fj7Cxň}<9'1ㅅB yC$cD#8;*P Hs$Z0MQD#Ԧ5ۨfm¦T(&*1Hh"KH1$^ w{ݾP[ I Y psc{ڵ?z\<+Pnjx'K}z}jܗ-r7,yH ;n, g (UٓJ9UD=hZ,Uv0XhݯoMŶu*ߞ[q&=](WmgNUv\ǯ48.`ֺ %[{b8XO|:ߡQ~)1R ѯ4.F9T[H2nnnzi:yXn-9.as ݿ+ K[\eb݌hz7'?ta/OJB6}.U OߕVɜN峧(Så9^/#^Sc ~ɬ>s_b-"K+C<WZYm+ˀNx@L͈Iˮk ~EΈxxSϡ8JVW߽Bz2A!bg*#amརŸ h2-¤ ;[NIKp͇IOϰ sP_GQ^ûD Ïi $FSC?w)YaFVJVGcyw9xt kѱij@ݪ:5s6hZ sv5||y)aK,gp;MїѴlTb D/JGhxyg??Z}=RWaVu2 )1Co8!!nUE ݆8¼He))8M6,1jht4:NQZp.l1[ vBpv/"'\u,^4LQ\DZNI4U(F :zHn Z󃬡t@_f6J)ŇyIe|nZu!c@A%eCk VJ:cC[7ƠʶQ 4cԒM.Fd&Ψr6Y׼]Ă}8-Az&' v^ w(ҽqե)޵884ip?,C孰a(8`$`]6ӧ0RGw"M@(=86ѫ x`Inܑ=Q>)[0=Ux9 EcFמ?n k>LIUkSN+Y;?a\qHd=Y_/iW#P^UR=6m%|0+X0|Ҿ=xi+yzqAXb d7OSIGyc-X|Dgу_ב3lQYFpy(7 89aBpKS2XY9 ۙe$N0h?zE M96Qn8Ij\$aXGvjhoi+L%&@{٢Y\jyBeô?F!.ejI ]^ju}OB"ŵkob-n@*8Fa VJZ7~ɸWq:*6.e9Q! jw3!CcBcr/G*Okk *i#1D8]ƪuM .!NF_ /Wm.@JGQwW_V q iWa%S({mS{/3',N/ Д$j;_R5 ށ8 L`jNӯŧ\q!@aR[gG|D`pzT$^yjJyûO,,/ⱃq)R8 {s80?jC8?{4KY q[I? c}NQqLh~ ̜I+\>)g"Pu}/x~ T:l'G(*o1M/#]{D#!~0-9ռ#ۥ; Sz4̢ݡ{vb7~ϞczI;&fknBf^fF4W13| 1pd#jL﹵VDn(Q Vǻը%r! -,:z`0v%be>~ l6] w14yANe~epF㪲G/LS4fmxGaU!hen`,2]F+S*C;ՁAI7m0ӗ(3P  m~7fGà D,JTi jީZ!&DK*Ipaq pp;yX]2H'^')!Hr%,">ɡOT4KН+a|8{Zn*=ô'R{f,z-~™cbv9X z2?ib`)3ˉgW^<ѶBl*ZvLx4kN+TZyT{*%$I/(nzW (v  +Nwڙs M+f6~u_F;ɵ#w3ۈgr$~$ f/rMnɝEUf ߼ѫ/p]gj4\wr+jk[*^}+lJBděOTbrj4G=ĹP=W @|X_>T\:QW}@q0v"W]2coyQw3:8\406*Y"v riH,cf [8Yz1J톖wyJwjn-35_0 ?])H&خqyN٪p@*Kζ`^CZWQTjaՀ學-h[UƬ1[&OB }r7 3ٗ|8Oם ,FxaIl*rmJm>6پ$F(JlpŘ]Vq \70AGqmrZh*B=W[L5\:WƲLvBZ(ԸRYKxq j^FKb0xFqy]; eoQUhwъQ>\x`+9 Ҳ#ޜf:ʽXa ހGo#B1 x+ɢpy2܂׺xC)vU7)%¢&zޟl{@cI3*i8eOTRz3rֺ0_TL)4!mh9L.j"uh~<sDr`³AKX+G7ww3FujC=I {ē4~Vg(q"lcm?l҈^(DhQJ9|%8rfhwL̥o怮yƜ>J7P5'Ƣ|9L2 f|إY[|׆@w2z@¸ۻs;t|5vHD4i|WZW9 /'toy@Bظs1ق㠤)>ky6 w 8v'ֹ6ԪfttfE`:4e6]LYOp|3MUpws? iɽ1-zm_AWG-m~7ǤPɳ7iF[d͛;O_'gҲ_x cpKyɾ+w=WP@AeFvL=e]U.lUe)5*_'"DX/5TJt{NIGLsEcV:w8m:c hJ>̻0x\+SK?Šl0j _y:ɻ=O6]jets1 /9h=K=0MD\]LJRFnZT-hyrAPL:㟴/[Q!hv,mBO"{"|;, "Ka2q5mG3~n!TQB~< Tg~wbIܕdT&DK,yy5Y(B0>-M!Fj^rؽx4p: 607oTqRo}f7^&}jIK4#/OOunmغ%_| TW9of(3{KQ[`V_~^(.k^c`v$gM:t`rʋaqp틯ob7,v[. WJ}kJAJ8IJ$U+Q\K]9jZQ-Z1$W7u-`W^U'ق̲vY^bݪ:[!L̯}XeOL\ƞU^bs^]L=.p: ɰޔW/ byb*y[<rF=ԊGS Dqύ^Z*@;6csj`މ?k_&"ՠ.)B55/-vi9Hm1fGqC)QnKV&|PdeMDQ AlGh4qo0'+0?>%ONb-&1w~E&]s ȮTWuAP jݪCn/㘲iFҲfQ&;{\4~] @Ǩ^o:4X6 R B_.a]{9=l*9z?Ge3wZCQ.Tw$0jdzG$"HN 5wB֖^=PٚJRvGH!7 —b($~Z_6f#sxIk !&s@$O P&g)ߡ۰uRXi';b1r$kKtUXO =j.%rnݯ}X{֭ç(TXT^6Y섊e`VѽI <!g`[]e}RчRKkN:f FZ*WdT\Uڢ^("~=qڒʮn:GJokaK@irtՐ^"0)eREuB *-B1ŠpfX Z@BLSȹTK|!x8aVƣV8<ekz0Ÿ3p-f\Z`}+۱pm!PtluQB#*PM$I=e2p 6<4иZ;s EخR529n`0i7fs}S1mꐼ04Z+] :i=GywsP'%1h .$/^_aت7bܨo<$ Iwp *]7G=A'Y%U)r7R0q/em\xXO-cBE$M|VnN cD@4Կ% r( cS8gkqwh^acఓx%7&U*} _F;^O Mҽ=܍uv$0BH 0AϣH.G 4AX Y>3k 8G h}!#\L=J{Ж*7TQFAsfhZ;[G_ C[HY\A7rz _AF#M\dZ)JThTnJ܎)zeuląÃ0zjAݚ,3ra:XQ\eja,۹jH׺/ +тٚD9j P[:|gngC=Uq PrC=3jҮiԩfv~ ЀIOj1z^t2. ]?_ir,1y<5PhH'Ty^h:Nj۹coxEߑ=W1j>:$3Nb3(s1ck ?"ЗSGkE{vJF> G98!.A;pĽ+)o ?̓h2K I^$wCnn,C#/qs]꺘iw8Ej /^9y%EIQI\tjtm[-,񋖇<~Ŋ | |ufr=e;nm1,PG.A/䕗8#hԚ[1&ܻT?q#Խ@N|?z '{`@+b5lcݼ7sq]d!a BF]<3g!p jckD0ĝmM=)J>WC<֓x+idO'$HbZL/e15Y5@}H@MFnhUsgc 5*FJ㸻2U~+CMQ0:tӰ2BHO3kcq/J`?SQ\V̠9yܽɓ"g| +AhK!pH]n+{'TQPEˠ&6K$IwQ)>)}F␻>T;Ed֐-1dby+XJIq!n1#[+E"%j/̏yݳiB5{pRAf+Sʄz'qdAI8ீ#.B3Ln'ɭ>UDWgW w#ϥ"" եKK}qW3D'<sU;G)ȞL,ȹyQ}͏u ]cͯ9Eᴇ^~B|SbliFUYz`\Kb<^Q}?BiڿYL8ڜ/QIkgN#;`tupq]Q8Ū39x &wɵ[YY}@>(&cwe‘.̳L?.H{=py\*g==!j".@V͠V^0:y K?:PKK>iEs x‚cn-s2-1. Кup$Htb2qw#XQ=>ZLŏY%9mwv^ssR[ `Wp! jKY:g_3@s;xc!"-DՅ^wZ%㖬W:"MnxvIJӊO:. d\f8/)ʸឋv|0OȾ=qZb Jr7LD۝> YDSPRppx@~?^?^u +5!:w3ȉ< <5KP>Rxcyy/~v_ɁlF1q12 ''ׯqb %1bˢwUeTZSqv=Oݭ 3 b+6}0qeq/=w&swض! } Q3B9F'9Zv"/Aw)֓\g $l{Cu5YOA{䓵1:㫸,Eo=0/tynjG\*Yp'\Y;Lݭߑ+[%U9^ bbȍ.bNNÎیڏ,bD81xn+ցwU*&e|\7ߑ[h WtY*ޘuV$)A4l;RtMf03:'.1r0W܈oad M#\?;bb!c^mI) _:Y\Y=}>E%?~Iϋ8&pV1;-UW35+j d6u;_KNΔk`K:yISZwzv$zd@|jw!kn_\e\m^jaho2Y4S~dFz+qj7wZ=>=?պ';D)嬀v ;ƛ_M#nڿXOXSw_ݡ4c4c5vgغ_ l'bmbǹ~!.MI$i?4t:,*ް|^5XS{Q^mE6v?@AX>R| |AEEEc&o|wa{*iU{-NVQM%2؍]x-"!5 g ϚمZ#3#B`I+V5ueYw6׬zZ6q8ooiȁ9޲&8ZdB M\Z^ .SDP,߉?dc=hߍE_24]꒿Pi߂hR_{{^9ț=\'}Kf;Jz(jxTx~AZekȮ.vj~vy\^ՙB̆)Ó*/lkkm Pjrev!)8J{:= Ӽv\b4Dw^;-7t/DDdQPWŧLuXG^*]_ w'6͑oj*DYOL:=1R9[ndzxl6 yWP&b:_a3@xow݄O~<@4'W n #yf~AU|$[gnv|QD>F_:_r,S^YiwAX-oãM`u,in<!{2& x?>O=鍧_=C3y^>/`Âb[<'`^bu!q̈́4 (Vn_ϑ&ċ2eL>law*N6W $Րt0ԇnt56fm}`)!'6vzmFqzY/ b,`ڃn |`+a~%i_S9ڹDaٿlaxkªR s(P YG=37щEoQ@9"*xUZ[6-D ; bj]FLxv(0{d*ni2ugpרc?fƠ?uPrÀ t=+*UAn|U!M{A6b_ o+26#2.S絖."qD0@ZFIœ {/g\$"xqeg")NNAyT m8n4C 5Fg=X_2(y?S^?oi YT@0U*UPnqV~ laLLrxn^H,6lvQ6;Y'ciZڢK$Pp&q./mRH>xLB pcq 3DWotYeO-gd+Ki#hYrͷmTkn֚÷l@ 1e1oP))Bj0͆ND[E.%#^'q6Pu=ّoAJw}"fz&<_-Acmg7bH9q !RŐr4 ވí9h&*@}ނ2 Hָ Sk$a3hv+6c?ktlȳ$ޥ(B#(=GjexhNq5nd]AJTu > hb딾K8;k=A}`ͭt&qf%p6n,Ch..)UvGEAE! Y49Y qy+.mn$BŁٴ:#ˣX:uD=}(o1JkV(.Agg\>'Hۉnjge_³|\KP鏆uOUZy{'ASk謳&EW˿7"W.tE*|D`Y, ,j7VÐg b `# jt?,R*T_ W;CѬuF-ɿm܏(.U5oNSHNKCd}XbB[PV>闡s0مoPWͽżX}+i7o9Zty<10lR!v5'юz@P}8ӆ͏\cl9x]-{PVM1̑`~ۗfpM* N7; γ'h33/%҈Ҕ:/Bֽ]@- '*ω> =%뤒)`Ϟ 1 wy_sT8:*vA+v,)jz:oNPϖ  Be,lQuðD@AXs "j3}  sECgYطJ88tUww?#ú3flYGEœ34鄶B+Wo_dsѲI{HRm[[|V,Z,L*FAƅnZrl tAimJUݍC[a+muIqdZNw(c_I9>&H{IhAIМ(O$H E9r_i*Ճy@g3r&tkVKP#"$w1%y#gW(Vl)a$CV,(ԟʞ P֨D۹h9YPB,z;E~.7\z9|y:r|%ӺʯX&z!鑔}z?yYUvuM#oTDG>dC0:ڋ:th;@.^.dzĎӓYݷ9SV-즱ʷaIB4T{Trt䏡53/ v JʺawbL}.52o^V OiGnGQ[h[KwJ^<~]+$[U@6?~ނ M` [;$VǶ2=L}T|(=Ahf3=zdj (ycH1%Clk78ɋ*i: k2IBVUO㢍{Z}vT,j߭QQ%V3m"}u|kSaz"g j6H(t~䪆j]y;UOhMRA`ɡtzD#'r'z{ނzn*J BzmwmM=_2D] [[:~.kiO9z8jL(bv}D/l1ľPf3fv T"Yyw {cWYc$-?` |&q' J2.Áfd/-S]B,F'PP;<'5OV:"z@ 49k`]sK}+R_e=_<.׋IR؆c&뤬:dJя!sgb79͌Ey.YTQ%jOP3(VavWNtY{EN< Cm^]##bu?.@"8&)䥣kihX JSڧ,6ψt+F n̻Iʯ։̉e)v_ ʮfT<;yQc7*~qmp=3' ٲFo7ӿ_so %Ŏ,:q̦ʧExf* |4HaTt`>v6{j8c/| +X9 ZJ'̂f|[6<0n/D٠ ō zW#8?܎˭ĵ'r_Ca")!ovÙ(Ms?(R[<"{_K5DTAZ\Q:%g2|U̐*% U"lAo^0X{@} B# L@JSnG' @14yi~r\u{-hi6db_csΨy䙦JO> B(揤!W|B 6x.&TkX^86_ r =. *&P7֡VYQkE#E^Hv7Kx`ze~*/ Bm8"j Bui ܢ~jNޥ |0Ջ $sV>^hnvhPx ~T#o%?& dje$ؚ*cQ(8'zJrb` 3.Uf(SLS ~*E!ܪ[^ 26m "vR`f v0d-̏`3j{M+~:&^yK:rӧ[V =*P"ѥ[&eaԆ>oց4 2)1Q| *4 RdEl XORF@ \[@=NJK.8 zHqgN@e0IC:'3uS 5gU~LQNX\& XkY6pDR1$`"L١ tkq84hD [E*!8(~Nqyn<)x4a=)UyRU AJ▄k>߼n]}"ߊ1y$;[!P%H9$:}(Ɏy"ZN `x[Σn=Elh ̭lkx,K,)߳^o\5Lj|'>]ijK$KMrJ3.N)OQ954nPKh%M6j=C=A:|0ML^eĂ8knTH#b^-_|==/KzIwxgKM1nm7|Ђ/w1ߞ+d&79Pdk>?xYƠQ4虊'0݁mf; l_dx39{-HZdD,'S[%f`mrVK%,e=lr](yZ>Ay3}9Gb+[6_oCIGpi['_b~;7P=,Od7pmq5Π}|y )1R0lE9)dЄ&J z_]e#H۴ԼӐ6 [ 0U懭bZQ[ڵ32I6mrtǒB}j4)*#<| l*.#=j;Կ'!_'$y&hʝ>3i%.޳䓗0=jSP[i96Ϣ`x"Tvn'n-K/3= V EQNǡ1:Sf##0Rt pےom)>VnAG`;QIG ڿ}Je2~SuRrDZA=}3P~n̓'&zo6J.ˤC&h&v}&^x&8!YGWw[]3>z̠* sH63׈%GZ W؅0x ChzrM('=cļDo3aRߎّd0-n7on>.2@^s8/^~bZ X˫'0WOmRMhnqtܥJ@@TP3!fia {jtܻӺQjòQR1]OB,=n2O,ӡJy,ߖMEt}$^Uv"kGAيL*0EQ%#R<zL;v[VN.~K=_<;-uv˄ Q5d:>TE?(:8:>>ejOqћ6wL}e%x P4εupPȨZ 1n@{b=.w{.W/;,rt0Sziop+[90<1F[/c>s.W;K_w_wSD7b4WqN3{H0U`ZԩH(޶^3 0ifjo$ f3$%j<yW5aE( uEG(qV=D|" .Ew $hju$M `z&0~}="< g!34\${=J2>u[4Dwg Ӂ.h2:qGgHd8y*T`JҚ)0b?\1@Z w+.s|eQBhBh,N4,攪=f[I{&*C.>yۣj972'Q }K'I]6L,U5~[]#n6Ȕ$NR!FL!ՉU:0FKM ]@c0\e\hPZ5\?N07 9Ʉ| ]ʷm5f99A븇{($*i|J*_^S@q}6"p'3] W3:VK;*q(9Sev#kdLzX?l?g<<>C&fzW[ˤ5\qr7Zbݺ76g1`}.߶ ׍,%+ /w44j'P\WfA|vV6&nǗ ;ZٝyB J vl@ J|/J Z9Q& /2ri^9c AZ&xyfxl_шr>0IzDF!V| :f_18)Kx5,2D2彀8D$UDEvw :[Npp-F/Rbf4GVVRcZL$X^ԏ-lrLiȴL<_hr~Ĵ 9#|a.42_fjQ=JW|:HZ֧ 裼>ra֭wXk?>p:щ;97\="sS2յNg#sk>-Rxi XHXė>|&||M+ LQ4gƔGa=E*6]m2-qj^Lw췯z ;,"z6@u wk@?q:]s]oS]ӳV֟*G8mrm2fSKrodҋ04߃}E4I$KWf~[>}[L҈<+!'W_<ڻ7{-E ywJYdҸ3oՓoff?`琿c- Iw7eN[`; ] B8{[ )+R?6i,piIws}L]* m>l /2y w 1yʞX//|dA/=qۆVY](du!:- P㙐6dwXf=6/[O5d`Xt@rd@S{|<EAEbRq[YOq._'O9אS)rskfdWNycd-p մpj!|LP:׃iCxX.DR$へQpj~Y8=@Pyuva1n70<:yD8R5k[hTGHtz-rQ. &b-JI5~|x -[+B+=O֯[u_ W5W cti_R kPv!)z?B9 hujKuf䟋~YSsVGw*t?[ԡ= j]dk< %8dUAeWa-AD22Q~0)Sμ?(:!5*{MnIVgM[ycY=Z ?0.-Je)LRL ҹvqch=a3b~\$X$`p8*TFP]u;ڜ`4C6_vZ/ ъ#GýNBaߪ}rLUȢGgS˔%{}9V㭆bXjD p~Ojh>&->L\չi#'2o{ikǖˡ:$ZFAa}'xR@bPΏLqiLg}m}t>GJ ~)]yHYN4Sӎ1{iHX5ߔV(RP BJM ~g= ZM xG?gXfȭ.lRǼPnkmgHhK`VQD0UPNkZ!0QgiB|| ^bPR%,qp!LQ?ՖԓnTiQq|=G^8u5QP3uiY#lP9եXQ*D2~O${:(t=s-qƮvWLdnsf̔O $/\%`6KBxh{حş53mj'1<53]|l lgxҊI&N^V)aq|!< ;l\ s9l^V:\B$8j]|璼C*c2C1`ZA^iB=GOFlޝzSDuqKQ6{ s q7]WK^-A⑷r5ʖ@ʟ* N%;5fIq$6+%U+:0Pj!2yc?kӏI225dR{q <yf//dLtfUCI&2G&)N*F"5zW 33!=OL~x}@y.Ubv BKӍ=\tFW -KFp*9ҨU;G)Af#tB(OC1T}Ho|OMi7=,8@?xBzKmjڷO-]|RY =_:j:0RǕG;19ª#<GAO^;_[ ҊU‚ɺC=/3ERE/whA0aʶ'1jPu8dT$b<%P.oT4mf*ލ4:W2NZ="RχaTBbq?xT;Ge)| u?*))I&야A§bL*hgAI,r!4%7[+zȩ%~~BIW֣Ox!wϞęB꫽$rKv.{kPq=8ޭa7̄_{Wge O T"i1ąF:r!M֋SD8%jW.3!`hϣPdOɼ$TNpr_Kx8ZQfkB14VpЩk&`>bS[A|C.SQ+fE"t5 ٜKPLG"ܙN9zQAoS &DMUqUj_ 2iZ:oTG7>ѹo$ʌ԰3XYJSoh Wr*b N t|IjJf*3ZOԙ潄)O?dDJh K[o ekRԟr+oJSppM;Il>'Hr$Q{aN{{?)8w|",Qk:߃t \8urxeMl+kgA8/ثk Y{KeqG7Ү+հY౟SQz~|KIIæS@< hd$yneh;5|keQ_pHJcK%UקDB4rYvг]!%kHXDj? sC@7 9)on*yS?'$T@:۹(|˺<.;=snzGD+ϯnoIh$Npd*/wK.$2M푶]Dg7]9s xO78'[j7qxDz d˚]!ƶk 1HzKq;q&qS|2͑ F^ň\ x寿}!V.mn ps| !j( z_PTM{ DT.J>QO(7󾕠F6ڱa[ӳ5B=>(3$;ѽbR͍jzUq~%uYOfCi5>T]0|+JtF骕-8k#t)| NŽ'!v-;/zIC?ސ@AD>{UI7v;,jG3ggRѸu{gݼPݙ2` :uM{[ fKI B1HG~zڅQ(le1PIP1!,cm[mPIR_'܁"ɛ!D6$4F\P;ҪQ D(ժVĤ0._q(NBiuy;-ay4eװީшXļ]_+(\q#-Mǰ"ŧ =-iK/ zU6O>5Wߋ+{U`|VQ[£B h;sa}hC_/hk"tiIk=6 \ ;/)hNoGht)j_n5-Oe8-< Ӯn"KȀzRu*`זݺw_CF#4ўh}z_ϔtd l+tauz`v(&vL)N-o;T|-Smi\KI/rW4d0? !HlF4+S|Af~#˴P݄pN%ɞXq.LzBsš̾[OaB[~J'rM>c,K!и+-CzHncxjc%﫛 /2:jHBZآΩU%d{}kT;JN@W,u@Un{!ZZ7IAqik[R%)VUg¦ 6NJu-J1󄔧'$Ř$P wUatu`o% ʏSsJU>>*s * F]SYl%&p|"#:.twx.׏e_G>h}uD9Bf?YdPR]ܪO.q^:i<&z-lAԸ:-p¼}=b6 =ZW c$g=-a'SM3Pyy2eM/NķDY᎖Z"%X0ljj E=۬6 =^ j1{)%<эZ˜,C7 [-۰# /2}&_է0EeLcmwy{0E3~勛,=R? ?b~M|cW|Ԍ+Ԧ2Op J#?\}L rd^LO P|A2 SχdU6U: #Z"8& z]Rl-G/ϋCLtRJ¾Fe |ˁf*e<} DXN拇'sݵLkEZ1 oA,. /HӉ)Tٜ3Ô$߉-siZ@kKBd=~1V b;J BY6ȎLfO;.po ͈`:|UN)4:y#4nƤ"4!xǎ..R>뷽ڸO`n&DEZ`e%k̨?˶R|bIpvN(iWjsP|`ka _{MR'mVP$n)m!Vl_>▩[n)dEGɕwMC(QFdaf)bW#iZJtx~ 3YA E^VlF; otQE-5 g%C/bqA!VTPS|W|jN<&uerO]KusY|jdm(?Z8\PZǗQ@̎_n|]htCd?6MGPʨ} /l=*du{Hgܡ&~s1gnߠqU.ve.|(J R9;f(sTHIݸoxچ|\Y A?`' k=7xŸ|m)a >^T*HeI7%ɳNIor.znJj2@ ;$t0 bWMJKQz?k,ͣ# F?0V8{KJ**b#K6h6_{5jše*]`ob"qj[LQ wGhXv\j93E\Pcwv4:+騤-Ӳhɉ|s%P H2ƪ|с:̘^$X;hE!( &!`ͪ%ek&(LF 2#xbfV$gVUUwN_j<Z[kը>e%$L6O j@ ˘I9_*:*gj\#~-MỶJ?NDK`W+,*h1m_iND-Vo/ڣ h7KL  $;vX>n&;4$Ґ_jy^<߯flVItT~ ?9Ae1 ^J"ٟ ' EɆ,JG鏅C Ϛ^@C#Pa2To^(Ў ݭD`]u~vζAK*ak!bMx=Pҁ (* %b3T4Y R #q͙b߯؟~Yb7hdރʻ { g'GI5R V* ׸ _{+N)jI(&@JhX cv"fpCǪǐڀ9JJ +an6 EcH(% m-L=ҋ5yWPk(q6ػGzu+/VLy5ƧӪ>Zܛ:͏p5nMiŞ5]ZRfҮJJ$5HC ZnK}E֏n%/eIBIIb*]蜑$sv8塱v_v(8ckbפ^82.J+|1 eM+->] \2s$DQc 4_NS\ۊKe?ި-JM=pjbK&Fxǚ#Z6R:U hYި8G&?QQVW{ȋ-)+JBoWqKOgy kB 3d:QI; vu"Ih xh7FD%vy#-H=,$oX( PA#u0(0]?y(oD.r tRVKNZK%ش"=V&_eu+^ q$ Dňn2=6߇ߑ{97buL_D78eavDҮntU3(f.>7T).Y[̥3"A:^1aEfrFOrŲr6%Fh}ߦ 0#xFڱ)"nnXy={^Dzu3JB[t[L^Rc BҀAWBbj#J|ݸjO`&0u"mNk .8Vqk#!kdw+ t$}^Hea<35*f}#Tlxb/-ll6;rWQw)q'5Tnʇr_'YoËOZBkA Pͫ1CsYD j4~~(~ />ߴ9^'#ar`2LGClh X l- n|aK6sp_-׋}'za*,8\ ܋7czx =7epm Y46>cףf?bj>%ҧ9R@ 7能MsiR) &_ޏI@(>LA@\g2Ѻn;\ QYu0 NCU2K >< @q4X@C0?0s 8|Yw(؅b81SRb_rJS!`'.C9J\۞G:RumcY~c_Xb?A$;7r{C 06 {Hؠ/ʿOIFY2mWҞ7`SQeN³vq$jo27P}~Y6[{c+[|(:qUIpdFM77q.հ1z^ b^49qv/Ty(a{2^P>2j3}L=B5=(zts C ǩ(jMC_Z%@a+2Y-@.MW=эe7tn@"m5~>P)JY*f֛x27]mrb]*!aΈ'2E,nH+ͱɶj7E%wcTQay9W4~JuCQ)LU!9/( 2y݊=Tdb1V0=&"$cYew:*GkAҼ-狲WuJH& H*BKks"oL-K@+=/#tZ-%pu3)m${7GB8xtb?P3~&νe5ۧNi8v{JzAʠN)S嫖FXV?N ,ῤ 2c?. Alo=S߅PF;V4_oI&.=6Nb j95nuiTyr_pi`@׀g":GSau+G}%"!lW+Z*5ڭGka'΢wti5DLn!:%P򨿺 Ym՗Jfr4<\q /2GS^ՖG4NX#fdl@ʫ-zB VL3O KշћPS7<5}$o0M2*U~ *:90E  F3jK,P}!ingr{gji:j4>.?UKJo0jEm(QY'8-]7轑qoy9_Swk<WdG Z* X/B5"`.@NGF^ʢn0a ߱q.5 ;l&QRL`DwG2)БqE[R|[j{*fGŲyf 4t{#xMPux1ƳluqR{%}3Fۧ.ʲqQW=zTyWW3Cqvףb;`*Tv$B1R;_2*F1N/9 ' mR!ԝua~.# K+rHkuX)9PJlqAPP % 8,G8nc$!f˂v3&:8f-=+ecY׽-}&/\"rqY`R82r$qN=[>rٚz0 D܁UݱlELPU\N!On:GF#7.2a77$e\߄UsvZߤky;}[>x3[&,@:xKc?y@$ n B-AiawB^>-~v*0I&A:2 O]2ue_Qj?+:2`7I+!3%^6_7Vi"-ȑG͛O3i+RjӞ%Kg٠v}]ؽyHV \eiq :Us7K bxzS*Ye~:_}qΒZ۫uhjsLSctƻ|f^5yr6ؗ!^$}ja}-7Ti\YzSTT{_@M.f4=?ƏXX`SzEg֬&jR^吝>S@EȊkm I*/;$iYs.?%_{STz9(]a@TQUtn{'9Hj.r~B esM&RF%瑕:qũhd¾pɲ놢Ov|LAB3;xpg4B[ sftMkB a<h{\% t^}$1^|9+ B=+=^@ k$ *\Ø"멈 cuSܣ+A@SAwm׻v ZصAuC"o8vqܮl Lոwx<)S+R4<8n:tȉ@x]nZgטkDrd<'I\;UpdD|PM0{kQ' ~mYnv?1Piy;5|K@,SMBX8MPn:jTq;PȬk\?ҭ7!˿r?  xSi]qe;Z3> ~F?c|Dױw: nm+^őrdbr?ق&+N_8I#d?+ ]5MDqb:aˍ:$5WLd/+CSCtL෿JKNGEbF+h}^ĥT{zvDzn썞܂ 衖lYdTKj4U+쿚f&ߓ~8FD 9{j7 37ܑJ| <[Q=݇n,H86~廾*OOD a,+_#ʊ5k>00ZK$,oh4-tvScдnIKP삋+NNSl)$w|uô rm)Գ\֟8췡<8'h.wW%2B,rMBm.b W@"k/d5(6Ć??yѩz FE}%du*՗~'k8waZWN׫~g!Iqu+܃|]ν]Q,K^RMDXBLXV&$MaǂwNI)¼:ܔ*Sb;" [:ٯ ^,6^n` U >ߛ{Z D!Unz(e8%톀I^U0'zw[,&VfژU1'.V\q-MIRyS4a =okR9%^HVm?mod:og> 2@֕l Ekm a:s~/C z`K5[B=(y8v JyW΍Kd$֍m { =tmfh?9cVgn6l2y^>o3z}7G6@5 =š@OŝظuVwg=ש q*YAs +X9C,t=FhoX-K{fWkyVh=C݊C_Գ1Jgйlb pZ^,q,Fν|,ek?/|E u8/SEIፂP<* lQOV)fiRu9L,xϔG"CjVd-$MHu+|("Kb0.g<-%ۡhdgw5heӕXS t6\z">@MErP1CP/8bd4_!e\G便Pw<+J5i,``/17\M´,襋>tܿ,g]FqvL3;W9-k"|jYRKeceoB|޼)Y.| C:M,@V鏳vWtU#ed_(YUFO:U9 -VyŽG&]:y΍RNFJXhFJ"7yJjkv#&q:P=ΪI{=a0[m1RB՗ vrζ,OP 7Twd5$G;*F,VS Nag oBN X@iCl8֯j_R{Lw60(asIenV>B|V_@Bp> !zxxԿوsf ͜ܟGeUNO鑩E,œ??;e_m<; ,czeT_ kiY˱9D.uB97$n|툅'4~'c4U٠Z#\\=,ϪxKcrߗh`&Xa1qQdV[PΤDZjĹVxv42H %nޝ%VUU\6li5h_}q@(y>^>;J0g%2mj2y!*Xb< gXTqX>ȁ5:L~(dFY<ۡ dUO0/TPP%jr94CۛYϟ'<+q#MTp%&ȱޠ* ^ ;7io(uG:r.3NJwV=,G!T;a*Ag7΋H'\tnZJ(jdqWBaZ',fѵMʩ~̫YZ%0Ix=niMuIΞ-“WnHӧSač ށGj50[.z^c"bᏗqh8mUG[6Stk[7UR-!, lu|9.%@e #~wBO$Yؑ?;*Jd;[Nkg;jgj)~nOc]A*cK!(ir5 ݝUup!۸&lGLg/^CaUMjGO٥x'5دO K Đ8df'ӄ+ gfj8n`+W5m!JAUFjx%%/t .;T΋\T -~3Sm1M7NP9 }m?C&eXDu7=A%Ni!{sPu,D{nT;,k'Za>Щh%sPיGUA#)P+Džk㍂ҳ3Avn7fmy>~e|x&gh+0rEC#TN>%+I(f~/BkHm pmw'Q>ᬕlmf:pa0rYtڝbl{7PQmW±=㸈J z=m`yrnLa<ǹPY!c2h4Ph͗+(} j;˨Y+Lфߏ-J"6c2"l4;GJ֝ q-f5Ä  *J/X}YƝ|)g݂]lc['>JZ=3AYeq5P1L`9l_6)ion!q>߯*l(F;ά|>wp (v_cfhr%Mڿ` 1-PGk; @@ T6Fc%t+dT #oӛ-m1zc5O'Wbҍ.LZn8Nv*G;H%S,,s|(Jds zG`{1_ϋy1=v3[@{Ϧ{OvY8#E=G,ϭ-n-6ac":fD-܏Q9NP-?[5&-|NBXDҚpAp.>~b/vȌ΍o WZbRbH0M(r([[@%nKG w^5Qzk~myi&@l<V  ]^ABIFX};gй4ET\<,_8'kVE9A͞f? 5FQO^$S~=l? } 2j=\h2\ 6D?7:6-E K@,/HQ){^kXZpiFBQiΫ!9Iba])G9JJ0}YW^ L 9Aa9Qlb̷`Z}w;HW6c\WDBiٹ|OOфlĨ?j' /9w:{X8PXDU,1}JO '0ݻ0!b/&Ⰺ?Yd:N,xB7zm-cYULwgk 44 _}_}W C9d tڳd6M [Q pQ>ODav3iD^k+OR$j: 9ׄm{uy'dsxLK\ޓ㳄NioW]2W;ʪ8 jۉwwIWGr0e3yBh[KVJ`G˜liܒY1ֲeMY~ɒWD9B5% @X{} : Kz ၛb#eԀoJْiip[jG1t_aO]ǡw,| (ޒө*-5P䴡bJ&qZ @t.&Kf{ nV =7h@ɟܦˆf)Bg+ި%E{ܣ2R9ibrG B'6CoKb4W'݇ёuޭA<((npn:X5DQ~/3)LP2DpM!/U =s CkXᎨG>IGNG*/hSܣ+j`n* 4 ,R˭ oXIEPpz9!ƨ#R?q2Fs6EPGoXOU&i:'co{ zUsCq oZ<_ſb-YQ迳_s$,ɷ +n oZElՁo>a&fs%Ԓa@< H'S øq"mkwț8%L<ފ*vnwñähD i@RxS|rɓ@qYK>Dy~-sthRA@6=c ?6T V}zNUgILYX9 ؼq H%,.7[K|,O~Xw&'W3QGB.@[ƒ`/Cꃂ1<8ň/< Ib |vbBs$ (mUXjq~`c5vGСZ_}"*AKuV&%2i5c*^8Xcp<0kU{+ӬA"y7/q>Xսl@CNuN9PW 8&~cU *^{ *"]<{t pM}VhFE+[ky80yy3LT[c]jpDZRmU8}Z { &4lhA"r;̗{d[p+y" pJN9 C}_#. N 7[zY=a'UbU4_ s(4;ACcAK{*N6r0PU vyiY&;&Si5n=unF%#n6Kg0.Ou̻te)|4*נUʢyL=+O%Ŏ,Wr`GF?rKu đVKi |,(MNJHT(I 0E72BouîrE]R72OÈ(K4'E*>Vgb!k@^^'g:\Os񐜛H`Z6>|6ܗ$+۱C`l]c,*| a&vk4@ӊYP~׹i|kB[ J_Bw@LT{r0_zBd%B\RÊcƘ"I'(d4AП(JyG9ܨ:FJສ-T$c?B!w=fѥ =.2h_Dü.yK|OjEYf )FE7rR2؉lᔂKkc-ije]Xَ؄8nq+f2=smGbڈbL7Tf:sё^9wg>Б=EԄ OӠh3i~*X'~! A_idEA s;dv2 qle BW/2#Ăi[ͣd[U/gWٮ9sv504OA|ukD$uGrX]^2^ z!L(~+]iv3}%,u-/rh(U;9QB: z< 5:ж}oi]8 Ґk jh_pY\ < bPls:.i*,==+& S,y"nm9խf nkiJ]]to~\J}-VtPjQ&qMfM5yőKAf@C@<x^|E-Us" bڼ@ %j?&8$4a:Nc96 f9+FGZTgc[g~qcN9[цhhuP',uT *Bd/-=M\d6 |[?lfx9ҺTzt<5.A`eWQ5huǹuZmD[r]e(DžmcЌX!f.]< amR(oI; #:ISYIElF+h}U25"@^9xmJ eW%HGL:, w5d믨؎$ „CG1uK;93̣xa61u% &N&4 }z\zCi&wTT\-cIKش.F3x :_'~fq M>JCT1Nwq4<*"(E;iL!⁷ Bi-0u-{@iFIe#N1M.׸BsW},OPK4zD*!";0 n%Xy, m'ov58k hݏZ-~,)z=r=N@Ay3A % cF$ d$SG|U)"~wk ,M$AP{9Y˽>|>, OvވUQpD؈]Rr#6BڷUe*:F|$-.d08j{Qo y-,оv=Ʋb>jwTm;|#Kk9۷Y]_r "oI"X* sY T6F!q3%`){j\emG2rk*v~;! K4?G)n` tȟc}~v380Ym:[%O.8PcMQ\|$A|SdXH3d[2XaU=v}u;xB'8@"LHhs El!T#C(u&NMa(;z{Fa-^},J T6=c _ZKC`VӖI_m _A1fY Pܜ&&ӅK@VP滠dyҺ'(RseP6^ھ.jWJ+&@q[,fpg56$[$ed0 t҈F}C;l VIe)FijR)0g{݈wFCh^ q'A"|?1ECj%*Gܙ@[M)4} Az457<ڃ}e'p (pۗpkZqPRzŽuSWG/17?b^4;"7dmDliT!+gЗc3>ԩxMgE)U&"סE]MdXVŞ?X*9B=s[lUm?,"U{P+\yH[zʱ fmؗPc4󤎪d9-9ul]`%Fb\~+ȗ[7\$x7}23X 'Io!}k=wIIڹqPbѡv5V?f?Q_TJ,]d(.ZAɇ{Z@ԩv\Гd ZHڷT¥HTu}:2wx]QoDp(h bVssLr{]., CzWlIKbī$v\QTv(D2*Vk25O4=cE'tr&TQ@m:eOھ<}|OTJ]Ǣ8\Оhn _H)Ղ *BC-Vϋa"x¿L㼹%$d.Tz+#UvhP#M $e.@;o>8։&D'",~8/!j8q o# :%`UB%ڰ_ vOhj(V2IVplGe5KBD'mM Veԇi˨^ƽem"=eo[7w/ R*kla׾=PaGKz "J#  0 uy4fJnjɷɺ’غ&̧AEkwA0?ҢwAU0r.QZ6T㒥T5Yv5.Dx .rNW9~i(BhegKDWܬM*B-ɐۗS>08jfvf:Qdb+;ӣdl$j㇬q4f>=?wdwB*1K'deSLd{vn3`Q+Z  (J]p8n Β6hlP.y`4 ] fKc^; uE;#S*@_@F?dZ%y)ىN1o_ܱքoN]p!IOR'FG(Q_ 7RZȚ%QdŌQQ_V݃1 }(t4Aap7uPC'amMq6_$pȻ=5I>US0tOT@v1t9x.~;5kyr>#:PS*vpF =QpC )i;G/g+;O) ȥ9S%*hs7r*5x|,m~'a4V̵#'-XHߚ6+r 'W ,л5vVXؤo]S*b)@k:;V+>\OhHjS:p2[9a|We 'oA<2fp x6L#"jrfkhRjfTj&lŸ'8pn,58SΞnlH)&e?&;/)+OQy۴x/gn1F3 { 0e|,nRrZz?R^ۇsZMZXݬExo\K]+|@OsRUj,GTyΧ/~xM}wzƬγ,ܺHҡL"6 VJ[g9s/$ lVCr,UDQ?1)K}ss_,{(rD*F QpoAMx[c E k͐2v YC|\!:Mqdc sȡ;[1AAKEib4u3&㬁\4^o Vq>[:UɏV,h(W|<,jl@=:g-{0n['Eq/{=f;:zMv{[tߝ[eH?! cee|0Q|7__E:A _'l]Ti p)Q(hXoDbh/Zd+5>gX J*2:ߒө*j!Ԅ)::}S:!h@FC#PZ+-'ٗ-p@عћ' .l/H=tl'*祎>h*%]O1, ʝCZ$ӸP'ErnjÄe dC)( ? ;u;(NLgZڗtaOxdɅIN6 :lE9`)l؍,*k8#cM 1=SmRi^m o:m*vה:5 &aO*b6N$'~UX)Fƕ2|`<zC0=%it$H[}rL97up*^?X 5b8}n3 b=_H%$6ݠ2d~ mapхdkZ LC^{s7P)a}).|&q@xaЄ&/;lw3.N\x+DkTa~=RtDmRY2a).o+RNZѧY Ci :<>NSGIq>˛X-CB|Rv;3h^sgY$B̯͢z֦JhlJtU/R4V_K sc]:#\Plr]!:,Db]Y)./p{ϓ\]m3,;WE!R{[@3WLx'ƞ;=i' pgqugl}1*͠Z?NamP-R[+=oa 5Kdލ%#q=:G;0)&əX#:z"\PaQ=:"a*q\=)}NWrXlVbfLo%SI@) 2C72;/2CMHz@ )t$fDC3&?v`e1c+ʖac[ASC䱮ǭ۰;eb ʑH4) C1 $jeჶY6vy[ZƩMCKc*/> pf1 3Â87*LA3Jyll%> 55 (ƺn09F{[kH7v!] n ƣ_܅{i˔EeR)~%; Sĥ]Ttk:+E9zSo^YfA Akf(Mwm#3t>߳;gm\ :}()kz`RnD1c7sv$?A *ha\٭WfnTXVMУhι'v!\^2S}V^a6j6ŝ;mؤ.n 7`I֨s"'wϫC0_A?YaEpkd]HYeY߲ ]RQ`q풺%o,2&ø?ˢ`1,>7NRB6yubOW"e ab7e(J+ Gc:צp™̂ŏbXz|M.}r紪QuP8s_Uwz _*6/"Z\P gpe:oL%FB`4t \]N"pmrǥt 0?3vh#3 td]ASQ\eD谱?p@a Wv?S*^YD^!hᇎ;D-P)_e=X3bhY5f,fܴ`2m-4*M_ZIR)'rȶl5]f}ԩg %ӳ?qqMgӶRXV(v+[$CJf#@0Qf8CiI@1 $xYLk16^ Fﲊ (,udr*{"-LqC X흡Kpd|zCumg0ƲnmaҌ$iټٷ;CYimXzJEED4a iCDzw'LN_%OStvcq4GP,0snM2HnkUwv/ж5KP! 7^?φ`Gx29+ SBDɟYH@M̧H15F0l Jüv YI0.KQ:-(C@sjVDfe<"!VXa A)(I3T  Jm"Xlfr9߃9!4Q6 Lb)MIK$p\ˍnu}Ϫ<}#$J@ Ӻp3Ań@G[`blfa\{;jZ=_A1?onUaJ;Fo8*M}5q$9Hv5x07Ap۸X2:,=t7MjԪ 8⮁y$tM$${h+*˼,p}vE>EdVs*yzaڊE*MD6Vk0JU #<@i[d*\[ڱ i.b $͹r|MZsn z2UilĪ:Hrs?)/-XxӦ;nagV%M`5y<Rqa4 Wsa\'oj ̶z[YU;$ 3u|>>jno:.J/Q: }r)cX*QEF ϋbNf_9p +ɽCm`6^ڗR^_}rK /v iC""gḵ38Q). G9LJ&_J7{ 6q;D[ !]X*!VAU|z`ڽsӤi~w=MsE3";v|ZVboW ]ܾzƵ-A;Na @pBFE͹(1SGф ()XPTL]HPŗK=mҮsCH:hi%u7nI/ezБLjKZpq=ѽ"ɳ2Rn&:5 KR8²O\%o`a+n| w%n ݙKŗz%).bw |aH9.E0~N(Ebq'>yais UDH-Ug±g Jȧm!)u Bm|>V}R~ڟ$i݇]ngDZg&1@DlN w"ۏwb.";`Tτelfi412X ?6Eo.iWR3K?Ƹƫ%RSNșw ;8z|)LH?JC0Ug3,v>`bw3HX(l<4Mw;gQ6#>oV#sTl BX÷_EN> qWI]g]+H\ Q1[Mӣ[&DpNQ* (kSӏHf`9v6*#qZBrXb55;%MGllUP{B9vlwVXIuhS[+x7Vtawϸ!Xc0XBy B#PzDQ>9U%9: dsɟ.Q/hzs'{yOkUOVn˄Qd[*WQyM'')FBi9%g_èvG_0w)̓J;nNmhXĉ  ^v6H>X ukv(B Yqy=״4Roǜm+<)ITw&7 A"l8}]X 6hn.O,Qt#(O=Sv'XO(VӷM7)ASn}i kdf1w􆦾6Oƨ/e_Lg_#6Ȼ|D٩ؾrJ :x*V]B]≪]% •]2^~te|~&h7:i:;'y%&CSmw`-hň~sz(0y10,yΞѐp@E~a&xF;ѮJ#)La{ 2zimGkWQ?D޳8aMIOIdV_Q;ZWo+0 61ͅ}gVE=b+֛?GΤ7QM(`§ &sn BߕVhUG5KDLJHZ+BCY?15D`oeZΦE0=˧Ӝo nNKyIl$DS?G؞!.ߓЫ'?bIa^ hiq>9zkE.Y zʮ6>uC 2rS4ɉzzYĬm)o9} vp8ߦa؛nqkۓ-Ʌ~J=O^ED-~TeJgDƺ^Ѿ-![QY2^U>/ b1"KHY|_̎YHѩ1KC{&{m7ٖƒ InxT]pZ%_Y+٤cK$YF} \?R2,Zvl, !d6ׇٝ3N!}kjJ"Pm}\=K,M6WȲU\K$ :IUڨY,N0$1*k*z["Lm hs|& s(ގn|藴Zb% o9;q^/YV ,jc$<Ne*OBQ2Bzv,j/}d+b`x{^ 5o.>EeR#OMyVP}o %O٫Xy*C/k}eK/(:'1p4KŚ&t3w/i$ԍmUN0ͯp؊,;/bi31TQ4I51j%s^ӣsl笩j%bQ *{`}*djy8 P"gB0#<ߊuZx[\ZU쓰p}@#^c"3y/T.0NeߍDchdV!2gz #v^1Vk?Jw?`hqZyYdɈE|mԾD\D>B}橲S0΄.W8jQ,TznۣuUܮq!$0NÜg#&2oQ.tT)Ygv, NyMޛqb\HU X`[&GhsuK ?Ng[spJQ 4*s>VsTei[n/{Ed?.v*őH8X߃Gpڰ^'WO,MH{7ys`MRyka-b0QzŠRJQ]naE,:К6X/ڊf1S ɦZx?VaV]?NE^l6 Y#ń8s^,oͭ{V7LTɴxRvf2߅hZ ,>YEgh@ D[ia6b<uxtx#8VЌQCj(=eCO/yل~ :܉ g:XJ;TNvP8H=yY,-%S$A|̡{DÝپu u+|Gw/O@%k]d p A\i ~=&>%R;䥸@N>.YhO#m2!./C@"q$]b=ZGKj̱[84t#zM$JIXLңH琻2Q)IBOsz.h@J'U8=>?pmz{VXd>H16,Q|Ae/\A T;#5ya`=曻 ǒx򘈟ɭ*X}_Z&_c8gyfS4| :hcH%I:8[ *)8~|7rjzlA6ɂmqxݲ;pG|jJxe﫹UQ-r:iK.h4!Db9rNN7T(ǰ6KY1wHوaǧ%}Mc_>-{s9ÑBco֐O惐brc d(yl_j%Db~RkKEkt,ocd{{3vpb5</OfY!3IŐ;',l غ ;S#ө~9vtd)*HVEKB-s5]ho֋V (IO|~J㐥X,ϣ÷=>[hذ09"&Q' 'mĕs*Ю'{+aQ6-c@iv ӎ nt!p*8ajZڐSU3VdSI n/=H9i)!AG\-5*11S!ݚ}qʐt>PgFwjُ7_l^֋}o2 }Z~ypJc|K,sX_ԸWzkI5Qt9 ٹgYLǢ)bxEUӽi3RVf / M$r WE,w]H?=p5Uu< j1 ]9ٔo dFd{b斈n$v:% Dʩ3)S|y&sݬz̿mbSv{T4dL|{ _irK C|yz9y7O3q-0QA!'1!&>␍}qREJ;Q9K'}WIn0 zޚ9vZI1fk 0&_:r.OISw@޿F W!C5/?Kfm NloGCq zJuF.'"#A֋5 .a>ӎEP)'+w9 }Rs*l59׫Ɔ?&L:O3!!ïcMB@w_2M\\)ԝ 8Op§n9dV41JSqsXk.|Niˠ'w5#`{5.UX^G q)XhJ)gЍJU6iiGܘE<̋( ծg΋&(>[W?L#gnLy?=lm JmHV8 ^Q0&w  "ReX?U\h"^u2j$߃iL5U{IJ(LEZR/䋰KHuټD9׈ Q2ۈ|B/Y؍Rn-vN2Ss$73mɧz86$ a^+Rxt$]ɑ9wM[g}-VBiGۡ^>S[`SϾItlc',W51OԶk&+C$uA6MTN VR#Й ,xRu/o3(fvc qDeTSxhن&0?O4Z~B31b) $ij- |Qy!B6U| R/06CE5QR+NGKeeFy )jŪIZZKYU!/HA$qBLol _1]>d1r8j8YPH AM|]'t`=r]ֹ{Ɋ$X t='`yMgo8ɏUR#Ң2?i#dQR#Ra$YNQjƩ 4|pn2$`([Mt&{>h>1GSQ# BJ橛]۸J6+Dk{zi ꚲU((zQ_L:Z[1gR,'xӣ@UDTmVsD86Z4Fy% td"pZ+=L: ٗ$W.˘ďht/˨z~`;`IQYUu?/ #3Wvh 窕/$W328WD`Rmm|Hk=aOvB~h ۹da |*iǮSNFvEg(-ӯO|gIi=;} صu6a|[&Xnu\gğ[mium(Eb"ZL/V%=BG^2qB#.{X}K:vO3U$Z}F[[Ͽ [@B`'IffZv Ǫ}j֨ (.I-r#&tL&sNcudZT͔jtl&E؍&Ʊ0SG|$ĨeFpH_e??ٹ<%MViferJ46R 9*\A VwvX3o,.;F0U$N!dK#kt"qra˖5 Xc(<>5|WBXt$ boIH% ٚ5Ei5Y=EF9iWsʥMۂ )FtnU4JZq21/l\8ƈ_*,UGR1~j3n2o:g#ɩ.`!rޗ|" \V *k7t`aY?w#_c{aƆ7^=Rs2q]"?>V';dC*DׂxN22ϡ K^auqovcLpQ{$R=mwI.TA qYS8>FG pAN}t,\vgNWDhc5/{bo KtVIKA8U6VW0]o^A(4`X4k{e-%*8Q3_ )4M,{E_,)}ArYI)z M*"kXPy\XFlD QT@S*?ku4ȘWS)%ԅ'/}H7YR"ua_TOQ~s$^ln48=t+3"ī;@#>"ť3t:cL3(կQ%2ܢ>eš D[$R }.NB pEfw XOčJLM7M{;B318:=;EVdSkT+ٙ Hɔ~zQM0 >ehrn2־;qdY]#\) 4KJ_2rCBPIAJE UdE0[=L[6&tI5=E%2r{FUޫ0 t+&iG )ORüXfkB}[삺;bQ,|Xj8m߄3}p^ 7'^)mЫӲ'6{/ 4GN=Zp*=Mwe {\KO'AYgw: ?w+|qxɷ;U CEM4$_ {}אZ6b#\*#*5_cIW9bg d|lv MsJq7G3)q_(bH 0a:0i%Y&N$]u_~ҵ5>[+W;^R>W~Øj׵ i ^('p])rAP_w)qy *€p'T՞J8t< (h8-.$KD) ]hr$w_rrg?|h0 "BE"[Oubw+c %,a 1lGg4)w3Q<@=r*RJ$jyjV'עO;;Hafb%!I+3b/B-_#pֹy]f n {IJbD z;4֠75zMy&ޕSWKr c6IezΓv{˓:ʑ eO_qGDX}a[ E7Q|O{,U{SEvF*“ q^u dUza~x4$ه ]6oO/tL-6@r9'p=Y R8e vԓbp"{EdEm)pk(䶸>ug$}k`S*8@bUGٱd/ . \>4[Eҿi:~d?iΜu/??BvEXBL^}[LZODe*߶#hjv>\W+$ j?HcmۖQTɁյGI՘h nU6N%<(P]P{! TL`msݝ:X^t6ߓUrʄO T fO* {.xb(/ό{˘T*ՀW'NҜGʬMri*x|-GSc"W}0uA.%*Rb*-IRUG$ܼ>V 3i0zF/OGH-_h>/&Y0m]5y0)4*na?'hPPQ-& 08&@RQ}q}chI՘% 5#(UPj lA>`{%=U_ꁈ7&ӟ)Xu>mquWegٰ/˟,JR9ƟMĀ B^rJX:}kʩ׫`og_V`DY̽yZE$)aRi&6Yօ ;L.j.f<9|B:-O┿Uw!PCiъG0dz"/T(Әhk(\@Jӝ`ou o[&q*}5I6{Ԓ5 Ƕ7JpGCl/ܼdz,b=_+c<>̞={6Jbs*0s}DV}]gJ.1p'|\?$?>Ǵm._{eR5w3l8(%J.*52J__,u8MٔK֡PoYSȯuL|zjB`PL;Ա)o^ZsAs#ة{cqZk_Տ:6nA3eNN')<ɒ&#nXİv$ڭ1*dߣzЗ[FLD ԟlj̈#GJ<@Rz^8bmt-/D!5WjmTu:X4JZ {\lmWZoj4 IŢQAbA8io?Z\/La>$F2<A%8hB҆m@grwj%@\q$8:3(iSu~3Aa;S?8W%\310@\U4n.-[L!Ը>3yGOԷeCvacHp֦];?mg8404G~/kZ*C oCfq|^{L#q0CI"o^5 ֟8$3t6whAl/G*(M] 쮙;i1/9 la*y -썟im^¾p&YqAAGG݆Ŗwz( 4~5LFStH;۞#Di$YJgJՒu-9slM/$<9hKrq}>O{30t9Zu]\y^V~M=~r ::\T2Z¸JKZB8y6.U 3tvXn#@3{Q} Z`b.#: Tc1D7?WE؞<,o.x US("G.j JZȋJbPEƲ1k-'!l@ ѽ3*fF+OMmwbLNc4.&{$iyFbj#˹kٗj)p2l_vCZ&׏V]+)e?*֫Cw0Ok7d|x_RZhhcR2 VIz#dHj;zq"h@~uգWa`I N~0r,Ð`1p]}P@d%e•\ETzXّdN&WYl}]Ƃm7\tXZ&m>Mu"}ɓAs"h)ʿyAO`uVO S!9 I >y&l kҠuʉ;[ ߀3ijW:υզM Dej4կɽ#T^Zl1IUOCjNF"0ލ_2rr&n?0m$;ӎ맫>_&飈7YO5uFP&Z64Vf};hjr QQWJBey/և&BY8̠ wS Vb ~-_{ :z`9S K?ׄt yGR `SϦoVIDMTIև-=Q PADUhNְV:WDpyMaV$ ~+]JOA~uJ>8(`DxzA i*Dy߉wuO=z*iNvrz i䥡ҶlU_zƋ]MlXÿjWԔɟK/TP=QZËژJFyr= >>`"t`Z]{< |>Wf.(jzҤu$8_"4[5pM1}B.N']JYC*d[.dStŋ>2_?W$%({kD50c]^#L5P+@nObYn8axdgg p@BͰ^:bVdL#xEiY!RKQ&tBjGB vL=DTO-r~A< sf߅XZ⹼f$}Efbwpp2=؀g$j1]azy l+a! \ 2/j LrY/,%y^}wTzpCV.*Ћt)ʬn_s&)'lK|ϧ8갵VX ݛW÷ߑ׈41 P5B[1!:޳6¹NxMW/ wh<Cٳf)KO-Rr fWkn%pp˜hmnK O5**0;^nw@Ɵ2ZՔJX CM4e "dcXT׳f|z2G g,P`l{+*$9E,bbG35@1kw ܚc5hbDpwj_]GZYLxCi c&> P1˞n12jOӸE'{&џeH(DƯG_Dl);e]WG7idO߼6m~J^<+ ^nꗯXMbjG\.5%,%`Ј#ڲX=;O.}xa 2wXtE/™6[*]?uk?g0= tς v^H$RCᜯξ\CMm4-s' @s;*JH&LX$f"(^d8_E)}^0} :ko|+{D !YhbqoPpL~d;ysu0HZ4g\A :A_x4O"W<_| ь+\lZiSWUPA~B7kM̌KAAD_*;^QY/BOѝEǣֱ8݉շR}4!S7x,JjnNh53k'Y]8ȺSΈC>sŞE~Έ? I5+Qf7zvLtװX uh` l@(QiGZ.fKR^5\Up4 ;֛fB#.#SA$FsdyQJbJmGՉXeQu5Z'DfC۪{LMTPb`Ж|IbY|J Z (xn{f`2g rPZ"$pQ9-Ԧ|*:9abl閛ҢM[$$\Gugz? ?,\h![ Gus`ϴh,hL"^JNJ6BU_Q5u} ^$t5j~t'Wh|oV+^ v8<<tҺO!:(f P@?8IvM# kEAa(WCq4A(zi”ab ^ȧENiFO~wݚN"6S#?U z mAeҘ5T?O0ӷaF@~p=#7ST(iPg/p*8R-ބЯzet:Zzi"; 1`9< vXxHzx|?Z!H릦 (M blAO`cÁ]uv&9oKwImQ 7oa?3cne5侨p~q:truw˅b4irpo}CBe(kEն웄m)|Ky\4tJ%BGj8׋kfx yC NW0S"Wb-i_EE^·h좭0G?#& M5 .؝jd9)ч&ɚ&O b 'ZY|O@f4:l%Vl!B6fgc((%vzVtt PDEHx j#x9)NC>-5d(\XP/6kgFT|hCҮn]/jZBdHy5ĝV<Ȣccw KGxCagFv΀̺WBr޺ac}O>YZc(q /KfDMWRs*myb[ X:&^FnGwJQ&HFT|(N 8#BiVucos2FS1+Ĩf 星L\&Ze/59j=Pj^{Kֈg;rV↔?`bTvknA\b@jw例x$12 t+IUuVgGٯ[b~=gO'?ҥA24GSaZM\c>a˫mI՟mD~CF3P!g&=Şg uy:4snL:زrH#qcc *eHgARGkȃN~wѼl3^!:,9 kG+7kD%4vuu7L|W<*N%1_{{"-1֚-hdgUQ`4"Y|Nv|Zl[h-ӣT‹Cf- ogJUAtpkՃٻjɟ[T'Nу_By^6D7:&O˧Ŵ^hLhglEi+8`EuBhP$XhxBo_VV(8?~ovfRwD)${Y+㤊ޙ)SBvÿ ?~&H@ *'z̖`l@cU :cЦeE/,2i=xIH=gV=E.r2&G̵5Dv:?]oC{>8zYcDOdOk:8%$JN8F-R}.Q5gpdy"LDIq׾Exy_=H3RdJs`>gtehS7?P(W6iyJBd_hdthU񤀂^q ض8.P" I /$p/_R ǫ%` AlGjϧ弋: ;AB{TƘr;kT^yZ?X음]=rNMC%)KTowv;f?=Ee#pObN='(P p/ YARvs,-gc6{7BqzZl1vI0^6+Wqn1«FLI\C5>_T*ǞYQM6ve$l',)U+ uM֧+e h44Y=>w[ذhč853«miv(는;Hd ٥ @4_uA*-%ӤX#VO&K4$VSUea S8cJiS|Ҫȏ-~7N}efs%Jϭ\$mkj&O <:4Vw볩 mD:P3-MIuws|kkrw/)@' Tةh=HdrX$z y2'KkyI,/#EΚ̻ހ6' ɒ%ū `PG}o޾)o2B {G}QP*Eת.ߑt T+[tf.7sİγS h a3nA)fTJxo?B0AfṮ~f``.ó/SByDG0c&/(0/x\-n/ ,*FlšnO|7ɚtJpMį.S+!hPYCZ\wBMf":H?АGMNv6+O'xQQA 'yP1KN? IT׏ճX4*_Q g~sҗF1qS#&Ty erΊ#kt) ^:AѸ3xRTNz^Z?;4Fwyqǡύ) /L+4;!a}xkU"$ɱ> _Bd/,ns1_#F]$CҴ,0M'?hg-lbOٷzA A7ǔ"eW7MQ?ud ~hj:am:>dLzG x^^pJTܔP3$7j1mt=jkě/U'kQ"9ߒl9DuXY곉~6muEy:Z$ g~(ٻTaJPYg,gLkǑx#=DU+S\ӃCF(A⾅v99kZJ̏N`[IX;T)ӎ uP?+*Y2.`j:x7@/1B >NJ'ԇ~ۢn M؄_NUdkT}VX/7#p{{5hY H^7 /wE]`, Ua<05;LKD=)l\8 QEXث=Di`i3.ؑA+!՟˶$ Un&&=Z+ؔi)9 ď".nMy!, !P/PlٔtThP͜ICmGi{ '*F5&_XMա G:u7`%:Zt<2ZWHkﰒ_sdުN?MNHxݠN2کѬC1ry>l g~!$hpS~vIAKiBZ.~,f.8/@Dz.QrDp㎺[A}Dr |q$؂_{d"U,_Cgï9#3 i?6``TeX73h˯G.;,ls:K~!Q Czw'{d vp=V7Y)n me=_7i}S$)J\Ҕt^'-DQVޘJ:x t7\Mun ׹/G/lBb7l};}zYج$g=9]=1l@w?΀8=vU{īV%[ UVЄBrZ Gg&w:ݻtczURH᝽ŸIq>2\TBs)3Sv`4;Px0.z 79\ '/7i&UF!`OݹJ\׬S|N{zɐӼo' :X;tNn9]ux˞qŷ~O^pȋ&<ʼ7˜m uy5Ar}tB}x ~`;7k!h`î6-I$j5< ECP&K.c%uꨁlGixAwsSomJym@P=ɢ5G\ KUYu6Jd>oFuǍD (I!`;8`Y Rc2wѵ|OONtLF:&֯iy:nCY&[AEDe ^OǴVz.._ GS\Jx(GH}&v{잦قa6m֍׮pƦ-Jh*:(%i"AЧрZ"]O@"yӲ!M^A^BeOVoCH(8K֦<ܟrO]Ġ _G$ GT>ObHZt|υ;h@}L"h]4˜XW?rG^w^~(1u5|ξ )˾B iIJesآh>3[~)yG@n7?lP^;̇ʈY~]Ni:lC`Am,f0Ƨv~LOC}!F@yuq9}nH/z[^y!eH"e ZFVf&igݪi[foƍUFHԆYi3_ u6I!mPU+OڣA[$#S0߉r`3x^a?GڨFNf:VrE3`Sϊ 2+am0c`IM+{zt"rjOrFPR)W} 7DWuR55il(:N(n6 C&~;r,*BaҨ8*| QydI9EE}l,K<d2OKT6Yk3&΢ 7mcǴ6f' oCF{D靝*eQd'epeԺlœU39IfzbѨiԡ+Idɂ( nhCCT _p c2z(k-I̺NjQjzڅ(-.nuLb1ĈqF!^Nх_ns6+߯~]ԾysƗ3H\qmyMfm6O+3crҌx  'HxY<[poɦW_lW戶L1qڞȖG\N~:K&sU/$bT/ݱv-Z>pPmZrJHtMJRvuTy%mRK7R6|h('nU9MBuS` k; xac+*:]JdQG^ɭ բ9/JD/!]/3slixmpp-slix-1.4.2/docs/sasl.rst000066400000000000000000000000741342457644200166270ustar00rootroot00000000000000How SASL Authentication Works ============================= slixmpp-slix-1.4.2/docs/using_asyncio.rst000066400000000000000000000105331342457644200205400ustar00rootroot00000000000000.. _using_asyncio: ============= Using asyncio ============= Block on IQ sending ~~~~~~~~~~~~~~~~~~~ :meth:`.Iq.send` now returns a :class:`~.Future` so you can easily block with: .. code-block:: python result = yield from iq.send() .. warning:: If the reply is an IQ with an ``error`` type, this will raise an :class:`.IqError`, and if it timeouts, it will raise an :class:`.IqTimeout`. Don't forget to catch it. You can still use callbacks instead. XEP plugin integration ~~~~~~~~~~~~~~~~~~~~~~ The same changes from the SleekXMPP API apply, so you can do: .. code-block:: python iq_info = yield from self.xmpp['xep_0030'].get_info(jid) But the following will only return a Future: .. code-block:: python iq_info = self.xmpp['xep_0030'].get_info(jid) Callbacks, Event Handlers, and Stream Handlers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ IQ callbacks and :term:`Event Handlers ` can be coroutine functions; in this case, they will be scheduled in the event loop using :meth:`.asyncio.async` and not ran immediately. A :class:`.CoroutineCallback` class has been added as well for :term:`Stream Handlers `, which will use :meth:`.asyncio.async` to schedule the callback. Running the event loop ~~~~~~~~~~~~~~~~~~~~~~ :meth:`.XMLStream.process` is only a thin wrapper on top of ``loop.run_forever()`` (if ``timeout`` is provided then it will only run for this amount of time, and if ``forever`` is False it will run until disconnection). Therefore you can handle the event loop in any way you like instead of using ``process()``. Examples ~~~~~~~~ Blocking until the session is established ----------------------------------------- This code blocks until the XMPP session is fully established, which can be useful to make sure external events aren’t triggering XMPP callbacks while everything is not ready. .. code-block:: python import asyncio, slixmpp client = slixmpp.ClientXMPP('jid@example', 'password') client.connected_event = asyncio.Event() callback = lambda _: client.connected_event.set() client.add_event_handler('session_start', callback) client.connect() loop.run_until_complete(event.wait()) # do some other stuff before running the event loop, e.g. # loop.run_until_complete(httpserver.init()) client.process() Use with other asyncio-based libraries -------------------------------------- This code interfaces with aiohttp to retrieve two pages asynchronously when the session is established, and then send the HTML content inside a simple . .. code-block:: python import asyncio, aiohttp, slixmpp @asyncio.coroutine def get_pythonorg(event): req = yield from aiohttp.request('get', 'http://www.python.org') text = yield from req.text client.send_message(mto='jid2@example', mbody=text) @asyncio.coroutine def get_asyncioorg(event): req = yield from aiohttp.request('get', 'http://www.asyncio.org') text = yield from req.text client.send_message(mto='jid3@example', mbody=text) client = slixmpp.ClientXMPP('jid@example', 'password') client.add_event_handler('session_start', get_pythonorg) client.add_event_handler('session_start', get_asyncioorg) client.connect() client.process() Blocking Iq ----------- This client checks (via XEP-0092) the software used by every entity it receives a message from. After this, it sends a message to a specific JID indicating its findings. .. code-block:: python import asyncio, slixmpp class ExampleClient(slixmpp.ClientXMPP): def __init__(self, *args, **kwargs): slixmpp.ClientXMPP.__init__(self, *args, **kwargs) self.register_plugin('xep_0092') self.add_event_handler('message', self.on_message) @asyncio.coroutine def on_message(self, event): # You should probably handle IqError and IqTimeout exceptions here # but this is an example. version = yield from self['xep_0092'].get_version(message['from']) text = "%s sent me a message, he runs %s" % (message['from'], version['software_version']['name']) self.send_message(mto='master@example.tld', mbody=text) client = ExampleClient('jid@example', 'password') client.connect() client.process() slixmpp-slix-1.4.2/docs/xeps.rst000066400000000000000000000035711342457644200166510ustar00rootroot00000000000000Supported XEPS ============== ======= ============================= ================ XEP Description Notes ======= ============================= ================ `0004`_ Data forms `0009`_ Jabber RPC `0012`_ Last Activity `0030`_ Service Discovery `0033`_ Extended Stanza Addressing `0045`_ Multi-User Chat (MUC) Client-side only `0050`_ Ad-hoc Commands `0059`_ Result Set Management `0060`_ Publish/Subscribe (PubSub) Client-side only `0066`_ Out-of-band Data `0078`_ Non-SASL Authentication `0082`_ XMPP Date and Time Profiles `0085`_ Chat-State Notifications `0086`_ Error Condition Mappings `0092`_ Software Version `0128`_ Service Discovery Extensions `0202`_ Entity Time `0203`_ Delayed Delivery `0224`_ Attention `0249`_ Direct MUC Invitations ======= ============================= ================ .. _0004: http://xmpp.org/extensions/xep-0004.html .. _0009: http://xmpp.org/extensions/xep-0009.html .. _0012: http://xmpp.org/extensions/xep-0012.html .. _0030: http://xmpp.org/extensions/xep-0030.html .. _0033: http://xmpp.org/extensions/xep-0033.html .. _0045: http://xmpp.org/extensions/xep-0045.html .. _0050: http://xmpp.org/extensions/xep-0050.html .. _0059: http://xmpp.org/extensions/xep-0059.html .. _0060: http://xmpp.org/extensions/xep-0060.html .. _0066: http://xmpp.org/extensions/xep-0066.html .. _0078: http://xmpp.org/extensions/xep-0078.html .. _0082: http://xmpp.org/extensions/xep-0082.html .. _0085: http://xmpp.org/extensions/xep-0085.html .. _0086: http://xmpp.org/extensions/xep-0086.html .. _0092: http://xmpp.org/extensions/xep-0092.html .. _0128: http://xmpp.org/extensions/xep-0128.html .. _0199: http://xmpp.org/extensions/xep-0199.html .. _0202: http://xmpp.org/extensions/xep-0202.html .. _0203: http://xmpp.org/extensions/xep-0203.html .. _0224: http://xmpp.org/extensions/xep-0224.html .. _0249: http://xmpp.org/extensions/xep-0249.html slixmpp-slix-1.4.2/docs/xmpp_tdg.rst000066400000000000000000000247451342457644200175220ustar00rootroot00000000000000Following *XMPP: The Definitive Guide* ====================================== Slixmpp was featured in the first edition of the O'Reilly book `XMPP: The Definitive Guide `_ 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 Slixmpp release, is available at http://github.com/legastero/xmpp-tdg. However, since publication, Slixmpp 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 ` 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 Slixmpp. 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.send_message(message["from"], message["body"]) `View full source `_ | `View original code `_ 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 Slixmpp 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 ``

`` tag. Updated Code ~~~~~~~~~~~~ .. code-block:: python def handleIncomingXMPPEvent(self, event): msgLocations = {slixmpp.stanza.presence.Presence: "status", slixmpp.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 = "

%(user)s: %(message)s

" % { "uri": self.url + "/" + message.user, "user" : message.user, "message" : message.text } for subscriberJID in self.backend.getSubscriberJIDs(message.user) : self.xmpp.send_message(subscriberJID, body, mhtml=htmlBody) `View full source `_ | `View original code `_ 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; Slixmpp 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 `_ | `View original code `_ 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 ``make_presence`` 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.send_message``. Updated Code ~~~~~~~~~~~~ .. code-block:: python def handleXMPPPresenceProbe(self, event) : self.xmpp.send_presence(pto = event["from"]) def handleXMPPPresenceSubscription(self, subscription) : if subscription["type"] == "subscribe" : userJID = subscription["from"].jid self.xmpp.send_presence_subscription(pto=userJID, ptype="subscribed") self.xmpp.send_presence(pto = userJID) self.xmpp.send_presence_subscription(pto=userJID, ptype="subscribe") def handleMessageAddedToBackend(self, message) : body = message.user + ": " + message.text for subscriberJID in self.backend.getSubscriberJIDs(message.user) : self.xmpp.send_message(subscriberJID, body, mfrom=self.xmpp.jid) `View full source `_ | `View original code `_ 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 Slixmpp plugin, see the tutorial :ref:`tutorial-create-plugin`. `View full source `_ | `View original code `_ 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 Slixmpp. 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.send_presence_subscription( pfrom=contactJID, pto=userJID, ptype="subscribed", pnick=user) self.sendPresenceOfContactToUser(contactJID=contactJID, userJID=userJID) if contactJID == self.componentDomain : self.sendAllContactSubscriptionRequestsToUser(userJID) `View full source `_ | `View original code `_ slixmpp-slix-1.4.2/examples/000077500000000000000000000000001342457644200160205ustar00rootroot00000000000000slixmpp-slix-1.4.2/examples/IoT_TestDevice.py000077500000000000000000000145461342457644200212210ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Implementation of xeps for Internet of Things http://wiki.xmpp.org/web/Tech_pages/IoT_systems Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from os.path import basename, join as pjoin from argparse import ArgumentParser from urllib import urlopen from getpass import getpass import slixmpp from slixmpp.plugins.xep_0323.device import Device #from slixmpp.exceptions import IqError, IqTimeout class IoT_TestDevice(slixmpp.ClientXMPP): """ A simple IoT device that can act as server or client """ def __init__(self, jid, password): slixmpp.ClientXMPP.__init__(self, jid, password) self.add_event_handler("session_start", self.session_start) self.add_event_handler("message", self.message) self.device=None self.releaseMe=False self.beServer=True self.clientJID=None def datacallback(self,from_jid,result,nodeId=None,timestamp=None,fields=None,error_msg=None): """ This method will be called when you ask another IoT device for data with the xep_0323 se script below for the registration of the callback """ logging.debug("we got data %s from %s",str(result),from_jid) def beClientOrServer(self,server=True,clientJID=None ): if server: self.beServer=True self.clientJID=None else: self.beServer=False self.clientJID=clientJID def testForRelease(self): # todo thread safe return self.releaseMe def doReleaseMe(self): # todo thread safe self.releaseMe=True def addDevice(self, device): self.device=device def session_start(self, event): self.send_presence() self.get_roster() # tell your preffered friend that you are alive self.send_message(mto='jocke@jabber.sust.se', mbody=self.boundjid.bare +' is now online use xep_323 stanza to talk to me') if not(self.beServer): session=self['xep_0323'].request_data(self.boundjid.full,self.clientJID,self.datacallback) def message(self, msg): if msg['type'] in ('chat', 'normal'): logging.debug("got normal chat message" + str(msg)) ip=urlopen('http://icanhazip.com').read() msg.reply("Hi I am " + self.boundjid.full + " and I am on IP " + ip).send() else: logging.debug("got unknown message type %s", str(msg['type'])) class TheDevice(Device): """ This is the actual device object that you will use to get information from your real hardware You will be called in the refresh method when someone is requesting information from you """ def __init__(self,nodeId): Device.__init__(self,nodeId) self.counter=0 def refresh(self,fields): """ the implementation of the refresh method """ self._set_momentary_timestamp(self._get_timestamp()) self.counter+=self.counter self._add_field_momentary_data(self, "Temperature", self.counter) if __name__ == '__main__': # Setup the command line arguments. # # This script can act both as # "server" an IoT device that can provide sensorinformation # python IoT_TestDevice.py -j "serverjid@yourdomain.com" -p "password" -n "TestIoT" --debug # # "client" an IoT device or other party that would like to get data from another device parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) parser.add_argument("-t", "--pingto", help="set jid to ping", action="store", type="string", dest="pingjid", default=None) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") # IoT test parser.add_argument("-c", "--sensorjid", dest="sensorjid", help="Another device to call for data on", default=None) parser.add_argument("-n", "--nodeid", dest="nodeid", help="I am a device get ready to be called", default=None) args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") xmpp = IoT_TestDevice(args.jid,args.password) xmpp.register_plugin('xep_0030') #xmpp['xep_0030'].add_feature(feature='urn:xmpp:iot:sensordata', # node=None, # jid=None) xmpp.register_plugin('xep_0323') xmpp.register_plugin('xep_0325') if args.nodeid: # xmpp['xep_0030'].add_feature(feature='urn:xmpp:sn', # node=args.nodeid, # jid=xmpp.boundjid.full) myDevice = TheDevice(args.nodeid); # myDevice._add_field(name="Relay", typename="numeric", unit="Bool"); myDevice._add_field(name="Temperature", typename="numeric", unit="C") myDevice._set_momentary_timestamp("2013-03-07T16:24:30") myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"}) xmpp['xep_0323'].register_node(nodeId=args.nodeid, device=myDevice, commTimeout=10); xmpp.beClientOrServer(server=True) while not(xmpp.testForRelease()): xmpp.connect() xmpp.process(block=True) logging.debug("lost connection") if args.sensorjid: logging.debug("will try to call another device for data") xmpp.beClientOrServer(server=False,clientJID=args.sensorjid) xmpp.connect() xmpp.process(block=True) logging.debug("ready ending") else: print("noopp didn't happen") slixmpp-slix-1.4.2/examples/adhoc_provider.py000077500000000000000000000142441342457644200213720ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp class CommandBot(slixmpp.ClientXMPP): """ A simple Slixmpp bot that provides a basic adhoc command. """ def __init__(self, jid, password): slixmpp.ClientXMPP.__init__(self, jid, password) # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to # listen for this event so that we we can initialize # our roster. self.add_event_handler("session_start", self.start) def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.send_presence() self.get_roster() # We add the command after session_start has fired # to ensure that the correct full JID is used. # If using a component, may also pass jid keyword parameter. self['xep_0050'].add_command(node='greeting', name='Greeting', handler=self._handle_command) def _handle_command(self, iq, session): """ Respond to the initial request for a command. Arguments: iq -- The iq stanza containing the command request. session -- A dictionary of data relevant to the command session. Additional, custom data may be saved here to persist across handler callbacks. """ form = self['xep_0004'].make_form('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. parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") # Setup the CommandBot and register plugins. Note that while plugins may # have interdependencies, the order in which you register them does # not matter. xmpp = CommandBot(args.jid, args.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}) # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/adhoc_user.py000077500000000000000000000137721342457644200205230ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp class CommandUserBot(slixmpp.ClientXMPP): """ A simple Slixmpp bot that uses the adhoc command provided by the adhoc_provider.py example. """ def __init__(self, jid, password, other, greeting): slixmpp.ClientXMPP.__init__(self, jid, password) self.command_provider = other self.greeting = greeting # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to # listen for this event so that we we can initialize # our roster. self.add_event_handler("session_start", self.start) self.add_event_handler("message", self.message) def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.send_presence() self.get_roster() # We first create a session dictionary containing: # 'next' -- the handler to execute on a successful response # 'error' -- the handler to execute if an error occurs # The session may also contain custom data. session = {'greeting': self.greeting, 'next': self._command_start, 'error': self._command_error} self['xep_0050'].start_command(jid=self.command_provider, node='greeting', session=session) def message(self, msg): """ Process incoming message stanzas. Arguments: msg -- The received message stanza. """ logging.info(msg['body']) def _command_start(self, iq, session): """ Process the initial command result. Arguments: iq -- The iq stanza containing the command result. session -- A dictionary of data relevant to the command session. Additional, custom data may be saved here to persist across handler callbacks. """ # The greeting command provides a form with a single field: # # # form = self['xep_0004'].make_form(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. parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") parser.add_argument("-o", "--other", dest="other", help="JID providing commands") parser.add_argument("-g", "--greeting", dest="greeting", help="Greeting") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") if args.other is None: args.other = input("JID Providing Commands: ") if args.greeting is None: args.greeting = 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(args.jid, args.password, args.other, args.greeting) xmpp.register_plugin('xep_0030') # Service Discovery xmpp.register_plugin('xep_0004') # Data Forms xmpp.register_plugin('xep_0050') # Adhoc Commands # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/admin_commands.py000077500000000000000000000114121342457644200213450ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp class AdminCommands(slixmpp.ClientXMPP): """ A simple Slixmpp bot that uses admin commands to add a new user to a server. """ def __init__(self, jid, password, command): slixmpp.ClientXMPP.__init__(self, jid, password) self.command = command self.add_event_handler("session_start", self.start) def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.send_presence() self.get_roster() def command_success(iq, session): print('Command completed') if iq['command']['form']: for var, field in iq['command']['form']['fields'].items(): print('%s: %s' % (var, field['value'])) if iq['command']['notes']: print('Command Notes:') for note in iq['command']['notes']: print('%s: %s' % note) self.disconnect() def command_error(iq, session): print('Error completing command') print('%s: %s' % (iq['error']['condition'], iq['error']['text'])) self['xep_0050'].terminate_command(session) self.disconnect() def process_form(iq, session): form = iq['command']['form'] answers = {} for var, field in form['fields'].items(): if var != 'FORM_TYPE': if field['type'] == 'boolean': answers[var] = input('%s (y/n): ' % field['label']) if answers[var].lower() in ('1', 'true', 'y', 'yes'): answers[var] = '1' else: answers[var] = '0' else: answers[var] = input('%s: ' % field['label']) else: answers['FORM_TYPE'] = field['value'] form['type'] = 'submit' form['values'] = answers session['next'] = command_success session['payload'] = form self['xep_0050'].complete_command(session) session = {'next': process_form, 'error': command_error} command = self.command.replace('-', '_') handler = getattr(self['xep_0133'], command, None) if handler: handler(session={ 'next': process_form, 'error': command_error }) else: print('Invalid command name: %s' % self.command) self.disconnect() if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") parser.add_argument("-c", "--command", dest="command", help="admin command to use") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") if args.command is None: args.command = input("Admin command: ") # Setup the CommandBot and register plugins. Note that while plugins may # have interdependencies, the order in which you register them does # not matter. xmpp = AdminCommands(args.jid, args.password, args.command) xmpp.register_plugin('xep_0133') # Service Administration # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/confirm_answer.py000077500000000000000000000057611342457644200214220ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2015 Emmanuel Gil Peyrot This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp from slixmpp.exceptions import XMPPError log = logging.getLogger(__name__) class AnswerConfirm(slixmpp.ClientXMPP): """ A basic client demonstrating how to confirm or deny an HTTP request. """ def __init__(self, jid, password, trusted): slixmpp.ClientXMPP.__init__(self, jid, password) self.add_event_handler("http_confirm", self.confirm) self.add_event_handler("session_start", self.start) def start(self, *args): self.make_presence().send() def prompt(self, stanza): confirm = stanza['confirm'] print('Received confirm request %s from %s to access %s using ' 'method %s' % ( confirm['id'], stanza['from'], confirm['url'], confirm['method']) ) result = input("Do you accept (y/N)? ") return 'y' == result.lower() def confirm(self, stanza): if self.prompt(stanza): reply = stanza.reply() else: reply = stanza.reply() reply.enable('error') reply['error']['type'] = 'auth' reply['error']['code'] = '401' reply['error']['condition'] = 'not-authorized' reply.append(stanza['confirm']) reply.send() if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() parser.add_argument("-q","--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d","--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") # Other options. parser.add_argument("-t", "--trusted", nargs='*', help="List of trusted JIDs") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") xmpp = AnswerConfirm(args.jid, args.password, args.trusted) xmpp.register_plugin('xep_0070') # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/confirm_ask.py000077500000000000000000000101711342457644200206700ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2015 Emmanuel Gil Peyrot This file is part of Slixmpp. See the file LICENSE for copying permission. """ import sys import logging from getpass import getpass from argparse import ArgumentParser import slixmpp from slixmpp.exceptions import XMPPError, IqError from slixmpp import asyncio log = logging.getLogger(__name__) class AskConfirm(slixmpp.ClientXMPP): """ A basic client asking an entity if they confirm the access to an HTTP URL. """ def __init__(self, jid, password, recipient, id, url, method): slixmpp.ClientXMPP.__init__(self, jid, password) self.recipient = recipient self.id = id self.url = url self.method = method # Will be used to set the proper exit code. self.confirmed = asyncio.Future() self.add_event_handler("session_start", self.start) self.add_event_handler("message", self.start) self.add_event_handler("http_confirm_message", self.confirm) def confirm(self, message): print(message) if message['confirm']['id'] == self.id: if message['type'] == 'error': self.confirmed.set_result(False) else: self.confirmed.set_result(True) async def start(self, event): log.info('Sending confirm request %s to %s who wants to access %s using ' 'method %s...' % (self.id, self.recipient, self.url, self.method)) try: confirmed = await self['xep_0070'].ask_confirm(self.recipient, id=self.id, url=self.url, method=self.method, message='Plz say yes or no for {method} {url} ({id}).') if isinstance(confirmed, slixmpp.Message): confirmed = await self.confirmed else: confirmed = True except IqError: confirmed = False if confirmed: print('Confirmed') else: print('Denied') self.disconnect() if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() parser.add_argument("-q","--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d","--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") # Other options. parser.add_argument("-r", "--recipient", required=True, help="Recipient JID") parser.add_argument("-i", "--id", required=True, help="id TODO") parser.add_argument("-u", "--url", required=True, help="URL the user tried to access") parser.add_argument("-m", "--method", required=True, help="HTTP method used") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") xmpp = AskConfirm(args.jid, args.password, args.recipient, args.id, args.url, args.method) xmpp.register_plugin('xep_0070') # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process(forever=False) sys.exit(0 if xmpp.confirmed else 1) slixmpp-slix-1.4.2/examples/custom_stanzas/000077500000000000000000000000001342457644200210755ustar00rootroot00000000000000slixmpp-slix-1.4.2/examples/custom_stanzas/custom_stanza_provider.py000077500000000000000000000106341342457644200262620ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp from slixmpp import ClientXMPP, Iq from slixmpp.exceptions import IqError, IqTimeout, XMPPError from slixmpp.xmlstream import register_stanza_plugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from stanza import Action class ActionBot(slixmpp.ClientXMPP): """ A simple Slixmpp bot that receives a custom stanza from another client. """ def __init__(self, jid, password): slixmpp.ClientXMPP.__init__(self, jid, password) # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to # listen for this event so that we we can initialize # our roster. self.add_event_handler("session_start", self.start) self.register_handler( Callback('Some custom iq', StanzaPath('iq@type=set/action'), self._handle_action)) self.add_event_handler('custom_action', self._handle_action_event) register_stanza_plugin(Iq, Action) async def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.send_presence() self.get_roster() def _handle_action(self, iq): """ Raise an event for the stanza so that it can be processed in its own thread without blocking the main stanza processing loop. """ self.event('custom_action', iq) async def _handle_action_event(self, iq): """ Respond to the custom action event. """ method = iq['action']['method'] param = iq['action']['param'] if method == 'is_prime' and param == '2': print("got message: %s" % iq) rep = iq.reply() rep['action']['status'] = 'done' await rep.send() elif method == 'bye': print("got message: %s" % iq) rep = iq.reply() rep['action']['status'] = 'done' await rep.send() self.disconnect() else: print("got message: %s" % iq) rep = iq.reply() rep['action']['status'] = 'error' await rep.send() if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") # Setup the CommandBot and register plugins. Note that while plugins may # have interdependencies, the order in which you register them does # not matter. xmpp = ActionBot(args.jid, args.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}) # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/custom_stanzas/custom_stanza_user.py000077500000000000000000000105531342457644200254060ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp from slixmpp import Iq from slixmpp.exceptions import XMPPError from slixmpp.xmlstream import register_stanza_plugin from stanza import Action class ActionUserBot(slixmpp.ClientXMPP): """ A simple Slixmpp bot that sends a custom action stanza to another client. """ def __init__(self, jid, password, other): slixmpp.ClientXMPP.__init__(self, jid, password) self.action_provider = other # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to # listen for this event so that we we can initialize # our roster. self.add_event_handler("session_start", self.start) self.add_event_handler("message", self.message) register_stanza_plugin(Iq, Action) async def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.send_presence() await self.get_roster() await self.send_custom_iq() async def send_custom_iq(self): """Create and send two custom actions. If the first action was successful, then send a shutdown command and then disconnect. """ iq = self.Iq() iq['to'] = self.action_provider iq['type'] = 'set' iq['action']['method'] = 'is_prime' iq['action']['param'] = '2' try: resp = await iq.send() if resp['action']['status'] == 'done': #sending bye iq2 = self.Iq() iq2['to'] = self.action_provider iq2['type'] = 'set' iq2['action']['method'] = 'bye' await iq2.send() self.disconnect() except XMPPError: print('There was an error sending the custom action.') def message(self, msg): """ Process incoming message stanzas. Arguments: msg -- The received message stanza. """ logging.info(msg['body']) if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") parser.add_argument("-o", "--other", dest="other", help="JID providing custom stanza") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") if args.other is None: args.other = input("JID Providing custom stanza: ") # Setup the CommandBot and register plugins. Note that while plugins may # have interdependencies, the order in which you register them does # not matter. xmpp = ActionUserBot(args.jid, args.password, args.other) xmpp.register_plugin('xep_0030') # Service Discovery xmpp.register_plugin('xep_0004') # Data Forms xmpp.register_plugin('xep_0050') # Adhoc Commands # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/custom_stanzas/stanza.py000066400000000000000000000035311342457644200227510ustar00rootroot00000000000000from slixmpp.xmlstream import ElementBase class Action(ElementBase): """ A stanza class for XML content of the form: X X X """ #: The `name` field refers to the basic XML tag name of the #: stanza. Here, the tag name will be 'action'. name = 'action' #: The namespace of the main XML tag. namespace = 'slixmpp:custom:actions' #: The `plugin_attrib` value is the name that can be used #: with a parent stanza to access this stanza. For example #: from an Iq stanza object, accessing: #: #: iq['action'] #: #: would reference an Action object, and will even create #: an Action object and append it to the Iq stanza if #: one doesn't already exist. plugin_attrib = 'action' #: Stanza objects expose dictionary-like interfaces for #: accessing and manipulating substanzas and other values. #: The set of interfaces defined here are the names of #: these dictionary-like interfaces provided by this stanza #: type. For example, an Action stanza object can use: #: #: action['method'] = 'foo' #: print(action['param']) #: del action['status'] #: #: to set, get, or remove its values. interfaces = {'method', 'param', 'status'} #: By default, values in the `interfaces` set are mapped to #: attribute values. This can be changed such that an interface #: maps to a subelement's text value by adding interfaces to #: the sub_interfaces set. For example, here all interfaces #: are marked as sub_interfaces, and so the XML produced will #: look like: #: #: #: foo #: sub_interfaces = interfaces slixmpp-slix-1.4.2/examples/disco_browser.py000077500000000000000000000131421342457644200212420ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp from slixmpp.exceptions import IqError, IqTimeout class Disco(slixmpp.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=''): slixmpp.ClientXMPP.__init__(self, jid, password) # Using service discovery requires the XEP-0030 plugin. self.register_plugin('xep_0030') self.get = get self.target_jid = target_jid self.target_node = target_node # Values to control which disco entities are reported self.info_types = ['', 'all', 'info', 'identities', 'features'] self.identity_types = ['', 'all', 'info', 'identities'] self.feature_types = ['', 'all', 'info', 'features'] self.items_types = ['', 'all', 'items'] # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to # listen for this event so that we we can initialize # our roster. self.add_event_handler("session_start", self.start) async def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. In this case, we send disco#info and disco#items stanzas to the requested JID and print the results. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.get_roster() self.send_presence() try: if self.get in self.info_types: # function using the callback parameter. info = await self['xep_0030'].get_info(jid=self.target_jid, node=self.target_node) if self.get in self.items_types: # The same applies from above. Listen for the # disco_items event or pass a callback function # if you need to process a non-blocking request. items = await self['xep_0030'].get_items(jid=self.target_jid, node=self.target_node) if self.get not in self.info_types and self.get not in self.items_types: logging.error("Invalid disco request type.") return except IqError as e: logging.error("Entity returned an error: %s" % e.iq['error']['condition']) except IqTimeout: logging.error("No response received.") else: 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)) finally: self.disconnect() if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser(description=Disco.__doc__) parser.add_argument("-q","--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.ERROR) parser.add_argument("-d","--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.ERROR) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") parser.add_argument("query", choices=["all", "info", "items", "identities", "features"]) parser.add_argument("target_jid") parser.add_argument("node", nargs='?') args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") # Setup the Disco browser. xmpp = Disco(args.jid, args.password, args.target_jid, args.node, args.query) # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process(forever=False) slixmpp-slix-1.4.2/examples/download_avatars.py000077500000000000000000000126011342457644200217250ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp from slixmpp.exceptions import XMPPError from slixmpp import asyncio FILE_TYPES = { 'image/png': 'png', 'image/gif': 'gif', 'image/jpeg': 'jpg' } class AvatarDownloader(slixmpp.ClientXMPP): """ A basic script for downloading the avatars for a user's contacts. """ def __init__(self, jid, password): slixmpp.ClientXMPP.__init__(self, jid, password) self.add_event_handler("session_start", self.start) self.add_event_handler("changed_status", self.wait_for_presences) self.add_event_handler('vcard_avatar_update', self.on_vcard_avatar) self.add_event_handler('avatar_metadata_publish', self.on_avatar) self.received = set() self.presences_received = asyncio.Event() self.roster_received = asyncio.Event() def roster_received_cb(self, event): self.roster_received.set() self.presences_received.clear() async def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.send_presence() self.get_roster(callback=self.roster_received_cb) print('Waiting for presence updates...\n') await self.roster_received.wait() print('Roster received') await self.presences_received.wait() self.disconnect() async def on_vcard_avatar(self, pres): print("Received vCard avatar update from %s" % pres['from'].bare) try: result = await self['xep_0054'].get_vcard(pres['from'].bare, cached=True, timeout=5) except XMPPError: print("Error retrieving avatar for %s" % pres['from']) return avatar = result['vcard_temp']['PHOTO'] filetype = FILE_TYPES.get(avatar['TYPE'], 'png') filename = 'vcard_avatar_%s_%s.%s' % ( pres['from'].bare, pres['vcard_temp_update']['photo'], filetype) with open(filename, 'wb+') as img: img.write(avatar['BINVAL']) async def on_avatar(self, msg): print("Received avatar update from %s" % msg['from']) metadata = msg['pubsub_event']['items']['item']['avatar_metadata'] for info in metadata['items']: if not info['url']: try: result = await self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'], timeout=5) except XMPPError: print("Error retrieving avatar for %s" % msg['from']) return avatar = result['pubsub']['items']['item']['avatar_data'] filetype = FILE_TYPES.get(metadata['type'], 'png') filename = 'avatar_%s_%s.%s' % (msg['from'].bare, info['id'], filetype) with open(filename, 'wb+') as img: img.write(avatar['value']) else: # We could retrieve the avatar via HTTP, etc here instead. pass def wait_for_presences(self, pres): """ Wait to receive updates from all roster contacts. """ self.received.add(pres['from'].bare) print((len(self.received), len(self.client_roster.keys()))) 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. parser = ArgumentParser() parser.add_argument("-q","--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.ERROR) parser.add_argument("-d","--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.ERROR) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") xmpp = AvatarDownloader(args.jid, args.password) xmpp.register_plugin('xep_0054') xmpp.register_plugin('xep_0153') xmpp.register_plugin('xep_0084') # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/echo_client.py000077500000000000000000000072701342457644200206570ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp class EchoBot(slixmpp.ClientXMPP): """ A simple Slixmpp bot that will echo messages it receives, along with a short thank you message. """ def __init__(self, jid, password): slixmpp.ClientXMPP.__init__(self, jid, password) # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to # listen for this event so that we we can initialize # our roster. self.add_event_handler("session_start", self.start) # The message event is triggered whenever a message # stanza is received. Be aware that that includes # MUC messages and error messages. self.add_event_handler("message", self.message) def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.send_presence() self.get_roster() def message(self, msg): """ Process incoming message stanzas. Be aware that this also includes MUC messages and error messages. It is usually a good idea to check the messages's type before processing or sending replies. Arguments: msg -- The received message stanza. See the documentation for stanza objects and the Message stanza to see how it may be used. """ if msg['type'] in ('chat', 'normal'): msg.reply("Thanks for sending\n%(body)s" % msg).send() if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser(description=EchoBot.__doc__) # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") # Setup the EchoBot and register plugins. Note that while plugins may # have interdependencies, the order in which you register them does # not matter. xmpp = EchoBot(args.jid, args.password) xmpp.register_plugin('xep_0030') # Service Discovery xmpp.register_plugin('xep_0004') # Data Forms xmpp.register_plugin('xep_0060') # PubSub xmpp.register_plugin('xep_0199') # XMPP Ping # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/echo_component.py000077500000000000000000000071561342457644200214060ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp from slixmpp.componentxmpp import ComponentXMPP class EchoComponent(ComponentXMPP): """ A simple Slixmpp 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. parser = ArgumentParser(description=EchoComponent.__doc__) # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") parser.add_argument("-s", "--server", dest="server", help="server to connect to") parser.add_argument("-P", "--port", dest="port", help="port to connect to") args = parser.parse_args() if args.jid is None: args.jid = input("Component JID: ") if args.password is None: args.password = getpass("Password: ") if args.server is None: args.server = input("Server: ") if args.port is None: args.port = int(input("Port: ")) # Setup logging. logging.basicConfig(level=args.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(args.jid, args.password, args.server, args.port) 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 # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/gtalk_custom_domain.py000077500000000000000000000106401342457644200224210ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp import ssl from slixmpp.xmlstream import cert class GTalkBot(slixmpp.ClientXMPP): """ A demonstration of using Slixmpp with accounts from a Google Apps account with a custom domain, because it requires custom certificate validation. """ def __init__(self, jid, password): slixmpp.ClientXMPP.__init__(self, jid, password) # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to # listen for this event so that we we can initialize # our roster. self.add_event_handler("session_start", self.start) # The message event is triggered whenever a message # stanza is received. Be aware that that includes # MUC messages and error messages. self.add_event_handler("message", self.message) # Using a Google Apps custom domain, the certificate # does not contain the custom domain, just the GTalk # server name. So we will need to process invalid # certifcates ourselves and check that it really # is from Google. self.add_event_handler("ssl_invalid_cert", self.invalid_cert) def invalid_cert(self, pem_cert): der_cert = ssl.PEM_cert_to_DER_cert(pem_cert) try: cert.verify('talk.google.com', der_cert) logging.debug("CERT: Found GTalk certificate") except cert.CertificateError as err: logging.error(err.message) self.disconnect() def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.send_presence() self.get_roster() def message(self, msg): """ Process incoming message stanzas. Be aware that this also includes MUC messages and error messages. It is usually a good idea to check the messages's type before processing or sending replies. Arguments: msg -- The received message stanza. See the documentation for stanza objects and the Message stanza to see how it may be used. """ if msg['type'] in ('chat', 'normal'): msg.reply("Thanks for sending\n%(body)s" % msg).send() if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") # Setup the GTalkBot and register plugins. Note that while plugins may # have interdependencies, the order in which you register them does # not matter. xmpp = GTalkBot(args.jid, args.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 # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/http_over_xmpp.py000066400000000000000000000055211342457644200214530ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Implementation of HTTP over XMPP transport http://xmpp.org/extensions/xep-0332.html Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com This file is part of slixmpp. See the file LICENSE for copying permission. """ from slixmpp import ClientXMPP from argparse import ArgumentParser import logging import getpass class HTTPOverXMPPClient(ClientXMPP): def __init__(self, jid, password): ClientXMPP.__init__(self, jid, password) self.register_plugin('xep_0332') # HTTP over XMPP Transport self.add_event_handler( 'session_start', self.session_start ) self.add_event_handler('http_request', self.http_request_received) self.add_event_handler('http_response', self.http_response_received) def http_request_received(self, iq): pass def http_response_received(self, iq): print('HTTP Response Received : %s' % iq) print('From : %s' % iq['from']) print('To : %s' % iq['to']) print('Type : %s' % iq['type']) print('Headers : %s' % iq['resp']['headers']) print('Code : %s' % iq['resp']['code']) print('Message : %s' % iq['resp']['message']) print('Data : %s' % iq['resp']['data']) def session_start(self, event): # TODO: Fill in the blanks self['xep_0332'].send_request( to='?', method='?', resource='?', headers={} ) self.disconnect() if __name__ == '__main__': # # NOTE: To run this example, fill up the blanks in session_start() and # use the following command. # # ./http_over_xmpp.py -J -P -i -p [-v] # parser = ArgumentParser() # Output verbosity options. parser.add_argument( '-v', '--verbose', help='set logging to DEBUG', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.ERROR ) # JID and password options. parser.add_argument('-J', '--jid', dest='jid', help='JID') parser.add_argument('-P', '--password', dest='password', help='Password') # XMPP server ip and port options. parser.add_argument( '-i', '--ipaddr', dest='ipaddr', help='IP Address of the XMPP server', default=None ) parser.add_argument( '-p', '--port', dest='port', help='Port of the XMPP server', default=None ) args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input('Username: ') if args.password is None: args.password = getpass.getpass('Password: ') xmpp = HTTPOverXMPPClient(args.jid, args.password) xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/http_upload.py000077500000000000000000000062051342457644200207230ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2018 Emmanuel Gil Peyrot This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp log = logging.getLogger(__name__) class HttpUpload(slixmpp.ClientXMPP): """ A basic client asking an entity if they confirm the access to an HTTP URL. """ def __init__(self, jid, password, recipient, filename, domain=None): slixmpp.ClientXMPP.__init__(self, jid, password) self.recipient = recipient self.filename = filename self.domain = domain self.add_event_handler("session_start", self.start) async def start(self, event): log.info('Uploading file %s...', self.filename) def timeout_callback(arg): raise TimeoutError("could not send message in time") url = await self['xep_0363'].upload_file( self.filename, domain=self.domain, timeout=10, timeout_callback=timeout_callback) log.info('Upload success!') log.info('Sending file to %s', self.recipient) html = '%s' % (url, url) self.send_message(self.recipient, url, mhtml=html) self.disconnect() if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() parser.add_argument("-q","--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d","--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") # Other options. parser.add_argument("-r", "--recipient", required=True, help="Recipient JID") parser.add_argument("-f", "--file", required=True, help="File to send") parser.add_argument("--domain", help="Domain to use for HTTP File Upload (leave out for your own server’s)") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") xmpp = HttpUpload(args.jid, args.password, args.recipient, args.file, args.domain) xmpp.register_plugin('xep_0071') xmpp.register_plugin('xep_0128') xmpp.register_plugin('xep_0363') # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process(forever=False) slixmpp-slix-1.4.2/examples/ibb_transfer/000077500000000000000000000000001342457644200204605ustar00rootroot00000000000000slixmpp-slix-1.4.2/examples/ibb_transfer/ibb_receiver.py000077500000000000000000000071371342457644200234650ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp class IBBReceiver(slixmpp.ClientXMPP): """ A basic example of creating and using an in-band bytestream. """ def __init__(self, jid, password, filename): slixmpp.ClientXMPP.__init__(self, jid, password) self.file = open(filename, 'wb') # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to # listen for this event so that we we can initialize # our roster. self.add_event_handler("session_start", self.start) self.add_event_handler("ibb_stream_start", self.stream_opened) self.add_event_handler("ibb_stream_data", self.stream_data) self.add_event_handler("ibb_stream_end", self.stream_closed) def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.send_presence() self.get_roster() def stream_opened(self, stream): print('Stream opened: %s from %s' % (stream.sid, stream.peer_jid)) def stream_data(self, stream): self.file.write(stream.read()) def stream_closed(self, stream): print('Stream closed: %s from %s' % (stream.sid, stream.peer_jid)) self.file.close() self.disconnect() if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") parser.add_argument("-o", "--out", dest="filename", help="file to save to") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") if args.filename is None: args.filename = input("File path: ") # Setup the IBBReceiver and register plugins. Note that while plugins may # have interdependencies, the order in which you register them does # not matter. xmpp = IBBReceiver(args.jid, args.password, args.filename) xmpp.register_plugin('xep_0030') # Service Discovery xmpp.register_plugin('xep_0047', { 'auto_accept': True }) # In-band Bytestreams # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process(forever=False) slixmpp-slix-1.4.2/examples/ibb_transfer/ibb_sender.py000077500000000000000000000101711342457644200231310ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp from slixmpp.exceptions import IqError, IqTimeout class IBBSender(slixmpp.ClientXMPP): """ A basic example of creating and using an in-band bytestream. """ def __init__(self, jid, password, receiver, filename, use_messages=False): slixmpp.ClientXMPP.__init__(self, jid, password) self.receiver = receiver self.file = open(filename, 'rb') self.use_messages = use_messages # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to # listen for this event so that we we can initialize # our roster. self.add_event_handler("session_start", self.start) async def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.send_presence() self.get_roster() try: # Open the IBB stream in which to write to. stream = await self['xep_0047'].open_stream(self.receiver, use_messages=self.use_messages) # If you want to send in-memory bytes, use stream.sendall() instead. await stream.sendfile(self.file, timeout=10) # And finally close the stream. await stream.close(timeout=10) except (IqError, IqTimeout): print('File transfer errored') else: print('File transfer finished') finally: self.file.close() self.disconnect() if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") parser.add_argument("-r", "--receiver", dest="receiver", help="JID of the receiver") parser.add_argument("-f", "--file", dest="filename", help="file to send") parser.add_argument("-m", "--use-messages", action="store_true", help="use messages instead of iqs for file transfer") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") if args.receiver is None: args.receiver = input("Receiver: ") if args.filename is None: args.filename = input("File path: ") # Setup the IBBSender and register plugins. Note that while plugins may # have interdependencies, the order in which you register them does # not matter. xmpp = IBBSender(args.jid, args.password, args.receiver, args.filename, args.use_messages) xmpp.register_plugin('xep_0030') # Service Discovery xmpp.register_plugin('xep_0047') # In-band Bytestreams # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process(forever=False) slixmpp-slix-1.4.2/examples/mam.py000077500000000000000000000061251342457644200171530ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2017 Mathieu Pasquet This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp from slixmpp.exceptions import XMPPError log = logging.getLogger(__name__) class MAM(slixmpp.ClientXMPP): """ A basic client fetching mam archive messages """ def __init__(self, jid, password, remote_jid, start): slixmpp.ClientXMPP.__init__(self, jid, password) self.remote_jid = remote_jid self.start_date = start self.add_event_handler("session_start", self.start) async def start(self, *args): """ Fetch mam results for the specified JID. Use RSM to paginate the results. """ results = self.plugin['xep_0313'].retrieve(jid=self.remote_jid, iterator=True, rsm={'max': 10}, start=self.start_date) page = 1 async for rsm in results: print('Page %d' % page) for msg in rsm['mam']['results']: forwarded = msg['mam_result']['forwarded'] timestamp = forwarded['delay']['stamp'] message = forwarded['stanza'] print('[%s] %s: %s' % (timestamp, message['from'], message['body'])) page += 1 self.disconnect() if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() parser.add_argument("-q","--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d","--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") # Other options parser.add_argument("-r", "--remote-jid", dest="remote_jid", help="Remote JID") parser.add_argument("--start", help="Start date", default='2017-09-20T12:00:00Z') args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") if args.remote_jid is None: args.remote_jid = input("Remote JID: ") if args.start is None: args.start = input("Start time: ") xmpp = MAM(args.jid, args.password, args.remote_jid, args.start) xmpp.register_plugin('xep_0313') # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process(forever=False) slixmpp-slix-1.4.2/examples/markup.py000077500000000000000000000077331342457644200177060ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp from slixmpp.plugins.xep_0394 import stanza as markup_stanza class EchoBot(slixmpp.ClientXMPP): """ A simple Slixmpp bot that will echo messages it receives, along with a short thank you message. """ def __init__(self, jid, password): slixmpp.ClientXMPP.__init__(self, jid, password) # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to # listen for this event so that we we can initialize # our roster. self.add_event_handler("session_start", self.start) # The message event is triggered whenever a message # stanza is received. Be aware that that includes # MUC messages and error messages. self.add_event_handler("message", self.message) def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.send_presence() self.get_roster() def message(self, msg): """ Process incoming message stanzas. Be aware that this also includes MUC messages and error messages. It is usually a good idea to check the messages's type before processing or sending replies. Arguments: msg -- The received message stanza. See the documentation for stanza objects and the Message stanza to see how it may be used. """ body = msg['body'] new_body = self['xep_0394'].to_plain_text(body, msg['markup']) xhtml = self['xep_0394'].to_xhtml_im(body, msg['markup']) print('Plain text:', new_body) print('XHTML-IM:', xhtml['body']) message = msg.reply() message['body'] = new_body message['html']['body'] = xhtml['body'] self.send(message) if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser(description=EchoBot.__doc__) # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") # Setup the EchoBot and register plugins. Note that while plugins may # have interdependencies, the order in which you register them does # not matter. xmpp = EchoBot(args.jid, args.password) xmpp.register_plugin('xep_0030') # Service Discovery xmpp.register_plugin('xep_0199') # XMPP Ping xmpp.register_plugin('xep_0394') # Message Markup # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/migrate_roster.py000077500000000000000000000063321342457644200214270ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys import logging from getpass import getpass from argparse import ArgumentParser import slixmpp # Setup the command line arguments. parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("--oldjid", dest="old_jid", help="JID of the old account") parser.add_argument("--oldpassword", dest="old_password", help="password of the old account") parser.add_argument("--newjid", dest="new_jid", help="JID of the old account") parser.add_argument("--newpassword", dest="new_password", help="password of the old account") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.old_jid is None: args.old_jid = input("Old JID: ") if args.old_password is None: args.old_password = getpass("Old Password: ") if args.new_jid is None: args.new_jid = input("New JID: ") if args.new_password is None: args.new_password = getpass("New Password: ") old_xmpp = slixmpp.ClientXMPP(args.old_jid, args.old_password) # If you are connecting to Facebook and wish to use the # X-FACEBOOK-PLATFORM authentication mechanism, you will need # your API key and an access token. Then you'll set: # xmpp.credentials['api_key'] = 'THE_API_KEY' # xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN' # If you are connecting to MSN, then you will need an # access token, and it does not matter what JID you # specify other than that the domain is 'messenger.live.com', # so '_@messenger.live.com' will work. You can specify # the access token as so: # xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN' # 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" roster = [] def on_session(event): roster.append(old_xmpp.get_roster()) old_xmpp.disconnect() old_xmpp.add_event_handler('session_start', on_session) if old_xmpp.connect(): old_xmpp.process(block=True) if not roster: print('No roster to migrate') sys.exit() new_xmpp = slixmpp.ClientXMPP(args.new_jid, args.new_password) def on_session2(event): new_xmpp.get_roster() new_xmpp.send_presence() logging.info(roster[0]) data = roster[0]['roster']['items'] logging.info(data) for jid, item in data.items(): if item['subscription'] != 'none': new_xmpp.send_presence(ptype='subscribe', pto=jid) new_xmpp.update_roster(jid, name = item['name'], groups = item['groups']) new_xmpp.disconnect() new_xmpp.add_event_handler('session_start', on_session2) new_xmpp.connect() new_xmpp.process() slixmpp-slix-1.4.2/examples/muc.py000077500000000000000000000143351342457644200171670ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp class MUCBot(slixmpp.ClientXMPP): """ A simple Slixmpp 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): slixmpp.ClientXMPP.__init__(self, jid, password) self.room = room self.nick = nick # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to # listen for this event so that we we can initialize # our roster. self.add_event_handler("session_start", self.start) # The groupchat_message event is triggered whenever a message # stanza is received from any chat room. If you also also # register a handler for the 'message' event, MUC messages # will be processed by both handlers. self.add_event_handler("groupchat_message", self.muc_message) # The groupchat_presence event is triggered whenever a # presence stanza is received from any chat room, including # any presences you send yourself. To limit event handling # to a single room, use the events muc::room@server::presence, # muc::room@server::got_online, or muc::room@server::got_offline. self.add_event_handler("muc::%s::got_online" % self.room, self.muc_online) def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.get_roster() self.send_presence() self.plugin['xep_0045'].join_muc(self.room, self.nick, # If a room password is needed, use: # password=the_room_password, wait=True) def muc_message(self, msg): """ 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. parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") parser.add_argument("-r", "--room", dest="room", help="MUC room to join") parser.add_argument("-n", "--nick", dest="nick", help="MUC nickname") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") if args.room is None: args.room = input("MUC room: ") if args.nick is None: args.nick = input("MUC nickname: ") # Setup the MUCBot and register plugins. Note that while plugins may # have interdependencies, the order in which you register them does # not matter. xmpp = MUCBot(args.jid, args.password, args.room, args.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. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/ping.py000077500000000000000000000071561342457644200173430ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser from slixmpp.exceptions import IqError, IqTimeout import slixmpp class PingTest(slixmpp.ClientXMPP): """ A simple Slixmpp bot that will send a ping request to a given JID. """ def __init__(self, jid, password, pingjid): slixmpp.ClientXMPP.__init__(self, jid, password) if pingjid is None: pingjid = self.boundjid.bare self.pingjid = pingjid # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to # listen for this event so that we we can initialize # our roster. self.add_event_handler("session_start", self.start) async def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.send_presence() self.get_roster() try: rtt = await self['xep_0199'].ping(self.pingjid, timeout=10) logging.info("Success! RTT: %s", rtt) except IqError as e: logging.info("Error pinging %s: %s", self.pingjid, e.iq['error']['condition']) except IqTimeout: logging.info("No response from %s", self.pingjid) finally: self.disconnect() if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) parser.add_argument("-t", "--pingto", help="set jid to ping", dest="pingjid", default=None) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") # Setup the PingTest and register plugins. Note that while plugins may # have interdependencies, the order in which you register them does # not matter. xmpp = PingTest(args.jid, args.password, args.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 # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/proxy_echo_client.py000077500000000000000000000110371342457644200221140ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp class EchoBot(slixmpp.ClientXMPP): """ A simple Slixmpp bot that will echo messages it receives, along with a short thank you message. """ def __init__(self, jid, password): slixmpp.ClientXMPP.__init__(self, jid, password) # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to # listen for this event so that we we can initialize # our roster. self.add_event_handler("session_start", self.start) # The message event is triggered whenever a message # stanza is received. Be aware that that includes # MUC messages and error messages. self.add_event_handler("message", self.message) def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.send_presence() self.get_roster() def message(self, msg): """ Process incoming message stanzas. Be aware that this also includes MUC messages and error messages. It is usually a good idea to check the messages's type before processing or sending replies. Arguments: msg -- The received message stanza. See the documentation for stanza objects and the Message stanza to see how it may be used. """ msg.reply("Thanks for sending\n%(body)s" % msg).send() if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") parser.add_argument("--phost", dest="proxy_host", help="Proxy hostname") parser.add_argument("--pport", dest="proxy_port", help="Proxy port") parser.add_argument("--puser", dest="proxy_user", help="Proxy username") parser.add_argument("--ppass", dest="proxy_pass", help="Proxy password") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") if args.proxy_host is None: args.proxy_host = input("Proxy host: ") if args.proxy_port is None: args.proxy_port = input("Proxy port: ") if args.proxy_user is None: args.proxy_user = input("Proxy username: ") if args.proxy_pass is None and args.proxy_user: args.proxy_pass = 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(args.jid, args.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 xmpp.use_proxy = True xmpp.proxy_config = { 'host': args.proxy_host, 'port': int(args.proxy_port), 'username': args.proxy_user, 'password': args.proxy_pass} # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/pubsub_client.py000077500000000000000000000153561342457644200212450ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- import logging from getpass import getpass from argparse import ArgumentParser import slixmpp from slixmpp.exceptions import XMPPError from slixmpp.xmlstream import ET, tostring class PubsubClient(slixmpp.ClientXMPP): def __init__(self, jid, password, server, node=None, action='nodes', data=''): super().__init__(jid, password) self.register_plugin('xep_0030') self.register_plugin('xep_0059') self.register_plugin('xep_0060') self.actions = ['nodes', 'create', 'delete', 'get_configure', 'publish', 'get', 'retract', 'purge', 'subscribe', 'unsubscribe'] self.action = action self.node = node self.data = data self.pubsub_server = server self.add_event_handler('session_start', self.start) async def start(self, event): self.get_roster() self.send_presence() try: await getattr(self, self.action)() except: logging.exception('Could not execute %s:', self.action) self.disconnect() async def nodes(self): try: result = await self['xep_0060'].get_nodes(self.pubsub_server, self.node) for item in result['disco_items']['items']: logging.info(' - %s', str(item)) except XMPPError as error: logging.error('Could not retrieve node list: %s', error.format()) async def create(self): try: await self['xep_0060'].create_node(self.pubsub_server, self.node) logging.info('Created node %s', self.node) except XMPPError as error: logging.error('Could not create node %s: %s', self.node, error.format()) async def delete(self): try: await self['xep_0060'].delete_node(self.pubsub_server, self.node) logging.info('Deleted node %s', self.node) except XMPPError as error: logging.error('Could not delete node %s: %s', self.node, error.format()) async def get_configure(self): try: configuration_form = await self['xep_0060'].get_node_config(self.pubsub_server, self.node) logging.info('Configure form received from node %s: %s', self.node, configuration_form['pubsub_owner']['configure']['form']) except XMPPError as error: logging.error('Could not retrieve configure form from node %s: %s', self.node, error.format()) async def publish(self): payload = ET.fromstring("%s" % self.data) try: result = await self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload) logging.info('Published at item id: %s', result['pubsub']['publish']['item']['id']) except XMPPError as error: logging.error('Could not publish to %s: %s', self.node, error.format()) async def get(self): try: result = await self['xep_0060'].get_item(self.pubsub_server, self.node, self.data) for item in result['pubsub']['items']['substanzas']: logging.info('Retrieved item %s: %s', item['id'], tostring(item['payload'])) except XMPPError as error: logging.error('Could not retrieve item %s from node %s: %s', self.data, self.node, error.format()) async def retract(self): try: await self['xep_0060'].retract(self.pubsub_server, self.node, self.data) logging.info('Retracted item %s from node %s', self.data, self.node) except XMPPError as error: logging.error('Could not retract item %s from node %s: %s', self.data, self.node, error.format()) async def purge(self): try: await self['xep_0060'].purge(self.pubsub_server, self.node) logging.info('Purged all items from node %s', self.node) except XMPPError as error: logging.error('Could not purge items from node %s: %s', self.node, error.format()) async def subscribe(self): try: iq = await self['xep_0060'].subscribe(self.pubsub_server, self.node) subscription = iq['pubsub']['subscription'] logging.info('Subscribed %s to node %s', subscription['jid'], subscription['node']) except XMPPError as error: logging.error('Could not subscribe %s to node %s: %s', self.boundjid.bare, self.node, error.format()) async def unsubscribe(self): try: await self['xep_0060'].unsubscribe(self.pubsub_server, self.node) logging.info('Unsubscribed %s from node %s', self.boundjid.bare, self.node) except XMPPError as error: logging.error('Could not unsubscribe %s from node %s: %s', self.boundjid.bare, self.node, error.format()) if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() parser.version = '%%prog 0.1' parser.usage = "Usage: %%prog [options] " + \ 'nodes|create|delete|get_configure|purge|subscribe|unsubscribe|publish|retract|get' + \ ' [ ]' parser.add_argument("-q","--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d","--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") parser.add_argument("server") parser.add_argument("action", choices=["nodes", "create", "delete", "get_configure", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"]) parser.add_argument("node", nargs='?') parser.add_argument("data", nargs='?') args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") # Setup the Pubsub client xmpp = PubsubClient(args.jid, args.password, server=args.server, node=args.node, action=args.action, data=args.data) # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process(forever=False) slixmpp-slix-1.4.2/examples/pubsub_events.py000077500000000000000000000106141342457644200212630ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- import logging from getpass import getpass from argparse import ArgumentParser import slixmpp from slixmpp.xmlstream import ET, tostring from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.xmlstream.handler import Callback class PubsubEvents(slixmpp.ClientXMPP): def __init__(self, jid, password): super().__init__(jid, password) self.register_plugin('xep_0030') self.register_plugin('xep_0059') self.register_plugin('xep_0060') self.add_event_handler('session_start', self.start) # Some services may require configuration to allow # sending delete, configuration, or subscription events. self.add_event_handler('pubsub_publish', self._publish) self.add_event_handler('pubsub_retract', self._retract) self.add_event_handler('pubsub_purge', self._purge) self.add_event_handler('pubsub_delete', self._delete) self.add_event_handler('pubsub_config', self._config) self.add_event_handler('pubsub_subscription', self._subscription) # Want to use nicer, more specific pubsub event names? # self['xep_0060'].map_node_event('node_name', 'event_prefix') # self.add_event_handler('event_prefix_publish', handler) # self.add_event_handler('event_prefix_retract', handler) # self.add_event_handler('event_prefix_purge', handler) # self.add_event_handler('event_prefix_delete', handler) def start(self, event): self.get_roster() self.send_presence() def _publish(self, msg): """Handle receiving a publish item event.""" print('Published item %s to %s:' % ( msg['pubsub_event']['items']['item']['id'], msg['pubsub_event']['items']['node'])) data = msg['pubsub_event']['items']['item']['payload'] if data is not None: print(tostring(data)) else: print('No item content') def _retract(self, msg): """Handle receiving a retract item event.""" print('Retracted item %s from %s' % ( msg['pubsub_event']['items']['retract']['id'], msg['pubsub_event']['items']['node'])) def _purge(self, msg): """Handle receiving a node purge event.""" print('Purged all items from %s' % ( msg['pubsub_event']['purge']['node'])) def _delete(self, msg): """Handle receiving a node deletion event.""" print('Deleted node %s' % ( msg['pubsub_event']['delete']['node'])) def _config(self, msg): """Handle receiving a node configuration event.""" print('Configured node %s:' % ( msg['pubsub_event']['configuration']['node'])) print(msg['pubsub_event']['configuration']['form']) def _subscription(self, msg): """Handle receiving a node subscription event.""" print('Subscription change for node %s:' % ( msg['pubsub_event']['subscription']['node'])) print(msg['pubsub_event']['subscription']) if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") logging.info("Run this in conjunction with the pubsub_client.py " + \ "example to watch events happen as you give commands.") # Setup the PubsubEvents listener xmpp = PubsubEvents(args.jid, args.password) # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/register_account.py000077500000000000000000000120561342457644200217410ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp from slixmpp.exceptions import IqError, IqTimeout class RegisterBot(slixmpp.ClientXMPP): """ A basic bot that will attempt to register an account with an XMPP server. NOTE: This follows the very basic registration workflow from XEP-0077. More advanced server registration workflows will need to check for data forms, etc. """ def __init__(self, jid, password): slixmpp.ClientXMPP.__init__(self, jid, password) # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to # listen for this event so that we we can initialize # our roster. self.add_event_handler("session_start", self.start) # The register event provides an Iq result stanza with # a registration form from the server. This may include # the basic registration fields, a data form, an # out-of-band URL, or any combination. For more advanced # cases, you will need to examine the fields provided # and respond accordingly. Slixmpp provides plugins # for data forms and OOB links that will make that easier. self.add_event_handler("register", self.register) def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.send_presence() self.get_roster() # We're only concerned about registering, so nothing more to do here. self.disconnect() async def register(self, iq): """ Fill out and submit a registration form. The form may be composed of basic registration fields, a data form, an out-of-band link, or any combination thereof. Data forms and OOB links can be checked for as so: if iq.match('iq/register/form'): # do stuff with data form # iq['register']['form']['fields'] if iq.match('iq/register/oob'): # do stuff with OOB URL # iq['register']['oob']['url'] To get the list of basic registration fields, you can use: iq['register']['fields'] """ resp = self.Iq() resp['type'] = 'set' resp['register']['username'] = self.boundjid.user resp['register']['password'] = self.password try: await resp.send() logging.info("Account created for %s!" % self.boundjid) except IqError as e: logging.error("Could not register account: %s" % e.iq['error']['text']) self.disconnect() except IqTimeout: logging.error("No response from server.") self.disconnect() if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") # Setup the RegisterBot and register plugins. Note that while plugins may # have interdependencies, the order in which you register them does # not matter. xmpp = RegisterBot(args.jid, args.password) xmpp.register_plugin('xep_0030') # Service Discovery xmpp.register_plugin('xep_0004') # Data forms xmpp.register_plugin('xep_0066') # Out-of-band Data xmpp.register_plugin('xep_0077') # In-band Registration # Some servers don't advertise support for inband registration, even # though they allow it. If this applies to your server, use: xmpp['xep_0077'].force_registration = True # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/roster_browser.py000077500000000000000000000107401342457644200214600ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp from slixmpp.exceptions import IqError, IqTimeout from slixmpp.xmlstream.asyncio import asyncio class RosterBrowser(slixmpp.ClientXMPP): """ A basic script for dumping a client's roster to the command line. """ def __init__(self, jid, password): slixmpp.ClientXMPP.__init__(self, jid, password) # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to # listen for this event so that we we can initialize # our roster. self.add_event_handler("session_start", self.start) self.add_event_handler("changed_status", self.wait_for_presences) self.received = set() self.presences_received = asyncio.Event() async def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ future = asyncio.Future() def callback(result): future.set_result(None) try: self.get_roster(callback=callback) await future except IqError as err: print('Error: %s' % err.iq['error']['condition']) except IqTimeout: print('Error: Request timed out') self.send_presence() print('Waiting for presence updates...\n') await asyncio.sleep(10) 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. parser = ArgumentParser() parser.add_argument("-q","--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.ERROR) parser.add_argument("-d","--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.ERROR) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") xmpp = RosterBrowser(args.jid, args.password) # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/rpc_async.py000077500000000000000000000014341342457644200203600ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2011 Dann Martens This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.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() slixmpp-slix-1.4.2/examples/rpc_client_side.py000077500000000000000000000021361342457644200215250ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2011 Dann Martens This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.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() slixmpp-slix-1.4.2/examples/rpc_server_side.py000077500000000000000000000021451342457644200215550ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2011 Dann Martens This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.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() slixmpp-slix-1.4.2/examples/s5b_transfer/000077500000000000000000000000001342457644200204155ustar00rootroot00000000000000slixmpp-slix-1.4.2/examples/s5b_transfer/s5b_receiver.py000077500000000000000000000054221342457644200233520ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2015 Emmanuel Gil Peyrot This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp class S5BReceiver(slixmpp.ClientXMPP): """ A basic example of creating and using a SOCKS5 bytestream. """ def __init__(self, jid, password, filename): slixmpp.ClientXMPP.__init__(self, jid, password) self.file = open(filename, 'wb') self.add_event_handler("socks5_connected", self.stream_opened) self.add_event_handler("socks5_data", self.stream_data) self.add_event_handler("socks5_closed", self.stream_closed) def stream_opened(self, sid): logging.info('Stream opened. %s', sid) def stream_data(self, data): self.file.write(data) def stream_closed(self, exception): logging.info('Stream closed. %s', exception) self.file.close() self.disconnect() if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") parser.add_argument("-o", "--out", dest="filename", help="file to save to") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") if args.filename is None: args.filename = input("File path: ") # Setup the S5BReceiver and register plugins. Note that while plugins may # have interdependencies, the order in which you register them does # not matter. xmpp = S5BReceiver(args.jid, args.password, args.filename) xmpp.register_plugin('xep_0030') # Service Discovery xmpp.register_plugin('xep_0065', { 'auto_accept': True }) # SOCKS5 Bytestreams # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process(forever=False) slixmpp-slix-1.4.2/examples/s5b_transfer/s5b_sender.py000077500000000000000000000076541342457644200230370ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2015 Emmanuel Gil Peyrot This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp from slixmpp.exceptions import IqError, IqTimeout class S5BSender(slixmpp.ClientXMPP): """ A basic example of creating and using a SOCKS5 bytestream. """ def __init__(self, jid, password, receiver, filename): slixmpp.ClientXMPP.__init__(self, jid, password) self.receiver = receiver self.file = open(filename, 'rb') # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. self.add_event_handler("session_start", self.start) async def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ try: # Open the S5B stream in which to write to. proxy = await self['xep_0065'].handshake(self.receiver) # Send the entire file. while True: data = self.file.read(1048576) if not data: break await proxy.write(data) # And finally close the stream. proxy.transport.write_eof() except (IqError, IqTimeout): print('File transfer errored') else: print('File transfer finished') finally: self.file.close() self.disconnect() if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") parser.add_argument("-r", "--receiver", dest="receiver", help="JID of the receiver") parser.add_argument("-f", "--file", dest="filename", help="file to send") parser.add_argument("-m", "--use-messages", action="store_true", help="use messages instead of iqs for file transfer") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") if args.receiver is None: args.receiver = input("Receiver: ") if args.filename is None: args.filename = input("File path: ") # Setup the S5BSender and register plugins. Note that while plugins may # have interdependencies, the order in which you register them does # not matter. xmpp = S5BSender(args.jid, args.password, args.receiver, args.filename) xmpp.register_plugin('xep_0030') # Service Discovery xmpp.register_plugin('xep_0065') # SOCKS5 Bytestreams # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process(forever=False) slixmpp-slix-1.4.2/examples/send_client.py000077500000000000000000000067271342457644200207000ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from getpass import getpass from argparse import ArgumentParser import slixmpp class SendMsgBot(slixmpp.ClientXMPP): """ A basic Slixmpp bot that will log in, send a message, and then log out. """ def __init__(self, jid, password, recipient, message): slixmpp.ClientXMPP.__init__(self, jid, password) # The message we wish to send, and the JID that # will receive it. self.recipient = recipient self.msg = message # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to # listen for this event so that we we can initialize # our roster. self.add_event_handler("session_start", self.start) def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.send_presence() self.get_roster() self.send_message(mto=self.recipient, mbody=self.msg, mtype='chat') self.disconnect() if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser(description=SendMsgBot.__doc__) # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") parser.add_argument("-t", "--to", dest="to", help="JID to send the message to") parser.add_argument("-m", "--message", dest="message", help="message to send") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") if args.to is None: args.to = input("Send To: ") if args.message is None: args.message = 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(args.jid, args.password, args.to, args.message) xmpp.register_plugin('xep_0030') # Service Discovery xmpp.register_plugin('xep_0199') # XMPP Ping # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/set_avatar.py000077500000000000000000000105541342457644200205330ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import os import imghdr import logging from getpass import getpass import threading from argparse import ArgumentParser import slixmpp from slixmpp.exceptions import XMPPError class AvatarSetter(slixmpp.ClientXMPP): """ A basic script for downloading the avatars for a user's contacts. """ def __init__(self, jid, password, filepath): slixmpp.ClientXMPP.__init__(self, jid, password) self.add_event_handler("session_start", self.start) self.filepath = filepath async def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.send_presence() self.get_roster() avatar_file = None try: avatar_file = open(os.path.expanduser(self.filepath), 'rb') except IOError: print('Could not find file: %s' % self.filepath) return self.disconnect() avatar = avatar_file.read() avatar_type = 'image/%s' % imghdr.what('', avatar) avatar_id = self['xep_0084'].generate_id(avatar) avatar_bytes = len(avatar) avatar_file.close() used_xep84 = False print('Publish XEP-0084 avatar data') result = await self['xep_0084'].publish_avatar(avatar) if isinstance(result, XMPPError): print('Could not publish XEP-0084 avatar') else: used_xep84 = True print('Update vCard with avatar') result = await self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type) if isinstance(result, XMPPError): print('Could not set vCard avatar') if used_xep84: print('Advertise XEP-0084 avatar metadata') result = await self['xep_0084'].publish_avatar_metadata([ {'id': avatar_id, 'type': avatar_type, 'bytes': avatar_bytes} # We could advertise multiple avatars to provide # options in image type, source (HTTP vs pubsub), # size, etc. # {'id': ....} ]) if isinstance(result, XMPPError): print('Could not publish XEP-0084 metadata') print('Wait for presence updates to propagate...') self.schedule('end', 5, self.disconnect, kwargs={'wait': True}) if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() parser.add_argument("-q","--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.ERROR) parser.add_argument("-d","--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.ERROR) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") parser.add_argument("-f", "--file", dest="filepath", help="path to the avatar file") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") if args.filepath is None: args.filepath = input("Avatar file location: ") xmpp = AvatarSetter(args.jid, args.password, args.filepath) xmpp.register_plugin('xep_0054') xmpp.register_plugin('xep_0153') xmpp.register_plugin('xep_0084') # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/thirdparty_auth.py000077500000000000000000000161061342457644200216140ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import sys import logging from getpass import getpass from argparse import ArgumentParser try: from httplib import HTTPSConnection from urllib import urlencode except ImportError: from urllib.parse import urlencode from http.client import HTTPSConnection import slixmpp from slixmpp.xmlstream import JID class ThirdPartyAuthBot(slixmpp.ClientXMPP): """ A simple Slixmpp bot that will echo messages it receives, along with a short thank you message. This version uses a thirdpary service for authentication, such as Facebook or Google. """ def __init__(self, jid, password): slixmpp.ClientXMPP.__init__(self, jid, password) # The X-GOOGLE-TOKEN mech is ranked lower than PLAIN # due to Google only allowing a single SASL attempt per # connection. So PLAIN will be used for TLS connections, # and X-GOOGLE-TOKEN for non-TLS connections. To use # X-GOOGLE-TOKEN with a TLS connection, explicitly select # it using: # # slixmpp.ClientXMPP.__init__(self, jid, password, # sasl_mech="X-GOOGLE-TOKEN") # The session_start event will be triggered when # the bot establishes its connection with the server # and the XML streams are ready for use. We want to # listen for this event so that we we can initialize # our roster. self.add_event_handler("session_start", self.start) # The message event is triggered whenever a message # stanza is received. Be aware that that includes # MUC messages and error messages. self.add_event_handler("message", self.message) def start(self, event): """ Process the session_start event. Typical actions for the session_start event are requesting the roster and broadcasting an initial presence stanza. Arguments: event -- An empty dictionary. The session_start event does not provide any additional data. """ self.send_presence() self.get_roster() def message(self, msg): """ Process incoming message stanzas. Be aware that this also includes MUC messages and error messages. It is usually a good idea to check the messages's type before processing or sending replies. Arguments: msg -- The received message stanza. See the documentation for stanza objects and the Message stanza to see how it may be used. """ if msg['type'] in ('chat', 'normal'): msg.reply("Thanks for sending\n%(body)s" % msg).send() if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") access_token = None # Since documentation on how to work with Google tokens # can be difficult to find, we'll demo a basic version # here. Note that responses could refer to a Captcha # URL that would require a browser. # Using Facebook or MSN's custom authentication requires # a browser, but the process is the same once a token # has been retrieved. # Request an access token from Google: try: conn = HTTPSConnection('www.google.com') except: print('Could not connect to Google') sys.exit() params = urlencode({ 'accountType': 'GOOGLE', 'service': 'mail', 'Email': JID(args.jid).bare, 'Passwd': args.password }) headers = { 'Content-Type': 'application/x-www-form-urlencoded' } try: conn.request('POST', '/accounts/ClientLogin', params, headers) resp = conn.getresponse().read() data = {} for line in resp.split(): k, v = line.split(b'=', 1) data[k] = v except Exception as e: print('Could not retrieve login data') sys.exit() if b'SID' not in data: print('Required data not found') sys.exit() params = urlencode({ 'SID': data[b'SID'], 'LSID': data[b'LSID'], 'service': 'mail' }) try: conn.request('POST', '/accounts/IssueAuthToken', params, headers) resp = conn.getresponse() data = resp.read().split() except: print('Could not retrieve auth data') sys.exit() if not data: print('Could not retrieve token') sys.exit() access_token = data[0] # Setup the ThirdPartyAuthBot and register plugins. Note that while plugins # may have interdependencies, the order in which you register them does not # matter. # If using MSN, the JID should be "user@messenger.live.com", which will # be overridden on session bind. # We're using an access token instead of a password, so we'll use `''` as # a password argument filler. xmpp = ThirdPartyAuthBot(args.jid, '') xmpp.credentials['access_token'] = access_token # The credentials dictionary is used to provide additional authentication # information beyond just a password. xmpp.register_plugin('xep_0030') # Service Discovery xmpp.register_plugin('xep_0004') # Data Forms xmpp.register_plugin('xep_0060') # PubSub # MSN will kill connections that have been inactive for even # short periods of time. So use pings to keep the session alive; # whitespace keepalives do not work. xmpp.register_plugin('xep_0199', {'keepalive': True, 'frequency': 60}) # 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. # Google only allows one SASL attempt per connection, so in order to # enable the X-GOOGLE-TOKEN mechanism, we'll disable TLS. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/user_location.py000077500000000000000000000061701342457644200212470ustar00rootroot00000000000000#!/usr/bin/env python3 import sys import logging from getpass import getpass from argparse import ArgumentParser try: import json except ImportError: import simplejson as json try: import requests except ImportError: print('This demo requires the requests package for using HTTP.') sys.exit() from slixmpp import ClientXMPP class LocationBot(ClientXMPP): def __init__(self, jid, password): super().__init__(jid, password) self.add_event_handler('session_start', self.start) self.add_event_handler('user_location_publish', self.user_location_publish) self.register_plugin('xep_0004') self.register_plugin('xep_0030') self.register_plugin('xep_0060') self.register_plugin('xep_0115') self.register_plugin('xep_0128') self.register_plugin('xep_0163') self.register_plugin('xep_0080') self.current_tune = None def start(self, event): self.send_presence() self.get_roster() self['xep_0115'].update_caps() print("Using freegeoip.net to get geolocation.") r = requests.get('http://freegeoip.net/json/') try: data = json.loads(r.text) except: print("Could not retrieve user location.") self.disconnect() return self['xep_0080'].publish_location( lat=data['latitude'], lon=data['longitude'], locality=data['city'], region=data['region_name'], country=data['country_name'], countrycode=data['country_code'], postalcode=data['zipcode']) def user_location_publish(self, msg): geo = msg['pubsub_event']['items']['item']['geoloc'] print("%s is at:" % msg['from']) for key, val in geo.values.items(): if val: print(" %s: %s" % (key, val)) if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") xmpp = LocationBot(args.jid, args.password) # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/examples/user_tune.py000077500000000000000000000075371342457644200204220ustar00rootroot00000000000000#!/usr/bin/env python3 import sys import logging from getpass import getpass from argparse import ArgumentParser try: from appscript import * except ImportError: print('This demo requires the appscript package to interact with iTunes.') sys.exit() from slixmpp import ClientXMPP class TuneBot(ClientXMPP): def __init__(self, jid, password): super().__init__(jid, password) # Check for the current song every 5 seconds. self.schedule('Check Current Tune', 5, self._update_tune, repeat=True) self.add_event_handler('session_start', self.start) self.add_event_handler('user_tune_publish', self.user_tune_publish) self.register_plugin('xep_0004') self.register_plugin('xep_0030') self.register_plugin('xep_0060') self.register_plugin('xep_0115') self.register_plugin('xep_0118') self.register_plugin('xep_0128') self.register_plugin('xep_0163') self.current_tune = None def start(self, event): self.send_presence() self.get_roster() self['xep_0115'].update_caps() def _update_tune(self): itunes_count = app('System Events').processes[its.name == 'iTunes'].count() if itunes_count > 0: iTunes = app('iTunes') if iTunes.player_state.get() == k.playing: track = iTunes.current_track.get() length = track.time.get() if ':' in length: minutes, secs = map(int, length.split(':')) secs += minutes * 60 else: secs = int(length) artist = track.artist.get() title = track.name.get() source = track.album.get() rating = track.rating.get() / 10 tune = (artist, secs, rating, source, title) if tune != self.current_tune: self.current_tune = tune # We have a new song playing, so publish it. self['xep_0118'].publish_tune( artist=artist, length=secs, title=title, rating=rating, source=source) else: # No song is playing, clear the user tune. tune = None if tune != self.current_tune: self.current_tune = tune self['xep_0118'].stop() def user_tune_publish(self, msg): tune = msg['pubsub_event']['items']['item']['tune'] print("%s is listening to: %s" % (msg['from'], tune['title'])) if __name__ == '__main__': # Setup the command line arguments. parser = ArgumentParser() # Output verbosity options. parser.add_argument("-q", "--quiet", help="set logging to ERROR", action="store_const", dest="loglevel", const=logging.ERROR, default=logging.INFO) parser.add_argument("-d", "--debug", help="set logging to DEBUG", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # JID and password options. parser.add_argument("-j", "--jid", dest="jid", help="JID to use") parser.add_argument("-p", "--password", dest="password", help="password to use") args = parser.parse_args() # Setup logging. logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') if args.jid is None: args.jid = input("Username: ") if args.password is None: args.password = getpass("Password: ") xmpp = TuneBot(args.jid, args.password) # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() xmpp.process() slixmpp-slix-1.4.2/run_tests.py000077500000000000000000000032171342457644200166100ustar00rootroot00000000000000#!/usr/bin/env python3 import sys import logging import unittest from argparse import ArgumentParser from distutils.core import Command from importlib import import_module from pathlib import Path def run_tests(filenames=None): """ Find and run all tests in the tests/ directory. Excludes live tests (tests/live_*). """ if not filenames: filenames = [i for i in Path('tests').glob('test_*')] else: filenames = [Path(i) for i in filenames] modules = ['.'.join(test.parts[:-1] + (test.stem,)) for test in filenames] suites = [] for filename in modules: module = import_module(filename) suites.append(module.suite) tests = unittest.TestSuite(suites) runner = unittest.TextTestRunner(verbosity=2) # Disable logging output logging.basicConfig(level=100) logging.disable(100) result = runner.run(tests) return result # Add a 'test' command for setup.py class TestCommand(Command): user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): run_tests() if __name__ == '__main__': parser = ArgumentParser(description='Run unit tests.') parser.add_argument('tests', metavar='TEST', nargs='*', help='list of tests to run, or nothing to run them all') args = parser.parse_args() result = run_tests(args.tests) print("" % ( "xmlns='http//andyet.net/protocol/tests'", result.testsRun, len(result.errors), len(result.failures), result.wasSuccessful())) sys.exit(not result.wasSuccessful()) slixmpp-slix-1.4.2/setup.py000077500000000000000000000056711342457644200157300ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- 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 os from pathlib import Path from subprocess import call, DEVNULL, check_output, CalledProcessError from tempfile import TemporaryFile try: from setuptools import setup except ImportError: from distutils.core import setup from run_tests import TestCommand from slixmpp.version import __version__ VERSION = __version__ DESCRIPTION = ('Slixmpp is an elegant Python library for XMPP (aka Jabber, ' 'Google Talk, etc).') with open('README.rst', encoding='utf8') as readme: LONG_DESCRIPTION = readme.read() CLASSIFIERS = [ 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Topic :: Internet :: XMPP', 'Topic :: Software Development :: Libraries :: Python Modules', ] packages = [str(mod.parent) for mod in Path('slixmpp').rglob('__init__.py')] def check_include(library_name, header): command = [os.environ.get('PKG_CONFIG', 'pkg-config'), '--cflags', library_name] try: cflags = check_output(command).decode('utf-8').split() except FileNotFoundError: print('pkg-config not found.') return False except CalledProcessError: # pkg-config already prints the missing libraries on stderr. return False command = [os.environ.get('CC', 'cc')] + cflags + ['-E', '-'] with TemporaryFile('w+') as c_file: c_file.write('#include <%s>' % header) c_file.seek(0) try: return call(command, stdin=c_file, stdout=DEVNULL, stderr=DEVNULL) == 0 except FileNotFoundError: print('%s headers not found.' % library_name) return False HAS_PYTHON_HEADERS = check_include('python3', 'Python.h') HAS_STRINGPREP_HEADERS = check_include('libidn', 'stringprep.h') ext_modules = None if HAS_PYTHON_HEADERS and HAS_STRINGPREP_HEADERS: try: from Cython.Build import cythonize except ImportError: print('Cython not found, falling back to the slow stringprep module.') else: ext_modules = cythonize('slixmpp/stringprep.pyx') else: print('Falling back to the slow stringprep module.') setup( name="slixmpp", version=VERSION, description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='Florent Le Coz', author_email='louiz@louiz.org', url='https://dev.louiz.org/projects/slixmpp', license='MIT', platforms=['any'], packages=packages, ext_modules=ext_modules, install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules', 'aiohttp'], classifiers=CLASSIFIERS, cmdclass={'test': TestCommand} ) slixmpp-slix-1.4.2/slixmpp/000077500000000000000000000000001342457644200156765ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/__init__.py000066400000000000000000000017241342457644200200130ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging logging.getLogger(__name__).addHandler(logging.NullHandler()) import asyncio # Required for python < 3.7 to use the old ssl implementation # and manage to do starttls as an unintended side effect asyncio.sslproto._is_sslproto_available = lambda: False from slixmpp.stanza import Message, Presence, Iq from slixmpp.jid import JID, InvalidJID from slixmpp.xmlstream.stanzabase import ET, ElementBase, register_stanza_plugin from slixmpp.xmlstream.handler import * from slixmpp.xmlstream import XMLStream from slixmpp.xmlstream.matcher import * from slixmpp.xmlstream.asyncio import asyncio, future_wrapper from slixmpp.basexmpp import BaseXMPP from slixmpp.clientxmpp import ClientXMPP from slixmpp.componentxmpp import ComponentXMPP from slixmpp.version import __version__, __version_info__ slixmpp-slix-1.4.2/slixmpp/api.py000066400000000000000000000164121342457644200170250ustar00rootroot00000000000000from slixmpp.xmlstream import JID class APIWrapper(object): def __init__(self, api, name): self.api = api self.name = name if name not in self.api.settings: self.api.settings[name] = {} def __getattr__(self, attr): """Curry API management commands with the API name.""" if attr == 'name': return self.name elif attr == 'settings': return self.api.settings[self.name] elif attr == 'register': def partial(handler, op, jid=None, node=None, default=False): register = getattr(self.api, attr) return register(handler, self.name, op, jid, node, default) return partial elif attr == 'register_default': def partial(handler, op, jid=None, node=None): return getattr(self.api, attr)(handler, self.name, op) return partial elif attr in ('run', 'restore_default', 'unregister'): def partial(*args, **kwargs): return getattr(self.api, attr)(self.name, *args, **kwargs) return partial return None def __getitem__(self, attr): def partial(jid=None, node=None, ifrom=None, args=None): return self.api.run(self.name, attr, jid, node, ifrom, args) return partial class APIRegistry(object): def __init__(self, xmpp): self._handlers = {} self._handler_defaults = {} self.xmpp = xmpp self.settings = {} def _setup(self, ctype, op): """Initialize the API callback dictionaries. :param string ctype: The name of the API to initialize. :param string op: The API operation to initialize. """ if ctype not in self.settings: self.settings[ctype] = {} if ctype not in self._handler_defaults: self._handler_defaults[ctype] = {} if ctype not in self._handlers: self._handlers[ctype] = {} if op not in self._handlers[ctype]: self._handlers[ctype][op] = {'global': None, 'jid': {}, 'node': {}} def wrap(self, ctype): """Return a wrapper object that targets a specific API.""" return APIWrapper(self, ctype) def purge(self, ctype): """Remove all information for a given API.""" del self.settings[ctype] del self._handler_defaults[ctype] del self._handlers[ctype] def run(self, ctype, op, jid=None, node=None, ifrom=None, args=None): """Execute an API callback, based on specificity. The API callback that is executed is chosen based on the combination of the provided JID and node: JID | node | Handler ============================== Given | Given | Node handler Given | None | JID handler None | None | Global handler A node handler is responsible for servicing a single node at a single JID, while a JID handler may respond for any node at a given JID, and the global handler will answer to any JID+node combination. Handlers should check that the JID ``ifrom`` is authorized to perform the desired action. :param string ctype: The name of the API to use. :param string op: The API operation to perform. :param JID jid: Optionally provide specific JID. :param string node: Optionally provide specific node. :param JID ifrom: Optionally provide the requesting JID. :param tuple args: Optional positional arguments to the handler. """ self._setup(ctype, op) if not jid: jid = self.xmpp.boundjid elif jid and not isinstance(jid, JID): jid = JID(jid) elif jid == JID(''): jid = self.xmpp.boundjid if node is None: node = '' if self.xmpp.is_component: if self.settings[ctype].get('component_bare', False): jid = jid.bare else: jid = jid.full else: if self.settings[ctype].get('client_bare', False): jid = jid.bare else: jid = jid.full jid = JID(jid) handler = self._handlers[ctype][op]['node'].get((jid, node), None) if handler is None: handler = self._handlers[ctype][op]['jid'].get(jid, None) if handler is None: handler = self._handlers[ctype][op].get('global', None) if handler: try: return handler(jid, node, ifrom, args) except TypeError: # To preserve backward compatibility, drop the ifrom # parameter for existing handlers that don't understand it. return handler(jid, node, args) def register(self, handler, ctype, op, jid=None, node=None, default=False): """Register an API callback, with JID+node specificity. The API callback can later be executed based on the specificity of the provided JID+node combination. See :meth:`~ApiRegistry.run` for more details. :param string ctype: The name of the API to use. :param string op: The API operation to perform. :param JID jid: Optionally provide specific JID. :param string node: Optionally provide specific node. """ self._setup(ctype, op) if jid is None and node is None: if handler is None: handler = self._handler_defaults[op] self._handlers[ctype][op]['global'] = handler elif jid is not None and node is None: self._handlers[ctype][op]['jid'][jid] = handler else: self._handlers[ctype][op]['node'][(jid, node)] = handler if default: self.register_default(handler, ctype, op) def register_default(self, handler, ctype, op): """Register a default, global handler for an operation. :param func handler: The default, global handler for the operation. :param string ctype: The name of the API to modify. :param string op: The API operation to use. """ self._setup(ctype, op) self._handler_defaults[ctype][op] = handler def unregister(self, ctype, op, jid=None, node=None): """Remove an API callback. The API callback chosen for removal is based on the specificity of the provided JID+node combination. See :meth:`~ApiRegistry.run` for more details. :param string ctype: The name of the API to use. :param string op: The API operation to perform. :param JID jid: Optionally provide specific JID. :param string node: Optionally provide specific node. """ self._setup(ctype, op) self.register(None, ctype, op, jid, node) def restore_default(self, ctype, op, jid=None, node=None): """Reset an API callback to use a default handler. :param string ctype: The name of the API to use. :param string op: The API operation to perform. :param JID jid: Optionally provide specific JID. :param string node: Optionally provide specific node. """ self.unregister(ctype, op, jid, node) self.register(self._handler_defaults[ctype][op], ctype, op, jid, node) slixmpp-slix-1.4.2/slixmpp/basexmpp.py000066400000000000000000000723111342457644200200730ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ slixmpp.basexmpp ~~~~~~~~~~~~~~~~~~ This module provides the common XMPP functionality for both clients and components. Part of Slixmpp: The Slick XMPP Library :copyright: (c) 2011 Nathanael C. Fritz :license: MIT, see LICENSE for more details """ import asyncio import logging from slixmpp import plugins, roster, stanza from slixmpp.api import APIRegistry from slixmpp.exceptions import IqError, IqTimeout from slixmpp.stanza import Message, Presence, Iq, StreamError from slixmpp.stanza.roster import Roster from slixmpp.xmlstream import XMLStream, JID from slixmpp.xmlstream import ET, register_stanza_plugin from slixmpp.xmlstream.matcher import MatchXPath from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.stanzabase import XML_NS from slixmpp.plugins import PluginManager, load_plugin log = logging.getLogger(__name__) class BaseXMPP(XMLStream): """ The BaseXMPP class adapts the generic XMLStream class for use with XMPP. It also provides a plugin mechanism to easily extend and add support for new XMPP features. :param default_ns: Ensure that the correct default XML namespace is used during initialization. """ def __init__(self, jid='', default_ns='jabber:client', **kwargs): XMLStream.__init__(self, **kwargs) self.default_ns = default_ns self.stream_ns = 'http://etherx.jabber.org/streams' self.namespace_map[self.stream_ns] = 'stream' #: An identifier for the stream as given by the server. self.stream_id = None #: The JabberID (JID) requested for this connection. self.requested_jid = JID(jid) #: The JabberID (JID) used by this connection, #: as set after session binding. This may even be a #: different bare JID than what was requested. self.boundjid = JID(jid) self._expected_server_name = self.boundjid.host self._redirect_attempts = 0 #: The maximum number of consecutive see-other-host #: redirections that will be followed before quitting. self.max_redirects = 5 self.session_bind_event = asyncio.Event() #: A dictionary mapping plugin names to plugins. self.plugin = PluginManager(self) #: Configuration options for whitelisted plugins. #: If a plugin is registered without any configuration, #: and there is an entry here, it will be used. self.plugin_config = {} #: A list of plugins that will be loaded if #: :meth:`register_plugins` is called. self.plugin_whitelist = [] #: The main roster object. This roster supports multiple #: owner JIDs, as in the case for components. For clients #: which only have a single JID, see :attr:`client_roster`. self.roster = roster.Roster(self) self.roster.add(self.boundjid) #: The single roster for the bound JID. This is the #: equivalent of:: #: #: self.roster[self.boundjid.bare] self.client_roster = self.roster[self.boundjid] #: The distinction between clients and components can be #: important, primarily for choosing how to handle the #: ``'to'`` and ``'from'`` JIDs of stanzas. self.is_component = False #: Messages may optionally be tagged with ID values. Setting #: :attr:`use_message_ids` to `True` will assign all outgoing #: messages an ID. Some plugin features require enabling #: this option. self.use_message_ids = False #: Presence updates may optionally be tagged with ID values. #: Setting :attr:`use_message_ids` to `True` will assign all #: outgoing messages an ID. self.use_presence_ids = False #: The API registry is a way to process callbacks based on #: JID+node combinations. Each callback in the registry is #: marked with: #: #: - An API name, e.g. xep_0030 #: - The name of an action, e.g. get_info #: - The JID that will be affected #: - The node that will be affected #: #: API handlers with no JID or node will act as global handlers, #: while those with a JID and no node will service all nodes #: for a JID, and handlers with both a JID and node will be #: used only for that specific combination. The handler that #: provides the most specificity will be used. self.api = APIRegistry(self) #: Flag indicating that the initial presence broadcast has #: been sent. Until this happens, some servers may not #: behave as expected when sending stanzas. self.sentpresence = False #: A reference to :mod:`slixmpp.stanza` to make accessing #: stanza classes easier. self.stanza = stanza self.register_handler( Callback('IM', MatchXPath('{%s}message/{%s}body' % (self.default_ns, self.default_ns)), self._handle_message)) self.register_handler( Callback('IMError', MatchXPath('{%s}message/{%s}error' % (self.default_ns, self.default_ns)), self._handle_message_error)) self.register_handler( Callback('Presence', MatchXPath("{%s}presence" % self.default_ns), self._handle_presence)) self.register_handler( Callback('Stream Error', MatchXPath("{%s}error" % self.stream_ns), self._handle_stream_error)) self.add_event_handler('session_start', self._handle_session_start) self.add_event_handler('disconnected', self._handle_disconnected) self.add_event_handler('presence_available', self._handle_available) self.add_event_handler('presence_dnd', self._handle_available) self.add_event_handler('presence_xa', self._handle_available) self.add_event_handler('presence_chat', self._handle_available) self.add_event_handler('presence_away', self._handle_available) self.add_event_handler('presence_unavailable', self._handle_unavailable) self.add_event_handler('presence_subscribe', self._handle_subscribe) self.add_event_handler('presence_subscribed', self._handle_subscribed) self.add_event_handler('presence_unsubscribe', self._handle_unsubscribe) self.add_event_handler('presence_unsubscribed', self._handle_unsubscribed) self.add_event_handler('roster_subscription_request', self._handle_new_subscription) # Set up the XML stream with XMPP's root stanzas. self.register_stanza(Message) self.register_stanza(Iq) self.register_stanza(Presence) self.register_stanza(StreamError) # Initialize a few default stanza plugins. register_stanza_plugin(Iq, Roster) def start_stream_handler(self, xml): """Save the stream ID once the streams have been established. :param xml: The incoming stream's root element. """ self.stream_id = xml.get('id', '') self.stream_version = xml.get('version', '') self.peer_default_lang = xml.get('{%s}lang' % XML_NS, None) if not self.is_component and not self.stream_version: log.warning('Legacy XMPP 0.9 protocol detected.') self.event('legacy_protocol') def process(self, *, forever=True, timeout=None): self.init_plugins() XMLStream.process(self, forever=forever, timeout=timeout) def init_plugins(self): for name in self.plugin: if not hasattr(self.plugin[name], 'post_inited'): if hasattr(self.plugin[name], 'post_init'): self.plugin[name].post_init() self.plugin[name].post_inited = True def register_plugin(self, plugin, pconfig=None, module=None): """Register and configure a plugin for use in this stream. :param plugin: The name of the plugin class. Plugin names must be unique. :param pconfig: A dictionary of configuration data for the plugin. Defaults to an empty dictionary. :param module: Optional refence to the module containing the plugin class if using custom plugins. """ # Use the global plugin config cache, if applicable if not pconfig: pconfig = self.plugin_config.get(plugin, {}) if not self.plugin.registered(plugin): load_plugin(plugin, module) self.plugin.enable(plugin, pconfig) def register_plugins(self): """Register and initialize all built-in plugins. Optionally, the list of plugins loaded may be limited to those contained in :attr:`plugin_whitelist`. Plugin configurations stored in :attr:`plugin_config` will be used. """ if self.plugin_whitelist: plugin_list = self.plugin_whitelist else: plugin_list = plugins.__all__ for plugin in plugin_list: if plugin in plugins.__all__: self.register_plugin(plugin) else: raise NameError("Plugin %s not in plugins.__all__." % plugin) def __getitem__(self, key): """Return a plugin given its name, if it has been registered.""" if key in self.plugin: return self.plugin[key] else: log.warning("Plugin '%s' is not loaded.", key) return False def get(self, key, default): """Return a plugin given its name, if it has been registered.""" return self.plugin.get(key, default) def Message(self, *args, **kwargs): """Create a Message stanza associated with this stream.""" msg = Message(self, *args, **kwargs) msg['lang'] = self.default_lang return msg def Iq(self, *args, **kwargs): """Create an Iq stanza associated with this stream.""" return Iq(self, *args, **kwargs) def Presence(self, *args, **kwargs): """Create a Presence stanza associated with this stream.""" pres = Presence(self, *args, **kwargs) pres['lang'] = self.default_lang return pres def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None): """Create a new Iq stanza with a given Id and from JID. :param id: An ideally unique ID value for this stanza thread. Defaults to 0. :param ifrom: The from :class:`~slixmpp.xmlstream.jid.JID` to use for this stanza. :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID` for this stanza. :param itype: The :class:`~slixmpp.stanza.iq.Iq`'s type, one of: ``'get'``, ``'set'``, ``'result'``, or ``'error'``. :param iquery: Optional namespace for adding a query element. """ iq = self.Iq() iq['id'] = str(id) iq['to'] = ito iq['from'] = ifrom iq['type'] = itype iq['query'] = iquery return iq def make_iq_get(self, queryxmlns=None, ito=None, ifrom=None, iq=None): """Create an :class:`~slixmpp.stanza.iq.Iq` stanza of type ``'get'``. Optionally, a query element may be added. :param queryxmlns: The namespace of the query to use. :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID` for this stanza. :param ifrom: The ``'from'`` :class:`~slixmpp.xmlstream.jid.JID` to use for this stanza. :param iq: Optionally use an existing stanza instead of generating a new one. """ if not iq: iq = self.Iq() iq['type'] = 'get' iq['query'] = queryxmlns if ito: iq['to'] = ito if ifrom: iq['from'] = ifrom return iq def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None): """ Create an :class:`~slixmpp.stanza.iq.Iq` stanza of type ``'result'`` with the given ID value. :param id: An ideally unique ID value. May use :meth:`new_id()`. :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID` for this stanza. :param ifrom: The ``'from'`` :class:`~slixmpp.xmlstream.jid.JID` to use for this stanza. :param iq: Optionally use an existing stanza instead of generating a new one. """ if not iq: iq = self.Iq() if id is None: id = self.new_id() iq['id'] = id iq['type'] = 'result' if ito: iq['to'] = ito if ifrom: iq['from'] = ifrom return iq def make_iq_set(self, sub=None, ito=None, ifrom=None, iq=None): """ Create an :class:`~slixmpp.stanza.iq.Iq` stanza of type ``'set'``. Optionally, a substanza may be given to use as the stanza's payload. :param sub: Either an :class:`~slixmpp.xmlstream.stanzabase.ElementBase` stanza object or an :class:`~xml.etree.ElementTree.Element` XML object to use as the :class:`~slixmpp.stanza.iq.Iq`'s payload. :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID` for this stanza. :param ifrom: The ``'from'`` :class:`~slixmpp.xmlstream.jid.JID` to use for this stanza. :param iq: Optionally use an existing stanza instead of generating a new one. """ if not iq: iq = self.Iq() iq['type'] = 'set' if sub != None: iq.append(sub) if ito: iq['to'] = ito if ifrom: iq['from'] = ifrom return iq def make_iq_error(self, id, type='cancel', condition='feature-not-implemented', text=None, ito=None, ifrom=None, iq=None): """ Create an :class:`~slixmpp.stanza.iq.Iq` stanza of type ``'error'``. :param id: An ideally unique ID value. May use :meth:`new_id()`. :param type: The type of the error, such as ``'cancel'`` or ``'modify'``. Defaults to ``'cancel'``. :param condition: The error condition. Defaults to ``'feature-not-implemented'``. :param text: A message describing the cause of the error. :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID` for this stanza. :param ifrom: The ``'from'`` :class:`~slixmpp.xmlstream.jid.JID` to use for this stanza. :param iq: Optionally use an existing stanza instead of generating a new one. """ if not iq: iq = self.Iq() iq['id'] = id iq['error']['type'] = type iq['error']['condition'] = condition iq['error']['text'] = text if ito: iq['to'] = ito if ifrom: iq['from'] = ifrom return iq def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None): """ Create or modify an :class:`~slixmpp.stanza.iq.Iq` stanza to use the given query namespace. :param iq: Optionally use an existing stanza instead of generating a new one. :param xmlns: The query's namespace. :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID` for this stanza. :param ifrom: The ``'from'`` :class:`~slixmpp.xmlstream.jid.JID` to use for this stanza. """ if not iq: iq = self.Iq() iq['query'] = xmlns if ito: iq['to'] = ito if ifrom: iq['from'] = ifrom return iq def make_query_roster(self, iq=None): """Create a roster query element. :param iq: Optionally use an existing stanza instead of generating a new one. """ if iq: iq['query'] = 'jabber:iq:roster' return ET.Element("{jabber:iq:roster}query") def make_message(self, mto, mbody=None, msubject=None, mtype=None, mhtml=None, mfrom=None, mnick=None): """ Create and initialize a new :class:`~slixmpp.stanza.message.Message` stanza. :param mto: The recipient of the message. :param mbody: The main contents of the message. :param msubject: Optional subject for the message. :param mtype: The message's type, such as ``'chat'`` or ``'groupchat'``. :param mhtml: Optional HTML body content in the form of a string. :param mfrom: The sender of the message. if sending from a client, be aware that some servers require that the full JID of the sender be used. :param mnick: Optional nickname of the sender. """ message = self.Message(sto=mto, stype=mtype, sfrom=mfrom) message['body'] = mbody message['subject'] = msubject if mnick is not None: message['nick'] = mnick if mhtml is not None: message['html']['body'] = mhtml return message def make_presence(self, pshow=None, pstatus=None, ppriority=None, pto=None, ptype=None, pfrom=None, pnick=None): """ Create and initialize a new :class:`~slixmpp.stanza.presence.Presence` stanza. :param pshow: The presence's show value. :param pstatus: The presence's status message. :param ppriority: This connection's priority. :param pto: The recipient of a directed presence. :param ptype: The type of presence, such as ``'subscribe'``. :param pfrom: The sender of the presence. :param pnick: Optional nickname of the presence's sender. """ presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto) if pshow is not None: presence['type'] = pshow if pfrom is None and self.is_component: presence['from'] = self.boundjid.full presence['priority'] = ppriority presence['status'] = pstatus presence['nick'] = pnick return presence def send_message(self, mto, mbody, msubject=None, mtype=None, mhtml=None, mfrom=None, mnick=None): """ Create, initialize, and send a new :class:`~slixmpp.stanza.message.Message` stanza. :param mto: The recipient of the message. :param mbody: The main contents of the message. :param msubject: Optional subject for the message. :param mtype: The message's type, such as ``'chat'`` or ``'groupchat'``. :param mhtml: Optional HTML body content in the form of a string. :param mfrom: The sender of the message. if sending from a client, be aware that some servers require that the full JID of the sender be used. :param mnick: Optional nickname of the sender. """ self.make_message(mto, mbody, msubject, mtype, mhtml, mfrom, mnick).send() def send_presence(self, pshow=None, pstatus=None, ppriority=None, pto=None, pfrom=None, ptype=None, pnick=None): """ Create, initialize, and send a new :class:`~slixmpp.stanza.presence.Presence` stanza. :param pshow: The presence's show value. :param pstatus: The presence's status message. :param ppriority: This connection's priority. :param pto: The recipient of a directed presence. :param ptype: The type of presence, such as ``'subscribe'``. :param pfrom: The sender of the presence. :param pnick: Optional nickname of the presence's sender. """ self.make_presence(pshow, pstatus, ppriority, pto, ptype, pfrom, pnick).send() def send_presence_subscription(self, pto, pfrom=None, ptype='subscribe', pnick=None): """ Create, initialize, and send a new :class:`~slixmpp.stanza.presence.Presence` stanza of type ``'subscribe'``. :param pto: The recipient of a directed presence. :param pfrom: The sender of the presence. :param ptype: The type of presence, such as ``'subscribe'``. :param pnick: Optional nickname of the presence's sender. """ self.make_presence(ptype=ptype, pfrom=pfrom, pto=JID(pto).bare, pnick=pnick).send() @property def jid(self): """Attribute accessor for bare jid""" log.warning("jid property deprecated. Use boundjid.bare") return self.boundjid.bare @jid.setter def jid(self, value): log.warning("jid property deprecated. Use boundjid.bare") self.boundjid.bare = value @property def fulljid(self): """Attribute accessor for full jid""" log.warning("fulljid property deprecated. Use boundjid.full") return self.boundjid.full @fulljid.setter def fulljid(self, value): log.warning("fulljid property deprecated. Use boundjid.full") self.boundjid.full = value @property def resource(self): """Attribute accessor for jid resource""" log.warning("resource property deprecated. Use boundjid.resource") return self.boundjid.resource @resource.setter def resource(self, value): log.warning("fulljid property deprecated. Use boundjid.resource") self.boundjid.resource = value @property def username(self): """Attribute accessor for jid usernode""" log.warning("username property deprecated. Use boundjid.user") return self.boundjid.user @username.setter def username(self, value): log.warning("username property deprecated. Use boundjid.user") self.boundjid.user = value @property def server(self): """Attribute accessor for jid host""" log.warning("server property deprecated. Use boundjid.host") return self.boundjid.server @server.setter def server(self, value): log.warning("server property deprecated. Use boundjid.host") self.boundjid.server = value @property def auto_authorize(self): """Auto accept or deny subscription requests. If ``True``, auto accept subscription requests. If ``False``, auto deny subscription requests. If ``None``, don't automatically respond. """ return self.roster.auto_authorize @auto_authorize.setter def auto_authorize(self, value): self.roster.auto_authorize = value @property def auto_subscribe(self): """Auto send requests for mutual subscriptions. If ``True``, auto send mutual subscription requests. """ return self.roster.auto_subscribe @auto_subscribe.setter def auto_subscribe(self, value): self.roster.auto_subscribe = value def set_jid(self, jid): """Rip a JID apart and claim it as our own.""" log.debug("setting jid to %s", jid) self.boundjid = JID(jid) def getjidresource(self, fulljid): if '/' in fulljid: return fulljid.split('/', 1)[-1] else: return '' def getjidbare(self, fulljid): return fulljid.split('/', 1)[0] def _handle_session_start(self, event): """Reset redirection attempt count.""" self._redirect_attempts = 0 def _handle_disconnected(self, event): """When disconnected, reset the roster""" self.roster.reset() self.session_bind_event.clear() def _handle_stream_error(self, error): self.event('stream_error', error) if error['condition'] == 'see-other-host': other_host = error['see_other_host'] if not other_host: log.warning("No other host specified.") return if self._redirect_attempts > self.max_redirects: log.error("Exceeded maximum number of redirection attempts.") return self._redirect_attempts += 1 host = other_host port = 5222 if '[' in other_host and ']' in other_host: host = other_host.split(']')[0][1:] elif ':' in other_host: host = other_host.split(':')[0] port_sec = other_host.split(']')[-1] if ':' in port_sec: port = int(port_sec.split(':')[1]) self.address = (host, port) self.default_domain = host self.dns_records = None self.reconnect() def _handle_message(self, msg): """Process incoming message stanzas.""" if not self.is_component and not msg['to'].bare: msg['to'] = self.boundjid self.event('message', msg) def _handle_message_error(self, msg): """Process incoming message error stanzas.""" if not self.is_component and not msg['to'].bare: msg['to'] = self.boundjid self.event('message_error', msg) def _handle_available(self, pres): self.roster[pres['to']][pres['from']].handle_available(pres) def _handle_unavailable(self, pres): self.roster[pres['to']][pres['from']].handle_unavailable(pres) def _handle_new_subscription(self, pres): """Attempt to automatically handle subscription requests. Subscriptions will be approved if the request is from a whitelisted JID, of :attr:`auto_authorize` is True. They will be rejected if :attr:`auto_authorize` is False. Setting :attr:`auto_authorize` to ``None`` will disable automatic subscription handling (except for whitelisted JIDs). If a subscription is accepted, a request for a mutual subscription will be sent if :attr:`auto_subscribe` is ``True``. """ roster = self.roster[pres['to']] item = self.roster[pres['to']][pres['from']] if item['whitelisted']: item.authorize() if roster.auto_subscribe: item.subscribe() elif roster.auto_authorize: item.authorize() if roster.auto_subscribe: item.subscribe() elif roster.auto_authorize == False: item.unauthorize() def _handle_removed_subscription(self, pres): self.roster[pres['to']][pres['from']].handle_unauthorize(pres) def _handle_subscribe(self, pres): self.roster[pres['to']][pres['from']].handle_subscribe(pres) def _handle_subscribed(self, pres): self.roster[pres['to']][pres['from']].handle_subscribed(pres) def _handle_unsubscribe(self, pres): self.roster[pres['to']][pres['from']].handle_unsubscribe(pres) def _handle_unsubscribed(self, pres): self.roster[pres['to']][pres['from']].handle_unsubscribed(pres) def _handle_presence(self, presence): """Process incoming presence stanzas. Update the roster with presence information. """ if self.roster[presence['from']].ignore_updates: return if not self.is_component and not presence['to'].bare: presence['to'] = self.boundjid self.event('presence', presence) self.event('presence_%s' % presence['type'], presence) # Check for changes in subscription state. if presence['type'] in ('subscribe', 'subscribed', 'unsubscribe', 'unsubscribed'): self.event('changed_subscription', presence) return elif not presence['type'] in ('available', 'unavailable') and \ not presence['type'] in presence.showtypes: return def exception(self, exception): """Process any uncaught exceptions, notably :class:`~slixmpp.exceptions.IqError` and :class:`~slixmpp.exceptions.IqTimeout` exceptions. :param exception: An unhandled :class:`Exception` object. """ if isinstance(exception, IqError): iq = exception.iq log.error('%s: %s', iq['error']['condition'], iq['error']['text']) log.warning('You should catch IqError exceptions') elif isinstance(exception, IqTimeout): iq = exception.iq log.error('Request timed out: %s', iq) log.warning('You should catch IqTimeout exceptions') elif isinstance(exception, SyntaxError): # Hide stream parsing errors that occur when the # stream is disconnected (they've been handled, we # don't need to make a mess in the logs). pass else: log.exception(exception) slixmpp-slix-1.4.2/slixmpp/clientxmpp.py000066400000000000000000000277171342457644200204510ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ slixmpp.clientxmpp ~~~~~~~~~~~~~~~~~~~~ This module provides XMPP functionality that is specific to client connections. Part of Slixmpp: The Slick XMPP Library :copyright: (c) 2011 Nathanael C. Fritz :license: MIT, see LICENSE for more details """ import asyncio import logging from slixmpp.jid import JID from slixmpp.stanza import StreamFeatures from slixmpp.basexmpp import BaseXMPP from slixmpp.exceptions import XMPPError from slixmpp.xmlstream import XMLStream from slixmpp.xmlstream.matcher import StanzaPath, MatchXPath from slixmpp.xmlstream.handler import Callback, CoroutineCallback # 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): """ Slixmpp's client class. (Use only for good, not for evil.) Typical use pattern: .. code-block:: python xmpp = ClientXMPP('user@server.tld/resource', 'password') # ... Register plugins and event handlers ... xmpp.connect() xmpp.process(block=False) # block=True will block the current # thread. By default, block=False :param jid: The JID of the XMPP user account. :param password: The password for the XMPP user account. :param plugin_config: A dictionary of plugin configurations. :param plugin_whitelist: A list of approved plugins that will be loaded when calling :meth:`~slixmpp.basexmpp.BaseXMPP.register_plugins()`. :param escape_quotes: **Deprecated.** """ def __init__(self, jid, password, plugin_config=None, plugin_whitelist=None, escape_quotes=True, sasl_mech=None, lang='en', **kwargs): if not plugin_whitelist: plugin_whitelist = [] if not plugin_config: plugin_config = {} BaseXMPP.__init__(self, jid, 'jabber:client', **kwargs) self.escape_quotes = escape_quotes self.plugin_config = plugin_config self.plugin_whitelist = plugin_whitelist self.default_port = 5222 self.default_lang = lang self.credentials = {} self.password = password self.stream_header = "" % ( self.boundjid.host, "xmlns:stream='%s'" % self.stream_ns, "xmlns='%s'" % self.default_ns, "xml:lang='%s'" % self.default_lang, "version='1.0'") self.stream_footer = "" self.features = set() self._stream_feature_handlers = {} self._stream_feature_order = [] self.dns_service = 'xmpp-client' #TODO: Use stream state here self.authenticated = False self.sessionstarted = False self.bound = False self.bindfail = False self.add_event_handler('connected', self._reset_connection_state) self.add_event_handler('session_bind', self._handle_session_bind) self.add_event_handler('roster_update', self._handle_roster) self.register_stanza(StreamFeatures) self.register_handler( CoroutineCallback('Stream Features', MatchXPath('{%s}features' % self.stream_ns), self._handle_stream_features)) def roster_push_filter(iq): from_ = iq['from'] if from_ and from_ != JID('') and from_ != self.boundjid.bare: reply = iq.reply() reply['type'] = 'error' reply['error']['type'] = 'cancel' reply['error']['code'] = 503 reply['error']['condition'] = 'service-unavailable' reply.send() return self.event('roster_update', iq) self.register_handler( Callback('Roster Update', StanzaPath('iq@type=set/roster'), roster_push_filter)) # Setup default stream features self.register_plugin('feature_starttls') self.register_plugin('feature_bind') self.register_plugin('feature_session') self.register_plugin('feature_rosterver') self.register_plugin('feature_preapproval') self.register_plugin('feature_mechanisms') if sasl_mech: self['feature_mechanisms'].use_mech = sasl_mech @property def password(self): return self.credentials.get('password', '') @password.setter def password(self, value): self.credentials['password'] = value def connect(self, address=tuple(), use_ssl=False, force_starttls=True, disable_starttls=False): """Connect to the XMPP server. When no address is given, a SRV lookup for the server will be attempted. If that fails, the server user in the JID will be used. :param address: A tuple containing the server's host and port. :param force_starttls: Indicates that negotiation should be aborted if the server does not advertise support for STARTTLS. Defaults to ``True``. :param disable_starttls: Disables TLS for the connection. Defaults to ``False``. :param use_ssl: Indicates if the older SSL connection method should be used. Defaults to ``False``. """ # If an address was provided, disable using DNS SRV lookup; # otherwise, use the domain from the client JID with the standard # XMPP client port and allow SRV lookup. if address: self.dns_service = None else: address = (self.boundjid.host, 5222) self.dns_service = 'xmpp-client' return XMLStream.connect(self, address[0], address[1], use_ssl=use_ssl, force_starttls=force_starttls, disable_starttls=disable_starttls) def register_feature(self, name, handler, restart=False, order=5000): """Register a stream feature handler. :param name: The name of the stream feature. :param handler: The function to execute if the feature is received. :param restart: Indicates if feature processing should halt with this feature. Defaults to ``False``. :param order: The relative ordering in which the feature should be negotiated. Lower values will be attempted earlier when available. """ self._stream_feature_handlers[name] = (handler, restart) self._stream_feature_order.append((order, name)) self._stream_feature_order.sort() def unregister_feature(self, name, order): if name in self._stream_feature_handlers: del self._stream_feature_handlers[name] self._stream_feature_order.remove((order, name)) self._stream_feature_order.sort() def update_roster(self, jid, **kwargs): """Add or change a roster item. :param jid: The JID of the entry to modify. :param name: The user's nickname for this JID. :param subscription: The subscription status. May be one of ``'to'``, ``'from'``, ``'both'``, or ``'none'``. If set to ``'remove'``, the entry will be deleted. :param groups: The roster groups that contain this item. :param timeout: The length of time (in seconds) to wait for a response before continuing if blocking is used. Defaults to :attr:`~slixmpp.xmlstream.xmlstream.XMLStream.response_timeout`. :param callback: Optional reference to a stream handler function. Will be executed when the roster is received. Implies ``block=False``. """ current = self.client_roster[jid] name = kwargs.get('name', current['name']) subscription = kwargs.get('subscription', current['subscription']) groups = kwargs.get('groups', current['groups']) timeout = kwargs.get('timeout', None) callback = kwargs.get('callback', None) return self.client_roster.update(jid, name, subscription, groups, timeout, callback) def del_roster_item(self, jid): """Remove an item from the roster. This is done by setting its subscription status to ``'remove'``. :param jid: The JID of the item to remove. """ return self.client_roster.remove(jid) def get_roster(self, callback=None, timeout=None, timeout_callback=None): """Request the roster from the server. :param callback: Reference to a stream handler function. Will be executed when the roster is received. """ iq = self.Iq() iq['type'] = 'get' iq.enable('roster') if 'rosterver' in self.features: iq['roster']['ver'] = self.client_roster.version if callback is None: callback = lambda resp: self.event('roster_update', resp) else: orig_cb = callback def wrapped(resp): self.event('roster_update', resp) orig_cb(resp) callback = wrapped return iq.send(callback, timeout, timeout_callback) def _reset_connection_state(self, event=None): #TODO: Use stream state here self.authenticated = False self.sessionstarted = False self.bound = False self.bindfail = False self.features = set() async def _handle_stream_features(self, features): """Process the received stream features. :param features: The features stanza. """ for order, name in self._stream_feature_order: if name in features['features']: handler, restart = self._stream_feature_handlers[name] if asyncio.iscoroutinefunction(handler): result = await handler(features) else: result = handler(features) if result and restart: # Don't continue if the feature requires # restarting the XML stream. return True log.debug('Finished processing stream features.') self.event('stream_negotiated') def _handle_roster(self, iq): """Update the roster after receiving a roster stanza. :param iq: The roster stanza. """ if iq['type'] == 'set': if iq['from'].bare and iq['from'].bare != self.boundjid.bare: raise XMPPError(condition='service-unavailable') roster = self.client_roster if iq['roster']['ver']: roster.version = iq['roster']['ver'] items = iq['roster']['items'] valid_subscriptions = ('to', 'from', 'both', 'none', 'remove') for jid, item in items.items(): if item['subscription'] in valid_subscriptions: 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') roster[jid].save(remove=(item['subscription'] == 'remove')) if iq['type'] == 'set': resp = self.Iq(stype='result', sto=iq['from'], sid=iq['id']) resp.enable('roster') resp.send() def _handle_session_bind(self, jid): """Set the client roster to the JID set by the server. :param :class:`slixmpp.xmlstream.jid.JID` jid: The bound JID as dictated by the server. The same as :attr:`boundjid`. """ self.client_roster = self.roster[jid] slixmpp-slix-1.4.2/slixmpp/componentxmpp.py000066400000000000000000000115111342457644200211560ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ slixmpp.clientxmpp ~~~~~~~~~~~~~~~~~~~~ This module provides XMPP functionality that is specific to external server component connections. Part of Slixmpp: The Slick XMPP Library :copyright: (c) 2011 Nathanael C. Fritz :license: MIT, see LICENSE for more details """ import logging import hashlib from slixmpp.basexmpp import BaseXMPP from slixmpp.xmlstream import XMLStream from slixmpp.xmlstream import ET from slixmpp.xmlstream.matcher import MatchXPath from slixmpp.xmlstream.handler import Callback log = logging.getLogger(__name__) class ComponentXMPP(BaseXMPP): """ Slixmpp's basic XMPP server component. Use only for good, not for evil. :param jid: The JID of the component. :param secret: The secret or password for the component. :param host: The server accepting the component. :param port: The port used to connect to the server. :param plugin_config: A dictionary of plugin configurations. :param plugin_whitelist: A list of approved plugins that will be loaded when calling :meth:`~slixmpp.basexmpp.BaseXMPP.register_plugins()`. :param use_jc_ns: Indicates if the ``'jabber:client'`` namespace should be used instead of the standard ``'jabber:component:accept'`` namespace. Defaults to ``False``. """ def __init__(self, jid, secret, host=None, port=None, plugin_config=None, plugin_whitelist=None, use_jc_ns=False): if not plugin_whitelist: plugin_whitelist = [] if not plugin_config: plugin_config = {} if use_jc_ns: default_ns = 'jabber:client' else: default_ns = 'jabber:component:accept' BaseXMPP.__init__(self, jid, default_ns) self.auto_authorize = None self.stream_header = '' % ( 'xmlns="jabber:component:accept"', 'xmlns:stream="%s"' % self.stream_ns, jid) self.stream_footer = "" 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.sessionstarted = False 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, host=None, port=None, use_ssl=False): """Connect to the server. :param host: The name of the desired server for the connection. Defaults to :attr:`server_host`. :param port: Port to connect to on the server. Defauts to :attr:`server_port`. :param use_ssl: Flag indicating if SSL should be used by connecting directly to a port using SSL. """ if host is None: host = self.server_host if port is None: port = self.server_port self.server_name = self.boundjid.host log.debug("Connecting to %s:%s", host, port) return XMLStream.connect(self, host=host, port=port, use_ssl=use_ssl) def incoming_filter(self, xml): """ Pre-process incoming XML stanzas by converting any ``'jabber:client'`` namespaced elements to the component's default namespace. :param xml: The XML stanza to pre-process. """ if xml.tag.startswith('{jabber:client}'): xml.tag = xml.tag.replace('jabber:client', self.default_ns) 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. :param xml: The incoming stream's root element. """ BaseXMPP.start_stream_handler(self, xml) # Construct a hash of the stream ID and the component secret. sid = xml.get('id', '') pre_hash = bytes('%s%s' % (sid, self.secret), 'utf-8') handshake = ET.Element('{jabber:component:accept}handshake') handshake.text = hashlib.sha1(pre_hash).hexdigest().lower() self.send_xml(handshake) def _handle_handshake(self, xml): """The handshake has been accepted. :param xml: The reply handshake stanza. """ self.session_bind_event.set() self.sessionstarted = True self.event('session_bind', self.boundjid) self.event('session_start') def _handle_probe(self, pres): self.roster[pres['to']][pres['from']].handle_probe(pres) slixmpp-slix-1.4.2/slixmpp/exceptions.py000066400000000000000000000064671342457644200204460ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ slixmpp.exceptions ~~~~~~~~~~~~~~~~~~~~ Part of Slixmpp: The Slick XMPP Library :copyright: (c) 2011 Nathanael C. Fritz :license: MIT, see LICENSE for more details """ 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 :class:`~slixmpp.stanza.rootstanza.RootStanza` will create an error stanza and initialize any additional substanzas using the extension information included in the exception. Meant for use in Slixmpp plugins and applications using Slixmpp. Extension information can be included to add additional XML elements to the generated error stanza. :param condition: The XMPP defined error condition. Defaults to ``'undefined-condition'``. :param text: Human readable text describing the error. :param etype: The XMPP error type, such as ``'cancel'`` or ``'modify'``. Defaults to ``'cancel'``. :param extension: Tag name of the extension's XML content. :param extension_ns: XML namespace of the extensions' XML content. :param extension_args: Content and attributes for the extension element. Same as the additional arguments to the :class:`~xml.etree.ElementTree.Element` constructor. :param clear: Indicates if the stanza's contents should be removed before replying with an error. Defaults to ``True``. """ def __init__(self, condition='undefined-condition', text='', etype='cancel', extension=None, extension_ns=None, extension_args=None, clear=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 def format(self): """ Format the error in a simple user-readable string. """ text = [self.etype, self.condition] if self.text: text.append(self.text) if self.extension: text.append(self.extension) # TODO: handle self.extension_args return ': '.join(text) 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().__init__( condition='remote-server-timeout', etype='cancel') #: The :class:`~slixmpp.stanza.iq.Iq` stanza whose response #: did not arrive before the timeout expired. self.iq = iq class IqError(XMPPError): """ An exception raised when an Iq stanza of type 'error' is received after making a blocking send call. """ def __init__(self, iq): super().__init__( condition=iq['error']['condition'], text=iq['error']['text'], etype=iq['error']['type']) #: The :class:`~slixmpp.stanza.iq.Iq` error result stanza. self.iq = iq slixmpp-slix-1.4.2/slixmpp/features/000077500000000000000000000000001342457644200175145ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/features/__init__.py000066400000000000000000000005121342457644200216230ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ __all__ = [ 'feature_starttls', 'feature_mechanisms', 'feature_bind', 'feature_session', 'feature_rosterver', 'feature_preapproval' ] slixmpp-slix-1.4.2/slixmpp/features/feature_bind/000077500000000000000000000000001342457644200221435ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/features/feature_bind/__init__.py000066400000000000000000000005561342457644200242620ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.features.feature_bind.bind import FeatureBind from slixmpp.features.feature_bind.stanza import Bind register_plugin(FeatureBind) slixmpp-slix-1.4.2/slixmpp/features/feature_bind/bind.py000066400000000000000000000037121342457644200234340ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import asyncio import logging from slixmpp.jid import JID from slixmpp.stanza import Iq, StreamFeatures from slixmpp.features.feature_bind import stanza from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins import BasePlugin log = logging.getLogger(__name__) class FeatureBind(BasePlugin): name = 'feature_bind' description = 'RFC 6120: Stream Feature: Resource Binding' dependencies = set() stanza = stanza def plugin_init(self): 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) async def _handle_bind_resource(self, features): """ Handle requesting a specific resource. Arguments: features -- The stream features stanza. """ log.debug("Requesting resource: %s", self.xmpp.requested_jid.resource) self.features = features iq = self.xmpp.Iq() iq['type'] = 'set' iq.enable('bind') if self.xmpp.requested_jid.resource: iq['bind']['resource'] = self.xmpp.requested_jid.resource await iq.send(callback=self._on_bind_response) def _on_bind_response(self, response): self.xmpp.boundjid = JID(response['bind']['jid']) self.xmpp.bound = True self.xmpp.event('session_bind', self.xmpp.boundjid) self.xmpp.session_bind_event.set() self.xmpp.features.add('bind') log.info("JID set to: %s", self.xmpp.boundjid.full) if 'session' not in self.features['features']: log.debug("Established Session") self.xmpp.sessionstarted = True self.xmpp.event('session_start') slixmpp-slix-1.4.2/slixmpp/features/feature_bind/stanza.py000066400000000000000000000006501342457644200240160ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase class Bind(ElementBase): """ """ name = 'bind' namespace = 'urn:ietf:params:xml:ns:xmpp-bind' interfaces = {'resource', 'jid'} sub_interfaces = interfaces plugin_attrib = 'bind' slixmpp-slix-1.4.2/slixmpp/features/feature_mechanisms/000077500000000000000000000000001342457644200233565ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/features/feature_mechanisms/__init__.py000066400000000000000000000011141342457644200254640ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.features.feature_mechanisms.mechanisms import FeatureMechanisms from slixmpp.features.feature_mechanisms.stanza import Mechanisms from slixmpp.features.feature_mechanisms.stanza import Auth from slixmpp.features.feature_mechanisms.stanza import Success from slixmpp.features.feature_mechanisms.stanza import Failure register_plugin(FeatureMechanisms) slixmpp-slix-1.4.2/slixmpp/features/feature_mechanisms/mechanisms.py000066400000000000000000000213601342457644200260610ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import ssl import logging from slixmpp.util import sasl from slixmpp.util.stringprep_profiles import StringPrepError from slixmpp.stanza import StreamFeatures from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins import BasePlugin from slixmpp.xmlstream.matcher import MatchXPath from slixmpp.xmlstream.handler import Callback from slixmpp.features.feature_mechanisms import stanza log = logging.getLogger(__name__) class FeatureMechanisms(BasePlugin): name = 'feature_mechanisms' description = 'RFC 6120: Stream Feature: SASL' dependencies = set() stanza = stanza default_config = { 'use_mech': None, 'use_mechs': None, 'min_mech': None, 'sasl_callback': None, 'security_callback': None, 'encrypted_plain': True, 'unencrypted_plain': False, 'unencrypted_digest': False, 'unencrypted_cram': False, 'unencrypted_scram': True, 'order': 100 } def plugin_init(self): if self.sasl_callback is None: self.sasl_callback = self._default_credentials if self.security_callback is None: self.security_callback = self._default_security creds = self.sasl_callback({'username'}, set()) if not self.use_mech and not creds['username']: self.use_mech = 'ANONYMOUS' self.mech = None self.mech_list = set() self.attempted_mechs = set() 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_stanza(stanza.Abort) self.xmpp.register_handler( Callback('SASL Success', MatchXPath(stanza.Success.tag_name()), self._handle_success, instream=True)) self.xmpp.register_handler( Callback('SASL Failure', MatchXPath(stanza.Failure.tag_name()), self._handle_fail, instream=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.order) def _default_credentials(self, required_values, optional_values): creds = self.xmpp.credentials result = {} values = required_values.union(optional_values) for value in values: if value == 'username': result[value] = creds.get('username', self.xmpp.requested_jid.user) elif value == 'email': jid = self.xmpp.requested_jid.bare result[value] = creds.get('email', jid) elif value == 'channel_binding': if isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)): result[value] = self.xmpp.socket.get_channel_binding() else: result[value] = None elif value == 'host': result[value] = creds.get('host', self.xmpp.requested_jid.domain) elif value == 'realm': result[value] = creds.get('realm', self.xmpp.requested_jid.domain) elif value == 'service-name': result[value] = creds.get('service-name', self.xmpp._service_name) elif value == 'service': result[value] = creds.get('service', 'xmpp') elif value in creds: result[value] = creds[value] return result def _default_security(self, values): result = {} for value in values: if value == 'encrypted': if 'starttls' in self.xmpp.features: result[value] = True elif isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)): result[value] = True else: result[value] = False else: result[value] = self.config.get(value, False) return result 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 enforce_limit = False limited_mechs = self.use_mechs if limited_mechs is None: limited_mechs = set() elif limited_mechs and not isinstance(limited_mechs, set): limited_mechs = set(limited_mechs) enforce_limit = True if self.use_mech: limited_mechs.add(self.use_mech) enforce_limit = True if enforce_limit: self.use_mechs = limited_mechs self.mech_list = set(features['mechanisms']) return self._send_auth() def _send_auth(self): mech_list = self.mech_list - self.attempted_mechs try: self.mech = sasl.choose(mech_list, self.sasl_callback, self.security_callback, limit=self.use_mechs, min_mech=self.min_mech) except sasl.SASLNoAppropriateMechanism: log.error("No appropriate login method.") self.xmpp.event("failed_all_auth") if not self.attempted_mechs: # Only trigger this event if we didn't try at least one # method self.xmpp.event("no_auth") self.attempted_mechs = set() return self.xmpp.disconnect() except StringPrepError: log.exception("A credential value did not pass SASLprep.") self.xmpp.disconnect() resp = stanza.Auth(self.xmpp) resp['mechanism'] = self.mech.name try: resp['value'] = self.mech.process() except sasl.SASLCancelled: self.attempted_mechs.add(self.mech.name) self._send_auth() except sasl.SASLMutualAuthFailed: log.error("Mutual authentication failed! " + \ "A security breach is possible.") self.attempted_mechs.add(self.mech.name) self.xmpp.disconnect() except sasl.SASLFailed: self.attempted_mechs.add(self.mech.name) self._send_auth() else: resp.send() return True def _handle_challenge(self, stanza): """SASL challenge received. Process and send response.""" resp = self.stanza.Response(self.xmpp) try: resp['value'] = self.mech.process(stanza['value']) except sasl.SASLCancelled: self.stanza.Abort(self.xmpp).send() except sasl.SASLMutualAuthFailed: log.error("Mutual authentication failed! " + \ "A security breach is possible.") self.attempted_mechs.add(self.mech.name) self.xmpp.disconnect() except sasl.SASLFailed: self.stanza.Abort(self.xmpp).send() else: if resp.get_value() == '': resp.del_value() resp.send() def _handle_success(self, stanza): """SASL authentication succeeded. Restart the stream.""" try: final = self.mech.process(stanza['value']) except sasl.SASLMutualAuthFailed: log.error("Mutual authentication failed! " + \ "A security breach is possible.") self.attempted_mechs.add(self.mech.name) self.xmpp.disconnect() else: self.attempted_mechs = set() self.xmpp.authenticated = True self.xmpp.features.add('mechanisms') self.xmpp.event('auth_success', stanza) # Restart the stream self.xmpp.init_parser() self.xmpp.send_raw(self.xmpp.stream_header) def _handle_fail(self, stanza): """SASL authentication failed. Disconnect and shutdown.""" self.attempted_mechs.add(self.mech.name) log.info("Authentication failed: %s", stanza['condition']) self.xmpp.event("failed_auth", stanza) self._send_auth() return True slixmpp-slix-1.4.2/slixmpp/features/feature_mechanisms/stanza/000077500000000000000000000000001342457644200246565ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/features/feature_mechanisms/stanza/__init__.py000066400000000000000000000012401342457644200267640ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.features.feature_mechanisms.stanza.mechanisms import Mechanisms from slixmpp.features.feature_mechanisms.stanza.auth import Auth from slixmpp.features.feature_mechanisms.stanza.success import Success from slixmpp.features.feature_mechanisms.stanza.failure import Failure from slixmpp.features.feature_mechanisms.stanza.challenge import Challenge from slixmpp.features.feature_mechanisms.stanza.response import Response from slixmpp.features.feature_mechanisms.stanza.abort import Abort slixmpp-slix-1.4.2/slixmpp/features/feature_mechanisms/stanza/abort.py000066400000000000000000000007361342457644200263450ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import StanzaBase class Abort(StanzaBase): """ """ name = 'abort' 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() slixmpp-slix-1.4.2/slixmpp/features/feature_mechanisms/stanza/auth.py000066400000000000000000000023251342457644200261730ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import base64 from slixmpp.util import bytes from slixmpp.xmlstream import StanzaBase class Auth(StanzaBase): """ """ name = 'auth' namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' interfaces = {'mechanism', 'value'} plugin_attrib = name #: Some SASL mechs require sending values as is, #: without converting base64. plain_mechs = {'X-MESSENGER-OAUTH2'} def setup(self, xml): StanzaBase.setup(self, xml) self.xml.tag = self.tag_name() def get_value(self): if not self['mechanism'] in self.plain_mechs: return base64.b64decode(bytes(self.xml.text)) else: return self.xml.text def set_value(self, values): if not self['mechanism'] in self.plain_mechs: if values: self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') elif values == b'': self.xml.text = '=' else: self.xml.text = bytes(values).decode('utf-8') def del_value(self): self.xml.text = '' slixmpp-slix-1.4.2/slixmpp/features/feature_mechanisms/stanza/challenge.py000066400000000000000000000015141342457644200271530ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import base64 from slixmpp.util import bytes from slixmpp.xmlstream import StanzaBase class Challenge(StanzaBase): """ """ name = 'challenge' namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' interfaces = {'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): if values: self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') else: self.xml.text = '=' def del_value(self): self.xml.text = '' slixmpp-slix-1.4.2/slixmpp/features/feature_mechanisms/stanza/failure.py000066400000000000000000000045221342457644200266620ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import StanzaBase, ET class Failure(StanzaBase): """ """ name = 'failure' namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' interfaces = {'condition', 'text'} plugin_attrib = name sub_interfaces = {'text'} conditions = {'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: 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: 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 slixmpp-slix-1.4.2/slixmpp/features/feature_mechanisms/stanza/mechanisms.py000066400000000000000000000023021342457644200273540ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase, ET class Mechanisms(ElementBase): """ """ name = 'mechanisms' namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' interfaces = {'mechanisms', 'required'} plugin_attrib = name is_extension = True def get_required(self): """ """ return True def get_mechanisms(self): """ """ results = [] mechs = self.xml.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.xml.findall('{%s}mechanism' % self.namespace) if mechs: for mech in mechs: self.xml.remove(mech) slixmpp-slix-1.4.2/slixmpp/features/feature_mechanisms/stanza/response.py000066400000000000000000000015121342457644200270650ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import base64 from slixmpp.util import bytes from slixmpp.xmlstream import StanzaBase class Response(StanzaBase): """ """ name = 'response' namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' interfaces = {'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): if values: self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') else: self.xml.text = '=' def del_value(self): self.xml.text = '' slixmpp-slix-1.4.2/slixmpp/features/feature_mechanisms/stanza/success.py000066400000000000000000000015071342457644200267030ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import base64 from slixmpp.util import bytes from slixmpp.xmlstream import StanzaBase class Success(StanzaBase): """ """ name = 'success' namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' interfaces = {'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): if values: self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') else: self.xml.text = '=' def del_value(self): self.xml.text = '' slixmpp-slix-1.4.2/slixmpp/features/feature_preapproval/000077500000000000000000000000001342457644200235625ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/features/feature_preapproval/__init__.py000066400000000000000000000006301342457644200256720ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.features.feature_preapproval.preapproval import FeaturePreApproval from slixmpp.features.feature_preapproval.stanza import PreApproval register_plugin(FeaturePreApproval) slixmpp-slix-1.4.2/slixmpp/features/feature_preapproval/preapproval.py000066400000000000000000000022241342457644200264670ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp.stanza import StreamFeatures from slixmpp.features.feature_preapproval import stanza from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins.base import BasePlugin log = logging.getLogger(__name__) class FeaturePreApproval(BasePlugin): name = 'feature_preapproval' description = 'RFC 6121: Stream Feature: Subscription Pre-Approval' dependences = set() stanza = stanza def plugin_init(self): self.xmpp.register_feature('preapproval', self._handle_preapproval, restart=False, order=9001) register_stanza_plugin(StreamFeatures, stanza.PreApproval) def _handle_preapproval(self, features): """Save notice that the server support subscription pre-approvals. Arguments: features -- The stream features stanza. """ log.debug("Server supports subscription pre-approvals.") self.xmpp.features.add('preapproval') slixmpp-slix-1.4.2/slixmpp/features/feature_preapproval/stanza.py000066400000000000000000000005641342457644200254410ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase class PreApproval(ElementBase): name = 'sub' namespace = 'urn:xmpp:features:pre-approval' interfaces = set() plugin_attrib = 'preapproval' slixmpp-slix-1.4.2/slixmpp/features/feature_rosterver/000077500000000000000000000000001342457644200232625ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/features/feature_rosterver/__init__.py000066400000000000000000000006141342457644200253740ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.features.feature_rosterver.rosterver import FeatureRosterVer from slixmpp.features.feature_rosterver.stanza import RosterVer register_plugin(FeatureRosterVer) slixmpp-slix-1.4.2/slixmpp/features/feature_rosterver/rosterver.py000066400000000000000000000021141342457644200256650ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp.stanza import StreamFeatures from slixmpp.features.feature_rosterver import stanza from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins.base import BasePlugin log = logging.getLogger(__name__) class FeatureRosterVer(BasePlugin): name = 'feature_rosterver' description = 'RFC 6121: Stream Feature: Roster Versioning' dependences = set() stanza = stanza def plugin_init(self): self.xmpp.register_feature('rosterver', self._handle_rosterver, restart=False, order=9000) register_stanza_plugin(StreamFeatures, stanza.RosterVer) def _handle_rosterver(self, features): """Enable using roster versioning. Arguments: features -- The stream features stanza. """ log.debug("Enabling roster versioning.") self.xmpp.features.add('rosterver') slixmpp-slix-1.4.2/slixmpp/features/feature_rosterver/stanza.py000066400000000000000000000005551342457644200251410ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase class RosterVer(ElementBase): name = 'ver' namespace = 'urn:xmpp:features:rosterver' interfaces = set() plugin_attrib = 'rosterver' slixmpp-slix-1.4.2/slixmpp/features/feature_session/000077500000000000000000000000001342457644200227125ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/features/feature_session/__init__.py000066400000000000000000000006001342457644200250170ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.features.feature_session.session import FeatureSession from slixmpp.features.feature_session.stanza import Session register_plugin(FeatureSession) slixmpp-slix-1.4.2/slixmpp/features/feature_session/session.py000066400000000000000000000031041342457644200247450ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import asyncio import logging from slixmpp.stanza import Iq, StreamFeatures from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins import BasePlugin from slixmpp.features.feature_session import stanza log = logging.getLogger(__name__) class FeatureSession(BasePlugin): name = 'feature_session' description = 'RFC 3920: Stream Feature: Start Session' dependencies = set() stanza = stanza def plugin_init(self): 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) async def _handle_start_session(self, features): """ Handle the start of the session. Arguments: feature -- The stream features element. """ if features['session']['optional']: self.xmpp.sessionstarted = True self.xmpp.event('session_start') return iq = self.xmpp.Iq() iq['type'] = 'set' iq.enable('session') await iq.send(callback=self._on_start_session_response) def _on_start_session_response(self, response): self.xmpp.features.add('session') log.debug("Established Session") self.xmpp.sessionstarted = True self.xmpp.event('session_start') slixmpp-slix-1.4.2/slixmpp/features/feature_session/stanza.py000066400000000000000000000015061342457644200245660ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase, ET class Session(ElementBase): """ """ name = 'session' namespace = 'urn:ietf:params:xml:ns:xmpp-session' interfaces = {'optional'} plugin_attrib = 'session' def get_optional(self): return self.xml.find('{%s}optional' % self.namespace) is not None def set_optional(self, value): if value: optional = ET.Element('{%s}optional' % self.namespace) self.xml.append(optional) else: self.del_optional() def del_optional(self): optional = self.xml.find('{%s}optional' % self.namespace) self.xml.remove(optional) slixmpp-slix-1.4.2/slixmpp/features/feature_starttls/000077500000000000000000000000001342457644200231075ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/features/feature_starttls/__init__.py000066400000000000000000000005771342457644200252310ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.features.feature_starttls.starttls import FeatureSTARTTLS from slixmpp.features.feature_starttls.stanza import * register_plugin(FeatureSTARTTLS) slixmpp-slix-1.4.2/slixmpp/features/feature_starttls/stanza.py000066400000000000000000000013651342457644200247660ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import StanzaBase, ElementBase class STARTTLS(ElementBase): """ """ name = 'starttls' namespace = 'urn:ietf:params:xml:ns:xmpp-tls' interfaces = {'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() slixmpp-slix-1.4.2/slixmpp/features/feature_starttls/starttls.py000066400000000000000000000040141342457644200253400ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp.stanza import StreamFeatures from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins import BasePlugin from slixmpp.xmlstream.matcher import MatchXPath from slixmpp.xmlstream.handler import CoroutineCallback from slixmpp.features.feature_starttls import stanza log = logging.getLogger(__name__) class FeatureSTARTTLS(BasePlugin): name = 'feature_starttls' description = 'RFC 6120: Stream Feature: STARTTLS' dependencies = set() stanza = stanza def plugin_init(self): self.xmpp.register_handler( CoroutineCallback('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 self.xmpp.disable_starttls: return False else: self.xmpp.send(features['starttls']) return True async def _handle_starttls_proceed(self, proceed): """Restart the XML stream when TLS is accepted.""" log.debug("Starting TLS") if await self.xmpp.start_tls(): self.xmpp.features.add('starttls') slixmpp-slix-1.4.2/slixmpp/jid.py000066400000000000000000000300571342457644200170230ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ slixmpp.jid ~~~~~~~~~~~~~~~~~~~~~~~ This module allows for working with Jabber IDs (JIDs). Part of Slixmpp: The Slick XMPP Library :copyright: (c) 2011 Nathanael C. Fritz :license: MIT, see LICENSE for more details """ import re import socket from copy import deepcopy from functools import lru_cache from slixmpp.stringprep import nodeprep, resourceprep, idna, StringprepError HAVE_INET_PTON = hasattr(socket, 'inet_pton') #: The basic regex pattern that a JID must match in order to determine #: the local, domain, and resource parts. This regex does NOT do any #: validation, which requires application of nodeprep, resourceprep, etc. JID_PATTERN = re.compile( "^(?:([^\"&'/:<>@]{1,1023})@)?([^/@]{1,1023})(?:/(.{1,1023}))?$" ) #: The set of escape sequences for the characters not allowed by nodeprep. JID_ESCAPE_SEQUENCES = {'\\20', '\\22', '\\26', '\\27', '\\2f', '\\3a', '\\3c', '\\3e', '\\40', '\\5c'} #: The reverse mapping of escape sequences to their original forms. JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ', '\\22': '"', '\\26': '&', '\\27': "'", '\\2f': '/', '\\3a': ':', '\\3c': '<', '\\3e': '>', '\\40': '@', '\\5c': '\\'} # TODO: Find the best cache size for a standard usage. @lru_cache(maxsize=1024) def _parse_jid(data): """ Parse string data into the node, domain, and resource components of a JID, if possible. :param string data: A string that is potentially a JID. :raises InvalidJID: :returns: tuple of the validated local, domain, and resource strings """ match = JID_PATTERN.match(data) if not match: raise InvalidJID('JID could not be parsed') (node, domain, resource) = match.groups() node = _validate_node(node) domain = _validate_domain(domain) resource = _validate_resource(resource) return node, domain, resource def _validate_node(node): """Validate the local, or username, portion of a JID. :raises InvalidJID: :returns: The local portion of a JID, as validated by nodeprep. """ if node is None: return '' try: node = nodeprep(node) except StringprepError: raise InvalidJID('Nodeprep failed') if not node: raise InvalidJID('Localpart must not be 0 bytes') if len(node) > 1023: raise InvalidJID('Localpart must be less than 1024 bytes') return node def _validate_domain(domain): """Validate the domain portion of a JID. IP literal addresses are left as-is, if valid. Domain names are stripped of any trailing label separators (`.`), and are checked with the nameprep profile of stringprep. If the given domain is actually a punyencoded version of a domain name, it is converted back into its original Unicode form. Domains must also not start or end with a dash (`-`). :raises InvalidJID: :returns: The validated domain name """ ip_addr = False # First, check if this is an IPv4 address try: socket.inet_aton(domain) ip_addr = True except socket.error: pass # Check if this is an IPv6 address if not ip_addr and HAVE_INET_PTON and domain[0] == '[' and domain[-1] == ']': try: ip = domain[1:-1] socket.inet_pton(socket.AF_INET6, ip) ip_addr = True except (socket.error, ValueError): pass if not ip_addr: # This is a domain name, which must be checked further if domain and domain[-1] == '.': domain = domain[:-1] try: domain = idna(domain) except StringprepError: raise InvalidJID('idna validation failed') if ':' in domain: raise InvalidJID('Domain containing a port') for label in domain.split('.'): if not label: raise InvalidJID('Domain containing too many dots') if '-' in (label[0], label[-1]): raise InvalidJID('Domain started or ended with -') if not domain: raise InvalidJID('Domain must not be 0 bytes') if len(domain) > 1023: raise InvalidJID('Domain must be less than 1024 bytes') return domain def _validate_resource(resource): """Validate the resource portion of a JID. :raises InvalidJID: :returns: The local portion of a JID, as validated by resourceprep. """ if resource is None: return '' try: resource = resourceprep(resource) except StringprepError: raise InvalidJID('Resourceprep failed') if not resource: raise InvalidJID('Resource must not be 0 bytes') if len(resource) > 1023: raise InvalidJID('Resource must be less than 1024 bytes') return resource def _unescape_node(node): """Unescape a local portion of a JID. .. note:: The unescaped local portion is meant ONLY for presentation, and should not be used for other purposes. """ unescaped = [] seq = '' for i, char in enumerate(node): if char == '\\': seq = node[i:i+3] if seq not in JID_ESCAPE_SEQUENCES: seq = '' if seq: if len(seq) == 3: unescaped.append(JID_UNESCAPE_TRANSFORMATIONS.get(seq, char)) # Pop character off the escape sequence, and ignore it seq = seq[1:] else: unescaped.append(char) return ''.join(unescaped) def _format_jid(local=None, domain=None, resource=None): """Format the given JID components into a full or bare JID. :param string local: Optional. The local portion of the JID. :param string domain: Required. The domain name portion of the JID. :param strin resource: Optional. The resource portion of the JID. :return: A full or bare JID string. """ if domain is None: return '' if local is not None: result = local + '@' + domain else: result = domain if resource is not None: result += '/' + resource return result class InvalidJID(ValueError): """ Raised when attempting to create a JID that does not pass validation. It can also be raised if modifying an existing JID in such a way as to make it invalid, such trying to remove the domain from an existing full JID while the local and resource portions still exist. """ # pylint: disable=R0903 class UnescapedJID: """ .. versionadded:: 1.1.10 """ __slots__ = ('_node', '_domain', '_resource') def __init__(self, node, domain, resource): self._node = node self._domain = domain self._resource = resource def __getattribute__(self, name): """Retrieve the given JID component. :param name: one of: user, server, domain, resource, full, or bare. """ if name == 'resource': return self._resource or '' if name in ('user', 'username', 'local', 'node'): return self._node or '' if name in ('server', 'domain', 'host'): return self._domain or '' if name in ('full', 'jid'): return _format_jid(self._node, self._domain, self._resource) if name == 'bare': return _format_jid(self._node, self._domain) return object.__getattribute__(self, name) def __str__(self): """Use the full JID as the string value.""" return _format_jid(self._node, self._domain, self._resource) def __repr__(self): """Use the full JID as the representation.""" return _format_jid(self._node, self._domain, self._resource) class JID: """ A representation of a Jabber ID, or JID. Each JID may have three components: a user, a domain, and an optional resource. For example: user@domain/resource When a resource is not used, the JID is called a bare JID. The JID is a full JID otherwise. **JID Properties:** :full: The string value of the full JID. :jid: Alias for ``full``. :bare: The string value of the bare JID. :node: The node portion of the JID. :user: Alias for ``node``. :local: Alias for ``node``. :username: Alias for ``node``. :domain: The domain name portion of the JID. :server: Alias for ``domain``. :host: Alias for ``domain``. :resource: The resource portion of the JID. :param string jid: A string of the form ``'[user@]domain[/resource]'``. :raises InvalidJID: """ __slots__ = ('_node', '_domain', '_resource', '_bare', '_full') def __init__(self, jid=None): if not jid: self._node = '' self._domain = '' self._resource = '' self._bare = '' self._full = '' return elif not isinstance(jid, JID): self._node, self._domain, self._resource = _parse_jid(jid) else: self._node = jid._node self._domain = jid._domain self._resource = jid._resource self._update_bare_full() def unescape(self): """Return an unescaped JID object. Using an unescaped JID is preferred for displaying JIDs to humans, and they should NOT be used for any other purposes than for presentation. :return: :class:`UnescapedJID` .. versionadded:: 1.1.10 """ return UnescapedJID(_unescape_node(self._node), self._domain, self._resource) def _update_bare_full(self): """Format the given JID into a bare and a full JID. """ self._bare = (self._node + '@' + self._domain if self._node else self._domain) self._full = (self._bare + '/' + self._resource if self._resource else self._bare) @property def node(self): return self._node @property def domain(self): return self._domain @property def resource(self): return self._resource @property def bare(self): return self._bare @property def full(self): return self._full @node.setter def node(self, value): self._node = _validate_node(value) self._update_bare_full() @domain.setter def domain(self, value): self._domain = _validate_domain(value) self._update_bare_full() @bare.setter def bare(self, value): node, domain, resource = _parse_jid(value) assert not resource self._node = node self._domain = domain self._update_bare_full() @resource.setter def resource(self, value): self._resource = _validate_resource(value) self._update_bare_full() @full.setter def full(self, value): self._node, self._domain, self._resource = _parse_jid(value) self._update_bare_full() user = node local = node username = node server = domain host = domain jid = full def __str__(self): """Use the full JID as the string value.""" return self._full def __repr__(self): """Use the full JID as the representation.""" return self._full # pylint: disable=W0212 def __eq__(self, other): """Two JIDs are equal if they have the same full JID value.""" if isinstance(other, UnescapedJID): return False if not isinstance(other, JID): other = JID(other) return (self._node == other._node and self._domain == other._domain and self._resource == other._resource) def __ne__(self, other): """Two JIDs are considered unequal if they are not equal.""" return not self == other def __hash__(self): """Hash a JID based on the string version of its full JID.""" return hash(self._full) slixmpp-slix-1.4.2/slixmpp/plugins/000077500000000000000000000000001342457644200173575ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/__init__.py000066400000000000000000000062711342457644200214760ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import PluginManager, PluginNotFound, BasePlugin from slixmpp.plugins.base import register_plugin, load_plugin __all__ = [ # XEPS 'xep_0004', # Data Forms 'xep_0009', # Jabber-RPC 'xep_0012', # Last Activity 'xep_0013', # Flexible Offline Message Retrieval 'xep_0016', # Privacy Lists 'xep_0020', # Feature Negotiation 'xep_0027', # Current Jabber OpenPGP Usage 'xep_0030', # Service Discovery 'xep_0033', # Extended Stanza Addresses 'xep_0045', # Multi-User Chat (Client) 'xep_0047', # In-Band Bytestreams 'xep_0048', # Bookmarks 'xep_0049', # Private XML Storage 'xep_0050', # Ad-hoc Commands 'xep_0054', # vcard-temp 'xep_0059', # Result Set Management 'xep_0060', # Pubsub (Client) 'xep_0065', # SOCKS5 Bytestreams 'xep_0066', # Out of Band Data 'xep_0071', # XHTML-IM 'xep_0077', # In-Band Registration # 'xep_0078', # Non-SASL auth. Don't automatically load 'xep_0079', # Advanced Message Processing 'xep_0080', # User Location 'xep_0082', # XMPP Date and Time Profiles 'xep_0084', # User Avatar 'xep_0085', # Chat State Notifications 'xep_0086', # Legacy Error Codes 'xep_0091', # Legacy Delayed Delivery 'xep_0092', # Software Version 'xep_0106', # JID Escaping 'xep_0107', # User Mood 'xep_0108', # User Activity 'xep_0115', # Entity Capabilities 'xep_0118', # User Tune 'xep_0122', # Data Forms Validation 'xep_0128', # Extended Service Discovery 'xep_0131', # Standard Headers and Internet Metadata 'xep_0133', # Service Administration 'xep_0152', # Reachability Addresses 'xep_0153', # vCard-Based Avatars 'xep_0163', # Personal Eventing Protocol 'xep_0172', # User Nickname 'xep_0184', # Message Receipts 'xep_0186', # Invisible Command 'xep_0191', # Blocking Command 'xep_0196', # User Gaming 'xep_0198', # Stream Management 'xep_0199', # Ping 'xep_0202', # Entity Time 'xep_0203', # Delayed Delivery 'xep_0221', # Data Forms Media Element 'xep_0222', # Persistent Storage of Public Data via Pubsub 'xep_0223', # Persistent Storage of Private Data via Pubsub 'xep_0224', # Attention 'xep_0231', # Bits of Binary 'xep_0235', # OAuth Over XMPP 'xep_0242', # XMPP Client Compliance 2009 'xep_0249', # Direct MUC Invitations 'xep_0256', # Last Activity in Presence 'xep_0257', # Client Certificate Management for SASL EXTERNAL 'xep_0258', # Security Labels in XMPP 'xep_0270', # XMPP Compliance Suites 2010 'xep_0279', # Server IP Check 'xep_0280', # Message Carbons 'xep_0297', # Stanza Forwarding 'xep_0302', # XMPP Compliance Suites 2012 'xep_0308', # Last Message Correction 'xep_0313', # Message Archive Management 'xep_0319', # Last User Interaction in Presence 'xep_0323', # IoT Systems Sensor Data 'xep_0325', # IoT Systems Control 'xep_0332', # HTTP Over XMPP Transport ] slixmpp-slix-1.4.2/slixmpp/plugins/base.py000066400000000000000000000273111342457644200206470ustar00rootroot00000000000000# -*- encoding: utf-8 -*- """ slixmpp.plugins.base ~~~~~~~~~~~~~~~~~~~~~~ This module provides XMPP functionality that is specific to client connections. Part of Slixmpp: The Slick XMPP Library :copyright: (c) 2012 Nathanael C. Fritz :license: MIT, see LICENSE for more details """ import sys import copy import logging import threading log = logging.getLogger(__name__) #: Associate short string names of plugins with implementations. The #: plugin names are based on the spec used by the plugin, such as #: `'xep_0030'` for a plugin that implements XEP-0030. PLUGIN_REGISTRY = {} #: In order to do cascading plugin disabling, reverse dependencies #: must be tracked. PLUGIN_DEPENDENTS = {} #: Only allow one thread to manipulate the plugin registry at a time. REGISTRY_LOCK = threading.RLock() class PluginNotFound(Exception): """Raised if an unknown plugin is accessed.""" def register_plugin(impl, name=None): """Add a new plugin implementation to the registry. :param class impl: The plugin class. The implementation class must provide a :attr:`~BasePlugin.name` value that will be used as a short name for enabling and disabling the plugin. The name should be based on the specification used by the plugin. For example, a plugin implementing XEP-0030 would be named `'xep_0030'`. """ if name is None: name = impl.name with REGISTRY_LOCK: PLUGIN_REGISTRY[name] = impl if name not in PLUGIN_DEPENDENTS: PLUGIN_DEPENDENTS[name] = set() for dep in impl.dependencies: if dep not in PLUGIN_DEPENDENTS: PLUGIN_DEPENDENTS[dep] = set() PLUGIN_DEPENDENTS[dep].add(name) def load_plugin(name, module=None): """Find and import a plugin module so that it can be registered. This function is called to import plugins that have selected for enabling, but no matching registered plugin has been found. :param str name: The name of the plugin. It is expected that plugins are in packages matching their name, even though the plugin class name does not have to match. :param str module: The name of the base module to search for the plugin. """ try: if not module: try: module = 'slixmpp.plugins.%s' % name __import__(module) mod = sys.modules[module] except ImportError: module = 'slixmpp.features.%s' % name __import__(module) mod = sys.modules[module] elif isinstance(module, str): __import__(module) mod = sys.modules[module] else: mod = module # Add older style plugins to the registry. if hasattr(mod, name): plugin = getattr(mod, name) if hasattr(plugin, 'xep') or hasattr(plugin, 'rfc'): plugin.name = name # Mark the plugin as an older style plugin so # we can work around dependency issues. plugin.old_style = True register_plugin(plugin, name) except ImportError: log.exception("Unable to load plugin: %s", name) class PluginManager(object): def __init__(self, xmpp, config=None): #: We will track all enabled plugins in a set so that we #: can enable plugins in batches and pull in dependencies #: without problems. self._enabled = set() #: Maintain references to active plugins. self._plugins = {} self._plugin_lock = threading.RLock() #: Globally set default plugin configuration. This will #: be used for plugins that are auto-enabled through #: dependency loading. self.config = config if config else {} self.xmpp = xmpp def register(self, plugin, enable=True): """Register a new plugin, and optionally enable it. :param class plugin: The implementation class of the plugin to register. :param bool enable: If ``True``, immediately enable the plugin after registration. """ register_plugin(plugin) if enable: self.enable(plugin.name) def enable(self, name, config=None, enabled=None): """Enable a plugin, including any dependencies. :param string name: The short name of the plugin. :param dict config: Optional settings dictionary for configuring plugin behaviour. """ if enabled is None: enabled = set() with self._plugin_lock: if name not in self._enabled: enabled.add(name) self._enabled.add(name) if not self.registered(name): load_plugin(name) plugin_class = PLUGIN_REGISTRY.get(name, None) if not plugin_class: raise PluginNotFound(name) if config is None: config = self.config.get(name, None) plugin = plugin_class(self.xmpp, config) self._plugins[name] = plugin for dep in plugin.dependencies: self.enable(dep, enabled=enabled) plugin._init() for name in enabled: if hasattr(self._plugins[name], 'old_style'): # Older style plugins require post_init() # to run just before stream processing begins, # so we don't call it here. pass else: self._plugins[name].post_init() def enable_all(self, names=None, config=None): """Enable all registered plugins. :param list names: A list of plugin names to enable. If none are provided, all registered plugins will be enabled. :param dict config: A dictionary mapping plugin names to configuration dictionaries, as used by :meth:`~PluginManager.enable`. """ names = names if names else PLUGIN_REGISTRY.keys() if config is None: config = {} for name in names: self.enable(name, config.get(name, {})) def enabled(self, name): """Check if a plugin has been enabled. :param string name: The name of the plugin to check. :return: boolean """ return name in self._enabled def registered(self, name): """Check if a plugin has been registered. :param string name: The name of the plugin to check. :return: boolean """ return name in PLUGIN_REGISTRY def disable(self, name, _disabled=None): """Disable a plugin, including any dependent upon it. :param string name: The name of the plugin to disable. :param set _disabled: Private set used to track the disabled status of plugins during the cascading process. """ if _disabled is None: _disabled = set() with self._plugin_lock: if name not in _disabled and name in self._enabled: _disabled.add(name) plugin = self._plugins.get(name, None) if plugin is None: raise PluginNotFound(name) for dep in PLUGIN_DEPENDENTS[name]: self.disable(dep, _disabled) plugin._end() if name in self._enabled: self._enabled.remove(name) del self._plugins[name] def __keys__(self): """Return the set of enabled plugins.""" return self._plugins.keys() def __getitem__(self, name): """ Allow plugins to be accessed through the manager as if it were a dictionary. """ plugin = self._plugins.get(name, None) if plugin is None: raise PluginNotFound(name) return plugin def __iter__(self): """Return an iterator over the set of enabled plugins.""" return self._plugins.__iter__() def __len__(self): """Return the number of enabled plugins.""" return len(self._plugins) class BasePlugin(object): #: A short name for the plugin based on the implemented specification. #: For example, a plugin for XEP-0030 would use `'xep_0030'`. name = '' #: A longer name for the plugin, describing its purpose. For example, #: a plugin for XEP-0030 would use `'Service Discovery'` as its #: description value. description = '' #: Some plugins may depend on others in order to function properly. #: Any plugin names included in :attr:`~BasePlugin.dependencies` will #: be initialized as needed if this plugin is enabled. dependencies = set() #: The basic, standard configuration for the plugin, which may #: be overridden when initializing the plugin. The configuration #: fields included here may be accessed directly as attributes of #: the plugin. For example, including the configuration field 'foo' #: would mean accessing `plugin.foo` returns the current value of #: `plugin.config['foo']`. default_config = {} def __init__(self, xmpp, config=None): self.xmpp = xmpp if self.xmpp: self.api = self.xmpp.api.wrap(self.name) #: A plugin's behaviour may be configurable, in which case those #: configuration settings will be provided as a dictionary. self.config = copy.copy(self.default_config) if config: self.config.update(config) def __getattr__(self, key): """Provide direct access to configuration fields. If the standard configuration includes the option `'foo'`, then accessing `self.foo` should be the same as `self.config['foo']`. """ if key in self.default_config: return self.config.get(key, None) else: return object.__getattribute__(self, key) def __setattr__(self, key, value): """Provide direct assignment to configuration fields. If the standard configuration includes the option `'foo'`, then assigning to `self.foo` should be the same as assigning to `self.config['foo']`. """ if key in self.default_config: self.config[key] = value else: super().__setattr__(key, value) def _init(self): """Initialize plugin state, such as registering event handlers. Also sets up required event handlers. """ if self.xmpp is not None: self.xmpp.add_event_handler('session_bind', self.session_bind) if self.xmpp.session_bind_event.is_set(): self.session_bind(self.xmpp.boundjid.full) self.plugin_init() log.debug('Loaded Plugin: %s', self.description) def _end(self): """Cleanup plugin state, and prepare for plugin removal. Also removes required event handlers. """ if self.xmpp is not None: self.xmpp.del_event_handler('session_bind', self.session_bind) self.plugin_end() log.debug('Disabled Plugin: %s' % self.description) def plugin_init(self): """Initialize plugin state, such as registering event handlers.""" pass def plugin_end(self): """Cleanup plugin state, and prepare for plugin removal.""" pass def session_bind(self, jid): """Initialize plugin state based on the bound JID.""" pass def post_init(self): """Initialize any cross-plugin state. Only needed if the plugin has circular dependencies. """ pass slixmpp-slix-1.4.2/slixmpp/plugins/gmail_notify.py000066400000000000000000000110721342457644200224130ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp.plugins import BasePlugin from .. xmlstream.handler.callback import Callback from .. xmlstream.matcher.xpath import MatchXPath from .. xmlstream.stanzabase import register_stanza_plugin, 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 = {'newer-than-time', 'newer-than-tid', 'q', 'search'} def get_search(self): return self['q'] def set_search(self, search): self['q'] = search def del_search(self): del self['q'] class MailBox(ElementBase): namespace = 'google:mail:notify' name = 'mailbox' plugin_attrib = 'mailbox' interfaces = {'result-time', 'total-matched', 'total-estimate', 'url', 'threads', 'matched', 'estimate'} def get_threads(self): threads = [] for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace, MailThread.name)): threads.append(MailThread(xml=threadXML, parent=None)) return threads def get_matched(self): return self['total-matched'] def get_estimate(self): return self['total-estimate'] == '1' class MailThread(ElementBase): namespace = 'google:mail:notify' name = 'mail-thread-info' plugin_attrib = 'thread' interfaces = {'tid', 'participation', 'messages', 'date', 'senders', 'url', 'labels', 'subject', 'snippet'} sub_interfaces = {'labels', 'subject', 'snippet'} def get_senders(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 = {'address', 'name', 'originator', 'unread'} def get_originator(self): return self.xml.attrib.get('originator', '0') == '1' def get_unread(self): return self.xml.attrib.get('unread', '0') == '1' class NewMail(ElementBase): namespace = 'google:mail:notify' name = 'new-mail' plugin_attrib = 'new-mail' class gmail_notify(BasePlugin): """ 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)) register_stanza_plugin(Iq, GmailQuery) register_stanza_plugin(Iq, MailBox) register_stanza_plugin(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() slixmpp-slix-1.4.2/slixmpp/plugins/google/000077500000000000000000000000001342457644200206335ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/google/__init__.py000066400000000000000000000022621342457644200227460ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin, BasePlugin from slixmpp.plugins.google.gmail import Gmail from slixmpp.plugins.google.auth import GoogleAuth from slixmpp.plugins.google.settings import GoogleSettings from slixmpp.plugins.google.nosave import GoogleNoSave class Google(BasePlugin): """ Google: Custom GTalk Features Also see: """ name = 'google' description = 'Google: Custom GTalk Features' dependencies = set([ 'gmail', 'google_settings', 'google_nosave', 'google_auth' ]) def __getitem__(self, attr): if attr in ('settings', 'nosave', 'auth'): return self.xmpp['google_%s' % attr] elif attr == 'gmail': return self.xmpp['gmail'] else: raise KeyError(attr) register_plugin(Gmail) register_plugin(GoogleAuth) register_plugin(GoogleSettings) register_plugin(GoogleNoSave) register_plugin(Google) slixmpp-slix-1.4.2/slixmpp/plugins/google/auth/000077500000000000000000000000001342457644200215745ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/google/auth/__init__.py000066400000000000000000000004441342457644200237070ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.google.auth import stanza from slixmpp.plugins.google.auth.auth import GoogleAuth slixmpp-slix-1.4.2/slixmpp/plugins/google/auth/auth.py000066400000000000000000000027221342457644200231120ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins import BasePlugin from slixmpp.plugins.google.auth import stanza class GoogleAuth(BasePlugin): """ Google: Auth Extensions (JID Domain Discovery, OAuth2) Also see: """ name = 'google_auth' description = 'Google: Auth Extensions (JID Domain Discovery, OAuth2)' dependencies = set(['feature_mechanisms']) stanza = stanza def plugin_init(self): self.xmpp.namespace_map['http://www.google.com/talk/protocol/auth'] = 'ga' register_stanza_plugin(self.xmpp['feature_mechanisms'].stanza.Auth, stanza.GoogleAuth) self.xmpp.add_filter('out', self._auth) def plugin_end(self): self.xmpp.del_filter('out', self._auth) def _auth(self, stanza): if isinstance(stanza, self.xmpp['feature_mechanisms'].stanza.Auth): stanza.stream = self.xmpp stanza['google']['client_uses_full_bind_result'] = True if stanza['mechanism'] == 'X-OAUTH2': stanza['google']['service'] = 'oauth2' print(stanza) return stanza slixmpp-slix-1.4.2/slixmpp/plugins/google/auth/stanza.py000066400000000000000000000026611342457644200234530ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase, ET class GoogleAuth(ElementBase): name = 'auth' namespace = 'http://www.google.com/talk/protocol/auth' plugin_attrib = 'google' interfaces = {'client_uses_full_bind_result', 'service'} discovery_attr= '{%s}client-uses-full-bind-result' % namespace service_attr= '{%s}service' % namespace def setup(self, xml): """Don't create XML for the plugin.""" self.xml = ET.Element('') def get_client_uses_full_bind_result(self): return self.parent()._get_attr(self.discovery_attr) == 'true' def set_client_uses_full_bind_result(self, value): if value in (True, 'true'): self.parent()._set_attr(self.discovery_attr, 'true') else: self.parent()._del_attr(self.discovery_attr) def del_client_uses_full_bind_result(self): self.parent()._del_attr(self.discovery_attr) def get_service(self): return self.parent()._get_attr(self.service_attr, '') def set_service(self, value): if value: self.parent()._set_attr(self.service_attr, value) else: self.parent()._del_attr(self.service_attr) def del_service(self): self.parent()._del_attr(self.service_attr) slixmpp-slix-1.4.2/slixmpp/plugins/google/gmail/000077500000000000000000000000001342457644200217245ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/google/gmail/__init__.py000066400000000000000000000004521342457644200240360ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.google.gmail import stanza from slixmpp.plugins.google.gmail.notifications import Gmail slixmpp-slix-1.4.2/slixmpp/plugins/google/gmail/notifications.py000066400000000000000000000054271342457644200251570ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp.stanza import Iq from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import MatchXPath from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins import BasePlugin from slixmpp.plugins.google.gmail import stanza log = logging.getLogger(__name__) class Gmail(BasePlugin): """ Google: Gmail Notifications Also see . """ name = 'gmail' description = 'Google: Gmail Notifications' dependencies = set() stanza = stanza def plugin_init(self): register_stanza_plugin(Iq, stanza.GmailQuery) register_stanza_plugin(Iq, stanza.MailBox) register_stanza_plugin(Iq, stanza.NewMail) self.xmpp.register_handler( Callback('Gmail New Mail', MatchXPath('{%s}iq/{%s}%s' % ( self.xmpp.default_ns, stanza.NewMail.namespace, stanza.NewMail.name)), self._handle_new_mail)) self._last_result_time = None self._last_result_tid = None def plugin_end(self): self.xmpp.remove_handler('Gmail New Mail') def _handle_new_mail(self, iq): log.info('Gmail: New email!') iq.reply().send() self.xmpp.event('gmail_notification') def check(self, timeout=None, callback=None): last_time = self._last_result_time last_tid = self._last_result_tid callback = lambda iq: self._update_last_results(iq, callback) return self.search(newer_time=last_time, newer_tid=last_tid, timeout=timeout, callback=callback) def _update_last_results(self, iq, callback=None): self._last_result_time = iq['gmail_messages']['result_time'] threads = iq['gmail_messages']['threads'] if threads: self._last_result_tid = threads[0]['tid'] if callback: callback(iq) def search(self, query=None, newer_time=None, newer_tid=None, timeout=None, callback=None): if not query: log.info('Gmail: Checking for new email') else: log.info('Gmail: Searching for emails matching: "%s"', query) iq = self.xmpp.Iq() iq['type'] = 'get' iq['to'] = self.xmpp.boundjid.bare iq['gmail']['search'] = query iq['gmail']['newer_than_time'] = newer_time iq['gmail']['newer_than_tid'] = newer_tid return iq.send(timeout=timeout, callback=callback) slixmpp-slix-1.4.2/slixmpp/plugins/google/gmail/stanza.py000066400000000000000000000053101342457644200235750ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase, register_stanza_plugin class GmailQuery(ElementBase): namespace = 'google:mail:notify' name = 'query' plugin_attrib = 'gmail' interfaces = set(['newer_than_time', 'newer_than_tid', 'search']) def get_search(self): return self._get_attr('q', '') def set_search(self, search): self._set_attr('q', search) def del_search(self): self._del_attr('q') def get_newer_than_time(self): return self._get_attr('newer-than-time', '') def set_newer_than_time(self, value): self._set_attr('newer-than-time', value) def del_newer_than_time(self): self._del_attr('newer-than-time') def get_newer_than_tid(self): return self._get_attr('newer-than-tid', '') def set_newer_than_tid(self, value): self._set_attr('newer-than-tid', value) def del_newer_than_tid(self): self._del_attr('newer-than-tid') class MailBox(ElementBase): namespace = 'google:mail:notify' name = 'mailbox' plugin_attrib = 'gmail_messages' interfaces = set(['result_time', 'url', 'matched', 'estimate']) def get_matched(self): return self._get_attr('total-matched', '') def get_estimate(self): return self._get_attr('total-estimate', '') == '1' def get_result_time(self): return self._get_attr('result-time', '') class MailThread(ElementBase): namespace = 'google:mail:notify' name = 'mail-thread-info' plugin_attrib = 'thread' plugin_multi_attrib = 'threads' interfaces = set(['tid', 'participation', 'messages', 'date', 'senders', 'url', 'labels', 'subject', 'snippet']) sub_interfaces = set(['labels', 'subject', 'snippet']) def get_senders(self): result = [] senders = self.xml.findall('{%s}senders/{%s}sender' % ( self.namespace, self.namespace)) for sender in senders: result.append(MailSender(xml=sender)) return result class MailSender(ElementBase): namespace = 'google:mail:notify' name = 'sender' plugin_attrib = name interfaces = set(['address', 'name', 'originator', 'unread']) def get_originator(self): return self.xml.attrib.get('originator', '0') == '1' def get_unread(self): return self.xml.attrib.get('unread', '0') == '1' class NewMail(ElementBase): namespace = 'google:mail:notify' name = 'new-mail' plugin_attrib = 'gmail_notification' register_stanza_plugin(MailBox, MailThread, iterable=True) slixmpp-slix-1.4.2/slixmpp/plugins/google/nosave/000077500000000000000000000000001342457644200221265ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/google/nosave/__init__.py000066400000000000000000000004541342457644200242420ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.google.nosave import stanza from slixmpp.plugins.google.nosave.nosave import GoogleNoSave slixmpp-slix-1.4.2/slixmpp/plugins/google/nosave/nosave.py000066400000000000000000000050361342457644200237770ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of slixmpp. See the file LICENSE for copying permission. """ from slixmpp.stanza import Iq, Message from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins import BasePlugin from slixmpp.plugins.google.nosave import stanza class GoogleNoSave(BasePlugin): """ Google: Off the Record Chats NOTE: This is NOT an encryption method. Also see . """ name = 'google_nosave' description = 'Google: Off the Record Chats' dependencies = set(['google_settings']) stanza = stanza def plugin_init(self): register_stanza_plugin(Message, stanza.NoSave) register_stanza_plugin(Iq, stanza.NoSaveQuery) self.xmpp.register_handler( Callback('Google Nosave', StanzaPath('iq@type=set/google_nosave'), self._handle_nosave_change)) def plugin_end(self): self.xmpp.remove_handler('Google Nosave') def enable(self, jid=None, timeout=None, callback=None): if jid is None: self.xmpp['google_settings'].update({'archiving_enabled': False}, timeout=timeout, callback=callback) else: iq = self.xmpp.Iq() iq['type'] = 'set' iq['google_nosave']['item']['jid'] = jid iq['google_nosave']['item']['value'] = True return iq.send(timeout=timeout, callback=callback) def disable(self, jid=None, timeout=None, callback=None): if jid is None: self.xmpp['google_settings'].update({'archiving_enabled': True}, timeout=timeout, callback=callback) else: iq = self.xmpp.Iq() iq['type'] = 'set' iq['google_nosave']['item']['jid'] = jid iq['google_nosave']['item']['value'] = False return iq.send(timeout=timeout, callback=callback) def get(self, timeout=None, callback=None): iq = self.xmpp.Iq() iq['type'] = 'get' iq.enable('google_nosave') return iq.send(timeout=timeout, callback=callback) def _handle_nosave_change(self, iq): reply = self.xmpp.Iq() reply['type'] = 'result' reply['id'] = iq['id'] reply['to'] = iq['from'] reply.send() self.xmpp.event('google_nosave_change', iq) slixmpp-slix-1.4.2/slixmpp/plugins/google/nosave/stanza.py000066400000000000000000000026601342457644200240040ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of slixmpp. See the file LICENSE for copying permission. """ from slixmpp.jid import JID from slixmpp.xmlstream import ElementBase, register_stanza_plugin class NoSave(ElementBase): name = 'x' namespace = 'google:nosave' plugin_attrib = 'google_nosave' interfaces = {'value'} def get_value(self): return self._get_attr('value', '') == 'enabled' def set_value(self, value): self._set_attr('value', 'enabled' if value else 'disabled') class NoSaveQuery(ElementBase): name = 'query' namespace = 'google:nosave' plugin_attrib = 'google_nosave' interfaces = set() class Item(ElementBase): name = 'item' namespace = 'google:nosave' plugin_attrib = 'item' plugin_multi_attrib = 'items' interfaces = {'jid', 'source', 'value'} def get_value(self): return self._get_attr('value', '') == 'enabled' def set_value(self, value): self._set_attr('value', 'enabled' if value else 'disabled') def get_jid(self): return JID(self._get_attr('jid', '')) def set_jid(self, value): self._set_attr('jid', str(value)) def get_source(self): return JID(self._get_attr('source', '')) def set_source(self, value): self._set_attr('source', str(value)) register_stanza_plugin(NoSaveQuery, Item) slixmpp-slix-1.4.2/slixmpp/plugins/google/settings/000077500000000000000000000000001342457644200224735ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/google/settings/__init__.py000066400000000000000000000004641342457644200246100ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.google.settings import stanza from slixmpp.plugins.google.settings.settings import GoogleSettings slixmpp-slix-1.4.2/slixmpp/plugins/google/settings/settings.py000066400000000000000000000035031342457644200247060ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of slixmpp. See the file LICENSE for copying permission. """ from slixmpp.stanza import Iq from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins import BasePlugin from slixmpp.plugins.google.settings import stanza class GoogleSettings(BasePlugin): """ Google: Gmail Notifications Also see . """ name = 'google_settings' description = 'Google: User Settings' dependencies = set() stanza = stanza def plugin_init(self): register_stanza_plugin(Iq, stanza.UserSettings) self.xmpp.register_handler( Callback('Google Settings', StanzaPath('iq@type=set/google_settings'), self._handle_settings_change)) def plugin_end(self): self.xmpp.remove_handler('Google Settings') def get(self, timeout=None, callback=None): iq = self.xmpp.Iq() iq['type'] = 'get' iq.enable('google_settings') return iq.send(timeout=timeout, callback=callback) def update(self, settings, timeout=None, callback=None): iq = self.xmpp.Iq() iq['type'] = 'set' iq.enable('google_settings') for setting, value in settings.items(): iq['google_settings'][setting] = value return iq.send(timeout=timeout, callback=callback) def _handle_settings_change(self, iq): reply = self.xmpp.Iq() reply['type'] = 'result' reply['id'] = iq['id'] reply['to'] = iq['from'] reply.send() self.xmpp.event('google_settings_change', iq) slixmpp-slix-1.4.2/slixmpp/plugins/google/settings/stanza.py000066400000000000000000000065521342457644200243550ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ET, ElementBase class UserSettings(ElementBase): name = 'usersetting' namespace = 'google:setting' plugin_attrib = 'google_settings' interfaces = set(['auto_accept_suggestions', 'mail_notifications', 'archiving_enabled', 'gmail', 'email_verified', 'domain_privacy_notice', 'display_name']) def _get_setting(self, setting): xml = self.xml.find('{%s}%s' % (self.namespace, setting)) if xml is not None: return xml.attrib.get('value', '') == 'true' return False def _set_setting(self, setting, value): self._del_setting(setting) if value in (True, False): xml = ET.Element('{%s}%s' % (self.namespace, setting)) xml.attrib['value'] = 'true' if value else 'false' self.xml.append(xml) def _del_setting(self, setting): xml = self.xml.find('{%s}%s' % (self.namespace, setting)) if xml is not None: self.xml.remove(xml) def get_display_name(self): xml = self.xml.find('{%s}%s' % (self.namespace, 'displayname')) if xml is not None: return xml.attrib.get('value', '') return '' def set_display_name(self, value): self._del_setting(setting) if value: xml = ET.Element('{%s}%s' % (self.namespace, 'displayname')) xml.attrib['value'] = value self.xml.append(xml) def del_display_name(self): self._del_setting('displayname') def get_auto_accept_suggestions(self): return self._get_setting('autoacceptsuggestions') def get_mail_notifications(self): return self._get_setting('mailnotifications') def get_archiving_enabled(self): return self._get_setting('archivingenabled') def get_gmail(self): return self._get_setting('gmail') def get_email_verified(self): return self._get_setting('emailverified') def get_domain_privacy_notice(self): return self._get_setting('domainprivacynotice') def set_auto_accept_suggestions(self, value): self._set_setting('autoacceptsuggestions', value) def set_mail_notifications(self, value): self._set_setting('mailnotifications', value) def set_archiving_enabled(self, value): self._set_setting('archivingenabled', value) def set_gmail(self, value): self._set_setting('gmail', value) def set_email_verified(self, value): self._set_setting('emailverified', value) def set_domain_privacy_notice(self, value): self._set_setting('domainprivacynotice', value) def del_auto_accept_suggestions(self): self._del_setting('autoacceptsuggestions') def del_mail_notifications(self): self._del_setting('mailnotifications') def del_archiving_enabled(self): self._del_setting('archivingenabled') def del_gmail(self): self._del_setting('gmail') def del_email_verified(self): self._del_setting('emailverified') def del_domain_privacy_notice(self): self._del_setting('domainprivacynotice') slixmpp-slix-1.4.2/slixmpp/plugins/xep_0004/000077500000000000000000000000001342457644200206165ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0004/__init__.py000066400000000000000000000006671342457644200227400ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0004.stanza import Form from slixmpp.plugins.xep_0004.stanza import FormField, FieldOption from slixmpp.plugins.xep_0004.dataforms import XEP_0004 register_plugin(XEP_0004) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0004/dataforms.py000066400000000000000000000031571342457644200231560ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp import Message from slixmpp.xmlstream import register_stanza_plugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.plugins import BasePlugin from slixmpp.plugins.xep_0004 import stanza from slixmpp.plugins.xep_0004.stanza import Form, FormField, FieldOption class XEP_0004(BasePlugin): """ XEP-0004: Data Forms """ name = 'xep_0004' description = 'XEP-0004: Data Forms' dependencies = {'xep_0030'} stanza = stanza def plugin_init(self): self.xmpp.register_handler( Callback('Data Form', StanzaPath('message/form'), self.handle_form)) register_stanza_plugin(FormField, FieldOption, iterable=True) register_stanza_plugin(Form, FormField, iterable=True) register_stanza_plugin(Message, Form) def plugin_end(self): self.xmpp.remove_handler('Data Form') self.xmpp['xep_0030'].del_feature(feature='jabber:x:data') def session_bind(self, jid): self.xmpp['xep_0030'].add_feature('jabber:x:data') def make_form(self, ftype='form', title='', instructions=''): f = Form() f['type'] = ftype f['title'] = title f['instructions'] = instructions return f def handle_form(self, message): self.xmpp.event("message_xform", message) def build_form(self, xml): return Form(xml=xml) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0004/stanza/000077500000000000000000000000001342457644200221165ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0004/stanza/__init__.py000066400000000000000000000004741342457644200242340ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.xep_0004.stanza.field import FormField, FieldOption from slixmpp.plugins.xep_0004.stanza.form import Form slixmpp-slix-1.4.2/slixmpp/plugins/xep_0004/stanza/field.py000066400000000000000000000137731342457644200235660ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase, ET class FormField(ElementBase): namespace = 'jabber:x:data' name = 'field' plugin_attrib = 'field' plugin_multi_attrib = 'fields' interfaces = {'answer', 'desc', 'required', 'value', 'label', 'type', 'var'} sub_interfaces = {'desc'} plugin_tag_map = {} plugin_attrib_map = {} field_types = {'boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private', 'text-single'} true_values = {True, '1', 'true'} option_types = {'list-multi', 'list-single'} multi_line_types = {'hidden', 'text-multi'} multi_value_types = {'hidden', 'jid-multi', 'list-multi', 'text-multi'} def setup(self, xml=None): if ElementBase.setup(self, xml): self._type = None else: self._type = self['type'] def set_type(self, value): self._set_attr('type', value) if value: self._type = value def add_option(self, label='', value=''): if self._type is None or self._type in self.option_types: opt = FieldOption() opt['label'] = label opt['value'] = value self.append(opt) else: raise ValueError("Cannot add options to " + \ "a %s field." % self['type']) def del_options(self): optsXML = self.xml.findall('{%s}option' % self.namespace) for optXML in optsXML: self.xml.remove(optXML) def del_required(self): reqXML = self.xml.find('{%s}required' % self.namespace) if reqXML is not None: self.xml.remove(reqXML) def del_value(self): valsXML = self.xml.findall('{%s}value' % self.namespace) for valXML in valsXML: self.xml.remove(valXML) def get_answer(self): return self['value'] def get_options(self): options = [] optsXML = self.xml.findall('{%s}option' % self.namespace) for optXML in optsXML: opt = FieldOption(xml=optXML) options.append({'label': opt['label'], 'value': opt['value']}) return options def get_required(self): reqXML = self.xml.find('{%s}required' % self.namespace) return reqXML is not None def get_value(self, convert=True): valsXML = self.xml.findall('{%s}value' % self.namespace) if len(valsXML) == 0: return None elif self._type == 'boolean': if convert: return valsXML[0].text in self.true_values return valsXML[0].text elif self._type in self.multi_value_types or len(valsXML) > 1: values = [] for valXML in valsXML: if valXML.text is None: valXML.text = '' values.append(valXML.text) if self._type == 'text-multi' and convert: values = "\n".join(values) return values else: if valsXML[0].text is None: return '' return valsXML[0].text def set_answer(self, answer): self['value'] = answer def set_false(self): self['value'] = False def set_options(self, options): for value in options: if isinstance(value, dict): self.add_option(**value) else: self.add_option(value=value) def set_required(self, required): exists = self['required'] if not exists and required: self.xml.append(ET.Element('{%s}required' % self.namespace)) elif exists and not required: del self['required'] def set_true(self): self['value'] = True def set_value(self, value): del self['value'] valXMLName = '{%s}value' % self.namespace if self._type == 'boolean': if value in self.true_values: valXML = ET.Element(valXMLName) valXML.text = '1' self.xml.append(valXML) else: valXML = ET.Element(valXMLName) valXML.text = '0' self.xml.append(valXML) elif self._type in self.multi_value_types or self._type in ('', None): if isinstance(value, bool): value = [value] if not isinstance(value, list): value = value.replace('\r', '') value = value.split('\n') for val in value: if self._type in ('', None) and val in self.true_values: val = '1' valXML = ET.Element(valXMLName) valXML.text = val self.xml.append(valXML) else: if isinstance(value, list): raise ValueError("Cannot add multiple values " + \ "to a %s field." % self._type) valXML = ET.Element(valXMLName) valXML.text = value self.xml.append(valXML) class FieldOption(ElementBase): namespace = 'jabber:x:data' name = 'option' plugin_attrib = 'option' interfaces = {'label', 'value'} sub_interfaces = {'value'} plugin_multi_attrib = 'options' FormField.addOption = FormField.add_option FormField.delOptions = FormField.del_options FormField.delRequired = FormField.del_required FormField.delValue = FormField.del_value FormField.getAnswer = FormField.get_answer FormField.getOptions = FormField.get_options FormField.getRequired = FormField.get_required FormField.getValue = FormField.get_value FormField.setAnswer = FormField.set_answer FormField.setFalse = FormField.set_false FormField.setOptions = FormField.set_options FormField.setRequired = FormField.set_required FormField.setTrue = FormField.set_true FormField.setValue = FormField.set_value slixmpp-slix-1.4.2/slixmpp/plugins/xep_0004/stanza/form.py000066400000000000000000000206121342457644200234340ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import copy import logging from collections import OrderedDict from slixmpp.thirdparty import OrderedSet from slixmpp.xmlstream import ElementBase, ET from slixmpp.plugins.xep_0004.stanza import FormField log = logging.getLogger(__name__) class Form(ElementBase): namespace = 'jabber:x:data' name = 'x' plugin_attrib = 'form' interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', 'values')) sub_interfaces = {'title'} form_types = {'cancel', 'form', 'result', 'submit'} def __init__(self, *args, **kwargs): title = None if 'title' in kwargs: title = kwargs['title'] del kwargs['title'] ElementBase.__init__(self, *args, **kwargs) if title is not None: self['title'] = title def setup(self, xml=None): if ElementBase.setup(self, xml): # If we had to generate xml self['type'] = 'form' @property def field(self): return self.get_fields() def set_type(self, ftype): self._set_attr('type', ftype) if ftype == 'submit': fields = self.get_fields() for var in fields: field = fields[var] del field['type'] del field['label'] del field['desc'] del field['required'] del field['options'] elif ftype == 'cancel': del self['fields'] def add_field(self, var='', ftype=None, label='', desc='', required=False, value=None, options=None, **kwargs): kwtype = kwargs.get('type', None) if kwtype is None: kwtype = ftype field = FormField() field['var'] = var field['type'] = kwtype field['value'] = value if self['type'] in ('form', 'result'): field['label'] = label field['desc'] = desc field['required'] = required if options is not None: for option in options: field.add_option(**option) else: del field['type'] self.append(field) return field def add_item(self, values): itemXML = ET.Element('{%s}item' % self.namespace) self.xml.append(itemXML) reported_vars = self['reported'].keys() for var in reported_vars: field = FormField() field._type = self['reported'][var]['type'] field['var'] = var field['value'] = values.get(var, None) itemXML.append(field.xml) def add_reported(self, var, ftype=None, label='', desc='', **kwargs): kwtype = kwargs.get('type', None) if kwtype is None: kwtype = ftype reported = self.xml.find('{%s}reported' % self.namespace) if reported is None: reported = ET.Element('{%s}reported' % self.namespace) self.xml.append(reported) fieldXML = ET.Element('{%s}field' % FormField.namespace) reported.append(fieldXML) field = FormField(xml=fieldXML) field['var'] = var field['type'] = kwtype field['label'] = label field['desc'] = desc return field def cancel(self): self['type'] = 'cancel' def del_fields(self): fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) for fieldXML in fieldsXML: self.xml.remove(fieldXML) def del_instructions(self): instsXML = self.xml.findall('{%s}instructions') for instXML in instsXML: self.xml.remove(instXML) def del_items(self): itemsXML = self.xml.find('{%s}item' % self.namespace) for itemXML in itemsXML: self.xml.remove(itemXML) def del_reported(self): reportedXML = self.xml.find('{%s}reported' % self.namespace) if reportedXML is not None: self.xml.remove(reportedXML) def get_fields(self, use_dict=False): fields = OrderedDict() for stanza in self['substanzas']: if isinstance(stanza, FormField): fields[stanza['var']] = stanza return fields def get_instructions(self): instsXML = self.xml.findall('{%s}instructions' % self.namespace) return "\n".join([instXML.text for instXML in instsXML]) def get_items(self): items = [] itemsXML = self.xml.findall('{%s}item' % self.namespace) for itemXML in itemsXML: item = OrderedDict() fieldsXML = itemXML.findall('{%s}field' % FormField.namespace) for fieldXML in fieldsXML: field = FormField(xml=fieldXML) item[field['var']] = field['value'] items.append(item) return items def get_reported(self): fields = OrderedDict() xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace, FormField.namespace)) for field in xml: field = FormField(xml=field) fields[field['var']] = field return fields def get_values(self): values = OrderedDict() fields = self.get_fields() for var in fields: values[var] = fields[var]['value'] return values def reply(self): if self['type'] == 'form': self['type'] = 'submit' elif self['type'] == 'submit': self['type'] = 'result' def set_fields(self, fields): del self['fields'] if not isinstance(fields, list): fields = fields.items() for var, field in fields: field['var'] = var self.add_field( var=field.get('var'), label=field.get('label'), desc=field.get('desc'), required=field.get('required'), value=field.get('value'), options=field.get('options'), type=field.get('type')) def set_instructions(self, instructions): del self['instructions'] if instructions in [None, '']: return if not isinstance(instructions, list): instructions = instructions.split('\n') for instruction in instructions: inst = ET.Element('{%s}instructions' % self.namespace) inst.text = instruction self.xml.append(inst) def set_items(self, items): for item in items: self.add_item(item) def set_reported(self, reported): """ This either needs a dictionary of dictionaries or a dictionary of form fields. :param reported: :return: """ for var in reported: field = reported[var] if isinstance(field, dict): self.add_reported(**field) else: reported = self.xml.find('{%s}reported' % self.namespace) if reported is None: reported = ET.Element('{%s}reported' % self.namespace) self.xml.append(reported) fieldXML = ET.Element('{%s}field' % FormField.namespace) reported.append(fieldXML) new_field = FormField(xml=fieldXML) new_field.values = field.values def set_values(self, values): fields = self.get_fields() for field in values: if field not in self.get_fields(): fields[field] = self.add_field(var=field) self.get_fields()[field]['value'] = values[field] def merge(self, other): new = copy.copy(self) if type(other) == dict: new['values'] = other return new nfields = new['fields'] ofields = other['fields'] nfields.update(ofields) new['fields'] = nfields return new Form.addField = Form.add_field Form.addReported = Form.add_reported Form.delFields = Form.del_fields Form.delInstructions = Form.del_instructions Form.delReported = Form.del_reported Form.getFields = Form.get_fields Form.getInstructions = Form.get_instructions Form.getReported = Form.get_reported Form.getValues = Form.get_values Form.setFields = Form.set_fields Form.setInstructions = Form.set_instructions Form.setReported = Form.set_reported Form.setValues = Form.set_values slixmpp-slix-1.4.2/slixmpp/plugins/xep_0009/000077500000000000000000000000001342457644200206235ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0009/__init__.py000066400000000000000000000007011342457644200227320ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON). This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0009 import stanza from slixmpp.plugins.xep_0009.rpc import XEP_0009 from slixmpp.plugins.xep_0009.stanza import RPCQuery, MethodCall, MethodResponse register_plugin(XEP_0009) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0009/binding.py000066400000000000000000000132061342457644200226110ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON). This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ET import base64 import logging import time log = logging.getLogger(__name__) _namespace = 'jabber:iq:rpc' def fault2xml(fault): value = dict() value['faultCode'] = fault['code'] value['faultString'] = fault['string'] fault = ET.Element("fault", {'xmlns': _namespace}) fault.append(_py2xml((value))) return fault def xml2fault(params): vals = [] for value in params.xml.findall('{%s}value' % _namespace): vals.append(_xml2py(value)) fault = dict() fault['code'] = vals[0]['faultCode'] fault['string'] = vals[0]['faultString'] return fault def py2xml(*args): params = ET.Element("{%s}params" % _namespace) for x in args: param = ET.Element("{%s}param" % _namespace) param.append(_py2xml(x)) params.append(param) #... return params def _py2xml(*args): for x in args: val = ET.Element("{%s}value" % _namespace) if x is None: nil = ET.Element("{%s}nil" % _namespace) val.append(nil) elif type(x) is int: i4 = ET.Element("{%s}i4" % _namespace) i4.text = str(x) val.append(i4) elif type(x) is bool: boolean = ET.Element("{%s}boolean" % _namespace) boolean.text = str(int(x)) val.append(boolean) elif type(x) is str: string = ET.Element("{%s}string" % _namespace) string.text = x val.append(string) elif type(x) is float: double = ET.Element("{%s}double" % _namespace) double.text = str(x) val.append(double) elif type(x) is rpcbase64: b64 = ET.Element("{%s}base64" % _namespace) b64.text = x.encoded() val.append(b64) elif type(x) is rpctime: iso = ET.Element("{%s}dateTime.iso8601" % _namespace) iso.text = str(x) val.append(iso) elif type(x) in (list, tuple): array = ET.Element("{%s}array" % _namespace) data = ET.Element("{%s}data" % _namespace) for y in x: data.append(_py2xml(y)) array.append(data) val.append(array) elif type(x) is dict: struct = ET.Element("{%s}struct" % _namespace) for y in x.keys(): member = ET.Element("{%s}member" % _namespace) name = ET.Element("{%s}name" % _namespace) name.text = y member.append(name) member.append(_py2xml(x[y])) struct.append(member) val.append(struct) return val def xml2py(params): namespace = 'jabber:iq:rpc' vals = [] for param in params.findall('{%s}param' % namespace): vals.append(_xml2py(param.find('{%s}value' % namespace))) return vals def _xml2py(value): namespace = 'jabber:iq:rpc' find_value = value.find if find_value('{%s}nil' % namespace) is not None: return None if find_value('{%s}i4' % namespace) is not None: return int(find_value('{%s}i4' % namespace).text) if find_value('{%s}int' % namespace) is not None: return int(find_value('{%s}int' % namespace).text) if find_value('{%s}boolean' % namespace) is not None: return bool(int(find_value('{%s}boolean' % namespace).text)) if find_value('{%s}string' % namespace) is not None: return find_value('{%s}string' % namespace).text if find_value('{%s}double' % namespace) is not None: return float(find_value('{%s}double' % namespace).text) if find_value('{%s}base64' % namespace) is not None: return rpcbase64(find_value('{%s}base64' % namespace).text.encode()) if find_value('{%s}Base64' % namespace) is not None: # Older versions of XEP-0009 used Base64 return rpcbase64(find_value('{%s}Base64' % namespace).text.encode()) if find_value('{%s}dateTime.iso8601' % namespace) is not None: return rpctime(find_value('{%s}dateTime.iso8601' % namespace).text) if find_value('{%s}struct' % namespace) is not None: struct = {} for member in find_value('{%s}struct' % namespace).findall('{%s}member' % namespace): struct[member.find('{%s}name' % namespace).text] = _xml2py(member.find('{%s}value' % namespace)) return struct if find_value('{%s}array' % namespace) is not None: array = [] for val in find_value('{%s}array' % namespace).find('{%s}data' % namespace).findall('{%s}value' % namespace): array.append(_xml2py(val)) return array raise ValueError() class rpcbase64(object): def __init__(self, data): #base 64 encoded string self.data = data def decode(self): return base64.b64decode(self.data) def __str__(self): return self.decode().decode() def encoded(self): return self.data.decode() class rpctime(object): def __init__(self,data=None): #assume string data is in iso format YYYYMMDDTHH:MM:SS if type(data) is str: self.timestamp = time.strptime(data,"%Y%m%dT%H:%M:%S") elif type(data) is time.struct_time: self.timestamp = data elif data is None: self.timestamp = time.gmtime() else: raise ValueError() def iso8601(self): #return a iso8601 string return time.strftime("%Y%m%dT%H:%M:%S",self.timestamp) def __str__(self): return self.iso8601() slixmpp-slix-1.4.2/slixmpp/plugins/xep_0009/remote.py000066400000000000000000000642771342457644200225100ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON). This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.xep_0009.binding import py2xml, xml2py, xml2fault, fault2xml from threading import RLock import abc import inspect import logging import slixmpp import sys import threading import traceback log = logging.getLogger(__name__) def _isstr(obj): return isinstance(obj, str) # Class decorator to declare a metaclass to a class in a way compatible with Python 2 and 3. # This decorator is copied from 'six' (https://bitbucket.org/gutworth/six): # # Copyright (c) 2010-2015 Benjamin Peterson # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. def _add_metaclass(metaclass): def wrapper(cls): orig_vars = cls.__dict__.copy() slots = orig_vars.get('__slots__') if slots is not None: if isinstance(slots, str): slots = [slots] for slots_var in slots: orig_vars.pop(slots_var) orig_vars.pop('__dict__', None) orig_vars.pop('__weakref__', None) return metaclass(cls.__name__, cls.__bases__, orig_vars) return wrapper def _intercept(method, name, public): def _resolver(instance, *args, **kwargs): log.debug("Locally calling %s.%s with arguments %s.", instance.FQN(), method.__name__, args) try: value = method(instance, *args, **kwargs) if value == NotImplemented: raise InvocationException("Local handler does not implement %s.%s!" % (instance.FQN(), method.__name__)) return value except InvocationException: raise except Exception as e: raise InvocationException("A problem occurred calling %s.%s!" % (instance.FQN(), method.__name__), e) _resolver._rpc = public _resolver._rpc_name = method.__name__ if name is None else name return _resolver def remote(function_argument, public = True): ''' Decorator for methods which are remotely callable. This decorator works in conjunction with classes which extend ABC Endpoint. Example: @remote def remote_method(arg1, arg2) Arguments: function_argument -- a stand-in for either the actual method OR a new name (string) for the method. In that case the method is considered mapped: Example: @remote("new_name") def remote_method(arg1, arg2) public -- A flag which indicates if this method should be part of the known dictionary of remote methods. Defaults to True. Example: @remote(False) def remote_method(arg1, arg2) Note: renaming and revising (public vs. private) can be combined. Example: @remote("new_name", False) def remote_method(arg1, arg2) ''' if hasattr(function_argument, '__call__'): return _intercept(function_argument, None, public) else: if not _isstr(function_argument): if not isinstance(function_argument, bool): raise Exception('Expected an RPC method name or visibility modifier!') else: def _wrap_revised(function): function = _intercept(function, None, function_argument) return function return _wrap_revised def _wrap_remapped(function): function = _intercept(function, function_argument, public) return function return _wrap_remapped class ACL: ''' An Access Control List (ACL) is a list of rules, which are evaluated in order until a match is found. The policy of the matching rule is then applied. Rules are 3-tuples, consisting of a policy enumerated type, a JID expression and a RCP resource expression. Examples: [ (ACL.ALLOW, '*', '*') ] allow everyone everything, no restrictions [ (ACL.DENY, '*', '*') ] deny everyone everything, no restrictions [ (ACL.ALLOW, 'test@xmpp.org/unit', 'test.*'), (ACL.DENY, '*', '*') ] deny everyone everything, except named JID, which is allowed access to endpoint 'test' only. The use of wildcards is allowed in expressions, as follows: '*' everyone, or everything (= all endpoints and methods) 'test@xmpp.org/*' every JID regardless of JID resource '*@xmpp.org/rpc' every JID from domain xmpp.org with JID res 'rpc' 'frank@*' every 'frank', regardless of domain or JID res 'system.*' all methods of endpoint 'system' '*.reboot' all methods reboot regardless of endpoint ''' ALLOW = True DENY = False @classmethod def check(cls, rules, jid, resource): if rules is None: return cls.DENY # No rules means no access! jid = str(jid) # Check the string representation of the JID. if not jid: return cls.DENY # Can't check an empty JID. for rule in rules: policy = cls._check(rule, jid, resource) if policy is not None: return policy return cls.DENY # By default if not rule matches, deny access. @classmethod def _check(cls, rule, jid, resource): if cls._match(jid, rule[1]) and cls._match(resource, rule[2]): return rule[0] else: return None @classmethod def _next_token(cls, expression, index): new_index = expression.xml.find('*', index) if new_index == 0: return '' else: if new_index == -1: return expression[index : ] else: return expression[index : new_index] @classmethod def _match(cls, value, expression): #! print "_match [VALUE] %s [EXPR] %s" % (value, expression) index = 0 position = 0 while index < len(expression): token = cls._next_token(expression, index) #! print "[TOKEN] '%s'" % token size = len(token) if size > 0: token_index = value.xml.find(token, position) if token_index == -1: return False else: #! print "[INDEX-OF] %s" % token_index position = token_index + len(token) pass if size == 0: index += 1 else: index += size #! print "index %s position %s" % (index, position) return True ANY_ALL = [ (ACL.ALLOW, '*', '*') ] class RemoteException(Exception): ''' Base exception for RPC. This exception is raised when a problem occurs in the network layer. ''' def __init__(self, message="", cause=None): ''' Initializes a new RemoteException. Arguments: message -- The message accompanying this exception. cause -- The underlying cause of this exception. ''' self._message = message self._cause = cause pass def __str__(self): return repr(self._message) def get_message(self): return self._message def get_cause(self): return self._cause class InvocationException(RemoteException): ''' Exception raised when a problem occurs during the remote invocation of a method. ''' pass class AuthorizationException(RemoteException): ''' Exception raised when the caller is not authorized to invoke the remote method. ''' pass class TimeoutException(Exception): ''' Exception raised when the synchronous execution of a method takes longer than the given threshold because an underlying asynchronous reply did not arrive in time. ''' pass @_add_metaclass(abc.ABCMeta) class Callback(object): ''' A base class for callback handlers. ''' @abc.abstractproperty def set_value(self, value): return NotImplemented @abc.abstractproperty def cancel_with_error(self, exception): return NotImplemented class Future(Callback): ''' Represents the result of an asynchronous computation. ''' def __init__(self): ''' Initializes a new Future. ''' self._value = None self._exception = None self._event = threading.Event() pass def set_value(self, value): ''' Sets the value of this Future. Once the value is set, a caller blocked on get_value will be able to continue. ''' self._value = value self._event.set() def get_value(self, timeout=None): ''' Gets the value of this Future. This call will block until the result is available, or until an optional timeout expires. When this Future is cancelled with an error, Arguments: timeout -- The maximum waiting time to obtain the value. ''' self._event.wait(timeout) if self._exception: raise self._exception if not self._event.is_set(): raise TimeoutException return self._value def is_done(self): ''' Returns true if a value has been returned. ''' return self._event.is_set() def cancel_with_error(self, exception): ''' Cancels the Future because of an error. Once cancelled, a caller blocked on get_value will be able to continue. ''' self._exception = exception self._event.set() @_add_metaclass(abc.ABCMeta) class Endpoint(object): ''' The Endpoint class is an abstract base class for all objects participating in an RPC-enabled XMPP network. A user subclassing this class is required to implement the method: FQN(self) where FQN stands for Fully Qualified Name, an unambiguous name which specifies which object an RPC call refers to. It is the first part in a RPC method name '.'. ''' def __init__(self, session, target_jid): ''' Initialize a new Endpoint. This constructor should never be invoked by a user, instead it will be called by the factories which instantiate the RPC-enabled objects, of which only the classes are provided by the user. Arguments: session -- An RPC session instance. target_jid -- the identity of the remote XMPP entity. ''' self.session = session self.target_jid = target_jid @abc.abstractproperty def FQN(self): return NotImplemented def get_methods(self): ''' Returns a dictionary of all RPC method names provided by this class. This method returns the actual method names as found in the class definition which have been decorated with: @remote def some_rpc_method(arg1, arg2) Unless: (1) the name has been remapped, in which case the new name will be returned. @remote("new_name") def some_rpc_method(arg1, arg2) (2) the method is set to hidden @remote(False) def some_hidden_method(arg1, arg2) ''' result = dict() for function in dir(self): test_attr = getattr(self, function, None) try: if test_attr._rpc: result[test_attr._rpc_name] = test_attr except Exception: pass return result class Proxy(Endpoint): ''' Implementation of the Proxy pattern which is intended to wrap around Endpoints in order to intercept calls, marshall them and forward them to the remote object. ''' def __init__(self, endpoint, callback = None): ''' Initializes a new Proxy. Arguments: endpoint -- The endpoint which is proxified. ''' self._endpoint = endpoint self._callback = callback def __getattribute__(self, name, *args): if name in ('__dict__', '_endpoint', '_callback'): return object.__getattribute__(self, name) elif name == 'async': return lambda callback: Proxy(self._endpoint, callback) else: attribute = self._endpoint.__getattribute__(name) if hasattr(attribute, '__call__'): try: if attribute._rpc: def _remote_call(*args, **kwargs): log.debug("Remotely calling '%s.%s' with arguments %s.", self._endpoint.FQN(), attribute._rpc_name, args) return self._endpoint.session._call_remote(self._endpoint.target_jid, "%s.%s" % (self._endpoint.FQN(), attribute._rpc_name), self._callback, *args, **kwargs) return _remote_call except: pass # If the attribute doesn't exist, don't care! return attribute def get_endpoint(self): ''' Returns the proxified endpoint. ''' return self._endpoint def FQN(self): return self._endpoint.FQN() class JabberRPCEntry(object): def __init__(self, endpoint_FQN, call): self._endpoint_FQN = endpoint_FQN self._call = call def call_method(self, args): return_value = self._call(*args) if return_value is None: return return_value else: return self._return(return_value) def get_endpoint_FQN(self): return self._endpoint_FQN def _return(self, *args): return args class RemoteSession(object): ''' A context object for a Jabber-RPC session. ''' def __init__(self, client, session_close_callback): ''' Initializes a new RPC session. Arguments: client -- The Slixmpp client associated with this session. session_close_callback -- A callback called when the session is closed. ''' self._client = client self._session_close_callback = session_close_callback self._event = threading.Event() self._entries = {} self._callbacks = {} self._acls = {} self._lock = RLock() def _wait(self): self._event.wait() def _notify(self, event): log.debug("RPC Session as %s started.", self._client.boundjid.full) self._client.send_presence() self._event.set() pass def _register_call(self, endpoint, method, name=None): ''' Registers a method from an endpoint as remotely callable. ''' if name is None: name = method.__name__ key = "%s.%s" % (endpoint, name) log.debug("Registering call handler for %s (%s).", key, method) with self._lock: if key in self._entries: raise KeyError("A handler for %s has already been regisered!" % endpoint) self._entries[key] = JabberRPCEntry(endpoint, method) return key def _register_acl(self, endpoint, acl): log.debug("Registering ACL %s for endpoint %s.", repr(acl), endpoint) with self._lock: self._acls[endpoint] = acl def _register_callback(self, pid, callback): with self._lock: self._callbacks[pid] = callback def forget_callback(self, callback): with self._lock: pid = self._find_key(self._callbacks, callback) if pid is not None: del self._callback[pid] else: raise ValueError("Unknown callback!") pass def _find_key(self, dict, value): """return the key of dictionary dic given the value""" search = [k for k, v in dict.items() if v == value] if len(search) == 0: return None else: return search[0] def _unregister_call(self, key): #removes the registered call with self._lock: if self._entries[key]: del self._entries[key] else: raise ValueError() def new_proxy(self, target_jid, endpoint_cls): ''' Instantiates a new proxy object, which proxies to a remote endpoint. This method uses a class reference without constructor arguments to instantiate the proxy. Arguments: target_jid -- the XMPP entity ID hosting the endpoint. endpoint_cls -- The remote (duck) type. ''' try: argspec = inspect.getargspec(endpoint_cls.__init__) args = [None] * (len(argspec[0]) - 1) result = endpoint_cls(*args) Endpoint.__init__(result, self, target_jid) return Proxy(result) except: traceback.print_exc(file=sys.stdout) def new_handler(self, acl, handler_cls, *args, **kwargs): ''' Instantiates a new handler object, which is called remotely by others. The user can control the effect of the call by implementing the remote method in the local endpoint class. The returned reference can be called locally and will behave as a regular instance. Arguments: acl -- Access control list (see ACL class) handler_clss -- The local (duck) type. *args -- Constructor arguments for the local type. **kwargs -- Constructor keyworded arguments for the local type. ''' argspec = inspect.getargspec(handler_cls.__init__) base_argspec = inspect.getargspec(Endpoint.__init__) if(argspec == base_argspec): result = handler_cls(self, self._client.boundjid.full) else: result = handler_cls(*args, **kwargs) Endpoint.__init__(result, self, self._client.boundjid.full) method_dict = result.get_methods() for method_name, method in method_dict.items(): #!!! self._client.plugin['xep_0009'].register_call(result.FQN(), method, method_name) self._register_call(result.FQN(), method, method_name) self._register_acl(result.FQN(), acl) return result # def is_available(self, targetCls, pto): # return self._client.is_available(pto) def _call_remote(self, pto, pmethod, callback, *arguments): iq = self._client.plugin['xep_0009'].make_iq_method_call(pto, pmethod, py2xml(*arguments)) pid = iq['id'] if callback is None: future = Future() self._register_callback(pid, future) iq.send() return future.get_value(30) else: log.debug("[RemoteSession] _call_remote %s", callback) self._register_callback(pid, callback) iq.send() def close(self, wait=False): ''' Closes this session. ''' self._client.disconnect(wait=wait) self._session_close_callback() def _on_jabber_rpc_method_call(self, iq): iq.enable('rpc_query') params = iq['rpc_query']['method_call']['params'] args = xml2py(params) pmethod = iq['rpc_query']['method_call']['method_name'] try: with self._lock: entry = self._entries[pmethod] rules = self._acls[entry.get_endpoint_FQN()] if ACL.check(rules, iq['from'], pmethod): return_value = entry.call_method(args) else: raise AuthorizationException("Unauthorized access to %s from %s!" % (pmethod, iq['from'])) if return_value is None: return_value = () response = self._client.plugin['xep_0009'].make_iq_method_response(iq['id'], iq['from'], py2xml(*return_value)) response.send() except InvocationException as ie: fault = dict() fault['code'] = 500 fault['string'] = ie.get_message() self._client.plugin['xep_0009']._send_fault(iq, fault2xml(fault)) except AuthorizationException as ae: log.error(ae.get_message()) error = self._client.plugin['xep_0009']._forbidden(iq) error.send() except Exception as e: if isinstance(e, KeyError): log.error("No handler available for %s!", pmethod) error = self._client.plugin['xep_0009']._item_not_found(iq) else: traceback.print_exc(file=sys.stderr) log.error("An unexpected problem occurred invoking method %s!", pmethod) error = self._client.plugin['xep_0009']._undefined_condition(iq) #! print "[REMOTE.PY] _handle_remote_procedure_call AN ERROR SHOULD BE SENT NOW %s " % e error.send() def _on_jabber_rpc_method_response(self, iq): iq.enable('rpc_query') args = xml2py(iq['rpc_query']['method_response']['params']) pid = iq['id'] with self._lock: callback = self._callbacks[pid] del self._callbacks[pid] if(len(args) > 0): callback.set_value(args[0]) else: callback.set_value(None) pass def _on_jabber_rpc_method_response2(self, iq): iq.enable('rpc_query') if iq['rpc_query']['method_response']['fault'] is not None: self._on_jabber_rpc_method_fault(iq) else: args = xml2py(iq['rpc_query']['method_response']['params']) pid = iq['id'] with self._lock: callback = self._callbacks[pid] del self._callbacks[pid] if(len(args) > 0): callback.set_value(args[0]) else: callback.set_value(None) pass def _on_jabber_rpc_method_fault(self, iq): iq.enable('rpc_query') fault = xml2fault(iq['rpc_query']['method_response']['fault']) pid = iq['id'] with self._lock: callback = self._callbacks[pid] del self._callbacks[pid] e = { 500: InvocationException }[fault['code']](fault['string']) callback.cancel_with_error(e) def _on_jabber_rpc_error(self, iq): pid = iq['id'] pmethod = self._client.plugin['xep_0009']._extract_method(iq['rpc_query']) code = iq['error']['code'] type = iq['error']['type'] condition = iq['error']['condition'] #! print("['REMOTE.PY']._BINDING_handle_remote_procedure_error -> ERROR! ERROR! ERROR! Condition is '%s'" % condition) with self._lock: callback = self._callbacks[pid] del self._callbacks[pid] e = { 'item-not-found': RemoteException("No remote handler available for %s at %s!" % (pmethod, iq['from'])), 'forbidden': AuthorizationException("Forbidden to invoke remote handler for %s at %s!" % (pmethod, iq['from'])), 'undefined-condition': RemoteException("An unexpected problem occurred trying to invoke %s at %s!" % (pmethod, iq['from'])), }[condition] if e is None: RemoteException("An unexpected exception occurred at %s!" % iq['from']) callback.cancel_with_error(e) class Remote(object): ''' Bootstrap class for Jabber-RPC sessions. New sessions are openend with an existing XMPP client, or one is instantiated on demand. ''' _instance = None _sessions = dict() _lock = threading.RLock() @classmethod def new_session_with_client(cls, client, callback=None): ''' Opens a new session with a given client. Arguments: client -- An XMPP client. callback -- An optional callback which can be used to track the starting state of the session. ''' with Remote._lock: if(client.boundjid.bare in cls._sessions): raise RemoteException("There already is a session associated with these credentials!") else: cls._sessions[client.boundjid.bare] = client def _session_close_callback(): with Remote._lock: del cls._sessions[client.boundjid.bare] result = RemoteSession(client, _session_close_callback) client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_call', result._on_jabber_rpc_method_call, threaded=True) client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_response', result._on_jabber_rpc_method_response, threaded=True) client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_fault', result._on_jabber_rpc_method_fault, threaded=True) client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_error', result._on_jabber_rpc_error, threaded=True) if callback is None: start_event_handler = result._notify else: start_event_handler = callback client.add_event_handler("session_start", start_event_handler) if client.connect(): client.process(threaded=True) else: raise RemoteException("Could not connect to XMPP server!") pass if callback is None: result._wait() return result @classmethod def new_session(cls, jid, password, callback=None): ''' Opens a new session and instantiates a new XMPP client. Arguments: jid -- The XMPP JID for logging in. password -- The password for logging in. callback -- An optional callback which can be used to track the starting state of the session. ''' client = slixmpp.ClientXMPP(jid, password) #? Register plug-ins. client.register_plugin('xep_0004') # Data Forms client.register_plugin('xep_0009') # Jabber-RPC client.register_plugin('xep_0030') # Service Discovery client.register_plugin('xep_0060') # PubSub client.register_plugin('xep_0199') # XMPP Ping return cls.new_session_with_client(client, callback) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0009/rpc.py000066400000000000000000000207241342457644200217660ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON). This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp import Iq from slixmpp.xmlstream import ET, register_stanza_plugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import MatchXPath from slixmpp.plugins import BasePlugin from slixmpp.plugins.xep_0009 import stanza from slixmpp.plugins.xep_0009.stanza.RPC import RPCQuery, MethodCall, MethodResponse log = logging.getLogger(__name__) class XEP_0009(BasePlugin): name = 'xep_0009' description = 'XEP-0009: Jabber-RPC' dependencies = {'xep_0030'} stanza = stanza def plugin_init(self): register_stanza_plugin(Iq, RPCQuery) register_stanza_plugin(RPCQuery, MethodCall) register_stanza_plugin(RPCQuery, MethodResponse) self.xmpp.register_handler( Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodCall' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)), self._handle_method_call) ) self.xmpp.register_handler( Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodResponse' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)), self._handle_method_response) ) self.xmpp.register_handler( Callback('RPC Call', MatchXPath('{%s}iq/{%s}error' % (self.xmpp.default_ns, self.xmpp.default_ns)), self._handle_error) ) self.xmpp.add_event_handler('jabber_rpc_method_call', self._on_jabber_rpc_method_call) self.xmpp.add_event_handler('jabber_rpc_method_response', self._on_jabber_rpc_method_response) self.xmpp.add_event_handler('jabber_rpc_method_fault', self._on_jabber_rpc_method_fault) self.xmpp.add_event_handler('jabber_rpc_error', self._on_jabber_rpc_error) self.xmpp.add_event_handler('error', self._handle_error) #self.activeCalls = [] self.xmpp['xep_0030'].add_feature('jabber:iq:rpc') self.xmpp['xep_0030'].add_identity('automation','rpc') def make_iq_method_call(self, pto, pmethod, params): iq = self.xmpp.make_iq_set() iq.attrib['to'] = pto iq.attrib['from'] = self.xmpp.boundjid.full iq.enable('rpc_query') iq['rpc_query']['method_call']['method_name'] = pmethod iq['rpc_query']['method_call']['params'] = params return iq def make_iq_method_response(self, pid, pto, params): iq = self.xmpp.make_iq_result(pid) iq.attrib['to'] = pto iq.attrib['from'] = self.xmpp.boundjid.full iq.enable('rpc_query') iq['rpc_query']['method_response']['params'] = params return iq def make_iq_method_response_fault(self, pid, pto, params): iq = self.xmpp.make_iq_result(pid) iq.attrib['to'] = pto iq.attrib['from'] = self.xmpp.boundjid.full iq.enable('rpc_query') iq['rpc_query']['method_response']['params'] = None iq['rpc_query']['method_response']['fault'] = params return iq # def make_iq_method_error(self, pto, pid, pmethod, params, code, type, condition): # iq = self.xmpp.make_iq_error(pid) # iq.attrib['to'] = pto # iq.attrib['from'] = self.xmpp.boundjid.full # iq['error']['code'] = code # iq['error']['type'] = type # iq['error']['condition'] = condition # iq['rpc_query']['method_call']['method_name'] = pmethod # iq['rpc_query']['method_call']['params'] = params # return iq def _item_not_found(self, iq): payload = iq.get_payload() iq = iq.reply() iq.error().set_payload(payload) iq['error']['code'] = '404' iq['error']['type'] = 'cancel' iq['error']['condition'] = 'item-not-found' return iq def _undefined_condition(self, iq): payload = iq.get_payload() iq = iq.reply() iq.error().set_payload(payload) iq['error']['code'] = '500' iq['error']['type'] = 'cancel' iq['error']['condition'] = 'undefined-condition' return iq def _forbidden(self, iq): payload = iq.get_payload() iq = iq.reply() iq.error().set_payload(payload) iq['error']['code'] = '403' iq['error']['type'] = 'auth' iq['error']['condition'] = 'forbidden' return iq def _recipient_unvailable(self, iq): payload = iq.get_payload() iq = iq.reply() iq.error().set_payload(payload) iq['error']['code'] = '404' iq['error']['type'] = 'wait' iq['error']['condition'] = 'recipient-unavailable' return iq def _handle_method_call(self, iq): type = iq['type'] if type == 'set': log.debug("Incoming Jabber-RPC call from %s", iq['from']) self.xmpp.event('jabber_rpc_method_call', iq) else: if type == 'error' and ['rpc_query'] is None: self.handle_error(iq) else: log.debug("Incoming Jabber-RPC error from %s", iq['from']) self.xmpp.event('jabber_rpc_error', iq) def _handle_method_response(self, iq): if iq['rpc_query']['method_response']['fault'] is not None: log.debug("Incoming Jabber-RPC fault from %s", iq['from']) #self._on_jabber_rpc_method_fault(iq) self.xmpp.event('jabber_rpc_method_fault', iq) else: log.debug("Incoming Jabber-RPC response from %s", iq['from']) self.xmpp.event('jabber_rpc_method_response', iq) def _handle_error(self, iq): print("['XEP-0009']._handle_error -> ERROR! Iq is '%s'" % iq) print("#######################") print("### NOT IMPLEMENTED ###") print("#######################") def _on_jabber_rpc_method_call(self, iq, forwarded=False): """ A default handler for Jabber-RPC method call. If another handler is registered, this one will defer and not run. If this handler is called by your own custom handler with forwarded set to True, then it will run as normal. """ if not forwarded and self.xmpp.event_handled('jabber_rpc_method_call') > 1: return # Reply with error by default error = self.client.plugin['xep_0009']._item_not_found(iq) error.send() def _on_jabber_rpc_method_response(self, iq, forwarded=False): """ A default handler for Jabber-RPC method response. If another handler is registered, this one will defer and not run. If this handler is called by your own custom handler with forwarded set to True, then it will run as normal. """ if not forwarded and self.xmpp.event_handled('jabber_rpc_method_response') > 1: return error = self.client.plugin['xep_0009']._recpient_unavailable(iq) error.send() def _on_jabber_rpc_method_fault(self, iq, forwarded=False): """ A default handler for Jabber-RPC fault response. If another handler is registered, this one will defer and not run. If this handler is called by your own custom handler with forwarded set to True, then it will run as normal. """ if not forwarded and self.xmpp.event_handled('jabber_rpc_method_fault') > 1: return error = self.client.plugin['xep_0009']._recpient_unavailable(iq) error.send() def _on_jabber_rpc_error(self, iq, forwarded=False): """ A default handler for Jabber-RPC error response. If another handler is registered, this one will defer and not run. If this handler is called by your own custom handler with forwarded set to True, then it will run as normal. """ if not forwarded and self.xmpp.event_handled('jabber_rpc_error') > 1: return error = self.client.plugin['xep_0009']._recpient_unavailable(iq, iq.get_payload()) error.send() def _send_fault(self, iq, fault_xml): # fault = self.make_iq_method_response_fault(iq['id'], iq['from'], fault_xml) fault.send() def _send_error(self, iq): print("['XEP-0009']._send_error -> ERROR! Iq is '%s'" % iq) print("#######################") print("### NOT IMPLEMENTED ###") print("#######################") def _extract_method(self, stanza): xml = ET.fromstring("%s" % stanza) return xml.find("./methodCall/methodName").text slixmpp-slix-1.4.2/slixmpp/plugins/xep_0009/stanza/000077500000000000000000000000001342457644200221235ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0009/stanza/RPC.py000066400000000000000000000031031342457644200231160ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON). This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream.stanzabase import ElementBase from xml.etree import cElementTree as ET class RPCQuery(ElementBase): name = 'query' namespace = 'jabber:iq:rpc' plugin_attrib = 'rpc_query' interfaces = {} subinterfaces = {} plugin_attrib_map = {} plugin_tag_map = {} class MethodCall(ElementBase): name = 'methodCall' namespace = 'jabber:iq:rpc' plugin_attrib = 'method_call' interfaces = {'method_name', 'params'} subinterfaces = {} plugin_attrib_map = {} plugin_tag_map = {} def get_method_name(self): return self._get_sub_text('methodName') def set_method_name(self, value): return self._set_sub_text('methodName', value) def get_params(self): return self.xml.find('{%s}params' % self.namespace) def set_params(self, params): self.append(params) class MethodResponse(ElementBase): name = 'methodResponse' namespace = 'jabber:iq:rpc' plugin_attrib = 'method_response' interfaces = {'params', 'fault'} subinterfaces = {} plugin_attrib_map = {} plugin_tag_map = {} def get_params(self): return self.xml.find('{%s}params' % self.namespace) def set_params(self, params): self.append(params) def get_fault(self): return self.xml.find('{%s}fault' % self.namespace) def set_fault(self, fault): self.append(fault) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0009/stanza/__init__.py000066400000000000000000000004311342457644200242320ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON). This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.xep_0009.stanza.RPC import RPCQuery, MethodCall, MethodResponse slixmpp-slix-1.4.2/slixmpp/plugins/xep_0012/000077500000000000000000000000001342457644200206155ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0012/__init__.py000066400000000000000000000006001342457644200227220ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0012.stanza import LastActivity from slixmpp.plugins.xep_0012.last_activity import XEP_0012 register_plugin(XEP_0012) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0012/last_activity.py000066400000000000000000000117221342457644200240510ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from datetime import datetime, timedelta from slixmpp.plugins import BasePlugin, register_plugin from slixmpp import future_wrapper, Iq from slixmpp.exceptions import XMPPError from slixmpp.xmlstream import JID, register_stanza_plugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.plugins.xep_0012 import stanza, LastActivity log = logging.getLogger(__name__) class XEP_0012(BasePlugin): """ XEP-0012 Last Activity """ name = 'xep_0012' description = 'XEP-0012: Last Activity' dependencies = {'xep_0030'} stanza = stanza def plugin_init(self): register_stanza_plugin(Iq, LastActivity) self._last_activities = {} self.xmpp.register_handler( Callback('Last Activity', StanzaPath('iq@type=get/last_activity'), self._handle_get_last_activity)) self.api.register(self._default_get_last_activity, 'get_last_activity', default=True) self.api.register(self._default_set_last_activity, 'set_last_activity', default=True) self.api.register(self._default_del_last_activity, 'del_last_activity', default=True) def plugin_end(self): self.xmpp.remove_handler('Last Activity') self.xmpp['xep_0030'].del_feature(feature='jabber:iq:last') def session_bind(self, jid): self.xmpp['xep_0030'].add_feature('jabber:iq:last') def begin_idle(self, jid=None, status=None): self.set_last_activity(jid, 0, status) def end_idle(self, jid=None): self.del_last_activity(jid) def start_uptime(self, status=None): self.set_last_activity(None, 0, status) def set_last_activity(self, jid=None, seconds=None, status=None): self.api['set_last_activity'](jid, args={ 'seconds': seconds, 'status': status}) def del_last_activity(self, jid): self.api['del_last_activity'](jid) @future_wrapper def get_last_activity(self, jid, local=False, ifrom=None, timeout=None, callback=None, timeout_callback=None): if jid is not None and not isinstance(jid, JID): jid = JID(jid) if self.xmpp.is_component: if jid.domain == self.xmpp.boundjid.domain: local = True else: if str(jid) == str(self.xmpp.boundjid): local = True jid = jid.full if local or jid in (None, ''): log.debug("Looking up local last activity data for %s", jid) return self.api['get_last_activity'](jid, None, ifrom, None) iq = self.xmpp.Iq() iq['from'] = ifrom iq['to'] = jid iq['type'] = 'get' iq.enable('last_activity') return iq.send(timeout=timeout, callback=callback, timeout_callback=timeout_callback) def _handle_get_last_activity(self, iq): log.debug("Received last activity query from " + \ "<%s> to <%s>.", iq['from'], iq['to']) reply = self.api['get_last_activity'](iq['to'], None, iq['from'], iq) reply.send() # ================================================================= # Default in-memory implementations for storing last activity data. # ================================================================= def _default_set_last_activity(self, jid, node, ifrom, data): seconds = data.get('seconds', None) if seconds is None: seconds = 0 status = data.get('status', None) if status is None: status = '' self._last_activities[jid] = { 'seconds': datetime.now() - timedelta(seconds=seconds), 'status': status} def _default_del_last_activity(self, jid, node, ifrom, data): if jid in self._last_activities: del self._last_activities[jid] def _default_get_last_activity(self, jid, node, ifrom, iq): if not isinstance(iq, Iq): reply = self.xmpp.Iq() else: reply = iq.reply() if jid not in self._last_activities: raise XMPPError('service-unavailable') bare = JID(jid).bare if bare != self.xmpp.boundjid.bare: if bare in self.xmpp.roster[jid]: sub = self.xmpp.roster[jid][bare]['subscription'] if sub not in ('from', 'both'): raise XMPPError('forbidden') td = datetime.now() - self._last_activities[jid]['seconds'] seconds = td.seconds + td.days * 24 * 3600 status = self._last_activities[jid]['status'] reply['last_activity']['seconds'] = seconds reply['last_activity']['status'] = status return reply slixmpp-slix-1.4.2/slixmpp/plugins/xep_0012/stanza.py000066400000000000000000000013301342457644200224640ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase class LastActivity(ElementBase): name = 'query' namespace = 'jabber:iq:last' plugin_attrib = 'last_activity' interfaces = {'seconds', 'status'} def get_seconds(self): return int(self._get_attr('seconds')) def set_seconds(self, value): self._set_attr('seconds', str(value)) def get_status(self): return self.xml.text def set_status(self, value): self.xml.text = str(value) def del_status(self): self.xml.text = '' slixmpp-slix-1.4.2/slixmpp/plugins/xep_0013/000077500000000000000000000000001342457644200206165ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0013/__init__.py000066400000000000000000000005631342457644200227330ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permissio """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0013.stanza import Offline from slixmpp.plugins.xep_0013.offline import XEP_0013 register_plugin(XEP_0013) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0013/offline.py000066400000000000000000000073461342457644200226240ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permissio """ import logging import slixmpp from slixmpp.stanza import Message, Iq from slixmpp.exceptions import XMPPError from slixmpp.xmlstream.handler import Collector from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins import BasePlugin from slixmpp.plugins.xep_0013 import stanza log = logging.getLogger(__name__) class XEP_0013(BasePlugin): """ XEP-0013 Flexible Offline Message Retrieval """ name = 'xep_0013' description = 'XEP-0013: Flexible Offline Message Retrieval' dependencies = {'xep_0030'} stanza = stanza def plugin_init(self): register_stanza_plugin(Iq, stanza.Offline) register_stanza_plugin(Message, stanza.Offline) def get_count(self, **kwargs): return self.xmpp['xep_0030'].get_info( node='http://jabber.org/protocol/offline', local=False, **kwargs) def get_headers(self, **kwargs): return self.xmpp['xep_0030'].get_items( node='http://jabber.org/protocol/offline', local=False, **kwargs) def view(self, nodes, ifrom=None, timeout=None, callback=None, timeout_callback=None): if not isinstance(nodes, (list, set)): nodes = [nodes] iq = self.xmpp.Iq() iq['type'] = 'get' iq['from'] = ifrom offline = iq['offline'] for node in nodes: item = stanza.Item() item['node'] = node item['action'] = 'view' offline.append(item) collector = Collector( 'Offline_Results_%s' % iq['id'], StanzaPath('message/offline')) self.xmpp.register_handler(collector) def wrapped_cb(iq): results = collector.stop() if iq['type'] == 'result': iq['offline']['results'] = results callback(iq) iq.send(timeout=timeout, callback=wrapped_cb, timeout_callback=timeout_callback) def remove(self, nodes, ifrom=None, timeout=None, callback=None, timeout_callback=None): if not isinstance(nodes, (list, set)): nodes = [nodes] iq = self.xmpp.Iq() iq['type'] = 'set' iq['from'] = ifrom offline = iq['offline'] for node in nodes: item = stanza.Item() item['node'] = node item['action'] = 'remove' offline.append(item) iq.send(timeout=timeout, callback=callback, timeout_callback=timeout_callback) def fetch(self, ifrom=None, timeout=None, callback=None, timeout_callback=None): iq = self.xmpp.Iq() iq['type'] = 'set' iq['from'] = ifrom iq['offline']['fetch'] = True collector = Collector( 'Offline_Results_%s' % iq['id'], StanzaPath('message/offline')) self.xmpp.register_handler(collector) def wrapped_cb(iq): results = collector.stop() if iq['type'] == 'result': iq['offline']['results'] = results callback(iq) iq.send(timeout=timeout, callback=wrapped_cb, timeout_callback=timeout_callback) def purge(self, ifrom=None, timeout=None, callback=None, timeout_callback=None): iq = self.xmpp.Iq() iq['type'] = 'set' iq['from'] = ifrom iq['offline']['purge'] = True iq.send(timeout=timeout, callback=callback, timeout_callback=timeout_callback) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0013/stanza.py000066400000000000000000000024251342457644200224730ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permissio """ from slixmpp.jid import JID from slixmpp.xmlstream import ElementBase, register_stanza_plugin class Offline(ElementBase): name = 'offline' namespace = 'http://jabber.org/protocol/offline' plugin_attrib = 'offline' interfaces = {'fetch', 'purge', 'results'} bool_interfaces = interfaces def setup(self, xml=None): ElementBase.setup(self, xml) self._results = [] # The results interface is meant only as an easy # way to access the set of collected message responses # from the query. def get_results(self): return self._results def set_results(self, values): self._results = values def del_results(self): self._results = [] class Item(ElementBase): name = 'item' namespace = 'http://jabber.org/protocol/offline' plugin_attrib = 'item' interfaces = {'action', 'node', 'jid'} actions = {'view', 'remove'} def get_jid(self): return JID(self._get_attr('jid')) def set_jid(self, value): self._set_attr('jid', str(value)) register_stanza_plugin(Offline, Item, iterable=True) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0016/000077500000000000000000000000001342457644200206215ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0016/__init__.py000066400000000000000000000006411342457644200227330ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0016 import stanza from slixmpp.plugins.xep_0016.stanza import Privacy from slixmpp.plugins.xep_0016.privacy import XEP_0016 register_plugin(XEP_0016) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0016/privacy.py000066400000000000000000000104771342457644200226610ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp import Iq from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins import BasePlugin from slixmpp.plugins.xep_0016 import stanza from slixmpp.plugins.xep_0016.stanza import Privacy, Item class XEP_0016(BasePlugin): name = 'xep_0016' description = 'XEP-0016: Privacy Lists' dependencies = {'xep_0030'} stanza = stanza def plugin_init(self): register_stanza_plugin(Iq, Privacy) def plugin_end(self): self.xmpp['xep_0030'].del_feature(feature=Privacy.namespace) def session_bind(self, jid): self.xmpp['xep_0030'].add_feature(Privacy.namespace) def get_privacy_lists(self, timeout=None, callback=None, timeout_callback=None): iq = self.xmpp.Iq() iq['type'] = 'get' iq.enable('privacy') iq.send(timeout=timeout, callback=callback, timeout_callback=timeout_callback) def get_list(self, name, timeout=None, callback=None, timeout_callback=None): iq = self.xmpp.Iq() iq['type'] = 'get' iq['privacy']['list']['name'] = name iq.send(timeout=timeout, callback=callback, timeout_callback=timeout_callback) def get_active(self, timeout=None, callback=None, timeout_callback=None): iq = self.xmpp.Iq() iq['type'] = 'get' iq['privacy'].enable('active') iq.send(timeout=timeout, callback=callback, timeout_callback=timeout_callback) def get_default(self, timeout=None, callback=None, timeout_callback=None): iq = self.xmpp.Iq() iq['type'] = 'get' iq['privacy'].enable('default') iq.send(timeout=timeout, callback=callback, timeout_callback=timeout_callback) def activate(self, name, timeout=None, callback=None, timeout_callback=None): iq = self.xmpp.Iq() iq['type'] = 'set' iq['privacy']['active']['name'] = name iq.send(timeout=timeout, callback=callback, timeout_callback=timeout_callback) def deactivate(self, timeout=None, callback=None, timeout_callback=None): iq = self.xmpp.Iq() iq['type'] = 'set' iq['privacy'].enable('active') iq.send(timeout=timeout, callback=callback, timeout_callback=timeout_callback) def make_default(self, name, timeout=None, callback=None, timeout_callback=None): iq = self.xmpp.Iq() iq['type'] = 'set' iq['privacy']['default']['name'] = name iq.send(timeout=timeout, callback=callback, timeout_callback=timeout_callback) def remove_default(self, timeout=None, callback=None, timeout_callback=None): iq = self.xmpp.Iq() iq['type'] = 'set' iq['privacy'].enable('default') iq.send(timeout=timeout, callback=callback, timeout_callback=timeout_callback) def edit_list(self, name, rules, timeout=None, callback=None, timeout_callback=None): iq = self.xmpp.Iq() iq['type'] = 'set' iq['privacy']['list']['name'] = name priv_list = iq['privacy']['list'] if not rules: rules = [] for rule in rules: if isinstance(rule, Item): priv_list.append(rule) continue priv_list.add_item( rule['value'], rule['action'], rule['order'], itype=rule.get('type', None), iq=rule.get('iq', None), message=rule.get('message', None), presence_in=rule.get('presence_in', rule.get('presence-in', None)), presence_out=rule.get('presence_out', rule.get('presence-out', None))) def remove_list(self, name, timeout=None, callback=None, timeout_callback=None): iq = self.xmpp.Iq() iq['type'] = 'set' iq['privacy']['list']['name'] = name iq.send(timeout=timeout, callback=callback, timeout_callback=timeout_callback) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0016/stanza.py000066400000000000000000000057741342457644200225100ustar00rootroot00000000000000from slixmpp.xmlstream import ET, ElementBase, register_stanza_plugin class Privacy(ElementBase): name = 'query' namespace = 'jabber:iq:privacy' plugin_attrib = 'privacy' interfaces = set() def add_list(self, name): priv_list = List() priv_list['name'] = name self.append(priv_list) return priv_list class Active(ElementBase): name = 'active' namespace = 'jabber:iq:privacy' plugin_attrib = name interfaces = {'name'} class Default(ElementBase): name = 'default' namespace = 'jabber:iq:privacy' plugin_attrib = name interfaces = {'name'} class List(ElementBase): name = 'list' namespace = 'jabber:iq:privacy' plugin_attrib = name plugin_multi_attrib = 'lists' interfaces = {'name'} def add_item(self, value, action, order, itype=None, iq=False, message=False, presence_in=False, presence_out=False): item = Item() item.values = {'type': itype, 'value': value, 'action': action, 'order': order, 'message': message, 'iq': iq, 'presence_in': presence_in, 'presence_out': presence_out} self.append(item) return item class Item(ElementBase): name = 'item' namespace = 'jabber:iq:privacy' plugin_attrib = name plugin_multi_attrib = 'items' interfaces = {'type', 'value', 'action', 'order', 'iq', 'message', 'presence_in', 'presence_out'} bool_interfaces = {'message', 'iq', 'presence_in', 'presence_out'} type_values = ('', 'jid', 'group', 'subscription') action_values = ('allow', 'deny') def set_type(self, value): if value and value not in self.type_values: raise ValueError('Unknown type value: %s' % value) else: self._set_attr('type', value) def set_action(self, value): if value not in self.action_values: raise ValueError('Unknown action value: %s' % value) else: self._set_attr('action', value) def set_presence_in(self, value): keep = True if value else False self._set_sub_text('presence-in', '', keep=keep) def get_presence_in(self): pres = self.xml.find('{%s}presence-in' % self.namespace) return pres is not None def del_presence_in(self): self._del_sub('{%s}presence-in' % self.namespace) def set_presence_out(self, value): keep = True if value else False self._set_sub_text('presence-in', '', keep=keep) def get_presence_out(self): pres = self.xml.find('{%s}presence-in' % self.namespace) return pres is not None def del_presence_out(self): self._del_sub('{%s}presence-in' % self.namespace) register_stanza_plugin(Privacy, Active) register_stanza_plugin(Privacy, Default) register_stanza_plugin(Privacy, List, iterable=True) register_stanza_plugin(List, Item, iterable=True) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0020/000077500000000000000000000000001342457644200206145ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0020/__init__.py000066400000000000000000000006701342457644200227300ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0020 import stanza from slixmpp.plugins.xep_0020.stanza import FeatureNegotiation from slixmpp.plugins.xep_0020.feature_negotiation import XEP_0020 register_plugin(XEP_0020) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0020/feature_negotiation.py000066400000000000000000000017651342457644200252320ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp import Iq, Message from slixmpp.plugins import BasePlugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.xmlstream import register_stanza_plugin, JID from slixmpp.plugins.xep_0020 import stanza, FeatureNegotiation from slixmpp.plugins.xep_0004 import Form log = logging.getLogger(__name__) class XEP_0020(BasePlugin): name = 'xep_0020' description = 'XEP-0020: Feature Negotiation' dependencies = {'xep_0004', 'xep_0030'} stanza = stanza def plugin_init(self): self.xmpp['xep_0030'].add_feature(FeatureNegotiation.namespace) register_stanza_plugin(FeatureNegotiation, Form) register_stanza_plugin(Iq, FeatureNegotiation) register_stanza_plugin(Message, FeatureNegotiation) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0020/stanza.py000066400000000000000000000006301342457644200224650ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase class FeatureNegotiation(ElementBase): name = 'feature' namespace = 'http://jabber.org/protocol/feature-neg' plugin_attrib = 'feature_neg' interfaces = set() slixmpp-slix-1.4.2/slixmpp/plugins/xep_0027/000077500000000000000000000000001342457644200206235ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0027/__init__.py000066400000000000000000000005731342457644200227410ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0027.stanza import Signed, Encrypted from slixmpp.plugins.xep_0027.gpg import XEP_0027 register_plugin(XEP_0027) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0027/gpg.py000066400000000000000000000131631342457644200217560ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.thirdparty import GPG from slixmpp.stanza import Presence, Message from slixmpp.plugins.base import BasePlugin, register_plugin from slixmpp.xmlstream import ElementBase, register_stanza_plugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.plugins.xep_0027 import stanza, Signed, Encrypted def _extract_data(data, kind): stripped = [] begin_headers = False begin_data = False for line in data.split('\n'): if not begin_headers and 'BEGIN PGP %s' % kind in line: begin_headers = True continue if begin_headers and line.strip() == '': begin_data = True continue if 'END PGP %s' % kind in line: return '\n'.join(stripped) if begin_data: stripped.append(line) return '' class XEP_0027(BasePlugin): name = 'xep_0027' description = 'XEP-0027: Current Jabber OpenPGP Usage' dependencies = set() stanza = stanza default_config = { 'gpg_binary': 'gpg', 'gpg_home': '', 'use_agent': True, 'keyring': None, 'key_server': 'pgp.mit.edu' } def plugin_init(self): self.gpg = GPG(gnupghome=self.gpg_home, gpgbinary=self.gpg_binary, use_agent=self.use_agent, keyring=self.keyring) self.xmpp.add_filter('out', self._sign_presence) self._keyids = {} self.api.register(self._set_keyid, 'set_keyid', default=True) self.api.register(self._get_keyid, 'get_keyid', default=True) self.api.register(self._del_keyid, 'del_keyid', default=True) self.api.register(self._get_keyids, 'get_keyids', default=True) register_stanza_plugin(Presence, Signed) register_stanza_plugin(Message, Encrypted) self.xmpp.add_event_handler('unverified_signed_presence', self._handle_unverified_signed_presence) self.xmpp.register_handler( Callback('Signed Presence', StanzaPath('presence/signed'), self._handle_signed_presence)) self.xmpp.register_handler( Callback('Encrypted Message', StanzaPath('message/encrypted'), self._handle_encrypted_message)) def plugin_end(self): self.xmpp.remove_handler('Encrypted Message') self.xmpp.remove_handler('Signed Presence') self.xmpp.del_filter('out', self._sign_presence) self.xmpp.del_event_handler('unverified_signed_presence', self._handle_unverified_signed_presence) def _sign_presence(self, stanza): if isinstance(stanza, Presence): if stanza['type'] == 'available' or \ stanza['type'] in Presence.showtypes: stanza['signed'] = stanza['status'] return stanza def sign(self, data, jid=None): keyid = self.get_keyid(jid) if keyid: signed = self.gpg.sign(data, keyid=keyid) return _extract_data(signed.data, 'SIGNATURE') def encrypt(self, data, jid=None): keyid = self.get_keyid(jid) if keyid: enc = self.gpg.encrypt(data, keyid) return _extract_data(enc.data, 'MESSAGE') def decrypt(self, data, jid=None): template = '-----BEGIN PGP MESSAGE-----\n' + \ '\n' + \ '%s\n' + \ '-----END PGP MESSAGE-----\n' dec = self.gpg.decrypt(template % data) return dec.data def verify(self, data, sig, jid=None): template = '-----BEGIN PGP SIGNED MESSAGE-----\n' + \ 'Hash: SHA1\n' + \ '\n' + \ '%s\n' + \ '-----BEGIN PGP SIGNATURE-----\n' + \ '\n' + \ '%s\n' + \ '-----END PGP SIGNATURE-----\n' v = self.gpg.verify(template % (data, sig)) return v def set_keyid(self, jid=None, keyid=None): self.api['set_keyid'](jid, args=keyid) def get_keyid(self, jid=None): return self.api['get_keyid'](jid) def del_keyid(self, jid=None): self.api['del_keyid'](jid) def get_keyids(self): return self.api['get_keyids']() def _handle_signed_presence(self, pres): self.xmpp.event('unverified_signed_presence', pres) def _handle_unverified_signed_presence(self, pres): verified = self.verify(pres['status'], pres['signed']) if verified.key_id: if not self.get_keyid(pres['from']): known_keyids = [e['keyid'] for e in self.gpg.list_keys()] if verified.key_id not in known_keyids: self.gpg.recv_keys(self.key_server, verified.key_id) self.set_keyid(jid=pres['from'], keyid=verified.key_id) self.xmpp.event('signed_presence', pres) def _handle_encrypted_message(self, msg): self.xmpp.event('encrypted_message', msg) # ================================================================= def _set_keyid(self, jid, node, ifrom, keyid): self._keyids[jid] = keyid def _get_keyid(self, jid, node, ifrom, keyid): return self._keyids.get(jid, None) def _del_keyid(self, jid, node, ifrom, keyid): if jid in self._keyids: del self._keyids[jid] def _get_keyids(self, jid, node, ifrom, data): return self._keyids slixmpp-slix-1.4.2/slixmpp/plugins/xep_0027/stanza.py000066400000000000000000000024671342457644200225060ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase class Signed(ElementBase): name = 'x' namespace = 'jabber:x:signed' plugin_attrib = 'signed' interfaces = {'signed'} is_extension = True def set_signed(self, value): parent = self.parent() xmpp = parent.stream data = xmpp['xep_0027'].sign(value, parent['from']) if data: self.xml.text = data else: del parent['signed'] def get_signed(self): return self.xml.text class Encrypted(ElementBase): name = 'x' namespace = 'jabber:x:encrypted' plugin_attrib = 'encrypted' interfaces = {'encrypted'} is_extension = True def set_encrypted(self, value): parent = self.parent() xmpp = parent.stream data = xmpp['xep_0027'].encrypt(value, parent['to']) if data: self.xml.text = data else: del parent['encrypted'] def get_encrypted(self): parent = self.parent() xmpp = parent.stream if self.xml.text: return xmpp['xep_0027'].decrypt(self.xml.text, parent['to']) return None slixmpp-slix-1.4.2/slixmpp/plugins/xep_0030/000077500000000000000000000000001342457644200206155ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0030/__init__.py000066400000000000000000000007451342457644200227340ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0030 import stanza from slixmpp.plugins.xep_0030.stanza import DiscoInfo, DiscoItems from slixmpp.plugins.xep_0030.static import StaticDisco from slixmpp.plugins.xep_0030.disco import XEP_0030 register_plugin(XEP_0030) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0030/disco.py000066400000000000000000000747261342457644200223100ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import asyncio import logging from slixmpp import Iq from slixmpp import future_wrapper from slixmpp.plugins import BasePlugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.xmlstream import register_stanza_plugin, JID from slixmpp.plugins.xep_0030 import stanza, DiscoInfo, DiscoItems from slixmpp.plugins.xep_0030 import StaticDisco log = logging.getLogger(__name__) class XEP_0030(BasePlugin): """ XEP-0030: Service Discovery Service discovery in XMPP allows entities to discover information about other agents in the network, such as the feature sets supported by a client, or signposts to other, related entities. Also see . The XEP-0030 plugin works using a hierarchy of dynamic node handlers, ranging from global handlers to specific JID+node handlers. The default set of handlers operate in a static manner, storing disco information in memory. However, custom handlers may use any available backend storage mechanism desired, such as SQLite or Redis. Node handler hierarchy: JID | Node | Level --------------------- None | None | Global Given | None | All nodes for the JID None | Given | Node on self.xmpp.boundjid Given | Given | A single node Stream Handlers: Disco Info -- Any Iq stanze that includes a query with the namespace http://jabber.org/protocol/disco#info. Disco Items -- Any Iq stanze that includes a query with the namespace http://jabber.org/protocol/disco#items. Events: disco_info -- Received a disco#info Iq query result. disco_items -- Received a disco#items Iq query result. disco_info_query -- Received a disco#info Iq query request. disco_items_query -- Received a disco#items Iq query request. Attributes: stanza -- A reference to the module containing the stanza classes provided by this plugin. static -- Object containing the default set of static node handlers. default_handlers -- A dictionary mapping operations to the default global handler (by default, the static handlers). xmpp -- The main Slixmpp object. Methods: set_node_handler -- Assign a handler to a JID/node combination. del_node_handler -- Remove a handler from a JID/node combination. get_info -- Retrieve disco#info data, locally or remote. get_items -- Retrieve disco#items data, locally or remote. set_identities -- set_features -- set_items -- del_items -- del_identity -- del_feature -- del_item -- add_identity -- add_feature -- add_item -- """ name = 'xep_0030' description = 'XEP-0030: Service Discovery' dependencies = set() stanza = stanza default_config = { 'use_cache': True, 'wrap_results': False } def plugin_init(self): """ Start the XEP-0030 plugin. """ self.xmpp.register_handler( Callback('Disco Info', StanzaPath('iq/disco_info'), self._handle_disco_info)) self.xmpp.register_handler( Callback('Disco Items', StanzaPath('iq/disco_items'), self._handle_disco_items)) register_stanza_plugin(Iq, DiscoInfo) register_stanza_plugin(Iq, DiscoItems) self.static = StaticDisco(self.xmpp, self) self._disco_ops = [ 'get_info', 'set_info', 'set_identities', 'set_features', 'get_items', 'set_items', 'del_items', 'add_identity', 'del_identity', 'add_feature', 'del_feature', 'add_item', 'del_item', 'del_identities', 'del_features', 'cache_info', 'get_cached_info', 'supports', 'has_identity'] for op in self._disco_ops: self.api.register(getattr(self.static, op), op, default=True) self.domain_infos = {} def session_bind(self, jid): self.add_feature('http://jabber.org/protocol/disco#info') def plugin_end(self): self.del_feature('http://jabber.org/protocol/disco#info') def _add_disco_op(self, op, default_handler): self.api.register(default_handler, op) self.api.register_default(default_handler, op) def set_node_handler(self, htype, jid=None, node=None, handler=None): """ Add a node handler for the given hierarchy level and handler type. Node handlers are ordered in a hierarchy where the most specific handler is executed. Thus, a fallback, global handler can be used for the majority of cases with a few node specific handler that override the global behavior. Node handler hierarchy: JID | Node | Level --------------------- None | None | Global Given | None | All nodes for the JID None | Given | Node on self.xmpp.boundjid Given | Given | A single node Handler types: get_info get_items set_identities set_features set_items del_items del_identities del_identity del_feature del_features del_item add_identity add_feature add_item Arguments: htype -- The operation provided by the handler. jid -- The JID the handler applies to. May be narrowed further if a node is given. node -- The particular node the handler is for. If no JID is given, then the self.xmpp.boundjid.full is assumed. handler -- The handler function to use. """ self.api.register(handler, htype, jid, node) def del_node_handler(self, htype, jid, node): """ Remove a handler type for a JID and node combination. The next handler in the hierarchy will be used if one exists. If removing the global handler, make sure that other handlers exist to process existing nodes. Node handler hierarchy: JID | Node | Level --------------------- None | None | Global Given | None | All nodes for the JID None | Given | Node on self.xmpp.boundjid Given | Given | A single node Arguments: htype -- The type of handler to remove. jid -- The JID from which to remove the handler. node -- The node from which to remove the handler. """ self.api.unregister(htype, jid, node) def restore_defaults(self, jid=None, node=None, handlers=None): """ Change all or some of a node's handlers to the default handlers. Useful for manually overriding the contents of a node that would otherwise be handled by a JID level or global level dynamic handler. The default is to use the built-in static handlers, but that may be changed by modifying self.default_handlers. Arguments: jid -- The JID owning the node to modify. node -- The node to change to using static handlers. handlers -- Optional list of handlers to change to the default version. If provided, only these handlers will be changed. Otherwise, all handlers will use the default version. """ if handlers is None: handlers = self._disco_ops for op in handlers: self.api.restore_default(op, jid, node) def supports(self, jid=None, node=None, feature=None, local=False, cached=True, ifrom=None): """ Check if a JID supports a given feature. Return values: True -- The feature is supported False -- The feature is not listed as supported None -- Nothing could be found due to a timeout Arguments: jid -- Request info from this JID. node -- The particular node to query. feature -- The name of the feature to check. local -- If true, then the query is for a JID/node combination handled by this Slixmpp instance and no stanzas need to be sent. Otherwise, a disco stanza must be sent to the remove JID to retrieve the info. cached -- If true, then look for the disco info data from the local cache system. If no results are found, send the query as usual. The self.use_cache setting must be set to true for this option to be useful. If set to false, then the cache will be skipped, even if a result has already been cached. Defaults to false. ifrom -- Specifiy the sender's JID. """ data = {'feature': feature, 'local': local, 'cached': cached} return self.api['supports'](jid, node, ifrom, data) def has_identity(self, jid=None, node=None, category=None, itype=None, lang=None, local=False, cached=True, ifrom=None): """ Check if a JID provides a given identity. Return values: True -- The identity is provided False -- The identity is not listed None -- Nothing could be found due to a timeout Arguments: jid -- Request info from this JID. node -- The particular node to query. category -- The category of the identity to check. itype -- The type of the identity to check. lang -- The language of the identity to check. local -- If true, then the query is for a JID/node combination handled by this Slixmpp instance and no stanzas need to be sent. Otherwise, a disco stanza must be sent to the remove JID to retrieve the info. cached -- If true, then look for the disco info data from the local cache system. If no results are found, send the query as usual. The self.use_cache setting must be set to true for this option to be useful. If set to false, then the cache will be skipped, even if a result has already been cached. Defaults to false. ifrom -- Specifiy the sender's JID. """ data = {'category': category, 'itype': itype, 'lang': lang, 'local': local, 'cached': cached} return self.api['has_identity'](jid, node, ifrom, data) async def get_info_from_domain(self, domain=None, timeout=None, cached=True, callback=None): if domain is None: domain = self.xmpp.boundjid.domain if not cached or domain not in self.domain_infos: infos = [self.get_info( domain, timeout=timeout)] iq_items = await self.get_items( domain, timeout=timeout) items = iq_items['disco_items']['items'] infos += [ self.get_info(item[0], timeout=timeout) for item in items] info_futures, _ = await asyncio.wait( infos, timeout=timeout, loop=self.xmpp.loop ) self.domain_infos[domain] = [ future.result() for future in info_futures if not future.exception()] results = self.domain_infos[domain] if callback is not None: callback(results) return results @future_wrapper def get_info(self, jid=None, node=None, local=None, cached=None, **kwargs): """ Retrieve the disco#info results from a given JID/node combination. Info may be retrieved from both local resources and remote agents; the local parameter indicates if the information should be gathered by executing the local node handlers, or if a disco#info stanza must be generated and sent. If requesting items from a local JID/node, then only a DiscoInfo stanza will be returned. Otherwise, an Iq stanza will be returned. Arguments: jid -- Request info from this JID. node -- The particular node to query. local -- If true, then the query is for a JID/node combination handled by this Slixmpp instance and no stanzas need to be sent. Otherwise, a disco stanza must be sent to the remove JID to retrieve the info. cached -- If true, then look for the disco info data from the local cache system. If no results are found, send the query as usual. The self.use_cache setting must be set to true for this option to be useful. If set to false, then the cache will be skipped, even if a result has already been cached. Defaults to false. ifrom -- Specifiy the sender's JID. timeout -- The time in seconds to wait for reply, before calling timeout_callback callback -- Optional callback to execute when a reply is received instead of blocking and waiting for the reply. timeout_callback -- Optional callback to execute when no result has been received in timeout seconds. """ if local is None: if jid is not None and not isinstance(jid, JID): jid = JID(jid) if self.xmpp.is_component: if jid.domain == self.xmpp.boundjid.domain: local = True else: if str(jid) == str(self.xmpp.boundjid): local = True jid = jid.full elif jid in (None, ''): local = True if local: log.debug("Looking up local disco#info data " + \ "for %s, node %s.", jid, node) info = self.api['get_info'](jid, node, kwargs.get('ifrom', None), kwargs) info = self._fix_default_info(info) return self._wrap(kwargs.get('ifrom', None), jid, info) if cached: log.debug("Looking up cached disco#info data " + \ "for %s, node %s.", jid, node) info = self.api['get_cached_info'](jid, node, kwargs.get('ifrom', None), kwargs) if info is not None: return self._wrap(kwargs.get('ifrom', None), jid, info) iq = self.xmpp.Iq() # Check dfrom parameter for backwards compatibility iq['from'] = kwargs.get('ifrom', kwargs.get('dfrom', '')) iq['to'] = jid iq['type'] = 'get' iq['disco_info']['node'] = node if node else '' return iq.send(timeout=kwargs.get('timeout', None), callback=kwargs.get('callback', None), timeout_callback=kwargs.get('timeout_callback', None)) def set_info(self, jid=None, node=None, info=None): """ Set the disco#info data for a JID/node based on an existing disco#info stanza. """ if isinstance(info, Iq): info = info['disco_info'] self.api['set_info'](jid, node, None, info) @future_wrapper def get_items(self, jid=None, node=None, local=False, **kwargs): """ Retrieve the disco#items results from a given JID/node combination. Items may be retrieved from both local resources and remote agents; the local parameter indicates if the items should be gathered by executing the local node handlers, or if a disco#items stanza must be generated and sent. If requesting items from a local JID/node, then only a DiscoItems stanza will be returned. Otherwise, an Iq stanza will be returned. Arguments: jid -- Request info from this JID. node -- The particular node to query. local -- If true, then the query is for a JID/node combination handled by this Slixmpp instance and no stanzas need to be sent. Otherwise, a disco stanza must be sent to the remove JID to retrieve the items. ifrom -- Specifiy the sender's JID. timeout -- The time in seconds to block while waiting for a reply. If None, then wait indefinitely. callback -- Optional callback to execute when a reply is received instead of blocking and waiting for the reply. iterator -- If True, return a result set iterator using the XEP-0059 plugin, if the plugin is loaded. Otherwise the parameter is ignored. timeout_callback -- Optional callback to execute when no result has been received in timeout seconds. """ if local or local is None and jid is None: items = self.api['get_items'](jid, node, kwargs.get('ifrom', None), kwargs) return self._wrap(kwargs.get('ifrom', None), jid, items) iq = self.xmpp.Iq() # Check dfrom parameter for backwards compatibility iq['from'] = kwargs.get('ifrom', kwargs.get('dfrom', '')) iq['to'] = jid iq['type'] = 'get' iq['disco_items']['node'] = node if node else '' if kwargs.get('iterator', False) and self.xmpp['xep_0059']: raise NotImplementedError("XEP 0059 has not yet been fixed") return self.xmpp['xep_0059'].iterate(iq, 'disco_items') else: return iq.send(timeout=kwargs.get('timeout', None), callback=kwargs.get('callback', None), timeout_callback=kwargs.get('timeout_callback', None)) def set_items(self, jid=None, node=None, **kwargs): """ Set or replace all items for the specified JID/node combination. The given items must be in a list or set where each item is a tuple of the form: (jid, node, name). Arguments: jid -- The JID to modify. node -- Optional node to modify. items -- A series of items in tuple format. """ self.api['set_items'](jid, node, None, kwargs) def del_items(self, jid=None, node=None, **kwargs): """ Remove all items from the given JID/node combination. Arguments: jid -- The JID to modify. node -- Optional node to modify. """ self.api['del_items'](jid, node, None, kwargs) def add_item(self, jid='', name='', node=None, subnode='', ijid=None): """ Add a new item element to the given JID/node combination. Each item is required to have a JID, but may also specify a node value to reference non-addressable entities. Arguments: jid -- The JID for the item. name -- Optional name for the item. node -- The node to modify. subnode -- Optional node for the item. ijid -- The JID to modify. """ if not jid: jid = self.xmpp.boundjid.full kwargs = {'ijid': jid, 'name': name, 'inode': subnode} self.api['add_item'](ijid, node, None, kwargs) def del_item(self, jid=None, node=None, **kwargs): """ Remove a single item from the given JID/node combination. Arguments: jid -- The JID to modify. node -- The node to modify. ijid -- The item's JID. inode -- The item's node. """ self.api['del_item'](jid, node, None, kwargs) def add_identity(self, category='', itype='', name='', node=None, jid=None, lang=None): """ Add a new identity to the given JID/node combination. Each identity must be unique in terms of all four identity components: category, type, name, and language. Multiple, identical category/type pairs are allowed only if the xml:lang values are different. Likewise, multiple category/type/xml:lang pairs are allowed so long as the names are different. A category and type is always required. Arguments: category -- The identity's category. itype -- The identity's type. name -- Optional name for the identity. lang -- Optional two-letter language code. node -- The node to modify. jid -- The JID to modify. """ kwargs = {'category': category, 'itype': itype, 'name': name, 'lang': lang} self.api['add_identity'](jid, node, None, kwargs) def add_feature(self, feature, node=None, jid=None): """ Add a feature to a JID/node combination. Arguments: feature -- The namespace of the supported feature. node -- The node to modify. jid -- The JID to modify. """ kwargs = {'feature': feature} self.api['add_feature'](jid, node, None, kwargs) def del_identity(self, jid=None, node=None, **kwargs): """ Remove an identity from the given JID/node combination. Arguments: jid -- The JID to modify. node -- The node to modify. category -- The identity's category. itype -- The identity's type value. name -- Optional, human readable name for the identity. lang -- Optional, the identity's xml:lang value. """ self.api['del_identity'](jid, node, None, kwargs) def del_feature(self, jid=None, node=None, **kwargs): """ Remove a feature from a given JID/node combination. Arguments: jid -- The JID to modify. node -- The node to modify. feature -- The feature's namespace. """ self.api['del_feature'](jid, node, None, kwargs) def set_identities(self, jid=None, node=None, **kwargs): """ Add or replace all identities for the given JID/node combination. The identities must be in a set where each identity is a tuple of the form: (category, type, lang, name) Arguments: jid -- The JID to modify. node -- The node to modify. identities -- A set of identities in tuple form. lang -- Optional, xml:lang value. """ self.api['set_identities'](jid, node, None, kwargs) def del_identities(self, jid=None, node=None, **kwargs): """ Remove all identities for a JID/node combination. If a language is specified, only identities using that language will be removed. Arguments: jid -- The JID to modify. node -- The node to modify. lang -- Optional. If given, only remove identities using this xml:lang value. """ self.api['del_identities'](jid, node, None, kwargs) def set_features(self, jid=None, node=None, **kwargs): """ Add or replace the set of supported features for a JID/node combination. Arguments: jid -- The JID to modify. node -- The node to modify. features -- The new set of supported features. """ self.api['set_features'](jid, node, None, kwargs) def del_features(self, jid=None, node=None, **kwargs): """ Remove all features from a JID/node combination. Arguments: jid -- The JID to modify. node -- The node to modify. """ self.api['del_features'](jid, node, None, kwargs) def _run_node_handler(self, htype, jid, node=None, ifrom=None, data=None): """ Execute the most specific node handler for the given JID/node combination. Arguments: htype -- The handler type to execute. jid -- The JID requested. node -- The node requested. data -- Optional, custom data to pass to the handler. """ if not data: data = {} return self.api[htype](jid, node, ifrom, data) def _handle_disco_info(self, iq): """ Process an incoming disco#info stanza. If it is a get request, find and return the appropriate identities and features. If it is an info result, fire the disco_info event. Arguments: iq -- The incoming disco#items stanza. """ if iq['type'] == 'get': log.debug("Received disco info query from " + \ "<%s> to <%s>.", iq['from'], iq['to']) info = self.api['get_info'](iq['to'], iq['disco_info']['node'], iq['from'], iq) if isinstance(info, Iq): info['id'] = iq['id'] info.send() else: node = iq['disco_info']['node'] iq = iq.reply() if info: info = self._fix_default_info(info) info['node'] = node iq.set_payload(info.xml) iq.send() elif iq['type'] == 'result': log.debug("Received disco info result from " + \ "<%s> to <%s>.", iq['from'], iq['to']) if self.use_cache: log.debug("Caching disco info result from " \ "<%s> to <%s>.", iq['from'], iq['to']) if self.xmpp.is_component: ito = iq['to'].full else: ito = None self.api['cache_info'](iq['from'], iq['disco_info']['node'], ito, iq) self.xmpp.event('disco_info', iq) def _handle_disco_items(self, iq): """ Process an incoming disco#items stanza. If it is a get request, find and return the appropriate items. If it is an items result, fire the disco_items event. Arguments: iq -- The incoming disco#items stanza. """ if iq['type'] == 'get': log.debug("Received disco items query from " + \ "<%s> to <%s>.", iq['from'], iq['to']) items = self.api['get_items'](iq['to'], iq['disco_items']['node'], iq['from'], iq) if isinstance(items, Iq): items.send() else: iq = iq.reply() if items: iq.set_payload(items.xml) iq.send() elif iq['type'] == 'result': log.debug("Received disco items result from " + \ "%s to %s.", iq['from'], iq['to']) self.xmpp.event('disco_items', iq) def _fix_default_info(self, info): """ Disco#info results for a JID are required to include at least one identity and feature. As a default, if no other identity is provided, Slixmpp will use either the generic component or the bot client identity. A the standard disco#info feature will also be added if no features are provided. Arguments: info -- The disco#info quest (not the full Iq stanza) to modify. """ result = info if isinstance(info, Iq): info = info['disco_info'] if not info['node']: if not info['identities']: if self.xmpp.is_component: log.debug("No identity found for this entity. " + \ "Using default component identity.") info.add_identity('component', 'generic') else: log.debug("No identity found for this entity. " + \ "Using default client identity.") info.add_identity('client', 'bot') if not info['features']: log.debug("No features found for this entity. " + \ "Using default disco#info feature.") info.add_feature(info.namespace) return result def _wrap(self, ito, ifrom, payload, force=False): """ Ensure that results are wrapped in an Iq stanza if self.wrap_results has been set to True. Arguments: ito -- The JID to use as the 'to' value ifrom -- The JID to use as the 'from' value payload -- The disco data to wrap force -- Force wrapping, regardless of self.wrap_results """ if (force or self.wrap_results) and not isinstance(payload, Iq): iq = self.xmpp.Iq() # Since we're simulating a result, we have to treat # the 'from' and 'to' values opposite the normal way. iq['to'] = self.xmpp.boundjid if ito is None else ito iq['from'] = self.xmpp.boundjid if ifrom is None else ifrom iq['type'] = 'result' iq.append(payload) return iq return payload slixmpp-slix-1.4.2/slixmpp/plugins/xep_0030/stanza/000077500000000000000000000000001342457644200221155ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0030/stanza/__init__.py000066400000000000000000000004651342457644200242330ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.xep_0030.stanza.info import DiscoInfo from slixmpp.plugins.xep_0030.stanza.items import DiscoItems slixmpp-slix-1.4.2/slixmpp/plugins/xep_0030/stanza/info.py000066400000000000000000000242471342457644200234330ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase, ET class DiscoInfo(ElementBase): """ XMPP allows for users and agents to find the identities and features supported by other entities in the XMPP network through service discovery, or "disco". In particular, the "disco#info" query type for stanzas is used to request the list of identities and features offered by a JID. An identity is a combination of a category and type, such as the 'client' category with a type of 'pc' to indicate the agent is a human operated client with a GUI, or a category of 'gateway' with a type of 'aim' to identify the agent as a gateway for the legacy AIM protocol. See for a full list of accepted category and type combinations. Features are simply a set of the namespaces that identify the supported features. For example, a client that supports service discovery will include the feature 'http://jabber.org/protocol/disco#info'. Since clients and components may operate in several roles at once, identity and feature information may be grouped into "nodes". If one were to write all of the identities and features used by a client, then node names would be like section headings. Example disco#info stanzas: Stanza Interface: node -- The name of the node to either query or return info from. identities -- A set of 4-tuples, where each tuple contains the category, type, xml:lang, and name of an identity. features -- A set of namespaces for features. Methods: add_identity -- Add a new, single identity. del_identity -- Remove a single identity. get_identities -- Return all identities in tuple form. set_identities -- Use multiple identities, each given in tuple form. del_identities -- Remove all identities. add_feature -- Add a single feature. del_feature -- Remove a single feature. get_features -- Return a list of all features. set_features -- Use a given list of features. del_features -- Remove all features. """ name = 'query' namespace = 'http://jabber.org/protocol/disco#info' plugin_attrib = 'disco_info' interfaces = {'node', 'features', 'identities'} lang_interfaces = {'identities'} # Cache identities and features _identities = set() _features = set() def setup(self, xml=None): """ Populate the stanza object using an optional XML object. Overrides ElementBase.setup Caches identity and feature information. Arguments: xml -- Use an existing XML object for the stanza's values. """ ElementBase.setup(self, xml) self._identities = {id[0:3] for id in self['identities']} self._features = self['features'] def add_identity(self, category, itype, name=None, lang=None): """ Add a new identity element. Each identity must be unique in terms of all four identity components. Multiple, identical category/type pairs are allowed only if the xml:lang values are different. Likewise, multiple category/type/xml:lang pairs are allowed so long as the names are different. In any case, a category and type are required. Arguments: 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. """ identity = (category, itype, lang) if identity not in self._identities: self._identities.add(identity) id_xml = ET.Element('{%s}identity' % self.namespace) id_xml.attrib['category'] = category id_xml.attrib['type'] = itype if lang: id_xml.attrib['{%s}lang' % self.xml_ns] = lang if name: id_xml.attrib['name'] = name self.xml.insert(0, id_xml) return True return False def del_identity(self, category, itype, name=None, lang=None): """ Remove a given identity. Arguments: category -- The general category to which the agent belonged. itype -- A more specific designation with the category. name -- Optional human readable name for this identity. lang -- Optional, standard xml:lang value. """ identity = (category, itype, lang) if identity in self._identities: self._identities.remove(identity) for id_xml in self.xml.findall('{%s}identity' % self.namespace): id = (id_xml.attrib['category'], id_xml.attrib['type'], id_xml.attrib.get('{%s}lang' % self.xml_ns, None)) if id == identity: self.xml.remove(id_xml) return True return False def get_identities(self, lang=None, dedupe=True): """ Return a set of all identities in tuple form as so: (category, type, lang, name) If a language was specified, only return identities using that language. Arguments: lang -- Optional, standard xml:lang value. dedupe -- If True, de-duplicate identities, otherwise return a list of all identities. """ if dedupe: identities = set() else: identities = [] for id_xml in self.xml.findall('{%s}identity' % self.namespace): xml_lang = id_xml.attrib.get('{%s}lang' % self.xml_ns, None) if lang is None or xml_lang == lang: id = (id_xml.attrib['category'], id_xml.attrib['type'], id_xml.attrib.get('{%s}lang' % self.xml_ns, None), id_xml.attrib.get('name', None)) if dedupe: identities.add(id) else: identities.append(id) return identities def set_identities(self, identities, lang=None): """ Add or replace all identities. The identities must be a in set where each identity is a tuple of the form: (category, type, lang, name) If a language is specifified, any identities using that language will be removed to be replaced with the given identities. NOTE: An identity's language will not be changed regardless of the value of lang. Arguments: identities -- A set of identities in tuple form. lang -- Optional, standard xml:lang value. """ self.del_identities(lang) for identity in identities: category, itype, lang, name = identity self.add_identity(category, itype, name, lang) def del_identities(self, lang=None): """ Remove all identities. If a language was specified, only remove identities using that language. Arguments: lang -- Optional, standard xml:lang value. """ for id_xml in self.xml.findall('{%s}identity' % self.namespace): if lang is None: self.xml.remove(id_xml) elif id_xml.attrib.get('{%s}lang' % self.xml_ns, None) == lang: self._identities.remove(( id_xml.attrib['category'], id_xml.attrib['type'], id_xml.attrib.get('{%s}lang' % self.xml_ns, None))) self.xml.remove(id_xml) def add_feature(self, feature): """ Add a single, new feature. Arguments: feature -- The namespace of the supported feature. """ if feature not in self._features: self._features.add(feature) feature_xml = ET.Element('{%s}feature' % self.namespace) feature_xml.attrib['var'] = feature self.xml.append(feature_xml) return True return False def del_feature(self, feature): """ Remove a single feature. Arguments: feature -- The namespace of the removed feature. """ if feature in self._features: self._features.remove(feature) for feature_xml in self.xml.findall('{%s}feature' % self.namespace): if feature_xml.attrib['var'] == feature: self.xml.remove(feature_xml) return True return False def get_features(self, dedupe=True): """Return the set of all supported features.""" if dedupe: features = set() else: features = [] for feature_xml in self.xml.findall('{%s}feature' % self.namespace): if dedupe: features.add(feature_xml.attrib['var']) else: features.append(feature_xml.attrib['var']) return features def set_features(self, features): """ Add or replace the set of supported features. Arguments: features -- The new set of supported features. """ self.del_features() for feature in features: self.add_feature(feature) def del_features(self): """Remove all features.""" self._features = set() for feature_xml in self.xml.findall('{%s}feature' % self.namespace): self.xml.remove(feature_xml) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0030/stanza/items.py000066400000000000000000000107671342457644200236230ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase, register_stanza_plugin class DiscoItems(ElementBase): """ Example disco#items stanzas: Stanza Interface: node -- The name of the node to either query or return info from. items -- A list of 3-tuples, where each tuple contains the JID, node, and name of an item. Methods: add_item -- Add a single new item. del_item -- Remove a single item. get_items -- Return all items. set_items -- Set or replace all items. del_items -- Remove all items. """ name = 'query' namespace = 'http://jabber.org/protocol/disco#items' plugin_attrib = 'disco_items' interfaces = {'node', 'items'} # Cache items _items = set() def setup(self, xml=None): """ Populate the stanza object using an optional XML object. Overrides ElementBase.setup Caches item information. Arguments: xml -- Use an existing XML object for the stanza's values. """ ElementBase.setup(self, xml) self._items = {item[0:2] for item in self['items']} def add_item(self, jid, node=None, name=None): """ Add a new item element. Each item is required to have a JID, but may also specify a node value to reference non-addressable entitities. Arguments: jid -- The JID for the item. node -- Optional additional information to reference non-addressable items. name -- Optional human readable name for the item. """ if (jid, node) not in self._items: self._items.add((jid, node)) item = DiscoItem(parent=self) item['jid'] = jid item['node'] = node item['name'] = name self.iterables.append(item) return True return False def del_item(self, jid, node=None): """ Remove a single item. Arguments: jid -- JID of the item to remove. node -- Optional extra identifying information. """ if (jid, node) in self._items: for item_xml in self.xml.findall('{%s}item' % self.namespace): item = (item_xml.attrib['jid'], item_xml.attrib.get('node', None)) if item == (jid, node): self.xml.remove(item_xml) return True return False def get_items(self): """Return all items.""" items = set() for item in self['substanzas']: if isinstance(item, DiscoItem): items.add((item['jid'], item['node'], item['name'])) return items def set_items(self, items): """ Set or replace all items. The given items must be in a list or set where each item is a tuple of the form: (jid, node, name) Arguments: items -- A series of items in tuple format. """ self.del_items() for item in items: jid, node, name = item self.add_item(jid, node, name) def del_items(self): """Remove all items.""" self._items = set() items = [i for i in self.iterables if isinstance(i, DiscoItem)] for item in items: self.xml.remove(item.xml) self.iterables.remove(item) class DiscoItem(ElementBase): name = 'item' namespace = 'http://jabber.org/protocol/disco#items' plugin_attrib = name interfaces = {'jid', 'node', 'name'} def get_node(self): """Return the item's node name or ``None``.""" return self._get_attr('node', None) def get_name(self): """Return the item's human readable name, or ``None``.""" return self._get_attr('name', None) register_stanza_plugin(DiscoItems, DiscoItem, iterable=True) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0030/static.py000066400000000000000000000343771342457644200224740ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp import Iq from slixmpp.exceptions import XMPPError, IqError, IqTimeout from slixmpp.xmlstream import JID from slixmpp.plugins.xep_0030 import DiscoInfo, DiscoItems log = logging.getLogger(__name__) class StaticDisco(object): """ While components will likely require fully dynamic handling of service discovery information, most clients and simple bots only need to manage a few disco nodes that will remain mostly static. StaticDisco provides a set of node handlers that will store static sets of disco info and items in memory. Attributes: nodes -- A dictionary mapping (JID, node) tuples to a dict containing a disco#info and a disco#items stanza. xmpp -- The main Slixmpp object. """ def __init__(self, xmpp, disco): """ Create a static disco interface. Sets of disco#info and disco#items are maintained for every given JID and node combination. These stanzas are used to store disco information in memory without any additional processing. Arguments: xmpp -- The main Slixmpp object. """ self.nodes = {} self.xmpp = xmpp self.disco = disco def add_node(self, jid=None, node=None, ifrom=None): """ Create a new set of stanzas for the provided JID and node combination. Arguments: jid -- The JID that will own the new stanzas. node -- The node that will own the new stanzas. """ if jid is None: jid = self.xmpp.boundjid.full if node is None: node = '' if ifrom is None: ifrom = '' if isinstance(ifrom, JID): ifrom = ifrom.full if (jid, node, ifrom) not in self.nodes: new_node = {'info': DiscoInfo(), 'items': DiscoItems()} new_node['info']['node'] = node new_node['items']['node'] = node self.nodes[(jid, node, ifrom)] = new_node return self.nodes[(jid, node, ifrom)] def get_node(self, jid=None, node=None, ifrom=None): if jid is None: jid = self.xmpp.boundjid.full if node is None: node = '' if ifrom is None: ifrom = '' if isinstance(ifrom, JID): ifrom = ifrom.full if (jid, node, ifrom) not in self.nodes: self.add_node(jid, node, ifrom) return self.nodes[(jid, node, ifrom)] def node_exists(self, jid=None, node=None, ifrom=None): if jid is None: jid = self.xmpp.boundjid.full if node is None: node = '' if ifrom is None: ifrom = '' if isinstance(ifrom, JID): ifrom = ifrom.full if (jid, node, ifrom) not in self.nodes: return False return True # ================================================================= # Node Handlers # # Each handler accepts four arguments: jid, node, ifrom, and data. # The jid and node parameters together determine the set of info # and items stanzas that will be retrieved or added. Additionally, # the ifrom value allows for cached results when results vary based # on the requester's JID. The data parameter is a dictionary with # additional parameters that will be passed to other calls. # # This implementation does not allow different responses based on # the requester's JID, except for cached results. To do that, # register a custom node handler. def supports(self, jid, node, ifrom, data): """ Check if a JID supports a given feature. The data parameter may provide: feature -- The feature to check for support. local -- If true, then the query is for a JID/node combination handled by this Slixmpp instance and no stanzas need to be sent. Otherwise, a disco stanza must be sent to the remove JID to retrieve the info. cached -- If true, then look for the disco info data from the local cache system. If no results are found, send the query as usual. The self.use_cache setting must be set to true for this option to be useful. If set to false, then the cache will be skipped, even if a result has already been cached. Defaults to false. """ feature = data.get('feature', None) data = {'local': data.get('local', False), 'cached': data.get('cached', True)} if not feature: return False try: info = self.disco.get_info(jid=jid, node=node, ifrom=ifrom, **data) info = self.disco._wrap(ifrom, jid, info, True) features = info['disco_info']['features'] return feature in features except IqError: return False except IqTimeout: return None def has_identity(self, jid, node, ifrom, data): """ Check if a JID has a given identity. The data parameter may provide: category -- The category of the identity to check. itype -- The type of the identity to check. lang -- The language of the identity to check. local -- If true, then the query is for a JID/node combination handled by this Slixmpp instance and no stanzas need to be sent. Otherwise, a disco stanza must be sent to the remove JID to retrieve the info. cached -- If true, then look for the disco info data from the local cache system. If no results are found, send the query as usual. The self.use_cache setting must be set to true for this option to be useful. If set to false, then the cache will be skipped, even if a result has already been cached. Defaults to false. """ identity = (data.get('category', None), data.get('itype', None), data.get('lang', None)) data = {'local': data.get('local', False), 'cached': data.get('cached', True)} try: info = self.disco.get_info(jid=jid, node=node, ifrom=ifrom, **data) info = self.disco._wrap(ifrom, jid, info, True) trunc = lambda i: (i[0], i[1], i[2]) return identity in map(trunc, info['disco_info']['identities']) except IqError: return False except IqTimeout: return None def get_info(self, jid, node, ifrom, data): """ Return the stored info data for the requested JID/node combination. The data parameter is not used. """ if not self.node_exists(jid, node): if not node: return DiscoInfo() else: raise XMPPError(condition='item-not-found') else: return self.get_node(jid, node)['info'] def set_info(self, jid, node, ifrom, data): """ Set the entire info stanza for a JID/node at once. The data parameter is a disco#info substanza. """ new_node = self.add_node(jid, node) new_node['info'] = data def del_info(self, jid, node, ifrom, data): """ Reset the info stanza for a given JID/node combination. The data parameter is not used. """ if self.node_exists(jid, node): self.get_node(jid, node)['info'] = DiscoInfo() def get_items(self, jid, node, ifrom, data): """ Return the stored items data for the requested JID/node combination. The data parameter is not used. """ if not self.node_exists(jid, node): if not node: return DiscoItems() else: raise XMPPError(condition='item-not-found') else: return self.get_node(jid, node)['items'] def set_items(self, jid, node, ifrom, data): """ Replace the stored items data for a JID/node combination. The data parameter may provide: items -- A set of items in tuple format. """ items = data.get('items', set()) new_node = self.add_node(jid, node) new_node['items']['items'] = items def del_items(self, jid, node, ifrom, data): """ Reset the items stanza for a given JID/node combination. The data parameter is not used. """ if self.node_exists(jid, node): self.get_node(jid, node)['items'] = DiscoItems() def add_identity(self, jid, node, ifrom, 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. """ new_node = self.add_node(jid, node) new_node['info'].add_identity( data.get('category', ''), data.get('itype', ''), data.get('name', None), data.get('lang', None)) def set_identities(self, jid, node, ifrom, data): """ Add or replace all identities for a JID/node combination. The data parameter should include: identities -- A list of identities in tuple form: (category, type, name, lang) """ identities = data.get('identities', set()) new_node = self.add_node(jid, node) new_node['info']['identities'] = identities def del_identity(self, jid, node, ifrom, data): """ Remove an identity from a JID/node combination. The data parameter may provide: category -- The general category to which the agent belonged. itype -- A more specific designation with the category. name -- Optional human readable name for this identity. lang -- Optional, standard xml:lang value. """ if self.node_exists(jid, node): self.get_node(jid, node)['info'].del_identity( data.get('category', ''), data.get('itype', ''), data.get('name', None), data.get('lang', None)) def del_identities(self, jid, node, ifrom, data): """ Remove all identities from a JID/node combination. The data parameter is not used. """ if self.node_exists(jid, node): del self.get_node(jid, node)['info']['identities'] def add_feature(self, jid, node, ifrom, data): """ Add a feature to a JID/node combination. The data parameter should include: feature -- The namespace of the supported feature. """ new_node = self.add_node(jid, node) new_node['info'].add_feature( data.get('feature', '')) def set_features(self, jid, node, ifrom, data): """ Add or replace all features for a JID/node combination. The data parameter should include: features -- The new set of supported features. """ features = data.get('features', set()) new_node = self.add_node(jid, node) new_node['info']['features'] = features def del_feature(self, jid, node, ifrom, data): """ Remove a feature from a JID/node combination. The data parameter should include: feature -- The namespace of the removed feature. """ if self.node_exists(jid, node): self.get_node(jid, node)['info'].del_feature( data.get('feature', '')) def del_features(self, jid, node, ifrom, data): """ Remove all features from a JID/node combination. The data parameter is not used. """ if not self.node_exists(jid, node): return del self.get_node(jid, node)['info']['features'] def add_item(self, jid, node, ifrom, data): """ Add an item to a JID/node combination. The data parameter may include: ijid -- The JID for the item. inode -- Optional additional information to reference non-addressable items. name -- Optional human readable name for the item. """ new_node = self.add_node(jid, node) new_node['items'].add_item( data.get('ijid', ''), node=data.get('inode', ''), name=data.get('name', '')) def del_item(self, jid, node, ifrom, data): """ Remove an item from a JID/node combination. The data parameter may include: ijid -- JID of the item to remove. inode -- Optional extra identifying information. """ if self.node_exists(jid, node): self.get_node(jid, node)['items'].del_item( data.get('ijid', ''), node=data.get('inode', None)) def cache_info(self, jid, node, ifrom, data): """ Cache disco information for an external JID. The data parameter is the Iq result stanza containing the disco info to cache, or the disco#info substanza itself. """ if isinstance(data, Iq): data = data['disco_info'] new_node = self.add_node(jid, node, ifrom) new_node['info'] = data def get_cached_info(self, jid, node, ifrom, data): """ Retrieve cached disco info data. The data parameter is not used. """ if not self.node_exists(jid, node, ifrom): return None else: return self.get_node(jid, node, ifrom)['info'] slixmpp-slix-1.4.2/slixmpp/plugins/xep_0033/000077500000000000000000000000001342457644200206205ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0033/__init__.py000066400000000000000000000006561342457644200227400ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0033 import stanza from slixmpp.plugins.xep_0033.stanza import Addresses, Address from slixmpp.plugins.xep_0033.addresses import XEP_0033 register_plugin(XEP_0033) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0033/addresses.py000066400000000000000000000016511342457644200231520ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp import Message, Presence from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins import BasePlugin from slixmpp.plugins.xep_0033 import stanza, Addresses class XEP_0033(BasePlugin): """ XEP-0033: Extended Stanza Addressing """ name = 'xep_0033' description = 'XEP-0033: Extended Stanza Addressing' dependencies = {'xep_0030'} stanza = stanza def plugin_init(self): register_stanza_plugin(Message, Addresses) register_stanza_plugin(Presence, Addresses) def plugin_end(self): self.xmpp['xep_0030'].del_feature(feature=Addresses.namespace) def session_bind(self, jid): self.xmpp['xep_0030'].add_feature(Addresses.namespace) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0033/stanza.py000066400000000000000000000071251342457644200224770ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import JID, ElementBase, ET, register_stanza_plugin class Addresses(ElementBase): name = 'addresses' namespace = 'http://jabber.org/protocol/address' plugin_attrib = 'addresses' interfaces = set() def add_address(self, atype='to', jid='', node='', uri='', desc='', delivered=False): addr = Address(parent=self) addr['type'] = atype addr['jid'] = jid addr['node'] = node addr['uri'] = uri addr['desc'] = desc addr['delivered'] = delivered return addr # Additional methods for manipulating sets of addresses # based on type are generated below. class Address(ElementBase): name = 'address' namespace = 'http://jabber.org/protocol/address' plugin_attrib = 'address' interfaces = {'type', 'jid', 'node', 'uri', 'desc', 'delivered'} address_types = {'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'} def get_jid(self): return JID(self._get_attr('jid')) def set_jid(self, value): self._set_attr('jid', str(value)) def get_delivered(self): value = self._get_attr('delivered', False) return value and value.lower() in ('true', '1') def set_delivered(self, delivered): if delivered: self._set_attr('delivered', 'true') else: del self['delivered'] def set_uri(self, uri): if uri: del self['jid'] del self['node'] self._set_attr('uri', uri) else: self._del_attr('uri') # ===================================================================== # Auto-generate address type filters for the Addresses class. def _addr_filter(atype): def _type_filter(addr): if isinstance(addr, Address): if atype == 'all' or addr['type'] == atype: return True return False return _type_filter def _build_methods(atype): def get_multi(self): return list(filter(_addr_filter(atype), self)) def set_multi(self, value): del self[atype] for addr in value: # Support assigning dictionary versions of addresses # instead of full Address objects. if not isinstance(addr, Address): if atype != 'all': addr['type'] = atype elif 'atype' in addr and 'type' not in addr: addr['type'] = addr['atype'] addrObj = Address() addrObj.values = addr addr = addrObj self.append(addr) def del_multi(self): res = list(filter(_addr_filter(atype), self)) for addr in res: self.iterables.remove(addr) self.xml.remove(addr.xml) return get_multi, set_multi, del_multi for atype in ('all', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'): get_multi, set_multi, del_multi = _build_methods(atype) Addresses.interfaces.add(atype) setattr(Addresses, "get_%s" % atype, get_multi) setattr(Addresses, "set_%s" % atype, set_multi) setattr(Addresses, "del_%s" % atype, del_multi) if atype == 'all': Addresses.interfaces.add('addresses') setattr(Addresses, "get_addresses", get_multi) setattr(Addresses, "set_addresses", set_multi) setattr(Addresses, "del_addresses", del_multi) register_stanza_plugin(Addresses, Address, iterable=True) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0045.py000066400000000000000000000366641342457644200212140ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from __future__ import with_statement import logging from slixmpp import Presence from slixmpp.plugins import BasePlugin, register_plugin from slixmpp.xmlstream import register_stanza_plugin, ElementBase, JID, ET from slixmpp.xmlstream.handler.callback import Callback from slixmpp.xmlstream.matcher.xpath import MatchXPath from slixmpp.xmlstream.matcher.xmlmask import MatchXMLMask from slixmpp.exceptions import IqError, IqTimeout log = logging.getLogger(__name__) class MUCPresence(ElementBase): name = 'x' namespace = 'http://jabber.org/protocol/muc#user' plugin_attrib = 'muc' interfaces = {'affiliation', 'role', 'jid', 'nick', 'room'} affiliations = {'', } roles = {'', } def get_xml_item(self): item = self.xml.find('{http://jabber.org/protocol/muc#user}item') if item is None: item = ET.Element('{http://jabber.org/protocol/muc#user}item') self.xml.append(item) return item def get_affiliation(self): #TODO if no affilation, set it to the default and return default item = self.get_xml_item() return item.get('affiliation', '') def set_affiliation(self, value): item = self.get_xml_item() #TODO check for valid affiliation item.attrib['affiliation'] = value return self def del_affiliation(self): item = self.get_xml_item() #TODO set default affiliation if 'affiliation' in item.attrib: del item.attrib['affiliation'] return self def get_jid(self): item = self.get_xml_item() return JID(item.get('jid', '')) def set_jid(self, value): item = self.get_xml_item() if not isinstance(value, str): value = str(value) item.attrib['jid'] = value return self def del_jid(self): item = self.get_xml_item() if 'jid' in item.attrib: del item.attrib['jid'] return self def get_role(self): item = self.get_xml_item() #TODO get default role, set default role if none return item.get('role', '') def set_role(self, value): item = self.get_xml_item() #TODO check for valid role item.attrib['role'] = value return self def del_role(self): item = self.get_xml_item() #TODO set default role if 'role' in item.attrib: del item.attrib['role'] return self def get_nick(self): return self.parent()['from'].resource def get_room(self): return self.parent()['from'].bare def set_nick(self, value): log.warning("Cannot set nick through mucpresence plugin.") return self def set_room(self, value): log.warning("Cannot set room through mucpresence plugin.") return self def del_nick(self): log.warning("Cannot delete nick through mucpresence plugin.") return self def del_room(self): log.warning("Cannot delete room through mucpresence plugin.") return self class XEP_0045(BasePlugin): """ Implements XEP-0045 Multi-User Chat """ name = 'xep_0045' description = 'XEP-0045: Multi-User Chat' dependencies = {'xep_0030', 'xep_0004'} def plugin_init(self): self.rooms = {} self.our_nicks = {} self.xep = '0045' # load MUC support in presence stanzas register_stanza_plugin(Presence, MUCPresence) self.xmpp.register_handler(Callback('MUCPresence', MatchXMLMask("" % self.xmpp.default_ns), self.handle_groupchat_presence)) self.xmpp.register_handler(Callback('MUCError', MatchXMLMask("" % self.xmpp.default_ns), self.handle_groupchat_error_message)) self.xmpp.register_handler(Callback('MUCMessage', MatchXMLMask("" % self.xmpp.default_ns), self.handle_groupchat_message)) self.xmpp.register_handler(Callback('MUCSubject', MatchXMLMask("" % self.xmpp.default_ns), self.handle_groupchat_subject)) self.xmpp.register_handler(Callback('MUCConfig', MatchXMLMask("" % self.xmpp.default_ns), self.handle_config_change)) self.xmpp.register_handler(Callback('MUCInvite', MatchXPath("{%s}message/{%s}x/{%s}invite" % ( self.xmpp.default_ns, 'http://jabber.org/protocol/muc#user', 'http://jabber.org/protocol/muc#user')), self.handle_groupchat_invite)) def plugin_end(self): self.xmpp.plugin['xep_0030'].del_feature(feature='http://jabber.org/protocol/muc') def session_bind(self, jid): self.xmpp.plugin['xep_0030'].add_feature('http://jabber.org/protocol/muc') def handle_groupchat_invite(self, inv): """ Handle an invite into a muc. """ logging.debug("MUC invite to %s from %s: %s", inv['to'], inv["from"], inv) if inv['from'] not in self.rooms.keys(): self.xmpp.event("groupchat_invite", inv) def handle_config_change(self, msg): """Handle a MUC configuration change (with status code).""" self.xmpp.event('groupchat_config_status', msg) self.xmpp.event('muc::%s::config_status' % msg['from'].bare , msg) def handle_groupchat_presence(self, pr): """ Handle a presence in a muc. """ got_offline = False got_online = False if pr['muc']['room'] not in self.rooms.keys(): return self.xmpp.roster[pr['from']].ignore_updates = True entry = pr['muc'].get_stanza_values() entry['show'] = pr['show'] entry['status'] = pr['status'] entry['alt_nick'] = pr['nick'] if pr['type'] == 'unavailable': if entry['nick'] in self.rooms[entry['room']]: del self.rooms[entry['room']][entry['nick']] got_offline = True else: if entry['nick'] not in self.rooms[entry['room']]: got_online = True self.rooms[entry['room']][entry['nick']] = entry log.debug("MUC presence from %s/%s : %s", entry['room'],entry['nick'], entry) self.xmpp.event("groupchat_presence", pr) self.xmpp.event("muc::%s::presence" % entry['room'], pr) if got_offline: self.xmpp.event("muc::%s::got_offline" % entry['room'], pr) if got_online: self.xmpp.event("muc::%s::got_online" % entry['room'], pr) def handle_groupchat_message(self, msg): """ Handle a message event in a muc. """ self.xmpp.event('groupchat_message', msg) self.xmpp.event("muc::%s::message" % msg['from'].bare, msg) def handle_groupchat_error_message(self, msg): """ Handle a message error event in a muc. """ self.xmpp.event('groupchat_message_error', msg) self.xmpp.event("muc::%s::message_error" % msg['from'].bare, msg) def handle_groupchat_subject(self, msg): """ Handle a message coming from a muc indicating a change of subject (or announcing it when joining the room) """ self.xmpp.event('groupchat_subject', msg) def jid_in_room(self, room, jid): for nick in self.rooms[room]: entry = self.rooms[room][nick] if entry is not None and entry['jid'].full == jid: return True return False def get_nick(self, room, jid): for nick in self.rooms[room]: entry = self.rooms[room][nick] if entry is not None and entry['jid'].full == jid: return nick def configure_room(self, room, form=None, ifrom=None): if form is None: form = self.get_room_config(room, ifrom=ifrom) iq = self.xmpp.make_iq_set() iq['to'] = room if ifrom is not None: iq['from'] = ifrom query = ET.Element('{http://jabber.org/protocol/muc#owner}query') form['type'] = 'submit' query.append(form) iq.append(query) # For now, swallow errors to preserve existing API try: result = iq.send() except IqError: return False except IqTimeout: return False return True def join_muc(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None, pfrom=None): """ Join the specified room, requesting 'maxhistory' lines of history. """ stanza = self.xmpp.make_presence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow, pfrom=pfrom) x = ET.Element('{http://jabber.org/protocol/muc}x') if password: passelement = ET.Element('{http://jabber.org/protocol/muc}password') passelement.text = password x.append(passelement) if maxhistory: history = ET.Element('{http://jabber.org/protocol/muc}history') if maxhistory == "0": history.attrib['maxchars'] = maxhistory else: history.attrib['maxstanzas'] = maxhistory x.append(history) stanza.append(x) if not wait: self.xmpp.send(stanza) else: #wait for our own room presence back expect = ET.Element("{%s}presence" % self.xmpp.default_ns, {'from':"%s/%s" % (room, nick)}) self.xmpp.send(stanza, expect) self.rooms[room] = {} self.our_nicks[room] = nick def destroy(self, room, reason='', altroom = '', ifrom=None): iq = self.xmpp.make_iq_set() if ifrom is not None: iq['from'] = ifrom iq['to'] = room query = ET.Element('{http://jabber.org/protocol/muc#owner}query') destroy = ET.Element('{http://jabber.org/protocol/muc#owner}destroy') if altroom: destroy.attrib['jid'] = altroom xreason = ET.Element('{http://jabber.org/protocol/muc#owner}reason') xreason.text = reason destroy.append(xreason) query.append(destroy) iq.append(query) # For now, swallow errors to preserve existing API try: r = iq.send() except IqError: return False except IqTimeout: return False return True def set_affiliation(self, room, jid=None, nick=None, affiliation='member', ifrom=None): """ Change room affiliation.""" if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'): raise TypeError query = ET.Element('{http://jabber.org/protocol/muc#admin}query') if nick is not None: item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'affiliation':affiliation, 'nick':nick}) else: item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'affiliation':affiliation, 'jid':jid}) query.append(item) iq = self.xmpp.make_iq_set(query) iq['to'] = room iq['from'] = ifrom # For now, swallow errors to preserve existing API try: result = iq.send() except IqError: return False except IqTimeout: return False return True def set_role(self, room, nick, role): """ Change role property of a nick in a room. Typically, roles are temporary (they last only as long as you are in the room), whereas affiliations are permanent (they last across groupchat sessions). """ if role not in ('moderator', 'participant', 'visitor', 'none'): raise TypeError query = ET.Element('{http://jabber.org/protocol/muc#admin}query') item = ET.Element('item', {'role':role, 'nick':nick}) query.append(item) iq = self.xmpp.make_iq_set(query) iq['to'] = room result = iq.send() if result is False or result['type'] != 'result': raise ValueError return True def invite(self, room, jid, reason='', mfrom=''): """ Invite a jid to a room.""" msg = self.xmpp.make_message(room) msg['from'] = mfrom x = ET.Element('{http://jabber.org/protocol/muc#user}x') invite = ET.Element('{http://jabber.org/protocol/muc#user}invite', {'to': jid}) if reason: rxml = ET.Element('{http://jabber.org/protocol/muc#user}reason') rxml.text = reason invite.append(rxml) x.append(invite) msg.append(x) self.xmpp.send(msg) def leave_muc(self, room, nick, msg='', pfrom=None): """ Leave the specified room. """ if msg: self.xmpp.send_presence(pshow='unavailable', pto="%s/%s" % (room, nick), pstatus=msg, pfrom=pfrom) else: self.xmpp.send_presence(pshow='unavailable', pto="%s/%s" % (room, nick), pfrom=pfrom) del self.rooms[room] def get_room_config(self, room, ifrom=''): iq = self.xmpp.make_iq_get('http://jabber.org/protocol/muc#owner') iq['to'] = room iq['from'] = ifrom # For now, swallow errors to preserve existing API try: result = iq.send() except IqError: raise ValueError except IqTimeout: raise ValueError form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') if form is None: raise ValueError return self.xmpp.plugin['xep_0004'].build_form(form) def cancel_config(self, room, ifrom=None): query = ET.Element('{http://jabber.org/protocol/muc#owner}query') x = ET.Element('{jabber:x:data}x', type='cancel') query.append(x) iq = self.xmpp.make_iq_set(query) iq['to'] = room iq['from'] = ifrom iq.send() def set_room_config(self, room, config, ifrom=''): query = ET.Element('{http://jabber.org/protocol/muc#owner}query') config['type'] = 'submit' query.append(config) iq = self.xmpp.make_iq_set(query) iq['to'] = room iq['from'] = ifrom iq.send() def get_joined_rooms(self): return self.rooms.keys() def get_our_jid_in_room(self, room_jid): """ Return the jid we're using in a room. """ return "%s/%s" % (room_jid, self.our_nicks[room_jid]) def get_jid_property(self, room, nick, jid_property): """ Get the property of a nick in a room, such as its 'jid' or 'affiliation' If not found, return None. """ if room in self.rooms and nick in self.rooms[room] and jid_property in self.rooms[room][nick]: return self.rooms[room][nick][jid_property] else: return None def get_roster(self, room): """ Get the list of nicks in a room. """ if room not in self.rooms.keys(): return None return self.rooms[room].keys() def get_users_by_affiliation(cls, room, affiliation='member', ifrom=None): if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'): raise TypeError query = ET.Element('{http://jabber.org/protocol/muc#admin}query') item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'affiliation': affiliation}) query.append(item) iq = cls.xmpp.Iq(sto=room, sfrom=ifrom, stype='get') iq.append(query) return iq.send() register_plugin(XEP_0045) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0047/000077500000000000000000000000001342457644200206255ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0047/__init__.py000066400000000000000000000007401342457644200227370ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0047 import stanza from slixmpp.plugins.xep_0047.stanza import Open, Close, Data from slixmpp.plugins.xep_0047.stream import IBBytestream from slixmpp.plugins.xep_0047.ibb import XEP_0047 register_plugin(XEP_0047) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0047/ibb.py000066400000000000000000000145401342457644200217370ustar00rootroot00000000000000import asyncio import uuid import logging from slixmpp import Message, Iq from slixmpp.exceptions import XMPPError from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins import BasePlugin from slixmpp.plugins.xep_0047 import stanza, Open, Close, Data, IBBytestream log = logging.getLogger(__name__) class XEP_0047(BasePlugin): name = 'xep_0047' description = 'XEP-0047: In-band Bytestreams' dependencies = {'xep_0030'} stanza = stanza default_config = { 'block_size': 4096, 'max_block_size': 8192, 'auto_accept': False, } def plugin_init(self): self._streams = {} self._preauthed_sids = {} register_stanza_plugin(Iq, Open) register_stanza_plugin(Iq, Close) register_stanza_plugin(Iq, Data) register_stanza_plugin(Message, Data) self.xmpp.register_handler(Callback( 'IBB Open', StanzaPath('iq@type=set/ibb_open'), self._handle_open_request)) self.xmpp.register_handler(Callback( 'IBB Close', StanzaPath('iq@type=set/ibb_close'), self._handle_close)) self.xmpp.register_handler(Callback( 'IBB Data', StanzaPath('iq@type=set/ibb_data'), self._handle_data)) self.xmpp.register_handler(Callback( 'IBB Message Data', StanzaPath('message/ibb_data'), self._handle_data)) self.api.register(self._authorized, 'authorized', default=True) self.api.register(self._authorized_sid, 'authorized_sid', default=True) self.api.register(self._preauthorize_sid, 'preauthorize_sid', default=True) self.api.register(self._get_stream, 'get_stream', default=True) self.api.register(self._set_stream, 'set_stream', default=True) self.api.register(self._del_stream, 'del_stream', default=True) def plugin_end(self): self.xmpp.remove_handler('IBB Open') self.xmpp.remove_handler('IBB Close') self.xmpp.remove_handler('IBB Data') self.xmpp.remove_handler('IBB Message Data') self.xmpp['xep_0030'].del_feature(feature='http://jabber.org/protocol/ibb') def session_bind(self, jid): self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/ibb') def _get_stream(self, jid, sid, peer_jid, data): return self._streams.get((jid, sid, peer_jid), None) def _set_stream(self, jid, sid, peer_jid, stream): self._streams[(jid, sid, peer_jid)] = stream def _del_stream(self, jid, sid, peer_jid, data): if (jid, sid, peer_jid) in self._streams: del self._streams[(jid, sid, peer_jid)] def _accept_stream(self, iq): receiver = iq['to'] sender = iq['from'] sid = iq['ibb_open']['sid'] if self.api['authorized_sid'](receiver, sid, sender, iq): return True return self.api['authorized'](receiver, sid, sender, iq) def _authorized(self, jid, sid, ifrom, iq): if self.auto_accept: return True return False def _authorized_sid(self, jid, sid, ifrom, iq): if (jid, sid, ifrom) in self._preauthed_sids: del self._preauthed_sids[(jid, sid, ifrom)] return True return False def _preauthorize_sid(self, jid, sid, ifrom, data): self._preauthed_sids[(jid, sid, ifrom)] = True def open_stream(self, jid, block_size=None, sid=None, use_messages=False, ifrom=None, timeout=None, callback=None): if sid is None: sid = str(uuid.uuid4()) if block_size is None: block_size = self.block_size iq = self.xmpp.Iq() iq['type'] = 'set' iq['to'] = jid iq['from'] = ifrom iq['ibb_open']['block_size'] = block_size iq['ibb_open']['sid'] = sid iq['ibb_open']['stanza'] = 'message' if use_messages else 'iq' stream = IBBytestream(self.xmpp, sid, block_size, iq['from'], iq['to'], use_messages) stream_future = asyncio.Future() def _handle_opened_stream(iq): log.debug('IBB stream (%s) accepted by %s', stream.sid, iq['from']) stream.self_jid = iq['to'] stream.peer_jid = iq['from'] stream.stream_started = True self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream) stream_future.set_result(stream) if callback is not None: callback(stream) self.xmpp.event('ibb_stream_start', stream) self.xmpp.event('stream:%s:%s' % (stream.sid, stream.peer_jid), stream) iq.send(timeout=timeout, callback=_handle_opened_stream) return stream_future def _handle_open_request(self, iq): sid = iq['ibb_open']['sid'] size = iq['ibb_open']['block_size'] or self.block_size log.debug('Received IBB stream request from %s', iq['from']) if not sid: raise XMPPError(etype='modify', condition='bad-request') if not self._accept_stream(iq): raise XMPPError(etype='cancel', condition='not-acceptable') if size > self.max_block_size: raise XMPPError('resource-constraint') stream = IBBytestream(self.xmpp, sid, size, iq['to'], iq['from']) stream.stream_started = True self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream) iq.reply().send() self.xmpp.event('ibb_stream_start', stream) self.xmpp.event('stream:%s:%s' % (sid, stream.peer_jid), stream) def _handle_data(self, stanza): sid = stanza['ibb_data']['sid'] stream = self.api['get_stream'](stanza['to'], sid, stanza['from']) if stream is not None and stanza['from'] == stream.peer_jid: stream._recv_data(stanza) else: raise XMPPError('item-not-found') def _handle_close(self, iq): sid = iq['ibb_close']['sid'] stream = self.api['get_stream'](iq['to'], sid, iq['from']) if stream is not None and iq['from'] == stream.peer_jid: stream._closed(iq) self.api['del_stream'](stream.self_jid, stream.sid, stream.peer_jid) else: raise XMPPError('item-not-found') slixmpp-slix-1.4.2/slixmpp/plugins/xep_0047/stanza.py000066400000000000000000000032541342457644200225030ustar00rootroot00000000000000import re import base64 from slixmpp.util import bytes from slixmpp.exceptions import XMPPError from slixmpp.xmlstream import ElementBase VALID_B64 = re.compile(r'[A-Za-z0-9\+\/]*=*') def to_b64(data): return bytes(base64.b64encode(bytes(data))).decode('utf-8') def from_b64(data): return bytes(base64.b64decode(bytes(data))) class Open(ElementBase): name = 'open' namespace = 'http://jabber.org/protocol/ibb' plugin_attrib = 'ibb_open' interfaces = {'block_size', 'sid', 'stanza'} def get_block_size(self): return int(self._get_attr('block-size', '0')) def set_block_size(self, value): self._set_attr('block-size', str(value)) def del_block_size(self): self._del_attr('block-size') class Data(ElementBase): name = 'data' namespace = 'http://jabber.org/protocol/ibb' plugin_attrib = 'ibb_data' interfaces = {'seq', 'sid', 'data'} sub_interfaces = {'data'} def get_seq(self): return int(self._get_attr('seq', '0')) def set_seq(self, value): self._set_attr('seq', str(value)) def get_data(self): text = self.xml.text if not text: raise XMPPError('not-acceptable', 'IBB data element is empty.') b64_data = text.strip() if VALID_B64.match(b64_data).group() == b64_data: return from_b64(b64_data) else: raise XMPPError('not-acceptable') def set_data(self, value): self.xml.text = to_b64(value) def del_data(self): self.xml.text = '' class Close(ElementBase): name = 'close' namespace = 'http://jabber.org/protocol/ibb' plugin_attrib = 'ibb_close' interfaces = {'sid'} slixmpp-slix-1.4.2/slixmpp/plugins/xep_0047/stream.py000066400000000000000000000071541342457644200225010ustar00rootroot00000000000000import asyncio import socket import logging from slixmpp.stanza import Iq from slixmpp.exceptions import XMPPError log = logging.getLogger(__name__) class IBBytestream(object): def __init__(self, xmpp, sid, block_size, jid, peer, use_messages=False): self.xmpp = xmpp self.sid = sid self.block_size = block_size self.use_messages = use_messages if jid is None: jid = xmpp.boundjid self.self_jid = jid self.peer_jid = peer self.send_seq = -1 self.recv_seq = -1 self.stream_started = False self.stream_in_closed = False self.stream_out_closed = False self.recv_queue = asyncio.Queue() async def send(self, data, timeout=None): if not self.stream_started or self.stream_out_closed: raise socket.error if len(data) > self.block_size: data = data[:self.block_size] self.send_seq = (self.send_seq + 1) % 65535 seq = self.send_seq if self.use_messages: msg = self.xmpp.Message() msg['to'] = self.peer_jid msg['from'] = self.self_jid msg['id'] = self.xmpp.new_id() msg['ibb_data']['sid'] = self.sid msg['ibb_data']['seq'] = seq msg['ibb_data']['data'] = data msg.send() else: iq = self.xmpp.Iq() iq['type'] = 'set' iq['to'] = self.peer_jid iq['from'] = self.self_jid iq['ibb_data']['sid'] = self.sid iq['ibb_data']['seq'] = seq iq['ibb_data']['data'] = data await iq.send(timeout=timeout) return len(data) async def sendall(self, data, timeout=None): sent_len = 0 while sent_len < len(data): sent_len += await self.send(data[sent_len:self.block_size], timeout=timeout) async def sendfile(self, file, timeout=None): while True: data = file.read(self.block_size) if not data: break await self.send(data, timeout=timeout) def _recv_data(self, stanza): new_seq = stanza['ibb_data']['seq'] if new_seq != (self.recv_seq + 1) % 65535: self.close() raise XMPPError('unexpected-request') self.recv_seq = new_seq data = stanza['ibb_data']['data'] if len(data) > self.block_size: self.close() raise XMPPError('not-acceptable') self.recv_queue.put_nowait(data) self.xmpp.event('ibb_stream_data', self) if isinstance(stanza, Iq): stanza.reply().send() def recv(self, *args, **kwargs): return self.read() def read(self): if not self.stream_started or self.stream_in_closed: raise socket.error return self.recv_queue.get_nowait() def close(self, timeout=None): iq = self.xmpp.Iq() iq['type'] = 'set' iq['to'] = self.peer_jid iq['from'] = self.self_jid iq['ibb_close']['sid'] = self.sid self.stream_out_closed = True def _close_stream(_): self.stream_in_closed = True future = iq.send(timeout=timeout, callback=_close_stream) self.xmpp.event('ibb_stream_end', self) return future def _closed(self, iq): self.stream_in_closed = True self.stream_out_closed = True iq.reply().send() self.xmpp.event('ibb_stream_end', self) def makefile(self, *args, **kwargs): return self def connect(*args, **kwargs): return None def shutdown(self, *args, **kwargs): return None slixmpp-slix-1.4.2/slixmpp/plugins/xep_0048/000077500000000000000000000000001342457644200206265ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0048/__init__.py000066400000000000000000000006121342457644200227360ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0048.stanza import Bookmarks, Conference, URL from slixmpp.plugins.xep_0048.bookmarks import XEP_0048 register_plugin(XEP_0048) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0048/bookmarks.py000066400000000000000000000044651342457644200232010ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp import Iq from slixmpp.plugins import BasePlugin from slixmpp.exceptions import XMPPError from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins.xep_0048 import stanza, Bookmarks, Conference, URL log = logging.getLogger(__name__) class XEP_0048(BasePlugin): name = 'xep_0048' description = 'XEP-0048: Bookmarks' dependencies = {'xep_0045', 'xep_0049', 'xep_0060', 'xep_0163', 'xep_0223'} stanza = stanza default_config = { 'auto_join': False, 'storage_method': 'xep_0049' } def plugin_init(self): register_stanza_plugin(self.xmpp['xep_0060'].stanza.Item, Bookmarks) self.xmpp['xep_0049'].register(Bookmarks) self.xmpp['xep_0163'].register_pep('bookmarks', Bookmarks) self.xmpp.add_event_handler('session_start', self._autojoin) def plugin_end(self): self.xmpp.del_event_handler('session_start', self._autojoin) def _autojoin(self, __): if not self.auto_join: return try: result = self.get_bookmarks(method=self.storage_method) except XMPPError: return if self.storage_method == 'xep_0223': bookmarks = result['pubsub']['items']['item']['bookmarks'] else: bookmarks = result['private']['bookmarks'] for conf in bookmarks['conferences']: if conf['autojoin']: log.debug('Auto joining %s as %s', conf['jid'], conf['nick']) self.xmpp['xep_0045'].join_muc(conf['jid'], conf['nick'], password=conf['password']) def set_bookmarks(self, bookmarks, method=None, **iqargs): if not method: method = self.storage_method return self.xmpp[method].store(bookmarks, **iqargs) def get_bookmarks(self, method=None, **iqargs): if not method: method = self.storage_method loc = 'storage:bookmarks' if method == 'xep_0223' else 'bookmarks' return self.xmpp[method].retrieve(loc, **iqargs) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0048/stanza.py000066400000000000000000000033571342457644200225100ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ET, ElementBase, register_stanza_plugin class Bookmarks(ElementBase): name = 'storage' namespace = 'storage:bookmarks' plugin_attrib = 'bookmarks' interfaces = set() def add_conference(self, jid, nick, name=None, autojoin=None, password=None): conf = Conference() conf['jid'] = jid conf['nick'] = nick if name is None: name = jid conf['name'] = name conf['autojoin'] = autojoin conf['password'] = password self.append(conf) def add_url(self, url, name=None): saved_url = URL() saved_url['url'] = url if name is None: name = url saved_url['name'] = name self.append(saved_url) class Conference(ElementBase): name = 'conference' namespace = 'storage:bookmarks' plugin_attrib = 'conference' plugin_multi_attrib = 'conferences' interfaces = {'nick', 'password', 'autojoin', 'jid', 'name'} sub_interfaces = {'nick', 'password'} def get_autojoin(self): value = self._get_attr('autojoin') return value in ('1', 'true') def set_autojoin(self, value): del self['autojoin'] if value in ('1', 'true', True): self._set_attr('autojoin', 'true') class URL(ElementBase): name = 'url' namespace = 'storage:bookmarks' plugin_attrib = 'url' plugin_multi_attrib = 'urls' interfaces = {'url', 'name'} register_stanza_plugin(Bookmarks, Conference, iterable=True) register_stanza_plugin(Bookmarks, URL, iterable=True) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0049/000077500000000000000000000000001342457644200206275ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0049/__init__.py000066400000000000000000000006001342457644200227340ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0049.stanza import PrivateXML from slixmpp.plugins.xep_0049.private_storage import XEP_0049 register_plugin(XEP_0049) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0049/private_storage.py000066400000000000000000000031041342457644200243750ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp import Iq from slixmpp.plugins import BasePlugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins.xep_0049 import stanza, PrivateXML log = logging.getLogger(__name__) class XEP_0049(BasePlugin): name = 'xep_0049' description = 'XEP-0049: Private XML Storage' dependencies = {} stanza = stanza def plugin_init(self): register_stanza_plugin(Iq, PrivateXML) def register(self, stanza): register_stanza_plugin(PrivateXML, stanza, iterable=True) def store(self, data, ifrom=None, timeout=None, callback=None, timeout_callback=None): iq = self.xmpp.Iq() iq['type'] = 'set' iq['from'] = ifrom if not isinstance(data, list): data = [data] for elem in data: iq['private'].append(elem) return iq.send(timeout=timeout, callback=callback, timeout_callback=timeout_callback) def retrieve(self, name, ifrom=None, timeout=None, callback=None, timeout_callback=None): iq = self.xmpp.Iq() iq['type'] = 'get' iq['from'] = ifrom iq['private'].enable(name) return iq.send(timeout=timeout, callback=callback, timeout_callback=timeout_callback) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0049/stanza.py000066400000000000000000000005711342457644200225040ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ET, ElementBase class PrivateXML(ElementBase): name = 'query' namespace = 'jabber:iq:private' plugin_attrib = 'private' interfaces = set() slixmpp-slix-1.4.2/slixmpp/plugins/xep_0050/000077500000000000000000000000001342457644200206175ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0050/__init__.py000066400000000000000000000005631342457644200227340ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0050.stanza import Command from slixmpp.plugins.xep_0050.adhoc import XEP_0050 register_plugin(XEP_0050) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0050/adhoc.py000066400000000000000000000601101342457644200222450ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging import time from slixmpp import Iq from slixmpp.exceptions import XMPPError from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.xmlstream import register_stanza_plugin, JID from slixmpp.plugins import BasePlugin from slixmpp.plugins.xep_0050 import stanza from slixmpp.plugins.xep_0050 import Command from slixmpp.plugins.xep_0004 import Form log = logging.getLogger(__name__) class XEP_0050(BasePlugin): """ XEP-0050: Ad-Hoc Commands XMPP's Adhoc Commands provides a generic workflow mechanism for interacting with applications. The result is similar to menu selections and multi-step dialogs in normal desktop applications. Clients do not need to know in advance what commands are provided by any particular application or agent. While adhoc commands provide similar functionality to Jabber-RPC, adhoc commands are used primarily for human interaction. Also see Events: command_execute -- Received a command with action="execute" command_next -- Received a command with action="next" command_complete -- Received a command with action="complete" command_cancel -- Received a command with action="cancel" Attributes: commands -- A dictionary mapping JID/node pairs to command names and handlers. sessions -- A dictionary or equivalent backend mapping session IDs to dictionaries containing data relevant to a command's session. Methods: plugin_init -- Overrides BasePlugin.plugin_init post_init -- Overrides BasePlugin.post_init new_session -- Return a new session ID. prep_handlers -- Placeholder. May call with a list of handlers to prepare them for use with the session storage backend, if needed. set_backend -- Replace the default session storage with some external storage mechanism, such as a database. The provided backend wrapper must be able to act using the same syntax as a dictionary. add_command -- Add a command for use by external entitites. get_commands -- Retrieve a list of commands provided by a remote agent. send_command -- Send a command request to a remote agent. start_command -- Command user API: initiate a command session continue_command -- Command user API: proceed to the next step cancel_command -- Command user API: cancel a command complete_command -- Command user API: finish a command terminate_command -- Command user API: delete a command's session """ name = 'xep_0050' description = 'XEP-0050: Ad-Hoc Commands' dependencies = {'xep_0030', 'xep_0004'} stanza = stanza default_config = { 'session_db': None } def plugin_init(self): """Start the XEP-0050 plugin.""" self.sessions = self.session_db if self.sessions is None: self.sessions = {} self.commands = {} self.xmpp.register_handler( Callback("Ad-Hoc Execute", StanzaPath('iq@type=set/command'), self._handle_command)) register_stanza_plugin(Iq, Command) register_stanza_plugin(Command, Form, iterable=True) self.xmpp.add_event_handler('command_execute', self._handle_command_start) self.xmpp.add_event_handler('command_next', self._handle_command_next) self.xmpp.add_event_handler('command_cancel', self._handle_command_cancel) self.xmpp.add_event_handler('command_complete', self._handle_command_complete) def plugin_end(self): self.xmpp.del_event_handler('command_execute', self._handle_command_start) self.xmpp.del_event_handler('command_next', self._handle_command_next) self.xmpp.del_event_handler('command_cancel', self._handle_command_cancel) self.xmpp.del_event_handler('command_complete', self._handle_command_complete) self.xmpp.remove_handler('Ad-Hoc Execute') self.xmpp['xep_0030'].del_feature(feature=Command.namespace) self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple()) def session_bind(self, jid): self.xmpp['xep_0030'].add_feature(Command.namespace) self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple()) def set_backend(self, db): """ Replace the default session storage dictionary with a generic, external data storage mechanism. The replacement backend must be able to interact through the same syntax and interfaces as a normal dictionary. Arguments: db -- The new session storage mechanism. """ self.sessions = db def prep_handlers(self, handlers, **kwargs): """ Prepare a list of functions for use by the backend service. Intended to be replaced by the backend service as needed. Arguments: handlers -- A list of function pointers **kwargs -- Any additional parameters required by the backend. """ pass # ================================================================= # Server side (command provider) API def add_command(self, jid=None, node=None, name='', handler=None): """ Make a new command available to external entities. Access control may be implemented in the provided handler. Command workflow is done across a sequence of command handlers. The first handler is given the initial Iq stanza of the request in order to support access control. Subsequent handlers are given only the payload items of the command. All handlers will receive the command's session data. Arguments: jid -- The JID that will expose the command. node -- The node associated with the command. name -- A human readable name for the command. handler -- A function that will generate the response to the initial command request, as well as enforcing any access control policies. """ if jid is None: jid = self.xmpp.boundjid elif not isinstance(jid, JID): jid = JID(jid) item_jid = jid.full self.xmpp['xep_0030'].add_identity(category='automation', itype='command-list', name='Ad-Hoc commands', node=Command.namespace, jid=jid) self.xmpp['xep_0030'].add_item(jid=item_jid, name=name, node=Command.namespace, subnode=node, ijid=jid) self.xmpp['xep_0030'].add_identity(category='automation', itype='command-node', name=name, node=node, jid=jid) self.xmpp['xep_0030'].add_feature(Command.namespace, None, jid) self.commands[(item_jid, node)] = (name, handler) def new_session(self): """Return a new session ID.""" return str(time.time()) + '-' + self.xmpp.new_id() def _handle_command(self, iq): """Raise command events based on the command action.""" self.xmpp.event('command_%s' % iq['command']['action'], iq) def _handle_command_start(self, iq): """ Process an initial request to execute a command. Arguments: iq -- The command execution request. """ sessionid = self.new_session() node = iq['command']['node'] key = (iq['to'].full, node) name, handler = self.commands.get(key, ('Not found', None)) if not handler: log.debug('Command not found: %s, %s', key, self.commands) raise XMPPError('item-not-found') payload = [] for stanza in iq['command']['substanzas']: payload.append(stanza) if len(payload) == 1: payload = payload[0] interfaces = {item.plugin_attrib for item in payload} payload_classes = {item.__class__ for item in payload} initial_session = {'id': sessionid, 'from': iq['from'], 'to': iq['to'], 'node': node, 'payload': payload, 'interfaces': interfaces, 'payload_classes': payload_classes, 'notes': None, 'has_next': False, 'allow_complete': False, 'allow_prev': False, 'past': [], 'next': None, 'prev': None, 'cancel': None} session = handler(iq, initial_session) self._process_command_response(iq, session) def _handle_command_next(self, iq): """ Process a request for the next step in the workflow for a command with multiple steps. Arguments: iq -- The command continuation request. """ sessionid = iq['command']['sessionid'] session = self.sessions.get(sessionid) if session: handler = session['next'] interfaces = session['interfaces'] results = [] for stanza in iq['command']['substanzas']: if stanza.plugin_attrib in interfaces: results.append(stanza) if len(results) == 1: results = results[0] session = handler(results, session) self._process_command_response(iq, session) else: raise XMPPError('item-not-found') def _handle_command_prev(self, iq): """ Process a request for the prev step in the workflow for a command with multiple steps. Arguments: iq -- The command continuation request. """ sessionid = iq['command']['sessionid'] session = self.sessions.get(sessionid) if session: handler = session['prev'] interfaces = session['interfaces'] results = [] for stanza in iq['command']['substanzas']: if stanza.plugin_attrib in interfaces: results.append(stanza) if len(results) == 1: results = results[0] session = handler(results, session) self._process_command_response(iq, session) else: raise XMPPError('item-not-found') def _process_command_response(self, iq, session): """ Generate a command reply stanza based on the provided session data. Arguments: iq -- The command request stanza. session -- A dictionary of relevant session data. """ sessionid = session['id'] payload = session['payload'] if payload is None: payload = [] if not isinstance(payload, list): payload = [payload] interfaces = session.get('interfaces', set()) payload_classes = session.get('payload_classes', set()) interfaces.update({item.plugin_attrib for item in payload}) payload_classes.update({item.__class__ for item in payload}) session['interfaces'] = interfaces session['payload_classes'] = payload_classes self.sessions[sessionid] = session for item in payload: register_stanza_plugin(Command, item.__class__, iterable=True) iq = iq.reply() iq['command']['node'] = session['node'] iq['command']['sessionid'] = session['id'] if session['next'] is None: iq['command']['actions'] = [] iq['command']['status'] = 'completed' elif session['has_next']: actions = ['next'] if session['allow_complete']: actions.append('complete') if session['allow_prev']: actions.append('prev') iq['command']['actions'] = actions iq['command']['status'] = 'executing' else: iq['command']['actions'] = ['complete'] iq['command']['status'] = 'executing' iq['command']['notes'] = session['notes'] for item in payload: iq['command'].append(item) iq.send() def _handle_command_cancel(self, iq): """ Process a request to cancel a command's execution. Arguments: iq -- The command cancellation request. """ node = iq['command']['node'] sessionid = iq['command']['sessionid'] session = self.sessions.get(sessionid) if session: handler = session['cancel'] if handler: handler(iq, session) del self.sessions[sessionid] iq = iq.reply() iq['command']['node'] = node iq['command']['sessionid'] = sessionid iq['command']['status'] = 'canceled' iq['command']['notes'] = session['notes'] iq.send() else: raise XMPPError('item-not-found') def _handle_command_complete(self, iq): """ Process a request to finish the execution of command and terminate the workflow. All data related to the command session will be removed. Arguments: iq -- The command completion request. """ node = iq['command']['node'] sessionid = iq['command']['sessionid'] session = self.sessions.get(sessionid) if session: handler = session['next'] interfaces = session['interfaces'] results = [] for stanza in iq['command']['substanzas']: if stanza.plugin_attrib in interfaces: results.append(stanza) if len(results) == 1: results = results[0] if handler: handler(results, session) del self.sessions[sessionid] payload = session['payload'] if payload is None: payload = [] if not isinstance(payload, list): payload = [payload] for item in payload: register_stanza_plugin(Command, item.__class__, iterable=True) iq = iq.reply() iq['command']['node'] = node iq['command']['sessionid'] = sessionid iq['command']['actions'] = [] iq['command']['status'] = 'completed' iq['command']['notes'] = session['notes'] for item in payload: iq['command'].append(item) iq.send() else: raise XMPPError('item-not-found') # ================================================================= # Client side (command user) API def get_commands(self, jid, **kwargs): """ Return a list of commands provided by a given JID. Arguments: jid -- The JID to query for commands. local -- If true, then the query is for a JID/node combination handled by this Slixmpp instance and no stanzas need to be sent. Otherwise, a disco stanza must be sent to the remove JID to retrieve the items. ifrom -- Specifiy the sender's JID. timeout -- The time in seconds to block while waiting for a reply. If None, then wait indefinitely. callback -- Optional callback to execute when a reply is received instead of blocking and waiting for the reply. iterator -- If True, return a result set iterator using the XEP-0059 plugin, if the plugin is loaded. Otherwise the parameter is ignored. """ return self.xmpp['xep_0030'].get_items(jid=jid, node=Command.namespace, **kwargs) def send_command(self, jid, node, ifrom=None, action='execute', payload=None, sessionid=None, flow=False, **kwargs): """ Create and send a command stanza, without using the provided workflow management APIs. Arguments: jid -- The JID to send the command request or result. node -- The node for the command. ifrom -- Specify the sender's JID. action -- May be one of: execute, cancel, complete, or cancel. payload -- Either a list of payload items, or a single payload item such as a data form. sessionid -- The current session's ID value. flow -- If True, process the Iq result using the command workflow methods contained in the session instead of returning the response stanza itself. Defaults to False. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received if flow=False. """ iq = self.xmpp.Iq() iq['type'] = 'set' iq['to'] = jid iq['from'] = ifrom iq['command']['node'] = node iq['command']['action'] = action if sessionid is not None: iq['command']['sessionid'] = sessionid if payload is not None: if not isinstance(payload, list): payload = [payload] for item in payload: iq['command'].append(item) if not flow: return iq.send(**kwargs) else: iq.send(callback=self._handle_command_result) def start_command(self, jid, node, session, ifrom=None): """ Initiate executing a command provided by a remote agent. The provided session dictionary should contain: next -- A handler for processing the command result. error -- A handler for processing any error stanzas generated by the request. Arguments: jid -- The JID to send the command request. node -- The node for the desired command. session -- A dictionary of relevant session data. ifrom -- Optionally specify the sender's JID. """ session['jid'] = jid session['node'] = node session['timestamp'] = time.time() if 'payload' not in session: session['payload'] = None iq = self.xmpp.Iq() iq['type'] = 'set' iq['to'] = jid iq['from'] = ifrom session['from'] = ifrom iq['command']['node'] = node iq['command']['action'] = 'execute' if session['payload'] is not None: payload = session['payload'] if not isinstance(payload, list): payload = list(payload) for stanza in payload: iq['command'].append(stanza) sessionid = 'client:pending_' + iq['id'] session['id'] = sessionid self.sessions[sessionid] = session iq.send(callback=self._handle_command_result) def continue_command(self, session, direction='next'): """ Execute the next action of the command. Arguments: session -- All stored data relevant to the current command session. """ sessionid = 'client:' + session['id'] self.sessions[sessionid] = session self.send_command(session['jid'], session['node'], ifrom=session.get('from', None), action=direction, payload=session.get('payload', None), sessionid=session['id'], flow=True) def cancel_command(self, session): """ Cancel the execution of a command. Arguments: session -- All stored data relevant to the current command session. """ sessionid = 'client:' + session['id'] self.sessions[sessionid] = session self.send_command(session['jid'], session['node'], ifrom=session.get('from', None), action='cancel', payload=session.get('payload', None), sessionid=session['id'], flow=True) def complete_command(self, session): """ Finish the execution of a command workflow. Arguments: session -- All stored data relevant to the current command session. """ sessionid = 'client:' + session['id'] self.sessions[sessionid] = session self.send_command(session['jid'], session['node'], ifrom=session.get('from', None), action='complete', payload=session.get('payload', None), sessionid=session['id'], flow=True) def terminate_command(self, session): """ Delete a command's session after a command has completed or an error has occurred. Arguments: session -- All stored data relevant to the current command session. """ sessionid = 'client:' + session['id'] try: del self.sessions[sessionid] except Exception as e: log.error("Error deleting adhoc command session: %s" % e.message) def _handle_command_result(self, iq): """ Process the results of a command request. Will execute the 'next' handler stored in the session data, or the 'error' handler depending on the Iq's type. Arguments: iq -- The command response. """ sessionid = 'client:' + iq['command']['sessionid'] pending = False if sessionid not in self.sessions: pending = True pendingid = 'client:pending_' + iq['id'] if pendingid not in self.sessions: return sessionid = pendingid session = self.sessions[sessionid] sessionid = 'client:' + iq['command']['sessionid'] session['id'] = iq['command']['sessionid'] self.sessions[sessionid] = session if pending: del self.sessions[pendingid] handler_type = 'next' if iq['type'] == 'error': handler_type = 'error' handler = session.get(handler_type, None) if handler: handler(iq, session) elif iq['type'] == 'error': self.terminate_command(session) if iq['command']['status'] == 'completed': self.terminate_command(session) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0050/stanza.py000066400000000000000000000141131342457644200224710ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase, ET class Command(ElementBase): """ XMPP's Adhoc Commands provides a generic workflow mechanism for interacting with applications. The result is similar to menu selections and multi-step dialogs in normal desktop applications. Clients do not need to know in advance what commands are provided by any particular application or agent. While adhoc commands provide similar functionality to Jabber-RPC, adhoc commands are used primarily for human interaction. Also see Example command stanzas: Information! Stanza Interface: action -- The action to perform. actions -- The set of allowable next actions. node -- The node associated with the command. notes -- A list of tuples for informative notes. sessionid -- A unique identifier for a command session. status -- May be one of: canceled, completed, or executing. Attributes: actions -- A set of allowed action values. statuses -- A set of allowed status values. next_actions -- A set of allowed next action names. Methods: get_action -- Return the requested action. get_actions -- Return the allowable next actions. set_actions -- Set the allowable next actions. del_actions -- Remove the current set of next actions. get_notes -- Return a list of informative note data. set_notes -- Set informative notes. del_notes -- Remove any note data. add_note -- Add a single note. """ name = 'command' namespace = 'http://jabber.org/protocol/commands' plugin_attrib = 'command' interfaces = {'action', 'sessionid', 'node', 'status', 'actions', 'notes'} actions = {'cancel', 'complete', 'execute', 'next', 'prev'} statuses = {'canceled', 'completed', 'executing'} next_actions = {'prev', 'next', 'complete'} def get_action(self): """ Return the value of the action attribute. If the Iq stanza's type is "set" then use a default value of "execute". """ if self.parent()['type'] == 'set': return self._get_attr('action', default='execute') return self._get_attr('action') def set_actions(self, values): """ Assign the set of allowable next actions. Arguments: values -- A list containing any combination of: 'prev', 'next', and 'complete' """ self.del_actions() if values: self._set_sub_text('{%s}actions' % self.namespace, '', True) actions = self.xml.find('{%s}actions' % self.namespace) for val in values: if val in self.next_actions: action = ET.Element('{%s}%s' % (self.namespace, val)) actions.append(action) def get_actions(self): """ Return the set of allowable next actions. """ actions = set() actions_xml = self.xml.find('{%s}actions' % self.namespace) if actions_xml is not None: for action in self.next_actions: action_xml = actions_xml.find('{%s}%s' % (self.namespace, action)) if action_xml is not None: actions.add(action) return actions def del_actions(self): """ Remove all allowable next actions. """ self._del_sub('{%s}actions' % self.namespace) def get_notes(self): """ Return a list of note information. Example: [('info', 'Some informative data'), ('warning', 'Use caution'), ('error', 'The command ran, but had errors')] """ notes = [] notes_xml = self.xml.findall('{%s}note' % self.namespace) for note in notes_xml: notes.append((note.attrib.get('type', 'info'), note.text)) return notes def set_notes(self, notes): """ Add multiple notes to the command result. Each note is a tuple, with the first item being one of: 'info', 'warning', or 'error', and the second item being any human readable message. Example: [('info', 'Some informative data'), ('warning', 'Use caution'), ('error', 'The command ran, but had errors')] Arguments: notes -- A list of tuples of note information. """ self.del_notes() for note in notes: self.add_note(note[1], note[0]) def del_notes(self): """ Remove all notes associated with the command result. """ notes_xml = self.xml.findall('{%s}note' % self.namespace) for note in notes_xml: self.xml.remove(note) def add_note(self, msg='', ntype='info'): """ Add a single note annotation to the command. Arguments: msg -- A human readable message. ntype -- One of: 'info', 'warning', 'error' """ xml = ET.Element('{%s}note' % self.namespace) xml.attrib['type'] = ntype xml.text = msg self.xml.append(xml) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0054/000077500000000000000000000000001342457644200206235ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0054/__init__.py000066400000000000000000000005721342457644200227400ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0054.stanza import VCardTemp from slixmpp.plugins.xep_0054.vcard_temp import XEP_0054 register_plugin(XEP_0054) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0054/stanza.py000066400000000000000000000354761342457644200225140ustar00rootroot00000000000000import base64 import datetime as dt from slixmpp.util import bytes from slixmpp.xmlstream import ElementBase, ET, register_stanza_plugin, JID from slixmpp.plugins import xep_0082 class VCardTemp(ElementBase): name = 'vCard' namespace = 'vcard-temp' plugin_attrib = 'vcard_temp' interfaces = {'FN', 'VERSION'} sub_interfaces = {'FN', 'VERSION'} class Name(ElementBase): name = 'N' namespace = 'vcard-temp' plugin_attrib = name interfaces = {'FAMILY', 'GIVEN', 'MIDDLE', 'PREFIX', 'SUFFIX'} sub_interfaces = interfaces def _set_component(self, name, value): if isinstance(value, list): value = ','.join(value) if value is not None: self._set_sub_text(name, value, keep=True) else: self._del_sub(name) def _get_component(self, name): value = self._get_sub_text(name, '') if ',' in value: value = [v.strip() for v in value.split(',')] return value def set_family(self, value): self._set_component('FAMILY', value) def get_family(self): return self._get_component('FAMILY') def set_given(self, value): self._set_component('GIVEN', value) def get_given(self): return self._get_component('GIVEN') def set_middle(self, value): print(value) self._set_component('MIDDLE', value) def get_middle(self): return self._get_component('MIDDLE') def set_prefix(self, value): self._set_component('PREFIX', value) def get_prefix(self): return self._get_component('PREFIX') def set_suffix(self, value): self._set_component('SUFFIX', value) def get_suffix(self): return self._get_component('SUFFIX') class Nickname(ElementBase): name = 'NICKNAME' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'nicknames' interfaces = {name} is_extension = True def set_nickname(self, value): if not value: self.xml.text = '' return if not isinstance(value, list): value = [value] self.xml.text = ','.join(value) def get_nickname(self): if self.xml.text: return self.xml.text.split(',') class Email(ElementBase): name = 'EMAIL' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'emails' interfaces = {'HOME', 'WORK', 'INTERNET', 'PREF', 'X400', 'USERID'} sub_interfaces = {'USERID'} bool_interfaces = {'HOME', 'WORK', 'INTERNET', 'PREF', 'X400'} class Address(ElementBase): name = 'ADR' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'addresses' interfaces = {'HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INTL', 'PREF', 'POBOX', 'EXTADD', 'STREET', 'LOCALITY', 'REGION', 'PCODE', 'CTRY'} sub_interfaces = {'POBOX', 'EXTADD', 'STREET', 'LOCALITY', 'REGION', 'PCODE', 'CTRY'} bool_interfaces = {'HOME', 'WORK', 'DOM', 'INTL', 'PREF'} class Telephone(ElementBase): name = 'TEL' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'telephone_numbers' interfaces = {'HOME', 'WORK', 'VOICE', 'FAX', 'PAGER', 'MSG', 'CELL', 'VIDEO', 'BBS', 'MODEM', 'ISDN', 'PCS', 'PREF', 'NUMBER'} sub_interfaces = {'NUMBER'} bool_interfaces = {'HOME', 'WORK', 'VOICE', 'FAX', 'PAGER', 'MSG', 'CELL', 'VIDEO', 'BBS', 'MODEM', 'ISDN', 'PCS', 'PREF'} def setup(self, xml=None): super().setup(xml=xml) ## this blanks out numbers received from server ##self._set_sub_text('NUMBER', '', keep=True) def set_number(self, value): self._set_sub_text('NUMBER', value, keep=True) def del_number(self): self._set_sub_text('NUMBER', '', keep=True) class Label(ElementBase): name = 'LABEL' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'labels' interfaces = {'HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INT', 'PREF', 'lines'} bool_interfaces = {'HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INT', 'PREF'} def add_line(self, value): line = ET.Element('{%s}LINE' % self.namespace) line.text = value self.xml.append(line) def get_lines(self): lines = self.xml.find('{%s}LINE' % self.namespace) if lines is None: return [] return [line.text for line in lines] def set_lines(self, values): self.del_lines() for line in values: self.add_line(line) def del_lines(self): lines = self.xml.find('{%s}LINE' % self.namespace) if lines is None: return for line in lines: self.xml.remove(line) class Geo(ElementBase): name = 'GEO' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'geolocations' interfaces = {'LAT', 'LON'} sub_interfaces = interfaces class Org(ElementBase): name = 'ORG' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'organizations' interfaces = {'ORGNAME', 'ORGUNIT', 'orgunits'} sub_interfaces = {'ORGNAME', 'ORGUNIT'} def add_orgunit(self, value): orgunit = ET.Element('{%s}ORGUNIT' % self.namespace) orgunit.text = value self.xml.append(orgunit) def get_orgunits(self): orgunits = self.xml.find('{%s}ORGUNIT' % self.namespace) if orgunits is None: return [] return [orgunit.text for orgunit in orgunits] def set_orgunits(self, values): self.del_orgunits() for orgunit in values: self.add_orgunit(orgunit) def del_orgunits(self): orgunits = self.xml.find('{%s}ORGUNIT' % self.namespace) if orgunits is None: return for orgunit in orgunits: self.xml.remove(orgunit) class Photo(ElementBase): name = 'PHOTO' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'photos' interfaces = {'TYPE', 'EXTVAL'} sub_interfaces = interfaces class Logo(ElementBase): name = 'LOGO' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'logos' interfaces = {'TYPE', 'EXTVAL'} sub_interfaces = interfaces class Sound(ElementBase): name = 'SOUND' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'sounds' interfaces = {'PHONETC', 'EXTVAL'} sub_interfaces = interfaces class BinVal(ElementBase): name = 'BINVAL' namespace = 'vcard-temp' plugin_attrib = name interfaces = {'BINVAL'} is_extension = True def setup(self, xml=None): self.xml = ET.Element('') return True def set_binval(self, value): self.del_binval() parent = self.parent() if value: xml = ET.Element('{%s}BINVAL' % self.namespace) xml.text = bytes(base64.b64encode(value)).decode('utf-8') parent.append(xml) def get_binval(self): parent = self.parent() xml = parent.xml.find('{%s}BINVAL' % self.namespace) if xml is not None: return base64.b64decode(bytes(xml.text)) return b'' def del_binval(self): self.parent()._del_sub('{%s}BINVAL' % self.namespace) class Classification(ElementBase): name = 'CLASS' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'classifications' interfaces = {'PUBLIC', 'PRIVATE', 'CONFIDENTIAL'} bool_interfaces = interfaces class Categories(ElementBase): name = 'CATEGORIES' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'categories' interfaces = {name} is_extension = True def set_categories(self, values): self.del_categories() for keyword in values: item = ET.Element('{%s}KEYWORD' % self.namespace) item.text = keyword self.xml.append(item) def get_categories(self): items = self.xml.findall('{%s}KEYWORD' % self.namespace) if items is None: return [] keywords = [] for item in items: keywords.append(item.text) return keywords def del_categories(self): items = self.xml.findall('{%s}KEYWORD' % self.namespace) for item in items: self.xml.remove(item) class Birthday(ElementBase): name = 'BDAY' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'birthdays' interfaces = {name} is_extension = True def set_bday(self, value): if isinstance(value, dt.datetime): value = xep_0082.format_datetime(value) self.xml.text = value def get_bday(self): if not self.xml.text: return None try: return xep_0082.parse(self.xml.text) except ValueError: return self.xml.text class Rev(ElementBase): name = 'REV' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'revision_dates' interfaces = {name} is_extension = True def set_rev(self, value): if isinstance(value, dt.datetime): value = xep_0082.format_datetime(value) self.xml.text = value def get_rev(self): if not self.xml.text: return None try: return xep_0082.parse(self.xml.text) except ValueError: return self.xml.text class Title(ElementBase): name = 'TITLE' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'titles' interfaces = {name} is_extension = True def set_title(self, value): self.xml.text = value def get_title(self): return self.xml.text class Role(ElementBase): name = 'ROLE' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'roles' interfaces = {name} is_extension = True def set_role(self, value): self.xml.text = value def get_role(self): return self.xml.text class Note(ElementBase): name = 'NOTE' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'notes' interfaces = {name} is_extension = True def set_note(self, value): self.xml.text = value def get_note(self): return self.xml.text class Desc(ElementBase): name = 'DESC' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'descriptions' interfaces = {name} is_extension = True def set_desc(self, value): self.xml.text = value def get_desc(self): return self.xml.text class URL(ElementBase): name = 'URL' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'urls' interfaces = {name} is_extension = True def set_url(self, value): self.xml.text = value def get_url(self): return self.xml.text class UID(ElementBase): name = 'UID' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'uids' interfaces = {name} is_extension = True def set_uid(self, value): self.xml.text = value def get_uid(self): return self.xml.text class ProdID(ElementBase): name = 'PRODID' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'product_ids' interfaces = {name} is_extension = True def set_prodid(self, value): self.xml.text = value def get_prodid(self): return self.xml.text class Mailer(ElementBase): name = 'MAILER' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'mailers' interfaces = {name} is_extension = True def set_mailer(self, value): self.xml.text = value def get_mailer(self): return self.xml.text class SortString(ElementBase): name = 'SORT-STRING' namespace = 'vcard-temp' plugin_attrib = 'SORT_STRING' plugin_multi_attrib = 'sort_strings' interfaces = {name} is_extension = True def set_sort_string(self, value): self.xml.text = value def get_sort_string(self): return self.xml.text class Agent(ElementBase): name = 'AGENT' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'agents' interfaces = {'EXTVAL'} sub_interfaces = interfaces class JabberID(ElementBase): name = 'JABBERID' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'jids' interfaces = {name} is_extension = True def set_jabberid(self, value): self.xml.text = JID(value).bare def get_jabberid(self): return JID(self.xml.text) class TimeZone(ElementBase): name = 'TZ' namespace = 'vcard-temp' plugin_attrib = name plugin_multi_attrib = 'timezones' interfaces = {name} is_extension = True def set_tz(self, value): time = xep_0082.time(offset=value) if time[-1] == 'Z': self.xml.text = 'Z' else: self.xml.text = time[-6:] def get_tz(self): if not self.xml.text: return xep_0082.tzutc() try: time = xep_0082.parse('00:00:00%s' % self.xml.text) return time.tzinfo except ValueError: return self.xml.text register_stanza_plugin(VCardTemp, Name) register_stanza_plugin(VCardTemp, Address, iterable=True) register_stanza_plugin(VCardTemp, Agent, iterable=True) register_stanza_plugin(VCardTemp, Birthday, iterable=True) register_stanza_plugin(VCardTemp, Categories, iterable=True) register_stanza_plugin(VCardTemp, Desc, iterable=True) register_stanza_plugin(VCardTemp, Email, iterable=True) register_stanza_plugin(VCardTemp, Geo, iterable=True) register_stanza_plugin(VCardTemp, JabberID, iterable=True) register_stanza_plugin(VCardTemp, Label, iterable=True) register_stanza_plugin(VCardTemp, Logo, iterable=True) register_stanza_plugin(VCardTemp, Mailer, iterable=True) register_stanza_plugin(VCardTemp, Note, iterable=True) register_stanza_plugin(VCardTemp, Nickname, iterable=True) register_stanza_plugin(VCardTemp, Org, iterable=True) register_stanza_plugin(VCardTemp, Photo, iterable=True) register_stanza_plugin(VCardTemp, ProdID, iterable=True) register_stanza_plugin(VCardTemp, Rev, iterable=True) register_stanza_plugin(VCardTemp, Role, iterable=True) register_stanza_plugin(VCardTemp, SortString, iterable=True) register_stanza_plugin(VCardTemp, Sound, iterable=True) register_stanza_plugin(VCardTemp, Telephone, iterable=True) register_stanza_plugin(VCardTemp, Title, iterable=True) register_stanza_plugin(VCardTemp, TimeZone, iterable=True) register_stanza_plugin(VCardTemp, UID, iterable=True) register_stanza_plugin(VCardTemp, URL, iterable=True) register_stanza_plugin(Photo, BinVal) register_stanza_plugin(Logo, BinVal) register_stanza_plugin(Sound, BinVal) register_stanza_plugin(Agent, VCardTemp) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0054/vcard_temp.py000066400000000000000000000107641342457644200233310ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp import JID, Iq from slixmpp.exceptions import XMPPError from slixmpp.xmlstream import register_stanza_plugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.plugins import BasePlugin from slixmpp.plugins.xep_0054 import VCardTemp, stanza from slixmpp import future_wrapper log = logging.getLogger(__name__) class XEP_0054(BasePlugin): """ XEP-0054: vcard-temp """ name = 'xep_0054' description = 'XEP-0054: vcard-temp' dependencies = {'xep_0030', 'xep_0082'} stanza = stanza def plugin_init(self): """ Start the XEP-0054 plugin. """ register_stanza_plugin(Iq, VCardTemp) self.api.register(self._set_vcard, 'set_vcard', default=True) self.api.register(self._get_vcard, 'get_vcard', default=True) self.api.register(self._del_vcard, 'del_vcard', default=True) self._vcard_cache = {} self.xmpp.register_handler( Callback('VCardTemp', StanzaPath('iq/vcard_temp'), self._handle_get_vcard)) def plugin_end(self): self.xmpp.remove_handler('VCardTemp') self.xmpp['xep_0030'].del_feature(feature='vcard-temp') def session_bind(self, jid): self.xmpp['xep_0030'].add_feature('vcard-temp') def make_vcard(self): return VCardTemp() @future_wrapper def get_vcard(self, jid=None, ifrom=None, local=None, cached=False, callback=None, timeout=None, timeout_callback=None): if local is None: if jid is not None and not isinstance(jid, JID): jid = JID(jid) if self.xmpp.is_component: if jid.domain == self.xmpp.boundjid.domain: local = True else: if str(jid) == str(self.xmpp.boundjid): local = True jid = jid.full elif jid in (None, ''): local = True if local: vcard = self.api['get_vcard'](jid, None, ifrom) if not isinstance(vcard, Iq): iq = self.xmpp.Iq() if vcard is None: vcard = VCardTemp() iq.append(vcard) return iq return vcard if cached: vcard = self.api['get_vcard'](jid, None, ifrom) if vcard is not None: if not isinstance(vcard, Iq): iq = self.xmpp.Iq() iq.append(vcard) return iq return vcard iq = self.xmpp.Iq() iq['to'] = jid iq['from'] = ifrom iq['type'] = 'get' iq.enable('vcard_temp') return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) @future_wrapper def publish_vcard(self, vcard=None, jid=None, ifrom=None, callback=None, timeout=None, timeout_callback=None): self.api['set_vcard'](jid, None, ifrom, vcard) if self.xmpp.is_component: return iq = self.xmpp.Iq() iq['to'] = jid iq['from'] = ifrom iq['type'] = 'set' iq.append(vcard) return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) def _handle_get_vcard(self, iq): if iq['type'] == 'result': self.api['set_vcard'](jid=iq['from'], args=iq['vcard_temp']) return elif iq['type'] == 'get' and self.xmpp.is_component: vcard = self.api['get_vcard'](iq['from'].bare) if isinstance(vcard, Iq): vcard.send() else: iq = iq.reply() iq.append(vcard) iq.send() elif iq['type'] == 'set': raise XMPPError('service-unavailable') # ================================================================= def _set_vcard(self, jid, node, ifrom, vcard): self._vcard_cache[jid.bare] = vcard def _get_vcard(self, jid, node, ifrom, vcard): return self._vcard_cache.get(jid.bare, None) def _del_vcard(self, jid, node, ifrom, vcard): if jid.bare in self._vcard_cache: del self._vcard_cache[jid.bare] slixmpp-slix-1.4.2/slixmpp/plugins/xep_0059/000077500000000000000000000000001342457644200206305ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0059/__init__.py000066400000000000000000000006041342457644200227410ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0059.stanza import Set from slixmpp.plugins.xep_0059.rsm import ResultIterator, XEP_0059 register_plugin(XEP_0059) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0059/rsm.py000066400000000000000000000142701342457644200220070ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging import slixmpp from slixmpp import Iq from slixmpp.plugins import BasePlugin, register_plugin from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins.xep_0059 import stanza, Set from slixmpp.exceptions import XMPPError log = logging.getLogger(__name__) class ResultIterator: """ An iterator for Result Set Management """ def __init__(self, query, interface, results='substanzas', amount=10, start=None, reverse=False, recv_interface=None, pre_cb=None, post_cb=None): """ Arguments: query -- The template query interface -- The substanza of the query to send, for example disco_items recv_interface -- The substanza of the query to receive, for example disco_items results -- The query stanza's interface which provides a countable list of query results. amount -- The max amounts of items to request per iteration start -- From which item id to start reverse -- If True, page backwards through the results pre_cb -- Callback to run before sending the stanza post_cb -- Callback to run after receiving the reply Example: q = Iq() q['to'] = 'pubsub.example.com' q['disco_items']['node'] = 'blog' for i in ResultIterator(q, 'disco_items', '10'): print i['disco_items']['items'] """ self.query = query self.amount = amount self.start = start self.interface = interface if recv_interface: self.recv_interface = recv_interface else: self.recv_interface = interface self.pre_cb = pre_cb self.post_cb = post_cb self.results = results self.reverse = reverse self._stop = False def __aiter__(self): return self async def __anext__(self): return await self.next() async def next(self): """ Return the next page of results from a query. Note: If using backwards paging, then the next page of results will be the items before the current page of items. """ if self._stop: raise StopAsyncIteration self.query[self.interface]['rsm']['before'] = self.reverse self.query['id'] = self.query.stream.new_id() self.query[self.interface]['rsm']['max'] = str(self.amount) if self.start and self.reverse: self.query[self.interface]['rsm']['before'] = self.start elif self.start: self.query[self.interface]['rsm']['after'] = self.start try: if self.pre_cb: self.pre_cb(self.query) r = await self.query.send() if not r[self.recv_interface]['rsm']['first'] and \ not r[self.recv_interface]['rsm']['last']: raise StopAsyncIteration if r[self.recv_interface]['rsm']['count'] and \ r[self.recv_interface]['rsm']['first_index']: count = int(r[self.recv_interface]['rsm']['count']) first = int(r[self.recv_interface]['rsm']['first_index']) num_items = len(r[self.recv_interface][self.results]) if first + num_items == count: self._stop = True if self.reverse: self.start = r[self.recv_interface]['rsm']['first'] else: self.start = r[self.recv_interface]['rsm']['last'] if self.post_cb: self.post_cb(r) return r except XMPPError: raise StopAsyncIteration class XEP_0059(BasePlugin): """ XEP-0050: Result Set Management """ name = 'xep_0059' description = 'XEP-0059: Result Set Management' dependencies = {'xep_0030'} stanza = stanza def plugin_init(self): """ Start the XEP-0059 plugin. """ register_stanza_plugin(self.xmpp['xep_0030'].stanza.DiscoItems, self.stanza.Set) def plugin_end(self): self.xmpp['xep_0030'].del_feature(feature=Set.namespace) def session_bind(self, jid): self.xmpp['xep_0030'].add_feature(Set.namespace) def iterate(self, stanza, interface, results='substanzas', recv_interface=None, pre_cb=None, post_cb=None): """ Create a new result set iterator for a given stanza query. Arguments: stanza -- A stanza object to serve as a template for queries made each iteration. For example, a basic disco#items query. interface -- The name of the substanza to which the result set management stanza should be appended in the query stanza. For example, for disco#items queries the interface 'disco_items' should be used. recv_interface -- The name of the substanza from which the result set management stanza should be read in the result stanza. If unspecified, it will be set to the same value as the ``interface`` parameter. pre_cb -- Callback to run before sending each stanza e.g. setting the MAM queryid and starting a stanza collector. post_cb -- Callback to run after receiving each stanza e.g. stopping a MAM stanza collector in order to gather results. results -- The name of the interface containing the query results (typically just 'substanzas'). """ return ResultIterator(stanza, interface, results, recv_interface=recv_interface, pre_cb=pre_cb, post_cb=post_cb) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0059/stanza.py000066400000000000000000000074101342457644200225040ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase, ET from slixmpp.plugins.xep_0030.stanza.items import DiscoItems class Set(ElementBase): """ XEP-0059 (Result Set Management) can be used to manage the results of queries. For example, limiting the number of items per response or starting at certain positions. Example set stanzas: 2 conference.example.com pubsub.example.com Stanza Interface: first_index -- The index attribute of after -- The id defining from which item to start before -- The id defining from which item to start when browsing backwards max -- Max amount per response first -- Id for the first item in the response last -- Id for the last item in the response index -- Used to set an index to start from count -- The number of remote items available Methods: set_first_index -- Sets the index attribute for and creates the element if it doesn't exist get_first_index -- Returns the value of the index attribute for del_first_index -- Removes the index attribute for but keeps the element set_before -- Sets the value of , if the value is True then the element will be created without a value get_before -- Returns the value of , if it is empty it will return True """ namespace = 'http://jabber.org/protocol/rsm' name = 'set' plugin_attrib = 'rsm' sub_interfaces = {'first', 'after', 'before', 'count', 'index', 'last', 'max'} interfaces = {'first_index', 'first', 'after', 'before', 'count', 'index', 'last', 'max'} def set_first_index(self, val): fi = self.xml.find("{%s}first" % (self.namespace)) if fi is not None: if val: fi.attrib['index'] = val elif 'index' in fi.attrib: del fi.attrib['index'] elif val: fi = ET.Element("{%s}first" % (self.namespace)) fi.attrib['index'] = val self.xml.append(fi) def get_first_index(self): fi = self.xml.find("{%s}first" % (self.namespace)) if fi is not None: return fi.attrib.get('index', '') def del_first_index(self): fi = self.xml.find("{%s}first" % (self.namespace)) if fi is not None: del fi.attrib['index'] def set_before(self, val): b = self.xml.find("{%s}before" % (self.namespace)) if b is None and val is True: self._set_sub_text('{%s}before' % self.namespace, '', True) else: self._set_sub_text('{%s}before' % self.namespace, val) def get_before(self): b = self.xml.find("{%s}before" % (self.namespace)) if b is not None and not b.text: return True elif b is not None: return b.text else: return None slixmpp-slix-1.4.2/slixmpp/plugins/xep_0060/000077500000000000000000000000001342457644200206205ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0060/__init__.py000066400000000000000000000005541342457644200227350ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0060.pubsub import XEP_0060 from slixmpp.plugins.xep_0060 import stanza register_plugin(XEP_0060) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0060/pubsub.py000066400000000000000000000610171342457644200224770ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp.xmlstream import JID from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.plugins.base import BasePlugin from slixmpp.plugins.xep_0060 import stanza log = logging.getLogger(__name__) class XEP_0060(BasePlugin): """ XEP-0060 Publish Subscribe """ name = 'xep_0060' description = 'XEP-0060: Publish-Subscribe' dependencies = {'xep_0030', 'xep_0004', 'xep_0082', 'xep_0131'} stanza = stanza def plugin_init(self): self.node_event_map = {} self.xmpp.register_handler( Callback('Pubsub Event: Items', StanzaPath('message/pubsub_event/items'), self._handle_event_items)) self.xmpp.register_handler( Callback('Pubsub Event: Purge', StanzaPath('message/pubsub_event/purge'), self._handle_event_purge)) self.xmpp.register_handler( Callback('Pubsub Event: Delete', StanzaPath('message/pubsub_event/delete'), self._handle_event_delete)) self.xmpp.register_handler( Callback('Pubsub Event: Configuration', StanzaPath('message/pubsub_event/configuration'), self._handle_event_configuration)) self.xmpp.register_handler( Callback('Pubsub Event: Subscription', StanzaPath('message/pubsub_event/subscription'), self._handle_event_subscription)) self.xmpp['xep_0131'].supported_headers.add('SubID') def plugin_end(self): self.xmpp.remove_handler('Pubsub Event: Items') self.xmpp.remove_handler('Pubsub Event: Purge') self.xmpp.remove_handler('Pubsub Event: Delete') self.xmpp.remove_handler('Pubsub Event: Configuration') self.xmpp.remove_handler('Pubsub Event: Subscription') def _handle_event_items(self, msg): """Raise events for publish and retraction notifications.""" node = msg['pubsub_event']['items']['node'] multi = len(msg['pubsub_event']['items']) > 1 values = {} if multi: values = msg.values del values['pubsub_event'] for item in msg['pubsub_event']['items']: event_name = self.node_event_map.get(node, None) event_type = 'publish' if item.name == 'retract': event_type = 'retract' if multi: condensed = self.xmpp.Message() condensed.values = values condensed['pubsub_event']['items']['node'] = node condensed['pubsub_event']['items'].append(item) self.xmpp.event('pubsub_%s' % event_type, msg) if event_name: self.xmpp.event('%s_%s' % (event_name, event_type), condensed) else: self.xmpp.event('pubsub_%s' % event_type, msg) if event_name: self.xmpp.event('%s_%s' % (event_name, event_type), msg) def _handle_event_purge(self, msg): """Raise events for node purge notifications.""" node = msg['pubsub_event']['purge']['node'] event_name = self.node_event_map.get(node, None) self.xmpp.event('pubsub_purge', msg) if event_name: self.xmpp.event('%s_purge' % event_name, msg) def _handle_event_delete(self, msg): """Raise events for node deletion notifications.""" node = msg['pubsub_event']['delete']['node'] event_name = self.node_event_map.get(node, None) self.xmpp.event('pubsub_delete', msg) if event_name: self.xmpp.event('%s_delete' % event_name, msg) def _handle_event_configuration(self, msg): """Raise events for node configuration notifications.""" node = msg['pubsub_event']['configuration']['node'] event_name = self.node_event_map.get(node, None) self.xmpp.event('pubsub_config', msg) if event_name: self.xmpp.event('%s_config' % event_name, msg) def _handle_event_subscription(self, msg): """Raise events for node subscription notifications.""" node = msg['pubsub_event']['subscription']['node'] event_name = self.node_event_map.get(node, None) self.xmpp.event('pubsub_subscription', msg) if event_name: self.xmpp.event('%s_subscription' % event_name, msg) def map_node_event(self, node, event_name): """ Map node names to events. When a pubsub event is received for the given node, raise the provided event. For example:: map_node_event('http://jabber.org/protocol/tune', 'user_tune') will produce the events 'user_tune_publish' and 'user_tune_retract' when the respective notifications are received from the node 'http://jabber.org/protocol/tune', among other events. Arguments: node -- The node name to map to an event. event_name -- The name of the event to raise when a notification from the given node is received. """ self.node_event_map[node] = event_name def create_node(self, jid, node, config=None, ntype=None, ifrom=None, timeout_callback=None, callback=None, timeout=None): """ Create and configure a new pubsub node. A server MAY use a different name for the node than the one provided, so be sure to check the result stanza for a server assigned name. If no configuration form is provided, the node will be created using the server's default configuration. To get the default configuration use get_node_config(). Arguments: jid -- The JID of the pubsub service. node -- Optional name of the node to create. If no name is provided, the server MAY generate a node ID for you. The server can also assign a different name than the one you provide; check the result stanza to see if the server assigned a name. config -- Optional XEP-0004 data form of configuration settings. ntype -- The type of node to create. Servers typically default to using 'leaf' if no type is provided. ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') iq['pubsub']['create']['node'] = node if config is not None: form_type = 'http://jabber.org/protocol/pubsub#node_config' if 'FORM_TYPE' in config.get_fields(): config.field['FORM_TYPE']['value'] = form_type else: config.add_field(var='FORM_TYPE', ftype='hidden', value=form_type) if ntype: if 'pubsub#node_type' in config.get_fields(): config.field['pubsub#node_type']['value'] = ntype else: config.add_field(var='pubsub#node_type', value=ntype) iq['pubsub']['configure'].append(config) return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) def subscribe(self, jid, node, bare=True, subscribee=None, options=None, ifrom=None, timeout_callback=None, callback=None, timeout=None): """ Subscribe to updates from a pubsub node. The rules for determining the JID that is subscribing to the node are: 1. If subscribee is given, use that as provided. 2. If ifrom was given, use the bare or full version based on bare. 3. Otherwise, use self.xmpp.boundjid based on bare. Arguments: jid -- The pubsub service JID. node -- The node to subscribe to. bare -- Indicates if the subscribee is a bare or full JID. Defaults to True for a bare JID. subscribee -- The JID that is subscribing to the node. options -- ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') iq['pubsub']['subscribe']['node'] = node if subscribee is None: if ifrom: if bare: subscribee = JID(ifrom).bare else: subscribee = ifrom else: if bare: subscribee = self.xmpp.boundjid.bare else: subscribee = self.xmpp.boundjid iq['pubsub']['subscribe']['jid'] = subscribee if options is not None: iq['pubsub']['options'].append(options) return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) def unsubscribe(self, jid, node, subid=None, bare=True, subscribee=None, ifrom=None, timeout_callback=None, callback=None, timeout=None): """ Unubscribe from updates from a pubsub node. The rules for determining the JID that is unsubscribing from the node are: 1. If subscribee is given, use that as provided. 2. If ifrom was given, use the bare or full version based on bare. 3. Otherwise, use self.xmpp.boundjid based on bare. Arguments: jid -- The pubsub service JID. node -- The node to unsubscribe from. subid -- The specific subscription, if multiple subscriptions exist for this JID/node combination. bare -- Indicates if the subscribee is a bare or full JID. Defaults to True for a bare JID. subscribee -- The JID that is unsubscribing from the node. ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') iq['pubsub']['unsubscribe']['node'] = node if subscribee is None: if ifrom: if bare: subscribee = JID(ifrom).bare else: subscribee = ifrom else: if bare: subscribee = self.xmpp.boundjid.bare else: subscribee = self.xmpp.boundjid iq['pubsub']['unsubscribe']['jid'] = subscribee iq['pubsub']['unsubscribe']['subid'] = subid return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) def get_subscriptions(self, jid, node=None, ifrom=None, timeout_callback=None, callback=None, timeout=None): iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get') iq['pubsub']['subscriptions']['node'] = node return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) def get_affiliations(self, jid, node=None, ifrom=None, timeout_callback=None, callback=None, timeout=None): iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get') iq['pubsub']['affiliations']['node'] = node return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) def get_subscription_options(self, jid, node=None, user_jid=None, ifrom=None, timeout_callback=None, callback=None, timeout=None): iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get') if user_jid is None: iq['pubsub']['default']['node'] = node else: iq['pubsub']['options']['node'] = node iq['pubsub']['options']['jid'] = user_jid return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) def set_subscription_options(self, jid, node, user_jid, options, ifrom=None, timeout_callback=None, callback=None, timeout=None): iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get') iq['pubsub']['options']['node'] = node iq['pubsub']['options']['jid'] = user_jid iq['pubsub']['options'].append(options) return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) def get_node_config(self, jid, node=None, ifrom=None, timeout_callback=None, callback=None, timeout=None): """ Retrieve the configuration for a node, or the pubsub service's default configuration for new nodes. Arguments: jid -- The JID of the pubsub service. node -- The node to retrieve the configuration for. If None, the default configuration for new nodes will be requested. Defaults to None. ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get') if node is None: iq['pubsub_owner']['default'] else: iq['pubsub_owner']['configure']['node'] = node return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) def get_node_subscriptions(self, jid, node, ifrom=None, timeout_callback=None, callback=None, timeout=None): """ Retrieve the subscriptions associated with a given node. Arguments: jid -- The JID of the pubsub service. node -- The node to retrieve subscriptions from. ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get') iq['pubsub_owner']['subscriptions']['node'] = node return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) def get_node_affiliations(self, jid, node, ifrom=None, timeout_callback=None, callback=None, timeout=None): """ Retrieve the affiliations associated with a given node. Arguments: jid -- The JID of the pubsub service. node -- The node to retrieve affiliations from. ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get') iq['pubsub_owner']['affiliations']['node'] = node return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) def delete_node(self, jid, node, ifrom=None, timeout_callback=None, callback=None, timeout=None): """ Delete a a pubsub node. Arguments: jid -- The JID of the pubsub service. node -- The node to delete. ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') iq['pubsub_owner']['delete']['node'] = node return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) def set_node_config(self, jid, node, config, ifrom=None, timeout_callback=None, callback=None, timeout=None): iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') iq['pubsub_owner']['configure']['node'] = node iq['pubsub_owner']['configure'].append(config) return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) def publish(self, jid, node, id=None, payload=None, options=None, ifrom=None, timeout_callback=None, callback=None, timeout=None): """ Add a new item to a node, or edit an existing item. For services that support it, you can use the publish command as an event signal by not including an ID or payload. When including a payload and you do not provide an ID then the service will generally create an ID for you. Publish options may be specified, and how those options are processed is left to the service, such as treating the options as preconditions that the node's settings must match. Arguments: jid -- The JID of the pubsub service. node -- The node to publish the item to. id -- Optionally specify the ID of the item. payload -- The item content to publish. options -- A form of publish options. ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') iq['pubsub']['publish']['node'] = node if id is not None: iq['pubsub']['publish']['item']['id'] = id if payload is not None: iq['pubsub']['publish']['item']['payload'] = payload iq['pubsub']['publish_options'] = options return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) def retract(self, jid, node, id, notify=None, ifrom=None, timeout_callback=None, callback=None, timeout=None): """ Delete a single item from a node. """ iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') iq['pubsub']['retract']['node'] = node iq['pubsub']['retract']['notify'] = notify iq['pubsub']['retract']['item']['id'] = id return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) def purge(self, jid, node, ifrom=None, timeout_callback=None, callback=None, timeout=None): """ Remove all items from a node. """ iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') iq['pubsub_owner']['purge']['node'] = node return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) def get_nodes(self, *args, **kwargs): """ Discover the nodes provided by a Pubsub service, using disco. """ return self.xmpp['xep_0030'].get_items(*args, **kwargs) def get_item(self, jid, node, item_id, ifrom=None, timeout_callback=None, callback=None, timeout=None): """ Retrieve the content of an individual item. """ iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get') item = stanza.Item() item['id'] = item_id iq['pubsub']['items']['node'] = node iq['pubsub']['items'].append(item) return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) def get_items(self, jid, node, item_ids=None, max_items=None, iterator=False, ifrom=None, timeout_callback=None, callback=None, timeout=None): """ Request the contents of a node's items. The desired items can be specified, or a query for the last few published items can be used. Pubsub services may use result set management for nodes with many items, so an iterator can be returned if needed. """ iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get') iq['pubsub']['items']['node'] = node iq['pubsub']['items']['max_items'] = max_items if item_ids is not None: for item_id in item_ids: item = stanza.Item() item['id'] = item_id iq['pubsub']['items'].append(item) if iterator: return self.xmpp['xep_0059'].iterate(iq, 'pubsub') else: return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) def get_item_ids(self, jid, node, ifrom=None, timeout_callback=None, callback=None, timeout=None, iterator=False): """ Retrieve the ItemIDs hosted by a given node, using disco. """ self.xmpp['xep_0030'].get_items(jid, node, ifrom=ifrom, callback=callback, timeout=timeout, iterator=iterator, timeout_callback=timeout_callback) def modify_affiliations(self, jid, node, affiliations=None, ifrom=None, timeout_callback=None, callback=None, timeout=None): iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') iq['pubsub_owner']['affiliations']['node'] = node if affiliations is None: affiliations = [] for jid, affiliation in affiliations: aff = stanza.OwnerAffiliation() aff['jid'] = jid aff['affiliation'] = affiliation iq['pubsub_owner']['affiliations'].append(aff) return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) def modify_subscriptions(self, jid, node, subscriptions=None, ifrom=None, timeout_callback=None, callback=None, timeout=None): iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') iq['pubsub_owner']['subscriptions']['node'] = node if subscriptions is None: subscriptions = [] for jid, subscription in subscriptions: sub = stanza.OwnerSubscription() sub['jid'] = jid sub['subscription'] = subscription iq['pubsub_owner']['subscriptions'].append(sub) return iq.send(callback=callback, timeout=timeout, timeout_callback=timeout_callback) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0060/stanza/000077500000000000000000000000001342457644200221205ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0060/stanza/__init__.py000066400000000000000000000006231342457644200242320ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.xep_0060.stanza.pubsub import * from slixmpp.plugins.xep_0060.stanza.pubsub_owner import * from slixmpp.plugins.xep_0060.stanza.pubsub_event import * from slixmpp.plugins.xep_0060.stanza.pubsub_errors import * slixmpp-slix-1.4.2/slixmpp/plugins/xep_0060/stanza/base.py000066400000000000000000000014231342457644200234040ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ET class OptionalSetting(object): interfaces = {'required'} def set_required(self, value): if value in (True, 'true', 'True', '1'): self.xml.append(ET.Element("{%s}required" % self.namespace)) elif self['required']: self.del_required() def get_required(self): required = self.xml.find("{%s}required" % self.namespace) return required is not None def del_required(self): required = self.xml.find("{%s}required" % self.namespace) if required is not None: self.xml.remove(required) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0060/stanza/pubsub.py000066400000000000000000000166101342457644200237760ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp import Iq, Message from slixmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID from slixmpp.plugins import xep_0004 from slixmpp.plugins.xep_0060.stanza.base import OptionalSetting class Pubsub(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'pubsub' plugin_attrib = name interfaces = set(tuple()) class Affiliations(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'affiliations' plugin_attrib = name interfaces = {'node'} class Affiliation(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'affiliation' plugin_attrib = name interfaces = {'node', 'affiliation', 'jid'} def set_jid(self, value): self._set_attr('jid', str(value)) def get_jid(self): return JID(self._get_attr('jid')) class Subscription(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'subscription' plugin_attrib = name interfaces = {'jid', 'node', 'subscription', 'subid'} def set_jid(self, value): self._set_attr('jid', str(value)) def get_jid(self): return JID(self._get_attr('jid')) class Subscriptions(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'subscriptions' plugin_attrib = name interfaces = {'node'} class SubscribeOptions(ElementBase, OptionalSetting): namespace = 'http://jabber.org/protocol/pubsub' name = 'subscribe-options' plugin_attrib = 'suboptions' interfaces = {'required'} class Item(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'item' plugin_attrib = name interfaces = {'id', 'payload'} def set_payload(self, value): del self['payload'] if isinstance(value, ElementBase): if value.tag_name() in self.plugin_tag_map: self.init_plugin(value.plugin_attrib, existing_xml=value.xml) self.xml.append(value.xml) else: self.xml.append(value) def get_payload(self): children = list(self.xml) if len(children) > 0: return children[0] def del_payload(self): for child in self.xml: self.xml.remove(child) class Items(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'items' plugin_attrib = name interfaces = {'node', 'max_items'} def set_max_items(self, value): self._set_attr('max_items', str(value)) class Create(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'create' plugin_attrib = name interfaces = {'node'} class Default(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'default' plugin_attrib = name interfaces = {'node', 'type'} def get_type(self): t = self._get_attr('type') if not t: return 'leaf' return t class Publish(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'publish' plugin_attrib = name interfaces = {'node'} class Retract(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'retract' plugin_attrib = name interfaces = {'node', 'notify'} def get_notify(self): notify = self._get_attr('notify') if notify in ('0', 'false'): return False elif notify in ('1', 'true'): return True return None def set_notify(self, value): del self['notify'] if value is None: return elif value in (True, '1', 'true', 'True'): self._set_attr('notify', 'true') else: self._set_attr('notify', 'false') class Unsubscribe(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'unsubscribe' plugin_attrib = name interfaces = {'node', 'jid', 'subid'} def set_jid(self, value): self._set_attr('jid', str(value)) def get_jid(self): return JID(self._get_attr('jid')) class Subscribe(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'subscribe' plugin_attrib = name interfaces = {'node', 'jid'} def set_jid(self, value): self._set_attr('jid', str(value)) def get_jid(self): return JID(self._get_attr('jid')) class Configure(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'configure' plugin_attrib = name interfaces = {'node', 'type'} def getType(self): t = self._get_attr('type') if not t: t == 'leaf' return t class Options(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'options' plugin_attrib = name interfaces = {'jid', 'node', 'options'} def __init__(self, *args, **kwargs): ElementBase.__init__(self, *args, **kwargs) def get_options(self): config = self.xml.find('{jabber:x:data}x') form = xep_0004.Form(xml=config) return form def set_options(self, value): if isinstance(value, ElementBase): self.xml.append(value.xml) else: self.xml.append(value) return self def del_options(self): config = self.xml.find('{jabber:x:data}x') self.xml.remove(config) def set_jid(self, value): self._set_attr('jid', str(value)) def get_jid(self): return JID(self._get_attr('jid')) class PublishOptions(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'publish-options' plugin_attrib = 'publish_options' interfaces = {'publish_options'} is_extension = True def get_publish_options(self): config = self.xml.find('{jabber:x:data}x') if config is None: return None form = xep_0004.Form(xml=config) return form def set_publish_options(self, value): if value is None: self.del_publish_options() else: if isinstance(value, ElementBase): self.xml.append(value.xml) else: self.xml.append(value) return self def del_publish_options(self): config = self.xml.find('{jabber:x:data}x') if config is not None: self.xml.remove(config) self.parent().xml.remove(self.xml) register_stanza_plugin(Iq, Pubsub) register_stanza_plugin(Pubsub, Affiliations) register_stanza_plugin(Pubsub, Configure) register_stanza_plugin(Pubsub, Create) register_stanza_plugin(Pubsub, Default) register_stanza_plugin(Pubsub, Items) register_stanza_plugin(Pubsub, Options) register_stanza_plugin(Pubsub, Publish) register_stanza_plugin(Pubsub, PublishOptions) register_stanza_plugin(Pubsub, Retract) register_stanza_plugin(Pubsub, Subscribe) register_stanza_plugin(Pubsub, Subscription) register_stanza_plugin(Pubsub, Subscriptions) register_stanza_plugin(Pubsub, Unsubscribe) register_stanza_plugin(Affiliations, Affiliation, iterable=True) register_stanza_plugin(Configure, xep_0004.Form) register_stanza_plugin(Items, Item, iterable=True) register_stanza_plugin(Publish, Item, iterable=True) register_stanza_plugin(Retract, Item) register_stanza_plugin(Subscribe, Options) register_stanza_plugin(Subscription, SubscribeOptions) register_stanza_plugin(Subscriptions, Subscription, iterable=True) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0060/stanza/pubsub_errors.py000066400000000000000000000057521342457644200253770ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.stanza import Error from slixmpp.xmlstream import ElementBase, ET, register_stanza_plugin class PubsubErrorCondition(ElementBase): plugin_attrib = 'pubsub' interfaces = {'condition', 'unsupported'} plugin_attrib_map = {} plugin_tag_map = {} conditions = {'closed-node', 'configuration-required', 'invalid-jid', 'invalid-options', 'invalid-payload', 'invalid-subid', 'item-forbidden', 'item-required', 'jid-required', 'max-items-exceeded', 'max-nodes-exceeded', 'nodeid-required', 'not-in-roster-group', 'not-subscribed', 'payload-too-big', 'payload-required', 'pending-subscription', 'presence-subscription-required', 'subid-required', 'too-many-subscriptions', 'unsupported'} condition_ns = 'http://jabber.org/protocol/pubsub#errors' def setup(self, xml): """Don't create XML for the plugin.""" self.xml = ET.Element('') def get_condition(self): """Return the condition element's name.""" for child in self.parent().xml: if "{%s}" % self.condition_ns in child.tag: cond = child.tag.split('}', 1)[-1] if cond in self.conditions: return cond return '' 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'] cond = ET.Element("{%s}%s" % (self.condition_ns, value)) self.parent().xml.append(cond) return self def del_condition(self): """Remove the condition element.""" for child in self.parent().xml: if "{%s}" % self.condition_ns in child.tag: tag = child.tag.split('}', 1)[-1] if tag in self.conditions: self.parent().xml.remove(child) return self def get_unsupported(self): """Return the name of an unsupported feature""" xml = self.parent().xml.find('{%s}unsupported' % self.condition_ns) if xml is not None: return xml.attrib.get('feature', '') return '' def set_unsupported(self, value): """Mark a feature as unsupported""" self.del_unsupported() xml = ET.Element('{%s}unsupported' % self.condition_ns) xml.attrib['feature'] = value self.parent().xml.append(xml) def del_unsupported(self): """Delete an unsupported feature condition.""" xml = self.parent().xml.find('{%s}unsupported' % self.condition_ns) if xml is not None: self.parent().xml.remove(xml) register_stanza_plugin(Error, PubsubErrorCondition) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0060/stanza/pubsub_event.py000066400000000000000000000102011342457644200251650ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import datetime as dt from slixmpp import Message from slixmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID from slixmpp.plugins.xep_0004 import Form from slixmpp.plugins import xep_0082 class Event(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' name = 'event' plugin_attrib = 'pubsub_event' interfaces = set() class EventItem(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' name = 'item' plugin_attrib = name interfaces = {'id', 'payload', 'node', 'publisher'} def set_payload(self, value): self.xml.append(value) def get_payload(self): children = list(self.xml) if len(children) > 0: return children[0] def del_payload(self): for child in self.xml: self.xml.remove(child) class EventRetract(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' name = 'retract' plugin_attrib = name interfaces = {'id'} class EventItems(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' name = 'items' plugin_attrib = name interfaces = {'node'} class EventCollection(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' name = 'collection' plugin_attrib = name interfaces = {'node'} class EventAssociate(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' name = 'associate' plugin_attrib = name interfaces = {'node'} class EventDisassociate(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' name = 'disassociate' plugin_attrib = name interfaces = {'node'} class EventConfiguration(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' name = 'configuration' plugin_attrib = name interfaces = {'node'} class EventPurge(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' name = 'purge' plugin_attrib = name interfaces = {'node'} class EventDelete(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' name = 'delete' plugin_attrib = name interfaces = {'node', 'redirect'} def set_redirect(self, uri): del self['redirect'] redirect = ET.Element('{%s}redirect' % self.namespace) redirect.attrib['uri'] = uri self.xml.append(redirect) def get_redirect(self): redirect = self.xml.find('{%s}redirect' % self.namespace) if redirect is not None: return redirect.attrib.get('uri', '') return '' def del_redirect(self): redirect = self.xml.find('{%s}redirect' % self.namespace) if redirect is not None: self.xml.remove(redirect) class EventSubscription(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' name = 'subscription' plugin_attrib = name interfaces = {'node', 'expiry', 'jid', 'subid', 'subscription'} def get_expiry(self): expiry = self._get_attr('expiry') if expiry.lower() == 'presence': return expiry return xep_0082.parse(expiry) def set_expiry(self, value): if isinstance(value, dt.datetime): value = xep_0082.format_datetime(value) self._set_attr('expiry', value) def set_jid(self, value): self._set_attr('jid', str(value)) def get_jid(self): return JID(self._get_attr('jid')) register_stanza_plugin(Message, Event) register_stanza_plugin(Event, EventCollection) register_stanza_plugin(Event, EventConfiguration) register_stanza_plugin(Event, EventPurge) register_stanza_plugin(Event, EventDelete) register_stanza_plugin(Event, EventItems) register_stanza_plugin(Event, EventSubscription) register_stanza_plugin(EventCollection, EventAssociate) register_stanza_plugin(EventCollection, EventDisassociate) register_stanza_plugin(EventConfiguration, Form) register_stanza_plugin(EventItems, EventItem, iterable=True) register_stanza_plugin(EventItems, EventRetract, iterable=True) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0060/stanza/pubsub_owner.py000066400000000000000000000075401342457644200252120ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp import Iq from slixmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID from slixmpp.plugins.xep_0004 import Form from slixmpp.plugins.xep_0060.stanza.base import OptionalSetting from slixmpp.plugins.xep_0060.stanza.pubsub import Affiliations, Affiliation from slixmpp.plugins.xep_0060.stanza.pubsub import Configure, Subscriptions class PubsubOwner(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#owner' name = 'pubsub' plugin_attrib = 'pubsub_owner' interfaces = set(tuple()) class DefaultConfig(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#owner' name = 'default' plugin_attrib = name interfaces = {'node', 'config'} def __init__(self, *args, **kwargs): ElementBase.__init__(self, *args, **kwargs) def get_config(self): return self['form'] def set_config(self, value): del self['from'] self.append(value) return self class OwnerAffiliations(Affiliations): namespace = 'http://jabber.org/protocol/pubsub#owner' interfaces = {'node'} def append(self, affiliation): if not isinstance(affiliation, OwnerAffiliation): raise TypeError self.xml.append(affiliation.xml) class OwnerAffiliation(Affiliation): namespace = 'http://jabber.org/protocol/pubsub#owner' interfaces = {'affiliation', 'jid'} class OwnerConfigure(Configure): namespace = 'http://jabber.org/protocol/pubsub#owner' name = 'configure' plugin_attrib = name interfaces = {'node'} class OwnerDefault(OwnerConfigure): namespace = 'http://jabber.org/protocol/pubsub#owner' interfaces = {'node'} class OwnerDelete(ElementBase, OptionalSetting): namespace = 'http://jabber.org/protocol/pubsub#owner' name = 'delete' plugin_attrib = name interfaces = {'node'} class OwnerPurge(ElementBase, OptionalSetting): namespace = 'http://jabber.org/protocol/pubsub#owner' name = 'purge' plugin_attrib = name interfaces = {'node'} class OwnerRedirect(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#owner' name = 'redirect' plugin_attrib = name interfaces = {'node', 'jid'} def set_jid(self, value): self._set_attr('jid', str(value)) def get_jid(self): return JID(self._get_attr('jid')) class OwnerSubscriptions(Subscriptions): name = 'subscriptions' namespace = 'http://jabber.org/protocol/pubsub#owner' plugin_attrib = name interfaces = {'node'} def append(self, subscription): if not isinstance(subscription, OwnerSubscription): raise TypeError self.xml.append(subscription.xml) class OwnerSubscription(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#owner' name = 'subscription' plugin_attrib = name interfaces = {'jid', 'subscription'} def set_jid(self, value): self._set_attr('jid', str(value)) def get_jid(self): return JID(self._get_attr('jid')) register_stanza_plugin(Iq, PubsubOwner) register_stanza_plugin(PubsubOwner, DefaultConfig) register_stanza_plugin(PubsubOwner, OwnerAffiliations) register_stanza_plugin(PubsubOwner, OwnerConfigure) register_stanza_plugin(PubsubOwner, OwnerDefault) register_stanza_plugin(PubsubOwner, OwnerDelete) register_stanza_plugin(PubsubOwner, OwnerPurge) register_stanza_plugin(PubsubOwner, OwnerSubscriptions) register_stanza_plugin(DefaultConfig, Form) register_stanza_plugin(OwnerAffiliations, OwnerAffiliation, iterable=True) register_stanza_plugin(OwnerConfigure, Form) register_stanza_plugin(OwnerDefault, Form) register_stanza_plugin(OwnerDelete, OwnerRedirect) register_stanza_plugin(OwnerSubscriptions, OwnerSubscription, iterable=True) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0065/000077500000000000000000000000001342457644200206255ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0065/__init__.py000066400000000000000000000003601342457644200227350ustar00rootroot00000000000000from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0065.socks5 import Socks5Protocol from slixmpp.plugins.xep_0065.stanza import Socks5 from slixmpp.plugins.xep_0065.proxy import XEP_0065 register_plugin(XEP_0065) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0065/proxy.py000066400000000000000000000244041342457644200223640ustar00rootroot00000000000000import asyncio import logging import socket from hashlib import sha1 from uuid import uuid4 from slixmpp.stanza import Iq from slixmpp.exceptions import XMPPError from slixmpp.xmlstream import register_stanza_plugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.plugins.base import BasePlugin from slixmpp.plugins.xep_0065 import stanza, Socks5, Socks5Protocol log = logging.getLogger(__name__) class XEP_0065(BasePlugin): name = 'xep_0065' description = "XEP-0065: SOCKS5 Bytestreams" dependencies = {'xep_0030'} default_config = { 'auto_accept': False } def plugin_init(self): register_stanza_plugin(Iq, Socks5) self._proxies = {} self._sessions = {} self._preauthed_sids = {} self.xmpp.register_handler( Callback('Socks5 Bytestreams', StanzaPath('iq@type=set/socks/streamhost'), self._handle_streamhost)) self.api.register(self._authorized, 'authorized', default=True) self.api.register(self._authorized_sid, 'authorized_sid', default=True) self.api.register(self._preauthorize_sid, 'preauthorize_sid', default=True) def session_bind(self, jid): self.xmpp['xep_0030'].add_feature(Socks5.namespace) def plugin_end(self): self.xmpp.remove_handler('Socks5 Bytestreams') self.xmpp.remove_handler('Socks5 Streamhost Used') self.xmpp['xep_0030'].del_feature(feature=Socks5.namespace) def get_socket(self, sid): """Returns the socket associated to the SID.""" return self._sessions.get(sid, None) async def handshake(self, to, ifrom=None, sid=None, timeout=None): """ Starts the handshake to establish the socks5 bytestreams connection. """ if not self._proxies: self._proxies = await self.discover_proxies() if sid is None: sid = uuid4().hex used = await self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout) proxy = used['socks']['streamhost_used']['jid'] if proxy not in self._proxies: log.warning('Received unknown SOCKS5 proxy: %s', proxy) return try: self._sessions[sid] = (await self._connect_proxy( self._get_dest_sha1(sid, self.xmpp.boundjid, to), self._proxies[proxy][0], self._proxies[proxy][1]))[1] except socket.error: return None addr, port = await self._sessions[sid].connected # Request that the proxy activate the session with the target. await self.activate(proxy, sid, to, timeout=timeout) sock = self.get_socket(sid) self.xmpp.event('stream:%s:%s' % (sid, to), sock) return sock def request_stream(self, to, sid=None, ifrom=None, timeout=None, callback=None): if sid is None: sid = uuid4().hex # Requester initiates S5B negotiation with Target by sending # IQ-set that includes the JabberID and network address of # StreamHost as well as the StreamID (SID) of the proposed # bytestream. iq = self.xmpp.Iq() iq['to'] = to iq['from'] = ifrom iq['type'] = 'set' iq['socks']['sid'] = sid for proxy, (host, port) in self._proxies.items(): iq['socks'].add_streamhost(proxy, host, port) return iq.send(timeout=timeout, callback=callback) async def discover_proxies(self, jid=None, ifrom=None, timeout=None): """Auto-discover the JIDs of SOCKS5 proxies on an XMPP server.""" if jid is None: if self.xmpp.is_component: jid = self.xmpp.server else: jid = self.xmpp.boundjid.server discovered = set() disco_items = await self.xmpp['xep_0030'].get_items(jid, timeout=timeout) disco_items = {item[0] for item in disco_items['disco_items']['items']} disco_info_futures = {} for item in disco_items: disco_info_futures[item] = self.xmpp['xep_0030'].get_info(item, timeout=timeout) for item in disco_items: try: disco_info = await disco_info_futures[item] except XMPPError: continue else: # Verify that the identity is a bytestream proxy. identities = disco_info['disco_info']['identities'] for identity in identities: if identity[0] == 'proxy' and identity[1] == 'bytestreams': discovered.add(disco_info['from']) for jid in discovered: try: addr = await self.get_network_address(jid, ifrom=ifrom, timeout=timeout) self._proxies[jid] = (addr['socks']['streamhost']['host'], addr['socks']['streamhost']['port']) except XMPPError: continue return self._proxies def get_network_address(self, proxy, ifrom=None, timeout=None, callback=None): """Get the network information of a proxy.""" iq = self.xmpp.Iq(sto=proxy, stype='get', sfrom=ifrom) iq.enable('socks') return iq.send(timeout=timeout, callback=callback) def _get_dest_sha1(self, sid, requester, target): # The hostname MUST be SHA1(SID + Requester JID + Target JID) # where the output is hexadecimal-encoded (not binary). digest = sha1() digest.update(sid.encode('utf8')) digest.update(str(requester).encode('utf8')) digest.update(str(target).encode('utf8')) return digest.hexdigest() def _handle_streamhost(self, iq): """Handle incoming SOCKS5 session request.""" sid = iq['socks']['sid'] if not sid: raise XMPPError(etype='modify', condition='bad-request') if not self._accept_stream(iq): raise XMPPError(etype='modify', condition='not-acceptable') streamhosts = iq['socks']['streamhosts'] requester = iq['from'] target = iq['to'] dest = self._get_dest_sha1(sid, requester, target) proxy_futures = [] for streamhost in streamhosts: proxy_futures.append(self._connect_proxy( dest, streamhost['host'], streamhost['port'])) async def gather(futures, iq, streamhosts): proxies = await asyncio.gather(*futures, return_exceptions=True) for streamhost, proxy in zip(streamhosts, proxies): if isinstance(proxy, ValueError): continue elif isinstance(proxy, socket.error): log.error('Socket error while connecting to the proxy.') continue proxy = proxy[1] # TODO: what if the future never happens? try: addr, port = await proxy.connected except socket.error: log.exception('Socket error while connecting to the proxy.') continue # TODO: make a better choice than just the first working one. used_streamhost = streamhost['jid'] conn = proxy break else: raise XMPPError(etype='cancel', condition='item-not-found') # TODO: close properly the connection to the other proxies. iq = iq.reply() self._sessions[sid] = conn iq['socks']['sid'] = sid iq['socks']['streamhost_used']['jid'] = used_streamhost iq.send() self.xmpp.event('socks5_stream', conn) self.xmpp.event('stream:%s:%s' % (sid, requester), conn) asyncio.ensure_future(gather(proxy_futures, iq, streamhosts)) def activate(self, proxy, sid, target, ifrom=None, timeout=None, callback=None): """Activate the socks5 session that has been negotiated.""" iq = self.xmpp.Iq(sto=proxy, stype='set', sfrom=ifrom) iq['socks']['sid'] = sid iq['socks']['activate'] = target return iq.send(timeout=timeout, callback=callback) def deactivate(self, sid): """Closes the proxy socket associated with this SID.""" sock = self._sessions.get(sid) if sock: try: # sock.close() will also delete sid from self._sessions (see _connect_proxy) sock.close() except socket.error: pass # Though this should not be necessary remove the closed session anyway if sid in self._sessions: log.warn(('SOCKS5 session with sid = "%s" was not ' + 'removed from _sessions by sock.close()') % sid) del self._sessions[sid] def close(self): """Closes all proxy sockets.""" for sid, sock in self._sessions.items(): sock.close() self._sessions = {} def _connect_proxy(self, dest, proxy, proxy_port): """ Returns a future to a connection between the client and the server-side Socks5 proxy. dest : The SHA-1 of (SID + Requester JID + Target JID), in hex. host : The hostname or the IP of the proxy. port : The port of the proxy. or """ factory = lambda: Socks5Protocol(dest, 0, self.xmpp.event) return self.xmpp.loop.create_connection(factory, proxy, proxy_port) def _accept_stream(self, iq): receiver = iq['to'] sender = iq['from'] sid = iq['socks']['sid'] if self.api['authorized_sid'](receiver, sid, sender, iq): return True return self.api['authorized'](receiver, sid, sender, iq) def _authorized(self, jid, sid, ifrom, iq): return self.auto_accept def _authorized_sid(self, jid, sid, ifrom, iq): log.debug('>>> authed sids: %s', self._preauthed_sids) log.debug('>>> lookup: %s %s %s', jid, sid, ifrom) if (jid, sid, ifrom) in self._preauthed_sids: del self._preauthed_sids[(jid, sid, ifrom)] return True return False def _preauthorize_sid(self, jid, sid, ifrom, data): log.debug('>>>> %s %s %s %s', jid, sid, ifrom, data) self._preauthed_sids[(jid, sid, ifrom)] = True slixmpp-slix-1.4.2/slixmpp/plugins/xep_0065/socks5.py000066400000000000000000000167661342457644200224260ustar00rootroot00000000000000'''Pure asyncio implementation of RFC 1928 - SOCKS Protocol Version 5.''' import asyncio import enum import logging import socket import struct from slixmpp.stringprep import punycode, StringprepError log = logging.getLogger(__name__) class ProtocolMismatch(Exception): '''We only implement SOCKS5, no other version or protocol.''' class ProtocolError(Exception): '''Some protocol error.''' class MethodMismatch(Exception): '''The server answered with a method we didn’t ask for.''' class MethodUnacceptable(Exception): '''None of our methods is supported by the server.''' class AddressTypeUnacceptable(Exception): '''The address type (ATYP) field isn’t one of IPv4, IPv6 or domain name.''' class ReplyError(Exception): '''The server answered with an error.''' possible_values = ( "succeeded", "general SOCKS server failure", "connection not allowed by ruleset", "Network unreachable", "Host unreachable", "Connection refused", "TTL expired", "Command not supported", "Address type not supported", "Unknown error") def __init__(self, result): if result < 9: Exception.__init__(self, self.possible_values[result]) else: Exception.__init__(self, self.possible_values[9]) class Method(enum.IntEnum): '''Known methods for a SOCKS5 session.''' none = 0 gssapi = 1 password = 2 # Methods 3 to 127 are reserved by IANA. # Methods 128 to 254 are reserved for private use. unacceptable = 255 not_yet_selected = -1 class Command(enum.IntEnum): '''Existing commands for requests.''' connect = 1 bind = 2 udp_associate = 3 class AddressType(enum.IntEnum): '''Existing address types.''' ipv4 = 1 domain = 3 ipv6 = 4 class Socks5Protocol(asyncio.Protocol): '''This implements SOCKS5 as an asyncio protocol.''' def __init__(self, dest_addr, dest_port, event): self.methods = {Method.none} self.selected_method = Method.not_yet_selected self.transport = None self.dest = (dest_addr, dest_port) self.connected = asyncio.Future() self.event = event self.paused = asyncio.Future() self.paused.set_result(None) def register_method(self, method): '''Register a SOCKS5 method.''' self.methods.add(method) def unregister_method(self, method): '''Unregister a SOCKS5 method.''' self.methods.remove(method) def connection_made(self, transport): '''Called when the connection to the SOCKS5 server is established.''' log.debug('SOCKS5 connection established.') self.transport = transport self._send_methods() def data_received(self, data): '''Called when we received some data from the SOCKS5 server.''' log.debug('SOCKS5 message received.') # If we are already connected, this is a data packet. if self.connected.done(): return self.event('socks5_data', data) # Every SOCKS5 message starts with the protocol version. if data[0] != 5: raise ProtocolMismatch() # Then select the correct handler for the data we just received. if self.selected_method == Method.not_yet_selected: self._handle_method(data) else: self._handle_connect(data) def connection_lost(self, exc): log.debug('SOCKS5 connection closed.') self.event('socks5_closed', exc) def pause_writing(self): self.paused = asyncio.Future() def resume_writing(self): self.paused.set_result(None) async def write(self, data): await self.paused self.transport.write(data) def _send_methods(self): '''Send the methods request, first thing a client should do.''' # Create the buffer for our request. request = bytearray(len(self.methods) + 2) # Protocol version. request[0] = 5 # Number of methods to send. request[1] = len(self.methods) # List every method we support. for i, method in enumerate(self.methods): request[i + 2] = method # Send the request. self.transport.write(request) def _send_request(self, command): '''Send a request, should be done after having negociated a method.''' # Encode the destination address to embed it in our request. # We need to do that first because its length is variable. address, port = self.dest addr = self._encode_addr(address) # Create the buffer for our request. request = bytearray(5 + len(addr)) # Protocol version. request[0] = 5 # Specify the command we want to use. request[1] = command # request[2] is reserved, keeping it at 0. # Add our destination address and port. request[3:3+len(addr)] = addr request[-2:] = struct.pack('>H', port) # Send the request. log.debug('SOCKS5 message sent.') self.transport.write(request) def _handle_method(self, data): '''Handle a method reply from the server.''' if len(data) != 2: raise ProtocolError() selected_method = data[1] if selected_method not in self.methods: raise MethodMismatch() if selected_method == Method.unacceptable: raise MethodUnacceptable() self.selected_method = selected_method self._send_request(Command.connect) def _handle_connect(self, data): '''Handle a connect reply from the server.''' try: addr, port = self._parse_result(data) except ReplyError as exception: self.connected.set_exception(exception) self.connected.set_result((addr, port)) self.event('socks5_connected', (addr, port)) def _parse_result(self, data): '''Parse a reply from the server.''' result = data[1] if result != 0: raise ReplyError(result) addr = self._parse_addr(data[3:-2]) port = struct.unpack('>H', data[-2:])[0] return (addr, port) @staticmethod def _parse_addr(addr): '''Parse an address (IP or domain) from a bytestream.''' addr_type = addr[0] if addr_type == AddressType.ipv6: try: return socket.inet_ntop(socket.AF_INET6, addr[1:]) except ValueError as e: raise AddressTypeUnacceptable(e) if addr_type == AddressType.ipv4: try: return socket.inet_ntop(socket.AF_INET, addr[1:]) except ValueError as e: raise AddressTypeUnacceptable(e) if addr_type == AddressType.domain: length = addr[1] address = addr[2:] if length != len(address): raise Exception('Size mismatch') return address.decode() raise AddressTypeUnacceptable(addr_type) @staticmethod def _encode_addr(addr): '''Encode an address (IP or domain) into a bytestream.''' try: ipv6 = socket.inet_pton(socket.AF_INET6, addr) return b'\x04' + ipv6 except OSError: pass try: ipv4 = socket.inet_aton(addr) return b'\x01' + ipv4 except OSError: pass try: domain = punycode(addr) return b'\x03' + bytes([len(domain)]) + domain except StringprepError: pass raise Exception('Err…') slixmpp-slix-1.4.2/slixmpp/plugins/xep_0065/stanza.py000066400000000000000000000023701342457644200225010ustar00rootroot00000000000000from slixmpp.jid import JID from slixmpp.xmlstream import ElementBase, register_stanza_plugin class Socks5(ElementBase): name = 'query' namespace = 'http://jabber.org/protocol/bytestreams' plugin_attrib = 'socks' interfaces = {'sid', 'activate'} sub_interfaces = {'activate'} def add_streamhost(self, jid, host, port): sh = StreamHost(parent=self) sh['jid'] = jid sh['host'] = host sh['port'] = port class StreamHost(ElementBase): name = 'streamhost' namespace = 'http://jabber.org/protocol/bytestreams' plugin_attrib = 'streamhost' plugin_multi_attrib = 'streamhosts' interfaces = {'host', 'jid', 'port'} def set_jid(self, value): return self._set_attr('jid', str(value)) def get_jid(self): return JID(self._get_attr('jid')) class StreamHostUsed(ElementBase): name = 'streamhost-used' namespace = 'http://jabber.org/protocol/bytestreams' plugin_attrib = 'streamhost_used' interfaces = {'jid'} def set_jid(self, value): return self._set_attr('jid', str(value)) def get_jid(self): return JID(self._get_attr('jid')) register_stanza_plugin(Socks5, StreamHost, iterable=True) register_stanza_plugin(Socks5, StreamHostUsed) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0066/000077500000000000000000000000001342457644200206265ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0066/__init__.py000066400000000000000000000006461342457644200227450ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0066 import stanza from slixmpp.plugins.xep_0066.stanza import OOB, OOBTransfer from slixmpp.plugins.xep_0066.oob import XEP_0066 register_plugin(XEP_0066) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0066/oob.py000066400000000000000000000126031342457644200217610ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp.stanza import Message, Presence, Iq from slixmpp.exceptions import XMPPError from slixmpp.xmlstream import register_stanza_plugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.plugins import BasePlugin from slixmpp.plugins.xep_0066 import stanza log = logging.getLogger(__name__) class XEP_0066(BasePlugin): """ XEP-0066: Out of Band Data Out of Band Data is a basic method for transferring files between XMPP agents. The URL of the resource in question is sent to the receiving entity, which then downloads the resource before responding to the OOB request. OOB is also used as a generic means to transmit URLs in other stanzas to indicate where to find additional information. Also see . Events: oob_transfer -- Raised when a request to download a resource has been received. Methods: send_oob -- Send a request to another entity to download a file or other addressable resource. """ name = 'xep_0066' description = 'XEP-0066: Out of Band Data' dependencies = {'xep_0030'} stanza = stanza def plugin_init(self): """Start the XEP-0066 plugin.""" self.url_handlers = {'global': self._default_handler, 'jid': {}} register_stanza_plugin(Iq, stanza.OOBTransfer) register_stanza_plugin(Message, stanza.OOB) register_stanza_plugin(Presence, stanza.OOB) self.xmpp.register_handler( Callback('OOB Transfer', StanzaPath('iq@type=set/oob_transfer'), self._handle_transfer)) def plugin_end(self): self.xmpp.remove_handler('OOB Transfer') self.xmpp['xep_0030'].del_feature(feature=stanza.OOBTransfer.namespace) self.xmpp['xep_0030'].del_feature(feature=stanza.OOB.namespace) def session_bind(self, jid): self.xmpp['xep_0030'].add_feature(stanza.OOBTransfer.namespace) self.xmpp['xep_0030'].add_feature(stanza.OOB.namespace) def register_url_handler(self, jid=None, handler=None): """ Register a handler to process download requests, either for all JIDs or a single JID. Arguments: jid -- If None, then set the handler as a global default. handler -- If None, then remove the existing handler for the given JID, or reset the global handler if the JID is None. """ if jid is None: if handler is not None: self.url_handlers['global'] = handler else: self.url_handlers['global'] = self._default_handler else: if handler is not None: self.url_handlers['jid'][jid] = handler else: del self.url_handlers['jid'][jid] def send_oob(self, to, url, desc=None, ifrom=None, **iqargs): """ Initiate a basic file transfer by sending the URL of a file or other resource. Arguments: url -- The URL of the resource to transfer. desc -- An optional human readable description of the item that is to be transferred. ifrom -- Specifiy the sender's JID. block -- If true, block and wait for the stanzas' reply. timeout -- The time in seconds to block while waiting for a reply. If None, then wait indefinitely. callback -- Optional callback to execute when a reply is received instead of blocking and waiting for the reply. """ iq = self.xmpp.Iq() iq['type'] = 'set' iq['to'] = to iq['from'] = ifrom iq['oob_transfer']['url'] = url iq['oob_transfer']['desc'] = desc return iq.send(**iqargs) def _run_url_handler(self, iq): """ Execute the appropriate handler for a transfer request. Arguments: iq -- The Iq stanza containing the OOB transfer request. """ if iq['to'] in self.url_handlers['jid']: return self.url_handlers['jid'][iq['to']](iq) else: if self.url_handlers['global']: self.url_handlers['global'](iq) else: raise XMPPError('service-unavailable') def _default_handler(self, iq): """ As a safe default, don't actually download files. Register a new handler using self.register_url_handler to screen requests and download files. Arguments: iq -- The Iq stanza containing the OOB transfer request. """ raise XMPPError('service-unavailable') def _handle_transfer(self, iq): """ Handle receiving an out-of-band transfer request. Arguments: iq -- An Iq stanza containing an OOB transfer request. """ log.debug('Received out-of-band data request for %s from %s:' % ( iq['oob_transfer']['url'], iq['from'])) self._run_url_handler(iq) iq.reply().send() slixmpp-slix-1.4.2/slixmpp/plugins/xep_0066/stanza.py000066400000000000000000000011631342457644200225010ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase class OOBTransfer(ElementBase): """ """ name = 'query' namespace = 'jabber:iq:oob' plugin_attrib = 'oob_transfer' interfaces = {'url', 'desc', 'sid'} sub_interfaces = {'url', 'desc'} class OOB(ElementBase): """ """ name = 'x' namespace = 'jabber:x:oob' plugin_attrib = 'oob' interfaces = {'url', 'desc'} sub_interfaces = interfaces slixmpp-slix-1.4.2/slixmpp/plugins/xep_0070/000077500000000000000000000000001342457644200206215ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0070/__init__.py000066400000000000000000000005441342457644200227350ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2015 Emmanuel Gil Peyrot This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0070.stanza import Confirm from slixmpp.plugins.xep_0070.confirm import XEP_0070 register_plugin(XEP_0070) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0070/confirm.py000066400000000000000000000051231342457644200226310ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2015 Emmanuel Gil Peyrot This file is part of Slixmpp. See the file LICENSE for copying permission. """ import asyncio import logging from uuid import uuid4 from slixmpp.plugins import BasePlugin, register_plugin from slixmpp import future_wrapper, Iq, Message from slixmpp.exceptions import XMPPError, IqError, IqTimeout from slixmpp.jid import JID from slixmpp.xmlstream import JID, register_stanza_plugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.plugins.xep_0070 import stanza, Confirm log = logging.getLogger(__name__) class XEP_0070(BasePlugin): """ XEP-0070 Verifying HTTP Requests via XMPP """ name = 'xep_0070' description = 'XEP-0070: Verifying HTTP Requests via XMPP' dependencies = {'xep_0030'} stanza = stanza def plugin_init(self): register_stanza_plugin(Iq, Confirm) register_stanza_plugin(Message, Confirm) self.xmpp.register_handler( Callback('Confirm', StanzaPath('iq@type=get/confirm'), self._handle_iq_confirm)) self.xmpp.register_handler( Callback('Confirm', StanzaPath('message/confirm'), self._handle_message_confirm)) def plugin_end(self): self.xmpp.remove_handler('Confirm') self.xmpp['xep_0030'].del_feature(feature='http://jabber.org/protocol/http-auth') def session_bind(self, jid): self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/http-auth') @future_wrapper def ask_confirm(self, jid, id, url, method, *, ifrom=None, message=None): jid = JID(jid) if jid.resource: stanza = self.xmpp.Iq() stanza['type'] = 'get' else: stanza = self.xmpp.Message() stanza['thread'] = uuid4().hex stanza['from'] = ifrom stanza['to'] = jid stanza['confirm']['id'] = id stanza['confirm']['url'] = url stanza['confirm']['method'] = method if not jid.resource: if message is not None: stanza['body'] = message.format(id=id, url=url, method=method) stanza.send() return stanza else: return stanza.send() def _handle_iq_confirm(self, iq): self.xmpp.event('http_confirm_iq', iq) self.xmpp.event('http_confirm', iq) def _handle_message_confirm(self, message): self.xmpp.event('http_confirm_message', message) self.xmpp.event('http_confirm', message) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0070/stanza.py000066400000000000000000000006101342457644200224700ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2015 Emmanuel Gil Peyrot This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase class Confirm(ElementBase): name = 'confirm' namespace = 'http://jabber.org/protocol/http-auth' plugin_attrib = 'confirm' interfaces = {'id', 'url', 'method'} slixmpp-slix-1.4.2/slixmpp/plugins/xep_0071/000077500000000000000000000000001342457644200206225ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0071/__init__.py000066400000000000000000000005651342457644200227410ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permissio """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0071.stanza import XHTML_IM from slixmpp.plugins.xep_0071.xhtml_im import XEP_0071 register_plugin(XEP_0071) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0071/stanza.py000066400000000000000000000054211342457644200224760ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.stanza import Message from slixmpp.util import unicode from collections import OrderedDict from slixmpp.xmlstream import ElementBase, ET, register_stanza_plugin, tostring XHTML_NS = 'http://www.w3.org/1999/xhtml' class XHTML_IM(ElementBase): namespace = 'http://jabber.org/protocol/xhtml-im' name = 'html' interfaces = {'body'} lang_interfaces = {'body'} plugin_attrib = name def set_body(self, content, lang=None): if lang is None: lang = self.get_lang() self.del_body(lang) if lang == '*': for sublang, subcontent in content.items(): self.set_body(subcontent, sublang) else: if isinstance(content, type(ET.Element('test'))): content = unicode(ET.tostring(content)) else: content = unicode(content) header = '>>>>>>>>>>>>>>>>>>>>>>>>>>>>') if msg['type'] == 'error': self.xmpp.event('amp_error', msg) elif msg['amp']['status'] in ('alert', 'notify'): self.xmpp.event('amp_%s' % msg['amp']['status'], msg) def _handle_amp_feature(self, features): log.debug('Advanced Message Processing is available.') self.xmpp.features.add('amp') def discover_support(self, jid=None, **iqargs): if jid is None: if self.xmpp.is_component: jid = self.xmpp.server_host else: jid = self.xmpp.boundjid.host return self.xmpp['xep_0030'].get_info( jid=jid, node='http://jabber.org/protocol/amp', **iqargs) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0079/stanza.py000066400000000000000000000050561342457644200225120ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from __future__ import unicode_literals from slixmpp import JID from slixmpp.xmlstream import ElementBase, register_stanza_plugin class AMP(ElementBase): namespace = 'http://jabber.org/protocol/amp' name = 'amp' plugin_attrib = 'amp' interfaces = {'from', 'to', 'status', 'per_hop'} def get_from(self): return JID(self._get_attr('from')) def set_from(self, value): return self._set_attr('from', str(value)) def get_to(self): return JID(self._get_attr('from')) def set_to(self, value): return self._set_attr('to', str(value)) def get_per_hop(self): return self._get_attr('per-hop') == 'true' def set_per_hop(self, value): if value: return self._set_attr('per-hop', 'true') else: return self._del_attr('per-hop') def del_per_hop(self): return self._del_attr('per-hop') def add_rule(self, action, condition, value): rule = Rule(parent=self) rule['action'] = action rule['condition'] = condition rule['value'] = value class Rule(ElementBase): namespace = 'http://jabber.org/protocol/amp' name = 'rule' plugin_attrib = name plugin_multi_attrib = 'rules' interfaces = {'action', 'condition', 'value'} class InvalidRules(ElementBase): namespace = 'http://jabber.org/protocol/amp' name = 'invalid-rules' plugin_attrib = 'invalid_rules' class UnsupportedConditions(ElementBase): namespace = 'http://jabber.org/protocol/amp' name = 'unsupported-conditions' plugin_attrib = 'unsupported_conditions' class UnsupportedActions(ElementBase): namespace = 'http://jabber.org/protocol/amp' name = 'unsupported-actions' plugin_attrib = 'unsupported_actions' class FailedRule(Rule): namespace = 'http://jabber.org/protocol/amp#errors' class FailedRules(ElementBase): namespace = 'http://jabber.org/protocol/amp#errors' name = 'failed-rules' plugin_attrib = 'failed_rules' class AMPFeature(ElementBase): namespace = 'http://jabber.org/features/amp' name = 'amp' register_stanza_plugin(AMP, Rule, iterable=True) register_stanza_plugin(InvalidRules, Rule, iterable=True) register_stanza_plugin(UnsupportedConditions, Rule, iterable=True) register_stanza_plugin(UnsupportedActions, Rule, iterable=True) register_stanza_plugin(FailedRules, FailedRule, iterable=True) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0080/000077500000000000000000000000001342457644200206225ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0080/__init__.py000066400000000000000000000005721342457644200227370ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0080.stanza import Geoloc from slixmpp.plugins.xep_0080.geoloc import XEP_0080 register_plugin(XEP_0080) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0080/geoloc.py000066400000000000000000000117221342457644200224470ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging import slixmpp from slixmpp.plugins.base import BasePlugin from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins.xep_0080 import stanza, Geoloc log = logging.getLogger(__name__) class XEP_0080(BasePlugin): """ XEP-0080: User Location """ name = 'xep_0080' description = 'XEP-0080: User Location' dependencies = {'xep_0163'} stanza = stanza def plugin_end(self): self.xmpp['xep_0163'].remove_interest(Geoloc.namespace) self.xmpp['xep_0030'].del_feature(feature=Geoloc.namespace) def session_bind(self, jid): self.xmpp['xep_0163'].register_pep('user_location', Geoloc) def publish_location(self, **kwargs): """ Publish the user's current location. Arguments: accuracy -- Horizontal GPS error in meters. alt -- Altitude in meters above or below sea level. area -- A named area such as a campus or neighborhood. bearing -- GPS bearing (direction in which the entity is heading to reach its next waypoint), measured in decimal degrees relative to true north. building -- A specific building on a street or in an area. country -- The nation where the user is located. countrycode -- The ISO 3166 two-letter country code. datum -- GPS datum. description -- A natural-language name for or description of the location. error -- Horizontal GPS error in arc minutes. Obsoleted by the accuracy parameter. floor -- A particular floor in a building. lat -- Latitude in decimal degrees North. locality -- A locality within the administrative region, such as a town or city. lon -- Longitude in decimal degrees East. postalcode -- A code used for postal delivery. region -- An administrative region of the nation, such as a state or province. room -- A particular room in a building. speed -- The speed at which the entity is moving, in meters per second. street -- A thoroughfare within the locality, or a crossing of two thoroughfares. text -- A catch-all element that captures any other information about the location. timestamp -- UTC timestamp specifying the moment when the reading was taken. uri -- A URI or URL pointing to information about the location. options -- Optional form of publish options. ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ options = kwargs.get('options', None) ifrom = kwargs.get('ifrom', None) callback = kwargs.get('callback', None) timeout = kwargs.get('timeout', None) timeout_callback = kwargs.get('timeout_callback', None) for param in ('ifrom', 'block', 'callback', 'timeout', 'options', 'timeout_callback'): if param in kwargs: del kwargs[param] geoloc = Geoloc() geoloc.values = kwargs return self.xmpp['xep_0163'].publish(geoloc, options=options, ifrom=ifrom, callback=callback, timeout=timeout, timeout_callback=timeout_callback) def stop(self, ifrom=None, callback=None, timeout=None, timeout_callback=None): """ Clear existing user location information to stop notifications. Arguments: ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ geoloc = Geoloc() return self.xmpp['xep_0163'].publish(geoloc, ifrom=ifrom, callback=callback, timeout=timeout, timeout_callback=None) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0080/stanza.py000066400000000000000000000176731342457644200225120ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase from slixmpp.plugins import xep_0082 class Geoloc(ElementBase): """ XMPP's stanza allows entities to know the current geographical or physical location of an entity. (XEP-0080: User Location) Example stanzas: 20 Italy 45.44 Venice 12.33 Stanza Interface: accuracy -- Horizontal GPS error in meters. alt -- Altitude in meters above or below sea level. area -- A named area such as a campus or neighborhood. bearing -- GPS bearing (direction in which the entity is heading to reach its next waypoint), measured in decimal degrees relative to true north. building -- A specific building on a street or in an area. country -- The nation where the user is located. countrycode -- The ISO 3166 two-letter country code. datum -- GPS datum. description -- A natural-language name for or description of the location. error -- Horizontal GPS error in arc minutes. Obsoleted by the accuracy parameter. floor -- A particular floor in a building. lat -- Latitude in decimal degrees North. locality -- A locality within the administrative region, such as a town or city. lon -- Longitude in decimal degrees East. postalcode -- A code used for postal delivery. region -- An administrative region of the nation, such as a state or province. room -- A particular room in a building. speed -- The speed at which the entity is moving, in meters per second. street -- A thoroughfare within the locality, or a crossing of two thoroughfares. text -- A catch-all element that captures any other information about the location. timestamp -- UTC timestamp specifying the moment when the reading was taken. uri -- A URI or URL pointing to information about the location. """ namespace = 'http://jabber.org/protocol/geoloc' name = 'geoloc' interfaces = {'accuracy', 'alt', 'area', 'bearing', 'building', 'country', 'countrycode', 'datum', 'dscription', 'error', 'floor', 'lat', 'locality', 'lon', 'postalcode', 'region', 'room', 'speed', 'street', 'text', 'timestamp', 'uri'} sub_interfaces = interfaces plugin_attrib = name def exception(self, e): """ Override exception passback for presence. """ pass def set_accuracy(self, accuracy): """ Set the value of the element. Arguments: accuracy -- Horizontal GPS error in meters """ self._set_sub_text('accuracy', text=str(accuracy)) return self def get_accuracy(self): """ Return the value of the element as an integer. """ p = self._get_sub_text('accuracy') if not p: return None else: try: return int(p) except ValueError: return None def set_alt(self, alt): """ Set the value of the element. Arguments: alt -- Altitude in meters above or below sea level """ self._set_sub_text('alt', text=str(alt)) return self def get_alt(self): """ Return the value of the element as an integer. """ p = self._get_sub_text('alt') if not p: return None else: try: return int(p) except ValueError: return None def set_bearing(self, bearing): """ Set the value of the element. Arguments: bearing -- GPS bearing (direction in which the entity is heading to reach its next waypoint), measured in decimal degrees relative to true north """ self._set_sub_text('bearing', text=str(bearing)) return self def get_bearing(self): """ Return the value of the element as a float. """ p = self._get_sub_text('bearing') if not p: return None else: try: return float(p) except ValueError: return None def set_error(self, error): """ Set the value of the element. Arguments: error -- Horizontal GPS error in arc minutes; this element is deprecated in favor of """ self._set_sub_text('error', text=str(error)) return self def get_error(self): """ Return the value of the element as a float. """ p = self._get_sub_text('error') if not p: return None else: try: return float(p) except ValueError: return None def set_lat(self, lat): """ Set the value of the element. Arguments: lat -- Latitude in decimal degrees North """ self._set_sub_text('lat', text=str(lat)) return self def get_lat(self): """ Return the value of the element as a float. """ p = self._get_sub_text('lat') if not p: return None else: try: return float(p) except ValueError: return None def set_lon(self, lon): """ Set the value of the element. Arguments: lon -- Longitude in decimal degrees East """ self._set_sub_text('lon', text=str(lon)) return self def get_lon(self): """ Return the value of the element as a float. """ p = self._get_sub_text('lon') if not p: return None else: try: return float(p) except ValueError: return None def set_speed(self, speed): """ Set the value of the element. Arguments: speed -- The speed at which the entity is moving, in meters per second """ self._set_sub_text('speed', text=str(speed)) return self def get_speed(self): """ Return the value of the element as a float. """ p = self._get_sub_text('speed') if not p: return None else: try: return float(p) except ValueError: return None def set_timestamp(self, timestamp): """ Set the value of the element. Arguments: timestamp -- UTC timestamp specifying the moment when the reading was taken """ self._set_sub_text('timestamp', text=str(xep_0082.datetime(timestamp))) return self def get_timestamp(self): """ Return the value of the element as a DateTime. """ p = self._get_sub_text('timestamp') if not p: return None else: return xep_0082.datetime(p) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0082.py000066400000000000000000000151701342457644200212020ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import datetime as dt from slixmpp.plugins import BasePlugin, register_plugin from slixmpp.thirdparty import tzutc, tzoffset, parse_iso # ===================================================================== # To make it easier for stanzas without direct access to plugin objects # to use the XEP-0082 utility methods, we will define them as top-level # functions and then just reference them in the plugin itself. def parse(time_str): """ Convert a string timestamp into a datetime object. Arguments: time_str -- A formatted timestamp string. """ return parse_iso(time_str) def format_date(time_obj): """ Return a formatted string version of a date object. Format: YYYY-MM-DD Arguments: time_obj -- A date or datetime object. """ if isinstance(time_obj, dt.datetime): time_obj = time_obj.date() return time_obj.isoformat() def format_time(time_obj): """ Return a formatted string version of a time object. format: hh:mm:ss[.sss][TZD] arguments: time_obj -- A time or datetime object. """ if isinstance(time_obj, dt.datetime): time_obj = time_obj.timetz() timestamp = time_obj.isoformat() if time_obj.tzinfo == tzutc(): timestamp = timestamp[:-6] return '%sZ' % timestamp return timestamp def format_datetime(time_obj): """ Return a formatted string version of a datetime object. Format: YYYY-MM-DDThh:mm:ss[.sss]TZD arguments: time_obj -- A datetime object. """ timestamp = time_obj.isoformat('T') if time_obj.tzinfo == tzutc(): timestamp = timestamp[:-6] return '%sZ' % timestamp return timestamp def date(year=None, month=None, day=None, obj=False): """ Create a date only timestamp for the given instant. Unspecified components default to their current counterparts. Arguments: year -- Integer value of the year (4 digits) month -- Integer value of the month day -- Integer value of the day of the month. obj -- If True, return the date object instead of a formatted string. Defaults to False. """ today = dt.datetime.utcnow() if year is None: year = today.year if month is None: month = today.month if day is None: day = today.day value = dt.date(year, month, day) if obj: return value return format_date(value) def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False): """ Create a time only timestamp for the given instant. Unspecified components default to their current counterparts. Arguments: hour -- Integer value of the hour. min -- Integer value of the number of minutes. sec -- Integer value of the number of seconds. micro -- Integer value of the number of microseconds. offset -- Either a positive or negative number of seconds to offset from UTC to match a desired timezone, or a tzinfo object. obj -- If True, return the time object instead of a formatted string. Defaults to False. """ now = dt.datetime.utcnow() if hour is None: hour = now.hour if min is None: min = now.minute if sec is None: sec = now.second if micro is None: micro = now.microsecond if offset is None: offset = tzutc() elif not isinstance(offset, dt.tzinfo): offset = tzoffset(None, offset) value = dt.time(hour, min, sec, micro, offset) if obj: return value return format_time(value) def datetime(year=None, month=None, day=None, hour=None, min=None, sec=None, micro=None, offset=None, separators=True, obj=False): """ Create a datetime timestamp for the given instant. Unspecified components default to their current counterparts. Arguments: year -- Integer value of the year (4 digits) month -- Integer value of the month day -- Integer value of the day of the month. hour -- Integer value of the hour. min -- Integer value of the number of minutes. sec -- Integer value of the number of seconds. micro -- Integer value of the number of microseconds. offset -- Either a positive or negative number of seconds to offset from UTC to match a desired timezone, or a tzinfo object. obj -- If True, return the datetime object instead of a formatted string. Defaults to False. """ now = dt.datetime.utcnow() if year is None: year = now.year if month is None: month = now.month if day is None: day = now.day if hour is None: hour = now.hour if min is None: min = now.minute if sec is None: sec = now.second if micro is None: micro = now.microsecond if offset is None: offset = tzutc() elif not isinstance(offset, dt.tzinfo): offset = tzoffset(None, offset) value = dt.datetime(year, month, day, hour, min, sec, micro, offset) if obj: return value return format_datetime(value) class XEP_0082(BasePlugin): """ XEP-0082: XMPP Date and Time Profiles XMPP uses a subset of the formats allowed by ISO 8601 as a matter of pragmatism based on the relatively few formats historically used by the XMPP. Also see . Methods: date -- Create a time stamp using the Date profile. datetime -- Create a time stamp using the DateTime profile. time -- Create a time stamp using the Time profile. format_date -- Format an existing date object. format_datetime -- Format an existing datetime object. format_time -- Format an existing time object. parse -- Convert a time string into a Python datetime object. """ name = 'xep_0082' description = 'XEP-0082: XMPP Date and Time Profiles' dependencies = set() def plugin_init(self): """Start the XEP-0082 plugin.""" self.date = date self.datetime = datetime self.time = time self.format_date = format_date self.format_datetime = format_datetime self.format_time = format_time self.parse = parse register_plugin(XEP_0082) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0084/000077500000000000000000000000001342457644200206265ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0084/__init__.py000066400000000000000000000006471342457644200227460ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0084 import stanza from slixmpp.plugins.xep_0084.stanza import Data, MetaData from slixmpp.plugins.xep_0084.avatar import XEP_0084 register_plugin(XEP_0084) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0084/avatar.py000066400000000000000000000076451342457644200224720ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import hashlib import logging from slixmpp import Iq from slixmpp.plugins import BasePlugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.xmlstream import register_stanza_plugin, JID from slixmpp.plugins.xep_0084 import stanza, Data, MetaData log = logging.getLogger(__name__) class XEP_0084(BasePlugin): name = 'xep_0084' description = 'XEP-0084: User Avatar' dependencies = {'xep_0163', 'xep_0060'} stanza = stanza def plugin_init(self): pubsub_stanza = self.xmpp['xep_0060'].stanza register_stanza_plugin(pubsub_stanza.Item, Data) register_stanza_plugin(pubsub_stanza.EventItem, Data) self.xmpp['xep_0060'].map_node_event(Data.namespace, 'avatar_data') def plugin_end(self): self.xmpp['xep_0030'].del_feature(feature=MetaData.namespace) self.xmpp['xep_0163'].remove_interest(MetaData.namespace) def session_bind(self, jid): self.xmpp['xep_0163'].register_pep('avatar_metadata', MetaData) def generate_id(self, data): return hashlib.sha1(data).hexdigest() def retrieve_avatar(self, jid, id, url=None, ifrom=None, callback=None, timeout=None, timeout_callback=None): return self.xmpp['xep_0060'].get_item(jid, Data.namespace, id, ifrom=ifrom, callback=callback, timeout=timeout, timeout_callback=timeout_callback) def publish_avatar(self, data, ifrom=None, callback=None, timeout=None, timeout_callback=None): payload = Data() payload['value'] = data return self.xmpp['xep_0163'].publish(payload, id=self.generate_id(data), ifrom=ifrom, callback=callback, timeout=timeout, timeout_callback=timeout_callback) def publish_avatar_metadata(self, items=None, pointers=None, ifrom=None, callback=None, timeout=None, timeout_callback=None): metadata = MetaData() if items is None: items = [] if not isinstance(items, (list, set)): items = [items] for info in items: metadata.add_info(info['id'], info['type'], info['bytes'], height=info.get('height', ''), width=info.get('width', ''), url=info.get('url', '')) if pointers is not None: for pointer in pointers: metadata.add_pointer(pointer) return self.xmpp['xep_0163'].publish(metadata, id=info['id'], ifrom=ifrom, callback=callback, timeout=timeout, timeout_callback=timeout_callback) def stop(self, ifrom=None, callback=None, timeout=None, timeout_callback=None): """ Clear existing avatar metadata information to stop notifications. Arguments: ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ metadata = MetaData() return self.xmpp['xep_0163'].publish(metadata, node=MetaData.namespace, ifrom=ifrom, callback=callback, timeout=timeout, timeout_callback=timeout_callback) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0084/stanza.py000066400000000000000000000040041342457644200224760ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from base64 import b64encode, b64decode from slixmpp.util import bytes from slixmpp.xmlstream import ET, ElementBase, register_stanza_plugin class Data(ElementBase): name = 'data' namespace = 'urn:xmpp:avatar:data' plugin_attrib = 'avatar_data' interfaces = {'value'} def get_value(self): if self.xml.text: return b64decode(bytes(self.xml.text)) return b'' def set_value(self, value): if value: self.xml.text = b64encode(bytes(value)).decode() else: self.xml.text = '' def del_value(self): self.xml.text = '' class MetaData(ElementBase): name = 'metadata' namespace = 'urn:xmpp:avatar:metadata' plugin_attrib = 'avatar_metadata' interfaces = set() def add_info(self, id, itype, ibytes, height=None, width=None, url=None): info = Info() info.values = {'id': id, 'type': itype, 'bytes': '%s' % ibytes, 'height': height, 'width': width, 'url': url} self.append(info) def add_pointer(self, xml): if not isinstance(xml, Pointer): pointer = Pointer() pointer.append(xml) self.append(pointer) else: self.append(xml) class Info(ElementBase): name = 'info' namespace = 'urn:xmpp:avatar:metadata' plugin_attrib = 'info' plugin_multi_attrib = 'items' interfaces = {'bytes', 'height', 'id', 'type', 'url', 'width'} class Pointer(ElementBase): name = 'pointer' namespace = 'urn:xmpp:avatar:metadata' plugin_attrib = 'pointer' plugin_multi_attrib = 'pointers' interfaces = set() register_stanza_plugin(MetaData, Info, iterable=True) register_stanza_plugin(MetaData, Pointer, iterable=True) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0085/000077500000000000000000000000001342457644200206275ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0085/__init__.py000066400000000000000000000005711342457644200227430ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permissio """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0085.stanza import ChatState from slixmpp.plugins.xep_0085.chat_states import XEP_0085 register_plugin(XEP_0085) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0085/chat_states.py000066400000000000000000000031671342457644200235120ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permissio """ import logging import slixmpp from slixmpp.stanza import Message from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.xmlstream import register_stanza_plugin, ElementBase, ET from slixmpp.plugins import BasePlugin from slixmpp.plugins.xep_0085 import stanza, ChatState log = logging.getLogger(__name__) class XEP_0085(BasePlugin): """ XEP-0085 Chat State Notifications """ name = 'xep_0085' description = 'XEP-0085: Chat State Notifications' dependencies = {'xep_0030'} stanza = stanza def plugin_init(self): self.xmpp.register_handler( Callback('Chat State', StanzaPath('message/chat_state'), self._handle_chat_state)) register_stanza_plugin(Message, stanza.Active) register_stanza_plugin(Message, stanza.Composing) register_stanza_plugin(Message, stanza.Gone) register_stanza_plugin(Message, stanza.Inactive) register_stanza_plugin(Message, stanza.Paused) def plugin_end(self): self.xmpp.remove_handler('Chat State') def session_bind(self, jid): self.xmpp.plugin['xep_0030'].add_feature(ChatState.namespace) def _handle_chat_state(self, msg): state = msg['chat_state'] log.debug("Chat State: %s, %s", state, msg['from'].jid) self.xmpp.event('chatstate', msg) self.xmpp.event('chatstate_%s' % state, msg) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0085/stanza.py000066400000000000000000000042311342457644200225010ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permissio """ import slixmpp from slixmpp.xmlstream import ElementBase, ET class ChatState(ElementBase): """ Example chat state stanzas: Stanza Interfaces: chat_state Attributes: states Methods: get_chat_state set_chat_state del_chat_state """ name = '' namespace = 'http://jabber.org/protocol/chatstates' plugin_attrib = 'chat_state' interfaces = {'chat_state'} sub_interfaces = interfaces is_extension = True states = {'active', 'composing', 'gone', 'inactive', 'paused'} def setup(self, xml=None): self.xml = ET.Element('') return True def get_chat_state(self): parent = self.parent() for state in self.states: state_xml = parent.xml.find('{%s}%s' % (self.namespace, state)) if state_xml is not None: self.xml = state_xml return state return '' def set_chat_state(self, state): self.del_chat_state() parent = self.parent() if state in self.states: self.xml = ET.Element('{%s}%s' % (self.namespace, state)) parent.append(self.xml) elif state not in [None, '']: raise ValueError('Invalid chat state') def del_chat_state(self): parent = self.parent() for state in self.states: state_xml = parent.xml.find('{%s}%s' % (self.namespace, state)) if state_xml is not None: self.xml = ET.Element('') parent.xml.remove(state_xml) class Active(ChatState): name = 'active' class Composing(ChatState): name = 'composing' class Gone(ChatState): name = 'gone' class Inactive(ChatState): name = 'inactive' class Paused(ChatState): name = 'paused' slixmpp-slix-1.4.2/slixmpp/plugins/xep_0086/000077500000000000000000000000001342457644200206305ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0086/__init__.py000066400000000000000000000005761342457644200227510ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0086.stanza import LegacyError from slixmpp.plugins.xep_0086.legacy_error import XEP_0086 register_plugin(XEP_0086) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0086/legacy_error.py000066400000000000000000000026501342457644200236620ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.stanza import Error from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins import BasePlugin from slixmpp.plugins.xep_0086 import stanza, LegacyError class XEP_0086(BasePlugin): """ XEP-0086: Error Condition Mappings Older XMPP implementations used code based error messages, similar to HTTP response codes. Since then, error condition elements have been introduced. XEP-0086 provides a mapping between the new condition elements and a combination of error types and the older response codes. Also see . Configuration Values: override -- Indicates if applying legacy error codes should be done automatically. Defaults to True. If False, then inserting legacy error codes can be done using: iq['error']['legacy']['condition'] = ... """ name = 'xep_0086' description = 'XEP-0086: Error Condition Mappings' dependencies = set() stanza = stanza default_config = { 'override': True } def plugin_init(self): register_stanza_plugin(Error, LegacyError, overrides=self.override) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0086/stanza.py000066400000000000000000000063421342457644200225070ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.stanza import Error from slixmpp.xmlstream import ElementBase, ET, register_stanza_plugin class LegacyError(ElementBase): """ Older XMPP implementations used code based error messages, similar to HTTP response codes. Since then, error condition elements have been introduced. XEP-0086 provides a mapping between the new condition elements and a combination of error types and the older response codes. Also see . Example legacy error stanzas: Attributes: error_map -- A map of error conditions to error types and code values. Methods: setup -- Overrides ElementBase.setup set_condition -- Remap the type and code interfaces when a condition is set. """ name = 'legacy' namespace = Error.namespace plugin_attrib = name interfaces = {'condition'} overrides = ['set_condition'] error_map = {'bad-request': ('modify', '400'), 'conflict': ('cancel', '409'), 'feature-not-implemented': ('cancel', '501'), 'forbidden': ('auth', '403'), 'gone': ('modify', '302'), 'internal-server-error': ('wait', '500'), 'item-not-found': ('cancel', '404'), 'jid-malformed': ('modify', '400'), 'not-acceptable': ('modify', '406'), 'not-allowed': ('cancel', '405'), 'not-authorized': ('auth', '401'), 'payment-required': ('auth', '402'), 'recipient-unavailable': ('wait', '404'), 'redirect': ('modify', '302'), 'registration-required': ('auth', '407'), 'remote-server-not-found': ('cancel', '404'), 'remote-server-timeout': ('wait', '504'), 'resource-constraint': ('wait', '500'), 'service-unavailable': ('cancel', '503'), 'subscription-required': ('auth', '407'), 'undefined-condition': (None, '500'), 'unexpected-request': ('wait', '400')} def setup(self, xml): """Don't create XML for the plugin.""" self.xml = ET.Element('') def set_condition(self, value): """ Set the error type and code based on the given error condition value. Arguments: value -- The new error condition. """ self.parent().set_condition(value) error_data = self.error_map.get(value, None) if error_data is not None: if error_data[0] is not None: self.parent()['type'] = error_data[0] self.parent()['code'] = error_data[1] slixmpp-slix-1.4.2/slixmpp/plugins/xep_0091/000077500000000000000000000000001342457644200206245ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0091/__init__.py000066400000000000000000000006521342457644200227400ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0091 import stanza from slixmpp.plugins.xep_0091.stanza import LegacyDelay from slixmpp.plugins.xep_0091.legacy_delay import XEP_0091 register_plugin(XEP_0091) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0091/legacy_delay.py000066400000000000000000000013271342457644200236230ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.stanza import Message, Presence from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins import BasePlugin from slixmpp.plugins.xep_0091 import stanza class XEP_0091(BasePlugin): """ XEP-0091: Legacy Delayed Delivery """ name = 'xep_0091' description = 'XEP-0091: Legacy Delayed Delivery' dependencies = set() stanza = stanza def plugin_init(self): register_stanza_plugin(Message, stanza.LegacyDelay) register_stanza_plugin(Presence, stanza.LegacyDelay) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0091/stanza.py000066400000000000000000000023011342457644200224720ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import datetime as dt from slixmpp.jid import JID from slixmpp.xmlstream import ElementBase from slixmpp.plugins import xep_0082 class LegacyDelay(ElementBase): name = 'x' namespace = 'jabber:x:delay' plugin_attrib = 'legacy_delay' interfaces = {'from', 'stamp', 'text'} def get_from(self): from_ = self._get_attr('from') return JID(from_) if from_ else None def set_from(self, value): self._set_attr('from', str(value)) def get_stamp(self): timestamp = self._get_attr('stamp') return xep_0082.parse('%sZ' % timestamp) if timestamp else None def set_stamp(self, value): if isinstance(value, dt.datetime): value = value.astimezone(xep_0082.tzutc) value = xep_0082.format_datetime(value) self._set_attr('stamp', value[0:19].replace('-', '')) def get_text(self): return self.xml.text def set_text(self, value): self.xml.text = value def del_text(self): self.xml.text = '' slixmpp-slix-1.4.2/slixmpp/plugins/xep_0092/000077500000000000000000000000001342457644200206255ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0092/__init__.py000066400000000000000000000006411342457644200227370ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0092 import stanza from slixmpp.plugins.xep_0092.stanza import Version from slixmpp.plugins.xep_0092.version import XEP_0092 register_plugin(XEP_0092) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0092/stanza.py000066400000000000000000000021301342457644200224730ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase, ET class Version(ElementBase): """ XMPP allows for an agent to advertise the name and version of the underlying software libraries, as well as the operating system that the agent is running on. Example version stanzas: Slixmpp 1.0 Linux Stanza Interface: name -- The human readable name of the software. version -- The specific version of the software. os -- The name of the operating system running the program. """ name = 'query' namespace = 'jabber:iq:version' plugin_attrib = 'software_version' interfaces = {'name', 'version', 'os'} sub_interfaces = interfaces slixmpp-slix-1.4.2/slixmpp/plugins/xep_0092/version.py000066400000000000000000000050661342457644200226730ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging import slixmpp from slixmpp import Iq from slixmpp.xmlstream import register_stanza_plugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.plugins import BasePlugin from slixmpp.plugins.xep_0092 import Version, stanza log = logging.getLogger(__name__) class XEP_0092(BasePlugin): """ XEP-0092: Software Version """ name = 'xep_0092' description = 'XEP-0092: Software Version' dependencies = {'xep_0030'} stanza = stanza default_config = { 'software_name': 'Slixmpp', 'version': slixmpp.__version__, 'os': '' } def plugin_init(self): """ Start the XEP-0092 plugin. """ if 'name' in self.config: self.software_name = self.config['name'] self.xmpp.register_handler( Callback('Software Version', StanzaPath('iq@type=get/software_version'), self._handle_version)) register_stanza_plugin(Iq, Version) def plugin_end(self): self.xmpp.remove_handler('Software Version') self.xmpp['xep_0030'].del_feature(feature='jabber:iq:version') def session_bind(self, jid): self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:version') def _handle_version(self, iq): """ Respond to a software version query. Arguments: iq -- The Iq stanza containing the software version query. """ iq = iq.reply() if self.software_name: iq['software_version']['name'] = self.software_name iq['software_version']['version'] = self.version iq['software_version']['os'] = self.os else: iq.error() iq['error']['type'] = 'cancel' iq['error']['condition'] = 'service-unavailable' iq.send() def get_version(self, jid, ifrom=None, timeout=None, callback=None, timeout_callback=None): """ Retrieve the software version of a remote agent. Arguments: jid -- The JID of the entity to query. """ iq = self.xmpp.Iq() iq['to'] = jid iq['from'] = ifrom iq['type'] = 'get' iq['query'] = Version.namespace return iq.send(timeout=timeout, callback=callback, timeout_callback=timeout_callback) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0095/000077500000000000000000000000001342457644200206305ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0095/__init__.py000066400000000000000000000006461342457644200227470ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0095 import stanza from slixmpp.plugins.xep_0095.stanza import SI from slixmpp.plugins.xep_0095.stream_initiation import XEP_0095 register_plugin(XEP_0095) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0095/stanza.py000066400000000000000000000012001342457644200224730ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase class SI(ElementBase): name = 'si' namespace = 'http://jabber.org/protocol/si' plugin_attrib = 'si' interfaces = {'id', 'mime_type', 'profile'} def get_mime_type(self): return self._get_attr('mime-type', 'application/octet-stream') def set_mime_type(self, value): self._set_attr('mime-type', value) def del_mime_type(self): self._del_attr('mime-type') slixmpp-slix-1.4.2/slixmpp/plugins/xep_0095/stream_initiation.py000066400000000000000000000155201342457644200247270ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging import threading from uuid import uuid4 from slixmpp import Iq, Message from slixmpp.exceptions import XMPPError from slixmpp.plugins import BasePlugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.xmlstream import register_stanza_plugin, JID from slixmpp.plugins.xep_0095 import stanza, SI log = logging.getLogger(__name__) SOCKS5 = 'http://jabber.org/protocol/bytestreams' IBB = 'http://jabber.org/protocol/ibb' class XEP_0095(BasePlugin): name = 'xep_0095' description = 'XEP-0095: Stream Initiation' dependencies = {'xep_0020', 'xep_0030', 'xep_0047', 'xep_0065'} stanza = stanza def plugin_init(self): self._profiles = {} self._methods = {} self._methods_order = [] self._pending_lock = threading.Lock() self._pending= {} self.register_method(SOCKS5, 'xep_0065', 100) self.register_method(IBB, 'xep_0047', 50) register_stanza_plugin(Iq, SI) register_stanza_plugin(SI, self.xmpp['xep_0020'].stanza.FeatureNegotiation) self.xmpp.register_handler( Callback('SI Request', StanzaPath('iq@type=set/si'), self._handle_request)) self.api.register(self._add_pending, 'add_pending', default=True) self.api.register(self._get_pending, 'get_pending', default=True) self.api.register(self._del_pending, 'del_pending', default=True) def session_bind(self, jid): self.xmpp['xep_0030'].add_feature(SI.namespace) def plugin_end(self): self.xmpp.remove_handler('SI Request') self.xmpp['xep_0030'].del_feature(feature=SI.namespace) def register_profile(self, profile_name, plugin): self._profiles[profile_name] = plugin def unregister_profile(self, profile_name): try: del self._profiles[profile_name] except KeyError: pass def register_method(self, method, plugin_name, order=50): self._methods[method] = (plugin_name, order) self._methods_order.append((order, method, plugin_name)) self._methods_order.sort() def unregister_method(self, method): if method in self._methods: plugin_name, order = self._methods[method] del self._methods[method] self._methods_order.remove((order, method, plugin_name)) self._methods_order.sort() def _handle_request(self, iq): profile = iq['si']['profile'] sid = iq['si']['id'] if not sid: raise XMPPError(etype='modify', condition='bad-request') if profile not in self._profiles: raise XMPPError( etype='modify', condition='bad-request', extension='bad-profile', extension_ns=SI.namespace) neg = iq['si']['feature_neg']['form'].get_fields() options = neg['stream-method']['options'] or [] methods = [] for opt in options: methods.append(opt['value']) for method in methods: if method in self._methods: supported = True break else: raise XMPPError('bad-request', extension='no-valid-streams', extension_ns=SI.namespace) selected_method = None log.debug('Available: %s', methods) for order, method, plugin in self._methods_order: log.debug('Testing: %s', method) if method in methods: selected_method = method break receiver = iq['to'] sender = iq['from'] self.api['add_pending'](receiver, sid, sender, { 'response_id': iq['id'], 'method': selected_method, 'profile': profile }) self.xmpp.event('si_request', iq) def offer(self, jid, sid=None, mime_type=None, profile=None, methods=None, payload=None, ifrom=None, **iqargs): if sid is None: sid = uuid4().hex if methods is None: methods = list(self._methods.keys()) if not isinstance(methods, (list, tuple, set)): methods = [methods] si = self.xmpp.Iq() si['to'] = jid si['from'] = ifrom si['type'] = 'set' si['si']['id'] = sid si['si']['mime_type'] = mime_type si['si']['profile'] = profile if not isinstance(payload, (list, tuple, set)): payload = [payload] for item in payload: si['si'].append(item) si['si']['feature_neg']['form'].add_field( var='stream-method', ftype='list-single', options=methods) return si.send(**iqargs) def accept(self, jid, sid, payload=None, ifrom=None, stream_handler=None): stream = self.api['get_pending'](ifrom, sid, jid) iq = self.xmpp.Iq() iq['id'] = stream['response_id'] iq['to'] = jid iq['from'] = ifrom iq['type'] = 'result' if payload: iq['si'].append(payload) iq['si']['feature_neg']['form']['type'] = 'submit' iq['si']['feature_neg']['form'].add_field( var='stream-method', ftype='list-single', value=stream['method']) if ifrom is None: ifrom = self.xmpp.boundjid method_plugin = self._methods[stream['method']][0] self.xmpp[method_plugin].api['preauthorize_sid'](ifrom, sid, jid) self.api['del_pending'](ifrom, sid, jid) if stream_handler: self.xmpp.add_event_handler('stream:%s:%s' % (sid, jid), stream_handler, disposable=True) return iq.send() def decline(self, jid, sid, ifrom=None): stream = self.api['get_pending'](ifrom, sid, jid) if not stream: return iq = self.xmpp.Iq() iq['id'] = stream['response_id'] iq['to'] = jid iq['from'] = ifrom iq['type'] = 'error' iq['error']['condition'] = 'forbidden' iq['error']['text'] = 'Offer declined' self.api['del_pending'](ifrom, sid, jid) return iq.send() def _add_pending(self, jid, node, ifrom, data): with self._pending_lock: self._pending[(jid, node, ifrom)] = data def _get_pending(self, jid, node, ifrom, data): with self._pending_lock: return self._pending.get((jid, node, ifrom), None) def _del_pending(self, jid, node, ifrom, data): with self._pending_lock: if (jid, node, ifrom) in self._pending: del self._pending[(jid, node, ifrom)] slixmpp-slix-1.4.2/slixmpp/plugins/xep_0096/000077500000000000000000000000001342457644200206315ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0096/__init__.py000066400000000000000000000006441342457644200227460ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0096 import stanza from slixmpp.plugins.xep_0096.stanza import File from slixmpp.plugins.xep_0096.file_transfer import XEP_0096 register_plugin(XEP_0096) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0096/file_transfer.py000066400000000000000000000033651342457644200240350ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp import Iq, Message from slixmpp.plugins import BasePlugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.xmlstream import register_stanza_plugin, JID from slixmpp.plugins.xep_0096 import stanza, File log = logging.getLogger(__name__) class XEP_0096(BasePlugin): name = 'xep_0096' description = 'XEP-0096: SI File Transfer' dependencies = {'xep_0095'} stanza = stanza def plugin_init(self): register_stanza_plugin(self.xmpp['xep_0095'].stanza.SI, File) self.xmpp['xep_0095'].register_profile(File.namespace, self) def session_bind(self, jid): self.xmpp['xep_0030'].add_feature(File.namespace) def plugin_end(self): self.xmpp['xep_0030'].del_feature(feature=File.namespace) self.xmpp['xep_0095'].unregister_profile(File.namespace, self) def request_file_transfer(self, jid, sid=None, name=None, size=None, desc=None, hash=None, date=None, allow_ranged=False, mime_type=None, **iqargs): data = File() data['name'] = name data['size'] = size data['date'] = date data['desc'] = desc data['hash'] = hash if allow_ranged: data.enable('range') return self.xmpp['xep_0095'].offer(jid, sid=sid, mime_type=mime_type, profile=File.namespace, payload=data, **iqargs) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0096/stanza.py000066400000000000000000000023711342457644200225060ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import datetime as dt from slixmpp.xmlstream import ElementBase, register_stanza_plugin from slixmpp.plugins import xep_0082 class File(ElementBase): name = 'file' namespace = 'http://jabber.org/protocol/si/profile/file-transfer' plugin_attrib = 'file' interfaces = {'name', 'size', 'date', 'hash', 'desc'} sub_interfaces = {'desc'} def set_size(self, value): self._set_attr('size', str(value)) def get_date(self): timestamp = self._get_attr('date') return xep_0082.parse(timestamp) def set_date(self, value): if isinstance(value, dt.datetime): value = xep_0082.format_datetime(value) self._set_attr('date', value) class Range(ElementBase): name = 'range' namespace = 'http://jabber.org/protocol/si/profile/file-transfer' plugin_attrib = 'range' interfaces = {'length', 'offset'} def set_length(self, value): self._set_attr('length', str(value)) def set_offset(self, value): self._set_attr('offset', str(value)) register_stanza_plugin(File, Range) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0106.py000066400000000000000000000011311342457644200211670ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins import BasePlugin, register_plugin class XEP_0106(BasePlugin): name = 'xep_0106' description = 'XEP-0106: JID Escaping' dependencies = {'xep_0030'} def session_bind(self, jid): self.xmpp['xep_0030'].add_feature(feature='jid\\20escaping') def plugin_end(self): self.xmpp['xep_0030'].del_feature(feature='jid\\20escaping') register_plugin(XEP_0106) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0107/000077500000000000000000000000001342457644200206225ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0107/__init__.py000066400000000000000000000006441342457644200227370ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0107 import stanza from slixmpp.plugins.xep_0107.stanza import UserMood from slixmpp.plugins.xep_0107.user_mood import XEP_0107 register_plugin(XEP_0107) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0107/stanza.py000066400000000000000000000042461342457644200225020ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase, ET class UserMood(ElementBase): name = 'mood' namespace = 'http://jabber.org/protocol/mood' plugin_attrib = 'mood' interfaces = {'value', 'text'} sub_interfaces = {'text'} moods = {'afraid', 'amazed', 'amorous', 'angry', 'annoyed', 'anxious', 'aroused', 'ashamed', 'bored', 'brave', 'calm', 'cautious', 'cold', 'confident', 'confused', 'contemplative', 'contented', 'cranky', 'crazy', 'creative', 'curious', 'dejected', 'depressed', 'disappointed', 'disgusted', 'dismayed', 'distracted', 'embarrassed', 'envious', 'excited', 'flirtatious', 'frustrated', 'grateful', 'grieving', 'grumpy', 'guilty', 'happy', 'hopeful', 'hot', 'humbled', 'humiliated', 'hungry', 'hurt', 'impressed', 'in_awe', 'in_love', 'indignant', 'interested', 'intoxicated', 'invincible', 'jealous', 'lonely', 'lost', 'lucky', 'mean', 'moody', 'nervous', 'neutral', 'offended', 'outraged', 'playful', 'proud', 'relaxed', 'relieved', 'remorseful', 'restless', 'sad', 'sarcastic', 'satisfied', 'serious', 'shocked', 'shy', 'sick', 'sleepy', 'spontaneous', 'stressed', 'strong', 'surprised', 'thankful', 'thirsty', 'tired', 'undefined', 'weak', 'worried'} def set_value(self, value): self.del_value() if value in self.moods: self._set_sub_text(value, '', keep=True) else: raise ValueError('Unknown mood value') def get_value(self): for child in self.xml: if child.tag.startswith('{%s}' % self.namespace): elem_name = child.tag.split('}')[-1] if elem_name in self.moods: return elem_name return '' def del_value(self): curr_value = self.get_value() if curr_value: self._set_sub_text(curr_value, '', keep=False) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0107/user_mood.py000066400000000000000000000062211342457644200231710ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp import Message from slixmpp.xmlstream import register_stanza_plugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import MatchXPath from slixmpp.plugins.base import BasePlugin from slixmpp.plugins.xep_0107 import stanza, UserMood log = logging.getLogger(__name__) class XEP_0107(BasePlugin): """ XEP-0107: User Mood """ name = 'xep_0107' description = 'XEP-0107: User Mood' dependencies = {'xep_0163'} stanza = stanza def plugin_init(self): register_stanza_plugin(Message, UserMood) def plugin_end(self): self.xmpp['xep_0030'].del_feature(feature=UserMood.namespace) self.xmpp['xep_0163'].remove_interest(UserMood.namespace) def session_bind(self, jid): self.xmpp['xep_0163'].register_pep('user_mood', UserMood) def publish_mood(self, value=None, text=None, options=None, ifrom=None, callback=None, timeout=None, timeout_callback=None): """ Publish the user's current mood. Arguments: value -- The name of the mood to publish. text -- Optional natural-language description or reason for the mood. options -- Optional form of publish options. ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ mood = UserMood() mood['value'] = value mood['text'] = text self.xmpp['xep_0163'].publish(mood, node=UserMood.namespace, options=options, ifrom=ifrom, callback=callback, timeout=timeout, timeout_callback=timeout_callback) def stop(self, ifrom=None, callback=None, timeout=None, timeout_callback=None): """ Clear existing user mood information to stop notifications. Arguments: ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ mood = UserMood() self.xmpp['xep_0163'].publish(mood, node=UserMood.namespace, ifrom=ifrom, callback=callback, timeout=timeout, timeout_callback=timeout_callback) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0108/000077500000000000000000000000001342457644200206235ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0108/__init__.py000066400000000000000000000006541342457644200227410ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0108 import stanza from slixmpp.plugins.xep_0108.stanza import UserActivity from slixmpp.plugins.xep_0108.user_activity import XEP_0108 register_plugin(XEP_0108) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0108/stanza.py000066400000000000000000000066231342457644200225040ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase, ET class UserActivity(ElementBase): name = 'activity' namespace = 'http://jabber.org/protocol/activity' plugin_attrib = 'activity' interfaces = {'value', 'text'} sub_interfaces = {'text'} general = {'doing_chores', 'drinking', 'eating', 'exercising', 'grooming', 'having_appointment', 'inactive', 'relaxing', 'talking', 'traveling', 'undefined', 'working'} specific = {'at_the_spa', 'brushing_teeth', 'buying_groceries', 'cleaning', 'coding', 'commuting', 'cooking', 'cycling', 'dancing', 'day_off', 'doing_maintenance', 'doing_the_dishes', 'doing_the_laundry', 'driving', 'fishing', 'gaming', 'gardening', 'getting_a_haircut', 'going_out', 'hanging_out', 'having_a_beer', 'having_a_snack', 'having_breakfast', 'having_coffee', 'having_dinner', 'having_lunch', 'having_tea', 'hiding', 'hiking', 'in_a_car', 'in_a_meeting', 'in_real_life', 'jogging', 'on_a_bus', 'on_a_plane', 'on_a_train', 'on_a_trip', 'on_the_phone', 'on_vacation', 'on_video_phone', 'other', 'partying', 'playing_sports', 'praying', 'reading', 'rehearsing', 'running', 'running_an_errand', 'scheduled_holiday', 'shaving', 'shopping', 'skiing', 'sleeping', 'smoking', 'socializing', 'studying', 'sunbathing', 'swimming', 'taking_a_bath', 'taking_a_shower', 'thinking', 'walking', 'walking_the_dog', 'watching_a_movie', 'watching_tv', 'working_out', 'writing'} def set_value(self, value): self.del_value() general = value specific = None if isinstance(value, tuple) or isinstance(value, list): general = value[0] specific = value[1] if general in self.general: gen_xml = ET.Element('{%s}%s' % (self.namespace, general)) if specific: spec_xml = ET.Element('{%s}%s' % (self.namespace, specific)) if specific in self.specific: gen_xml.append(spec_xml) else: raise ValueError('Unknown specific activity') self.xml.append(gen_xml) else: raise ValueError('Unknown general activity') def get_value(self): general = None specific = None gen_xml = None for child in self.xml: if child.tag.startswith('{%s}' % self.namespace): elem_name = child.tag.split('}')[-1] if elem_name in self.general: general = elem_name gen_xml = child if gen_xml is not None: for child in gen_xml: if child.tag.startswith('{%s}' % self.namespace): elem_name = child.tag.split('}')[-1] if elem_name in self.specific: specific = elem_name return (general, specific) def del_value(self): curr_value = self.get_value() if curr_value[0]: self._set_sub_text(curr_value[0], '', keep=False) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0108/user_activity.py000066400000000000000000000063101342457644200240670ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp.plugins.base import BasePlugin from slixmpp.plugins.xep_0108 import stanza, UserActivity log = logging.getLogger(__name__) class XEP_0108(BasePlugin): """ XEP-0108: User Activity """ name = 'xep_0108' description = 'XEP-0108: User Activity' dependencies = {'xep_0163'} stanza = stanza def plugin_end(self): self.xmpp['xep_0030'].del_feature(feature=UserActivity.namespace) self.xmpp['xep_0163'].remove_interest(UserActivity.namespace) def session_bind(self, jid): self.xmpp['xep_0163'].register_pep('user_activity', UserActivity) def publish_activity(self, general, specific=None, text=None, options=None, ifrom=None, callback=None, timeout=None, timeout_callback=None): """ Publish the user's current activity. Arguments: general -- The required general category of the activity. specific -- Optional specific activity being done as part of the general category. text -- Optional natural-language description or reason for the activity. options -- Optional form of publish options. ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ activity = UserActivity() activity['value'] = (general, specific) activity['text'] = text self.xmpp['xep_0163'].publish(activity, node=UserActivity.namespace, options=options, ifrom=ifrom, callback=callback, timeout=timeout, timeout_callback=timeout_callback) def stop(self, ifrom=None, callback=None, timeout=None, timeout_callback=None): """ Clear existing user activity information to stop notifications. Arguments: ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ activity = UserActivity() self.xmpp['xep_0163'].publish(activity, node=UserActivity.namespace, ifrom=ifrom, callback=callback, timeout=timeout, timeout_callback=timeout_callback) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0115/000077500000000000000000000000001342457644200206215ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0115/__init__.py000066400000000000000000000006561342457644200227410ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0115.stanza import Capabilities from slixmpp.plugins.xep_0115.static import StaticCaps from slixmpp.plugins.xep_0115.caps import XEP_0115 register_plugin(XEP_0115) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0115/caps.py000066400000000000000000000277011342457644200221300ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging import hashlib import base64 from slixmpp import __version__ from slixmpp.stanza import StreamFeatures, Presence, Iq from slixmpp.xmlstream import register_stanza_plugin, JID from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.util import MemoryCache from slixmpp import asyncio from slixmpp.exceptions import XMPPError, IqError, IqTimeout from slixmpp.plugins import BasePlugin from slixmpp.plugins.xep_0115 import stanza, StaticCaps log = logging.getLogger(__name__) class XEP_0115(BasePlugin): """ XEP-0115: Entity Capabilities """ name = 'xep_0115' description = 'XEP-0115: Entity Capabilities' dependencies = {'xep_0030', 'xep_0128', 'xep_0004'} stanza = stanza default_config = { 'hash': 'sha-1', 'caps_node': None, 'broadcast': True, 'cache': None, } def plugin_init(self): self.hashes = {'sha-1': hashlib.sha1, 'sha1': hashlib.sha1, 'md5': hashlib.md5} if self.caps_node is None: self.caps_node = 'http://slixmpp.com/ver/%s' % __version__ if self.cache is None: self.cache = MemoryCache() register_stanza_plugin(Presence, stanza.Capabilities) register_stanza_plugin(StreamFeatures, stanza.Capabilities) self._disco_ops = ['cache_caps', 'get_caps', 'assign_verstring', 'get_verstring', 'supports', 'has_identity'] self.xmpp.register_handler( Callback('Entity Capabilites', StanzaPath('presence/caps'), self._handle_caps)) self.xmpp.add_filter('out', self._filter_add_caps) self.xmpp.add_event_handler('entity_caps', self._process_caps) if not self.xmpp.is_component: self.xmpp.register_feature('caps', self._handle_caps_feature, restart=False, order=10010) disco = self.xmpp['xep_0030'] self.static = StaticCaps(self.xmpp, disco.static) for op in self._disco_ops: self.api.register(getattr(self.static, op), op, default=True) for op in ('supports', 'has_identity'): self.xmpp['xep_0030'].api.register(getattr(self.static, op), op) self._run_node_handler = disco._run_node_handler disco.cache_caps = self.cache_caps disco.update_caps = self.update_caps disco.assign_verstring = self.assign_verstring disco.get_verstring = self.get_verstring def plugin_end(self): self.xmpp['xep_0030'].del_feature(feature=stanza.Capabilities.namespace) self.xmpp.del_filter('out', self._filter_add_caps) self.xmpp.del_event_handler('entity_caps', self._process_caps) self.xmpp.remove_handler('Entity Capabilities') if not self.xmpp.is_component: self.xmpp.unregister_feature('caps', 10010) for op in ('supports', 'has_identity'): self.xmpp['xep_0030'].restore_defaults(op) def session_bind(self, jid): self.xmpp['xep_0030'].add_feature(stanza.Capabilities.namespace) def _filter_add_caps(self, stanza): if not isinstance(stanza, Presence) or not self.broadcast: return stanza if stanza['type'] not in ('available', 'chat', 'away', 'dnd', 'xa'): return stanza ver = self.get_verstring(stanza['from']) if ver: stanza['caps']['node'] = self.caps_node stanza['caps']['hash'] = self.hash stanza['caps']['ver'] = ver return stanza def _handle_caps(self, presence): if not self.xmpp.is_component: if presence['from'] == self.xmpp.boundjid: return self.xmpp.event('entity_caps', presence) def _handle_caps_feature(self, features): # We already have a method to process presence with # caps, so wrap things up and use that. p = Presence() p['from'] = self.xmpp.boundjid.domain p.append(features['caps']) self.xmpp.features.add('caps') self.xmpp.event('entity_caps', p) async def _process_caps(self, pres): if not pres['caps']['hash']: log.debug("Received unsupported legacy caps: %s, %s, %s", pres['caps']['node'], pres['caps']['ver'], pres['caps']['ext']) self.xmpp.event('entity_caps_legacy', pres) return ver = pres['caps']['ver'] existing_verstring = self.get_verstring(pres['from'].full) if str(existing_verstring) == str(ver): return existing_caps = self.get_caps(verstring=ver) if existing_caps is not None: self.assign_verstring(pres['from'], ver) return if pres['caps']['hash'] not in self.hashes: try: log.debug("Unknown caps hash: %s", pres['caps']['hash']) self.xmpp['xep_0030'].get_info(jid=pres['from']) return except XMPPError: return log.debug("New caps verification string: %s", ver) try: node = '%s#%s' % (pres['caps']['node'], ver) caps = await self.xmpp['xep_0030'].get_info(pres['from'], node, coroutine=True) if isinstance(caps, Iq): caps = caps['disco_info'] if self._validate_caps(caps, pres['caps']['hash'], pres['caps']['ver']): self.assign_verstring(pres['from'], pres['caps']['ver']) except XMPPError: log.debug("Could not retrieve disco#info results for caps for %s", node) def _validate_caps(self, caps, hash, check_verstring): # Check Identities full_ids = caps.get_identities(dedupe=False) deduped_ids = caps.get_identities() if len(full_ids) != len(deduped_ids): log.debug("Duplicate disco identities found, invalid for caps") return False # Check Features full_features = caps.get_features(dedupe=False) deduped_features = caps.get_features() if len(full_features) != len(deduped_features): log.debug("Duplicate disco features found, invalid for caps") return False # Check Forms form_types = [] deduped_form_types = set() for stanza in caps['substanzas']: if not isinstance(stanza, self.xmpp['xep_0004'].stanza.Form): log.debug("Non form extension found, ignoring for caps") caps.xml.remove(stanza.xml) continue if 'FORM_TYPE' in stanza.get_fields(): f_type = tuple(stanza.get_fields()['FORM_TYPE']['value']) form_types.append(f_type) deduped_form_types.add(f_type) if len(form_types) != len(deduped_form_types): log.debug("Duplicated FORM_TYPE values, " + \ "invalid for caps") return False if len(f_type) > 1: deduped_type = set(f_type) if len(f_type) != len(deduped_type): log.debug("Extra FORM_TYPE data, invalid for caps") return False if stanza.get_fields()['FORM_TYPE']['type'] != 'hidden': log.debug("Field FORM_TYPE type not 'hidden', " + \ "ignoring form for caps") caps.xml.remove(stanza.xml) else: log.debug("No FORM_TYPE found, ignoring form for caps") caps.xml.remove(stanza.xml) verstring = self.generate_verstring(caps, hash) if verstring != check_verstring: log.debug("Verification strings do not match: %s, %s" % ( verstring, check_verstring)) return False self.cache_caps(verstring, caps) return True def generate_verstring(self, info, hash): hash = self.hashes.get(hash, None) if hash is None: return None S = '' # Convert None to '' in the identities def clean_identity(id): return map(lambda i: i or '', id) identities = map(clean_identity, info['identities']) identities = sorted(('/'.join(i) for i in identities)) features = sorted(info['features']) S += '<'.join(identities) + '<' S += '<'.join(features) + '<' form_types = {} for stanza in info['substanzas']: if isinstance(stanza, self.xmpp['xep_0004'].stanza.Form): if 'FORM_TYPE' in stanza.get_fields(): f_type = stanza['values']['FORM_TYPE'] if len(f_type): f_type = f_type[0] if f_type not in form_types: form_types[f_type] = [] form_types[f_type].append(stanza) sorted_forms = sorted(form_types.keys()) for f_type in sorted_forms: for form in form_types[f_type]: S += '%s<' % f_type fields = sorted(form.get_fields().keys()) fields.remove('FORM_TYPE') for field in fields: S += '%s<' % field vals = form.get_fields()[field].get_value(convert=False) if vals is None: S += '<' else: if not isinstance(vals, list): vals = [vals] S += '<'.join(sorted(vals)) + '<' binary = hash(S.encode('utf8')).digest() return base64.b64encode(binary).decode('utf-8') async def update_caps(self, jid=None, node=None, preserve=False): try: info = await self.xmpp['xep_0030'].get_info(jid, node, local=True) if isinstance(info, Iq): info = info['disco_info'] ver = self.generate_verstring(info, self.hash) self.xmpp['xep_0030'].set_info( jid=jid, node='%s#%s' % (self.caps_node, ver), info=info) self.cache_caps(ver, info) self.assign_verstring(jid, ver) if self.xmpp.sessionstarted and self.broadcast: if self.xmpp.is_component or preserve: for contact in self.xmpp.roster[jid]: self.xmpp.roster[jid][contact].send_last_presence() else: self.xmpp.roster[jid].send_last_presence() except XMPPError: return def get_verstring(self, jid=None): if jid in ('', None): jid = self.xmpp.boundjid.full if isinstance(jid, JID): jid = jid.full return self.api['get_verstring'](jid) def assign_verstring(self, jid=None, verstring=None): if jid in (None, ''): jid = self.xmpp.boundjid.full if isinstance(jid, JID): jid = jid.full return self.api['assign_verstring'](jid, args={ 'verstring': verstring}) def cache_caps(self, verstring=None, info=None): data = {'verstring': verstring, 'info': info} return self.api['cache_caps'](args=data) def get_caps(self, jid=None, verstring=None): if verstring is None: if jid is not None: verstring = self.get_verstring(jid) else: return None if isinstance(jid, JID): jid = jid.full data = {'verstring': verstring} return self.api['get_caps'](jid, args=data) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0115/stanza.py000066400000000000000000000007001342457644200224700ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from __future__ import unicode_literals from slixmpp.xmlstream import ElementBase class Capabilities(ElementBase): namespace = 'http://jabber.org/protocol/caps' name = 'c' plugin_attrib = 'caps' interfaces = {'hash', 'node', 'ver', 'ext'} slixmpp-slix-1.4.2/slixmpp/plugins/xep_0115/static.py000066400000000000000000000122141342457644200224620ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp.xmlstream import JID from slixmpp.exceptions import IqError, IqTimeout log = logging.getLogger(__name__) class StaticCaps(object): """ Extend the default StaticDisco implementation to provide support for extended identity information. """ def __init__(self, xmpp, static): """ Augment the default XEP-0030 static handler object. Arguments: static -- The default static XEP-0030 handler object. """ self.xmpp = xmpp self.disco = self.xmpp['xep_0030'] self.caps = self.xmpp['xep_0115'] self.static = static self.jid_vers = {} def supports(self, jid, node, ifrom, data): """ Check if a JID supports a given feature. The data parameter may provide: feature -- The feature to check for support. local -- If true, then the query is for a JID/node combination handled by this Slixmpp instance and no stanzas need to be sent. Otherwise, a disco stanza must be sent to the remove JID to retrieve the info. cached -- If true, then look for the disco info data from the local cache system. If no results are found, send the query as usual. The self.use_cache setting must be set to true for this option to be useful. If set to false, then the cache will be skipped, even if a result has already been cached. Defaults to false. """ feature = data.get('feature', None) data = {'local': data.get('local', False), 'cached': data.get('cached', True)} if not feature: return False if node in (None, ''): info = self.caps.get_caps(jid) if info and feature in info['features']: return True try: info = self.disco.get_info(jid=jid, node=node, ifrom=ifrom, **data) info = self.disco._wrap(ifrom, jid, info, True) return feature in info['disco_info']['features'] except IqError: return False except IqTimeout: return None def has_identity(self, jid, node, ifrom, data): """ Check if a JID has a given identity. The data parameter may provide: category -- The category of the identity to check. itype -- The type of the identity to check. lang -- The language of the identity to check. local -- If true, then the query is for a JID/node combination handled by this Slixmpp instance and no stanzas need to be sent. Otherwise, a disco stanza must be sent to the remove JID to retrieve the info. cached -- If true, then look for the disco info data from the local cache system. If no results are found, send the query as usual. The self.use_cache setting must be set to true for this option to be useful. If set to false, then the cache will be skipped, even if a result has already been cached. Defaults to false. """ identity = (data.get('category', None), data.get('itype', None), data.get('lang', None)) data = {'local': data.get('local', False), 'cached': data.get('cached', True)} trunc = lambda i: (i[0], i[1], i[2]) if node in (None, ''): info = self.caps.get_caps(jid) if info and identity in map(trunc, info['identities']): return True try: info = self.disco.get_info(jid=jid, node=node, ifrom=ifrom, **data) info = self.disco._wrap(ifrom, jid, info, True) return identity in map(trunc, info['disco_info']['identities']) except IqError: return False except IqTimeout: return None def cache_caps(self, jid, node, ifrom, data): verstring = data.get('verstring', None) info = data.get('info', None) if not verstring or not info: return self.caps.cache.store(verstring, info) def assign_verstring(self, jid, node, ifrom, data): if isinstance(jid, JID): jid = jid.full self.jid_vers[jid] = data.get('verstring', None) def get_verstring(self, jid, node, ifrom, data): return self.jid_vers.get(jid, None) def get_caps(self, jid, node, ifrom, data): verstring = data.get('verstring', None) if verstring is None: return None return self.caps.cache.retrieve(verstring) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0118/000077500000000000000000000000001342457644200206245ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0118/__init__.py000066400000000000000000000006441342457644200227410ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0118 import stanza from slixmpp.plugins.xep_0118.stanza import UserTune from slixmpp.plugins.xep_0118.user_tune import XEP_0118 register_plugin(XEP_0118) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0118/stanza.py000066400000000000000000000012251342457644200224760ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase, ET class UserTune(ElementBase): name = 'tune' namespace = 'http://jabber.org/protocol/tune' plugin_attrib = 'tune' interfaces = {'artist', 'length', 'rating', 'source', 'title', 'track', 'uri'} sub_interfaces = interfaces def set_length(self, value): self._set_sub_text('length', str(value)) def set_rating(self, value): self._set_sub_text('rating', str(value)) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0118/user_tune.py000066400000000000000000000065611342457644200232170ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp.plugins.base import BasePlugin from slixmpp.plugins.xep_0118 import stanza, UserTune log = logging.getLogger(__name__) class XEP_0118(BasePlugin): """ XEP-0118: User Tune """ name = 'xep_0118' description = 'XEP-0118: User Tune' dependencies = {'xep_0163'} stanza = stanza def plugin_end(self): self.xmpp['xep_0030'].del_feature(feature=UserTune.namespace) self.xmpp['xep_0163'].remove_interest(UserTune.namespace) def session_bind(self, jid): self.xmpp['xep_0163'].register_pep('user_tune', UserTune) def publish_tune(self, artist=None, length=None, rating=None, source=None, title=None, track=None, uri=None, options=None, ifrom=None, callback=None, timeout=None, timeout_callback=None): """ Publish the user's current tune. Arguments: artist -- The artist or performer of the song. length -- The length of the song in seconds. rating -- The user's rating of the song (from 1 to 10) source -- The album name, website, or other source of the song. title -- The title of the song. track -- The song's track number, or other unique identifier. uri -- A URL to more information about the song. options -- Optional form of publish options. ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ tune = UserTune() tune['artist'] = artist tune['length'] = length tune['rating'] = rating tune['source'] = source tune['title'] = title tune['track'] = track tune['uri'] = uri return self.xmpp['xep_0163'].publish(tune, node=UserTune.namespace, options=options, ifrom=ifrom, callback=callback, timeout=timeout, timeout_callback=timeout_callback) def stop(self, ifrom=None, callback=None, timeout=None, timeout_callback=None): """ Clear existing user tune information to stop notifications. Arguments: ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ tune = UserTune() return self.xmpp['xep_0163'].publish(tune, node=UserTune.namespace, ifrom=ifrom, callback=callback, timeout=timeout, timeout_callback=timeout_callback) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0122/000077500000000000000000000000001342457644200206175ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0122/__init__.py000066400000000000000000000003071342457644200227300ustar00rootroot00000000000000 from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0122.stanza import FormValidation from slixmpp.plugins.xep_0122.data_validation import XEP_0122 register_plugin(XEP_0122) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0122/data_validation.py000066400000000000000000000010251342457644200243120ustar00rootroot00000000000000from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins import BasePlugin from slixmpp.plugins.xep_0004 import stanza from slixmpp.plugins.xep_0004.stanza import FormField from slixmpp.plugins.xep_0122.stanza import FormValidation class XEP_0122(BasePlugin): """ XEP-0122: Data Forms """ name = 'xep_0122' description = 'XEP-0122: Data Forms Validation' dependencies = {'xep_0004'} stanza = stanza def plugin_init(self): register_stanza_plugin(FormField, FormValidation) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0122/stanza.py000066400000000000000000000053311342457644200224730ustar00rootroot00000000000000from slixmpp.xmlstream import ElementBase, ET class FormValidation(ElementBase): """ Validation values for form fields. Example: 2003-10-06T11:22:00-07:00 Questions: Should this look at the datatype value and convert the range values as appropriate? Should this stanza provide a pass/fail for a value from the field, or convert field value to datatype? """ namespace = 'http://jabber.org/protocol/xdata-validate' name = 'validate' plugin_attrib = 'validate' interfaces = {'datatype', 'basic', 'open', 'range', 'regex', } sub_interfaces = {'basic', 'open', 'range', 'regex', } plugin_attrib_map = {} plugin_tag_map = {} def _add_field(self, name): self.remove_all() item_xml = ET.Element('{%s}%s' % (self.namespace, name)) self.xml.append(item_xml) return item_xml def set_basic(self, value): if value: self._add_field('basic') else: del self['basic'] def set_open(self, value): if value: self._add_field('open') else: del self['open'] def set_regex(self, regex): if regex: _regex = self._add_field('regex') _regex.text = regex else: del self['regex'] def set_range(self, value, minimum=None, maximum=None): if value: _range = self._add_field('range') _range.attrib['min'] = str(minimum) _range.attrib['max'] = str(maximum) else: del self['range'] def remove_all(self, except_tag=None): for a in self.sub_interfaces: if a != except_tag: del self[a] def get_basic(self): present = self.xml.find('{%s}basic' % self.namespace) return present is not None def get_open(self): present = self.xml.find('{%s}open' % self.namespace) return present is not None def get_regex(self): present = self.xml.find('{%s}regex' % self.namespace) if present is not None: return present.text return False def get_range(self): present = self.xml.find('{%s}range' % self.namespace) if present is not None: attributes = present.attrib return_value = dict() if 'min' in attributes: return_value['minimum'] = attributes['min'] if 'max' in attributes: return_value['maximum'] = attributes['max'] return return_value return False slixmpp-slix-1.4.2/slixmpp/plugins/xep_0128/000077500000000000000000000000001342457644200206255ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0128/__init__.py000066400000000000000000000006101342457644200227330ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0128.static import StaticExtendedDisco from slixmpp.plugins.xep_0128.extended_disco import XEP_0128 register_plugin(XEP_0128) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0128/extended_disco.py000066400000000000000000000062461342457644200241700ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging import slixmpp from slixmpp import Iq from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins import BasePlugin from slixmpp.plugins.xep_0004 import Form from slixmpp.plugins.xep_0030 import DiscoInfo from slixmpp.plugins.xep_0128 import StaticExtendedDisco class XEP_0128(BasePlugin): """ XEP-0128: Service Discovery Extensions Allow the use of data forms to add additional identity information to disco#info results. Also see . Attributes: disco -- A reference to the XEP-0030 plugin. static -- Object containing the default set of static node handlers. xmpp -- The main Slixmpp object. Methods: set_extended_info -- Set extensions to a disco#info result. add_extended_info -- Add an extension to a disco#info result. del_extended_info -- Remove all extensions from a disco#info result. """ name = 'xep_0128' description = 'XEP-0128: Service Discovery Extensions' dependencies = {'xep_0030', 'xep_0004'} def plugin_init(self): """Start the XEP-0128 plugin.""" self._disco_ops = ['set_extended_info', 'add_extended_info', 'del_extended_info'] register_stanza_plugin(DiscoInfo, Form, iterable=True) self.disco = self.xmpp['xep_0030'] self.static = StaticExtendedDisco(self.disco.static) self.disco.set_extended_info = self.set_extended_info self.disco.add_extended_info = self.add_extended_info self.disco.del_extended_info = self.del_extended_info for op in self._disco_ops: self.api.register(getattr(self.static, op), op, default=True) def set_extended_info(self, jid=None, node=None, **kwargs): """ Set additional, extended identity information to a node. Replaces any existing extended information. Arguments: jid -- The JID to modify. node -- The node to modify. data -- Either a form, or a list of forms to use as extended information, replacing any existing extensions. """ self.api['set_extended_info'](jid, node, None, kwargs) def add_extended_info(self, jid=None, node=None, **kwargs): """ Add additional, extended identity information to a node. Arguments: jid -- The JID to modify. node -- The node to modify. data -- Either a form, or a list of forms to add as extended information. """ self.api['add_extended_info'](jid, node, None, kwargs) def del_extended_info(self, jid=None, node=None, **kwargs): """ Remove all extended identity information to a node. Arguments: jid -- The JID to modify. node -- The node to modify. """ self.api['del_extended_info'](jid, node, None, kwargs) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0128/static.py000066400000000000000000000037041342457644200224720ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging import slixmpp from slixmpp.plugins.xep_0030 import StaticDisco log = logging.getLogger(__name__) class StaticExtendedDisco(object): """ Extend the default StaticDisco implementation to provide support for extended identity information. """ def __init__(self, static): """ Augment the default XEP-0030 static handler object. Arguments: static -- The default static XEP-0030 handler object. """ self.static = static def set_extended_info(self, jid, node, ifrom, data): """ Replace the extended identity data for a JID/node combination. The data parameter may provide: data -- Either a single data form, or a list of data forms. """ self.del_extended_info(jid, node, ifrom, data) self.add_extended_info(jid, node, ifrom, data) def add_extended_info(self, jid, node, ifrom, data): """ Add additional extended identity data for a JID/node combination. The data parameter may provide: data -- Either a single data form, or a list of data forms. """ self.static.add_node(jid, node) forms = data.get('data', []) if not isinstance(forms, list): forms = [forms] info = self.static.get_node(jid, node)['info'] for form in forms: info.append(form) def del_extended_info(self, jid, node, ifrom, data): """ Replace the extended identity data for a JID/node combination. The data parameter is not used. """ if self.static.node_exists(jid, node): info = self.static.get_node(jid, node)['info'] for form in info['substanza']: info.xml.remove(form.xml) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0131/000077500000000000000000000000001342457644200206175ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0131/__init__.py000066400000000000000000000006411342457644200227310ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0131 import stanza from slixmpp.plugins.xep_0131.stanza import Headers from slixmpp.plugins.xep_0131.headers import XEP_0131 register_plugin(XEP_0131) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0131/headers.py000066400000000000000000000024151342457644200226060ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp import Message, Presence from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins import BasePlugin from slixmpp.plugins.xep_0131 import stanza from slixmpp.plugins.xep_0131.stanza import Headers class XEP_0131(BasePlugin): name = 'xep_0131' description = 'XEP-0131: Stanza Headers and Internet Metadata' dependencies = {'xep_0030'} stanza = stanza default_config = { 'supported_headers': set() } def plugin_init(self): register_stanza_plugin(Message, Headers) register_stanza_plugin(Presence, Headers) def plugin_end(self): self.xmpp['xep_0030'].del_feature(feature=Headers.namespace) for header in self.supported_headers: self.xmpp['xep_0030'].del_feature( feature='%s#%s' % (Headers.namespace, header)) def session_bind(self, jid): self.xmpp['xep_0030'].add_feature(Headers.namespace) for header in self.supported_headers: self.xmpp['xep_0030'].add_feature('%s#%s' % ( Headers.namespace, header)) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0131/stanza.py000066400000000000000000000030731342457644200224740ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from collections import OrderedDict from slixmpp.xmlstream import ET, ElementBase class Headers(ElementBase): name = 'headers' namespace = 'http://jabber.org/protocol/shim' plugin_attrib = 'headers' interfaces = {'headers'} is_extension = True def get_headers(self): result = OrderedDict() headers = self.xml.findall('{%s}header' % self.namespace) for header in headers: name = header.attrib.get('name', '') value = header.text if name in result: if not isinstance(result[name], set): result[name] = [result[name]] else: result[name] = [] result[name].add(value) else: result[name] = value return result def set_headers(self, values): self.del_headers() for name in values: vals = values[name] if not isinstance(vals, (list, set)): vals = [values[name]] for value in vals: header = ET.Element('{%s}header' % self.namespace) header.attrib['name'] = name header.text = value self.xml.append(header) def del_headers(self): headers = self.xml.findall('{%s}header' % self.namespace) for header in headers: self.xml.remove(header) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0133.py000066400000000000000000000035521342457644200212000ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins import BasePlugin, register_plugin class XEP_0133(BasePlugin): name = 'xep_0133' description = 'XEP-0133: Service Administration' dependencies = {'xep_0030', 'xep_0004', 'xep_0050'} commands = {'add-user', 'delete-user', 'disable-user', 'reenable-user', 'end-user-session', 'get-user-password', 'change-user-password', 'get-user-roster', 'get-user-lastlogin', 'user-stats', 'edit-blacklist', 'edit-whitelist', 'get-registered-users-num', 'get-disabled-users-num', 'get-online-users-num', 'get-active-users-num', 'get-idle-users-num', 'get-registered-users-list', 'get-disabled-users-list', 'get-online-users-list', 'get-online-users', 'get-active-users', 'get-idle-userslist', 'announce', 'set-motd', 'edit-motd', 'delete-motd', 'set-welcome', 'delete-welcome', 'edit-admin', 'restart', 'shutdown'} def get_commands(self, jid=None, **kwargs): if jid is None: jid = self.xmpp.boundjid.server return self.xmpp['xep_0050'].get_commands(jid, **kwargs) def create_command(name): def admin_command(self, jid=None, session=None, ifrom=None): if jid is None: jid = self.xmpp.boundjid.server self.xmpp['xep_0050'].start_command( jid=jid, node='http://jabber.org/protocol/admin#%s' % name, session=session, ifrom=ifrom) return admin_command for cmd in XEP_0133.commands: setattr(XEP_0133, cmd.replace('-', '_'), create_command(cmd)) register_plugin(XEP_0133) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0138.py000066400000000000000000000101061342457644200211760ustar00rootroot00000000000000""" slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz This file is part of slixmpp. See the file LICENSE for copying permission. """ import logging import zlib from slixmpp.stanza import StreamFeatures from slixmpp.xmlstream import RestartStream, register_stanza_plugin, ElementBase, StanzaBase from slixmpp.xmlstream.matcher import * from slixmpp.xmlstream.handler import * from slixmpp.plugins import BasePlugin, register_plugin log = logging.getLogger(__name__) class Compression(ElementBase): name = 'compression' namespace = 'http://jabber.org/features/compress' interfaces = {'methods'} plugin_attrib = 'compression' plugin_tag_map = {} plugin_attrib_map = {} def get_methods(self): methods = [] for method in self.xml.findall('{%s}method' % self.namespace): methods.append(method.text) return methods class Compress(StanzaBase): name = 'compress' namespace = 'http://jabber.org/protocol/compress' interfaces = {'method'} sub_interfaces = interfaces plugin_attrib = 'compress' plugin_tag_map = {} plugin_attrib_map = {} def setup(self, xml): StanzaBase.setup(self, xml) self.xml.tag = self.tag_name() class Compressed(StanzaBase): name = 'compressed' namespace = 'http://jabber.org/protocol/compress' interfaces = set() plugin_tag_map = {} plugin_attrib_map = {} def setup(self, xml): StanzaBase.setup(self, xml) self.xml.tag = self.tag_name() class ZlibSocket(object): def __init__(self, socketobj): self.__socket = socketobj self.compressor = zlib.compressobj() self.decompressor = zlib.decompressobj(zlib.MAX_WBITS) def __getattr__(self, name): return getattr(self.__socket, name) def send(self, data): sentlen = len(data) data = self.compressor.compress(data) data += self.compressor.flush(zlib.Z_SYNC_FLUSH) log.debug(b'>>> (compressed)' + (data.encode("hex"))) #return self.__socket.send(data) sentactuallen = self.__socket.send(data) assert(sentactuallen == len(data)) return sentlen def recv(self, *args, **kwargs): data = self.__socket.recv(*args, **kwargs) log.debug(b'<<< (compressed)' + data.encode("hex")) return self.decompressor.decompress(self.decompressor.unconsumed_tail + data) class XEP_0138(BasePlugin): """ XEP-0138: Compression """ name = "xep_0138" description = "XEP-0138: Compression" dependencies = {"xep_0030"} def plugin_init(self): self.xep = '0138' self.description = 'Stream Compression (Generic)' self.compression_methods = {'zlib': True} register_stanza_plugin(StreamFeatures, Compression) self.xmpp.register_stanza(Compress) self.xmpp.register_stanza(Compressed) self.xmpp.register_handler( Callback('Compressed', StanzaPath('compressed'), self._handle_compressed, instream=True)) self.xmpp.register_feature('compression', self._handle_compression, restart=True, order=self.config.get('order', 5)) def register_compression_method(self, name, handler): self.compression_methods[name] = handler def _handle_compression(self, features): for method in features['compression']['methods']: if method in self.compression_methods: log.info('Attempting to use %s compression' % method) c = Compress(self.xmpp) c['method'] = method c.send(now=True) return True return False def _handle_compressed(self, stanza): self.xmpp.features.add('compression') log.debug('Stream Compressed!') compressed_socket = ZlibSocket(self.xmpp.socket) self.xmpp.set_socket(compressed_socket) raise RestartStream() def _handle_failure(self, stanza): pass xep_0138 = XEP_0138 register_plugin(XEP_0138) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0152/000077500000000000000000000000001342457644200206225ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0152/__init__.py000066400000000000000000000006531342457644200227370ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0152 import stanza from slixmpp.plugins.xep_0152.stanza import Reachability from slixmpp.plugins.xep_0152.reachability import XEP_0152 register_plugin(XEP_0152) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0152/reachability.py000066400000000000000000000063631342457644200236440ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp.plugins.base import BasePlugin from slixmpp.plugins.xep_0152 import stanza, Reachability log = logging.getLogger(__name__) class XEP_0152(BasePlugin): """ XEP-0152: Reachability Addresses """ name = 'xep_0152' description = 'XEP-0152: Reachability Addresses' dependencies = {'xep_0163'} stanza = stanza def plugin_end(self): self.xmpp['xep_0030'].del_feature(feature=Reachability.namespace) self.xmpp['xep_0163'].remove_interest(Reachability.namespace) def session_bind(self, jid): self.xmpp['xep_0163'].register_pep('reachability', Reachability) def publish_reachability(self, addresses, options=None, ifrom=None, callback=None, timeout=None, timeout_callback=None): """ Publish alternative addresses where the user can be reached. Arguments: addresses -- A list of dictionaries containing the URI and optional description for each address. options -- Optional form of publish options. ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ if not isinstance(addresses, (list, tuple)): addresses = [addresses] reach = Reachability() for address in addresses: if not hasattr(address, 'items'): address = {'uri': address} addr = stanza.Address() for key, val in address.items(): addr[key] = val reach.append(addr) return self.xmpp['xep_0163'].publish(reach, node=Reachability.namespace, options=options, ifrom=ifrom, callback=callback, timeout=timeout, timeout_callback=timeout_callback) def stop(self, ifrom=None, callback=None, timeout=None, timeout_callback=None): """ Clear existing user activity information to stop notifications. Arguments: ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ reach = Reachability() return self.xmpp['xep_0163'].publish(reach, node=Reachability.namespace, ifrom=ifrom, callback=callback, timeout=timeout, timeout_callback=timeout_callback) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0152/stanza.py000066400000000000000000000012771342457644200225030ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase, register_stanza_plugin class Reachability(ElementBase): name = 'reach' namespace = 'urn:xmpp:reach:0' plugin_attrib = 'reach' interfaces = set() class Address(ElementBase): name = 'addr' namespace = 'urn:xmpp:reach:0' plugin_attrib = 'address' plugin_multi_attrib = 'addresses' interfaces = {'uri', 'desc'} lang_interfaces = {'desc'} sub_interfaces = {'desc'} register_stanza_plugin(Reachability, Address, iterable=True) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0153/000077500000000000000000000000001342457644200206235ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0153/__init__.py000066400000000000000000000006021342457644200227320ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0153.stanza import VCardTempUpdate from slixmpp.plugins.xep_0153.vcard_avatar import XEP_0153 register_plugin(XEP_0153) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0153/stanza.py000066400000000000000000000013611342457644200224760ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase class VCardTempUpdate(ElementBase): name = 'x' namespace = 'vcard-temp:x:update' plugin_attrib = 'vcard_temp_update' interfaces = {'photo'} sub_interfaces = interfaces def set_photo(self, value): if value is not None: self._set_sub_text('photo', value, keep=True) else: self._del_sub('photo') def get_photo(self): photo = self.xml.find('{%s}photo' % self.namespace) if photo is None: return None return photo.text slixmpp-slix-1.4.2/slixmpp/plugins/xep_0153/vcard_avatar.py000066400000000000000000000146771342457644200236510ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import hashlib import logging from slixmpp.stanza import Presence from slixmpp.exceptions import XMPPError, IqTimeout from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins.base import BasePlugin from slixmpp.plugins.xep_0153 import stanza, VCardTempUpdate from slixmpp import asyncio, future_wrapper log = logging.getLogger(__name__) class XEP_0153(BasePlugin): name = 'xep_0153' description = 'XEP-0153: vCard-Based Avatars' dependencies = {'xep_0054'} stanza = stanza def plugin_init(self): self._hashes = {} register_stanza_plugin(Presence, VCardTempUpdate) self.xmpp.add_filter('out', self._update_presence) self.xmpp.add_event_handler('session_start', self._start) self.xmpp.add_event_handler('session_end', self._end) self.xmpp.add_event_handler('presence_available', self._recv_presence) self.xmpp.add_event_handler('presence_dnd', self._recv_presence) self.xmpp.add_event_handler('presence_xa', self._recv_presence) self.xmpp.add_event_handler('presence_chat', self._recv_presence) self.xmpp.add_event_handler('presence_away', self._recv_presence) self.api.register(self._set_hash, 'set_hash', default=True) self.api.register(self._get_hash, 'get_hash', default=True) self.api.register(self._reset_hash, 'reset_hash', default=True) def plugin_end(self): self.xmpp.del_filter('out', self._update_presence) self.xmpp.del_event_handler('session_start', self._start) self.xmpp.del_event_handler('session_end', self._end) self.xmpp.del_event_handler('presence_available', self._recv_presence) self.xmpp.del_event_handler('presence_dnd', self._recv_presence) self.xmpp.del_event_handler('presence_xa', self._recv_presence) self.xmpp.del_event_handler('presence_chat', self._recv_presence) self.xmpp.del_event_handler('presence_away', self._recv_presence) @future_wrapper def set_avatar(self, jid=None, avatar=None, mtype=None, timeout=None, callback=None, timeout_callback=None): if jid is None: jid = self.xmpp.boundjid.bare future = asyncio.Future() def propagate_timeout_exception(fut): try: fut.done() except IqTimeout as e: future.set_exception(e) def custom_callback(result): vcard = result['vcard_temp'] vcard['PHOTO']['TYPE'] = mtype vcard['PHOTO']['BINVAL'] = avatar new_future = self.xmpp['xep_0054'].publish_vcard(jid=jid, vcard=vcard, timeout=timeout, callback=next_callback, timeout_callback=timeout_callback) new_future.add_done_callback(propagate_timeout_exception) def next_callback(result): if result['type'] == 'error': future.set_exception(result) else: self.api['reset_hash'](jid) self.xmpp.roster[jid].send_last_presence() future.set_result(result) first_future = self.xmpp['xep_0054'].get_vcard(jid, cached=False, timeout=timeout, callback=custom_callback, timeout_callback=timeout_callback) first_future.add_done_callback(propagate_timeout_exception) return future async def _start(self, event): try: vcard = await self.xmpp['xep_0054'].get_vcard(self.xmpp.boundjid.bare) data = vcard['vcard_temp']['PHOTO']['BINVAL'] if not data: new_hash = '' else: new_hash = hashlib.sha1(data).hexdigest() self.api['set_hash'](self.xmpp.boundjid, args=new_hash) except XMPPError: log.debug('Could not retrieve vCard for %s', self.xmpp.boundjid.bare) def _end(self, event): pass def _update_presence(self, stanza): if not isinstance(stanza, Presence): return stanza if stanza['type'] not in ('available', 'dnd', 'chat', 'away', 'xa'): return stanza current_hash = self.api['get_hash'](stanza['from']) stanza['vcard_temp_update']['photo'] = current_hash return stanza def _reset_hash(self, jid, node, ifrom, args): own_jid = (jid.bare == self.xmpp.boundjid.bare) if self.xmpp.is_component: own_jid = (jid.domain == self.xmpp.boundjid.domain) self.api['set_hash'](jid, args=None) if own_jid: self.xmpp.roster[jid].send_last_presence() def callback(iq): if iq['type'] == 'error': log.debug('Could not retrieve vCard for %s', jid) return try: data = iq['vcard_temp']['PHOTO']['BINVAL'] except ValueError: log.debug('Invalid BINVAL in vCard’s PHOTO for %s:', jid, exc_info=True) data = None if not data: new_hash = '' else: new_hash = hashlib.sha1(data).hexdigest() self.api['set_hash'](jid, args=new_hash) self.xmpp['xep_0054'].get_vcard(jid=jid.bare, ifrom=ifrom, callback=callback) def _recv_presence(self, pres): try: if pres['muc']['affiliation']: # Don't process vCard avatars for MUC occupants # since they all share the same bare JID. return except: pass if not pres.match('presence/vcard_temp_update'): self.api['set_hash'](pres['from'], args=None) return data = pres['vcard_temp_update']['photo'] if data is None: return self.xmpp.event('vcard_avatar_update', pres) # ================================================================= def _get_hash(self, jid, node, ifrom, args): return self._hashes.get(jid.bare, None) def _set_hash(self, jid, node, ifrom, args): self._hashes[jid.bare] = args slixmpp-slix-1.4.2/slixmpp/plugins/xep_0163.py000066400000000000000000000111651342457644200212020ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp import asyncio from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins.base import BasePlugin, register_plugin log = logging.getLogger(__name__) class XEP_0163(BasePlugin): """ XEP-0163: Personal Eventing Protocol (PEP) """ name = 'xep_0163' description = 'XEP-0163: Personal Eventing Protocol (PEP)' dependencies = {'xep_0030', 'xep_0060', 'xep_0115'} def register_pep(self, name, stanza): """ Setup and configure events and stanza registration for the given PEP stanza: - Add disco feature for the PEP content. - Register disco interest in the PEP content. - Map events from the PEP content's namespace to the given name. :param str name: The event name prefix to use for PEP events. :param stanza: The stanza class for the PEP content. """ pubsub_stanza = self.xmpp['xep_0060'].stanza register_stanza_plugin(pubsub_stanza.EventItem, stanza) self.add_interest(stanza.namespace) self.xmpp['xep_0030'].add_feature(stanza.namespace) self.xmpp['xep_0060'].map_node_event(stanza.namespace, name) def add_interest(self, namespace, jid=None): """ Mark an interest in a PEP subscription by including a disco feature with the '+notify' extension. Arguments: namespace -- The base namespace to register as an interest, such as 'http://jabber.org/protocol/tune'. This may also be a list of such namespaces. jid -- Optionally specify the JID. """ if not isinstance(namespace, set) and not isinstance(namespace, list): namespace = [namespace] for ns in namespace: self.xmpp['xep_0030'].add_feature('%s+notify' % ns, jid=jid) asyncio.ensure_future( self.xmpp['xep_0115'].update_caps(jid), loop=self.xmpp.loop, ) def remove_interest(self, namespace, jid=None): """ Mark an interest in a PEP subscription by including a disco feature with the '+notify' extension. Arguments: namespace -- The base namespace to remove as an interest, such as 'http://jabber.org/protocol/tune'. This may also be a list of such namespaces. jid -- Optionally specify the JID. """ if not isinstance(namespace, (set, list)): namespace = [namespace] for ns in namespace: self.xmpp['xep_0030'].del_feature(jid=jid, feature='%s+notify' % namespace) asyncio.ensure_future( self.xmpp['xep_0115'].update_caps(jid), loop=self.xmpp.loop, ) def publish(self, stanza, node=None, id=None, options=None, ifrom=None, timeout_callback=None, callback=None, timeout=None): """ Publish a PEP update. This is just a (very) thin wrapper around the XEP-0060 publish() method to set the defaults expected by PEP. Arguments: stanza -- The PEP update stanza to publish. node -- The node to publish the item to. If not specified, the stanza's namespace will be used. id -- Optionally specify the ID of the item. options -- A form of publish options. ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ if node is None: node = stanza.namespace if id is None: id = 'current' return self.xmpp['xep_0060'].publish(ifrom, node, id=id, payload=stanza.xml, options=options, ifrom=ifrom, callback=callback, timeout=timeout, timeout_callback=timeout_callback) register_plugin(XEP_0163) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0172/000077500000000000000000000000001342457644200206245ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0172/__init__.py000066400000000000000000000006441342457644200227410ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0172 import stanza from slixmpp.plugins.xep_0172.stanza import UserNick from slixmpp.plugins.xep_0172.user_nick import XEP_0172 register_plugin(XEP_0172) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0172/stanza.py000066400000000000000000000040101342457644200224710ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase, ET class UserNick(ElementBase): """ XEP-0172: User Nickname allows the addition of a element in several stanza types, including and stanzas. The nickname contained in a should be the global, friendly or informal name chosen by the owner of a bare JID. The element may be included when establishing communications with new entities, such as normal XMPP users or MUC services. The nickname contained in a element will not necessarily be the same as the nickname used in a MUC. Example stanzas: The User ... The User Stanza Interface: nick -- A global, friendly or informal name chosen by a user. Methods: setup -- Overrides ElementBase.setup. get_nick -- Return the nickname in the element. set_nick -- Add a element with the given nickname. del_nick -- Remove the element. """ namespace = 'http://jabber.org/protocol/nick' name = 'nick' plugin_attrib = name interfaces = {'nick'} def set_nick(self, nick): """ Add a element with the given nickname. Arguments: nick -- A human readable, informal name. """ self.xml.text = nick def get_nick(self): """Return the nickname in the element.""" return self.xml.text def del_nick(self): """Remove the element.""" if self.parent is not None: self.parent().xml.remove(self.xml) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0172/user_nick.py000066400000000000000000000062101342457644200231570ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp.stanza.message import Message from slixmpp.stanza.presence import Presence from slixmpp.xmlstream import register_stanza_plugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import MatchXPath from slixmpp.plugins.base import BasePlugin from slixmpp.plugins.xep_0172 import stanza, UserNick log = logging.getLogger(__name__) class XEP_0172(BasePlugin): """ XEP-0172: User Nickname """ name = 'xep_0172' description = 'XEP-0172: User Nickname' dependencies = {'xep_0163'} stanza = stanza def plugin_init(self): register_stanza_plugin(Message, UserNick) register_stanza_plugin(Presence, UserNick) def plugin_end(self): self.xmpp['xep_0030'].del_feature(feature=UserNick.namespace) self.xmpp['xep_0163'].remove_interest(UserNick.namespace) def session_bind(self, jid): self.xmpp['xep_0163'].register_pep('user_nick', UserNick) def publish_nick(self, nick=None, options=None, ifrom=None, timeout_callback=None, callback=None, timeout=None): """ Publish the user's current nick. Arguments: nick -- The user nickname to publish. options -- Optional form of publish options. ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ nickname = UserNick() nickname['nick'] = nick self.xmpp['xep_0163'].publish(nickname, node=UserNick.namespace, options=options, ifrom=ifrom, callback=callback, timeout=timeout, timeout_callback=timeout_callback) def stop(self, ifrom=None, timeout_callback=None, callback=None, timeout=None): """ Clear existing user nick information to stop notifications. Arguments: ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ nick = UserNick() return self.xmpp['xep_0163'].publish(nick, node=UserNick.namespace, ifrom=ifrom, callback=callback, timeout=timeout, timeout_callback=timeout_callback) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0184/000077500000000000000000000000001342457644200206275ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0184/__init__.py000066400000000000000000000006061342457644200227420ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Erik Reuterborg Larsson, Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0184.stanza import Request, Received from slixmpp.plugins.xep_0184.receipt import XEP_0184 register_plugin(XEP_0184) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0184/receipt.py000066400000000000000000000074471342457644200226500ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Erik Reuterborg Larsson, Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp.stanza import Message from slixmpp.xmlstream import register_stanza_plugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.plugins import BasePlugin from slixmpp.plugins.xep_0184 import stanza, Request, Received class XEP_0184(BasePlugin): """ XEP-0184: Message Delivery Receipts """ name = 'xep_0184' description = 'XEP-0184: Message Delivery Receipts' dependencies = {'xep_0030'} stanza = stanza default_config = { 'auto_ack': True, 'auto_request': False } ack_types = ('normal', 'chat', 'headline') def plugin_init(self): register_stanza_plugin(Message, Request) register_stanza_plugin(Message, Received) self.xmpp.add_filter('out', self._filter_add_receipt_request) self.xmpp.register_handler( Callback('Message Receipt', StanzaPath('message/receipt'), self._handle_receipt_received)) self.xmpp.register_handler( Callback('Message Receipt Request', StanzaPath('message/request_receipt'), self._handle_receipt_request)) def plugin_end(self): self.xmpp['xep_0030'].del_feature('urn:xmpp:receipts') self.xmpp.del_filter('out', self._filter_add_receipt_request) self.xmpp.remove_handler('Message Receipt') self.xmpp.remove_handler('Message Receipt Request') def session_bind(self, jid): self.xmpp['xep_0030'].add_feature('urn:xmpp:receipts') def ack(self, msg): """ Acknowledge a message by sending a receipt. Arguments: msg -- The message to acknowledge. """ ack = self.xmpp.Message() ack['to'] = msg['from'] ack['receipt'] = msg['id'] ack.send() def _handle_receipt_received(self, msg): self.xmpp.event('receipt_received', msg) def _handle_receipt_request(self, msg): """ Auto-ack message receipt requests if ``self.auto_ack`` is ``True``. Arguments: msg -- The incoming message requesting a receipt. """ if self.auto_ack: if msg['type'] in self.ack_types: if not msg['receipt']: self.ack(msg) def _filter_add_receipt_request(self, stanza): """ Auto add receipt requests to outgoing messages, if: - ``self.auto_request`` is set to ``True`` - The message is not for groupchat - The message does not contain a receipt acknowledgment - The recipient is a bare JID or, if a full JID, one that has the ``urn:xmpp:receipts`` feature enabled The disco cache is checked if a full JID is specified in the outgoing message, which may mean a round-trip disco#info delay for the first message sent to the JID if entity caps are not used. """ if not self.auto_request: return stanza if not isinstance(stanza, Message): return stanza if stanza['request_receipt']: return stanza if not stanza['type'] in self.ack_types: return stanza if stanza['receipt']: return stanza if not stanza['body']: return stanza if stanza['to'].resource: if not self.xmpp['xep_0030'].supports(stanza['to'], feature='urn:xmpp:receipts', cached=True): return stanza stanza['request_receipt'] = True return stanza slixmpp-slix-1.4.2/slixmpp/plugins/xep_0184/stanza.py000066400000000000000000000037511342457644200225070ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Erik Reuterborg Larsson, Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream.stanzabase import ElementBase, ET class Request(ElementBase): namespace = 'urn:xmpp:receipts' name = 'request' plugin_attrib = 'request_receipt' interfaces = {'request_receipt'} sub_interfaces = interfaces is_extension = True def setup(self, xml=None): self.xml = ET.Element('') return True def set_request_receipt(self, val): self.del_request_receipt() if val: parent = self.parent() parent._set_sub_text("{%s}request" % self.namespace, keep=True) if not parent['id']: if parent.stream: parent['id'] = parent.stream.new_id() def get_request_receipt(self): parent = self.parent() if parent.xml.find("{%s}request" % self.namespace) is not None: return True else: return False def del_request_receipt(self): self.parent()._del_sub("{%s}request" % self.namespace) class Received(ElementBase): namespace = 'urn:xmpp:receipts' name = 'received' plugin_attrib = 'receipt' interfaces = {'receipt'} sub_interfaces = interfaces is_extension = True def setup(self, xml=None): self.xml = ET.Element('') return True def set_receipt(self, value): self.del_receipt() if value: parent = self.parent() xml = ET.Element("{%s}received" % self.namespace) xml.attrib['id'] = value parent.append(xml) def get_receipt(self): parent = self.parent() xml = parent.xml.find("{%s}received" % self.namespace) if xml is not None: return xml.attrib.get('id', '') return '' def del_receipt(self): self.parent()._del_sub('{%s}received' % self.namespace) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0186/000077500000000000000000000000001342457644200206315ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0186/__init__.py000066400000000000000000000006661342457644200227520ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0186 import stanza from slixmpp.plugins.xep_0186.stanza import Invisible, Visible from slixmpp.plugins.xep_0186.invisible_command import XEP_0186 register_plugin(XEP_0186) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0186/invisible_command.py000066400000000000000000000022671342457644200246740ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp import Iq from slixmpp.plugins import BasePlugin from slixmpp.xmlstream import register_stanza_plugin from slixmpp.plugins.xep_0186 import stanza, Visible, Invisible log = logging.getLogger(__name__) class XEP_0186(BasePlugin): name = 'xep_0186' description = 'XEP-0186: Invisible Command' dependencies = {'xep_0030'} def plugin_init(self): register_stanza_plugin(Iq, Visible) register_stanza_plugin(Iq, Invisible) def set_invisible(self, ifrom=None, callback=None, timeout=None): iq = self.xmpp.Iq() iq['type'] = 'set' iq['from'] = ifrom iq.enable('invisible') return iq.send(callback=callback, timeout=timeout) def set_visible(self, ifrom=None, callback=None, timeout=None): iq = self.xmpp.Iq() iq['type'] = 'set' iq['from'] = ifrom iq.enable('visible') return iq.send(callback=callback, timeout=timeout) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0186/stanza.py000066400000000000000000000010151342457644200225000ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase, ET class Invisible(ElementBase): name = 'invisible' namespace = 'urn:xmpp:invisible:0' plugin_attrib = 'invisible' interfaces = set() class Visible(ElementBase): name = 'visible' namespace = 'urn:xmpp:visible:0' plugin_attrib = 'visible' interfaces = set() slixmpp-slix-1.4.2/slixmpp/plugins/xep_0191/000077500000000000000000000000001342457644200206255ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0191/__init__.py000066400000000000000000000006101342457644200227330ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0191.stanza import Block, Unblock, BlockList from slixmpp.plugins.xep_0191.blocking import XEP_0191 register_plugin(XEP_0191) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0191/blocking.py000066400000000000000000000052651342457644200227770ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp import Iq from slixmpp.plugins import BasePlugin from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.xmlstream import register_stanza_plugin, JID from slixmpp.plugins.xep_0191 import stanza, Block, Unblock, BlockList log = logging.getLogger(__name__) class XEP_0191(BasePlugin): name = 'xep_0191' description = 'XEP-0191: Blocking Command' dependencies = {'xep_0030'} stanza = stanza def plugin_init(self): register_stanza_plugin(Iq, BlockList) register_stanza_plugin(Iq, Block) register_stanza_plugin(Iq, Unblock) self.xmpp.register_handler( Callback('Blocked Contact', StanzaPath('iq@type=set/block'), self._handle_blocked)) self.xmpp.register_handler( Callback('Unblocked Contact', StanzaPath('iq@type=set/unblock'), self._handle_unblocked)) def plugin_end(self): self.xmpp.remove_handler('Blocked Contact') self.xmpp.remove_handler('Unblocked Contact') def get_blocked(self, ifrom=None, timeout=None, callback=None, timeout_callback=None): iq = self.xmpp.Iq() iq['type'] = 'get' iq['from'] = ifrom iq.enable('blocklist') return iq.send(timeout=timeout, callback=callback, timeout_callback=timeout_callback) def block(self, jids, ifrom=None, timeout=None, callback=None, timeout_callback=None): iq = self.xmpp.Iq() iq['type'] = 'set' iq['from'] = ifrom if not isinstance(jids, (set, list)): jids = [jids] iq['block']['items'] = jids return iq.send(timeout=timeout, callback=callback, timeout_callback=timeout_callback) def unblock(self, jids=None, ifrom=None, timeout=None, callback=None, timeout_callback=None): iq = self.xmpp.Iq() iq['type'] = 'set' iq['from'] = ifrom if jids is None: jids = [] if not isinstance(jids, (set, list)): jids = [jids] iq['unblock']['items'] = jids return iq.send(timeout=timeout, callback=callback, timeout_callback=timeout_callback) def _handle_blocked(self, iq): self.xmpp.event('blocked', iq) def _handle_unblocked(self, iq): self.xmpp.event('unblocked', iq) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0191/stanza.py000066400000000000000000000024171342457644200225030ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ET, ElementBase, JID class BlockList(ElementBase): name = 'blocklist' namespace = 'urn:xmpp:blocking' plugin_attrib = 'blocklist' interfaces = {'items'} def get_items(self): result = set() items = self.xml.findall('{%s}item' % self.namespace) if items is not None: for item in items: jid = JID(item.attrib.get('jid', '')) if jid: result.add(jid) return result def set_items(self, values): self.del_items() for jid in values: if jid: item = ET.Element('{%s}item' % self.namespace) item.attrib['jid'] = JID(jid).full self.xml.append(item) def del_items(self): items = self.xml.findall('{%s}item' % self.namespace) if items is not None: for item in items: self.xml.remove(item) class Block(BlockList): name = 'block' plugin_attrib = 'block' class Unblock(BlockList): name = 'unblock' plugin_attrib = 'unblock' slixmpp-slix-1.4.2/slixmpp/plugins/xep_0196/000077500000000000000000000000001342457644200206325ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0196/__init__.py000066400000000000000000000006501342457644200227440ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0196 import stanza from slixmpp.plugins.xep_0196.stanza import UserGaming from slixmpp.plugins.xep_0196.user_gaming import XEP_0196 register_plugin(XEP_0196) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0196/stanza.py000066400000000000000000000010071342457644200225020ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.xmlstream import ElementBase, ET class UserGaming(ElementBase): name = 'gaming' namespace = 'urn:xmpp:gaming:0' plugin_attrib = 'gaming' interfaces = {'character_name', 'character_profile', 'name', 'level', 'server_address', 'server_name', 'uri'} sub_interfaces = interfaces slixmpp-slix-1.4.2/slixmpp/plugins/xep_0196/user_gaming.py000066400000000000000000000076571342457644200235230ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging from slixmpp.plugins.base import BasePlugin from slixmpp.plugins.xep_0196 import stanza, UserGaming log = logging.getLogger(__name__) class XEP_0196(BasePlugin): """ XEP-0196: User Gaming """ name = 'xep_0196' description = 'XEP-0196: User Gaming' dependencies = {'xep_0163'} stanza = stanza def plugin_end(self): self.xmpp['xep_0030'].del_feature(feature=UserGaming.namespace) self.xmpp['xep_0163'].remove_interest(UserGaming.namespace) def session_bind(self, jid): self.xmpp['xep_0163'].register_pep('user_gaming', UserGaming) def publish_gaming(self, name=None, level=None, server_name=None, uri=None, character_name=None, character_profile=None, server_address=None, options=None, ifrom=None, callback=None, timeout=None, timeout_callback=None): """ Publish the user's current gaming status. Arguments: name -- The name of the game. level -- The user's level in the game. uri -- A URI for the game or relevant gaming service server_name -- The name of the server where the user is playing. server_address -- The hostname or IP address of the server where the user is playing. character_name -- The name of the user's character in the game. character_profile -- A URI for a profile of the user's character. options -- Optional form of publish options. ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ gaming = UserGaming() gaming['name'] = name gaming['level'] = level gaming['uri'] = uri gaming['character_name'] = character_name gaming['character_profile'] = character_profile gaming['server_name'] = server_name gaming['server_address'] = server_address return self.xmpp['xep_0163'].publish(gaming, node=UserGaming.namespace, options=options, ifrom=ifrom, callback=callback, timeout=timeout, timeout_callback=timeout_callback) def stop(self, ifrom=None, callback=None, timeout=None, timeout_callback=None): """ Clear existing user gaming information to stop notifications. Arguments: ifrom -- Specify the sender's JID. timeout -- The length of time (in seconds) to wait for a response before exiting the send call if blocking is used. Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT callback -- Optional reference to a stream handler function. Will be executed when a reply stanza is received. """ gaming = UserGaming() return self.xmpp['xep_0163'].publish(gaming, node=UserGaming.namespace, ifrom=ifrom, callback=callback, timeout=timeout, timeout_callback=timeout_callback) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0198/000077500000000000000000000000001342457644200206345ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0198/__init__.py000066400000000000000000000011601342457644200227430ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0198.stanza import Enable, Enabled from slixmpp.plugins.xep_0198.stanza import Resume, Resumed from slixmpp.plugins.xep_0198.stanza import Failed from slixmpp.plugins.xep_0198.stanza import StreamManagement from slixmpp.plugins.xep_0198.stanza import Ack, RequestAck from slixmpp.plugins.xep_0198.stream_management import XEP_0198 register_plugin(XEP_0198) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0198/stanza.py000066400000000000000000000067261342457644200225210ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.stanza import Error from slixmpp.xmlstream import ElementBase, StanzaBase class Enable(StanzaBase): name = 'enable' namespace = 'urn:xmpp:sm:3' interfaces = {'max', 'resume'} def setup(self, xml): StanzaBase.setup(self, xml) self.xml.tag = self.tag_name() def get_resume(self): return self._get_attr('resume', 'false').lower() in ('true', '1') def set_resume(self, val): self._del_attr('resume') self._set_attr('resume', 'true' if val else 'false') class Enabled(StanzaBase): name = 'enabled' namespace = 'urn:xmpp:sm:3' interfaces = {'id', 'location', 'max', 'resume'} def setup(self, xml): StanzaBase.setup(self, xml) self.xml.tag = self.tag_name() def get_resume(self): return self._get_attr('resume', 'false').lower() in ('true', '1') def set_resume(self, val): self._del_attr('resume') self._set_attr('resume', 'true' if val else 'false') class Resume(StanzaBase): name = 'resume' namespace = 'urn:xmpp:sm:3' interfaces = {'h', 'previd'} def setup(self, xml): StanzaBase.setup(self, xml) self.xml.tag = self.tag_name() def get_h(self): h = self._get_attr('h', None) if h: return int(h) return None def set_h(self, val): self._set_attr('h', str(val)) class Resumed(StanzaBase): name = 'resumed' namespace = 'urn:xmpp:sm:3' interfaces = {'h', 'previd'} def setup(self, xml): StanzaBase.setup(self, xml) self.xml.tag = self.tag_name() def get_h(self): h = self._get_attr('h', None) if h: return int(h) return None def set_h(self, val): self._set_attr('h', str(val)) class Failed(StanzaBase, Error): name = 'failed' namespace = 'urn:xmpp:sm:3' interfaces = set() def setup(self, xml): StanzaBase.setup(self, xml) self.xml.tag = self.tag_name() class StreamManagement(ElementBase): name = 'sm' namespace = 'urn:xmpp:sm:3' plugin_attrib = name interfaces = {'required', 'optional'} def get_required(self): return self.xml.find('{%s}required' % self.namespace) is not None def set_required(self, val): self.del_required() if val: self._set_sub_text('required', '', keep=True) def del_required(self): self._del_sub('required') def get_optional(self): return self.xml.find('{%s}optional' % self.namespace) is not None def set_optional(self, val): self.del_optional() if val: self._set_sub_text('optional', '', keep=True) def del_optional(self): self._del_sub('optional') class RequestAck(StanzaBase): name = 'r' namespace = 'urn:xmpp:sm:3' interfaces = set() def setup(self, xml): StanzaBase.setup(self, xml) self.xml.tag = self.tag_name() class Ack(StanzaBase): name = 'a' namespace = 'urn:xmpp:sm:3' interfaces = {'h'} def setup(self, xml): StanzaBase.setup(self, xml) self.xml.tag = self.tag_name() def get_h(self): h = self._get_attr('h', None) if h: return int(h) return None def set_h(self, val): self._set_attr('h', str(val)) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0198/stream_management.py000066400000000000000000000257451342457644200247120ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ import asyncio import logging import collections from slixmpp.stanza import Message, Presence, Iq, StreamFeatures from slixmpp.xmlstream import register_stanza_plugin from slixmpp.xmlstream.handler import Callback, Waiter from slixmpp.xmlstream.matcher import MatchXPath, MatchMany from slixmpp.plugins.base import BasePlugin from slixmpp.plugins.xep_0198 import stanza log = logging.getLogger(__name__) MAX_SEQ = 2 ** 32 class XEP_0198(BasePlugin): """ XEP-0198: Stream Management """ name = 'xep_0198' description = 'XEP-0198: Stream Management' dependencies = set() stanza = stanza default_config = { #: The last ack number received from the server. 'last_ack': 0, #: The number of stanzas to wait between sending ack requests to #: the server. Setting this to ``1`` will send an ack request after #: every sent stanza. Defaults to ``5``. 'window': 5, #: The stream management ID for the stream. Knowing this value is #: required in order to do stream resumption. 'sm_id': None, #: A counter of handled incoming stanzas, mod 2^32. 'handled': 0, #: A counter of unacked outgoing stanzas, mod 2^32. 'seq': 0, #: Control whether or not the ability to resume the stream will be #: requested when enabling stream management. Defaults to ``True``. 'allow_resume': True, 'order': 10100, 'resume_order': 9000 } def plugin_init(self): """Start the XEP-0198 plugin.""" # Only enable stream management for non-components, # since components do not yet perform feature negotiation. if self.xmpp.is_component: return self.window_counter = self.window self.enabled = False self.unacked_queue = collections.deque() register_stanza_plugin(StreamFeatures, stanza.StreamManagement) self.xmpp.register_stanza(stanza.Enable) self.xmpp.register_stanza(stanza.Enabled) self.xmpp.register_stanza(stanza.Resume) self.xmpp.register_stanza(stanza.Resumed) self.xmpp.register_stanza(stanza.Ack) self.xmpp.register_stanza(stanza.RequestAck) # Only end the session when a
element is sent, # not just because the connection has died. self.xmpp.end_session_on_disconnect = False # Register the feature twice because it may be ordered two # different ways: enabling after binding and resumption # before binding. self.xmpp.register_feature('sm', self._handle_sm_feature, restart=True, order=self.order) self.xmpp.register_feature('sm', self._handle_sm_feature, restart=True, order=self.resume_order) self.xmpp.register_handler( Callback('Stream Management Enabled', MatchXPath(stanza.Enabled.tag_name()), self._handle_enabled, instream=True)) self.xmpp.register_handler( Callback('Stream Management Resumed', MatchXPath(stanza.Resumed.tag_name()), self._handle_resumed, instream=True)) self.xmpp.register_handler( Callback('Stream Management Failed', MatchXPath(stanza.Failed.tag_name()), self._handle_failed, instream=True)) self.xmpp.register_handler( Callback('Stream Management Ack', MatchXPath(stanza.Ack.tag_name()), self._handle_ack, instream=True)) self.xmpp.register_handler( Callback('Stream Management Request Ack', MatchXPath(stanza.RequestAck.tag_name()), self._handle_request_ack, instream=True)) self.xmpp.add_filter('in', self._handle_incoming) self.xmpp.add_filter('out_sync', self._handle_outgoing) self.xmpp.add_event_handler('session_end', self.session_end) def plugin_end(self): if self.xmpp.is_component: return self.xmpp.unregister_feature('sm', self.order) self.xmpp.unregister_feature('sm', self.resume_order) self.xmpp.del_event_handler('session_end', self.session_end) self.xmpp.del_filter('in', self._handle_incoming) self.xmpp.del_filter('out_sync', self._handle_outgoing) self.xmpp.remove_handler('Stream Management Enabled') self.xmpp.remove_handler('Stream Management Resumed') self.xmpp.remove_handler('Stream Management Failed') self.xmpp.remove_handler('Stream Management Ack') self.xmpp.remove_handler('Stream Management Request Ack') self.xmpp.remove_stanza(stanza.Enable) self.xmpp.remove_stanza(stanza.Enabled) self.xmpp.remove_stanza(stanza.Resume) self.xmpp.remove_stanza(stanza.Resumed) self.xmpp.remove_stanza(stanza.Ack) self.xmpp.remove_stanza(stanza.RequestAck) def session_end(self, event): """Reset stream management state.""" self.enabled = False self.unacked_queue.clear() self.sm_id = None self.handled = 0 self.seq = 0 self.last_ack = 0 def send_ack(self): """Send the current ack count to the server.""" ack = stanza.Ack(self.xmpp) ack['h'] = self.handled self.xmpp.send_raw(str(ack)) def request_ack(self, e=None): """Request an ack from the server.""" req = stanza.RequestAck(self.xmpp) self.xmpp.send_raw(str(req)) async def _handle_sm_feature(self, features): """ Enable or resume stream management. If no SM-ID is stored, and resource binding has taken place, stream management will be enabled. If an SM-ID is known, and the server allows resumption, the previous stream will be resumed. """ if 'stream_management' in self.xmpp.features: # We've already negotiated stream management, # so no need to do it again. return False if not self.sm_id: if 'bind' in self.xmpp.features: enable = stanza.Enable(self.xmpp) enable['resume'] = self.allow_resume enable.send() self.enabled = True self.handled = 0 self.unacked_queue.clear() waiter = Waiter('enabled_or_failed', MatchMany([ MatchXPath(stanza.Enabled.tag_name()), MatchXPath(stanza.Failed.tag_name())])) self.xmpp.register_handler(waiter) result = await waiter.wait() elif self.sm_id and self.allow_resume and 'bind' not in self.xmpp.features: self.enabled = True resume = stanza.Resume(self.xmpp) resume['h'] = self.handled resume['previd'] = self.sm_id resume.send() # Wait for a response before allowing stream feature processing # to continue. The actual result processing will be done in the # _handle_resumed() or _handle_failed() methods. waiter = Waiter('resumed_or_failed', MatchMany([ MatchXPath(stanza.Resumed.tag_name()), MatchXPath(stanza.Failed.tag_name())])) self.xmpp.register_handler(waiter) result = await waiter.wait() if result is not None and result.name == 'resumed': return True return False def _handle_enabled(self, stanza): """Save the SM-ID, if provided. Raises an :term:`sm_enabled` event. """ self.xmpp.features.add('stream_management') if stanza['id']: self.sm_id = stanza['id'] self.xmpp.event('sm_enabled', stanza) def _handle_resumed(self, stanza): """Finish resuming a stream by resending unacked stanzas. Raises a :term:`session_resumed` event. """ self.xmpp.features.add('stream_management') self._handle_ack(stanza) for id, stanza in self.unacked_queue: self.xmpp.send(stanza, use_filters=False) self.xmpp.event('session_resumed', stanza) def _handle_failed(self, stanza): """ Disable and reset any features used since stream management was requested (tracked stanzas may have been sent during the interval between the enable request and the enabled response). Raises an :term:`sm_failed` event. """ self.enabled = False self.unacked_queue.clear() self.xmpp.event('sm_failed', stanza) def _handle_ack(self, ack): """Process a server ack by freeing acked stanzas from the queue. Raises a :term:`stanza_acked` event for each acked stanza. """ if ack['h'] == self.last_ack: return num_acked = (ack['h'] - self.last_ack) % MAX_SEQ num_unacked = len(self.unacked_queue) log.debug("Ack: %s, Last Ack: %s, " + \ "Unacked: %s, Num Acked: %s, " + \ "Remaining: %s", ack['h'], self.last_ack, num_unacked, num_acked, num_unacked - num_acked) if num_acked > len(self.unacked_queue) or num_acked < 0: log.error('Inconsistent sequence numbers from the server,' ' ignoring and replacing ours with them.') num_acked = len(self.unacked_queue) for x in range(num_acked): seq, stanza = self.unacked_queue.popleft() self.xmpp.event('stanza_acked', stanza) self.last_ack = ack['h'] def _handle_request_ack(self, req): """Handle an ack request by sending an ack.""" self.send_ack() def _handle_incoming(self, stanza): """Increment the handled counter for each inbound stanza.""" if not self.enabled: return stanza if isinstance(stanza, (Message, Presence, Iq)): # Sequence numbers are mod 2^32 self.handled = (self.handled + 1) % MAX_SEQ return stanza def _handle_outgoing(self, stanza): """Store outgoing stanzas in a queue to be acked.""" if not self.enabled: return stanza if isinstance(stanza, (Message, Presence, Iq)): seq = None # Sequence numbers are mod 2^32 self.seq = (self.seq + 1) % MAX_SEQ seq = self.seq self.unacked_queue.append((seq, stanza)) self.window_counter -= 1 if self.window_counter == 0: self.window_counter = self.window self.request_ack() return stanza slixmpp-slix-1.4.2/slixmpp/plugins/xep_0199/000077500000000000000000000000001342457644200206355ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0199/__init__.py000066400000000000000000000005351342457644200227510ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0199.stanza import Ping from slixmpp.plugins.xep_0199.ping import XEP_0199 register_plugin(XEP_0199) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0199/ping.py000066400000000000000000000137611342457644200221540ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import time import logging from slixmpp.jid import JID from slixmpp.stanza import Iq from slixmpp import asyncio from slixmpp.exceptions import IqError, IqTimeout from slixmpp.xmlstream import register_stanza_plugin from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.xmlstream.handler import Callback from slixmpp.plugins import BasePlugin from slixmpp.plugins.xep_0199 import stanza, Ping log = logging.getLogger(__name__) class XEP_0199(BasePlugin): """ XEP-0199: XMPP Ping Given that XMPP is based on TCP connections, it is possible for the underlying connection to be terminated without the application's awareness. Ping stanzas provide an alternative to whitespace based keepalive methods for detecting lost connections. Also see . Attributes: keepalive -- If True, periodically send ping requests to the server. If a ping is not answered, the connection will be reset. interval -- Time in seconds between keepalive pings. Defaults to 300 seconds. timeout -- Time in seconds to wait for a ping response. Defaults to 30 seconds. Methods: send_ping -- Send a ping to a given JID, returning the round trip time. """ name = 'xep_0199' description = 'XEP-0199: XMPP Ping' dependencies = {'xep_0030'} stanza = stanza default_config = { 'keepalive': False, 'interval': 300, 'timeout': 30 } def plugin_init(self): """ Start the XEP-0199 plugin. """ register_stanza_plugin(Iq, Ping) self.xmpp.register_handler( Callback('Ping', StanzaPath('iq@type=get/ping'), self._handle_ping)) if self.keepalive: self.xmpp.add_event_handler('session_start', self.enable_keepalive) self.xmpp.add_event_handler('session_end', self.disable_keepalive) def plugin_end(self): self.xmpp['xep_0030'].del_feature(feature=Ping.namespace) self.xmpp.remove_handler('Ping') if self.keepalive: self.xmpp.del_event_handler('session_start', self.enable_keepalive) self.xmpp.del_event_handler('session_end', self.disable_keepalive) def session_bind(self, jid): self.xmpp['xep_0030'].add_feature(Ping.namespace) def enable_keepalive(self, interval=None, timeout=None): if interval: self.interval = interval if timeout: self.timeout = timeout self.keepalive = True handler = lambda event=None: asyncio.ensure_future( self._keepalive(event), loop=self.xmpp.loop, ) self.xmpp.schedule('Ping keepalive', self.interval, handler, repeat=True) def disable_keepalive(self, event=None): self.xmpp.cancel_schedule('Ping keepalive') async def _keepalive(self, event=None): log.debug("Keepalive ping...") try: rtt = await self.ping(self.xmpp.boundjid.host, timeout=self.timeout) except IqTimeout: log.debug("Did not receive ping back in time." + \ "Requesting Reconnect.") self.xmpp.reconnect() else: log.debug('Keepalive RTT: %s' % rtt) def _handle_ping(self, iq): """Automatically reply to ping requests.""" log.debug("Pinged by %s", iq['from']) iq.reply().send() def send_ping(self, jid, ifrom=None, timeout=None, callback=None, timeout_callback=None): """Send a ping request. Arguments: jid -- The JID that will receive the ping. ifrom -- Specifiy the sender JID. timeout -- Time in seconds to wait for a response. Defaults to self.timeout. callback -- Optional handler to execute when a pong is received. """ if not timeout: timeout = self.timeout iq = self.xmpp.Iq() iq['type'] = 'get' iq['to'] = jid iq['from'] = ifrom iq.enable('ping') return iq.send(timeout=timeout, callback=callback, timeout_callback=timeout_callback) async def ping(self, jid=None, ifrom=None, timeout=None): """Send a ping request and calculate RTT. This is a coroutine. Arguments: jid -- The JID that will receive the ping. ifrom -- Specifiy the sender JID. timeout -- Time in seconds to wait for a response. Defaults to self.timeout. """ own_host = False if not jid: if self.xmpp.is_component: jid = self.xmpp.server else: jid = self.xmpp.boundjid.host jid = JID(jid) if jid == self.xmpp.boundjid.host or \ self.xmpp.is_component and jid == self.xmpp.server: own_host = True if not timeout: timeout = self.timeout start = time.time() log.debug('Pinging %s' % jid) try: await self.send_ping(jid, ifrom=ifrom, timeout=timeout) except IqError as e: if own_host: rtt = time.time() - start log.debug('Pinged %s, RTT: %s', jid, rtt) return rtt else: raise e else: rtt = time.time() - start log.debug('Pinged %s, RTT: %s', jid, rtt) return rtt slixmpp-slix-1.4.2/slixmpp/plugins/xep_0199/stanza.py000066400000000000000000000014471342457644200225150ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import slixmpp from slixmpp.xmlstream import ElementBase class Ping(ElementBase): """ Given that XMPP is based on TCP connections, it is possible for the underlying connection to be terminated without the application's awareness. Ping stanzas provide an alternative to whitespace based keepalive methods for detecting lost connections. Example ping stanza: Stanza Interface: None Methods: None """ name = 'ping' namespace = 'urn:xmpp:ping' plugin_attrib = 'ping' interfaces = set() slixmpp-slix-1.4.2/slixmpp/plugins/xep_0202/000077500000000000000000000000001342457644200206165ustar00rootroot00000000000000slixmpp-slix-1.4.2/slixmpp/plugins/xep_0202/__init__.py000066400000000000000000000006411342457644200227300ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of Slixmpp. See the file LICENSE for copying permission. """ from slixmpp.plugins.base import register_plugin from slixmpp.plugins.xep_0202 import stanza from slixmpp.plugins.xep_0202.stanza import EntityTime from slixmpp.plugins.xep_0202.time import XEP_0202 register_plugin(XEP_0202) slixmpp-slix-1.4.2/slixmpp/plugins/xep_0202/stanza.py000066400000000000000000000071321342457644200224730ustar00rootroot00000000000000""" Slixmpp: The Slick XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of Slixmpp. See the file LICENSE for copying permission. """ import logging import datetime as dt from slixmpp.xmlstream import ElementBase from slixmpp.plugins import xep_0082 from slixmpp.thirdparty import tzutc, tzoffset class EntityTime(ElementBase): """ The