#[cfg(test)]
mod tests;
use crate::storage;
use crate::storage::wait_athena_storage::PlaintextResult;
use crate::DecoderResult;
use colored::Colorize;
use std::env;
use std::fs::write;
use text_io::read;
pub fn parse_rgb(rgb: &str) -> Option<(u8, u8, u8)> {
let parts: Vec<&str> = rgb.split(',').collect();
if parts.len() != 3 {
eprintln!("Invalid RGB format: '{}'. Expected format: 'r,g,b' where r,g,b are numbers between 0-255", rgb);
return None;
}
let r = match parts[0].trim().parse::<u8>() {
Ok(val) => val,
Err(_) => {
eprintln!(
"Invalid red value '{}': must be a number between 0-255",
parts[0]
);
return None;
}
};
let g = match parts[1].trim().parse::<u8>() {
Ok(val) => val,
Err(_) => {
eprintln!(
"Invalid green value '{}': must be a number between 0-255",
parts[1]
);
return None;
}
};
let b = match parts[2].trim().parse::<u8>() {
Ok(val) => val,
Err(_) => {
eprintln!(
"Invalid blue value '{}': must be a number between 0-255",
parts[2]
);
return None;
}
};
Some((r, g, b))
}
fn color_string(text: &str, role: &str) -> String {
let config = crate::config::get_config();
let rgb = match config.colourscheme.get(role) {
Some(color) => color.clone(),
None => config
.colourscheme
.get("statement")
.cloned()
.unwrap_or_else(|| "255,255,255".to_string()),
};
if let Some((r, g, b)) = parse_rgb(&rgb) {
text.truecolor(r, g, b).bold().to_string()
} else {
if let Some(statement_rgb) = config.colourscheme.get("statement") {
if let Some((r, g, b)) = parse_rgb(statement_rgb) {
return text.truecolor(r, g, b).bold().to_string();
}
}
text.white().to_string()
}
}
pub fn statement(text: &str, role: Option<&str>) -> String {
match role {
Some(r) => color_string(text, r),
None => color_string(text, "statement"),
}
}
#[allow(dead_code)]
pub fn warning(text: &str) -> String {
color_string(text, "warning")
}
pub fn success(text: &str) -> String {
color_string(text, "success")
}
#[allow(dead_code)]
fn error(text: &str) -> String {
color_string(text, "warning")
}
fn question(text: &str) -> String {
color_string(text, "question")
}
pub fn program_exiting_successful_decoding(result: DecoderResult) {
let config = crate::config::get_config();
if config.api_mode {
return;
}
if config.top_results {
return;
}
let plaintext = result.text;
let decoded_path = result
.path
.iter()
.map(|c| c.decoder)
.collect::<Vec<_>>()
.join(" → ");
let decoded_path_coloured = statement(&decoded_path, Some("informational"));
let decoded_path_string = if !decoded_path.contains('→') {
format!("the decoder used is {decoded_path_coloured}")
} else {
format!("the decoders used are {decoded_path_coloured}")
};
const INVIS_CHARS_DETECTION_PERCENTAGE: f64 = 0.3;
let mut invis_chars_found: f64 = 0.0;
for char in plaintext[0].chars() {
if storage::INVISIBLE_CHARS
.iter()
.any(|invis_chars| *invis_chars == char)
{
invis_chars_found += 1.0;
}
}
let invis_char_percentage = invis_chars_found / plaintext[0].len() as f64;
if invis_char_percentage > INVIS_CHARS_DETECTION_PERCENTAGE {
let invis_char_percentage_string = format!("{:2.0}%", invis_char_percentage * 100.0);
println!(
"{}",
question(
&format!(
"{} of the plaintext is invisible characters, would you like to save to a file instead? (y/N)",
invis_char_percentage_string.white().bold()
)
)
);
let reply: String = read!("{}\n");
let result = reply.to_ascii_lowercase().starts_with('y');
if result {
println!(
"Please enter a filename: (default: {}/ares_text.txt)",
env::var("HOME").unwrap_or_default().white().bold()
);
let mut file_path: String = read!("{}\n");
if file_path.is_empty() {
file_path = format!("{}/ares_text.txt", env::var("HOME").unwrap_or_default());
}
println!(
"Outputting plaintext to file: {}\n\n{}",
statement(&file_path, None),
decoded_path_string
);
write(file_path, &plaintext[0]).expect("Error writing to file.");
return;
}
}
println!(
"The plaintext is:\n{}\n{}",
success(&plaintext[0]),
decoded_path_string
);
}
pub fn decoded_how_many_times(depth: u32) {
let config = crate::config::get_config();
if config.api_mode {
return;
}
let decoders = crate::filtration_system::filter_and_get_decoders(&DecoderResult::default());
let decoded_times_int = depth * (decoders.components.len() as u32 + 40); println!(
"\n🥳 Ares has decoded {} times.\n",
statement(&decoded_times_int.to_string(), None)
);
}
pub fn human_checker_check(description: &str, text: &str) {
println!(
"🕵️ I think the plaintext is {}.\nPossible plaintext: '{}' (y/N): ",
statement(description, Some("informational")),
statement(text, Some("informational"))
);
}
pub fn failed_to_decode() {
let config = crate::config::get_config();
if config.api_mode {
return;
}
println!(
"{}",
warning("⛔️ Ares has failed to decode the text.\nIf you want more help, please ask in #coded-messages in our Discord http://discord.skerritt.blog")
);
}
pub fn countdown_until_program_ends(seconds_spent_running: u32, duration: u32) {
let config = crate::config::get_config();
if config.api_mode {
return;
}
if seconds_spent_running % 5 == 0 && seconds_spent_running != 0 {
let time_left = duration - seconds_spent_running;
if time_left == 0 {
return;
}
println!(
"{} seconds have passed. {} remaining",
statement(&seconds_spent_running.to_string(), None),
statement(&time_left.to_string(), None)
);
}
}
pub fn return_early_because_input_text_is_plaintext() {
let config = crate::config::get_config();
if config.api_mode {
return;
}
println!("{}", success("Your input text is the plaintext 🥳"));
}
pub fn panic_failure_both_input_and_fail_provided() {
let config = crate::config::get_config();
if config.api_mode {
return;
}
panic!("Failed -- both file and text were provided. Please only use one.")
}
pub fn panic_failure_no_input_provided() {
let config = crate::config::get_config();
if config.api_mode {
return;
}
panic!("Failed -- no input was provided. Please use -t for text or -f for files.")
}
pub fn warning_unknown_config_key(key: &str) {
let config = crate::config::get_config();
if config.api_mode {
return;
}
eprintln!(
"{}",
warning(&format!(
"Unknown configuration key found in config file: {}",
key
))
);
}
pub fn display_top_results(results: &[PlaintextResult]) {
let config = crate::config::get_config();
if config.api_mode {
return;
}
if results.is_empty() {
println!("{}", success("No potential plaintexts found."));
return;
}
println!("{}", success("\n🎊 List of Possible Plaintexts 🎊"));
println!(
"{}",
success(&format!(
"Found {} potential plaintext results:",
results.len()
))
);
if results.len() > 10 {
println!("{}", warning("There are more than 10 possible plaintexts. I think you should write them to a file."));
println!("{}", question("Would you like to write to a file? (y/N)"));
let mut input = String::new();
std::io::stdin()
.read_line(&mut input)
.expect("Failed to read input");
let result = input.trim().to_ascii_lowercase().starts_with('y');
if result {
println!(
"{}",
question(&format!(
"Please enter a filename: (default: {}/ares_text.txt)",
statement(&env::var("HOME").unwrap_or_default(), None)
))
);
let mut file_path = String::new();
std::io::stdin()
.read_line(&mut file_path)
.expect("Failed to read input");
file_path = file_path.trim().to_string();
if file_path.is_empty() {
file_path = format!("{}/ares_text.txt", env::var("HOME").unwrap_or_default());
}
let mut file_content = String::new();
for (i, result) in results.iter().enumerate() {
file_content.push_str(&format!("Result #{}: {}\n", i + 1, result.text));
file_content.push_str(&format!("Decoder: {}\n", result.decoder_name));
file_content.push_str(&format!("Checker: {}\n", result.checker_name));
file_content.push_str(&format!("Description: {}\n", result.description));
if results.len() > 1 {
file_content.push_str("---\n");
}
}
match write(&file_path, file_content) {
Ok(_) => println!("{}", success(&format!("Results written to {}", file_path))),
Err(e) => println!("{}", warning(&format!("Failed to write to file: {}", e))),
}
return;
}
}
for (i, result) in results.iter().enumerate() {
println!(
"{}",
success(&format!("Result #{}: {}", i + 1, result.text))
);
println!("{}", success(&format!("Decoder: {}", result.decoder_name)));
println!("{}", success(&format!("Checker: {}", result.checker_name)));
println!(
"{}",
success(&format!("Description: {}", result.description))
);
if results.len() > 1 {
println!("{}", success("---"));
}
}
println!("{}", success("=== End of Top Results ===\n"));
}
#[test]
fn test_parse_rgb() {
let test_cases = vec![
"255,0,0", "0, 255, 0", "0,0,255", ];
for case in test_cases {
let result = parse_rgb(case);
assert!(result.is_some());
}
}