MODULE API DOCUMENTATION ======================== This file documents the mozbot 2.5 bot module API. Revisions are welcome. Sample module ------------- Here is the HelloWorld module: ################################ # Hello World Module # ################################ package BotModules::HelloWorld; use vars qw(@ISA); @ISA = qw(BotModules); 1; sub Help { my $self = shift; my ($event) = @_; return { '' => 'This is the demo module that says Hello World.', 'hi' => 'Requests that the bot emit a hello world string.', }; } sub Told { my $self = shift; my ($event, $message) = @_; if ($message =~ /^\s*hi\s*$/osi) { $self->say($event, 'Hello World!'); } else { return $self->SUPER::Told(@_); } } ################################ Creating a module ----------------- Modules are perl objects with names that start with 'BotModules::' and that are stored in files with the '.bm' extension in the 'BotModules' directory. The first non-comment line of each module should be the 'package' line, which in the HelloWorld module reads: package BotModules::HelloWorld; For a module to work correctly, it should inherit from the 'BotModules' module (which is implemented internally in the main bot executable). This is done by including the following two lines immediately after the 'package' line: use vars qw(@ISA); @ISA = qw(BotModules); Since modules are dynamically loaded and unloaded, they should avoid using package globals. All variables should be stored in the '$self' blessed hashref. For more details, see the documentation of the 'Initialise' function (below). Another result of the dynamic nature of modules is that they should not use BEGIN {} or END {} blocks, nor should they execute any code during their evaluation. Thus, immediately after the @ISA... line, the module should return success. This can be done easily: 1; Following this, you are free to implement all the functions you need for your module. Certain functions have certain calling semantics, these are described below. Module Functions ---------------- This section contains the names and descriptions of the functions in your module that will be called automatically depending on what is happening on IRC. All your functions should start by shifting the $self variable from the argument list: my $self = shift; After this, it is common to get the other variables too: my ($event, @anythingElse) = @_; ...where the bit in the brackets is given in the brackets of the definitions of the functions as shown below. For example, for JoinedChannel it would be ($event, $channel), so a function to override the default JoinedChannel action would be something like: sub JoinedChannel { my $self = shift; my ($event, $channel) = @_; # ... return $self->SUPER::JoinedChannel($event, $channel); # call inherited method } Many functions have to return a special value, typically 0 if the event was handled, and 1 if it was not. For these functions, what actually happens is that for the relevant event, the bot has a list of event handlers it should call. For example, if someone says 'bot: hi' then the bot wants to call the Told() handler and the Baffled() handler. It first calls the Told() handler of every module. It then looks to see if any of the handlers returned 0. If so, it stops. Note, though, that every Told() handler got called! If none of the handlers returned 0, then it looks to see what the highest return value was. If it was greater than 1, then it increments the 'level' field of the $event hash (see below) and calls all the Told() handlers that returned 1 or more again. This means that if your module decides whether or not to respond by looking at a random number, it is prone to being confused by another module! YOU SHOULD NOT USE RANDOM NUMBERS TO DECIDE WHETHER OR NOT TO RESPOND TO A MESSAGE! Once all the relevant Told() handlers have been called again, the bot once again examines all the return results, and stops if any returned 0. If none did and if the current value of the level field is less than the highest number returned from any of the modules, then it repeats the whole process again. Once the level field is equal to the highest number returned, then, if no module has ever returned 0 in that whole loopy time, it moves on to the next handler in the list (in this case Baffled()), and does the _entire_ process again. You may be asking yourself "Why oh why!". It is to allow you to implement priority based responses. If your module returns '5' to the Told() function, and only handles the event (i.e., only returns 0) once the level field is 5, then it will only handle the event if no other module has wanted to handle the event in any of the prior levels. It also allows inter-module communication, although since that is dodgy, the details are left as an exercise to the reader. Important: if you use this, make sure that you only reply to the user once, based on the $event->{'level'} field. e.g., if you replied when level was zero, then don't reply _again_ when it is set to 1. This won't be a problem if your module only returns 1 (the default) or 0 (indicating success). *** Help($event) Every module that does anything visible should provide a 'Help' function. This is called by the General module's 'help' command implementation. This function should return a hashref, with each key representing a topic (probably a command) and each value the relevant help string. The '' topic is special and should contain the help string for the module itself. *** Initialise() Called when the module is loaded. No special return values. *** Schedule($event) Schedule - Called after bot is set up, to set up any scheduled tasks. See 'schedule' in the API documentation below for information on how to do this. No special return values. Always call inherited function! *** JoinedIRC($event) Called before joining any channels (but after module is setup). This does not get called for dynamically installed modules. No special return values. Always call inherited function! *** JoinedChannel($event, $channel) Called after joining a channel for the first time, for example if the bot has been /invited. No special return values. Always call inherited the function, as this is where the autojoin function is implemented. *** PartedChannel($event, $channel) Called after the bot has left a channel, for example if the bot has been /kicked. No special return values. Always call inherited the function, as this is where the autopart function is implemented. *** InChannel($event) Called to determine if the module is 'in' the channel or not. Generally you will not need to override this. Return 0 if the module is not enabled in the channel in which the event occured, non zero otherwise. *** IsBanned($event) Same as InChannel(), but for determining if the user is banned or not. Return 1 if the user that caused the event is banned from this module, non zero otherwise. *** Log($event) Called once for most events, regardless of the result of the other handlers. This is the event to use if you wish to log everything that happens on IRC (duh). No return value. *** Baffled($event, $message) Called for messages prefixed by the bot's nick which we don't understand (i.e., that Told couldn't deal with). Return 1 if you can't do anything (this is all the default implementation of Baffled() does). *** Told($event, $message) Called for messages heard that are prefixed by the bot's nick. See also Baffled. Return 1 if you can't do anything (this is all the default implementation of Told() does). *** Heard($event, $message) Called for all messages not aimed directly at the bot, or those aimed at the bot but with no content (e.g., "bot!!!"). Return 1 if you can't do anything (this is all the default implementation of Heard() does). *** Noticed($event, $message) Called for all 'notice' messages, whether aimed directly at the bot or not. Don't use this message to trigger responses! Doing so is a violation of the IRC protocol. To quote RFC 1459: # [...] automatic replies must never be sent in response to a NOTICE # message. [...] The object of this rule is to avoid loops between a # client automatically sending something in response to something it # received. This is typically used by automatons (clients with either # an AI or other interactive program controlling their actions) which # are always seen to be replying lest they end up in a loop with # another automaton. Return 1 if you can't do anything (this is all the default implementation of Noticed() does). *** Felt($event, $message) Called for all emotes containing bot's nick. Return 1 if you can't do anything (this is all the default implementation of Felt() does). *** Saw($event, $message) Called for all emotes except those directly at the bot. Return 1 if you can't do anything (this is all the default implementation does). *** Invited($event, $channel) Called when bot is invited into another channel. Return 1 if you can't do anything (this is all the default implementation does). *** Kicked($event, $channel) Called when bot is kicked out of a channel. Return 1 if you can't do anything (this is all the default implementation does). *** ModeChange($event, $what, $change, $who) Called when either the channel or a person has a mode flag changed. Return 1 if you can't do anything (this is all the default implementation does). *** GotOpped($event, $channel, $who) Called when the bot is opped. (Not currently implemented.) Return 1 if you can't do anything (this is all the default implementation does). *** GotDeopped($event, $channel, $who) Called when the bot is deopped. (Not currently implemented.) Return 1 if you can't do anything (this is all the default implementation does). *** Authed($event, $who) Called when someone authenticates with us. Note that you cannot do any channel-specific operations here since authentication is done directly and without any channels involved. (Of course, you can always do channel-wide stuff based on a channel list...) Return 1 if you can't do anything (this is all the default implementation does). *** SpottedNickChange($event, $from, $to) Called when someone changes their nick. You cannot use directSay here, since $event has the details of the old nick. And 'say' is useless since the channel is the old userhost string... This may be changed in a future implementation. Return 1 if you can't do anything (this is all the default implementation does). *** SpottedTopicChange($event, $channel, $newtopic) Called when the topic in a channel is changed. Return 1 if you can't do anything (this is all the default implementation does). *** SpottedJoin($event, $channel, $who) Called when someone joins a channel (including the bot). Return 1 if you can't do anything (this is all the default implementation does). *** SpottedPart($event, $channel, $who) Called when someone leaves a channel (including the bot). Return 1 if you can't do anything (this is all the default implementation does). *** SpottedKick($event, $channel, $who) Called when someone leaves a channel, um, forcibly (including the bot). Return 1 if you can't do anything (this is all the default implementation does). *** SpottedQuit($event, $who, $why) Called when someone leaves a server. You can't use say or directSay as no channel involved and the user has quit, anyway (obviously). This may change in future implementations (don't ask me how, please...). This does not get called for the bot itself. There is no way to reliably detect this (the core code itself has difficulty detecting this case, and sometimes only detects it when it is not really in a position to call into the modules). You may wish to use the 'unload' handler or 'DESTROY' function instead. Return 1 if you can't do anything (this is all the default implementation does). *** SpottedOpping($event, $channel, $who) Called when someone is opped. (Not currently implemented.) Return 1 if you can't do anything (this is all the default implementation does). *** SpottedDeopping($event, $channel, $who) Called when someone is... deopped, maybe? (Not currently implemented.) Return 1 if you can't do anything (this is all the default implementation does). *** CTCPPing($event, $who, $what) Called when the bot receives a CTCP ping. Return 1 if you can't do anything (this is all the default implementation does). *** CTCPVerson($event, $who, $what) Called when the bot receives a CTCP verson. Return 1 if you can't do anything (this is all the default implementation does). *** CTCPSource($event, $who, $what) Called when the bot receives a CTCP source. Return 1 if you can't do anything (this is all the default implementation does). *** Scheduled($event, @data) Called when a scheduled timer triggers. (See 'schedule' in the next section to see how to schedule stuff.) By default, if the first element of the @data array is a coderef, then the coderef is called with ($event,@data) as the arguments. Otherwise, 'debug' is called (see below). No special return values. Always call inherited function if you cannot handle the scheduled event yourself. *** GotURI($event, $uri, $contents, @data) Called when a requested URI has been downloaded. $contents contains the actual contents of the file. See getURI(). No special return values. *** ChildCompleted($event, $type, $output, @data) Called when a spawned child has completed. $output contains the output of the process. $type contains the child type as given to the spawnChild() API function (which see). No special return values. Always call the inherited function if you cannot handle the given '$type'! *** DataAvailable($event, $handle, $data, $closed) Called when $handle has data available. See registerDataHandle(). $data is the string that was read from the handle. Don't perform blocking read I/O on $handle, since all the data that was available has been read. (The handle is only returned because it is expected you will use that as a key to work out who is talking to you.) The $closed argument will be set to true if the handle is now closed. No special return values. Make sure to call the inherited function if you did not expect to see data on the specified $handle. *** RegisterConfig() Called when initialised, should call registerVariables(), which see below. No special return values. Always call inherited function! *** Set($event, $variable, $value) Called to set a variable to a particular value. Should return one of the following: -1 - silent success (caller should not report back to user) 0 - success 1 - can't set variable because it is of type ref($module->{$variable}) 2 - variable not found or not writable (if $module->{$variable}) 3 - variable is list and wrong format was used 4 - variable is hash and wrong format was used 9 - unknown error Note that error codes 1-4 are probably too specific to the default 'Set' function to be of any use. Reporting your own error messages is fine. Always call inherited function if you cannot set the variable yourself! *** Get($event, $variable) Called to get a particular variable. Should return the value of the variable. Default returns the value of $self->{$variable}. Always call inherited function if you cannot get the variable yourself! *** Unload() Called when the module is unloaded. However, this is not always reliably called when the module is unloaded immediately prior to the bot shutting down or branching to a different process. In general, relying on this function is poor design. It should only really be used for things like untie-ing from hashes or disconnecting from databases, where the code executing is not critical, merely good manners or helpful. No special return values. You are encouraged not to use this method. It is documented for completeness only. Default implementation does nothing. The $event variable is a hash with the following keys: 'bot' => the IRC bot object - DO NOT USE THIS!!! [1] 'channel' => the channel the event occured in, or '' if n/a [2] 'from' => the nick of the person who created the event, if any 'target' => the target of the 'say' function (channel || from) 'user' => the userhost of the event 'data' => the main data of the event 'fulldata' => the data of the event before it got mangled [3] 'to' => the target of the event 'subtype' => the IRC module's idea of what the event was [1] 'maintype' => the name of the first handler called (eg. 'Told') 'level' => the number of times the handler has been called in a row 'userName' => the name of the user as they authenticated 'userFlags' => used internally for the implementation of isAdmin() [1] 'nick' => the nick of the bot 'time' => the value of time() when the event was constructed [4] It is passed to most functions, as the first parameter. Modify at your own risk! ;-) If you do write to this hash at all, ensure that you make a 'local' copy first. See the 'Parrot' module for an example of safely modifying the $event hash. Note that some of these fields may be inaccurate at times, due to peculiarities of the IRC protocol. [1]: These fields are dependent on the underlying implementation, so if you use them then your modules will not be compatible with any other implementations that use the same API. The 'bot' field in particular is a blessed reference to a Net::IRC::Connection object in this implementation, and is passed around so that the API functions know what to operate on. However, in a POE implementation it could be something totally different, maybe even undef. There are some other fields in the $event hash that start with an underscore (in particular there is '_event'). Do not even _think_ about using those. Using them is akin to hard-coding the ionode of the 'ls' program into your source so that you can read directories by branching to a disk address. [2]: The 'channel' field is ALWAYS lowercase. You should always lowercase any channel names you get from users before using them in comparisons or hash lookups. [3]: This is the same as the 'data' slot except for Told and Baffled events where it also contains the prefix that was stripped. [4]: Use this instead of calling time() so as to avoid time drift when comparing times at various points. Module API ---------- This section contains the names and descriptions of the functions that your module can call. While you can override these, it is not recommended. *** debug(@messages) Outputs each item in @messages to the console (or the log file if the bot has lost its controlling tty). Example: $self->debug('about to fetch listing from FTP...'); *** saveConfig() Saves the state of the module's registered variables to the configuration file. This should be called when the variables have changed. Example: $self->saveConfig(); # save our state! *** registerVariables( [ $name, $persistent, $settable, $value ] ) Registers variables (duh). It actually takes a list of arrayrefs. The first item in each arrayref is the name to use (the name of the variable in the blessed hashref that is the module's object, i.e. $self). The second controls if the variable is saved when saveConfig() is called. If it is set to 1 then the variable is saved, if 0 then it is not, and if undef then the current setting is not changed. Similarly, the third item controls whether or not the variable can be set using the 'vars' command (in the Admin module). 1 = yes, 0 = no, undef = leave unchanged. The fourth value, if defined, is used to set the variable. See the Initialise function's entry for more details. Example: $self->registerVariables( [ 'ftpDelay', 1, 1, 60 ], [ 'ftpSite', 1, 1, 'ftp.mozilla.org' ], ); Only simple scalars, references to arrays of scalars, and references to hashes of scalars, can be stored in registered variables. *** schedule($event, $time, $times, @data) Schedules one or more events. $event is the usual event hash. $time is the number of seconds to wait. It can be a scalarref to a variable that contains this number, too, in which case it is dereferenced. This comes in useful for making the frequency of repeating events customisable. $times is the number of times to perform the event, which can also be -1 meaning 'forever'. @data (the remainder of the parameters) will be passed, untouched, to the event handler, Scheduled. See the previous section. Example: $self->schedule($event, \$self->{'ftpDelay'}, -1, 'ftp', \$ftpsite); *** getURI($event, $uri, @data) Gets a URI in the background then calls GotURI (which see, above). Example: $self->getURI($event, $ftpsite, 'ftp'); *** spawnChild($event, $command, $arguments, $type, $data) Spawns a child in the background then calls ChildCompleted (which see, above). $arguments and $data are array refs! $command is either a command name (e.g., 'wget', 'ls') or a CODEREF. If it is a CODEREF, then you will be wanting to make sure that the first argument is the object reference, unless we are talking inlined code or something... Example: $self->spawnChild($event, '/usr/games/fortune', ['-s', '-o'], 'fortune', [@data]); *** registerDataHandle($event, $handle) Adds $handle to the list of file handles and sockets to watch. When data is available on that socket, DataAvailable() will be called. *** getModule($name) Returns a reference to the module with the given name. In general you should not need to use this, but if you write a management module, for instance, then this could be useful. See God.bm for an example of this. IT IS VITAL THAT YOU DO NOT KEEP THE REFERENCE THAT THIS FUNCTION RETURNS!!! If you did so, the module would not get garbage collected if it ever got unloaded or some such. Example: my $module = $self->getModule('Admin'); push(@{$module->{'files'}}, 'BotModules/SupportFile.pm'); *** getModules() Returns the list of module names that are loaded, in alphabetical order, which you can then use with getModule(). Example: my @modulenames = $self->getModules(); local $" = ', '; $self->ctcpReply($event, 'VERSION', "mozbot $VERSION (@modulenames)"); *** getMessageQueue() Returns a reference to the message queue. Manipulating this is probably not a good idea. In particular, don't add anything to this array, use the say(), directSay(), channelSay(), announce(), tellAdmin(), etc, methods defined below. Each item in this array is an array ref, consisting of three subitems. The first subitem is a scalar with the name of the channel or nick targetted, the second is the message to send, and the third is a scalar equal to one of: 'msg', 'me', 'notice', 'ctcpSend', 'ctcpReply'. The second subitem is a scalar, except in the case of 'ctcpSend' messages, in which case it's an array ref consisting of first the type of the CTCP message, and then the data. Note: Don't use 'delete' to remove items from this array, since that leaves undefs in the array, which will later cause a crash. Example: my $queue = $self->getMessageQueue(); foreach my $message (@$queue) { ++$count if $message->[0] eq $event->{'from'}; } *** getHelpLine() Returns the bot's help line. Example: $self->say($event, $self->getHelpLine()); *** getLogFilename($name) Returns a filename (with path) appropriate to use for logging. $name should be the filename wanted, without a path. Example: my $logName = $self->getLogFilename("$channel.log"); if (open(LOG, ">>$logName")) { print LOG $data; close LOG; } else { # XXX error handling } *** unescapeXML($xml) Performs the following conversions on the argument and returns the result: ' => ' " => " < => < > => > & => & Example: my $text = $self->unescapeXML($output); *** tellAdmin($event, $data); Tries to tell an administrator $data. As currently implemented, only one administrator will get the message, and there is no guarentee that they will read it or even that the admin in question is actually on IRC at the time. Example: $self->tellAdmin($event, 'Someone just tried to crack me...'); *** say($event, $data) Says $data in whatever channel the event was spotted in (this can be /msg if that is how the event occured). Example: $self->say($event, 'Yo, dude.'); *** announce($event, $data) Says $data in all the channels the module is in. Example: $self->announce($event, 'Bugzilla is back up.'); *** directSay($event, $data) Sends a message directly to the cause of the last event (i.e., like /msg). It is recommended to use 'say' normally, so that users have a choice of whether or not to get the answer in the channel (they would say their command there) or not (they would /msg their command). Example: $self->directSay($event, 'Actually, that\'s not right.'); *** channelSay($event, $data) Sends a message to the channel in which the message was given. If the original command was sent in a /msg, then this will result in precisely nothing. Useful in conjunction with directSay() to make it clear that a reply was sent privately. Example: $self->directSay($event, $veryLongReply); $self->channelSay($event, "$event->{'from'}: data /msg'ed"); *** emote($event, $what) *** directEmote($event, $what) Same as say() and directSay(), but do the equivalent of /me instead. Examples: $self->emote($event, "slaps $event->{'from'} with a big smelly trout."); $self->directEmote($event, "waves."); *** sayOrEmote($event, $what) *** directSayOrEmote($event, $what) Call say (directSay) or emote (directEmote) based on the contents of $what. If $what starts with '/me' then the relevant emote variation is called, otherwise the say variations are used. The leading '/me' is trimmed before being passed on. Examples: $self->sayOrEmote($event, $greeting); $self->directSayOrEmote($event, $privateMessage); *** ctcpSend($event, $type, $data) Same as say() but for sending CTCP messages. Examples: $self->ctcpSend($event, 'PING', $event->{'time'}); *** ctcpReply($event, $type, $data) Same as ctcpSend() but for sending CTCP replies. Examples: $self->ctcpReply($event, 'VERSION', "Version $major.$minor"); *** notice($event, $data) Sends a notice containing $data to whatever channel the event was spotted in (this can be /msg if that is how the event occured). Example: foreach (@{$self->{'channels'}}) { local $event->{'target'} = $_; $self->notice($event, 'This is a test of the emergency announcement system.'); } *** isAdmin($event) Returns true if the cause of the event was an authenticated administrator. Example: if ($self->isAdmin($event)) { ... } *** setAway($event, $message) Set the bot's 'away' flag. A blank message will mark the bot as back. Note: If you need this you are doing something wrong!!! Remember that you should not be doing any lengthy processes since if you are away for any length of time, the bot will be disconnected! Also note that in 2.0 this is not throttled, so DO NOT call this repeatedly, or put yourself in any position where you allow IRC users to cause your module to call this. Otherwise, you open yourself to denial of service attacks. Finally, note that calling 'do', 'emote', 'say', and all the related functions will also reset the 'away' flag. Example: $self->setAway($event, 'brb...'); *** setNick($event, $nick) Set the bot's nick. This handles all the changing of the internal state variables and saving the configuration and everything. It will also add the nick to the list of nicks to try when the bot finds its nick is already in use. Note that in 2.0 this is not throttled, so DO NOT call this repeatedly, or put yourself in any position where you allow IRC users to cause your module to call this. Otherwise, you open yourself to denial of service attacks. Example: $self->setNick($event, 'zippy'); *** mode($event, $channel, $mode, $argument) Changes a mode of channel $channel. Example: $self->mode($event, $event->{'channel'}, '+o', 'Hixie'); *** invite($event, $who, $channel) Invite $who to channel $channel. This can be used for intrabot control, or to get people into a +i channel, for instance. Example: $self->invite($event, 'Hixie', '#privateChannel'); *** prettyPrint($preferredLineLength, $prefix, $indent, $divider, @input) Takes @input, and resorts it so that the lines are of roughly the same length, aiming optimally at $preferredLineLength, prefixing each line with $indent, placing $divider between each item in @input if they appear on the same line, and sticking $prefix at the start of it all on the first line. The $prefix may be undef. Returns the result of all that. This is what the 'help' command uses to pretty print its output. This is basically the same as wordWrap() but it can change the order of the input. Example: my @result = $self->prettyPrint($linelength, undef, 'Info: ', ' -- ', @infoItems); *** wordWrap($preferredLineLength, $prefix, $indent, $divider, @input) Takes @input, and places each item sequentially on lines, aiming optimally at $preferredLineLength, prefixing each line with $indent, placing $divider between each item in @input if they appear on the same line, and sticking $prefix at the start of it all on the first line, without ever cutting items across lines. The $prefix may be undef. Returns the result of all that. This is basically the same as prettyPrint() but it doesn't change the order of the input. Example: my @result = $self->wordWrap($linelength, undef, 'Info: ', ' ', split(/\s+/os, @lines); *** days($time) Returns a string describing the length of time between $time and now. Example: $self->debug('uptime: '.$self->days($^T)); *** sanitizeRegexp($regexp) Checks to see if $regexp is a valid regular expression. If it is, returns the argument unchanged. Otherwise, returns quotemeta($regexp), which should be safe to use in regular expressions as a plain text search string. Do not add prefixes or suffixes to the pattern after sanitizing it. Example: $pattern = $self->sanitizeRegexp($pattern); $data =~ /$pattern//gsi; -- end --