637 lines
21 KiB
Markdown
637 lines
21 KiB
Markdown
# Интеграция 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. ⬜ Реализовать обработку обновлений в реальном времени
|