portfolio-roast-bot/src/main.rs
2026-09-25 13:00:45 +08:00

116 lines
3.6 KiB
Rust

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<dyn std::error::Error + Send + Sync>;
type Context<'a> = poise::Context<'a, Data, Error>;
#[derive(Debug)]
pub struct Data {
conn: Arc<Mutex<Connection>>,
thread_cooldowns: Arc<Mutex<HashMap<serenity::ChannelId, Instant>>>,
}
impl Data {
// helper method to safely execute database operations
pub async fn execute_db<F, R>(&self, f: F) -> Result<R, Error>
where
F: FnOnce(&Connection) -> Result<R, rusqlite::Error> + 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();
}