use dotenv; use poise::serenity_prelude as serenity; use rusqlite::Connection; use std::{collections::HashMap, sync::Arc, time::Instant}; use tokio::sync::Mutex; mod commands; mod database; mod events; mod utils; use crate::database::database::{init_db, init_tables}; // Types used by all command functions type Error = Box; type Context<'a> = poise::Context<'a, Data, Error>; #[derive(Debug)] pub struct Data { conn: Arc>, thread_cooldowns: Arc>>, } impl Data { // helper method to safely execute database operations pub async fn execute_db(&self, f: F) -> Result where F: FnOnce(&Connection) -> Result + Send + 'static, R: Send + 'static, { let conn = self.conn.clone(); tokio::task::spawn_blocking(move || { let conn = conn.blocking_lock(); f(&conn) }) .await? .map_err(|e| e.into()) } } async fn event_handler( ctx: &serenity::Context, event: &serenity::FullEvent, _framework: poise::FrameworkContext<'_, Data, Error>, data: &Data, ) -> Result<(), Error> { match event { serenity::FullEvent::Ready { data_about_bot, .. } => { events::ready::on_ready(ctx, data_about_bot).await?; } serenity::FullEvent::ThreadCreate { thread } => { events::thread_create::on_thread_create(ctx, thread, data).await?; } _ => {} } Ok(()) } async fn on_error(error: poise::FrameworkError<'_, Data, Error>) { // This is our custom error handler // They are many errors that can occur, so we only handle the ones we want to customize // and forward the rest to the default handler match error { poise::FrameworkError::Setup { error, .. } => panic!("Failed to start bot: {:?}", error), poise::FrameworkError::Command { error, ctx, .. } => { println!("Error in command `{}`: {:?}", ctx.command().name, error,); } error => { if let Err(e) = poise::builtins::on_error(error).await { println!("Error while handling error: {}", e) } } } } #[tokio::main] async fn main() { dotenv::dotenv().ok(); // SQLite let conn = init_db().expect("Failed to start database"); init_tables(&conn).expect("Failed to init tables"); let conn = Arc::new(Mutex::new(conn)); // Discord bot let token = std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN"); let intents = serenity::GatewayIntents::non_privileged(); let framework = poise::Framework::builder() .options(poise::FrameworkOptions { commands: vec![ commands::info::help(), commands::faq::faq(), commands::forum::forum(), commands::lock::lock(), ], event_handler: |ctx, event, framework, data| { Box::pin(event_handler(ctx, event, framework, data)) }, on_error: |error| Box::pin(on_error(error)), ..Default::default() }) .setup(|ctx, _ready, framework| { Box::pin(async move { poise::builtins::register_globally(ctx, &framework.options().commands).await?; Ok(Data { conn, thread_cooldowns: Arc::new(Mutex::new(HashMap::new())), }) }) }) .build(); let client = serenity::ClientBuilder::new(token, intents) .framework(framework) .await; client.unwrap().start().await.unwrap(); }