POEでIRC bot

Yokohama.pmで、kanさんの発表を聞いて、MooseX::POEはなかなか良さそうだと思い、少し使ってみようと思っていたのですが、そもそもPOEのことを全然わかってないので、POE::Component::IRCを使って簡単なbotを作ってみました。
http://github.com/dann/yaircbot/tree/master

bot用のコマンドは、Shell::Amazon::S3で使ったCommand拡張の仕組みを使ったので、Commandは簡単に拡張できるようになっています。YAIRCBot::Command以下にファイルを置けば、Commandが追加できるようになっています。

さて、POEを触ってわかってことですが、

  • Componentはeventをhookするだけで実装できるようになっている。定型処理はcomponent側で実装されている
  • 引数はPOE::Sugar::Argsで。生で扱うと引数がとても汚くなる
  • $poe->kernel->postなどと全体的に処理が長くなる
  • デバッグは、以下のようにしてKernelのデバッグを出しておくと、少しやりやすい
sub POE::Kernel::ASSERT_DEFAULT () {1}
sub POE::Kernel::TRACE_DEFAULT ()  {1}
  • POE::Componenet::IRCをspwanしてから、POE::Kernel->runというのが定型処理

大体処理の流れはわかったので、次はMooseX::POEを使って、Mooooooooooooose化して遊んでみようかなと思ってます。今はお遊びスクリプトですが、最終的にはIRC botを簡単に作る仕組みをMooseベースで作ってみたいかなと。

起動スクリプト

#!/usr/bin/env perl
use strict;
use warnings;
use FindBin::libs;
use YAIRCBot;

my $nick   = 'commandbot';
my $server = 'localhost';
my $port   = 6667;

my $bot = YAIRCBot->new(
    nick   => $nick,
    server => $server,
    port   => $port
);
$bot->run;

__END__

main class

package YAIRCBot;
use Moose;

use POE::Component::IRC;
use POE::Session;
use POE::Kernel;
use YAIRCBot::Component::IRCClient;
use YAIRCBot::CommandDispatcher;

our $VERSION = '0.01';

has 'nick' => (
    isa => 'Str',
    is => 'rw',
);

has 'server' => (
    isa => 'Str',
    is => 'rw',
);

has 'port' => (
    isa => 'Int',
    is => 'rw',
);

sub run {
    my $self = shift;
    POE::Component::IRC->spawn(
        alias  => 'bot',
        nick   => $self->nick,
        server => $self->server,
        port   => $self->port,
    );

    POE::Session->create(
        inline_states => {
            _start     => \&YAIRCBot::Component::IRCClient::irc_start,
            irc_001    => \&YAIRCBot::Component::IRCClient::irc_001,
            irc_public => \&YAIRCBot::Component::IRCClient::irc_public,
            irc_error  => \&YAIRCBot::Component::IRCClient::irc_reconnect,
        }
    );

    POE::Kernel->sig( INT => sub { POE::Kernel->stop } );
    POE::Kernel->run;
}

1;
__END__

Component

package YAIRCBot::Component::IRCClient;
use strict;

use POE qw(
    Sugar::Args
    Component::IRC
);
use YAIRCBot::CommandDispatcher;
use Perl6::Say;

#sub POE::Kernel::ASSERT_DEFAULT () {1}
#sub POE::Kernel::TRACE_DEFAULT ()  {0}

sub parse_input {
    my ($line) = @_;
    my @tokens = split( /\s/, $line );
    return unless @tokens >= 1;
    my $command = shift @tokens;
    return ( $command, \@tokens );
}

sub execute_command {
    my ( $command, $args ) = @_;
    my $dispatcher = YAIRCBot::CommandDispatcher->new;
    my $result = eval { $dispatcher->dispatch( $command, $args ); };
    return $result;
}

sub irc_start {
    my $poe = sweet_args;
    $poe->kernel->post( bot => register => 'all' );
    $poe->kernel->post( bot => connect  => {} );
}

sub irc_001 {
    my $poe = sweet_args;
    $poe->kernel->post( $poe->sender => join => '#test' );
}

sub irc_public {
    my $poe   = sweet_args;
    my $who   = $poe->args->[0];
    my $where = $poe->args->[1];
    my $what  = $poe->args->[2];

    my $nick         = ( split /!/, $who )[0];
    my $channel_name = $where->[0];
    my $self         = $_[0]->[OBJECT];
    if ( $what =~ /\!(.*)/ ) {
        my $command_line = $1;
        my ( $command, $tokens ) = parse_input($command_line);
        my $result = execute_command( $command, $tokens );
        $poe->kernel->post(
            $poe->sender => notice => $channel_name => $result );
    }
}

sub irc_connected {
    say 'irc_connected';
}

sub irc_error {
    my $p = sweet_args;
    my ($error) = @{ $p->args };
    say 'irc_error:' . $error;
}

sub irc_reconnect {
    say 'irc_reconnect';

}

1;