231 lines
6.9 KiB
Rust
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);
|
|
}
|
|
}
|