This commit is contained in:
Mikhail Kilin
2026-01-18 14:49:31 +03:00
parent d701464fde
commit b6d9291864
29 changed files with 3920 additions and 833 deletions

636
docs/TDLIB_INTEGRATION.md Normal file
View File

@@ -0,0 +1,636 @@
# Интеграция 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. ⬜ Реализовать обработку обновлений в реальном времени