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 inherits from 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
$something
contains arendered
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
response
field 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 Content
is answered to the webhook call.
What Should You Provide/Override
This is what you should provide and probably override in the general case:
BUILD
to make sure the route is installed, like this:sub BUILD { my $self = shift; $self->install_route; }
"BUILD_code" if you want to set a default code different from the default different from the default;
"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.
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 "BUILD_code" for the default value.
Available as of (non-developer) release 0.004.
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_code
Builder for "code". Defaults to 204
, which is the HTTP code for No Response
. You might want to change it depending on how your webhook behaves, e.g. to 200
(OK
) if it actually provides a response back or to 202
(Accepted
) if the request is fine but you still cannot guarantee on the outcomes.
Available as of (non-developer) release 0.004.
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::WebHook
and getsomething
back, 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:
handler
-
the handler to be installed, as a sub reference. Defaults to "handler", which is passed
\%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. 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
.
parse_request
my @updates = $obj->parse_request($req);
Parse a single Mojo::Message::Request and return all the updates inside.
Defaults to just returning $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 "REQUIRED METHODS", the default was introduced later.
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. Note that some of these methods are actually provided by Bot::ChatBots::Role::Source and you are not required to provide them yourself (unless you want to override the defaults, of course).
process_updates
my @processed = $obj->process_updates(%args); # OR
@processed = $obj->process_updates(\%args);
Provided by Bot::ChatBots::Role::Source.
Process the updates received via the webhook, called by "handler". The %args
will contain the following keys:
refs
hash reference with three keys inside:
app
,controller
andstash
. When used with roleBot::ChatBots::Role::Source
, this part is put inside therefs
key in sectionsource
;source_pairs
this 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
flags
key inside theirsource
section.You can then set
rendered
to a true value if you plan to do the rendering yourself, otherwise "handler" will perform a rendering for you (using "code"). Note that if you use "normal" ways for rendering a response (via Mojolicious::Controller methods), this flag will be automatically set for you when hook "after_dispatch" in Mojolicious is emitted.updates
array 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.