Bot-ChatBots/lib/Bot/ChatBots/Role/WebHook.pod
=pod
=encoding utf8
=head1 NAME
Bot::ChatBots::Role::WebHook - Bot::ChatBots Role for WebHooks
=head1 SYNOPSIS
package Bot::ChatBots::Whatever::WebHook;
use Moo;
with 'Bot::ChatBots::Role::Source';
with 'Bot::ChatBots::Role::WebHook';
sub normalize_record {
return shift; # not much of a normalization, huh?
}
sub parse_request {
my ($self, $request) = @_;
my @updates = $request->json;
return @updates;
}
sub render_response {
my ($self, $controller, $response, $update) = @_;
# E.g. Telegram allows you to answer directly...
$response = {text => $response} unless ref $response;
local $response->{method} = $response->{method}
// 'sendMessage';
local $response->{chat_id} = $response->{chat_id}
// $update->{message}{chat}{id};
return $controller->render(json => $response);
}
1;
=head1 DESCRIPTION
This is an updates receiver and dispatcher role for defining WebHooks
(i.e. when the platform pushes updates through a webhook). It inherits
from C<Bot::ChatBots::Role::Source>, which provides some of the required
methods.
=head2 Operation Model
The generic model is the following:
=over
=item *
you register a webhook at your service. How this is done is beyond the
scope of this Role, although it allows you to L</install_route> in
L<Mojolicious> to listen for the calls to the webhook;
=item *
when the remote service calls the webhook, the request object is passed to
L</parse_request> (that is mandatorily provided by the class composing
this role) to get a list of updates back;
=item *
for each of the received updates, L</process> is called, with the following
input hash reference:
{
batch => {
count => $i, # id of this update in batch, starting from 1
total => $N, # number of updates in this batch
},
source => {
args => \%args, # whatever you passed in to install_route
class => $string , # the class of your webhook
refs => {
app => $app, # Mojolicious object
c => $controller, # Mojolicious::Controller object
self => $obj, # Your very object
}
type => $typename, # defaults to lc() of last part of class name
$obj->class_custom_pairs, # whatever you want to add...
@{$obj->custom_pairs}, # whatever you client wants to add
},
stash => $hashref,
update => $update_object_parsed_by_parse_request,
}
=item *
the last call to L</process> can return a hash reference I<$something>. In
this case, this might condition the answer to the webhook via
L</render_response>.
In particular, if C<$something> contains a C<rendered> field, the
assumption is that you already rendered the response and nothing more has
to be done. You this with caution.
Otherwise, you might include a C<response> field in C<$something>. If this
is defined and your object/composing class also supports a method
C<render_response>, it is called with the following signature:
$obj->render_response(
$c, # the Mojolicious::Controller of this request
$response, # what you got from $something->{response}
$update, # the last one parsed from the request
);
For example, the Telegram Bot API supports returning an answer message
directly as a response to the webhook call... why not use it if useful?
=item *
otherwise, a status C<204 No Content> is answered to the webhook call.
=back
=head2 What Should You Provide/Override
This is what you should provide and probably override in the general case:
=over
=item *
C<BUILD> to make sure the route is installed, like this:
sub BUILD {
my $self = shift;
$self->install_route;
}
=item *
L</BUILD_code> if you want to set a default code different from the
default different from the default;
=item *
L</normalize_record> is mandatory and it allows you to provide a "default"
shape to the records, in order to make life easier to the tube down along
the road;
=item *
L</parse_request> is mandatory and is how you get from
a L<Mojo::Message::Request> object to an update
=item *
L</render_response> is something you MIGHT want to provide if it makes
sense
=item *
L</class_custom_pairs> might be overridden to always include
class-specific key/value pairs, e.g. a token if it exists.
=back
=head1 ACCESSORS
The following methods have a same-named option that can be passed to the
constructor.
=head2 B<< app >>
my $app = $obj->app;
Read-only accessor for the app object, which CAN be set in the
construction. Optionally used by L</install_route>, unless it has
parameter C<routes> in its arguments list. It should comply to the
L<Mojolicious> object interface.
=head2 B<< code >>
my $code = $obj->code;
The code that is used in the rendering, by default. This is ignored in
case you do the rendering yourself, of course. See also L</BUILD_code> for
the default value.
Available as of (non-developer) release 0.004.
=head2 B<< custom_pairs >>
my $hash_ref = $obj->custom_pairs;
$obj->custom_pairs(\%some_key_value_pairs);
Accessor for custom key/value pairs. These are expanded in the C<source>
section of the record passed to L</process>.
=head2 B<< method >>
my $method = $obj->method;
Read-only accessor for the method to be used as default by
L</install_route>. Defaults to whatever L</BUILD_method> says.
=head2 B<< path >>
my $path = $obj->path;
Read-only accessor for the path that is used for setting the route in the
L<Mojolicious> app by L</install_route>. If not present, it is derived
(lazily) from L</url>. If neither one is present, an exception is thrown
via L<Ouch> (with code C<500>). The lazy loading is done by
L</BUILD_path>.
=head2 B<< processor >>
my $processor_sub = $obj->processor;
Read-only accessor for a processor sub reference.
By default, L</process> calls this to retrieve a sub reference that will
be called with the update record. You might want to look at
L<Data::Tubes>, although anything supporting the L</process> interface
will do.
=head2 B<< typename >>
my $name = $obj->typename;
Read-only accessor to the type of this source of messages. See
L<BUILD_typename> for the default value generated from the class name.
=head2 B<< url >>
my $url = $obj->url;
Read-only accessor for the URL where your webhook lives, if available.
Possibly used by L</BUILD_processor>.
=head1 METHODS
It should be safe to override the following methods in your classes
composing this role.
=head2 B<< BUILD_code >>
Builder for L</code>. Defaults to C<204>, which is the HTTP code for C<No
Response>. You might want to change it depending on how your webhook
behaves, e.g. to C<200> (C<OK>) if it actually provides a response back or
to C<202> (C<Accepted>) if the request is fine but you still cannot
guarantee on the outcomes.
Available as of (non-developer) release 0.004.
=head2 B<< BUILD_method >>
Builder for L</method>. Defaults to C<post>.
=head2 B<< BUILD_path >>
Builder for L</path>. Auto-extracts the path from L</url>. You can
override this in your composing class.
=head2 B<< BUILD_processor >>
Builder for L</processor>. Throws an exception. You can override this in
your composing class.
=head2 B<< BUILD_typename >>
Builder for L</typename>. It is derived from the class name by getting the
last meaningful part, see examples below:
WebHook --> webhook
Bot::ChatBots::Telegram::WebHook --> telegram
Bot::ChatBots::Whatever --> whatever
In simple terms:
=over
=item *
if the class name has one single part only, take it
=item *
otherwise, take last if it's not C<webhook> (case-insensitively)
=item *
otherwise get the previous to last. This lets you call your class
C<Something::WebHook> and get C<something> back, which makes more sense
than taking C<webhook> (as it would probably be the name for a lot of
adapters!).
=back
Of course you can set L</typename> directly on construction if you want.
=head2 B<< class_custom_pairs >>
my @pairs = $obj->class_custom_pairs;
Returns a list of I<custom> key/value pairs to be added in the C<source>
section of the record passed to L</process>, specific to the class (see
also L</custom_pairs>.
=head2 B<< handler >>
my $subref = $obj->handler(%args);
$subref = $obj->handler(\%args);
Return a subroutine reference suitable for being installed as a route in
L<Mojolicious>; it is used by L</install_route> behind the scenes.
See L</DESCRIPTION> for its behaviour.
=head2 B<< install_route >>
my $route = $obj->install_route(%args); # OR
$route = $obj->install_route(\%args);
Sets a route in L<Mojolicious> for listening to the webhook calls. The
input arguments in C<%arg> are:
=over
=item C<handler>
the handler to be installed, as a sub reference. Defaults to L</handler>,
which is passed C<\%args>. You will generally not need to pass this, but
sometimes (e.g. the Trello API) you have to install more than one route and
specify some specific (although simple) action for them.
=item C<method>
the method of the registered route. Defaults to L</method>. Note that it
is used in its lowercase form.
=item C<path>
the path associated to the route. Defaults to L</path>.
=item C<routes>
the L<Mojolicious::Routes> where the new route should be installed. By
default, L</app> is used to retrieve the routes via C<< $obj->app->routes
>>.
=back
=head2 B<< parse_request >>
my @updates = $obj->parse_request($req);
Parse a single L<Mojo::Message::Request> and return all the updates
inside.
Defaults to just returning C<< $req->json >>, i.e. the JSON decoding of
the request's content, which should work in most cases. You should
override this method otherwise. For example, the Telegram Bot API only
delivers one single update per call (so this default is sensible), while
the Facebook Messenger API can deliver a batch of updates all in one
single WebHook call (so it needs to be overridden).
NOTE: up to and including version 0.008 this method used to be one of the
L</REQUIRED METHODS>, the default was introduced later.
=head1 REQUIRED METHODS
This class defines a L<Moo::Role>, so it's not a standalone thing by
itself. The following methods are I<required> to exist in the class that
composes this role. Note that some of these methods are actually provided
by L<Bot::ChatBots::Role::Source> and you are not required to provide them
yourself (unless you want to override the defaults, of course).
=head2 B<< process_updates >>
my @processed = $obj->process_updates(%args); # OR
@processed = $obj->process_updates(\%args);
Provided by L<Bot::ChatBots::Role::Source>.
Process the updates received via the webhook, called by L</handler>. The
C<%args> will contain the following keys:
=over
=item * C<refs>
hash reference with three keys inside: C<app>, C<controller> and C<stash>.
When used with role C<Bot::ChatBots::Role::Source>, this part is put
inside the C<refs> key in section C<source>;
=item * C<source_pairs>
this is a hash reference with the following structure:
{ flags => { rendered => 0 } }
When this role is consumed along with L<Bot::ChatBots::Role::Source>, this
helps building records that contain the C<flags> key inside their
C<source> section.
You can then set C<rendered> to a true value if you plan to do the
rendering yourself, otherwise L</handler> will perform a rendering for you
(using L</code>). Note that if you use "normal" ways for rendering
a response (via L<Mojolicious::Controller> methods), this flag will be
automatically set for you when hook L<Mojolicious/after_dispatch> is
emitted.
=item * C<updates>
array reference containing the updates to be processed.
=back
In addition, all arguments passed to L</handler> will be expanded,
possibly overriding any or all of the keys above.
=head1 SEE ALSO
L<Bot::ChatBots>, L<Bot::ChatBots::Role::Source>.
=head1 AUTHOR
Flavio Poletti <polettix@cpan.org>
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2016 by Flavio Poletti <polettix@cpan.org>
This module is free software. You can redistribute it and/or modify it
under the terms of the Artistic License 2.0.
This program is distributed in the hope that it will be useful, but
without any warranty; without even the implied warranty of
merchantability or fitness for a particular purpose.
=cut