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 )