Phase 6: Refactor tdlib/client.rs ✅ - Extract update handlers to update_handlers.rs (302 lines, 8 functions) - Extract message converter to message_converter.rs (250 lines, 6 functions) - Extract chat helpers to chat_helpers.rs (149 lines, 3 functions) - Result: client.rs 1259 → 599 lines (-52%) Phase 7: Refactor tdlib/messages.rs ✅ - Create message_conversion.rs module (158 lines) - Extract 6 helper functions: - extract_content_text() - content extraction (~80 lines) - extract_entities() - formatting extraction (~10 lines) - extract_sender_name() - sender name with API call (~15 lines) - extract_forward_info() - forward info (~12 lines) - extract_reply_info() - reply info (~15 lines) - extract_reactions() - reactions extraction (~26 lines) - Result: convert_message() 150 → 57 lines (-62%) - Result: messages.rs 850 → 757 lines (-11%) Summary: - ✅ All 4 large files refactored (100%) - ✅ All 629 tests passing - ✅ Category #2 "Large files/functions" COMPLETE - ✅ Documentation updated (REFACTORING_OPPORTUNITIES.md, CONTEXT.md) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
252 lines
9.2 KiB
Rust
252 lines
9.2 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);
|
||
|
||
// Применяем флаги
|
||
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();
|
||
}
|
||
}
|
||
}
|