«Клиентов нужно не искать, а создавать»: погружение в Telegram API через TDLib

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!


Сперва я рассказывал простые вещи о Telegram Bot API и делал интересных ботов — виртуальную подругу и друга для заказа шавермы. Затем коснулся тестовых серверов и юзерботов. И наконец, пришла пора заглянуть глубже — узнать, как сделать свой клиент для Telegram. Что такое TL-схема и TDLib? Об этом мы сегодня и узнаем.

Данная статья не только поможет тем, кто решил написать свой клиент для Telegram, но и немного расширит кругозор остальным: MTProto — это не приевшийся JSON API. Добро пожаловать под кат!

Готовы показать свои знания в IT? Примите участие в IT-кроссворде Selectel, выиграйте 10 000 рублей на аренду серверов и эксклюзивный мерч Selectel.


Прежде чем мы начнем


Telegram поощряет, ну или по крайней мере не наказывает, за разработку пользовательских клиентов. Для создания клиента необходимо придерживаться следующих правил:

  1. Необходимо использовать свой уникальный APP_ID.
  2. Необходимо следовать правилам безопасности.
  3. Можно расширять функциональность Telegram, но нельзя заставлять пользователей других приложений переходить в ваше приложение.
  4. Нельзя нарушать базовые механики мессенджера, например, делать «невидимки» и «нечитайки».
  5. Нельзя выполнять действия без ведома пользователя, например, автоматически подписываться на канал или рассылать сообщения.
  6. Если клиент обеспечивает доступ к каналам, то необходимо также реализовать функциональность «спонсированных сообщений».
  7. Нельзя выдавать приложение за официальное.
  8. Монетизировать можно любым легальным способам, если о нем написано на странице приложения.

Нарушение этих правил приведет к предупреждению, а его игнорирование — к отключению API для вашего приложения. Также команда Telegram может запросить удалить ваше приложение из магазинов.

Также не стоит забывать, что деструктивные действия, такие как спам и распространение незаконного контента, вне зависимости от клиента являются нарушением и могут привести к бану аккаунта.

Готовые решения


Когда смотришь на количество «фич» в современном Telegram-клиенте, невольно представляешь себе поезд прогресса, у которого отказали тормоза. Быстро написать «с нуля» что-то сравнимое по функциональности с официальными клиентами практически невозможно. Поэтому энтузиасты делают то, что официальный Telegram не дает. В первую очередь вспоминаются юзерботы — их можно разделить на две категории.

Классические боты в «шкуре» пользователя

У обычных ботов в Telegram очень мало прав. У них по умолчанию нет возможности посмотреть историю сообщений в чате, даже если есть разрешение на доступ к переписке.

За доступ к истории чатов придется «заплатить» — вы не сможете создавать кнопки и использовать особые сообщения, например, выставлять счета. Нехорошие люди используют такой вид ботов для спама в публичных чатах.

Автоматизация действий пользователя

В этом варианте «бот» — это программа, которая использует основной аккаунт пользователя и реализует функциональность, недоступную в официальном приложении. Например, есть расширение PMPermit, которое автоматически отправляет в черный список незнакомцев, которые вам пишут.

Использование такого вида ботов — интересный процесс. Пользователь пишет команду в чат, где хочет выполнить действие. Сообщение отправляется на серверы Telegram, а оттуда «прилетает» обновлением в «клиент» юзербота. Бот удаляет сообщение-команду из чата и выполняет заданное действие.

На GitHub есть много юзербот-проектов, но большинство из них на Python и используют фреймворки Telethon или Pyrogram. Для реализации своих задумок обычно достаточно использовать готового юзербота или написать личного на указанных фреймворках.

Функции и классы обычно имеют исчерпывающую документацию, которая доступна для среды разработки. Но почему у проектов такая хорошая документация? Пришла пора поговорить о TL-схемах.


TL-схема


TL — это от словосочетания «Type Language». Если коротко, то это особый язык описания типов и функций. Для Telegram существует несколько схем: организация шифрования для MTProto, основное API, e2e-шифрование и секретные чаты.

Рассмотрим описание одного конструктора для класса User:

user#d23c81a3 id:int first_name:string last_name:string = User;

Что в этой строке есть:

  • user — человеко-читаемое имя конструктора.
  • d23c81a3 — машинное представление конструктора. Считается как CRC32 от строки.
  • id:int first_name:string last_name:string — имена аргументов и их типы.
  • User — человеко-читаемое имя класса, которому принадлежит конструкторв.

Описание функций выглядит аналогично, но находится после строки ---functions---. Рассмотрим объявление функции.

getUser#b0f732d5 id:int = User;

Отличия от описания типа:

  • getUser — это имя функции.
  • User — это возвращаемое значение. Обратите внимание, что функции могут вернуть ошибку вместо значения — и это никак не отображается в схеме.

Telegram выкладывает обновления в виде слоев (layer). Каждый слой API имеет полную TL-схему и определяет поддерживаемую функциональность приложения.

Type Language — это не совсем оригинальное детище Telegram. В TL-парсере можно встретить упоминание ВК. В первых опубликованных исходниках kPHP находится оригинальный парсер. Вторая попытка открыть исходный код kPHP принесла документацию, в том числе по Type Language. Документация ссылается… на сайт Telegram!

В идеальном мире TL-схема — это исчерпывающее описание интерфейса Telegram. В реальности же есть нюансы. На момент подготовки этой статьи Telegram выпустил обновление от 29 октября с персональными цветами, цитатами и подсветкой синтаксиса. Это 166 слой схемы. На официальном сайте же доступен только слой 158 — общие папки и выбор обоев в чате от 21 апреля.

На corefork-поддомене (как его нашли?) есть слой 164 с историями каналов от 22 сентября. Актуальную схему можно найти в репозитории Telegram Desktop.

Сам файл схемы не содержит документации, за объяснением и возможными ошибками необходимо идти на отдельную страницу Telegram. Но повторюсь: там может не быть актуальной схемы.

Кроме того, в 2019 году nuclight написал лонгрид, посвященный костылям вызовам, которые встречаются при попытке реализовать MTProto с нуля по документации. И там очень много интересного.

К счастью, у Telegram есть решение на случай, если вы не хотите разбираться в тонкостях MTProto — TDLib.

Если вам интересно читать топики о программировании, Telegram и других технологиях, подписывайтесь на мой канал, где периодически пишу на разные темы.

TDLib


TDLib (Telegram Database Library) — это библиотека, которая абстрагирует разработчика от тонкостей работы с MTProto. Библиотека написана на С++ и имеет несколько интерфейсов:

  • Нативный. Библиотека используется как обычная С++-библиотека.
  • JNI (Java Native Interface) — биндинги (bindings) для вызова нативного кода из Java.
  • С++/CX — интерфейс для вызова нативного кода из .NET-окружения.
  • JSON — интерфейс, в котором общение происходит в формате JSON. Этот интерфейс позволяет «связать» TDLib со множеством других языков программирования, например, Python. Поговорим подробнее об этом интерфейсе.

Компиляция библиотеки долгая, но не вызывает трудностей. В документации есть блок, посвященный сборке, а также доступен генератор инструкций для различных языков программирования и операционных систем.

Как и любое другое приложение Telegram, TDLib использует схему для взаимодействия с API. Возникает вопрос: а как в этом проекте с актуальностью? Здесь есть хорошая и плохая новости.

Плохая новость заключается в том, что TDLib все еще на шаг позади и использует слой 165. Актуальный, напомню, 166. Хорошая же новость — это документация интерфейсов TDLib. Библиотека приносит четвертую TL-схему — td_api.tl, которая содержит документацию в комментариях:

// @description Represents a user
// @id User identifier
// @first_name First name of the user
// @last_name Last name of the user
// @usernames Usernames of the user; may be null
// @phone_number Phone number of the user
// @status Current online status of the user
// @profile_photo Profile photo of the user; may be null
// @emoji_status Emoji status to be shown instead of the default Telegram Premium badge; may be null. For Telegram Premium users only
// @is_contact The user is a contact of the current user
// @is_mutual_contact The user is a contact of the current user and the current user is a contact of the user
// @is_close_friend The user is a close friend of the current user; implies that the user is a contact
// @is_verified True, if the user is verified
// @is_premium True, if the user is a Telegram Premium user
// @is_support True, if the user is Telegram support account
// @restriction_reason If non-empty, it contains a human-readable description of the reason why access to this user must be restricted
// @is_scam True, if many users reported this user as a scam
// @is_fake True, if many users reported this user as a fake account
// @has_active_stories True, if the user has non-expired stories available to the current user
// @has_unread_active_stories True, if the user has unread non-expired stories available to the current user 
// @have_access If false, the user is inaccessible, and the only information known about the user is inside this class. Identifier of the user can't be passed to any method
// @type Type of the user
// @language_code IETF language tag of the user's language; only available to bots
// @added_to_attachment_menu True, if the user added the current bot to attachment menu; only available to bots
user id:int53 first_name:string last_name:string usernames:usernames phone_number:string status:UserStatus profile_photo:profilePhoto emoji_status:emojiStatus is_contact:Bool is_mutual_contact:Bool is_close_friend:Bool is_verified:Bool is_premium:Bool is_support:Bool restriction_reason:string is_scam:Bool is_fake:Bool has_active_stories:Bool has_unread_active_stories:Bool have_access:Bool type:UserType language_code:string added_to_attachment_menu:Bool = User;
–--functions---

@description Returns information about a user by their identifier. This is an offline request if the current user is not a bot @user_id User identifier
getUser user_id:int53 = User;

Достаточно подробно. Имена полей совпадают с полями в JSON, а имя конструктора (user) передается в поле с именем @type:

{
    "@type": "user",
    "id": 777000,
    "first_name": "Telegram",
    "last_name": "Notifications",
    "phone_number": "42777",
    "status": {
        "@type": "userStatusOnline",
        "expires": 2147483647
    },
    "is_contact": false,
    "is_mutual_contact": false,
    "is_close_friend": false,
    "is_verified": true,
    "is_premium": false,
    "is_support": true,
    "restriction_reason": "",
    "is_scam": false,
    "is_fake": false,
    "has_active_stories": false,
    "has_unread_active_stories": false,
    "have_access": true,
    "type": {
        "@type": "userTypeRegular"
    },
    "language_code": "",
    "added_to_attachment_menu": false
}

Есть нюанс при работе с типами. Telegram различает целочисленные типы разной длины — int32, int53 и int64. Для JSON это все один целочисленный тип. Второй особенный тип — bytes. В JSON-интерфейсе это base64-строка.

С функциями все аналогично. В поле @type нужно передать имя функции, а остальное — как прописано в схеме:

{
    "@type": "getUser",
    "user_id": 777000
}

TDLib — это асинхронная библиотека, в которой практически отсутствуют блокирующие вызовы:

// Создаем инстанс TdJson
void* td = td_json_client_create();

// Ожидаем ответ от TdJson в течение одной секунды.
// Если библиотека ничего не отдала, то указатель будет NULL.
// Эту функцию следует вызывать исключительно в одном потоке.
const char* res = td_json_client_receive(td, 1);

// Отправляем запрос в TdJson. Этот метод потокобезопасный.
const char* payload = "{\"@type\": \"getUser\", \"user_id\": 777000}";
td_json_client_send(td, payload);

// Прибираем за собой при завершении программы.
td_json_client_destroy(td);

Асинхронность подразумевает, что функция td_json_client_receive может получать результаты в произвольном порядке. Более того, вместо результата может прийти ошибка:

{
    "@type": "error",
    "code": 404,
    "message": "Not Found",
}

Как разобраться, к какому запросу относится ответ? Решение гениально и кроется в поле @extra. При вызове функции можно дополнить запрос полем, которое будет перенесено в ответ!

Содержимое этого поля может быть любым — числовым, строковым или даже словарем. TDLib перенесет его в ответ как есть:

// Запрос
{
    "@type": "getUser",
    "user_id": 777000,
    "@extra": {
        "request_id": "4a05c088-525f-4464-a501-017f1060fcc5"
    }
}

// Ответ
{
    "@type": "error",
    "code": 404,
    "message": "Not Found",
    "@extra": {
        "request_id": "4a05c088-525f-4464-a501-017f1060fcc5"
    }
}

Важный момент: дополнительное поле «пробрасывается» только в ответ на запрос. Например, есть функция loadChats, которая делает следующее:

  1. Побуждает TDLib сгенерировать объекты updateChat по одному на каждый чат. При этом обновления (update) неотличимы от обычных обновлений.
  2. После генерации обновлений возвращается результат работы — объект ok или error. Только этот ответ содержит поле @extra.

Заключение


Сейчас существует множество проектов, «улучшающих» взаимодействие с Telegram. Среди них — готовые юзерботы, которые можно расширять до «суровых» фреймворков. Интересно, как получить безграничную свободу в работе с Telegram API? Тогда следите за обновлениями в нашем блоге на Хабре!

Другие статьи по теме


  • Как сделать бота для заказа шавермы и оставить голодными лишь 1,1% коллег
  • Как создать мод для Cyberpunk 2077 и какие инструменты выбрать
  • Как и зачем у нас появился статический анализатор типов для Python
Источник: https://habr.com/ru/companies/selectel/articles/771496/


Интересные статьи

Интересные статьи

Приветствую всех! В этой статье я бы хотел поделиться опытом подготовки и сдачи экзамена ALCSA-1.7: Сертифицированный системный администратор Astra Linux Special Edition 1.7. Вопросов от коллег (...
Когда вам нужно сообщить об использовании шифрования вашим приложением?Ваше приложение использует шифрование? - Это первое, что вы прочтете после нажатия кнопки «Отправить на проверку» в App Store Con...
Я провел в изучении JMM много часов и теперь делюсь с вами знаниями в простой и понятной форме. В этой статье мы подробно разберем Java Memory Model (JMM) и применим полученные знания на практике. ...
Cloud4Y уже рассказывал про то, как некоторые прогрессивные страны прорабатывают вопросы внедрения государственных файерволов, во многом копируя китайскую концепцию контр...
Автор материала, перевод которого мы сегодня публикуем, говорит, что современные люди, жизнь которых переполнена работой, часто забывают писать сообщения своим родным и близким. Он, глядя на то, ...