<?xml version="1.0" encoding="windows-1252"?>
<node id="135642" title="TriviaHam - Quiz Irc Bot" created="2002-01-02 09:35:25" updated="2005-08-05 23:17:56">
<type id="1042">
CUFP</type>
<author id="114167">
giulienk</author>
<data>
<field name="doctext">
A while ago i saw someone in an irc channel making quizzes to people using a Mirc script. So i thought a Perl Bot could do the same job, better. I grabbed [kobe://POE::Component::IRC] and started coding something: follows what i came up with.
&lt;READMORE&gt;
&lt;CODE&gt;
#!/usr/bin/perl -w

#TriviaHam IRC Bot - Helps quizzing in an IRC Channel
#Copyright 2001-2002 Giulio Motta - http://www.giuliomotta.com

#This program is free software; you can redistribute it and/or
#modify it under the terms of the GNU General Public License
#http://www.gnu.org/copyleft/gpl.html
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#GNU General Public License for more details.

use strict;
use POE;
use POE::Component::IRC;
use Getopt::Std;

my %args;
getopt('spnucioP', \%args);

my $nick = $args{n} || 'TriviaHam';
my $server = $args{s} || 'irc.slashnet.org';
my $port = $args{p} || 6667;
my $username = $args{u} || 'trivia';
my $ircname = $args{i} || "I'm TriviaHam Master";
my $channel = "#" . $args{c} if $args{c};
my $passwd = $args{P} || 'p455W0Rd';
my $owner_nick = $args{o};
my ($q_num, $owner, $question, $answer, $quiztime,  %chart);

sub _start {
    my ($kernel) = $_[KERNEL];

    $kernel-&gt;alias_set( 'smileyninja' );
    $kernel-&gt;post( 'triviabot', 'register', 'all');
    $kernel-&gt;post( 'triviabot', 'connect', { Debug    =&gt; 1,
                     Nick     =&gt; $nick,
                     Server   =&gt; $server,
                     Port     =&gt; $port,
                     Username =&gt; $username,
                     Ircname  =&gt; $ircname, }
           );
}

sub irc_001 {
    my ($kernel) = $_[KERNEL];

    $kernel-&gt;post( 'triviabot', 'mode', $nick, '+i' );
    $kernel-&gt;post( 'triviabot', 'join', $channel ) if $channel;
    if ($owner_nick) { 
        #this is useless cause i don't know how to
        #handle the 'whois' i got back
        $kernel-&gt;post( 'triviabot', 'whois', $owner_nick);
        #here i should set $short_whois = $owner
    }
}

sub irc_disconnected {
    my ($server) = $_[ARG0];
    print "Lost connection to server $server.\n";
}

sub irc_error {
    my $err = $_[ARG0];
    print "Server error occurred! $err\n";
}

sub irc_socketerr {
    my $err = $_[ARG0];
    print "Couldn't connect to server: $err\n";
}

sub _stop {
    my ($kernel) = $_[KERNEL];

    print "Control session stopped.\n";
    $kernel-&gt;post( 'triviabot', 'quit', 'Neenios on ice!' );
    $kernel-&gt;alias_remove( 'smileyninja' );
}

sub irc_public {
    if ($question) {
        my ($kernel, $who, $chan, $msg) = @_[KERNEL, ARG0 .. ARG2];
        #i use $answer as a regexp now. still this needs
        #better handling cause not everybody knows regexp
        if ($msg =~ /$answer/i) {
            my $nick = get_nick($who);
            $chart{$nick}{p}++;
            $quiztime = time - $quiztime;
            $chart{$nick}{t} += $quiztime;
            my ($secs, $mins)  = localtime $quiztime;
            $kernel-&gt;post('triviabot', 'privmsg', $channel,
                sprintf(
"\cC4Congratulations \cB%s\cB! The answer is \c_\cB%s\cB\c_. (%02d:%02d)", 
                $nick, $answer, $mins, $secs)
            );
            $kernel-&gt;post('triviabot', 'privmsg', $channel,
                "Now \cB$nick\cB has \cB$chart{$nick}{p}\cB point" .
                 ($chart{$nick}{p}&lt;=1?'':'s') . ".");
            ($question, $answer, $quiztime) = undef;
        } 
    }
}

sub irc_msg {
    my ($kernel, $who, $recipients, $msg) = @_[KERNEL, ARG0 .. ARG2];
    my ($command, $rest) = $msg =~ /^\s*(\w+)\s*(.*)/;
    if ($who eq $owner) {
        SWITCH: for (lc $command) {
            ($_ eq 'abort')   &amp;&amp; do{ abort ($kernel); last};
            ($_ eq 'channel') &amp;&amp; do{ channel ($kernel, $rest); last};
            ($_ eq 'cheat')   &amp;&amp; do{ cheat ($kernel, $rest); last};
            ($_ eq 'help')    &amp;&amp; do{ help ($kernel); last};
            ($_ eq 'new')     &amp;&amp; do{ new_question ($kernel, $rest); last};
            ($_ eq 'reset')   &amp;&amp; do{ _reset ($kernel); last};
            ($_ eq 'status')  &amp;&amp; do{ show_chart ($kernel, $rest); last};
            ($_ eq 'quit')    &amp;&amp; do{ quit ($kernel); last};
        }
    } elsif ($command eq 'owner') {
        set_owner ($kernel, $rest, $who);
    }
}

sub get_nick {
    $_ = shift;
    /^(.*)!.*$/ or die "Weird-ass who: $_";
    return $1;
}

sub set_owner {
    my ($kernel, $rest, $who) = @_;
    if ($rest eq $passwd) {
        $owner = $who;
        $kernel-&gt;post('triviabot', 'privmsg', get_nick($who), 
            "You've been successfully authenticated! ".
            'Type "help" for commands.');
    }
}

sub abort {
    my $kernel = shift;
    ($question, $answer, $quiztime) = undef;
    $kernel-&gt;post('triviabot', 'privmsg', $channel,
        "\cBQuestion number $q_num went unanswered." );
}

sub channel {
    my $kernel;
    ($kernel, $_) = @_;
    s/^([^#])/#$1/;
    if ($channel) {
        $kernel-&gt;post('triviabot', 'part', $channel);
        undef $channel;
    }
    unless ( $kernel-&gt;post('triviabot', 'join', $_) ) {
        # this should be error checking but it won't work
        # cause i can't get an error code back
        $kernel-&gt;post('triviabot', 'privmsg', get_nick($owner),
                "Cannot join $_ !" );
        return;
    }
    $channel = $_;
}

sub cheat {
    my $kernel;
    ($kernel, $_) = @_;
    if (my ($nick, $modifier) = /^\s*(.+)\s+([+-]?\d+)\s*$/) {
        $chart{$nick}{p} += $modifier; 
        $kernel-&gt;post('triviabot', 'privmsg', get_nick($owner),
            "Score for $nick has changed by $modifier. " .
            "Now is $chart{$nick}{p}." );
    } else {
        $kernel-&gt;post('triviabot', 'privmsg', get_nick($owner),
            "Cannot cheat: usage 'cheat &lt;nick&gt; &lt;modifier&gt;'" );
    }
}

sub help {
    my $kernel = shift;
    my $help = &lt;&lt;'EOH';
Usage of TriviaHam
owner &lt;password&gt;                    # authenticates yourself
new \q&lt;question&gt;\a&lt;answer&gt;          # new question
status [param]                      # display current standings
         #  no param               =&gt; returns first 3 positions
         #  param = 0              =&gt; returns all the positions
         #  param = &lt;other_number&gt; =&gt; returns that many positions
abort                               # abort current question
cheat &lt;nick&gt; &lt;modifier&gt;             # changes nick score by modifier
channel &lt;new_channel&gt;               # moves bot to another channel
reset                               # reset everything
quit                                # quit the bot
help                                # this help
EOH
    for my $line (split "\n", $help) {
        $kernel-&gt;post('triviabot', 'privmsg', get_nick($owner), $line );
         select(undef, undef, undef, 0.25);
    }
}

sub _reset {
    my $kernel = shift;
    ($q_num, %chart, $question, $answer) = undef;
    $kernel-&gt;post('triviabot', 'privmsg', get_nick($owner),
        "Quiz reset!" );
}

sub new_question {
    my $kernel;
    ($kernel, $_) = @_;
    if (($question, $answer) = /^\\q\s*(.+?)\s*\\a\s*(.+?)\s*$/) {
        $q_num++;
        $kernel-&gt;post('triviabot', 'privmsg', $channel,
                "\cBQuestion Number\cC4 $q_num\cC0,1: $question" );
        $quiztime = time;
    } else {
        $kernel-&gt;post('triviabot', 'privmsg', get_nick($owner),
                "Cannot parse new question '$_'" );
    }
}

sub show_chart {
    my ($kernel, $how_many) = @_;
    $how_many =~ s/^.*?(\d+).*?$/$1/;
    $how_many = keys %chart if ($how_many eq '0');
    $how_many ||= 3;
    $how_many--;
    my ($j, $min_color, $max_color) = (0, 6, 15);
    my $loop = $max_color - $min_color;
    my $to_color = $min_color - 1;
    $kernel-&gt;post('triviabot', 'privmsg', $channel,
        "\cVTrivial Standings after \cB$q_num\cB question" . ($q_num&lt;=1?'':'s') . 
        ". QuizMaster is \cB" . get_nick($owner));
    my @sorted_comps = sort 
    {$chart{$b}{p} &lt;=&gt; $chart{$a}{p} || $chart{$a}{t} &lt;=&gt; $chart{$b}{t}} 
                            keys %chart;
    for (@sorted_comps[0..$how_many]) {
        last unless $_;
        my ($secs, $mins) = localtime $chart{$_}{t};
        $kernel-&gt;post('triviabot', 'privmsg', $channel,
        sprintf( "\cC%d %d) \cB%-9s\cB with \cB%2d\cB point%1s in %02d:%02d",
            ++$j + $to_color, $j, $_, $chart{$_}{p}, 
            $chart{$_}{p}&lt;=1?'':'s', $mins, $secs )
        );
        $to_color -= $loop unless ( $j % $loop ); 
    }
}

sub quit {
    my $kernel = shift;
    $kernel-&gt;post( 'triviabot', 'quit', 'Game Over' );
}

POE::Component::IRC-&gt;new( 'triviabot' ) or
  die "Can't instantiate new IRC component!\n";

POE::Session-&gt;new( 'main' =&gt; [qw(
        _start  _stop irc_001   irc_disconnected 
        irc_socketerr irc_error irc_public irc_msg
    )] );

$poe_kernel-&gt;run();

exit 0;

&lt;/code&gt;
Some notes about the code:
&lt;ul&gt;
&lt;li&gt;I started coding on dicebot.pl example included with [kobe://POE::Component::IRC], so some code is just borrowed from there.
&lt;li&gt;I still got some trouble using POE: i put comments where i can't solve my problems, maybe someone can help me out.
&lt;li&gt;I thought about making it a Xchat Perl plug-in not to waste [id://102319|YASC], but i just liked more this solution that lets you install it even on remote shells without Xchat.
&lt;li&gt;Thanks goes to [Juerd] and his [id://132260]. Still someone may find the colors i'm using disturbing or just disgusting. Please give me suggestion on this.
&lt;li&gt;I should write some POD, of course. Next thing to do.
&lt;li&gt;Yes, i thought about using a DB of answers/questions. Supporting it should be no issue. Still putting questions realtime is funnier. And you need a HUGE DB not to get repeated questions. If someone knows of such a Db (it should be free, of course), please let me know.
&lt;li&gt;Help could be powered with specific help about single commands with obvious syntax like &lt;tt&gt;help &amp;lt;command&amp;gt;&lt;/tt&gt;.
&lt;li&gt;Biggest issue at the moment is $answer being a full regexp. It's powerful but at least confusing to most. Please note that matching like &lt;tt&gt;/$answer/i&lt;/tt&gt; instead of &lt;tt&gt;/^$answer$/i&lt;/tt&gt; isn't a bug, but the correct behavior.
&lt;li&gt;Any other comment, improvement, suggestion is welcome.
&lt;/ul&gt;

&lt;P&gt;&lt;TT&gt;g&lt;FONT SIZE=-5 COLOR=#EEEEEE&gt;k&lt;/font&gt;i&lt;FONT SIZE=-5 COLOR=#EEEEEE&gt;n&lt;/font&gt;u&lt;FONT SIZE=-5 COLOR=#EEEEEE&gt;e&lt;/font&gt;l&lt;FONT SIZE=-5 COLOR=#EEEEEE&gt;i&lt;/font&gt;i&lt;FONT SIZE=-5 COLOR=#EEEEEE&gt;l&lt;/font&gt;e&lt;FONT SIZE=-5 COLOR=#EEEEEE&gt;u&lt;/font&gt;n&lt;FONT SIZE=-5 COLOR=#EEEEEE&gt;i&lt;/font&gt;k&lt;FONT SIZE=-5 COLOR=#EEEEEE&gt;g&lt;/font&gt;&lt;/tt&gt;</field>
</data>
</node>
