# Интеграция TDLib в Telegram TUI ## Обзор TDLib (Telegram Database Library) — это официальная кроссплатформенная библиотека для создания Telegram клиентов. Она предоставляет полный функционал Telegram API с автоматическим управлением сессиями, кэшированием и синхронизацией. ## Выбор библиотеки для Rust Существует несколько Rust-оберток для TDLib: ### 1. rust-tdlib - **GitHub**: [antonio-antuan/rust-tdlib](https://github.com/antonio-antuan/rust-tdlib) - **docs.rs**: https://docs.rs/rust-tdlib - **Особенности**: - Async/await с tokio - Client/Worker архитектура - Требует предварительной сборки TDLib ### 2. tdlib-rs (Рекомендуется) - **GitHub**: [FedericoBruzzone/tdlib-rs](https://github.com/FedericoBruzzone/tdlib-rs) - **crates.io**: https://crates.io/crates/tdlib-rs - **docs.rs**: https://docs.rs/tdlib/latest/tdlib/ - **Преимущества**: - ✅ Не требует предварительной установки TDLib - ✅ Кроссплатформенность (Windows, Linux, macOS) - ✅ Автоматическая загрузка прекомпилированных бинарников - ✅ Поддержка TDLib v1.8.29 - ✅ Автогенерация типов из TL схемы ## Установка tdlib-rs ### Вариант 1: Автоматическая загрузка (Рекомендуется) **Cargo.toml:** ```toml [dependencies] tdlib-rs = { version = "0.3", features = ["download-tdlib"] } tokio = { version = "1", features = ["full"] } [build-dependencies] tdlib-rs = { version = "0.3", features = ["download-tdlib"] } ``` **build.rs:** ```rust fn main() { tdlib_rs::build::build(None); } ``` ### Вариант 2: Локальная установка TDLib Если TDLib уже установлен (версия 1.8.29): ```bash export LOCAL_TDLIB_PATH=$HOME/lib/tdlib ``` ```toml [dependencies] tdlib-rs = { version = "0.3", features = ["local-tdlib"] } ``` ### Вариант 3: Через pkg-config ```bash export PKG_CONFIG_PATH=$HOME/lib/tdlib/lib/pkgconfig/:$PKG_CONFIG_PATH export LD_LIBRARY_PATH=$HOME/lib/tdlib/lib/:$LD_LIBRARY_PATH ``` ```toml [dependencies] tdlib-rs = { version = "0.3", features = ["pkg-config"] } ``` ## Архитектура TDLib ### Основные компоненты ``` ┌─────────────────────────────────────────────────────────┐ │ Your Application │ ├─────────────────────────────────────────────────────────┤ │ ┌────────────┐ ┌──────────────┐ ┌────────────────┐ │ │ │ Client │ │ Update Stream │ │ API Requests │ │ │ └────────────┘ └──────────────┘ └────────────────┘ │ ├─────────────────────────────────────────────────────────┤ │ TDLib Client │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Auth State │ │ Local Cache │ │ API Handler │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ ├─────────────────────────────────────────────────────────┤ │ Telegram Servers │ └─────────────────────────────────────────────────────────┘ ``` ### Поток работы 1. **Инициализация** → TDLib запускается с параметрами 2. **Авторизация** → Проход через стейт-машину авторизации 3. **Синхронизация** → Загрузка базовых данных (чаты, контакты) 4. **Updates Stream** → Постоянный поток обновлений от сервера 5. **API Requests** → Запросы на получение данных / отправку сообщений ## Процесс авторизации ### Стейт-машина авторизации TDLib работает через систему состояний. Приложение получает обновления `updateAuthorizationState` и реагирует на них: ``` authorizationStateWaitTdlibParameters ↓ (вызываем setTdlibParameters) authorizationStateWaitPhoneNumber ↓ (вызываем setAuthenticationPhoneNumber) authorizationStateWaitCode ↓ (вызываем checkAuthenticationCode) authorizationStateWaitPassword (опционально, если 2FA) ↓ (вызываем checkAuthenticationPassword) authorizationStateReady ✓ ``` ### Шаг 1: Получение API ключей Перед началом работы нужно: 1. Зайти на https://my.telegram.org 2. Войти с номером телефона 3. Перейти в "API development tools" 4. Создать приложение и получить `api_id` и `api_hash` ### Шаг 2: Инициализация TDLib ```rust use tdlib::{functions, types}; async fn init_tdlib() { // Параметры инициализации let params = types::TdlibParameters { database_directory: "./tdlib_db".to_string(), use_message_database: true, use_secret_chats: true, api_id: env::var("API_ID").unwrap().parse().unwrap(), api_hash: env::var("API_HASH").unwrap(), system_language_code: "en".to_string(), device_model: "Desktop".to_string(), system_version: "Unknown".to_string(), application_version: "0.1.0".to_string(), enable_storage_optimizer: true, ignore_file_names: false, }; // Отправляем параметры functions::set_tdlib_parameters(params, &client).await?; } ``` ### Шаг 3: Ввод номера телефона ```rust async fn authenticate_with_phone(phone: String, client: &Client) { let phone_number = types::SetAuthenticationPhoneNumber { phone_number: phone, settings: None, }; functions::set_authentication_phone_number(phone_number, client).await?; } ``` ### Шаг 4: Ввод кода подтверждения ```rust async fn verify_code(code: String, client: &Client) { let check_code = types::CheckAuthenticationCode { code, }; functions::check_authentication_code(check_code, client).await?; } ``` ### Шаг 5: Ввод пароля 2FA (если включен) ```rust async fn verify_password(password: String, client: &Client) { let check_password = types::CheckAuthenticationPassword { password, }; functions::check_authentication_password(check_password, client).await?; } ``` ## Получение списка чатов ### Концепция чатов в TDLib TDLib автоматически кэширует чаты локально. Приложение должно: 1. Подписаться на обновления `updateNewChat` 2. Вызвать `loadChats()` для загрузки чатов 3. Поддерживать локальный кэш с сортировкой ### Типы списков чатов - **Main** — основные чаты - **Archive** — архивные чаты - **Folder** — пользовательские папки ### Загрузка чатов ```rust use tdlib::{functions, types}; async fn load_chats(client: &Client) -> Result> { // Указываем тип списка (Main, Archive, или конкретная папка) let chat_list = types::ChatList::Main; // Загружаем чаты // limit - количество чатов для загрузки functions::load_chats( types::LoadChats { chat_list: Some(chat_list), limit: 50, }, client ).await?; // После вызова loadChats, чаты будут приходить через updateNewChat Ok(vec![]) } ``` ### Получение информации о чате ```rust async fn get_chat_info(chat_id: i64, client: &Client) -> Result { let chat = functions::get_chat( types::GetChat { chat_id }, client ).await?; Ok(chat) } ``` ### Сортировка чатов Чаты нужно сортировать по паре `(position.order, chat.id)` в порядке убывания: ```rust chats.sort_by(|a, b| { let order_a = a.positions.get(0).map(|p| p.order).unwrap_or(0); let order_b = b.positions.get(0).map(|p| p.order).unwrap_or(0); order_b.cmp(&order_a) .then_with(|| b.id.cmp(&a.id)) }); ``` ## Получение истории сообщений ### Загрузка сообщений из чата ```rust async fn get_chat_history( chat_id: i64, from_message_id: i64, limit: i32, client: &Client ) -> Result> { let history = functions::get_chat_history( types::GetChatHistory { chat_id, from_message_id, // 0 для последних сообщений offset: 0, limit, only_local: false, }, client ).await?; Ok(history.messages.unwrap_or_default()) } ``` ### Пагинация сообщений Сообщения возвращаются в обратном хронологическом порядке (новые → старые). Для загрузки следующей страницы: ```rust // Первая загрузка (последние сообщения) let messages = get_chat_history(chat_id, 0, 50, &client).await?; // Загрузка более старых сообщений if let Some(oldest_msg) = messages.last() { let older_messages = get_chat_history( chat_id, oldest_msg.id, 50, &client ).await?; } ``` ## Обработка обновлений (Updates Stream) ### Типы обновлений TDLib отправляет обновления через `Update` enum: - `UpdateNewMessage` — новое сообщение - `UpdateMessageContent` — изменение контента сообщения - `UpdateMessageSendSucceeded` — сообщение успешно отправлено - `UpdateMessageSendFailed` — ошибка отправки - `UpdateChatLastMessage` — изменилось последнее сообщение чата - `UpdateChatPosition` — изменилась позиция чата в списке - `UpdateNewChat` — новый чат добавлен - `UpdateUser` — обновилась информация о пользователе - `UpdateUserStatus` — изменился статус пользователя (онлайн/оффлайн) - `UpdateChatReadInbox` — прочитаны входящие сообщения - `UpdateChatReadOutbox` — прочитаны исходящие сообщения ### Слушатель обновлений ```rust use tdlib::types::Update; async fn handle_updates(client: Client) { loop { match client.receive() { Some(Update::NewMessage(update)) => { println!("New message in chat {}: {}", update.message.chat_id, update.message.content ); } Some(Update::MessageSendSucceeded(update)) => { println!("Message sent successfully: {}", update.message.id); } Some(Update::UserStatus(update)) => { println!("User {} is now {:?}", update.user_id, update.status ); } Some(Update::NewChat(update)) => { println!("New chat added: {}", update.chat.title); } _ => {} } } } ``` ## Отправка сообщений ### Отправка текстового сообщения ```rust async fn send_message( chat_id: i64, text: String, client: &Client ) -> Result { let input_content = types::InputMessageContent::InputMessageText( types::InputMessageText { text: types::FormattedText { text, entities: vec![], }, disable_web_page_preview: false, clear_draft: true, } ); let message = functions::send_message( types::SendMessage { chat_id, message_thread_id: 0, reply_to: None, options: None, reply_markup: None, input_message_content: input_content, }, client ).await?; Ok(message) } ``` ### Статусы доставки и прочтения Для отображения ✓ и ✓✓: ```rust fn get_message_status(message: &types::Message) -> &str { if message.is_outgoing { match &message.sending_state { Some(types::MessageSendingState::Pending) => "", // отправляется Some(types::MessageSendingState::Failed(_)) => "✗", // ошибка None => { // Отправлено успешно if message.chat_id > 0 { // личный чат // Проверяем, прочитано ли // (нужно следить за UpdateChatReadOutbox) "✓✓" // или "✓" если не прочитано } else { "✓" // групповой чат } } } } else { "" // входящее сообщение } } ``` ## Работа с папками (Folders) ### Получение списка папок ```rust async fn get_chat_folders(client: &Client) -> Result> { let folders = functions::get_chat_folders( types::GetChatFolders {}, client ).await?; Ok(folders.chat_folders) } ``` ### Фильтрация чатов по папке ```rust async fn get_chats_in_folder(folder_id: i32, client: &Client) { let chat_list = types::ChatList::Folder { chat_folder_id: folder_id }; functions::load_chats( types::LoadChats { chat_list: Some(chat_list), limit: 50, }, client ).await?; } ``` ## Архитектура приложения ### Рекомендуемая структура ``` src/ ├── main.rs # Entry point, UI loop ├── tdlib/ │ ├── mod.rs # TDLib module │ ├── client.rs # Client wrapper │ ├── auth.rs # Authentication logic │ └── updates.rs # Update handlers ├── ui/ │ ├── mod.rs │ ├── app.rs # App state │ ├── layout.rs # UI layout │ └── components/ # UI components └── models/ ├── chat.rs # Chat models └── message.rs # Message models ``` ### Разделение ответственности 1. **TDLib Client** — управление клиентом, запросы к API 2. **Update Handler** — обработка обновлений в фоне 3. **App State** — состояние приложения (чаты, сообщения, UI) 4. **UI Layer** — отрисовка интерфейса (ratatui) ### Коммуникация между слоями ```rust // Используем каналы для коммуникации use tokio::sync::mpsc; #[derive(Debug)] enum AppEvent { NewMessage(Message), ChatUpdated(Chat), UserStatusChanged(i64, UserStatus), } #[tokio::main] async fn main() { // Канал для событий от TDLib let (tx, mut rx) = mpsc::channel::(100); // Запускаем TDLib в отдельной задаче tokio::spawn(async move { run_tdlib_client(tx).await; }); // Основной UI loop loop { // Проверяем события while let Ok(event) = rx.try_recv() { match event { AppEvent::NewMessage(msg) => { // Обновляем UI } _ => {} } } // Отрисовываем UI terminal.draw(|f| ui(f, &app))?; // Обрабатываем ввод пользователя handle_input()?; } } ``` ## Пример: Минимальный клиент ```rust use tdlib::{Client, ClientState, functions, types}; use tokio::sync::mpsc; #[tokio::main] async fn main() -> Result<(), Box> { // 1. Создаем клиент let (sender, mut receiver) = mpsc::channel(100); let client = Client::new(sender); // 2. Запускаем клиент tokio::spawn(async move { client.start().await; }); // 3. Ждем авторизации let mut authorized = false; while let Some(update) = receiver.recv().await { match update { types::Update::AuthorizationState(state) => { match state.authorization_state { types::AuthorizationState::WaitTdlibParameters => { // Отправляем параметры init_tdlib(&client).await?; } types::AuthorizationState::WaitPhoneNumber => { // Запрашиваем номер у пользователя let phone = read_phone_from_user(); authenticate_with_phone(phone, &client).await?; } types::AuthorizationState::WaitCode(_) => { // Запрашиваем код let code = read_code_from_user(); verify_code(code, &client).await?; } types::AuthorizationState::Ready => { authorized = true; break; } _ => {} } } _ => {} } } // 4. Загружаем чаты if authorized { load_chats(&client).await?; // 5. Слушаем обновления while let Some(update) = receiver.recv().await { handle_update(update); } } Ok(()) } ``` ## Best Practices ### 1. Кэширование - Всегда включай `use_message_database: true` - Храни кэш чатов и сообщений в памяти - Используй `only_local: true` для быстрого доступа ### 2. Обработка ошибок - Все TDLib функции возвращают `Result` - Обрабатывай потерю соединения - Переподключайся при ошибках сети ### 3. Производительность - Не загружай все чаты сразу (используй пагинацию) - Лимитируй количество сообщений в истории - Используй `offset` для ленивой загрузки ### 4. UI/UX - Показывай индикаторы загрузки - Кэшируй отрисованные элементы - Обновляй UI только при изменениях ## Полезные ссылки ### Официальная документация - [TDLib Getting Started](https://core.telegram.org/tdlib/getting-started) - [TDLib Documentation](https://core.telegram.org/tdlib/docs/) ### Rust библиотеки - [rust-tdlib GitHub](https://github.com/antonio-antuan/rust-tdlib) - [rust-tdlib docs.rs](https://docs.rs/rust-tdlib) - [tdlib-rs GitHub](https://github.com/FedericoBruzzone/tdlib-rs) - [tdlib-rs docs.rs](https://docs.rs/tdlib/latest/tdlib/) ### API Reference - [tdlib::functions](https://docs.rs/tdlib/latest/tdlib/functions/index.html) - [tdlib::types](https://docs.rs/tdlib-types/latest/tdlib_types/types/index.html) ## Следующие шаги 1. ✅ Изучить документацию TDLib 2. ⬜ Добавить зависимость tdlib-rs в проект 3. ⬜ Реализовать модуль авторизации 4. ⬜ Реализовать загрузку чатов 5. ⬜ Реализовать загрузку сообщений 6. ⬜ Интегрировать с существующим UI 7. ⬜ Добавить отправку сообщений 8. ⬜ Реализовать обработку обновлений в реальном времени