1use colored::Colorize;
8use gibberish_or_not::download_model_with_progress_bar;
9use rpassword;
10use std::collections::HashMap;
11use std::fmt::Display;
12use std::io::{self, Write};
13use std::path::Path;
14
15#[derive(Debug)]
19pub struct ColorScheme {
20 pub informational: String,
23 pub warning: String,
26 pub success: String,
29 pub question: String,
32 pub statement: String,
35}
36
37fn print_statement<T: Display>(text: T) -> String {
45 text.to_string().white().to_string()
46}
47
48fn print_warning<T: Display>(text: T) -> String {
56 text.to_string().red().to_string()
57}
58
59fn print_question<T: Display>(text: T) -> String {
67 text.to_string().yellow().to_string()
68}
69
70fn print_success<T: Display>(text: T) -> String {
78 text.to_string().green().to_string()
79}
80
81fn print_rgb(text: &str, rgb: &str) -> String {
90 let parts: Vec<&str> = rgb.split(',').collect();
91 if parts.len() != 3 {
92 return text.to_string();
93 }
94
95 if let (Ok(r), Ok(g), Ok(b)) = (
96 parts[0].trim().parse::<u8>(),
97 parts[1].trim().parse::<u8>(),
98 parts[2].trim().parse::<u8>(),
99 ) {
100 text.truecolor(r, g, b).to_string()
101 } else {
102 text.to_string()
103 }
104}
105
106fn get_capptucin_scheme() -> ColorScheme {
111 ColorScheme {
112 informational: "238,212,159".to_string(), warning: "237,135,150".to_string(), success: "166,218,149".to_string(), question: "202,211,245".to_string(), statement: "244,219,214".to_string(), }
118}
119
120fn get_darcula_scheme() -> ColorScheme {
125 ColorScheme {
126 informational: "241,250,140".to_string(), warning: "255,85,85".to_string(), success: "80,250,123".to_string(), question: "139,233,253".to_string(), statement: "248,248,242".to_string(), }
132}
133
134fn get_girly_pop_scheme() -> ColorScheme {
139 ColorScheme {
140 informational: "237,69,146".to_string(), warning: "241,218,165".to_string(), success: "243,214,243".to_string(), question: "255,128,177".to_string(), statement: "255,148,219".to_string(), }
146}
147
148fn get_default_scheme() -> ColorScheme {
153 ColorScheme {
154 informational: "255,215,0".to_string(), warning: "255,0,0".to_string(), success: "0,255,0".to_string(), question: "255,215,0".to_string(), statement: "255,255,255".to_string(), }
160}
161
162pub fn run_first_time_setup() -> HashMap<String, String> {
171 println!(
172 "\n{}",
173 print_statement("🤠 Howdy! This is your first time running Ares.")
174 );
175 println!("{}", print_statement("Let me help you configure Ares."));
176
177 if ask_yes_no_question("Do you want a tutorial?", true) {
179 println!("ares -t 'encoded text here' to decode.");
180 println!("Have a crib you know is in the plaintext? use --regex 'crib here'");
181 println!("yah that's it. Will write more when we add more :-D\n");
182 }
183
184 let want_custom = ask_yes_no_question(
186 "Do you want a custom colour scheme? Will be applied after we're done configuring",
187 false,
188 );
189
190 let mut config = if !want_custom {
191 color_scheme_to_hashmap(get_default_scheme())
193 } else {
194 println!(
196 "\n{}",
197 print_statement("What colour scheme looks best to you?")
198 );
199
200 println!("1. Capptucin");
201 let capptucin = get_capptucin_scheme();
202 print!(" ");
203 print!(
204 "{} | ",
205 print_rgb("Informational", &capptucin.informational)
206 );
207 print!("{} | ", print_rgb("Warning", &capptucin.warning));
208 print!("{} | ", print_rgb("Success", &capptucin.success));
209 print!("{} | ", print_rgb("Questions", &capptucin.question));
210 println!("{}\n", print_rgb("Statements", &capptucin.statement));
211
212 println!("2. Darcula");
213 let darcula = get_darcula_scheme();
214 print!(" ");
215 print!("{} | ", print_rgb("Informational", &darcula.informational));
216 print!("{} | ", print_rgb("Warning", &darcula.warning));
217 print!("{} | ", print_rgb("Success", &darcula.success));
218 print!("{} | ", print_rgb("Questions", &darcula.question));
219 println!("{}\n", print_rgb("Statements", &darcula.statement));
220
221 println!("3. 💖✨💐 GirlyPop");
222 let girly = get_girly_pop_scheme();
223 print!(" ");
224 print!("{} | ", print_rgb("Informational", &girly.informational));
225 print!("{} | ", print_rgb("Warning", &girly.warning));
226 print!("{} | ", print_rgb("Success", &girly.success));
227 print!("{} | ", print_rgb("Questions", &girly.question));
228 println!("{}\n", print_rgb("Statements", &girly.statement));
229
230 println!("4. Default");
231 let default = get_default_scheme();
232 print!(" ");
233 print!("{} | ", print_rgb("Informational", &default.informational));
234 print!("{} | ", print_rgb("Warning", &default.warning));
235 print!("{} | ", print_rgb("Success", &default.success));
236 print!("{} | ", print_rgb("Questions", &default.question));
237 println!("{}\n", print_rgb("Statements", &default.statement));
238
239 println!("5. Custom");
241 println!(" Format: r,g,b (e.g., 255,0,0 for red)");
242 println!(" Values must be between 0 and 255");
243 println!(" You'll be prompted to enter RGB values for each color.\n");
244
245 let choice = get_user_input_range("Enter your choice (1-5): ", 1, 5);
247
248 match choice {
249 1 => color_scheme_to_hashmap(get_capptucin_scheme()),
250 2 => color_scheme_to_hashmap(get_darcula_scheme()),
251 3 => color_scheme_to_hashmap(get_girly_pop_scheme()),
252 4 => color_scheme_to_hashmap(get_default_scheme()),
253 5 => {
254 println!(
256 "\n{}",
257 print_statement("Enter RGB values for each color (format: r,g,b)")
258 );
259
260 let informational = get_user_input_rgb("Informational: ");
261 let warning = get_user_input_rgb("Warning: ");
262 let success = get_user_input_rgb("Success: ");
263 let question = get_user_input_rgb("Questions: ");
264 let statement = get_user_input_rgb("Statements: ");
265
266 let custom_scheme = ColorScheme {
267 informational,
268 warning,
269 success,
270 question,
271 statement,
272 };
273
274 color_scheme_to_hashmap(custom_scheme)
275 }
276 _ => unreachable!(),
277 }
278 };
279
280 println!("\n{}", print_question("What sounds better to you?"));
282 println!(
283 "\n{}",
284 print_statement("1. Ares will ask you everytime it detects plaintext if it is plaintext.\n2. Ares stores all possible plaintext in a list, and at the end of the program presents it to you.")
285 );
286 let wait_athena_choice = get_user_input_range("Enter your choice", 1, 2);
287
288 let top_results = wait_athena_choice == 2;
290 config.insert("top_results".to_string(), top_results.to_string());
291
292 let mut timeout = 5; if top_results {
296 println!(
298 "\n{}",
299 print_statement("Ares by default runs for 5 seconds. For this mode we suggest 3 seconds. Please do not complain if you choose too high of a number and your PC freezes up.\n")
300 );
301 timeout = get_user_input_range(
302 "How many seconds do you want Ares to run? (3 suggested) seconds",
303 1,
304 500,
305 );
306 }
307
308 config.insert("timeout".to_string(), timeout.to_string());
310
311 println!(
313 "{}",
314 print_question("\nWould you like Ares to use custom wordlists to detect plaintext?")
315 );
316 println!(
317 "{}",
318 print_statement(
319 "Ares can use custom wordlists to detect plaintext by checking for exact matches."
320 )
321 );
322 println!(
323 "{}",
324 print_warning("Note: If your wordlist is very large, this can generate excessive matches.")
325 );
326
327 if ask_yes_no_question("", false) {
328 if let Some(wordlist_path) = get_wordlist_path() {
329 config.insert("wordlist_path".to_string(), wordlist_path);
330 }
331 }
332
333 println!(
335 "{}",
336 print_question("\nWould you like to enable Enhanced Plaintext Detection?")
337 );
338 println!("{}", print_statement("This will increase accuracy by around 40%, and you will be asked less frequently if something is plaintext or not."));
339 println!(
340 "{}",
341 print_statement("This will download a 500mb AI model.")
342 );
343 println!(
344 "{}",
345 print_statement("You will need to follow these steps to download it:")
346 );
347 println!(
348 "{}",
349 print_statement("1. Make a HuggingFace account https://huggingface.co/")
350 );
351 println!(
352 "{}",
353 print_statement("2. Make a READ Token https://huggingface.co/settings/tokens")
354 );
355 println!(
356 "{}",
357 print_warning(
358 "Note: You will be able to do this later by running `ares --enable-enhanced-detection`"
359 )
360 );
361 println!("{}", print_statement("We will prompt you for the token if you click Yes. We will not store this token, just use it to download a model."));
362
363 if ask_yes_no_question("", false) {
364 config.insert("enhanced_detection".to_string(), "true".to_string());
366
367 let mut config_dir_path = crate::config::get_config_file_path();
369 config_dir_path.pop();
370 config_dir_path.push("models");
371
372 std::fs::create_dir_all(&config_dir_path).unwrap_or_else(|_| {
374 println!(
375 "{}",
376 print_warning(
377 "Could not create models directory. Enhanced detection may not work."
378 )
379 );
380 });
381
382 config_dir_path.push("model.bin");
383
384 config.insert(
385 "model_path".to_string(),
386 config_dir_path.display().to_string(),
387 );
388
389 println!(
391 "{}",
392 print_statement("Please enter your HuggingFace token:")
393 );
394 print!(
395 "{}",
396 print_question("Token [invisible for privacy reasons]: ")
397 );
398 io::stdout().flush().unwrap();
399
400 let token = rpassword::read_password().unwrap_or_else(|_| String::new());
402
403 if let Err(e) = download_model_with_progress_bar(&config_dir_path, Some(&token)) {
405 println!(
406 "{}",
407 print_warning(format!("Failed to download model: {}", e))
408 );
409 println!(
410 "{}",
411 print_warning("Enhanced detection may not work properly.")
412 );
413 } else {
414 println!("{}", print_success("Model downloaded successfully!"));
415 }
416 }
417
418 if ask_yes_no_question("Do you want to see a cute cat?", false) {
420 println!(
421 r#"
422 /\_/\
423 ( o.o )
424 o( ( ))
425 "#
426 );
427 }
428
429 config
430}
431
432fn ask_yes_no_question(question: &str, default_yes: bool) -> bool {
441 if !question.is_empty() {
443 println!("\n{}", print_question(question));
444 }
445
446 let prompt = if default_yes { "(Y/n): " } else { "(y/N): " };
448
449 print!("{}", print_question(prompt));
450 io::stdout().flush().unwrap();
451
452 let mut input = String::new();
453 io::stdin().read_line(&mut input).unwrap();
454
455 let input = input.trim().to_lowercase();
456
457 if input.is_empty() {
458 return default_yes;
459 }
460
461 match input.as_str() {
462 "y" | "yes" => true,
463 "n" | "no" => false,
464 _ => {
465 println!(
466 "{}",
467 print_warning("Invalid input. Please enter 'y' or 'n'.")
468 );
469 ask_yes_no_question(question, default_yes)
470 }
471 }
472}
473
474fn get_user_input_range(prompt: &str, min: u32, max: u32) -> u32 {
484 let input_prompt = format!("{} ({}-{}): ", prompt, min, max);
486 print!("{}", print_question(input_prompt));
487 io::stdout().flush().unwrap();
488
489 let mut input = String::new();
490 io::stdin().read_line(&mut input).unwrap();
491
492 let input = input.trim();
493
494 match input.parse::<u32>() {
495 Ok(num) if num >= min && num <= max => num,
496 _ => {
497 println!(
498 "{}",
499 print_warning(format!(
500 "Invalid input. Please enter a number between {} and {}.",
501 min, max
502 ))
503 );
504 get_user_input_range(prompt, min, max)
505 }
506 }
507}
508
509fn get_user_input_rgb(prompt: &str) -> String {
517 print!("{}", print_question(prompt));
518 io::stdout().flush().unwrap();
519
520 let mut input = String::new();
521 io::stdin().read_line(&mut input).unwrap();
522
523 let input = input.trim();
524
525 if let Some(rgb) = parse_rgb_input(input) {
527 rgb
528 } else {
529 println!(
530 "{}",
531 print_warning("Invalid RGB format. Please use the format 'r,g,b' (e.g., '255,0,0').")
532 );
533 get_user_input_rgb(prompt)
534 }
535}
536
537fn parse_rgb_input(input: &str) -> Option<String> {
545 let parts: Vec<&str> = input.split(',').collect();
546
547 if parts.len() != 3 {
548 return None;
549 }
550
551 let r = parts[0].trim().parse::<u8>().ok()?;
552 let g = parts[1].trim().parse::<u8>().ok()?;
553 let b = parts[2].trim().parse::<u8>().ok()?;
554
555 Some(format!("{},{},{}", r, g, b))
556}
557
558fn color_scheme_to_hashmap(scheme: ColorScheme) -> HashMap<String, String> {
566 let mut map = HashMap::new();
567 map.insert("informational".to_string(), scheme.informational);
568 map.insert("warning".to_string(), scheme.warning);
569 map.insert("success".to_string(), scheme.success);
570 map.insert("question".to_string(), scheme.question);
571 map.insert("statement".to_string(), scheme.statement);
572 map
573}
574
575fn get_wordlist_path() -> Option<String> {
578 println!(
579 "\n{}",
580 print_statement("Enter the path to your wordlist file:")
581 );
582 println!("{}", print_statement("(Leave empty to cancel)"));
583
584 let mut input = String::new();
585 std::io::stdin()
586 .read_line(&mut input)
587 .expect("Failed to read input");
588 let input = input.trim();
589
590 if input.is_empty() {
591 println!("{}", print_statement("No wordlist will be used."));
592 return None;
593 }
594
595 if !Path::new(input).exists() {
597 println!("{}", print_warning("File does not exist!"));
598 return get_wordlist_path(); }
600
601 match std::fs::File::open(input) {
603 Ok(_) => Some(input.to_string()),
604 Err(e) => {
605 println!("{}", print_warning(format!("Cannot read file: {}", e)));
606 get_wordlist_path() }
608 }
609}