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

NAME

Bot::Backbone - Extensible framework for building bots

VERSION

version 0.161950

SYNOPSIS

  package MyBot;
  use v5.14; # because newer Perl is cooler than older Perl
  use Bot::Backbone;

  use DateTime;
  use AI::MegaHAL;
  use WWW::Wikipedia;

  service chat_bot => (
      service  => 'JabberChat',
      jid      => 'mybot@example.com',
      password => 'secret',
      host     => 'example.com',
  );

  service group_foo => (
      service    => 'GroupChat',
      group      => 'foo',
      chat       => 'chat_bot',
      dispatcher => 'group_chat', # defined below
  );

  # This would invoke a service named MyBot::Service::Pastebin
  service pastebin => (
      service  => '.Pastebin',
      chats    => [ 'group_foo' ],
      host     => 'localhost',
      port     => 5000,
  );

  has megahal => (
      is         => 'ro',
      isa        => 'AI::MegaHAL',
      default    => sub { AI::MegaHAL->new },
  );

  has wikipedia => (
      is         => 'ro',
      isa        => 'WWW::Wikipedia',
      default    => sub { WWW::Wikipedia->new },
  );

  dispatcher group_chat => as {
      # Report the bot's time
      command '!time' => respond { DateTime->now->format_cldr('ddd, MMM d, yyyy @ hh:mm:ss') };

      # Basic echo command, with arguments
      command '!echo' => given_parameters {
          parameter echo_this => ( matching => qr/.*/ );
      } respond {
          my ($self, $message) = @_;
          $message->arguments->{echo_this};
      };

      # Include the pastebin commands (whatever they may be)
      redispatch_to 'pastebin';

      # Look for wikiwords in a comment and report the summaries for each
      also not_to_me respond {
          my ($self, $message) = @_;

          my (@wikiwords) = $message->text =~ /\[\[(\w+)\]\]/g;

           map { "$_->[0]: " . $_->[1] }
          grep { defined $_->[1] }
           map { [ $_, $self->wikipedia->search($_) }
              @wikiwords;
      };

      # Return an AI::MegaHAL resopnse for any message address to the bot
      to_me respond {
          my ($self, $message) = @_;
          $self->megahal->do_response($message->text);
      };

      # Finally:
      #  - also: match even if something else already responded
      #  - not_command: but not if a command matched
      #  - not_to_me: but not if addressed to me
      #  - run: run this code, but do not respond
      also not_command not_to_me run_this {
          my ($self, $message) = @_;
          $self->megahal->learn($message->text);
      };
  };

  my $bot = MyBot->new;
  $bot->run;

DESCRIPTION

Bots should be easy to build. Also a bot framework does not need to be tied to a particular protocol (e.g., IRC, Jabber, Slack, etc.). However, most bot tools fail at either of these. Finally, it should be possible to create generic services that a bot can consume or share with other bots. This framework aims at solving all of these.

This framework provides the following tools to this end.

Services

A service is a generic sub-application that runs within your bot, possibly independent of the rest. Here are some examples of possible services:

Chat Service

Each chat server connects to a chat service. This might be a Jabber server or an IRC server or Slack or even just a local REPL for running commands on the console. A single bot may have multiple connections to these servers by running more than one chat service.

See Bot::Backbone::Service::JabberChat and Bot::Backbone::Service::ConsoleChat for examples.

See Bot::Backbone::Service::Role::Chat for responsibilities.

Group Service

These will ask a chat service to join a particular room or channel.

See Bot::Backbone::Service::GroupChat.

Direct Message Service

These services are similar to Channel services, but are used to connect to another individual account or a list of other accounts.

See Bot::Backbone::Service::DirectChat.

Dispatched Service

A dispatched service may provide a group of common commands to the dispatcher.

See Bot::Backbone::Service for help on building such a service and see Bot::Backbone::Service::Role::ChatConsumer for responsibilities.

Other Services

These could do anything you could imagine: search the web or your wiki, check your email, notify you of new messages, monitor server logs, run RiveScript, run a Markov Chain-based conversation, manage a pastebin, play Russian Roulette, or whatever.

I have written a few of these services and may publish someday in separate projects in the future.

Basically, services are the place for any kind of tool the bot might need. Simple services might be embedded into the bot itself, but it's recommended for simplicity that the large dispatcher above not be emulated. Instead separate each sub-application in your bot into a service to make them easier to maintain separately.

Dispatcher

A dispatcher is a collection of predicates paired with run modes. A dispatcher may be applied to a chat, channel, or direct message service to handle incoming messages. When a message comes in from the service, each predicate is checked against that message. The run mode of the first matching predicate is executed (as well as any also predicates.

Dispatchers are extensible, allowing for new predicates and run mode operations to be defined as needed.

SUBROUTINES

init_meta

Setup the bot package with Bot::Backbone::Meta::Class as the meta class and Bot::Backbone::Bot as the base class.

SETUP ROUTINES

send_policy

  send_policy $name => ( ... );

Add a new send policy configuration.

service

  service $name => ( ... );

Add a new service configuration.

dispatcher

  dispatcher $name => ...;

This predicate is provided at the top level and is usually paired with the "as" run mode operation, though it could be paired with any of them. This declares a named dispatcher that can be referred to as the dispatcher attribute on services that support dispatching.

DISPATCHER PREDICATES

redispatch_to

  redispatch_to 'service_name';

Given a service name for a service implementing Bot::Backbone::Service::Role::Dispatch, we will ask the dispatcher on that object (if any) to perform dispatch.

command

  command $name => ...;

A command predicate matches the very first word found in the incoming message text. It only matches an exact string and only messages not preceded by whitespace (unless the message is addressed to the bot, in which case whitespace is allowed).

not_command

  not_command ...;

This is not useful unless paired with the "also" predicate. This only matches if no command has been matched so far for the current message.

given_parameters

  given_parameters { parameter $name => %config; ... } ...

This is used in conjunction with parameter to define arguments expected to come next.

If the given_parameters predicate matches completely, the message will have each of the named parameters set on the parameters hash inside the nested "run_this" or "respond".

The %config may contain the following keys:

match

This is a string a regular expression that will be used to match against the next part of the input, as if it were a command-line. The string or expression must match the entire next chunk (or provide a default) or the dispatcher will move on to the next dispatch predicate.

match_original

Rather than matching the next command-line split chunk of the input, this matches some next portion of the string. If it matches or there is a default provided, success.

default

This sets the default. If this is set, the parameter match will always succeed and gain this default value if the match itself fails.

You must provide either match or match_original in each parameter. Parameters my interleave match and match_original style matches as well and Backbone should do the right thing.

to_me

  to_me ...

Matches messages that are considered directed toward the bot. This may be a direct message or a channel message prefixed by the bot's name.

not_to_me

  not_to_me ...

This is the opposite of "to_me". It matches any message not sent directly to the bot.

shouted

  shouted ...

Matches messages that are received from outside the current chat, such as a system message or administrator alert sent to all channels.

head2 spoken

  spoken ...

Matches messages that are stated within the channel to all participants. This is the usual volume level.

whispered

  whispered ...

Matches messages that are stated within the channel to only a subset of the listeners, such as a private message within a channel.

also

  also ...;

In general, only the run mode operation for the first matching predicate will be executed. The also predicate, however, tells the dispatcher to try and match against it even if the dispatcher has already responded.

RUN MODE OPERATIONS

as

  as { ... }

This nests another set of dispatchers inside a predicate. Each set of predicates defined within will be executed in turn if this run mode oepration is reached.

respond

  respond { ... }

If a response is executed, the code ref given will be executed with three arguments. The first will be a reference to the bot's main object. The second will be a message object describing the incoming message. The third is the service that sent the message.

The return value of the executed code ref will be used to respond to the user. It will be called in list context and all the values returned will be sent to the user. If an empty list or undef is returned, then no message will be sent to the user and dispatching will continue as if the predicate had not matched.

respond_with_method

  respond_with_method 'method_name'

Given the name of a method defined on the current bot package, that method will be called if all the dispatch predicates in front of it match.

It is called and used exactly as described under "respond".

respond_with_service_method

  respond_with_service_method 'method_name'

This directive should only be used with dispatchers created in the bot class and then assigned to the service using the "dispatcher" in Bot::Backbone::Service::Role::Dispatch attribute. This allows the bot to define dispatchers on behalf of a service, which still calls the services methods.

run_this

  run_this { ... }

This will execute the given code ref, passing it the reference to the bot, the message, and the service as arguments. The return value is ignored.

run_this_method

  run_this_method 'method_name'

This will execute the named method on the bot class. It will be called and used in exactly the same way as "run_this".

run_this_service_method

  run_this_service_method 'method_name'

This dispatch directive should only be used on dispatchers that are assigned to a service using the "dispatcher" in Bot::Backbone::Service::Role::Dispatch argument. It allows the bot to specify a custom dispatcher for that service that still calls that service's methods.

AUTHOR

Andrew Sterling Hanenkamp <hanenkamp@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2016 by Qubling Software LLC.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.