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

21 KiB
Raw Blame History

Интеграция TDLib в Telegram TUI

Обзор

TDLib (Telegram Database Library) — это официальная кроссплатформенная библиотека для создания Telegram клиентов. Она предоставляет полный функционал Telegram API с автоматическим управлением сессиями, кэшированием и синхронизацией.

Выбор библиотеки для Rust

Существует несколько Rust-оберток для TDLib:

1. rust-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                       │
└─────────────────────────────────────────────────────────┘

Поток работы

  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

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 автоматически кэширует чаты локально. Приложение должно:

  1. Подписаться на обновления updateNewChat
  2. Вызвать loadChats() для загрузки чатов
  3. Поддерживать локальный кэш с сортировкой

Типы списков чатов

  • 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

Разделение ответственности

  1. TDLib Client — управление клиентом, запросы к API
  2. Update Handler — обработка обновлений в фоне
  3. App State — состояние приложения (чаты, сообщения, UI)
  4. 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

Следующие шаги

  1. Изучить документацию TDLib
  2. Добавить зависимость tdlib-rs в проект
  3. Реализовать модуль авторизации
  4. Реализовать загрузку чатов
  5. Реализовать загрузку сообщений
  6. Интегрировать с существующим UI
  7. Добавить отправку сообщений
  8. Реализовать обработку обновлений в реальном времени