Dev Week -- Developing IRC Bots -- tsimpson -- Wed, Mar 2nd, 2011

   1 [19:01] <tsimpson> Hello everyone \o
   2 [19:01] <ClassBot> Logs for this session will be available at following the conclusion of the session.
   3 [19:01] <tsimpson> my name is Terence Simpson, and I'm lead developer of the Ubuntu Bots project (that's the one that gives us ubottu and friends)
   4 [19:01] <tsimpson> I'm also an IRC operator in several Ubuntu channels, and a councillor on the Ubuntu IRC Council
   5 [19:01] <tsimpson> so I have come to know IRC quite well over the years
   6 [19:01] <tsimpson> and contrary to some reports, I haven't been hacking on IRC bots since I was 5, I'm just not that 1337 ;)
   7 [19:01] <tsimpson> this is the Launchpad project for Ubuntu Bots:
   8 [19:02] <tsimpson> today I'm going to talk to you a bit about the IRC protocol, and creating a simple plugin for the supybot IRC bot
   9 [19:02] <tsimpson> so you may want to 'sudo apt-get install supybot' now if you want to play along
  10 [19:02] <tsimpson> the IRC bot most of you will be familiar with is ubottu, or one of its ubot* and lubotu* clones
  11 [19:03] <tsimpson> these bots are all standard supybot IRC bots with our custom plugins, which are developed in the launchpad project above
  12 [19:03] <tsimpson> later, I'll go through creating a custom plugin, some python knowledge is required for that part
  13 [19:03] <tsimpson> before that though, you should know a something abou the IRC protocol itself
  14 [19:03] <tsimpson> some of what I'm going to discuss may not be necessary for developing a supybot plugin, but it's useful to understand what's actually going on
  15 [19:04] <tsimpson> also, other IRC bots may require more/less knowledge than others, or you may be feeling particularly insane and want to write your own IRC bot/client
  16 [19:04] <tsimpson> contains lots useful information about the IRC protocol, as well as the RFC (the document describing the IRC protocol)
  17 [19:04] <tsimpson> the general way IRC works is, you send a command to the IRC server, followed by carriage-return new-line, and it responds in some way
  18 [19:04] <tsimpson> in this way we say IRC is an "event driven" protocol
  19 [19:04] <tsimpson> the carriage-return and new-line characters are represented by "\r" and "\n" in character strings, new-line is also known as line-feed
  20 [19:05] <tsimpson> and the server uses those two characters to determine when you're done sending your commands
  21 [19:05] <tsimpson> the IRC command you'll care most about when developing an IRC bot, or plugins for IRC bots, is the "PRIVMSG" command
  22 [19:05] <tsimpson> a PRIVMSG is how I'm talking to you now, it how clients send messages, and despite its name, it's not inherently a PRIVate MeSsaGe.
  23 [19:05] <tsimpson> when you type a message into your IRC client, and hit enter, the client will send a PRIVMSG command to the server
  24 [19:06] <tsimpson> command in IRC usually have some arguments/parameters that go along with them
  25 [19:06] <tsimpson> for PRIVMSG, you'll want to tell the server where you want the message sent to
  26 [19:06] <tsimpson> and what the message is
  27 [19:06] <tsimpson> in IRC each argument is separated by a space
  28 [19:06] <tsimpson> if you need to send an argument with a space in it, it has to be the last argument and it needs to be prefixed with a ':' character
  29 [19:06] <tsimpson> our messages usually contain spaces, so that's an instance of where you need to do that
  30 [19:06] <tsimpson> so here's an example
  31 [19:07] <tsimpson> if I wanted to send the message "Hello World!" to the channel "#mychannel", this is what my client would send:
  32 [19:07] <tsimpson> PRIVMSG #mychannel :Hello World!\r\n
  33 [19:07] <tsimpson> the way the server responds to this is to forward that message to all of the other servers it's connected to and clients connected to it
  34 [19:07] <tsimpson> however, when the server forwards these messages, it adds something called a "prefix" to the message
  35 [19:07] <tsimpson> his prefix tells everyone who receives the message where it came from, it's made up of your nick name, user/ident, and host. then prefixed with a ':'
  36 [19:07] <tsimpson> the format is ':nick!ident@host', so the prefix for me is ":tsimpson!~tsimpson@ubuntu/member/stdin"
  37 [19:07] <tsimpson> when your client sees the "Hello World!" message from me, it would receive this:
  38 [19:08] <tsimpson> :tsimpson!~tsimpson@ubuntu/member/stdin PRIVMSG #mychannel :Hello World!\r\n
  39 [19:08] <tsimpson> the only difference between a public channel message and a private one-to-one message is the "target"
  40 [19:08] <tsimpson> the target is either a channel, as #mychannel is above, or a nick of another user
  41 [19:08] <tsimpson> there are lots of commands in the IRC protocol, like NICK, JOIN, PART, QUIT, PRIVMSG, NOTICE, MODE, and many numeric commands
  42 [19:08] <tsimpson> all the numeric commands come from the server and are usually informational or indicate errors
  43 [19:08] <tsimpson> all these messages have the same form when you receive them
  44 [19:08] <tsimpson> :<prefix> <command> <arg0..argN> :<last>
  45 [19:09] <tsimpson> where each item after the <command> is optional (depending on the command)
  46 [19:09] <tsimpson> it's also worth noting that when you get a message from the server, rather than another person, the <prefix> is the server rather than a user mask
  47 [19:09] <tsimpson> for instance, if you connect to, that will be the prefix when you get a message from the server.
  48 [19:09] <tsimpson> using the information in the IRC RFC you could create your own IRC client, and have that do whatever you wanted
  49 [19:09] <tsimpson> and IRC bot is just an IRC client that reacts to the messages in some custom way
  50 [19:09] <tsimpson> really, there is no difference between a "real" IRC client, and some script that connects to an IRC server
  51 [19:10] <tsimpson> another thing to note about IRC, is that it has no character encoding. that is, it's not ASCII, or UTF-8, or anything
  52 [19:10] <tsimpson> it is up to the client to figure out what each particular message is encoded in
  53 [19:10] <tsimpson> but most clients are UTF-8 aware by default, and that's what you should use
  54 [19:10] <tsimpson> IRC is also case insensitive, that is "PRIVMSG #MyChannel" and "privmsg #mychannel" are exactly the same command with exactly the same destination
  55 [19:10] <tsimpson> the only exception to this is the characters '{', '}', and '|', which are considered the lower-case forms of '[', ']' and '\', respectively
  56 [19:11] <tsimpson> the reason for that has to do with IRCs Scandinavian origin, and it's something you'll need to be aware of when comparing nick names etc
  57 [19:11] <tsimpson> from that, you should have a good idea of how ubottu works when responding to factoid requests
  58 [19:11] <tsimpson> it receives a PRIVMSG command from the server, looks at the message target (a channel or itself)
  59 [19:11] <tsimpson> if the message is in a channel, it checks that it starts with the '!' character
  60 [19:11] <tsimpson> then it goes looking in its database for that factoid reply, and sends that off to either the channel or the nick name
  61 [19:11] <tsimpson> ubottu does more complicated things than that
  62 [19:11] <tsimpson> like prefixing a nick in the "!factoid | nick" form, and sending the messages in private with the "!factoid > nick" form
  63 [19:12] <tsimpson> that's a little more complicated, but it all starts with a PRIVMSG command.
  64 [19:12] <tsimpson> there's lots more I could tell you about the IRC protocol
  65 [19:12] <tsimpson> but I wanted to give you an example of how to write a plugin for supybot
  66 [19:12] <tsimpson> some Python knowledge is required here on in, but you should get the general idea even if you don't know Python
  67 [19:13] <tsimpson> if you've installed supybot, you can see its files in /usr/share/pyshared/supybot/
  68 [19:13] <tsimpson> the first thing you need to do, after installing supybot, is to choose a directory where your bot is going to live in
  69 [19:13] <tsimpson> this will be where all the config file and plugin will go
  70 [19:13] <tsimpson> ~/bot is a good example, and that's what I'm going to use in this example
  71 [19:13] <tsimpson> you need to have a terminal open for this, as supybot is set-up via the command-line
  72 [19:14] <tsimpson> irst you create the directory; "mkdir ~/bot", then change to that directory; "cd ~/bot"
  73 [19:14] <tsimpson> then you run though the supybot set-up with the "supybot-wizard" command from ~/bot
  74 [19:14] <tsimpson> you can just hit enter for the first question about bolding, and then 'y' if it works
  75 [19:14] <tsimpson> choose 'n' for the question "Are you an advanced supybot user?"
  76 [19:14] <tsimpson> you can look through that later if you want, but it's not necessary right now
  77 [19:14] <tsimpson> then hit enter to create the directories in the current (~/bot) directory
  78 [19:14] <tsimpson> for the "IRC network", enter: freenode
  79 [19:15] <tsimpson> for the server:
  80 [19:15] <tsimpson> then hit enter (or choose 'n') for "Does this connection require connection to a non-standard port?"
  81 [19:15] <tsimpson> then you need to choose a nick name for your bot, remember that it needs to be unique on the network (so don't choose ubottu ;)
  82 [19:15] <tsimpson> for this example, I'll refer to the nick "mybot", but you should choose something different
  83 [19:15] <tsimpson> press enter, or choose 'n', for the question about setting a server password
  84 [19:15] <tsimpson> next it'll ask if you want the bot to join some channels when it connects, you'll want to choose 'y' here
  85 [19:15] <tsimpson> you should now get yourself a temporary channel, this is easy enough, you just join an empty channel
  86 [19:15] <tsimpson> a good idea is to create one based on your nick, so if you are "someone", you'd /join ##someone
  87 [19:16] <tsimpson> the double-# on freenode indicates it's an "about" or unaffiliated channel, anyone is free to create that kind of channel on freenode
  88 [19:16] <tsimpson> so, once you're in your new channel, type the name into the supybot wizard and press enter
  89 [19:16] <tsimpson> supybot comes with many plugins pre-installed, you can look at them later
  90 [19:16] <tsimpson> for now just answer 'n' when it asks if you want to look at them individually
  91 [19:16] <tsimpson> next, it will ask if you want to add an owner for the bot, you do
  92 [19:17] <tsimpson> supybot will only accept certain commands from an owner or an admin
  93 [19:17] <tsimpson> commands that make it join/part channels or change nicks or quit for example
  94 [19:17] <tsimpson> type in your IRC nick for the user-name and then pick a password you'll use
  95 [19:17] <tsimpson> it'll ask twice for the password to make sure you don't mistype
  96 [19:17] <tsimpson> the next question is about the "prefix char", this is a character that the bot will use to recognise when you are giving it a command
  97 [19:17] <tsimpson> press 'y', then enter, then you can choose a character. a good choice is the '@' character
  98 [19:17] <tsimpson> and that's it, a basic set-up for a working supybot IRC bot
  99 [19:18] <tsimpson> to start the IRC bot and get it connected to IRC, you run the command "supybot mybot.conf" (where "mybot" is the nick you chose for your bot)
 100 [19:18] <tsimpson> you'll see it connecting from the console, it'll print lots of information there so you can monitor it
 101 [19:18] <tsimpson> as a note, you can run a supybot instance in "daemon" mode by adding '-d' to the command
 102 [19:18] <tsimpson> that will make the supybot instance run in the background and log messages to ~/bot/log/messages.log
 103 [19:18] <tsimpson> for now though, we will want to see the messages in case there are any errors when we start developing a plugin
 104 [19:19] <tsimpson> which is quite common
 105 [19:19] <tsimpson> you should see something like "Join to ##someone on freenode synced in 1.15 seconds.", that means the bot has successfully joined the channel you gave it
 106 [19:19] <tsimpson> and you should see it sitting there in your channel
 107 [19:19] <tsimpson> the first thing you need to do is let the bot know you are the owner, you do this in /msg as it requires you sending a password
 108 [19:19] <tsimpson> so you do /msg mybot identify someone my_password
 109 [19:19] <tsimpson> (changing "someone" and "my_password" for the name of the owner and the password you gave to supybot-wizard)
 110 [19:20] <tsimpson> you should get a message back with "<mybot> The operation succeeded."
 111 [19:20] <tsimpson> the bot knows you are the owner now
 112 [19:20] <tsimpson> next you should type this in the channel where the bot is: @config supybot.reply.whenNotCommand False
 113 [19:20] <tsimpson> (replacing '@' with the prefix character you choose)
 114 [19:20] <tsimpson> I'll come back to why you did that that later
 115 [19:20] <tsimpson> with its default set-up, the bot won't do very much
 116 [19:20] <tsimpson> so we are going to make a plugin for the bot to do something :)
 117 [19:21] <tsimpson> you'll want to open another terminal window/tab and navigate to ~/bot/plugins (cd ~/bot/plugins)
 118 [19:21] <ClassBot> abhinav19 asked: are there any additional dependencies or requirements for running supybot-wizard as I got some errors ?
 119 [19:21] <tsimpson> there shouldn't be
 120 [19:21] <tsimpson> it just requires the basic python stuff
 121 [19:22] <tsimpson> which should be pre-installed
 122 [19:22] <tsimpson> ok, so we're in ~/bot/plugins
 123 [19:22] <tsimpson> hat is where the bot will look for plugins you create for it
 124 [19:22] <tsimpson> *that
 125 [19:22] <tsimpson> we are going to create a plugin, called Say, that will make the bot say something when we tell it to
 126 [19:22] <tsimpson> it will have one command in it, "say", which will repeat everything after the command
 127 [19:23] <tsimpson> so when we do "@say Hello, World!", our bot will reply with "Hello, World!"
 128 [19:23] <tsimpson> how exciting :)
 129 [19:23] <tsimpson> to start we run the command "supybot-plugin-create"
 130 [19:23] <tsimpson> it'll ask what the name of the plugin should be
 131 [19:23] <tsimpson> et's choose "Say"
 132 [19:23] <tsimpson> (plugin names should usually start with an upper-case character)
 133 [19:23] <tsimpson> the question about if it should be threaded is for more advanced plugins, that may be doing many things at the same time. we don't need that so we choose 'n'
 134 [19:23] <tsimpson> it'll then ask for your real name, so it can add a copyright and licensing information, go ahead and enter that
 135 [19:24] <tsimpson> and choose 'y' to use the Supybot license
 136 [19:24] <tsimpson> you can choose another license for your plugins if you wish, but the supybot license is an open-source license
 137 [19:24] <tsimpson> the template for the plugin should now be in the "Say" sub-directory at ~/bot/plugins/Say, so navigate there
 138 [19:24] <tsimpson> you'll see 5 files and one directory, "", "", "", "README.txt", and "" are the files
 139 [19:24] <tsimpson> the directory is there in case you want to create custom python modules
 140 [19:24] <tsimpson> we won't be using it in this example, but it does no harm being there
 141 [19:24] <tsimpson> "" is where all the configuration for the plugin will go, we don't need any configuration for this plugin, so we won't modify that
 142 [19:25] <tsimpson> "" is there to make that directory a python module, you can put some things in there if you wanted, but we don't need to do anything special here
 143 [19:25] <tsimpson> "" is where our actual plugin code will be, we'll come back to that in a second
 144 [19:25] <tsimpson> "README.txt" is pretty self-explanatory, it's where you'll put some information about your plugin, like a description and any set-up information
 145 [19:25] <tsimpson> "" is for testing, for complex plugins you can use that with the supybot-test script to run tests on the plugin
 146 [19:25] <tsimpson> now go ahead and open in your favourite editor or python IDE
 147 [19:26] <tsimpson> the comment block at the top is the your copyright and the supybot license
 148 [19:26] <tsimpson> then it has some default imports followed by our plugin class "class Say(callbacks.Plugin):"
 149 [19:26] <tsimpson> all supybot plugin classes need to derive from supybot.callbacks.Plugin in some way, and there are a few special sub-classes too
 150 [19:26] <tsimpson> then is the line "Class = Say"
 151 [19:26] <tsimpson> when supybot imports a plugin module, it looks for "Class" in that module to identify the plugin
 152 [19:26] <tsimpson> and in it imports "plugin", and sets Class = plugin.Class
 153 [19:27] <tsimpson> now we should change the doc-string to something meaningful
 154 [19:27] <tsimpson> supybot uses the doc-strings of classes and methods to provide the "@help" command
 155 [19:27] <tsimpson> so it's good practice to always document as you go
 156 [19:27] <tsimpson> something like """A plugin to say something when told to""" is good enough for this plugin
 157 [19:27] <tsimpson> when supybot receives a command from the IRC server, it looks for a corresponding method in the plugins it has loaded
 158 [19:27] <tsimpson> the command is lower-cased and capitalized, so "PRIVMSG" becomes "Privmsg"
 159 [19:28] <tsimpson> the method supybot looks for is "do" followed by the command
 160 [19:28] <tsimpson> so, if we want to listen for channel/private message, we need to create an "doPrivmsg" method
 161 [19:28] <tsimpson> for all of these kinds of method, supybot passes 2 arguments
 162 [19:28] <tsimpson> an instance of the supybot.irclib.Irc class (or a wrapper class), which represents the IRC client/bot, remember there's really no difference
 163 [19:28] <tsimpson> and an instance of the supybot.ircmsgs.IrcMsg class, which represents and IRC message
 164 [19:28] <tsimpson> in our method the Irc class we get will be a 'supybot.callbacks.ReplyIrcProxy'
 165 [19:29] <tsimpson> it just adds some convenience methods for replying to messages
 166 [19:29] <tsimpson> the Irc instance will contain information such as the network connected to
 167 [19:29] <tsimpson> nick name of the bot on that network
 168 [19:29] <tsimpson> and state information, like what channels are joined and what users are in those channels etc
 169 [19:29] <tsimpson> as I mentioned earlier, when you want to compare channels or nicks in IRC, there are some special characters to consider
 170 [19:29] <tsimpson> supybot has some utility function that help us, they are located in the supybot.ircutils module, which is already imported as "ircutils"
 171 [19:30] <tsimpson> back to the plugin
 172 [19:30] <tsimpson> in you plugin class (Say), remove the line "pass"
 173 [19:30] <tsimpson> then create a method called "doPrivmsg" taking 3 arguments, "self", "irc", and "msg"
 174 [19:30] <tsimpson> you should now have something similar to
 175 [19:30] <tsimpson> still nothing useful there, but we're about to change that
 176 [19:30] <tsimpson> this doPrivmsg method of our plugin class will be called when ever the bot sees a message
 177 [19:31] <tsimpson> now, we need some way to tell if the message was addressed to the bot or not
 178 [19:31] <tsimpson> we don't want our bot to respond to random messages after all
 179 [19:31] <tsimpson> our bot is addressed for example, if the message starts with the bots nick or command character you set
 180 [19:31] <tsimpson> now we could write some code to do all that
 181 [19:31] <tsimpson> but I'm lazy ;)
 182 [19:32] <tsimpson> and besides, we don't need to
 183 [19:32] <tsimpson> there is a "addressed" function in the 'supybot.callbacks' module which will do all that for us
 184 [19:32] <tsimpson> and supybot.callbacks is already imported as 'callbacks' for us
 185 [19:32] <tsimpson> we give it the bots nick, and the IRC message ('msg' parameter)
 186 [19:32] <tsimpson> it will either return an empty string, in which case the bot wasn't addressed
 187 [19:32] <tsimpson> or it will return the message without the part of the message used to address the bot
 188 [19:32] <tsimpson> for example, if the message way "mybot: this is a message", and we executed this code:
 189 [19:32] <tsimpson> message = callbacks.addressed(irc.nick, msg)
 190 [19:32] <tsimpson> the result in 'message' would be "this is a message"
 191 [19:33] <tsimpson> f the message was just "this is a message", then 'message' would be an empty string ''
 192 [19:33] <tsimpson> by the way, as I mentioned above the 'irc' parameter contains information, such as the bots nick. that's what 'irc.nick' gives us
 193 [19:33] <tsimpson> in the 'msg' parameter there is the property 'args'
 194 [19:33] <tsimpson> that holds a tuple with all the arguments of the message
 195 [19:33] <tsimpson> remember that PRIVMSG has 2 arguments
 196 [19:33] <tsimpson> the "target" (a channel or nick), and the message
 197 [19:33] <tsimpson> the target is in 'msg.args[0]' and the message is in 'msg.args[1]'
 198 [19:33] <tsimpson> when we call 'callbacks.addressed', it looks at both msg.args[0] and msg.args[1]
 199 [19:34] <tsimpson> it will check if msg.args[0] (the target of the message) is the bots nick, in which case it is a private message
 200 [19:34] <tsimpson> so, the first thing we do in our doPrivmsg method, is this:
 201 [19:34] <tsimpson> message = callbacks.addressed(irc.nick, msg)
 202 [19:34] <tsimpson> then we can check if 'message' is empty and, in that case, just return
 203 [19:34] <tsimpson> this is the code:
 204 [19:34] <tsimpson> if not message:
 205 [19:34] <tsimpson>     return
 206 [19:34] <tsimpson> that way our plugin will just ignore all the messages which are not addressed to it
 207 [19:34] <tsimpson> next we will need to check that the message is our "say" command
 208 [19:35] <tsimpson> we can do this by splitting off the first word of 'message' and checking that it equals 'say'
 209 [19:35] <tsimpson> we'll also want to remove it from the 'message' string, as it's not part of what we are supposed to repeat
 210 [19:35] <tsimpson> this is the code:
 211 [19:35] <tsimpson> bot_cmd = message.split(None, 1)[0]
 212 [19:35] <tsimpson> message = message[len(bot_cmd):].strip()
 213 [19:35] <tsimpson> here we're also slicing the 'message' to remove the command, and calling strip on that
 214 [19:35] <tsimpson> the command could well just be one word, so we can't just split it and assign to two variables
 215 [19:35] <tsimpson> we need to use strip() so that we don't have any leading space if/when we reply
 216 [19:35] <tsimpson> now we check if the command in 'bot_cmd' is the one we are looking for
 217 [19:36] <tsimpson> this is the code:
 218 [19:36] <tsimpson> if bot_cmd.lower() != 'say':
 219 [19:36] <tsimpson>     return
 220 [19:36] <tsimpson> we lower-case the command and check if it's "say", if not we just return as we aren't interested in other commands
 221 [19:36] <tsimpson> the next thing we need to do is check if we were actually given a message to repeat
 222 === mrjazzcat is now known as mrjazzcat-lunch
 223 [19:36] <tsimpson> there's not much point calling a command that repeats a message without a message to repeat!
 224 [19:36] <tsimpson> if we weren't give a message, then we need to respond with an error message indicating that we need more arguments
 225 [19:36] <tsimpson> remember that 'message' is now whatever was left after we removed the command
 226 [19:36] <tsimpson> this is the code:
 227 [19:36] <tsimpson> if not message:
 228 [19:36] <tsimpson>     irc.error("I need something to say")
 229 [19:36] <tsimpson>     return
 230 [19:37] <tsimpson> 'error' is a method on the 'irc' object that lets us respond with an error message
 231 [19:37] <tsimpson> it will prefix "Error: " to whatever message you pass to it
 232 [19:37] <tsimpson> if we get past that part of the code, then we know three things
 233 [19:37] <tsimpson> 1) we know the bot was addressed in some way
 234 [19:37] <tsimpson> 2) we know the command is "say"
 235 [19:37] <tsimpson> 3) we know we have the message to repeat
 236 [19:37] <tsimpson> the only thing left to do, is actually repeat the message!
 237 [19:37] <tsimpson> this is the code:
 238 [19:37] <tsimpson> irc.reply(message)
 239 [19:37] <tsimpson> that's it, the 'reply' method does exactly what you think it does, replies to the message we got
 240 [19:38] <tsimpson> now our plugin is ready to be used, it should look something like this:
 241 [19:38] <tsimpson> that's around 12 lines of python we've added, and it does exactly what we want :)
 242 [19:38] <tsimpson> to tell you bot to load that plugin, you issue the command "@load Say" in the channel it's in, or in a /msg
 243 [19:38] <tsimpson> you should see a "The operation succeeded." message from the bot
 244 [19:38] <tsimpson> if you get an error message, you probably have some typo in the code
 245 [19:38] <tsimpson> now you can test the plugin out
 246 [19:39] <tsimpson> I'll have nubotu join here to show you how it should work
 247 [19:39] <tsimpson> nubotu is my general testing/development bot
 248 [19:39] <tsimpson> @load Say
 249 [19:39] <nubotu> tsimpson: The operation succeeded.
 250 [19:39] <tsimpson> @say Hello, World!
 251 [19:39] <nubotu> tsimpson: Hello, World!
 252 [19:39] <tsimpson> the bot won't respond to this message, callbacks.addressed() will return an empty string
 253 [19:39] <tsimpson> @the bot won't respond to this either, as "the" is not the "say" command we want
 254 [19:39] <tsimpson> nubotu: and it won't respond here, as "and" isn't the "say" command either
 255 [19:40] <tsimpson> this will cause nubotu to respond with an error, as I'm leaving out the message
 256 [19:40] <tsimpson> @say
 257 [19:40] <nubotu> tsimpson: Error: I need something to say
 258 [19:40] <tsimpson> nifty ey?
 259 [19:40] <tsimpson> now, even though that's only around 12 lines of code
 260 [19:40] <tsimpson> it's still an awful lot of code for a simple repeater command
 261 [19:40] <tsimpson> fortunately supybot can help us some more here
 262 [19:40] <tsimpson> in the above example, we didn't really create a command
 263 [19:40] <tsimpson> at least not in the sense supybot cares about
 264 [19:40] <tsimpson> remember we did "@config supybot.reply.whenNotCommand False"?
 265 [19:41] <tsimpson> if we set that back to True, you'll see what I mean
 266 [19:41] <tsimpson> @config supybot.reply.whenNotCommand True
 267 [19:41] <nubotu> tsimpson: The operation succeeded.
 268 [19:41] <tsimpson> @say
 269 [19:41] <nubotu> tsimpson: Error: "say" is not a valid command.
 270 [19:41] <nubotu> tsimpson: Error: I need something to say
 271 [19:41] <tsimpson> @say Hello, World!
 272 [19:41] <nubotu> tsimpson: Error: The "Say" plugin is loaded, but there is no command named "Hello," in it.  Try "list Say" to see the commands in the "Say" plugin.
 273 [19:41] <nubotu> tsimpson: Hello, World!
 274 [19:41] <tsimpson> that's not so good :(
 275 [19:41] <tsimpson> in supybot, we create command by defining a method of the same name, and then "wrapping" it
 276 [19:41] <tsimpson> I'll talk more about wrap in a second
 277 [19:41] <tsimpson> so now go ahead and delete the entire "doPrivmsg" method from your
 278 [19:42] <tsimpson> the whole thing
 279 [19:42] <tsimpson> we don't need it
 280 [19:42] <tsimpson> and replace it with this:
 281 [19:42] <tsimpson> def say(self, irc, msg, args, message):
 282 [19:42] <tsimpson>     """<message>
 283 [19:42] <tsimpson>     Repeats <message>
 284 [19:42] <tsimpson>     """
 285 [19:42] <tsimpson>     irc.reply(message)
 286 [19:42] <tsimpson> and that's our 'say' command
 287 [19:42] <tsimpson> the doc-string describes how to use the command, we take only one argument '<message>', so that goes on the first line
 288 [19:42] <tsimpson> then we have a blank line, followed by a description of the command.
 289 [19:42] <tsimpson> this will be shown when we do @help say
 290 [19:42] <tsimpson> the irc and msg parameters are pretty much the same as in our previous doPrivmsg method
 291 [19:43] <tsimpson> except that 'irc' will usually be a supybot.callbacks.NestedCommandsIrcProxy, that' not something you really need to care about though
 292 [19:43] <tsimpson> you don't need to worry about the 'args' parameter for now, it's for more advanced commands
 293 [19:43] <tsimpson> the 'message' parameter is what is going to hold the message we are supposed to say, we don't need to do anything special like with doPrivmsg
 294 [19:43] <tsimpson> when our command method is called, we *will* have a message to repeat, 'wrap' will make sure for us
 295 [19:43] <tsimpson> next we need to tell supybot that that method is a command, we do this using the 'wrap' function, which is imported from supybot.commands
 296 [19:43] <tsimpson> put this under you method:
 297 [19:43] <tsimpson> at the same indentation as "def"
 298 [19:44] <tsimpson> say = wrap(say, ['text'])
 299 [19:44] <tsimpson> the 'wrap' function takes a method, and then a list of so-called "converters"
 300 [19:44] <tsimpson> in this case, we only want some text, we don't care what it is as long as it's there
 301 [19:44] <tsimpson> by the way, if you are creating a command that doesn't take any parameters, you can use wrap as a method decorator by putting '@wrap' before the definition
 302 [19:44] <tsimpson> but you'll usually want to have some parameters for your commands
 303 [19:44] <tsimpson> now your should look like this:
 304 [19:44] <tsimpson> without all the comments and doc-strings, it's just 3 lines. nice :)
 305 [19:45] <tsimpson> let's reload the plugin so the changes take effect
 306 [19:45] <tsimpson> @reload Say
 307 [19:45] <nubotu> tsimpson: The operation succeeded.
 308 [19:45] <tsimpson> now we test
 309 [19:45] <tsimpson> @help say
 310 [19:45] <nubotu> tsimpson: (say <message>) -- Repeats <message>
 311 [19:45] <tsimpson> @say Hello, World!
 312 [19:45] <nubotu> tsimpson: Hello, World!
 313 [19:45] <tsimpson> @say
 314 [19:45] <nubotu> tsimpson: (say <message>) -- Repeats <message>
 315 [19:45] <tsimpson> nubotu: say hello to all the people
 316 [19:45] <nubotu> tsimpson: hello to all the people
 317 [19:45] <tsimpson> it works!
 318 [19:45] <tsimpson> by the way, if you do '@config supybot.reply.withNickPrefix False', it won't prefix your nick to the response
 319 [19:46] <tsimpson> @config supybot.reply.withNickPrefix False
 320 [19:46] <nubotu> tsimpson: The operation succeeded.
 321 [19:46] <tsimpson> nubotu: say hello to all the nice people
 322 [19:46] <nubotu> hello to all the nice people
 323 [19:46] <tsimpson> and that's the _basics_ of building plugins for supybot
 324 [19:46] <tsimpson> things can get a lot more complicated very quickly, have a look at our plugins at:
 325 [19:46] <tsimpson> that's basically ubottu right there
 326 [19:46] <tsimpson> there's also some useful documentation for using supybot at and
 327 [19:46] <tsimpson> and the documentation that comes with supybot in /usr/share/doc/supybot/, especially the USING_WRAP.gz document
 328 [19:47] <tsimpson> oh, and come join us in #ubuntu-bots-devel if you want to help make ubottu even better :)
 329 [19:47] <tsimpson> I'll answer any questions you may have now
 330 [19:47] <tsimpson> (but please don't expect me to know the details of other IRC bot programs, I'll do my best though ;)
 331 [19:47] <tsimpson> nubotu: part
 332 [19:48] <tsimpson> hey, I just zoomed through the IRC protocol and bot plugin development in 47 minutes
 333 [19:49] <tsimpson> no one has questions?
 334 [19:51] <ClassBot> There are 10 minutes remaining in the current session.
 335 [19:52] <ClassBot> Mkaysi asked: Doesn't Supybot require pysqlite ?
 336 [19:52] <tsimpson> no
 337 [19:52] <tsimpson> not the basic supybot
 338 [19:52] <tsimpson> but as the plugins are just python modules
 339 [19:53] <tsimpson> they can, and do, have their own dependencies
 340 [19:53] <tsimpson> Encyclopedia (the factoid plugin for ubottu), does use it, for example
 341 [19:54] <tsimpson> we plan to change that though :)
 342 [19:55] <tsimpson> I should probably end by saying that supybot isn't the only IRC bot out there
 343 [19:55] <tsimpson> there are many others
 344 [19:55] <tsimpson> in different languages
 345 [19:55] <tsimpson> and with different capabilities
 346 [19:56] <ClassBot> There are 5 minutes remaining in the current session.
 347 [19:56] <tsimpson> we use supybot mostly because it's the easiest to develop in, as Ubuntu comes with python anyway
 348 [19:56] <tsimpson> but other bots can be, and are, just as good or better for you
 349 [19:56] <ClassBot> Mkaysi asked: What things I should tell supybot to forget from Encyclopedia? In example op ops calltheops owner.
 350 [19:57] <tsimpson> there is detailed use info on the bot wiki
 351 [19:57] <tsimpson>
 352 [19:57] <tsimpson> and we can help in #ubuntu-bots too
 353 [19:57] <ClassBot> chadadavis asked: Can a I configure a plugin to load on start (myplugin.conf)?
 354 [19:58] <tsimpson> by default supybot loads all the plugins that were running when the bot shut down
 355 [19:58] <tsimpson> so unless you @unload a plugin, it will auto-load
 356 [19:59] <tsimpson> if you have any more question, the bots team hangs out in #ubuntu-bots and #ubuntu-bots-devel
 357 [19:59] <tsimpson> we'll help you there :)
 358 [20:00] <tsimpson> thanks for listening, and I hope you'll all come help us make ubottu rock! :D

MeetingLogs/devweek1103/DevelopingIRCBots (last edited 2011-03-03 13:23:28 by pendulum)