Files
void-sentinel/src/commands/utility.rs
2026-01-24 02:24:48 +05:30

311 lines
9.3 KiB
Rust

use crate::commands::fun::AiChatKey;
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",
required_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",
required_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",
required_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;
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() {
db.query("UPDATE type::thing($thing) SET auto_responses = $responses")
.bind(("thing", ("guilds", guild_id.to_string())))
.bind(("responses", responses))
.await?
.check()?;
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",
required_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(())
}
#[poise::command(slash_command, prefix_command, guild_only)]
pub async fn summary(
ctx: Context<'_>,
#[description = "Message to summarize (reply to one or provide message link)"] message: Option<
serenity::Message,
>,
) -> Result<(), Error> {
if let poise::Context::Application(app_ctx) = ctx {
app_ctx.defer().await?;
}
let msg = if let Some(m) = message {
m
} else if let poise::Context::Prefix(prefix_ctx) = ctx {
// For prefix, check if replying to a message
if let Some(reply) = prefix_ctx.msg.referenced_message.as_ref() {
(**reply).clone()
} else {
ctx.say("You must reply to a message or provide a message to summarize.")
.await?;
return Ok(());
}
} else {
ctx.say("You must reply to a message or provide a message to summarize.")
.await?;
return Ok(());
};
let data = ctx.serenity_context().data.read().await;
let ai_chat_manager = data
.get::<AiChatKey>()
.cloned()
.ok_or_else(|| anyhow::anyhow!("AI Chat manager not found"))?;
let content = msg.content.trim();
if content.is_empty() {
ctx.say("The message is empty, nothing to summarize.")
.await?;
return Ok(());
}
let prompt = format!("Summarize this message in 1-2 sentences: {}", content);
match ai_chat_manager.query_ollama_direct(&prompt).await {
Ok(summary) => {
ctx.say(format!(
"**Summary of message by {}:**\n{}",
msg.author.name, summary
))
.await?;
}
Err(err) => {
tracing::error!(error = %err, "Failed to summarize message");
ctx.say("Failed to generate summary. Try again later.")
.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(())
}