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
|
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.
|
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.
|
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.
|
details.
|
||||||
|
|
||||||
First, the easiest way to start writing a module is to use the wizard
|
First, the easiest way to start writing a module is to use the wizard
|
||||||
provided, scripts/newplugin.py. Here's an example session:
|
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
|
What should the name of the plugin be? Random
|
||||||
Supybot offers two major types of plugins: command-based and regexp-
|
Supybot offers two major types of plugins: command-based and regexp-
|
||||||
based. Command-based plugins are the kind of plugins you've seen most
|
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
|
#!/usr/bin/env python
|
||||||
|
|
||||||
###
|
###
|
||||||
# Copyright (c) 2002, Jeremiah Fincher
|
# Copyright (c) 2004, Jeremiah Fincher
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
# Redistribution and use in source and binary forms, with or without
|
# 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 plugins
|
||||||
|
|
||||||
|
import conf
|
||||||
import utils
|
import utils
|
||||||
import privmsgs
|
import privmsgs
|
||||||
import callbacks
|
import callbacks
|
||||||
|
|
||||||
|
|
||||||
def configure(onStart, afterConnect, advanced):
|
def configure(advanced):
|
||||||
# This will be called by setup.py to configure this module. onStart and
|
# This will be called by setup.py to configure this module. Advanced is
|
||||||
# afterConnect are both lists. Append to onStart the commands you would
|
# a bool that specifies whether the user identified himself as an advanced
|
||||||
# like to be run when the bot is started; append to afterConnect the
|
# user or not. You should effect your configuration by manipulating the
|
||||||
# commands you would like to be run when the bot has finished connecting.
|
# registry as appropriate.
|
||||||
from questions import expect, anything, something, yn
|
from questions import expect, anything, something, yn
|
||||||
onStart.append('load Random')
|
conf.registerPlugin('Random', True)
|
||||||
|
|
||||||
class Random(callbacks.Privmsg):
|
class Random(callbacks.Privmsg):
|
||||||
pass
|
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 :)
|
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
|
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
|
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
|
for a given module (instead of help for a certain command). We'll
|
||||||
change this one to "Lots of stuff relating to random numbers."
|
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
|
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.
|
we go ahead and add the import to the template.
|
||||||
|
|
||||||
Then you see a "configure" function. This the function that's called
|
Then you see a "configure" function. This is the function that's
|
||||||
when users decide to add your module in scripts/setup.py. You'll note
|
called when users decide to add your module in supybot-wizard. You'll
|
||||||
that by default it simply adds "load Example" (where 'Example' is the name you
|
note that by default it simply registers the plugin to be
|
||||||
provided as the name of your plugin, so in our case it is "load Random") at
|
automatically loaded on startup. For many plugins this is all you
|
||||||
the bottom. For many plugins this is all you need; for more complex plugins,
|
need; for more complex plugins, you might need to ask questions and
|
||||||
you might need to ask questions and add commands based on the answers.
|
add commands based on the answers.
|
||||||
|
|
||||||
Now comes the meat of the plugin: the plugin class.
|
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
|
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
|
before it can do anything interesting, add a command that gets those
|
||||||
values. By convention, those commands begin with "start" -- check out
|
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
|
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
|
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
|
Returns the next random number generated by the random number
|
||||||
generator.
|
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
|
And that's it! Pretty simple, huh? Anyway, you're probably wondering
|
||||||
what all that *means*. We'll start with the def statement:
|
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
|
about the Irc object in irclib.py (you won't find .reply or .error
|
||||||
there, though, because you're actually getting an IrcObjectProxy, but
|
there, though, because you're actually getting an IrcObjectProxy, but
|
||||||
that's beyond the level we want to describe here :)). You can read
|
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
|
about the msg object in ircmsgs.py. But again, you'll very rarely be
|
||||||
irc.reply or irc.error, you'll very rarely be using these objects.
|
using these objects.
|
||||||
|
|
||||||
(In case you're curious, the answer is yes, you *must* name your
|
(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
|
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
|
with help and everything: the docstring *IS* the help! Given the
|
||||||
above docstring, this is what a supybot does:
|
above docstring, this is what a supybot does:
|
||||||
|
|
||||||
<angryman> jemfinch: random takes no arguments (for more help
|
<jemfinch> @help random
|
||||||
use the morehelp command)
|
<angryman> jemfinch: (random takes no arguments) -- Returns the
|
||||||
<jemfinch> $morehelp random
|
next random number from the random number generator.
|
||||||
<angryman> jemfinch: Returns the next random number from the
|
|
||||||
current random number generator.
|
|
||||||
|
|
||||||
'help <command>' replies with the command name followed by the first line of
|
Now on to the actual body of the function:
|
||||||
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:
|
|
||||||
|
|
||||||
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
|
irc.reply takes one simple argument: a string. The string is the
|
||||||
your function) and a string. The IrcMsg is used to determine who the
|
reply to be sent. Don't worry about length restrictions or anything
|
||||||
reply should go to and whether or not it should be sent in private
|
-- if the string you want to send is too big for an IRC message (and
|
||||||
message (commands sent in private are replied to in private). The
|
oftentimes that turns out to be the case :)) the Supybot framework
|
||||||
string is the reply to be sent. Don't worry about length restrictions
|
handles that entirely transparently to you. Do make sure, however,
|
||||||
or anything -- if the string you want to send is too big for an IRC
|
that you give irc.reply a string. It doesn't take anything else
|
||||||
message (and oftentimes that turns out to be the case :)) the supybot
|
(sometimes even unicode fails!). That's why we have
|
||||||
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
|
"str(self.rng.random())" instead of simply "self.rng.random()" -- we
|
||||||
had to give irc.reply a string.
|
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)
|
seed = long(seed)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# It wasn't a valid long!
|
# 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
|
return
|
||||||
self.rng.seed(seed)
|
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.
|
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
|
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.
|
significantly different.
|
||||||
|
|
||||||
privmsgs.getArgs is a function you're going to be seeing a lot of when
|
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
|
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
|
argument. But we might have been given any number of arguments by the
|
||||||
user. So privmsgs.getArgs joins them appropriately, leaving us with
|
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.
|
for the command, thus saving us the effort.
|
||||||
|
|
||||||
So we have the seed from privmsgs.getArgs. But it's a string. The
|
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
|
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
|
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
|
in irc.reply, but it makes sure to remind the user that an error has
|
||||||
@ -335,10 +328,10 @@ function:
|
|||||||
end = int(end)
|
end = int(end)
|
||||||
start = int(start)
|
start = int(start)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
irc.error(msg, '<start> and <end> must both be integers.')
|
irc.error('<start> and <end> must both be integers.')
|
||||||
return
|
return
|
||||||
# .randrange() doesn't include the endpoint, so we use end+1.
|
# .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
|
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
|
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)
|
except IndexError: # raised by .pop(0)
|
||||||
raise callbacks.ArgumentError
|
raise callbacks.ArgumentError
|
||||||
except ValueError:
|
except ValueError:
|
||||||
irc.error(msg, '<number of items> must be an integer.')
|
irc.error('<number of items> must be an integer.')
|
||||||
return
|
return
|
||||||
if n > len(args):
|
if n > len(args):
|
||||||
irc.error(msg, '<number of items> must be less than the number '
|
irc.error('<number of items> must be less than the number '
|
||||||
'of arguments.')
|
'of arguments.')
|
||||||
return
|
return
|
||||||
sample = self.rng.sample(args, n)
|
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
|
Most everything here is familiar. The difference between this and the
|
||||||
previous examples is that we're dealing with args directly, rather
|
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 = 6
|
||||||
n = int(n)
|
n = int(n)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
irc.error(msg, 'Dice have integer numbers of sides. Use one.')
|
irc.error('Dice have integer numbers of sides. Use one.')
|
||||||
return
|
return
|
||||||
s = 'rolls a %s' % self.rng.randrange(1, n+1)
|
s = 'rolls a %s' % self.rng.randrange(1, n+1)
|
||||||
irc.queueMsg(ircmsgs.action(ircutils.replyTo(msg), s))
|
irc.reply(s, action=True)
|
||||||
raise callbacks.CannotNest
|
|
||||||
|
|
||||||
There's a lot of stuff you haven't seen before in there. The most
|
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:
|
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
|
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.
|
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
|
You'll also note that irc.reply was given a keyword argument here,
|
||||||
irc.queueMsg, the general interface for sending messages to the
|
"action". This means that the reply is to be made as an action rather
|
||||||
server. It's what irc.reply is using under the covers. It takes an
|
than a normal reply.
|
||||||
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]".
|
|
||||||
|
|
||||||
So that's our plugin. 5 commands, each building in complexity. You
|
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.
|
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
|
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.
|
:)). 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
|
TODO: Describe the registry and how to write a proper plugin configure
|
||||||
us. Here it is, in case you've forgotten:
|
function.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
We've written our own plugin from scratch (well, from the boilerplate
|
We've written our own plugin from scratch (well, from the boilerplate
|
||||||
that we got from scripts/newplugin.py :)) and survived! Now go write
|
that we got from scripts/newplugin.py :)) and survived! Now go write
|
||||||
|
Loading…
Reference in New Issue
Block a user