sillycord-bot/src/main.rs

231 lines
6.9 KiB
Rust

mod commands;
mod events;
mod handlers;
mod structs;
mod utils;
use std::sync::Arc;
use events::event_handler;
use handlers::db::DatabaseController;
use serde::{Deserialize, Serialize};
use serenity::{
all::{ClientBuilder, GatewayIntents},
futures::lock::Mutex,
};
use sqlx::mysql::MySqlPoolOptions;
use sqlx::MySqlPool;
use structs::vouch::Vouch;
use tracing::{event, info, info_span, Level};
fn check_required_env_vars() {
let env_span = info_span!("check_required_env_vars");
let required_vars = vec!["DATABASE_URL", "BOT_TOKEN"];
// Enter into the span then check the required environment variables
let _enter = env_span.enter();
for var in required_vars {
info!("checking {}", var);
if std::env::var(var).is_err() {
event!(
Level::ERROR,
"required environment variable {} is not set",
var
);
panic!(
"required environment variable {} is not set, cannot continue",
var
);
}
}
info!("all required environment variables are set");
// Exit the span
drop(_enter);
}
static MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!("./migrations");
async fn init_sqlx() -> MySqlPool {
let sqlx_span = info_span!("init_sqlx");
// Enter into the span then initialize SQLx
let _enter = sqlx_span.enter();
// Create a pooled connection to the database
info!("creating a database connection pool");
let pool = MySqlPoolOptions::new()
.max_connections(5)
.connect(&std::env::var("DATABASE_URL").unwrap())
.await
.expect("failed to create a database connection pool");
info!("database connection pool created");
// Ensure database schema is up to date
info!("running database migrations");
MIGRATOR
.run(&pool)
.await
.expect("Migrations did not succeed");
info!("database migrations completed");
info!("SQLx initialized");
// Exit the span
drop(_enter);
pool
}
struct Data {
database_controller: DatabaseController,
owners: Vec<u64>,
uptime: std::time::Instant,
config: Config,
vouch_store: Mutex<Vec<Vouch>>,
} // User data, which is stored and accessible in all command invocations
pub type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
type Context<'a> = poise::Context<'a, Data, Error>;
#[derive(Deserialize, Serialize)]
struct Config {
main_guild_id: u64,
channels: Channels,
roles: Roles,
}
#[derive(Deserialize, Serialize)]
struct Channels {
welcome: u64,
main: u64,
logs_public: u64,
logs_mod: u64,
starboard: u64,
}
#[derive(Deserialize, Serialize)]
struct Roles {
admin: u64,
silly_role: u64,
}
#[tokio::main]
async fn main() {
let start_time = std::time::Instant::now();
tracing_subscriber::fmt::init();
let init_span = info_span!("init");
let main_span = info_span!("main");
let _enter = init_span.enter();
info!("loading dotenv");
dotenv::dotenv().ok();
info!("checking required environment variables");
check_required_env_vars();
info!("loading config");
// Do we have a config.toml file? If we do, load it
// If we don't, create it with the default values, then exit the program and tell the user to fill it out
let config: Config = match std::fs::read_to_string("config.toml") {
Ok(config) => toml::from_str(&config).expect("failed to parse config.toml"),
Err(_) => {
let default_config = Config {
main_guild_id: 0,
channels: Channels {
welcome: 0,
main: 0,
logs_public: 0,
logs_mod: 0,
starboard: 0,
},
roles: Roles {
admin: 0,
silly_role: 0,
},
};
let default_config_toml = toml::to_string_pretty(&default_config).unwrap();
std::fs::write("config.toml", default_config_toml)
.expect("failed to write config.toml");
event!(Level::WARN, "config.toml not found, created a default one");
event!(
Level::ERROR,
"please fill out config.toml and restart the bot"
);
panic!("config.toml not found, created a default one, please fill it out and restart the bot");
}
};
info!("initializing SQLx");
let pool = init_sqlx().await;
info!("initializing bot");
let intents = GatewayIntents::privileged()
| GatewayIntents::GUILD_MEMBERS
| GatewayIntents::GUILD_MESSAGES;
let framework = poise::Framework::builder()
.options(poise::FrameworkOptions::<Data, Error> {
commands: vec![
commands::ping::ping(),
commands::vouch::vouch(),
commands::profile::profiles(),
commands::dog::dog(),
commands::cta::cta(),
commands::action::use_action_hug(),
commands::action::use_action_kiss(),
commands::action::use_action_pat(),
commands::eval::eval(),
commands::quote::quote_action(),
commands::quote::random_quote(),
commands::quote::user_quotes(),
],
event_handler: |ctx, event, framework, data| {
Box::pin(event_handler(ctx, event, framework, data))
},
prefix_options: poise::PrefixFrameworkOptions {
prefix: Some("~".into()),
edit_tracker: Some(Arc::new(poise::EditTracker::for_timespan(
std::time::Duration::from_secs(3600),
))),
case_insensitive_commands: true,
..Default::default()
},
..Default::default()
})
.setup(|ctx, _ready, framework| {
Box::pin(async move {
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
Ok(Data {
// Initialize user data here
database_controller: DatabaseController::new(pool.clone()),
uptime: std::time::Instant::now(),
config,
vouch_store: Mutex::new(Vec::new()),
// Sticks, Emi, Katie
owners: vec![1017196087276220447, 272871217256726531, 1033331958291369984],
})
})
})
.build();
let mut client = ClientBuilder::new(std::env::var("BOT_TOKEN").unwrap(), intents)
.framework(framework)
.await
.expect("Error creating client");
info!("bot initialized");
drop(_enter);
let _enter = main_span.enter();
info!("init done in {:?}", start_time.elapsed());
info!("starting bot");
if let Err(why) = client.start().await {
event!(Level::ERROR, "Client error: {:?}", why);
}
}