diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..88f9974 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +_build/* diff --git a/docs/_static/haiku.css b/docs/_static/haiku.css index 615ed47..3d8ee6a 100644 --- a/docs/_static/haiku.css +++ b/docs/_static/haiku.css @@ -59,9 +59,10 @@ body { margin: auto; padding: 0px; font-family: "Helvetica Neueu", Helvetica, sans-serif; - min-width: 59em; + min-width: 30em; max-width: 70em; color: #444; + text-align: center; } div.footer { @@ -124,21 +125,25 @@ a.headerlink:hover { /* basic text elements */ div.content { + margin: auto; margin-top: 20px; - margin-left: 40px; - margin-right: 40px; 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; @@ -185,12 +190,12 @@ div.topnav { z-index: 0; } div.topnav p { + margin: auto; margin-top: 0; - margin-left: 40px; - margin-right: 40px; margin-bottom: 0px; text-align: right; font-size: 0.8em; + width: 750px; } div.bottomnav { background: #eeeeee; @@ -404,3 +409,23 @@ div.viewcode-block:target { 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; +} diff --git a/docs/_static/images/from_&yet.png b/docs/_static/images/from_&yet.png new file mode 100644 index 0000000..ed5d8f3 Binary files /dev/null and b/docs/_static/images/from_&yet.png differ diff --git a/docs/_static/pygments.css b/docs/_static/pygments.css new file mode 100644 index 0000000..f04bc73 --- /dev/null +++ b/docs/_static/pygments.css @@ -0,0 +1,70 @@ +.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 */ diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html new file mode 100644 index 0000000..de6f724 --- /dev/null +++ b/docs/_templates/layout.html @@ -0,0 +1,70 @@ +{# + 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 %} + diff --git a/docs/conf.py b/docs/conf.py index dd83f24..72e39d0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,7 +50,7 @@ copyright = u'2011, Nathan Fritz, Lance Stout' # The short X.Y version. version = '1.0' # The full version, including alpha/beta/rc tags. -release = '1.0RC3' +release = '1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -91,7 +91,7 @@ pygments_style = 'tango' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'nature' +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 diff --git a/docs/getting_started/muc.rst b/docs/getting_started/muc.rst index 08f721f..26e1fa5 100644 --- a/docs/getting_started/muc.rst +++ b/docs/getting_started/muc.rst @@ -1,2 +1,208 @@ +.. _mucbot: + +========================= Mulit-User Chat (MUC) Bot ========================= + +.. note:: + + If you have any issues working through this quickstart guide + or the other tutorials here, please either send a message to the + `mailing list `_ + or join the chat room at `sleek@conference.jabber.org + `_. + +If you have not yet installed SleekXMPP, do so now by either checking out a version +from `Github `_, or installing it using ``pip`` +or ``easy_install``. + +.. code-block:: sh + + pip install sleekxmpp # Or: easy_install sleekxmpp + + +Now that you've got the basic gist of using SleekXMPP 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 sleekxmpp + + class MUCBot(sleekxmpp.ClientXMPP): + + def __init__(self, jid, password, room, nick): + sleekxmpp.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'].joinMUC(self.room, + self.nick, + wait=True) + +Note that as in :ref:`echobot`, we need to include send an initial presence and request +the roster. Next, we want to join the group chat, so we call the +``joinMUC`` method of the MUC plugin. + +.. 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): + sleekxmpp.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): + sleekxmpp.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: diff --git a/docs/index.rst b/docs/index.rst index fc6541d..fe7df7f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -59,6 +59,72 @@ SleekXMPP's design goals and philosphy are: 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 SleekXMPP Bot: +-------------------------------- + +.. code-block:: python + + import logging + + from sleekxmpp import ClientXMPP + from sleekxmpp.exceptions import IqError, IqTimeout + + + class EchoBot(ClientXMPP): + + def __init__(self, jid, password): + ClientXMPP.__init__(self, jid, password) + + self.add_event_handler("session_start", self.session_start) + self.add_event_handler("message", self.message) + + self.register_plugin('xep_0030') # Service Discovery + self.register_plugin('xep_0199') # XMPP Ping + + # Here's how to access plugins once you've registered them: + # self['xep_0030'].add_feature('echodemo') + # You can also use self.plugin['xep_0030'] + + # 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(block=True) + + + Getting Started (with Examples) ------------------------------- .. toctree:: diff --git a/examples/muc.py b/examples/muc.py index 8fe2eb4..cfb99c2 100755 --- a/examples/muc.py +++ b/examples/muc.py @@ -76,8 +76,13 @@ class MUCBot(sleekxmpp.ClientXMPP): event does not provide any additional data. """ +<<<<<<< HEAD self.getRoster() self.sendPresence() +======= + self.get_roster() + self.send_presence() +>>>>>>> docs self.plugin['xep_0045'].joinMUC(self.room, self.nick, # If a room password is needed, use: