Weird bug: pattern.rs
use crate::models::PatternGroup;
use regex::Regex;
use std::collections::HashMap;

pub fn analyze_patterns(channels: &[String]) -> Vec<PatternGroup> {
    // Define regex patterns to match
    let patterns = vec![
        regex_patterns("zfeature\\w+"),
        regex_patterns("zproject\\w+"),
        regex_patterns("zbugfix\\w+"),
        regex_patterns("zcanary\\w*"),
        // Add more patterns as needed
    ];

    let mut pattern_map: HashMap<String, Vec<String>> = HashMap::new();

    for channel in channels {
        let mut matched = false;
        for (pattern_str, regex) in &patterns {
            if regex.is_match(channel) {
                pattern_map
                    .entry(pattern_str.clone())
                    .or_insert_with(Vec::new)
                    .push(channel.clone());
                matched = true;
                break;
            }
        }
        if !matched {
            pattern_map
                .entry("Others".to_string())
                .or_insert_with(Vec::new)
                .push(channel.clone());
        }
    }

    // Convert map to vector of PatternGroup
    let mut grouped_patterns = Vec::new();
    for (pattern, channels) in pattern_map {
        grouped_patterns.push(PatternGroup { pattern, channels });
    }

    grouped_patterns
}

fn regex_patterns(pattern: &str) -> (String, Regex) {
    (pattern.to_string(), Regex::new(&format!("(?i)^{}", pattern)).unwrap())
}

warning: `smart` (bin "smart") generated 4 warnings (run `cargo fix --bin "smart"` to apply 4 suggestions)
    Finished `release` profile [optimized] target(s) in 0.19s
     Running `target\release\smart.exe -i hey.txt -c 500`
2024-09-29T00:30:27.217816Z  INFO smart: Reading wordlist from file: "hey.txt"
2024-09-29T00:30:27.221339Z  INFO smart: Generating combinations...
2024-09-29T00:30:27.222003Z  INFO smart: Total combinations generated: 2955
2024-09-29T00:30:27.238505Z  INFO smart: Starting channel checks with concurrency level: 500
⠤ [00:00:06] [#######################################>] 2950/2955 (0s)                                                                                                                                                    
2024-09-29T00:30:37.520517Z ERROR smart::checker: Error checking http://setup.rbxcdn.com/channel/zstartconnectionflag/DeployHistory.txt: error sending request for url (http://setup.rbxcdn.com/channel/zstartconnectionflag/DeployHistory.txt)
2024-09-29T00:30:38.322712Z ERROR smart::checker: Error checking http://setup.rbxcdn.com/channel/zintegrationtestsmoke/Roblox.exe: error sending request for url (http://setup.rbxcdn.com/channel/zintegrationtestsmoke/Roblox.exe)
2024-09-29T00:30:38.322712Z ERROR smart::checker: Error checking http://setup.rbxcdn.com/channel/zflagintegrationstart/Roblox.exe: error sending request for url (http://setup.rbxcdn.com/channel/zflagintegrationstart/Roblox.exe)
2024-09-29T00:30:41.091920Z ERROR smart::checker: Error checking http://setup.rbxcdn.com/channel/zcontrolconnection1/Roblox.exe: error sending request for url (http://setup.rbxcdn.com/channel/zcontrolconnection1/Roblox.exe)
2024-09-29T00:30:48.027302Z ERROR smart::checker: Error checking http://setup.rbxcdn.com/channel/zstartconnectionflag/DeployHistory.txt: error sending request for url (http://setup.rbxcdn.com/channel/zstartconnectionflag/DeployHistory.txt)
2024-09-29T00:30:48.829028Z ERROR smart::checker: Error checking http://setup.rbxcdn.com/channel/zintegrationtestsmoke/Roblox.exe: error sending request for url (http://setup.rbxcdn.com/channel/zintegrationtestsmoke/Roblox.exe)
2024-09-29T00:30:48.829026Z ERROR smart::checker: Error checking http://setup.rbxcdn.com/channel/zflagintegrationstart/Roblox.exe: error sending request for url (http://setup.rbxcdn.com/channel/zflagintegrationstart/Roblox.exe)
2024-09-29T00:30:51.603766Z ERROR smart::checker: Error checking http://setup.rbxcdn.com/channel/zcontrolconnection1/Roblox.exe: error sending request for url (http://setup.rbxcdn.com/channel/zcontrolconnection1/Roblox.exe)
  [00:00:30] [########################################] 2955/2955 (0s)                                                                                                                                                    
2024-09-29T00:30:57.596944Z  INFO smart: Analyzing channel patterns...
=== Overview ===
Total Valid Channels: 13
Channels with 'feature' in their name: 0

=== Channels ===
+--------------------+------------+-------------------+-----------------------+----------------+
| Channel            | Roblox.exe | DeployHistory.txt | Mac DeployHistory.txt | ClientSettings |
+--------------------+------------+-------------------+-----------------------+----------------+
| flag             | ✅       | ✅              | ✅                  | ✅           |
+--------------------+------------+-------------------+-----------------------+----------------+
| test1            | ✅       | ✅              | ✅                  | ✅           |
+--------------------+------------+-------------------+-----------------------+----------------+
| integration1     | ✅       | ✅              | ✅                  | ✅           |
+--------------------+------------+-------------------+-----------------------+----------------+
| integrationtest  | ✅       | ❌              | ❌                  | ✅           |
+--------------------+------------+-------------------+-----------------------+----------------+
| integrationtest2 | ✅       | ❌              | ❌                  | ✅           |
+--------------------+------------+-------------------+-----------------------+----------------+
| integrationtest3 | ✅       | ❌              | ❌                  | ✅           |
+--------------------+------------+-------------------+-----------------------+----------------+
| featureharmony   | ✅       | ✅              | ✅                  | ✅           |
+--------------------+------------+-------------------+-----------------------+----------------+
| integration3     | ✅       | ❌              | ❌                  | ✅           |
+--------------------+------------+-------------------+-----------------------+----------------+
| luastartpage     | ✅       | ✅              | ✅                  | ✅           |
+--------------------+------------+-------------------+-----------------------+----------------+
| integration      | ✅       | ✅              | ✅                  | ✅           |
+--------------------+------------+-------------------+-----------------------+----------------+
| integrationtest1 | ✅       | ❌              | ❌                  | ✅           |
+--------------------+------------+-------------------+-----------------------+----------------+
| integration2     | ✅       | ✅              | ✅                  | ✅           |
+--------------------+------------+-------------------+-----------------------+----------------+
| luatest          | ✅       | ✅              | ✅                  | ✅           |
+--------------------+------------+-------------------+-----------------------+----------------+

=== Pattern Analysis ===
+---------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+       
| Pattern | Number of Channels | Channels                                                                                                                                                                         |       
+---------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+       
| Others  | 13                 | flag, test1, integration1, integrationtest, integrationtest2, integrationtest3, featureharmony, integration3, luastartpage, integration, integrationtest1, integration2, luatest |       
+---------+--------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+       

Harmony should be there . ?

Also try to append z to the names
Make this more visually advanced aswell

models.rs:
use colored::ColoredString;
use serde::Serialize;

#[derive(Debug, Serialize, Clone)]
pub struct ChannelInfo {
    #[serde(skip_serializing)]
    pub colored_channel: ColoredString,
    #[serde(rename = "channel")]
    pub original_channel: String,
    pub roblox_exe: bool,
    pub deploy_history: bool,
    pub mac_deploy_history: bool,
    pub client_settings: bool,
}

#[derive(Debug, Clone, Serialize)]
pub struct PatternGroup {
    pub pattern: String,
    pub channels: Vec<String>,
}

main.rs:
use crate::checker::check_channel;
use crate::cli::parse_args;
use crate::errors::AppError;
use crate::models::ChannelInfo;
use crate::models::PatternGroup;
use crate::pattern::analyze_patterns;
pub mod checker;
pub mod cli;
pub mod errors;
pub mod models;
pub mod pattern;
use colored::Colorize;
use indicatif::{ProgressBar, ProgressStyle};
use itertools::Itertools;
use prettytable::{cell, row, Table};
use rayon::prelude::*;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use reqwest::blocking::Client;
use std::collections::HashSet;
use std::fs;
use std::io::{self, BufRead};
use std::path::Path;
use std::time::Duration;
use tracing::{error, info};
use tracing_subscriber::fmt::format::FmtSpan;

fn main() -> Result<(), AppError> {
    // Initialize logging
    tracing_subscriber::fmt()
        .with_span_events(FmtSpan::CLOSE) // Log span end events
        .with_max_level(tracing::Level::INFO)
        .init();

    // Parse command-line arguments
    let matches = parse_args();

    // Read wordlist from file or stdin
    let wordlist = if let Some(input_path) = matches.get_one::<String>("input") {
        read_wordlist_from_file(input_path)?
    } else {
        info!("Reading wordlist from stdin. Enter words followed by Enter. Press Ctrl+D (Unix) or Ctrl+Z (Windows) to finish:");
        read_wordlist_from_stdin()?
    };

    // Deduplicate wordlist
    let mut unique_wordlist: Vec<String> = wordlist.into_iter().unique().collect();
    unique_wordlist.sort();

    // Wordlist Augmentation
    if let Some(prefixes_str) = matches.get_one::<String>("prefixes") {
        let prefixes: Vec<&str> = prefixes_str.split(',').collect();
        augment_wordlist_with_prefixes(&mut unique_wordlist, &prefixes);
        info!("Augmented wordlist with prefixes.");
    }

    if let Some(suffixes_str) = matches.get_one::<String>("suffixes") {
        let suffixes: Vec<&str> = suffixes_str.split(',').collect();
        augment_wordlist_with_suffixes(&mut unique_wordlist, &suffixes);
        info!("Augmented wordlist with suffixes.");
    }

    if let Some(num_random_str) = matches.get_one::<String>("random-strings") {
        let num_random: usize = num_random_str.parse().unwrap_or(0);
        augment_wordlist_with_random_strings(&mut unique_wordlist, num_random);
        info!("Augmented wordlist with {} random strings.", num_random);
    }

    if unique_wordlist.is_empty() {
        error!("Wordlist is empty. Please provide at least one word.");
        return Ok(());
    }

    info!("Generating combinations...");
    let combinations = generate_combinations(&unique_wordlist);
    info!("Total combinations generated: {}", combinations.len());

    // Configure HTTP client
    let timeout = matches
        .get_one::<String>("timeout")
        .unwrap()
        .parse::<u64>()
        .unwrap_or(10);
    let client = Client::builder()
        .timeout(Duration::from_secs(timeout))
        .build()?;

    // Setup progress bar
    let pb = ProgressBar::new(combinations.len() as u64);
    pb.set_style(
        ProgressStyle::default_bar()
            .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})")
            .unwrap()
            .progress_chars("#>-"),
    );

    let concurrency = matches
        .get_one::<String>("concurrency")
        .unwrap()
        .parse::<usize>()
        .unwrap_or(8);
    rayon::ThreadPoolBuilder::new()
        .num_threads(concurrency)
        .build_global()
        .unwrap();

    info!("Starting channel checks with concurrency level: {}", concurrency);

    // Process combinations in parallel
    let results: Vec<ChannelInfo> = combinations
        .par_iter()
        .map(|combination| {
            let result = check_channel(&client, combination);
            pb.inc(1);
            result
        })
        .filter_map(|res| res)
        .collect();

    pb.finish_with_message("Channel checks completed.");

    info!("Analyzing channel patterns...");
    let patterns = analyze_patterns(
        &results
            .iter()
            .map(|c| c.original_channel.clone())
            .collect::<Vec<_>>(),
    );

    // Display results in CLI
    print_overview(&results, &patterns);
    print_channels(&results);
    print_patterns(&patterns);

    // Save results to file if output path is provided
    if let Some(output_path) = matches.get_one::<String>("output") {
        info!("Saving results to {}", output_path);
        save_results_to_file(&results, output_path)?;
    }

    Ok(())
}

// Reads wordlist from a file
fn read_wordlist_from_file<P: AsRef<Path>>(path: P) -> Result<Vec<String>, AppError> {
    info!("Reading wordlist from file: {:?}", path.as_ref());
    let file = fs::File::open(path)?;
    let reader = io::BufReader::new(file);
    let wordlist = reader
        .lines()
        .filter_map(Result::ok)
        .filter(|line| !line.trim().is_empty())
        .collect();
    Ok(wordlist)
}

// Reads wordlist from standard input
fn read_wordlist_from_stdin() -> Result<Vec<String>, AppError> {
    let stdin = io::stdin();
    let wordlist = stdin
        .lock()
        .lines()
        .filter_map(Result::ok)
        .filter(|line| !line.trim().is_empty())
        .collect();
    Ok(wordlist)
}

// Generates combinations of words
fn generate_combinations(wordlist: &[String]) -> HashSet<String> {
    let mut combinations = HashSet::new();
    let max_combination_length = 3; // Adjust as needed

    for length in 1..=max_combination_length {
        for combo in wordlist.iter().permutations(length) {
            let concatenated = combo.into_iter().join("");
            combinations.insert(concatenated);
        }
    }

    combinations
}

// Augments the wordlist with prefixes
fn augment_wordlist_with_prefixes(wordlist: &mut Vec<String>, prefixes: &[&str]) {
    let mut new_words = Vec::new();
    for prefix in prefixes {
        for word in wordlist.iter() {
            new_words.push(format!("{}{}", prefix, word));
        }
    }
    wordlist.append(&mut new_words);
}

// Augments the wordlist with suffixes
fn augment_wordlist_with_suffixes(wordlist: &mut Vec<String>, suffixes: &[&str]) {
    let mut new_words = Vec::new();
    for suffix in suffixes {
        for word in wordlist.iter() {
            new_words.push(format!("{}{}", word, suffix));
        }
    }
    wordlist.append(&mut new_words);
}

// Augments the wordlist with random strings
fn augment_wordlist_with_random_strings(wordlist: &mut Vec<String>, num_strings: usize) {
    let rng = thread_rng();
    for _ in 0..num_strings {
        let random_string: String = rng.clone()
            .sample_iter(&Alphanumeric)
            .take(8) // Adjust the length of random strings as needed
            .map(char::from)
            .collect();
        wordlist.push(random_string);
    }
}

// Saves results to a JSON file
fn save_results_to_file(results: &[ChannelInfo], path: &str) -> Result<(), AppError> {
    let json = serde_json::to_string_pretty(results)?;
    fs::write(path, json)?;
    Ok(())
}

// Prints an overview of the results
fn print_overview(results: &[ChannelInfo], patterns: &[PatternGroup]) {
    println!("{}", "=== Overview ===".bold().underline());
    println!("Total Valid Channels: {}", results.len());

    let feature_channels = patterns
        .iter()
        .find(|p| p.pattern.to_lowercase().contains("feature"))
        .map(|p| p.channels.len())
        .unwrap_or(0);
    println!(
        "Channels with '{}' in their name: {}\n",
        "feature".cyan(),
        feature_channels
    );
}

// Prints the details of each channel in a table
fn print_channels(results: &[ChannelInfo]) {
    let mut table = Table::new();
    table.add_row(row![
        "Channel",
        "Roblox.exe",
        "DeployHistory.txt",
        "Mac DeployHistory.txt",
        "ClientSettings"
    ]);

    for channel in results {
        table.add_row(row![
            channel.colored_channel.to_string(),
            if channel.roblox_exe { "✅".green() } else { "❌".red() },
            if channel.deploy_history { "✅".green() } else { "❌".red() },
            if channel.mac_deploy_history { "✅".green() } else { "❌".red() },
            if channel.client_settings { "✅".green() } else { "❌".red() },
        ]);
    }

    println!("{}", "=== Channels ===".bold().underline());
    table.printstd();
    println!(); // Add an empty line for better readability
}

// Prints the pattern analysis in a table
fn print_patterns(patterns: &[PatternGroup]) {
    let mut table = Table::new();
    table.add_row(row!["Pattern", "Number of Channels", "Channels"]);

    for pattern in patterns {
        let channel_count = pattern.channels.len();
        let channels_joined = pattern.channels.join(", ");
        table.add_row(row![
            pattern.pattern.clone(),
            channel_count,
            channels_joined
        ]);
    }

    println!("{}", "=== Pattern Analysis ===".bold().underline());
    table.printstd();
    println!(); // Add an empty line for better readability
}

errors.rs:
use thiserror::Error;

#[derive(Debug, Error)]
pub enum AppError {
    #[error("IO error occurred: {0}")]
    Io(#[from] std::io::Error),

    #[error("HTTP request error: {0}")]
    Http(#[from] reqwest::Error),

    #[error("Serialization error: {0}")]
    Serde(#[from] serde_json::Error),

    #[error("Regex error: {0}")]
    Regex(#[from] regex::Error),
    
    #[error("Other error: {0}")]
    Other(String),
}

cli.rs:
use clap::{Arg, Command};

pub struct CliArgs {
    pub input: Option<String>,
    pub output: Option<String>,
    pub concurrency: usize,
    pub timeout: u64,
    pub verbose: bool,
    pub prefixes: Vec<String>,
    pub suffixes: Vec<String>,
    pub random_strings: usize,
}

pub fn parse_args() -> clap::ArgMatches {
    Command::new("Roblox Channel Checker")
        .version("2.0")
        .author("Your Name <youremail@example.com>")
        .about("Checks for valid Roblox channels based on wordlist combinations and analyzes channel patterns")
        .arg(
            Arg::new("input")
                .short('i')
                .long("input")
                .value_name("FILE")
                .help("Sets a custom input wordlist file")
        )
        .arg(
            Arg::new("output")
                .short('o')
                .long("output")
                .value_name("FILE")
                .help("Sets an output file to save results in JSON format")
        )
        .arg(
            Arg::new("concurrency")
                .short('c')
                .long("concurrency")
                .value_name("NUMBER")
                .help("Sets the level of concurrency for HTTP requests")
                .default_value("8"),
        )
        .arg(
            Arg::new("timeout")
                .short('t')
                .long("timeout")
                .value_name("SECONDS")
                .help("Sets the timeout for HTTP requests in seconds")
                .default_value("10"),
        )
        .arg(
            Arg::new("verbose")
                .short('v')
                .long("verbose")
                .help("Increases verbosity level")
        )
        .arg(
            Arg::new("prefixes")
                .short('p')
                .long("prefixes")
                .value_name("PREFIXES")
                .help("Comma-separated list of prefixes to add to words")
        )
        .arg(
            Arg::new("suffixes")
                .short('s')
                .long("suffixes")
                .value_name("SUFFIXES")
                .help("Comma-separated list of suffixes to add to words")
        )
        .arg(
            Arg::new("random-strings")
                .short('r')
                .long("random-strings")
                .value_name("NUMBER")
                .help("Number of random alphanumeric strings to generate and add to the wordlist")
        )
        .get_matches()
}

Make this whole program waaaay more visually advanced.