This commit is contained in:
Mikhail Kilin
2026-01-30 23:55:01 +03:00
parent 433233d766
commit bba5cbd22d
25 changed files with 5896 additions and 1469 deletions

545
src/tdlib/messages.rs Normal file
View File

@@ -0,0 +1,545 @@
use crate::constants::{MAX_MESSAGES_IN_CHAT, TDLIB_MESSAGE_LIMIT};
use tdlib_rs::enums::{ChatAction, InputMessageContent, InputMessageReplyTo, MessageContent, MessageSender, SearchMessagesFilter, TextParseMode};
use tdlib_rs::functions;
use tdlib_rs::types::{Chat as TdChat, FormattedText, InputMessageReplyToMessage, InputMessageText, Message as TdMessage, TextEntity, TextParseModeMarkdown};
use super::types::{ForwardInfo, MessageInfo, ReactionInfo, ReplyInfo};
/// Менеджер сообщений
pub struct MessageManager {
pub current_chat_messages: Vec<MessageInfo>,
pub current_chat_id: Option<i64>,
pub current_pinned_message: Option<MessageInfo>,
/// Очередь сообщений для отметки как прочитанных: (chat_id, message_ids)
pub pending_view_messages: Vec<(i64, Vec<i64>)>,
client_id: i32,
}
impl MessageManager {
pub fn new(client_id: i32) -> Self {
Self {
current_chat_messages: Vec::new(),
current_chat_id: None,
current_pinned_message: None,
pending_view_messages: Vec::new(),
client_id,
}
}
/// Добавить сообщение в список текущего чата
pub fn push_message(&mut self, msg: MessageInfo) {
self.current_chat_messages.insert(0, msg);
// Ограничиваем размер списка
if self.current_chat_messages.len() > MAX_MESSAGES_IN_CHAT {
self.current_chat_messages.truncate(MAX_MESSAGES_IN_CHAT);
}
}
/// Получить историю чата
pub async fn get_chat_history(
&mut self,
chat_id: i64,
limit: i32,
) -> Result<Vec<MessageInfo>, String> {
// Устанавливаем текущий чат для получения новых сообщений
self.current_chat_id = Some(chat_id);
let result = functions::get_chat_history(
chat_id,
0, // from_message_id
0, // offset
limit,
false,
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);
}
}
}
Ok(messages)
}
Ok(_) => Err("Неожиданный тип сообщений".to_string()),
Err(e) => Err(format!("Ошибка загрузки истории: {:?}", e)),
}
}
/// Загрузить более старые сообщения
pub async fn load_older_messages(
&mut self,
chat_id: i64,
from_message_id: i64,
) -> Result<Vec<MessageInfo>, String> {
let result = functions::get_chat_history(
chat_id,
from_message_id,
0, // offset
TDLIB_MESSAGE_LIMIT,
false,
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);
}
}
}
Ok(messages)
}
Ok(_) => Err("Неожиданный тип сообщений".to_string()),
Err(e) => Err(format!("Ошибка загрузки старых сообщений: {:?}", e)),
}
}
/// Получить закреплённые сообщения
pub async fn get_pinned_messages(&mut self, chat_id: i64) -> Result<Vec<MessageInfo>, String> {
let result = functions::search_chat_messages(
chat_id,
String::new(),
None,
0, // from_message_id
0, // offset
100, // limit
Some(SearchMessagesFilter::Pinned),
0, // message_thread_id
0, // saved_messages_topic_id
self.client_id,
)
.await;
match result {
Ok(tdlib_rs::enums::FoundChatMessages::FoundChatMessages(messages_obj)) => {
let mut pinned_messages = Vec::new();
for msg in messages_obj.messages.iter().rev() {
if let Some(info) = self.convert_message(msg).await {
pinned_messages.push(info);
}
}
Ok(pinned_messages)
}
Ok(_) => Err("Неожиданный тип результата поиска".to_string()),
Err(e) => Err(format!("Ошибка загрузки закреплённых: {:?}", e)),
}
}
/// Загрузить текущее закреплённое сообщение
pub async fn load_current_pinned_message(&mut self, chat_id: i64) {
// TODO: В tdlib-rs 1.8.29 поле pinned_message_id было удалено из Chat.
// Нужно использовать getChatPinnedMessage или альтернативный способ.
// Временно отключено.
let _ = chat_id;
self.current_pinned_message = None;
// match functions::get_chat(chat_id, self.client_id).await {
// Ok(tdlib_rs::enums::Chat::Chat(chat)) => {
// // chat.pinned_message_id больше не существует
// }
// _ => {}
// }
}
/// Поиск сообщений в чате
pub async fn search_messages(
&self,
chat_id: i64,
query: &str,
) -> Result<Vec<MessageInfo>, String> {
let result = functions::search_chat_messages(
chat_id,
query.to_string(),
None,
0, // from_message_id
0, // offset
100, // limit
None,
0, // message_thread_id
0, // saved_messages_topic_id
self.client_id,
)
.await;
match result {
Ok(tdlib_rs::enums::FoundChatMessages::FoundChatMessages(messages_obj)) => {
let mut search_results = Vec::new();
for msg in messages_obj.messages.iter().rev() {
if let Some(info) = self.convert_message(msg).await {
search_results.push(info);
}
}
Ok(search_results)
}
Ok(_) => Err("Неожиданный тип результата поиска".to_string()),
Err(e) => Err(format!("Ошибка поиска: {:?}", e)),
}
}
/// Отправить сообщение
pub async fn send_message(
&self,
chat_id: i64,
text: String,
reply_to_message_id: Option<i64>,
_reply_info: Option<ReplyInfo>,
) -> Result<MessageInfo, String> {
// Парсим markdown в тексте
let formatted_text = match functions::parse_text_entities(
text.clone(),
TextParseMode::Markdown(TextParseModeMarkdown { version: 2 }),
self.client_id,
)
.await
{
Ok(tdlib_rs::enums::FormattedText::FormattedText(ft)) => {
FormattedText {
text: ft.text,
entities: ft.entities,
}
}
Err(_) => FormattedText {
text: text.clone(),
entities: vec![],
},
};
let content = InputMessageContent::InputMessageText(InputMessageText {
text: formatted_text,
link_preview_options: None,
clear_draft: true,
});
let reply_to = reply_to_message_id.map(|msg_id| {
InputMessageReplyTo::Message(InputMessageReplyToMessage {
chat_id: 0,
message_id: msg_id,
quote: None,
})
});
let result = functions::send_message(
chat_id,
0, // message_thread_id
reply_to,
None, // options
content,
self.client_id,
)
.await;
match result {
Ok(tdlib_rs::enums::Message::Message(msg)) => self
.convert_message(&msg)
.await
.ok_or_else(|| "Не удалось конвертировать сообщение".to_string()),
Ok(_) => Err("Неожиданный тип сообщения".to_string()),
Err(e) => Err(format!("Ошибка отправки сообщения: {:?}", e)),
}
}
/// Редактировать сообщение
pub async fn edit_message(
&self,
chat_id: i64,
message_id: i64,
text: String,
) -> Result<MessageInfo, String> {
let formatted_text = match functions::parse_text_entities(
text.clone(),
TextParseMode::Markdown(TextParseModeMarkdown { version: 2 }),
self.client_id,
)
.await
{
Ok(tdlib_rs::enums::FormattedText::FormattedText(ft)) => {
FormattedText {
text: ft.text,
entities: ft.entities,
}
}
Err(_) => FormattedText {
text: text.clone(),
entities: vec![],
},
};
let content = InputMessageContent::InputMessageText(InputMessageText {
text: formatted_text,
link_preview_options: None,
clear_draft: true,
});
let result =
functions::edit_message_text(chat_id, message_id, content, self.client_id).await;
match result {
Ok(tdlib_rs::enums::Message::Message(msg)) => self
.convert_message(&msg)
.await
.ok_or_else(|| "Не удалось конвертировать отредактированное сообщение".to_string()),
Ok(_) => Err("Неожиданный тип сообщения".to_string()),
Err(e) => Err(format!("Ошибка редактирования: {:?}", e)),
}
}
/// Удалить сообщения
pub async fn delete_messages(
&self,
chat_id: i64,
message_ids: Vec<i64>,
revoke: bool,
) -> Result<(), String> {
let result =
functions::delete_messages(chat_id, message_ids, revoke, self.client_id).await;
match result {
Ok(_) => Ok(()),
Err(e) => Err(format!("Ошибка удаления: {:?}", e)),
}
}
/// Переслать сообщения
pub async fn forward_messages(
&self,
to_chat_id: i64,
from_chat_id: i64,
message_ids: Vec<i64>,
) -> Result<(), String> {
let result = functions::forward_messages(
to_chat_id,
0, // message_thread_id
from_chat_id,
message_ids,
None, // options
false, // send_copy
false, // remove_caption
self.client_id,
)
.await;
match result {
Ok(_) => Ok(()),
Err(e) => Err(format!("Ошибка пересылки: {:?}", e)),
}
}
/// Установить черновик
pub async fn set_draft_message(&self, chat_id: i64, text: String) -> Result<(), String> {
use tdlib_rs::types::DraftMessage;
let draft = if text.is_empty() {
None
} else {
Some(DraftMessage {
reply_to: None,
date: 0,
input_message_text: InputMessageContent::InputMessageText(InputMessageText {
text: FormattedText {
text: text.clone(),
entities: vec![],
},
link_preview_options: None,
clear_draft: false,
}),
})
};
let result = functions::set_chat_draft_message(chat_id, 0, draft, self.client_id).await;
match result {
Ok(_) => Ok(()),
Err(e) => Err(format!("Ошибка сохранения черновика: {:?}", e)),
}
}
/// Обработать очередь просмотра сообщений
pub async fn process_pending_view_messages(&mut self) {
if self.pending_view_messages.is_empty() {
return;
}
let batch = std::mem::take(&mut self.pending_view_messages);
for (chat_id, message_ids) in batch {
let _ = functions::view_messages(chat_id, message_ids, None, true, self.client_id).await;
}
}
/// Конвертировать TdMessage в MessageInfo
async fn convert_message(&self, msg: &TdMessage) -> Option<MessageInfo> {
let content_text = match &msg.content {
MessageContent::MessageText(t) => t.text.text.clone(),
MessageContent::MessagePhoto(p) => {
let caption_text = p.caption.text.clone();
if caption_text.is_empty() { "[Фото]".to_string() } else { caption_text }
}
MessageContent::MessageVideo(v) => {
let caption_text = v.caption.text.clone();
if caption_text.is_empty() { "[Видео]".to_string() } else { caption_text }
}
MessageContent::MessageDocument(d) => {
let caption_text = d.caption.text.clone();
if caption_text.is_empty() { format!("[Файл: {}]", d.document.file_name) } else { caption_text }
}
MessageContent::MessageSticker(s) => {
format!("[Стикер: {}]", s.sticker.emoji)
}
MessageContent::MessageAnimation(a) => {
let caption_text = a.caption.text.clone();
if caption_text.is_empty() { "[GIF]".to_string() } else { caption_text }
}
MessageContent::MessageVoiceNote(v) => {
let caption_text = v.caption.text.clone();
if caption_text.is_empty() { "[Голосовое]".to_string() } else { caption_text }
}
MessageContent::MessageAudio(a) => {
let caption_text = a.caption.text.clone();
if caption_text.is_empty() {
let title = a.audio.title.clone();
let performer = a.audio.performer.clone();
if !title.is_empty() || !performer.is_empty() {
format!("[Аудио: {} - {}]", performer, title)
} else {
"[Аудио]".to_string()
}
} else {
caption_text
}
}
_ => "[Неподдерживаемый тип сообщения]".to_string(),
};
let entities = if let MessageContent::MessageText(t) = &msg.content {
t.text.entities.clone()
} else {
vec![]
};
let sender_name = match &msg.sender_id {
MessageSender::User(user) => {
match functions::get_user(user.user_id, self.client_id).await {
Ok(tdlib_rs::enums::User::User(u)) => format!("{} {}", u.first_name, u.last_name).trim().to_string(),
_ => format!("User {}", user.user_id),
}
}
MessageSender::Chat(chat) => format!("Chat {}", chat.chat_id),
};
let forward_from = msg.forward_info.as_ref().and_then(|fi| {
if let tdlib_rs::enums::MessageOrigin::User(origin_user) = &fi.origin {
Some(ForwardInfo {
sender_name: format!("User {}", origin_user.sender_user_id),
date: fi.date,
})
} else {
None
}
});
let reply_to = if let Some(ref reply_to) = msg.reply_to {
if let tdlib_rs::enums::MessageReplyTo::Message(reply_msg) = reply_to {
// Здесь можно загрузить информацию об оригинальном сообщении
Some(ReplyInfo {
message_id: reply_msg.message_id,
sender_name: "Unknown".to_string(),
text: "...".to_string(),
})
} else {
None
}
} else {
None
};
let reactions: Vec<ReactionInfo> = msg
.interaction_info
.as_ref()
.and_then(|ii| ii.reactions.as_ref())
.map(|reactions| {
reactions
.reactions
.iter()
.filter_map(|r| {
if let tdlib_rs::enums::ReactionType::Emoji(emoji_type) = &r.r#type {
Some(ReactionInfo {
emoji: emoji_type.emoji.clone(),
count: r.total_count,
is_chosen: r.is_chosen,
})
} else {
None
}
})
.collect()
})
.unwrap_or_default();
Some(MessageInfo {
id: msg.id,
sender_name,
is_outgoing: msg.is_outgoing,
content: content_text,
entities,
date: msg.date,
edit_date: msg.edit_date,
is_read: !msg.contains_unread_mention,
can_be_edited: msg.can_be_edited,
can_be_deleted_only_for_self: msg.can_be_deleted_only_for_self,
can_be_deleted_for_all_users: msg.can_be_deleted_for_all_users,
reply_to,
forward_from,
reactions,
})
}
/// Получить недостающую reply информацию для сообщений
pub async fn fetch_missing_reply_info(&mut self) {
// Collect message IDs that need to be fetched
let mut to_fetch = Vec::new();
for msg in &self.current_chat_messages {
if let Some(ref reply) = msg.reply_to {
if reply.sender_name == "Unknown" {
to_fetch.push(reply.message_id);
}
}
}
// Fetch missing messages
if let Some(chat_id) = self.current_chat_id {
for message_id in to_fetch {
if let Ok(original_msg_enum) =
functions::get_message(chat_id, message_id, self.client_id).await
{
if let tdlib_rs::enums::Message::Message(original_msg) = original_msg_enum {
if let Some(orig_info) = self.convert_message(&original_msg).await {
// Update the reply info
for msg in &mut self.current_chat_messages {
if let Some(ref mut reply) = msg.reply_to {
if reply.message_id == message_id {
reply.sender_name = orig_info.sender_name.clone();
reply.text = orig_info
.content
.chars()
.take(50)
.collect::<String>();
}
}
}
}
}
}
}
}
}
}