The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Bot::Cobalt::Manual::Plugins::Tutorial - Let's write a simple plugin

SYNOPSIS

Quick-start plugin authoring guide.

DESCRIPTION

Let's write a simple plugin that will respond to a command with a random string.

Outline

We'll at least need a package name, a version, some utilities, and an object constructor.

Declare our package:

  package My::Bot::Cobalt::Plugin;
  our $VERSION = '0.01';

We might need Bot::Cobalt::Common utilities. It's good practice to use strict and warnings explicitly, although Bot::Cobalt::Common will import them for you. It's even better to make warnings fatal as well via strictures:

  use strictures 1;
  use Bot::Cobalt::Common;

We probably also want the syntax sugar exported by Bot::Cobalt. This provides a simpler function interface to common core methods; we'll be able to call broadcast() to relay events and logger() to log messages.

  use Bot::Cobalt;

(See Bot::Cobalt::Core::Sugar for more on exported sugar.)

We can add a simple empty constructor:

  sub new { bless {}, shift }

Registering

Our plugin can't be loaded unless it can handle Cobalt_register and Cobalt_unregister events. It's also probably not very useful unless it's registered to receive some kind of event, most often from Bot::Cobalt::IRC.

Let's register to receive the in-channel command 'fortune' -- we'll figure out some responses to it later. Since we 'use Bot::Cobalt' we have the register() and logger() functions available:

  sub Cobalt_register {
    my ($self, $core) = splice @_, 0, 2;

    register( $self, 'SERVER',
      ## A list of events to catch.
      qw/ 
        public_cmd_fortune
      /
    );
    
    ## It's polite to log that we're here now.
    logger->info("Loaded - $VERSION");
    
    return PLUGIN_EAT_NONE
  }
  
  sub Cobalt_unregister {
    my ($self, $core) = splice @_, 0, 2;
    
    logger->info("Unloaded");
    
    return PLUGIN_EAT_NONE
  }

Notice the PLUGIN_EAT_NONE? Our event handlers should always return a Object::Pluggable::Constants constant indicating what to do with the event after the handler is complete. This is typically one of PLUGIN_EAT_NONE or PLUGIN_EAT_ALL, indicating whether to let the event continue on down the pipeline or be terminated, respectively.

Handling commands

Now we're loadable and will register to receive the 'SERVER' event public_cmd_fortune -- we just need a handler for it.

The first argument after the $self and $core objects will be a reference to a Bot::Cobalt::IRC::Message::Public object. We'll de-reference it and call some common methods to find out what we want to know.

  sub Bot_public_cmd_fortune {
    my ($self, $core) = splice @_, 0, 2;
    
    ## Get our (de-referenced) message object.
    my $msg = ${ $_[0] };

    ## Get our server context, source nickname, and channel.
    my $context  = $msg->context;
    my $src_nick = $msg->src_nick;
    my $channel  = $msg->channel;
    
    ## We'll write our response method fortune() later.
    my $fortune = $self->fortune;
    my $response_string = "$src_nick: $fortune";
    
    ## Relay our response string back to Bot::Cobalt::IRC
    broadcast( 'message', $context, $channel,
      $response_string
    );
  
    ## This one eats the event when it's complete.
    return PLUGIN_EAT_ALL
  }

Add some data

For convenience, we'll store our fortune cookies in the DATA filehandle until we need them.

At the bottom of your plugin module:

  1;  ## Perl modules must return a true value
  __DATA__

  You are not dead yet. Watch for further reports.
  Don't look up.
  You look tired.
  Fine day for friends. Crappy day for you.

Add as many as you like, one per line. When we want to retrieve them, we just read DATA like a normal file handle.

Let's write our fortune() method to pull a random fortune from our retrieved DATA -- this is nice and simple:

  sub fortune {
    my ($self) = @_;

    $self->{fortunes} = [ readline(DATA) ]
      unless defined $self->{fortunes};
    
    return $self->{fortunes}->[ rand( @{$self->{fortunes}} ) ]
  }

Finished product

  package My::Bot::Cobalt::Plugin;
  our $VERSION = '0.01';

  use strictures 1;
  use Bot::Cobalt::Common;
  
  use Bot::Cobalt;

  sub new { bless {}, shift }

  sub Cobalt_register {
    my ($self, $core) = splice @_, 0, 2;

    register( $self, 'SERVER',
      ## A list of events to catch.
      qw/ 
        public_cmd_fortune
      /
    );
    
    ## It's polite to log that we're here now.
    logger->info("Loaded - $VERSION");
    
    return PLUGIN_EAT_NONE
  }
  
  sub Cobalt_unregister {
    my ($self, $core) = splice @_, 0, 2;
    
    logger->info("Unloaded");
    
    return PLUGIN_EAT_NONE
  }

  sub Bot_public_cmd_fortune {
    my ($self, $core) = splice @_, 0, 2;
    
    ## Get our (de-referenced) message object.
    my $msg = ${ $_[0] };

    ## Get our server context, source nickname, and channel.
    my $context  = $msg->context;
    my $src_nick = $msg->src_nick;
    my $channel  = $msg->channel;
    
    my $fortune = $self->fortune;
    my $response_string = "$src_nick: $fortune";
    
    ## Relay our response string back to Bot::Cobalt::IRC
    broadcast( 'message', $context, $channel,
      $response_string
    );
  
    ## This one eats the event when it's complete.
    return PLUGIN_EAT_ALL
  }

  sub fortune {
    my ($self) = @_;

    $self->{fortunes} = [ readline(DATA) ]
      unless defined $self->{fortunes};
  
    return $self->{fortunes}->[ rand( @{$self->{fortunes}} ) ]
  }

  1;  ## Perl modules must return a true value
  __DATA__
  You are not dead yet. Watch for further reports.
  Don't look up.
  You look tired.
  Fine day for friends. Crappy day for you.

SEE ALSO

Bot::Cobalt::Manual::Plugins - Plugin authoring handbook

Bot::Cobalt::Core

Bot::Cobalt::Core::Sugar

Bot::Cobalt::IRC

Bot::Cobalt::IRC::Event

Bot::Cobalt::IRC::Message

AUTHOR

Jon Portnoy <avenj@cobaltirc.org>

http://www.cobaltirc.org