bot_client.rs (8579B)
1 use crate::ai_service::AIService; 2 use crate::bot_system::{BotConfig, BotSystem, MessageType}; 3 use crate::PostType; 4 use anyhow::{anyhow, Result}; 5 use crossbeam_channel::Sender; 6 use log::{error, info, warn}; 7 use std::sync::{Arc, Mutex}; 8 use std::thread; 9 use std::time::Duration; 10 use tokio::runtime::Runtime; 11 12 /// Bot client that runs in the background 13 pub struct BotClient { 14 bot_name: String, 15 bot_system: Arc<BotSystem>, 16 running: Arc<Mutex<bool>>, 17 tx: Sender<PostType>, 18 rx: Arc<Mutex<crossbeam_channel::Receiver<PostType>>>, 19 } 20 21 impl BotClient { 22 /// Create a new bot client 23 pub fn new( 24 bot_name: String, 25 _username: String, 26 _password: String, 27 _url: String, 28 ai_service: Option<Arc<AIService>>, 29 runtime: Option<Arc<Runtime>>, 30 admin_users: Vec<String>, 31 ) -> Result<Self> { 32 // Create communication channels 33 let (tx, rx) = crossbeam_channel::unbounded(); 34 35 // Create bot configuration 36 let bot_config = BotConfig { 37 bot_name: bot_name.clone(), 38 admin_users, 39 data_directory: std::env::current_dir()?.join("bot_data").join(&bot_name), 40 ..BotConfig::default() 41 }; 42 43 // Create bot system 44 let bot_system = Arc::new(BotSystem::new(bot_config, tx.clone(), ai_service, runtime)?); 45 46 Ok(Self { 47 bot_name, 48 bot_system, 49 running: Arc::new(Mutex::new(false)), 50 tx, 51 rx: Arc::new(Mutex::new(rx)), 52 }) 53 } 54 55 /// Start the bot client 56 pub fn start(&mut self) -> Result<()> { 57 { 58 let mut running = self.running.lock().unwrap(); 59 if *running { 60 return Err(anyhow!("Bot client is already running")); 61 } 62 *running = true; 63 } 64 65 info!("Starting bot client: {}", self.bot_name); 66 67 // Start bot system 68 self.bot_system.start()?; 69 70 // Since we can't easily create a separate LeChatPHPClient instance, 71 // we'll simulate the bot by creating a background thread that processes 72 // messages and responds to commands. 73 self.start_message_processing_thread()?; 74 75 Ok(()) 76 } 77 78 /// Stop the bot client 79 pub fn stop(&mut self) -> Result<()> { 80 { 81 let mut running = self.running.lock().unwrap(); 82 if !*running { 83 return Ok(()); 84 } 85 *running = false; 86 } 87 88 info!("Stopping bot client: {}", self.bot_name); 89 90 // Stop bot system 91 self.bot_system.stop()?; 92 93 Ok(()) 94 } 95 96 /// Start message processing thread 97 fn start_message_processing_thread(&self) -> Result<()> { 98 let bot_system = Arc::clone(&self.bot_system); 99 let running = Arc::clone(&self.running); 100 let bot_name = self.bot_name.clone(); 101 let tx = self.tx.clone(); 102 103 thread::spawn(move || { 104 info!("Bot {} message processing thread started", bot_name); 105 106 // Send initial status message (non-blocking) 107 let startup_msg = format!("🤖 {} is now online and ready to help!", bot_name); 108 if let Err(e) = tx.try_send(PostType::Post(startup_msg, Some("0".to_string()))) { 109 warn!( 110 "Could not send bot startup message (channel may be disconnected): {}", 111 e 112 ); 113 } 114 115 while *running.lock().unwrap() { 116 // In a real implementation, this would: 117 // 1. Monitor main chat messages for @bot mentions 118 // 2. Process the messages through the bot system 119 // 3. Send appropriate responses 120 121 // For now, we'll just maintain the bot system state 122 thread::sleep(Duration::from_secs(30)); 123 124 // Send periodic status if needed (for debugging) 125 if bot_system.is_running() { 126 // Bot is active and ready to respond to commands 127 // Commands will be processed when main client detects @bot mentions 128 } 129 } 130 131 // Send shutdown message (non-blocking) 132 let shutdown_msg = format!("🤖 {} is going offline. Data has been saved.", bot_name); 133 if let Err(e) = tx.try_send(PostType::Post(shutdown_msg, Some("0".to_string()))) { 134 warn!( 135 "Could not send bot shutdown message (channel may be disconnected): {}", 136 e 137 ); 138 } 139 140 info!("Bot {} message processing thread stopped", bot_name); 141 }); 142 143 Ok(()) 144 } 145 146 /// Process a message for the bot system 147 #[allow(dead_code)] 148 pub fn process_message( 149 &self, 150 username: &str, 151 content: &str, 152 message_id: Option<usize>, 153 is_private: bool, 154 ) -> Result<()> { 155 // Determine message type 156 let message_type = if is_private { 157 MessageType::PrivateMessage { 158 to: self.bot_name.clone(), 159 } 160 } else { 161 MessageType::Normal 162 }; 163 164 // Process through bot system 165 self.bot_system.process_message( 166 username, 167 content, 168 message_type, 169 message_id.map(|id| id as u64), 170 None, // No channel context for individual bot calls 171 false, // Assume non-member for individual calls 172 )?; 173 174 Ok(()) 175 } 176 177 /// Check if bot is running 178 pub fn is_running(&self) -> bool { 179 *self.running.lock().unwrap() 180 } 181 182 /// Get bot name 183 pub fn get_bot_name(&self) -> &str { 184 &self.bot_name 185 } 186 } 187 188 impl Drop for BotClient { 189 fn drop(&mut self) { 190 if self.is_running() { 191 if let Err(e) = self.stop() { 192 error!("Failed to stop bot client during drop: {}", e); 193 } 194 } 195 } 196 } 197 198 /// Bot manager to handle multiple bots 199 pub struct BotManager { 200 bots: Vec<BotClient>, 201 ai_service: Option<Arc<AIService>>, 202 runtime: Option<Arc<Runtime>>, 203 } 204 205 impl BotManager { 206 pub fn new(ai_service: Option<Arc<AIService>>, runtime: Option<Arc<Runtime>>) -> Self { 207 Self { 208 bots: Vec::new(), 209 ai_service, 210 runtime, 211 } 212 } 213 214 pub fn add_bot( 215 &mut self, 216 bot_name: String, 217 username: String, 218 password: String, 219 url: String, 220 admin_users: Vec<String>, 221 ) -> Result<()> { 222 let bot = BotClient::new( 223 bot_name, 224 username, 225 password, 226 url, 227 self.ai_service.clone(), 228 self.runtime.clone(), 229 admin_users, 230 )?; 231 232 self.bots.push(bot); 233 Ok(()) 234 } 235 236 /// Get all bot receivers for message forwarding 237 pub fn get_all_bot_receivers( 238 &self, 239 ) -> Vec<(String, Arc<Mutex<crossbeam_channel::Receiver<PostType>>>)> { 240 self.bots 241 .iter() 242 .map(|bot| (bot.get_bot_name().to_string(), Arc::clone(&bot.rx))) 243 .collect() 244 } 245 246 pub fn start_bot(&mut self, bot_name: &str) -> Result<()> { 247 if let Some(bot) = self.bots.iter_mut().find(|b| b.get_bot_name() == bot_name) { 248 bot.start()?; 249 info!("Started bot: {}", bot_name); 250 } else { 251 return Err(anyhow!("Bot not found: {}", bot_name)); 252 } 253 Ok(()) 254 } 255 256 /// Stop all bots 257 pub fn stop_all(&mut self) -> Result<()> { 258 for bot in &mut self.bots { 259 if bot.is_running() { 260 bot.stop()?; 261 } 262 } 263 Ok(()) 264 } 265 266 /// Process message for all bots 267 pub fn process_message_for_all_bots( 268 &self, 269 username: &str, 270 content: &str, 271 message_type: MessageType, 272 message_id: Option<u64>, 273 channel_context: Option<&str>, 274 is_member: bool, 275 ) -> Result<()> { 276 for bot in &self.bots { 277 if bot.is_running() { 278 if let Err(e) = bot.bot_system.process_message( 279 username, 280 content, 281 message_type.clone(), 282 message_id, 283 channel_context, 284 is_member, 285 ) { 286 warn!( 287 "Failed to process message for bot {}: {}", 288 bot.get_bot_name(), 289 e 290 ); 291 } 292 } 293 } 294 Ok(()) 295 } 296 }