Official MadelineProto channel. Italian Channel: @MadelineProtoIta Group: @pwrtelegramgroup
I've released MadelineProto 8.0.0-beta136, it features many fixes, including fixes for proxies and a fix to avoid reporting the same error more than once in a minute.
Also, as mentioned in the previous post, I completely removed all deprecated APIs, including the legacy array settings and Generator polyfills.
These APIs were deprecated and due to be removed a long time ago, but in case you haven't switched over yet, here's a quick recap on how to upgrade:
- Update to php 8.1.17+ or php 8.2.4+ (PHP 8.2.9 is strongly recommended)
- Remove all calls to async(false), async(true), all yields and unwrap all code wrapped in loop(function ()...) blocks (because async is now always enabled by default, and you can use async functions without yield since php 8.1).
- Replace legacy loops with the new loop API from daniil.it/loop
- Replace array settings with object settings, for example for the API ID:
Before:$settings = ['app_info' => ['api_id' => 123456, 'api_hash' => 'xxx']];
After:$settings = (new \danog\MadelineProto\Settings\AppInfo) ->setApiId(124) ->setApiHash('xx');
See docs.madelineproto.xyz/docs/SETTINGS.html and docs.madelineproto.xyz for more info.
Finished writing my own OGG muxer in pure PHP, for easy conversion of songs to the maximum possible bitrate, for the best possible VoIP quality :)
Читать полностью…MadelineProto 8.0.0-beta130 was released!
Features:
- Add CallbackQuery
, ButtonQuery
, ChatButtonQuery
, InlineButtonQuery
, GameQuery
, ChatGameQuery
, InlineGameQuery
simple update classes with many useful properties and methods:
- $query->queryId
: Query ID
- $query->userId
: ID of the user that pressed the button
- $query->chatInstance
: Global identifier, uniquely corresponding to the chat to which the message with the callback button was sent. Useful for high scores in games.
- $query->data
: for ButtonQuery
and subclasses, data associated with the callback button.
- $query->matches
: for ButtonQuery
and subclasses, regex matches, if a filter regex is present.
- $query->chatId
: for Chat*Query
classes, chat where the inline keyboard was sent.
- $query->messageId
: for Chat*Query
classes, message ID.
- $query->gameShortName
: for GameQuery
and subclasses, short name of a Game to be returned, serves as the unique identifier for the game
- $query->inlineMessageId
: for Inline*Query
and subclasses, inline message ID
- $query->editText(...)
: for Chat*Query
classes, edits the message's text.
- $query->answer(string $message, bool $alert = false, ?string $url = null, int $cacheTime = 5 * 60)
- Answer an inline query
- Add translate
and editText
methods to Message
class
- Add FilterButtonQueryData
filter for ButtonQuery
- Adapt FilterFromAdmin
and FilterRegex
filter for ButtonQuery
- Add FromAdminOrOutgoing
simple filter
- Add markdownUrlEscape
method!
Fixes:
- Fix usage of inputMediaPhotoExternal
and inputMediaDocumentExternal
with messages.sendMultiMedia
, messages.sendMedia
, messages.uploadMedia
- Fix IsReplyToSelf
simple filter
- Fix CustomEmoji
media type
- Fix bot API conversion for messages forwarded from private channels (+ other bot API conversion fixes)
- Fix pagination hash generation
- Improve markdownCodeblockEscape
method
- Properly terminate IPC worker and delete session in case of session revocation
- Fix CDN file downloads
- Fix connection to testmode
- Properly wait when making getPwrChat requests
- Many other fixes!
MadelineProto was updated (8.0.0-beta126)!
Features:
- Add addReaction
, delReaction
, getOurReactions, pin, unpin
bound Message methods by @J_A_V_A
Fixes:
- Allow inclusion of traits, interfaces in plugins
- Fix creation of sessions
MadelineProto was updated (8.0.0-beta121)!
Features:
- Added StaticSticker, AnimatedSticker, VideoSticker media classes.
Fixes:
- Fix localization of some error messages
- Make sure the event handler class is in scope before unserializing
- Other various fixes
I've released MadelineProto 8 beta 117!
Features:
- Add FilterTextCaseInsensitive filter
Fixes:
- Multiple file-related fixes
You can also post stories using MadelineProto, here's a simple example that reposts messages as stories: https://github.com/danog/MadelineProto/blob/v8/examples/bot.php#L164
$media can be an update a message or inputMedia constructor with a URL/file path just like the one accepted by sendMedia!
Another example:
$media = [
'_' => 'inputMediaUploadedPhoto',
'file' => 'faust.jpg'
];
$MadelineProto->stories->sendStory(
media: $media,
caption: "This story was posted using [MadelineProto](/channel/MadelineProto)!",
parse_mode: ParseMode::MARKDOWN,
privacy_rules: [['_' => 'inputPrivacyValueAllowAll']]
);
You can now download any Telegram Story using @tgstories_dl_bot!
Just send it a /dlStory @username command, and it will generate download links for all stories on the user's profile!
@tgstories_dl_bot is powered by @MadelineProto, and it's fully open source!
You can subscribe to my stories and add @danogentili as a contact to receive early updates about my projects ❤️
Читать полностью…MadelineProto 8.0.0-beta110 was released!
Fixes:
- Fix thumbnail parsing
- Fix parsing of round videos in some conditions
- Fix content-type/extension of getDownloadLink/downloadToBrowser
Also, check out the farsi channel for a translation of all the posts describing the features of MadelineProto 8.0.0-beta100+!
به کانال فارسی مدلین برای دیدن توضیحات تمام ویژگی های MadelineProto 8.0.0-beta100+ سر بزنید!
/channel/MadeLine_Farsi/3917
Released beta108 with a few additional features and fixes:
Features:
- Allow specifying command types in FilterCommand
Fixes:
- Fix localization issues
- Fix the IPC server when using a standalone madeline.phar
Note: to get a download link for bot API file IDs as a bot (files up to 4gb), the file name, size and mime type should also be provided:
<?php
if (!file_exists('madeline.php')) {
copy('https://phar.madelineproto.xyz/madeline.php', 'madeline.php');
}
include 'madeline.php';
$MadelineProto = new \danog\MadelineProto\API('session.madeline');
$MadelineProto->botLogin('token');
$botApiFileId = '...';
$fileName = '...';
$fileSize = 123..;
$mimeType = '...';
$link = $MadelineProto->getDownloadLink($botApiFileId, size: $fileSize, name: $fileName, mime: $mimeType);
class MyEventHandler extends SimpleEventHandler {Читать полностью…
/**
* Gets a download link for any file up to 4GB!
*/
#[FilterCommand('dl')]
public function downloadLink(Incoming&Message $message): void
{
if (!$message->replyToMsgId) {
$message->reply("This command must reply to a media message!");
return;
}
$message = $message->getReply();
if (!$message instanceof Message || !$message->media) {
$message->reply("This command must reply to a media message!");
return;
}
$message->reply("Download link: ".$message->media->getDownloadLink());
}
}
MadelineProto was updated (8.0.0-beta101)!
After introducing plugins », bound methods », filters », a built-in cron system », IPC support for the event handler » and automatic static analysis for event handler code » in beta100, beta101 brings some bugfixes and the getDownloadLink
function!getDownloadLink
can be used to fetch a direct download link for any file up to 4GB, even using a bot API file ID!
Other features:
- Added an openFileAppendOnly
function, that can be used to asynchronously open a file in append-only mode!
Fixes:
- Improved the markdownEscape
function!
- Translated even more MadelineProto UI elements!
- Improve the static analyzer.
- Fixed some bugs in simple filters.
- Relax markdown parser.
Here's an example on how to use the new getDownloadLink()
function:
<?php
if (!file_exists('madeline.php')) {
copy('https://phar.madelineproto.xyz/madeline.php', 'madeline.php');
}
include 'madeline.php';
$MadelineProto = new \danog\MadelineProto\API('session.madeline');
$MadelineProto->botLogin('token');
$fileId = '...'; // bot API file ID
$link = $MadelineProto->getDownloadLink($fileId);
$MessageMedia
can be a a media object or even a bot API file ID (files up to 4GB are supported!).getDownloadLink()
bound method:class MyEventHandler extends SimpleEventHandler {In both cases, the download link will be generated automatically if running via web.
/**
* Gets a download link for any file up to 4GB!
*/
#[FilterCommand('dl')]
public function downloadLink(Incoming&Message $message): void
{
if (!$message->replyToMsgId) {
$message->reply("This command must reply to a media message!");
return;
}
$message = $message->getReply();
if (!$message instanceof Message || !$message->media) {
$message->reply("This command must reply to a media message!");
return;
}
$message->reply("Download link: ".$message->media->getDownloadLink());
}
}
$link
will point to your own server, and the link will stream files directly to the browser (no temporary files will be created, 0 disk space will be used). Here's a more detailed explanation of the most important new features of MadelineProto 8.0.0-beta100!
- Native plugin system
To create a plugin, simply create an event handler that extends PluginEventHandler.
For example, create a plugins/Danogentili/PingPlugin.php
file:
<?php declare(strict_types=1);And use a plugin base to run all plugins included in the
namespace MadelinePlugin\Danogentili\PingPlugin;
use danog\MadelineProto\PluginEventHandler;
use danog\MadelineProto\EventHandler\Filter\FilterText;
use danog\MadelineProto\EventHandler\Message;
use danog\MadelineProto\EventHandler\SimpleFilter\Incoming;
class PingPlugin extends PluginEventHandler
{
#[FilterCommand('echo')]
public function echoCmd(Incoming & Message $message): void
{
// Contains the arguments of the command
$args = $message->commandArgs;
$message->reply($args[0] ?? '');
}
#[FilterRegex('/.*(mt?proto).*/i')]
public function testRegex(Incoming & Message $message): void
{
$message->reply("Did you mean to write MadelineProto instead of ".$message->matches[1].'?');
}
#[FilterText('ping')]
public function pingCommand(Incoming&Message $message): void
{
$message->reply("Pong");
}
}
plugins
folder. reply()
, delete()
, getReply()
, getHTML()
and simplified properties like chatId
, senderId
, command
, commandArgs
and many more, see the documentation for more info!Cron
attribute are now automatically invoked by MadelineProto every period
seconds: use danog\MadelineProto\EventHandler\Attributes\Cron;See the documentation for more info!
class MyEventHandler extends SimpleEventHandler
{
/**
* This cron function will be executed forever, every 60 seconds.
*/
#[Cron(period: 60.0)]
public function cron1(): void
{
$this->sendMessageToAdmins("The bot is online, current time ".date(DATE_RFC850)."!");
}
}
getEventHandler()
on an API
instance, see the docs for more info!Verification emails for weblate should now be sent correctly!
Читать полностью…As a birthday present for Telegram's 🥳th birthday, MadelineProto now supports VoIP calls again!
Try calling the Magna Luna webradio @magicalcrazypony to hear some cool songs, powered by @MadelineProto!
The new MadelineProto VoIP implementation is written in pure PHP, so it works even on free webhosts!
Check out the new VoIP documentation for more info on how to write your very own Telegram webradio using MadelineProto!
Features (8.0.0-beta131):
- VoIP calls!
- You can now play()
audio files of any format, local files, stream URLs or even stream data using AMP streams!
- You can now play()
audio files even on webhosts, by pre-converting the files using @libtgvoip_bot!
- Added a downloadToReturnedStream
method!
- Updated to layer 161!
New Methods:
- contacts.setBlocked
- stories.activateStealthMode
- stories.sendReaction
Changed Methods:
Added my_stories_from param to contacts.block
Added my_stories_from param to contacts.unblock
Added my_stories_from param to contacts.getBlocked
Added media_areas param to stories.sendStory
Added media_areas param to stories.editStory
Added just_contacts param to stories.getStoryViewsList
Added reactions_first param to stories.getStoryViewsList
Added q param to stories.getStoryViewsList
Added offset param to stories.getStoryViewsList
Removed offset_date param from stories.getStoryViewsList
Removed offset_id param from stories.getStoryViewsList
New Constructors:
- updateStoriesStealthMode
- updateSentStoryReaction
- storiesStealthMode
- mediaAreaCoordinates
- mediaAreaVenue
- inputMediaAreaVenue
- mediaAreaGeoPoint
Changed Constructors:
Added blocked_my_stories_from param to userFull
Added blocked_my_stories_from param to updatePeerBlocked
Added reactions_count param to storyViews
Added media_areas param to storyItem
Added sent_reaction param to storyItem
Added stealth_mode param to stories.allStoriesNotModified
Added stealth_mode param to stories.allStories
Added blocked param to storyView
Added blocked_my_stories_from param to storyView
Added reaction param to storyView
Added reactions_count param to stories.storyViewsList
Added next_offset param to stories.storyViewsList
Fixes:
- Greatly improved performance by deferring all ORM operations!
- CDN fixes!
- Fix connection to the database when a password is accidentally provided but none is needed
- Removed all generator polyfilling code and deprecated generator functions!
As a side note, I'm very happy of how the current VoIP implementation turned out, and it was a lot of fun to write!
Adding native webhost support required me to write a pure PHP OGG OPUS muxer and demuxer, as well as a full reimplementation of the GrVP protocol in pure PHP: doing it in such a high-level language allowed me to easily use amphp's libraries to easily add support for URLs, streams and all audio formats.
I also wrote a PHP FFI wrapper for libopus for this project, I'll probably split it to a separate package along with the OGG muxer/demuxer because it's really useful :)
P. S. It seems like the official Telegram iOS app has a bug which prevents magnaluna from working properly, I'm looking into it, try calling from your Mac/Android/PC, instead!
VoIP calls are working again!
They will be released in the next beta, Magna Luna will also come back to life very soon ;)
P.S. This is done using a pure PHP implementation of VoIP, so it'll work even on webhosts ;)
And here comes beta127 with multiple fixes and improvements to plugins and error handling!
Читать полностью…beta123 adds a FilterFromSender filter (single version of FilterFromSenders), fixes some issues, and disables non-IPC fallback mode, which could have led to long IPC startup times in certain edge conditions.
Читать полностью…beta120 includes multiple UX improvements, such as:
- Event handler attribute validation
- Warnings about missing extensions and MadelineProto updates in the login UI
And various fixes:
- Improve renaming using bot API file IDs and sendDocument/sendPhoto
- Improve getReply
- Fix the HTTP/HTTPS/WS/WSS transports
- Various file-related fixes
MadelineProto 8 beta 116 fixes a few issues with updateSettings, getReply and FilterReplyToSelf!
Читать полностью…P. S. It works for protected stories as well :)
Читать полностью…MadelineProto was updated (8.0.0-beta115)!
Features:
- You can now get direct download links for or directly download stories, check out the open-source MadelineProto-based @tgstories_dl_bot to download any Telegram story!
- Added support for parse_mode
parsing for story methods.
- getReply
now simply returns null if the message doesn't reply to any other message.
- getReply
now has an optional parameter that can be used to filter the returned message type.
- Added isSelfUser()
, isSelfBot()
messages to check whether the current user is a user or a bot.
- Improved IDE typehinting.
- CLI bots: you can now optionally specify a default download link URL (used by getDownloadLink
) in the settings.
- Added DialogMessagePinned
service message with a getPinnedMessage()
method.
Fixes:
- Fixed simple filters with service messages.
- Fixed IDE typehinting for getEventHandler
.
- Fixed startAndLoopMulti.
- Tweaked the default drop timeout on media DCs to avoid timeout errors on slow networks.
- Now the admin list only contains user report peers.
- Make markdownEscape
method accessible.
- Fixed getDownloadLink
for non-event-handler web IPC instances.
MadelineProto supports Telegram Stories in 8.0.0-beta113!
New Methods:
- users.getStoriesMaxIDs
- account.invalidateSignInCodes
- contacts.editCloseFriends
- contacts.toggleStoriesHidden
- channels.clickSponsoredMessage
- stories.sendStory
- stories.editStory
- stories.deleteStories
- stories.togglePinned
- stories.getAllStories
- stories.getUserStories
- stories.getPinnedStories
- stories.getStoriesArchive
- stories.getStoriesByID
- stories.toggleAllStoriesHidden
- stories.getAllReadUserStories
- stories.readStories
- stories.incrementStoryViews
- stories.getStoryViewsList
- stories.getStoriesViews
- stories.exportStoryLink
- stories.report
New Constructors:
- inputMediaStory
- messageMediaStory
- updateStory
- updateReadStories
- updateStoryID
- inputPrivacyKeyAbout
- privacyKeyAbout
- inputPrivacyValueAllowCloseFriends
- privacyValueAllowCloseFriends
- webPageAttributeStory
- messageReplyStoryHeader
- messagePeerVote
- messagePeerVoteInputOption
- messagePeerVoteMultiple
- sponsoredWebPage
- storyViews
- storyItemDeleted
- storyItemSkipped
- storyItem
- userStories
- stories.allStoriesNotModified
- stories.allStories
- stories.stories
- stories.userStories
- storyView
- stories.storyViewsList
- stories.storyViews
- inputReplyToMessage
- inputReplyToStory
- exportedStoryLink
Changed Constructors:
Added close_friend param to user
Added stories_hidden param to user
Added stories_unavailable param to user
Added stories_max_id param to user
Added alt_document param to messageMediaDocument
Added stories_muted param to inputPeerNotifySettings
Added stories_hide_sender param to inputPeerNotifySettings
Added stories_sound param to inputPeerNotifySettings
Added stories_muted param to peerNotifySettings
Added stories_hide_sender param to peerNotifySettings
Added stories_ios_sound param to peerNotifySettings
Added stories_android_sound param to peerNotifySettings
Added stories_other_sound param to peerNotifySettings
Added stories_pinned_available param to userFull
Added stories param to userFull
Added peer param to updateMessagePollVote
Removed user_id param from updateMessagePollVote
Added nosound param to documentAttributeVideo
Added preload_prefix_size param to documentAttributeVideo
Added stories_preload param to autoDownloadSettings
Added small_queue_active_operations_max param to autoDownloadSettings
Added large_queue_active_operations_max param to autoDownloadSettings
Added chats param to messages.votesList
Added keep_archived_unmuted param to globalPrivacySettings
Added keep_archived_folders param to globalPrivacySettings
Added webpage param to sponsoredMessage
Added my param to messagePeerReaction
Click here to start using Telegram Stories in MadelineProto!
beta109 brings some additional fixes and allows passing a class name to getEventHandler!
Читать полностью…MadelineProto was updated (8.0.0-beta107)!
Features:
- Add getPeriodicLoops
function to get all periodic loops created by Cron attribtues
- Add isReply
bound method
- Add exception logging in the browser
Fixes:
- Relax dl.php verification for getDownloadLink
- Made more fixes to photo bot API conversion
- Fix functions.php plugin inclusion
Also here's a more detailed list of the properties and bound methods of the new simple event handler API:
- $message->message
: string Content of the message
- $message->fwdInfo
: ?ForwardedInfo Info about a forwarded message
- $message->command
: ?string Bot command (if present)
- $message->commandType
: ?CommandType Bot command type (if present)
- $message->commandArgs
: list<string> Bot command arguments (if present)
- $message->protected
: bool Whether this message is protected
- $message->matches
: list<string> Regex matches, if a filter regex is present
- $message->fromScheduled
: bool Whether this message is a sent scheduled message
- $message->viaBotId
: ?int If the message was generated by an inline query, ID of the bot that generated it
- $message->editDate
: ?int Last edit date of the message
- $message->keyboard
: InlineKeyboard|ReplyKeyboard|null Inline or reply keyboard.
- $message->imported
: bool Whether this message was imported from a foreign chat service
- $message->psaType
: ?string For Public Service Announcement messages, the PSA type
- $message->nextSent
: ?self @readonly For sent messages, contains the next message in the chain if the original message had to be split.
- $message->id
: int Message ID
- $message->out
: bool Whether the message is outgoing
- $message->chatId
: int ID of the chat where the message was sent
- $message->senderId
: int ID of the sender of the message
- $message->replyToMsgId
: ?int ID of the message to which this message is replying
- $message->date
: int When was the message sent
- $message->topicId
: ?int ID of the forum topic where the message was sent
- $message->threadId
: ?int ID of the message thread where the message was sent
- $message->replyToScheduled
: bool Whether this is a reply to a scheduled message
- $message->mentioned
: bool Whether we were mentioned in this message
- $message->silent
: bool Whether this message was sent without any notification (silently)
- $message->ttlPeriod
: ?int Time-to-live of the message
- $message->media
: Media|null Attached media.
- $message->media->botApiFileId
: Bot API file ID of media.
- $message->media->botApiFileUniqueId
: Bot API unique file ID of media.
- $message->media->fileName, fileExt, creationDate, size, ...
: see the full list of properties by clicking on the documentation for all classes in the \danog\MadelineProto\EventHandler\Media namespace!
Methods:
- $message->getHTML(bool $allowTelegramTags = false): string
: Get an HTML version of the message. $allowTelegramTags
specifies whether to allow telegram-specific tags like tg-spoiler, tg-emoji, mention links and so on…
- $message->getReply(): ?self
: Get replied-to message. May return null if the replied-to message was deleted.
- $message->delete(bool $revoke = true): void
: Delete the message. $revoke
is true by default, if false it deletes the message on just one side of the chat.
- $message->reply(string $message, ParseMode $parseMode, ...): \danog\MadelineProto\EventHandler\Message
: Reply to the message, see the documentation for more info on the parameters!
Also, you can now more easily press inline buttons using the new simple keyboard API:
- $message->keyboard->
press(string $label, bool $waitForResult)
: Presses the first keyboard button with the specified label.- $message->keyboard->pressByCoordinates(int $row, int $column, bool $waitForResult): Presses button at the specified keyboard coordinates.
Update: fixed a small issue with getDownloadLink with bot api file ids (up to 4gb) and quality selections of photos, run composer update to get the latest version (8.0.0-beta102)!
Читать полностью…Coming up next, even more abstracted media methods, keyboard builders, a new web UI, MadelineProto in the browser with WASM, a getDownloadLink
method and much more!
Introducing MadelineProto's biggest update yet, 8.0.0-beta100!
This version introduces plugins », bound methods », filters », a built-in cron system », IPC support for the event handler » and automatic static analysis for event handler code ».
See the following post for examples!
Other features:
- Thanks to the many translation contributors @ https://weblate.madelineproto.xyz/, MadelineProto is now localized in Hebrew, Persian, Kurdish, Uzbek, Russian, French and Italian!
- Added simplified sendMessage
, sendDocument
, sendPhoto
methods that return abstract Message objects with simplified properties and bound methods!
- You can now use Tools::callFork
to fork a new green thread!
- You can now automatically pin messages broadcasted using broadcastMessages
, broadcastForwardMessages
by using the new pin: true
parameter!
- You can now use sendMessageToAdmins
to send messages to the bot's admin (the peers returned by getReportPeers
).
- Added wrapUpdate
, wrapMessage
, wrapMedia
methods to wrap low-level MTProto updates into an abstracted Message object with bound methods!
- The waveform
attribute of Voice
objects is now automatically encoded and decoded to an array of 100 integer values!
- Added a custom PeerNotInDbException
class for "This peer is not present in the internal peer database" errors
- Added a label
property to the Button class, directly indicating the button label (instead of manually fetching it as an array key).
- Added isForum
method to check whether a given supergroup is a forum
- Added an entitiesToHtml
method to convert a message and a set of Telegram entities to an HTML string!
- You can now use reportMemoryProfile()
to generate and send a pprof
memory profile to all report peers to debug the causes of high memory usage.
- Added support for pay
, loginurl, webapp
and tg://user?id=
buttons in bot API syntax!
- Added a getAdminIds
function that returns the IDs of the admin of the bot (equal to the peers returned by getReportPeers in the event handler).
- Added a new ParseMode
enum!
- Added support for HTML lists in parseMode!
- Fixed parsing of markdown code blocks!
Breaking changes:
- Switched to a custom markdown parser with bot API MarkdownV2 syntax, which differs from the previous Markdown syntax supported by parsedown.
- Markdown text can't contain HTML anymore.
Fixes:
- Fixed file uploads with ext-uv!
- Fixed file re-uploads!
- Improve background broadcasting with the broadcast API using a pre-defined list of whitelist
IDs!
- Fixed a bug that caused updates to get paused if an exception is thrown during onStart.
- Broadcast IDs are now unique across multiple broadcasts, even if previous broadcasts already completed their ID will never be re-used.
- Now uploadMedia, sendMedia and upload can upload files from string buffers created using ReadableBuffer
.
- Reduced memory usage during flood waits by tweaking config defaults.
- Reduced memory usage by clearing the min database automatically as needed.
- Automatically try caching all dialogs if a peer not found error is about to be thrown.
- Fixed some issues with pure phar installs.
- Fixed splitting of HTML and markdown messages
- Fixed formatting of multiline markdown codeblocks
- And many other performance improvements and bugfixes!
While I'm preparing the biggest MadelineProto update yet for beta100 (with plugins, filters, attributes and much more!), check out the new FAQ section of the MadelineProto docs: https://docs.madelineproto.xyz/docs/FAQ.html
Читать полностью…