21 KiB
Интеграция TDLib в Telegram TUI
Обзор
TDLib (Telegram Database Library) — это официальная кроссплатформенная библиотека для создания Telegram клиентов. Она предоставляет полный функционал Telegram API с автоматическим управлением сессиями, кэшированием и синхронизацией.
Выбор библиотеки для Rust
Существует несколько Rust-оберток для TDLib:
1. rust-tdlib
- GitHub: antonio-antuan/rust-tdlib
- docs.rs: https://docs.rs/rust-tdlib
- Особенности:
- Async/await с tokio
- Client/Worker архитектура
- Требует предварительной сборки TDLib
2. tdlib-rs (Рекомендуется)
- GitHub: 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:
[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:
fn main() {
tdlib_rs::build::build(None);
}
Вариант 2: Локальная установка TDLib
Если TDLib уже установлен (версия 1.8.29):
export LOCAL_TDLIB_PATH=$HOME/lib/tdlib
[dependencies]
tdlib-rs = { version = "0.3", features = ["local-tdlib"] }
Вариант 3: Через pkg-config
export PKG_CONFIG_PATH=$HOME/lib/tdlib/lib/pkgconfig/:$PKG_CONFIG_PATH
export LD_LIBRARY_PATH=$HOME/lib/tdlib/lib/:$LD_LIBRARY_PATH
[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 │
└─────────────────────────────────────────────────────────┘
Поток работы
- Инициализация → TDLib запускается с параметрами
- Авторизация → Проход через стейт-машину авторизации
- Синхронизация → Загрузка базовых данных (чаты, контакты)
- Updates Stream → Постоянный поток обновлений от сервера
- API Requests → Запросы на получение данных / отправку сообщений
Процесс авторизации
Стейт-машина авторизации
TDLib работает через систему состояний. Приложение получает обновления updateAuthorizationState и реагирует на них:
authorizationStateWaitTdlibParameters
↓ (вызываем setTdlibParameters)
authorizationStateWaitPhoneNumber
↓ (вызываем setAuthenticationPhoneNumber)
authorizationStateWaitCode
↓ (вызываем checkAuthenticationCode)
authorizationStateWaitPassword (опционально, если 2FA)
↓ (вызываем checkAuthenticationPassword)
authorizationStateReady ✓
Шаг 1: Получение API ключей
Перед началом работы нужно:
- Зайти на https://my.telegram.org
- Войти с номером телефона
- Перейти в "API development tools"
- Создать приложение и получить
api_idиapi_hash
Шаг 2: Инициализация TDLib
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: Ввод номера телефона
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: Ввод кода подтверждения
async fn verify_code(code: String, client: &Client) {
let check_code = types::CheckAuthenticationCode {
code,
};
functions::check_authentication_code(check_code, client).await?;
}
Шаг 5: Ввод пароля 2FA (если включен)
async fn verify_password(password: String, client: &Client) {
let check_password = types::CheckAuthenticationPassword {
password,
};
functions::check_authentication_password(check_password, client).await?;
}
Получение списка чатов
Концепция чатов в TDLib
TDLib автоматически кэширует чаты локально. Приложение должно:
- Подписаться на обновления
updateNewChat - Вызвать
loadChats()для загрузки чатов - Поддерживать локальный кэш с сортировкой
Типы списков чатов
- Main — основные чаты
- Archive — архивные чаты
- Folder — пользовательские папки
Загрузка чатов
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![])
}
Получение информации о чате
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) в порядке убывания:
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))
});
Получение истории сообщений
Загрузка сообщений из чата
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())
}
Пагинация сообщений
Сообщения возвращаются в обратном хронологическом порядке (новые → старые).
Для загрузки следующей страницы:
// Первая загрузка (последние сообщения)
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— прочитаны исходящие сообщения
Слушатель обновлений
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);
}
_ => {}
}
}
}
Отправка сообщений
Отправка текстового сообщения
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)
}
Статусы доставки и прочтения
Для отображения ✓ и ✓✓:
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)
Получение списка папок
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)
}
Фильтрация чатов по папке
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
Разделение ответственности
- TDLib Client — управление клиентом, запросы к API
- Update Handler — обработка обновлений в фоне
- App State — состояние приложения (чаты, сообщения, UI)
- UI Layer — отрисовка интерфейса (ratatui)
Коммуникация между слоями
// Используем каналы для коммуникации
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()?;
}
}
Пример: Минимальный клиент
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 только при изменениях
Полезные ссылки
Официальная документация
Rust библиотеки
API Reference
Следующие шаги
- ✅ Изучить документацию TDLib
- ⬜ Добавить зависимость tdlib-rs в проект
- ⬜ Реализовать модуль авторизации
- ⬜ Реализовать загрузку чатов
- ⬜ Реализовать загрузку сообщений
- ⬜ Интегрировать с существующим UI
- ⬜ Добавить отправку сообщений
- ⬜ Реализовать обработку обновлений в реальном времени