Документація tg_client
Цей документ описує всі модулі пакету tg_client, окрім директорії
graphQL/. У центрі уваги — робота з TDLib, керування сесіями userbot-ів,
авторизація, взаємодія з Redis/WebSocket та сервіси для діалогів і медіа.
1. Огляд архітектури
Пакет складається з кількох горизонтальних шарів:
- Моделі описують userbot-ів і кешовані кастомні емодзі.
- _tg_utils містить конфігурацію TDLib та менеджер сесій.
- auth реалізує WebSocket-флоу для авторизації userbot-а.
- dialogs — основний стек для роботи з чатами: сервіси, обробники оновлень, лістенери та WebSocket-консюмери.
Основні залежності
- TDLib (через
python-telegram) - Redis для шини подій/команд
- Django ORM + Channels
- S3/MinIO для медіафайлів
Ключові сервіси
TDLibHelper— фасад для APISessionManager— контроль сесійTDLibDialogService— парсер історії- WS-консюмери для auth та списку чатів
2. Структура директорій
| Каталог | Призначення |
|---|---|
tg_client/models.py |
Моделі UserBot та CustomEmoji. |
tg_client/_tg_utils/ |
Конфіг TDLib (config.py) і SessionManager для блокувань та життєвого циклу клієнтів. |
tg_client/auth/ |
Channels-консюмер TDLibAuthConsumer для проходження login/code/password. |
tg_client/dialogs/services/ |
Бізнес-логіка: TDLibHelper, парсер повідомлень, збереження медіа, хелпер UTF-16. |
tg_client/dialogs/handlers/ |
Обробка updateNewMessage з TDLib та пуш у Redis. |
tg_client/dialogs/listeners/ |
Головний лістенер, що стартує userbot-клієнти, слухає Redis-команди й шле відповіді. |
tg_client/dialogs/ws/ |
WebSocket API для фронтенду (наприклад, list_chats/consumers.py). |
3. Моделі Django
UserBot
- Зберігає
phone,api_id,api_hash,session_path,session_strта статусis_authenticated. - Пов'язаний із
accounts.UserProfileтаcompany.Company. - Поля
tg_nickname,username,premiumоновлюються після авторизації. - Метод
save()автогенеруєsession_pathяк/app/session_accounts/<pk>_<phone>/.
CustomEmoji
- Кешує кастомні емодзі (JSON Lottie або WEBM) для повторного використання.
- Використовується
TDLibHelperпід час форматування тексту.
4. _tg_utils
config.py
- Визначає шлях до
libtdjson.so,API_ID,API_HASHтаdatabase_encryption_key. DEV_MODEдозволяє жорстко задати тестові значення; у проді всі секрети тягнуться черезget_secret().
session_manager.py
SessionManagerгарантує, що лише один процес працює з TDLib-сесією.- Метод
get_client()— асинхронний контекст, що блокує сесію, створюєTelegram-клієнт, повертає його й потім безпечно відключає (client.stop()). create_new_session()ініціює клієнт для авторизації (використовується у WebSocket auth).check_session_health()запускає quick health-check (get_me()).- Функція
get_session_manager()забезпечує сінглтон менеджера.
5. WebSocket-авторизація (tg_client/auth)
TDLibAuthConsumer керує повним циклом логіну userbot-а:
- start — ініціалізує
SessionManager, створює TDLib-клієнт, запускаєlogin(blocking=False). - send_code — передає код підтвердження (AuthorizationState.WAIT_CODE).
- send_password — вводить 2FA (AuthorizationState.WAIT_PASSWORD).
- Після
READYвитягує профіль черезget_me(), оновлюєUserBot, закриває сесію.
Комунікація завжди ведеться через JSON-повідомлення Channels, Redis тут не залучений.
Ендпойнт WebSocket: wss://<host>/ws/tg-auth/ (див. tg_client/routing.py).
Приклади клієнтських запитів
1. Старт авторизації:
{
"action": "start",
"userbot_id": 12,
"phone": "+380955870336"
}
2. Передача одноразового коду (стає доступною після події code_required):
{
"action": "send_code",
"code": "12345"
}
3. Введення 2FA-пароля (коли бекенд надіслав password_required):
{
"action": "send_password",
"password": "my-secret"
}
Приклади відповідей сервера
{"type": "connected", "message": "WebSocket connected"}
{"type": "status", "message": "Connecting to Telegram..."}
{"type": "code_required", "message": "Confirmation code sent"}
{"type": "authorized", "message": "Authorization completed", "username": "akva_bot", ...}
Після закриття з'єднання сесія примусово завершується, тому повторні спроби починаються з нового start.
6. Сервіси діалогів
TDLibHelper (dialogs/services/tg_requests.py)
- Уніфікує виклики TDLib через
tg_call()(повертає{ok, result, error}). - Містить кешовані методи
get_me(),get_chat(),get_user(). get_chats(limit, offset_order, offset_chat_id)підтримує пагінацію;build_chat_item()повертає order як рядок.get_history(),get_message(),view_messages(),send_message(),forward_message(),reply_message(),get_contacts()охоплюють усі основні операції.- Блок MEDIA завантажує файли через TDLib, передає їх у
save_media(), повертає presigned URL або метадані. - Методи для преміум-емодзі:
get_full_info_premium_emoji(),get_premium_emoji_asset(), форматування тексту з кастомними емодзі (formated_msg(),utf16_index_to_py_index()). send_ws()— хелпер для пушу відповіді у Redis-каналchat_list_updates.
TDLibDialogService
- Фасад
get_dialog()тягне історію, дані чату та парсить повідомлення. - Позначає вхідні повідомлення прочитаними через
helper.view_messages(). - Витягує дані відправника, ріплаїв, пересилань, обробляє photo/emoji/unsupported контент.
- Повертає вже відформатований payload для фронтенду.
save_to_cloud.py
save_media()— завантаження локального файлу у MinIO/S3 черезdefault_storage.generate_presigned_url()створює короткоживуче посилання на об'єкт.
utf_index_to_py_index.py
Конвертує UTF-16 індекси (які повертає TDLib) у Python-індекси, щоб коректно працювати з емодзі/сурогатами.
7. Лістенери та обробники
main_listener.py
start_userbot_listener()відкриває TDLib-клієнт черезSessionManager, реєструєupdateNewMessageта запускаєlisten_for_commands().listen_for_commands()слухає Redis-каналtg_commands, приймає payload та виконує команди черезTDLibHelper.- Підтримувані команди:
get_chats,search_user,get_user,open_dialog(опц.topic_id),send_message,forward_message,reply_message,import_contacts,get_prem_path. - Результати надсилаються у
chat_list_updatesчерезsend_ws(), щоб їх підхопив WebSocket-клієнт.
handle_new_update.py
- Прямий обробник
updateNewMessage, що приходить із TDLib. - Використовує
TDLibDialogService.parse_message(), щоб отримати повний опис повідомлення (ID, текст, ентиті, reply/forward/медіа). - Публікує результат у
chat_list_updatesразом ізuserbot_idтаunread_count, тож push-події мають такий самий формат, як і історіяopen_dialog.
8. WebSocket API для діалогів
TDLibListChatsConsumer
- Після
open_clientпідписується наchat_list_updatesі може одночасно слухати кілька userbot-ів черезuserbot_ids(масив) або одиночнийuserbot_id. get_chats→ відправляє команду у Redis зlimit,offset_order,offset_chat_id.search_user,get_user,open_dialog,get_prem_path— тонкі проксі в Redis.open_dialogзtopic_idвідкриває історію конкретного топіка; безtopic_idу форумних чатах повертає список топіків у форматі повідомлень (is_topic,topic_id,topic_name,topic_icon_emoji_id).send_message— базове відправлення тексту.forward_message— пересилає повідомлення між чатами без редагування контенту.reply_message— створює відповідь із посиланням наreply_to_message_id.import_contacts— викликаєTDLibHelper.get_contacts(), повертає результат у відповідному WS-івенті.- Відсіює повідомлення за
userbot_id, щоб користувач отримував лише свій стрім. - Медіа приходять поступово через подію
media_ready(файл/кастомні емодзі/іконка топіка), щоб UI міг оновлюватись без блокування основного потоку.
| Action (WS) | Очікувані поля | Результат із Redis |
|---|---|---|
get_chats |
userbot_id, опц. limit, offset_order, offset_chat_id |
Послідовність {type: "get_chats", payload: chat_item} |
open_dialog |
chat_id, from_message_id, опц. topic_id |
Потік повідомлень (message) + dialog_end + media_ready; для форумів без topic_id повертає топіки |
send_message |
chat_id, text |
send_message або send_message_error |
get_prem_path |
emoji_id |
Посилання на лотті/відео кастомного емодзі |
forward_message |
to_chat_id, from_chat_id, message_id |
forward_message або forward_message_error |
reply_message |
chat_id, reply_to_message_id, text |
reply_message або reply_message_error |
import_contacts |
userbot_id, phone |
import_contacts або import_contacts_error |
Події з Redis
message— основний payload повідомлення або топіка (у форматі повідомлення).dialog_end— ознака завершення відвантаження історії.media_ready— поступове дозавантаження медіа:{kind: "file"|"custom_emoji", file_id|emoji_id, path|emoji_json|emoji_webm, chat_id, msg_id, topic_id}.dialog_error— помилка під часopen_dialog.
Приклади payload-ів
/ws/chats/
{"action": "open_client", "userbot_ids": [1, 2], "user_id": 1}
{"action": "get_chats", "userbot_id": 1, "user_id": 1}
{"action": "open_dialog", "userbot_id": 1, "from_message_id": "0", "chat_id": "123"}
{"action": "open_dialog", "userbot_id": 1, "from_message_id": "0", "chat_id": "123", "topic_id": "456"}
// Відправити повідомлення
{"action": "send_message", "userbot_id": 1, "chat_id": "123", "text": "Привіт"}
// Переслати повідомлення
{"action": "forward_message", "userbot_id": 1, "to_chat_id": "488386685", "from_chat_id": "2123445553", "message_id": "984071798784"}
// Reply на конкретне повідомлення
{"action": "reply_message", "userbot_id": 1, "chat_id": "2123445553", "reply_to_message_id": "984121081856", "text": "Відповідаю"}
9. Потік даних
Команди від фронтенду
- React/SPA шле action у WebSocket (
dialogs/ws). - WS-консюмер публікує JSON у Redis-канал
tg_commands. listen_for_commands()читає повідомлення, викликаєTDLibHelper.- Результат (або помилка) повертається через
send_ws()у каналchat_list_updates. - WS-консюмер відправляє payload назад клієнту.
Push-оновлення
- TDLib кидає
updateNewMessage. handle_new_updateпарсить повідомлення черезTDLibDialogServiceта формує payload із тими ж полями, що повертаєopen_dialog.- Подія з
userbot_id,unread_countта деталями месиджу публікується уchat_list_updatesй летить на фронт. - Якщо у повідомленні є файл/кастомне емодзі/іконка топіка, окремо приходить
media_readyз метаданими (поступова доставка медіа).
10. Типові сценарії
Пагінація чатів
Беріть order і chat_id з останнього елемента відповіді. Передавайте їх як
offset_order/offset_chat_id у наступному get_chats, аби TDLib видав наступний блок.
Завантаження медіа
TDLibHelper.download_file() викликає downloadFile+getFile, чекає завершення, зберігає у S3/MinIO та повертає presigned URL. Встановіть return_meta=True, якщо потрібно локальний шлях.
Парсинг історії
TDLibDialogService.get_dialog() приймає chat_id та from_message_id. Якщо історія порожня, поверне is_empty=True + дані чату.
Робота з кастомними емодзі
При першому використанні емодзі helper завантажує asset, зберігає у CustomEmoji, повертає JSON/WebM. Повторні звернення беруться з БД.
11. Рекомендації з розширення
- Нові команди достатньо додати у
listeners/main_listener.py(обробка) та у відповідний WS-консюмер. - Тримайте
SessionManagerєдиною точкою входу: не створюйтеTelegramнапряму. - GraphQL-шар може повторно використовувати ці сервіси; достатньо звертатись до нього як до внутрішнього API.