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.
- 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.
- 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.