command_router.rs (7495B)
1 use crate::ai_service::AIService; 2 use crate::chatops::{ChatOpResult, CommandContext, CommandRegistry, UserRole}; 3 use std::sync::Arc; 4 use tokio::runtime::Runtime; 5 6 /// Main router for handling ChatOps commands 7 pub struct ChatOpsRouter { 8 registry: CommandRegistry, 9 } 10 11 impl ChatOpsRouter { 12 pub fn new() -> Self { 13 Self { 14 registry: crate::chatops::init_chatops(), 15 } 16 } 17 18 pub fn new_with_ai(ai_service: Arc<AIService>, runtime: Arc<Runtime>) -> Self { 19 Self { 20 registry: crate::chatops::init_chatops_with_ai(ai_service, runtime), 21 } 22 } 23 24 /// Process a slash command input 25 pub fn process_command( 26 &self, 27 input: &str, 28 username: &str, 29 role: UserRole, 30 ) -> Option<ChatOpResult> { 31 // Skip if not a chatops command (let existing system handle it) 32 if !self.is_chatops_command(input) { 33 return None; 34 } 35 36 let parts = self.parse_command(input); 37 if parts.is_empty() { 38 return Some(ChatOpResult::Error("Empty command".to_string())); 39 } 40 41 let command_name = &parts[0]; 42 let args = parts[1..].to_vec(); 43 44 // Handle special built-in commands 45 match command_name.as_str() { 46 "chatops" => return Some(self.handle_help_command(vec![], &role)), 47 "commands" | "list" => return Some(self.handle_list_commands(&role)), 48 "help" => { 49 // Only handle specific chatops help, let general help fall through 50 if !args.is_empty() && self.registry.get_help(&args[0]).is_some() { 51 return Some(self.handle_help_command(args, &role)); 52 } 53 // Let general /help fall through to main help system 54 return None; 55 } 56 _ => {} 57 } 58 59 let context = CommandContext { 60 username: username.to_string(), 61 room: None, // TODO: Extract from app state if needed 62 role, 63 }; 64 65 match self.registry.execute_command(command_name, args, &context) { 66 Ok(result) => { 67 // Truncate long results to prevent chat spam but allow more context 68 let truncated = if result.should_truncate(40) { 69 result.truncate(40) 70 } else { 71 result 72 }; 73 Some(truncated) 74 } 75 Err(error) => Some(ChatOpResult::Error(error.to_string())), 76 } 77 } 78 79 /// Check if this is a chatops command (vs existing system command) 80 fn is_chatops_command(&self, input: &str) -> bool { 81 if !input.starts_with('/') { 82 return false; 83 } 84 85 let parts = self.parse_command(input); 86 if parts.is_empty() { 87 return false; 88 } 89 90 let command_name = &parts[0]; 91 92 // Built-in chatops commands 93 if matches!(command_name.as_str(), "help" | "commands" | "list") { 94 return true; 95 } 96 97 // Check if it's a registered chatops command 98 self.registry.get_command(command_name).is_some() 99 } 100 101 /// Parse command input into parts 102 fn parse_command(&self, input: &str) -> Vec<String> { 103 let input = input.trim_start_matches('/'); 104 105 // Simple parsing - split on whitespace but respect quotes 106 let mut parts = Vec::new(); 107 let mut current = String::new(); 108 let mut in_quotes = false; 109 let mut chars = input.chars().peekable(); 110 111 while let Some(ch) = chars.next() { 112 match ch { 113 '"' if !in_quotes => { 114 in_quotes = true; 115 } 116 '"' if in_quotes => { 117 in_quotes = false; 118 if !current.is_empty() { 119 parts.push(current.clone()); 120 current.clear(); 121 } 122 } 123 ' ' | '\t' if !in_quotes => { 124 if !current.is_empty() { 125 parts.push(current.clone()); 126 current.clear(); 127 } 128 } 129 _ => { 130 current.push(ch); 131 } 132 } 133 } 134 135 if !current.is_empty() { 136 parts.push(current); 137 } 138 139 parts 140 } 141 142 /// Handle help command 143 fn handle_help_command(&self, args: Vec<String>, _role: &UserRole) -> ChatOpResult { 144 if args.is_empty() { 145 // General ChatOps help - return as single message for better full-screen display 146 let help_text = vec![ 147 "🤖 **ChatOps Developer Commands Available:**", 148 "", 149 "**Documentation & Lookup:**", 150 "/man <command> - Manual pages", 151 "/doc <lang> <term> - Language docs", 152 "/explain <topic> - Explain concepts", 153 "/cheat <term> - Cheat sheets", 154 "/so <query> - StackOverflow search", 155 "", 156 "**Tools & Utilities:**", 157 "/hash <algo> <text> - Hash functions", 158 "/uuid - Generate UUID", 159 "/base64 <encode|decode> <text>", 160 "/regex <pattern> <text> - Test regex", 161 "/time - Current time", 162 "", 163 "**Network Tools:**", 164 "/whois <domain> - Domain lookup", 165 "/dig <domain> - DNS lookup", 166 "/ping <host> - Ping host", 167 "/headers <url> - HTTP headers", 168 "", 169 "**GitHub Integration:**", 170 "/github <user>/<repo> - Repo info", 171 "/crates <crate> - Rust crate info", 172 "/npm <package> - NPM package info", 173 "", 174 "Use `/help <command>` for help on specific ChatOps commands.", 175 "Use `/commands` to list available commands for your role.", 176 "Use `/help` for general chat commands and shortcuts.", 177 ] 178 .join("\n"); 179 180 ChatOpResult::Message(help_text) 181 } else { 182 // Specific command help 183 let command_name = &args[0]; 184 match self.registry.get_help(command_name) { 185 Some(help) => ChatOpResult::Message(help), 186 None => { 187 ChatOpResult::Error(format!("No help available for command: {}", command_name)) 188 } 189 } 190 } 191 } 192 193 /// Handle list commands 194 fn handle_list_commands(&self, role: &UserRole) -> ChatOpResult { 195 let commands = self.registry.list_commands(role); 196 if commands.is_empty() { 197 return ChatOpResult::Message("No commands available for your role.".to_string()); 198 } 199 200 let mut output = vec![ 201 format!("📋 **Available Commands ({} total):**", commands.len()), 202 "".to_string(), 203 ]; 204 205 for (name, description) in commands { 206 output.push(format!("**{}** - {}", name, description)); 207 } 208 209 ChatOpResult::Block(output) 210 } 211 212 /// Register a user alias 213 #[allow(dead_code)] 214 pub fn register_user_alias(&mut self, alias: String, target: String) { 215 self.registry.register_alias(alias, target); 216 } 217 218 /// Remove a user alias 219 #[allow(dead_code)] 220 pub fn remove_user_alias(&mut self, alias: &str) { 221 self.registry.remove_alias(alias); 222 } 223 }