gecko-dev/webtools/mozbot/BotModules/devel.txt

973 строки
29 KiB
Plaintext

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'!
*** 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.
This method's name may change in a future version of the mozbot API.
You are encouraged not to use this method. It is documented for
completeness only.
No special return values. Always call inherited function! (Failure to
do so can result in memory leaks.)
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' ],
);
*** 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]);
*** 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:
' => '
" => "
&lt; => <
&gt; => >
&amp; => &
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 --