Files
telegram-tui/src/tdlib/chats.rs
Mikhail Kilin 7081a886ad refactor: implement newtype pattern for IDs (P2.4)
Добавлены типобезопасные обёртки ChatId, MessageId, UserId для предотвращения
смешивания разных типов идентификаторов на этапе компиляции.

Изменения:
- Создан src/types.rs с тремя newtype структурами
- Реализованы методы: new(), as_i64(), From<i64>, Display
- Добавлены traits: Hash, Eq, Serialize, Deserialize
- Обновлены 15+ модулей для использования новых типов:
  * tdlib: types.rs, chats.rs, messages.rs, users.rs, reactions.rs, client.rs
  * app: mod.rs, chat_state.rs
  * input: main_input.rs
  * tests: app_builder.rs, test_data.rs
- Исправлены 53 ошибки компиляции связанные с type conversions

Преимущества:
- Компилятор предотвращает смешивание разных типов ID
- Улучшенная читаемость кода (явные типы вместо i64)
- Самодокументирующиеся типы

Статус: Priority 2 теперь 60% (3/5 задач)
-  Error enum
-  Config validation
-  Newtype для ID
-  MessageInfo реструктуризация
-  MessageBuilder pattern

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-31 01:33:18 +03:00

213 lines
8.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use crate::constants::TDLIB_CHAT_LIMIT;
use crate::types::{ChatId, UserId};
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<(UserId, 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: ChatId) -> Result<(), String> {
let result = functions::leave_chat(chat_id.as_i64(), self.client_id).await;
match result {
Ok(_) => Ok(()),
Err(e) => Err(format!("Ошибка выхода из чата: {:?}", e)),
}
}
/// Получить информацию профиля чата
pub async fn get_profile_info(&self, chat_id: ChatId) -> Result<ProfileInfo, String> {
// Получаем основную информацию о чате
let chat_result = functions::get_chat(chat_id.as_i64(), 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: ChatId, action: ChatAction) {
let _ = functions::send_chat_action(chat_id.as_i64(), 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())
}
}