File Coverage

blib/lib/WWW/Telegram/BotAPI.pm
Criterion Covered Total %
statement 64 112 57.1
branch 26 72 36.1
condition 13 56 23.2
subroutine 14 20 70.0
pod 4 4 100.0
total 121 264 45.8


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