use discord_rich_presence::activity::{Activity, Assets, Timestamps}; use discord_rich_presence::{DiscordIpc, DiscordIpcClient}; use parking_lot::RwLock; use std::sync::Arc; pub struct DiscordRpcManager { client: Arc>>, session_name: Arc>, model: Arc>, started_at: Arc>, } impl DiscordRpcManager { pub fn new() -> Self { Self { client: Arc::new(RwLock::new(None)), session_name: Arc::new(RwLock::new(String::new())), model: Arc::new(RwLock::new(String::new())), started_at: Arc::new(RwLock::new(0)), } } pub fn init(&self, initial_session_name: String, initial_model: String, started_at: i64) -> Result<(), String> { tracing::debug!("Attempting to initialize Discord RPC..."); tracing::debug!("Application ID: 1391117878182281316"); tracing::debug!("Initial session: '{}', model: '{}', timestamp: {}", initial_session_name, initial_model, started_at); let mut client = DiscordIpcClient::new("1391117878182281316") .map_err(|e| { let error_msg = format!("Failed to create Discord RPC client: {} (is Discord running?)", e); tracing::error!("{}", error_msg); error_msg })?; tracing::debug!("DiscordIpcClient created successfully"); client .connect() .map_err(|e| { let error_msg = format!("Failed to connect to Discord RPC: {} (ensure Discord is running)", e); tracing::error!("{}", error_msg); error_msg })?; tracing::debug!("Connected to Discord IPC socket"); // Set initial activity immediately after connecting tracing::debug!("Building initial activity..."); let state_text = format!("Model: {}", initial_model); let assets = Assets::new() .large_image("hikari") .large_text("Hikari - Claude Code Assistant"); tracing::debug!("Assets created - large_image: 'hikari', large_text: 'Hikari - Claude Code Assistant'"); let timestamps = Timestamps::new() .start(started_at); tracing::debug!("Timestamps created - start: {}", started_at); let activity = Activity::new() .details(initial_session_name.as_str()) .state(state_text.as_str()) .assets(assets) .timestamps(timestamps); tracing::debug!("Activity created - details: '{}', state: '{}'", initial_session_name, state_text); tracing::debug!("Attempting to set initial activity..."); client .set_activity(activity) .map_err(|e| { let error_msg = format!("Failed to set initial Discord RPC activity: {}", e); tracing::error!("{}", error_msg); error_msg })?; tracing::debug!("Initial activity set successfully!"); // Store the client and initial state *self.client.write() = Some(client); *self.session_name.write() = initial_session_name.clone(); *self.model.write() = initial_model.clone(); *self.started_at.write() = started_at; tracing::info!("Discord RPC connected successfully with initial activity: session='{}', model='{}'", initial_session_name, initial_model); Ok(()) } pub fn update( &self, session_name: String, model: String, started_at: i64, ) -> Result<(), String> { tracing::debug!("update() called with session='{}', model='{}', timestamp={}", session_name, model, started_at); *self.session_name.write() = session_name.clone(); *self.model.write() = model.clone(); *self.started_at.write() = started_at; tracing::debug!("State variables updated"); let mut client_guard = self.client.write(); let client = client_guard .as_mut() .ok_or_else(|| { let error_msg = "Discord RPC client not initialized".to_string(); tracing::error!("{}", error_msg); error_msg })?; tracing::debug!("Client lock acquired"); let state_text = format!("Model: {}", model); let assets = Assets::new() .large_image("hikari") .large_text("Hikari - Claude Code Assistant"); tracing::debug!("Assets created - large_image: 'hikari', large_text: 'Hikari - Claude Code Assistant'"); let timestamps = Timestamps::new() .start(started_at); tracing::debug!("Timestamps created - start: {}", started_at); let activity = Activity::new() .details(session_name.as_str()) .state(state_text.as_str()) .assets(assets) .timestamps(timestamps); tracing::debug!("Activity created - details: '{}', state: '{}'", session_name, state_text); tracing::debug!("Attempting to set activity..."); client .set_activity(activity) .map_err(|e| { let error_msg = format!("Failed to update Discord RPC: {}", e); tracing::error!("{}", error_msg); error_msg })?; tracing::info!("Updated Discord RPC: session='{}', model='{}'", session_name, model); Ok(()) } pub fn stop(&self) -> Result<(), String> { tracing::debug!("stop() called"); let mut client_guard = self.client.write(); if let Some(mut client) = client_guard.take() { tracing::debug!("Client found, attempting to close..."); client .close() .map_err(|e| { let error_msg = format!("Failed to close Discord RPC: {}", e); tracing::error!("{}", error_msg); error_msg })?; tracing::info!("Discord RPC stopped successfully"); } else { tracing::debug!("No client to stop (already stopped or never initialized)"); } Ok(()) } } impl Default for DiscordRpcManager { fn default() -> Self { Self::new() } }