Some checks failed
ci/woodpecker/pr/check Pipeline failed
CI / Check (pull_request) Has been cancelled
CI / Format (pull_request) Has been cancelled
CI / Clippy (pull_request) Has been cancelled
CI / Build (macos-latest) (pull_request) Has been cancelled
CI / Build (ubuntu-latest) (pull_request) Has been cancelled
CI / Build (windows-latest) (pull_request) Has been cancelled
235 lines
9.0 KiB
Rust
235 lines
9.0 KiB
Rust
//! 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<ReplyInfo> {
|
||
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<ForwardInfo> {
|
||
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<ReactionInfo> {
|
||
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<i64, (String, String)> = 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();
|
||
}
|
||
}
|
||
}
|