diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 17b547f0c..73e5139f4 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -362,6 +362,26 @@ pub fn run() { } } + // Start the TDLib client early so it's ready before any skill + // starts. The worker loop auto-sends setTdlibParameters when + // TDLib requests them — no JS involvement needed. + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { + let data_dir = app + .path() + .app_data_dir() + .unwrap_or_else(|_| { + dirs::home_dir() + .unwrap_or_else(|| std::path::PathBuf::from(".")) + .join(".alphahuman") + }); + let tdlib_data_dir = data_dir.join("telegram"); + match crate::services::tdlib::TDLIB_MANAGER.create_client(tdlib_data_dir) { + Ok(id) => log::info!("[tdlib] Client created at app startup (ID {})", id), + Err(e) => log::error!("[tdlib] Failed to create client at startup: {}", e), + } + } + // Create the SocketManager (persistent Rust-native Socket.io) let socket_mgr = std::sync::Arc::new( runtime::socket_manager::SocketManager::new(), diff --git a/src-tauri/src/services/quickjs-libs/bootstrap.js b/src-tauri/src/services/quickjs-libs/bootstrap.js index dc3e3f3e4..839abca3a 100644 --- a/src-tauri/src/services/quickjs-libs/bootstrap.js +++ b/src-tauri/src/services/quickjs-libs/bootstrap.js @@ -941,6 +941,16 @@ globalThis.tdlib = { } }, + /** + * Create client and set TDLib parameters in a single atomic call. + * API credentials are stored on the Rust side only. + * @param {string} dataDir - Path to store TDLib data files. + * @returns {Promise} Client ID. + */ + ensureInitialized: async function (dataDir) { + return await __ops.tdlib_ensure_initialized(dataDir); + }, + /** * Create a TDLib client with the given data directory. * @param {string} dataDir - Path to store TDLib data files. diff --git a/src-tauri/src/services/quickjs-libs/qjs_ops/ops_tdlib.rs b/src-tauri/src/services/quickjs-libs/qjs_ops/ops_tdlib.rs index 8b673573a..2adfb0b77 100644 --- a/src-tauri/src/services/quickjs-libs/qjs_ops/ops_tdlib.rs +++ b/src-tauri/src/services/quickjs-libs/qjs_ops/ops_tdlib.rs @@ -13,6 +13,31 @@ pub fn register<'js>(ctx: &Ctx<'js>, ops: &Object<'js>, skill_context: SkillCont ))?; } + { + let sc = skill_context.clone(); + ops.set("tdlib_ensure_initialized", Function::new(ctx.clone(), + Async(move |data_dir: String| { + let skill_id = sc.skill_id.clone(); + async move { + log::info!("[tdlib_v8] ensure_initialized with data_dir: {}", data_dir); + check_telegram_skill(&skill_id).map_err(|e| { + log::error!("[tdlib_v8] Skill check failed: {}", e); + js_err(e) + })?; + let client_id = crate::services::tdlib::TDLIB_MANAGER + .ensure_initialized(PathBuf::from(data_dir)) + .await + .map_err(|e| { + log::error!("[tdlib_v8] ensure_initialized failed: {}", e); + js_err(e) + })?; + log::info!("[tdlib_v8] ensure_initialized succeeded with client ID: {}", client_id); + Ok::(client_id) + } + }), + ))?; + } + { let sc = skill_context.clone(); ops.set("tdlib_create_client", Function::new(ctx.clone(), diff --git a/src-tauri/src/services/tdlib/manager.rs b/src-tauri/src/services/tdlib/manager.rs index 772f7d416..2a09d7011 100644 --- a/src-tauri/src/services/tdlib/manager.rs +++ b/src-tauri/src/services/tdlib/manager.rs @@ -16,6 +16,10 @@ use once_cell::sync::Lazy; use parking_lot::RwLock; use tokio::sync::{broadcast, mpsc, oneshot}; +/// Telegram API credentials (kept on the Rust side only). +const API_ID: i32 = 28685916; +const API_HASH: &str = "d540ab21dece5404af298c44f4f6386d"; + /// Global TDLib manager instance. pub static TDLIB_MANAGER: Lazy = Lazy::new(TdLibManager::new); @@ -122,6 +126,7 @@ impl TdLibManager { /// Returns the client ID. If a client already exists, returns its ID. /// Only one client can exist at a time; blocks if a destroy is in progress. pub fn create_client(&self, data_dir: PathBuf) -> Result { + log::info!("[tdlib] create_client called with data dir: {:?}", data_dir); // Block creation while a destroy is in progress to prevent // creating a new C++ client before the old one releases its database lock. if self.is_destroying.load(Ordering::SeqCst) { @@ -143,7 +148,8 @@ impl TdLibManager { // Create TDLib client using tdlib_rs let client_id = tdlib_rs::create_client(); - // Store data directory + // Store data directory and pass a clone to the worker + let worker_data_dir = data_dir.clone(); *self.data_dir.write() = Some(data_dir); // Create request channel @@ -158,7 +164,7 @@ impl TdLibManager { let cid = client_id; let handle = std::thread::spawn(move || { - Self::worker_loop(cid, state, request_rx); + Self::worker_loop(cid, state, request_rx, worker_data_dir); }); *self.worker_handle.write() = Some(handle); @@ -170,7 +176,7 @@ impl TdLibManager { let tdlib_verbosity: i32 = std::env::var("TDLIB_LOG_LEVEL") .ok() .and_then(|v| v.parse().ok()) - .unwrap_or(0); + .unwrap_or(1); let cid_for_log = client_id; tauri::async_runtime::spawn(async move { if let Err(e) = tdlib_rs::functions::set_log_verbosity_level(tdlib_verbosity, cid_for_log).await { @@ -181,11 +187,71 @@ impl TdLibManager { Ok(client_id) } + /// Create the client (if needed) and return the client ID. + /// + /// `setTdlibParameters` is sent **reactively** by the worker loop whenever + /// TDLib reports `authorizationStateWaitTdlibParameters`, so callers don't + /// need to send it themselves. + pub async fn ensure_initialized(&self, data_dir: PathBuf) -> Result { + self.create_client(data_dir) + } + + pub async fn send_set_tdlib_parameters(&self) -> Result<(), String> { + // Resolve client_id from manager state + let client_id_opt = *self.client_id.read(); + let client_id = match client_id_opt { + Some(id) => id, + None => { + log::error!("[tdlib] Cannot send setTdlibParameters: client_id not initialized"); + return Err("TDLib client is not initialized".to_string()); + } + }; + + // Resolve data_dir from manager state + let data_dir_opt = self.data_dir.read().clone(); + let data_dir = match data_dir_opt { + Some(dir) => dir, + None => { + log::error!("[tdlib] Cannot send setTdlibParameters: data_dir not set"); + return Err("TDLib data directory is not set".to_string()); + } + }; + + log::info!("[tdlib] Sending setTdlibParameters to client {}", client_id); + let db_dir = data_dir.to_string_lossy().to_string(); + let files_dir = data_dir.join("files").to_string_lossy().to_string(); + let params = serde_json::json!({ + "@type": "setTdlibParameters", + "use_test_dc": false, + "database_directory": db_dir, + "files_directory": files_dir, + "database_encryption_key": "", + "use_file_database": true, + "use_chat_info_database": true, + "use_message_database": true, + "use_secret_chats": false, + "api_id": API_ID, + "api_hash": API_HASH, + "system_language_code": "en", + "device_model": "Desktop", + "system_version": "", + "application_version": "1.0.0" + }); + match Self::send_json_request(client_id, ¶ms).await { + Err(e) => { + log::error!("[tdlib] Auto-send setTdlibParameters failed: {}", e); + Err(format!("Failed to send setTdlibParameters: {}", e)) + } + Ok(_) => Ok(()), + } + } + /// Worker loop that handles requests and polls for updates. fn worker_loop( client_id: i32, state: Arc, mut request_rx: mpsc::Receiver, + data_dir: PathBuf, ) { log::info!("[tdlib] Worker loop started for client {}", client_id); @@ -271,6 +337,26 @@ impl TdLibManager { // Convert update to JSON for processing let json = serde_json::to_value(&update).unwrap_or(serde_json::Value::Null); + log::info!("[tdlib] Handling response: {}", serde_json::to_string_pretty(&json).unwrap_or_else(|_| "invalid JSON".to_string())); + + // React to authorizationStateWaitTdlibParameters — auto-send + // setTdlibParameters with the configured credentials. + if let Some(auth_type) = json + .get("authorization_state") + .and_then(|s| s.get("@type")) + .and_then(|t| t.as_str()) + { + if auth_type == "authorizationStateWaitTdlibParameters" { + log::info!("[tdlib] TDLib requests parameters — scheduling auto-send"); + tauri::async_runtime::spawn(async { + if let Err(e) = TDLIB_MANAGER.send_set_tdlib_parameters().await { + log::error!("[tdlib] Auto-send setTdlibParameters failed: {}", e); + } + }); + return; + } + } + // Check if this is a response to a pending request (has @extra) if let Some(extra) = json.get("@extra").and_then(|v| v.as_str()) { // Find and complete the pending request @@ -558,6 +644,7 @@ impl TdLibManager { /// Receive the next update from TDLib (with timeout). pub async fn receive(&self, timeout_ms: u32) -> Option { + log::info!("[tdlib] receive called with timeout: {}ms", timeout_ms); let request_tx = { self.request_tx.read().clone() }?;