This commit is contained in:
Mikhail Kilin
2026-01-30 15:07:13 +03:00
parent 126c7482af
commit 4deb0fbe00
32 changed files with 1049 additions and 697 deletions

View File

@@ -1,7 +1,10 @@
use std::env;
use std::collections::HashMap;
use std::env;
use std::time::Instant;
use tdlib_rs::enums::{AuthorizationState, ChatAction, ChatList, ChatType, ConnectionState, MessageContent, MessageSender, SearchMessagesFilter, Update, User, UserStatus};
use tdlib_rs::enums::{
AuthorizationState, ChatAction, ChatList, ChatType, ConnectionState, MessageContent,
MessageSender, SearchMessagesFilter, Update, User, UserStatus,
};
use tdlib_rs::types::TextEntity;
/// Максимальный размер кэшей пользователей
@@ -311,7 +314,11 @@ 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) {
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;
@@ -350,7 +357,8 @@ impl TdClient {
/// Например: "Вася печатает..."
pub fn get_typing_text(&self) -> Option<String> {
self.typing_status.as_ref().map(|(user_id, action, _)| {
let name = self.user_names
let name = self
.user_names
.peek(user_id)
.cloned()
.unwrap_or_else(|| "Кто-то".to_string());
@@ -361,20 +369,20 @@ impl TdClient {
/// Инициализация TDLib с параметрами
pub async fn init(&mut self) -> Result<(), String> {
let result = functions::set_tdlib_parameters(
false, // use_test_dc
"tdlib_data".to_string(), // database_directory
"".to_string(), // files_directory
"".to_string(), // database_encryption_key
true, // use_file_database
true, // use_chat_info_database
true, // use_message_database
false, // use_secret_chats
self.api_id, // api_id
self.api_hash.clone(), // api_hash
"en".to_string(), // system_language_code
"Desktop".to_string(), // device_model
"".to_string(), // system_version
env!("CARGO_PKG_VERSION").to_string(), // application_version
false, // use_test_dc
"tdlib_data".to_string(), // database_directory
"".to_string(), // files_directory
"".to_string(), // database_encryption_key
true, // use_file_database
true, // use_chat_info_database
true, // use_message_database
false, // use_secret_chats
self.api_id, // api_id
self.api_hash.clone(), // api_hash
"en".to_string(), // system_language_code
"Desktop".to_string(), // device_model
"".to_string(), // system_version
env!("CARGO_PKG_VERSION").to_string(), // application_version
self.client_id,
)
.await;
@@ -457,7 +465,9 @@ impl TdClient {
if update.position.order == 0 {
// Чат больше не в Main (перемещён в архив и т.д.)
self.chats.retain(|c| c.id != update.chat_id);
} else if let Some(chat) = self.chats.iter_mut().find(|c| c.id == update.chat_id) {
} else if let Some(chat) =
self.chats.iter_mut().find(|c| c.id == update.chat_id)
{
// Обновляем позицию существующего чата
chat.order = update.position.order;
chat.is_pinned = update.position.is_pinned;
@@ -493,7 +503,10 @@ impl TdClient {
let is_incoming = !msg_info.is_outgoing;
// Проверяем, есть ли уже сообщение с таким id
let existing_idx = self.current_chat_messages.iter().position(|m| m.id == msg_info.id);
let existing_idx = self
.current_chat_messages
.iter()
.position(|m| m.id == msg_info.id);
match existing_idx {
Some(idx) => {
@@ -505,8 +518,10 @@ impl TdClient {
// но сохраняем reply_to (добавленный при отправке)
let existing = &mut self.current_chat_messages[idx];
existing.can_be_edited = msg_info.can_be_edited;
existing.can_be_deleted_only_for_self = msg_info.can_be_deleted_only_for_self;
existing.can_be_deleted_for_all_users = msg_info.can_be_deleted_for_all_users;
existing.can_be_deleted_only_for_self =
msg_info.can_be_deleted_only_for_self;
existing.can_be_deleted_for_all_users =
msg_info.can_be_deleted_for_all_users;
existing.is_read = msg_info.is_read;
}
}
@@ -529,9 +544,8 @@ impl TdClient {
if user.first_name.is_empty() && user.last_name.is_empty() {
// Удаляем чаты с этим пользователем из списка
let user_id = user.id;
self.chats.retain(|c| {
self.chat_user_ids.get(&c.id) != Some(&user_id)
});
self.chats
.retain(|c| self.chat_user_ids.get(&c.id) != Some(&user_id));
return;
}
@@ -550,7 +564,8 @@ impl TdClient {
// Обновляем username в чатах, связанных с этим пользователем
for (&chat_id, &user_id) in &self.chat_user_ids.clone() {
if user_id == user.id {
if let Some(chat) = self.chats.iter_mut().find(|c| c.id == chat_id) {
if let Some(chat) = self.chats.iter_mut().find(|c| c.id == chat_id)
{
chat.username = Some(format!("@{}", username));
}
}
@@ -564,10 +579,7 @@ impl TdClient {
self.folders = update
.chat_folders
.into_iter()
.map(|f| FolderInfo {
id: f.id,
name: f.title,
})
.map(|f| FolderInfo { id: f.id, name: f.title })
.collect();
self.main_chat_list_position = update.main_chat_list_position;
}
@@ -607,14 +619,26 @@ impl TdClient {
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::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::UploadingDocument(_) => {
Some("отправляет файл...".to_string())
}
ChatAction::ChoosingSticker => Some("выбирает стикер...".to_string()),
ChatAction::RecordingVideoNote => Some("записывает видеосообщение...".to_string()),
ChatAction::UploadingVideoNote(_) => Some("отправляет видеосообщение...".to_string()),
ChatAction::RecordingVideoNote => {
Some("записывает видеосообщение...".to_string())
}
ChatAction::UploadingVideoNote(_) => {
Some("отправляет видеосообщение...".to_string())
}
ChatAction::Cancel => None, // Отмена — сбрасываем статус
_ => None,
};
@@ -633,7 +657,9 @@ impl TdClient {
if let Some(chat) = self.chats.iter_mut().find(|c| c.id == update.chat_id) {
chat.draft_text = update.draft_message.as_ref().and_then(|draft| {
// Извлекаем текст из InputMessageText
if let tdlib_rs::enums::InputMessageContent::InputMessageText(text_msg) = &draft.input_message_text {
if let tdlib_rs::enums::InputMessageContent::InputMessageText(text_msg) =
&draft.input_message_text
{
Some(text_msg.text.text.clone())
} else {
None
@@ -644,7 +670,11 @@ impl TdClient {
Update::MessageInteractionInfo(update) => {
// Обновляем реакции в текущем открытом чате
if Some(update.chat_id) == self.current_chat_id {
if let Some(msg) = self.current_chat_messages.iter_mut().find(|m| m.id == update.message_id) {
if let Some(msg) = self
.current_chat_messages
.iter_mut()
.find(|m| m.id == update.message_id)
{
// Извлекаем реакции из interaction_info
msg.reactions = update
.interaction_info
@@ -656,8 +686,12 @@ impl TdClient {
.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,
tdlib_rs::enums::ReactionType::Emoji(e) => {
e.emoji.clone()
}
tdlib_rs::enums::ReactionType::CustomEmoji(_) => {
return None
}
};
Some(ReactionInfo {
@@ -697,9 +731,10 @@ impl TdClient {
}
// Ищем позицию в Main списке (если есть)
let main_position = td_chat.positions.iter().find(|pos| {
matches!(pos.list, ChatList::Main)
});
let main_position = td_chat
.positions
.iter()
.find(|pos| matches!(pos.list, ChatList::Main));
// Получаем order и is_pinned из позиции, или используем значения по умолчанию
let (order, is_pinned) = main_position
@@ -716,7 +751,9 @@ impl TdClient {
let username = match &td_chat.r#type {
ChatType::Private(private) => {
// Ограничиваем размер chat_user_ids
if self.chat_user_ids.len() >= MAX_CHAT_USER_IDS && !self.chat_user_ids.contains_key(&td_chat.id) {
if self.chat_user_ids.len() >= MAX_CHAT_USER_IDS
&& !self.chat_user_ids.contains_key(&td_chat.id)
{
// Удаляем случайную запись (первую найденную)
if let Some(&key) = self.chat_user_ids.keys().next() {
self.chat_user_ids.remove(&key);
@@ -724,7 +761,9 @@ impl TdClient {
}
self.chat_user_ids.insert(td_chat.id, private.user_id);
// Проверяем, есть ли уже username в кэше (peek не обновляет LRU)
self.user_usernames.peek(&private.user_id).map(|u| format!("@{}", u))
self.user_usernames
.peek(&private.user_id)
.map(|u| format!("@{}", u))
}
_ => None,
};
@@ -784,7 +823,13 @@ impl TdClient {
// Ограничиваем количество чатов
if self.chats.len() > MAX_CHATS {
// Удаляем чат с наименьшим order (наименее активный)
if let Some(min_idx) = self.chats.iter().enumerate().min_by_key(|(_, c)| c.order).map(|(i, _)| i) {
if let Some(min_idx) = self
.chats
.iter()
.enumerate()
.min_by_key(|(_, c)| c.order)
.map(|(i, _)| i)
{
self.chats.remove(min_idx);
}
}
@@ -891,11 +936,7 @@ impl TdClient {
.unwrap_or_default()
};
Some(ReplyInfo {
message_id: reply.message_id,
sender_name,
text,
})
Some(ReplyInfo { message_id: reply.message_id, sender_name, text })
}
_ => None,
}
@@ -905,10 +946,7 @@ impl TdClient {
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,
}
ForwardInfo { sender_name, date: info.date }
})
}
@@ -944,24 +982,24 @@ impl TdClient {
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::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())
}
MessageOrigin::Channel(c) => self
.chats
.iter()
.find(|chat| chat.id == c.chat_id)
.map(|chat| chat.title.clone())
.unwrap_or_else(|| "Канал".to_string()),
}
}
@@ -1032,19 +1070,17 @@ impl TdClient {
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())
}
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));
@@ -1068,12 +1104,7 @@ impl TdClient {
/// Отправка номера телефона
pub async fn send_phone_number(&mut self, phone: String) -> Result<(), String> {
let result = functions::set_authentication_phone_number(
phone,
None,
self.client_id,
)
.await;
let result = functions::set_authentication_phone_number(phone, None, self.client_id).await;
match result {
Ok(_) => Ok(()),
@@ -1103,12 +1134,7 @@ impl TdClient {
/// Загрузка списка чатов
pub async fn load_chats(&mut self, limit: i32) -> Result<(), String> {
let result = functions::load_chats(
Some(ChatList::Main),
limit,
self.client_id,
)
.await;
let result = functions::load_chats(Some(ChatList::Main), limit, self.client_id).await;
match result {
Ok(_) => Ok(()),
@@ -1118,16 +1144,10 @@ impl TdClient {
/// Загрузка чатов для конкретной папки
pub async fn load_folder_chats(&mut self, folder_id: i32, limit: i32) -> Result<(), String> {
let chat_list = ChatList::Folder(tdlib_rs::types::ChatListFolder {
chat_folder_id: folder_id,
});
let chat_list =
ChatList::Folder(tdlib_rs::types::ChatListFolder { chat_folder_id: folder_id });
let result = functions::load_chats(
Some(chat_list),
limit,
self.client_id,
)
.await;
let result = functions::load_chats(Some(chat_list), limit, self.client_id).await;
match result {
Ok(_) => Ok(()),
@@ -1155,9 +1175,9 @@ impl TdClient {
let result = functions::get_chat_history(
chat_id,
from_message_id,
0, // offset
0, // offset
limit,
false, // only_local - загружаем с сервера!
false, // only_local - загружаем с сервера!
self.client_id,
)
.await;
@@ -1209,8 +1229,8 @@ impl TdClient {
let _ = functions::view_messages(
chat_id,
message_ids,
None, // source
true, // force_read
None, // source
true, // force_read
self.client_id,
)
.await;
@@ -1223,14 +1243,14 @@ impl TdClient {
pub async fn get_pinned_messages(&mut self, chat_id: i64) -> Result<Vec<MessageInfo>, String> {
let result = functions::search_chat_messages(
chat_id,
"".to_string(), // query
None, // sender_id
0, // from_message_id
0, // offset
100, // limit
Some(SearchMessagesFilter::Pinned), // filter
0, // message_thread_id
0, // saved_messages_topic_id
"".to_string(), // query
None, // sender_id
0, // from_message_id
0, // offset
100, // limit
Some(SearchMessagesFilter::Pinned), // filter
0, // message_thread_id
0, // saved_messages_topic_id
self.client_id,
)
.await;
@@ -1279,7 +1299,11 @@ impl TdClient {
}
/// Поиск сообщений в чате по тексту
pub async fn search_messages(&mut self, chat_id: i64, query: &str) -> Result<Vec<MessageInfo>, String> {
pub async fn search_messages(
&mut self,
chat_id: i64,
query: &str,
) -> Result<Vec<MessageInfo>, String> {
if query.trim().is_empty() {
return Ok(Vec::new());
}
@@ -1287,13 +1311,13 @@ impl TdClient {
let result = functions::search_chat_messages(
chat_id,
query.to_string(),
None, // sender_id
0, // from_message_id
0, // offset
50, // limit
None, // filter (no filter = search by text)
0, // message_thread_id
0, // saved_messages_topic_id
None, // sender_id
0, // from_message_id
0, // offset
50, // limit
None, // filter (no filter = search by text)
0, // message_thread_id
0, // saved_messages_topic_id
self.client_id,
)
.await;
@@ -1359,8 +1383,12 @@ impl TdClient {
profile.online_status = Some(match user.status {
tdlib_rs::enums::UserStatus::Online(_) => "Онлайн".to_string(),
tdlib_rs::enums::UserStatus::Recently(_) => "Был(а) недавно".to_string(),
tdlib_rs::enums::UserStatus::LastWeek(_) => "Был(а) на этой неделе".to_string(),
tdlib_rs::enums::UserStatus::LastMonth(_) => "Был(а) в этом месяце".to_string(),
tdlib_rs::enums::UserStatus::LastWeek(_) => {
"Был(а) на этой неделе".to_string()
}
tdlib_rs::enums::UserStatus::LastMonth(_) => {
"Был(а) в этом месяце".to_string()
}
tdlib_rs::enums::UserStatus::Offline(offline) => {
crate::utils::format_was_online(offline.was_online)
}
@@ -1369,8 +1397,10 @@ impl TdClient {
}
// Bio (getUserFullInfo)
let full_info_result = functions::get_user_full_info(private_chat.user_id, self.client_id).await;
if let Ok(tdlib_rs::enums::UserFullInfo::UserFullInfo(full_info)) = full_info_result {
let full_info_result =
functions::get_user_full_info(private_chat.user_id, self.client_id).await;
if let Ok(tdlib_rs::enums::UserFullInfo::UserFullInfo(full_info)) = full_info_result
{
if let Some(bio_obj) = full_info.bio {
profile.bio = Some(bio_obj.text);
}
@@ -1381,14 +1411,21 @@ impl TdClient {
profile.is_group = true;
// Получаем информацию о группе
let group_result = functions::get_basic_group(basic_group.basic_group_id, self.client_id).await;
let group_result =
functions::get_basic_group(basic_group.basic_group_id, self.client_id).await;
if let Ok(tdlib_rs::enums::BasicGroup::BasicGroup(group)) = group_result {
profile.member_count = Some(group.member_count);
}
// Полная информация о группе
let full_info_result = functions::get_basic_group_full_info(basic_group.basic_group_id, self.client_id).await;
if let Ok(tdlib_rs::enums::BasicGroupFullInfo::BasicGroupFullInfo(full_info)) = full_info_result {
let full_info_result = functions::get_basic_group_full_info(
basic_group.basic_group_id,
self.client_id,
)
.await;
if let Ok(tdlib_rs::enums::BasicGroupFullInfo::BasicGroupFullInfo(full_info)) =
full_info_result
{
if !full_info.description.is_empty() {
profile.description = Some(full_info.description);
}
@@ -1399,9 +1436,14 @@ impl TdClient {
}
ChatType::Supergroup(supergroup) => {
// Получаем информацию о супергруппе
let sg_result = functions::get_supergroup(supergroup.supergroup_id, self.client_id).await;
let sg_result =
functions::get_supergroup(supergroup.supergroup_id, self.client_id).await;
if let Ok(tdlib_rs::enums::Supergroup::Supergroup(sg)) = sg_result {
profile.chat_type = if sg.is_channel { "Канал".to_string() } else { "Супергруппа".to_string() };
profile.chat_type = if sg.is_channel {
"Канал".to_string()
} else {
"Супергруппа".to_string()
};
profile.is_group = !sg.is_channel;
profile.member_count = Some(sg.member_count);
@@ -1414,8 +1456,12 @@ impl TdClient {
}
// Полная информация о супергруппе
let full_info_result = functions::get_supergroup_full_info(supergroup.supergroup_id, self.client_id).await;
if let Ok(tdlib_rs::enums::SupergroupFullInfo::SupergroupFullInfo(full_info)) = full_info_result {
let full_info_result =
functions::get_supergroup_full_info(supergroup.supergroup_id, self.client_id)
.await;
if let Ok(tdlib_rs::enums::SupergroupFullInfo::SupergroupFullInfo(full_info)) =
full_info_result
{
if !full_info.description.is_empty() {
profile.description = Some(full_info.description);
}
@@ -1451,9 +1497,9 @@ impl TdClient {
let result = functions::get_chat_history(
chat_id,
from_message_id,
0, // offset
0, // offset
limit,
false, // only_local
false, // only_local
self.client_id,
)
.await;
@@ -1497,11 +1543,9 @@ impl TdClient {
/// Получение моего user_id
pub async fn get_me(&self) -> Result<i64, String> {
match functions::get_me(self.client_id).await {
Ok(user) => {
match user {
User::User(u) => Ok(u.id),
}
}
Ok(user) => match user {
User::User(u) => Ok(u.id),
},
Err(e) => Err(format!("Ошибка получения профиля: {:?}", e)),
}
}
@@ -1513,30 +1557,37 @@ impl TdClient {
0, // message_thread_id
Some(action),
self.client_id,
).await;
)
.await;
}
/// Отправка текстового сообщения с поддержкой 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};
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::enums::{InputMessageContent, InputMessageReplyTo, TextParseMode};
use tdlib_rs::types::{
FormattedText, InputMessageReplyToMessage, InputMessageText, TextParseModeMarkdown,
};
// Парсим 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,
},
)
.await
{
Ok(tdlib_rs::enums::FormattedText::FormattedText(ft)) => {
FormattedText { text: ft.text, entities: ft.entities }
}
Err(_) => {
// Если парсинг не удался, отправляем как plain text
FormattedText {
text: text.clone(),
entities: vec![],
}
FormattedText { text: text.clone(), entities: vec![] }
}
};
@@ -1558,9 +1609,9 @@ impl TdClient {
let result = functions::send_message(
chat_id,
0, // message_thread_id
0, // message_thread_id
reply_to,
None, // options
None, // options
content,
self.client_id,
)
@@ -1592,7 +1643,6 @@ impl TdClient {
}
}
/// Получить доступные реакции для сообщения
pub async fn get_message_available_reactions(
&mut self,
@@ -1653,9 +1703,9 @@ impl TdClient {
message_id: i64,
emoji: String,
) -> Result<(), String> {
use tdlib_rs::enums::ReactionType;
use tdlib_rs::functions;
use tdlib_rs::types::ReactionTypeEmoji;
use tdlib_rs::enums::ReactionType;
let reaction_type = ReactionType::Emoji(ReactionTypeEmoji { emoji });
@@ -1678,17 +1728,18 @@ impl TdClient {
/// Редактирование текстового сообщения с поддержкой Markdown
/// Устанавливает черновик для чата через TDLib API
pub async fn set_draft_message(&self, chat_id: i64, text: String) -> Result<(), String> {
use tdlib_rs::types::{FormattedText, InputMessageText, DraftMessage};
use tdlib_rs::enums::InputMessageContent;
use tdlib_rs::types::{DraftMessage, FormattedText, InputMessageText};
if text.is_empty() {
// Очищаем черновик
let result = functions::set_chat_draft_message(
chat_id,
0, // message_thread_id
0, // message_thread_id
None, // draft_message (None = очистить)
self.client_id,
).await;
)
.await;
match result {
Ok(_) => Ok(()),
@@ -1696,10 +1747,7 @@ impl TdClient {
}
} else {
// Создаём черновик
let formatted_text = FormattedText {
text: text.clone(),
entities: vec![],
};
let formatted_text = FormattedText { text: text.clone(), entities: vec![] };
let input_message = InputMessageContent::InputMessageText(InputMessageText {
text: formatted_text,
@@ -1718,7 +1766,8 @@ impl TdClient {
0, // message_thread_id
Some(draft),
self.client_id,
).await;
)
.await;
match result {
Ok(_) => Ok(()),
@@ -1727,26 +1776,29 @@ impl TdClient {
}
}
pub async fn edit_message(&self, chat_id: i64, message_id: i64, text: String) -> Result<MessageInfo, String> {
use tdlib_rs::types::{FormattedText, InputMessageText, TextParseModeMarkdown};
pub async fn edit_message(
&self,
chat_id: i64,
message_id: i64,
text: String,
) -> Result<MessageInfo, String> {
use tdlib_rs::enums::{InputMessageContent, TextParseMode};
use tdlib_rs::types::{FormattedText, InputMessageText, TextParseModeMarkdown};
// Парсим 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,
},
)
.await
{
Ok(tdlib_rs::enums::FormattedText::FormattedText(ft)) => {
FormattedText { text: ft.text, entities: ft.entities }
}
Err(_) => {
// Если парсинг не удался, отправляем как plain text
FormattedText {
text: text.clone(),
entities: vec![],
}
FormattedText { text: text.clone(), entities: vec![] }
}
};
@@ -1756,13 +1808,8 @@ impl TdClient {
clear_draft: true,
});
let result = functions::edit_message_text(
chat_id,
message_id,
content,
self.client_id,
)
.await;
let result =
functions::edit_message_text(chat_id, message_id, content, self.client_id).await;
match result {
Ok(tdlib_rs::enums::Message::Message(msg)) => {
@@ -1790,14 +1837,13 @@ impl TdClient {
/// Удаление сообщений
/// revoke = true удаляет для всех, false только для себя
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;
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(()),
@@ -1806,13 +1852,18 @@ impl TdClient {
}
/// Пересылка сообщений
pub async fn forward_messages(&self, to_chat_id: i64, from_chat_id: i64, message_ids: Vec<i64>) -> Result<(), String> {
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
None, // options
false, // send_copy
false, // remove_caption
self.client_id,
@@ -1832,8 +1883,8 @@ impl TdClient {
let _ = functions::view_messages(
chat_id,
message_ids,
None, // source
true, // force_read
None, // source
true, // force_read
self.client_id,
)
.await;
@@ -1847,7 +1898,8 @@ impl TdClient {
const BATCH_SIZE: usize = 5;
// Убираем дубликаты и уже загруженные
self.pending_user_ids.retain(|id| !self.user_names.contains_key(id));
self.pending_user_ids
.retain(|id| !self.user_names.contains_key(id));
self.pending_user_ids.dedup();
// Берём последние BATCH_SIZE элементов
@@ -1885,16 +1937,17 @@ impl TdClient {
/// Статическая функция для извлечения текста и entities сообщения (без &self)
fn extract_message_text_static(message: &TdMessage) -> (String, Vec<TextEntity>) {
match &message.content {
MessageContent::MessageText(text) => {
(text.text.text.clone(), text.text.entities.clone())
}
MessageContent::MessageText(text) => (text.text.text.clone(), text.text.entities.clone()),
MessageContent::MessagePhoto(photo) => {
if photo.caption.text.is_empty() {
("[Фото]".to_string(), vec![])
} else {
// Добавляем смещение для "[Фото] " к entities
let prefix_len = "[Фото] ".chars().count() as i32;
let adjusted_entities: Vec<TextEntity> = photo.caption.entities.iter()
let adjusted_entities: Vec<TextEntity> = photo
.caption
.entities
.iter()
.map(|e| TextEntity {
offset: e.offset + prefix_len,
length: e.length,
@@ -1909,7 +1962,10 @@ fn extract_message_text_static(message: &TdMessage) -> (String, Vec<TextEntity>)
("[Видео]".to_string(), vec![])
} else {
let prefix_len = "[Видео] ".chars().count() as i32;
let adjusted_entities: Vec<TextEntity> = video.caption.entities.iter()
let adjusted_entities: Vec<TextEntity> = video
.caption
.entities
.iter()
.map(|e| TextEntity {
offset: e.offset + prefix_len,
length: e.length,
@@ -1932,7 +1988,10 @@ fn extract_message_text_static(message: &TdMessage) -> (String, Vec<TextEntity>)
("[GIF]".to_string(), vec![])
} else {
let prefix_len = "[GIF] ".chars().count() as i32;
let adjusted_entities: Vec<TextEntity> = anim.caption.entities.iter()
let adjusted_entities: Vec<TextEntity> = anim
.caption
.entities
.iter()
.map(|e| TextEntity {
offset: e.offset + prefix_len,
length: e.length,
@@ -1942,9 +2001,7 @@ fn extract_message_text_static(message: &TdMessage) -> (String, Vec<TextEntity>)
(format!("[GIF] {}", anim.caption.text), adjusted_entities)
}
}
MessageContent::MessageAudio(audio) => {
(format!("[Аудио: {}]", audio.audio.title), vec![])
}
MessageContent::MessageAudio(audio) => (format!("[Аудио: {}]", audio.audio.title), vec![]),
MessageContent::MessageCall(_) => ("[Звонок]".to_string(), vec![]),
MessageContent::MessagePoll(poll) => {
(format!("[Опрос: {}]", poll.poll.question.text), vec![])

View File

@@ -1,13 +1,13 @@
pub mod client;
pub use client::TdClient;
pub use client::UserOnlineStatus;
pub use client::ChatInfo;
pub use client::FolderInfo;
pub use client::ForwardInfo;
pub use client::MessageInfo;
pub use client::NetworkState;
pub use client::ProfileInfo;
pub use client::ChatInfo;
pub use client::MessageInfo;
pub use client::ReactionInfo;
pub use client::ReplyInfo;
pub use client::ForwardInfo;
pub use client::FolderInfo;
pub use client::TdClient;
pub use client::UserOnlineStatus;
pub use tdlib_rs::enums::ChatAction;