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.


In reply to Irssi as IRC bot framework by Juerd

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.