NAME
Bot::ChatBots::Role::WebHook - Bot::ChatBots Role for WebHooks
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;
DESCRIPTION
This is an updates receiver and dispatcher role for defining WebHooks (i.e. when the platform pushes updates through a webhook). It is most probably best used with Bot::ChatBots::Role::Source, which provides some of the required methods.
Operation Model
The generic model is the following:
you register a webhook at your service. How this is done is beyond the scope of this Role, although it allows you to "install_route" in Mojolicious to listen for the calls to the webhook;
when the remote service calls the webhook, the request object is passed to "parse_request" (that is mandatorily provided by the class composing this role) to get a list of updates back;
for each of the received updates, "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, }the last call to "process" can return a hash reference $something. In this case, this might condition the answer to the webhook via "render_response".
In particular, if
$somethingcontains arenderedfield, 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
responsefield in$something. If this is defined and your object/composing class also supports a methodrender_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?
otherwise, a status
204 No Contentis answered to the webhook call.
What Should You Provide/Override
This is what you should provide and probably override in the general case:
BUILDto make sure the route is installed, like this:sub BUILD { my $self = shift; $self->install_route; }"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;
"parse_request" is mandatory and is how you get from a Mojo::Message::Request object to an update
"render_response" is something you MIGHT want to provide if it makes sense
"class_custom_pairs" might be overridden to always include class-specific key/value pairs, e.g. a token if it exists.
ACCESSORS
The following methods have a same-named option that can be passed to the constructor.
app
my $app = $obj->app;
Read-only accessor for the app object, which CAN be set in the construction. Optionally used by "install_route", unless it has parameter routes in its arguments list. It should comply to the Mojolicious object interface.
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 source section of the record passed to "process".
method
my $method = $obj->method;
Read-only accessor for the method to be used as default by "install_route". Defaults to whatever "BUILD_method" says.
path
my $path = $obj->path;
Read-only accessor for the path that is used for setting the route in the Mojolicious app by "install_route". If not present, it is derived (lazily) from "url". If neither one is present, an exception is thrown via Ouch (with code 500). The lazy loading is done by "BUILD_path".
processor
my $processor_sub = $obj->processor;
Read-only accessor for a processor sub reference.
By default, "process" calls this to retrieve a sub reference that will be called with the update record. You might want to look at Data::Tubes, although anything supporting the "process" interface will do.
typename
my $name = $obj->typename;
Read-only accessor to the type of this source of messages. See BUILD_typename for the default value generated from the class name.
url
my $url = $obj->url;
Read-only accessor for the URL where your webhook lives, if available. Possibly used by "BUILD_processor".
METHODS
It should be safe to override the following methods in your classes composing this role.
BUILD_method
Builder for "method". Defaults to post.
BUILD_path
Builder for "path". Auto-extracts the path from "url". You can override this in your composing class.
BUILD_processor
Builder for "processor". Throws an exception. You can override this in your composing class.
BUILD_typename
Builder for "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:
if the class name has one single part only, take it
otherwise, take last if it's not
webhook(case-insensitively)otherwise get the previous to last. This lets you call your class
Something::WebHookand getsomethingback, which makes more sense than takingwebhook(as it would probably be the name for a lot of adapters!).
Of course you can set "typename" directly on construction if you want.
class_custom_pairs
my @pairs = $obj->class_custom_pairs;
Returns a list of custom key/value pairs to be added in the source section of the record passed to "process", specific to the class (see also "custom_pairs".
handler
my $subref = $obj->handler(%args);
$subref = $obj->handler(\%args);
Return a subroutine reference suitable for being installed as a route in Mojolicious; it is used by "install_route" behind the scenes.
See "DESCRIPTION" for its behaviour.
install_route
my $route = $obj->install_route(%args); # OR
$route = $obj->install_route(\%args);
Sets a route in Mojolicious for listening to the webhook calls. The input arguments in %arg are:
method-
the method of the registered route. Defaults to "method". Note that it is used in its lowercase form.
path-
the path associated to the route. Defaults to "path".
routes-
the Mojolicious::Routes where the new route should be installed. By default, "app" is used to retrieve the routes via
$obj->app->routes.
process
my $outcome = $obj->process($hashref);
Process an incoming record. This is built starting from each single update returned by "parse_request", with additional data to provide context to the following processing elements (so that you can theoretically build a generic processor for updates coming from different sources).
See "DESCRIPTION" for the shape of $hashref.
By default it is a thin wrapper around "processor", in order to ease your library's client to provide a processing sub reference.
REQUIRED METHODS
This class defines a Moo::Role, so it's not a standalone thing by itself. The following methods are required to exist in the class that composes this role.
parse_request
my @updates = $obj->parse_request($c->req);
Parse a single Mojo::Message::Request and return all the updates inside. For some perspective, the Telegram Bot API only delivers one single update per call, while the Facebook Messenger API can deliver a batch of updates all in one single WebHook call.
process_updates
my @processed = $obj->process_updates(%args); # OR
@processed = $obj->process_updates(\%args);
Process the updates received via the webhook, called by "handler". The %args will contain the following keys:
refshash reference with three keys inside:
app,controllerandstash. When used with roleBot::ChatBots::Role::Source, this part is put inside therefskey in sectionsource;source_pairsthis is a hash reference with the following structure:
{ flags => { rendered => 0 } }When this role is consumed along with Bot::ChatBots::Role::Source, this helps building records that contain the
flagskey inside theirsourcesection. You can then setrenderedto a true value if you plan to do the rendering yourself, otherwise "handler" will perform a rendering for you (setting a204 No Contentresponse code);updatesarray reference containing the updates to be processed.
In addition, all arguments passed to "handler" will be expanded, possibly overriding any or all of the keys above.
SEE ALSO
Bot::ChatBots, Bot::ChatBots::Role::Source.
AUTHOR
Flavio Poletti <polettix@cpan.org>
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.