docs: complete rustdoc documentation for all public APIs (P4.12)

Added comprehensive rustdoc documentation for all TDLib modules,
configuration, and utility functions.

TDLib modules documented:
- src/tdlib/auth.rs - AuthManager, AuthState (6 doctests)
- src/tdlib/chats.rs - ChatManager (8 doctests)
- src/tdlib/messages.rs - MessageManager (14 methods, 6 doctests)
- src/tdlib/reactions.rs - ReactionManager (3 doctests)
- src/tdlib/users.rs - UserCache, LruCache (2 doctests)

Configuration and utilities:
- src/config.rs - Config, ColorsConfig, GeneralConfig (4 doctests)
- src/formatting.rs - format_text_with_entities (2 doctests)

Documentation includes:
- Detailed descriptions of all public structs and methods
- Usage examples with code snippets
- Parameter and return value documentation
- Notes about async behavior and edge cases
- Cross-references between related functions

Total: 34 doctests (30 ignored for async, 4 compiled)
All 464 unit tests passing 

Priority 4.12 (Rustdoc) - 100% complete

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Kilin
2026-02-01 01:03:30 +03:00
parent 326bf6cc46
commit 93e43a59d0
7 changed files with 901 additions and 57 deletions

View File

@@ -1,25 +1,88 @@
use tdlib_rs::enums::{AuthorizationState, Update};
use tdlib_rs::functions;
/// Состояние процесса авторизации в Telegram.
///
/// Отслеживает текущий этап аутентификации пользователя,
/// от инициализации TDLib до полной авторизации.
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub enum AuthState {
/// Ожидание параметров TDLib (начальное состояние).
WaitTdlibParameters,
/// Ожидание ввода номера телефона.
WaitPhoneNumber,
/// Ожидание ввода кода подтверждения из SMS/Telegram.
WaitCode,
/// Ожидание ввода пароля двухфакторной аутентификации (2FA).
WaitPassword,
/// Авторизация завершена, клиент готов к работе.
Ready,
/// Соединение закрыто.
Closed,
/// Произошла ошибка авторизации.
Error(String),
}
/// Менеджер авторизации TDLib
/// Менеджер авторизации TDLib.
///
/// Управляет процессом авторизации пользователя в Telegram,
/// отслеживает текущее состояние и предоставляет методы
/// для отправки учетных данных (номер телефона, код, пароль).
///
/// # Процесс авторизации
///
/// 1. `WaitTdlibParameters` → автоматически
/// 2. `WaitPhoneNumber` → [`send_phone_number()`](Self::send_phone_number)
/// 3. `WaitCode` → [`send_code()`](Self::send_code)
/// 4. `WaitPassword` (опционально) → [`send_password()`](Self::send_password)
/// 5. `Ready` → авторизация завершена
///
/// # Examples
///
/// ```ignore
/// let mut auth_manager = AuthManager::new(client_id);
///
/// // Отправляем номер телефона
/// auth_manager.send_phone_number("+1234567890".to_string()).await?;
///
/// // После получения кода из SMS
/// auth_manager.send_code("12345".to_string()).await?;
///
/// // Если включена 2FA
/// if auth_manager.state == AuthState::WaitPassword {
/// auth_manager.send_password("my_password".to_string()).await?;
/// }
///
/// // Проверяем авторизацию
/// if auth_manager.is_authenticated() {
/// println!("Successfully authenticated!");
/// }
/// ```
pub struct AuthManager {
/// Текущее состояние авторизации.
pub state: AuthState,
/// ID клиента TDLib для API вызовов.
client_id: i32,
}
impl AuthManager {
/// Создает новый менеджер авторизации.
///
/// # Arguments
///
/// * `client_id` - ID клиента TDLib для API вызовов
///
/// # Returns
///
/// Новый экземпляр `AuthManager` в состоянии `WaitTdlibParameters`.
pub fn new(client_id: i32) -> Self {
Self {
state: AuthState::WaitTdlibParameters,
@@ -27,11 +90,36 @@ impl AuthManager {
}
}
/// Проверяет, завершена ли авторизация.
///
/// # Returns
///
/// `true` если состояние равно `AuthState::Ready`, иначе `false`.
///
/// # Examples
///
/// ```ignore
/// if auth_manager.is_authenticated() {
/// println!("User is authenticated");
/// }
/// ```
pub fn is_authenticated(&self) -> bool {
self.state == AuthState::Ready
}
/// Обработать обновление авторизации
/// Обрабатывает обновление состояния авторизации от TDLib.
///
/// Автоматически обновляет внутреннее состояние [`AuthState`] на основе
/// полученного update от TDLib.
///
/// # Arguments
///
/// * `update` - Обновление от TDLib (проверяется на `Update::AuthorizationState`)
///
/// # Note
///
/// Этот метод должен вызываться для каждого update от TDLib,
/// чтобы состояние авторизации оставалось актуальным.
pub fn handle_auth_update(&mut self, update: &Update) {
if let Update::AuthorizationState(auth_update) = update {
self.state = match &auth_update.authorization_state {
@@ -46,7 +134,25 @@ impl AuthManager {
}
}
/// Отправить номер телефона
/// Отправляет номер телефона для авторизации.
///
/// Используется на этапе [`AuthState::WaitPhoneNumber`].
/// После успешной отправки состояние изменится на `WaitCode`.
///
/// # Arguments
///
/// * `phone` - Номер телефона в международном формате (например, "+1234567890")
///
/// # Returns
///
/// * `Ok(())` - Номер телефона принят, ожидайте SMS с кодом
/// * `Err(String)` - Ошибка (неверный формат, проблемы с сетью и т.д.)
///
/// # Examples
///
/// ```ignore
/// auth_manager.send_phone_number("+1234567890".to_string()).await?;
/// ```
pub async fn send_phone_number(&self, phone: String) -> Result<(), String> {
functions::set_authentication_phone_number(phone, None, self.client_id)
.await
@@ -54,7 +160,26 @@ impl AuthManager {
.map_err(|e| format!("Ошибка отправки номера: {:?}", e))
}
/// Отправить код подтверждения
/// Отправляет код подтверждения из SMS или Telegram.
///
/// Используется на этапе [`AuthState::WaitCode`].
/// После успешной проверки состояние изменится на `Ready` или `WaitPassword`
/// (если включена двухфакторная аутентификация).
///
/// # Arguments
///
/// * `code` - Код подтверждения (обычно 5 цифр)
///
/// # Returns
///
/// * `Ok(())` - Код верный
/// * `Err(String)` - Неверный код или истек срок действия
///
/// # Examples
///
/// ```ignore
/// auth_manager.send_code("12345".to_string()).await?;
/// ```
pub async fn send_code(&self, code: String) -> Result<(), String> {
functions::check_authentication_code(code, self.client_id)
.await
@@ -62,7 +187,27 @@ impl AuthManager {
.map_err(|e| format!("Ошибка проверки кода: {:?}", e))
}
/// Отправить пароль 2FA
/// Отправляет пароль двухфакторной аутентификации (2FA).
///
/// Используется на этапе [`AuthState::WaitPassword`] (только если 2FA включена).
/// После успешной проверки состояние изменится на `Ready`.
///
/// # Arguments
///
/// * `password` - Пароль двухфакторной аутентификации
///
/// # Returns
///
/// * `Ok(())` - Пароль верный, авторизация завершена
/// * `Err(String)` - Неверный пароль
///
/// # Examples
///
/// ```ignore
/// if auth_manager.state == AuthState::WaitPassword {
/// auth_manager.send_password("my_2fa_password".to_string()).await?;
/// }
/// ```
pub async fn send_password(&self, password: String) -> Result<(), String> {
functions::check_authentication_password(password, self.client_id)
.await

View File

@@ -6,17 +6,58 @@ use tdlib_rs::functions;
use super::types::{ChatInfo, FolderInfo, MessageInfo, ProfileInfo};
/// Менеджер чатов
/// Менеджер чатов TDLib.
///
/// Управляет списком чатов, папками, информацией о профилях
/// и typing-статусом собеседников.
///
/// # Основные возможности
///
/// - Загрузка чатов из главного списка и папок
/// - Получение информации о профиле чата/пользователя
/// - Отправка typing-индикатора ("печатает...")
/// - Отслеживание typing-статуса собеседников
/// - Выход из чатов/групп
///
/// # Examples
///
/// ```ignore
/// let mut chat_manager = ChatManager::new(client_id);
///
/// // Загружаем чаты
/// chat_manager.load_chats(50).await?;
///
/// // Получаем информацию о профиле
/// let profile = chat_manager.get_profile_info(chat_id).await?;
/// println!("Bio: {}", profile.bio.unwrap_or_default());
/// ```
pub struct ChatManager {
/// Список загруженных чатов.
pub chats: Vec<ChatInfo>,
/// Список папок чатов.
pub folders: Vec<FolderInfo>,
/// Позиция в главном списке чатов для пагинации.
pub main_chat_list_position: i32,
/// Typing status для текущего чата: (user_id, action_text, timestamp)
/// Typing status для текущего чата: (user_id, action_text, timestamp).
pub typing_status: Option<(UserId, String, Instant)>,
/// ID клиента TDLib для API вызовов.
client_id: i32,
}
impl ChatManager {
/// Создает новый менеджер чатов.
///
/// # Arguments
///
/// * `client_id` - ID клиента TDLib для API вызовов
///
/// # Returns
///
/// Новый экземпляр `ChatManager` с пустым списком чатов.
pub fn new(client_id: i32) -> Self {
Self {
chats: Vec::new(),
@@ -27,7 +68,25 @@ impl ChatManager {
}
}
/// Загрузить чаты из основного списка
/// Загружает чаты из главного списка.
///
/// Запрашивает у TDLib чаты из основного списка (исключая архив).
/// После вызова чаты будут доступны через updates от TDLib.
///
/// # Arguments
///
/// * `limit` - Максимальное количество чатов для загрузки
///
/// # Returns
///
/// * `Ok(())` - Запрос отправлен, чаты будут загружены через updates
/// * `Err(String)` - Ошибка при отправке запроса
///
/// # Examples
///
/// ```ignore
/// chat_manager.load_chats(50).await?;
/// ```
pub async fn load_chats(&mut self, limit: i32) -> Result<(), String> {
let result = functions::load_chats(Some(ChatList::Main), limit, self.client_id).await;
@@ -37,7 +96,24 @@ impl ChatManager {
}
}
/// Загрузить чаты из папки
/// Загружает чаты из указанной папки.
///
/// # Arguments
///
/// * `folder_id` - ID папки чатов
/// * `limit` - Максимальное количество чатов для загрузки
///
/// # Returns
///
/// * `Ok(())` - Запрос отправлен
/// * `Err(String)` - Ошибка при отправке запроса
///
/// # Examples
///
/// ```ignore
/// // Загрузить чаты из папки с ID 1
/// chat_manager.load_folder_chats(1, 50).await?;
/// ```
pub async fn load_folder_chats(&mut self, folder_id: i32, limit: i32) -> Result<(), String> {
let chat_list =
ChatList::Folder(tdlib_rs::types::ChatListFolder { chat_folder_id: folder_id });
@@ -50,7 +126,24 @@ impl ChatManager {
}
}
/// Покинуть чат/группу
/// Выходит из чата или группы.
///
/// Для приватных чатов — удаляет историю, для групп — покидает группу.
///
/// # Arguments
///
/// * `chat_id` - ID чата для выхода
///
/// # Returns
///
/// * `Ok(())` - Успешный выход
/// * `Err(String)` - Ошибка (нет прав, чат не найден и т.д.)
///
/// # Examples
///
/// ```ignore
/// chat_manager.leave_chat(ChatId::new(123456)).await?;
/// ```
pub async fn leave_chat(&self, chat_id: ChatId) -> Result<(), String> {
let result = functions::leave_chat(chat_id.as_i64(), self.client_id).await;
match result {
@@ -59,7 +152,29 @@ impl ChatManager {
}
}
/// Получить информацию профиля чата
/// Получает детальную информацию о профиле чата или пользователя.
///
/// Загружает полную информацию включая bio, номер телефона, username,
/// статус онлайн (для личных чатов), количество участников и описание
/// (для групп/каналов).
///
/// # Arguments
///
/// * `chat_id` - ID чата для получения информации
///
/// # Returns
///
/// * `Ok(ProfileInfo)` - Информация о профиле
/// * `Err(String)` - Ошибка получения данных
///
/// # Examples
///
/// ```ignore
/// let profile = chat_manager.get_profile_info(ChatId::new(123)).await?;
/// println!("Title: {}", profile.title);
/// println!("Bio: {}", profile.bio.unwrap_or_default());
/// println!("Members: {}", profile.member_count.unwrap_or(0));
/// ```
pub async fn get_profile_info(&self, chat_id: ChatId) -> Result<ProfileInfo, String> {
// Получаем основную информацию о чате
let chat_result = functions::get_chat(chat_id.as_i64(), self.client_id).await;
@@ -187,12 +302,55 @@ impl ChatManager {
})
}
/// Отправить typing action
/// Отправляет typing-действие в чат.
///
/// Показывает собеседнику индикатор "печатает..." или другой статус активности.
/// Действие автоматически сбрасывается через 5 секунд.
///
/// # Arguments
///
/// * `chat_id` - ID чата
/// * `action` - Тип действия (Typing, RecordingVideo, UploadingPhoto и т.д.)
///
/// # Note
///
/// Этот метод нужно вызывать периодически (каждые 5 секунд) пока действие активно.
///
/// # Examples
///
/// ```ignore
/// use tdlib_rs::enums::ChatAction;
///
/// // Показать индикатор "печатает..."
/// chat_manager.send_chat_action(
/// chat_id,
/// ChatAction::Typing
/// ).await;
/// ```
pub async fn send_chat_action(&self, chat_id: ChatId, action: ChatAction) {
let _ = functions::send_chat_action(chat_id.as_i64(), 0, Some(action), self.client_id).await;
}
/// Очистить устаревший typing status (вызывать периодически)
/// Очищает устаревший typing-статус.
///
/// Удаляет typing-статус если прошло более 5 секунд с момента последнего обновления.
/// Вызывайте этот метод периодически (например, каждый тик UI) для своевременной
/// очистки индикатора "печатает...".
///
/// # Returns
///
/// * `true` - Если статус был очищен
/// * `false` - Если статус актуален или его не было
///
/// # Examples
///
/// ```ignore
/// // В основном цикле UI
/// if chat_manager.clear_stale_typing_status() {
/// // Перерисовать UI чтобы убрать индикатор "печатает..."
/// needs_redraw = true;
/// }
/// ```
pub fn clear_stale_typing_status(&mut self) -> bool {
if let Some((_, _, timestamp)) = self.typing_status {
if timestamp.elapsed().as_secs() > 5 {
@@ -203,7 +361,20 @@ impl ChatManager {
false
}
/// Получить текст typing индикатора
/// Получает текст typing-индикатора для отображения.
///
/// # Returns
///
/// * `Some(String)` - Текст действия (например, "печатает...", "записывает видео...")
/// * `None` - Нет активного typing-статуса
///
/// # Examples
///
/// ```ignore
/// if let Some(typing_text) = chat_manager.get_typing_text() {
/// println!("Status: {}", typing_text);
/// }
/// ```
pub fn get_typing_text(&self) -> Option<String> {
self.typing_status
.as_ref()

View File

@@ -6,17 +6,65 @@ use tdlib_rs::types::{Chat as TdChat, FormattedText, InputMessageReplyToMessage,
use super::types::{ForwardInfo, MessageBuilder, MessageInfo, ReactionInfo, ReplyInfo};
/// Менеджер сообщений
/// Менеджер сообщений TDLib.
///
/// Управляет загрузкой, отправкой, редактированием и удалением сообщений.
/// Кеширует сообщения текущего открытого чата и закрепленные сообщения.
///
/// # Основные возможности
///
/// - Загрузка истории сообщений чата
/// - Отправка текстовых сообщений с поддержкой Markdown
/// - Редактирование и удаление сообщений
/// - Пересылка сообщений между чатами
/// - Поиск сообщений по тексту
/// - Управление закрепленными сообщениями
/// - Управление черновиками
/// - Автоматическая отметка сообщений как прочитанных
///
/// # Examples
///
/// ```ignore
/// let mut msg_manager = MessageManager::new(client_id);
///
/// // Загрузить историю чата
/// let messages = msg_manager.get_chat_history(chat_id, 50).await?;
///
/// // Отправить сообщение
/// let msg = msg_manager.send_message(
/// chat_id,
/// "Hello, **world**!".to_string(),
/// None,
/// None
/// ).await?;
/// ```
pub struct MessageManager {
/// Список сообщений текущего открытого чата (до MAX_MESSAGES_IN_CHAT).
pub current_chat_messages: Vec<MessageInfo>,
/// ID текущего открытого чата.
pub current_chat_id: Option<ChatId>,
/// Текущее закрепленное сообщение открытого чата.
pub current_pinned_message: Option<MessageInfo>,
/// Очередь сообщений для отметки как прочитанных: (chat_id, message_ids)
/// Очередь сообщений для отметки как прочитанных: (chat_id, message_ids).
pub pending_view_messages: Vec<(ChatId, Vec<MessageId>)>,
/// ID клиента TDLib для API вызовов.
client_id: i32,
}
impl MessageManager {
/// Создает новый менеджер сообщений.
///
/// # Arguments
///
/// * `client_id` - ID клиента TDLib для API вызовов
///
/// # Returns
///
/// Новый экземпляр `MessageManager` с пустым списком сообщений.
pub fn new(client_id: i32) -> Self {
Self {
current_chat_messages: Vec::new(),
@@ -27,7 +75,19 @@ impl MessageManager {
}
}
/// Добавить сообщение в список текущего чата
/// Добавляет сообщение в список текущего чата.
///
/// Автоматически ограничивает размер списка до [`MAX_MESSAGES_IN_CHAT`],
/// удаляя старые сообщения при превышении лимита.
///
/// # Arguments
///
/// * `msg` - Сообщение для добавления
///
/// # Note
///
/// Сообщение добавляется в конец списка. При превышении лимита
/// удаляются самые старые сообщения из начала списка.
pub fn push_message(&mut self, msg: MessageInfo) {
self.current_chat_messages.push(msg); // Добавляем в конец
@@ -37,7 +97,31 @@ impl MessageManager {
}
}
/// Получить историю чата
/// Загружает историю сообщений чата.
///
/// Запрашивает последние сообщения из указанного чата и сохраняет их
/// в [`current_chat_messages`](Self::current_chat_messages). Делает несколько попыток
/// загрузки при неудаче.
///
/// # Arguments
///
/// * `chat_id` - ID чата для загрузки истории
/// * `limit` - Максимальное количество сообщений (обычно до 50)
///
/// # Returns
///
/// * `Ok(Vec<MessageInfo>)` - Список загруженных сообщений (от старых к новым)
/// * `Err(String)` - Ошибка загрузки после всех попыток
///
/// # Examples
///
/// ```ignore
/// let messages = msg_manager.get_chat_history(
/// ChatId::new(123),
/// 50
/// ).await?;
/// println!("Loaded {} messages", messages.len());
/// ```
pub async fn get_chat_history(
&mut self,
chat_id: ChatId,
@@ -102,7 +186,30 @@ impl MessageManager {
Ok(all_messages)
}
/// Загрузить более старые сообщения
/// Загружает более старые сообщения для пагинации.
///
/// Используется для подгрузки предыдущих сообщений при прокрутке
/// истории чата вверх.
///
/// # Arguments
///
/// * `chat_id` - ID чата
/// * `from_message_id` - ID сообщения, от которого загружать историю
///
/// # Returns
///
/// * `Ok(Vec<MessageInfo>)` - Список старых сообщений (от старых к новым)
/// * `Err(String)` - Ошибка загрузки
///
/// # Examples
///
/// ```ignore
/// // Загрузить сообщения старше указанного
/// let older = msg_manager.load_older_messages(
/// chat_id,
/// MessageId::new(12345)
/// ).await?;
/// ```
pub async fn load_older_messages(
&mut self,
chat_id: ChatId,
@@ -135,7 +242,25 @@ impl MessageManager {
}
}
/// Получить закреплённые сообщения
/// Получает все закрепленные сообщения чата.
///
/// Выполняет поиск всех сообщений с фильтром "pinned" и возвращает их список.
///
/// # Arguments
///
/// * `chat_id` - ID чата
///
/// # Returns
///
/// * `Ok(Vec<MessageInfo>)` - Список закрепленных сообщений (до 100)
/// * `Err(String)` - Ошибка загрузки
///
/// # Examples
///
/// ```ignore
/// let pinned = msg_manager.get_pinned_messages(chat_id).await?;
/// println!("Found {} pinned messages", pinned.len());
/// ```
pub async fn get_pinned_messages(&mut self, chat_id: ChatId) -> Result<Vec<MessageInfo>, String> {
let result = functions::search_chat_messages(
chat_id.as_i64(),
@@ -166,7 +291,17 @@ impl MessageManager {
}
}
/// Загрузить текущее закреплённое сообщение
/// Загружает текущее верхнее закрепленное сообщение.
///
/// # Arguments
///
/// * `chat_id` - ID чата
///
/// # Note
///
/// TODO: В tdlib-rs 1.8.29 поле `pinned_message_id` было удалено из `Chat`.
/// Нужно использовать `getChatPinnedMessage` или альтернативный способ.
/// Временно отключено, возвращает `None`.
pub async fn load_current_pinned_message(&mut self, chat_id: ChatId) {
// TODO: В tdlib-rs 1.8.29 поле pinned_message_id было удалено из Chat.
// Нужно использовать getChatPinnedMessage или альтернативный способ.
@@ -182,7 +317,23 @@ impl MessageManager {
// }
}
/// Поиск сообщений в чате
/// Выполняет поиск сообщений по тексту в указанном чате.
///
/// # Arguments
///
/// * `chat_id` - ID чата для поиска
/// * `query` - Текстовый запрос для поиска
///
/// # Returns
///
/// * `Ok(Vec<MessageInfo>)` - Найденные сообщения (до 100)
/// * `Err(String)` - Ошибка поиска
///
/// # Examples
///
/// ```ignore
/// let results = msg_manager.search_messages(chat_id, "hello").await?;
/// ```
pub async fn search_messages(
&self,
chat_id: ChatId,
@@ -217,7 +368,41 @@ impl MessageManager {
}
}
/// Отправить сообщение
/// Отправляет текстовое сообщение в чат с поддержкой Markdown.
///
/// Автоматически парсит Markdown v2 форматирование (**bold**, *italic*, `code` и т.д.).
///
/// # Arguments
///
/// * `chat_id` - ID чата-получателя
/// * `text` - Текст сообщения (поддерживает Markdown v2)
/// * `reply_to_message_id` - Опциональный ID сообщения для ответа
/// * `reply_info` - Опциональная информация об исходном сообщении
///
/// # Returns
///
/// * `Ok(MessageInfo)` - Отправленное сообщение
/// * `Err(String)` - Ошибка отправки
///
/// # Examples
///
/// ```ignore
/// // Простое сообщение
/// let msg = msg_manager.send_message(
/// chat_id,
/// "Hello, **world**!".to_string(),
/// None,
/// None
/// ).await?;
///
/// // Ответ на сообщение
/// let reply = msg_manager.send_message(
/// chat_id,
/// "Got it!".to_string(),
/// Some(MessageId::new(123)),
/// Some(reply_info)
/// ).await?;
/// ```
pub async fn send_message(
&self,
chat_id: ChatId,
@@ -288,7 +473,18 @@ impl MessageManager {
}
}
/// Редактировать сообщение
/// Редактирует существующее сообщение.
///
/// # Arguments
///
/// * `chat_id` - ID чата
/// * `message_id` - ID сообщения для редактирования
/// * `text` - Новый текст (поддерживает Markdown v2)
///
/// # Returns
///
/// * `Ok(MessageInfo)` - Отредактированное сообщение
/// * `Err(String)` - Ошибка (нет прав, сообщение слишком старое и т.д.)
pub async fn edit_message(
&self,
chat_id: ChatId,
@@ -333,7 +529,18 @@ impl MessageManager {
}
}
/// Удалить сообщения
/// Удаляет одно или несколько сообщений.
///
/// # Arguments
///
/// * `chat_id` - ID чата
/// * `message_ids` - Список ID сообщений для удаления
/// * `revoke` - `true` - удалить для всех, `false` - только для себя
///
/// # Returns
///
/// * `Ok(())` - Сообщения удалены
/// * `Err(String)` - Ошибка удаления
pub async fn delete_messages(
&self,
chat_id: ChatId,
@@ -349,7 +556,18 @@ impl MessageManager {
}
}
/// Переслать сообщения
/// Пересылает сообщения из одного чата в другой.
///
/// # Arguments
///
/// * `to_chat_id` - ID чата-получателя
/// * `from_chat_id` - ID чата-источника
/// * `message_ids` - Список ID сообщений для пересылки
///
/// # Returns
///
/// * `Ok(())` - Сообщения переслань
/// * `Err(String)` - Ошибка пересылки
pub async fn forward_messages(
&self,
to_chat_id: ChatId,
@@ -375,7 +593,20 @@ impl MessageManager {
}
}
/// Установить черновик
/// Сохраняет черновик сообщения для чата.
///
/// Черновик отображается в списке чатов и восстанавливается
/// при следующем открытии чата.
///
/// # Arguments
///
/// * `chat_id` - ID чата
/// * `text` - Текст черновика (пустая строка удаляет черновик)
///
/// # Returns
///
/// * `Ok(())` - Черновик сохранен
/// * `Err(String)` - Ошибка сохранения
pub async fn set_draft_message(&self, chat_id: ChatId, text: String) -> Result<(), String> {
use tdlib_rs::types::DraftMessage;
@@ -404,7 +635,14 @@ impl MessageManager {
}
}
/// Обработать очередь просмотра сообщений
/// Обрабатывает очередь сообщений для отметки как прочитанных.
///
/// Автоматически отмечает просмотренные сообщения как прочитанные,
/// что сбрасывает счетчик непрочитанных сообщений в чате.
///
/// # Note
///
/// Вызывайте периодически (например, в основном цикле) для обработки накопленной очереди.
pub async fn process_pending_view_messages(&mut self) {
if self.pending_view_messages.is_empty() {
return;
@@ -571,7 +809,14 @@ impl MessageManager {
Some(builder.build())
}
/// Получить недостающую reply информацию для сообщений
/// Загружает недостающую информацию об исходных сообщениях для ответов.
///
/// Ищет все reply-сообщения с `sender_name == "Unknown"` и загружает
/// полную информацию (имя отправителя, текст) из TDLib.
///
/// # Note
///
/// Вызывайте после загрузки истории чата для заполнения информации о цитируемых сообщениях.
pub async fn fetch_missing_reply_info(&mut self) {
// Collect message IDs that need to be fetched
let mut to_fetch = Vec::new();

View File

@@ -3,17 +3,66 @@ use tdlib_rs::enums::ReactionType;
use tdlib_rs::functions;
use tdlib_rs::types::ReactionTypeEmoji;
/// Менеджер реакций на сообщения
/// Менеджер реакций на сообщения.
///
/// Управляет добавлением, удалением и получением списка доступных
/// реакций (emoji) для сообщений в чатах.
///
/// # Examples
///
/// ```ignore
/// let reaction_manager = ReactionManager::new(client_id);
///
/// // Получить доступные реакции
/// let reactions = reaction_manager.get_message_available_reactions(
/// chat_id,
/// message_id
/// ).await?;
///
/// // Добавить/удалить реакцию
/// reaction_manager.toggle_reaction(chat_id, message_id, "👍".to_string()).await?;
/// ```
pub struct ReactionManager {
/// ID клиента TDLib для API вызовов.
client_id: i32,
}
impl ReactionManager {
/// Создает новый менеджер реакций.
///
/// # Arguments
///
/// * `client_id` - ID клиента TDLib для API вызовов
pub fn new(client_id: i32) -> Self {
Self { client_id }
}
/// Получить доступные реакции для сообщения
/// Получает список доступных реакций для сообщения.
///
/// # Arguments
///
/// * `chat_id` - ID чата
/// * `message_id` - ID сообщения
///
/// # Returns
///
/// * `Ok(Vec<String>)` - Список доступных emoji реакций
/// * `Err(String)` - Ошибка получения
///
/// # Note
///
/// В tdlib-rs 1.8.29 структура AvailableReactions изменилась.
/// Временно возвращается стандартный набор из 12 популярных реакций.
///
/// # Examples
///
/// ```ignore
/// let reactions = manager.get_message_available_reactions(
/// ChatId::new(123),
/// MessageId::new(456)
/// ).await?;
/// println!("Available: {:?}", reactions);
/// ```
pub async fn get_message_available_reactions(
&self,
chat_id: ChatId,
@@ -87,7 +136,28 @@ impl ReactionManager {
}
}
/// Переключить реакцию на сообщение
/// Переключает реакцию на сообщение (добавляет/удаляет).
///
/// Сначала пытается добавить реакцию. Если не удалось (уже есть),
/// то удаляет её.
///
/// # Arguments
///
/// * `chat_id` - ID чата
/// * `message_id` - ID сообщения
/// * `emoji` - Emoji реакции (например, "👍", "❤️")
///
/// # Returns
///
/// * `Ok(())` - Реакция переключена
/// * `Err(String)` - Ошибка переключения
///
/// # Examples
///
/// ```ignore
/// // Добавить или удалить 👍
/// manager.toggle_reaction(chat_id, message_id, "👍".to_string()).await?;
/// ```
pub async fn toggle_reaction(
&self,
chat_id: ChatId,

View File

@@ -6,15 +6,35 @@ use tdlib_rs::functions;
use super::types::UserOnlineStatus;
/// Простой LRU-кэш на основе HashMap + Vec для отслеживания порядка
/// LRU (Least Recently Used) кэш с фиксированной ёмкостью.
///
/// Автоматически удаляет самые давно использованные элементы при достижении лимита.
/// Основан на HashMap для быстрого доступа и Vec для отслеживания порядка использования.
///
/// # Type Parameters
///
/// * `V` - Тип значения (должен реализовывать `Clone`)
///
/// # Examples
///
/// ```ignore
/// let mut cache = LruCache::<String>::new(100);
/// cache.insert(UserId::new(1), "Alice".to_string());
/// assert_eq!(cache.get(&UserId::new(1)), Some(&"Alice".to_string()));
/// ```
pub struct LruCache<V> {
/// Хранилище ключ-значение.
map: HashMap<UserId, V>,
/// Порядок доступа: последний элемент — самый недавно использованный
/// Порядок доступа: последний элемент — самый недавно использованный.
order: Vec<UserId>,
/// Максимальная ёмкость кэша.
capacity: usize,
}
impl<V: Clone> LruCache<V> {
/// Создает новый LRU кэш с заданной ёмкостью.
pub fn new(capacity: usize) -> Self {
Self {
map: HashMap::with_capacity(capacity),
@@ -23,7 +43,7 @@ impl<V: Clone> LruCache<V> {
}
}
/// Получить значение и обновить порядок доступа
/// Получает значение и обновляет порядок доступа (помечает как использованное).
pub fn get(&mut self, key: &UserId) -> Option<&V> {
if self.map.contains_key(key) {
// Перемещаем ключ в конец (самый недавно использованный)
@@ -72,22 +92,56 @@ impl<V: Clone> LruCache<V> {
}
}
/// Кеш пользователей и их данных
/// Кэш информации о пользователях Telegram.
///
/// Хранит данные пользователей (имена, usernames, статусы) в LRU-кэшах
/// для быстрого доступа без повторных запросов к TDLib.
///
/// # Возможности
///
/// - Кэширование имен пользователей (first_name + last_name)
/// - Кэширование usernames (@username)
/// - Кэширование онлайн-статусов
/// - Связь chat_id → user_id для приватных чатов
/// - Ленивая загрузка данных пользователей порциями
///
/// # Examples
///
/// ```ignore
/// let mut cache = UserCache::new(client_id);
///
/// // Обработать обновление пользователя
/// cache.handle_user_update(&user_enum);
///
/// // Получить имя
/// let name = cache.get_user_name(user_id).await;
/// ```
pub struct UserCache {
/// LRU-кэш usernames: user_id -> username
/// LRU-кэш usernames: user_id username.
pub user_usernames: LruCache<String>,
/// LRU-кэш имён: user_id -> display_name (first_name + last_name)
/// LRU-кэш имён: user_id → display_name (first_name + last_name).
pub user_names: LruCache<String>,
/// Связь chat_id -> user_id для приватных чатов
/// Связь chat_id → user_id для приватных чатов.
pub chat_user_ids: HashMap<ChatId, UserId>,
/// Очередь user_id для загрузки имён
/// Очередь user_id для ленивой загрузки имён.
pub pending_user_ids: Vec<UserId>,
/// LRU-кэш онлайн-статусов пользователей: user_id -> status
/// LRU-кэш онлайн-статусов: user_id → status.
pub user_statuses: LruCache<UserOnlineStatus>,
/// ID клиента TDLib для API вызовов.
client_id: i32,
}
impl UserCache {
/// Создает новый кэш пользователей.
///
/// # Arguments
///
/// * `client_id` - ID клиента TDLib для API вызовов
pub fn new(client_id: i32) -> Self {
Self {
user_usernames: LruCache::new(MAX_USER_CACHE_SIZE),
@@ -120,7 +174,13 @@ impl UserCache {
self.user_statuses.peek(user_id)
}
/// Обработать обновление пользователя
/// Обрабатывает обновление пользователя от TDLib.
///
/// Сохраняет username, имя и статус пользователя в соответствующие кэши.
///
/// # Arguments
///
/// * `user_enum` - Обновление пользователя от TDLib
pub fn handle_user_update(&mut self, user_enum: &User) {
if let User::User(user) = user_enum {
let user_id = user.id;
@@ -139,7 +199,12 @@ impl UserCache {
}
}
/// Обработать обновление статуса пользователя
/// Обновляет онлайн-статус пользователя.
///
/// # Arguments
///
/// * `user_id` - ID пользователя
/// * `status` - Новый статус от TDLib
pub fn update_status(&mut self, user_id: UserId, status: &UserStatus) {
let online_status = match status {
UserStatus::Online(_) => UserOnlineStatus::Online,
@@ -157,7 +222,17 @@ impl UserCache {
self.chat_user_ids.insert(chat_id, user_id);
}
/// Получить имя пользователя (асинхронно с загрузкой если нужно)
/// Получает имя пользователя из кэша или загружает из TDLib.
///
/// Сначала проверяет кэш, затем при необходимости загружает из API.
///
/// # Arguments
///
/// * `user_id` - ID пользователя
///
/// # Returns
///
/// Имя пользователя (first_name + last_name) или "User {id}" если не найден.
pub async fn get_user_name(&self, user_id: UserId) -> String {
// Сначала пытаемся получить из кэша
if let Some(name) = self.user_names.peek(&user_id) {
@@ -174,7 +249,14 @@ impl UserCache {
}
}
/// Обработать очередь отложенных user_ids (загрузка имён небольшими порциями)
/// Обрабатывает очередь отложенных user_ids для ленивой загрузки.
///
/// Загружает данные пользователей небольшими порциями (по [`LAZY_LOAD_USERS_PER_TICK`])
/// для избежания блокировки UI.
///
/// # Note
///
/// Вызывайте периодически в основном цикле приложения.
pub async fn process_pending_user_ids(&mut self) {
if self.pending_user_ids.is_empty() {
return;