use colored::Colorize;
use gibberish_or_not::download_model_with_progress_bar;
use rpassword;
use std::collections::HashMap;
use std::fmt::Display;
use std::io::{self, Write};
use std::path::Path;
#[derive(Debug)]
pub struct ColorScheme {
pub informational: String,
pub warning: String,
pub success: String,
pub question: String,
pub statement: String,
}
fn print_statement<T: Display>(text: T) -> String {
text.to_string().white().to_string()
}
fn print_warning<T: Display>(text: T) -> String {
text.to_string().red().to_string()
}
fn print_question<T: Display>(text: T) -> String {
text.to_string().yellow().to_string()
}
fn print_success<T: Display>(text: T) -> String {
text.to_string().green().to_string()
}
fn print_rgb(text: &str, rgb: &str) -> String {
let parts: Vec<&str> = rgb.split(',').collect();
if parts.len() != 3 {
return text.to_string();
}
if let (Ok(r), Ok(g), Ok(b)) = (
parts[0].trim().parse::<u8>(),
parts[1].trim().parse::<u8>(),
parts[2].trim().parse::<u8>(),
) {
text.truecolor(r, g, b).to_string()
} else {
text.to_string()
}
}
fn get_capptucin_scheme() -> ColorScheme {
ColorScheme {
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(), }
}
fn get_darcula_scheme() -> ColorScheme {
ColorScheme {
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(), }
}
fn get_girly_pop_scheme() -> ColorScheme {
ColorScheme {
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(), }
}
fn get_default_scheme() -> ColorScheme {
ColorScheme {
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(), }
}
pub fn run_first_time_setup() -> HashMap<String, String> {
println!(
"\n{}",
print_statement("🤠 Howdy! This is your first time running Ares.")
);
println!("{}", print_statement("Let me help you configure Ares."));
if ask_yes_no_question("Do you want a tutorial?", true) {
println!("ares -t 'encoded text here' to decode.");
println!("Have a crib you know is in the plaintext? use --regex 'crib here'");
println!("yah that's it. Will write more when we add more :-D\n");
}
let want_custom = ask_yes_no_question(
"Do you want a custom colour scheme? Will be applied after we're done configuring",
false,
);
let mut config = if !want_custom {
color_scheme_to_hashmap(get_default_scheme())
} else {
println!(
"\n{}",
print_statement("What colour scheme looks best to you?")
);
println!("1. Capptucin");
let capptucin = get_capptucin_scheme();
print!(" ");
print!(
"{} | ",
print_rgb("Informational", &capptucin.informational)
);
print!("{} | ", print_rgb("Warning", &capptucin.warning));
print!("{} | ", print_rgb("Success", &capptucin.success));
print!("{} | ", print_rgb("Questions", &capptucin.question));
println!("{}\n", print_rgb("Statements", &capptucin.statement));
println!("2. Darcula");
let darcula = get_darcula_scheme();
print!(" ");
print!("{} | ", print_rgb("Informational", &darcula.informational));
print!("{} | ", print_rgb("Warning", &darcula.warning));
print!("{} | ", print_rgb("Success", &darcula.success));
print!("{} | ", print_rgb("Questions", &darcula.question));
println!("{}\n", print_rgb("Statements", &darcula.statement));
println!("3. 💖✨💐 GirlyPop");
let girly = get_girly_pop_scheme();
print!(" ");
print!("{} | ", print_rgb("Informational", &girly.informational));
print!("{} | ", print_rgb("Warning", &girly.warning));
print!("{} | ", print_rgb("Success", &girly.success));
print!("{} | ", print_rgb("Questions", &girly.question));
println!("{}\n", print_rgb("Statements", &girly.statement));
println!("4. Default");
let default = get_default_scheme();
print!(" ");
print!("{} | ", print_rgb("Informational", &default.informational));
print!("{} | ", print_rgb("Warning", &default.warning));
print!("{} | ", print_rgb("Success", &default.success));
print!("{} | ", print_rgb("Questions", &default.question));
println!("{}\n", print_rgb("Statements", &default.statement));
println!("5. Custom");
println!(" Format: r,g,b (e.g., 255,0,0 for red)");
println!(" Values must be between 0 and 255");
println!(" You'll be prompted to enter RGB values for each color.\n");
let choice = get_user_input_range("Enter your choice (1-5): ", 1, 5);
match choice {
1 => color_scheme_to_hashmap(get_capptucin_scheme()),
2 => color_scheme_to_hashmap(get_darcula_scheme()),
3 => color_scheme_to_hashmap(get_girly_pop_scheme()),
4 => color_scheme_to_hashmap(get_default_scheme()),
5 => {
println!(
"\n{}",
print_statement("Enter RGB values for each color (format: r,g,b)")
);
let informational = get_user_input_rgb("Informational: ");
let warning = get_user_input_rgb("Warning: ");
let success = get_user_input_rgb("Success: ");
let question = get_user_input_rgb("Questions: ");
let statement = get_user_input_rgb("Statements: ");
let custom_scheme = ColorScheme {
informational,
warning,
success,
question,
statement,
};
color_scheme_to_hashmap(custom_scheme)
}
_ => unreachable!(),
}
};
println!("\n{}", print_question("What sounds better to you?"));
println!(
"\n{}",
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.")
);
let wait_athena_choice = get_user_input_range("Enter your choice", 1, 2);
let top_results = wait_athena_choice == 2;
config.insert("top_results".to_string(), top_results.to_string());
let mut timeout = 5;
if top_results {
println!(
"\n{}",
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")
);
timeout = get_user_input_range(
"How many seconds do you want Ares to run? (3 suggested) seconds",
1,
500,
);
}
config.insert("timeout".to_string(), timeout.to_string());
println!(
"{}",
print_question("\nWould you like Ares to use custom wordlists to detect plaintext?")
);
println!(
"{}",
print_statement(
"Ares can use custom wordlists to detect plaintext by checking for exact matches."
)
);
println!(
"{}",
print_warning("Note: If your wordlist is very large, this can generate excessive matches.")
);
if ask_yes_no_question("", false) {
if let Some(wordlist_path) = get_wordlist_path() {
config.insert("wordlist_path".to_string(), wordlist_path);
}
}
println!(
"{}",
print_question("\nWould you like to enable Enhanced Plaintext Detection?")
);
println!("{}", print_statement("This will increase accuracy by around 40%, and you will be asked less frequently if something is plaintext or not."));
println!(
"{}",
print_statement("This will download a 500mb AI model.")
);
println!(
"{}",
print_statement("You will need to follow these steps to download it:")
);
println!(
"{}",
print_statement("1. Make a HuggingFace account https://huggingface.co/")
);
println!(
"{}",
print_statement("2. Make a READ Token https://huggingface.co/settings/tokens")
);
println!(
"{}",
print_warning(
"Note: You will be able to do this later by running `ares --enable-enhanced-detection`"
)
);
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."));
if ask_yes_no_question("", false) {
config.insert("enhanced_detection".to_string(), "true".to_string());
let mut config_dir_path = crate::config::get_config_file_path();
config_dir_path.pop();
config_dir_path.push("models");
std::fs::create_dir_all(&config_dir_path).unwrap_or_else(|_| {
println!(
"{}",
print_warning(
"Could not create models directory. Enhanced detection may not work."
)
);
});
config_dir_path.push("model.bin");
config.insert(
"model_path".to_string(),
config_dir_path.display().to_string(),
);
println!(
"{}",
print_statement("Please enter your HuggingFace token:")
);
print!(
"{}",
print_question("Token [invisible for privacy reasons]: ")
);
io::stdout().flush().unwrap();
let token = rpassword::read_password().unwrap_or_else(|_| String::new());
if let Err(e) = download_model_with_progress_bar(&config_dir_path, Some(&token)) {
println!(
"{}",
print_warning(format!("Failed to download model: {}", e))
);
println!(
"{}",
print_warning("Enhanced detection may not work properly.")
);
} else {
println!("{}", print_success("Model downloaded successfully!"));
}
}
if ask_yes_no_question("Do you want to see a cute cat?", false) {
println!(
r#"
/\_/\
( o.o )
o( ( ))
"#
);
}
config
}
fn ask_yes_no_question(question: &str, default_yes: bool) -> bool {
if !question.is_empty() {
println!("\n{}", print_question(question));
}
let prompt = if default_yes { "(Y/n): " } else { "(y/N): " };
print!("{}", print_question(prompt));
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
let input = input.trim().to_lowercase();
if input.is_empty() {
return default_yes;
}
match input.as_str() {
"y" | "yes" => true,
"n" | "no" => false,
_ => {
println!(
"{}",
print_warning("Invalid input. Please enter 'y' or 'n'.")
);
ask_yes_no_question(question, default_yes)
}
}
}
fn get_user_input_range(prompt: &str, min: u32, max: u32) -> u32 {
let input_prompt = format!("{} ({}-{}): ", prompt, min, max);
print!("{}", print_question(input_prompt));
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
let input = input.trim();
match input.parse::<u32>() {
Ok(num) if num >= min && num <= max => num,
_ => {
println!(
"{}",
print_warning(format!(
"Invalid input. Please enter a number between {} and {}.",
min, max
))
);
get_user_input_range(prompt, min, max)
}
}
}
fn get_user_input_rgb(prompt: &str) -> String {
print!("{}", print_question(prompt));
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
let input = input.trim();
if let Some(rgb) = parse_rgb_input(input) {
rgb
} else {
println!(
"{}",
print_warning("Invalid RGB format. Please use the format 'r,g,b' (e.g., '255,0,0').")
);
get_user_input_rgb(prompt)
}
}
fn parse_rgb_input(input: &str) -> Option<String> {
let parts: Vec<&str> = input.split(',').collect();
if parts.len() != 3 {
return None;
}
let r = parts[0].trim().parse::<u8>().ok()?;
let g = parts[1].trim().parse::<u8>().ok()?;
let b = parts[2].trim().parse::<u8>().ok()?;
Some(format!("{},{},{}", r, g, b))
}
fn color_scheme_to_hashmap(scheme: ColorScheme) -> HashMap<String, String> {
let mut map = HashMap::new();
map.insert("informational".to_string(), scheme.informational);
map.insert("warning".to_string(), scheme.warning);
map.insert("success".to_string(), scheme.success);
map.insert("question".to_string(), scheme.question);
map.insert("statement".to_string(), scheme.statement);
map
}
fn get_wordlist_path() -> Option<String> {
println!(
"\n{}",
print_statement("Enter the path to your wordlist file:")
);
println!("{}", print_statement("(Leave empty to cancel)"));
let mut input = String::new();
std::io::stdin()
.read_line(&mut input)
.expect("Failed to read input");
let input = input.trim();
if input.is_empty() {
println!("{}", print_statement("No wordlist will be used."));
return None;
}
if !Path::new(input).exists() {
println!("{}", print_warning("File does not exist!"));
return get_wordlist_path(); }
match std::fs::File::open(input) {
Ok(_) => Some(input.to_string()),
Err(e) => {
println!("{}", print_warning(format!("Cannot read file: {}", e)));
get_wordlist_path() }
}
}