Анатомия клиента Telegram на Compose Multiplatform: матрёшки TDLib 1.8, стеганография словами и грабли первой альфы

Решил написать свой Telegram на KMP за 30 дней. Что может TDLib и с чем у меня возникли проблемы

Пока искал работу, решил ради инженерного эксперимента написать альтернативный клиент Telegram на Kotlin Multiplatform (Android + Desktop) (мог бы ещё на iOS, но у меня нет ни макбука, ни денег на него). Уделял проекту по 2–3 часа под ночь. Думал, что TDLib возьмёт на себя всю грязную работу, но вместо этого ловил бесконечные рекомпозиции, падения от нехватки памяти (OOM) и загадочные ошибки файловой системы. В итоге всё‑таки допилил клиент со встроенным ИИ, Anti‑Revoke и антиспамом. В статье разбираю, какие были трудности, баги и т.д., как я их чинил, ну и оставляю ссылку на готовый APK.

Язык и UI: Kotlin 2.4, KMP & CMP (Android + Linux Desktop). Ядро мессенджера: TDLib 1.8 (C++ через JNI). Архитектура: MVI + Clean Architecture, Koin. ИИ: Google Gemini API (с поддержкой своего ключа — BYOK).

Начиналось всё оптимистично. Подключил C++ ядро TDLib. На ПК всё запустилось быстро, но как только я скомпилировал билд под Android — приложение упало с FileNotFoundException / EROFS (Read‑only file system).

Оказалось, что TDLib по привычке пытался писать логи и сессию прямо в корень рабочей папки. На Linux это прокатывало, а Android с его песочницей мгновенно падал.

Пришлось писать свой платформенный AppStorageConfig, который через Koin скармливает ядру путь к context.filesDir на телефоне и ~/.ghostgram на компе:

data class AppStorageConfig(val basePath: String) в init koin в модуле нужно было прописать: module { single { AppStorageConfig(appStoragePath) } }, и дальше при вызове init koin нужно было прописывать appStoragePath вот пример: initKoin(appStoragePath = this@GhostGramApp.filesDir.absolutePath) { androidContext(this@GhostGramApp) }

Когда я доделал до ± адекватного уровня отправку сообщений, я решил сделать отправку фотографий и видео. Написал стандартный JSON по старым туториалам из интернета: @type: inputMessagePhoto и путь к файлу.

И тут TDLib начал выдавать каждый раз ошибку: error 400: InputFile is not specified

Я потратил два дня на решение, перерыл логи, думал на права доступа и пути файлов. А разгадка крылась в недокументированном изменении TDLib 1.8 (на эту же мину наступили авторы библиотек на Node.js и Emacs в GitHub Issue #3683): разработчики Telegram добавили спойлеры и истории, и втихую завернули все медиафайлы в обязательные объекты‑матрёшки.

Фото теперь требует: inputMessagePhoto ➔ inputPhoto ➔ inputFileLocal. Видео требует: inputMessageVideo ➔ inputVideo ➔ inputFileLocal. Стикер требует: inputMessageSticker ➔ inputSticker ➔ inputFileId.

Стоило добавить этот скрытый уровень вложенности в JSON‑билдер — медиа улетели как они и задумывались.

Как я сделал Anti‑Revoke (удалённые сообщения были видны)

В официальном клиенте, когда собеседник удаляет сообщение, сервер шлёт updateDeleteMessages, и приложение стирает запись из памяти.

Я пошёл другим путём: все сообщения падают в реактивную базу Room KMP. Когда от серверов Telegram прилетает команда стереть ID, мой MessageUpdateHandler не делает DELETE, а просто выставляет флаг.

В UI у пузыря сообщения появляется красная плашка «🗑️ Удалено отправителем». А в настройках я разделил ручную очистку: можно удалить все локальные файлы, но оставить удалённые нетронутыми.

Что добавила gemini в пользовательском опыте

В приложение я добавил gemini, который используется для catch‑up и ответа собеседнику. Catch‑up gemini анализирует ваш диалог (зашифрованные сообщения gemini не видит) и присылает краткий ответ, какие события вы пропустили в диалоге.

Ответ собеседнику пока работает как Т9. И чтобы у всех gemini работало нормально, я сделал так, что в приложении можно вести свой ключ от Gemini API и пользоваться ИИ со своими лимитами; и, конечно, если вам лень, я оставил возможность пользования ИИ без ввода ключей, но при наплыве пользователей ИИ будет работать по праздникам.

Стеганография: зачем шифровать слова словами

Обычные «секретные чаты» Telegram шифруют трафик сквозным методом, но сервер видит сам факт: «эти двое открыли секретный чат и гоняют бинарный шум».

Мне хотелось сделать что‑то вроде PGP‑стеганографии:

Клиенты обмениваются ключами на эллиптических кривых (ECDH P‑256). Полученный шифротекст кодируется байт‑в‑слово через статический словарь из 256 обычных русских слов (WordCoder).

В итоге для серверов Telegram и любого сетевого фильтра сообщение выглядит как несвязный диалог: «утюг жизнь искра стриж вокзал». А клиент на лету выдирает зашифрованную суть и вешает зелёный замок «🔒».

В будущем скорее всего улучшу шифрование — пока это можно считать MVP. У этого метода есть недостатки, но для демонстрации и пробы пера сойдёт.

Производительность и текущее состояние

Пока что приложение не оптимизировано на 100%. На ПК оно работает нормально, без фризов и просадок. Но на телефоне при выгрузке, так сказать, стены из стикеров пользователь может заметить просадку FPS, которая стабилизируется спустя пару секунд; скорее всего эта проблема не проявится на более мощных телефонах, но на моём тестовом телефоне для проверки производительности это заметно.

Не сделано и половины мессенджера; фокус был на том, чтобы работали основные функции: переписка один на один, отправка стикеров, медиа, сообщений и т.д., хоть какое‑то нормальное взаимодействие в чате где больше трёх человек. И добавил свои фичи, которые как мне показались полезными: просмотр удалённых сообщений, анонимный просмотр сообщений, ИИ, в частности catch‑up.

Уложиться в 30 дней в соло с новым стеком реально, но с некоторыми условиями. Тем не менее, KMP показал себя потрясающе: 80% кода (вся бизнес‑логика, база, крипта и верстка) работает одинаково на телефоне и на моём Linux‑ноутбуке.

Проект и дальше будет обновляться по крайней мере пока не выйдет в релиз; дальше буду думать — продолжать или забросить проект.

GitHub (GPL‑3.0): https://github.com/vdggrtff/GhostGram

Готовый APK (v0.1.0‑alpha): https://github.com/vdggrtff/GhostGram/releases

Kotlin Multiplatform & Android Developer

Claude за полчаса разгадал загадку АНБ, которой больше 25 лет. Ответ был у криптографов под носом

Четверть века вокруг одного из главных стандартов современной криптографии держалась почти детективная интрига. В 2000 году американский институт стандартов NIST утвердил набор эллиптических кривых от P‑192 до P‑521 – сегодня на P‑256 держатся подписи сертификатов сайтов, токены входа и защищённые чипы телефонов. Параметры этих кривых выведены из начальных значений (seed), которые Агентство национальной безопасности США передало в стандарт без всяких объяснений. 16 октября 2026 года команда Anthropic сообщила, что её внутренняя исследовательская модель Claude меньше чем за полчаса нашла происхождение этих чисел, а криптограф Филиппо Вальсорда, объявивший за разгадку награду, подтвердил правильность решения.

Как я выгружал 1,8 TB с Яндекс Диска после аварии в дата‑центрах

Anthropic забанила мой Claude и отдала в экспорте только телефон. Теперь мне отвечает Grok в парике.