fixes
This commit is contained in:
@@ -113,6 +113,26 @@ pub struct ChatInfo {
|
||||
pub is_muted: bool,
|
||||
}
|
||||
|
||||
/// Информация о сообщении, на которое отвечают
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ReplyInfo {
|
||||
/// ID сообщения, на которое отвечают
|
||||
pub message_id: i64,
|
||||
/// Имя отправителя оригинального сообщения
|
||||
pub sender_name: String,
|
||||
/// Текст оригинального сообщения (превью)
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
/// Информация о пересланном сообщении
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ForwardInfo {
|
||||
/// Имя оригинального отправителя
|
||||
pub sender_name: String,
|
||||
/// Дата оригинального сообщения
|
||||
pub date: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MessageInfo {
|
||||
pub id: i64,
|
||||
@@ -131,6 +151,10 @@ pub struct MessageInfo {
|
||||
pub can_be_deleted_only_for_self: bool,
|
||||
/// Можно ли удалить для всех
|
||||
pub can_be_deleted_for_all_users: bool,
|
||||
/// Информация о reply (если это ответ на сообщение)
|
||||
pub reply_to: Option<ReplyInfo>,
|
||||
/// Информация о forward (если сообщение переслано)
|
||||
pub forward_from: Option<ForwardInfo>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -240,7 +264,17 @@ impl TdClient {
|
||||
}
|
||||
|
||||
/// Добавляет сообщение в текущий чат с соблюдением лимита
|
||||
/// Если сообщение с таким id уже есть — заменяет его (сохраняя reply_to)
|
||||
pub fn push_message(&mut self, msg: MessageInfo) {
|
||||
// Проверяем, есть ли уже сообщение с таким id
|
||||
if let Some(idx) = self.current_chat_messages.iter().position(|m| m.id == msg.id) {
|
||||
// Если новое сообщение имеет reply_to, или старое не имеет — заменяем
|
||||
if msg.reply_to.is_some() || self.current_chat_messages[idx].reply_to.is_none() {
|
||||
self.current_chat_messages[idx] = msg;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
self.current_chat_messages.push(msg);
|
||||
// Ограничиваем количество сообщений (удаляем старые)
|
||||
if self.current_chat_messages.len() > MAX_MESSAGES_IN_CHAT {
|
||||
@@ -389,12 +423,25 @@ impl TdClient {
|
||||
let msg_info = self.convert_message(&new_msg.message, chat_id);
|
||||
let msg_id = msg_info.id;
|
||||
let is_incoming = !msg_info.is_outgoing;
|
||||
// Проверяем, что сообщение ещё не добавлено (по id)
|
||||
if !self.current_chat_messages.iter().any(|m| m.id == msg_info.id) {
|
||||
self.push_message(msg_info);
|
||||
// Если это входящее сообщение — добавляем в очередь для отметки как прочитанное
|
||||
if is_incoming {
|
||||
self.pending_view_messages.push((chat_id, vec![msg_id]));
|
||||
|
||||
// Проверяем, есть ли уже сообщение с таким id
|
||||
let existing_idx = self.current_chat_messages.iter().position(|m| m.id == msg_info.id);
|
||||
|
||||
match existing_idx {
|
||||
Some(idx) => {
|
||||
// Сообщение уже есть - обновляем только если входящее
|
||||
// (исходящие уже добавлены через send_message с правильным reply_to)
|
||||
if is_incoming {
|
||||
self.current_chat_messages[idx] = msg_info;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Нового сообщения нет - добавляем
|
||||
self.push_message(msg_info);
|
||||
// Если это входящее сообщение — добавляем в очередь для отметки как прочитанное
|
||||
if is_incoming {
|
||||
self.pending_view_messages.push((chat_id, vec![msg_id]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -630,6 +677,12 @@ impl TdClient {
|
||||
|
||||
let (content, entities) = extract_message_text_static(message);
|
||||
|
||||
// Извлекаем информацию о reply
|
||||
let reply_to = self.extract_reply_info(message);
|
||||
|
||||
// Извлекаем информацию о forward
|
||||
let forward_from = self.extract_forward_info(message);
|
||||
|
||||
MessageInfo {
|
||||
id: message.id,
|
||||
sender_name,
|
||||
@@ -642,6 +695,187 @@ impl TdClient {
|
||||
can_be_edited: message.can_be_edited,
|
||||
can_be_deleted_only_for_self: message.can_be_deleted_only_for_self,
|
||||
can_be_deleted_for_all_users: message.can_be_deleted_for_all_users,
|
||||
reply_to,
|
||||
forward_from,
|
||||
}
|
||||
}
|
||||
|
||||
/// Извлекает информацию о reply из сообщения
|
||||
fn extract_reply_info(&self, message: &TdMessage) -> Option<ReplyInfo> {
|
||||
use tdlib_rs::enums::MessageReplyTo;
|
||||
|
||||
match &message.reply_to {
|
||||
Some(MessageReplyTo::Message(reply)) => {
|
||||
// Получаем имя отправителя из origin или ищем сообщение в текущем списке
|
||||
let sender_name = if let Some(origin) = &reply.origin {
|
||||
self.get_origin_sender_name(origin)
|
||||
} else {
|
||||
// Пробуем найти оригинальное сообщение в текущем списке
|
||||
self.current_chat_messages
|
||||
.iter()
|
||||
.find(|m| m.id == reply.message_id)
|
||||
.map(|m| m.sender_name.clone())
|
||||
.unwrap_or_else(|| "...".to_string())
|
||||
};
|
||||
|
||||
// Получаем текст из content или quote
|
||||
let text = if let Some(quote) = &reply.quote {
|
||||
quote.text.text.clone()
|
||||
} else if let Some(content) = &reply.content {
|
||||
extract_content_text(content)
|
||||
} else {
|
||||
// Пробуем найти в текущих сообщениях
|
||||
self.current_chat_messages
|
||||
.iter()
|
||||
.find(|m| m.id == reply.message_id)
|
||||
.map(|m| m.content.clone())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
Some(ReplyInfo {
|
||||
message_id: reply.message_id,
|
||||
sender_name,
|
||||
text,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Извлекает информацию о forward из сообщения
|
||||
fn extract_forward_info(&self, message: &TdMessage) -> Option<ForwardInfo> {
|
||||
message.forward_info.as_ref().map(|info| {
|
||||
let sender_name = self.get_origin_sender_name(&info.origin);
|
||||
ForwardInfo {
|
||||
sender_name,
|
||||
date: info.date,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Получает имя отправителя из MessageOrigin
|
||||
fn get_origin_sender_name(&self, origin: &tdlib_rs::enums::MessageOrigin) -> String {
|
||||
use tdlib_rs::enums::MessageOrigin;
|
||||
match origin {
|
||||
MessageOrigin::User(u) => {
|
||||
self.user_names.peek(&u.sender_user_id)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| format!("User_{}", u.sender_user_id))
|
||||
}
|
||||
MessageOrigin::Chat(c) => {
|
||||
self.chats.iter()
|
||||
.find(|chat| chat.id == c.sender_chat_id)
|
||||
.map(|chat| chat.title.clone())
|
||||
.unwrap_or_else(|| "Чат".to_string())
|
||||
}
|
||||
MessageOrigin::HiddenUser(h) => h.sender_name.clone(),
|
||||
MessageOrigin::Channel(c) => {
|
||||
self.chats.iter()
|
||||
.find(|chat| chat.id == c.chat_id)
|
||||
.map(|chat| chat.title.clone())
|
||||
.unwrap_or_else(|| "Канал".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Обновляет reply info для сообщений, где данные не были загружены
|
||||
/// Вызывается после загрузки истории, когда все сообщения уже в списке
|
||||
fn update_reply_info_from_loaded_messages(&mut self) {
|
||||
// Собираем данные для обновления (id -> (sender_name, content))
|
||||
let msg_data: std::collections::HashMap<i64, (String, String)> = self
|
||||
.current_chat_messages
|
||||
.iter()
|
||||
.map(|m| (m.id, (m.sender_name.clone(), m.content.clone())))
|
||||
.collect();
|
||||
|
||||
// Обновляем reply_to для сообщений с неполными данными
|
||||
for msg in &mut self.current_chat_messages {
|
||||
if let Some(ref mut reply) = msg.reply_to {
|
||||
// Если sender_name = "..." или text пустой — пробуем заполнить
|
||||
if reply.sender_name == "..." || reply.text.is_empty() {
|
||||
if let Some((sender, content)) = msg_data.get(&reply.message_id) {
|
||||
if reply.sender_name == "..." {
|
||||
reply.sender_name = sender.clone();
|
||||
}
|
||||
if reply.text.is_empty() {
|
||||
reply.text = content.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Асинхронно обновляет reply info, загружая недостающие сообщения
|
||||
pub async fn fetch_missing_reply_info(&mut self) {
|
||||
let chat_id = match self.current_chat_id {
|
||||
Some(id) => id,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Собираем message_id для которых нужно загрузить данные
|
||||
let missing_ids: Vec<i64> = self
|
||||
.current_chat_messages
|
||||
.iter()
|
||||
.filter_map(|msg| {
|
||||
msg.reply_to.as_ref().and_then(|reply| {
|
||||
if reply.sender_name == "..." || reply.text.is_empty() {
|
||||
Some(reply.message_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
if missing_ids.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Загружаем каждое сообщение и кэшируем данные
|
||||
let mut reply_cache: std::collections::HashMap<i64, (String, String)> =
|
||||
std::collections::HashMap::new();
|
||||
|
||||
for msg_id in missing_ids {
|
||||
if reply_cache.contains_key(&msg_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(tdlib_rs::enums::Message::Message(msg)) =
|
||||
functions::get_message(chat_id, msg_id, self.client_id).await
|
||||
{
|
||||
let sender_name = match &msg.sender_id {
|
||||
tdlib_rs::enums::MessageSender::User(user) => {
|
||||
self.user_names
|
||||
.get(&user.user_id)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| format!("User_{}", user.user_id))
|
||||
}
|
||||
tdlib_rs::enums::MessageSender::Chat(chat) => {
|
||||
self.chats
|
||||
.iter()
|
||||
.find(|c| c.id == chat.chat_id)
|
||||
.map(|c| c.title.clone())
|
||||
.unwrap_or_else(|| "Чат".to_string())
|
||||
}
|
||||
};
|
||||
let (content, _) = extract_message_text_static(&msg);
|
||||
reply_cache.insert(msg_id, (sender_name, content));
|
||||
}
|
||||
}
|
||||
|
||||
// Применяем загруженные данные
|
||||
for msg in &mut self.current_chat_messages {
|
||||
if let Some(ref mut reply) = msg.reply_to {
|
||||
if let Some((sender, content)) = reply_cache.get(&reply.message_id) {
|
||||
if reply.sender_name == "..." {
|
||||
reply.sender_name = sender.clone();
|
||||
}
|
||||
if reply.text.is_empty() {
|
||||
reply.text = content.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,6 +1013,9 @@ impl TdClient {
|
||||
all_messages.reverse();
|
||||
self.current_chat_messages = all_messages.clone();
|
||||
|
||||
// Обновляем reply info для сообщений где данные не были загружены
|
||||
self.update_reply_info_from_loaded_messages();
|
||||
|
||||
// Отмечаем сообщения как прочитанные
|
||||
if !all_messages.is_empty() {
|
||||
let message_ids: Vec<i64> = all_messages.iter().map(|m| m.id).collect();
|
||||
@@ -860,10 +1097,10 @@ impl TdClient {
|
||||
}
|
||||
}
|
||||
|
||||
/// Отправка текстового сообщения с поддержкой Markdown
|
||||
pub async fn send_message(&self, chat_id: i64, text: String) -> Result<MessageInfo, String> {
|
||||
use tdlib_rs::types::{FormattedText, InputMessageText, TextParseModeMarkdown};
|
||||
use tdlib_rs::enums::{InputMessageContent, TextParseMode};
|
||||
/// Отправка текстового сообщения с поддержкой Markdown и reply
|
||||
pub async fn send_message(&self, chat_id: i64, text: String, reply_to_message_id: Option<i64>, reply_info: Option<ReplyInfo>) -> Result<MessageInfo, String> {
|
||||
use tdlib_rs::types::{FormattedText, InputMessageText, TextParseModeMarkdown, InputMessageReplyToMessage};
|
||||
use tdlib_rs::enums::{InputMessageContent, TextParseMode, InputMessageReplyTo};
|
||||
|
||||
// Парсим markdown в тексте
|
||||
let formatted_text = match functions::parse_text_entities(
|
||||
@@ -890,10 +1127,20 @@ impl TdClient {
|
||||
clear_draft: true,
|
||||
});
|
||||
|
||||
// Создаём reply_to если есть message_id для ответа
|
||||
// chat_id: 0 означает ответ в том же чате
|
||||
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
|
||||
None, // reply_to
|
||||
reply_to,
|
||||
None, // options
|
||||
content,
|
||||
self.client_id,
|
||||
@@ -904,6 +1151,7 @@ impl TdClient {
|
||||
Ok(tdlib_rs::enums::Message::Message(msg)) => {
|
||||
// Извлекаем текст и entities из отправленного сообщения
|
||||
let (content, entities) = extract_message_text_static(&msg);
|
||||
|
||||
Ok(MessageInfo {
|
||||
id: msg.id,
|
||||
sender_name: "Вы".to_string(),
|
||||
@@ -916,6 +1164,8 @@ impl TdClient {
|
||||
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: reply_info,
|
||||
forward_from: None,
|
||||
})
|
||||
}
|
||||
Err(e) => Err(format!("Ошибка отправки сообщения: {:?}", e)),
|
||||
@@ -975,6 +1225,8 @@ impl TdClient {
|
||||
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: None, // При редактировании reply сохраняется из оригинала
|
||||
forward_from: None, // При редактировании forward сохраняется из оригинала
|
||||
})
|
||||
}
|
||||
Err(e) => Err(format!("Ошибка редактирования сообщения: {:?}", e)),
|
||||
@@ -998,6 +1250,26 @@ impl TdClient {
|
||||
}
|
||||
}
|
||||
|
||||
/// Пересылка сообщений
|
||||
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 process_pending_view_messages(&mut self) {
|
||||
let pending = std::mem::take(&mut self.pending_view_messages);
|
||||
@@ -1125,3 +1397,33 @@ fn extract_message_text_static(message: &TdMessage) -> (String, Vec<TextEntity>)
|
||||
_ => ("[Сообщение]".to_string(), vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Извлекает текст из MessageContent (для reply preview)
|
||||
fn extract_content_text(content: &MessageContent) -> String {
|
||||
match content {
|
||||
MessageContent::MessageText(text) => text.text.text.clone(),
|
||||
MessageContent::MessagePhoto(photo) => {
|
||||
if photo.caption.text.is_empty() {
|
||||
"[Фото]".to_string()
|
||||
} else {
|
||||
format!("[Фото] {}", photo.caption.text)
|
||||
}
|
||||
}
|
||||
MessageContent::MessageVideo(video) => {
|
||||
if video.caption.text.is_empty() {
|
||||
"[Видео]".to_string()
|
||||
} else {
|
||||
format!("[Видео] {}", video.caption.text)
|
||||
}
|
||||
}
|
||||
MessageContent::MessageDocument(doc) => format!("[Файл: {}]", doc.document.file_name),
|
||||
MessageContent::MessageVoiceNote(_) => "[Голосовое]".to_string(),
|
||||
MessageContent::MessageVideoNote(_) => "[Видеосообщение]".to_string(),
|
||||
MessageContent::MessageSticker(sticker) => format!("[Стикер: {}]", sticker.sticker.emoji),
|
||||
MessageContent::MessageAnimation(_) => "[GIF]".to_string(),
|
||||
MessageContent::MessageAudio(audio) => format!("[Аудио: {}]", audio.audio.title),
|
||||
MessageContent::MessageCall(_) => "[Звонок]".to_string(),
|
||||
MessageContent::MessagePoll(poll) => format!("[Опрос: {}]", poll.poll.question.text),
|
||||
_ => "[Сообщение]".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user