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:

~/bot/config ~/bot/data/ ~/bot/scripts/ bot.pl autorun/bot.pl (symlink) ~/bot/modules/ DBIx/Simple.pm Karma.pm ... ~/bot/commands/ =init ping rot13 ...
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.pl

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';
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.

=init

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'); }
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>

And here's one of the commands (a very simple one), rot13:

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 $_;
I use "return say" as some sort of die(). The return value is not used, so I like to cheat this way.

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.)

- Yes, I reinvent wheels.
- Spam: Visit eurotraQ.

Replies are listed 'Best First'.
Re: Irssi as IRC bot framework
by Beatnik (Parson) on Jul 10, 2002 at 18:43 UTC
    XChat allows Perl scripting too (altho the docs on it are really old). I released a few scripts for it and currently use a DBI powered one for info stuff, line counting, karma etc.

    Greetz
    Beatnik
    ...Perl is like sex: if you're doing it wrong, there's no fun to it.

      XChat allows Perl scripting too

      I have used xchat some years ago. Although it's the nicest GUI client I've ever seen, I like irssi better. And besides, it's quite hard to keep a graphical client running remotely if your own connection is unstable. It'd probably require Xvnc, but X is too heavy for this purpose (and this particular machine...).

      My irssi based bot uses DBI (for pulling user information from Cu2, a reasonably large Dutch website) too, and keeps track of karma (heh, could we live without?). It also acts as a calculator and provides train schedule information. The old version was a bunch of elsifs, now I can extend it very easily.

      - Yes, I reinvent wheels.
      - Spam: Visit eurotraQ.
      

        Actually XChat also has a text version (console based that is) so you dont really need VNC. Perl scripts work equally well in that version since it's just a text based front-end instead of a X based one.

        Greetz
        Beatnik
        ...Perl is like sex: if you're doing it wrong, there's no fun to it.