Bot::ChatBots::Role::WebHook - Bot::ChatBots Role for WebHooks
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;
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.
Bot::ChatBots::Role::Source
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 a rendered field, the assumption is that you already rendered the response and nothing more has to be done. You this with caution.
$something
rendered
Otherwise, you might include a response field in $something. If this is defined and your object/composing class also supports a method render_response, it is called with the following signature:
response
render_response
$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.
204 No Content
This is what you should provide and probably override in the general case:
BUILD to make sure the route is installed, like this:
BUILD
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.
The following methods have a same-named option that can be passed to the constructor.
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.
routes
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.
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".
source
my $method = $obj->method;
Read-only accessor for the method to be used as default by "install_route". Defaults to whatever "BUILD_method" says.
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".
500
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.
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.
my $url = $obj->url;
Read-only accessor for the URL where your webhook lives, if available. Possibly used by "BUILD_processor".
It should be safe to override the following methods in your classes composing this role.
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.
204
No Response
200
OK
202
Accepted
Builder for "method". Defaults to post.
post
Builder for "path". Auto-extracts the path from "url". You can override this in your composing class.
Builder for "processor". Throws an exception. You can override this in your composing class.
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)
webhook
otherwise get the previous to last. This lets you call your class Something::WebHook and get something back, which makes more sense than taking webhook (as it would probably be the name for a lot of adapters!).
Something::WebHook
something
Of course you can set "typename" directly on construction if you want.
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".
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.
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:
%arg
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".
the Mojolicious::Routes where the new route should be installed. By default, "app" is used to retrieve the routes via $obj->app->routes.
$obj->app->routes
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.
$hashref
By default it is a thin wrapper around "processor", in order to ease your library's client to provide a processing sub reference.
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.
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.
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:
%args
refs
hash reference with three keys inside: app, controller and stash. When used with role Bot::ChatBots::Role::Source, this part is put inside the refs key in section source;
app
controller
stash
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 their source 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 (setting a 204 No Content response code);
flags
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.
Bot::ChatBots, Bot::ChatBots::Role::Source.
Flavio Poletti <polettix@cpan.org>
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.
To install Bot::ChatBots, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Bot::ChatBots
CPAN shell
perl -MCPAN -e shell install Bot::ChatBots
For more information on module installation, please visit the detailed CPAN module installation guide.