factorion-bot-discord 3.0.2

factorion-bot (for factorials and related) on Discord
#![doc = include_str!("../README.md")]
use dotenvy::dotenv;
use factorion_lib::Consts;
use factorion_lib::influxdb::INFLUX_CLIENT;
use factorion_lib::locale::Locale;
use factorion_lib::rug::integer::IntegerExt64;
use factorion_lib::rug::{Complete, Integer};
use log::{error, info, warn};
use std::collections::HashMap;
use std::error::Error;
use std::panic;

mod discord_api;

fn init() {
    dotenv().ok();
    env_logger::builder()
        .format(|buf, record| {
            use std::io::Write;
            let style = buf.default_level_style(record.level());
            writeln!(
                buf,
                "{style}{} | {} | {} | {}",
                record.level(),
                record.target(),
                buf.timestamp(),
                record.args()
            )
        })
        .init();

    panic::set_hook(Box::new(|panic_info| {
        let location = panic_info
            .location()
            .map(|l| format!("{}:{}", l.file(), l.line()))
            .unwrap_or_else(|| "unknown location".to_string());

        let message = panic_info
            .payload()
            .downcast_ref::<&str>()
            .map(|s| s.to_string())
            .or_else(|| panic_info.payload().downcast_ref::<String>().cloned())
            .unwrap_or_else(|| format!("Unknown panic payload: {panic_info:?}"));

        error!("Thread panicked at {location} with message: {message}");
    }));

    info!("factorion-lib initialized successfully");
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let args = std::env::args().collect::<Vec<_>>();
    if args.len() > 1 && (args[1] == "--version" || args[1] == "-v") {
        println!("factorion-bot-discord v{}", env!("CARGO_PKG_VERSION"));
        return Ok(());
    }

    init();

    let consts = Consts {
        float_precision: std::env::var("FLOAT_PRECISION")
            .map(|s| s.parse().unwrap())
            .unwrap_or_else(|_| factorion_lib::recommended::FLOAT_PRECISION),
        upper_calculation_limit: std::env::var("UPPER_CALCULATION_LIMIT")
            .map(|s| s.parse().unwrap())
            .unwrap_or_else(|_| factorion_lib::recommended::UPPER_CALCULATION_LIMIT()),
        upper_approximation_limit: std::env::var("UPPER_APPROXIMATION_LIMIT")
            .map(|s| Integer::u64_pow_u64(10, s.parse().unwrap()).complete())
            .unwrap_or_else(|_| factorion_lib::recommended::UPPER_APPROXIMATION_LIMIT()),
        upper_subfactorial_limit: std::env::var("UPPER_SUBFACTORIAL_LIMIT")
            .map(|s| s.parse().unwrap())
            .unwrap_or_else(|_| factorion_lib::recommended::UPPER_SUBFACTORIAL_LIMIT()),
        upper_termial_limit: std::env::var("UPPER_TERMIAL_LIMIT")
            .map(|s| Integer::u64_pow_u64(10, s.parse().unwrap()).complete())
            .unwrap_or_else(|_| factorion_lib::recommended::UPPER_TERMIAL_LIMIT()),
        upper_termial_approximation_limit: std::env::var("UPPER_TERMIAL_APPROXIMATION_LIMIT")
            .map(|s| s.parse().unwrap())
            .unwrap_or_else(|_| factorion_lib::recommended::UPPER_TERMIAL_APPROXIMATION_LIMIT),
        integer_construction_limit: std::env::var("INTEGER_CONSTRUCTION_LIMIT")
            .map(|s| s.parse().unwrap())
            .unwrap_or_else(|_| factorion_lib::recommended::INTEGER_CONSTRUCTION_LIMIT()),
        number_decimals_scientific: std::env::var("NUMBER_DECIMALS_SCIENTIFIC")
            .map(|s| s.parse().unwrap())
            .unwrap_or_else(|_| factorion_lib::recommended::NUMBER_DECIMALS_SCIENTIFIC),
        locales: std::env::var("LOCALES_DIR")
            .map(|dir| {
                let files = std::fs::read_dir(dir).unwrap();
                let mut map = HashMap::new();
                for (key, value) in files
                    .map(|file| {
                        let file = file.unwrap();
                        let mut locale: Locale<'static> = serde_json::de::from_str(
                            std::fs::read_to_string(file.path()).unwrap().leak(),
                        )
                        .unwrap();
                        locale.bot_disclaimer = "".into();
                        (file.file_name().into_string().unwrap(), locale)
                    })
                    .collect::<Box<_>>()
                {
                    map.insert(key, value);
                }
                map
            })
            .unwrap_or_else(|_| {
                factorion_lib::locale::get_all()
                    .map(|(k, mut v)| {
                        v.bot_disclaimer = "".into();
                        (k.to_owned(), v)
                    })
                    .collect()
            }),
        default_locale: "en".to_owned(),
    };

    let token = std::env::var("DISCORD_TOKEN").expect("DISCORD_TOKEN must be set in environment");

    info!("Starting Discord bot...");

    if INFLUX_CLIENT.is_none() {
        warn!("InfluxDB client not configured. No influxdb metrics will be logged.");
    } else {
        info!("InfluxDB client configured. Metrics will be logged.");
    }

    discord_api::start_bot(token, consts, &INFLUX_CLIENT).await?;

    Ok(())
}