Bot::Net::Mixin::Bot::IRC - mixin class for building IRC bots


Bot-Net documentation Contained in the Bot-Net distribution.

Index


Code Index:

NAME

Top

Bot::Net::Mixin::Bot::IRC - mixin class for building IRC bots

SYNOPSIS

Top

  # Build an Eliza chatbot for IRC
  use strict;
  use warnings;
  package MyBotNet::Bot::Eliza;

  use Bot::Net::Bot;
  use Bot::Net::Mixin::Bot::IRC;
  use Chatbot::Eliza; # available separately on CPAN

  on bot startup => run {
      remember eliza => Chatbot::Eliza->new;
  };

  on bot message_to_me => run {
      my $event = get ARG0;

      my $reply = recall('eliza')->transform( $event->message );
      yield reply_to_sender => $message, $reply;
  };

  1;

DESCRIPTION

Top

This is the mixin-class for Bot::Net IRC bots. You "inherit" all the features of this mixin by using the class:

  use Bot::Net::Bot;             # define a Bot::Net bot
  use Bot::Net::Mixin::Bot::IRC; # become an IRC bot

METHODS

Top

setup

Adds the IRC component to the bot's memory on setup.

default_configuration PACKAGE

Returns a base configuration for an IRC bot.

BOT STATES

Top

The following states are avaiable for your bot to implement.

on bot connect

This is automatically yielded by on _start unless the auto_connect configuration option is set to "0". If it is set to "0", you may emit this state to cause the bot to connect to the IRC server.

on bot connected

This state is emitted as soon as the bot has connected to the server and the server has passed back a message. This means it is now safe to communicate with the server.

on bot message_to_group EVENT

This state occurs when the bot observes a message sent to a channel it is in, but the message is not directed at the bot (i.e., is not prefixed with "MyNick:").

The EVENT is a Bot::Net::Message object setup to contain the group message.

on bot message_to_me EVENT

This state occurs when either the bot observes a message sent to a channel that it is in that has been prefixed with "MyNick:" or a private message was sent directly to the bot.

The EVENT is a Bot::Net::Message object setup for either a group or private message depending on which occurred. If a pblic message the "MyNick:" part of the message will have been stripped.

MIXIN STATES

Top

on _start

Sets up the IRC client.

on bot connect

Tells the IRC component to connect to the IRC server using the configuration stored in the irc_connect option of the configuration.

on irc_001

Connects to the channels that the bot has been configured to join. This then fires the on bot connected event which indicates that it is now safe to issue IRC commands, if your bot needs to do so.

on irc_disconnected

on irc_error

on irc_socketerr

Handles bot disconnections. If the bot becomes disconnected it will automatically attempt to reconnect until the server returns or lets the bot back. It will attempt to do so in a way that will not cause it to be blocked for flooding.

on attempt_reconnect

This state is invoked by on irc_disconnected and related states. This tells the IRC client to attempt ot reconnect. This will be called repeatedly (on a delay) until a connection is reestablished.

irc_msg USERHOST, ME, MESSAGE

Handles IRC messages sent directly to the bot. This emits:

bot message_to_me

See bot message_to_me.

irc_public USERHOST, CHANNEL, MESSAGE

Handles IRC messages sent to a public channel. This then emits additional bot states:

bot message_to_group

Reports that a message was sent to a channel the bot is in. See bot message_to_group.

bot message_to_me

Reports that a messages was sent to me publically in a channel. See bot message_to_me.

send_to DESTINATION, MESSAGE

This sends the given MESSAGE to the given DESTINATION.

The DESTINATION may be one of the following:

#channel

The name of a channel to send to. In this case, no special modifications will be made to the message.

nick

The name of a nick to send to. In this case, no special modifications will be made to the message.

[ #channel1, #channel2, nick1, nick2 ]

A list of channels and nicks to send to. In this case, no special modifications will be made to the message.

{ #channel1 => nick1, #channel2 => [ nick1, nick2 ] }

A hash of channels pointing to nicks. The nicks may be either a single nick or an array of nicks. In this case, the messages will have the given nick (or nicks) prepended to every message sent (except continuations).

If MESSAGE contains new lines (\n), then the message will be broken up and sent in pieces.

If any message that would be sent to the server approaches the 512 byte limit on IRC messages, the line will be broken. The broken line will have a backslash (\) appended at the break point of the message to signal the recipient that the line was broken. A line may be broken multiple times.

on reply_to_sender EVENT, MESSAGE

Sends the MESSAGE back to the nick that sent the given EVENT. The EVENT should be a Bot::Net::Message object. The MESSAGE may be broken up and chunked as specified in send_to.

This method will reply back to a user in all the channels it received the message in if the EVENT was sent to a channel (or group of channels) or will send privately back if the EVENT was private.

on reply_to_sender_privately EVENT, MESSAGE

Sends the MESSAGE back to the nick that sent the given EVENT, but via a private message directly to their nick, even if the original EVENT took place as a channel message.

on reply_to_sender_group EVENT, CHANNEL, MESSAGE

Sends the MESSAGE back to the nick that sent the given EVENT, but via the public group name in the CHANNEL argument.

on reply_to_general EVENT, MESSAGE

This is similar to reply_to_sender, except that if sent to a channel, the sender will not be identified by nick.

on bot quit

This causes the IRC client to close down the connection and quit.

AUTHORS

Top

Andrew Sterling Hanenkamp <hanenkamp@cpan.org>

COPYRIGHT AND LICENSE

Top


Bot-Net documentation Contained in the Bot-Net distribution.
use strict;
use warnings;

package Bot::Net::Mixin::Bot::IRC;

use Bot::Net::Mixin;

use POE qw/ Component::IRC::State /;
use Scalar::Util qw/ reftype /;

sub setup {
    my $self  = shift;
    my $brain = shift;

    $brain->remember(
        [ 'irc' ] => POE::Component::IRC::State->spawn( alias => 'irc' )
    );
}

sub default_configuration {
    my $class   = shift;
    my $package = shift;

    my $name = Bot::Net->short_name_for_bot($package);
    $name =~ s/\W+/_/g;

    my $default_channel = Bot::Net->config->net('ApplicationName');
    $default_channel =~ s/\W+/_/g;

    return {
        auto_connect => 1,

        irc_connect => {
            nick     => $name,
            username => lc $name,
            ircname  => Bot::Net->short_name_for_bot($package),

            server   => 'localhost',
            port     => 6667,

            flood    => 1,
        },
        
        channels => [ '#'.$default_channel ],
    };
}

on _start => run {
    post irc => register => 'all';

    my $auto_connect = recall [ config => 'auto_connect' ];
    if (not defined $auto_connect or $auto_connect) {
        yield 'bot_connect';
    }
};

on bot_connect => run {
    my $config = recall [ config => 'irc_connect' ] ;
    call irc => connect  => $config;
};

on irc_001 => run {
    my $self = get OBJECT;
    my $log  = recall 'log';

    my $channels = recall [ config => 'channels' ];
    for my $channel (@$channels) {
        $log->info("Joining $channel...");
        post irc => join => $channel;
    }

    yield 'bot_connected';

    # Report readiness (helpful for testing)
    my $config = recall [ config => 'irc_connect' ] ;
    recall('log')->info(
        "BOT READY : nick $config->{nick} server $config->{server} "
       ."port $config->{port}");
};

on [ qw( irc_disconnected irc_error irc_socketerr ) ] => run {
    # TODO XXX FIXME Add support for notifying the bot that the connection has
    # been lost and then notifying the bot again when the connection is
    # re-established.

    delay attempt_reconnect => 60;
};

on attempt_reconnect => run {
    post irc => 'connect';
};

on irc_msg => run {
    my $userhost = get ARG0;
    my $me       = get ARG1;
    my $message  = get ARG2;

    my ($nick, $host) = split /!/, $userhost;

    my $event = Bot::Net::Message->new({
        sender_nick     => $nick,
        sender_host     => $host,
        recipient_nicks => $me,
        message         => $message,
        private         => 1,
    });

    yield bot_message_to_me => $event;
};

on irc_public => run {
    my $userhost = get ARG0;
    my $channel  = get ARG1;
    my $message  = get ARG2;

    my $my_nick = recall [ config => irc_connect => 'nick' ];

    my $state;
    if ($message =~ s/^\Q$my_nick\E:\s*//) {
        $state = 'message_to_me';
    }

    else {
        $state = 'message_to_group';
    }

    my ($nick, $host) = split /!/, $userhost;

    my $event = Bot::Net::Message->new({
        sender_nick      => $nick,
        sender_host      => $host,
        recipient_groups => $channel,
        message          => $message,
        public           => 1,
    });

    yield 'bot_' . $state => $event;
};

on send_to => run {
    my $to           = get ARG0;
    my $full_message = get ARG1;

    my $log     = recall 'log';

    # Split the message up by newlines
    my @messages = split /\n/, $full_message;

    # Normalize the destination by channels and nicks
    my (%group_destinations, %private_destinations);

    # Given a single channel or nick
    if (!defined reftype $to) {

        # Given a single channel
        if ($to =~ /\#/) {
            $group_destinations{$to} = [ ];
        }

        # Given a single nick
        else {
            $private_destinations{$to}++;
        }
    }

    # Given an array of channels and/or nicks
    elsif (reftype($to) eq 'ARRAY') {

        # For each channel/nick in the array...
        for my $thing (@$to) {

            # Add channels to the group list
            if ($thing =~ /\#/) {
                $group_destinations{$thing} = [ ];
            }

            # Add nicks to the private list
            else {
                $private_destinations{$thing}++;
            }
        }
    }

    # Given a hash of channels => nicks
    elsif (reftype($to) eq 'HASH') {

        # For each channel in the hash...
        for my $channel (keys %$to) {
            my $nicks = $to->{$channel};

            # If a single nick, wrap it in an array
            if (!defined reftype($nicks)) {
                $group_destinations{$channel} = [ $nicks ];
            }

            # If an array, assume an array of nicks
            elsif (reftype($nicks) eq 'ARRAY') {
                $group_destinations{$channel} = $nicks;
            }
            
            # Wha?
            else {
                $log->error("send_to: Don't know what to do with $channel => $nicks, ignoring.");
            }

        }
    }

    # Wha?
    else {
        $log->error("send_to: Don't know what to do with $to, ignore.");
        return;
    }

    # Internal function for chunking the message
    my $message_chunks = sub {
        my $message = shift;
        my @chunks;
        while (length $message > 400) {
            my $chunk = substr $message, 0, 400;
            $message  = substr $message, 400;
            push @chunks, $chunk.'\\';
        }
        push @chunks, $message;
        return @chunks;
    };

    # Handle group messages
    for my $channel (keys %group_destinations) {
        my $nicks = $group_destinations{$channel};

        # For each message line
        for my $message (@messages) {

            # Prepend any nicks we have
            if (@$nicks) {
                $message = join(',', @$nicks).': '.$message;
            }

            # Post the message by chunks
            post irc => privmsg => $channel => $_ 
                for $message_chunks->($message);
        }
    }

    # Send out the private messages
    for my $nick (keys %private_destinations) {

        # For each message line, post it to the nick in chunks
        for my $message (@messages) {
            post irc => privmsg => $nick => $_
                for $message_chunks->($message);
        }
    }

};

on reply_to_sender => run {
    my $event   = get ARG0;
    my $message = get ARG1;

    if ($event->public) {
        my %send_to = map { $_ => $event->sender_nick } 
                         @{ $event->recipient_groups };
        yield send_to => \%send_to => $message;
    }

    else {
        yield send_to => $event->sender_nick => $message;
    }
};

on reply_to_sender_privately => run {
    my $event   = get ARG0;
    my $message = get ARG1;

    yield send_to => $event->sender_nick => $message;
};

on reply_to_sender_group => run {
    my $event   = get ARG0;
    my $channel = get ARG1;
    my $message = get ARG2;

    yield send_to => { $channel => $event->sender_nick } => $message;
};

on reply_to_general => run {
    my $event   = get ARG0;
    my $message = get ARG1;

    if ($event->public) {
        yield send_to => $event->recipient_groups => $message;
    }

    else {
        yield send_to => $event->sender_nick => $message;
    }
};

on bot_quit => run {
    my $message = get ARG0;

    recall('log')->warn("Quitting the IRC connection.");
    post irc => quit => ($message || 'Quitting.');

    post irc => unregister => 'all';
    forget 'irc';
};

1;