I just finished a re-write of an information bot I wrote some time ago. The original was way over two thousand lines, of which over 1800 in a single file. It used POE::Component::IRC, but no strict. It did use warnings, but I never bothered making it -w free, so at startup it gives about 40 warnings, and dozens of warnings per hour when running.
For my re-write, I wanted a prefab bot framework. Just a nice package that would keep track of channel user lists, modes, server connections, etcetera. So I could focus on the actual information part.
Net::IRC is out of the question, because it is old and not very flexible. POE::Component::IRC was nice, but I don't like POE programming. I searched for a bot framework that would use PCI internally, but provide me with a cleaner interface. POE::Component::IRC::Object was the closest I could find, but it would still require me to write POE-related code, and it does not keep track of users and modes.
I did some more searching, to no avail. When using irssi, I realised how powerful its scripting language is. I wrote a little irssi script that would load other pieces of code in an Apache::Registry-like way, reloading when the mtime changes. Then, I made three very simple subs to simplify common tasks: say(), reply() and match().
My directory structure is as follows:
I have the modules there, because I can't install modules system wide on that box. The commands directory is the most important one. It contains a file for every command (e.g. the 'rot13' script is called when some IRC user does '!rot13'), and files containing \W characters can never be loaded by the user, because only /!\w+/ are valid commands. The '=' breaks the \w+. I use =init for startup stuff, and some others (=mask, =db, etc) as if they were modules (so they get re-loaded when they change).~/bot/config ~/bot/data/ ~/bot/scripts/ bot.pl autorun/bot.pl (symlink) ~/bot/modules/ DBIx/Simple.pm Karma.pm ... ~/bot/commands/ =init ping rot13 ...
bot.pl
As you can see, the command scripts are wrapped in sub references, and get a hash of event related information in $_[0], which is put in %_ for convenience. (No ugly @_[KERNEL, ARG1..ARG3]! yay!) I use %global for all persistent information.use Irssi; use lib 'module'; use strict; our %global; sub clean_eval { return eval shift; } my $char = '!'; my $charre = quotemeta $char; my $commands = 'commands'; sub reply { $_{server}->command("msg $_{target} $_{nick}: $_") for @_ +} sub say { $_{server}->command("msg $_{target} $_") for @_ } sub match { $_{server}->masks_match("@_", $_{nick}, $_{address}) } sub load { my ($command) = @_; my $mtime = (stat "$commands/$command")[9]; if ($mtime) { if ($mtime > $global{filecache}{$command}{mtime}) { local $/ = undef; open my $fh, "$commands/$command"; # no die $global{filecache}{$command} = { mtime => $mtime, code => clean_eval join "\n", 'sub {', 'local %_ = %{ +shift };', "#line 1 $command", readline($fh), '}' }; Irssi::print $@ ? $@ : "Loaded $command"; } return $global{filecache}{$command}{code}; } Irssi::print "Could not load $command"; delete $global{filecache}{$command} if exists $global{filecache}{$ +command}; return undef; } sub message { my ($server, $msg, $nick, $address, $target) = @_; return unless $msg =~ s/^$charre(\w+)(?:$| )//; my $command = $1; my $code = load($command); return if not ref $code; Irssi::print "$command by $nick${\ ($target ? qq/ in $target/ : '' +) } on " . "$server->{address}"; $_[1] = "\cO" . $_[1]; Irssi::signal_emit($target ? 'message public' : 'message private', + @_); $target ||= $nick; eval { $code->( { command => "$char$command", server => $server, msg => $msg, nick => $nick, address => $address, target => $target } ); }; Irssi::print $@ if $@; Irssi::signal_stop; } Irssi::signal_add_last 'message public' => \&message; Irssi::signal_add_last 'message private' => \&message; load '=init';
=init
The BEGIN block is to execute directly so (load '=init')->(); is not necessary. The ugly symbolic reference is used for a reason: if I'd use \&_sig_karma, the signal would still call the OLD sub if _sig_karma were redefined. </code>use Karma; sub _sig_karma { # Contents of this sub are irrelevant } BEGIN { unless ($global{done_init}) { no strict 'refs'; Irssi::signal_add 'message public' => sub { goto &{'_sig_karma +'} }; $global{done_init} = 1; } $global{karma} = Karma->new('data/karmadb'); }
And here's one of the commands (a very simple one), rot13:
I use "return say" as some sort of die(). The return value is not used, so I like to cheat this way.return say "Usage: $_{command} [--double] text" if $_{msg} eq '' or $_{msg} eq '--help'; local $_ = $_{msg}; tr/A-Za-z/N-ZA-Mn-za-m/ if s/^--double\s*//; tr/A-Za-z/N-ZA-Mn-za-m/; tr/\0-\x1F//d; reply $_;
Who would have thought te ideal (I think it is) IRC bot framework for Perl would not be written in Perl, but be a full screen IRC client? (Some people don't like that. I do: debugging is very easy if you have scrollbuffers.)
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: Irssi as IRC bot framework
by Beatnik (Parson) on Jul 10, 2002 at 18:43 UTC | |
by Juerd (Abbot) on Jul 10, 2002 at 18:59 UTC | |
by Beatnik (Parson) on Jul 10, 2002 at 22:17 UTC |