Files
telegram-tui/docs/TDLIB_INTEGRATION.md
Mikhail Kilin b6d9291864 fixes
2026-01-20 01:00:12 +03:00

637 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Интеграция 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<Vec<Chat>> {
// Указываем тип списка (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<types::Chat> {
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<Vec<types::Message>> {
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<types::Message> {
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<Vec<types::ChatFolderInfo>> {
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::<AppEvent>(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<dyn std::error::Error>> {
// 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. ⬜ Реализовать обработку обновлений в реальном времени