| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package WWW::Telegram::BotAPI; | 
| 2 | 3 |  |  | 3 |  | 65050 | use strict; | 
|  | 3 |  |  |  |  | 21 |  | 
|  | 3 |  |  |  |  | 72 |  | 
| 3 | 3 |  |  | 3 |  | 14 | use warnings; | 
|  | 3 |  |  |  |  | 4 |  | 
|  | 3 |  |  |  |  | 71 |  | 
| 4 | 3 |  |  | 3 |  | 14 | use warnings::register; | 
|  | 3 |  |  |  |  | 5 |  | 
|  | 3 |  |  |  |  | 319 |  | 
| 5 | 3 |  |  | 3 |  | 28 | use Carp (); | 
|  | 3 |  |  |  |  | 4 |  | 
|  | 3 |  |  |  |  | 44 |  | 
| 6 | 3 |  |  | 3 |  | 1390 | use Encode (); | 
|  | 3 |  |  |  |  | 25972 |  | 
|  | 3 |  |  |  |  | 101 |  | 
| 7 | 3 |  |  | 3 |  | 364 | use JSON::MaybeXS (); | 
|  | 3 |  |  |  |  | 7759 |  | 
|  | 3 |  |  |  |  | 77 |  | 
| 8 | 3 |  | 50 | 3 |  | 13 | use constant DEBUG => $ENV{TELEGRAM_BOTAPI_DEBUG} || 0; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 347 |  | 
| 9 |  |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | our $VERSION = "0.11"; | 
| 11 |  |  |  |  |  |  | my $json; # for debugging purposes, only defined when DEBUG = 1 | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | BEGIN { | 
| 14 | 3 | 50 | 33 | 3 |  | 155 | eval "require Mojo::UserAgent; 1" or | 
| 15 |  |  |  |  |  |  | eval "require LWP::UserAgent; 1" or | 
| 16 |  |  |  |  |  |  | die "Either Mojo::UserAgent or LWP::UserAgent is required.\n$@"; | 
| 17 | 3 |  |  |  |  | 3579 | $json = JSON::MaybeXS->new (pretty => 1, utf8 => 1) if DEBUG; | 
| 18 |  |  |  |  |  |  | } | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | # Debugging functions (only used when DEBUG is true) | 
| 21 | 0 |  |  | 0 |  | 0 | sub _dprintf { printf "-T- $_[0]\n", splice @_, 1 } | 
| 22 |  |  |  |  |  |  | sub _ddump | 
| 23 |  |  |  |  |  |  | { | 
| 24 | 0 |  |  | 0 |  | 0 | my ($varname, $to_dump) = splice @_, -2; | 
| 25 | 0 | 0 |  |  |  | 0 | _dprintf @_ if @_; | 
| 26 | 0 |  |  |  |  | 0 | printf "%s = %s", $varname, $json->encode ($to_dump); | 
| 27 |  |  |  |  |  |  | } | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | # %settings = ( | 
| 30 |  |  |  |  |  |  | #     async     => Bool, | 
| 31 |  |  |  |  |  |  | #     token     => String, | 
| 32 |  |  |  |  |  |  | #     api_url   => "http://something/%s/%s", # 1st %s = tok, 2nd %s = method | 
| 33 |  |  |  |  |  |  | #     force_lwp => Bool | 
| 34 |  |  |  |  |  |  | # ) | 
| 35 |  |  |  |  |  |  | sub new | 
| 36 |  |  |  |  |  |  | { | 
| 37 | 6 |  |  | 6 | 1 | 3932 | my ($class, %settings) = @_; | 
| 38 |  |  |  |  |  |  | exists $settings{token} | 
| 39 | 6 | 100 |  |  |  | 191 | or Carp::croak "ERROR: missing 'token' from \%settings."; | 
| 40 |  |  |  |  |  |  | # When DEBUG is enabled, and Mojo::UserAgent is used, Mojolicious must be at | 
| 41 |  |  |  |  |  |  | # least version 6.22 (https://github.com/kraih/mojo/blob/v6.22/Changes). This is because | 
| 42 |  |  |  |  |  |  | # Mojo::JSON used incompatible JSON boolean constants which led JSON::MaybeXS to crash | 
| 43 |  |  |  |  |  |  | # with a mysterious error message. To prevent this, we force LWP in this case. | 
| 44 | 5 |  |  |  |  | 8 | if (DEBUG && Mojo::JSON->can ("true") && ref Mojo::JSON->true ne "JSON::PP::Boolean") | 
| 45 |  |  |  |  |  |  | { | 
| 46 |  |  |  |  |  |  | warnings::warnif ( | 
| 47 |  |  |  |  |  |  | "WARNING: Enabling DEBUG with Mojolicious versions < 6.22 won't work. Forcing " . | 
| 48 |  |  |  |  |  |  | "LWP::UserAgent. (update Mojolicious or disable DEBUG to fix)" | 
| 49 |  |  |  |  |  |  | ); | 
| 50 |  |  |  |  |  |  | ++$settings{force_lwp}; | 
| 51 |  |  |  |  |  |  | } | 
| 52 |  |  |  |  |  |  | # Ensure that LWP is loaded if "force_lwp" is specified. | 
| 53 |  |  |  |  |  |  | $settings{force_lwp} | 
| 54 | 5 | 100 |  |  |  | 25 | and require LWP::UserAgent; | 
| 55 |  |  |  |  |  |  | # Instantiate the correct user-agent. This automatically detects whether Mojo::UserAgent is | 
| 56 |  |  |  |  |  |  | # available or not. | 
| 57 | 5 | 50 | 66 |  |  | 48 | $settings{_agent} = ($settings{force_lwp} or !Mojo::UserAgent->can ("new")) ? | 
| 58 |  |  |  |  |  |  | LWP::UserAgent->new : Mojo::UserAgent->new; | 
| 59 | 5 | 100 | 100 |  |  | 5563 | ($settings{async}  ||= 0) and $settings{_agent}->isa ("LWP::UserAgent") | 
|  |  |  | 66 |  |  |  |  | 
| 60 |  |  |  |  |  |  | and Carp::croak "ERROR: Mojo::UserAgent is required to use 'async'."; | 
| 61 | 4 |  | 50 |  |  | 15 | $settings{api_url} ||= "https://api.telegram.org/bot%s/%s"; | 
| 62 |  |  |  |  |  |  | DEBUG && _dprintf "WWW::Telegram::BotAPI initialized (v%s), using agent %s %ssynchronously.", | 
| 63 | 4 |  |  |  |  | 4 | $VERSION, ref $settings{_agent}, $settings{async} ? "a" : ""; | 
| 64 | 4 |  |  |  |  | 52 | bless \%settings, $class | 
| 65 |  |  |  |  |  |  | } | 
| 66 |  |  |  |  |  |  |  | 
| 67 |  |  |  |  |  |  | # Don't let old Perl versions call AUTOLOAD when DESTROYing our class. | 
| 68 |  |  |  | 0 |  |  | sub DESTROY {} | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | # Magically provide methods named as the Telegram API ones, such as $o->sendMessage. | 
| 71 |  |  |  |  |  |  | sub AUTOLOAD | 
| 72 |  |  |  |  |  |  | { | 
| 73 | 4 |  |  | 4 |  | 19 | my $self = shift; | 
| 74 | 4 |  |  |  |  | 6 | our $AUTOLOAD; | 
| 75 | 4 |  |  |  |  | 21 | (my $method = $AUTOLOAD) =~ s/.*:://; # removes the package name at the beginning | 
| 76 | 4 |  |  |  |  | 11 | $self->api_request ($method, @_); | 
| 77 |  |  |  |  |  |  | } | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | # The real stuff! | 
| 80 |  |  |  |  |  |  | sub api_request | 
| 81 |  |  |  |  |  |  | { | 
| 82 | 4 |  |  | 4 | 1 | 10 | my ($self, $method) = splice @_, 0, 2; | 
| 83 |  |  |  |  |  |  | # Detect if the user provided a callback to use for async requests. | 
| 84 |  |  |  |  |  |  | # The only parameter whose order matters is $method. The callback and the request parameters | 
| 85 |  |  |  |  |  |  | # can be put in any order, like this: $o->api_request ($method, sub {}, { a => 1 }) or | 
| 86 |  |  |  |  |  |  | # $o->api_request ($method, { a => 1 }, sub {}), or even | 
| 87 |  |  |  |  |  |  | # $o->api_request ($method, "LOL", "DONGS", sub {}, { a => 1 }). | 
| 88 | 4 |  |  |  |  | 5 | my ($postdata, $async_cb); | 
| 89 | 4 |  |  |  |  | 8 | for my $arg (@_) | 
| 90 |  |  |  |  |  |  | { | 
| 91 |  |  |  |  |  |  | # Poor man's switch block | 
| 92 | 2 |  |  |  |  | 4 | for (ref $arg) | 
| 93 |  |  |  |  |  |  | { | 
| 94 |  |  |  |  |  |  | # Ensure that we don't get async callbacks when we aren't in async mode. | 
| 95 | 2 | 0 | 33 |  |  | 7 | ($async_cb = $arg, last) if $_ eq "CODE" and $self->{async}; | 
| 96 | 2 | 50 |  |  |  | 6 | ($postdata = $arg, last) if $_ eq "HASH"; | 
| 97 |  |  |  |  |  |  | } | 
| 98 | 2 | 50 | 33 |  |  | 5 | last if defined $async_cb and defined $postdata; | 
| 99 |  |  |  |  |  |  | } | 
| 100 |  |  |  |  |  |  | # Prepare the request method parameters. | 
| 101 | 4 |  |  |  |  | 6 | my @request; | 
| 102 | 4 |  |  |  |  | 7 | my $is_lwp = $self->_is_lwp; | 
| 103 |  |  |  |  |  |  | # Push the request URI (this is the same in LWP and Mojo) | 
| 104 | 4 |  |  |  |  | 17 | push @request, sprintf ($self->{api_url}, $self->{token}, $method); | 
| 105 | 4 | 100 |  |  |  | 9 | if (defined $postdata) | 
| 106 |  |  |  |  |  |  | { | 
| 107 |  |  |  |  |  |  | # POST arguments which are array/hash references need to be handled as follows: | 
| 108 |  |  |  |  |  |  | # - if no file upload exists, use application/json and encode everything with JSON::MaybeXS | 
| 109 |  |  |  |  |  |  | #   or let Mojo::UserAgent handle everything, when available. | 
| 110 |  |  |  |  |  |  | # - whenever a file upload exists, the MIME type is switched to multipart/form-data. | 
| 111 |  |  |  |  |  |  | #   Other refs which are not file uploads are then encoded with JSON::MaybeXS. | 
| 112 | 2 |  |  |  |  | 5 | my @fixable_keys; # This array holds keys found before file uploads which have to be fixed. | 
| 113 |  |  |  |  |  |  | my @utf8_keys; # This array holds keys found before file uploads which have to be encoded. | 
| 114 | 2 |  |  |  |  | 0 | my $has_file_upload; | 
| 115 |  |  |  |  |  |  | # Traverse the post arguments. | 
| 116 | 2 |  |  |  |  | 8 | for my $k (keys %$postdata) | 
| 117 |  |  |  |  |  |  | { | 
| 118 |  |  |  |  |  |  | # Ensure we pass octets to LWP with multipart/form-data and that we deal only with | 
| 119 |  |  |  |  |  |  | # references. | 
| 120 |  |  |  |  |  |  | ($is_lwp | 
| 121 |  |  |  |  |  |  | ? $has_file_upload ? $postdata->{$k} = Encode::encode ("utf-8", $postdata->{$k}) | 
| 122 |  |  |  |  |  |  | : push @utf8_keys, $k | 
| 123 | 2 | 50 |  |  |  | 11 | : ()), next unless my $ref = ref $postdata->{$k}; | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 124 |  |  |  |  |  |  | # Process file uploads. | 
| 125 | 0 | 0 | 0 |  |  | 0 | if ($ref eq "HASH" and | 
|  |  |  | 0 |  |  |  |  | 
| 126 |  |  |  |  |  |  | (exists $postdata->{$k}{file} or exists $postdata->{$k}{content})) | 
| 127 |  |  |  |  |  |  | { | 
| 128 |  |  |  |  |  |  | # WARNING: using file uploads implies switching to the MIME type | 
| 129 |  |  |  |  |  |  | # multipart/form-data, which needs a JSON stringification for every complex object. | 
| 130 | 0 |  |  |  |  | 0 | ++$has_file_upload; | 
| 131 |  |  |  |  |  |  | # No particular treatment is needed for file uploads when using Mojo. | 
| 132 | 0 | 0 |  |  |  | 0 | next unless $is_lwp; | 
| 133 |  |  |  |  |  |  | # The structure of the hash must be: | 
| 134 |  |  |  |  |  |  | # { content => 'file content' } or { file => 'path to file' } | 
| 135 |  |  |  |  |  |  | # With an optional key "filename" and optional headers to be merged into the | 
| 136 |  |  |  |  |  |  | # multipart/form-data stuff. | 
| 137 |  |  |  |  |  |  | # See https://metacpan.org/pod/Mojo::UserAgent::Transactor#tx | 
| 138 |  |  |  |  |  |  | # HTTP::Request::Common uses this syntax instead: | 
| 139 |  |  |  |  |  |  | # [ $file, $filename, SomeHeader => 'bla bla', Content => 'fileContent' ] | 
| 140 |  |  |  |  |  |  | # See p3rl.org/HTTP::Request::Common#POST-url-Header-Value-...-Content-content | 
| 141 | 0 |  |  |  |  | 0 | my $new_val = []; | 
| 142 |  |  |  |  |  |  | # Push and remove the keys 'file' and 'filename' (if defined) to $new_val. | 
| 143 |  |  |  |  |  |  | push @$new_val, delete $postdata->{$k}{file}, | 
| 144 | 0 |  |  |  |  | 0 | delete $postdata->{$k}{filename}; | 
| 145 |  |  |  |  |  |  | # Push 'Content' (note the uppercase 'C') | 
| 146 |  |  |  |  |  |  | exists $postdata->{$k}{content} | 
| 147 | 0 | 0 |  |  |  | 0 | and push @$new_val, Content => delete $postdata->{$k}{content}; | 
| 148 |  |  |  |  |  |  | # Push the other headers. | 
| 149 | 0 |  |  |  |  | 0 | push @$new_val, %{$postdata->{$k}}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 150 |  |  |  |  |  |  | # Finalize the changes. | 
| 151 | 0 |  |  |  |  | 0 | $postdata->{$k} = $new_val; | 
| 152 |  |  |  |  |  |  | } | 
| 153 |  |  |  |  |  |  | else | 
| 154 |  |  |  |  |  |  | { | 
| 155 | 0 | 0 |  |  |  | 0 | $postdata->{$k} = JSON::MaybeXS::encode_json ($postdata->{$k}), next | 
| 156 |  |  |  |  |  |  | if $has_file_upload; | 
| 157 | 0 |  |  |  |  | 0 | push @fixable_keys, $k; | 
| 158 |  |  |  |  |  |  | } | 
| 159 |  |  |  |  |  |  | } | 
| 160 | 2 | 50 |  |  |  | 5 | if ($has_file_upload) | 
| 161 |  |  |  |  |  |  | { | 
| 162 |  |  |  |  |  |  | # Fix keys found before the file upload. | 
| 163 | 0 |  |  |  |  | 0 | $postdata->{$_} = JSON::MaybeXS::encode_json ($postdata->{$_}) for @fixable_keys; | 
| 164 | 0 |  |  |  |  | 0 | $postdata->{$_} = Encode::encode ("utf-8", $postdata->{$_})  for @utf8_keys; | 
| 165 | 0 | 0 | 0 |  |  | 0 | $is_lwp | 
| 166 |  |  |  |  |  |  | and push @request, Content      => $postdata, | 
| 167 |  |  |  |  |  |  | Content_Type => "form-data" | 
| 168 |  |  |  |  |  |  | or  push @request, form         => $postdata; | 
| 169 |  |  |  |  |  |  | } | 
| 170 |  |  |  |  |  |  | else | 
| 171 |  |  |  |  |  |  | { | 
| 172 | 2 | 50 | 33 |  |  | 21 | $is_lwp | 
| 173 |  |  |  |  |  |  | and push @request, DEBUG ? (DBG => $postdata) : (), # handled in _fix_request_args | 
| 174 |  |  |  |  |  |  | Content      => JSON::MaybeXS::encode_json ($postdata), | 
| 175 |  |  |  |  |  |  | Content_Type => "application/json" | 
| 176 |  |  |  |  |  |  | or  push @request, json         => $postdata; | 
| 177 |  |  |  |  |  |  | } | 
| 178 |  |  |  |  |  |  | } | 
| 179 |  |  |  |  |  |  | # Protip (also mentioned in the doc): if you are using non-blocking requests with | 
| 180 |  |  |  |  |  |  | # Mojo::UserAgent, remember to start the event loop with Mojo::IOLoop->start. | 
| 181 |  |  |  |  |  |  | # This is superfluous when using this module in a Mojolicious app. | 
| 182 | 4 | 50 |  |  |  | 8 | push @request, $async_cb if $async_cb; | 
| 183 |  |  |  |  |  |  | # Stop here if this is a test - specified using the (internal) "_dry_run" flag. | 
| 184 | 4 | 50 |  |  |  | 19 | return 1 if $self->{_dry_run}; | 
| 185 | 0 |  |  |  |  | 0 | DEBUG and _ddump "BEGIN REQUEST to /%s :: %s", $method, scalar localtime, | 
| 186 |  |  |  |  |  |  | PAYLOAD => _fix_request_args ($self, \@request); | 
| 187 |  |  |  |  |  |  | # Perform the request. | 
| 188 | 0 |  |  |  |  | 0 | my $tx = $self->agent->post (@request); | 
| 189 | 0 |  |  |  |  | 0 | DEBUG and $async_cb and | 
| 190 |  |  |  |  |  |  | _dprintf "END REQUEST to /%s (async) :: %s", $method, scalar localtime; | 
| 191 |  |  |  |  |  |  | # We're done if the request is asynchronous. | 
| 192 | 0 | 0 |  |  |  | 0 | return $tx if $async_cb; | 
| 193 |  |  |  |  |  |  | # Pre-decode the response to provide, if possible, an error message. | 
| 194 |  |  |  |  |  |  | my $response = $is_lwp ? | 
| 195 | 0 | 0 | 0 |  |  | 0 | eval { JSON::MaybeXS::decode_json ($tx->decoded_content) } || undef : | 
| 196 |  |  |  |  |  |  | $tx->res->json; | 
| 197 |  |  |  |  |  |  | # Dump it in debug mode. | 
| 198 | 0 |  |  |  |  | 0 | DEBUG and _ddump RESPONSE => $response; | 
| 199 |  |  |  |  |  |  | # If we (or the server) f****d up... die horribly. | 
| 200 | 0 | 0 | 0 |  |  | 0 | unless (($is_lwp ? $tx->is_success : !$tx->error) && $response && $response->{ok}) | 
|  |  | 0 | 0 |  |  |  |  | 
| 201 |  |  |  |  |  |  | { | 
| 202 | 0 |  | 0 |  |  | 0 | $response ||= {}; | 
| 203 |  |  |  |  |  |  | # Handle old errors supplied by ancient Mojolicious versions: in some conditions, | 
| 204 |  |  |  |  |  |  | # `$tx->error` returned a string instead of the expected hash reference. See issue #16. | 
| 205 |  |  |  |  |  |  | my $error = $response->{description} || ($is_lwp ? $tx->status_line : | 
| 206 | 0 |  | 0 |  |  | 0 | ((ref ($tx->error || {}) ? $tx->error : { message => $tx->error }) || {})->{message}); | 
| 207 |  |  |  |  |  |  | # Print either the error returned by the API or the HTTP status line. | 
| 208 |  |  |  |  |  |  | Carp::confess | 
| 209 | 0 | 0 | 0 |  |  | 0 | "ERROR: ", ($response->{error_code} ? "code " . $response->{error_code} . ": " : ""), | 
| 210 |  |  |  |  |  |  | $error || "something went wrong!"; | 
| 211 |  |  |  |  |  |  | } | 
| 212 | 0 |  |  |  |  | 0 | DEBUG and _dprintf "END REQUEST to /%s :: %s", $method, scalar localtime; | 
| 213 | 0 |  |  |  |  | 0 | $response | 
| 214 |  |  |  |  |  |  | } | 
| 215 |  |  |  |  |  |  |  | 
| 216 |  |  |  |  |  |  | sub parse_error | 
| 217 |  |  |  |  |  |  | { | 
| 218 | 4 |  | 33 | 4 | 1 | 5297 | my $r = { type => "unknown", msg => $_[1] || $@ }; | 
| 219 |  |  |  |  |  |  | # The following regexp matches the error code to the first group and the error message to the | 
| 220 |  |  |  |  |  |  | # second. | 
| 221 |  |  |  |  |  |  | # Issue #19: match only `at ...` messages separated by at least one space. See t/02-exceptions | 
| 222 | 4 | 100 |  |  |  | 50 | return $r unless $r->{msg} =~ /ERROR: (?:code ([0-9]+): )?(.+?)(?:\s+at .+)?$/m; | 
| 223 |  |  |  |  |  |  | # Find and save the error code and message. | 
| 224 | 3 | 100 |  |  |  | 16 | $r->{code} = $1 if $1; | 
| 225 | 3 |  |  |  |  | 8 | $r->{msg}  = $2; | 
| 226 |  |  |  |  |  |  | # If the error message has a code, then it comes from the BotAPI. Otherwise, it's our agent | 
| 227 |  |  |  |  |  |  | # telling us something went wrong. | 
| 228 | 3 | 100 |  |  |  | 35 | $r->{type} = exists $r->{code} ? "api" : "agent" if $r->{msg} ne "something went wrong!"; | 
|  |  | 50 |  |  |  |  |  | 
| 229 | 3 |  |  |  |  | 14 | $r | 
| 230 |  |  |  |  |  |  | } | 
| 231 |  |  |  |  |  |  |  | 
| 232 |  |  |  |  |  |  | sub agent | 
| 233 |  |  |  |  |  |  | { | 
| 234 |  |  |  |  |  |  | shift->{_agent} | 
| 235 | 6 |  |  | 6 | 1 | 375 | } | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | # Hides the bot's token from the request arguments and improves debugging output. | 
| 238 |  |  |  |  |  |  | sub _fix_request_args | 
| 239 |  |  |  |  |  |  | { | 
| 240 | 0 |  |  | 0 |  | 0 | my ($self, $args) = @_; | 
| 241 | 0 |  |  |  |  | 0 | my $args_cpy = [ @$args ]; | 
| 242 | 0 |  |  |  |  | 0 | $args_cpy->[0] =~ s/\Q$self->{token}\E/XXXXXXXXX/g; | 
| 243 |  |  |  |  |  |  | # Note for the careful reader: you may remember that the position of Perl's hash keys is | 
| 244 |  |  |  |  |  |  | # undeterminate - that is, an hash has no particular order. This is true, however we are | 
| 245 |  |  |  |  |  |  | # dealing with an array which has a fixed order, so no particular problem arises here. | 
| 246 |  |  |  |  |  |  | # Addendum: the original reference of $args is used here to get rid of `DBG => $postdata`. | 
| 247 | 0 | 0 | 0 |  |  | 0 | if (@$args > 1 and $args->[1] eq "DBG") | 
| 248 |  |  |  |  |  |  | { | 
| 249 | 0 |  |  |  |  | 0 | my (undef, $data) = splice @$args, 1, 2; | 
| 250 |  |  |  |  |  |  | # Be sure to get rid of the `DBG` key in our copy too. | 
| 251 | 0 |  |  |  |  | 0 | splice @$args_cpy, 1, 2; | 
| 252 |  |  |  |  |  |  | # In the debug output, substitute the JSON-encoded data (which is not human readable) with | 
| 253 |  |  |  |  |  |  | # the raw POST arguments. | 
| 254 | 0 |  |  |  |  | 0 | $args_cpy->[2] = $data; | 
| 255 |  |  |  |  |  |  | } | 
| 256 |  |  |  |  |  |  | # Ensure that we do NOT try display async subroutines! | 
| 257 | 0 | 0 |  |  |  | 0 | pop @$args_cpy if ref $args_cpy->[-1] eq "CODE"; | 
| 258 | 0 |  |  |  |  | 0 | $args_cpy | 
| 259 |  |  |  |  |  |  | } | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | sub _is_lwp | 
| 262 |  |  |  |  |  |  | { | 
| 263 | 4 |  |  | 4 |  | 8 | shift->agent->isa ("LWP::UserAgent") | 
| 264 |  |  |  |  |  |  | } | 
| 265 |  |  |  |  |  |  |  | 
| 266 |  |  |  |  |  |  | 1; | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  | =encoding utf8 | 
| 269 |  |  |  |  |  |  |  | 
| 270 |  |  |  |  |  |  | =head1 NAME | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  | WWW::Telegram::BotAPI - Perl implementation of the Telegram Bot API | 
| 273 |  |  |  |  |  |  |  | 
| 274 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 275 |  |  |  |  |  |  |  | 
| 276 |  |  |  |  |  |  | use WWW::Telegram::BotAPI; | 
| 277 |  |  |  |  |  |  | my $api = WWW::Telegram::BotAPI->new ( | 
| 278 |  |  |  |  |  |  | token => 'my_token' | 
| 279 |  |  |  |  |  |  | ); | 
| 280 |  |  |  |  |  |  | # The API methods die when an error occurs. | 
| 281 |  |  |  |  |  |  | say $api->getMe->{result}{username}; | 
| 282 |  |  |  |  |  |  | # ... but error handling is available as well. | 
| 283 |  |  |  |  |  |  | my $result = eval { $api->getMe } | 
| 284 |  |  |  |  |  |  | or die 'Got error message: ', $api->parse_error->{msg}; | 
| 285 |  |  |  |  |  |  | # Uploading files is easier than ever. | 
| 286 |  |  |  |  |  |  | $api->sendPhoto ({ | 
| 287 |  |  |  |  |  |  | chat_id => 123456, | 
| 288 |  |  |  |  |  |  | photo   => { | 
| 289 |  |  |  |  |  |  | file => '/home/me/cool_pic.png' | 
| 290 |  |  |  |  |  |  | }, | 
| 291 |  |  |  |  |  |  | caption => 'Look at my cool photo!' | 
| 292 |  |  |  |  |  |  | }); | 
| 293 |  |  |  |  |  |  | # Complex objects are as easy as writing a Perl object. | 
| 294 |  |  |  |  |  |  | $api->sendMessage ({ | 
| 295 |  |  |  |  |  |  | chat_id      => 123456, | 
| 296 |  |  |  |  |  |  | # Object: ReplyKeyboardMarkup | 
| 297 |  |  |  |  |  |  | reply_markup => { | 
| 298 |  |  |  |  |  |  | resize_keyboard => \1, # \1 = true when JSONified, \0 = false | 
| 299 |  |  |  |  |  |  | keyboard => [ | 
| 300 |  |  |  |  |  |  | # Keyboard: row 1 | 
| 301 |  |  |  |  |  |  | [ | 
| 302 |  |  |  |  |  |  | # Keyboard: button 1 | 
| 303 |  |  |  |  |  |  | 'Hello world!', | 
| 304 |  |  |  |  |  |  | # Keyboard: button 2 | 
| 305 |  |  |  |  |  |  | { | 
| 306 |  |  |  |  |  |  | text => 'Give me your phone number!', | 
| 307 |  |  |  |  |  |  | request_contact => \1 | 
| 308 |  |  |  |  |  |  | } | 
| 309 |  |  |  |  |  |  | ] | 
| 310 |  |  |  |  |  |  | ] | 
| 311 |  |  |  |  |  |  | } | 
| 312 |  |  |  |  |  |  | }); | 
| 313 |  |  |  |  |  |  | # Asynchronous request are supported with Mojo::UserAgent. | 
| 314 |  |  |  |  |  |  | $api = WWW::Telegram::BotAPI->new ( | 
| 315 |  |  |  |  |  |  | token => 'my_token', | 
| 316 |  |  |  |  |  |  | async => 1 # WARNING: may fail if Mojo::UserAgent is not available! | 
| 317 |  |  |  |  |  |  | ); | 
| 318 |  |  |  |  |  |  | $api->sendMessage ({ | 
| 319 |  |  |  |  |  |  | chat_id => 123456, | 
| 320 |  |  |  |  |  |  | text    => 'Hello world!' | 
| 321 |  |  |  |  |  |  | }, sub { | 
| 322 |  |  |  |  |  |  | my ($ua, $tx) = @_; | 
| 323 |  |  |  |  |  |  | die 'Something bad happened!' if $tx->error; | 
| 324 |  |  |  |  |  |  | say $tx->res->json->{ok} ? 'YAY!' : ':('; # Not production ready! | 
| 325 |  |  |  |  |  |  | }); | 
| 326 |  |  |  |  |  |  | Mojo::IOLoop->start; | 
| 327 |  |  |  |  |  |  |  | 
| 328 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 329 |  |  |  |  |  |  |  | 
| 330 |  |  |  |  |  |  | This module provides an easy to use interface for the | 
| 331 |  |  |  |  |  |  | L. It also supports async requests out of the | 
| 332 |  |  |  |  |  |  | box using L, which makes this module easy to integrate with an existing | 
| 333 |  |  |  |  |  |  | L application. | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | =head1 METHODS | 
| 336 |  |  |  |  |  |  |  | 
| 337 |  |  |  |  |  |  | L implements the following methods. | 
| 338 |  |  |  |  |  |  |  | 
| 339 |  |  |  |  |  |  | =head2 new | 
| 340 |  |  |  |  |  |  |  | 
| 341 |  |  |  |  |  |  | my $api = WWW::Telegram::BotAPI->new (%options); | 
| 342 |  |  |  |  |  |  |  | 
| 343 |  |  |  |  |  |  | Creates a new L instance. | 
| 344 |  |  |  |  |  |  |  | 
| 345 |  |  |  |  |  |  | B you should only create one instance of this module and reuse it when needed. Calling | 
| 346 |  |  |  |  |  |  | C each time you run an async request causes unexpected behavior with L and | 
| 347 |  |  |  |  |  |  | won't work correctly. See also | 
| 348 |  |  |  |  |  |  | L. | 
| 349 |  |  |  |  |  |  |  | 
| 350 |  |  |  |  |  |  | C<%options> may contain the following: | 
| 351 |  |  |  |  |  |  |  | 
| 352 |  |  |  |  |  |  | =over 4 | 
| 353 |  |  |  |  |  |  |  | 
| 354 |  |  |  |  |  |  | =item * C<< token => 'my_token' >> | 
| 355 |  |  |  |  |  |  |  | 
| 356 |  |  |  |  |  |  | The token that will be used to authenticate the bot. | 
| 357 |  |  |  |  |  |  |  | 
| 358 |  |  |  |  |  |  | B | 
| 359 |  |  |  |  |  |  |  | 
| 360 |  |  |  |  |  |  | =item * C<< api_url => 'https://api.example.com/token/%s/method/%s' >> | 
| 361 |  |  |  |  |  |  |  | 
| 362 |  |  |  |  |  |  | A format string that will be used to create the final API URL. The first parameter specifies | 
| 363 |  |  |  |  |  |  | the token, the second one specifies the method. | 
| 364 |  |  |  |  |  |  |  | 
| 365 |  |  |  |  |  |  | Defaults to C. | 
| 366 |  |  |  |  |  |  |  | 
| 367 |  |  |  |  |  |  | =item * C<< async => 1 >> | 
| 368 |  |  |  |  |  |  |  | 
| 369 |  |  |  |  |  |  | Enables asynchronous requests. | 
| 370 |  |  |  |  |  |  |  | 
| 371 |  |  |  |  |  |  | B, and the method will croak if it isn't found.> | 
| 372 |  |  |  |  |  |  |  | 
| 373 |  |  |  |  |  |  | Defaults to C<0>. | 
| 374 |  |  |  |  |  |  |  | 
| 375 |  |  |  |  |  |  | =item * C<< force_lwp => 1 >> | 
| 376 |  |  |  |  |  |  |  | 
| 377 |  |  |  |  |  |  | Forces the usage of L instead of L, even if the latter is | 
| 378 |  |  |  |  |  |  | available. | 
| 379 |  |  |  |  |  |  |  | 
| 380 |  |  |  |  |  |  | By default, the module tries to load L, and on failure it uses L. | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | =back | 
| 383 |  |  |  |  |  |  |  | 
| 384 |  |  |  |  |  |  | =head2 AUTOLOAD | 
| 385 |  |  |  |  |  |  |  | 
| 386 |  |  |  |  |  |  | $api->getMe; | 
| 387 |  |  |  |  |  |  | $api->sendMessage ({ | 
| 388 |  |  |  |  |  |  | chat_id => 123456, | 
| 389 |  |  |  |  |  |  | text    => 'Hello world!' | 
| 390 |  |  |  |  |  |  | }); | 
| 391 |  |  |  |  |  |  | # with async => 1 and the IOLoop already started | 
| 392 |  |  |  |  |  |  | $api->setWebhook ({ url => 'https://example.com/webhook' }, sub { | 
| 393 |  |  |  |  |  |  | my ($ua, $tx) = @_; | 
| 394 |  |  |  |  |  |  | die if $tx->error; | 
| 395 |  |  |  |  |  |  | say 'Webhook set!' | 
| 396 |  |  |  |  |  |  | }); | 
| 397 |  |  |  |  |  |  |  | 
| 398 |  |  |  |  |  |  | This module makes use of L. This means that B | 
| 399 |  |  |  |  |  |  | method of the Telegram Bot API can be used by calling its Perl equivalent>, without requiring an | 
| 400 |  |  |  |  |  |  | update of the module. | 
| 401 |  |  |  |  |  |  |  | 
| 402 |  |  |  |  |  |  | If you'd like to avoid using C, then you may simply call the L"api_request"> method | 
| 403 |  |  |  |  |  |  | specifying the method name as the first argument. | 
| 404 |  |  |  |  |  |  |  | 
| 405 |  |  |  |  |  |  | $api->api_request ('getMe'); | 
| 406 |  |  |  |  |  |  |  | 
| 407 |  |  |  |  |  |  | This is, by the way, the exact thing the C method of this module does. | 
| 408 |  |  |  |  |  |  |  | 
| 409 |  |  |  |  |  |  | =head2 api_request | 
| 410 |  |  |  |  |  |  |  | 
| 411 |  |  |  |  |  |  | # Remember: each of these samples can be aliased with | 
| 412 |  |  |  |  |  |  | # $api->methodName ($params). | 
| 413 |  |  |  |  |  |  | $api->api_request ('getMe'); | 
| 414 |  |  |  |  |  |  | $api->api_request ('sendMessage', { | 
| 415 |  |  |  |  |  |  | chat_id => 123456, | 
| 416 |  |  |  |  |  |  | text    => 'Oh, hai' | 
| 417 |  |  |  |  |  |  | }); | 
| 418 |  |  |  |  |  |  | # file upload | 
| 419 |  |  |  |  |  |  | $api->api_request ('sendDocument', { | 
| 420 |  |  |  |  |  |  | chat_id  => 123456, | 
| 421 |  |  |  |  |  |  | document => { | 
| 422 |  |  |  |  |  |  | filename => 'dump.txt', | 
| 423 |  |  |  |  |  |  | content  => 'secret stuff' | 
| 424 |  |  |  |  |  |  | } | 
| 425 |  |  |  |  |  |  | }); | 
| 426 |  |  |  |  |  |  | # complex objects are supported natively since v0.04 | 
| 427 |  |  |  |  |  |  | $api->api_request ('sendMessage', { | 
| 428 |  |  |  |  |  |  | chat_id      => 123456, | 
| 429 |  |  |  |  |  |  | reply_markup => { | 
| 430 |  |  |  |  |  |  | keyboard => [ [ 'Button 1', 'Button 2' ] ] | 
| 431 |  |  |  |  |  |  | } | 
| 432 |  |  |  |  |  |  | }); | 
| 433 |  |  |  |  |  |  | # with async => 1 and the IOLoop already started | 
| 434 |  |  |  |  |  |  | $api->api_request ('getMe', sub { | 
| 435 |  |  |  |  |  |  | my ($ua, $tx) = @_; | 
| 436 |  |  |  |  |  |  | die if $tx->error; | 
| 437 |  |  |  |  |  |  | # ... | 
| 438 |  |  |  |  |  |  | }); | 
| 439 |  |  |  |  |  |  |  | 
| 440 |  |  |  |  |  |  | This method performs an API request. The first argument must be the method name | 
| 441 |  |  |  |  |  |  | (L). | 
| 442 |  |  |  |  |  |  |  | 
| 443 |  |  |  |  |  |  | Once the request is completed, the response is decoded using L and then | 
| 444 |  |  |  |  |  |  | returned. If L is used as the user-agent, then the response is decoded | 
| 445 |  |  |  |  |  |  | automatically using L. | 
| 446 |  |  |  |  |  |  |  | 
| 447 |  |  |  |  |  |  | If the request is not successful or the server tells us something isn't C, then this method | 
| 448 |  |  |  |  |  |  | dies with the first available error message (either the error description or the status line). | 
| 449 |  |  |  |  |  |  | You can make this method non-fatal using C: | 
| 450 |  |  |  |  |  |  |  | 
| 451 |  |  |  |  |  |  | my $response = eval { $api->api_request ($method, $args) } | 
| 452 |  |  |  |  |  |  | or warn "Request failed with error '$@', but I'm still alive!"; | 
| 453 |  |  |  |  |  |  |  | 
| 454 |  |  |  |  |  |  | Further processing of error messages can be obtained using L"parse_error">. | 
| 455 |  |  |  |  |  |  |  | 
| 456 |  |  |  |  |  |  | Request parameters can be specified using an hash reference. Additionally, complex objects can be | 
| 457 |  |  |  |  |  |  | specified like you do in JSON. See the previous examples or the example bot provided in | 
| 458 |  |  |  |  |  |  | L"SEE ALSO">. | 
| 459 |  |  |  |  |  |  |  | 
| 460 |  |  |  |  |  |  | File uploads can be specified using an hash reference containing the following mappings: | 
| 461 |  |  |  |  |  |  |  | 
| 462 |  |  |  |  |  |  | =over 4 | 
| 463 |  |  |  |  |  |  |  | 
| 464 |  |  |  |  |  |  | =item * C<< file => '/path/to/file.ext' >> | 
| 465 |  |  |  |  |  |  |  | 
| 466 |  |  |  |  |  |  | Path to the file you want to upload. | 
| 467 |  |  |  |  |  |  |  | 
| 468 |  |  |  |  |  |  | Required only if C is not specified. | 
| 469 |  |  |  |  |  |  |  | 
| 470 |  |  |  |  |  |  | =item * C<< filename => 'file_name.ext' >> | 
| 471 |  |  |  |  |  |  |  | 
| 472 |  |  |  |  |  |  | An optional filename that will be used instead of the real name of the file. | 
| 473 |  |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  | Particularly recommended when C is specified. | 
| 475 |  |  |  |  |  |  |  | 
| 476 |  |  |  |  |  |  | =item * C<< content => 'Being a file is cool :-)' >> | 
| 477 |  |  |  |  |  |  |  | 
| 478 |  |  |  |  |  |  | The content of the file to send. When using this, C must not be specified. | 
| 479 |  |  |  |  |  |  |  | 
| 480 |  |  |  |  |  |  | =item * C<< AnyCustom => 'Header' >> | 
| 481 |  |  |  |  |  |  |  | 
| 482 |  |  |  |  |  |  | Custom headers can be specified as hash mappings. | 
| 483 |  |  |  |  |  |  |  | 
| 484 |  |  |  |  |  |  | =back | 
| 485 |  |  |  |  |  |  |  | 
| 486 |  |  |  |  |  |  | Upload of multiple files is not supported. See L for more | 
| 487 |  |  |  |  |  |  | information about file uploads. | 
| 488 |  |  |  |  |  |  |  | 
| 489 |  |  |  |  |  |  | To resend files, you don't need to perform a file upload at all. Just pass the ID as a normal | 
| 490 |  |  |  |  |  |  | parameter. | 
| 491 |  |  |  |  |  |  |  | 
| 492 |  |  |  |  |  |  | $api->sendPhoto ({ | 
| 493 |  |  |  |  |  |  | chat_id => 123456, | 
| 494 |  |  |  |  |  |  | photo   => $photo_id | 
| 495 |  |  |  |  |  |  | }); | 
| 496 |  |  |  |  |  |  |  | 
| 497 |  |  |  |  |  |  | When asynchronous requests are enabled, a callback can be specified as an argument. | 
| 498 |  |  |  |  |  |  | The arguments passed to the callback are, in order, the user-agent (a L object) | 
| 499 |  |  |  |  |  |  | and the response (a L object). More information can be found in the | 
| 500 |  |  |  |  |  |  | documentation of L and L. | 
| 501 |  |  |  |  |  |  |  | 
| 502 |  |  |  |  |  |  | B ensure that the event loop L is started when using asynchronous requests. | 
| 503 |  |  |  |  |  |  | This is not needed when using this module inside a L app. | 
| 504 |  |  |  |  |  |  |  | 
| 505 |  |  |  |  |  |  | The order of the arguments, except of the first one, does not matter: | 
| 506 |  |  |  |  |  |  |  | 
| 507 |  |  |  |  |  |  | $api->api_request ('sendMessage', $parameters, $callback); | 
| 508 |  |  |  |  |  |  | $api->api_request ('sendMessage', $callback, $parameters); # same thing! | 
| 509 |  |  |  |  |  |  |  | 
| 510 |  |  |  |  |  |  | =head2 parse_error | 
| 511 |  |  |  |  |  |  |  | 
| 512 |  |  |  |  |  |  | unless (eval { $api->doSomething(...) }) { | 
| 513 |  |  |  |  |  |  | my $error = $api->parse_error; | 
| 514 |  |  |  |  |  |  | die "Unknown error: $error->{msg}" if $error->{type} eq 'unknown'; | 
| 515 |  |  |  |  |  |  | # Handle error gracefully using "type", "msg" and "code" (optional) | 
| 516 |  |  |  |  |  |  | } | 
| 517 |  |  |  |  |  |  | # Or, use it with a custom error message. | 
| 518 |  |  |  |  |  |  | my $error = $api->parse_error ($message); | 
| 519 |  |  |  |  |  |  |  | 
| 520 |  |  |  |  |  |  | When sandboxing calls to L methods using C, it is useful to parse | 
| 521 |  |  |  |  |  |  | error messages using this method. | 
| 522 |  |  |  |  |  |  |  | 
| 523 |  |  |  |  |  |  | B up until version 0.09, this method incorrectly stopped at the first occurence of C | 
| 524 |  |  |  |  |  |  | in error messages, producing results such as C instead of C. | 
| 525 |  |  |  |  |  |  |  | 
| 526 |  |  |  |  |  |  | This method accepts an error message as its first argument, otherwise C<$@> is used. | 
| 527 |  |  |  |  |  |  |  | 
| 528 |  |  |  |  |  |  | An hash reference containing the following elements is returned: | 
| 529 |  |  |  |  |  |  |  | 
| 530 |  |  |  |  |  |  | =over 4 | 
| 531 |  |  |  |  |  |  |  | 
| 532 |  |  |  |  |  |  | =item * C<< type => unknown|agent|api >> | 
| 533 |  |  |  |  |  |  |  | 
| 534 |  |  |  |  |  |  | The source of the error. | 
| 535 |  |  |  |  |  |  |  | 
| 536 |  |  |  |  |  |  | C specifies an error originating from Telegram's BotAPI. When C is C, the key | 
| 537 |  |  |  |  |  |  | C  is guaranteed to exist.  | 
| 538 |  |  |  |  |  |  |  | 
| 539 |  |  |  |  |  |  | C specifies an error originating from this module's user-agent. This may indicate a network | 
| 540 |  |  |  |  |  |  | issue, a non-200 HTTP response code or any error not related to the API. | 
| 541 |  |  |  |  |  |  |  | 
| 542 |  |  |  |  |  |  | C specifies an error with no known source. | 
| 543 |  |  |  |  |  |  |  | 
| 544 |  |  |  |  |  |  | =item * C<< msg => ... >> | 
| 545 |  |  |  |  |  |  |  | 
| 546 |  |  |  |  |  |  | The error message. | 
| 547 |  |  |  |  |  |  |  | 
| 548 |  |  |  |  |  |  | =item * C<< code => ... >> | 
| 549 |  |  |  |  |  |  |  | 
| 550 |  |  |  |  |  |  | The error code. B is C>. | 
| 551 |  |  |  |  |  |  |  | 
| 552 |  |  |  |  |  |  | =back | 
| 553 |  |  |  |  |  |  |  | 
| 554 |  |  |  |  |  |  | =head2 agent | 
| 555 |  |  |  |  |  |  |  | 
| 556 |  |  |  |  |  |  | my $user_agent = $api->agent; | 
| 557 |  |  |  |  |  |  |  | 
| 558 |  |  |  |  |  |  | Returns the instance of the user-agent used by the module. You can determine if the module is using | 
| 559 |  |  |  |  |  |  | L or L by using C: | 
| 560 |  |  |  |  |  |  |  | 
| 561 |  |  |  |  |  |  | my $is_lwp = $user_agent->isa ('LWP::UserAgent'); | 
| 562 |  |  |  |  |  |  |  | 
| 563 |  |  |  |  |  |  | =head1 DEBUGGING | 
| 564 |  |  |  |  |  |  |  | 
| 565 |  |  |  |  |  |  | To perform some cool troubleshooting, you can set the environment variable C | 
| 566 |  |  |  |  |  |  | to a true value: | 
| 567 |  |  |  |  |  |  |  | 
| 568 |  |  |  |  |  |  | TELEGRAM_BOTAPI_DEBUG=1 perl script.pl | 
| 569 |  |  |  |  |  |  |  | 
| 570 |  |  |  |  |  |  | This dumps the content of each request and response in a friendly, human-readable way. | 
| 571 |  |  |  |  |  |  | It also prints the version and the configuration of the module. As a security measure, the bot's | 
| 572 |  |  |  |  |  |  | token is automatically removed from the output of the dump. | 
| 573 |  |  |  |  |  |  |  | 
| 574 |  |  |  |  |  |  | B using this option along with an old Mojolicious version (< 6.22) leads to a warning, | 
| 575 |  |  |  |  |  |  | and forces L instead of L. This is because L | 
| 576 |  |  |  |  |  |  | used incompatible boolean values up to version 6.21, which led to an horrible death of | 
| 577 |  |  |  |  |  |  | L when serializing the data. | 
| 578 |  |  |  |  |  |  |  | 
| 579 |  |  |  |  |  |  | =head1 CAVEATS | 
| 580 |  |  |  |  |  |  |  | 
| 581 |  |  |  |  |  |  | When asynchronous mode is enabled, no error handling is performed. You have to do it by | 
| 582 |  |  |  |  |  |  | yourself as shown in the L"SYNOPSIS">. | 
| 583 |  |  |  |  |  |  |  | 
| 584 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 585 |  |  |  |  |  |  |  | 
| 586 |  |  |  |  |  |  | L, L, | 
| 587 |  |  |  |  |  |  | L, L, | 
| 588 |  |  |  |  |  |  | L, | 
| 589 |  |  |  |  |  |  | L | 
| 590 |  |  |  |  |  |  |  | 
| 591 |  |  |  |  |  |  | =head1 AUTHOR | 
| 592 |  |  |  |  |  |  |  | 
| 593 |  |  |  |  |  |  | Roberto Frenna (robertof AT cpan DOT org) | 
| 594 |  |  |  |  |  |  |  | 
| 595 |  |  |  |  |  |  | =head1 BUGS | 
| 596 |  |  |  |  |  |  |  | 
| 597 |  |  |  |  |  |  | Please report any bugs or feature requests to | 
| 598 |  |  |  |  |  |  | L. | 
| 599 |  |  |  |  |  |  |  | 
| 600 |  |  |  |  |  |  | =head1 THANKS | 
| 601 |  |  |  |  |  |  |  | 
| 602 |  |  |  |  |  |  | Thanks to L for inspiration about the license and the | 
| 603 |  |  |  |  |  |  | documentation. | 
| 604 |  |  |  |  |  |  |  | 
| 605 |  |  |  |  |  |  | =head1 LICENSE | 
| 606 |  |  |  |  |  |  |  | 
| 607 |  |  |  |  |  |  | Copyright (C) 2015, Roberto Frenna. | 
| 608 |  |  |  |  |  |  |  | 
| 609 |  |  |  |  |  |  | This program is free software, you can redistribute it and/or modify it under the terms of the | 
| 610 |  |  |  |  |  |  | Artistic License version 2.0. | 
| 611 |  |  |  |  |  |  |  | 
| 612 |  |  |  |  |  |  | =cut |