use crate::types::{ChatId, MessageId, UserId}; use std::env; use tdlib_rs::enums::{ ChatList, ConnectionState, Update, UserStatus, Chat as TdChat }; use tdlib_rs::types::Message as TdMessage; use tdlib_rs::functions; use super::auth::{AuthManager, AuthState}; use super::chats::ChatManager; use super::messages::MessageManager; use super::reactions::ReactionManager; use super::types::{ChatInfo, FolderInfo, MessageInfo, NetworkState, ProfileInfo, UserOnlineStatus}; use super::users::UserCache; /// TDLib client wrapper for Telegram integration. /// /// Provides high-level API for authentication, chat management, messaging, /// and user caching. Delegates functionality to specialized managers: /// - `AuthManager` for authentication flow /// - `ChatManager` for chat operations /// - `MessageManager` for message operations /// - `UserCache` for user information caching /// - `ReactionManager` for message reactions /// /// # Examples /// /// ```ignore /// use tele_tui::tdlib::TdClient; /// /// let mut client = TdClient::new(); /// /// // Start authorization /// client.send_phone_number("+1234567890".to_string()).await?; /// client.send_code("12345".to_string()).await?; /// /// // Load chats /// client.load_chats(50).await?; /// # Ok::<(), String>(()) /// ``` pub struct TdClient { pub api_id: i32, pub api_hash: String, client_id: i32, // Менеджеры (делегируем им функциональность) pub auth: AuthManager, pub chat_manager: ChatManager, pub message_manager: MessageManager, pub user_cache: UserCache, pub reaction_manager: ReactionManager, // Состояние сети pub network_state: NetworkState, } impl TdClient { /// Creates a new TDLib client instance. /// /// Reads API credentials from: /// 1. ~/.config/tele-tui/credentials file /// 2. Environment variables `API_ID` and `API_HASH` (fallback) /// /// Initializes all managers and sets initial network state to Connecting. /// /// # Returns /// /// A new `TdClient` instance ready for authentication. pub fn new() -> Self { // Пробуем загрузить credentials из Config (файл или env) let (api_id, api_hash) = crate::config::Config::load_credentials() .unwrap_or_else(|_| { // Fallback на прямое чтение из env (старое поведение) let api_id = env::var("API_ID") .unwrap_or_else(|_| "0".to_string()) .parse() .unwrap_or(0); let api_hash = env::var("API_HASH").unwrap_or_default(); (api_id, api_hash) }); let client_id = tdlib_rs::create_client(); Self { api_id, api_hash, client_id, auth: AuthManager::new(client_id), chat_manager: ChatManager::new(client_id), message_manager: MessageManager::new(client_id), user_cache: UserCache::new(client_id), reaction_manager: ReactionManager::new(client_id), network_state: NetworkState::Connecting, } } // Делегирование к auth /// Sends phone number for authentication. /// /// This is the first step of the authentication flow. /// /// # Arguments /// /// * `phone` - Phone number in international format (e.g., "+1234567890") /// /// # Errors /// /// Returns an error if the phone number is invalid or network request fails. pub async fn send_phone_number(&self, phone: String) -> Result<(), String> { self.auth.send_phone_number(phone).await } /// Sends authentication code received via SMS. /// /// This is the second step of the authentication flow. /// /// # Arguments /// /// * `code` - Authentication code (typically 5 digits) /// /// # Errors /// /// Returns an error if the code is invalid or expired. pub async fn send_code(&self, code: String) -> Result<(), String> { self.auth.send_code(code).await } /// Sends 2FA password if required. /// /// This is the third step of the authentication flow (if 2FA is enabled). /// /// # Arguments /// /// * `password` - Two-factor authentication password /// /// # Errors /// /// Returns an error if the password is incorrect. pub async fn send_password(&self, password: String) -> Result<(), String> { self.auth.send_password(password).await } // Делегирование к chat_manager /// Loads chats from the main chat list. /// /// Loads up to `limit` chats from ChatList::Main, excluding archived chats. /// Filters out "Deleted Account" chats automatically. /// /// # Arguments /// /// * `limit` - Maximum number of chats to load (typically 50-200) /// /// # Errors /// /// Returns an error if the network request fails. pub async fn load_chats(&mut self, limit: i32) -> Result<(), String> { self.chat_manager.load_chats(limit).await } /// Loads chats from a specific folder. /// /// # Arguments /// /// * `folder_id` - Folder ID (1-9 for user folders) /// * `limit` - Maximum number of chats to load /// /// # Errors /// /// Returns an error if the folder doesn't exist or network request fails. pub async fn load_folder_chats(&mut self, folder_id: i32, limit: i32) -> Result<(), String> { self.chat_manager.load_folder_chats(folder_id, limit).await } /// Leaves a group or channel. /// /// # Arguments /// /// * `chat_id` - ID of the chat to leave /// /// # Errors /// /// Returns an error if the user is not a member or network request fails. pub async fn leave_chat(&self, chat_id: ChatId) -> Result<(), String> { self.chat_manager.leave_chat(chat_id).await } /// Gets profile information for a chat. /// /// Fetches detailed information including bio, username, member count, etc. /// /// # Arguments /// /// * `chat_id` - ID of the chat /// /// # Returns /// /// `ProfileInfo` with chat details /// /// # Errors /// /// Returns an error if the chat doesn't exist or network request fails. pub async fn get_profile_info(&self, chat_id: ChatId) -> Result { self.chat_manager.get_profile_info(chat_id).await } pub async fn send_chat_action(&self, chat_id: ChatId, action: tdlib_rs::enums::ChatAction) { self.chat_manager.send_chat_action(chat_id, action).await } pub fn clear_stale_typing_status(&mut self) -> bool { self.chat_manager.clear_stale_typing_status() } // Делегирование к message_manager pub async fn get_chat_history( &mut self, chat_id: ChatId, limit: i32, ) -> Result, String> { self.message_manager.get_chat_history(chat_id, limit).await } pub async fn load_older_messages( &mut self, chat_id: ChatId, from_message_id: MessageId, ) -> Result, String> { self.message_manager .load_older_messages(chat_id, from_message_id) .await } pub async fn get_pinned_messages(&mut self, chat_id: ChatId) -> Result, String> { self.message_manager.get_pinned_messages(chat_id).await } pub async fn load_current_pinned_message(&mut self, chat_id: ChatId) { self.message_manager.load_current_pinned_message(chat_id).await } pub async fn search_messages( &self, chat_id: ChatId, query: &str, ) -> Result, String> { self.message_manager.search_messages(chat_id, query).await } pub async fn send_message( &self, chat_id: ChatId, text: String, reply_to_message_id: Option, reply_info: Option, ) -> Result { self.message_manager .send_message(chat_id, text, reply_to_message_id, reply_info) .await } pub async fn edit_message( &self, chat_id: ChatId, message_id: MessageId, text: String, ) -> Result { self.message_manager .edit_message(chat_id, message_id, text) .await } pub async fn delete_messages( &self, chat_id: ChatId, message_ids: Vec, revoke: bool, ) -> Result<(), String> { self.message_manager .delete_messages(chat_id, message_ids, revoke) .await } pub async fn forward_messages( &self, to_chat_id: ChatId, from_chat_id: ChatId, message_ids: Vec, ) -> Result<(), String> { self.message_manager .forward_messages(to_chat_id, from_chat_id, message_ids) .await } pub async fn set_draft_message(&self, chat_id: ChatId, text: String) -> Result<(), String> { self.message_manager.set_draft_message(chat_id, text).await } pub fn push_message(&mut self, msg: MessageInfo) { self.message_manager.push_message(msg) } pub async fn fetch_missing_reply_info(&mut self) { self.message_manager.fetch_missing_reply_info().await } pub async fn process_pending_view_messages(&mut self) { self.message_manager.process_pending_view_messages().await } // Делегирование к user_cache pub fn get_user_status_by_chat_id(&self, chat_id: ChatId) -> Option<&UserOnlineStatus> { self.user_cache.get_status_by_chat_id(chat_id) } pub async fn process_pending_user_ids(&mut self) { self.user_cache.process_pending_user_ids().await } // Делегирование к reaction_manager pub async fn get_message_available_reactions( &self, chat_id: ChatId, message_id: MessageId, ) -> Result, String> { self.reaction_manager .get_message_available_reactions(chat_id, message_id) .await } pub async fn toggle_reaction( &self, chat_id: ChatId, message_id: MessageId, emoji: String, ) -> Result<(), String> { self.reaction_manager .toggle_reaction(chat_id, message_id, emoji) .await } // Вспомогательные методы pub fn client_id(&self) -> i32 { self.client_id } pub async fn get_me(&self) -> Result { match functions::get_me(self.client_id).await { Ok(tdlib_rs::enums::User::User(user)) => Ok(user.id), Err(e) => Err(format!("Ошибка получения текущего пользователя: {:?}", e)), } } // Accessor methods для обратной совместимости pub fn auth_state(&self) -> &AuthState { &self.auth.state } pub fn chats(&self) -> &[ChatInfo] { &self.chat_manager.chats } pub fn chats_mut(&mut self) -> &mut Vec { &mut self.chat_manager.chats } pub fn folders(&self) -> &[FolderInfo] { &self.chat_manager.folders } pub fn folders_mut(&mut self) -> &mut Vec { &mut self.chat_manager.folders } pub fn current_chat_messages(&self) -> &[MessageInfo] { &self.message_manager.current_chat_messages } pub fn current_chat_messages_mut(&mut self) -> &mut Vec { &mut self.message_manager.current_chat_messages } pub fn current_chat_id(&self) -> Option { self.message_manager.current_chat_id } pub fn set_current_chat_id(&mut self, chat_id: Option) { self.message_manager.current_chat_id = chat_id; } pub fn current_pinned_message(&self) -> Option<&MessageInfo> { self.message_manager.current_pinned_message.as_ref() } pub fn set_current_pinned_message(&mut self, msg: Option) { self.message_manager.current_pinned_message = msg; } pub fn typing_status(&self) -> Option<&(crate::types::UserId, String, std::time::Instant)> { self.chat_manager.typing_status.as_ref() } pub fn set_typing_status(&mut self, status: Option<(crate::types::UserId, String, std::time::Instant)>) { self.chat_manager.typing_status = status; } pub fn pending_view_messages(&self) -> &[(crate::types::ChatId, Vec)] { &self.message_manager.pending_view_messages } pub fn pending_view_messages_mut(&mut self) -> &mut Vec<(crate::types::ChatId, Vec)> { &mut self.message_manager.pending_view_messages } pub fn pending_user_ids(&self) -> &[crate::types::UserId] { &self.user_cache.pending_user_ids } pub fn pending_user_ids_mut(&mut self) -> &mut Vec { &mut self.user_cache.pending_user_ids } pub fn main_chat_list_position(&self) -> i32 { self.chat_manager.main_chat_list_position } pub fn set_main_chat_list_position(&mut self, position: i32) { self.chat_manager.main_chat_list_position = position; } // User cache accessors pub fn user_cache(&self) -> &UserCache { &self.user_cache } pub fn user_cache_mut(&mut self) -> &mut UserCache { &mut self.user_cache } // ==================== Helper методы для упрощения обработки updates ==================== /// Находит мутабельную ссылку на чат по ID. /// /// Упрощает повторяющийся паттерн `self.chats_mut().iter_mut().find(...)`. /// /// # Arguments /// /// * `chat_id` - ID чата для поиска /// /// # Returns /// /// * `Some(&mut ChatInfo)` - если чат найден /// * `None` - если чат не найден /// Обрабатываем одно обновление от TDLib pub fn handle_update(&mut self, update: Update) { match update { Update::AuthorizationState(state) => { crate::tdlib::update_handlers::handle_auth_state(self, state.authorization_state); } Update::NewChat(new_chat) => { // new_chat.chat is already a Chat struct, wrap it in TdChat enum let td_chat = TdChat::Chat(new_chat.chat.clone()); crate::tdlib::chat_helpers::add_or_update_chat(self, &td_chat); } Update::ChatLastMessage(update) => { let chat_id = ChatId::new(update.chat_id); let (last_message_text, last_message_date) = update .last_message .as_ref() .map(|msg| (Self::extract_message_text_static(msg).0, msg.date)) .unwrap_or_default(); crate::tdlib::chat_helpers::update_chat(self, chat_id, |chat| { chat.last_message = last_message_text; chat.last_message_date = last_message_date; }); // Обновляем позиции если они пришли for pos in update.positions.iter().filter(|p| matches!(p.list, ChatList::Main)) { crate::tdlib::chat_helpers::update_chat(self, chat_id, |chat| { chat.order = pos.order; chat.is_pinned = pos.is_pinned; }); } // Пересортируем по order self.chats_mut().sort_by(|a, b| b.order.cmp(&a.order)); } Update::ChatReadInbox(update) => { crate::tdlib::chat_helpers::update_chat(self, ChatId::new(update.chat_id), |chat| { chat.unread_count = update.unread_count; }); } Update::ChatUnreadMentionCount(update) => { crate::tdlib::chat_helpers::update_chat(self, ChatId::new(update.chat_id), |chat| { chat.unread_mention_count = update.unread_mention_count; }); } Update::ChatNotificationSettings(update) => { crate::tdlib::chat_helpers::update_chat(self, ChatId::new(update.chat_id), |chat| { // mute_for > 0 означает что чат замьючен chat.is_muted = update.notification_settings.mute_for > 0; }); } Update::ChatReadOutbox(update) => { // Обновляем last_read_outbox_message_id когда собеседник прочитал сообщения let last_read_msg_id = MessageId::new(update.last_read_outbox_message_id); crate::tdlib::chat_helpers::update_chat(self, ChatId::new(update.chat_id), |chat| { chat.last_read_outbox_message_id = last_read_msg_id; }); // Если это текущий открытый чат — обновляем is_read у сообщений if Some(ChatId::new(update.chat_id)) == self.current_chat_id() { for msg in self.current_chat_messages_mut().iter_mut() { if msg.is_outgoing() && msg.id() <= last_read_msg_id { msg.state.is_read = true; } } } } Update::ChatPosition(update) => { crate::tdlib::update_handlers::handle_chat_position_update(self, update); } Update::NewMessage(new_msg) => { crate::tdlib::update_handlers::handle_new_message_update(self, new_msg); } Update::User(update) => { crate::tdlib::update_handlers::handle_user_update(self, update); } Update::ChatFolders(update) => { // Обновляем список папок *self.folders_mut() = update .chat_folders .into_iter() .map(|f| FolderInfo { id: f.id, name: f.title }) .collect(); self.set_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_cache.user_statuses.insert(UserId::new(update.user_id), status); } Update::ConnectionState(update) => { // Обновляем состояние сетевого соединения self.network_state = match update.state { ConnectionState::WaitingForNetwork => NetworkState::WaitingForNetwork, ConnectionState::ConnectingToProxy => NetworkState::ConnectingToProxy, ConnectionState::Connecting => NetworkState::Connecting, ConnectionState::Updating => NetworkState::Updating, ConnectionState::Ready => NetworkState::Ready, }; } Update::ChatAction(update) => { crate::tdlib::update_handlers::handle_chat_action_update(self, update); } Update::ChatDraftMessage(update) => { crate::tdlib::update_handlers::handle_chat_draft_message_update(self, update); } Update::MessageInteractionInfo(update) => { crate::tdlib::update_handlers::handle_message_interaction_info_update(self, update); } Update::MessageSendSucceeded(update) => { crate::tdlib::update_handlers::handle_message_send_succeeded_update(self, update); } _ => {} } } // Helper functions pub fn extract_message_text_static(message: &TdMessage) -> (String, Vec) { use tdlib_rs::enums::MessageContent; match &message.content { MessageContent::MessageText(text) => (text.text.text.clone(), text.text.entities.clone()), _ => (String::new(), Vec::new()), } } pub fn extract_content_text(content: &tdlib_rs::enums::MessageContent) -> String { use tdlib_rs::enums::MessageContent; match content { MessageContent::MessageText(text) => text.text.text.clone(), _ => String::new(), } } }