refactor: complete large files/functions refactoring (Phase 6-7)

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>
This commit is contained in:
Mikhail Kilin
2026-02-04 01:29:26 +03:00
parent b081886e34
commit 5ac10ea24c
9 changed files with 1198 additions and 780 deletions

View File

@@ -1,21 +1,19 @@
use crate::types::{ChatId, MessageId, UserId};
use std::env;
use std::time::Instant;
use tdlib_rs::enums::{
AuthorizationState, ChatAction, ChatList, ChatType, ConnectionState,
MessageSender, Update, UserStatus,
ChatList, ConnectionState, Update, UserStatus,
Chat as TdChat
};
use tdlib_rs::types::{Message as TdMessage, UpdateNewMessage, UpdateChatAction};
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::types::{ChatInfo, FolderInfo, MessageInfo, NetworkState, ProfileInfo, UserOnlineStatus};
use super::users::UserCache;
/// TDLib client wrapper for Telegram integration.
@@ -443,16 +441,31 @@ impl TdClient {
&mut self.user_cache
}
// ==================== Helper методы для упрощения обработки updates ====================
/// Находит мутабельную ссылку на чат по ID.
///
/// Упрощает повторяющийся паттерн `self.chats_mut().iter_mut().find(...)`.
///
/// # Arguments
///
/// * `chat_id` - ID чата для поиска
///
/// # Returns
///
/// * `Some(&mut ChatInfo)` - если чат найден
/// * `None` - если чат не найден
/// Обрабатываем одно обновление от TDLib
pub fn handle_update(&mut self, update: Update) {
match update {
Update::AuthorizationState(state) => {
self.handle_auth_state(state.authorization_state);
crate::tdlib::update_handlers::handle_auth_state(self, 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);
crate::tdlib::chat_helpers::add_or_update_chat(self, &td_chat);
}
Update::ChatLastMessage(update) => {
let chat_id = ChatId::new(update.chat_id);
@@ -462,46 +475,44 @@ impl TdClient {
.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) {
crate::tdlib::chat_helpers::update_chat(self, chat_id, |chat| {
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;
}
}
for pos in update.positions.iter().filter(|p| matches!(p.list, ChatList::Main)) {
crate::tdlib::chat_helpers::update_chat(self, chat_id, |chat| {
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 == ChatId::new(update.chat_id)) {
crate::tdlib::chat_helpers::update_chat(self, ChatId::new(update.chat_id), |chat| {
chat.unread_count = update.unread_count;
}
});
}
Update::ChatUnreadMentionCount(update) => {
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == ChatId::new(update.chat_id)) {
crate::tdlib::chat_helpers::update_chat(self, ChatId::new(update.chat_id), |chat| {
chat.unread_mention_count = update.unread_mention_count;
}
});
}
Update::ChatNotificationSettings(update) => {
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == ChatId::new(update.chat_id)) {
crate::tdlib::chat_helpers::update_chat(self, ChatId::new(update.chat_id), |chat| {
// mute_for > 0 означает что чат замьючен
chat.is_muted = update.notification_settings.mute_for > 0;
}
});
}
Update::ChatReadOutbox(update) => {
// Обновляем last_read_outbox_message_id когда собеседник прочитал сообщения
let last_read_msg_id = MessageId::new(update.last_read_outbox_message_id);
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == ChatId::new(update.chat_id)) {
crate::tdlib::chat_helpers::update_chat(self, ChatId::new(update.chat_id), |chat| {
chat.last_read_outbox_message_id = last_read_msg_id;
}
});
// Если это текущий открытый чат — обновляем is_read у сообщений
if Some(ChatId::new(update.chat_id)) == self.current_chat_id() {
for msg in self.current_chat_messages_mut().iter_mut() {
@@ -512,84 +523,13 @@ impl TdClient {
}
}
Update::ChatPosition(update) => {
// Обновляем позицию чата или удаляем его из списка
let chat_id = ChatId::new(update.chat_id);
match &update.position.list {
ChatList::Main => {
if update.position.order == 0 {
// Чат больше не в Main (перемещён в архив и т.д.)
self.chats_mut().retain(|c| c.id != chat_id);
} else if let Some(chat) =
self.chats_mut().iter_mut().find(|c| c.id == 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 == 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 => {
// Архив пока не обрабатываем
}
}
crate::tdlib::update_handlers::handle_chat_position_update(self, update);
}
Update::NewMessage(new_msg) => {
self.handle_new_message_update(new_msg);
crate::tdlib::update_handlers::handle_new_message_update(self, new_msg);
}
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(&UserId::new(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(UserId::new(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(UserId::new(user.id), username.clone());
// Обновляем username в чатах, связанных с этим пользователем
for (&chat_id, &user_id) in &self.user_cache.chat_user_ids.clone() {
if user_id == UserId::new(user.id) {
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == chat_id)
{
chat.username = Some(format!("@{}", username));
}
}
}
}
}
// LRU-кэш автоматически удаляет старые записи при вставке
crate::tdlib::update_handlers::handle_user_update(self, update);
}
Update::ChatFolders(update) => {
// Обновляем список папок
@@ -623,541 +563,22 @@ impl TdClient {
};
}
Update::ChatAction(update) => {
self.handle_chat_action_update(update);
crate::tdlib::update_handlers::handle_chat_action_update(self, update);
}
Update::ChatDraftMessage(update) => {
// Обновляем черновик в списке чатов
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == ChatId::new(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
}
});
}
crate::tdlib::update_handlers::handle_chat_draft_message_update(self, update);
}
Update::MessageInteractionInfo(update) => {
// Обновляем реакции в текущем открытом чате
if Some(ChatId::new(update.chat_id)) == self.current_chat_id() {
if let Some(msg) = self
.current_chat_messages_mut()
.iter_mut()
.find(|m| m.id() == MessageId::new(update.message_id))
{
// Извлекаем реакции из interaction_info
msg.interactions.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();
}
}
crate::tdlib::update_handlers::handle_message_interaction_info_update(self, update);
}
Update::MessageSendSucceeded(update) => {
// Сообщение успешно отправлено, заменяем временный ID на настоящий
let old_id = MessageId::new(update.old_message_id);
let chat_id = ChatId::new(update.message.chat_id);
// Обрабатываем только если это текущий открытый чат
if Some(chat_id) == self.current_chat_id() {
// Находим сообщение с временным ID
if let Some(idx) = self
.current_chat_messages()
.iter()
.position(|m| m.id() == old_id)
{
// Конвертируем новое сообщение
let mut new_msg = self.convert_message(&update.message, chat_id);
// Сохраняем reply_info из старого сообщения (если было)
let old_reply = self.current_chat_messages()[idx]
.interactions
.reply_to
.clone();
if let Some(reply) = old_reply {
new_msg.interactions.reply_to = Some(reply);
}
// Заменяем старое сообщение на новое
self.current_chat_messages_mut()[idx] = new_msg;
}
}
crate::tdlib::update_handlers::handle_message_send_succeeded_update(self, update);
}
_ => {}
}
}
/// Обрабатывает Update::NewMessage - добавление нового сообщения
fn handle_new_message_update(&mut self, new_msg: UpdateNewMessage) {
// Добавляем новое сообщение если это текущий открытый чат
let chat_id = ChatId::new(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.state.can_be_edited = msg_info.state.can_be_edited;
existing.state.can_be_deleted_only_for_self =
msg_info.state.can_be_deleted_only_for_self;
existing.state.can_be_deleted_for_all_users =
msg_info.state.can_be_deleted_for_all_users;
existing.state.is_read = msg_info.state.is_read;
}
}
None => {
// Нового сообщения нет - добавляем
self.push_message(msg_info.clone());
// Если это входящее сообщение — добавляем в очередь для отметки как прочитанное
if is_incoming {
self.pending_view_messages_mut().push((chat_id, vec![msg_id]));
}
}
}
}
}
/// Обрабатывает Update::ChatAction - статус набора текста/отправки файлов
fn handle_chat_action_update(&mut self, update: UpdateChatAction) {
// Обрабатываем только для текущего открытого чата
if Some(ChatId::new(update.chat_id)) == self.current_chat_id() {
// Извлекаем user_id из sender_id
let user_id = match update.sender_id {
MessageSender::User(user) => Some(UserId::new(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);
}
}
}
}
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 TdChat::Chat(td_chat) = td_chat_enum;
// Пропускаем удалённые аккаунты
if td_chat.title == "Deleted Account" || td_chat.title.is_empty() {
// Удаляем из списка если уже был добавлен
self.chats_mut().retain(|c| c.id != ChatId::new(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
let chat_id = ChatId::new(td_chat.id);
if self.user_cache.chat_user_ids.len() >= MAX_CHAT_USER_IDS
&& !self.user_cache.chat_user_ids.contains_key(&chat_id)
{
// Удаляем случайную запись (первую найденную)
if let Some(&key) = self.user_cache.chat_user_ids.keys().next() {
self.user_cache.chat_user_ids.remove(&key);
}
}
let user_id = UserId::new(private.user_id);
self.user_cache.chat_user_ids.insert(chat_id, user_id);
// Проверяем, есть ли уже username в кэше (peek не обновляет LRU)
self.user_cache.user_usernames
.peek(&user_id)
.map(|u| format!("@{}", u))
}
_ => None,
};
// Извлекаем ID папок из позиций
let folder_ids: Vec<i32> = 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: ChatId::new(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: MessageId::new(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 == ChatId::new(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: 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);
if let Some(name) = self.user_cache.user_names.get(&user_id).cloned() {
name
} else {
// Добавляем в очередь для загрузки
if !self.pending_user_ids().contains(&user_id) {
self.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);
self.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 чата
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);
// Используем 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 из сообщения
fn extract_reply_info(&self, message: &TdMessage) -> Option<ReplyInfo> {
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 {
// Пробуем найти оригинальное сообщение в текущем списке
let reply_msg_id = MessageId::new(reply.message_id);
self.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 = 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_msg_id)
.map(|m| m.text().to_string())
.unwrap_or_default()
};
Some(ReplyInfo { message_id: reply_msg_id, sender_name, text })
}
_ => None,
}
}
/// Извлекает информацию о forward из сообщения
fn extract_forward_info(&self, message: &TdMessage) -> Option<ForwardInfo> {
message.forward_info.as_ref().map(|info| {
let sender_name = self.get_origin_sender_name(&info.origin);
ForwardInfo { sender_name }
})
}
/// Извлекает информацию о реакциях из сообщения
fn extract_reactions(&self, message: &TdMessage) -> Vec<ReactionInfo> {
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(&UserId::new(u.sender_user_id))
.cloned()
.unwrap_or_else(|| format!("User_{}", u.sender_user_id)),
MessageOrigin::Chat(c) => self
.chats()
.iter()
.find(|chat| chat.id == ChatId::new(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 == ChatId::new(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<i64, (String, String)> = self
.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 self.current_chat_messages_mut().iter_mut() {
if let Some(ref mut reply) = msg.interactions.reply_to {
// Если sender_name = "..." или text пустой — пробуем заполнить
if reply.sender_name == "..." || reply.text.is_empty() {
if let Some((sender, content)) = msg_data.get(&reply.message_id.as_i64()) {
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<tdlib_rs::types::TextEntity>) {