EV-Telegram-TDLib

 view release on metacpan or  search on metacpan

README  view on Meta::CPAN

    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

README  view on Meta::CPAN

    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.

README  view on Meta::CPAN

        Raise this on a heavily loaded machine or under a sanitizer, where
        everything runs several times slower.

    TDLIB_LOG_VERBOSITY
        TDLib's log verbosity level, applied once when the module is loaded.
        Defaults to 1; TDLib's own default of 5 is very noisy on stderr.

    TD_API_ID, TD_API_HASH, TD_PHONE, TD_BOT_TOKEN, TD_DATABASE_DIRECTORY
        Not the module's API: the credential convention shared by the
        scripts in eg/ and by xt/live_auth.t. The module itself takes
        credentials only as constructor options; see "new(%opt)".

EXAMPLES
    Runnable scripts live in eg/ (from the distribution root: "perl -Mblib
    eg/NAME.pl"; credentials come from the environment, see "ENVIRONMENT"):

    eg/01-login.pl
        user login with phone, SMS code and 2FA; creates the session
        database

    eg/02-bot-echo.pl
        bot login via token; echoes incoming text messages

    eg/03-list-chats.pl
        loads the chat list and prints id and title per chat

eg/01-login.pl  view on Meta::CPAN

#
# Demonstrates: phone_number authorization, the on_code and on_password
# credential callbacks, me(), and a clean close.
#
# This is the script that creates the session database; the other examples
# reuse it. The database directory (default ./tdlib-db) holds the session:
# it is exactly as sensitive as a password, so do not commit it, back it up,
# or leave it world-readable.
#
# Environment:
#   TD_API_ID, TD_API_HASH  application credentials from https://my.telegram.org
#   TD_PHONE                phone number in international format, e.g. +10000000000
#   TD_DATABASE_DIRECTORY   optional, default ./tdlib-db
#
# Run: perl -Mblib eg/01-login.pl

use strict;
use warnings;
use EV;
use EV::Telegram::TDLib;

eg/02-bot-echo.pl  view on Meta::CPAN

# 02-bot-echo.pl - bot that echoes every text message back to its chat
#
# Demonstrates: bot_token authorization (no credential callbacks needed),
# the on_message handler, send_message, and running until Ctrl-C.
#
# The database directory (default ./tdlib-bot-db) holds the session: it is
# exactly as sensitive as a password. A bot session needs its own directory;
# do not point it at the user session created by 01-login.pl.
#
# Environment:
#   TD_API_ID, TD_API_HASH  application credentials from https://my.telegram.org
#   TD_BOT_TOKEN            bot token from BotFather
#   TD_DATABASE_DIRECTORY   optional, default ./tdlib-bot-db
#
# Run: perl -Mblib eg/02-bot-echo.pl

use strict;
use warnings;
use EV;
use EV::Telegram::TDLib;

eg/03-list-chats.pl  view on Meta::CPAN

# 03-list-chats.pl - load the chat list and print id and title of each chat
#
# Demonstrates: load_chats() paged until TDLib reports the list exhausted,
# chats collected through on_chat, and titles read back from the chat()
# cache. Uses the session created by 01-login.pl (or a bot token).
#
# The database directory (default ./tdlib-db) holds the session: it is
# exactly as sensitive as a password.
#
# Environment:
#   TD_API_ID, TD_API_HASH  application credentials from https://my.telegram.org
#   TD_PHONE                phone number in international format, unless TD_BOT_TOKEN
#   TD_BOT_TOKEN            bot token from BotFather, alternative to TD_PHONE
#   TD_DATABASE_DIRECTORY   optional, default ./tdlib-db
#
# Run: perl -Mblib eg/03-list-chats.pl

use strict;
use warnings;
use EV;
use EV::Telegram::TDLib;

eg/04-send-message.pl  view on Meta::CPAN

#
# Demonstrates: send_message with parse_mode => 'markdown' and the default
# wait => 'sent', so the callback fires on updateMessageSendSucceeded and
# carries the final message id. Uses the session created by 01-login.pl
# (or a bot token).
#
# The database directory (default ./tdlib-db) holds the session: it is
# exactly as sensitive as a password.
#
# Environment:
#   TD_API_ID, TD_API_HASH  application credentials from https://my.telegram.org
#   TD_PHONE                phone number in international format, unless TD_BOT_TOKEN
#   TD_BOT_TOKEN            bot token from BotFather, alternative to TD_PHONE
#   TD_DATABASE_DIRECTORY   optional, default ./tdlib-db
#
# Run: perl -Mblib eg/04-send-message.pl CHAT_ID [TEXT]

use strict;
use warnings;
use EV;
use EV::Telegram::TDLib;

eg/05-download-file.pl  view on Meta::CPAN

# Demonstrates: download() with on_progress printing a percentage, and the
# local path reported once is_downloading_completed is reached. Uses the
# session created by 01-login.pl (or a bot token). File ids come from
# document/photo/video message content; they are per-session.
#
# The database directory (default ./tdlib-db) holds the session: it is
# exactly as sensitive as a password. Downloaded files land in the same
# directory tree unless files_directory says otherwise.
#
# Environment:
#   TD_API_ID, TD_API_HASH  application credentials from https://my.telegram.org
#   TD_PHONE                phone number in international format, unless TD_BOT_TOKEN
#   TD_BOT_TOKEN            bot token from BotFather, alternative to TD_PHONE
#   TD_DATABASE_DIRECTORY   optional, default ./tdlib-db
#
# Run: perl -Mblib eg/05-download-file.pl FILE_ID

use strict;
use warnings;
use EV;
use EV::Telegram::TDLib;

eg/06-raw-method.pl  view on Meta::CPAN

#
# Demonstrates: calling TDLib methods the mixins do not wrap.
#   call({...}) is the usual way in: it fills in @type from the method
#   name and rejects an argument name TDLib does not have, so a typo
#   fails here instead of coming back as an opaque server error.
#   execute({...}) is synchronous td_execute: no network, no authorization.
#   send({...}) is asynchronous: the reply arrives on the loop, correlated
#   by @extra. @extra is assigned by send() itself (and returned), so it
#   must never be set by hand; a caller-supplied value is overwritten.
# Both requests used here (getOption, parseTextEntities) work without any
# credentials, so auto_auth is off and no session database is written.
#
# Environment: none required. TD_API_ID/TD_API_HASH are picked up if set,
# but the requests below do not need them.
#
# Run: perl -Mblib eg/06-raw-method.pl

use strict;
use warnings;
use EV;
use EV::Telegram::TDLib;

eg/10-webapp-bot.pl  view on Meta::CPAN

# without writing a page, drive the other side from a user session with
# send_web_app_data (see xt/live_webapp.t).
#
# The Mini App must exist first: create it with /newapp in BotFather and
# point TD_WEBAPP_URL at the URL you gave it.
#
# The database directory (default ./tdlib-bot-db) holds the session: it is
# exactly as sensitive as a password.
#
# Environment:
#   TD_API_ID, TD_API_HASH  application credentials from https://my.telegram.org
#   TD_BOT_TOKEN            bot token from BotFather
#   TD_WEBAPP_URL           the Mini App URL
#   TD_DATABASE_DIRECTORY   optional, default ./tdlib-bot-db
#
# Run: perl -Mblib eg/10-webapp-bot.pl

use strict;
use warnings;
use EV;
use EV::Telegram::TDLib;

lib/EV/Telegram/TDLib.pm  view on Meta::CPAN

=head2 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
L</CAVEATS>. Options:

=over 4

=item api_id, api_hash

Telegram application credentials from https://my.telegram.org. Keep
them in the environment, not in source; see L</SECURITY>.

=item 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.

lib/EV/Telegram/TDLib.pm  view on Meta::CPAN

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 (C<android>, C<ios>, C<macos>,
C<tdesktop>, C<weba>, C<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 C<tdesktop>.

=head2 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.

=head1 AUTHORIZATION

TDLib drives authorization as a state machine reported through
updateAuthorizationState; L</auth_state()> exposes the current state.
With auto_auth on (the default), each state is answered automatically
or routed to a credential callback:

=over 4

=item 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.

=item 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.

lib/EV/Telegram/TDLib.pm  view on Meta::CPAN


=item TDLIB_LOG_VERBOSITY

TDLib's log verbosity level, applied once when the module is loaded.
Defaults to 1; TDLib's own default of 5 is very noisy on stderr.

=item TD_API_ID, TD_API_HASH, TD_PHONE, TD_BOT_TOKEN, TD_DATABASE_DIRECTORY

Not the module's API: the credential convention shared by the scripts
in F<eg/> and by F<xt/live_auth.t>. The module itself takes
credentials only as constructor options; see L</new(%opt)>.

=back

=head1 EXAMPLES

Runnable scripts live in F<eg/> (from the distribution root:
C<perl -Mblib eg/NAME.pl>; credentials come from the environment, see
L</ENVIRONMENT>):

=over 4

=item F<eg/01-login.pl>

user login with phone, SMS code and 2FA; creates the session database

=item F<eg/02-bot-echo.pl>

lib/EV/Telegram/TDLib/Cookbook.pod  view on Meta::CPAN

(TD_API_ID, TD_API_HASH, TD_PHONE, TD_BOT_TOKEN) -- a convention of
these recipes and F<eg/>, not the module's API.

=item *

Every snippet ends up inside a running EV loop; stand-alone recipes
finish with C<EV::run>.

=back

The recipes that need no credentials (formatted text, raw methods,
multiple clients) were run for real against TDLib 1.8.66. The rest
were checked field by field against the pinned TDLib scheme
(td_api.tl at commit 022d60202e446ad1287b9fb68e687c8a0760788b).

=head1 RECIPES

=head2 Logging in as a user, and reusing the session

Problem: authorize a user account with phone number, SMS code and
optional 2FA password -- and do it only once.

lib/EV/Telegram/TDLib/Cookbook.pod  view on Meta::CPAN

    print "TDLib $version->{value}\n";   # an optionValueString reply

    my $extra;
    $extra = $td->send({ '@type' => 'getOption', name => 'version' }, sub {
        my ($res, $err) = @_;
        die "getOption failed: $err->{message}\n" if $err;
        print "async reply: $res->{value} (\@extra $extra)\n";
        $td->close(sub { EV::break });
    });

This recipe runs with C<auto_auth =E<gt> 0> and no credentials at
all; F<eg/06-raw-method.pl> is exactly that.

=head2 Timing out a request

Problem: do not wait forever for a reply that may never come.

C<send> accepts a C<timeout> in seconds. On expiry the callback
fails with a synthetic error -- code -1, message C<timeout>:

    $td->send({ '@type' => 'getMe' }, sub {

lib/EV/Telegram/TDLib/Schema.pm  view on Meta::CPAN

    sendEmailAddressVerificationCode             => 'email_address',
    sendEphemeralMessage                         => 'chat_id topic_id receiver_user_id callback_query_id reply_to sending_id only_preview reply_markup input_message_content',
    sendGift                                     => 'gift_id owner_id text is_private pay_for_upgrade',
    sendGiftPurchaseOffer                        => 'owner_id gift_name price duration paid_message_star_count',
    sendGroupCallMessage                         => 'group_call_id text paid_message_star_count',
    sendInlineQueryResultMessage                 => 'chat_id topic_id reply_to options query_id result_id hide_via_bot',
    sendMessage                                  => 'chat_id topic_id reply_to options reply_markup input_message_content',
    sendMessageAlbum                             => 'chat_id topic_id reply_to options input_message_contents',
    sendMessageViewMetrics                       => 'chat_id message_id time_in_view_ms active_time_in_view_ms height_to_viewport_ratio_per_mille seen_range_ratio_per_mille',
    sendPassportAuthorizationForm                => 'authorization_form_id types',
    sendPaymentForm                              => 'input_invoice payment_form_id order_info_id shipping_option_id credentials tip_amount',
    sendPhoneNumberCode                          => 'phone_number settings type',
    sendPhoneNumberFirebaseSms                   => 'token',
    sendQuickReplyShortcutMessages               => 'chat_id shortcut_id sending_id',
    sendResoldGift                               => 'gift_name owner_id price',
    sendRichMessageDraft                         => 'chat_id forum_topic_id draft_id message',
    sendTextMessageDraft                         => 'chat_id forum_topic_id draft_id text',
    sendWebAppCustomRequest                      => 'bot_user_id method parameters',
    sendWebAppData                               => 'bot_user_id button_text data',
    setAccentColor                               => 'accent_color_id background_custom_emoji_id',
    setAccountTtl                                => 'ttl',

lib/EV/Telegram/TDLib/Schema.pm  view on Meta::CPAN

    messageGiveaway                                => { parameters => 'giveawayParameters', prize => 'GiveawayPrize', sticker => 'sticker' },
    messageGiveawayPrizeStars                      => { sticker => 'sticker' },
    messageGiveawayWinners                         => { prize => 'GiveawayPrize' },
    messageGroupCall                               => { other_participant_ids => 'array:MessageSender' },
    messageInteractionInfo                         => { reactions => 'messageReactions', reply_info => 'messageReplyInfo' },
    messageInvoice                                 => { paid_media => 'PaidMedia', paid_media_caption => 'formattedText', product_info => 'productInfo' },
    messageLinkInfo                                => { message => 'message', topic_id => 'MessageTopic' },
    messageLiveLocation                            => { location => 'liveLocation' },
    messageLocation                                => { location => 'location' },
    messagePaidMedia                               => { caption => 'formattedText', media => 'array:PaidMedia' },
    messagePassportDataReceived                    => { credentials => 'encryptedCredentials', elements => 'array:encryptedPassportElement' },
    messagePassportDataSent                        => { types => 'array:PassportElementType' },
    messagePaymentRefunded                         => { owner_id => 'MessageSender' },
    messagePaymentSuccessfulBot                    => { order_info => 'orderInfo' },
    messagePhoto                                   => { caption => 'formattedText', photo => 'photo', video => 'video' },
    messagePoll                                    => { description => 'formattedText', media => 'PollMedia', poll => 'poll' },
    messagePollOptionAdded                         => { text => 'formattedText' },
    messagePollOptionDeleted                       => { text => 'formattedText' },
    messagePositions                               => { positions => 'array:messagePosition' },
    messagePremiumGiftCode                         => { creator_id => 'MessageSender', sticker => 'sticker', text => 'formattedText' },
    messageProximityAlertTriggered                 => { traveler_id => 'MessageSender', watcher_id => 'MessageSender' },

lib/EV/Telegram/TDLib/Schema.pm  view on Meta::CPAN

    passportElementPersonalDetails                 => { personal_details => 'personalDetails' },
    passportElementRentalAgreement                 => { rental_agreement => 'personalDocument' },
    passportElementTemporaryRegistration           => { temporary_registration => 'personalDocument' },
    passportElementUtilityBill                     => { utility_bill => 'personalDocument' },
    passportElements                               => { elements => 'array:PassportElement' },
    passportElementsWithErrors                     => { elements => 'array:PassportElement', errors => 'array:passportElementError' },
    passportRequiredElement                        => { suitable_elements => 'array:passportSuitableElement' },
    passportSuitableElement                        => { type => 'PassportElementType' },
    passwordState                                  => { recovery_email_address_code_info => 'emailAddressAuthenticationCodeInfo' },
    paymentForm                                    => { product_info => 'productInfo', type => 'PaymentFormType' },
    paymentFormTypeRegular                         => { additional_payment_options => 'array:paymentOption', invoice => 'invoice', payment_provider => 'PaymentProvider', saved_credentials => 'array:savedCredentials', saved_order_info => 'orderInfo' }...
    paymentFormTypeStarSubscription                => { pricing => 'starSubscriptionPricing' },
    paymentReceipt                                 => { product_info => 'productInfo', type => 'PaymentReceiptType' },
    paymentReceiptTypeRegular                      => { invoice => 'invoice', order_info => 'orderInfo', shipping_option => 'shippingOption' },
    personalDetails                                => { birthdate => 'date' },
    personalDocument                               => { files => 'array:datedFile', translation => 'array:datedFile' },
    phoneNumberAuthenticationSettings              => { firebase_authentication_settings => 'FirebaseAuthenticationSettings' },
    phoneNumberInfo                                => { country => 'countryInfo' },
    photo                                          => { minithumbnail => 'minithumbnail', sizes => 'array:photoSize' },
    photoSize                                      => { photo => 'file' },
    pingProxy                                      => { proxy => 'proxy' },

lib/EV/Telegram/TDLib/Schema.pm  view on Meta::CPAN

    sendCallRating                                 => { call_id => 'InputCall', problems => 'array:CallProblem' },
    sendChatAction                                 => { action => 'ChatAction', topic_id => 'MessageTopic' },
    sendEphemeralMessage                           => { input_message_content => 'InputMessageContent', reply_markup => 'ReplyMarkup', reply_to => 'InputMessageReplyTo', topic_id => 'MessageTopic' },
    sendGift                                       => { owner_id => 'MessageSender', text => 'formattedText' },
    sendGiftPurchaseOffer                          => { owner_id => 'MessageSender', price => 'GiftResalePrice' },
    sendGroupCallMessage                           => { text => 'formattedText' },
    sendInlineQueryResultMessage                   => { options => 'messageSendOptions', reply_to => 'InputMessageReplyTo', topic_id => 'MessageTopic' },
    sendMessage                                    => { input_message_content => 'InputMessageContent', options => 'messageSendOptions', reply_markup => 'ReplyMarkup', reply_to => 'InputMessageReplyTo', topic_id => 'MessageTopic' },
    sendMessageAlbum                               => { input_message_contents => 'array:InputMessageContent', options => 'messageSendOptions', reply_to => 'InputMessageReplyTo', topic_id => 'MessageTopic' },
    sendPassportAuthorizationForm                  => { types => 'array:PassportElementType' },
    sendPaymentForm                                => { credentials => 'InputCredentials', input_invoice => 'InputInvoice' },
    sendPhoneNumberCode                            => { settings => 'phoneNumberAuthenticationSettings', type => 'PhoneNumberCodeType' },
    sendResoldGift                                 => { owner_id => 'MessageSender', price => 'GiftResalePrice' },
    sendRichMessageDraft                           => { message => 'inputRichMessage' },
    sendTextMessageDraft                           => { text => 'formattedText' },
    sentGiftRegular                                => { gift => 'gift' },
    sentGiftUpgraded                               => { gift => 'upgradedGift' },
    session                                        => { device_type => 'SessionDeviceType' },
    sessions                                       => { sessions => 'array:session' },
    setAccountTtl                                  => { ttl => 'accountTtl' },
    setArchiveChatListSettings                     => { settings => 'archiveChatListSettings' },

xt/eg_run.t  view on Meta::CPAN

use strict;
use warnings;
use Test::More;

plan skip_all => 'author test: set AUTHOR_TESTING=1' unless $ENV{AUTHOR_TESTING};
plan tests => 5;

# eg/06 needs no credentials and no network: execute() is local and the
# async getOption is answered by TDLib itself, so it can run for real.
# Compiling alone did not catch a wrong reply deref; running does.
my ($out, $rc);
eval {
    local $SIG{ALRM} = sub { die "timeout\n" };
    alarm 30;
    $out = `"$^X" -Mblib eg/06-raw-method.pl 2>&1`;
    $rc = $?;
    alarm 0;
};



( run in 1.702 second using v1.01-cache-2.11-cpan-007c89162af )