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; } #[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", 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, ) -> 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", 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 = 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 = 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, #[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(()) } #[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::() .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::() { 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(()) }