From 8b20ad77fa1be53347876022e2aff5444a58ab84 Mon Sep 17 00:00:00 2001 From: Jeremy Fincher Date: Thu, 19 Feb 2004 07:21:07 +0000 Subject: [PATCH] Updated. --- docs/EXAMPLE | 168 ++++++++++++++++----------------------------------- 1 file changed, 53 insertions(+), 115 deletions(-) diff --git a/docs/EXAMPLE b/docs/EXAMPLE index f5f74f3a8..2e159c171 100644 --- a/docs/EXAMPLE +++ b/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: - jemfinch: random takes no arguments (for more help - use the morehelp command) - $morehelp random - jemfinch: Returns the next random number from the - current random number generator. + @help random + jemfinch: (random takes no arguments) -- Returns the + next random number from the random number generator. -'help ' 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 ' 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, ' must be a valid int or long.') + irc.error(' 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, ' and must both be integers.') + irc.error(' and 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, ' must be an integer.') + irc.error(' must be an integer.') return if n > len(args): - irc.error(msg, ' must be less than the number ' - 'of arguments.') + irc.error(' 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