Add friend request command

This commit is contained in:
2025-08-14 19:04:57 -03:00
parent fe53f2e5c2
commit 8e0c591b21
5 changed files with 151 additions and 53 deletions
+1 -3
View File
@@ -18,9 +18,7 @@ use twilight_model::{
user::User as TwilightUser, user::User as TwilightUser,
}; };
use crate::{ use crate::{constants::DEFAULT_PREFIX, groups::calls::DisplayProfile, utils::get_random_u64};
constants::DEFAULT_PREFIX, groups::calls::events::DisplayProfile, utils::get_random_u64,
};
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum DatabaseOrDiscordError { pub enum DatabaseOrDiscordError {
+96
View File
@@ -4,7 +4,9 @@ use twilight_model::id::Id;
use crate::{ use crate::{
database::models::{Call, Channel}, database::models::{Call, Channel},
discord::sanitizing::SanitizeDiscordMarkdown,
framework::commands::{Command, CommandError, context::CommandContext}, framework::commands::{Command, CommandError, context::CommandContext},
groups::calls::get_display_profile,
state::State, state::State,
utils::Bye, utils::Bye,
}; };
@@ -304,3 +306,97 @@ impl Command<State> for HangupCommand {
Ok(()) Ok(())
} }
} }
#[derive(Debug)]
pub struct FriendRequestCommand;
#[async_trait]
impl Command<State> 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(())
}
}
+1 -49
View File
@@ -2,16 +2,14 @@ use std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use twilight_model::{gateway::payload::incoming::MessageCreate, id::Id}; use twilight_model::{gateway::payload::incoming::MessageCreate, id::Id};
use twilight_validate::request::webhook_username;
use crate::{ use crate::{
database::models::{Call, Channel, Message}, database::models::{Call, Channel, Message},
discord::images,
framework::{ framework::{
events::traits::{EventResult, OnMessageHandler}, events::traits::{EventResult, OnMessageHandler},
runtime::Runtime, runtime::Runtime,
}, },
groups::calls::moderation::escape_message, groups::calls::{get_display_profile, moderation::escape_message},
state::State, state::State,
}; };
@@ -128,49 +126,3 @@ impl OnMessageHandler<State> for MessageRedirector {
Ok(()) Ok(())
} }
} }
#[derive(Debug, Clone)]
pub struct DisplayProfile {
pub id: u64,
pub name: String,
pub avatar_url: String,
pub mask: Option<String>,
}
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,
}
}
+51
View File
@@ -1,5 +1,56 @@
//! Call commands and events. //! 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 commands;
pub mod events; pub mod events;
pub(super) mod moderation; pub(super) mod moderation;
#[derive(Debug, Clone)]
pub struct DisplayProfile {
pub id: u64,
pub name: String,
pub avatar_url: String,
pub mask: Option<String>,
}
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,
}
}
+2 -1
View File
@@ -126,7 +126,7 @@ use crate::{
groups::{ groups::{
about::{commands::HelpCommand, components::HelpGoBackButton}, about::{commands::HelpCommand, components::HelpGoBackButton},
calls::{ calls::{
commands::{CallCommand, HangupCommand, SkipCommand}, commands::{CallCommand, FriendRequestCommand, HangupCommand, SkipCommand},
events::MessageRedirector, events::MessageRedirector,
}, },
gateway::events::{ReadyHandler, ReconnectHandler}, gateway::events::{ReadyHandler, ReconnectHandler},
@@ -227,6 +227,7 @@ async fn start(token: String, database_addr: String, shard: ShardId) {
.command(CallCommand::new()) .command(CallCommand::new())
.command(SkipCommand) .command(SkipCommand)
.command(HangupCommand) .command(HangupCommand)
.command(FriendRequestCommand)
.command(PrefixCommand) .command(PrefixCommand)
.component(HelpGoBackButton) .component(HelpGoBackButton)
.on_ready(ReadyHandler) .on_ready(ReadyHandler)