//! Message conversion utilities for transforming TDLib messages. //! //! This module contains functions for converting TDLib message formats //! to the application's internal MessageInfo format, including extraction //! of replies, forwards, and reactions. use crate::types::{ChatId, MessageId, UserId}; use tdlib_rs::types::Message as TdMessage; use super::client::TdClient; use super::types::{ForwardInfo, MessageInfo, ReactionInfo, ReplyInfo}; /// Конвертирует TDLib сообщение в MessageInfo pub fn convert_message(client: &mut TdClient, message: &TdMessage, chat_id: ChatId) -> MessageInfo { let sender_name = match &message.sender_id { tdlib_rs::enums::MessageSender::User(user) => { // Пробуем получить имя из кеша (get обновляет LRU порядок) let user_id = UserId::new(user.user_id); client .user_cache .user_names .get(&user_id) .cloned() .unwrap_or_else(|| { // Добавляем в очередь для загрузки if !client.pending_user_ids().contains(&user_id) { client.pending_user_ids_mut().push(user_id); } format!("User_{}", user_id.as_i64()) }) } tdlib_rs::enums::MessageSender::Chat(chat) => { // Для чатов используем название чата let sender_chat_id = ChatId::new(chat.chat_id); client .chats() .iter() .find(|c| c.id == sender_chat_id) .map(|c| c.title.clone()) .unwrap_or_else(|| format!("Chat_{}", sender_chat_id.as_i64())) } }; // Определяем, прочитано ли исходящее сообщение let message_id = MessageId::new(message.id); let is_read = if message.is_outgoing { // Сообщение прочитано, если его ID <= last_read_outbox_message_id чата client .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) = TdClient::extract_message_text_static(message); // Извлекаем информацию о reply let reply_to = extract_reply_info(client, message); // Извлекаем информацию о forward let forward_from = extract_forward_info(client, message); // Извлекаем реакции let reactions = extract_reactions(client, message); // Используем MessageBuilder для более читабельного создания let mut builder = crate::tdlib::MessageBuilder::new(message_id) .sender_name(sender_name) .text(content) .entities(entities) .date(message.date) .edit_date(message.edit_date) .media_album_id(message.media_album_id); // Применяем флаги if message.is_outgoing { builder = builder.outgoing(); } if is_read { builder = builder.read(); } if message.can_be_edited { builder = builder.editable(); } if message.can_be_deleted_only_for_self { builder = builder.deletable_for_self(); } if message.can_be_deleted_for_all_users { builder = builder.deletable_for_all(); } // Добавляем опциональные данные if let Some(reply) = reply_to { builder = builder.reply_to(reply); } if let Some(forward) = forward_from { builder = builder.forward_from(forward); } if !reactions.is_empty() { builder = builder.reactions(reactions); } builder.build() } /// Извлекает информацию о reply из сообщения pub fn extract_reply_info(client: &TdClient, message: &TdMessage) -> Option { use tdlib_rs::enums::MessageReplyTo; match &message.reply_to { Some(MessageReplyTo::Message(reply)) => { // Получаем имя отправителя из origin или ищем сообщение в текущем списке let sender_name = reply .origin .as_ref() .map(|origin| get_origin_sender_name(origin)) .unwrap_or_else(|| { // Пробуем найти оригинальное сообщение в текущем списке let reply_msg_id = MessageId::new(reply.message_id); client .current_chat_messages() .iter() .find(|m| m.id() == reply_msg_id) .map(|m| m.sender_name().to_string()) .unwrap_or_else(|| "...".to_string()) }); // Получаем текст из content или quote let reply_msg_id = MessageId::new(reply.message_id); let text = reply .quote .as_ref() .map(|q| q.text.text.clone()) .or_else(|| reply.content.as_ref().map(TdClient::extract_content_text)) .unwrap_or_else(|| { // Пробуем найти в текущих сообщениях client .current_chat_messages() .iter() .find(|m| m.id() == reply_msg_id) .map(|m| m.text().to_string()) .unwrap_or_default() }); Some(ReplyInfo { message_id: reply_msg_id, sender_name, text }) } _ => None, } } /// Извлекает информацию о forward из сообщения pub fn extract_forward_info(_client: &TdClient, message: &TdMessage) -> Option { message.forward_info.as_ref().map(|info| { let sender_name = get_origin_sender_name(&info.origin); ForwardInfo { sender_name } }) } /// Извлекает реакции из сообщения pub fn extract_reactions(_client: &TdClient, message: &TdMessage) -> Vec { message .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() } /// Получает имя отправителя из MessageOrigin fn get_origin_sender_name(origin: &tdlib_rs::enums::MessageOrigin) -> String { use tdlib_rs::enums::MessageOrigin; match origin { MessageOrigin::User(u) => format!("User_{}", u.sender_user_id), MessageOrigin::Chat(c) => format!("Chat_{}", c.sender_chat_id), MessageOrigin::Channel(c) => c.author_signature.clone(), MessageOrigin::HiddenUser(h) => h.sender_name.clone(), } } /// Обновляет reply info для сообщений, где данные не были загружены /// Вызывается после загрузки истории, когда все сообщения уже в списке #[allow(dead_code)] pub fn update_reply_info_from_loaded_messages(client: &mut TdClient) { // Собираем данные для обновления (id -> (sender_name, content)) let msg_data: std::collections::HashMap = client .current_chat_messages() .iter() .map(|m| (m.id().as_i64(), (m.sender_name().to_string(), m.text().to_string()))) .collect(); // Обновляем reply_to для сообщений с неполными данными for msg in client.current_chat_messages_mut().iter_mut() { let Some(ref mut reply) = msg.interactions.reply_to else { continue; }; // Если sender_name = "..." или text пустой — пробуем заполнить if reply.sender_name != "..." && !reply.text.is_empty() { continue; } let Some((sender, content)) = msg_data.get(&reply.message_id.as_i64()) else { continue; }; if reply.sender_name == "..." { reply.sender_name = sender.clone(); } if reply.text.is_empty() { reply.text = content.clone(); } } }