mirror of
https://github.com/Mikaela/Limnoria.git
synced 2025-01-11 04:32:36 +01:00
Updated.
This commit is contained in:
parent
c563559b29
commit
8b20ad77fa
168
docs/EXAMPLE
168
docs/EXAMPLE
@ -1,19 +1,19 @@
|
||||
Ok, so you want to write a callback for supybot. Good, then this is
|
||||
Ok, so you want to write a callback for Supybot. Good, then this is
|
||||
the place to be. We're going to start from the top (the highest
|
||||
level, where supybot code does the most work for you) and move lower
|
||||
level, where Supybot code does the most work for you) and move lower
|
||||
after that.
|
||||
|
||||
So have you used supybot? If not, you need to go use it, get a feel
|
||||
So have you used Supybot? If not, you need to go use it, get a feel
|
||||
for it, see how the various commands work and such.
|
||||
|
||||
So now that we know you've used supybot, we'll start getting into
|
||||
So now that we know you've used Supybot, we'll start getting into
|
||||
details.
|
||||
|
||||
First, the easiest way to start writing a module is to use the wizard
|
||||
provided, scripts/newplugin.py. Here's an example session:
|
||||
|
||||
-----
|
||||
functor% scripts/newplugin.py
|
||||
functor% supybot-newplugin
|
||||
What should the name of the plugin be? Random
|
||||
Supybot offers two major types of plugins: command-based and regexp-
|
||||
based. Command-based plugins are the kind of plugins you've seen most
|
||||
@ -48,7 +48,7 @@ is available as examples/Random.py):
|
||||
#!/usr/bin/env python
|
||||
|
||||
###
|
||||
# Copyright (c) 2002, Jeremiah Fincher
|
||||
# Copyright (c) 2004, Jeremiah Fincher
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
@ -82,18 +82,19 @@ Add the module docstring here. This will be used by the setup.py script.
|
||||
|
||||
import plugins
|
||||
|
||||
import conf
|
||||
import utils
|
||||
import privmsgs
|
||||
import callbacks
|
||||
|
||||
|
||||
def configure(onStart, afterConnect, advanced):
|
||||
# This will be called by setup.py to configure this module. onStart and
|
||||
# afterConnect are both lists. Append to onStart the commands you would
|
||||
# like to be run when the bot is started; append to afterConnect the
|
||||
# commands you would like to be run when the bot has finished connecting.
|
||||
def configure(advanced):
|
||||
# This will be called by setup.py to configure this module. Advanced is
|
||||
# a bool that specifies whether the user identified himself as an advanced
|
||||
# user or not. You should effect your configuration by manipulating the
|
||||
# registry as appropriate.
|
||||
from questions import expect, anything, something, yn
|
||||
onStart.append('load Random')
|
||||
conf.registerPlugin('Random', True)
|
||||
|
||||
class Random(callbacks.Privmsg):
|
||||
pass
|
||||
@ -110,7 +111,7 @@ You'll probably want to change the copyright notice to be your name.
|
||||
It wouldn't stick even if you kept my name, so you might as well :)
|
||||
|
||||
Describe what you want the plugin to do in the docstring. This is
|
||||
used in scripts/setup.py in order to explain to the user the purpose
|
||||
used in supybot-wizard 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 this one to "Lots of stuff relating to random numbers."
|
||||
@ -120,12 +121,12 @@ you're given subclasses callbacks.Privmsg) but the privmsgs 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.
|
||||
|
||||
Then you see a "configure" function. This the function that's called
|
||||
when users decide to add your module in scripts/setup.py. You'll note
|
||||
that by default it simply adds "load Example" (where 'Example' is the name you
|
||||
provided as the name of your plugin, so in our case it is "load Random") at
|
||||
the bottom. For many plugins this is all you need; for more complex plugins,
|
||||
you might need to ask questions and add commands based on the answers.
|
||||
Then you see a "configure" function. This is the function that's
|
||||
called when users decide to add your module in supybot-wizard. You'll
|
||||
note that by default it simply registers the plugin to be
|
||||
automatically loaded on startup. For many plugins this is all you
|
||||
need; for more complex plugins, you might need to ask questions and
|
||||
add commands based on the answers.
|
||||
|
||||
Now comes the meat of the plugin: the plugin class.
|
||||
|
||||
@ -159,7 +160,7 @@ self, of course). There's no way anything will ever get to them! If
|
||||
you have some sort of initial values you need to get to your plugin
|
||||
before it can do anything interesting, add a command that gets those
|
||||
values. By convention, those commands begin with "start" -- check out
|
||||
the Relay and Enforcer plugins for examples of such commands.
|
||||
the Relay plugin for an example of such a command.
|
||||
|
||||
There's an easier way to get our plugin to have its own rng than to
|
||||
define an __init__. Plugins are unique among classes because we're
|
||||
@ -184,7 +185,7 @@ explanation of what each part means.
|
||||
Returns the next random number generated by the random number
|
||||
generator.
|
||||
"""
|
||||
irc.reply(msg, str(self.rng.random()))
|
||||
irc.reply(str(self.rng.random()))
|
||||
|
||||
And that's it! Pretty simple, huh? Anyway, you're probably wondering
|
||||
what all that *means*. We'll start with the def statement:
|
||||
@ -205,8 +206,8 @@ whitespace -- the work has already been done for you). You can read
|
||||
about the Irc object in irclib.py (you won't find .reply or .error
|
||||
there, though, because you're actually getting an IrcObjectProxy, but
|
||||
that's beyond the level we want to describe here :)). You can read
|
||||
about the msg object in ircmsgs.py. But again, aside from calling
|
||||
irc.reply or irc.error, you'll very rarely be using these objects.
|
||||
about the msg object in ircmsgs.py. But again, you'll very rarely be
|
||||
using these objects.
|
||||
|
||||
(In case you're curious, the answer is yes, you *must* name your
|
||||
arguments (self, irc, msg, args). The names of those arguments is one
|
||||
@ -225,29 +226,21 @@ the supybot framework is that it's easy to write complete commands
|
||||
with help and everything: the docstring *IS* the help! Given the
|
||||
above docstring, this is what a supybot does:
|
||||
|
||||
<angryman> jemfinch: random takes no arguments (for more help
|
||||
use the morehelp command)
|
||||
<jemfinch> $morehelp random
|
||||
<angryman> jemfinch: Returns the next random number from the
|
||||
current random number generator.
|
||||
<jemfinch> @help random
|
||||
<angryman> jemfinch: (random takes no arguments) -- Returns the
|
||||
next random number from the random number generator.
|
||||
|
||||
'help <command>' replies with the command name followed by the first line of
|
||||
the command's docstring; there should be a blank line following, and then
|
||||
'morehelp <command>' will reply with the remainder of the docstring. So that
|
||||
explains the docstring. Now on to the actual body of the function:
|
||||
Now on to the actual body of the function:
|
||||
|
||||
irc.reply(msg, str(self.rng.random()))
|
||||
irc.reply(str(self.rng.random()))
|
||||
|
||||
irc.reply takes two arguments, an IrcMsg (like the one passed into
|
||||
your function) and a string. The IrcMsg 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 irc.reply a string. It doesn't take anything
|
||||
else (sometimes even unicode fails!). That's why we have
|
||||
irc.reply takes one simple argument: a string. 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 irc.reply 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 irc.reply a string.
|
||||
|
||||
@ -268,10 +261,10 @@ thing. So we'll add a seed command to give the RNG a specific seed:
|
||||
seed = long(seed)
|
||||
except ValueError:
|
||||
# It wasn't a valid long!
|
||||
irc.error(msg, '<seed> must be a valid int or long.')
|
||||
irc.error('<seed> must be a valid int or long.')
|
||||
return
|
||||
self.rng.seed(seed)
|
||||
irc.reply(msg, conf.replySuccess)
|
||||
irc.replySuccess()
|
||||
|
||||
So this one's a bit more complicated. But it's still pretty simple.
|
||||
The method name is "seed" so that'll be the command name. The
|
||||
@ -280,7 +273,7 @@ need to go over that again. The body of the function, however, is
|
||||
significantly different.
|
||||
|
||||
privmsgs.getArgs is a function you're going to be seeing a lot of when
|
||||
you write plugins for supybot. What it does is basically give you the
|
||||
you write plugins for Supybot. What it does is basically give you the
|
||||
right number of arguments for your comamnd. In this case, we want one
|
||||
argument. But we might have been given any number of arguments by the
|
||||
user. So privmsgs.getArgs joins them appropriately, leaving us with
|
||||
@ -294,7 +287,7 @@ user didn't give us enough arguments, it'll reply with the help string
|
||||
for the command, thus saving us the effort.
|
||||
|
||||
So we have the seed from privmsgs.getArgs. But it's a string. The
|
||||
next three lines is pretty darn obvious: we're just converting the
|
||||
next three lines are pretty darn obvious: we're just converting the
|
||||
string to a int of some sort. But if it's not, that's when we're
|
||||
going to call irc.error. It has the same interface as we saw before
|
||||
in irc.reply, but it makes sure to remind the user that an error has
|
||||
@ -335,10 +328,10 @@ function:
|
||||
end = int(end)
|
||||
start = int(start)
|
||||
except ValueError:
|
||||
irc.error(msg, '<start> and <end> must both be integers.')
|
||||
irc.error('<start> and <end> must both be integers.')
|
||||
return
|
||||
# .randrange() doesn't include the endpoint, so we use end+1.
|
||||
irc.reply(msg, str(self.rng.randrange(start, end+1)))
|
||||
irc.reply(str(self.rng.randrange(start, end+1)))
|
||||
|
||||
Pretty simple. This is becoming old hat by now. The only new thing
|
||||
here is the call to privmsgs.getArgs. We have to make sure, since we
|
||||
@ -365,14 +358,14 @@ of arguments isn't right). Here's the code:
|
||||
except IndexError: # raised by .pop(0)
|
||||
raise callbacks.ArgumentError
|
||||
except ValueError:
|
||||
irc.error(msg, '<number of items> must be an integer.')
|
||||
irc.error('<number of items> must be an integer.')
|
||||
return
|
||||
if n > len(args):
|
||||
irc.error(msg, '<number of items> must be less than the number '
|
||||
'of arguments.')
|
||||
irc.error('<number of items> must be less than the number '
|
||||
'of arguments.')
|
||||
return
|
||||
sample = self.rng.sample(args, n)
|
||||
irc.reply(msg, utils.commaAndify(map(repr, sample)))
|
||||
irc.reply(utils.commaAndify(map(repr, sample)))
|
||||
|
||||
Most everything here is familiar. The difference between this and the
|
||||
previous examples is that we're dealing with args directly, rather
|
||||
@ -414,11 +407,10 @@ __" where __ is the number the bot rolled. So here's the code:
|
||||
n = 6
|
||||
n = int(n)
|
||||
except ValueError:
|
||||
irc.error(msg, 'Dice have integer numbers of sides. Use one.')
|
||||
irc.error('Dice have integer numbers of sides. Use one.')
|
||||
return
|
||||
s = 'rolls a %s' % self.rng.randrange(1, n+1)
|
||||
irc.queueMsg(ircmsgs.action(ircutils.replyTo(msg), s))
|
||||
raise callbacks.CannotNest
|
||||
irc.reply(s, action=True)
|
||||
|
||||
There's a lot of stuff you haven't seen before in there. The most
|
||||
important, though, is the first thing you'll notice that's different:
|
||||
@ -430,24 +422,9 @@ tell it that we don't *need* any arguments (via required=0) and that we
|
||||
argument, we'll get it -- if they don't, we'll just get an empty
|
||||
string. Hence the "if not n: n = 6", where we provide the default.
|
||||
|
||||
Later, though, you'll see something other than irc.reply. This is
|
||||
irc.queueMsg, the general interface for sending messages to the
|
||||
server. It's what irc.reply is using under the covers. It takes an
|
||||
IrcMsg object. Fortunately, that's exactly what's returned by
|
||||
ircmsgs.action. An action message, just in case you don't know, is a
|
||||
/me kind of message. ircmsgs.action 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 IrcMsg
|
||||
object. ircutils.replyTo simply takes an IrcMsg 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.
|
||||
|
||||
At the end, you might be surprised by the "raise callbacks.CannotNest".
|
||||
That's used simply because at the moment you can't nest actions (just like
|
||||
you can't nest anything that doesn't go through irc.reply). That raise just
|
||||
makes sure the user finds this out if he tries to nest this like "@rot13
|
||||
[diceroll]".
|
||||
You'll also note that irc.reply was given a keyword argument here,
|
||||
"action". This means that the reply is to be made as an action rather
|
||||
than a normal reply.
|
||||
|
||||
So that's our plugin. 5 commands, each building in complexity. You
|
||||
should now be able to write most anything you want to do in Supybot.
|
||||
@ -455,47 +432,8 @@ Except regexp-based plugins, but that's a story for another day (and
|
||||
those aren't nearly as cool as these command-based callbacks anyway
|
||||
:)). Now we need to flesh it out to make it a full-fledged plugin.
|
||||
|
||||
Let's take a look at that configure function newplugin.py made for
|
||||
us. Here it is, in case you've forgotten:
|
||||
|
||||
def configure(onStart, afterConnect, advanced):
|
||||
# This will be called by setup.py to configure this module. onStart and
|
||||
# afterConnect are both lists. Append to onStart the commands you would
|
||||
# like to be run when the bot is started; append to afterConnect the
|
||||
# commands you would like to be run when the bot has finished connecting.
|
||||
from questions import expect, anything, something, yn
|
||||
onStart.append('load Random')
|
||||
|
||||
You remember when you first started running supybot and ran
|
||||
scripts/setup.py 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 Random 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.
|
||||
|
||||
def configure(onStart, afterConnect, advanced):
|
||||
# This will be called by setup.py to configure this module. onStart and
|
||||
# afterConnect are both lists. Append to onStart the commands you would
|
||||
# like to be run when the bot is started; append to afterConnect the
|
||||
# commands you would like to be run when the bot has finished connecting.
|
||||
from questions import expect, anything, something, yn
|
||||
onStart.append('load Random')
|
||||
if yn('Do you want to specify a seed to be used for the RNG')=='y':
|
||||
seed = something('What seed? It must be an int or long.')
|
||||
while not seed.isdigit():
|
||||
print 'That\'s not a valid seed.'
|
||||
seed = something('What seed?')
|
||||
onStart.append('seed %s' % seed)
|
||||
|
||||
As you can see, what the questions module does is fairly self-evident:
|
||||
yn returns either 'y' or 'n'; something returns *something* (but not
|
||||
nothing; for nothing, you'd want anything). So basically we ask some
|
||||
questions until we get a good seed. Then we do this
|
||||
"onStart.append('seed %s' % seed)" doohickey. onStart 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 scripts/setup.py creates for the bot.
|
||||
TODO: Describe the registry and how to write a proper plugin configure
|
||||
function.
|
||||
|
||||
We've written our own plugin from scratch (well, from the boilerplate
|
||||
that we got from scripts/newplugin.py :)) and survived! Now go write
|
||||
|
Loading…
Reference in New Issue
Block a user