The "DocBook is fun" commit. We now have a whole new DTD and stylesheets to

use, as well as all the docs being in SGML now.
This commit is contained in:
Daniel DiPaolo 2004-02-26 17:33:02 +00:00
parent 8ea62d2503
commit 151b2f829b
10 changed files with 1905 additions and 172 deletions

View File

@ -1,4 +1,4 @@
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook V4.1//EN">
<!DOCTYPE article SYSTEM "supybot.dtd">
<article>
<articleinfo>
@ -18,7 +18,7 @@
<revision>
<revnumber>0.1</revnumber>
<date>18 Feb 2004</date>
<revremark>Initial revision</revremark>
<revremark>Initial Docbook translation</revremark>
</revision>
</revhistory>
</articleinfo>
@ -48,7 +48,7 @@
the bot a command. Commands, rather than checking for a user
level of 100, or checking if the user has an <varname>o</varname>
flag, are instead able to check if a user has the
<varname>owner</varname> capability. At this point such a
<capability>owner</capability> capability. At this point such a
difference might not seem revolutionary, but at least we can
already tell that this method is self-documenting, and easier for
users and developers to understand what's truly going on.
@ -68,49 +68,49 @@
&ldquo;anticapability&rdquo; for that command. An anticapability is
a capability that, instead of saying &ldquo;what a user can
do&rdquo;, says what a user <emphasis>cannot</emphasis> do. It's
formed rather simply by adding a dash (&ldquo;-&rdquo;) to the
beginning of a capability; <varname>rot13</varname> is a
capability, and <varname>-rot13</varname> is an anticapability.
Anyway, when a user issues the bot a command, perhaps
<function>calc</function> or <function>help</function> the bot
first checks to make sure the user doesn't have the
<varname>-calc</varname> or the <varname>-help</varname>
capabilities before even considering responding to the user. So
commands can be turned on or off on a <emphasis>per
user</emphasis> basis, offering finegrained control not often (if
at all!) seen in other bots.
formed rather simply by adding a dash (&ldquo;-&rdquo;) to the
beginning of a capability; <botcommand>rot13</botcommand> is a
capability, and <botcommand>-rot13</botcommand> is an
anticapability. Anyway, when a user issues the bot a command,
perhaps <botcommand>calc</botcommand> or
<botcommand>help</botcommand>, the bot first checks to make sure
the user doesn't have the <capability>-calc</capability> or the
<capability>-help</capability> capabilities before even
considering responding to the user. So commands can be turned on
or off on a <emphasis>per user</emphasis> basis, offering
finegrained control not often (if at all!) seen in other bots.
</para>
<sect2>
<title>Channel capabilities</title>
<para>
But that's not all! The capabilities system also supports
<emphasis>Channel</emphasis> capabilities, which are
capabilities that only apply to a specific channel; they're of
the form <varname>#channel.capability</varname>. Whenever a
user issues a command to the bot in a channel, the command
dispatcher also checks to make sure the user doesn't have the
anticapability for that command <emphasis>in that
channel</emphasis> and if the user does, the bot won't respond
to the user in the channel. Thus now, in addition to having
the ability to turn individual commands on or off for an
individual user, we can now turn commands on or off for an
individual user on an individual channel!
capabilities that only apply to a specific channel; they're of
the form <capability>#channel.capability</capability>.
Whenever a user issues a command to the bot in a channel, the
command dispatcher also checks to make sure the user doesn't
have the anticapability for that command <emphasis>in that
channel</emphasis> and if the user does, the bot won't respond
to the user in the channel. Thus now, in addition to having
the ability to turn individual commands on or off for an
individual user, we can now turn commands on or off for an
individual user on an individual channel!
</para>
<para>
So when a user <varname>foo</varname> sends a command
<function>bar</function> to the bot on channel
<varname>#baz</varname>, first the bot checks to see if the
user has the anticapability for the command by itself,
<varname>-bar</varname>. If so, it returns right then and
there, compltely ignoring the fact that the user issued that
command to it. If the user doesn't have that anticapability,
then the bot checks to see if the user issued the command over
a channel, and if so, checks to see if the user has the
antichannelcapability for that command,
<varname>#baz.-bar</varname>. If so, again, he returns right
then and there and doesn't even think about responding to the
bot. If neither of these anticapabilities are present, then
the bot just responds to the user like normal.
So when a user <nick>foo</nick> sends a command
<botcommand>bar</botcommand> to the bot on channel
<channel>#baz</channel>, first the bot checks to see if the
user has the anticapability for the command by itself,
<capability>-bar</capability>. If so, it returns right then
and there, compltely ignoring the fact that the user issued
that command to it. If the user doesn't have that
anticapability, then the bot checks to see if the user issued
the command over a channel, and if so, checks to see if the
user has the antichannelcapability for that command,
<capability>#baz.-bar</capability>. If so, again, he returns
right then and there and doesn't even think about responding
to the bot. If neither of these anticapabilities are present,
then the bot just responds to the user like normal.
</para>
</sect2>
</sect1>
@ -148,14 +148,14 @@
<title>Hard-coded supybot capabilities</title>
<para>
There are several default capabilities the bot uses. The most
important of these is the <varname>owner</varname> capability.
This capability allows the person having it to use
<emphasis>any</emphasis> command. It's best to keep this
capability reserved to people who actually have access to the
shell the bot is running on.
important of these is the <capability>owner</capability>
capability. This capability allows the person having it to use
<emphasis>any</emphasis> command. It's best to keep this
capability reserved to people who actually have access to the
shell the bot is running on.
</para>
<para>
There is also the <varname>admin</varname> capability for
There is also the <capability>admin</capability> capability for
non-owners that are highly trusted to administer the bot
appropriately. They can do things such as change the bot's nick,
globally enable/disable commands, cause the bot to ignore a given
@ -164,40 +164,41 @@
people with the next capability.
</para>
<para>
People who are to administer channels with the bot should have the
<varname>#channel.op</varname> capability -- whatever channel they
are to administrate, they should have that channel capability for
<varname>op</varname>. For example, since I want
<varname>inkedmn</varname> to be an administrator in
<varname>#supybot</varname>, I'll give him the
<varname>#supybot.op</varname> capability. This is in addition to
his <varname>admin</varname> capability, since the
<varname>admin</varname> capability doesn't give the person having
it control over channels. <varname>#channel.op</varname> is used
for such things as giving/receiving ops, kickbanning people,
lobotomizing the bot, ignoring users in the channel, and managing
the channel capabilities. The <varname>#channel.op</varname>
capability is also basically the equivalent of the owner
capability for capabilities involving <varname>#channel</varname>
&ndash; basically anyone with the <varname>#channel.op</varname>
capability is considered to have all positive capabilities and no
negative capabilities for <varname>#channel</varname>.
People who are to administer channels with the bot should have the
<capability>#channel.op</capability> capability -- whatever
channel they are to administrate, they should have that channel
capability for <capability>op</capability>. For example, since I
want <nick>inkedmn</nick> to be an administrator in
<channel>#supybot</channel>, I'll give him the
<capability>#supybot.op</capability> capability. This is in
addition to his <capability>admin</capability> capability, since
the <capability>admin</capability> capability doesn't give the
person having it control over channels.
<capability>#channel.op</capability> is used for such things as
giving/receiving ops, kickbanning people, lobotomizing the bot,
ignoring users in the channel, and managing the channel
capabilities. The <capability>#channel.op</capability> capability
is also basically the equivalent of the owner capability for
capabilities involving <channel>#channel</channel> &ndash;
basically anyone with the <capability>#channel.op</capability>
capability is considered to have all positive capabilities and no
negative capabilities for <channel>#channel</channel>.
</para>
<para>
One other globally important capability exists:
<varname>trusted</varname>. This is a command that basically says
&ldquo;This user can be trusted not to try and crash the
bot.&rdquo; It allows users to call commands like
<function>Math.icalc</function>, which potentially could cause the
bot to begin a calculation that could potentially never return (a
calculation like 10**10**10**10). Another command that requires
the trusted capability is <function>Utilties.re</function>, which
(due to the regular expression implementation in Python (and any
other language that uses NFA regular expressions, like Perl or
Ruby or Lua or &hellip;) which can allow a regular expression to
take exponential time to process). Consider what would happen if
the someone gave the bot the command <literal>re [strjoin "" s/./
[dict go] /] [dict go]</literal>.
One other globally important capability exists:
<capability>trusted</capability>. This is a command that
basically says &ldquo;This user can be trusted not to try and
crash the bot.&rdquo; It allows users to call commands like
<botcommand>Math.icalc</botcommand>, which potentially could cause the
bot to begin a calculation that could potentially never return (a
calculation like 10**10**10**10). Another command that requires
the trusted capability is <botcommand>Utilties.re</botcommand>, which
(due to the regular expression implementation in Python (and any
other language that uses NFA regular expressions, like Perl or
Ruby or Lua or &hellip;) which can allow a regular expression to
take exponential time to process). Consider what would happen if
the someone gave the bot the command <literal>re [strjoin "" s/./
[dict go] /] [dict go]</literal>.
</para>
</sect1>
</article>

View File

@ -0,0 +1,311 @@
<!DOCTYPE article SYSTEM "supybot.dtd">
<article>
<articleinfo>
<authorgroup>
<author>
<firstname>Jeremiah</firstname>
<surname>Fincher</surname>
</author>
<author>
<firstname>Daniel</firstname>
<surname>DiPaolo</surname>
</author>
<editor>
<firstname>Daniel</firstname>
<surname>DiPaolo</surname>
<contrib>DocBook translator</contrib>
</editor>
</authorgroup>
<title>Supybot configuration system explanation</title>
<revhistory>
<revision>
<revnumber>0.1</revnumber>
<date>18 Feb 2004</date>
<revremark>Initial Docbook translation</revremark>
</revision>
<revision>
<revnumber>0.2</revnumber>
<date>26 Feb 2004</date>
<revremark>Conversion to Supybot DTD</revremark>
</revision>
</revhistory>
</articleinfo>
<sect1>
<title>Introduction</title>
<para>
So you've got your Supybot up and running and there are some
things you don't like about it. Fortunately for you, chances are
that these things are configurable, and this document is here to
tell you how to configure them.
</para>
<para>
Configuration of Supybot is handled via the
<plugin>Config</plugin> plugin, which controls runtime access to
Supybot's registry (the configuration file generated by the
<script>supybot-wizard</script> program you ran). The
<plugin>Config</plugin> plugin provides a way to get or set
variables, to list the available variables, and even to get help
for certain variables. Take a moment now to read the help for
each of those commands: <botcommand>get</botcommand>,
<botcommand>set</botcommand>, <botcommand>list</botcommand>, and
<botcommand>help</botcommand>. If you don't know how to get help on
those commands, go ahead and read our
<filename>GETTING_STARTED</filename> document before this one.
</para>
</sect1>
<sect1>
<title>Supybot's registry</title>
<para>
Now, if you're used to the Windows registry, don't worry,
Supybot's registry is completely different. For one, it's
completely plain text. There's no binary database sensitive to
corruption, it's not necessary to use another program to edit it
&ndash; all you need is a simple text editor. But there is at
least one good idea in Windows' registry: hierarchical
configuration. Supybot's configuration variables are organized in
a hierarchy: variables having to do with the way Supybot makes
replies all start with
<registrygroup>supybot.reply</registrygroup>; variables having to
do with the way a plugin works all start with
<registrygroup>supybot.plugins.Plugin</registrygroup> (where
<plugin>Plugin</plugin> is the name of the plugin in question).
This hierarchy is nice because it means the user isn't inundated
with hundreds of unrelated and unsorted configuration variables.
</para>
<para>
Some of the more important configuration values are located
directly under the base group,
<registrygroup>supybot</registrygroup>. Things like the bot's
nick, its ident, etc. Along with these config values are a few
subgroups that contain other values. Some of the more prominent
subgroups are: <registrygroup>plugins</registrygroup> (where all
the plugin-specific configuration is held),
<registrygroup>reply</registrygroup> (where variables affecting
the way a Supybot makes its replies resides),
<registrygroup>replies</registrygroup> (where all the specific
standard replies are kept), and
<registrygroup>directories</registrygroup> (where all the
directories a Supybot uses are defined). There are other
subgroups as well, but these are the ones we'll use in our
example.
</para>
<sect2>
<title>Config plugin commands</title>
<sect3>
<title>Listing registry contents</title>
<para>
Using the <plugin>Config</plugin> plugin, you can list
the values in a subgroup and get or set any of the values
anywhere in the configuration hierarchy. For example,
let's say you wanted to see what configuration values were
under the <registrygroup>supybot</registrygroup> (the base
group) hierarchy. You would simply issue this command:
</para>
<ircsession>
&lt;jemfinch|lambda&gt; @config list supybot
&lt;supybot&gt; jemfinch|lambda: nick, ident, user, server, password,
channels, prefixChars, defaultCapabilities, defaultAllow, defaultIgnore,
humanTimestampFormat, externalIP, pipeSyntax,
followIdentificationThroughNickChanges, alwaysJoinOnInvite,
showSimpleSyntax, maxHistoryLength, nickmods, throttleTime,
snarfThrottle, threadAllCommands, pingServer, pingInterval,
upkeepInterval, flush, httpPeekSize, and defaultSocketTimeout
</ircsession>
<para>
These are all the configuration values you can set which
are under the base <registrygroup>supybot</registrygroup>
group. Actually, their full names would each have a
&ldquo;supybot.&rdquo; appended on to the front of them,
but it is omitted in the listing in order to shorten the
output.
</para>
<para>
Now, to see all of the available configuration groups
under the base <registrygroup>supybot</registrygroup>
group, we simply use the <flag>--groups</flag> flag to
<botcommand>config list</botcommand>:
</para>
<ircsession>
&lt;jemfinch|lambda&gt; @config list --groups supybot
&lt;supybot&gt; jemfinch|lambda: commands, databases, directories, drivers,
log, plugins, replies, and reply
</ircsession>
<para>
These are all the subgroups of
<registrygroup>supybot</registrygroup>. Again, the full
name of these would have &ldquo;supybot.&rdquo; prepended
to them. So really, we have
<registrygroup>supybot.commands</registrygroup>,
<registrygroup>supybot.databases</registrygroup>, etc.
</para>
<note>
<para>
An item can show up in both lists if it is a group
that itself has a value. For example, all plugins
fall under this category, as their value is a boolean
value determining whether or not that plugin is to be
loaded when the bot is started.
</para>
</note>
</sect3>
<sect3>
<title>Dealing with registry values</title>
<para>
Okay, now that you've used the <plugin>Config</plugin>
plugin to list configuration variables, it's time that we
start looking at individual variables and their values.
</para>
<sect4>
<title>Built-in help for registry values</title>
<para>
The first (and perhaps most important) thing you
should know about each configuration variable is that
they all have an associated help string to tell you
what they represent. So the first command we'll cover
is <botcommand>config help</botcommand>. To see the
help string for any value or group, simply use the
<botcommand>config help</botcommand> command. For
example, to see what this
<registrygroup>supybot.prefixChars</registrygroup>
configuration variable is all about, we'd do this:
</para>
<ircsession>
&lt;jemfinch|lambda&gt; @config help supybot.prefixChars
&lt;supybot&gt; jemfinch|lambda: Determines what prefix characters the bot
will reply to. A prefix character is a single character that the bot will
use to determine what messages are addressed to it; when there are no
prefix characters set, it just uses its nick.
</ircsession>
<para>
Pretty simple, eh?
</para>
</sect4>
<sect4>
<title>Getting/setting registry values</title>
<para>
Now, if you're curious what the current value of a
configuration variable is, you'll use the
<botcommand>config</botcommand> command with one
argument, the name of the variable you want to see the
value of:
</para>
<ircsession>
&lt;jemfinch|lambda&gt; @config supybot.prefixChars
&lt;supybot&gt; jemfinch|lambda: '@'
</ircsession>
<para>
To set this value, just stick an extra argument after
the name:
</para>
<ircsession>
&lt;jemfinch|lambda&gt; @config supybot.prefixChars @$
&lt;supybot&gt; jemfinch|lambda: The operation succeeded.
</ircsession>
<para>
Now, check this out:
</para>
<ircsession>
&lt;jemfinch|lambda&gt; $config supybot.prefixChars
&lt;supybot&gt; jemfinch|lambda: '@$'
</ircsession>
<para>
Note that we used <literal>$</literal> as our prefix
character, and that the value of the configuration
variable changed. If I were to use the
<botcommand>flush</botcommand> command now, this
change would be flushed to the registry file on disk
(this would also happen if I made the bot quit, or
pressed
<keycombo>
<keycap>Ctrl</keycap>
<keycap>C</keycap>
</keycombo>
in the terminal the bot was running in). Instead,
I'll revert the change:
</para>
<ircsession>
&lt;jemfinch|lambda&gt; $config supybot.prefixChars @
&lt;supybot&gt; jemfinch|lambda: The operation succeeded.
&lt;jemfinch|lambda&gt; $note that this makes no response.
</ircsession>
<para>
If you're ever curious what the default for a given
configuration variable is, use the <botcommand>config
default</botcommand> command:
</para>
<ircsession>
&lt;jemfinch|lambda&gt; @config default supybot.prefixChars
&lt;supybot&gt; jemfinch|lambda: ''
</ircsession>
<para>
Thus, to reset a configuration variable to its default
value, you can simply say:
</para>
<ircsession>
&lt;jemfinch|lambda&gt; @config supybot.prefixChars [config default
supybot.prefixChars]
&lt;supybot&gt; jemfinch|lambda: The operation succeeded.
&lt;jemfinch|lambda&gt; @note that this does nothing
</ircsession>
<para>
Simple, eh?
</para>
</sect4>
</sect3>
<sect3>
<title>Searching the registry</title>
<para>
Now, let's say you want to find all configuration
variables that might be even remotely related to opping.
For that, you'll want the <botcommand>config
search</botcommand> command. Check this out:
</para>
<ircsession>
&lt;jemfinch|lambda&gt; @config search op
&lt;supybot&gt; jemfinch|lambda: supybot.plugins.Enforcer.autoOp,
supybot.plugins.Enforcer.autoOp.#supybot,
supybot.plugins.Enforcer.autoHalfop,
supybot.plugins.Enforcer.cycleToGetOps, supybot.plugins.Topic,
supybot.plugins.Topic.separator, and supybot.plugins.Relay.topicSync
</ircsession>
<para>
Sure, it showed up all the topic-related stuff in there,
but it also showed you all the op-related stuff, too. Do
note, however, that you can only see configuration
variables for plugins that you have loaded or that you
loaded in the past; if you've never loaded a plugin,
there's no way for the bot to know what configuration
variables it registers.
</para>
<para>
Some people might like editing their registry file
directly rather than manipulating all these things through
the bot. For those people, we offer the
<botcommand>config reload</botcommand> command, which
reloads both registry configuration and
user/channel/ignore database configuration. Just edit the
interesting files and then give the bot the
<botcommand>config reload</botcommand> command and it'll
work as expected. Do note, however, that Supybot flushes
his configuration files and databases to disk every hour
or so, and if this happens after you've edited your
configuration files but before you reload your changes,
you could lose the changes you made. To prevent this, set
the <registrygroup>supybot.flush</registrygroup> value to
<literal>Off</literal>, and no automatic flushing will
occur.
</para>
</sect3>
</sect2>
</sect1>
<sect1>
<title>All done!</title>
<para>
Anyway, that's about it for configuration. Have fun, and enjoy
your configurable bot!
</para>
</sect1>
</article>

View File

@ -1,4 +1,4 @@
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook V4.1//EN">
<!DOCTYPE article SYSTEM "supybot.dtd">
<article>
<articleinfo>
@ -32,6 +32,11 @@
Updated to match EXAMPLE included with 0.75.0
</revremark>
</revision>
<revision>
<revnumber>0.4</revnumber>
<date>26 Feb 2004</date>
<revremark>Converted to use Supybot DTD</revremark>
</revision>
</revhistory>
</articleinfo>
<sect1>
@ -56,13 +61,12 @@
<title>Creating your own plugin</title>
<sect2>
<title>
Using <application>scripts/newplugin.py</application>
Using <script>scripts/newplugin.py</script>
</title>
<para>
First, the easiest way to start writing a module is to use the
wizard provided,
<application>scripts/newplugin.py</application>. Here's an
example session:
First, the easiest way to start writing a module is to use the
wizard provided, <script>scripts/newplugin.py</script>.
Here's an example session:
</para>
<screen>
functor% scripts/newplugin.py
@ -169,7 +173,7 @@ Class = Random
</para>
<para>
Describe what you want the plugin to do in the docstring.
This is used in <application>scripts/setup.py</application> in
This is used in <script>scripts/setup.py</script> in
order to explain to the user the purpose of the module. It's
also returned when someone asks the bot for help for a given
module (instead of help for a certain command). We'll change
@ -177,17 +181,18 @@ Class = Random
numbers."</literal>
</para>
<para>
Then there are the imports. The <varname>callbacks</varname>
Then there are the imports. The
<module>callbacks</module>
module is used (the class you're given subclasses
<varname>callbacks.Privmsg</varname>) but the
<varname>privmsgs</varname> module isn't used. That's
<classname>callbacks.Privmsg</classname>) but the
<module>privmsgs</module> module isn't used. That's
alright; we can almost guarantee you'll use it, so we go ahead
and add the import to the template.
</para>
<para>
Then you see a <function>configure</function> function. This
the function that's called when users decide to add your
module in <application>scripts/setup.py</application>. You'll
module in <script>scripts/setup.py</script>. You'll
note that by default it simply adds <literal>"load
Example"</literal> (where 'Example' is the name you provided
as the name of your plugin, so in our case it is
@ -204,8 +209,8 @@ Class = Random
</para>
<para>
What you're given is a skeleton: a simple subclass of
<varname>callbacks.Privmsg</varname> for you to start with.
Now let's add a command.
<classname>callbacks.Privmsg</classname> for you to start
with. Now let's add a command.
</para>
<para>
I don't know what you know about random number generators, but
@ -213,7 +218,7 @@ Class = Random
seed) and they continue (via some somewhat
complicated/unpredictable algorithm) from there. This seed
(and the rest of the sequence, really) is all nice and
packaged up in Python's <varname>random</varname> module, the
packaged up in Python's <module>random</module> module, the
<varname>Random</varname> object. So the first thing we're
going to have to do is give our plugin a
<varname>Random</varname> object.
@ -225,8 +230,8 @@ Class = Random
be careful of is that you call the superclass
<function>__init__</function> method at the end of your own
<function>__init__</function>. So to add this
<varname>random.Random</varname> object to our plugin, we can
replace the <function>pass</function> statement with
<classname>random.Random</classname> object to our plugin, we
can replace the <keyword>pass</keyword> statement with
this:
</para>
<programlisting>
@ -285,40 +290,40 @@ def __init__(self):
<para>
And that's it! Pretty simple, huh? Anyway, you're probably
wondering what all that <emphasis>means</emphasis>. We'll
start with the <function>def</function> statement:
start with the <keyword>def</keyword> statement:
</para>
<programlisting>
def random(self, irc, msg, args):
</programlisting>
<para>
What that does is define a command
<function>random</function>. You can call it by saying
"@random" (or whatever prefix character your specific bot
uses). The arguments are a bit less obvious.
<varname>self</varname> is self-evident (hah!).
<varname>irc</varname> is the <varname>Irc</varname> object
passed to the command; <varname>msg</varname> is the original
<varname>IrcMsg</varname> object. But you're really not going
to have to deal with either of these too much (with the
exception of calling <function>irc.reply</function> or
<function>irc.error</function>). What you're
<emphasis>really</emphasis> interested in is the
<varname>args</varname> arg. That if a list of all the
arguments passed to your command, pre-parsed and already
evaluated (i.e., you never have to worry about nested
commands, or handling double quoted strings, or splitting on
whitespace -- the work has already been done for you). You
can read about the <varname>Irc</varname> object in
<filename>irclib.py</filename> (you won't find
<function>.reply</function> or <function>.error</function>
there, though, because you're actually getting an
<varname>IrcObjectProxy</varname>, but that's beyond the level
we want to describe here :)). You can read about the
<varname>msg</varname> object in
<filename>ircmsgs.py</filename>. But again, aside from
calling <function>irc.reply</function> or
<function>irc.error</function>, you'll very rarely be using
these objects.
What that does is define a command
<function>random</function>. You can call it by saying
"@random" (or whatever prefix character your specific bot
uses). The arguments are a bit less obvious.
<varname>self</varname> is self-evident (hah!).
<varname>irc</varname> is the <classname>Irc</classname>
object passed to the command; <varname>msg</varname> is the
original <classname>IrcMsg</classname> object. But you're
really not going to have to deal with either of these too much
(with the exception of calling <function>irc.reply</function>
or <function>irc.error</function>). What you're
<emphasis>really</emphasis> interested in is the
<varname>args</varname> arg. That if a list of all the
arguments passed to your command, pre-parsed and already
evaluated (i.e., you never have to worry about nested
commands, or handling double quoted strings, or splitting on
whitespace -- the work has already been done for you). You
can read about the <classname>Irc</classname> object in
<filename>irclib.py</filename> (you won't find
<function>.reply</function> or <function>.error</function>
there, though, because you're actually getting an
<classname>IrcObjectProxy</classname>, but that's beyond the
level we want to describe here :)). You can read about the
<varname>msg</varname> object in
<filename>ircmsgs.py</filename>. But again, aside from
calling <function>irc.reply</function> or
<function>irc.error</function>, you'll very rarely be using
these objects.
</para>
<para>
(In case you're curious, the answer is yes, you
@ -342,13 +347,13 @@ def __init__(self):
<emphasis>is</emphasis> the help! Given the above docstring,
this is what a supybot does:
</para>
<screen>
<ircsession>
&lt;angryman&gt; jemfinch: random takes no arguments (for more help
use the morehelp command)
&lt;jemfinch&gt; $morehelp random
&lt;angryman&gt; jemfinch: Returns the next random number from the
current random number generator.
</screen>
</ircsession>
<para>
'help &lt;command&gt;' replies with the command name followed
by the first line of the command's docstring; there should be
@ -361,21 +366,22 @@ def __init__(self):
irc.reply(msg, str(self.rng.random()))
</programlisting>
<para>
<function>irc.reply</function> takes two arguments, an
<varname>IrcMsg</varname> (like the one passed into your
function) and a string. The <varname>IrcMsg</varname> is used
to determine who the reply should go to and whether or not it
should be sent in private message (commands sent in private
are replied to in private). The string is the reply to be
sent. Don't worry about length restrictions or anything -- if
the string you want to send is too big for an IRC message (and
oftentimes that turns out to be the case :)) the supybot
framework handles that entirely transparently to you. Do make
sure, however, that you give <function>irc.reply</function> a
string. It doesn't take anything else (sometimes even unicode
fails!). That's why we have "str(self.rng.random())" instead
of simply "self.rng.random()" -- we had to give
<function>irc.reply</function> a string.
<function>irc.reply</function> takes two arguments, an
<classname>IrcMsg</classname> (like the one passed into your
function) and a string. The <classname>IrcMsg</classname> is
used to determine who the reply should go to and whether or
not it should be sent in private message (commands sent in
private are replied to in private). The string is the reply
to be sent. Don't worry about length restrictions or anything
-- if the string you want to send is too big for an IRC
message (and oftentimes that turns out to be the case :)) the
supybot framework handles that entirely transparently to you.
Do make sure, however, that you give
<function>irc.reply</function> a string. It doesn't take
anything else (sometimes even unicode fails!). That's why we
have "str(self.rng.random())" instead of simply
"self.rng.random()" -- we had to give
<function>irc.reply</function> a string.
</para>
<para>
Anyway, now that we have an RNG, we have a need for seed! Of
@ -422,7 +428,7 @@ def __init__(self):
arguments are returned in a tuple/list). Yes, we could've
just said "seed = args[0]" and gotten the first argument, but
what if the user didn't pass us an argument at all? Then
we've got to catch the <varname>IndexError</varname> from
we've got to catch the <classname>IndexError</classname> from
<varname>args[0]</varname> and complain to the user about it.
<function>privmsgs.getArgs</function>, on the other hand,
handles all that for us. If the user didn't give us enough
@ -440,7 +446,7 @@ def __init__(self):
sure to remind the user that an error has been encountered
(currently, that means it puts "Error: " at the beginning of
the message). After erroring, we return. It's important to
remember this <function>return</function> here; otherwise,
remember this <keyword>return</keyword> here; otherwise,
we'll just keep going down through the function and try to use
this "seed" variable that never got assigned. A good general
rule of thumb is that any time you use
@ -500,9 +506,9 @@ def __init__(self):
to.
</para>
<para>
The <varname>Random</varname> object we're using offers us a
"sample" method that takes a sequence and a number (we'll call
it <varname>N</varname>) and returns a list of
The <classname>Random</classname> object we're using offers us
a "sample" method that takes a sequence and a number (we'll
call it <varname>N</varname>) and returns a list of
<varname>N</varname> items taken randomly from the sequence.
So I'll show you an example that takes advantage of multiple
arguments but doesn't use
@ -542,13 +548,13 @@ def __init__(self):
into a big long string that we'll just have to re-split. But
we still want the nice error handling of
<function>privmsgs.getArgs</function>. So what do we do? We
raise <varname>callbacks.ArgumentError</varname>! That's the
secret juju that <function>privmsgs.getArgs</function> is
doing; now we're just doing it ourself. Someone up our
callchain knows how to handle it so a neat error message is
returned. So in this function, if
<function>.pop(0)</function> fails, we weren't given enough
arguments and thus need to tell the user how to call us.
raise <classname>callbacks.ArgumentError</classname>! That's
the secret juju that <function>privmsgs.getArgs</function> is
doing; now we're just doing it ourself. Someone up our
callchain knows how to handle it so a neat error message is
returned. So in this function, if
<function>.pop(0)</function> fails, we weren't given enough
arguments and thus need to tell the user how to call us.
</para>
<para>
So we have the args, we have the number, we do a simple call
@ -607,22 +613,22 @@ def __init__(self):
<para>
Later, though, you'll see something other than
<function>irc.reply</function>. This is
<function>irc.queueMsg</function>, the general interface for
sending messages to the server. It's what
<function>irc.reply</function> is using under the covers. It
takes an <varname>IrcMsg</varname> object. Fortunately,
that's exactly what's returned by
<function>ircmsgs.action</function>. An action message, just
in case you don't know, is a /me kind of message.
<function>ircmsgs.action</function> is a helper function that
takes a target (a place to send the message, either a channel
or a person) and a payload (the thing to /me) and returns the
appropriate <varname>IrcMsg</varname> object.
<function>ircutils.replyTo</function> simply takes an
<varname>IrcMsg</varname> and returns where we should reply
to; if the message was originally sent to a channel, we'll
reply to there, if it was originally sent to us privately,
we'll reply in private.
<function>irc.queueMsg</function>, the general interface for
sending messages to the server. It's what
<function>irc.reply</function> is using under the covers. It
takes an <classname>IrcMsg</classname> object. Fortunately,
that's exactly what's returned by
<function>ircmsgs.action</function>. An action message, just
in case you don't know, is a /me kind of message.
<function>ircmsgs.action</function> is a helper function that
takes a target (a place to send the message, either a channel
or a person) and a payload (the thing to /me) and returns the
appropriate <classname>IrcMsg</classname> object.
<function>ircutils.replyTo</function> simply takes an
<classname>IrcMsg</classname> and returns where we should
reply to; if the message was originally sent to a channel,
we'll reply to there, if it was originally sent to us
privately, we'll reply in private.
</para>
<para>
At the end, you might be surprised by the "raise
@ -646,7 +652,7 @@ def __init__(self):
<title>Finishing touches</title>
<para>
Let's take a look at that <function>configure</function>
function <application>scripts/newplugin.py</application> made
function <script>scripts/newplugin.py</script> made
for us. Here it is, in case you've forgotten:
</para>
<programlisting>
@ -660,10 +666,10 @@ def configure(onStart, afterConnect, advanced):
</programlisting>
<para>
You remember when you first started running supybot and ran
<application>scripts/setup.py</application> and it asked you
<script>scripts/setup.py</script> and it asked you
all those questions? Well, now's your chance to ask other
users some questions of your own. In our case, with our
<varname>Random</varname> plugin, it might be nice to offer
<plugin>Random</plugin> plugin, it might be nice to offer
the user the ability to specify a seed to use whenever the
plugin is loaded. So let's ask him if he wants to do that,
and if so, let's ask him what the seed should be.
@ -684,7 +690,7 @@ def configure(onStart, afterConnect, advanced):
onStart.append('seed %s' % seed)
</programlisting>
<para>
As you can see, what the <varname>questions</varname> module
As you can see, what the <module>questions</module> module
does is fairly self-evident: <function>yn</function> returns
either 'y' or 'n'; <function>something</function> returns
<emphasis>something</emphasis> (but not nothing; for nothing,
@ -694,12 +700,12 @@ def configure(onStart, afterConnect, advanced):
<varname>onStart</varname> is a list of the commands to run
when the bot starts; we're just throwing our little piece into
it. These commands will then be written into the template
<application>scripts/setup.py</application> creates for the bot.
<script>scripts/setup.py</script> creates for the bot.
</para>
<para>
We've written our own plugin from scratch (well, from the
boilerplate that we got from
<application>scripts/newplugin.py</application> :)) and
<script>scripts/newplugin.py</script> :)) and
survived! Now go write more plugins for supybot, and send
them to me so I can use them too :)
</para>

284
docs/DocBook/faq.sgml Normal file
View File

@ -0,0 +1,284 @@
<!DOCTYPE article SYSTEM "supybot.dtd">
<article class="faq">
<articleinfo>
<authorgroup>
<author>
<firstname>Jeremiah</firstname>
<surname>Fincher</surname>
</author>
<editor>
<firstname>Daniel</firstname>
<surname>DiPaolo</surname>
<contrib>DocBook translator</contrib>
</editor>
</authorgroup>
<title>Supybot Frequently Asked Questions</title>
<revhistory>
<revision>
<revnumber>0.1</revnumber>
<date>18 Feb 2004</date>
<revremark>Initial Docbook translation</revremark>
</revision>
<revision>
<revnumber>0.2</revnumber>
<date>26 Feb 2004</date>
<revremark>Changed to Supybot DTD</revremark>
</revision>
</revhistory>
</articleinfo>
<qandaset defaultlabel="qanda">
<qandaentry>
<question>
<para>
Why does my bot not recognize me or tell me that I don't
have the <capability>owner</capability> capability?
</para>
</question>
<answer>
<para>
Because you're not given it anything to recognize you
from! You'll need to identify with the bot
(<botcommand>help identify</botcommand> to see how that
works) or add your hostmask to your user record
(<botcommand>help addhostmask</botcommand> to see how that
works) for it to know that you're you. You may wish to
note that <botcommand>addhostmask</botcommand> can accept
a password; rather than identify, you can send the command
<botcommand>addhostmask myOwnerUser [hostmask]
myOwnerUserPassword</botcommand> and the bot will add your
current hostmask to your owner user (of course, you should
change <literal>myOwnerUser</literal> and
<literal>myOwnerUserPassword</literal> appropriately for
your bot).
</para>
</answer>
</qandaentry>
<qandaentry>
<question>
<para>
How do I make Supybot op my users?
</para>
</question>
<answer>
<para>
First, you'll have to make sure that your users register
with the bot. They can do this with the
<botcommand>register</botcommand> command. After they do
so, you'll want to add the
<capability>#channel,op</capability> capability to their
user. Use the <botcommand>channel
addcapability</botcommand> command to do this. After
that, your users should be able to use the
<botcommand>op</botcommand> command to get ops.
</para>
<para>
If you want your users to be auto-opped when they join the
channel, you'll need to load the <plugin>Enforcer</plugin>
plugin and turn its <registrygroup>autoOp</registrygroup>
configuration variable on. Use the
<botcommand>config</botcommand> command to do so. Here's
an example of how to do these steps:
</para>
<ircsession>
&lt;jemfinch|lambda&gt; I'm going to make an example session for giving
you auto-ops, for our FAQ.
&lt;dunk1&gt; ah ok ;]
&lt;jemfinch|lambda&gt; First, I need you to register with supybot, using
the "register" command (remember to send it in private).
&lt;dunk1&gt; done
&lt;jemfinch|lambda&gt; what name are you registered under?
&lt;dunk1&gt; dunk1
&lt;jemfinch|lambda&gt; ok, cool.
&lt;jemfinch|lambda&gt; @channel addcapability dunk1 op
&lt;supybot&gt; jemfinch|lambda: The operation succeeded.
&lt;jemfinch|lambda&gt; now use the "op" command to get ops.
&lt;dunk1&gt; @op
&mdash; supybot gives channel operator status to dunk1
&lt;dunk1&gt; works!
&lt;dunk1&gt; ;]
&lt;jemfinch|lambda&gt; @load Enforcer
&lt;supybot&gt; jemfinch|lambda: The operation succeeded.
&lt;jemfinch|lambda&gt; @config supybot.plugins.Enforcer.autoOp.#supybot On
&lt;supybot&gt; jemfinch|lambda: The operation succeeded.
&lt;jemfinch|lambda&gt; ok, now cycle the channel (part and then rejoin)
&lt;&ndash; dunk1 (dunker@freebsd.nl) has left #supybot
&ndash;&gt; dunk1 (dunker@freebsd.nl) has joined #supybot
&mdash; supybot gives channel operator status to dunk1
&lt;jemfinch|lambda&gt; cool, thanks :)
</ircsession>
</answer>
</qandaentry>
<qandaentry>
<question>
<para>
Can users with the <capability>admin</capability>
capability change configuration variables?
</para>
</question>
<answer>
<para>
Currently, no. Since this is the first release of Supybot
that uses the registry, we wanted to stay on the
conservative side and require the
<capability>owner</capability> capability for changing all
non-channel-related configuration variables. Feel free to
make your case to us as to why a certain configuration
variable should only require the
<capability>admin</capability> capability instead of the
<capability>owner</capability> capability, and if we agree
with you, we'll change it for the next release.
</para>
</answer>
</qandaentry>
<qandaentry>
<question>
<para>
How do I make my Supybot connect to multiple servers?
</para>
</question>
<answer>
<para>
You'll need to use the <plugin>Relay</plugin> plugin. As
long as you don't call the <botcommand>relay
join</botcommand> command, it won't actually do any
relaying between channels (even if the bot is on the same
channel on different networks). In order to use the Relay
plugin, you'll want to first call the <botcommand>relay
start</botcommand> command, followed by the
<botcommand>relay connect</botcommand> command. These
commands are (unfortunately) not persistent at this time,
so you'll need to give them to the bot anytime you start
it up. We'll probably have this lack of persistence
rectified before the next release.
</para>
</answer>
</qandaentry>
<qandaentry>
<question>
<para>
Can Supybot do factoids?
</para>
</question>
<answer>
<para>
Supybot most certainly can! In fact, we offer two
full-fledged factoids-related plugins!
</para>
<para>
<plugin>Factoids</plugin> (written by
<nick>jemfinch</nick>) is Supybot's original
factoids-related plugin. It offers full integration with
Supybot's nested commands as well as a complete 1:n key to
factoid ratio, with lookup by individual number.
<plugin>Factoids</plugin> also uses a channel-specific
database instead of a global database, although in the
future it will likely be a configuration option whether to
use channel-specific or global databases for such plugins.
</para>
<para>
<plugin>MoobotFactoids</plugin> (written by
<nick>Strike</nick>) is much more full-featured, offering
users the ability to define factoids in a slightly more
user-friendly way, as well as parsing factoids to handle
&lt;reply&gt;, &lt;action&gt;, "see", and alternations
(defining a factoid "test" as "&lt;reply&gt;(foo|bar|baz)"
will make the bot send "foo" or "bar" or "baz" to the
channel (without the normal "test is " at the beginning)).
If you're accustomed to Moobot's factoids or Blootbot's
factoids, then this is the Factoids plugin for you.
Unfortunately, due to the more natural definition syntax
(required to be compatible with Moobot) you can't define
Factoids with nested commands; you'll have to evaluate the
command first and then copy the result into your factoid
definition. <plugin>MoobotFactoids</plugin> uses a global
database, so the factoids are the same for all channels.
</para>
<para>
In the future, we plan to have a compatibility plugin for
Infobot, but as of present we've not yet written one.
</para>
</answer>
</qandaentry>
<qandaentry>
<question>
<para>
Can I import my Infobot/Blootbot/Moobot factoids into
Supybot?
</para>
</question>
<answer>
<para>
As of present, we have no automated way to do so.
<nick>Strike</nick> has written a few scripts for
importing a Moobot database into
<plugin>MoobotFactoids</plugin>, however, so you'll want
to talk to him about helping you with that. We're
certainly happy to help you convert such databases; if you
can provide us with such a database exported to a flat
file, we can probably do the rest of the work to write a
script that imports it into a database for one of our
factoids-related plugins.
</para>
</answer>
</qandaentry>
<qandaentry>
<question>
<para>
I found a bug, what do I do?
</para>
</question>
<answer>
<para>
Submit it on Sourceforge through our Sourceforge project
page:
<ulink
url="http://sourceforge.net/tracker/?group_id=58965&amp;atid=489447">
http://sourceforge.net/tracker/?group_id=58965&amp;atid=489447
</ulink>. If Sourceforge happens to be down when you try
to submit your bug, then post it in the "Supybot Developer
Discussion" forum at our forums at
<ulink url="http://forums.supybot.org">
http://forums.supybot.org/
</ulink>. If that doesn't work, email
<email>supybot-bugs@lists.sourceforge.net</email>. If
that doesn't work, email
<email>jemfinch@supybot.org</email>. If that doesn't
work, find yourself some carrier pigeons and &hellip; hah!
You thought I was serious!
</para>
<para>
Anyway, when you submit your bug, we'll need several
things. If the bug involved an uncaught exception, we
need the traceback (basically the stuff from
&ldquo;Uncaught exception in &hellip;&rdquo; to the next
log entry). We'd also like to see the commands that
caused the bug, or happened around the time you saw the
bug. If the bug involved a database, we'd love to see the
database. Remember, it's always worse to send us too much
information in a bug report than too little.
</para>
</answer>
</qandaentry>
<qandaentry>
<question>
<para>
Karma doesn't seem to work for me.
</para>
</question>
<answer>
<para>
<plugin>Karma</plugin> by default doesn't acknowledge
karma updates. If you check the karma of whatever you
increased/decreased, you'll note that your increment or
decrement still took place. If you'd rather
<plugin>Karma</plugin> acknowledge karma updates, change
the
<registrygroup>supybot.plugins.Karma.response</registrygroup>
configuration variable to <literal>On</literal>.
</para>
</answer>
</qandaentry>
</qandaset>
</article>

View File

@ -0,0 +1,297 @@
<!DOCTYPE article SYSTEM "supybot.dtd">
<article>
<articleinfo>
<authorgroup>
<author>
<firstname>Jeremiah</firstname>
<surname>Fincher</surname>
</author>
<editor>
<firstname>Daniel</firstname>
<surname>DiPaolo</surname>
<contrib>DocBook translator</contrib>
</editor>
</authorgroup>
<title>Getting started with Supybot</title>
<revhistory>
<revision>
<revnumber>0.1</revnumber>
<date>18 Feb 2004</date>
<revremark>Initial Docbook translation</revremark>
</revision>
</revhistory>
</articleinfo>
<sect1>
<title>Introduction</title>
<para>
Ok, so you've decided to try out Supybot. That's great! The more
people who use Supybot, the more people can submit bugs and help
us to make it the best IRC bot in the world :)
</para>
<para>
First things first: Supybot <emphasis>requires</emphasis> Python
2.3. There ain't no getting around it. If you're a Python
developer, you probably know how superior 2.3 is to previous
incarnations. If you're not, just think about the difference
between a bowl of plain vanilla ice cream and a banana split. Or
something like that. Either way, <emphasis>we're</emphasis>
Python developers and we like banana splits.
</para>
</sect1>
<sect1>
<title>Installing the bot and its utilities</title>
<para>
So what do you do? First thing you'll want to do is run (with
root/admin privileges) <application>python setup.py
install</application>. This will install Supybot globally. If
you need to install locally for whatever reason, see this <ulink
url="http://tinyurl.com/2tb37">forum post</ulink> on how to do so.
You'll then have several new programs installed where Python
scripts are normally installed on your system
(<filename>/usr/bin</filename> or
<filename>/usr/local/bin</filename> are common on UNIX systems;
<filename>C:\Python23\Scripts</filename> is a common place on
Windows; and (watch out, this is a long one :))
<filename>/System/Library/Frameworks/Python.framework/Versions/2.3/bin</filename>
is a common place on MacOS X.). The two that might be of
particular interest to you, the new user, are
<script>supybot</script> and
<script>supybot-wizard</script> The former
(<script>supybot</script> is the script to run an actual
bot; the latter (<script>supybot-wizard</script> is an
in-depth wizard that provides a nice user interface for creating
configuration files for your bot. We'd prefer you to the use
<script>supybot-wizard</script>, but if you're in a
hurry or don't feel like being asked many questions, just run
supybot with no arguments and it'll ask you only the questions
necessary ")to run a bot.
</para>
</sect1>
<sect1>
<title>Firing up the bot for the first time</title>
<para>
So after running either of those two programs, you've got a nice
registry file handy. If you're not satisfied with your answers
to any of the questions you were asked, feel free to run the
program again until you're satisfied with all your answers. Once
you're satisfied, though, run the
<script>supybot</script> program with the
registry file you created as an argument. This will start the
bot; unless you turned off logging to stdout, you'll see some nice
log messages describing what the bot is doing at any particular
moment; it may pause for a significant amount of time after saying
"Connecting to ..." while the server tries to check its ident.
</para>
</sect1>
<sect1>
<title>Your first interactions with the bot</title>
<para>
Ok, so let's assume your bot connected to the server fine and
joined the channels you told it to join. For now we'll assume you
named your bot <nick>supybot</nick> (you probably didn't,
but it'll make it much clearer in the examples that follow to
assume that you did). We'll also assume that you told it to join
<channel>#channel</channel> (a nice generic name for a channel,
isn't it? :)) So what do you do with this bot that you just made
to join your channel? Try this in the channel:
</para>
<ircsession>
supybot: list
</ircsession>
<para>
Replacing <nick>supybot</nick> with the actual name you
picked for your bot, of course. Your bot should reply with a list
of the plugins he currently has loaded. At least
<plugin>Admin</plugin>, <plugin>Channel</plugin>,
<plugin>Config</plugin>, <plugin>Misc</plugin>,
<plugin>Owner</plugin>, and <plugin>User</plugin> should be
there; if you used <script>supybot-wizard</script> to
create your configuration file you may have many more plugins
loaded. The <botcommand>list</botcommand> command can also be used to
list the commands in a given plugin:
</para>
<ircsession>
supybot: list Misc
</ircsession>
<para>
Will list all the commands in the <plugin>Misc</plugin> plugin.
</para>
<sect2>
<title>Accessing the bot's online help</title>
<para>
If you want to see the help for any command, just use
the <botcommand>help</botcommand> command:
</para>
<ircsession>
supybot: help help
supybot: help list
supybot: help load
</ircsession>
</sect2>
<sect2>
<title>Dealing with ambiguous commands</title>
<para>
Sometimes more than one plugin will have a given command; for
instance, the <botcommand>list</botcommand> command exists in both
the <plugin>Misc</plugin> and <plugin>Config</plugin>
plugins (both loaded by default). <plugin>List</plugin>, in
this case, defaults to the <plugin>Misc</plugin> plugin, but
you may want to get the help for the
<botcommand>list</botcommand>
command in the <plugin>Config</plugin> plugin. In that
case, you'll want to give your command like this:
</para>
<ircsession>
supybot: help config list
</ircsession>
<para>
Anytime your bot tells you that a given command is defined in
several plugins, you'll want to use this syntax
(<botcommand>plugin command</botcommand>) to disambiguate which
plugin's command you wish to call. For instance, if you
wanted to call the <plugin>Config</plugin> plugin's
<botcommand>list</botcommand> command, then you'd need to say:
</para>
<ircsession>
supybot: config list
</ircsession>
<para>
Rather than just <botcommand>list</botcommand>.
</para>
</sect2>
<sect2>
<title>Loading plugins</title>
<para>
Now that you know how to deal with plugins having commands
with the same name, let's take a look at loading other
plugins. If you didn't use
<script>supybot-wizard</script>, though, you might
do well to try it before playing around with loading plugins
yourself: each plugin has its own
<function>configure</function> function that the wizard uses
to setup the appropriate registry entries if the plugin
requires any.
</para>
<sect3>
<title>Identifying yourself as the bot owner</title>
<para>
Now, if you do want to play around with loading plugins,
you're going to need to have the
<capability>owner</capability>
capability. If you ran the wizard, then chances are you
already added an owner user for yourself. If not,
however, you can add one via the handy-dandy
<script>supybot-adduser</script> script. You'll
want to run it while the bot is not running (otherwise it
could overwrite
<script>supybot-adduser</script>'s changes to
your user database before you get a chance to reload
them). Just follow the prompts, and when it asks if you
want to give the user any capabilities, say yes and then
give yourself the <capability>owner</capability> capability
(without the quotes), restart the bot and you'll be ready
to load some plugins!
</para>
<para>
Now, in order for the bot to recognize you as your owner
user, you'll have to identify with the bot. Open up a
query window in your irc client (/query should do it; if
not, just know that you can't identify in a channel
because it requires sending your password to the bot).
Then type this:
</para>
<ircsession>
help identify
</ircsession>
<para>
And follow the instructions; the command you send will
probably look like this, with your owner user and password
replaced:
</para>
<ircsession>
identify myowneruser myuserpassword
</ircsession>
<para>
The bot will tell you that &ldquo;The operation
succeeded&rdquo; if you got the right name and password.
Now that you're identified, you can do anything that
requires any privilege: that includes all the commands in
the <plugin>Owner</plugin> and <plugin>Admin</plugin>
plugins, which you may want to take a look at (using the
<botcommand>list</botcommand> and
<botcommand>help</botcommand>
commands, of course). One command in particular that you
might want to use (it's from the <plugin>User</plugin>
plugin) is the <botcommand>addhostmask</botcommand> command: it
lets you add a hostmask to your user record so the bot
recognizes you by your hostmask instead of requiring you
to always identify with it before it recognizes you. Use
the <botcommand>help</botcommand> command to see how this
command works. Here's how I often use it:
</para>
<ircsession>
addhostmask myuser [hostmask] mypassword
</ircsession>
<para>
You may not have seen that "[hostmask]" syntax before.
Supybot allows nested commands, which means that any
command's output can be nested as an argument to another
command. The hostmask command from the
<plugin>Misc</plugin> plugin returns the hostmask of a
given nick, but if given no arguments, it returns the
hostmask of the person giving the command. So the command
above adds the hostmask I'm currently using to my user's
list of recognized hostmasks. I'm only required to give
<literal>mypassword</literal> if I'm not already
identified with the bot.
</para>
</sect3>
</sect2>
<sect2>
<title>The <botcommand>more</botcommand> command</title>
<para>
Another command you might find yourself needing somewhat often
is the <botcommand>more</botcommand> command. The IRC protocol
limits messages to 512 bytes, 60 or so of which must be
devoted to some bookkeeping. Sometimes, however, Supybot
wants to send a message that's longer than that. What it
does, then, is break it into "chunks" and send the first one,
following it with "(X more messages)" where X is how many more
chunks there are. To get to these chunks, use the more
command. One way to try is to look at the listing of
configuration groups for the bot (more on this in the
CONFIGURATION document) by giving the command "config list
supybot". Last I checked, it'll overflow into a second chunk.
When you invoke this command, you should see output like:
</para>
<ircsession>
&lt;supybot&gt; nick, ident, user, server, password, channels, prefixChars,
defaultCapabilities, defaultAllow, defaultIgnore,
humanTimestampFormat, externalIP, bracketSyntax, pipeSyntax,
followIdentificationThroughNickChanges, alwaysJoinOnInvite,
showSimpleSyntax, maxHistoryLength, nickmods, throttleTime,
snarfThrottle, threadAllCommands, pingServer, pingInterval,
upkeepInterval, flush, (1 more message)
</ircsession>
<para>
Now, to see the rest of the output, simply give the command
<botcommand>more</botcommand>, and it will show you the rest:
</para>
<ircsession>
&lt;jemfinch&gt; more
&lt;supybot&gt; httpPeekSize, and defaultSocketTimeout
</ircsession>
</sect2>
</sect1>
<sect1>
<title>You're ready!</title>
<para>
You should now have a solid foundation for using Supybot. Be sure
to check the help that is built-in to the bot itself if you have
any questions, and enjoy using Supybot!
</para>
</sect1>
</article>

View File

@ -0,0 +1,585 @@
<!DOCTYPE article SYSTEM "supybot.dtd">
<article>
<articleinfo>
<authorgroup>
<author>
<firstname>Jeremiah</firstname>
<surname>Fincher</surname>
</author>
<editor>
<firstname>Daniel</firstname>
<surname>DiPaolo</surname>
<contrib>DocBook translator</contrib>
</editor>
</authorgroup>
<title>Supybot developer interfaces</title>
<revhistory>
<revision>
<revnumber>0.1</revnumber>
<date>19 Feb 2004</date>
<revremark>Initial Docbook translation</revremark>
</revision>
<revision>
<revnumber>0.2</revnumber>
<date>26 Feb 2004</date>
<revremark>Converted to Supybot DTD</revremark>
</revision>
</revhistory>
</articleinfo>
<sect1>
<title>Available interfaces</title>
<para>
These are the interfaces for some of the objects you'll deal with
if you code for Supybot.
</para>
<sect2>
<title><classname>ircmsgs.IrcMsg</classname>
<para>
This is the object that represents an IRC message. It has
several methods and attributes. The most important thing
about this class, however, is that it <emphasis>is</emphasis>
hashable, and thus <emphasis>cannot</emphasis> be modified.
Do not change any attributes; any code that modifies an IRC
message is <emphasis>broken</emphasis> and should not exist.
</para>
<variablelist>
<title>Interesting methods</title>
<varlistentry>
<term>__init__</term>
<listitem>
<para>
One of the more complex initializers in a class.
It can be used in three different ways:
</para>
<orderedlist numeration="arabic" spacing="normal">
<listitem>
<para>
It can be given a string, as one received
from the server, which it will then parse
into its separate components and
instantiate the class with those
components as attributes.
</para>
</listitem>
<listitem>
<para>
It can be given a command, some (optional)
arguments, and a (optional) prefix, and
will instantiate the class with those
components as attributes.
</para>
</listitem>
<listitem>
<para>
It can be given, in addition to any of the
above arguments, a <varname>msg</varname>
keyword argument that will use the
attributes of msg as defaults. This
exists to make it easier to copy messages,
since the class is immutable.
</para>
</listitem>
</orderedlist>
</listitem>
</varlistentry>
<varlistentry>
<term>__str__</term>
<listitem>
<para>
This returns the message in a string form suitable
for sending to a server.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>__repr__</term>
<listitem>
<para>
This returns the message in a form suitable for
<function>eval()</function>, assuming the name
<varname>IrcMsg</varname> is in your namespace and
is bound to this class.
</para>
</listitem>
</varlistentry>
</variablelist>
<para>
The following attributes are the meat of this class. These
are generally what you'll be looking at with
<varname>IrcMsg</varname>s.
</para>
<variablelist>
<title>Interesting attributes</title>
<varlistentry>
<term>command</term>
<listitem>
<para>
This is the command of the
<varname>IrcMsg</varname> &ndash;
<literal>PRIVMSG</literal>,
<literal>NOTICE</literal>,
<literal>WHOIS</literal>,
etc.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>args</term>
<listitem>
<para>
This is a tuple of the arguments to the
<varname>IrcMsg</varname>. Some messages have
arguments, some don't, depending on what command
they are. You are, of course, always assured that
<varname>args</varname> exists and is a tuple,
though it might be empty.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>prefix</term>
<listitem>
<para>
This is the hostmask of the person/server the
message is from. In general, you won't be setting
this on your outgoing messages, but incoming
messages will always have one. This is the whole
hostmask; if the message was received from a
server, it'll be the server's hostmask; if the
message was received from a user, it'll be the
whole user hostmask. In that case, however, it's
also parsed out into the
<varname>nick</varname>/<varname>user</varname>/<varname>host</varname>
attributes, which are probably more useful to
check for many purposes.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>nick</term>
<listitem>
<para>
If the message was sent by a user, this will be
the nick of the user. If it was sent by a server,
this will be the server's name (something like
<literal>calvino.freenode.net</literal> or
similar).
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>user</term>
<listitem>
<para>
If the message was sent by a user, this will be
the user string of the user &ndash; what they put
into their IRC client for their "full name." If
it was sent by a server, it'll be the server's
name, again.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>host</term>
<listitem>
<para>
If the message was sent by a user, this will be
the host portion of their hostmask. If it was
sent by a server, it'll be the server's name (yet
again :))
</para>
</listitem>
</varlistentry>
</variablelist>
</sect2>
<sect2>
<title><classname>irclib.Irc</classname>
<para>
This is the object to handle everything about IRC except the
actual connection to the server itself.
(<emphasis>NOTE</emphasis> that the object actually received
by commands in subclasses of
<classname>callbacks.Privmsg</classname> is an
<classname>IrcObjectProxy</classname>, which is described
later. It augments the following interface with several
methods of its own to help plugin authors.)
</para>
<variablelist>
<title>Interesting methods</title>
<varlistentry>
<term>queueMsg</term>
<listitem>
<para>
Queues a message for sending to the server. The
queue is generally FIFO, but it does prioritize
messages based on their command.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>sendMsg</term>
<listitem>
<para>
Queues a message for sending to the server prior
to any messages in the normal queue. This is
exactly a FIFO queue, no reordering is done at
all.
</para>
</listitem>
</varlistentry>
<!--<note>
<para>
The following two methods are the most important for
people writing new <varname>IrcDriver</varname>s.
Otherwise, you really don't need to pay attention to
them.
</para>
</note>-->
<varlistentry>
<term>feedMsg</term>
<listitem>
<para>
Feeds the <varname>Irc</varname> object a message
for it handle appropriately, as well as passing it
on to callbacks.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>takeMsg</term>
<listitem>
<para>
If the <varname>Irc</varname> object has a message
it's ready to send to the server, this will return
it. Otherwise, it will return
<literal>None</literal>.
</para>
</listitem>
</varlistentry>
<!--<note>
<para>
The next several methods are of far more marginal
utility. But someone may need them, so they're
documented here.
</para>
</note>-->
<varlistentry>
<term>addCallback</term>
<listitem>
<para>
Takes a callback to add to the list of callbacks
in the <varname>Irc</varname> object. See the
interface for <varname>IrcCallback</varname> for
more information.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>getCallback</term>
<listitem>
<para>
Gets a callback by name, if it is in the
<varname>Irc</varname> object's list of callbacks.
If it it isn't, returns <literal>None</literal>.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>removeCallback</term>
<listitem>
<para>
Removes a callback by name. Returns a list of the
callbacks removed (since it is technically
possible to have multiple callbacks with the same
name. This list may be empty.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>__init__</term>
<listitem>
<para>
Requires a <varname>nick</varname>. Optional
arguments include <varname>user</varname> and
<varname>ident</varname>, which default to the
nick given, <varname>password</varname>, which
defaults to the empty password, and
<varname>callbacks</varname>, a list of callbacks
(which defaults to nothing, an empty list).
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>reset</term>
<listitem>
<para>
Resets the <varname>Irc</varname> object to its
original state, as well as sends a
<function>reset()</function> to every callbacks.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>die</term>
<listitem>
<para>
Kills the IRC object and all its callbacks.
</para>
</listitem>
</varlistentry>
</variablelist>
<variablelist>
<title>Interesting attributes</title>
<varlistentry>
<term>nick</term>
<listitem>
<para>
The current nick of the bot.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>prefix</term>
<listitem>
<para>
The current prefix of the bot.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>server</term>
<listitem>
<para>
The current server the bot is connected to.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>network</term>
<listitem>
<para>
The current network name the bot is connected to.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>afterConnect</term>
<listitem>
<para>
<literal>False</literal> until the bot has
received a command sent after the connection is
finished &ndash; 376, 377, or 422.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>state</term>
<listitem>
<para>
An <varname>IrcState</varname> object for this
particular connection. See the interface for the
<varname>IrcState</varname> object for more
information.
</para>
</listitem>
</varlistentry>
</variablelist>
</sect2>
<sect2>
<title><classname>irclib.IrcCallback</classname></title>
<variablelist>
<title>Interesting Methods</title>
<varlistentry>
<term>name</term>
<listitem>
<para>
Returns the name of the callback. The default
implementation simply returns the name of the
class.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>__call__</term>
<listitem>
<para>
Called by the <varname>Irc</varname> object with
itself and the message whenever a message is fed
to the <varname>Irc</varname> object. Nothing is
done with the return value.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>inFilter</term>
<listitem>
<para>
Called by the <varname>Irc</varname> object with
itself and the message whenever a message is fed
to the <varname>Irc</varname> object. The return
value should be an <varname>IrcMsg</varname>
object to be passed to the next callback in the
<varname>Irc</varname>'s list of callbacks. If
<literal>None</literal> is returned, all
processing stops. This gives callbacks an
oppurtunity to "filter" incoming messages before
general callbacks are given them.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>outFilter</term>
<listitem>
<para>
Basically equivalent to
<varname>inFilter</varname>, except instead of
being called on messages as they enter the
<varname>Irc</varname> object, it's called on
messages as they leave the <varname>Irc</varname>
object.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>die</term>
<listitem>
<para>
Called when the parent <varname>Irc</varname> is
told to die. This gives callbacks an oppurtunity
to close open files, network connections, or
databases before they're deleted.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>reset</term>
<listitem>
<para>
Called when the parent <varname>Irc</varname> is
told to reset (which is generally when
reconnecting to the server). Most callbacks don't
need to define this.
</para>
</listitem>
</varlistentry>
</variablelist>
<variablelist>
<title>Interesting attributes</title>
<varlistentry>
<term>priority</term>
<listitem>
<para>
Determines the priority of the callback in the
<varname>Irc</varname> object's list of callbacks.
Defaults to <literal>99</literal>; the valid range
includes <literal>0</literal> through
<literal>sys.maxint-1</literal> (don't use
<literal>sys.maxint</literal> itself, that's
reserved for the <varname>Misc</varname> plugin).
The lower the number, the higher the priority.
High priority callbacks are called earlier in the
<varname>inFilter</varname> cycle, earlier in the
<varname>__call__</varname> cycle, and later in
the <varname>outFilter</varname> cycle &ndash;
basically, they're given the first chances on the
way in and the last chances on the way out.
</para>
</listitem>
</varlistentry>
</variablelist>
</sect2>
<sect2>
<title><classname>callbacks.IrcObjectProxy</classname></title>
<para>
<classname>IrcObjectProxy</classname> is a proxy for an
<classname>irclib.Irc</classname> instance that serves to
provide a much fuller interface for handling replies and
errors as well as to handle the nesting of commands. This is
what you'll be dealing with almost all the time when writing
commands; when writing <function>doCommand</function> methods
(the kind you read about in the interface description of
<classname>irclib.IrcCallback</classname>) you'll be dealing
with plain old <classname>irclib.Irc</classname> objects.
</para>
<variablelist>
<title>Interesting methods</title>
<varlistentry>
<term>reply</term>
<listitem>
<para>
Called to reply to the current message with a
string that is to be the reply. Uses the
<function>queueMsg</function> command discussed in
the <classname>irclib.Irc</classname> section.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>replySuccess</term>
<term>replyError</term>
<listitem>
<para>
These reply with the configured responses for
success and generic error, respectively. If an
additional argument is given, it's (intelligently)
appended to the generic message to be more
specific.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>error</term>
<listitem>
<para>
Called to send an error reply to the current
message; not only does the response indicate an
error, but commands that error out break the
nested-command chain, which is generally useful
for not confusing the user :)
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>errorNoCapability</term>
<listitem>
<para>
Like <function>error</function>, except it accepts
the capability that's missing and integrates it
into the configured error message for such things.
Also accepts an additional string for a more
descriptive message, if that's what you want.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>errorPossibleBug</term>
<term>errorNotRegistered</term>
<term>errorNoUser</term>
<term>errorRequiresPrivacy</term>
<listitem>
<para>
These methods reply with the appropriate
configured error message for the conditions in
their names; they all take an additional arguments
to be more specific about the conditions they
indicate, but this argument is very rarely
necessary.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>getRealIrc</term>
<listitem>
<para>
Returns the actual <classname>Irc</classname>
object being proxied for.
</para>
</listitem>
</varlistentry>
</variablelist>
</sect2>
</sect1>
</article>

View File

@ -0,0 +1,46 @@
(define %stylesheet% "../stylesheets/supybot.css")
(element botcommand
(make element gi: "span"
attributes: '(("class" "botcommand"))
(process-children)))
(element plugin
(make element gi: "span"
attributes: '(("class" "plugin"))
(process-children)))
(element flag
(make element gi: "span"
attributes: '(("class" "flag"))
(process-children)))
(element nick
(make element gi: "span"
attributes: '(("class" "nick"))
(process-children)))
(element capability
(make element gi: "span"
attributes: '(("class" "capability"))
(process-children)))
(element registrygroup
(make element gi: "span"
attributes: '(("class" "registrygroup"))
(process-children)))
(element ircsession
(make element gi: "pre"
attributes: '(("class" "ircsession"))
(process-children)))
(element script
(make element gi: "span"
attributes: '(("class" "script"))
(process-children)))
(element channel
(make element gi: "span"
attributes: '(("class" "channel"))
(process-children)))

View File

@ -0,0 +1,43 @@
(define %mono-font-family% "Courier New")
(element botcommand
(make sequence
font-family-name: %mono-font-family%))
(element plugin
(make sequence
font-weight: 'bold))
(element flag
(make sequence
font-posture: 'italic))
(element nick
(make sequence
font-family-name: %mono-font-family%))
(element capability
(make sequence
font-weight: 'bold))
(element registrygroup
(make sequence
font-weight: 'bold))
(element ircsession
(make paragraph
font-family-name: %mono-font-family%
space-before: 12pt
space-after: 12pt
start-indent: 6pt
lines: 'asis
input-whitespace-treatment: 'preserve))
(element script
(make sequence
font-family-name: %mono-font-family%))
(element channel
(make sequence
font-weight: 'bold))

23
docs/DocBook/supybot.dsl Normal file
View File

@ -0,0 +1,23 @@
<!DOCTYPE style-sheet PUBLIC "-//James Clark//DTD DSSSL Style Sheet//EN" [
<!ENTITY print-ss PUBLIC
"-//Norman Walsh//DOCUMENT DocBook Print Stylesheet//EN" CDATA DSSSL>
<!ENTITY html-ss PUBLIC
"-//Norman Walsh//DOCUMENT DocBook HTML Stylesheet//EN" CDATA DSSSL>
<!ENTITY supybot-print SYSTEM "supybot-print.dsl">
<!ENTITY supybot-html SYSTEM "supybot-html.dsl">
]>
<style-sheet>
<style-specification id="print" use="print-stylesheet">
<style-specification-body>
&supybot-print;
</style-specification-body>
</style-specification>
<style-specification id="html" use="html-stylesheet">
<style-specification-body>
&supybot-html;
</style-specification-body>
</style-specification>
<external-specification id="print-stylesheet" document="print-ss">
<external-specification id="html-stylesheet" document="html-ss">
</style-sheet>

137
docs/DocBook/supybot.dtd Normal file
View File

@ -0,0 +1,137 @@
<!-- Segregate all of our stuff into its own class for possible extension
later and just because I wanted to write my own class :) -->
<!ENTITY % local.supybot.class "">
<!ENTITY % supybot.class "BotCommand|Plugin|Flag|Nick|Capability
|RegistryGroup|Registry|Script
|Channel %local.supybot.class;">
<!-- Stuff that isn't supybot-specific, but it's python-related and no
suitable element exists in the DocBook DTD -->
<!ENTITY % local.python.class "">
<!ENTITY % python.class "Module|Keyword %local.python.class;">
<!-- Pretty much all of our stuff fits where stuff in the tech.char class
goes, so we simply add our stuff using the local extension -->
<!ENTITY % local.tech.char.class "|%supybot.class;|%python.class;">
<!-- linespecific is the same class as things like screen and programlisting,
so it's added here to fit with the DocBook stuff (i.e., so putting an
ircsession in where one of those previous two elements would be is a
valid operation -->
<!ENTITY % local.linespecific.class "|IrcSession">
<!-- Source the original DocBook DTD -->
<!ENTITY % DocBookDTD PUBLIC "-//OASIS//DTD DocBook V4.1//EN">
%DocBookDTD;
<!ELEMENT BotCommand - - ((%smallcptr.char.mix)+)>
<!ENTITY % local.botcommand.attrib "">
<!ENTITY % botcommand.role.attrib "%role.attrib;">
<!ATTLIST BotCommand
%common.attrib;
%local.botcommand.attrib;
%botcommand.role.attrib;
>
<!ELEMENT Plugin - - ((%smallcptr.char.mix)+)>
<!ENTITY % local.plugin.attrib "">
<!ENTITY % plugin.role.attrib "%role.attrib;">
<!ATTLIST Plugin
%common.attrib;
%local.plugin.attrib;
%plugin.role.attrib;
>
<!ELEMENT Flag - - ((%smallcptr.char.mix)+)>
<!ENTITY % local.flag.attrib "">
<!ENTITY % flag.role.attrib
"
flagtype (arg|noarg) #IMPLIED
%role.attrib;"
>
<!ATTLIST Flag
%common.attrib;
%local.flag.attrib;
%flag.role.attrib;
>
<!ELEMENT Nick - - ((%smallcptr.char.mix)+)>
<!ENTITY % local.nick.attrib "">
<!ENTITY % nick.role.attrib "%role.attrib;">
<!ATTLIST Nick
%common.attrib;
%local.nick.attrib;
%nick.role.attrib;
>
<!ELEMENT Capability - - ((%smallcptr.char.mix)+)>
<!ENTITY % local.capability.attrib "">
<!ENTITY % capability.role.attrib "%role.attrib;">
<!ATTLIST Capability
%common.attrib;
%local.capability.attrib;
%capability.role.attrib;
>
<!ELEMENT Comment - - ((%smallcptr.char.mix)+)>
<!ENTITY % local.comment.attrib "">
<!ENTITY % comment.role.attrib "%role.attrib;">
<!ATTLIST Comment
%common.attrib;
%local.comment.attrib;
%comment.role.attrib;
>
<!ELEMENT RegistryGroup - - ((%smallcptr.char.mix)+)>
<!ENTITY % local.registrygroup.attrib "">
<!ENTITY % registrygroup.role.attrib "%role.attrib;">
<!ATTLIST RegistryGroup
%common.attrib;
%local.registrygroup.attrib;
%registrygroup.role.attrib;
>
<!ELEMENT Registry - - ((RegistryGroup|Comment)+)>
<!ENTITY % local.registry.attrib "">
<!ENTITY % registry.role.attrib "%role.attrib;">
<!ATTLIST Registry
%common.attrib;
%local.registry.attrib;
%registry.role.attrib;
>
<!ELEMENT IrcSession - - ((%smallcptr.char.mix)+)>
<!ENTITY % local.ircsession.attrib "">
<!ENTITY % ircsession.role.attrib "%role.attrib;">
<!ATTLIST IrcSession
%common.attrib;
%local.ircsession.attrib;
%ircsession.role.attrib;
>
<!ELEMENT Script - - ((%smallcptr.char.mix)+)>
<!ENTITY % local.script.attrib "">
<!ENTITY % script.role.attrib "%role.attrib;">
<!ATTLIST Script
%common.attrib;
%local.script.attrib;
%script.role.attrib;
>
<!ELEMENT Channel - - ((%smallcptr.char.mix)+)>
<!ENTITY % local.channel.attrib "">
<!ENTITY % channel.role.attrib "%role.attrib;">
<!ATTLIST Channel
%common.attrib;
%local.channel.attrib;
%channel.role.attrib;
>
<!ELEMENT Module - - ((%smallcptr.char.mix)+)>
<!ENTITY % local.module.attrib "">
<!ENTITY % module.role.attrib "%role.attrib;">
<!ATTLIST Module
%common.attrib;
%local.module.attrib;
%module.role.attrib;
>