From 6e3f20ef021f497919c3b81fe0aa255793f24072 Mon Sep 17 00:00:00 2001 From: Kishor Ramanan Date: Sat, 20 Dec 2025 20:04:58 +0530 Subject: [PATCH] added utility: auto-response cmd --- src/commands/mod.rs | 3 +- src/commands/utility.rs | 252 ++++++++++++++++++++++++++++++++++++++++ src/listener.rs | 3 + src/main.rs | 5 + 4 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 src/commands/utility.rs diff --git a/src/commands/mod.rs b/src/commands/mod.rs index be98615..a80fb0b 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,2 +1,3 @@ -pub mod level; pub mod fun; +pub mod level; +pub mod utility; diff --git a/src/commands/utility.rs b/src/commands/utility.rs new file mode 100644 index 0000000..a598cf7 --- /dev/null +++ b/src/commands/utility.rs @@ -0,0 +1,252 @@ +use crate::{Context, Error}; +use poise::serenity_prelude as serenity; +use serde::{Deserialize, Serialize}; +use serenity::prelude::TypeMapKey; +use std::collections::HashMap; +use surrealdb::Surreal; +use surrealdb::engine::remote::ws::Client; + +pub struct DbKey; + +impl TypeMapKey for DbKey { + type Value = Surreal; +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct AutoResponse { + pub response: String, + pub mention_reply: bool, +} + +#[derive(Deserialize)] +struct GuildRecord { + auto_responses: Option>, +} + +#[poise::command( + slash_command, + prefix_command, + guild_only, + default_member_permissions = "MANAGE_MESSAGES" +)] +pub async fn auto_response( + ctx: Context<'_>, + #[description = "The trigger message"] msg: String, + #[description = "The response message"] response: String, + #[description = "Reply to the user? (default: false)"] mention_reply: Option, +) -> Result<(), Error> { + let guild_id = ctx + .guild_id() + .ok_or_else(|| Error::msg("Guild only command"))?; + let db = &ctx.data().db; + + let mention_reply = mention_reply.unwrap_or(false); + let auto_response = AutoResponse { + response: response.clone(), + mention_reply, + }; + + let _: Option = db + .update(("guilds", guild_id.to_string())) + .merge(serde_json::json!({ + "auto_responses": { + msg.clone(): auto_response + } + })) + .await?; + + ctx.say(format!( + "Auto-response configured: `{}` -> `{}` (Reply: {})", + msg, response, mention_reply + )) + .await?; + + Ok(()) +} + +#[poise::command( + slash_command, + prefix_command, + guild_only, + default_member_permissions = "MANAGE_MESSAGES" +)] +pub async fn view_auto_responses(ctx: Context<'_>) -> Result<(), Error> { + let guild_id = ctx + .guild_id() + .ok_or_else(|| Error::msg("Guild only command"))?; + let db = &ctx.data().db; + + let record: Option = db.select(("guilds", guild_id.to_string())).await?; + + let mut response = String::new(); + + if let Some(record) = record { + if let Some(responses) = record.auto_responses { + if responses.is_empty() { + response = "No auto-responses configured.".to_string(); + } else { + response.push_str("**Auto-Responses:**\n"); + for (trigger, data) in responses { + response.push_str(&format!( + "- `{}` -> `{}` (Reply: {})\n", + trigger, data.response, data.mention_reply + )); + } + } + } else { + response = "No auto-responses configured.".to_string(); + } + } else { + response = "No configuration found for this guild.".to_string(); + } + + ctx.say(response).await?; + + Ok(()) +} + +#[poise::command( + slash_command, + prefix_command, + guild_only, + default_member_permissions = "MANAGE_MESSAGES" +)] +pub async fn delete_auto_response( + ctx: Context<'_>, + #[description = "The trigger message to delete"] msg: String, +) -> Result<(), Error> { + let guild_id = ctx + .guild_id() + .ok_or_else(|| Error::msg("Guild only command"))?; + let db = &ctx.data().db; + + // To delete a key from a map in SurrealDB using merge, we might need to fetch, modify, and update, + // or use a specific unset operation if supported via JSON merge patch or similar. + // SurrealDB merge with `null` value for a key usually removes it? No, that sets it to null. + // We should probably fetch the current map, remove the key, and update. + + // Actually, let's try to fetch, modify locally, and update. + let record: Option = db.select(("guilds", guild_id.to_string())).await?; + + if let Some(record) = record { + if let Some(mut responses) = record.auto_responses { + if responses.remove(&msg).is_some() { + let _: Option = db + .update(("guilds", guild_id.to_string())) + .merge(serde_json::json!({ + "auto_responses": responses + })) + .await?; + ctx.say(format!("Auto-response for `{}` deleted.", msg)) + .await?; + } else { + ctx.say(format!("No auto-response found for `{}`.", msg)) + .await?; + } + } else { + ctx.say("No auto-responses configured.").await?; + } + } else { + ctx.say("No configuration found for this guild.").await?; + } + + Ok(()) +} + +#[poise::command( + slash_command, + prefix_command, + guild_only, + default_member_permissions = "MANAGE_MESSAGES" +)] +pub async fn edit_auto_response( + ctx: Context<'_>, + #[description = "The trigger message to edit"] msg: String, + #[description = "The new response message"] new_response: Option, + #[description = "Reply to the user?"] new_mention_reply: Option, +) -> Result<(), Error> { + let guild_id = ctx + .guild_id() + .ok_or_else(|| Error::msg("Guild only command"))?; + let db = &ctx.data().db; + + let record: Option = db.select(("guilds", guild_id.to_string())).await?; + + if let Some(record) = record { + if let Some(mut responses) = record.auto_responses { + let (response_str, mention_reply_bool) = { + if let Some(data) = responses.get_mut(&msg) { + if let Some(response) = new_response { + data.response = response; + } + if let Some(mention_reply) = new_mention_reply { + data.mention_reply = mention_reply; + } + (data.response.clone(), data.mention_reply) + } else { + ctx.say(format!("No auto-response found for `{}`.", msg)) + .await?; + return Ok(()); + } + }; + + let _: Option = db + .update(("guilds", guild_id.to_string())) + .merge(serde_json::json!({ + "auto_responses": responses + })) + .await?; + + ctx.say(format!( + "Auto-response for `{}` updated: `{}` (Reply: {})", + msg, response_str, mention_reply_bool + )) + .await?; + } else { + ctx.say("No auto-responses configured.").await?; + } + } else { + ctx.say("No configuration found for this guild.").await?; + } + + Ok(()) +} + +pub async fn process_auto_response( + ctx: &serenity::Context, + msg: &serenity::Message, +) -> Result<(), Error> { + if msg.author.bot { + return Ok(()); + } + + let guild_id = match msg.guild_id { + Some(id) => id, + None => return Ok(()), + }; + + let data = ctx.data.read().await; + let db = match data.get::() { + Some(db) => db, + None => { + tracing::error!("Database connection not found in context data for utility"); + return Ok(()); + } + }; + + let record: Option = db.select(("guilds", guild_id.to_string())).await?; + + if let Some(record) = record { + if let Some(responses) = record.auto_responses { + if let Some(auto_response) = responses.get(&msg.content) { + if auto_response.mention_reply { + msg.reply(ctx, &auto_response.response).await?; + } else { + msg.channel_id.say(ctx, &auto_response.response).await?; + } + } + } + } + + Ok(()) +} diff --git a/src/listener.rs b/src/listener.rs index a45bd51..bcc933a 100644 --- a/src/listener.rs +++ b/src/listener.rs @@ -32,6 +32,9 @@ impl EventHandler for Handler { if let Err(e) = crate::commands::level::process_message(&ctx, &msg).await { tracing::error!("Error processing message for leveling: {}", e); } + if let Err(e) = crate::commands::utility::process_auto_response(&ctx, &msg).await { + tracing::error!("Error processing message for auto-response: {}", e); + } } async fn guild_member_update( diff --git a/src/main.rs b/src/main.rs index 6b00232..b36f72a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,6 +61,10 @@ async fn main() -> Result<(), Error> { commands::level::set_levelup_message(), commands::level::levelup_role_bridger(), commands::fun::say(), + commands::utility::auto_response(), + commands::utility::view_auto_responses(), + commands::utility::delete_auto_response(), + commands::utility::edit_auto_response(), ], prefix_options: poise::PrefixFrameworkOptions { prefix: Some("!".into()), @@ -88,6 +92,7 @@ async fn main() -> Result<(), Error> { { let mut data = client.data.write().await; data.insert::(db.clone()); + data.insert::(db.clone()); } if let Err(why) = client.start_autosharded().await {