809 lines
32 KiB
Rust
809 lines
32 KiB
Rust
use std::env;
|
||
use std::collections::HashMap;
|
||
use tdlib_rs::enums::{AuthorizationState, ChatList, ChatType, MessageContent, Update, User, UserStatus};
|
||
use tdlib_rs::functions;
|
||
use tdlib_rs::types::{Chat as TdChat, Message as TdMessage};
|
||
|
||
#[derive(Debug, Clone, PartialEq)]
|
||
#[allow(dead_code)]
|
||
pub enum AuthState {
|
||
WaitTdlibParameters,
|
||
WaitPhoneNumber,
|
||
WaitCode,
|
||
WaitPassword,
|
||
Ready,
|
||
Closed,
|
||
Error(String),
|
||
}
|
||
|
||
#[derive(Debug, Clone)]
|
||
#[allow(dead_code)]
|
||
pub struct ChatInfo {
|
||
pub id: i64,
|
||
pub title: String,
|
||
pub username: Option<String>,
|
||
pub last_message: String,
|
||
pub last_message_date: i32,
|
||
pub unread_count: i32,
|
||
pub is_pinned: bool,
|
||
pub order: i64,
|
||
/// ID последнего прочитанного исходящего сообщения (для галочек)
|
||
pub last_read_outbox_message_id: i64,
|
||
/// ID папок, в которых находится чат
|
||
pub folder_ids: Vec<i32>,
|
||
}
|
||
|
||
#[derive(Debug, Clone)]
|
||
pub struct MessageInfo {
|
||
pub id: i64,
|
||
pub sender_name: String,
|
||
pub is_outgoing: bool,
|
||
pub content: String,
|
||
pub date: i32,
|
||
pub is_read: bool,
|
||
}
|
||
|
||
#[derive(Debug, Clone)]
|
||
pub struct FolderInfo {
|
||
pub id: i32,
|
||
pub name: String,
|
||
}
|
||
|
||
/// Онлайн-статус пользователя
|
||
#[derive(Debug, Clone, PartialEq)]
|
||
pub enum UserOnlineStatus {
|
||
/// Онлайн
|
||
Online,
|
||
/// Был недавно (менее часа назад)
|
||
Recently,
|
||
/// Был на этой неделе
|
||
LastWeek,
|
||
/// Был в этом месяце
|
||
LastMonth,
|
||
/// Давно не был
|
||
LongTimeAgo,
|
||
/// Оффлайн с указанием времени (unix timestamp)
|
||
Offline(i32),
|
||
}
|
||
|
||
pub struct TdClient {
|
||
pub auth_state: AuthState,
|
||
pub api_id: i32,
|
||
pub api_hash: String,
|
||
client_id: i32,
|
||
pub chats: Vec<ChatInfo>,
|
||
pub current_chat_messages: Vec<MessageInfo>,
|
||
/// ID текущего открытого чата (для получения новых сообщений)
|
||
pub current_chat_id: Option<i64>,
|
||
/// Кэш usernames: user_id -> username
|
||
user_usernames: HashMap<i64, String>,
|
||
/// Кэш имён: user_id -> display_name (first_name + last_name)
|
||
user_names: HashMap<i64, String>,
|
||
/// Связь chat_id -> user_id для приватных чатов
|
||
chat_user_ids: HashMap<i64, i64>,
|
||
/// Очередь сообщений для отметки как прочитанных: (chat_id, message_ids)
|
||
pub pending_view_messages: Vec<(i64, Vec<i64>)>,
|
||
/// Очередь user_id для загрузки имён
|
||
pub pending_user_ids: Vec<i64>,
|
||
/// Папки чатов
|
||
pub folders: Vec<FolderInfo>,
|
||
/// Позиция основного списка среди папок
|
||
pub main_chat_list_position: i32,
|
||
/// Онлайн-статусы пользователей: user_id -> status
|
||
user_statuses: HashMap<i64, UserOnlineStatus>,
|
||
}
|
||
|
||
#[allow(dead_code)]
|
||
impl TdClient {
|
||
pub fn new() -> Self {
|
||
let api_id: i32 = env::var("API_ID")
|
||
.unwrap_or_else(|_| "0".to_string())
|
||
.parse()
|
||
.unwrap_or(0);
|
||
let api_hash = env::var("API_HASH").unwrap_or_default();
|
||
|
||
let client_id = tdlib_rs::create_client();
|
||
|
||
TdClient {
|
||
auth_state: AuthState::WaitTdlibParameters,
|
||
api_id,
|
||
api_hash,
|
||
client_id,
|
||
chats: Vec::new(),
|
||
current_chat_messages: Vec::new(),
|
||
current_chat_id: None,
|
||
user_usernames: HashMap::new(),
|
||
user_names: HashMap::new(),
|
||
chat_user_ids: HashMap::new(),
|
||
pending_view_messages: Vec::new(),
|
||
pending_user_ids: Vec::new(),
|
||
folders: Vec::new(),
|
||
main_chat_list_position: 0,
|
||
user_statuses: HashMap::new(),
|
||
}
|
||
}
|
||
|
||
pub fn is_authenticated(&self) -> bool {
|
||
matches!(self.auth_state, AuthState::Ready)
|
||
}
|
||
|
||
pub fn client_id(&self) -> i32 {
|
||
self.client_id
|
||
}
|
||
|
||
/// Получение онлайн-статуса пользователя по chat_id (для приватных чатов)
|
||
pub fn get_user_status_by_chat_id(&self, chat_id: i64) -> Option<&UserOnlineStatus> {
|
||
self.chat_user_ids
|
||
.get(&chat_id)
|
||
.and_then(|user_id| self.user_statuses.get(user_id))
|
||
}
|
||
|
||
/// Инициализация 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
|
||
self.client_id,
|
||
)
|
||
.await;
|
||
|
||
match result {
|
||
Ok(_) => Ok(()),
|
||
Err(e) => Err(format!("Failed to set TDLib parameters: {:?}", e)),
|
||
}
|
||
}
|
||
|
||
/// Обрабатываем одно обновление от TDLib
|
||
pub fn handle_update(&mut self, update: Update) {
|
||
match update {
|
||
Update::AuthorizationState(state) => {
|
||
self.handle_auth_state(state.authorization_state);
|
||
}
|
||
Update::NewChat(new_chat) => {
|
||
self.add_or_update_chat(&new_chat.chat);
|
||
}
|
||
Update::ChatLastMessage(update) => {
|
||
let chat_id = update.chat_id;
|
||
let (last_message_text, last_message_date) = update
|
||
.last_message
|
||
.as_ref()
|
||
.map(|msg| (extract_message_text_static(msg), msg.date))
|
||
.unwrap_or_default();
|
||
|
||
if let Some(chat) = self.chats.iter_mut().find(|c| c.id == chat_id) {
|
||
chat.last_message = last_message_text;
|
||
chat.last_message_date = last_message_date;
|
||
}
|
||
|
||
// Обновляем позиции если они пришли
|
||
for pos in &update.positions {
|
||
if matches!(pos.list, ChatList::Main) {
|
||
if let Some(chat) = self.chats.iter_mut().find(|c| c.id == chat_id) {
|
||
chat.order = pos.order;
|
||
chat.is_pinned = pos.is_pinned;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Пересортируем по order
|
||
self.chats.sort_by(|a, b| b.order.cmp(&a.order));
|
||
}
|
||
Update::ChatReadInbox(update) => {
|
||
if let Some(chat) = self.chats.iter_mut().find(|c| c.id == update.chat_id) {
|
||
chat.unread_count = update.unread_count;
|
||
}
|
||
}
|
||
Update::ChatReadOutbox(update) => {
|
||
// Обновляем last_read_outbox_message_id когда собеседник прочитал сообщения
|
||
if let Some(chat) = self.chats.iter_mut().find(|c| c.id == update.chat_id) {
|
||
chat.last_read_outbox_message_id = update.last_read_outbox_message_id;
|
||
}
|
||
// Если это текущий открытый чат — обновляем is_read у сообщений
|
||
if Some(update.chat_id) == self.current_chat_id {
|
||
for msg in &mut self.current_chat_messages {
|
||
if msg.is_outgoing && msg.id <= update.last_read_outbox_message_id {
|
||
msg.is_read = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
Update::ChatPosition(update) => {
|
||
// Обновляем позицию чата или удаляем его из списка
|
||
match &update.position.list {
|
||
ChatList::Main => {
|
||
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) {
|
||
// Обновляем позицию существующего чата
|
||
chat.order = update.position.order;
|
||
chat.is_pinned = update.position.is_pinned;
|
||
}
|
||
// Пересортируем по order
|
||
self.chats.sort_by(|a, b| b.order.cmp(&a.order));
|
||
}
|
||
ChatList::Folder(folder) => {
|
||
// Обновляем folder_ids для чата
|
||
if let Some(chat) = self.chats.iter_mut().find(|c| c.id == update.chat_id) {
|
||
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::NewMessage(new_msg) => {
|
||
// Добавляем новое сообщение если это текущий открытый чат
|
||
let chat_id = new_msg.message.chat_id;
|
||
if Some(chat_id) == self.current_chat_id {
|
||
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.current_chat_messages.push(msg_info);
|
||
// Если это входящее сообщение — добавляем в очередь для отметки как прочитанное
|
||
if is_incoming {
|
||
self.pending_view_messages.push((chat_id, vec![msg_id]));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
Update::User(update) => {
|
||
// Сохраняем имя и username пользователя
|
||
let user = update.user;
|
||
|
||
// Пропускаем удалённые аккаунты (пустое имя)
|
||
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)
|
||
});
|
||
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)
|
||
};
|
||
self.user_names.insert(user.id, display_name);
|
||
|
||
// Сохраняем username если есть
|
||
if let Some(usernames) = user.usernames {
|
||
if let Some(username) = usernames.active_usernames.first() {
|
||
self.user_usernames.insert(user.id, username.clone());
|
||
// Обновляем 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) {
|
||
chat.username = Some(format!("@{}", username));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
Update::ChatFolders(update) => {
|
||
// Обновляем список папок
|
||
self.folders = update
|
||
.chat_folders
|
||
.into_iter()
|
||
.map(|f| FolderInfo {
|
||
id: f.id,
|
||
name: f.title,
|
||
})
|
||
.collect();
|
||
self.main_chat_list_position = update.main_chat_list_position;
|
||
}
|
||
Update::UserStatus(update) => {
|
||
// Обновляем онлайн-статус пользователя
|
||
let status = match update.status {
|
||
UserStatus::Online(_) => UserOnlineStatus::Online,
|
||
UserStatus::Offline(offline) => UserOnlineStatus::Offline(offline.was_online),
|
||
UserStatus::Recently(_) => UserOnlineStatus::Recently,
|
||
UserStatus::LastWeek(_) => UserOnlineStatus::LastWeek,
|
||
UserStatus::LastMonth(_) => UserOnlineStatus::LastMonth,
|
||
UserStatus::Empty => UserOnlineStatus::LongTimeAgo,
|
||
};
|
||
self.user_statuses.insert(update.user_id, status);
|
||
}
|
||
_ => {}
|
||
}
|
||
}
|
||
|
||
fn handle_auth_state(&mut self, state: AuthorizationState) {
|
||
self.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,
|
||
_ => self.auth_state.clone(),
|
||
};
|
||
}
|
||
|
||
fn add_or_update_chat(&mut self, td_chat: &TdChat) {
|
||
// Пропускаем удалённые аккаунты
|
||
if td_chat.title == "Deleted Account" || td_chat.title.is_empty() {
|
||
// Удаляем из списка если уже был добавлен
|
||
self.chats.retain(|c| c.id != td_chat.id);
|
||
return;
|
||
}
|
||
|
||
// Ищем позицию в Main списке (если есть)
|
||
let main_position = td_chat.positions.iter().find(|pos| {
|
||
matches!(pos.list, ChatList::Main)
|
||
});
|
||
|
||
// Получаем order и is_pinned из позиции, или используем значения по умолчанию
|
||
let (order, is_pinned) = main_position
|
||
.map(|p| (p.order, p.is_pinned))
|
||
.unwrap_or((1, false)); // order=1 чтобы чат отображался
|
||
|
||
let (last_message, last_message_date) = td_chat
|
||
.last_message
|
||
.as_ref()
|
||
.map(|m| (extract_message_text_static(m), m.date))
|
||
.unwrap_or_default();
|
||
|
||
// Извлекаем user_id для приватных чатов и сохраняем связь
|
||
let username = match &td_chat.r#type {
|
||
ChatType::Private(private) => {
|
||
self.chat_user_ids.insert(td_chat.id, private.user_id);
|
||
// Проверяем, есть ли уже username в кэше
|
||
self.user_usernames.get(&private.user_id).map(|u| format!("@{}", u))
|
||
}
|
||
_ => None,
|
||
};
|
||
|
||
// Извлекаем ID папок из позиций
|
||
let folder_ids: Vec<i32> = td_chat
|
||
.positions
|
||
.iter()
|
||
.filter_map(|pos| {
|
||
if let ChatList::Folder(folder) = &pos.list {
|
||
Some(folder.chat_folder_id)
|
||
} else {
|
||
None
|
||
}
|
||
})
|
||
.collect();
|
||
|
||
let chat_info = ChatInfo {
|
||
id: td_chat.id,
|
||
title: td_chat.title.clone(),
|
||
username,
|
||
last_message,
|
||
last_message_date,
|
||
unread_count: td_chat.unread_count,
|
||
is_pinned,
|
||
order,
|
||
last_read_outbox_message_id: td_chat.last_read_outbox_message_id,
|
||
folder_ids,
|
||
};
|
||
|
||
if let Some(existing) = self.chats.iter_mut().find(|c| c.id == td_chat.id) {
|
||
existing.title = chat_info.title;
|
||
existing.last_message = chat_info.last_message;
|
||
existing.last_message_date = chat_info.last_message_date;
|
||
existing.unread_count = chat_info.unread_count;
|
||
existing.last_read_outbox_message_id = chat_info.last_read_outbox_message_id;
|
||
existing.folder_ids = chat_info.folder_ids;
|
||
// Обновляем username если он появился
|
||
if chat_info.username.is_some() {
|
||
existing.username = chat_info.username;
|
||
}
|
||
// Обновляем позицию только если она пришла
|
||
if main_position.is_some() {
|
||
existing.is_pinned = chat_info.is_pinned;
|
||
existing.order = chat_info.order;
|
||
}
|
||
} else {
|
||
self.chats.push(chat_info);
|
||
}
|
||
|
||
// Сортируем чаты по order (TDLib order учитывает pinned и время)
|
||
self.chats.sort_by(|a, b| b.order.cmp(&a.order));
|
||
}
|
||
|
||
fn convert_message(&mut self, message: &TdMessage, chat_id: i64) -> MessageInfo {
|
||
let sender_name = match &message.sender_id {
|
||
tdlib_rs::enums::MessageSender::User(user) => {
|
||
// Пробуем получить имя из кеша
|
||
if let Some(name) = self.user_names.get(&user.user_id) {
|
||
name.clone()
|
||
} else {
|
||
// Добавляем в очередь для загрузки
|
||
if !self.pending_user_ids.contains(&user.user_id) {
|
||
self.pending_user_ids.push(user.user_id);
|
||
}
|
||
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(|| format!("Chat_{}", chat.chat_id))
|
||
}
|
||
};
|
||
|
||
// Определяем, прочитано ли исходящее сообщение
|
||
let is_read = if message.is_outgoing {
|
||
// Сообщение прочитано, если его ID <= last_read_outbox_message_id чата
|
||
self.chats
|
||
.iter()
|
||
.find(|c| c.id == chat_id)
|
||
.map(|c| message.id <= c.last_read_outbox_message_id)
|
||
.unwrap_or(false)
|
||
} else {
|
||
true // Входящие сообщения не показывают галочки
|
||
};
|
||
|
||
MessageInfo {
|
||
id: message.id,
|
||
sender_name,
|
||
is_outgoing: message.is_outgoing,
|
||
content: extract_message_text_static(message),
|
||
date: message.date,
|
||
is_read,
|
||
}
|
||
}
|
||
|
||
/// Отправка номера телефона
|
||
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;
|
||
|
||
match result {
|
||
Ok(_) => Ok(()),
|
||
Err(e) => Err(format!("Ошибка отправки номера: {:?}", e)),
|
||
}
|
||
}
|
||
|
||
/// Отправка кода подтверждения
|
||
pub async fn send_code(&mut self, code: String) -> Result<(), String> {
|
||
let result = functions::check_authentication_code(code, self.client_id).await;
|
||
|
||
match result {
|
||
Ok(_) => Ok(()),
|
||
Err(e) => Err(format!("Неверный код: {:?}", e)),
|
||
}
|
||
}
|
||
|
||
/// Отправка пароля 2FA
|
||
pub async fn send_password(&mut self, password: String) -> Result<(), String> {
|
||
let result = functions::check_authentication_password(password, self.client_id).await;
|
||
|
||
match result {
|
||
Ok(_) => Ok(()),
|
||
Err(e) => Err(format!("Неверный пароль: {:?}", e)),
|
||
}
|
||
}
|
||
|
||
/// Загрузка списка чатов
|
||
pub async fn load_chats(&mut self, limit: i32) -> Result<(), String> {
|
||
let result = functions::load_chats(
|
||
Some(ChatList::Main),
|
||
limit,
|
||
self.client_id,
|
||
)
|
||
.await;
|
||
|
||
match result {
|
||
Ok(_) => Ok(()),
|
||
Err(e) => Err(format!("Ошибка загрузки чатов: {:?}", e)),
|
||
}
|
||
}
|
||
|
||
/// Загрузка чатов для конкретной папки
|
||
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 result = functions::load_chats(
|
||
Some(chat_list),
|
||
limit,
|
||
self.client_id,
|
||
)
|
||
.await;
|
||
|
||
match result {
|
||
Ok(_) => Ok(()),
|
||
Err(e) => Err(format!("Ошибка загрузки чатов папки: {:?}", e)),
|
||
}
|
||
}
|
||
|
||
/// Загрузка истории сообщений чата
|
||
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 _ = functions::open_chat(chat_id, self.client_id).await;
|
||
|
||
// Пробуем загрузить несколько раз, так как сообщения могут подгружаться с сервера
|
||
let mut all_messages: Vec<MessageInfo> = Vec::new();
|
||
let mut from_message_id: i64 = 0;
|
||
let mut attempts = 0;
|
||
const MAX_ATTEMPTS: i32 = 3;
|
||
|
||
while attempts < MAX_ATTEMPTS {
|
||
let result = functions::get_chat_history(
|
||
chat_id,
|
||
from_message_id,
|
||
0, // offset
|
||
limit,
|
||
false, // only_local - загружаем с сервера!
|
||
self.client_id,
|
||
)
|
||
.await;
|
||
|
||
match result {
|
||
Ok(tdlib_rs::enums::Messages::Messages(messages)) => {
|
||
let mut batch: Vec<MessageInfo> = Vec::new();
|
||
for m in messages.messages.into_iter().flatten() {
|
||
batch.push(self.convert_message(&m, chat_id));
|
||
}
|
||
|
||
if batch.is_empty() {
|
||
break;
|
||
}
|
||
|
||
// Запоминаем ID самого старого сообщения для следующей загрузки
|
||
if let Some(oldest) = batch.last() {
|
||
from_message_id = oldest.id;
|
||
}
|
||
|
||
// Добавляем сообщения (они приходят от новых к старым)
|
||
all_messages.extend(batch);
|
||
attempts += 1;
|
||
|
||
// Если получили достаточно сообщений, выходим
|
||
if all_messages.len() >= limit as usize {
|
||
break;
|
||
}
|
||
}
|
||
Err(e) => {
|
||
if all_messages.is_empty() {
|
||
return Err(format!("Ошибка загрузки сообщений: {:?}", e));
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Сообщения приходят от новых к старым, переворачиваем
|
||
all_messages.reverse();
|
||
self.current_chat_messages = all_messages.clone();
|
||
|
||
// Отмечаем сообщения как прочитанные
|
||
if !all_messages.is_empty() {
|
||
let message_ids: Vec<i64> = all_messages.iter().map(|m| m.id).collect();
|
||
let _ = functions::view_messages(
|
||
chat_id,
|
||
message_ids,
|
||
None, // source
|
||
true, // force_read
|
||
self.client_id,
|
||
)
|
||
.await;
|
||
}
|
||
|
||
Ok(all_messages)
|
||
}
|
||
|
||
/// Загрузка старых сообщений (для скролла вверх)
|
||
pub async fn load_older_messages(
|
||
&mut self,
|
||
chat_id: i64,
|
||
from_message_id: i64,
|
||
limit: i32,
|
||
) -> Result<Vec<MessageInfo>, String> {
|
||
let result = functions::get_chat_history(
|
||
chat_id,
|
||
from_message_id,
|
||
0, // offset
|
||
limit,
|
||
false, // only_local
|
||
self.client_id,
|
||
)
|
||
.await;
|
||
|
||
match result {
|
||
Ok(tdlib_rs::enums::Messages::Messages(messages)) => {
|
||
let mut result_messages: Vec<MessageInfo> = Vec::new();
|
||
for m in messages.messages.into_iter().flatten() {
|
||
result_messages.push(self.convert_message(&m, chat_id));
|
||
}
|
||
|
||
// Сообщения приходят от новых к старым, переворачиваем
|
||
result_messages.reverse();
|
||
Ok(result_messages)
|
||
}
|
||
Err(e) => Err(format!("Ошибка загрузки сообщений: {:?}", e)),
|
||
}
|
||
}
|
||
|
||
/// Получение информации о пользователе по ID
|
||
pub async fn get_user_name(&self, user_id: i64) -> String {
|
||
match functions::get_user(user_id, self.client_id).await {
|
||
Ok(user) => {
|
||
// User is an enum, need to match it
|
||
match user {
|
||
User::User(u) => {
|
||
let first = u.first_name;
|
||
let last = u.last_name;
|
||
if last.is_empty() {
|
||
first
|
||
} else {
|
||
format!("{} {}", first, last)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
Err(_) => format!("User_{}", user_id),
|
||
}
|
||
}
|
||
|
||
/// Получение моего 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),
|
||
}
|
||
}
|
||
Err(e) => Err(format!("Ошибка получения профиля: {:?}", e)),
|
||
}
|
||
}
|
||
|
||
/// Отправка текстового сообщения
|
||
pub async fn send_message(&self, chat_id: i64, text: String) -> Result<MessageInfo, String> {
|
||
use tdlib_rs::types::{FormattedText, InputMessageText};
|
||
use tdlib_rs::enums::InputMessageContent;
|
||
|
||
let content = InputMessageContent::InputMessageText(InputMessageText {
|
||
text: FormattedText {
|
||
text: text.clone(),
|
||
entities: vec![],
|
||
},
|
||
link_preview_options: None,
|
||
clear_draft: true,
|
||
});
|
||
|
||
let result = functions::send_message(
|
||
chat_id,
|
||
0, // message_thread_id
|
||
None, // reply_to
|
||
None, // options
|
||
content,
|
||
self.client_id,
|
||
)
|
||
.await;
|
||
|
||
match result {
|
||
Ok(tdlib_rs::enums::Message::Message(msg)) => {
|
||
// Конвертируем отправленное сообщение в MessageInfo
|
||
Ok(MessageInfo {
|
||
id: msg.id,
|
||
sender_name: "You".to_string(),
|
||
is_outgoing: true,
|
||
content: text,
|
||
date: msg.date,
|
||
is_read: false,
|
||
})
|
||
}
|
||
Err(e) => Err(format!("Ошибка отправки сообщения: {:?}", e)),
|
||
}
|
||
}
|
||
|
||
/// Обработка очереди сообщений для отметки как прочитанных
|
||
pub async fn process_pending_view_messages(&mut self) {
|
||
let pending = std::mem::take(&mut self.pending_view_messages);
|
||
for (chat_id, message_ids) in pending {
|
||
let _ = functions::view_messages(
|
||
chat_id,
|
||
message_ids,
|
||
None, // source
|
||
true, // force_read
|
||
self.client_id,
|
||
)
|
||
.await;
|
||
}
|
||
}
|
||
|
||
/// Обработка очереди user_id для загрузки имён
|
||
pub async fn process_pending_user_ids(&mut self) {
|
||
let pending = std::mem::take(&mut self.pending_user_ids);
|
||
for user_id in pending {
|
||
// Пропускаем если имя уже есть
|
||
if self.user_names.contains_key(&user_id) {
|
||
continue;
|
||
}
|
||
// Загружаем информацию о пользователе
|
||
if let Ok(User::User(user)) = functions::get_user(user_id, self.client_id).await {
|
||
let display_name = if user.last_name.is_empty() {
|
||
user.first_name.clone()
|
||
} else {
|
||
format!("{} {}", user.first_name, user.last_name)
|
||
};
|
||
self.user_names.insert(user_id, display_name.clone());
|
||
|
||
// Обновляем имя в текущих сообщениях
|
||
for msg in &mut self.current_chat_messages {
|
||
if msg.sender_name == format!("User_{}", user_id) {
|
||
msg.sender_name = display_name.clone();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Статическая функция для извлечения текста сообщения (без &self)
|
||
fn extract_message_text_static(message: &TdMessage) -> String {
|
||
match &message.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(_) => "[Видео]".to_string(),
|
||
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(),
|
||
}
|
||
}
|