EV-Telegram-TDLib
view release on metacpan or search on metacpan
EV::Telegram::TDLib binds TDLib's tdjson C interface to the EV event
loop. A dedicated reader thread blocks in td_receive, copies each JSON
result, and wakes the loop through ev_async; the loop decodes,
correlates replies to pending requests by @extra, drives the
authorization state machine, maintains user and chat caches, and calls
your handlers.
Asynchronous callbacks follow the family idiom: they receive "($result,
$err)" where $err is undef on success and a decoded TDLib error object
on failure. A TDLib error is never thrown; see "CONVENTIONS" for what
does croak.
Requires a perl with 64-bit integers: Telegram ids are int64 and message
ids are shifted left by 20 bits, so they must never round-trip through
an NV. The Makefile refuses to build otherwise.
The bundled TDLib is 1.8.66, pinned by Alien::TDLib at commit
022d60202e446ad1287b9fb68e687c8a0760788b.
CONSTRUCTOR
new(%opt)
Creates a client and registers it in a process-global registry. The
client is held under a strong reference until close() completes; see
"CAVEATS". Options:
api_id, api_hash
Telegram application credentials from https://my.telegram.org. Keep
them in the environment, not in source; see "SECURITY".
phone_number
Phone number in international format for user authorization. Used
when the state machine reaches authorizationStateWaitPhoneNumber,
unless bot_token is present. Setting on_qr as well does not override
it: a QR link is requested only when on_qr is set and no
phone_number was given.
bot_token
Bot token from BotFather. When present it is sent automatically at
authorizationStateWaitPhoneNumber and no further credential
callbacks are needed.
database_directory
Session and database directory. Default "tdlib-db". See "SECURITY".
files_directory
Downloaded files directory. Defaults to database_directory.
database_encryption_key
Encryption key for the local database. Empty by default; set it.
use_test_dc
Use the Telegram test data centers instead of production. Always set
this in tests.
use_file_database, use_chat_info_database, use_message_database,
use_secret_chats
TDLib feature switches, all defaulting to true.
application_name
The platform identifier sent with every Mini App request, which the
app receives as "tgWebAppPlatform". Defaults to "tdesktop". See
"MINI APPS" for why the value matters and what it may contain.
system_language_code, device_model, system_version, application_version
Client identification sent with setTdlibParameters. Defaults: "en",
"EV::Telegram::TDLib", $^O, this distribution's version.
auto_auth
Drive the authorization state machine automatically (default true).
With auto_auth false, only the login and close lifecycle
continuations run; every credential step is left to you via send().
register
Hashref "{ first_name => ..., last_name => ... }". When set,
authorizationStateWaitRegistration is answered with registerUser;
without it the state fails login.
on_update, on_error, on_close, on_user, on_chat, on_message,
on_connection_state
Update handlers; see "UPDATES" and the mixin methods below.
on_code, on_password, on_email, on_email_code, on_qr
Authorization credential callbacks; see "AUTHORIZATION".
CONVENTIONS
Three patterns run through the whole interface. Knowing them saves
reading 300 method signatures.
Cache readers against remote getters
"chat($chat_id)" and "user($user_id)" are synchronous cache reads: they
take no callback, do no I/O, and return undef for something the client
has not seen. Everything else named after a noun -- "folder", "topic",
"secret_chat", "supergroup", "basic_group", "message", "file" -- is an
asynchronous getter that takes a callback and asks TDLib.
The two cache readers predate the rest and are kept as they are because
renaming a method already published on CPAN would break working code.
When you want the server's answer for a chat rather than the cached one,
use "fetch_chat($chat_id, $cb)".
Booleans
A method that turns something on or off takes the flag as its last
positional argument and defaults to true, so "pin_topic($chat, $id)"
pins and "pin_topic($chat, $id, 0)" unpins. That covers close_topic,
pin_topic, pin_chat, mark_unread, hide_general_topic, folder_tags,
pause_download, protect_content, the supergroup switches, and the
process_join_request pair.
Two older methods invert through an option instead ("block_user($id,
unblock => 1)", "react(..., remove => 1)"), and a few pairs are separate
methods where the two directions differ in more than a flag:
"mute"/"unmute", "archive"/"unarchive", "enable_proxy"/"disable_proxy".
Identifiers
TL "int64" values cross the JSON interface as strings, because a number
would lose precision above 2**53. This module does that for you, and
hands them back as strings: session ids, callback and inline query ids,
profile photo ids, custom emoji ids and Web App launch ids are all
strings you should keep as strings. Chat, message and user ids are
"int53" and stay numbers.
Secret mixin
End-to-end encrypted chats. A secret chat is a separate object from the
chat that displays it: creating one yields a chat whose type is
chatTypeSecret, and the methods below take the secret chat id found in
that type, not the chat id.
Secret chats live only in the local database. They are not on the
server, cannot be read from another device, and do not survive losing
the database.
new_secret_chat($user_id, $cb), open_secret_chat($secret_chat_id, $cb), secret_chat($secret_chat_id, $cb), close_secret_chat($secret_chat_id, $cb)
Start a secret chat with a user, reopen a known one, read its state, and
close it.
search_secret_messages($query, %opt, $cb)
Searches the local database, since secret messages exist nowhere else.
Options: "chat_id" to scope to one chat, "filter" (a
searchMessagesFilter name, with or without the prefix), "offset",
"limit".
set_database_encryption_key($key, $cb), session_accepts_secret_chats($session_id, $on, $cb)
Change the key the local database is encrypted with, and choose whether
a logged-in session may accept secret chats at all. Losing the key loses
every secret chat with it, as nothing on the server can restore them.
MINI APPS
A Mini App (Telegram also calls it a Web App) is a web page a bot
offers, opened inside a Telegram client. TDLib does not render it. It
resolves the app, returns a URL and a launch id, and relays the data the
page sends back; hosting a webview and loading the URL is the
application's job. Nothing here needs a browser if all you want is the
data channel.
The usual flow is:
$td->web_app($bot_id, 'probe', sub {
my ($found, $err) = @_;
...
});
$td->open_web_app($chat_id, $bot_id, $button_url, sub {
my ($info, $err) = @_;
# hand $info->{url} to a webview, keep $info->{launch_id}
});
$td->close_web_app($launch_id, sub { });
Data flows back either through "send_web_app_data($bot_user_id,
$button_text, $data, $cb)", which a client calls on the page's behalf
and the bot receives through "on_web_app_data($cb)", or through
"answer_web_app_query($query_id, \%result, $cb)" for the inline variant.
The platform identifier
"application_name" is not a free-form label. It is sent to Telegram as
the platform string and handed to the page as "tgWebAppPlatform".
Telegram accepts 0-64 characters from "A-Za-z0-9_" and rejects anything
else with "PLATFORM_INVALID", an error that names nothing near the real
cause; a hyphen is the easy way to trip it. This module validates the
value when the client is constructed, so the failure arrives with an
explanation instead.
Any accepted value works, but the value still matters. Real clients send
a conventional identifier ("android", "ios", "macos", "tdesktop",
"weba", "webk") and Mini App pages branch on it to pick layout, theming
and available features. An invented name passes validation and then
lands in whatever an app does with an unrecognised platform. The default
is "tdesktop".
Launch URLs carry credentials
The URL returned by open_web_app and the web_app_*_url methods has the
signed init data in its fragment: the user's name, username, photo URL
and an authentication hash. It is a credential. Do not log it, paste it
into a bug report, or store it anywhere the page itself would not go.
AUTHORIZATION
TDLib drives authorization as a state machine reported through
updateAuthorizationState; "auth_state()" exposes the current state. With
auto_auth on (the default), each state is answered automatically or
routed to a credential callback:
authorizationStateWaitTdlibParameters
setTdlibParameters is sent automatically from the constructor
options. No callback. An error reply (bad api credentials, an
unwritable database_directory) fails login: the values come from the
constructor, so there is no interactive channel to retry through.
authorizationStateWaitPhoneNumber
bot_token is sent when given; otherwise requestQrCodeAuthentication
when on_qr is set and no phone_number was given; otherwise the
phone_number is sent. No callback in any branch. An error reply (an
invalid phone number or bot token) fails login, for the same reason
as above.
authorizationStateWaitCode
"on_code" receives "($info, $submit)": $info is the decoded
authenticationCodeInfo, $submit is a code ref that sends the code.
The split exists so the code can come from anywhere (a prompt, a
GUI, a queue) without blocking the loop. A missing callback fails
login.
A rejected submission does not fail login: TDLib stays in the state
after an error reply (a mistyped code, an expired one), so the
handler is called again as "($info, $submit, $err)" with the decoded
error as the third argument, and may submit a corrected value. To
give up instead, close the client.
authorizationStateWaitPassword
"on_password" receives "($info, $submit)"; $info carries the
password_hint. A missing callback fails login. A rejected password
re-asks with the error as a third argument, as above.
authorizationStateWaitEmailAddress, authorizationStateWaitEmailCode
"on_email" and "on_email_code", same "($info, $submit)" shape and
the same retry-on-error behaviour.
authorizationStateWaitOtherDeviceConfirmation
"on_qr" receives "($link)" only. The signature is deliberately
asymmetric: QR confirmation has nothing to submit, the other device
confirms the login, so there is no $submit callback.
authorizationStateWaitRegistration
Answered automatically from the register option; without it login
fails. An error reply from registerUser fails login: like the other
automatic steps, it has no interactive channel.
authorizationStateWaitPremiumPurchase
Cannot be satisfied programmatically; login fails with an error.
( run in 1.564 second using v1.01-cache-2.11-cpan-b16cb0d3907 )