NAME
Bot::Telegram - a micro^W nano framework for creating Telegram bots based on WWW::Telegram::BotAPI
VERSION
version 1.10
SYNOPSIS
#!/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!" },
sub {
my ($ua, $tx) = @_;
if ($tx -> res -> json -> {ok}) {
say "> Greeted user $user";
}
}
);
},
edited_message => sub {
my ($bot, $update) = @_;
my $user = $$update{edited_message}{from}{username};
say "> User $user just edited their message";
},
);
# You might want to increase/disable inactivity timeouts for long polling
$bot
-> api
-> agent
-> inactivity_timeout(0);
# Maybe remove some default subscribers...
$bot -> unsubscribe('callback_error');
# Or replace them with custom ones...
$bot -> on(callback_error => sub {
my $error = pop;
$bot -> log -> fatal("update processing failed: $error");
exit 255;
});
# Start long polling
$bot -> start_polling;
Mojo::IOLoop -> start;
DESCRIPTION
This package provides a tiny wrapper around WWW::Telegram::BotAPI that takes care of the most annoying boilerplate, especially for the long polling scenario.
Supports both synchronous and asynchronous modes of WWW::Telegram::BotAPI.
Just like the aforementioned 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, ok
, result
, description
, error_code
[presence], getUpdates
POST body format and the assumption that getUpdates
response would be an array of update objects, each consisting of two fields - 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.
RATIONALE
WWW::Telegram::BotAPI (which this module heavily depends on) is a low-level thing not responsible for sorting updates by their types, setting up a long polling loop, etc, and using it alone might not be sufficient for complex applications. Even the simple "SYNOPSIS" example will quickly become an if-for-eval mess, should we rewrite it in pure WWW::Telegram::BotAPI, and maintaining/extending such a codebase would be a disaster.
All other similar libraries available on CPAN are either outdated, or incomplete, or... not very straightforward (imo), so I made my own!
EVENTS
Bot::Telegram inherits all events from Mojo::EventEmitter and can emit the following new ones.
callback_error
$bot -> on(callback_error => sub {
my ($bot, $update, $error) = @_;
warn "Update processing failed: $error";
});
Emitted when a callback dies.
Default subscriber will log the error message using "log" with the warn
log level:
[1970-01-01 00:00:00.00000] [12345] [warn] Update processing failed: error details here
polling_error
$bot -> on(polling_error => sub {
my ($bot, $tx, $type) = @_;
});
Emitted when a 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 "stop_polling" explicitly:
$bot -> on(polling_error => sub { $bot -> stop_polling });
In synchronous mode, $tx
will be a plain hash ref. The actual result of "parse_error" in WWW::Telegram::BotAPI is available as the 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 "parse_error" in WWW::Telegram::BotAPI, meaning you will always receive same $type
values for same errors in both synchronous and asynchronous modes.
See "parse_error" in WWW::Telegram::BotAPI for the list of error types and their meanings.
Default subscriber will log the error message using "log" with the warn
log level:
[1970-01-01 00:00:00.00000] [12345] [warn] Polling failed (error type: $type): error details here
unknown_update
$bot -> on(unknown_update => sub {
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. $self -> callbacks -> {$update_type}
is not a coderef).
Exists mostly for debugging purposes.
There are no default subscribers to this event.
PROPERTIES
Bot::Telegram inherits all properties from Mojo::EventEmitter and implements the following new ones.
api
my $api = $bot -> api;
$bot -> api($api);
WWW::Telegram::BotAPI instance used by the bot. Can be initialized via the "init_api" method, or set directly.
callbacks
my $callbacks = $bot -> callbacks;
$bot -> callbacks($callbacks);
Hash reference containing callbacks for different update types.
While you can manipulate it directly, "set_callbacks" and "remove_callbacks" methods provide a more convinient interface.
current_update
my $update = $bot -> current_update;
say "User $$update{message}{from}{username} says: $$update{message}{text}";
Update that is currently being processed.
ioloop
$loop = $bot -> ioloop;
$bot -> ioloop($loop);
A Mojo::IOLoop object used to delay execution in synchronous mode, defaults to a new Mojo::IOLoop object.
log
$log = $bot -> log;
$bot -> log($log);
A Mojo::Log instance used for logging, defaults to a new Mojo::Log object with log level set to info
.
polling_config
$bot -> polling_config($cfg);
$cfg = $bot -> polling_config;
See $cfg
in "start_polling".
METHODS
Bot::Telegram inherits all methods from Mojo::EventEmitter and implements the following new ones.
api_request
$bot -> api_request('getMe');
Just a proxy function for the underlying "api_request" in WWW::Telegram::BotAPI.
The above statement is basically equivalent to:
$bot -> api -> api_request('getMe');
except that it's shorter and adds another entry to your call stack.
api_request_p
$p = $bot -> api_request_p('getMe');
$p -> then(sub {
my ($ua, $tx) = @_;
say 1 if $res -> json -> {ok}; # always true
}) -> catch(sub {
my ($ua, $tx) = @_;
if (my $err = $tx -> error) {
die "$$err{code} response: $$err{message}"
if $$err{code};
die "Connection error: $$err{message}";
} else {
warn 'Action failed!';
say {STDERR} $tx -> res -> json -> {description};
}
});
A promisified wrapper for the underlying "api_request" in WWW::Telegram::BotAPI. The promise is rejected if there is an error
in $tx
or response is not ok
. For both resolve and reject scenarios, the callback receives ($ua, $tx)
from normal "api_request" in WWW::Telegram::BotAPI.
init_api
$bot = $bot -> init_api(%args);
Automatically creates a WWW::Telegram::BotAPI instance.
%args
will be proxied to "new" in WWW::Telegram::BotAPI.
For most use cases you only want to set $args{token}
to your bot's API token and leave everything else default.
NOTE: the WWW::Telegram::BotAPI instance created by "init_api" defaults to the asynchronous mode.
Exceptions
is_async
my $is_async = $bot -> is_async;
Returns true if the underlying WWW::Telegram::BotAPI instance is in asynchronous mode.
Exceptions
is_polling
my $is_polling = $bot -> is_polling;
Returns true if the bot is currently in the long polling state.
process_update
$bot = $bot -> process_update($update);
Process a single update and store it in "current_update".
This function will not die
regardless of the operation success. Instead, the "callback_error" event is emitted if things go bad.
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.
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.
set_webhook
$bot = $bot -> set_webhook($config);
$bot = $bot -> set_webhook($config, $cb);
Set a webhook. All arguments will be proxied to "api_request" in WWW::Telegram::BotAPI.
This function ensures that actual setWebhook
request will not be made as long as the polling loop is active:
eval { $bot -> set_webhook($config) };
if ($@ -> isa('Bot::Telegram::X::InvalidStateError')) {
$bot -> stop_polling;
$bot -> set_webhook($config);
}
For deleting the webhook, just use plain API calls:
$bot -> api_request(deleteWebhook => { drop_pending_updates => $bool }, sub { ... });
Exceptions
Bot::Telegram::X::InvalidArgumentsError
-
No config provided
Bot::Telegram::X::InvalidStateError
-
Disable long polling first
shift_offset
$bot = $bot -> shift_offset;
Recalculate the current offset
for long polling.
Set it to the ID of "current_update" plus one, if current update ID is greater than or equal to the current value.
This is done automatically inside the polling loop ("start_polling"), but the method is made public, if you want to roll your own custom polling loop for some reason.
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 "log" level to trace
to see additional debugging information.
Arguments
$cfg
-
A hash ref containing getUpdates options. Note that the offset parameter is automatically incremented - whenever an update is processed (whether successfully or not), the internally stored
offset
value becomes update ID plus one, IF update ID is greater than or equal to it. The initial offset is zero by default.The config is persistent between polling restarts and is available as "polling_config".
$bot -> start_polling($cfg); # ... $bot -> stop_polling; # ... $bot -> start_polling; # will reuse the previous config, offset preserved # ... say $bot -> polling_config eq $cfg; # 1
If none is provided and "polling_config" is empty, a default config will be generated:
{ timeout => 20, offset => 0 }
- restart
-
Set to true if the loop is already running, otherwise an exception will be thrown.
- interval
-
Interval in seconds between polling requests.
Floating point values are accepted (timers are set using "timer" in Mojo::IOLoop).
Default value is 0.3 (300ms).
Exceptions
stop_polling
$bot = $bot -> stop_polling;
Stop long polling.
SEE ALSO
Bot::ChatBots::Telegram - another library built on top of WWW::Telegram::BotAPI
Telegram::Bot - another, apparently incomplete, Telegram Bot API interface
Telegram::BotKit - provides utilities for building reply keyboards and stuff, also uses WWW::Telegram::BotAPI
WWW::Telegram::BotAPI - lower level Telegram Bot API library used here
AUTHOR
Vasyan <somerandomtext111@gmail.com>
COPYRIGHT AND LICENSE
This software is copyright (c) 2024 by Vasyan.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.