use std::env; use std::time::Instant; use tdlib_rs::enums::{ AuthorizationState, ChatAction, ChatList, ChatType, ConnectionState, MessageSender, Update, UserStatus, Chat as TdChat }; use tdlib_rs::types::{Message as TdMessage}; use tdlib_rs::functions; use crate::constants::{MAX_CHAT_USER_IDS, MAX_CHATS}; use super::auth::{AuthManager, AuthState}; use super::chats::ChatManager; use super::messages::MessageManager; use super::reactions::ReactionManager; use super::types::{ChatInfo, FolderInfo, ForwardInfo, MessageInfo, NetworkState, ProfileInfo, ReactionInfo, ReplyInfo, UserOnlineStatus}; use super::users::UserCache; pub struct TdClient { pub api_id: i32, pub api_hash: String, client_id: i32, // Менеджеры (делегируем им функциональность) pub auth: AuthManager, pub chat_manager: ChatManager, pub message_manager: MessageManager, pub user_cache: UserCache, pub reaction_manager: ReactionManager, // Состояние сети pub network_state: NetworkState, } #[allow(dead_code)] impl TdClient { pub fn new() -> Self { let api_id = env::var("API_ID") .unwrap_or_else(|_| "0".to_string()) .parse() .unwrap_or(0); let api_hash = env::var("API_HASH").unwrap_or_default(); let client_id = tdlib_rs::create_client(); Self { api_id, api_hash, client_id, auth: AuthManager::new(client_id), chat_manager: ChatManager::new(client_id), message_manager: MessageManager::new(client_id), user_cache: UserCache::new(client_id), reaction_manager: ReactionManager::new(client_id), network_state: NetworkState::Connecting, } } // Делегирование к auth pub fn is_authenticated(&self) -> bool { self.auth.is_authenticated() } pub async fn send_phone_number(&self, phone: String) -> Result<(), String> { self.auth.send_phone_number(phone).await } pub async fn send_code(&self, code: String) -> Result<(), String> { self.auth.send_code(code).await } pub async fn send_password(&self, password: String) -> Result<(), String> { self.auth.send_password(password).await } // Делегирование к chat_manager pub async fn load_chats(&mut self, limit: i32) -> Result<(), String> { self.chat_manager.load_chats(limit).await } pub async fn load_folder_chats(&mut self, folder_id: i32, limit: i32) -> Result<(), String> { self.chat_manager.load_folder_chats(folder_id, limit).await } pub async fn leave_chat(&self, chat_id: i64) -> Result<(), String> { self.chat_manager.leave_chat(chat_id).await } pub async fn get_profile_info(&self, chat_id: i64) -> Result { self.chat_manager.get_profile_info(chat_id).await } pub async fn send_chat_action(&self, chat_id: i64, action: tdlib_rs::enums::ChatAction) { self.chat_manager.send_chat_action(chat_id, action).await } pub fn get_typing_text(&self) -> Option { self.chat_manager.get_typing_text() } pub fn clear_stale_typing_status(&mut self) -> bool { self.chat_manager.clear_stale_typing_status() } // Делегирование к message_manager pub async fn get_chat_history( &mut self, chat_id: i64, limit: i32, ) -> Result, String> { self.message_manager.get_chat_history(chat_id, limit).await } pub async fn load_older_messages( &mut self, chat_id: i64, from_message_id: i64, ) -> Result, String> { self.message_manager .load_older_messages(chat_id, from_message_id) .await } pub async fn get_pinned_messages(&mut self, chat_id: i64) -> Result, String> { self.message_manager.get_pinned_messages(chat_id).await } pub async fn load_current_pinned_message(&mut self, chat_id: i64) { self.message_manager.load_current_pinned_message(chat_id).await } pub async fn search_messages( &self, chat_id: i64, query: &str, ) -> Result, String> { self.message_manager.search_messages(chat_id, query).await } pub async fn send_message( &self, chat_id: i64, text: String, reply_to_message_id: Option, reply_info: Option, ) -> Result { self.message_manager .send_message(chat_id, text, reply_to_message_id, reply_info) .await } pub async fn edit_message( &self, chat_id: i64, message_id: i64, text: String, ) -> Result { self.message_manager .edit_message(chat_id, message_id, text) .await } pub async fn delete_messages( &self, chat_id: i64, message_ids: Vec, revoke: bool, ) -> Result<(), String> { self.message_manager .delete_messages(chat_id, message_ids, revoke) .await } pub async fn forward_messages( &self, to_chat_id: i64, from_chat_id: i64, message_ids: Vec, ) -> Result<(), String> { self.message_manager .forward_messages(to_chat_id, from_chat_id, message_ids) .await } pub async fn set_draft_message(&self, chat_id: i64, text: String) -> Result<(), String> { self.message_manager.set_draft_message(chat_id, text).await } pub fn push_message(&mut self, msg: MessageInfo) { self.message_manager.push_message(msg) } pub async fn fetch_missing_reply_info(&mut self) { self.message_manager.fetch_missing_reply_info().await } pub async fn process_pending_view_messages(&mut self) { self.message_manager.process_pending_view_messages().await } // Делегирование к user_cache pub async fn get_user_name(&self, user_id: i64) -> String { self.user_cache.get_user_name(user_id).await } pub fn get_user_status_by_chat_id(&self, chat_id: i64) -> Option<&UserOnlineStatus> { self.user_cache.get_status_by_chat_id(chat_id) } pub async fn process_pending_user_ids(&mut self) { self.user_cache.process_pending_user_ids().await } // Делегирование к reaction_manager pub async fn get_message_available_reactions( &self, chat_id: i64, message_id: i64, ) -> Result, String> { self.reaction_manager .get_message_available_reactions(chat_id, message_id) .await } pub async fn toggle_reaction( &self, chat_id: i64, message_id: i64, emoji: String, ) -> Result<(), String> { self.reaction_manager .toggle_reaction(chat_id, message_id, emoji) .await } // Вспомогательные методы pub fn client_id(&self) -> i32 { self.client_id } pub async fn get_me(&self) -> Result { match functions::get_me(self.client_id).await { Ok(tdlib_rs::enums::User::User(user)) => Ok(user.id), Ok(_) => Err("Неожиданный тип пользователя".to_string()), Err(e) => Err(format!("Ошибка получения текущего пользователя: {:?}", e)), } } // Accessor methods для обратной совместимости pub fn auth_state(&self) -> &AuthState { &self.auth.state } pub fn chats(&self) -> &[ChatInfo] { &self.chat_manager.chats } pub fn chats_mut(&mut self) -> &mut Vec { &mut self.chat_manager.chats } pub fn folders(&self) -> &[FolderInfo] { &self.chat_manager.folders } pub fn folders_mut(&mut self) -> &mut Vec { &mut self.chat_manager.folders } pub fn current_chat_messages(&self) -> &[MessageInfo] { &self.message_manager.current_chat_messages } pub fn current_chat_messages_mut(&mut self) -> &mut Vec { &mut self.message_manager.current_chat_messages } pub fn current_chat_id(&self) -> Option { self.message_manager.current_chat_id } pub fn set_current_chat_id(&mut self, chat_id: Option) { self.message_manager.current_chat_id = chat_id; } pub fn current_pinned_message(&self) -> Option<&MessageInfo> { self.message_manager.current_pinned_message.as_ref() } pub fn set_current_pinned_message(&mut self, msg: Option) { self.message_manager.current_pinned_message = msg; } pub fn typing_status(&self) -> Option<&(i64, String, std::time::Instant)> { self.chat_manager.typing_status.as_ref() } pub fn set_typing_status(&mut self, status: Option<(i64, String, std::time::Instant)>) { self.chat_manager.typing_status = status; } pub fn pending_view_messages(&self) -> &[(i64, Vec)] { &self.message_manager.pending_view_messages } pub fn pending_view_messages_mut(&mut self) -> &mut Vec<(i64, Vec)> { &mut self.message_manager.pending_view_messages } pub fn pending_user_ids(&self) -> &[i64] { &self.user_cache.pending_user_ids } pub fn pending_user_ids_mut(&mut self) -> &mut Vec { &mut self.user_cache.pending_user_ids } pub fn main_chat_list_position(&self) -> i32 { self.chat_manager.main_chat_list_position } pub fn set_main_chat_list_position(&mut self, position: i32) { self.chat_manager.main_chat_list_position = position; } // User cache accessors pub fn user_cache(&self) -> &UserCache { &self.user_cache } pub fn user_cache_mut(&mut self) -> &mut UserCache { &mut self.user_cache } /// Инициализация TDLib pub async fn init(&mut self) -> Result<(), String> { let result = functions::set_tdlib_parameters( false, // use_test_dc "tdlib_data".to_string(), // database_directory "".to_string(), // files_directory "".to_string(), // database_encryption_key true, // use_file_database true, // use_chat_info_database true, // use_message_database false, // use_secret_chats self.api_id, // api_id self.api_hash.clone(), // api_hash "en".to_string(), // system_language_code "Desktop".to_string(), // device_model "".to_string(), // system_version env!("CARGO_PKG_VERSION").to_string(), // application_version self.client_id, ) .await; match result { Ok(_) => Ok(()), Err(e) => Err(format!("Failed to set TDLib parameters: {:?}", e)), } } /// Обрабатываем одно обновление от TDLib pub fn handle_update(&mut self, update: Update) { match update { Update::AuthorizationState(state) => { self.handle_auth_state(state.authorization_state); } Update::NewChat(new_chat) => { // new_chat.chat is already a Chat struct, wrap it in TdChat enum let td_chat = TdChat::Chat(new_chat.chat.clone()); self.add_or_update_chat(&td_chat); } Update::ChatLastMessage(update) => { let chat_id = update.chat_id; let (last_message_text, last_message_date) = update .last_message .as_ref() .map(|msg| (Self::extract_message_text_static(msg).0, msg.date)) .unwrap_or_default(); if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == chat_id) { chat.last_message = last_message_text; chat.last_message_date = last_message_date; } // Обновляем позиции если они пришли for pos in &update.positions { if matches!(pos.list, ChatList::Main) { if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == chat_id) { chat.order = pos.order; chat.is_pinned = pos.is_pinned; } } } // Пересортируем по order self.chats_mut().sort_by(|a, b| b.order.cmp(&a.order)); } Update::ChatReadInbox(update) => { if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == update.chat_id) { chat.unread_count = update.unread_count; } } Update::ChatUnreadMentionCount(update) => { if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == update.chat_id) { chat.unread_mention_count = update.unread_mention_count; } } Update::ChatNotificationSettings(update) => { if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == update.chat_id) { // mute_for > 0 означает что чат замьючен chat.is_muted = update.notification_settings.mute_for > 0; } } Update::ChatReadOutbox(update) => { // Обновляем last_read_outbox_message_id когда собеседник прочитал сообщения if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == update.chat_id) { chat.last_read_outbox_message_id = update.last_read_outbox_message_id; } // Если это текущий открытый чат — обновляем is_read у сообщений if Some(update.chat_id) == self.current_chat_id() { for msg in self.current_chat_messages_mut().iter_mut() { if msg.is_outgoing && msg.id <= update.last_read_outbox_message_id { msg.is_read = true; } } } } Update::ChatPosition(update) => { // Обновляем позицию чата или удаляем его из списка match &update.position.list { ChatList::Main => { if update.position.order == 0 { // Чат больше не в Main (перемещён в архив и т.д.) self.chats_mut().retain(|c| c.id != update.chat_id); } else if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == update.chat_id) { // Обновляем позицию существующего чата chat.order = update.position.order; chat.is_pinned = update.position.is_pinned; } // Пересортируем по order self.chats_mut().sort_by(|a, b| b.order.cmp(&a.order)); } ChatList::Folder(folder) => { // Обновляем folder_ids для чата if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == update.chat_id) { if update.position.order == 0 { // Чат удалён из папки chat.folder_ids.retain(|&id| id != folder.chat_folder_id); } else { // Чат добавлен в папку if !chat.folder_ids.contains(&folder.chat_folder_id) { chat.folder_ids.push(folder.chat_folder_id); } } } } ChatList::Archive => { // Архив пока не обрабатываем } } } Update::NewMessage(new_msg) => { // Добавляем новое сообщение если это текущий открытый чат let chat_id = new_msg.message.chat_id; if Some(chat_id) == self.current_chat_id() { let msg_info = self.convert_message(&new_msg.message, chat_id); let msg_id = msg_info.id; let is_incoming = !msg_info.is_outgoing; // Проверяем, есть ли уже сообщение с таким id let existing_idx = self .current_chat_messages() .iter() .position(|m| m.id == msg_info.id); match existing_idx { Some(idx) => { // Сообщение уже есть - обновляем if is_incoming { self.current_chat_messages_mut()[idx] = msg_info; } else { // Для исходящих: обновляем can_be_edited и другие поля, // но сохраняем reply_to (добавленный при отправке) let existing = &mut self.current_chat_messages_mut()[idx]; existing.can_be_edited = msg_info.can_be_edited; existing.can_be_deleted_only_for_self = msg_info.can_be_deleted_only_for_self; existing.can_be_deleted_for_all_users = msg_info.can_be_deleted_for_all_users; existing.is_read = msg_info.is_read; } } None => { // Нового сообщения нет - добавляем self.push_message(msg_info); // Если это входящее сообщение — добавляем в очередь для отметки как прочитанное if is_incoming { self.pending_view_messages_mut().push((chat_id, vec![msg_id])); } } } } } Update::User(update) => { // Сохраняем имя и username пользователя let user = update.user; // Пропускаем удалённые аккаунты (пустое имя) if user.first_name.is_empty() && user.last_name.is_empty() { // Удаляем чаты с этим пользователем из списка let user_id = user.id; // Clone chat_user_ids to avoid borrow conflict let chat_user_ids = self.user_cache.chat_user_ids.clone(); self.chats_mut() .retain(|c| chat_user_ids.get(&c.id) != Some(&user_id)); return; } // Сохраняем display name (first_name + last_name) let display_name = if user.last_name.is_empty() { user.first_name.clone() } else { format!("{} {}", user.first_name, user.last_name) }; self.user_cache.user_names.insert(user.id, display_name); // Сохраняем username если есть if let Some(usernames) = user.usernames { if let Some(username) = usernames.active_usernames.first() { self.user_cache.user_usernames.insert(user.id, username.clone()); // Обновляем username в чатах, связанных с этим пользователем for (&chat_id, &user_id) in &self.user_cache.chat_user_ids.clone() { if user_id == user.id { if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == chat_id) { chat.username = Some(format!("@{}", username)); } } } } } // LRU-кэш автоматически удаляет старые записи при вставке } Update::ChatFolders(update) => { // Обновляем список папок *self.folders_mut() = update .chat_folders .into_iter() .map(|f| FolderInfo { id: f.id, name: f.title }) .collect(); self.set_main_chat_list_position(update.main_chat_list_position); } Update::UserStatus(update) => { // Обновляем онлайн-статус пользователя let status = match update.status { UserStatus::Online(_) => UserOnlineStatus::Online, UserStatus::Offline(offline) => UserOnlineStatus::Offline(offline.was_online), UserStatus::Recently(_) => UserOnlineStatus::Recently, UserStatus::LastWeek(_) => UserOnlineStatus::LastWeek, UserStatus::LastMonth(_) => UserOnlineStatus::LastMonth, UserStatus::Empty => UserOnlineStatus::LongTimeAgo, }; self.user_cache.user_statuses.insert(update.user_id, status); } Update::ConnectionState(update) => { // Обновляем состояние сетевого соединения self.network_state = match update.state { ConnectionState::WaitingForNetwork => NetworkState::WaitingForNetwork, ConnectionState::ConnectingToProxy => NetworkState::ConnectingToProxy, ConnectionState::Connecting => NetworkState::Connecting, ConnectionState::Updating => NetworkState::Updating, ConnectionState::Ready => NetworkState::Ready, }; } Update::ChatAction(update) => { // Обрабатываем только для текущего открытого чата if Some(update.chat_id) == self.current_chat_id() { // Извлекаем user_id из sender_id let user_id = match update.sender_id { MessageSender::User(user) => Some(user.user_id), MessageSender::Chat(_) => None, // Игнорируем действия от имени чата }; if let Some(user_id) = user_id { // Определяем текст действия let action_text = match update.action { ChatAction::Typing => Some("печатает...".to_string()), ChatAction::RecordingVideo => Some("записывает видео...".to_string()), ChatAction::UploadingVideo(_) => { Some("отправляет видео...".to_string()) } ChatAction::RecordingVoiceNote => { Some("записывает голосовое...".to_string()) } ChatAction::UploadingVoiceNote(_) => { Some("отправляет голосовое...".to_string()) } ChatAction::UploadingPhoto(_) => Some("отправляет фото...".to_string()), ChatAction::UploadingDocument(_) => { Some("отправляет файл...".to_string()) } ChatAction::ChoosingSticker => Some("выбирает стикер...".to_string()), ChatAction::RecordingVideoNote => { Some("записывает видеосообщение...".to_string()) } ChatAction::UploadingVideoNote(_) => { Some("отправляет видеосообщение...".to_string()) } ChatAction::Cancel => None, // Отмена — сбрасываем статус _ => None, }; if let Some(text) = action_text { self.set_typing_status(Some((user_id, text, Instant::now()))); } else { // Cancel или неизвестное действие — сбрасываем self.set_typing_status(None); } } } } Update::ChatDraftMessage(update) => { // Обновляем черновик в списке чатов if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == update.chat_id) { chat.draft_text = update.draft_message.as_ref().and_then(|draft| { // Извлекаем текст из InputMessageText if let tdlib_rs::enums::InputMessageContent::InputMessageText(text_msg) = &draft.input_message_text { Some(text_msg.text.text.clone()) } else { None } }); } } Update::MessageInteractionInfo(update) => { // Обновляем реакции в текущем открытом чате if Some(update.chat_id) == self.current_chat_id() { if let Some(msg) = self .current_chat_messages_mut() .iter_mut() .find(|m| m.id == update.message_id) { // Извлекаем реакции из interaction_info msg.reactions = update .interaction_info .as_ref() .and_then(|info| info.reactions.as_ref()) .map(|reactions| { reactions .reactions .iter() .filter_map(|reaction| { let emoji = match &reaction.r#type { tdlib_rs::enums::ReactionType::Emoji(e) => { e.emoji.clone() } tdlib_rs::enums::ReactionType::CustomEmoji(_) => { return None } }; Some(ReactionInfo { emoji, count: reaction.total_count, is_chosen: reaction.is_chosen, }) }) .collect() }) .unwrap_or_default(); } } } _ => {} } } fn handle_auth_state(&mut self, state: AuthorizationState) { self.auth.state = match state { AuthorizationState::WaitTdlibParameters => AuthState::WaitTdlibParameters, AuthorizationState::WaitPhoneNumber => AuthState::WaitPhoneNumber, AuthorizationState::WaitCode(_) => AuthState::WaitCode, AuthorizationState::WaitPassword(_) => AuthState::WaitPassword, AuthorizationState::Ready => AuthState::Ready, AuthorizationState::Closed => AuthState::Closed, _ => self.auth.state.clone(), }; } fn add_or_update_chat(&mut self, td_chat_enum: &TdChat) { // Pattern match to get inner Chat struct let td_chat = match td_chat_enum { TdChat::Chat(chat) => chat, _ => return, }; // Пропускаем удалённые аккаунты if td_chat.title == "Deleted Account" || td_chat.title.is_empty() { // Удаляем из списка если уже был добавлен self.chats_mut().retain(|c| c.id != td_chat.id); return; } // Ищем позицию в Main списке (если есть) let main_position = td_chat .positions .iter() .find(|pos| matches!(pos.list, ChatList::Main)); // Получаем order и is_pinned из позиции, или используем значения по умолчанию let (order, is_pinned) = main_position .map(|p| (p.order, p.is_pinned)) .unwrap_or((1, false)); // order=1 чтобы чат отображался let (last_message, last_message_date) = td_chat .last_message .as_ref() .map(|m| (Self::extract_message_text_static(m).0, m.date)) .unwrap_or_default(); // Извлекаем user_id для приватных чатов и сохраняем связь let username = match &td_chat.r#type { ChatType::Private(private) => { // Ограничиваем размер chat_user_ids if self.user_cache.chat_user_ids.len() >= MAX_CHAT_USER_IDS && !self.user_cache.chat_user_ids.contains_key(&td_chat.id) { // Удаляем случайную запись (первую найденную) if let Some(&key) = self.user_cache.chat_user_ids.keys().next() { self.user_cache.chat_user_ids.remove(&key); } } self.user_cache.chat_user_ids.insert(td_chat.id, private.user_id); // Проверяем, есть ли уже username в кэше (peek не обновляет LRU) self.user_cache.user_usernames .peek(&private.user_id) .map(|u| format!("@{}", u)) } _ => None, }; // Извлекаем ID папок из позиций let folder_ids: Vec = td_chat .positions .iter() .filter_map(|pos| { if let ChatList::Folder(folder) = &pos.list { Some(folder.chat_folder_id) } else { None } }) .collect(); // Проверяем mute статус let is_muted = td_chat.notification_settings.mute_for > 0; let chat_info = ChatInfo { id: td_chat.id, title: td_chat.title.clone(), username, last_message, last_message_date, unread_count: td_chat.unread_count, unread_mention_count: td_chat.unread_mention_count, is_pinned, order, last_read_outbox_message_id: td_chat.last_read_outbox_message_id, folder_ids, is_muted, draft_text: None, }; if let Some(existing) = self.chats_mut().iter_mut().find(|c| c.id == td_chat.id) { existing.title = chat_info.title; existing.last_message = chat_info.last_message; existing.last_message_date = chat_info.last_message_date; existing.unread_count = chat_info.unread_count; existing.unread_mention_count = chat_info.unread_mention_count; existing.last_read_outbox_message_id = chat_info.last_read_outbox_message_id; existing.folder_ids = chat_info.folder_ids; existing.is_muted = chat_info.is_muted; // Обновляем username если он появился if chat_info.username.is_some() { existing.username = chat_info.username; } // Обновляем позицию только если она пришла if main_position.is_some() { existing.is_pinned = chat_info.is_pinned; existing.order = chat_info.order; } } else { self.chats_mut().push(chat_info); // Ограничиваем количество чатов if self.chats_mut().len() > MAX_CHATS { // Удаляем чат с наименьшим order (наименее активный) if let Some(min_idx) = self .chats() .iter() .enumerate() .min_by_key(|(_, c)| c.order) .map(|(i, _)| i) { self.chats_mut().remove(min_idx); } } } // Сортируем чаты по order (TDLib order учитывает pinned и время) self.chats_mut().sort_by(|a, b| b.order.cmp(&a.order)); } fn convert_message(&mut self, message: &TdMessage, chat_id: i64) -> MessageInfo { let sender_name = match &message.sender_id { tdlib_rs::enums::MessageSender::User(user) => { // Пробуем получить имя из кеша (get обновляет LRU порядок) if let Some(name) = self.user_cache.user_names.get(&user.user_id).cloned() { name } else { // Добавляем в очередь для загрузки if !self.pending_user_ids().contains(&user.user_id) { self.pending_user_ids_mut().push(user.user_id); } format!("User_{}", user.user_id) } } tdlib_rs::enums::MessageSender::Chat(chat) => { // Для чатов используем название чата self.chats() .iter() .find(|c| c.id == chat.chat_id) .map(|c| c.title.clone()) .unwrap_or_else(|| format!("Chat_{}", chat.chat_id)) } }; // Определяем, прочитано ли исходящее сообщение let is_read = if message.is_outgoing { // Сообщение прочитано, если его ID <= last_read_outbox_message_id чата self.chats() .iter() .find(|c| c.id == chat_id) .map(|c| message.id <= c.last_read_outbox_message_id) .unwrap_or(false) } else { true // Входящие сообщения не показывают галочки }; let (content, entities) = Self::extract_message_text_static(message); // Извлекаем информацию о reply let reply_to = self.extract_reply_info(message); // Извлекаем информацию о forward let forward_from = self.extract_forward_info(message); // Извлекаем реакции let reactions = self.extract_reactions(message); MessageInfo { id: message.id, sender_name, is_outgoing: message.is_outgoing, content, entities, date: message.date, edit_date: message.edit_date, is_read, can_be_edited: message.can_be_edited, can_be_deleted_only_for_self: message.can_be_deleted_only_for_self, can_be_deleted_for_all_users: message.can_be_deleted_for_all_users, reply_to, forward_from, reactions, } } /// Извлекает информацию о reply из сообщения fn extract_reply_info(&self, message: &TdMessage) -> Option { use tdlib_rs::enums::MessageReplyTo; match &message.reply_to { Some(MessageReplyTo::Message(reply)) => { // Получаем имя отправителя из origin или ищем сообщение в текущем списке let sender_name = if let Some(origin) = &reply.origin { self.get_origin_sender_name(origin) } else { // Пробуем найти оригинальное сообщение в текущем списке self.current_chat_messages() .iter() .find(|m| m.id == reply.message_id) .map(|m| m.sender_name.clone()) .unwrap_or_else(|| "...".to_string()) }; // Получаем текст из content или quote let text = if let Some(quote) = &reply.quote { quote.text.text.clone() } else if let Some(content) = &reply.content { Self::extract_content_text(content) } else { // Пробуем найти в текущих сообщениях self.current_chat_messages() .iter() .find(|m| m.id == reply.message_id) .map(|m| m.content.clone()) .unwrap_or_default() }; Some(ReplyInfo { message_id: reply.message_id, sender_name, text }) } _ => None, } } /// Извлекает информацию о forward из сообщения fn extract_forward_info(&self, message: &TdMessage) -> Option { message.forward_info.as_ref().map(|info| { let sender_name = self.get_origin_sender_name(&info.origin); ForwardInfo { sender_name, date: info.date } }) } /// Извлекает информацию о реакциях из сообщения fn extract_reactions(&self, message: &TdMessage) -> Vec { message .interaction_info .as_ref() .and_then(|info| info.reactions.as_ref()) .map(|reactions| { reactions .reactions .iter() .filter_map(|reaction| { // Извлекаем эмодзи из ReactionType let emoji = match &reaction.r#type { tdlib_rs::enums::ReactionType::Emoji(e) => e.emoji.clone(), tdlib_rs::enums::ReactionType::CustomEmoji(_) => return None, // Пока игнорируем custom emoji }; Some(ReactionInfo { emoji, count: reaction.total_count, is_chosen: reaction.is_chosen, }) }) .collect() }) .unwrap_or_default() } /// Получает имя отправителя из MessageOrigin fn get_origin_sender_name(&self, origin: &tdlib_rs::enums::MessageOrigin) -> String { use tdlib_rs::enums::MessageOrigin; match origin { MessageOrigin::User(u) => self .user_cache.user_names .peek(&u.sender_user_id) .cloned() .unwrap_or_else(|| format!("User_{}", u.sender_user_id)), MessageOrigin::Chat(c) => self .chats() .iter() .find(|chat| chat.id == c.sender_chat_id) .map(|chat| chat.title.clone()) .unwrap_or_else(|| "Чат".to_string()), MessageOrigin::HiddenUser(h) => h.sender_name.clone(), MessageOrigin::Channel(c) => self .chats() .iter() .find(|chat| chat.id == c.chat_id) .map(|chat| chat.title.clone()) .unwrap_or_else(|| "Канал".to_string()), } } /// Обновляет reply info для сообщений, где данные не были загружены /// Вызывается после загрузки истории, когда все сообщения уже в списке fn update_reply_info_from_loaded_messages(&mut self) { // Собираем данные для обновления (id -> (sender_name, content)) let msg_data: std::collections::HashMap = self .current_chat_messages() .iter() .map(|m| (m.id, (m.sender_name.clone(), m.content.clone()))) .collect(); // Обновляем reply_to для сообщений с неполными данными for msg in self.current_chat_messages_mut().iter_mut() { if let Some(ref mut reply) = msg.reply_to { // Если sender_name = "..." или text пустой — пробуем заполнить if reply.sender_name == "..." || reply.text.is_empty() { if let Some((sender, content)) = msg_data.get(&reply.message_id) { if reply.sender_name == "..." { reply.sender_name = sender.clone(); } if reply.text.is_empty() { reply.text = content.clone(); } } } } } } // Helper functions pub fn extract_message_text_static(message: &TdMessage) -> (String, Vec) { use tdlib_rs::enums::MessageContent; match &message.content { MessageContent::MessageText(text) => (text.text.text.clone(), text.text.entities.clone()), _ => (String::new(), Vec::new()), } } pub fn extract_content_text(content: &tdlib_rs::enums::MessageContent) -> String { use tdlib_rs::enums::MessageContent; match content { MessageContent::MessageText(text) => text.text.text.clone(), _ => String::new(), } } }