Split core and TUI crates
This commit is contained in:
324
crates/tele-core/src/tdlib/update_handlers.rs
Normal file
324
crates/tele-core/src/tdlib/update_handlers.rs
Normal file
@@ -0,0 +1,324 @@
|
||||
//! 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().to_string();
|
||||
|
||||
client.enqueue_incoming_message_event(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.replace_current_chat_message(msg_id, msg_info);
|
||||
} else {
|
||||
// Для исходящих: обновляем can_be_edited и другие поля,
|
||||
// но сохраняем reply_to (добавленный при отправке)
|
||||
client.update_current_chat_messages(|messages| {
|
||||
let existing = &mut messages[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.enqueue_pending_view_messages(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()),
|
||||
_ => 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.remove_chat(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.sort_chats_by_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.update_chats(|chats| {
|
||||
chats.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.update_user_cache(|cache| {
|
||||
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())
|
||||
{
|
||||
let affected_chat_ids = client.update_user_cache(|cache| {
|
||||
cache
|
||||
.user_usernames
|
||||
.insert(UserId::new(user.id), username.to_string());
|
||||
cache
|
||||
.chat_user_ids
|
||||
.iter()
|
||||
.filter_map(|(&chat_id, &user_id)| {
|
||||
(user_id == UserId::new(user.id)).then_some(chat_id)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
// Обновляем username в чатах, связанных с этим пользователем
|
||||
for chat_id in affected_chat_ids {
|
||||
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;
|
||||
}
|
||||
|
||||
// Извлекаем реакции из interaction_info
|
||||
let 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();
|
||||
client.update_current_chat_message(MessageId::new(update.message_id), |msg| {
|
||||
msg.interactions.reactions = reactions;
|
||||
});
|
||||
}
|
||||
|
||||
/// Обрабатывает 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.replace_current_chat_message(old_id, 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(),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user