#![warn(unused_extern_crates)] #![allow(clippy::needless_return)] #![allow(static_mut_refs)] mod cmds { pub mod eight_ball; pub mod embed; pub mod help; pub mod send; pub mod wwrps; } mod re_cmds { pub mod add; pub mod approve; pub mod generic_fns; pub mod get; pub mod remove; pub mod shorturl; pub mod top; pub mod update; pub mod vote; } mod debug_cmds { pub mod guild_invite; pub mod leave_guild; pub mod main_cmd; pub mod stop; pub mod ping; pub mod reload_cfg; pub mod view_guilds; pub mod whoami; } mod db_cmds { pub mod add_server; pub mod reddit_channel; pub mod main_cmd; pub mod wwrps_channel; } mod events; mod messages; mod python; mod macros; #[allow(unknown_lints)] mod websocket; mod data; mod schedule; mod gen; use std::process; use std::thread; use std::time::Duration; use std::vec; use std::error::Error as StdErr; use clap::Parser; use r#gen::gen_bot; use r#gen::gen_data; use poise::Command; use schedule::run_schedules; use serde::Serialize; use serde_json::Value; use tokio::runtime::Runtime; use tokio::sync::Mutex; use websocket::send_cmd_json; use crate::cmds::wwrps::RPSGame; use crate::data::get_toml_mutex; use crate::schedule::Schedule; #[derive(Parser, Serialize, Clone)] struct Args { #[arg(short = 'p', long, default_value = "2920", help = "Sets the port number, e.g 2200.")] port: u16, #[arg(long, help = "Runs only the Python part of the program.")] py: bool, #[arg(long, help = "Runs only the Rust part of the program.")] rs: bool, #[arg(short = 'd', long, help = "Enables dev mode. Dev mode shows more debug info and turns off certain security measures.")] dev: bool, #[arg(short = 'w', long, help = "Wipes all data before running the program.")] wipe: bool, #[arg(short = 't', long, help = "Makes the program use the ASSISTANT_TOKEN_TEST env var instead of ASSISTANT_TOKEN. This env var should hold the token of a non-production bot.")] test: bool, #[arg(long, help = "Adds annoying prints when the websockets send a ping. Why though?")] ping: bool, #[arg(long, help = "Makes the program not use the schedule system.")] nosched: bool } type Error = Box; type Context<'a> = poise::Context<'a, Data, Error>; type Cmd = Command>; struct Data { owners: Vec, ball_prompts: [Vec; 2], rps_game: Mutex, reddit_data: Mutex>, discord_data: Mutex>, cfg: Mutex>, bk_mods: Vec, args: Args, } static CFG_DATA_RE: &str = "posts"; pub static mut LANG_NAME: Option = None; pub static mut LANG: Option = None; pub static mut NOPING: bool = false; #[tokio::main] async fn main() { let args = ::parse(); let args_str = serde_json::to_string(&args).expect("Error serializing args to JSON"); unsafe { NOPING = !args.ping; } let own_env = std::env::var("ASSISTANT_OWNERS").unwrap_or("0".to_string()); let own_vec_str: Vec = own_env.split(",").map(String::from).collect(); let own_vec_u64: Vec = own_vec_str .iter() .map(|s| s.parse::().expect("Failed to parse ASSISTANT_OWNERS. Invalid syntax.")) .collect(); rs_println!("Generating and/or fetching data and config..."); let data = gen_data(args.clone(), own_vec_u64.clone()).await; rs_println!("Fetching language file..."); let data_binding = get_toml_mutex(&data.cfg).await.unwrap(); let lang_cfg = data_binding["general"]["lang"].as_str().unwrap(); data::load_lang_data(lang_cfg.to_string()); rs_println!("[IMPORTANT] The below message is a test message, it should be written in the language you've selected\nTest message: {}", lang!("log_lang_load_success")); if args.test { println!("----- USING TEST BOT -----"); } if args.dev { println!("----- DEV MODE ENABLED -----"); } if args.dev && args.wipe { println!("----- \"DON'T WORRY ABOUT IT\" MODE ENABLED -----"); } if args.nosched { println!("----- NO SCHEDULES -----"); } if args.py && !args.rs { println!("----- PYTHON ONLY MODE -----"); rs_println!("ARGS: {}", args_str); let _ = python::start(args).await; process::exit(0); } else if args.rs && ! args.py { println!("----- RUST ONLY MODE -----"); rs_println!("ARGS: {}", args_str); start(args, data).await; process::exit(0); } rs_println!("ARGS: {}", args_str); let cfg = get_toml_mutex(&data.cfg).await.unwrap(); let cfg_arr = cfg["commands"]["disabled_categories"].as_array().unwrap(); let run_py = !cfg_arr.iter().any(|val| val.as_str() == Some("re")); let rt_rs = Runtime::new().unwrap(); let rt_py = Runtime::new().unwrap(); let python_args = args.clone(); let rust_args = args.clone(); if !run_py { rs_println!("[IMPORTANT] You have disabled the \"re\" commands in the CFG. The app will not run the Python code and the websockets to save resources!"); } let rust = thread::spawn(move || { rt_rs.block_on(async { if run_py { websocket::start(rust_args.clone(), own_vec_u64.clone()).await; } start(rust_args, data).await; }); }); let python = thread::spawn(move || { rt_py.block_on(async { if run_py { let _ = python::start(python_args).await; } }); }); if !args.nosched { let dur = if args.test { Duration::from_secs(60) } else { Duration::from_secs(60 * 10) }; let schedules: Vec = vec![ (dur, || Box::pin(read_reddit_inbox())) ]; run_schedules(schedules).await; } rust.join().unwrap(); python.join().unwrap(); } async fn start(args: Args, data: Data) { let mut bot = gen_bot(data, args).await; rs_println!("Starting Discord bot..."); bot.start().await.unwrap(); } async fn read_reddit_inbox() { unsafe { if !websocket::HAS_CONNECTED { return; } send_cmd_json("respond_mentions", None, !NOPING).await; } }