diff --git a/src/database/models.rs b/src/database/models.rs index cbe1aec..03c8b22 100644 --- a/src/database/models.rs +++ b/src/database/models.rs @@ -18,9 +18,7 @@ use twilight_model::{ user::User as TwilightUser, }; -use crate::{ - constants::DEFAULT_PREFIX, groups::calls::events::DisplayProfile, utils::get_random_u64, -}; +use crate::{constants::DEFAULT_PREFIX, groups::calls::DisplayProfile, utils::get_random_u64}; #[derive(Debug, Error)] pub enum DatabaseOrDiscordError { diff --git a/src/groups/calls/commands.rs b/src/groups/calls/commands.rs index 8d7e4c9..85d1643 100644 --- a/src/groups/calls/commands.rs +++ b/src/groups/calls/commands.rs @@ -4,7 +4,9 @@ use twilight_model::id::Id; use crate::{ database::models::{Call, Channel}, + discord::sanitizing::SanitizeDiscordMarkdown, framework::commands::{Command, CommandError, context::CommandContext}, + groups::calls::get_display_profile, state::State, utils::Bye, }; @@ -304,3 +306,97 @@ impl Command for HangupCommand { Ok(()) } } + +#[derive(Debug)] +pub struct FriendRequestCommand; + +#[async_trait] +impl Command for FriendRequestCommand { + fn name(&self) -> &'static str { + "friendreq" + } + + fn aliases(&self) -> &'static [&'static str] { + &["f", "fr", "friendrequest"] + } + + fn description(&self) -> &'static str { + "Shows your username to the other party and asks them to send you a friend request on Discord." + } + + async fn execute( + &self, + ctx: CommandContext<'_, State>, + _args: &str, + ) -> Result<(), CommandError> { + let channel = Channel::get_by_id(ctx.event.channel_id.get(), &ctx.rt.state.db) + .await + .inspect_err(|e| log::error!("Failed to get channel: {e:?}")) + .map_err(|_| CommandError::DatabaseQueryFailed)?; + + let Some(channel) = channel else { + ctx.send() + .content(format!( + "❌ You're not on a call! Use `{}call` to start one.", + ctx.prefix + )) + .await + .map_err(|_| CommandError::MessageSendingFailed)?; + + return Ok(()); + }; + + let call = if let Some(call_id) = channel.call_id { + Call::get_by_id(call_id, &ctx.rt.state.db) + .await + .inspect_err(|e| log::error!("Failed to get call: {e:?}")) + .map_err(|_| CommandError::DatabaseQueryFailed)? + } else { + ctx.send() + .content(format!( + "❌ You're not on a call! Use `{}call` to start one.", + ctx.prefix + )) + .await + .map_err(|_| CommandError::MessageSendingFailed)?; + + return Ok(()); + }; + + let Some(call) = call else { + ctx.send() + .content("❌ Oops! We could not find that call.") + .await + .map_err(|_| CommandError::MessageSendingFailed)?; + + return Err(CommandError::DatabaseInconsistentState); + }; + + let other_channel_id = call.other_party_channel_id(channel.id); + + let profile = get_display_profile(ctx.event); + + ctx + .rt + .http + .create_message( + Id::new(other_channel_id) + ) + .content( + &format!( + "📬 **{}** wants you to send them a friend request on Discord! Their username is `{}`.", + profile.name.sanitize(false), + ctx.event.author.name.sanitize(false) + ) + ) + .await + .map_err(|_| CommandError::MessageSendingFailed)?; + + ctx.send() + .content("📬 Asked the other party to send you a friend request!") + .await + .map_err(|_| CommandError::MessageSendingFailed)?; + + Ok(()) + } +} diff --git a/src/groups/calls/events.rs b/src/groups/calls/events.rs index fbe8804..749fb9a 100644 --- a/src/groups/calls/events.rs +++ b/src/groups/calls/events.rs @@ -2,16 +2,14 @@ use std::sync::Arc; use async_trait::async_trait; use twilight_model::{gateway::payload::incoming::MessageCreate, id::Id}; -use twilight_validate::request::webhook_username; use crate::{ database::models::{Call, Channel, Message}, - discord::images, framework::{ events::traits::{EventResult, OnMessageHandler}, runtime::Runtime, }, - groups::calls::moderation::escape_message, + groups::calls::{get_display_profile, moderation::escape_message}, state::State, }; @@ -128,49 +126,3 @@ impl OnMessageHandler for MessageRedirector { Ok(()) } } - -#[derive(Debug, Clone)] -pub struct DisplayProfile { - pub id: u64, - pub name: String, - pub avatar_url: String, - pub mask: Option, -} - -fn get_display_profile(event: &MessageCreate) -> DisplayProfile { - let member = event.member.clone(); - - let name = if let Some(nick) = &member.clone().and_then(|member| member.nick) { - if webhook_username(nick).is_err() { - "Invalid Nickname".to_string() - } else { - nick.clone() - } - } else if let Some(name) = &event.author.global_name { - if webhook_username(name).is_err() { - "Invalid Global Name".to_string() - } else { - name.clone() - } - } else if webhook_username(&event.author.name).is_err() { - "Invalid Username".to_string() - } else { - event.author.name.clone() - }; - - let avatar_hash = member - .and_then(|inner| inner.avatar) - .or(event.author.avatar); - let avatar_url = images::get_avatar_url( - avatar_hash, - event.author.id.get(), - event.author.discriminator as u64, - ); - - DisplayProfile { - id: event.author.id.get(), - name, - avatar_url, - mask: None, - } -} diff --git a/src/groups/calls/mod.rs b/src/groups/calls/mod.rs index 21e492f..c6426ef 100644 --- a/src/groups/calls/mod.rs +++ b/src/groups/calls/mod.rs @@ -1,5 +1,56 @@ //! Call commands and events. +use twilight_model::gateway::payload::incoming::MessageCreate; +use twilight_validate::request::webhook_username; + +use crate::discord::images; + pub mod commands; pub mod events; pub(super) mod moderation; + +#[derive(Debug, Clone)] +pub struct DisplayProfile { + pub id: u64, + pub name: String, + pub avatar_url: String, + pub mask: Option, +} + +fn get_display_profile(event: &MessageCreate) -> DisplayProfile { + let member = event.member.clone(); + + let name = if let Some(nick) = &member.clone().and_then(|member| member.nick) { + if webhook_username(nick).is_err() { + "Invalid Nickname".to_string() + } else { + nick.clone() + } + } else if let Some(name) = &event.author.global_name { + if webhook_username(name).is_err() { + "Invalid Global Name".to_string() + } else { + name.clone() + } + } else if webhook_username(&event.author.name).is_err() { + "Invalid Username".to_string() + } else { + event.author.name.clone() + }; + + let avatar_hash = member + .and_then(|inner| inner.avatar) + .or(event.author.avatar); + let avatar_url = images::get_avatar_url( + avatar_hash, + event.author.id.get(), + event.author.discriminator as u64, + ); + + DisplayProfile { + id: event.author.id.get(), + name, + avatar_url, + mask: None, + } +} diff --git a/src/main.rs b/src/main.rs index b16fcc1..cab9222 100644 --- a/src/main.rs +++ b/src/main.rs @@ -126,7 +126,7 @@ use crate::{ groups::{ about::{commands::HelpCommand, components::HelpGoBackButton}, calls::{ - commands::{CallCommand, HangupCommand, SkipCommand}, + commands::{CallCommand, FriendRequestCommand, HangupCommand, SkipCommand}, events::MessageRedirector, }, gateway::events::{ReadyHandler, ReconnectHandler}, @@ -227,6 +227,7 @@ async fn start(token: String, database_addr: String, shard: ShardId) { .command(CallCommand::new()) .command(SkipCommand) .command(HangupCommand) + .command(FriendRequestCommand) .command(PrefixCommand) .component(HelpGoBackButton) .on_ready(ReadyHandler)