2 # -*- Mode: perl; indent-tabs-mode: nil -*-
3 # DO NOT REMOVE THE -T ON THE FIRST LINE!!!
6 # m o z i l l a |.| o r g | |
7 # _ __ ___ ___ ___| |__ ___ | |_
8 # | '_ ` _ \ / _ \_ / '_ \ / _ \| __|
9 # | | | | | | (_) / /| |_) | (_) | |_
10 # |_| |_| |_|\___/___|_.__/ \___/ \__|
11 # ====================================
13 # The contents of this file are subject to the Mozilla Public
14 # License Version 1.1 (the "License"); you may not use this file
15 # except in compliance with the License. You may obtain a copy of
16 # the License at http://www.mozilla.org/MPL/
18 # Software distributed under the License is distributed on an "AS
19 # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
20 # implied. See the License for the specific language governing
21 # rights and limitations under the License.
23 # The Original Code is the Bugzilla Bug Tracking System.
25 # The Initial Developer of the Original Code is Netscape Communications
26 # Corporation. Portions created by Netscape are
27 # Copyright (C) 1998 Netscape Communications Corporation. All
30 # Contributor(s): Harrison Page <harrison@netscape.com>
31 # Terry Weissman <terry@mozilla.org>
32 # Risto Kotalampi <risto@kotalampi.com>
33 # Josh Soref <timeless@bemail.org>
34 # Ian Hickson <mozbot@hixie.ch>
35 # Ken Coar <ken.coar@golux.com>
36 # Adam Di Carlo <adam@onshored.com>
38 # mozbot.pl harrison@netscape.com 1998-10-14
39 # "irc bot for the gang on #mozilla"
41 # mozbot.pl mozbot@hixie.ch 2000-07-04
42 # "irc bot engine for anyone" :-)
44 # hack on me! required reading:
47 # http://sourceforge.net/projects/net-irc/
49 # or get it from CPAN @ http://www.perl.com/CPAN
51 # RFC 1459 (Internet Relay Chat Protocol):
52 # http://sunsite.cnlab-switch.ch/ftp/doc/standard/rfc/14xx/1459
54 # Please file bugs in Bugzilla, under the 'Webtools' product,
55 # component 'Mozbot'. https://bugzilla.mozilla.org/
58 # XXX Something that checks modules that failed to compile and then
59 # reloads them when possible
60 # XXX an HTML entity convertor for things that speak web page contents
63 # XXX throttle nick changing and away setting (from module API)
64 # XXX compile self before run
65 # XXX parse mode (+o, etc)
67 # XXX maybe should catch hangup signal and go to background?
68 # XXX protect the bot from DOS attacks causing server overload
69 # XXX protect the server from an overflowing log (add log size limitter
71 # XXX fix the "hack hack hack" bits to be better.
74 ################################
76 ################################
78 # -- #mozwebtools was here --
79 # <Hixie> syntax error at oopsbot.pl line 48, near "; }"
80 # <Hixie> Execution of oopsbot.pl aborted due to compilation errors.
82 # <endico> hee hee. nice smily in the error message
84 # catch nasty occurances
85 $SIG{'INT'} = sub { &killed('INT'); };
86 $SIG{'KILL'} = sub { &killed('KILL'); };
87 $SIG{'TERM'} = sub { &killed('TERM'); };
89 # this allows us to exit() without shutting down (by exec($0)ing)
90 BEGIN { exit() if ((defined($ARGV[0])) and ($ARGV[0] eq '--abort')); }
98 if ((defined($ARGV[0])) and ($ARGV[0] eq '--chroot')) {
100 chroot('.') or die "chroot failed: $!\nAborted";
102 # This is hardcoded to use user ids and group ids 60001.
103 # You'll want to change this on your system.
104 $> = 60001; # setuid nobody
105 $) = 60001; # setgid nobody
109 } elsif ((defined($ARGV[0])) and ($ARGV[0] eq '--assume-chrooted')) {
118 use Net::IRC 0.7; # 0.7 is not backwards compatible with 0.63 for CTCP responses
119 use IO::SecurePipe; # internal based on IO::Pipe
121 use POSIX ":sys_wait_h";
122 use Carp qw(cluck confess);
123 use Configuration; # internal
124 use Mails; # internal
126 # Net::IRC 0.74+ require Time::HiRes, if its missing, Net::IRC will fail with
127 # a "No method called "time" for object." error during mozbot startup.
129 # Note: Net::SMTP is also used, see the sendmail function in Mails.
134 # internal 'constants'
135 my $USERNAME = "pid-$$";
138 # variables that should only be changed if you know what you are doing
139 my $LOGGING = 1; # set to '0' to disable logging
140 my $LOGFILEDIR; # set this to override the logging output directory
143 # set up the log directory
144 unless (defined($LOGFILEDIR)) {
146 $LOGFILEDIR = '/log';
148 # setpwent doesn't work on Windows, we should wrap this in some OS test
149 setpwent; # reset the search settings for the getpwuid call below
150 $LOGFILEDIR = (getpwuid($<))[7].'/log';
153 "$LOGFILEDIR/$0" =~ /^(.*)$/os; # untaints the evil $0.
154 $LOGFILEPREFIX = $1; # for some reason, $0 is considered tainted here, but not in other cases...
155 mkdir($LOGFILEDIR, 0700); # if this fails for a bad reason, we'll find out during the next line
158 # begin session log...
160 &debug('mozbot starting up');
161 &debug('compilation took '.&days($^T).'.');
163 &debug('mozbot chroot()ed successfully');
166 # secure the environment
168 # XXX could automatically remove the current directory here but I am
169 # more comfortable with people knowing it is not allowed -- see the
171 if ($ENV{'PATH'} =~ /^(?:.*:)?\.?(?::.*)?$/os) {
172 die 'SECURITY RISK. You cannot have \'.\' in the path. See the README. Aborted';
174 $ENV{'PATH'} =~ /^(.*)$/os;
175 $ENV{'PATH'} = $1; # we have to assume their path is otherwise safe, they called us!
176 delete (@ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'});
179 # read the configuration file
180 my $cfgfile = shift || "$0.cfg";
181 $cfgfile =~ /^(.*)$/os;
182 $cfgfile = $1; # untaint it -- we trust this, it comes from the admin.
183 &debug("reading configuration from '$cfgfile'...");
186 # note: owner is only used by the Mails module
187 my ($server, $port, $password, $localAddr, @nicks, @channels, %channelKeys, $owner,
188 @ignoredUsers, @ignoredTargets, $ssl);
191 my $connectTimeout = 120;
192 my $delaytime = 1.3; # amount of time to wait between outputs
193 my $recentMessageCountThreshold = 3; # threshold before we stop outputting
194 my $recentMessageCountPenalty = 10; # if we hit the threshold, bump it up by this much
195 my $recentMessageCountLimit = 20; # limit above which the count won't go
196 my $recentMessageCountDecrementRate = 0.1; # how much to take off per $delaytime
197 my $variablepattern = '[-_:a-zA-Z0-9]+';
198 my %users = ('admin' => &newPassword('password')); # default password for admin
199 my %userFlags = ('admin' => 3); # bitmask; 0x1 = admin, 0x2 = delete user a soon as other admin authenticates
200 my $helpline = 'http://www.mozilla.org/projects/mozbot/'; # used in IRC name and in help
201 my $serverRestrictsIRCNames = '';
202 my $serverExpectsValidUsername = '';
203 my $username = 0; # makes the username default to the pid ($USERNAME)
204 my @modulenames = ('General', 'Greeting', 'Infobot', 'Parrot');
205 my $gender = 'female'; #changed to female by special request
208 # - which variables can be saved.
209 ®isterConfigVariables(
210 [\$server, 'server'],
212 [\$password, 'password'],
213 [\$localAddr, 'localAddr'],
215 [\$nick, 'currentnick'], # pointer into @nicks
216 [\@channels, 'channels'],
217 [\%channelKeys, 'channelKeys'],
218 [\@ignoredUsers, 'ignoredUsers'],
219 [\@ignoredTargets, 'ignoredTargets'],
220 [\@modulenames, 'modules'],
222 [\$sleepdelay, 'sleep'],
223 [\$connectTimeout, 'connectTimeout'],
224 [\$delaytime, 'throttleTime'],
225 [\%users, 'users'], # usernames => &newPassword(passwords)
226 [\%userFlags, 'userFlags'], # usernames => bits
227 [\$variablepattern, 'variablepattern'],
228 [\$helpline, 'helpline'],
229 [\$username, 'username'],
230 [\$serverRestrictsIRCNames, 'simpleIRCNameServer'],
231 [\$serverExpectsValidUsername, 'validUsernameServer'],
234 [\$gender, 'gender'],
235 [\$Mails::smtphost, 'smtphost'],
239 &Configuration::Get($cfgfile, &configStructure()); # empty gets entire structure
241 # - check variables are ok
242 # note. Ensure only works on an interactive terminal (-t).
243 # It will abort otherwise.
244 { my $changed; # scope this variable
245 $changed = &Configuration::Ensure([
246 ['Connect to which server?', \$server],
247 ['To which port should I connect?', \$port],
248 ['Connect to this port using SSL?', \$ssl],
249 ['What is the server\'s password? (Leave blank if there isn\'t one.)', \$password],
250 ['What channels should I join?', \@channels],
251 ['What is the e-mail address of my owner?', \$owner],
252 ['What is your SMTP host?', \$Mails::smtphost],
255 # - check we have some nicks
257 $changed = &Configuration::Ensure([['What nicks should I use? (I need at least one.)', \@nicks]]) || $changed;
258 # the original 'mozbot 2.0' development codename (and thus nick) was oopsbot.
261 # - check current nick pointer is valid
262 # (we assume that no sillyness has happened with $[ as,
263 # according to man perlvar, "Its use is highly discouraged".)
264 $nick = 0 if (($nick > $#nicks) or ($nick < 0));
266 # - check channel names are all lowercase
267 foreach (@channels) { $_ = lc; }
269 # save configuration straight away, to make sure it is possible and to save
270 # any initial settings on the first run, if anything changed.
272 &debug("saving configuration to '$cfgfile'...");
273 &Configuration::Save($cfgfile, &configStructure());
276 } # close the scope for the $changed variable
278 # ensure Mails is ready
279 &debug("setting up Mails module...");
280 $Mails::debug = \&debug;
281 $Mails::owner = \$owner;
283 # setup the IRC variables
284 &debug("setting up IRC variables...");
286 my $irc = new Net::IRC or confess("Could not create a new Net::IRC object. Aborting");
289 &debug("attempting initial connection...");
292 # setup the modules array
293 my @modules; # we initialize it lower down (at the bottom in fact)
294 my $lastadmin; # nick of last admin to be seen
295 my %authenticatedUsers; # hash of user@hostname=>users who have authenticated
298 ################################
299 # Net::IRC handler subroutines #
300 ################################
304 if ($Net::IRC::VERSION == 0.75) {
305 # curses. This version of Net::IRC is broken. Work around
307 return $event->args(\@_);
309 return $event->args(@_);
319 &debug("connecting to $server:$port using nick '$nicks[$nick]'..."
320 . ($ssl && lc($ssl) eq 'yes')? "via SSL" : "");
325 my $ircname = 'mozbot';
326 if ($serverRestrictsIRCNames ne $server) {
327 $ircname = "[$ircname] $helpline";
330 my $identd = getpwuid($<);
331 if ($serverExpectsValidUsername ne $server) {
332 $identd = $username || $USERNAME;
335 until (inet_aton($server) and # we check this first because Net::IRC::Connection doesn't
336 $bot = $irc->newconn(
339 Password => $password ne '' ? $password : undef, # '' will cause PASS to be sent
340 Nick => $nicks[$nick],
343 LocalAddr => $localAddr,
344 SSL => ($ssl && lc($ssl) eq 'yes') ? 'true' : undef,
346 &debug("Could not connect. Are you sure '$server:$port' is a valid host?");
347 unless (inet_aton($server)) {
348 &debug('I couldn\'t resolve it.');
350 if (defined($localAddr)) {
351 &debug("Is '$localAddr' the correct address of the interface to use?");
353 &debug("Try editing '$cfgfile' to set 'localAddr' to the address of the interface to use.");
355 if ($Net::IRC::VERSION < 0.73) {
356 &debug("Note that to use 'localAddr' you need Net::IRC version 0.73 or higher (you have $Net::IRC::VERSION)");
358 $mailed = &Mails::ServerDown($server, $port, $localAddr, $nicks[$nick], $ircname, $identd) unless $mailed;
360 &Configuration::Get($cfgfile, &configStructure(\$server, \$port, \$password, \@nicks, \$nick, \$owner, \$sleepdelay));
361 &debug("connecting to $server:$port again...");
364 &debug("connected! woohoo!");
367 &debug("adding event handlers");
369 # $bot->debug(1); # this can help when debugging API stuff
371 $bot->add_global_handler([ # Informational messages -- print these to the console
372 251, # RPL_LUSERCLIENT
374 253, # RPL_LUSERUNKNOWN
375 254, # RPL_LUSERCHANNELS
382 $bot->add_global_handler([ # Informational messages -- print these to the console
383 'snotice', # server notices
384 461, # need more arguments for PASS command
386 405, # toomanychannels XXX should do something about this!
387 404, # cannot send to channel
388 403, # no such channel
389 401, # no such server
391 407, # too many targets
394 $bot->add_global_handler([ # should only be one command here - when to join channels
399 $bot->add_handler('welcome', \&on_welcome); # when we connect, to get our nick/umode
400 $bot->add_global_handler([ # when to change nick name
402 433, # ERR_NICKNAMEINUSE
403 436, # nick collision
405 $bot->add_handler('nick', \&on_nick); # when someone changes nick
407 $bot->add_global_handler([ # when to give up and go home
408 'disconnect', 'kill', # bad connection, booted offline
409 465, # ERR_YOUREBANNEDCREEP
410 ], \&on_disconnected);
411 $bot->add_handler('destroy', \&on_destroy); # when object is GCed.
413 $bot->add_handler('msg', \&on_private); # /msg bot hello
414 $bot->add_handler('public', \&on_public); # hello
415 $bot->add_handler('notice', \&on_noticemsg); # notice messages
416 $bot->add_handler('join', \&on_join); # when someone else joins
417 $bot->add_handler('part', \&on_part); # when someone else leaves
418 $bot->add_handler('topic', \&on_topic); # when topic changes in a channel
419 $bot->add_handler('notopic', \&on_topic); # when topic in a channel is cleared
420 $bot->add_handler('invite', \&on_invite); # when someone invites us
421 $bot->add_handler('quit', \&on_quit); # when someone quits IRC
422 $bot->add_handler('kick', \&on_kick); # when someone (or us) is kicked
423 $bot->add_handler('mode', \&on_mode); # when modes change
424 $bot->add_handler('umode', \&on_umode); # when modes of user change (by IRCop or ourselves)
425 # XXX could add handler for 474, # ERR_BANNEDFROMCHAN
427 $bot->add_handler([ # ones we handle to get our hostmask
430 $bot->add_handler([ # ones we handle just by outputting to the console
440 $bot->add_handler([ # names (currently just ignored)
441 353, # RPL_NAMREPLY "<channel> :[[@|+]<nick> [[@|+]<nick> [...]]]"
443 $bot->add_handler([ # end of names (we use this to establish that we have entered a channel)
444 366, # RPL_ENDOFNAMES "<channel> :End of /NAMES list"
445 ], \&on_join_channel);
447 $bot->add_handler('cping', \&on_cping); # client to client ping
448 $bot->add_handler('crping', \&on_cpong); # client to client ping (response)
449 $bot->add_handler('cversion', \&on_version); # version info of mozbot.pl
450 $bot->add_handler('csource', \&on_source); # where is mozbot.pl's source
451 $bot->add_handler('caction', \&on_me); # when someone says /me
452 $bot->add_handler('cgender', \&on_gender); # guess
454 $bot->schedule($connectTimeout, \&on_check_connect);
457 &Mails::ServerUp($server) if $mailed;
461 # called when the client receives a startup-related message
463 my ($self, $event) = @_;
464 my (@args) = $event->args;
466 &debug(join(' ', @args));
469 # called when the client receives a server notice
471 my ($self, $event) = @_;
472 &debug($event->type.': '.join(' ', $event->args));
475 # called when the client receives whois data
477 my ($self, $event) = @_;
478 &debug('collecting whois information: '.join('|', $event->args));
479 # XXX could cache this information and then autoop people from
480 # the bot's host, or whatever
483 my ($nickFirstTried, $nickHadProblem, $nickProblemEscalated) = (0, 0, 0);
485 # this is called for the welcome message (001) it calls on_set_nick to
486 # get our nick and on_set_umode to set our umode once we have a nick
488 my ($self, $event) = @_;
489 on_set_nick($self, $event);
490 on_set_umode($self, $event);
493 # this is called both for the welcome message (001) and by the on_nick handler
495 my ($self, $event) = @_;
496 ($lastNick) = $event->args; # (args can be either array or scalar, we want the first value)
499 $newnick++ while (($newnick < @nicks) and ($lastNick ne $nicks[$newnick]));
500 # If nick isn't there, add it.
501 if ($newnick >= @nicks) {
502 push(@nicks, $lastNick);
506 &debug("using nick '$nicks[$nick]'");
508 # try to get our hostname
509 $self->whois($nicks[$nick]);
511 if ($nickHadProblem) {
512 Mails::NickOk($nicks[$nick]) if $nickProblemEscalated;
517 &Configuration::Save($cfgfile, &::configStructure(\$nick, \@nicks));
521 my ($self, $event, $nickSlept) = @_, 0;
522 return unless $self->connected();
524 if ($event->type eq 'erroneusnickname') {
525 my ($currentNick, $triedNick, $err) = $event->args; # current, tried, errmsg
526 &debug("requested nick ('$triedNick') refused by server ('$err')");
527 } elsif ($event->type eq 'nicknameinuse') {
528 my ($currentNick, $triedNick, $err) = $event->args; # current, tried, errmsg
529 &debug("requested nick ('$triedNick') already in use ('$err')");
531 my $type = $event->type;
532 my $args = join(' ', $event->args);
533 &debug("message $type from server: $args");
536 if (defined $lastNick) {
537 &debug("silently abandoning nick change idea :-)");
541 # at this point, we don't yet have a nick, but we need one
544 &debug("waited for a bit -- reading $cfgfile then searching for a nick...");
545 &Configuration::Get($cfgfile, &configStructure(\@nicks, \$nick));
546 $nick = 0 if ($nick > $#nicks) or ($nick < 0); # sanitise
547 $nickFirstTried = $nick;
549 if (not $nickHadProblem) {
551 $nickFirstTried = $nick;
554 $nick = 0 if $nick > $#nicks; # sanitise
556 if ($nick == $nickFirstTried) {
559 &debug("could not find an acceptable nick");
560 &debug("nicks tried: @nicks");
563 &debug("edit $cfgfile to add more nicks *hint* *hint*");
564 $nickProblemEscalated ||= # only e-mail once (returns 0 on failure)
565 Mails::NickShortage($cfgfile, $self->server, $self->port,
566 $self->username, $self->ircname, @nicks)
567 &debug("going to wait $sleepdelay seconds so as not to overload ourselves.");
568 $self->schedule($sleepdelay, \&on_nick_taken, $event, 1); # try again
569 return; # otherwise we no longer respond to pings.
572 # else, we're terminal bound, ask user for nick
573 print "Please suggest a nick (blank to abort): ";
577 &debug("Could not find an acceptable nick");
580 # XXX this could introduce duplicates
581 @nicks = (@nicks[0..$nickFirstTried], $new, @nicks[$nickFirstTried+1..$#nicks]);
582 $nick += 1; # try the new nick now
583 $nickFirstTried = $nick;
587 &debug("now going to try nick '$nicks[$nick]'");
588 &Configuration::Save($cfgfile, &configStructure(\$nick, \@nicks));
589 $self->nick($nicks[$nick]);
592 #called by on_welcome after we get our nick
594 my ($self, $event) = @_;
595 # set usermode for the bot
597 &debug("using umode: '$umode'");
598 $self->mode($self->nick, $umode);
602 # called when we connect.
606 if (defined($self->{'__mozbot__shutdown'})) { # HACK HACK HACK
607 &debug('Uh oh. I connected anyway, even though I thought I had timed out.');
608 &debug('I\'m going to increase the timeout time by 20%.');
609 $connectTimeout = $connectTimeout * 1.2;
610 &Configuration::Save($cfgfile, &configStructure(\$connectTimeout));
611 $self->quit('having trouble connecting, brb...');
612 # XXX we don't call the SpottedQuit handlers here
616 # -- #mozwebtools was here --
617 # *** oopsbot (oopsbot@129.59.231.42) has joined channel #mozwebtools
618 # *** Mode change [+o oopsbot] on channel #mozwebtools by timeless
619 # <timeless> wow an oopsbot!
620 # *** Signoff: oopsbot (oopsbot@129.59.231.42) has left IRC [Leaving]
622 # <timeless> not very stable.
624 # now load all modules
625 my @modulesToLoad = @modulenames;
626 @modules = (BotModules::Admin->create('Admin', '')); # admin commands
627 @modulenames = ('Admin');
628 foreach (@modulesToLoad) {
629 next if $_ eq 'Admin'; # Admin is static and is installed manually above
630 my $result = LoadModule($_);
634 &debug("failed to load $_", $result);
638 # mass-configure the modules
639 &debug("loading module configurations...");
640 { my %struct; # scope this variable
641 foreach my $module (@modules) { %struct = (%struct, %{$module->configStructure()}); }
642 &Configuration::Get($cfgfile, \%struct);
643 } # close the scope for the %struct variable
645 # tell the modules they have joined IRC
646 my $event = newEvent({
649 foreach my $module (@modules) {
650 $module->JoinedIRC($event);
653 # tell the modules to set up the scheduled commands
654 &debug('setting up scheduler...');
655 foreach my $module (@modules) {
657 $module->Schedule($event);
660 &debug("Warning: An error occured while loading the module:\n$@");
665 &debug('going to join: '.join(',', @channels));
666 foreach my $channel (@channels) {
667 if (defined($channelKeys{$channel})) {
668 $self->join($channel, $channelKeys{$channel});
670 $self->join($channel);
675 # enable the drainmsgqueue
676 &drainmsgqueue($self);
677 $self->schedule($delaytime, \&lowerRecentMessageCount);
679 # signal that we are connected (see next two functions)
680 $self->{'__mozbot__active'} = 1; # HACK HACK HACK
683 &debug('initialisation took '.&days($uptime).'.');
688 sub on_check_connect {
690 return if (defined($self->{'__mozbot__shutdown'}) or defined($self->{'__mozbot__active'})); # HACK HACK HACK
691 $self->{'__mozbot__shutdown'} = 1; # HACK HACK HACK
692 &debug("connection timed out -- trying again");
693 # XXX we don't call the SpottedQuit handlers here
694 foreach (@modules) { $_->unload(); }
696 $self->quit('connection timed out -- trying to reconnect');
700 # if something nasty happens
701 sub on_disconnected {
702 my ($self, $event) = @_;
703 return if defined($self->{'__mozbot__shutdown'}); # HACK HACK HACK
704 $self->{'__mozbot__shutdown'} = 1; # HACK HACK HACK
705 # &do(@_, 'SpottedQuit'); # XXX do we want to do this?
706 my($reason) = $event->args;
707 if ($reason =~ /Connection timed out/osi
708 and ($serverRestrictsIRCNames ne $server
709 or $serverExpectsValidUsername ne $server)) {
710 # try to set everything up as simple as possible
711 $serverRestrictsIRCNames = $server;
712 $serverExpectsValidUsername = $server;
713 &Configuration::Save($cfgfile, &configStructure(\$serverRestrictsIRCNames));
714 &debug("Hrm, $server is having issues.");
715 &debug("We're gonna try again with different settings, hold on.");
716 &debug("The full message from the server was: '$reason'");
717 } elsif ($reason =~ /Bad user info/osi and $serverRestrictsIRCNames ne $server) {
718 # change our IRC name to something simpler by setting the flag
719 $serverRestrictsIRCNames = $server;
720 &Configuration::Save($cfgfile, &configStructure(\$serverRestrictsIRCNames));
721 &debug("Hrm, $server didn't like our IRC name.");
722 &debug("Trying again with a simpler one, hold on.");
723 &debug("The full message from the server was: '$reason'");
724 } elsif ($reason =~ /identd/osi and $serverExpectsValidUsername ne $server) {
725 # try setting our username to the actual username
726 $serverExpectsValidUsername = $server;
727 &Configuration::Save($cfgfile, &configStructure(\$delaytime));
728 &debug("Hrm, $server said something about an identd problem.");
729 &debug("Trying again with our real username, hold on.");
730 &debug("The full message from the server was: '$reason'");
731 } elsif ($reason =~ /Excess Flood/osi) {
732 # increase the delay by 20%
733 $delaytime = $delaytime * 1.2;
734 &Configuration::Save($cfgfile, &configStructure(\$delaytime));
735 &debug('Hrm, we it seems flooded the server. Trying again with a delay 20% longer.');
736 &debug("The full message from the server was: '$reason'");
737 } elsif ($reason =~ /Bad Password/osi) {
738 &debug('Hrm, we don\'t seem to know the server password.');
739 &debug("The full message from the server was: '$reason'");
741 print "Please enter the server password: ";
744 &Configuration::Save($cfgfile, &configStructure(\$password));
746 &debug("edit $cfgfile to set the password *hint* *hint*");
747 &debug("going to wait $sleepdelay seconds so as not to overload ourselves.");
751 &debug("eek! disconnected from network: '$reason'");
753 foreach (@modules) { $_->unload(); }
758 # on_join_channel: called when we join a channel
759 sub on_join_channel {
760 my ($self, $event) = @_;
761 my ($nick, $channel) = $event->args;
762 $channel = lc($channel);
763 push(@channels, $channel);
764 &Configuration::Save($cfgfile, &configStructure(\@channels));
765 &debug("joined $channel, about to autojoin modules...");
767 $_->JoinedChannel(newEvent({
769 'channel' => $channel,
770 'target' => $channel,
776 # if something nasty happens
778 &debug("Connection: garbage collected");
782 my ($data, $nick) = @_;
783 return $data =~ /^(\s*$nick(?:[\s,:;!?]+|\s*:-\s*|\s*--+\s*|\s*-+>?\s+))(.+)$/is ?
784 (defined $2 ? $2 : '') : undef;
787 # on_public: messages received on channels
789 my ($self, $event) = @_;
790 my $data = join(' ', $event->args);
791 if (defined($_ = targetted($data, quotemeta($nicks[$nick])))) {
793 setEventArgs($event, $_);
794 $event->{'__mozbot__fulldata'} = $data;
795 &do($self, $event, 'Told', 'Baffled');
797 &do($self, $event, 'Heard');
800 foreach my $nick (@ignoredTargets) {
801 if (defined targetted($data, $nick)) {
802 my $channel = &toToChannel($self, @{$event->to});
803 &debug("Ignored (target matched /$nick/): $channel <".$event->nick.'> '.join(' ', $event->args));
807 &do($self, $event, 'Heard');
811 # on_noticemsg: notice messages from the server, some service, or another
812 # user. beware! it's generally Bad Juju to respond to these, but for
813 # some things (like opn's NickServ) it's appropriate.
815 my ($self, $event) = @_;
816 &do($self, $event, 'Noticed');
820 my ($self, $event) = @_;
821 my $data = join(' ', $event->args);
822 my $nick = quotemeta($nicks[$nick]);
823 if (($data =~ /^($nick(?:[-\s,:;.!?]|\s*-+>?\s+))(.+)$/is) and ($2)) {
824 # we do this so that you can say 'mozbot do this' in both channels
825 # and /query screens alike (otherwise, in /query screens you would
826 # have to remember to omit the bot name).
827 setEventArgs($event, $2);
829 &do($self, $event, 'Told', 'Baffled');
832 # on_me: /me actions (CTCP actually)
834 my ($self, $event) = @_;
835 my @data = $event->args;
836 my $data = join(' ', @data);
837 setEventArgs($event, $data);
838 my $nick = quotemeta($nicks[$nick]);
839 if ($data =~ /(?:^|[\s":<([])$nick(?:[])>.,?!\s'&":]|$)/is) {
840 &do($self, $event, 'Felt');
842 &do($self, $event, 'Saw');
846 # on_topic: for when someone changes the topic
847 # also for when the server notifies us of the topic
848 # ...so we have to parse it carefully.
850 my ($self, $event) = @_;
851 if ($event->userhost eq '@') {
852 # server notification
854 my (undef, $channel, $topic) = $event->args;
855 setEventArgs($event, $topic);
856 $event->to($channel);
858 &do(@_, 'SpottedTopicChange');
861 # on_kick: parse the kick event
863 my ($self, $event) = @_;
864 my ($channel, $from) = $event->args; # from is already set anyway
865 my $who = $event->to;
866 $event->to($channel);
868 setEventArgs($event, $_);
869 if ($_ eq $nicks[$nick]) {
872 &do(@_, 'SpottedKick');
877 # Gives lag results for outgoing PINGs.
879 my ($self, $event) = @_;
880 &debug('completed CTCP PING with '.$event->nick.': '.days($event->args->[0]));
881 # XXX should be able to use this then... see also Greeting module
882 # in standard distribution
885 # -- #mozbot was here --
886 # <timeless> $conn->add_handler('gender',\&on_ctcp_gender);
887 # <timeless> sub on_ctcp_gender{
888 # <timeless> my (undef, $event)=@_;
889 # <timeless> my $nick=$event->nick;
890 # <Hixie> # timeless this suspense is killing me!
891 # <timeless> $bot->ctcp_reply($nick, 'neuter');
894 # on_gender: What gender are we?
896 my ($self, $event) = @_;
897 my $nick = $event->nick;
898 $self->ctcp_reply($nick, $gender);
901 # on_nick: A nick changed -- was it ours?
903 my ($self, $event) = @_;
904 if ($event->nick eq $nicks[$nick]) {
905 on_set_nick($self, $event);
907 &do(@_, 'SpottedNickChange');
910 # simple handler for when users do various things and stuff
911 sub on_join { &do(@_, 'SpottedJoin'); }
912 sub on_part { &do(@_, 'SpottedPart'); }
913 sub on_quit { &do(@_, 'SpottedQuit'); }
914 sub on_invite { &do(@_, 'Invited'); }
915 sub on_mode { &do(@_, 'ModeChange'); } # XXX need to parse modes # XXX on key change, change %channelKeys hash
916 sub on_umode { &do(@_, 'UModeChange'); }
917 sub on_version { &do(@_, 'CTCPVersion'); }
918 sub on_source { &do(@_, 'CTCPSource'); }
919 sub on_cping { &do(@_, 'CTCPPing'); }
923 $event->{'time'} = time();
932 if (defined($channel)) {
937 } elsif ($_ eq $nicks[$nick]) {
941 return lc($channel); # if message was sent to one person only, this is it
944 # XXX some code below calls this, on lines marked "hack hack hack". We
945 # should fix this so that those are supported calls.
948 my $event = shift @_;
950 my $channel = &toToChannel($self, @$to);
953 '_event' => $event, # internal internal internal do not use... ;-)
954 'channel' => $channel,
955 'from' => $event->nick,
956 'target' => $channel || $event->nick,
957 'user' => $event->userhost,
958 'data' => join(' ', $event->args),
959 'fulldata' => defined($event->{'__mozbot__fulldata'}) ? $event->{'__mozbot__fulldata'} : join(' ', $event->args),
961 'subtype' => $event->type,
962 'firsttype' => $_[0],
963 'nick' => $nicks[$nick],
967 # updated admin field if person is an admin
968 if ($authenticatedUsers{$event->userhost}) {
969 if (($userFlags{$authenticatedUsers{$event->userhost}} & 1) == 1) {
970 $lastadmin = $event->nick;
972 $e->{'userName'} = $authenticatedUsers{$event->userhost};
973 $e->{'userFlags'} = $userFlags{$authenticatedUsers{$event->userhost}};
975 $e->{'userName'} = 0;
977 unless (scalar(grep $e->{'user'} =~ /^$_$/gi, @ignoredUsers)) {
982 my @modulesInNextLoop = @modules;
984 $e->{'type'} = $type;
985 &debug("$type: $channel <".$event->nick.'> '.join(' ', $event->args));
988 $e->{'level'} = $level;
989 my @modulesInThisLoop = @modulesInNextLoop;
990 @modulesInNextLoop = ();
991 foreach my $module (@modulesInThisLoop) {
994 $currentResponse = $module->do($self, $event, $type, $e);
997 # $@ contains the error
998 &debug("ERROR IN MODULE $module->{'_name'}!!!", $@);
999 } elsif (!defined($currentResponse)) {
1000 &debug("ERROR IN MODULE $module->{'_name'}: invalid response code to event '$type'.");
1002 if ($currentResponse > $level) {
1003 push(@modulesInNextLoop, $module);
1005 $continue = ($continue and $currentResponse);
1008 } while ($continue and @modulesInNextLoop);
1009 } while ($continue and scalar(@_));
1011 &debug('Ignored (from \'' . $event->userhost . "'): $channel <".$event->nick.'> '.join(' ', $event->args));
1018 foreach my $module (@modules) {
1023 # $@ contains the error
1024 &debug("ERROR!!!", $@);
1030 ################################
1031 # internal utilities #
1032 ################################
1036 my $timeLastSetAway = 0; # the time since the away flag was last set, so that we don't set it repeatedly.
1038 # Use this routine, always, instead of the standard "privmsg" routine. This
1039 # one makes sure we don't send more than one message every two seconds or so,
1040 # which will make servers not whine about us flooding the channel.
1041 # messages aren't the only type of flood :-( away is included
1043 my ($self, $who, $msg, $do) = (@_, 'msg');
1044 unless ((defined($do) and defined($msg) and defined($who) and ($who ne '')) and
1045 ((($do eq 'msg') and (not ref($msg))) or
1046 (($do eq 'me') and (not ref($msg))) or
1047 (($do eq 'notice') and (not ref($msg))) or
1048 (($do eq 'ctcpSend') and (ref($msg) eq 'ARRAY') and (@$msg >= 2)) or
1049 (($do eq 'ctcpReply') and (not ref($msg))))) {
1050 cluck('Wrong arguments passed to sendmsg() - ignored');
1052 $self->schedule($delaytime / 2, \&drainmsgqueue) unless @msgqueue;
1053 if ($do eq 'msg' or $do eq 'me' or $do eq 'notice') {
1054 foreach (splitMessageAcrossLines($msg)) {
1055 push(@msgqueue, [$who, $_, $do]);
1058 push(@msgqueue, [$who, $msg, $do]);
1063 # send any pending messages
1066 return unless $self->connected;
1067 my $qln = @msgqueue;
1068 if (@msgqueue > 0) {
1069 my ($who, $msg, $do) = getnextmsg();
1070 unless (weHaveSaidThisTooManyTimesAlready($self, \$who, \$msg, \$do)) {
1073 &debug("->$who: $msg"); # XXX this makes logfiles large quickly...
1074 $self->privmsg($who, $msg); # it seems 'who' can be an arrayref and it works
1076 } elsif ($do eq 'me') {
1077 &debug("->$who * $msg"); # XXX
1078 $self->me($who, $msg);
1080 } elsif ($do eq 'notice') {
1081 &debug("=notice=>$who: $msg");
1082 $self->notice($who, $msg);
1084 } elsif ($do eq 'ctcpSend') {
1085 { local $" = ' '; &debug("->$who CTCP PRIVMSG @$msg"); }
1086 my $type = shift @$msg; # @$msg contains (type, args)
1087 $self->ctcp($type, $who, @$msg);
1089 } elsif ($do eq 'ctcpReply') {
1090 &debug("->$who CTCP NOTICE $msg");
1091 $self->ctcp_reply($who, $msg);
1094 &debug("Unknown action '$do' intended for '$who' (content: '$msg') ignored.");
1096 if (defined($type)) {
1100 'channel' => &toToChannel($self, $who),
1101 'from' => $nicks[$nick],
1103 'user' => undef, # XXX
1108 'firsttype' => $type,
1109 'nick' => $nicks[$nick],
1115 if (@msgqueue > 0) {
1116 if ((@msgqueue % 10 == 0) and (time() - $timeLastSetAway > 5 * $delaytime)) {
1117 &bot_longprocess($self, "Long send queue. There were $qln, and I just sent one to $who.");
1118 $timeLastSetAway = time();
1119 $self->schedule($delaytime * 4, # because previous one counts as message, plus you want to delay an extra bit regularly
1122 $self->schedule($delaytime, \&drainmsgqueue);
1125 &bot_back($self); # clear away state
1130 sub weHaveSaidThisTooManyTimesAlready {
1131 my($self, $who, $msg, $do) = @_;
1133 if ($$do eq 'ctcpSend') {
1135 $key = "$$who,$$do,@{$$msg}";
1137 $key = "$$who,$$do,$$msg";
1139 my $count = ++$recentMessages{$key};
1140 if ($count >= $recentMessageCountThreshold and
1141 $count < $recentMessageCountThreshold + 1 and
1142 $$do ne 'ctcpSend') {
1143 $recentMessages{$key} += $recentMessageCountPenalty;
1145 if (length($msg) > 23) { # arbitrary length (XXX)
1146 $text = substr($text, 0, 20) . '...';
1149 $$msg = "was going to say '$text' but has said it too many times today already";
1150 } elsif ($count >= $recentMessageCountThreshold) {
1151 if ($count > $recentMessageCountLimit) {
1152 # if the message keeps getting output, we'll get to the
1153 # point where if it stops it doesn't matter because the
1154 # recent count will be _so_ high we'll never see zero
1155 # again. So here we put a cap on the recent message count.
1156 $recentMessages{$key} = $recentMessageCountLimit;
1158 if ($$do eq 'msg') {
1159 &debug("MUTED: ->$$who: $$msg");
1160 } elsif ($$do eq 'me') {
1161 &debug("MUTED: ->$$who * $$msg"); # XXX
1162 } elsif ($$do eq 'notice') {
1163 &debug("MUTED: =notice=>$$who: $$msg");
1164 } elsif ($$do eq 'ctcpSend') {
1166 &debug("MUTED: ->$$who CTCP PRIVMSG @{$$msg}");
1167 } elsif ($$do eq 'ctcpReply') {
1168 &debug("MUTED: ->$$who CTCP NOTICE $$msg");
1170 &debug("MUTED: Unknown action '$$do' intended for '$$who' (content: '$$msg') ignored.");
1177 sub lowerRecentMessageCount {
1179 return unless $self->connected;
1180 foreach my $key (keys %recentMessages) {
1181 $recentMessages{$key} -= $recentMessageCountDecrementRate;
1182 if ($recentMessages{$key} <= 0) {
1183 delete $recentMessages{$key};
1186 $self->schedule($delaytime, \&lowerRecentMessageCount);
1189 # wrap long lines at spaces and hard returns (\n)
1190 # this is for IRC, not for the console -- long can be up to 255
1191 sub splitMessageAcrossLines {
1193 my $MAXPROTOCOLLENGTH = 255;
1195 # $str could be several lines split with \n, so split it first:
1196 foreach my $line (split(/\n/, $str)) {
1197 while (length($line) > $MAXPROTOCOLLENGTH) {
1198 # position is zero-based index
1199 my $pos = rindex($line, ' ', $MAXPROTOCOLLENGTH - 1);
1201 $pos = $MAXPROTOCOLLENGTH - 1;
1203 push(@output, substr($line, 0, $pos));
1204 $line = substr($line, $pos);
1205 $line =~ s/^\s+//gos;
1207 push(@output, $line) if length($line);
1212 # equivalent of shift or pop, but for the middle of the array.
1213 # used by getnextmsg() below to pull the messages out of the
1214 # msgqueue stack and shove them at the end.
1216 my ($index, $list) = @_;
1217 my $result = @{$list}[$index];
1218 @{$list} = (@{$list}[0..$index-1], @{$list}[$index+1..$#{$list}]);
1222 # looks at the msgqueue stack and decides which message to send next.
1224 my ($who, $msg, $do) = @{shift(@msgqueue)};
1227 while ($index < @msgqueue) {
1228 if ($msgqueue[$index]->[0] eq $who) {
1229 push(@newmsgqueue, &yank($index, \@msgqueue));
1234 push(@msgqueue, @newmsgqueue);
1235 return ($who, $msg, $do);
1240 # mark bot as being away
1241 sub bot_longprocess {
1243 &debug('[away: '.join(' ',@_).']');
1244 $self->away(join(' ',@_));
1248 # mark bot as not being away anymore
1251 $self->away('') if $markedaway;
1256 # internal routines for IO::Select handling
1260 $irc->removefh($pipe);
1261 # enable slurp mode for this function (see man perlvar for $/ documentation)
1265 &debug("child ${$pipe}->{'BotModules_PID'} completed ${$pipe}->{'BotModules_ChildType'}".
1266 (${$pipe}->{'BotModules_Module'}->{'_shutdown'} ?
1267 ' (nevermind, module has shutdown)': ''));
1268 kill 9, ${$pipe}->{'BotModules_PID'}; # ensure child is dead
1269 # non-blocking reap of any pending zombies
1270 1 while waitpid(-1,WNOHANG) > 0;
1271 return if ${$pipe}->{'BotModules_Module'}->{'_shutdown'}; # see unload()
1273 ${$pipe}->{'BotModules_Event'}->{'time'} = time(); # update the time field of the event
1274 ${$pipe}->{'BotModules_Module'}->ChildCompleted(
1275 ${$pipe}->{'BotModules_Event'},
1276 ${$pipe}->{'BotModules_ChildType'},
1278 @{${$pipe}->{'BotModules_Data'}}
1282 # $@ contains the error
1283 &debug("ERROR!!!", $@);
1285 # prevent any memory leaks by cleaning up all the variables we added
1286 foreach (keys %{${$pipe}}) {
1287 m/^BotModules_/ && delete(${$pipe}->{$_});
1291 sub bot_select_data_available {
1293 &debug("Module ${$handle}->{'BotModules_Module'}->{'_name'} received some data");
1294 # read data while there is some
1296 vec($fh, fileno($handle), 1) = 1;
1297 my $count = 0; # number of bytes read
1301 while (select($ready = $fh, undef, undef, 0.1) and
1302 vec($ready, fileno($handle), 1) and
1304 not $close) { # read up to 1kb
1305 sysread($handle, $data, 1, length($data)) or $close = 1;
1307 if (not ${$handle}->{'BotModules_Module'}->{'_shutdown'}) {
1309 ${$handle}->{'BotModules_Event'}->{'time'} = time();
1310 ${$handle}->{'BotModules_Module'}->DataAvailable(
1311 ${$handle}->{'BotModules_Event'},
1318 # $@ contains the error
1319 &debug("ERROR!!!", $@);
1322 # module doesn't care, it was shut down
1323 &debug("Dropping data - module is already shut down.");
1327 # Note: It's the responsibility of the module to actually
1329 &debug("Dropping handle...");
1330 $irc->removefh($handle);
1331 # prevent any memory leaks by cleaning up all the variables we added
1332 foreach (keys %{${$handle}}) {
1333 m/^BotModules_/ && delete(${$handle}->{$_});
1339 # internal routines for console output, stuff
1341 # print debugging info
1345 $line = $_; # can't chomp $_ since it is a hardref to the arguments...
1346 chomp $line; # ...and they are probably a constant string!
1348 print &logdate() . " ($$) $line";
1351 # XXX this file grows without bounds!!!
1352 if (open(LOG, ">>$LOGFILEPREFIX.$$.log")) {
1353 print LOG &logdate() . " $line\n";
1357 print " [not logged, $!]\n";
1363 # logdate: return nice looking date and time stamp
1365 my ($sec, $min, $hour, $mday, $mon, $year) = gmtime(shift or time());
1366 return sprintf("%d-%02d-%02d %02d:%02d:%02d UTC",
1367 $year + 1900, $mon + 1, $mday, $hour, $min, $sec);
1370 # days: how long ago was that?
1374 my $seconds = time() - $then;
1375 my $minutes = int ($seconds / 60);
1376 my $hours = int ($minutes / 60);
1377 my $days = int ($hours / 24);
1379 if ($seconds < 60) {
1380 return sprintf("%d second%s", $seconds, $seconds == 1 ? "" : "s");
1381 } elsif ($minutes < 60) {
1382 return sprintf("%d minute%s", $minutes, $minutes == 1 ? "" : "s");
1383 } elsif ($hours < 24) {
1384 return sprintf("%d hour%s", $hours, $hours == 1 ? "" : "s");
1386 return sprintf("%d day%s", $days, $days == 1 ? "" : "s");
1393 &debug("received signal $sig. shutting down...");
1394 &debug('This is evil. You should /msg me a shutdown command instead.');
1395 &debug('WARNING: SHUTTING ME DOWN LIKE THIS CAN CAUSE FORKED PROCESSES TO START UP AS BOTS!!!'); # XXX which we should fix, of course.
1396 exit(1); # sane exit, including shutting down any modules
1400 # internal routines for configuration
1402 my %configStructure; # hash of cfg file keys and associated variable refs
1404 # ok. In strict 'refs' mode, you cannot use strings as refs. Fair enough.
1405 # However, hash keys are _always_ strings. Using a ref as a hash key turns
1406 # it into a string. So we have to keep a virgin copy of the ref around.
1408 # So the structure of the %configStructure hash is:
1409 # "ref" => [ cfgName, ref ]
1412 sub registerConfigVariables {
1413 my (@variables) = @_;
1414 foreach (@variables) {
1415 $configStructure{$$_[0]} = [$$_[1], $$_[0]];
1417 } # are you confused yet?
1419 sub configStructure {
1420 my (@variables) = @_;
1422 @variables = keys %configStructure unless @variables;
1423 foreach (@variables) {
1424 confess("Function configStructure was passed something that is either not a ref or has not yet neem registered, so aborted") unless defined($configStructure{$_});
1425 $struct{$configStructure{$_}[0]} = $configStructure{$_}[1];
1431 # internal routines for handling the modules
1435 foreach my $module (@modules) { # XXX this is not cached as a hash as performance is not a priority here
1436 return $module if $name eq $module->{'_name'};
1444 $name =~ s/[^-a-zA-Z0-9]/-/gos;
1445 # check the module is not already loaded
1446 foreach (@modules) {
1447 if ($_->{'_name'} eq $name) {
1448 return "Failed [0]: Module already loaded. Don't forget to enable it in the various channels (vars $name channels '+#channelname').";
1451 # read the module in from a file
1452 my $filename = "./BotModules/$name.bm"; # bm = bot module
1453 my $result = open(my $file, "< $filename");
1456 local $/ = undef; # enable "slurp" mode
1457 <$file>; # whole file now here
1460 # if ($code =~ /package\s+\QBotModules::$name\E\s*;/gos) { XXX doesn't work reliably?? XXX
1462 $code =~ /^(.*)$/os;
1463 $code = $1; # completely defeat the tainting mechanism.
1464 # $code = "# FILE: $filename\n".$code; # "# file 1 '$filename' \n" would be good without Carp.pm
1465 { no warnings; # as per the warning, but doesn't work??? XXX
1468 # $@ contains the error
1469 return "Failed [4]: $@";
1471 # if ok, then create a module
1474 \$newmodule = BotModules::$name->create('$name', '$filename');
1477 # $@ contains the error
1478 return "Failed [5]: $@";
1480 # if ok, then add it to the @modules list
1481 push(@modules, $newmodule);
1482 push(@modulenames, $newmodule->{'_name'});
1483 &Configuration::Save($cfgfile, &::configStructure(\@modulenames));
1489 # return "Failed [3]: Could not find valid module definition line.";
1492 # $! contains the error
1494 return "Failed [2]: $!";
1496 return "Failed [2]: Module file is empty.";
1500 # $! contains the error
1501 return "Failed [1]: $!";
1507 # remove the reference from @modules
1510 foreach (@modules) {
1511 if ($name eq $_->{'_name'}) {
1512 if ($_->{'_static'}) {
1513 return 'Cannot unload this module, it is built in.';
1517 push(@newmodules, $_);
1518 push(@newmodulenames, $_->{'_name'});
1521 if (@modules == @newmodules) {
1522 return 'Module not loaded. Are you sure you have the right name?';
1524 @modules = @newmodules;
1525 @modulenames = @newmodulenames;
1526 &Configuration::Save($cfgfile, &::configStructure(\@modulenames));
1531 # password management functions
1534 # straight from man perlfunc
1535 return join('', ('.', '/', 0..9, 'A'..'Z', 'a'..'z')[rand 64, rand 64]);
1540 return crypt($text, &getSalt());
1544 my($text, $password) = @_;
1545 return (crypt($text, $password) eq $password);
1548 ################################
1550 ################################
1552 # And now, for my next trick, the base module (duh).
1556 1; # nothing to see here...
1560 # create - create a new BotModules object.
1561 # Do not call this yourself. We call it. Ok?
1562 # Do not override this either, unless you know what
1563 # you are doing (I don't, and I wrote it...). If you
1564 # want to add variables to $self, use Initialise.
1565 # The parameter is the name of the module.
1568 my ($name, $filename) = @_;
1571 '_shutdown' => 0, # see unload()
1572 '_static' => 0, # set to 1 to prevent module being unloaded
1575 '_filename' => $filename,
1576 '_filemodificationtime' => undef,
1578 bless($self, $class);
1579 $self->Initialise();
1580 $self->RegisterConfig();
1586 $self->debug('garbage collected');
1589 # called by &::UnloadModule().
1590 # this removes any pointers to the module.
1591 # for example, it stops the scheduler from installing new timers,
1592 # so that the bot [eventually] severs its connection with the module.
1595 $self->Unload(); # hook for bot modules to use
1596 $self->{'_shutdown'} = 1; # see doScheduled and bot_select
1599 # configStructure - return the hash needed for Configuration module
1600 sub configStructure {
1602 return $self->{'_config'};
1605 # do - called to do anything (duh) (no, do, not duh) (oh, ok, sorry)
1608 my ($bot, $event, $type, $e) = @_;
1609 # first, we check that the user is not banned from using this module. If he
1610 # is, then re give up straight away.
1611 return 1 if ($self->IsBanned($e));
1612 # next we check that the module is actually enabled in this channel, and
1613 # if it is not we quit straight away as well.
1614 return 1 unless ($e->{'channel'} eq '') or ($self->InChannel($e));
1615 # Ok, dispatch the event.
1616 if ($type eq 'Told') {
1617 return $self->Told($e, $e->{'data'});
1618 } elsif ($type eq 'Heard') {
1619 return $self->Heard($e, $e->{'data'});
1620 } elsif ($type eq 'Baffled') {
1621 return $self->Baffled($e, $e->{'data'});
1622 } elsif ($type eq 'Noticed') {
1623 return $self->Noticed($e, $e->{'data'});
1624 } elsif ($type eq 'Felt') {
1625 return $self->Felt($e, $e->{'data'});
1626 } elsif ($type eq 'Saw') {
1627 return $self->Saw($e, $e->{'data'});
1628 } elsif ($type eq 'Invited') {
1629 return $self->Invited($e, $e->{'data'});
1630 } elsif ($type eq 'Kicked') {
1631 return $self->Kicked($e, $e->{'channel'});
1632 } elsif ($type eq 'ModeChange') {
1633 return $self->ModeChange($e, $e->{'channel'}, $e->{'data'}, $e->{'from'});
1634 } elsif ($type eq 'Authed') {
1635 return $self->Authed($e, $e->{'from'});
1636 } elsif ($type eq 'SpottedNickChange') {
1637 return $self->SpottedNickChange($e, $e->{'from'}, $e->{'data'});
1638 } elsif ($type eq 'SpottedTopicChange') {
1639 return $self->SpottedTopicChange($e, $e->{'channel'}, $e->{'data'});
1640 } elsif ($type eq 'SpottedJoin') {
1641 return $self->SpottedJoin($e, $e->{'channel'}, $e->{'from'});
1642 } elsif ($type eq 'SpottedPart') {
1643 return $self->SpottedPart($e, $e->{'channel'}, $e->{'from'});
1644 } elsif ($type eq 'SpottedKick') {
1645 return $self->SpottedKick($e, $e->{'channel'}, $e->{'data'});
1646 } elsif ($type eq 'SpottedQuit') {
1647 return $self->SpottedQuit($e, $e->{'from'}, $e->{'data'});
1648 } elsif ($type eq 'CTCPPing') {
1649 return $self->CTCPPing($e, $e->{'from'}, $e->{'data'});
1650 } elsif ($type eq 'CTCPVersion') {
1651 return $self->CTCPVersion($e, $e->{'from'}, $e->{'data'});
1652 } elsif ($type eq 'CTCPSource') {
1653 return $self->CTCPSource($e, $e->{'from'}, $e->{'data'});
1655 # XXX have not implemented mode parsing yet
1656 } elsif ($type eq 'GotOpped') {
1657 return $self->GotOpped($e, $e->{'channel'}, $e->{'from'});
1658 } elsif ($type eq 'GotDeopped') {
1659 return $self->GotDeopped($e, $e->{'channel'}, $e->{'from'});
1660 } elsif ($type eq 'SpottedOpping') {
1661 return $self->SpottedOpping($e, $e->{'channel'}, $e->{'from'});
1662 } elsif ($type eq 'SpottedDeopping') {
1663 return $self->SpottedDeopping($e, $e->{'channel'}, $e->{'from'});
1665 $self->debug("Unknown action type '$type'. Ignored.");
1666 # XXX UModeChange (not implemented yet)
1667 return 1; # could not do it
1672 # MODULE API - use these from the your routines.
1674 # prints output to the console
1677 foreach my $line (@_) {
1678 &::debug('Module '.$self->{'_name'}.': '.$line);
1682 # saveConfig - call this when you change a configuration option. It resaves the config file.
1685 &Configuration::Save($cfgfile, $self->configStructure());
1688 # registerVariables - Registers a variable with the config system and the var setting system
1690 # [ 'name', persistent ? 1:0, editable ? 1:0, $value ],
1691 # use undef instead of 0 or 1 to leave as is
1692 # use undef (or don't mention) the $value to not set the value
1694 sub registerVariables {
1696 my (@variables) = @_;
1697 foreach (@variables) {
1698 $self->{$$_[0]} = $$_[3] if defined($$_[3]);
1699 if (defined($$_[1])) {
1701 $self->{'_config'}->{$self->{'_name'}.'::'.$$_[0]} = \$self->{$$_[0]};
1703 delete($self->{'_config'}->{$self->{'_name'}.'::'.$$_[0]});
1706 $self->{'_variables'}->{$$_[0]} = $$_[2] if defined($$_[2]);
1710 # internal implementation of the scheduler
1713 my ($self, $event, $time, $times, @data) = @_;
1714 return if ($self->{'_shutdown'}); # see unload()
1715 # $self->debug("scheduled event occured; $times left @ $$time second interval");
1717 $event->{'time'} = time(); # update the time field of the event
1718 $self->Scheduled($event, @data);
1719 $self->schedule($event, $time, --$times, @data);
1722 # $@ contains the error
1723 &::debug("ERROR!!!", $@);
1727 # schedule - Sets a timer to call Scheduled later
1728 # for events that should be setup at startup, call this from Schedule().
1731 my ($event, $time, $times, @data) = @_;
1732 return if ($times == 0 or $self->{'_shutdown'}); # see unload()
1733 $times = -1 if ($times < 0); # pass a negative number to have a recurring timer
1736 if (ref($time) eq 'SCALAR') {
1739 return; # XXX maybe be useful?
1743 # $self->debug("Vetoed aggressive scheduling; forcing to 1 second minimum");
1746 $event->{'bot'}->schedule($delay, \&doScheduled, $self, $event, $time, $times, @data);
1749 # spawnChild - spawns a child process and adds it to the list of file handles to monitor
1750 # eventually the bot calls ChildCompleted() with the output of the child process.
1753 my ($event, $command, $arguments, $type, $data) = @_;
1754 # uses IO::SecurePipe and fork and exec
1755 # secure, predictable, no dependencies on external code
1756 # uses fork explicitly (and once implicitly)
1757 my $pipe = IO::SecurePipe->new();
1758 if (defined($pipe)) {
1760 if (defined($child)) {
1762 # we are the parent process
1764 ${$pipe}->{'BotModules_Module'} = $self;
1765 ${$pipe}->{'BotModules_Event'} = {%$event}; # Must be unchanged
1766 ${$pipe}->{'BotModules_ChildType'} = $type;
1767 ${$pipe}->{'BotModules_Data'} = $data;
1768 ${$pipe}->{'BotModules_Command'} = $command;
1769 ${$pipe}->{'BotModules_Arguments'} = $arguments;
1770 ${$pipe}->{'BotModules_PID'} = $child;
1771 $irc->addfh($pipe, \&::bot_select);
1773 $self->debug("spawned $child ($command @$arguments)");
1777 # we are the child process
1778 # call $command and buffer the output
1779 $pipe->writer(); # get writing end of pipe, ready to output the result
1781 if (ref($command) eq 'CODE') {
1782 $output = &$command(@$arguments);
1784 # it would be nice if some of this was on a timeout...
1785 my $result = IO::SecurePipe->new(); # create a new pipe for $command
1786 # call $command (implicit fork(), which may of course fail)
1787 $result->reader($command, @$arguments);
1788 local $/; # to not affect the rest of the program (what little there is)
1789 $/ = \(2*1024*1024); # slurp up to two megabytes
1790 $output = <$result>; # blocks until child process has finished
1791 close($result); # reap child
1793 print $pipe $output if ($output); # output the lot in one go back to parent
1797 # $@ contains the error
1798 $self->debug('failed to spawn child', $@);
1801 # -- #mozwebtools was here --
1802 # <dawn> when is that stupid bot going to get checked in?
1803 # <timeless> after it stops fork bombing
1804 # <dawn> which one? yours or hixies?
1805 # <timeless> his, mine doesn't fork
1806 # <timeless> see topic
1807 # <dawn> are there plans to fix it?
1808 # <timeless> yes. but he isn't sure exactly what went wrong
1809 # <timeless> i think it's basically they fork for wget
1810 # <dawn> why don't you help him?
1811 # <timeless> i don't understand forking
1812 # <dawn> that didn't stop hixie
1813 # <timeless> not to mention the fact that his forking doesn't
1815 # <dawn> you have other machines. techbot1 runs on windows?
1816 # <timeless> yeah it runs on windows
1818 # <dawn> get a real os, man
1820 # The bug causing the 'fork bombing' was that I only
1821 # did the following if $@ was true or if the call to
1822 # 'reader' succeeded -- so if some other error occured
1823 # that didn't trip the $@ test but still crashed out
1824 # of the eval, then the script would quite happily
1825 # continue, and when it eventually died (e.g. because
1826 # of a bad connection), it would respawn multiple
1827 # times (as many times as it had failed to fork) and
1828 # it would succeed in reconnecting as many times as
1829 # had been configured nicks...
1832 $0 =~ m/^(.*)$/os; # untaint $0 so that we can call it below:
1833 exec { $1 } ($1, '--abort'); # do not call shutdown handlers
1834 # the previous line works because exec() bypasses
1835 # the perl object garbarge collection and simply
1836 # deallocates all the memory in one go. This means
1837 # the shutdown handlers (DESTROY and so on) are
1838 # never called for this fork. This is good,
1839 # because otherwise we would disconnect from IRC
1843 $self->debug("failed to shutdown cleanly!!! $@");
1844 exit(1); # exit in case exec($0) failed
1848 $self->debug("failed to fork: $!");
1851 $self->debug("failed to open pipe: $!");
1856 # registerDataHandle - eventually calls DataAvailable
1857 sub registerDataHandle {
1859 my ($event, $handle, $details) = @_;
1860 ${$handle}->{'BotModules_Module'} = $self;
1861 ${$handle}->{'BotModules_Event'} = $event;
1862 ${$handle}->{'BotModules_Details'} = $details;
1863 $irc->addfh($handle, \&::bot_select_data_available);
1864 my $fileno = fileno($handle);
1865 $self->debug("listening to filehandle or socket $fileno");
1868 # getURI - Downloads a file and then calls GotURI
1871 my ($event, $uri, @data) = @_;
1872 $self->spawnChild($event, 'wget', ['--quiet', '--passive', '--user-agent="Mozilla/5.0 (compatible; mozbot)"', '--output-document=-', $uri], 'URI', [$uri, @data]);
1875 # returns a reference to a module -- DO NOT STORE THIS REFERENCE!!!
1878 return &::getModule(@_);
1881 # returns a reference to @msgqueue
1882 # manipulating this is probably not a good idea. In particular,
1883 # don't add anything to this array (use the appropriate methods
1884 # instead, those that use &::sendmsg, below).
1885 sub getMessageQueue {
1890 # returns the value of $helpline
1895 # returns a sorted list of module names
1897 return sort(@modulenames);
1900 # returns a filename with path suitable to use for logging
1901 sub getLogFilename {
1904 return "$LOGFILEDIR/$name";
1907 # tellAdmin - may try to talk to an admin.
1908 # NO GUARANTEES! This will PROBABLY NOT reach anyone!
1911 my ($event, $data) = @_;
1913 $self->debug("Trying to tell admin '$lastadmin' this: $data");
1914 &::sendmsg($event->{'bot'}, $lastadmin, $data);
1916 $self->debug("Wanted to tell an admin '$data', but I've never seen one.");
1920 # ctcpSend - Sends a CTCP message to someone
1923 my ($event, $type, $data) = @_;
1924 &::sendmsg($event->{'bot'}, $event->{'target'}, [$type, $data], 'ctcpSend');
1927 # ctcpReply - Sends a CTCP reply to someone
1930 my ($event, $type, $data) = @_;
1931 unless (defined($type)) {
1932 cluck('No type passed to ctcpReply - ignored');
1934 if (defined($data)) {
1935 &::sendmsg($event->{'bot'}, $event->{'from'}, "$type $data", 'ctcpReply');
1937 &::sendmsg($event->{'bot'}, $event->{'from'}, $type, 'ctcpReply');
1941 # notice - Sends a notice to a channel or person
1944 my ($event, $data) = @_;
1945 &::sendmsg($event->{'bot'}, $event->{'target'}, $data, 'notice');
1948 # say - Sends a message to the channel
1951 my ($event, $data) = @_;
1952 return unless defined $event->{'target'};
1953 $data =~ s/^\Q$event->{'target'}\E: //gs;
1954 &::sendmsg($event->{'bot'}, $event->{'target'}, $data);
1957 # privsay - Sends message to person or channel directly
1958 # only use this if its time-senstive, otherwise you should use say
1961 my ($event, $data) = @_;
1962 return unless defined $event->{'target'};
1963 $data =~ s/^\Q$event->{'target'}\E: //gs;
1964 $event->{'bot'}->privmsg($event->{'target'}, $data);
1967 # announce - Sends a message to every channel
1970 my ($event, $data) = @_;
1971 foreach (@{$self->{'channels'}}) {
1972 &::sendmsg($event->{'bot'}, $_, $data);
1976 # directSay - Sends a message to the person who spoke
1979 my ($event, $data) = @_;
1980 &::sendmsg($event->{'bot'}, $event->{'from'}, $data);
1983 # channelSay - Sends a message to the channel the message came from, IFF it came from a channel.
1986 my ($event, $data) = @_;
1987 &::sendmsg($event->{'bot'}, $event->{'channel'}, $data) if $event->{'channel'};
1990 # -- #mozilla was here --
1991 # <richb> timeless: it's focal review time, and they are working out
1992 # where to allocate the money.
1993 # <richb> timeless: needless to say i have a vested interest in this.
1994 # <leaf> there's money in this?
1995 # <timeless> richb yes; leaf always
1996 # <leaf> how come nobody told me?
1997 # <timeless> because leaf doesn't need money
1998 # <timeless> for leaf it grows on trees
2001 # emote - Sends an emote to the channel
2004 my ($event, $data) = @_;
2005 &::sendmsg($event->{'bot'}, $event->{'target'}, $data, 'me');
2008 # directEmote - Sends an emote to the person who spoke
2011 my ($event, $data) = @_;
2012 &::sendmsg($event->{'bot'}, $event->{'from'}, $data, 'me');
2015 # sayOrEmote - calls say() or emote() depending on whether the string starts with /me or not.
2018 my ($event, $data) = @_;
2019 if ($data =~ /^\/me\s+/osi) {
2020 $data =~ s/^\/me\s+//gosi;
2021 $self->emote($event, $data);
2023 $self->say($event, $data);
2027 # directSayOrEmote - as sayOrEmote() but calls the direct versions instead
2028 sub directSayOrEmote {
2030 my ($event, $data) = @_;
2031 if ($data =~ /^\/me\s+/osi) {
2032 $data =~ s/^\/me\s+//gosi;
2033 $self->directEmote($event, $data);
2035 $self->directSay($event, $data);
2039 # isAdmin - Returns true if the person is an admin
2043 return (($event->{'userName'}) and (($event->{'userFlags'} & 1) == 1));
2046 # setAway - Set the bot's 'away' flag. A blank message will mark the bot as back.
2047 # Note: If you need this you are doing something wrong!!!
2050 my ($event, $message) = @_;
2051 $event->{'bot'}->away($message);
2054 # setNick - Set the bot's nick.
2055 # Note: Best not to use this too much, especially not based on user input,
2056 # as it is not throttled. XXX
2059 my ($event, $value) = @_;
2060 $event->{'bot'}->nick($value);
2065 my ($event, $channel, $mode, $arg) = @_;
2066 $event->{'bot'}->mode($channel, $mode, $arg);
2071 my ($event, $channel, $who, $reason) = @_;
2072 $event->{'bot'}->kick($channel, $who, $reason);
2077 my ($event, $who, $channel) = @_;
2078 $event->{'bot'}->invite($who, $channel);
2081 # pretty printer for turning lists of varying length strings into
2082 # lists of roughly equal length strings without losing any data
2085 my ($preferredLineLength, $prefix, $indent, $divider, @input) = @_;
2086 # sort numerically descending by length
2087 @input = sort {length($b) <=> length($a)} @input;
2088 # if we have a prefix defined, it goes first (duh)
2089 unshift(@input, $prefix) if defined($prefix);
2093 push(@output, $indent . shift(@input));
2095 while (($index <= $#input) and
2096 ((length($output[$#output]) + length($input[$#input])) < $preferredLineLength)) {
2097 # does this one fit?
2098 if ((length($output[$#output]) + length($input[$index])) < $preferredLineLength) {
2099 if (defined($prefix)) {
2100 # don't stick the divider between the prefix and the first item
2103 $output[$#output] .= $divider;
2105 $output[$#output] .= splice(@input, $index, 1);
2114 # wordWrap routines which takes a list and wraps it. A less pretty version
2115 # of prettyPrinter, but it keeps the order.
2118 my ($preferredLineLength, $prefix, $indent, $divider, @input) = @_;
2119 unshift(@input, $prefix) if defined($prefix);
2120 $indent = '' unless defined($indent);
2123 push(@output, $indent . shift(@input));
2124 while (($#input >= 0) and
2125 ((length($output[$#output]) + length($input[0])) < $preferredLineLength)) {
2126 $output[$#output] .= $divider . shift(@input);
2135 $string =~ s/'/'/gos;
2136 $string =~ s/"/"/gos;
2137 $string =~ s/</</gos;
2138 $string =~ s/>/>/gos;
2139 $string =~ s/&/&/gos;
2140 $string =~ s/&\#(\d+);/convertASCIICode($1)/ges;
2144 sub convertASCIICode {
2146 return chr($code) if ($code > 31 and $code < 127);
2153 return &::days($then);
2156 # return the argument if it is a valid regular expression,
2157 # otherwise quotes the argument and returns that.
2158 sub sanitizeRegexp {
2161 if (defined($regexp)) {
2165 $self->debug("regexp |$regexp| returned error |$@|, quoting...") if $@;
2166 return $@ ? quotemeta($regexp) : $regexp;
2168 $self->debug("blank regexp, returning wildcard regexp //...");
2174 # MODULE INTERFACE (override these)
2176 # Initialise - Called when the module is loaded
2181 # Schedule - Called after bot is set up, to set up any scheduled tasks
2182 # use $self->schedule($event, $delay, $times, $data)
2183 # where $times is 1 for a single event, -1 for recurring events,
2184 # and a +ve number for an event that occurs that many times.
2190 # JoinedIRC - Called before joining any channels (but after module is setup)
2191 # this does not get called for dynamically loaded modules
2199 my ($event, $channel) = @_;
2200 if ($self->{'autojoin'}) {
2201 push(@{$self->{'channels'}}, $channel)
2202 unless ((scalar(grep $_ eq $channel, @{$self->{'channels'}})) or
2203 (scalar(grep $_ eq $channel, @{$self->{'channelsBlocked'}})));
2204 $self->saveConfig();
2208 # Called by the Admin module's Kicked and SpottedPart handlers
2211 my ($event, $channel) = @_;
2212 if ($self->{'autojoin'}) {
2213 my %channels = map { $_ => 1 } @{$self->{'channels'}};
2214 if ($channels{$channel}) {
2215 delete($channels{$channel});
2216 @{$self->{'channels'}} = keys %channels;
2217 $self->saveConfig();
2225 return scalar(grep $_ eq $event->{'channel'}, @{$self->{'channels'}});
2226 # XXX could be optimised - cache the list into a hash.
2232 return 0 if scalar(grep { $_ = $self->sanitizeRegexp($_); $event->{'user'} =~ /^$_$/ } @{$self->{'allowusers'}});
2233 return scalar(grep { $_ = $self->sanitizeRegexp($_); $event->{'user'} =~ /^$_$/ } @{$self->{'denyusers'}});
2236 # Baffled - Called for messages prefixed by the bot's nick which we don't understand
2239 my ($event, $message) = @_;
2243 # Told - Called for messages prefixed by the bot's nick
2246 my ($event, $message) = @_;
2250 # Noticed - Called for notice messages
2253 my ($event, $message) = @_;
2257 # Heard - Called for all messages
2260 my ($event, $message) = @_;
2264 # Felt - Called for all emotes containing bot's nick
2267 my ($event, $message) = @_;
2271 # -- #mozilla was here --
2272 # * bryner tries to imagine the need for NS_TWIPS_TO_MILES
2273 #<Ben_Goodger> bryner: yeah, that isn't even a metric unit. should
2274 # be NS_TWIPS_TO_KILOMETERS
2275 # <bryner> there's that too
2277 #<Ben_Goodger> really?
2280 # <bryner> for when we use mozilla for surveying and such
2283 # BTW. They aren't kidding. See:
2284 # http://lxr.mozilla.org/seamonkey/search?string=NS_TWIPS_TO_KILOMETERS
2286 # Saw - Called for all emotes
2289 my ($event, $message) = @_;
2293 # Invited - Called when bot is invited into another channel
2296 my ($event, $channel) = @_;
2300 # Kicked - Called when bot is kicked out of a channel
2303 my ($event, $channel) = @_;
2307 # ModeChange - Called when channel or bot has a mode flag changed
2310 my ($event, $what, $change, $who) = @_;
2314 # GotOpped - Called when bot is opped
2317 my ($event, $channel, $who) = @_;
2321 # GotDeopped - Called when bot is deopped
2324 my ($event, $channel, $who) = @_;
2328 # SpottedNickChange - Called when someone changes their nick
2329 # Remember that you cannot use directSay here, since $event
2330 # has the details of the old nick. And 'say' is useless
2331 # since the channel is the old userhost string... XXX
2332 sub SpottedNickChange {
2334 my ($event, $from, $to) = @_;
2338 # Authed - Called when someone authenticates with us.
2339 # Remember that you cannot use say here, since this
2340 # cannot actually be done in a channel...
2343 my ($event, $who) = @_;
2347 # SpottedTopicChange - Called when someone thinks someone else said something funny
2348 sub SpottedTopicChange {
2350 my ($event, $channel, $new) = @_;
2354 # SpottedJoin - Called when someone joins a channel
2357 my ($event, $channel, $who) = @_;
2361 # SpottedPart - Called when someone leaves a channel
2364 my ($event, $channel, $who) = @_;
2368 # SpottedKick - Called when someone leaves a channel forcibly
2371 my ($event, $channel, $who) = @_;
2375 # SpottedQuit - Called when someone leaves a server
2376 # can't use say or directSay: no channel involved, and
2377 # user has quit (obviously). XXX
2380 my ($event, $who, $why) = @_;
2384 # CTCPPing - Called when we receive a CTCP Ping.
2387 my ($event, $who, $what) = @_;
2391 # CTCPVersion - Called when we receive a CTCP Version.
2394 my ($event, $who, $what) = @_;
2398 # CTCPSource - Called when we receive a CTCP Source.
2401 my ($event, $who, $what) = @_;
2405 # SpottedOpping - Called when someone is opped
2408 my ($event, $channel, $who) = @_;
2412 # SpottedDeopping - Called when someone is... deopped, maybe?
2413 sub SpottedDeopping {
2415 my ($event, $channel, $who) = @_;
2419 # Scheduled - Called when a scheduled timer triggers
2422 my ($event, @data) = @_;
2423 if (ref($data[0]) eq 'CODE') {
2424 &{$data[0]}($event, @data);
2426 $self->debug('Unhandled scheduled event... :-/');
2430 # ChildCompleted - Called when a child process has quit
2431 sub ChildCompleted {
2433 my ($event, $type, $output, @data) = @_;
2434 if ($type eq 'URI') {
2435 my $uri = shift(@data);
2436 $self->GotURI($event, $uri, $output, @data);
2440 # DataAvailable - Called when a handle registered with
2441 # registerDataHandle has made data available
2444 my ($event, $handle, $data, $close) = @_;
2448 # GotURI - Called when a requested URI has been downloaded
2451 my ($event, $uri, $contents, @data) = @_;
2454 # Help - Called to fully explain the module (return hash of command/description pairs)
2455 # the string given for the '' key should be a module description
2462 # RegisterConfig - Called when initialised, should call registerVariables
2463 sub RegisterConfig {
2465 $self->registerVariables(
2466 # [ name, save?, settable?, value ]
2467 ['channels', 1, 1, []],
2468 ['channelsBlocked', 1, 1, []], # the channels in which this module will not autojoin regardless
2469 ['autojoin', 1, 1, 1],
2470 ['allowusers', 1, 1, []],
2471 ['denyusers', 1, 1, []],
2475 # Set - called to set a variable to a particular value.
2478 my ($event, $variable, $value) = @_;
2479 if ($self->{'_variables'}->{$variable}) {
2480 if ((not defined($self->{$variable})) or (not ref($self->{$variable}))) {
2481 $self->{$variable} = $value;
2482 } elsif (ref($self->{$variable}) eq 'SCALAR') {
2483 ${$self->{$variable}} = $value;
2484 } elsif (ref($self->{$variable}) eq 'ARRAY') {
2485 if ($value =~ /^([-+])(.*)$/so) {
2487 push(@{$self->{$variable}}, $2);
2489 # We don't want to change the reference!!!
2490 # Other variables might be pointing to there,
2491 # it is *those* vars that affect the app.
2492 my @oldvalue = @{$self->{$variable}};
2493 @{$self->{$variable}} = ();
2494 foreach (@oldvalue) {
2495 push(@{$self->{$variable}}, $_) unless ($2 eq $_);
2497 # XXX no feedback if nothing is done
2500 return 3; # not the right format dude!
2502 } elsif (ref($self->{$variable}) eq 'HASH') {
2503 if ($value =~ /^\+(.)(.*)\1(.*)$/so) {
2504 $self->{$variable}->{$2} = $3;
2505 return -2 if $1 =~ /[a-zA-Z]/so;
2506 } elsif ($value =~ /^\-(.*)$/so) {
2507 # XXX no feedback if nothing is done
2508 delete($self->{$variable}->{$1});
2510 return 4; # not the right format dude!
2513 return 1; # please to not be trying to set coderefs or arrayrefs or hashrefs or ...
2516 return 2; # please to not be trying to set variables I not understand!
2518 $self->saveConfig();
2522 # Get - called to get a particular variable
2525 my ($event, $variable) = @_;
2526 return $self->{$variable};
2529 # Log - Called for every event
2535 # Log - Called for every event
2541 ################################
2543 ################################
2545 package BotModules::Admin;
2547 @ISA = qw(BotModules);
2550 # Initialise - Called when the module is loaded
2553 $self->{'_fileModifiedTimes'} = {};
2554 $self->{'_static'} = 1;
2557 # RegisterConfig - Called when initialised, should call registerVariables
2558 sub RegisterConfig {
2560 $self->SUPER::RegisterConfig(@_);
2561 $self->registerVariables(
2562 # [ name, save?, settable?, value ]
2563 ['allowInviting', 1, 1, 1], # by default, anyone can invite a bot into their channel
2564 ['allowChannelAdmin', 1, 1, 0], # by default, one cannot admin from a channel
2565 ['sourceCodeCheckDelay', 1, 1, 20], # by default, wait 20 seconds between source code checks
2566 ['files', 1, 1, [$0, 'lib/Mails.pm', 'lib/Configuration.pm', 'lib/IO/SecurePipe.pm']], # files to check for source code changes
2567 ['channels', 0, 0, undef], # remove the 'channels' internal variable...
2568 ['autojoin', 0, 0, 0], # remove the 'autojoin' internal variable...
2569 ['errorMessagesMaxLines', 1, 1, 5], # by default, only have 5 lines in error messages, trim middle if more
2571 # now add in all the global variables...
2572 foreach (keys %configStructure) {
2573 $self->registerVariables([$configStructure{$_}[0], 0, 1, $configStructure{$_}[1]]) if (ref($configStructure{$_}[1]) =~ /^(?:SCALAR|ARRAY|HASH)$/go);
2577 # saveConfig - make sure we also save the main config variables...
2580 $self->SUPER::saveConfig(@_);
2581 &Configuration::Save($cfgfile, &::configStructure());
2584 # Set - called to set a variable to a particular value.
2587 my ($event, $variable, $value) = @_;
2588 # First let's special case some magic variables...
2589 if ($variable eq 'currentnick') {
2590 $self->setNick($event, $value);
2591 $self->say($event, "Attempted to change nick to '$value'.");
2593 } elsif ($variable eq 'nicks') {
2594 if ($value =~ /^([-+])(.*)$/so) {
2596 # check it isn't there already and is not ''
2599 $self->say($event, "The empty string is not a valid nick.");
2603 $thenick++ while (($thenick < @nicks) and ($value ne $nicks[$thenick]));
2604 if ($thenick < @nicks) {
2605 $self->say($event, "That nick (value) is already on the list of possible nicks.");
2609 if ($2 eq $nicks[$nick]) {
2610 $self->say($event, "You cannot remove the current nick ('$nicks[$nick]') from the list of allowed nicks... Change the 'currentnick' variable first!");
2615 } elsif ($variable eq 'umode') {
2616 $self->mode($event, $nicks[$nick], $value, '');
2617 $self->say($event, "Attempted to change current umode to '$value'.");
2619 return $self->SUPER::Set($event, $variable, $value);
2622 # Get - called to get a particular variable.
2625 my ($event, $variable) = @_;
2626 # First let's special case some magic variables...
2627 if ($variable eq 'currentnick') {
2628 return $event->{'nick'};
2629 } elsif ($variable eq 'users') {
2630 my @users = sort keys %users;
2633 # else, check for known global variables...
2634 my $configStructure = &::configStructure();
2635 if (defined($configStructure->{$variable})) {
2636 return $configStructure->{$variable};
2638 return $self->SUPER::Get($event, $variable);
2643 # Schedule - called when bot connects to a server, to install any schedulers
2644 # use $self->schedule($event, $delay, $times, $data)
2645 # where $times is 1 for a single event, -1 for recurring events,
2646 # and a +ve number for an event that occurs that many times.
2650 $self->schedule($event, \$self->{'sourceCodeCheckDelay'}, -1, {'action'=>'source'});
2651 $self->SUPER::Schedule($event);
2658 'auth' => 'Authenticate yourself. Append the word \'quiet\' after your password if you don\'t want confirmation. Syntax: auth <username> <password> [quiet]',
2659 'password' => 'Change your password: password <oldpassword> <newpassword> <newpassword>',
2660 'newuser' => 'Registers a new username and password (with no privileges). Syntax: newuser <username> <newpassword> <newpassword>',
2662 if ($self->isAdmin($event)) {
2663 $result->{''} = 'The administration module is used to perform tasks that fundamentally affect the bot.';
2664 $result->{'shutdown'} = 'Shuts the bot down completely.';
2665 $result->{'shutup'} = 'Clears the output queue (you actually have to say \'shutup please\' or nothing will happen).';
2666 $result->{'restart'} = 'Shuts the bot down completely then restarts it, so that any source changes take effect.';
2667 $result->{'cycle'} = 'Makes the bot disconnect from the server then try to reconnect.';
2668 $result->{'changepassword'} = 'Change a user\'s password: changepassword <user> <newpassword> <newpassword>',
2669 $result->{'vars'} = 'Manage variables: vars [<module> [<variable> [\'<value>\']]], say \'vars\' for more details.';
2670 $result->{'join'} = 'Makes the bot attempt to join a channel. The same effect can be achieved using /invite. Syntax: join <channel>';
2671 $result->{'part'} = 'Makes the bot leave a channel. The same effect can be achieved using /kick. Syntax: part <channel>';
2672 $result->{'load'} = 'Loads a module from disk, if it is not already loaded: load <module>';
2673 $result->{'unload'} = 'Unloads a module from memory: load <module>';
2674 $result->{'reload'} = 'Unloads and then loads a module: reload <module>';
2675 $result->{'bless'} = 'Sets the \'admin\' flag on a registered user. Syntax: bless <user>';
2676 $result->{'unbless'} = 'Resets the \'admin\' flag on a registered user. Syntax: unbless <user>';
2677 $result->{'deleteuser'} = 'Deletes a user from the bot. Syntax: deleteuser <username>',
2682 # Told - Called for messages prefixed by the bot's nick
2685 my ($event, $message) = @_;
2686 return $self->SUPER::Told(@_) unless $self->{allowChannelAdmin} or $event->{channel} eq '';
2687 if ($message =~ /^\s*auth\s+($variablepattern)\s+($variablepattern)(\s+quiet)?\s*$/osi) {
2688 if (not $event->{'channel'}) {
2689 if (defined($users{$1})) {
2690 if (&::checkPassword($2, $users{$1})) {
2691 $authenticatedUsers{$event->{'user'}} = $1;
2692 if (not defined($3)) {
2693 $self->directSay($event, "Hi $1!");
2695 &::do($event->{'bot'}, $event->{'_event'}, 'Authed'); # hack hack hack
2697 $self->directSay($event, "No...");
2700 $self->directSay($event, "You have not been added as a user yet. Try the \'newuser\' command (see \'help newuser\' for details).");
2703 } elsif ($message =~ /^\s*password\s+($variablepattern)\s+($variablepattern)\s+($variablepattern)\s*$/osi) {
2704 if (not $event->{'channel'}) {
2705 if ($authenticatedUsers{$event->{'user'}}) {
2707 $self->say($event, 'New passwords did not match. Try again.');
2708 } elsif (&::checkPassword($1, $users{$authenticatedUsers{$event->{'user'}}})) {
2709 $users{$authenticatedUsers{$event->{'user'}}} = &::newPassword($2);
2710 delete($authenticatedUsers{$event->{'user'}});
2711 $self->say($event, 'Password changed. Please reauthenticate.');
2712 $self->saveConfig();
2714 delete($authenticatedUsers{$event->{'user'}});
2715 $self->say($event, 'That is not your current password. Please reauthenticate.');
2719 } elsif ($message =~ /^\s*new\s*user\s+($variablepattern)\s+($variablepattern)\s+($variablepattern)\s*$/osi) {
2720 if (not $event->{'channel'}) {
2721 if (defined($users{$1})) {
2722 $self->say($event, 'That user already exists in my list, you can\'t add them again!');
2723 } elsif ( $2 ne $3 ) {
2724 $self->say($event, 'New passwords did not match. Try again.');
2726 $users{$1} = &::newPassword($2);
2728 $self->directSay($event, "New user '$1' added with password '$2' and no rights.");
2729 $self->saveConfig();
2731 $self->say($event, 'That is not a valid user name.');
2734 } elsif ($self->isAdmin($event)) {
2735 if ($message =~ /^\s*(?:shutdown,?\s+please)\s*[?!.]*\s*$/osi) {
2736 $self->say($event, 'But of course. Have a nice day!');
2737 my $reason = 'I was told to shutdown by '.$event->{'from'}.'. :-( ';
2738 # XXX should do something like &::do($event->{'bot'}, $event->{'_event'}, 'SpottedQuit'); # hack hack hack
2739 # ...but it should have the right channel/nick/reason info
2740 # XXX we don't unload the modules here?
2741 $event->{'bot'}->quit($reason);
2742 exit(0); # prevents any other events happening...
2743 } elsif ($message =~ /^\s*shutdown/osi) {
2744 $self->say($event, 'If you really want me to shutdown, use the magic word.');
2745 $self->schedule($event, 7, 1, 'i.e., please.');
2746 } elsif ($message =~ /^\s*(?:restart,?\s+please)\s*[?!.]*\s*$/osi) {
2747 $self->Restart($event, "I was told to restart by $event->{'from'} -- brb");
2748 } elsif ($message =~ /^\s*restart/osi) {
2749 $self->say($event, 'If you really want me to restart, use the magic word.');
2750 $self->schedule($event, 7, 1, 'i.e., please.');
2751 } elsif ($message =~ /^\s*delete\s*user\s+($variablepattern)\s*$/osi) {
2752 if (not defined($users{$1})) {
2753 $self->say($event, "I don't know of a user called '$1', sorry.");
2755 # check user is not last admin
2756 my $doomedUser = $1;
2758 if (($userFlags{$doomedUser} & 1) == 1) {
2759 # deleting an admin. Count how many are left.
2761 foreach my $user (keys %users) {
2762 ++$count if ($user ne $doomedUser and
2763 ($userFlags{$user} & 1) == 1);
2766 # not deleting an admin. We know there is an admin in there, it's
2767 # the user doing the deleting. So we're safe.
2771 $self->deleteUser($doomedUser);
2772 $self->say($event, "User '$doomedUser' deleted.");
2774 $self->say($event, "Can't delete user '$doomedUser', that would leave you with no admins!");
2777 } elsif ($message =~ /^\s*change\s*password\s+($variablepattern)\s+($variablepattern)\s+($variablepattern)\s*$/osi) {
2778 if (not defined($users{$1})) {
2779 $self->say($event, "I don't know of a user called '$1', sorry.");
2780 } elsif ($2 ne $3) {
2781 $self->say($event, 'New passwords did not match. Try again.');
2783 $users{$1} = &::newPassword($2);
2785 foreach my $user (keys %authenticatedUsers) {
2786 if ($authenticatedUsers{$user} eq $1) {
2787 delete($authenticatedUsers{$user});
2792 $self->say($event, "Password changed for user '$1'. They must reauthenticate.");
2794 $self->say($event, "Password changed for user '$1'.");
2796 $self->saveConfig();
2798 } elsif ($message =~ /^\s*(?:shut\s*up,?\s+please)\s*[?!.]*\s*$/osi) {
2799 my $lost = @msgqueue;
2802 $self->say($event, "Ok, threw away $lost messages.");
2804 $self->say($event, 'But I wasn\'t saying anything!');
2806 } elsif ($message =~ /^\s*cycle(?:\s+please)?\s*[?!.]*\s*$/osi) {
2807 my $reason = 'I was told to cycle by '.$event->{'from'}.'. BRB!';
2808 # XXX should do something like &::do($event->{'bot'}, $event->{'_event'}, 'SpottedQuit'); # hack hack hack
2809 # ...but it should have the right channel/nick/reason info
2810 # XXX we don't unload the modules here?
2811 $event->{'bot'}->quit($reason);
2812 &Configuration::Get($cfgfile, &::configStructure());
2813 } elsif ($message =~ /^\s*join\s+([&#+][^\s]+)(?:\s+please)?\s*[?!.]*\s*$/osi) {
2814 $self->Invited($event, $1);
2815 } elsif ($message =~ /^\s*part\s+([&#+][^\s]+)(?:\s+please)?\s*[?!.]*\s*$/osi) {
2816 $event->{'bot'}->part("$1 :I was told to leave by $event->{'from'}. :-(");
2817 } elsif ($message =~ /^\s*bless\s+('?)($variablepattern)\1\s*$/osi) {
2818 if (defined($users{$2})) {
2819 $userFlags{$2} = $userFlags{$2} || 1;
2820 $self->saveConfig();
2821 $self->say($event, "Ok, $2 is now an admin.");
2823 $self->say($event, 'I don\'t know that user. Try the \'newuser\' command (see \'help newuser\' for details).');
2825 } elsif ($message =~ /^\s*unbless\s+('?)($variablepattern)\1\s*$/osi) {
2826 if (defined($users{$2})) {
2827 $userFlags{$2} = $userFlags{$2} &~ 1;
2828 $self->saveConfig();
2829 $self->say($event, "Ok, $2 is now a mundane luser.");
2831 $self->say($event, 'I don\'t know that user. Check your spelling!');
2833 } elsif ($message =~ /^\s*load\s+('?)($variablepattern)\1\s*$/osi) {
2834 $self->LoadModule($event, $2, 1);
2835 } elsif ($message =~ /^\s*reload\s+('?)($variablepattern)\1\s*$/osi) {
2836 $self->ReloadModule($event, $2, 1);
2837 } elsif ($message =~ /^\s*unload\s+('?)($variablepattern)\1\s*$/osi) {
2838 $self->UnloadModule($event, $2, 1);
2839 } elsif ($message =~ /^\s*vars(?:\s+($variablepattern)(?:\s+($variablepattern)(?:\s+'(.*)')?)?|(.*))?\s*$/osi) {
2840 $self->Vars($event, $1, $2, $3, $4);
2842 return $self->SUPER::Told(@_);
2845 return $self->SUPER::Told(@_);
2847 return 0; # if made it here then we did it!
2852 my ($event, $type) = @_;
2853 if ((ref($type) eq 'HASH') and ($type->{'action'} eq 'source')) {
2854 $self->CheckSource($event);
2855 } elsif (ref($type)) {
2856 $self->SUPER::Scheduled(@_);
2858 $self->directSay($event, $type);
2862 # remove any (other) temporary administrators when an admin authenticates
2865 my ($event, $who) = @_;
2866 if ($self->isAdmin($event)) {
2867 foreach (keys %userFlags) {
2868 if ((($userFlags{$_} & 2) == 2) and ($authenticatedUsers{$event->{'user'}} ne $_)) {
2869 $self->deleteUser($_);
2870 $self->directSay($event, "Temporary administrator '$_' removed from user list.");
2874 return $self->SUPER::Authed(@_); # this should not stop anything else happening
2877 # SpottedQuit - Called when someone leaves a server
2880 my ($event, $who, $why) = @_;
2881 delete($authenticatedUsers{$event->{'user'}});
2882 # XXX this doesn't deal with a user who has authenticated twice.
2883 return $self->SUPER::SpottedQuit(@_);
2889 foreach my $file (@{$self->{'files'}}) {
2890 my $lastModifiedTime = $self->{'_fileModifiedTimes'}->{$file};
2891 my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks)
2893 $self->{'_fileModifiedTimes'}->{$file} = $mtime;
2894 if (defined($lastModifiedTime) and ($mtime > $lastModifiedTime)) {
2895 $self->debug("Noticed that source code of $file had changed");
2896 # compile new bot using perl -cwT XXX
2897 if (1) { # XXX replace 1 with "did compile succeed" test
2898 $self->Restart($event, 'someone seems to have changed my source code. brb, unless I get a compile error!');
2900 # tellAdmin that it did not compile XXX
2901 # debug that it did not compile
2906 foreach my $module (@modules) {
2907 if ($module->{'_filename'}) {
2908 my $lastModifiedTime = $module->{'_fileModificationTime'};
2909 my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks)
2910 = stat($module->{'_filename'});
2911 $module->{'_fileModificationTime'} = $mtime;
2912 if (defined($lastModifiedTime) and ($mtime > $lastModifiedTime)) {
2913 push(@updatedModules, $module->{'_name'});
2917 foreach my $module (@updatedModules) {
2918 $self->ReloadModule($event, $module, 0);
2924 my ($event, $reason) = @_;
2925 # XXX should do something like &::do($event->{'bot'}, $event->{'_event'}, 'SpottedQuit'); # hack hack hack
2926 # ...but it should have the right channel/nick/reason info
2927 # ...and it is broken if called from CheckSource, which is a
2928 # scheduled event handler, since $event is then a very basic
2930 # XXX we don't unload modules here?
2931 $event->{'bot'}->quit($reason);
2932 # Note that `exec' will not call our `END' blocks, nor will it
2933 # call any `DESTROY' methods in our objects. So we fork a child to
2937 if (defined($child)) {
2939 # we are the parent process who is
2940 # about to exec($0), so wait for
2941 # child to shutdown.
2942 $self->debug("spawned $child to handle shutdown...");
2945 # we are the child process who is
2946 # in charge of shutting down cleanly.
2947 $self->debug("initiating shutdown for parent process $parent...");
2951 $self->debug("failed to fork: $!");
2953 $self->debug("About to defer to a new $0 process...");
2954 # we have done our best to shutdown, so go for it!
2956 $0 =~ m/^(.*)$/os; # untaint $0 so that we can call it below (as $1)
2958 exec { $1 } ($1, '--assume-chrooted', $cfgfile);
2960 exec { $1 } ($1, $cfgfile);
2962 # I am told (by some nice people in #perl on Efnet) that our
2963 # memory is all cleared up for us. So don't worry that even
2964 # though we don't call DESTROY in _this_ instance, we leave
2967 $self->debug("That failed!!! Bailing out to prevent all hell from breaking loose! $@ :-|");
2968 exit(1); # we never get here unless exec fails
2971 # handles the 'vars' command
2974 my ($event, $modulename, $variable, $value, $nonsense) = @_;
2975 if (defined($modulename)) {
2976 my $module = $self->getModule($modulename);
2977 if (defined($module)) {
2978 if (defined($variable)) {
2979 if (defined($value)) {
2980 my $result = $module->Set($event, $variable, $value);
2981 if ((not defined($result)) or ($result == 0)) {
2982 $self->say($event, "Variable '$variable' in module '$modulename' has changed.");
2983 } elsif ($result == 1) {
2984 $self->say($event, "Variable '$variable' is of type ".ref($module->{$variable}).' and I do not know how to set that kind of variable!');
2985 } elsif ($result == 2) { # we don't know that variable!
2986 if ($module->{$variable}) { # well we do, but only to read
2987 $self->say($event, "Variable '$variable' in module '$modulename' is read-only, sorry.");
2988 } else { # not known
2989 $self->say($event, "Module '$modulename' does not have a variable '$variable' as far as I can tell.");
2991 } elsif ($result == 3) {
2992 $self->say($event, "Variable '$variable' is a list. To add to a list, please use the '+' symbol before the value (vars <module> <variable> '+<value>'). To remove from a list, use the '-' symbol (vars <module> <variable> '-<value>').");
2993 } elsif ($result == 4) {
2994 $self->say($event, "Variable '$variable' is a hash. To add to a hash, please use the '+' symbol before the '|key|value' pair (vars <module> <variable> '+|<key>|<value>'). The separator symbol ('|' in this example) could be anything. To remove from a list, use the '-' symbol (vars <module> <variable> '-<key>').");
2995 } elsif ($result == -1) {
2996 # already reported success
2997 } elsif ($result == -2) {
2998 $self->say($event, "Variable '$variable' in module '$modulename' has changed, but may not be what you expect since it appears to me that you used a letter to delimit the sections. I hope that is what you meant to do...");
2999 } elsif ($result > 0) { # negative = success
3000 $self->say($event, "Variable '$variable' in module '$modulename' could not be set for some reason unknown to me.");
3002 } else { # else give variable's current value
3003 $value = $module->Get($event, $variable);
3004 if (defined($value)) {
3005 my $type = ref($value);
3006 if ($type eq 'SCALAR') {
3007 $self->say($event, "Variable '$variable' in module '$modulename' is set to: '$$value'");
3008 } elsif ($type eq 'ARRAY') {
3009 # XXX need a 'maximum number of items' feature to prevent flooding ourselves to pieces (or is shutup please enough?)
3011 local $" = '\', \'';
3012 $self->say($event, "Variable '$variable' in module '$modulename' is a list with the following values: '@$value'");
3014 $self->say($event, "Variable '$variable' in module '$modulename' is an empty list.");
3016 } elsif ($type eq 'HASH') {
3017 # XXX need a 'maximum number of items' feature to prevent flooding ourselves to pieces (or is shutup please enough?)
3018 $self->say($event, "Variable '$variable' in module '$modulename' is a hash with the following values:");
3019 foreach (sort keys %$value) {
3020 $self->say($event, " '$_' => '".($value->{$_}).'\' ');
3022 $self->say($event, "End of dump of variable '$variable'.");
3024 $self->say($event, "Variable '$variable' in module '$modulename' is set to: '$value'");
3026 } else { # we don't know that variable
3027 if ($module->{'_variables'}->{$variable}) { # well we do, but only to write
3028 $self->say($event, "Variable '$variable' in module '$modulename' is write-only, sorry.");
3029 } else { # not known
3030 $self->say($event, "Module '$modulename' does not have a variable '$variable' as far as I can tell.");
3034 } else { # else list variables
3036 # then enumerate its variables
3037 foreach my $variable (sort keys %{$module->{'_variables'}}) {
3038 push(@variables, $variable) if $module->{'_variables'}->{$variable};
3042 local $" = '\', \'';
3043 $self->say($event, "Module '$modulename' has the following published variables: '@variables'");
3045 $self->say($event, "Module '$modulename' has no settable variables.");
3048 } else { # complain no module
3049 $self->say($event, "I didn't recognise that module name ('$modulename'). Try just 'vars' on its own for help.");
3051 } elsif ($nonsense) {
3052 $self->say($event, 'I didn\'t quite understand that. Try just \'vars\' on its own for help.');
3053 $self->say($event, 'If you are trying to set a variable, don\'t forget the quotes around the value!');
3054 } else { # else give help
3055 $self->say($event, 'The \'vars\' command gives you an interface to the module variables in the bot.');
3056 $self->say($event, 'To list the variables in a module: vars <module>');
3057 $self->say($event, 'To get the value of a variable: vars <module> <variable>');
3058 $self->say($event, 'To set the value of a variable: vars <module> <variable> \'<value>\'');
3059 $self->say($event, 'Note the quotes around the value. They are required. If the value contains quotes itself, that is fine.');
3063 # This is also called when we are messaged a 'join' command
3066 my ($event, $channelName) = @_;
3067 # $channelName is the name as requested and as should be /joined.
3068 # This is important so that case is kept in the list of channels
3069 # on the server should the bot join first.
3070 my $channel = lc($channelName);
3071 if (grep $_ eq $channel, @channels) {
3072 $self->directSay($event, "I thought I was already *in* channel $channel! Oh well.");
3074 if ($self->isAdmin($event) || $self->{'allowInviting'}) {
3075 $self->debug("Joining $channel, since I was invited.");
3076 if (defined($channelKeys{$channel})) {
3077 $event->{'bot'}->join($channel, $channelKeys{$channel});
3079 $event->{'bot'}->join($channel);
3082 $self->debug($event->{'from'}." asked me to join $channel, but I refused.");
3083 $self->directSay($event, "Please contact one of my administrators if you want me to join $channel.");
3084 $self->tellAdmin($event, "Excuse me, but ".$event->{'from'}." asked me to join $channel. I thought you should know.");
3086 return $self->SUPER::Invited($event, $channel);
3091 my ($event, $channel) = @_;
3092 $self->debug("kicked from $channel by ".$event->{'from'});
3093 $self->debug('about to autopart modules...');
3094 foreach (@modules) {
3095 $_->PartedChannel($event, $channel);
3097 return $self->SUPER::Kicked($event, $channel);
3102 my ($event, $channel, $who) = @_;
3103 if (lc $who eq lc $event->{'nick'}) {
3104 $self->debug("parted $channel");
3105 $self->debug('about to autopart modules...');
3106 foreach (@modules) {
3107 $_->PartedChannel($event, $channel);
3110 return $self->SUPER::SpottedPart($event, $channel, $who);
3115 my ($event, $channel) = @_;
3116 $channel = lc($channel);
3117 my %channels = map { $_ => 1 } @channels;
3118 delete($channels{$channel});
3119 @channels = keys %channels;
3120 &Configuration::Save($cfgfile, &::configStructure(\@channels));
3121 return $self->SUPER::PartedChannel($event, $channel);
3126 my ($event, $name, $requested) = @_;
3127 my $newmodule = &::LoadModule($name);
3128 if (ref($newmodule)) {
3130 $newmodule->{'channels'} = [@channels];
3131 &Configuration::Get($cfgfile, $newmodule->configStructure());
3133 $newmodule->Schedule($event);
3136 $self->debug("Warning: An error occured while loading the module:\n$@");
3138 $self->say($event, "Warning: an error occured while loading module '$name'. Ignored.");
3141 $newmodule->saveConfig();
3142 $self->debug("Successfully loaded module '$name'.");
3144 $self->say($event, "Loaded module '$name'.");
3147 if ($requested) { # it failed, $newmodule contains error message
3148 my @errors = split(/[\n\r]/os, $newmodule);
3149 if (scalar(@errors) > $self->{'errorMessagesMaxLines'}) {
3150 # remove lines from the middle if the log is too long
3151 @errors = (@errors[0..int($self->{'errorMessagesMaxLines'} / 2)-1], '...', @errors[-(int($self->{'errorMessagesMaxLines'} / 2))..-1]);
3154 $self->say($event, "@errors");
3156 $self->debug($newmodule);
3162 my ($event, $name, $requested) = @_;
3163 my $result = &::UnloadModule($name);
3164 if (defined($result)) { # failed
3166 $self->say($event, $result);
3168 $self->debug($result);
3172 $self->say($event, "Unloaded module '$name'.");
3174 $self->debug("Successfully unloaded module '$name'.");
3181 # XXX there used to be a memory leak around this code. It seems to be fixed
3182 # now. However if your bot process suddenly balloons to 90M+, here would be a good
3183 # place to start looking. Of course if that happens and you never reloaded modules
3184 # then it is also a good time to remove this comment... ;-)
3185 $self->UnloadModule(@_);
3186 $self->LoadModule(@_);
3192 delete($userFlags{$who});
3193 delete($users{$who});
3194 # if they authenticated, remove the entry to prevent dangling links
3195 foreach my $user (keys %authenticatedUsers) {
3196 if ($authenticatedUsers{$user} eq $who) {
3197 delete($authenticatedUsers{$user});
3200 $self->saveConfig();
3204 ################################
3205 # Startup (aka main) #
3206 ################################
3210 # -- #mozilla was here --
3211 # <zero> is the bug with zilla hanging on startup on every
3212 # platform fixed in today's nightlies?
3216 # <leaf> we're shipping with it.
3217 # <andreww> helps hide our other bugs
3219 # Do this at the very end, so we can intersperse "my" initializations outside
3220 # of routines above and be assured that they will run.
3222 &debug('starting up command loop...');
3224 END { &debug('perl is shutting down...'); }
3228 # -- #mozilla was here --
3229 # <alecf> Maybe I'll file a bug about netcenter and that will
3230 # get some attention
3231 # <alecf> "Browser won't render home.netscape.com.. because it
3233 # <andreww> alecf how about "cant view banner ads - wont start up"
3234 # <alecf> even better
3235 # <pinkerton> all bugs are dependent on this one!
3237 # *** Disconnected from irc.mozilla.org