This commit is contained in:
liyunze 2025-12-30 22:57:20 +08:00
parent b73cb21a94
commit 1673087e07
9 changed files with 202 additions and 4 deletions

62
Cargo.lock generated
View file

@ -375,6 +375,18 @@ dependencies = [
"version_check",
]
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fastrand"
version = "2.3.0"
@ -403,6 +415,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foreign-types"
version = "0.3.2"
@ -572,12 +590,30 @@ version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "hashlink"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [
"hashbrown 0.15.5",
]
[[package]]
name = "http"
version = "1.4.0"
@ -884,6 +920,17 @@ version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]]
name = "libsqlite3-sys"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
@ -1140,6 +1187,7 @@ dependencies = [
"dotenv",
"poise",
"reqwest",
"rusqlite",
"serde",
"serde_json",
"serenity",
@ -1419,6 +1467,20 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rusqlite"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
name = "rustc-hash"
version = "2.1.1"

View file

@ -10,4 +10,5 @@ reqwest = "0.12.24"
serde_json = "1.0.145"
serenity = "0.12.5"
tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread"] }
serde = "1.0.228"
serde = "1.0.228"
rusqlite = { version = "0.37.0", features = ["bundled"] }

9
README.md Normal file
View file

@ -0,0 +1,9 @@
Portfolio roast bot
---
To do:
- [ ] Database
- [ ] FAQs
- [ ] Slash commands
- [ ] Portfolio Roast Forum: One tag only

BIN
bot_data.sqlite Normal file

Binary file not shown.

View file

@ -1 +1,2 @@
pub mod faq;
pub mod info;

24
src/commands/faq.rs Normal file
View file

@ -0,0 +1,24 @@
use crate::{Context, Error};
#[poise::command(slash_command, subcommands("add", "child2"))]
pub async fn faq(_ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
/// A subcommand of `parent`
#[poise::command(
prefix_command,
slash_command,
required_permissions = "MANAGE_MESSAGES" // mod only
)]
pub async fn add(ctx: Context<'_>) -> Result<(), Error> {
ctx.say("You invoked the first child command!").await?;
Ok(())
}
/// Another subcommand of `parent`
#[poise::command(prefix_command, slash_command)]
pub async fn child2(ctx: Context<'_>) -> Result<(), Error> {
ctx.say("You invoked the second child command!").await?;
Ok(())
}

1
src/database.rs Normal file
View file

@ -0,0 +1 @@
pub mod database;

51
src/database/database.rs Normal file
View file

@ -0,0 +1,51 @@
use std::path::Path;
// Using SQLite
use rusqlite::{Connection, OpenFlags, Result};
pub fn init_db(filename: &str) -> Result<Connection, rusqlite::Error> {
let filepath = Path::new(filename);
let open_flags = OpenFlags::default();
let conn = Connection::open_with_flags(filepath, open_flags);
conn
}
fn table_exists(conn: &Connection, table_name: &str) -> Result<bool> {
let exists: bool = conn.query_row(
&format!(
"SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type='table' AND name='{}')",
table_name
),
[],
|row| row.get(0),
)?;
Ok(exists)
}
pub fn init_tables(conn: &Connection) -> Result<()> {
// FAQ table
// ID: Primary Key
// FAQ title: string
// FAQ description: string
let faq_table_exists = table_exists(conn, "faqs").expect("FAQ table exists error");
if !faq_table_exists {
conn.execute(
"CREATE TABLE faqs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT NOT NULL
)",
(),
)
.expect("FAQ Table Creation error");
}
// if we need to create more tables we'll use this function
Ok(())
}

View file

@ -1,14 +1,38 @@
use dotenv;
use poise::serenity_prelude as serenity;
use rusqlite::Connection;
use std::sync::Arc;
use tokio::sync::Mutex;
mod commands;
mod database;
mod events;
struct Data {}
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>;
pub struct Data {
conn: Arc<Mutex<Connection>>,
}
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,
@ -24,9 +48,33 @@ async fn event_handler(
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("bot_data.sqlite").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();
@ -36,12 +84,13 @@ async fn main() {
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 {})
Ok(Data { conn })
})
})
.build();