Skip to content

Instantly share code, notes, and snippets.

@Robertof
Last active February 17, 2023 01:31
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save Robertof/d9358b80d95850e2eb34 to your computer and use it in GitHub Desktop.
Save Robertof/d9358b80d95850e2eb34 to your computer and use it in GitHub Desktop.
Basic Telegram bot implementation using WWW::Telegram::BotAPI
#!/usr/bin/env perl
# Basic Telegram Bot implementation using WWW::Telegram::BotAPI
use strict;
use warnings;
use WWW::Telegram::BotAPI;
use utf8;
my $api = WWW::Telegram::BotAPI->new (
token => (shift or die "ERROR: a token is required!\n")
);
# Bump up the timeout when Mojo::UserAgent is used (LWP::UserAgent uses 180s by default)
$api->agent->can ("inactivity_timeout") and $api->agent->inactivity_timeout (45);
my $me = $api->getMe or die;
my ($offset, $updates) = 0;
# The commands that this bot supports.
my $pic_id; # file_id of the last sent picture
my $commands = {
# Example demonstrating the use of parameters in a command.
"say" => sub { join " ", splice @_, 1 or "Usage: /say something" },
# Example showing how to use the result of an API call.
"whoami" => sub {
sprintf "Hello %s, I am %s! How are you?", shift->{from}{username}, $me->{result}{username}
},
# Example showing how to send multiple lines in a single message.
"knock" => sub {
sprintf "Knock-knock.\n- Who's there?\n@%s!", $me->{result}{username}
},
# Example displaying a keyboard with some simple options.
"keyboard" => sub {
+{
text => "Here's a cool keyboard.",
reply_markup => {
keyboard => [ [ "a" .. "c" ], [ "d" .. "f" ], [ "g" .. "i" ] ],
one_time_keyboard => \1 # \1 maps to "true" when being JSON-ified
}
}
},
# Let me identify yourself by sending your phone number to me.
"phone" => sub {
+{
text => "Would you allow me to get your phone number please?",
reply_markup => {
keyboard => [
[
{
text => "Sure!",
request_contact => \1
},
"No, go away!"
]
],
one_time_keyboard => \1
}
}
},
# Test UTF-8
"encoding" => sub { "Привет! こんにちは! Buondì!" },
# Example sending a photo with a known picture ID.
"lastphoto" => sub {
return "You didn't send any picture!" unless $pic_id;
+{
method => "sendPhoto",
photo => $pic_id,
caption => "Here it is!"
}
},
"_unknown" => "Unknown command :( Try /start"
};
# Generate the command list dynamically.
$commands->{start} = "Hello! Try /" . join " - /", grep !/^_/, keys %$commands;
# Special message type handling
my $message_types = {
# Save the picture ID to use it in `lastphoto`.
"photo" => sub { $pic_id = shift->{photo}[0]{file_id} },
# Receive contacts!
"contact" => sub {
my $contact = shift->{contact};
+{
method => "sendMessage",
parse_mode => "Markdown",
text => sprintf (
"Here's some information about this contact.\n" .
"- Name: *%s*\n- Surname: *%s*\n" .
"- Phone number: *%s*\n- Telegram UID: *%s*",
$contact->{first_name}, $contact->{last_name} || "?",
$contact->{phone_number}, $contact->{user_id} || "?"
)
}
}
};
printf "Hello! I am %s. Starting...\n", $me->{result}{username};
while (1) {
$updates = $api->getUpdates ({
timeout => 30, # Use long polling
$offset ? (offset => $offset) : ()
});
unless ($updates and ref $updates eq "HASH" and $updates->{ok}) {
warn "WARNING: getUpdates returned a false value - trying again...";
next;
}
for my $u (@{$updates->{result}}) {
$offset = $u->{update_id} + 1 if $u->{update_id} >= $offset;
if (my $text = $u->{message}{text}) { # Text message
printf "Incoming text message from \@%s\n", $u->{message}{from}{username};
printf "Text: %s\n", $text;
next if $text !~ m!^/[^_].!; # Not a command
my ($cmd, @params) = split / /, $text;
my $res = $commands->{substr ($cmd, 1)} || $commands->{_unknown};
# Pass to the subroutine the message object, and the parameters passed to the cmd.
$res = $res->($u->{message}, @params) if ref $res eq "CODE";
next unless $res;
my $method = ref $res && $res->{method} ? delete $res->{method} : "sendMessage";
$api->$method ({
chat_id => $u->{message}{chat}{id},
ref $res ? %$res : ( text => $res )
});
print "Reply sent.\n";
}
# Handle other message types.
for my $type (keys %{$u->{message} || {}}) {
next unless exists $message_types->{$type} and
ref (my $res = $message_types->{$type}->($u->{message}));
my $method = delete ($res->{method}) || "sendMessage";
$api->$method ({
chat_id => $u->{message}{chat}{id},
%$res
})
}
}
}
@JJ
Copy link

JJ commented Dec 16, 2016

How would it go with the asynchronous interface?

@DRVTiny
Copy link

DRVTiny commented Jan 19, 2017

How would it go with the asynchronous interface?
Well, its very interesting for me too :)

@Robertof
Copy link
Author

Robertof commented Oct 2, 2017

I was just re-reading this gist when I found your comments (for some reason, GitHub didn't notify me!).

I created a (basic) example asynchronous bot too, available at this link.

Also if anyone has further questions, feel free to shoot me up an email at robertof <AT> cpan <DOT> org.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment