The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

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

    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:

       $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 get something back, which makes more sense than taking webhook (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:

  • 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;

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