From 07c401e0f903ab29c0d82091a6e16ff71eed44f9 Mon Sep 17 00:00:00 2001 From: Mikhail Kilin Date: Sat, 31 Jan 2026 18:29:02 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=B1=D0=B0=D0=B3=D0=B8=20=D1=81=20?= =?UTF-8?q?=D1=81=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D1=8F=D0=BC?= =?UTF-8?q?=D0=B8,=20=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC=20=D0=B8=20reply?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Изменён порядок хранения сообщений (теперь от старых к новым) - Исправлена логика выбора сообщений для редактирования - Исправлена отправка reply (структура условий) - Добавлено сохранение reply_info при отправке - Удалены отладочные логи Fixes: сообщения теперь отображаются корректно в UI Fixes: редактирование работает без ошибки 'Message not found' Fixes: reply показывает превью исходного сообщения --- src/app/mod.rs | 37 ++++++++-------- src/input/main_input.rs | 76 ++++++++++++++++++-------------- src/tdlib/client.rs | 2 +- src/tdlib/messages.rs | 96 ++++++++++++++++++++++++++++------------- 4 files changed, 129 insertions(+), 82 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 08c2504..233415d 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -133,30 +133,34 @@ impl App { /// Начать выбор сообщения для редактирования (при стрелке вверх в пустом инпуте) pub fn start_message_selection(&mut self) { - if self.td_client.current_chat_messages().is_empty() { + let total = self.td_client.current_chat_messages().len(); + if total == 0 { return; } - // Начинаем с последнего сообщения (индекс 0 = самое новое снизу) - self.chat_state = ChatState::MessageSelection { selected_index: 0 }; + // Начинаем с последнего сообщения (индекс len-1 = самое новое внизу) + self.chat_state = ChatState::MessageSelection { selected_index: total - 1 }; } - /// Выбрать предыдущее сообщение (вверх по списку = увеличить индекс) + /// Выбрать предыдущее сообщение (вверх по списку = к старым = уменьшить индекс) pub fn select_previous_message(&mut self) { + if let ChatState::MessageSelection { selected_index } = &mut self.chat_state { + if *selected_index > 0 { + *selected_index -= 1; + } + } + } + + /// Выбрать следующее сообщение (вниз по списку = к новым = увеличить индекс) + pub fn select_next_message(&mut self) { let total = self.td_client.current_chat_messages().len(); if total == 0 { return; } if let ChatState::MessageSelection { selected_index } = &mut self.chat_state { - *selected_index = (*selected_index + 1).min(total - 1); - } - } - - /// Выбрать следующее сообщение (вниз по списку = уменьшить индекс) - pub fn select_next_message(&mut self) { - if let ChatState::MessageSelection { selected_index } = &mut self.chat_state { - if *selected_index > 0 { - *selected_index -= 1; + if *selected_index < total - 1 { + *selected_index += 1; } else { + // Дошли до самого нового сообщения - выходим из режима выбора self.chat_state = ChatState::Normal; } } @@ -165,12 +169,7 @@ impl App { /// Получить выбранное сообщение pub fn get_selected_message(&self) -> Option<&crate::tdlib::MessageInfo> { self.chat_state.selected_message_index().and_then(|idx| { - let total = self.td_client.current_chat_messages().len(); - if total == 0 || idx >= total { - return None; - } - // idx=0 это последнее сообщение (total-1), idx=1 это предпоследнее (total-2), и т.д. - self.td_client.current_chat_messages().get(total - 1 - idx) + self.td_client.current_chat_messages().get(idx) }) } diff --git a/src/input/main_input.rs b/src/input/main_input.rs index 4b25863..5edd6e8 100644 --- a/src/input/main_input.rs +++ b/src/input/main_input.rs @@ -494,7 +494,12 @@ pub async fn handle(app: &mut App, key: KeyEvent) { ) .await { - Ok(Ok(_)) => { + Ok(Ok(messages)) => { + // Сохраняем загруженные сообщения + *app.td_client.current_chat_messages_mut() = messages; + // ВАЖНО: Устанавливаем current_chat_id ТОЛЬКО ПОСЛЕ сохранения истории + // Это предотвращает race condition с Update::NewMessage + app.td_client.set_current_chat_id(Some(ChatId::new(chat_id))); // Загружаем недостающие reply info let _ = timeout( Duration::from_secs(5), @@ -563,39 +568,39 @@ pub async fn handle(app: &mut App, key: KeyEvent) { if let Some(chat_id) = app.get_selected_chat_id() { let text = app.message_input.clone(); - if let Some(msg_id) = app.chat_state.selected_message_id() { - if app.is_editing() { - // Режим редактирования - app.message_input.clear(); - app.cursor_position = 0; - app.chat_state = crate::app::ChatState::Normal; - - match timeout( - Duration::from_secs(5), - app.td_client.edit_message(ChatId::new(chat_id), msg_id, text), - ) - .await - { - Ok(Ok(edited_msg)) => { - // Обновляем сообщение в списке - if let Some(msg) = app - .td_client - .current_chat_messages_mut() - .iter_mut() - .find(|m| m.id() == msg_id) - { - msg.content.text = edited_msg.content.text; - msg.content.entities = edited_msg.content.entities; - msg.metadata.edit_date = edited_msg.metadata.edit_date; + if app.is_editing() { + // Режим редактирования + if let Some(msg_id) = app.chat_state.selected_message_id() { + match timeout( + Duration::from_secs(5), + app.td_client.edit_message(ChatId::new(chat_id), msg_id, text), + ) + .await + { + Ok(Ok(edited_msg)) => { + // Обновляем сообщение в списке + if let Some(msg) = app + .td_client + .current_chat_messages_mut() + .iter_mut() + .find(|m| m.id() == msg_id) + { + msg.content.text = edited_msg.content.text; + msg.content.entities = edited_msg.content.entities; + msg.metadata.edit_date = edited_msg.metadata.edit_date; + } + // Очищаем инпут и сбрасываем состояние ПОСЛЕ успешного редактирования + app.message_input.clear(); + app.cursor_position = 0; + app.chat_state = crate::app::ChatState::Normal; + } + Ok(Err(e)) => { + app.error_message = Some(e); + } + Err(_) => { + app.error_message = Some("Таймаут редактирования".to_string()); } } - Ok(Err(e)) => { - app.error_message = Some(e); - } - Err(_) => { - app.error_message = Some("Таймаут редактирования".to_string()); - } - } } } else { // Обычная отправка (или reply) @@ -663,7 +668,12 @@ pub async fn handle(app: &mut App, key: KeyEvent) { ) .await { - Ok(Ok(_)) => { + Ok(Ok(messages)) => { + // Сохраняем загруженные сообщения + *app.td_client.current_chat_messages_mut() = messages; + // ВАЖНО: Устанавливаем current_chat_id ТОЛЬКО ПОСЛЕ сохранения истории + // Это предотвращает race condition с Update::NewMessage + app.td_client.set_current_chat_id(Some(ChatId::new(chat_id))); // Загружаем недостающие reply info let _ = timeout( Duration::from_secs(5), diff --git a/src/tdlib/client.rs b/src/tdlib/client.rs index 6ec11ce..a9d6578 100644 --- a/src/tdlib/client.rs +++ b/src/tdlib/client.rs @@ -498,7 +498,7 @@ impl TdClient { } None => { // Нового сообщения нет - добавляем - self.push_message(msg_info); + self.push_message(msg_info.clone()); // Если это входящее сообщение — добавляем в очередь для отметки как прочитанное if is_incoming { self.pending_view_messages_mut().push((chat_id, vec![msg_id])); diff --git a/src/tdlib/messages.rs b/src/tdlib/messages.rs index 5aeb521..4155c80 100644 --- a/src/tdlib/messages.rs +++ b/src/tdlib/messages.rs @@ -29,11 +29,11 @@ impl MessageManager { /// Добавить сообщение в список текущего чата pub fn push_message(&mut self, msg: MessageInfo) { - self.current_chat_messages.insert(0, msg); + self.current_chat_messages.push(msg); // Добавляем в конец - // Ограничиваем размер списка + // Ограничиваем размер списка (удаляем старые с начала) if self.current_chat_messages.len() > MAX_MESSAGES_IN_CHAT { - self.current_chat_messages.truncate(MAX_MESSAGES_IN_CHAT); + self.current_chat_messages.drain(0..(self.current_chat_messages.len() - MAX_MESSAGES_IN_CHAT)); } } @@ -43,34 +43,63 @@ impl MessageManager { chat_id: ChatId, limit: i32, ) -> Result, String> { - // Устанавливаем текущий чат для получения новых сообщений - self.current_chat_id = Some(chat_id); + use tokio::time::{sleep, Duration}; - let result = functions::get_chat_history( - chat_id.as_i64(), - 0, // from_message_id - 0, // offset - limit, - false, - self.client_id, - ) - .await; + // ВАЖНО: Сначала открываем чат в TDLib + // Это сообщает TDLib что пользователь открыл чат и нужно загрузить историю + let _ = functions::open_chat(chat_id.as_i64(), self.client_id).await; - match result { - Ok(tdlib_rs::enums::Messages::Messages(messages_obj)) => { - let mut messages = Vec::new(); - for msg_opt in messages_obj.messages.iter().rev() { - if let Some(msg) = msg_opt { - if let Some(info) = self.convert_message(msg).await { - messages.push(info); + // Даём TDLib время на синхронизацию (загрузку истории с сервера) + sleep(Duration::from_millis(100)).await; + + // НЕ устанавливаем current_chat_id здесь! + // Он будет установлен снаружи ПОСЛЕ сохранения истории + // Это предотвращает race condition с Update::NewMessage + + // Пробуем загрузить несколько раз, TDLib может подгружать с сервера + let mut all_messages = Vec::new(); + let max_attempts = 3; + + for attempt in 1..=max_attempts { + let result = functions::get_chat_history( + chat_id.as_i64(), + 0, // from_message_id (0 = from latest) + 0, // offset + limit, + false, // only_local - false means can fetch from server + self.client_id, + ) + .await; + + match result { + Ok(tdlib_rs::enums::Messages::Messages(messages_obj)) => { + if !messages_obj.messages.is_empty() { + all_messages.clear(); // Очищаем предыдущие результаты + for msg_opt in messages_obj.messages.iter().rev() { + if let Some(msg) = msg_opt { + if let Some(info) = self.convert_message(msg).await { + all_messages.push(info); + } + } + } + + // Если получили достаточно сообщений, прекращаем попытки + if all_messages.len() >= 2 || attempt == max_attempts { + break; } } + + // Если сообщений мало, ждём перед следующей попыткой + if attempt < max_attempts { + sleep(Duration::from_millis(200)).await; + } } - Ok(messages) + Ok(_) => return Err("Неожиданный тип сообщений".to_string()), + Err(e) => return Err(format!("Ошибка загрузки истории: {:?}", e)), } - Ok(_) => Err("Неожиданный тип сообщений".to_string()), - Err(e) => Err(format!("Ошибка загрузки истории: {:?}", e)), } + + Ok(all_messages) } /// Загрузить более старые сообщения @@ -194,7 +223,7 @@ impl MessageManager { chat_id: ChatId, text: String, reply_to_message_id: Option, - _reply_info: Option, + reply_info: Option, ) -> Result { // Парсим markdown в тексте let formatted_text = match functions::parse_text_entities( @@ -241,10 +270,19 @@ impl MessageManager { .await; match result { - Ok(tdlib_rs::enums::Message::Message(msg)) => self - .convert_message(&msg) - .await - .ok_or_else(|| "Не удалось конвертировать сообщение".to_string()), + Ok(tdlib_rs::enums::Message::Message(msg)) => { + let mut msg_info = self + .convert_message(&msg) + .await + .ok_or_else(|| "Не удалось конвертировать сообщение".to_string())?; + + // Добавляем reply_info если был передан + if let Some(reply) = reply_info { + msg_info.interactions.reply_to = Some(reply); + } + + Ok(msg_info) + } Ok(_) => Err("Неожиданный тип сообщения".to_string()), Err(e) => Err(format!("Ошибка отправки сообщения: {:?}", e)), }