Bot-Telegram
view release on metacpan or search on metacpan
lib/Bot/Telegram.pm view on Meta::CPAN
}
sub stop_polling {
my $self = shift;
return $self unless $self -> is_polling;
for (my $agent = $self -> api -> agent) {
$self -> _polling(undef);
# In synchronous mode, it's enough to simply clear state
return $self -> _polling_interval(undef)
unless $agent -> isa('Mojo::UserAgent')
and $self -> is_async;
# In asynchronous mode, we also need to cancel existing timers
for (my $loop = Mojo::IOLoop -> singleton) {
$loop -> remove($self -> _polling_request_id);
$loop -> remove($self -> _polling_timer)
if $self -> _polling_timer; # if another request is scheduled, cancel it
}
# Reset state
$self -> _polling_request_id(undef)
-> _polling_interval(undef)
-> _polling_timer(undef);
}
$self
}
sub is_polling { !! shift -> _polling }
# In asynchronous mode: process getUpdates response or handle errors, if any
# In synchronous mode, WWW::Telegram::BotAPI::parse_error takes care of error handling for us.
sub _process_getUpdates_results {
my $self = shift;
my $async = $self -> is_async;
my ($response, $error);
$self -> log -> trace('processing getUpdates results');
my $retry_after;
if ($async) {
lib/Bot/Telegram.pm view on Meta::CPAN
$self -> emit(polling_error => $tx, $type);
}
} else {
($response, $error) = @_;
# NOTE: $response and $error are mutually exclusive - only one is `defined` at a time
if ($error) {
$error = $self -> api -> parse_error;
# no way to access the original $tx in synchronous mode
# https://metacpan.org/dist/WWW-Telegram-BotAPI/source/lib/WWW/Telegram/BotAPI.pm#L228
$self -> emit(polling_error => { error => $error }, $$error{type});
}
}
# Handle rate limits
if (exists $response -> {parameters}{retry_after}) {
$retry_after = $response -> {parameters}{retry_after};
$self -> log -> info("Rate limit exceeded, waiting ${retry_after}s before polling again");
}
lib/Bot/Telegram.pm view on Meta::CPAN
my $self = shift;
$self -> log -> trace('polling');
if ($self -> is_async) {
my $id = $self -> api -> api_request(
getUpdates => $self -> polling_config,
sub { $self -> _process_getUpdates_results(@_) }
);
# Assuming api_request always returns a valid ioloop connection ID when in asynchronous mode...
$self -> _polling_request_id($id);
} else {
my $response = eval {
$self -> api -> api_request(
getUpdates => $self -> polling_config)
};
$self -> _process_getUpdates_results($response, $@);
}
}
lib/Bot/Telegram.pm view on Meta::CPAN
# Start long polling
$bot -> start_polling;
Mojo::IOLoop -> start;
=head1 DESCRIPTION
This package provides a tiny wrapper around L<WWW::Telegram::BotAPI> that takes care of the most annoying boilerplate,
especially for the long polling scenario.
Supports both synchronous and asynchronous modes of L<WWW::Telegram::BotAPI>.
Just like the aforementioned L<WWW::Telegram::BotAPI>, it doesn't rely too much on current state of the API
- only a few fields and assumptions are used for decision making
(namely, C<ok>, C<result>, C<description>, C<error_code> [presence], C<getUpdates> POST body format
and the assumption that C<getUpdates> response would be an array of update objects,
each consisting of two fields - C<update_id> and the other one, named after the update it represents and holding the actual update contents),
meaning we don't have to update the code every week just to keep it usable.
=head1 RATIONALE
lib/Bot/Telegram.pm view on Meta::CPAN
my ($bot, $tx, $type) = @_;
});
Emitted when a C<getUpdates> request fails inside the polling loop.
Keep in mind that the loop will keep working despite the error.
To stop it, you will have to call L</"stop_polling"> explicitly:
$bot -> on(polling_error => sub { $bot -> stop_polling });
In synchronous mode, C<$tx> will be a plain hash ref.
The actual result of L<WWW::Telegram::BotAPI/"parse_error"> is available as the C<error> field of that hash.
$bot -> on(polling_error => sub {
my ($bot, $tx, $type) = @_;
for ($type) {
if (/api/) {
my $error = ($tx -> res -> json // {}) -> {description};
}
elsif (/agent/) {
if ($bot -> is_async) { # or `$tx -> isa('Mojo::Transaction::HTTP')`, if you prefer
my $error = $tx -> error -> {message};
} else {
my $error = $tx -> {error}{msg};
}
}
}
});
In asynchronous mode, the logic responsible for making the "error type" decision is modelled after L<WWW::Telegram::BotAPI/"parse_error">,
meaning you will always receive same C<$type> values for same errors in both synchronous and asynchronous modes.
See L<WWW::Telegram::BotAPI/"parse_error"> for the list of error types and their meanings.
Default subscriber will log the error message using L</"log"> with the C<warn> log level:
[1970-01-01 00:00:00.00000] [12345] [warn] Polling failed (error type: $type): error details here
=head2 unknown_update
$bot -> on(unknown_update => sub {
lib/Bot/Telegram.pm view on Meta::CPAN
my $update = $bot -> current_update;
say "User $$update{message}{from}{username} says: $$update{message}{text}";
Update that is currently being processed.
=head2 ioloop
$loop = $bot -> ioloop;
$bot -> ioloop($loop);
A L<Mojo::IOLoop> object used to delay execution in synchronous mode, defaults to a new L<Mojo::IOLoop> object.
=head2 log
$log = $bot -> log;
$bot -> log($log);
A L<Mojo::Log> instance used for logging, defaults to a new L<Mojo::Log> object with log level set to C<info>.
=head2 polling_config
lib/Bot/Telegram.pm view on Meta::CPAN
=head2 init_api
$bot = $bot -> init_api(%args);
Automatically creates a L<WWW::Telegram::BotAPI> instance.
C<%args> will be proxied to L<WWW::Telegram::BotAPI/"new">.
For most use cases you only want to set C<$args{token}> to your bot's API token and leave everything else default.
B<NOTE:> the L<WWW::Telegram::BotAPI> instance created by L</"init_api"> defaults to the asynchronous mode.
=head3 Exceptions
=over 4
=item C<Bot::Telegram::X::InvalidArgumentsError>
No token provided
=back
=head2 is_async
my $is_async = $bot -> is_async;
Returns true if the underlying L<WWW::Telegram::BotAPI> instance is in asynchronous mode.
=head3 Exceptions
=over 4
=item C<Bot::Telegram::X::InvalidStateError>
API is not initialized
=back
lib/Bot/Telegram.pm view on Meta::CPAN
=head2 start_polling
$bot = $bot -> start_polling;
$bot = $bot -> start_polling($cfg);
$bot = $bot -> start_polling(restart => 1, interval => 1);
$bot = $bot -> start_polling($cfg, restart => 1, interval => 1);
Start long polling.
This method will block in synchronous mode.
Set L</"log"> level to C<trace> to see additional debugging information.
=head3 Arguments
=over 4
=item C<$cfg>
A hash ref containing L<getUpdates|https://core.telegram.org/bots/api#getupdates> options.
t/06-sync.t view on Meta::CPAN
}
};
$bot -> api(bot_api);
is $bot -> is_async, 1, 'enabled';
$bot -> api -> {async} = 0;
is $bot -> is_async, 0, 'disabled';
};
subtest 'Looping in synchronous mode' => sub {
plan tests => 2;
my @UPDATES = qw/message edited_message callback_query/;
my $req_counter = 0;
my $bot = Bot::Telegram -> new;
my $api = bot_api json_response({
ok => \1,
result => [map {update $_ => $update_id++} @UPDATES]
}), json_response({
t/06-sync.t view on Meta::CPAN
my $passed;
$bot -> unsubscribe('callback_error');
$bot -> on(callback_error => sub {
note 'inside the callback';
$bot -> stop_polling;
$passed = 1;
});
$bot -> start_polling;
Mojo::IOLoop -> start;
ok $passed, 'events do work in synchronous mode';
};
# see 01-polling.t for the original test
# this one does the same thing but in synchronous mode
subtest 'Can sustain errors', sub {
plan tests => 3;
my $tx_error_response = json_response {};
$tx_error_response -> error('irrelevant error contents');
my ($req_counter, $err_counter, $upd_counter) = qw/0 0 0/;
my $bot = Bot::Telegram -> new;
my $api = bot_api
t/lib/Bot/Telegram/Test.pm view on Meta::CPAN
sub random_valid_polling_response {
my $updates_count = shift // 3;
json_response {
ok => \1,
result => [map { update $UPDATES[rand scalar @UPDATES], $_ } 1 .. $updates_count]
}
}
# Error handling in synchronous mode
# mostly copypasted from WWW::Telegram::BotAPI sources
sub _handle_error_sync {
my $tx = shift;
my $response = $tx -> res -> json;
unless (!$tx->error && $response && $response->{ok}) {
$response ||= {};
my $error = $response->{description} || WWW::Telegram::BotAPI::_mojo_error_to_string($tx);
# Print either the error returned by the API or the HTTP status line.
Carp::confess
( run in 0.685 second using v1.01-cache-2.11-cpan-d6f9594c0a5 )