//! Update handlers for TDLib events. //! //! This module contains functions that process various types of updates from TDLib. //! Each handler is responsible for updating the application state based on the received update. use crate::types::{ChatId, MessageId, UserId}; use std::time::Instant; use tdlib_rs::enums::{ AuthorizationState, ChatAction, ChatList, MessageSender, }; use tdlib_rs::types::{ UpdateChatAction, UpdateChatDraftMessage, UpdateChatPosition, UpdateMessageInteractionInfo, UpdateMessageSendSucceeded, UpdateNewMessage, UpdateUser, }; use super::auth::AuthState; use super::client::TdClient; use super::types::ReactionInfo; /// Обрабатывает Update::NewMessage - добавление нового сообщения pub fn handle_new_message_update(client: &mut TdClient, new_msg: UpdateNewMessage) { let chat_id = ChatId::new(new_msg.message.chat_id); // Если сообщение НЕ для текущего открытого чата - отправляем уведомление if Some(chat_id) != client.current_chat_id() { // Find and clone chat info to avoid borrow checker issues if let Some(chat) = client.chats().iter().find(|c| c.id == chat_id).cloned() { let msg_info = crate::tdlib::message_converter::convert_message(client, &new_msg.message, chat_id); // Get sender name (from message or user cache) let sender_name = msg_info.sender_name(); // Send notification let _ = client.notification_manager.notify_new_message( &chat, &msg_info, sender_name, ); } return; } // Добавляем новое сообщение если это текущий открытый чат let msg_info = crate::tdlib::message_converter::convert_message(client, &new_msg.message, chat_id); let msg_id = msg_info.id(); let is_incoming = !msg_info.is_outgoing(); // Проверяем, есть ли уже сообщение с таким id let existing_idx = client .current_chat_messages() .iter() .position(|m| m.id() == msg_info.id()); match existing_idx { Some(idx) => { // Сообщение уже есть - обновляем if is_incoming { client.current_chat_messages_mut()[idx] = msg_info; } else { // Для исходящих: обновляем can_be_edited и другие поля, // но сохраняем reply_to (добавленный при отправке) let existing = &mut client.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 => { // Нового сообщения нет - добавляем client.push_message(msg_info.clone()); // Если это входящее сообщение — добавляем в очередь для отметки как прочитанное if is_incoming { client.pending_view_messages_mut().push((chat_id, vec![msg_id])); } } } } /// Обрабатывает Update::ChatAction - статус набора текста/отправки файлов pub fn handle_chat_action_update(client: &mut TdClient, update: UpdateChatAction) { // Обрабатываем только для текущего открытого чата if Some(ChatId::new(update.chat_id)) != client.current_chat_id() { return; } // Извлекаем user_id из sender_id let MessageSender::User(user) = update.sender_id else { return; // Игнорируем действия от имени чата }; let user_id = UserId::new(user.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, // Отмена или неизвестное действие }; match action_text { Some(text) => client.set_typing_status(Some((user_id, text, Instant::now()))), None => client.set_typing_status(None), } } /// Обрабатывает Update::ChatPosition - изменение позиции чата в списке. /// /// Обновляет order и is_pinned для чатов в Main списке, /// управляет folder_ids для чатов в папках. pub fn handle_chat_position_update(client: &mut TdClient, update: UpdateChatPosition) { let chat_id = ChatId::new(update.chat_id); match &update.position.list { ChatList::Main => { if update.position.order == 0 { // Чат больше не в Main (перемещён в архив и т.д.) client.chats_mut().retain(|c| c.id != chat_id); } else { // Обновляем позицию существующего чата crate::tdlib::chat_helpers::update_chat(client, chat_id, |chat| { chat.order = update.position.order; chat.is_pinned = update.position.is_pinned; }); } // Пересортируем по order client.chats_mut().sort_by(|a, b| b.order.cmp(&a.order)); } ChatList::Folder(folder) => { // Обновляем folder_ids для чата crate::tdlib::chat_helpers::update_chat(client, chat_id, |chat| { 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 => { // Архив пока не обрабатываем } } } /// Обрабатывает Update::User - обновление информации о пользователе. /// /// Сохраняет display name и username в кэше, /// обновляет username в связанных чатах, /// удаляет "Deleted Account" из списка чатов. pub fn handle_user_update(client: &mut TdClient, update: UpdateUser) { 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 = client.user_cache.chat_user_ids.clone(); client .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) }; client.user_cache.user_names.insert(UserId::new(user.id), display_name); // Сохраняем username если есть (с упрощённым извлечением через and_then) if let Some(username) = user.usernames .as_ref() .and_then(|u| u.active_usernames.first()) { client.user_cache.user_usernames.insert(UserId::new(user.id), username.to_string()); // Обновляем username в чатах, связанных с этим пользователем for (&chat_id, &user_id) in &client.user_cache.chat_user_ids.clone() { if user_id == UserId::new(user.id) { crate::tdlib::chat_helpers::update_chat(client, chat_id, |chat| { chat.username = Some(format!("@{}", username)); }); } } } // LRU-кэш автоматически удаляет старые записи при вставке } /// Обрабатывает Update::MessageInteractionInfo - обновление реакций на сообщение. /// /// Обновляет список реакций для сообщения в текущем открытом чате. pub fn handle_message_interaction_info_update( client: &mut TdClient, update: UpdateMessageInteractionInfo, ) { // Обновляем реакции в текущем открытом чате if Some(ChatId::new(update.chat_id)) != client.current_chat_id() { return; } let Some(msg) = client .current_chat_messages_mut() .iter_mut() .find(|m| m.id() == MessageId::new(update.message_id)) else { return; }; // Извлекаем реакции из 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(); } /// Обрабатывает Update::MessageSendSucceeded - успешная отправка сообщения. /// /// Заменяет временный ID сообщения на настоящий ID от сервера, /// сохраняя reply_info из временного сообщения. pub fn handle_message_send_succeeded_update( client: &mut TdClient, update: UpdateMessageSendSucceeded, ) { let old_id = MessageId::new(update.old_message_id); let chat_id = ChatId::new(update.message.chat_id); // Обрабатываем только если это текущий открытый чат if Some(chat_id) != client.current_chat_id() { return; } // Находим сообщение с временным ID let Some(idx) = client .current_chat_messages() .iter() .position(|m| m.id() == old_id) else { return; }; // Конвертируем новое сообщение let mut new_msg = crate::tdlib::message_converter::convert_message(client, &update.message, chat_id); // Сохраняем reply_info из старого сообщения (если было) let old_reply = client.current_chat_messages()[idx] .interactions .reply_to .clone(); if let Some(reply) = old_reply { new_msg.interactions.reply_to = Some(reply); } // Заменяем старое сообщение на новое client.current_chat_messages_mut()[idx] = new_msg; } /// Обрабатывает Update::ChatDraftMessage - обновление черновика сообщения в чате. /// /// Извлекает текст черновика и сохраняет его в ChatInfo для отображения в списке чатов. pub fn handle_chat_draft_message_update(client: &mut TdClient, update: UpdateChatDraftMessage) { crate::tdlib::chat_helpers::update_chat(client, ChatId::new(update.chat_id), |chat| { chat.draft_text = update.draft_message.as_ref().and_then(|draft| { // Извлекаем текст из InputMessageText с помощью pattern matching match &draft.input_message_text { tdlib_rs::enums::InputMessageContent::InputMessageText(text_msg) => { Some(text_msg.text.text.clone()) } _ => None, } }); }); } /// Обрабатывает изменение состояния авторизации pub fn handle_auth_state(client: &mut TdClient, state: AuthorizationState) { client.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, _ => client.auth.state.clone(), }; }