212 lines
8.3 KiB
Rust
212 lines
8.3 KiB
Rust
use crate::constants::TDLIB_CHAT_LIMIT;
|
||
use std::time::Instant;
|
||
use tdlib_rs::enums::{ChatAction, ChatList, ChatType};
|
||
use tdlib_rs::functions;
|
||
|
||
use super::types::{ChatInfo, FolderInfo, MessageInfo, ProfileInfo};
|
||
|
||
/// Менеджер чатов
|
||
pub struct ChatManager {
|
||
pub chats: Vec<ChatInfo>,
|
||
pub folders: Vec<FolderInfo>,
|
||
pub main_chat_list_position: i32,
|
||
/// Typing status для текущего чата: (user_id, action_text, timestamp)
|
||
pub typing_status: Option<(i64, String, Instant)>,
|
||
client_id: i32,
|
||
}
|
||
|
||
impl ChatManager {
|
||
pub fn new(client_id: i32) -> Self {
|
||
Self {
|
||
chats: Vec::new(),
|
||
folders: Vec::new(),
|
||
main_chat_list_position: 0,
|
||
typing_status: None,
|
||
client_id,
|
||
}
|
||
}
|
||
|
||
/// Загрузить чаты из основного списка
|
||
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 leave_chat(&self, chat_id: i64) -> Result<(), String> {
|
||
let result = functions::leave_chat(chat_id, self.client_id).await;
|
||
match result {
|
||
Ok(_) => Ok(()),
|
||
Err(e) => Err(format!("Ошибка выхода из чата: {:?}", e)),
|
||
}
|
||
}
|
||
|
||
/// Получить информацию профиля чата
|
||
pub async fn get_profile_info(&self, chat_id: i64) -> Result<ProfileInfo, String> {
|
||
// Получаем основную информацию о чате
|
||
let chat_result = functions::get_chat(chat_id, self.client_id).await;
|
||
let chat_enum = match chat_result {
|
||
Ok(c) => c,
|
||
Err(e) => return Err(format!("Ошибка получения чата: {:?}", e)),
|
||
};
|
||
|
||
let chat = match chat_enum {
|
||
tdlib_rs::enums::Chat::Chat(c) => c,
|
||
_ => return Err("Неожиданный тип чата".to_string()),
|
||
};
|
||
|
||
let chat_type_str = match &chat.r#type {
|
||
ChatType::Private(_) => "Личный чат",
|
||
ChatType::Supergroup(sg) => {
|
||
if sg.is_channel {
|
||
"Канал"
|
||
} else {
|
||
"Группа"
|
||
}
|
||
}
|
||
ChatType::BasicGroup(_) => "Группа",
|
||
ChatType::Secret(_) => "Секретный чат",
|
||
};
|
||
|
||
let is_group = matches!(
|
||
&chat.r#type,
|
||
ChatType::Supergroup(_) | ChatType::BasicGroup(_)
|
||
);
|
||
|
||
// Для личных чатов получаем информацию о пользователе
|
||
let (bio, phone_number, username, online_status) = if let ChatType::Private(private_chat) =
|
||
&chat.r#type
|
||
{
|
||
match functions::get_user(private_chat.user_id, self.client_id).await {
|
||
Ok(tdlib_rs::enums::User::User(user)) => {
|
||
let bio_opt = if let Ok(tdlib_rs::enums::UserFullInfo::UserFullInfo(full_info)) =
|
||
functions::get_user_full_info(private_chat.user_id, self.client_id).await
|
||
{
|
||
full_info.bio.map(|b| b.text)
|
||
} else {
|
||
None
|
||
};
|
||
|
||
let online_status_str = match user.status {
|
||
tdlib_rs::enums::UserStatus::Online(_) => Some("В сети".to_string()),
|
||
tdlib_rs::enums::UserStatus::Recently(_) => {
|
||
Some("Был(а) недавно".to_string())
|
||
}
|
||
tdlib_rs::enums::UserStatus::LastWeek(_) => {
|
||
Some("Был(а) на этой неделе".to_string())
|
||
}
|
||
tdlib_rs::enums::UserStatus::LastMonth(_) => {
|
||
Some("Был(а) в этом месяце".to_string())
|
||
}
|
||
tdlib_rs::enums::UserStatus::Offline(s) => {
|
||
// Форматируем время последнего визита
|
||
Some(format!("Был(а) в сети {}", s.was_online))
|
||
}
|
||
_ => None,
|
||
};
|
||
|
||
let username_opt = user
|
||
.usernames
|
||
.as_ref()
|
||
.map(|u| u.editable_username.clone());
|
||
|
||
(bio_opt, Some(user.phone_number.clone()), username_opt, online_status_str)
|
||
}
|
||
_ => (None, None, None, None),
|
||
}
|
||
} else {
|
||
(None, None, None, None)
|
||
};
|
||
|
||
// Для групп/каналов получаем полную информацию
|
||
let (member_count, description, invite_link) = if is_group {
|
||
if let ChatType::Supergroup(sg) = &chat.r#type {
|
||
match functions::get_supergroup_full_info(sg.supergroup_id, self.client_id).await {
|
||
Ok(tdlib_rs::enums::SupergroupFullInfo::SupergroupFullInfo(full_info)) => {
|
||
let desc = if !full_info.description.is_empty() {
|
||
Some(full_info.description.clone())
|
||
} else {
|
||
None
|
||
};
|
||
let link = full_info.invite_link.as_ref().map(|l| l.invite_link.clone());
|
||
(Some(full_info.member_count), desc, link)
|
||
}
|
||
_ => (None, None, None),
|
||
}
|
||
} else if let ChatType::BasicGroup(bg) = &chat.r#type {
|
||
match functions::get_basic_group_full_info(bg.basic_group_id, self.client_id).await
|
||
{
|
||
Ok(tdlib_rs::enums::BasicGroupFullInfo::BasicGroupFullInfo(full_info)) => {
|
||
let desc = if !full_info.description.is_empty() {
|
||
Some(full_info.description.clone())
|
||
} else {
|
||
None
|
||
};
|
||
let link = full_info.invite_link.map(|l| l.invite_link);
|
||
(Some(full_info.members.len() as i32), desc, link)
|
||
}
|
||
Err(_) => (None, None, None),
|
||
}
|
||
} else {
|
||
(None, None, None)
|
||
}
|
||
} else {
|
||
(None, None, None)
|
||
};
|
||
|
||
Ok(ProfileInfo {
|
||
chat_id,
|
||
title: chat.title,
|
||
username,
|
||
bio,
|
||
phone_number,
|
||
chat_type: chat_type_str.to_string(),
|
||
member_count,
|
||
description,
|
||
invite_link,
|
||
is_group,
|
||
online_status,
|
||
})
|
||
}
|
||
|
||
/// Отправить typing action
|
||
pub async fn send_chat_action(&self, chat_id: i64, action: ChatAction) {
|
||
let _ = functions::send_chat_action(chat_id, 0, Some(action), self.client_id).await;
|
||
}
|
||
|
||
/// Очистить устаревший typing status (вызывать периодически)
|
||
pub fn clear_stale_typing_status(&mut self) -> bool {
|
||
if let Some((_, _, timestamp)) = self.typing_status {
|
||
if timestamp.elapsed().as_secs() > 5 {
|
||
self.typing_status = None;
|
||
return true;
|
||
}
|
||
}
|
||
false
|
||
}
|
||
|
||
/// Получить текст typing индикатора
|
||
pub fn get_typing_text(&self) -> Option<String> {
|
||
self.typing_status
|
||
.as_ref()
|
||
.map(|(_, action, _)| action.clone())
|
||
}
|
||
}
|