diff --git a/src/commands/fun/intelligence.rs b/src/commands/fun/intelligence.rs deleted file mode 100644 index a7cd727..0000000 --- a/src/commands/fun/intelligence.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::env; - -use color_eyre::eyre::{eyre, Context as _, Result}; -use serde::Deserialize; -use serde_json::json; - -use crate::{reqwest_client::HTTP, Context}; - -static ASK_API_URL: &str = "https://intelligence.valfisk.ryanccn.dev/ask"; - -#[derive(Deserialize)] -struct AskResponse { - response: String, -} - -/// Ask Valfisk Intelligenceā„¢ -#[poise::command(slash_command, guild_only)] -#[tracing::instrument(skip(ctx), fields(channel = ctx.channel_id().get(), author = ctx.author().id.get()))] -pub async fn ask( - ctx: Context<'_>, - #[description = "The query to ask about"] query: String, -) -> Result<()> { - ctx.defer().await?; - - let secret = env::var("INTELLIGENCE_SECRET") - .wrap_err_with(|| eyre!("Valfisk Intelligence API secret is not set!"))?; - - let resp: AskResponse = HTTP - .post(ASK_API_URL) - .bearer_auth(secret) - .json(&json!({ "query": query })) - .send() - .await? - .json() - .await?; - - ctx.say(&resp.response).await?; - - Ok(()) -} diff --git a/src/commands/fun/mod.rs b/src/commands/fun/mod.rs index 12d6656..f2fe24b 100644 --- a/src/commands/fun/mod.rs +++ b/src/commands/fun/mod.rs @@ -1,4 +1,3 @@ pub mod autoreply; -pub mod intelligence; pub mod owo; pub mod shiggy; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index afc142d..804e7ff 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -34,7 +34,6 @@ pub fn to_vec() -> Vec< command!(moderation, kick), command!(moderation, timeout), command!(fun, autoreply), - command!(fun, intelligence, ask), command!(fun, owo), command!(fun, shiggy), command!(utils, ping), diff --git a/src/commands/moderation/ban.rs b/src/commands/moderation/ban.rs index 32c7938..2b81845 100644 --- a/src/commands/moderation/ban.rs +++ b/src/commands/moderation/ban.rs @@ -2,7 +2,7 @@ use color_eyre::eyre::Result; use poise::serenity_prelude as serenity; use super::LOGS_CHANNEL; -use crate::{utils::serenity::unique_username, Context}; +use crate::Context; /// Ban a member #[poise::command( @@ -70,8 +70,7 @@ pub async fn ban( if let Some(logs_channel) = *LOGS_CHANNEL { let server_embed = dm_embed.footer( - serenity::CreateEmbedFooter::new(unique_username(ctx.author())) - .icon_url(ctx.author().face()), + serenity::CreateEmbedFooter::new(ctx.author().tag()).icon_url(ctx.author().face()), ); logs_channel diff --git a/src/commands/moderation/kick.rs b/src/commands/moderation/kick.rs index abcbe3d..54dbec2 100644 --- a/src/commands/moderation/kick.rs +++ b/src/commands/moderation/kick.rs @@ -2,7 +2,7 @@ use color_eyre::eyre::Result; use poise::serenity_prelude as serenity; use super::LOGS_CHANNEL; -use crate::{utils::serenity::unique_username, Context}; +use crate::Context; /// Kick a member #[poise::command( @@ -52,8 +52,7 @@ pub async fn kick( if let Some(logs_channel) = *LOGS_CHANNEL { let server_embed = dm_embed.footer( - serenity::CreateEmbedFooter::new(unique_username(ctx.author())) - .icon_url(ctx.author().face()), + serenity::CreateEmbedFooter::new(ctx.author().tag()).icon_url(ctx.author().face()), ); logs_channel diff --git a/src/commands/moderation/timeout.rs b/src/commands/moderation/timeout.rs index 9958cb5..6e6136b 100644 --- a/src/commands/moderation/timeout.rs +++ b/src/commands/moderation/timeout.rs @@ -2,7 +2,7 @@ use color_eyre::eyre::Result; use poise::serenity_prelude as serenity; use super::LOGS_CHANNEL; -use crate::{utils::serenity::unique_username, Context}; +use crate::Context; /// Timeout a member #[poise::command( @@ -70,8 +70,7 @@ pub async fn timeout( if let Some(logs_channel) = *LOGS_CHANNEL { let server_embed = dm_embed.footer( - serenity::CreateEmbedFooter::new(unique_username(ctx.author())) - .icon_url(ctx.author().face()), + serenity::CreateEmbedFooter::new(ctx.author().tag()).icon_url(ctx.author().face()), ); logs_channel diff --git a/src/commands/useful/self_timeout.rs b/src/commands/useful/self_timeout.rs index 542186c..197e7f2 100644 --- a/src/commands/useful/self_timeout.rs +++ b/src/commands/useful/self_timeout.rs @@ -1,7 +1,7 @@ use color_eyre::eyre::{eyre, Result}; use poise::serenity_prelude as serenity; -use crate::{utils::serenity::unique_username, Context}; +use crate::Context; /// Time yourself out for a specific duration #[poise::command(rename = "self-timeout", slash_command, guild_only)] @@ -52,7 +52,7 @@ pub async fn self_timeout( .unwrap_or(false) { let mut resp_embed = resp_embed.author( - serenity::CreateEmbedAuthor::new(unique_username(ctx.author())) + serenity::CreateEmbedAuthor::new(ctx.author().tag()) .icon_url(ctx.author().face()), ); diff --git a/src/handlers/intelligence.rs b/src/handlers/intelligence.rs new file mode 100644 index 0000000..7a33d2f --- /dev/null +++ b/src/handlers/intelligence.rs @@ -0,0 +1,64 @@ +use std::time::Duration; + +use color_eyre::eyre::Result; +use poise::serenity_prelude as serenity; +use tokio::{task, time}; + +use crate::intelligence; + +#[tracing::instrument(skip_all, fields(message_id = message.id.get()))] +pub async fn handle(message: &serenity::Message, ctx: &serenity::Context) -> Result<()> { + if message + .flags + .is_some_and(|flags| flags.contains(serenity::MessageFlags::SUPPRESS_NOTIFICATIONS)) + { + return Ok(()); + } + + if let Some(query) = message + .content + .strip_prefix(&format!("<@{}>", ctx.cache.current_user().id)) + .map(|s| s.trim()) + { + if query.is_empty() { + return Ok(()); + } + + let typing_task = task::spawn({ + let http = ctx.http.clone(); + let channel = message.channel_id; + + async move { + let mut interval = time::interval(Duration::from_secs(10)); + + loop { + interval.tick().await; + let _ = http.broadcast_typing(channel).await; + } + } + }); + + let username = message.author.tag(); + let display_name = message.author.global_name.clone().map(|s| s.into_string()); + + let nick = message + .member + .as_ref() + .and_then(|m| m.nick.as_ref().map(|s| s.to_owned().into_string())); + + let resp = intelligence::query(intelligence::Request { + query: query.to_owned(), + metadata: intelligence::RequestMetadata { + username, + display_name, + nick, + }, + }) + .await?; + + message.reply_ping(&ctx.http, resp).await?; + typing_task.abort(); + } + + Ok(()) +} diff --git a/src/handlers/log.rs b/src/handlers/log.rs index 04d8a3e..562922e 100644 --- a/src/handlers/log.rs +++ b/src/handlers/log.rs @@ -4,7 +4,7 @@ use poise::serenity_prelude::{self as serenity}; use color_eyre::eyre::Result; use once_cell::sync::Lazy; -use crate::{storage::log::MessageLog, utils::serenity::unique_username, Data}; +use crate::{storage::log::MessageLog, Data}; pub async fn handle_message(message: &serenity::Message, data: &Data) -> Result<()> { if let Some(storage) = &data.storage { @@ -194,11 +194,8 @@ pub async fn member_join(ctx: &serenity::Context, user: &serenity::User) -> Resu serenity::CreateMessage::default().embed( serenity::CreateEmbed::default() .author( - serenity::CreateEmbedAuthor::new(format!( - "@{} joined", - unique_username(user) - )) - .icon_url(user.face()), + serenity::CreateEmbedAuthor::new(format!("@{} joined", user.tag())) + .icon_url(user.face()), ) .field("User", user.to_string(), false) .field("ID", format!("`{}`", user.id), false) @@ -220,7 +217,7 @@ pub async fn member_leave( if let Some(logs_channel) = *MEMBER_LOGS_CHANNEL { let mut embed = serenity::CreateEmbed::default() .author( - serenity::CreateEmbedAuthor::new(format!("@{} left", unique_username(user))) + serenity::CreateEmbedAuthor::new(format!("@{} left", user.tag())) .icon_url(user.face()), ) .field("User", user.to_string(), false) diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index ec2a68f..a55f6fd 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -4,6 +4,7 @@ use poise::serenity_prelude as serenity; mod autoreply; mod code_expansion; mod error_handling; +mod intelligence; pub mod log; pub use error_handling::handle_error; @@ -20,6 +21,7 @@ pub async fn handle_message( code_expansion::handle(message, ctx), autoreply::handle(message, ctx, data), log::handle_message(message, data), + intelligence::handle(message, ctx), )?; Ok(()) diff --git a/src/intelligence.rs b/src/intelligence.rs new file mode 100644 index 0000000..1fa2156 --- /dev/null +++ b/src/intelligence.rs @@ -0,0 +1,44 @@ +use std::env; + +use color_eyre::eyre::{eyre, Result}; +use serde::{Deserialize, Serialize}; + +use crate::reqwest_client::HTTP; + +static API_URL: &str = "https://intelligence.valfisk.ryanccn.dev/v2"; + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RequestMetadata { + pub username: String, + pub display_name: Option, + pub nick: Option, +} + +#[derive(Serialize)] +pub struct Request { + pub query: String, + pub metadata: RequestMetadata, +} + +#[derive(Deserialize)] +pub struct Response { + pub response: String, +} + +pub async fn query(request: Request) -> Result { + let secret = env::var("INTELLIGENCE_SECRET") + .map_err(|_| eyre!("Valfisk Intelligence API secret is not set!"))?; + + let resp: Response = HTTP + .post(API_URL) + .bearer_auth(secret) + .json(&request) + .send() + .await? + .error_for_status()? + .json() + .await?; + + Ok(resp.response) +} diff --git a/src/main.rs b/src/main.rs index ab8d686..703779d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,7 @@ pub type Context<'a> = poise::Context<'a, Data, Report>; mod api; mod commands; mod handlers; +mod intelligence; mod reqwest_client; mod starboard; mod storage; diff --git a/src/starboard.rs b/src/starboard.rs index e46374a..1436c4a 100644 --- a/src/starboard.rs +++ b/src/starboard.rs @@ -5,8 +5,6 @@ use poise::serenity_prelude as serenity; use color_eyre::eyre::{OptionExt, Result}; use tracing::debug; -use crate::utils::serenity::unique_username; - fn channel_from_env(key: &str) -> Option { env::var(key) .ok() @@ -107,8 +105,7 @@ fn make_message_embed<'a>( content }) .author( - serenity::CreateEmbedAuthor::new(unique_username(&message.author)) - .icon_url(message.author.face()), + serenity::CreateEmbedAuthor::new(message.author.tag()).icon_url(message.author.face()), ) .timestamp(message.timestamp); diff --git a/src/utils/serenity.rs b/src/utils/serenity.rs index 9a90756..f5639e7 100644 --- a/src/utils/serenity.rs +++ b/src/utils/serenity.rs @@ -1,15 +1,6 @@ use color_eyre::eyre::Result; use poise::serenity_prelude as serenity; -pub fn unique_username(user: &serenity::User) -> String { - let mut ret = user.name.clone().into_string(); - if let Some(discrim) = user.discriminator { - ret.push_str(&format!("#{discrim}")); - } - - ret -} - #[tracing::instrument(skip(ctx))] pub async fn suppress_embeds(ctx: &serenity::Context, message: &serenity::Message) -> Result<()> { use poise::futures_util::StreamExt as _;