EV-Telegram-TDLib

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

Revision history for EV-Telegram-TDLib

0.05    2026-10-20
        Fix inclusive history paging and cancellation from download progress
        Preserve submitted requests across rate-limit retries

0.04    2026-09-22
        Stories, stickers, Stars, gifts, Business, routers, ask(), 429 retry
        SECURITY: an unusable TDLIB_LOG_VERBOSITY no longer logs credentials
        INCOMPATIBLE: database_encryption_key is encoded; a 0.03 key needs
        decode_base64. Invalid arguments croak, and some defaults changed

0.03    2026-08-27
        Mini Apps, forum topics, chat and group management, folders,
        contacts, payments, secret chats, bot provisioning, and
        schema validated call()

0.02    2026-08-26
        Entity text slicing, unicode documentation, GTK4/Tickit/MCP examples

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/. Once the module is installed, run one from
    anywhere with "perl eg/NAME.pl"; from an unpacked distribution "perl
    -Mblib eg/NAME.pl" works after "make", and not before it, since there is
    no blib until then. Credentials come from the environment, see
    "ENVIRONMENT":

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

README  view on Meta::CPAN

    something the module can fix: disable cores where it matters.

    Set database_encryption_key so the local database is encrypted at rest,
    and keep the key out of source control.

    api_id and api_hash identify your application to Telegram. Pass them
    from your environment rather than hardcoding them; new() does not read
    any environment variable itself, but the examples in eg/ take them from
    TD_API_ID and TD_API_HASH.

    The login callbacks receive credentials from wherever you choose to read
    them. If that is a terminal, do not echo the 2FA password: it lands in
    the scrollback, in a screen recording and in whatever the session is
    logged to. eg/01-login.pl shows the shape -- turn echo off through a
    guard object, so an interrupt cannot leave the terminal silent for
    whatever runs next.

    TDLib's log can carry credentials. Above the lowest levels it writes the
    requests it makes, and those include the ones carrying your api_hash and
    bot token; logging in with verbosity raised has been observed to put
    both on stderr in clear. TDLib's own default is 5, so this module sets 1
    at load, and a "TDLIB_LOG_VERBOSITY" it cannot read as a level falls
    back to 1 rather than leaving TDLib at 5. Raise it to debug something
    and whatever collects your stderr collects that too.

REQUIREMENTS
    *   perl 5.12 or later, built with 64-bit integers. Makefile.PL refuses
        to configure when ivsize is below 8: Telegram chat and user ids are

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 eg/01-login.pl   (add -Mblib to run from a built checkout)

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 eg/02-bot-echo.pl   (add -Mblib to run from a built checkout)

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

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

#
# 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. A user account only:
# TDLib refuses to load a chat list for a bot.
#
# 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
#   TD_DATABASE_DIRECTORY   optional, default ./tdlib-db
#
# Run: perl eg/03-list-chats.pl   (add -Mblib to run from a built checkout)

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 eg/04-send-message.pl CHAT_ID [TEXT]
#      (add -Mblib to run from a built checkout)
# TEXT is MarkdownV2, where . ! # - ( ) and the like are reserved: escape
# each with a backslash, or the parse error is all you get back.

use strict;

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 eg/05-download-file.pl FILE_ID
#      (add -Mblib to run from a built checkout)

use strict;
use warnings;
use EV;

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 eg/06-raw-method.pl   (add -Mblib to run from a built checkout)

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 eg/10-webapp-bot.pl   (add -Mblib to run from a built checkout)

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/>. Once the module is installed, run one
from anywhere with C<perl eg/NAME.pl>; from an unpacked distribution
C<perl -Mblib eg/NAME.pl> works after C<make>, and not before it, since
there is no F<blib> until then. Credentials come from the environment,
see L</ENVIRONMENT>:

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

something the module can fix: disable cores where it matters.

Set database_encryption_key so the local database is encrypted at
rest, and keep the key out of source control.

api_id and api_hash identify your application to Telegram. Pass them
from your environment rather than hardcoding them; new() does not read
any environment variable itself, but the examples in F<eg/> take them
from TD_API_ID and TD_API_HASH.

The login callbacks receive credentials from wherever you choose to
read them. If that is a terminal, do not echo the 2FA password: it
lands in the scrollback, in a screen recording and in whatever the
session is logged to. F<eg/01-login.pl> shows the shape -- turn echo
off through a guard object, so an interrupt cannot leave the terminal
silent for whatever runs next.

B<TDLib's log can carry credentials.> Above the lowest levels it writes
the requests it makes, and those include the ones carrying your
api_hash and bot token; logging in with verbosity raised has been
observed to put both on stderr in clear. TDLib's own default is 5, so
this module sets 1 at load, and a C<TDLIB_LOG_VERBOSITY> it cannot read
as a level falls back to 1 rather than leaving TDLib at 5. Raise it to
debug something and whatever collects your stderr collects that too.

=head1 REQUIREMENTS

=over 4

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

=item *

Where a recipe writes C<die> in a callback it is shorthand for "stop
here", not a way to end the program. A callback that dies is caught and
reported, and the loop keeps running, so a real script warns and calls
C<EV::break> instead. See
L<EV::Telegram::TDLib/"ERROR HANDLING">.

=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' },

t/45_required_args.t  view on Meta::CPAN

    like $sent[-1], qr/"text":"42"/, 'a numeric-looking string sends as a String';

    if ($text > 10) { }             # nothing but a comparison
    @sent = ();
    $td->send_message(-100, $text, sub {});
    like $sent[-1], qr/"text":"42"/,
        'and still does after the caller compares it numerically';
    unlike $sent[-1], qr/"text":42\b/, 'never as a bare JSON Number';
}

# --- the login credentials are string slots too. A phone number written
# without quotes is the obvious way to hit this.
{
    my $wait_phone = q({"@type":"updateAuthorizationState",)
                   . q("authorization_state":{"@type":"authorizationStateWaitPhoneNumber"}});
    @sent = ();
    my $c = EV::Telegram::TDLib->new(
        api_id => 1, api_hash => 'x', phone_number => 79991234567,
        database_directory => 't/tmp-authstr', on_error => sub {});
    $c->inject_raw($wait_phone);
    my ($req) = grep { /setAuthenticationPhoneNumber/ } @sent;

xt/argument_poison.t  view on Meta::CPAN

            qw(stringified address base64 passed-through followup execute);
    cmp_ok $n{stringified}, '>=', 100, "leaky plain_text is caught as a stringified ref at $n{stringified} sites";
    cmp_ok $n{address}, '>=', 150, "leaky num is caught as an address at $n{address} sites";
    cmp_ok $n{base64}, '>=', 2, "leaky tl_bytes is caught as base64 at $n{base64} sites";
    cmp_ok $n{'passed-through'}, '>=', 20, "leaky input_file is caught passing the ref through at $n{'passed-through'} sites";
    cmp_ok $n{execute}, '>=', 1, "a leak in a synchronous execute() request is caught at $n{execute} sites";
    cmp_ok $n{followup}, '>=', 1, "a leak in a request sent only after a reply is caught at $n{followup} sites";
    cmp_ok scalar(keys %$cdiverge), '>=', 50, 'leaky plain_text is caught by the overload rerun at '
        . scalar(keys %$cdiverge) . ' sites';
    my %nc = map { $_ => scalar keys %{ $ctor->{classes}{$_} || {} } } qw(stringified base64);
    cmp_ok $nc{stringified}, '>=', 5, "and in the constructor options and credentials at $nc{stringified} sites";
    cmp_ok $nc{base64}, '>=', 1, "including the base64 encryption key at $nc{base64} sites";
    cmp_ok scalar(keys %{ $ctor->{diverge} }), '>=', 5, 'and the constructor overload rerun at '
        . scalar(keys %{ $ctor->{diverge} }) . ' sites';
    diag sprintf 'positive control: %d poisoned and %d overload calls; %d distinct leaks (%s); %d divergences',
        $ccount{calls} // 0, $cocount{calls} // 0, scalar(keys %$control),
        join(', ', map { "$_ $n{$_}" } sort keys %n), scalar keys %$cdiverge;
}

phase('control');

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 2.448 seconds using v1.01-cache-2.11-cpan-3f962061493 )