tools.rs (16171B)
1 use crate::chatops::{ChatCommand, ChatOpError, ChatOpResult, CommandContext}; 2 use base64::{engine::general_purpose, Engine as _}; 3 use chrono::{DateTime, Utc}; 4 use rand::Rng; 5 use regex::Regex; 6 use std::process::Command; 7 use std::time::{SystemTime, UNIX_EPOCH}; 8 use uuid::Uuid; 9 10 /// Hash generation command 11 pub struct HashCommand; 12 13 impl ChatCommand for HashCommand { 14 fn name(&self) -> &'static str { 15 "hash" 16 } 17 fn description(&self) -> &'static str { 18 "Generate hash of text using various algorithms" 19 } 20 fn usage(&self) -> &'static str { 21 "/hash <algorithm> <text>" 22 } 23 24 fn execute( 25 &self, 26 args: Vec<String>, 27 _context: &CommandContext, 28 ) -> Result<ChatOpResult, ChatOpError> { 29 if args.len() < 2 { 30 return Err(ChatOpError::MissingArguments( 31 "Please specify algorithm and text".to_string(), 32 )); 33 } 34 35 let algorithm = args[0].to_lowercase(); 36 let text = args[1..].join(" "); 37 38 let hash_result = match algorithm.as_str() { 39 "md5" => { 40 let digest = md5::compute(text.as_bytes()); 41 format!("{:x}", digest) 42 } 43 "sha1" => { 44 use sha1::{Digest, Sha1}; 45 let mut hasher = Sha1::new(); 46 hasher.update(text.as_bytes()); 47 format!("{:x}", hasher.finalize()) 48 } 49 "sha256" => { 50 use sha2::{Digest, Sha256}; 51 let mut hasher = Sha256::new(); 52 hasher.update(text.as_bytes()); 53 format!("{:x}", hasher.finalize()) 54 } 55 "sha512" => { 56 use sha2::{Digest, Sha512}; 57 let mut hasher = Sha512::new(); 58 hasher.update(text.as_bytes()); 59 format!("{:x}", hasher.finalize()) 60 } 61 _ => { 62 return Err(ChatOpError::InvalidSyntax( 63 "Supported algorithms: md5, sha1, sha256, sha512".to_string(), 64 )) 65 } 66 }; 67 68 Ok(ChatOpResult::Message(format!( 69 "🔐 {} hash: `{}`", 70 algorithm.to_uppercase(), 71 hash_result 72 ))) 73 } 74 } 75 76 /// UUID generation command 77 pub struct UuidCommand; 78 79 impl ChatCommand for UuidCommand { 80 fn name(&self) -> &'static str { 81 "uuid" 82 } 83 fn description(&self) -> &'static str { 84 "Generate a random UUID" 85 } 86 fn usage(&self) -> &'static str { 87 "/uuid" 88 } 89 90 fn execute( 91 &self, 92 _args: Vec<String>, 93 _context: &CommandContext, 94 ) -> Result<ChatOpResult, ChatOpError> { 95 let uuid = Uuid::new_v4(); 96 Ok(ChatOpResult::Message(format!( 97 "🆔 Generated UUID: `{}`", 98 uuid 99 ))) 100 } 101 } 102 103 /// Base64 encoding/decoding command 104 pub struct Base64Command; 105 106 impl ChatCommand for Base64Command { 107 fn name(&self) -> &'static str { 108 "base64" 109 } 110 fn description(&self) -> &'static str { 111 "Encode or decode Base64 text" 112 } 113 fn usage(&self) -> &'static str { 114 "/base64 <encode|decode> <text>" 115 } 116 117 fn execute( 118 &self, 119 args: Vec<String>, 120 _context: &CommandContext, 121 ) -> Result<ChatOpResult, ChatOpError> { 122 if args.len() < 2 { 123 return Err(ChatOpError::MissingArguments( 124 "Please specify operation (encode/decode) and text".to_string(), 125 )); 126 } 127 128 let operation = args[0].to_lowercase(); 129 let text = args[1..].join(" "); 130 131 match operation.as_str() { 132 "encode" | "enc" => { 133 let encoded = general_purpose::STANDARD.encode(text.as_bytes()); 134 Ok(ChatOpResult::Message(format!( 135 "🔤 Base64 encoded: `{}`", 136 encoded 137 ))) 138 } 139 "decode" | "dec" => match general_purpose::STANDARD.decode(&text) { 140 Ok(decoded_bytes) => match String::from_utf8(decoded_bytes) { 141 Ok(decoded_text) => Ok(ChatOpResult::Message(format!( 142 "🔤 Base64 decoded: `{}`", 143 decoded_text 144 ))), 145 Err(_) => Ok(ChatOpResult::Message( 146 "🔤 Base64 decoded to binary data (not valid UTF-8)".to_string(), 147 )), 148 }, 149 Err(_) => Err(ChatOpError::InvalidSyntax( 150 "Invalid Base64 input".to_string(), 151 )), 152 }, 153 _ => Err(ChatOpError::InvalidSyntax( 154 "Operation must be 'encode' or 'decode'".to_string(), 155 )), 156 } 157 } 158 } 159 160 /// Regex testing command 161 pub struct RegexCommand; 162 163 impl ChatCommand for RegexCommand { 164 fn name(&self) -> &'static str { 165 "regex" 166 } 167 fn description(&self) -> &'static str { 168 "Test regex pattern against text" 169 } 170 fn usage(&self) -> &'static str { 171 "/regex <pattern> <text>" 172 } 173 fn aliases(&self) -> Vec<&'static str> { 174 vec!["re"] 175 } 176 177 fn execute( 178 &self, 179 args: Vec<String>, 180 _context: &CommandContext, 181 ) -> Result<ChatOpResult, ChatOpError> { 182 if args.len() < 2 { 183 return Err(ChatOpError::MissingArguments( 184 "Please specify pattern and text".to_string(), 185 )); 186 } 187 188 let pattern = &args[0]; 189 let text = args[1..].join(" "); 190 191 match Regex::new(pattern) { 192 Ok(re) => { 193 let matches: Vec<_> = re.find_iter(&text).collect(); 194 195 if matches.is_empty() { 196 Ok(ChatOpResult::Message("🔍 No matches found".to_string())) 197 } else { 198 let mut result = vec![format!("🔍 Found {} match(es):", matches.len())]; 199 200 for (i, m) in matches.iter().enumerate().take(5) { 201 // Limit to 5 matches 202 result.push(format!( 203 " {}. \"{}\" at position {}-{}", 204 i + 1, 205 m.as_str(), 206 m.start(), 207 m.end() 208 )); 209 } 210 211 if matches.len() > 5 { 212 result.push(format!(" ... and {} more", matches.len() - 5)); 213 } 214 215 Ok(ChatOpResult::Block(result)) 216 } 217 } 218 Err(e) => Err(ChatOpError::InvalidSyntax(format!( 219 "Invalid regex pattern: {}", 220 e 221 ))), 222 } 223 } 224 } 225 226 /// WHOIS lookup command 227 pub struct WhoisCommand; 228 229 impl ChatCommand for WhoisCommand { 230 fn name(&self) -> &'static str { 231 "whois" 232 } 233 fn description(&self) -> &'static str { 234 "Perform WHOIS lookup on a domain" 235 } 236 fn usage(&self) -> &'static str { 237 "/whois <domain>" 238 } 239 240 fn execute( 241 &self, 242 args: Vec<String>, 243 _context: &CommandContext, 244 ) -> Result<ChatOpResult, ChatOpError> { 245 if args.is_empty() { 246 return Err(ChatOpError::MissingArguments( 247 "Please specify a domain".to_string(), 248 )); 249 } 250 251 let domain = &args[0]; 252 253 match Command::new("whois").arg(domain).output() { 254 Ok(output) => { 255 if output.status.success() { 256 let result = String::from_utf8_lossy(&output.stdout); 257 let lines: Vec<String> = result 258 .lines() 259 .filter(|line| !line.trim().is_empty() && !line.starts_with('%')) 260 .take(10) // Limit output 261 .map(|line| line.trim().to_string()) 262 .collect(); 263 264 if lines.is_empty() { 265 Ok(ChatOpResult::Message(format!( 266 "🌐 No WHOIS data found for '{}'", 267 domain 268 ))) 269 } else { 270 Ok(ChatOpResult::Block(lines)) 271 } 272 } else { 273 Ok(ChatOpResult::Message(format!( 274 "🌐 WHOIS lookup failed for '{}'", 275 domain 276 ))) 277 } 278 } 279 Err(_) => Ok(ChatOpResult::Message(format!( 280 "🌐 WHOIS command not available. Try: https://whois.net/whois/{}", 281 domain 282 ))), 283 } 284 } 285 } 286 287 /// DNS lookup command 288 pub struct DigCommand; 289 290 impl ChatCommand for DigCommand { 291 fn name(&self) -> &'static str { 292 "dig" 293 } 294 fn description(&self) -> &'static str { 295 "Perform DNS lookup" 296 } 297 fn usage(&self) -> &'static str { 298 "/dig <domain> [record_type]" 299 } 300 301 fn execute( 302 &self, 303 args: Vec<String>, 304 _context: &CommandContext, 305 ) -> Result<ChatOpResult, ChatOpError> { 306 if args.is_empty() { 307 return Err(ChatOpError::MissingArguments( 308 "Please specify a domain".to_string(), 309 )); 310 } 311 312 let domain = &args[0]; 313 let record_type = args.get(1).map(|s| s.as_str()).unwrap_or("A"); 314 315 match Command::new("dig") 316 .args(&["+short", domain, record_type]) 317 .output() 318 { 319 Ok(output) => { 320 if output.status.success() { 321 let result = String::from_utf8_lossy(&output.stdout); 322 let lines: Vec<String> = result 323 .lines() 324 .filter(|line| !line.trim().is_empty()) 325 .map(|line| format!("🌐 {} {} {}", domain, record_type, line.trim())) 326 .collect(); 327 328 if lines.is_empty() { 329 Ok(ChatOpResult::Message(format!("🌐 No {} records found for '{}'", record_type, domain))) 330 } else { 331 Ok(ChatOpResult::Block(lines)) 332 } 333 } else { 334 Ok(ChatOpResult::Message(format!("🌐 DNS lookup failed for '{}'", domain))) 335 } 336 } 337 Err(_) => Ok(ChatOpResult::Message(format!("🌐 dig command not available. Try: https://www.nslookup.io/domains/{}/dns-records/", domain))), 338 } 339 } 340 } 341 342 /// IP information lookup command 343 pub struct IpInfoCommand; 344 345 impl ChatCommand for IpInfoCommand { 346 fn name(&self) -> &'static str { 347 "ipinfo" 348 } 349 fn description(&self) -> &'static str { 350 "Get IP address information" 351 } 352 fn usage(&self) -> &'static str { 353 "/ipinfo <ip_address>" 354 } 355 fn aliases(&self) -> Vec<&'static str> { 356 vec!["ip"] 357 } 358 359 fn execute( 360 &self, 361 args: Vec<String>, 362 _context: &CommandContext, 363 ) -> Result<ChatOpResult, ChatOpError> { 364 if args.is_empty() { 365 return Err(ChatOpError::MissingArguments( 366 "Please specify an IP address".to_string(), 367 )); 368 } 369 370 let ip = &args[0]; 371 372 // Basic IP validation 373 if !ip.chars().all(|c| c.is_ascii_digit() || c == '.') || ip.split('.').count() != 4 { 374 return Err(ChatOpError::InvalidSyntax( 375 "Please provide a valid IPv4 address".to_string(), 376 )); 377 } 378 379 // Try to get info from a free service 380 match Command::new("curl") 381 .args(&["-s", &format!("https://ipinfo.io/{}/json", ip)]) 382 .output() 383 { 384 Ok(output) => { 385 if output.status.success() { 386 let result = String::from_utf8_lossy(&output.stdout); 387 // Simple parsing - in a real implementation you'd use serde_json 388 if result.contains("\"ip\"") { 389 Ok(ChatOpResult::CodeBlock( 390 result.to_string(), 391 Some("json".to_string()), 392 )) 393 } else { 394 Ok(ChatOpResult::Message(format!( 395 "🌍 No information found for IP: {}", 396 ip 397 ))) 398 } 399 } else { 400 Ok(ChatOpResult::Message(format!( 401 "🌍 IP lookup failed for '{}'", 402 ip 403 ))) 404 } 405 } 406 Err(_) => Ok(ChatOpResult::Message(format!( 407 "🌍 IP lookup not available. Try: https://ipinfo.io/{}", 408 ip 409 ))), 410 } 411 } 412 } 413 414 /// Random number generator command 415 pub struct RandCommand; 416 417 impl ChatCommand for RandCommand { 418 fn name(&self) -> &'static str { 419 "rand" 420 } 421 fn description(&self) -> &'static str { 422 "Generate random number" 423 } 424 fn usage(&self) -> &'static str { 425 "/rand [min] [max]" 426 } 427 fn aliases(&self) -> Vec<&'static str> { 428 vec!["random", "rng"] 429 } 430 431 fn execute( 432 &self, 433 args: Vec<String>, 434 _context: &CommandContext, 435 ) -> Result<ChatOpResult, ChatOpError> { 436 let mut rng = rand::thread_rng(); 437 438 match args.len() { 439 0 => { 440 // Random float between 0 and 1 441 let num: f64 = rng.gen(); 442 Ok(ChatOpResult::Message(format!("🎲 Random: {:.6}", num))) 443 } 444 1 => { 445 // Random int between 0 and max 446 match args[0].parse::<i32>() { 447 Ok(max) if max > 0 => { 448 let num = rng.gen_range(0..=max); 449 Ok(ChatOpResult::Message(format!( 450 "🎲 Random (0-{}): {}", 451 max, num 452 ))) 453 } 454 _ => Err(ChatOpError::InvalidSyntax( 455 "Max must be a positive integer".to_string(), 456 )), 457 } 458 } 459 2 => { 460 // Random int between min and max 461 match (args[0].parse::<i32>(), args[1].parse::<i32>()) { 462 (Ok(min), Ok(max)) if min < max => { 463 let num = rng.gen_range(min..=max); 464 Ok(ChatOpResult::Message(format!( 465 "🎲 Random ({}-{}): {}", 466 min, max, num 467 ))) 468 } 469 _ => Err(ChatOpError::InvalidSyntax( 470 "Min and max must be integers with min < max".to_string(), 471 )), 472 } 473 } 474 _ => Err(ChatOpError::InvalidSyntax("Too many arguments".to_string())), 475 } 476 } 477 } 478 479 /// Time display command 480 pub struct TimeCommand; 481 482 impl ChatCommand for TimeCommand { 483 fn name(&self) -> &'static str { 484 "time" 485 } 486 fn description(&self) -> &'static str { 487 "Show current time in UTC and local" 488 } 489 fn usage(&self) -> &'static str { 490 "/time" 491 } 492 493 fn execute( 494 &self, 495 _args: Vec<String>, 496 _context: &CommandContext, 497 ) -> Result<ChatOpResult, ChatOpError> { 498 let now = SystemTime::now(); 499 let utc: DateTime<Utc> = now.into(); 500 501 // Get local time zone if possible 502 let local_info = match Command::new("date").output() { 503 Ok(output) => String::from_utf8_lossy(&output.stdout).trim().to_string(), 504 Err(_) => "Local time unavailable".to_string(), 505 }; 506 507 let unix_timestamp = now 508 .duration_since(UNIX_EPOCH) 509 .map(|d| d.as_secs()) 510 .unwrap_or(0); 511 512 let result = vec![ 513 "🕐 **Current Time:**".to_string(), 514 format!("UTC: {}", utc.format("%Y-%m-%d %H:%M:%S UTC")), 515 format!("Local: {}", local_info), 516 format!("Unix Timestamp: {}", unix_timestamp), 517 ]; 518 519 Ok(ChatOpResult::Block(result)) 520 } 521 }