added utility: auto-response cmd

This commit is contained in:
2025-12-20 20:04:58 +05:30
parent 07f101a119
commit 6e3f20ef02
4 changed files with 262 additions and 1 deletions

View File

@@ -1,2 +1,3 @@
pub mod level;
pub mod fun;
pub mod level;
pub mod utility;

252
src/commands/utility.rs Normal file
View File

@@ -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<Client>;
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AutoResponse {
pub response: String,
pub mention_reply: bool,
}
#[derive(Deserialize)]
struct GuildRecord {
auto_responses: Option<HashMap<String, AutoResponse>>,
}
#[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<bool>,
) -> 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<serde::de::IgnoredAny> = 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<GuildRecord> = 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<GuildRecord> = 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<serde::de::IgnoredAny> = 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<String>,
#[description = "Reply to the user?"] new_mention_reply: Option<bool>,
) -> Result<(), Error> {
let guild_id = ctx
.guild_id()
.ok_or_else(|| Error::msg("Guild only command"))?;
let db = &ctx.data().db;
let record: Option<GuildRecord> = 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<serde::de::IgnoredAny> = 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::<DbKey>() {
Some(db) => db,
None => {
tracing::error!("Database connection not found in context data for utility");
return Ok(());
}
};
let record: Option<GuildRecord> = 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(())
}

View File

@@ -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(

View File

@@ -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::<commands::level::DbKey>(db.clone());
data.insert::<commands::utility::DbKey>(db.clone());
}
if let Err(why) = client.start_autosharded().await {