Bot-Telegram

 view release on metacpan or  search on metacpan

examples/webhook.pl  view on Meta::CPAN

    return app -> log -> warn('invalid secret')
      unless $c -> req
                -> headers
                -> header('X-Telegram-Bot-Api-Secret-Token')
                eq $secret;
  }

  $bot -> process_update($c -> req -> json);
};

$bot -> set_callbacks(message => sub {
  my (undef, $update) = @_;
  my $chat_id = $$update{message}{from}{id};
  my $text    = $$update{message}{text};

  eval {
    $bot -> api -> sendMessage({
      chat_id => $chat_id,
      text => $text eq '/start' ? 'Hey there!' : "You said: $text",
    });
  };

lib/Bot/Telegram.pm  view on Meta::CPAN

  $self -> log -> warn("Update processing failed: $err");
};

has [qw/api current_update polling_config/];
has [qw/_polling
        _polling_timer
        _polling_interval
        _polling_request_id/
    ];

has callbacks => sub { {} };
has ioloop => sub { Mojo::IOLoop -> new };
has log => sub { Mojo::Log -> new -> level('info') };

sub new {
  my $self = shift -> SUPER::new(@_);

  $self -> on(polling_error  => DEFAULT_POLLING_ERROR_CB);
  $self -> on(callback_error => DEFAULT_CALLBACK_ERROR_CB);

  $self

lib/Bot/Telegram.pm  view on Meta::CPAN

        ? $resolve
        : $reject) -> ($ua, $tx);
    })
  });
}

################################################################################
# Callbacks
################################################################################

sub set_callbacks {
  my ($self, %cbs) = @_;

  while ( my ($key, $val) = each %cbs) {
    $self -> callbacks -> {$key} = $val;
  }

  return $self;
}

sub remove_callbacks {
  my ($self, @events) = @_;

  foreach my $event (@events) {
    delete $self -> callbacks -> {$event};
  }

  return $self;
}

################################################################################
# Updates
################################################################################

sub shift_offset {

lib/Bot/Telegram.pm  view on Meta::CPAN


sub process_update {
  my ($self, $update) = @_;

  $self -> current_update($update);
  my $type = $self -> _get_update_type($update);

  eval {
    # If update type is recognized, call the appropriate callback
    if ($type) {
      $self -> callbacks
            -> {$type}
            -> ($self, $update);
    }

    # Otherwise report an unknown update
    else { $self -> emit(unknown_update => $update) }
  };

  # Report a callback error if we failed to handle the update
  $self -> emit(callback_error => $update, $@) if $@;

lib/Bot/Telegram.pm  view on Meta::CPAN

  return $self;
}

# Return the update type if we have a callback for it
# Or just return zero, if we don't
sub _get_update_type {
  my ($self, $update) = @_;

  exists $$update{$_}
    and return $_
    for keys %{ $self -> callbacks };

  return 0;
}

################################################################################
# Webhook
################################################################################

sub set_webhook {
  my ($self, $config, $cb) = @_;

lib/Bot/Telegram.pm  view on Meta::CPAN


  #!/usr/bin/env perl

  use Mojo::Base -strict;
  use Bot::Telegram;

  my $bot = Bot::Telegram
    -> new
    -> init_api(token => YOUR_TOKEN_HERE);

  $bot -> set_callbacks(
    message => sub {
      my ($bot, $update) = @_;
      my $chat = $$update{message}{chat}{id};
      my $user = $$update{message}{from}{username};
      my $text = $$update{message}{text};

      say "> User $user says: $text";

      $bot -> api -> sendMessage(
        { chat_id => $chat, text => "Hello there, $user!" },

lib/Bot/Telegram.pm  view on Meta::CPAN

    my ($bot, $update) = @_;
    say "> No callback defined for this kind of updates. Anyway, here's the update object:";

    require Data::Dump;
    Data::Dump::dd($update);
  });

Emitted when an update of an unregistered type is received.

The type is considered "unregistered" if there is no matching callback configured
 (i.e. C<$self -E<gt> callbacks -E<gt> {$update_type}> is not a coderef).

Exists mostly for debugging purposes.

There are no default subscribers to this event.

=head1 PROPERTIES

L<Bot::Telegram> inherits all properties from L<Mojo::EventEmitter> and implements the following new ones.

=head2 api

  my $api = $bot -> api;
  $bot -> api($api);

L<WWW::Telegram::BotAPI> instance used by the bot. Can be initialized via the L</"init_api"> method, or set directly.

=head2 callbacks

  my $callbacks = $bot -> callbacks;
  $bot -> callbacks($callbacks);

Hash reference containing callbacks for different update types.

While you can manipulate it directly, L</"set_callbacks"> and L</"remove_callbacks"> methods provide a more convinient interface.

=head2 current_update

  my $update = $bot -> current_update;
  say "User $$update{message}{from}{username} says: $$update{message}{text}";

Update that is currently being processed.

=head2 ioloop

lib/Bot/Telegram.pm  view on Meta::CPAN


=head2 process_update

  $bot = $bot -> process_update($update);

Process a single update and store it in L</"current_update">.

This function will not C<die> regardless of the operation success.
Instead, the L</"callback_error"> event is emitted if things go bad.

=head2 remove_callbacks

  $bot = $bot -> remove_callbacks(qw/message edited_message/);
  # From now on, bot considers 'message' and 'edited_message' unknown updates

Remove callbacks for given update types, if set.

=head2 set_callbacks

  $bot -> set_callbacks(
    message => sub {
      my ($bot, $update) = @_;
      handle_message $update;
    },

    edited_message => sub {
      my ($bot, $update) = @_;
      handle_edited_message $update;
    }
  );

Set callbacks to match specified update types.

=head2 set_webhook

  $bot = $bot -> set_webhook($config);
  $bot = $bot -> set_webhook($config, $cb);

Set a webhook. All arguments will be proxied to L<WWW::Telegram::BotAPI/"api_request">.

This function ensures that actual C<setWebhook> request will not be made as long as the polling loop is active:

t/01-polling.t  view on Meta::CPAN

  my $messages = $bot -> log -> capture('info');
  my $polling_interval_after_rate_limit;

  my $stop = sub {
    $bot -> stop_polling;
    Mojo::IOLoop -> stop;
  };

  my $t = Mojo::IOLoop -> timer(3, $stop);

  $bot -> set_callbacks(message => sub {
    return unless pop -> {update_id} == 2;

    $polling_interval_after_rate_limit = $bot -> _polling_interval;

    $stop -> ();
    Mojo::IOLoop -> remove($t);
  });

  my $time_before = steady_time;

t/04-events.t  view on Meta::CPAN

      result => [ map { state $i = 0 ; update $_ => $i++ } qw/message edited_message callback_query/ ]
    };

    my $messages = $log -> capture('warn');
    my $upd_counter = 0;

    my $count_update = sub { ++ $upd_counter };

    $bot
      -> api(bot_api $res)
      -> set_callbacks(
        message => sub { die 'failed to process message' },
        edited_message => $count_update,
        callback_query => $count_update)
      -> start_polling;

    Mojo::IOLoop -> start;

    is scalar @$messages, 1, 'got 1 failure as expected';
    is $upd_counter, 2, 'processed 2 updates as expected';
    like $$messages[0], qr/Update processing failed: failed to process message/,

t/04-events.t  view on Meta::CPAN

  };
};

subtest callback_error => sub {
  plan tests => 2;

  my $bot = Bot::Telegram -> new;
  my $res = json_response { ok => \1, result => [update message => 1] };

  $bot -> api(bot_api $res);
  $bot -> set_callbacks(message => sub { This shit will definitely die });

  my ($pass, @args) = 0;
  $bot -> unsubscribe('callback_error');
  $bot -> on(callback_error => sub { @args = @_ });

  $bot -> start_polling;
  Mojo::IOLoop -> one_tick;
  $bot -> stop_polling;

  subtest arguments => sub {

t/04-events.t  view on Meta::CPAN

  my $res = json_response {
    ok   => \1,  
    result => [
      (update message => 1),
      (update thing => 2),
    ],
  };

  $bot -> api(bot_api $res);
  # $bot -> unsubscribe('unknown_update'); # there's nothing by default
  $bot -> set_callbacks(message => sub { 0 }); # make 'message' a "known" update

  my ($pass, @args) = 0;
  $bot -> on(unknown_update => sub { @args = @_ });

  $bot -> start_polling;
  Mojo::IOLoop -> one_tick;
  $bot -> stop_polling;

  subtest arguments => sub {
    plan tests => 2;

t/05-updates.t  view on Meta::CPAN


subtest 'on/off' => sub {
  plan tests => 3;

  my $bot = Bot::Telegram -> new;
  my $api = bot_api;

  my $message = sub { 'this is a callback for message' };
  my $edited_message = sub { 'this is a callback for edited_message' };

  $bot -> set_callbacks(message => $message, edited_message => $edited_message);

  is_deeply [ sort keys %{$bot -> callbacks} ], [qw/edited_message message/],
            'set callbacks';

  subtest subs => sub {
    is $bot -> callbacks -> {message}, $message;
    is $bot -> callbacks -> {edited_message}, $edited_message;
  };

  $bot -> remove_callbacks(qw/message edited_message/);
  is_deeply [ keys %{$bot -> callbacks} ], [],
            'remove callbacks';
};

subtest 'update types recognition' => sub {
  my $upds = [];
  my @list = qw/message callback_query inline_query foobar/;
  my $update_id = 0;

  plan tests => 4;

  push @$upds, (update $_ => $update_id++) for @list;

t/05-updates.t  view on Meta::CPAN

  my $api = bot_api
    json_response {
      ok     => \1,
      result => $upds,
    };

  $bot -> api($api);

  my $correctly_recognized = [];

  $bot -> set_callbacks($_ => updcheck $_) for @list;

  $bot -> start_polling;
  Mojo::IOLoop -> one_tick;
  $bot -> stop_polling;
};

done_testing;

t/06-sync.t  view on Meta::CPAN

    result => [map {update $_ => $update_id++} @UPDATES]
  }), json_response({
    ok => \1,
    result => [update something => 0]
  }), sub { ++ $req_counter };

  $bot -> api($api) -> api -> {async} = 0;

  my @processed;

  # let's make ourselves a callbacks generator to simplify things a little bit
  my $push = sub {
    my $name = shift;

    sub {
      shift; # discard $bot reference
      note "(callback) $name";
      push @processed, $name if ref shift -> {$name} eq 'HASH'
    }
  };

  $bot -> set_callbacks(
    (map { $_ => $_ -> $push } qw/message callback_query something/),
    edited_message => sub {
      ('edited_message' -> $push) -> (@_);

      # This will disable the polling loop once the second update is processed.
      # 'callback_query' will still get processed since it's already retrieved,
      #  but 'something' should never be reached.
      $bot -> stop_polling;
    }
  );

  $bot -> start_polling(interval => 3);

  is $req_counter, 1, 'made 1 request';
  is_deeply \@processed, \@UPDATES, 'processed all updates received during the first iteration';
};

subtest 'Events', sub {
  my $bot = Bot::Telegram -> new;
  $bot -> api(bot_api json_response { ok => \1, result => [update message => 1] });
  $bot -> set_callbacks(message => sub { die 'catch me if you can' });

  $bot -> api -> {async} = 0;

  my $passed;
  $bot -> unsubscribe('callback_error');
  $bot -> on(callback_error => sub {
    note 'inside the callback';
    $bot -> stop_polling;
    $passed = 1;
  });



( run in 1.573 second using v1.01-cache-2.11-cpan-9b1e4054eb1 )