mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-11-27 13:19:24 +01:00
208 lines
9.4 KiB
Plaintext
208 lines
9.4 KiB
Plaintext
Here's an example of how to code a few callbacks for SupyBot.
|
|
|
|
Let's say you want to make an annoying "Mimic" callback that repeats everything
|
|
anyone says to the bot or on the channels the bot is in. That
|
|
shouldn't be too hard. First, you'll need to make a new module to
|
|
hold the plugin:
|
|
|
|
$ scripts/newplugin.py Mimic
|
|
|
|
That'll make the file plugins/Mimic.py, which will
|
|
|
|
|
|
[code]
|
|
class AnnoyingMimic(irclib.IrcCallback):
|
|
def doPrivmsg(self, irc, msg):
|
|
irc.queueMsg(ircmsgs.privmsg(msg.args[0], msg.args[1]))
|
|
[/code]
|
|
|
|
Almost every callback will inherit from irclib.IrcCallback somewhere in their
|
|
class hierarchy. irclib.IrcCallback does a lot of the basic stuff that call-
|
|
backs have to do, and inheriting from it relieves the programmer from such
|
|
pedantries. All you have to do to start writing callbacks inheriting from
|
|
irclib.IrcCallback is write functions of the form "doCommand", where "Command"
|
|
is a valid ircCommand. The "ChannelLogger" plugin is a good illustrative
|
|
example of a callback being called on different commands.
|
|
|
|
The "irc" argument there is the irc object that is calling the callback. To
|
|
see what kind of interface it provides, read the class definition in irclib.
|
|
Really, you only need to know a few methods if you're writing simple callbacks:
|
|
'queueMsg', which queues a message to be sent later, and 'sendMsg' which tries
|
|
to send it right away (well, as soon as the Irc object's driver asks for
|
|
another message to send, which is generally right away). The Irc object also
|
|
provides some attributes that might come in useful, most notably "nick" (the
|
|
nick of the bot) and "state" (an IrcState object that does various useful
|
|
things like keeping a history of the most recent irc messages.)
|
|
|
|
Irc messsages are represented by the IrcMsg class in ircmsgs. It has several
|
|
useful methods and attributes, but it's probably easier for you to read the
|
|
code than for me to tell you about it. The ircmsgs module also provides a set
|
|
of useful little commands to create IrcMsg objects that do particular little
|
|
things; for instance, ircmsgs.privmsg(recipient, msg) sends a PRIVMSG command
|
|
to a channel or user (whatever recipient turns out to be). Check out the code
|
|
to see other functions for making IrcMsg objects.
|
|
|
|
Now, that wasn't too bad. Now, however you're going to have to get it into the
|
|
bot. Note that AnnoyingMimic doesn't have an __init__. This'll make it pretty
|
|
simple to get it into the configuration system. Look for the section of the
|
|
config file where you see all the configurations for Irc objects. These
|
|
configurations are going to be lists of (class name, args, kwargs) tuples which
|
|
contain the name of a callback class to be instantiated, a tuple of the argu-
|
|
ments to be passed to the __init__ function for that class, and a dictionary
|
|
of the keyword arguments to be passed to the __init__ function of that class.
|
|
For instance, if AnnoyingMimic was in a file 'mycallbacks.py', its config-
|
|
uration in the config file would look like this:
|
|
|
|
[code]
|
|
('mycallbacks.AnnoyingMimic', (), {})
|
|
[/code]
|
|
|
|
Since it doesn't have an __init__, there are no arguments or keyword arguments
|
|
to pass to the class. Just throw something like that in a list of callbacks
|
|
that you use for your bot (you can have several lists, you'll notice later on
|
|
in the 'drivers' variable that they're used), and you're ready to go!
|
|
|
|
Now, let's say you want to make your AnnoyingMimic class a little less
|
|
annoying. Now, you only want to mimic people *you* find annoying. The easiest
|
|
way to do that is to make it so you tell the class who to mimic when you
|
|
instantiate it. This means adding an __init__ function, and modifying your
|
|
configuration slightly.
|
|
|
|
[code]
|
|
class AnnoyingMimic(irclib.IrcCallback):
|
|
def __init__(self, nicksToAnnoy):
|
|
self.nicksToAnnoy = nicksToAnnoy
|
|
|
|
def doPrivmsg(self, irc, msg):
|
|
if msg.nick() in self.nicksToAnnoy:
|
|
irc.queueMsg(ircmsgs.privmsg(msg.args[0], msg.args[1]))
|
|
[/code]
|
|
|
|
(Now, really, to make this efficient, you'd want a slightly different version
|
|
that turned the nicksToAnnoy argument into a dictionary so nick lookups would
|
|
be O(1) instead of O(n) in the length of the list of nicks to annoy, but that
|
|
would obfuscate the problem. I'll leave that as an exercise left up to the
|
|
reader.)
|
|
|
|
So now your AnnoyingMimic class has an __init__ function that accepts a list
|
|
of nicks to annoy, but how do you pass it those nicks? Simple! Change the
|
|
configuration slightly:
|
|
|
|
[code]
|
|
('mycallbacks.AnnoyingMimic', (['jemfinch', 'GnuVince'],), {})
|
|
[/code]
|
|
|
|
That's the wonder of this configuration system -- you can use all the Python
|
|
syntax you want, so you have practically unlimited flexibility.
|
|
|
|
(Note that since the 'arguments' member of that tuple is a single-member tuple,
|
|
you'll have to stick a comma after the first (only) element because otherwise
|
|
Python wouldn't believe it's a tuple.)
|
|
|
|
So, again, you choose to make your AnnoyingMimic less annoying -- really, you
|
|
decide to make it not annoying at all by making it only mimic people who ask to
|
|
be repeated. You want to make a class that has an "echo" command that repeats
|
|
the message to those who ask it. You want people to be able to tell the bot,
|
|
"echo The quick brown fox jumps over the lazy dog!" and have the bot say right
|
|
back, "The quick brown fox jumps over the lazy dog!". That's easy! Here's the
|
|
code:
|
|
|
|
[code]
|
|
reply = callbacks.reply
|
|
|
|
class Echo(callbacks.Privmsg):
|
|
def echo(self, irc, msg, args):
|
|
"<text>"
|
|
text = self.getArgs(args)
|
|
self.reply(text)
|
|
[/code]
|
|
|
|
So that seemed pretty simple there, too. Let's explain what's going on:
|
|
|
|
callbacks.Privmsg is an easy way to create "commands" which are simple named
|
|
functions that use a universal scheme for delimiting arguments -- basically,
|
|
they'll all act the same in how they get their arguments. callbacks.Privmsg
|
|
takes a Privmsg (it has a doPrivmsg function and inherits from
|
|
irclib.IrcCallback) and first determines if it's addressed to the bot -- the
|
|
message must either be PRIVMSGed directly to the bot, or PRIVMSGed over a
|
|
channel the bot is in and either start with a character in conf.prefixchars or
|
|
start with the bot's name. Don't worry, callbacks.Privmsg almost always does
|
|
The Right Thing. After deciding that the bot has been addressed,
|
|
callbacks.Privmsg then parses the text of the message into a list of strings.
|
|
Here are a few examples of what it would do:
|
|
|
|
"""arg1 arg2 arg3"""
|
|
['arg1', 'arg2', 'arg3']
|
|
|
|
"""'arg1 arg2 arg3' arg4""" # Note the quotes.
|
|
['arg1 arg2 arg3', 'arg4']
|
|
|
|
getArgs is a function that just a little bit of magic. It takes an optional
|
|
argument (that defaults to 1) of the number of args needed. If more than one
|
|
argument is needed, it checks that the proper number of arguments has been
|
|
given, and then returns a tuple of those arguments. So if you wanted 3 args
|
|
from a message, you'd do something like this:
|
|
|
|
(name, oldpassword, newpassword) = self.getArgs(args, 3)
|
|
|
|
See how simple that is? If getArgs only needs one argument, however, it does
|
|
something a bit magic -- first of all, it doesn't return a tuple, it just
|
|
returns the argument itself. This makes it so you can type:
|
|
|
|
text = self.getArgs(args)
|
|
|
|
Instead of:
|
|
|
|
(text,) = self.getArgs(args)
|
|
|
|
It just makes things easier that way. Also, however, if *only* one argument
|
|
is needed, it does something a bit more magical. A lot of commands take only
|
|
one argument and then do some processing on it -- for example, look at the
|
|
privmsgs module, the "FunCommands" callback, at the commands 'leet' and
|
|
'rot13'. This is all great, but because of the way args are normally parsed
|
|
by callbacks.Privmsg, you'd have to always enclose that argument in quotes.
|
|
For instance, you'd have to type this:
|
|
|
|
bot: leet "The quick brown fox jumps over the lazy dog."
|
|
|
|
From experience, I can tell you that most people will forget the quotes almost
|
|
every time they talk to the bot. Since having only one argument is such a
|
|
command case, getArgs special-cases it to string.join all the args with spaces.
|
|
Now you can say:
|
|
|
|
bot: leet The quick brown fox jumps over the lazy dog.
|
|
|
|
And it'll return the same exact thing as above. Of course, the original still
|
|
works, but since people forget the quotes so often, it's good to go easy on
|
|
them :) We're actually using that behavior with our callback above: by using
|
|
getArgs, now our users can say:
|
|
|
|
echo foo bar baz
|
|
|
|
Instead of always having to say:
|
|
|
|
echo "foo bar baz"
|
|
|
|
Anyway, you're probably wondering how that callback works. It inherits from
|
|
callbacks.Privmsg, which as I mentioned before, has a doPrivmsg callback. So
|
|
when callbacks.Privmsg receives a PRIVMSG command, it parses it and then tries
|
|
to find if it has a method by the same name as the command -- if it does, and
|
|
that method looks like this:
|
|
|
|
def method(self, irc, msg, args):
|
|
...
|
|
|
|
Then it calls that method with the appropriate arguments. Easy, huh? Don't
|
|
worry, it gets even cooler :) So you write a command like echo and you want
|
|
to provide the user with some help using it. You were probably wondering why
|
|
the docstring to that "echo" method above looked so weird, but now you know:
|
|
it *is* the help for the command! callbacks.Privmsg has its own command, help,
|
|
which will return the *docstring* for any other command! So it's cake to write
|
|
your own commands and help.
|
|
|
|
(This, of course, means that if you *don't* write a help string for your
|
|
command, you have no excuse and are just plain lazy. So write help strings!)
|
|
|
|
There's a bit more I could tutorialize on, but it would be more esoteric, and
|
|
better a reference material than as a tutorial. I'll put that in another file.
|