fixes
This commit is contained in:
545
src/tdlib/messages.rs
Normal file
545
src/tdlib/messages.rs
Normal 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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user