From bb4707ad593aa2a0c52243d8e46b07d91c20d353 Mon Sep 17 00:00:00 2001 From: liyunze <50455574+liyunze-coding@users.noreply.github.com> Date: Fri, 25 Sep 2026 12:59:15 +0800 Subject: [PATCH] added lock commands --- src/commands.rs | 1 + src/commands/faq.rs | 49 +++---- src/commands/lock.rs | 281 +++++++++++++++++++++++++++++++++++++++ src/database/database.rs | 14 ++ src/main.rs | 2 + src/utils.rs | 1 + src/utils/format.rs | 6 + 7 files changed, 331 insertions(+), 23 deletions(-) create mode 100644 src/commands/lock.rs create mode 100644 src/utils.rs create mode 100644 src/utils/format.rs diff --git a/src/commands.rs b/src/commands.rs index 619a5f7..c1c2ca4 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,3 +1,4 @@ pub mod faq; pub mod forum; pub mod info; +pub mod lock; diff --git a/src/commands/faq.rs b/src/commands/faq.rs index 566202d..ef6afc4 100644 --- a/src/commands/faq.rs +++ b/src/commands/faq.rs @@ -1,3 +1,4 @@ +use crate::utils::format::format_embed_description; use crate::{Context, Error}; use futures::{Stream, StreamExt}; use poise::CreateReply; @@ -10,11 +11,6 @@ struct Faq { description: String, } -fn format_embed_description>(description: S) -> Result { - let string_val = description.into(); - Ok(str::replace(&string_val, "\\n", "\n")) -} - fn faq_exists(conn: &Connection, faq: &str, guild_id: &u64) -> Result { let guild_id = guild_id.to_string(); let exists: bool = conn.query_row( @@ -93,26 +89,33 @@ pub async fn view( let guild_id = server_guild_id.to_string(); move |conn| { - let mut stmt = conn.prepare( - "SELECT id, guild_id, title, description FROM faqs WHERE title = ?1 AND guild_id = ?2" - )?; + let mut stmt = conn.prepare( + "SELECT title, description FROM faqs WHERE title = ?1 AND guild_id = ?2", + )?; - let faqs: Vec = stmt - .query_map([&faq_title, &guild_id], |row| { - Ok(Faq { - // id: row.get(0)?, - // guild_id: row.get(1)?, - title: row.get(2)?, - description: row.get(3)?, - }) - })? - .collect::, _>>()?; + let faqs: Vec = stmt + .query_map([&faq_title, &guild_id], |row| { + Ok(Faq { + title: row.get(0)?, + description: row.get(1)?, + }) + })? + .collect::, _>>()?; - Ok(faqs) - }}) + Ok(faqs) + } + }) .await?; - let faq = faqs.first().unwrap(); + let Some(faq) = faqs.first() else { + let embed = CreateEmbed::new() + .title("Error 404") + .description("FAQ not found"); + + ctx.send(CreateReply::default().ephemeral(true).embed(embed)) + .await?; + return Ok(()); + }; let embed_description = format_embed_description(&faq.description).expect("Format error"); let embed = CreateEmbed::new() @@ -143,6 +146,7 @@ pub async fn preview( let server_guild_id = ctx.guild_id().expect("Guild ID not found"); let guild_id = server_guild_id.to_string(); + move |conn| { let mut stmt = conn.prepare( "SELECT id, guild_id, title, description FROM faqs WHERE title = ?1 AND guild_id = ?2" @@ -163,9 +167,8 @@ pub async fn preview( }}) .await?; - let faq = faqs.first().unwrap(); + let faq = faqs.first().expect("FAQ not found"); - // let embed_description = format_embed_description(&faq.description).expect("Format error"); let embed = CreateEmbed::new().title(&faq.title).description(format!( "``` {} diff --git a/src/commands/lock.rs b/src/commands/lock.rs new file mode 100644 index 0000000..d4cede1 --- /dev/null +++ b/src/commands/lock.rs @@ -0,0 +1,281 @@ +use crate::utils::format::format_embed_description; +use crate::{Context, Error}; +use poise::CreateReply; +use rusqlite::Connection; +use serenity::all::CreateEmbed; +use serenity::builder::EditThread; + +struct LockEmbed { + title: String, + description: String, +} + +fn lock_message_exists(conn: &Connection, guild_id: &u64) -> Result { + let guild_id = guild_id.to_string(); + let exists: bool = conn.query_row( + "SELECT EXISTS(SELECT 1 FROM lock WHERE guild_id = ?1)", + [&guild_id], + |row| row.get(0), + )?; + + Ok(exists) +} + +#[poise::command( + slash_command, + subcommands("vibecoded", "preview", "add", "edit", "delete"), + guild_only +)] +pub async fn lock(_ctx: Context<'_>) -> Result<(), Error> { + Ok(()) +} + +/// Post the vibe coded slop embed message +#[poise::command( + prefix_command, + slash_command, + required_permissions = "MANAGE_MESSAGES" // mod only +)] +pub async fn vibecoded( + ctx: Context<'_>, + #[description = "additional comment"] comment_option: Option, +) -> Result<(), Error> { + let data = ctx.data(); + + // lock thread, if it's a thread in a forum + + // let thread_lock = EditThread::new().locked(true); + let mut channel = ctx.guild_channel().await.expect("Not a thread"); + channel + .edit_thread(ctx, EditThread::new().locked(true)) + .await + .expect("Failed to lock thread, check permissions"); + + let lock_messages = data + .execute_db({ + let server_guild_id = ctx.guild_id().expect("Guild ID not found"); + let guild_id = server_guild_id.to_string(); + + move |conn| { + let mut stmt = + conn.prepare("SELECT title, description FROM lock WHERE guild_id = ?1")?; + + let lock_messages: Vec = stmt + .query_map([&guild_id], |row| { + Ok(LockEmbed { + title: row.get(0)?, + description: row.get(1)?, + }) + })? + .collect::, _>>()?; + + Ok(lock_messages) + } + }) + .await?; + + let Some(lock_message) = lock_messages.first() else { + let embed = CreateEmbed::new() + .title("Error 404") + .description("Lock message not found"); + + ctx.send(CreateReply::default().embed(embed).ephemeral(true)) + .await?; + return Ok(()); + }; + + let mut embed_description = + format_embed_description(&lock_message.description).expect("Format error"); + if !comment_option.is_none() { + let comment = comment_option.expect("Comment error"); + let new_description = format!( + "{}\n\n**Additional Comment(s)**\n{}", + &lock_message.description, comment + ); + embed_description = format_embed_description(new_description).expect("Format error"); + } + let embed = CreateEmbed::new() + .title(&lock_message.title) + .description(embed_description) + .color(0xff0000); + + ctx.send(CreateReply::default().embed(embed)).await?; + + Ok(()) +} + +/// Add vibe coded slop embed message +#[poise::command( + prefix_command, + slash_command, + required_permissions = "MANAGE_MESSAGES" // mod only +)] +pub async fn add( + ctx: Context<'_>, + #[description = "Lock message title"] title: String, + #[description = "Lock message description"] description: String, +) -> Result<(), Error> { + let data: &crate::Data = ctx.data(); + + let result = data + .execute_db({ + let title = title.clone(); + let description = description.clone(); + + let server_guild_id = ctx.guild_id().expect("Guild ID not found"); + let guild_id = server_guild_id.get(); + move |conn| { + let exists = lock_message_exists(conn, &guild_id).unwrap_or(false); + + if !exists { + conn.execute( + "INSERT INTO lock (guild_id, title, description) VALUES (?1, ?2, ?3)", + [guild_id.to_string(), title, description], + ) + } else { + Ok(0) + } + } + }) + .await?; + + if result == 1 { + let embed_description = format_embed_description(description).expect("Format error"); + let embed = CreateEmbed::new() + .title(format!("Added Lock Message: {}", &title)) + .description(embed_description); + ctx.send(CreateReply::default().embed(embed)).await?; + } else { + let embed = CreateEmbed::new() + .title(format!("Error adding lock message: {}", &title)) + .description("Lock message already exists!"); + ctx.send(CreateReply::default().embed(embed)).await?; + } + + Ok(()) +} + +/// Preview lock message +#[poise::command( + prefix_command, + slash_command, + required_permissions = "MANAGE_MESSAGES" // mod only +)] +pub async fn preview(ctx: Context<'_>) -> Result<(), Error> { + let data = ctx.data(); + + let lock_messages = data + .execute_db({ + let server_guild_id = ctx.guild_id().expect("Guild ID not found"); + let guild_id = server_guild_id.to_string(); + + move |conn| { + let mut stmt = + conn.prepare("SELECT title, description FROM lock WHERE guild_id = ?1")?; + + let lock_messages: Vec = stmt + .query_map([&guild_id], |row| { + Ok(LockEmbed { + title: row.get(0)?, + description: row.get(1)?, + }) + })? + .collect::, _>>()?; + + Ok(lock_messages) + } + }) + .await?; + + let lock_message = lock_messages.first().expect("Lock message not found"); + + let embed = CreateEmbed::new() + .title(&lock_message.title) + .description(format!( + "``` +{} + ```", + &lock_message.description + )); + + ctx.send(CreateReply::default().embed(embed)).await?; + + Ok(()) +} + +/// Edit lock message +#[poise::command( + prefix_command, + slash_command, + required_permissions = "MANAGE_MESSAGES" +)] +pub async fn edit( + ctx: Context<'_>, + #[description = "Title"] title: String, + #[description = "Description"] description: String, +) -> Result<(), Error> { + let data = ctx.data(); + + let result = data + .execute_db({ + let title = title.clone(); + let description = description.clone(); + + let server_guild_id = ctx.guild_id().expect("Guild ID not found"); + let guild_id = server_guild_id.get(); + + move |conn| { + conn.execute( + "UPDATE lock SET description = ?3, title = ?2 WHERE guild_id = ?1", + [guild_id.to_string(), title, description], + ) + } + }) + .await?; + + let embed_description = format_embed_description(description).expect("Format error"); + + if result == 1 { + let embed = CreateEmbed::new() + .title(format!("Edited lock message: {}", &title)) + .description(&embed_description); + ctx.send(CreateReply::default().embed(embed)).await?; + } else { + let embed = CreateEmbed::new() + .title(format!("Error editing lock message: {}", &title)) + .description(format!("{} rows were affected", result)); + ctx.send(CreateReply::default().embed(embed)).await?; + } + + Ok(()) +} + +/// Delete lock message +#[poise::command( + prefix_command, + slash_command, + required_permissions = "MANAGE_MESSAGES" +)] +pub async fn delete(ctx: Context<'_>) -> Result<(), Error> { + let data = ctx.data(); + let server_guild_id = ctx.guild_id().expect("Guild ID not found"); + let guild_id = server_guild_id.to_string(); + + let result = data + .execute_db({ + move |conn| conn.execute("DELETE FROM lock WHERE guild_id = ?1", [guild_id]) + }) + .await?; + + if result == 1 { + let embed = CreateEmbed::new().title(format!("Deleted lock message for this server")); + ctx.send(CreateReply::default().embed(embed)).await?; + } else { + let embed = CreateEmbed::new() + .title(format!("Error deleting lock message, ask Ryan")) + .description(format!("{} rows were affected", result)); + ctx.send(CreateReply::default().embed(embed)).await?; + } + + Ok(()) +} diff --git a/src/database/database.rs b/src/database/database.rs index b9e530c..3d6e1b3 100644 --- a/src/database/database.rs +++ b/src/database/database.rs @@ -62,6 +62,20 @@ pub fn init_tables(conn: &Connection) -> Result<()> { .expect("Forum Channels Table Creation error"); } + let lock_table_exists = table_exists(conn, "lock").expect("Lock table exists error"); + if !lock_table_exists { + conn.execute( + "CREATE TABLE lock ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + guild_id TEXT NOT NULL UNIQUE, + title TEXT NOT NULL, + description TEXT NOT NULL + )", + (), + ) + .expect("Lock Table Creation error"); + } + // if we need to create more tables we'll use this function Ok(()) } diff --git a/src/main.rs b/src/main.rs index a91d7dd..68b6613 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ 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 @@ -89,6 +90,7 @@ async fn main() { 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)) diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..db7b59d --- /dev/null +++ b/src/utils.rs @@ -0,0 +1 @@ +pub mod format; diff --git a/src/utils/format.rs b/src/utils/format.rs new file mode 100644 index 0000000..5fcbb2f --- /dev/null +++ b/src/utils/format.rs @@ -0,0 +1,6 @@ +use crate::Error; + +pub fn format_embed_description>(description: S) -> Result { + let string_val = description.into(); + Ok(str::replace(&string_val, "\\n", "\n")) +}