diff --git a/src/commands/mod.rs b/src/commands/mod.rs index da962858..dadec27c 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -15,7 +15,7 @@ pub fn to_global_commands() -> Vec> { general::say(), general::stars(), general::tag(), - moderation::ban_user(), - moderation::kick_user(), + moderation::ban(), + moderation::kick(), ] } diff --git a/src/commands/moderation/actions.rs b/src/commands/moderation/actions.rs index 89b99043..4a0ed5ad 100644 --- a/src/commands/moderation/actions.rs +++ b/src/commands/moderation/actions.rs @@ -1,25 +1,94 @@ use crate::{consts::COLORS, Context}; use color_eyre::eyre::{eyre, Result}; -use poise::serenity_prelude::{CreateEmbed, User}; +use poise::serenity_prelude::{ + futures::TryFutureExt, CreateEmbed, CreateMessage, FutureExt, Guild, Timestamp, User, UserId, +}; -fn create_moderation_embed( - title: String, - user: &User, - delete_messages_days: Option, - reason: Option, -) -> impl FnOnce(&mut CreateEmbed) -> &mut CreateEmbed { - let fields = [ - ("User", format!("{} ({})", user.name, user.id), false), - ("Reason", reason.unwrap_or("None".to_string()), false), - ( - "Deleted messages", - format!("Last {} days", delete_messages_days.unwrap_or(0)), - false, - ), - ]; +struct Action { + reason: String, + data: ActionData, +} - |e: &mut CreateEmbed| e.title(title).fields(fields).color(COLORS["red"]) +enum ActionData { + Kick, + Ban { purge: u8 }, + Timeout { until: Timestamp }, +} + +fn build_dm<'a, 'b>( + message: &'b mut CreateMessage<'a>, + guild: &Guild, + action: &Action, +) -> &'b mut CreateMessage<'a> { + let description = match &action.data { + ActionData::Kick => "kicked from".to_string(), + ActionData::Ban { purge: _ } => "banned from".to_string(), + ActionData::Timeout { until } => { + format!("timed out until in", until.unix_timestamp()) + } + }; + let guild_name = &guild.name; + let reason = &action.reason; + message.content(format!( + "You have been {description} {guild_name}.\nReason: {reason}" + )) +} + +async fn moderate( + ctx: &Context<'_>, + users: &Vec, + action: &Action, + quiet: bool, +) -> Result<()> { + let guild = ctx + .guild() + .ok_or_else(|| eyre!("Couldn't get guild from message!"))?; + let reason = &action.reason; + + let mut count = 0; + + for user in users { + if quiet { + if let Ok(channel) = user.create_dm_channel(ctx.http()).await { + let _ = channel + .send_message(ctx.http(), |message| build_dm(message, &guild, action)) + .await; + } + } + + let success = match action.data { + ActionData::Kick => guild + .kick_with_reason(ctx.http(), user, reason) + .await + .is_ok(), + + ActionData::Ban { purge } => guild + .ban_with_reason(ctx.http(), user, purge, reason) + .await + .is_ok(), + + ActionData::Timeout { until } => guild + .edit_member(ctx.http(), user, |member| { + member.disable_communication_until_datetime(until) + }) + .await + .is_ok(), + }; + if success { + count += 1; + } + } + + let total = users.len(); + if count == total { + ctx.reply("✅ Done!").await?; + } else { + ctx.reply(format!("⚠️ {count}/{total} succeeded!")) + .await?; + } + + Ok(()) } /// Ban a user @@ -27,28 +96,28 @@ fn create_moderation_embed( slash_command, prefix_command, default_member_permissions = "BAN_MEMBERS", - required_permissions = "BAN_MEMBERS" + required_permissions = "BAN_MEMBERS", + aliases("ban") )] -pub async fn ban_user( +pub async fn ban( ctx: Context<'_>, - user: User, - delete_messages_days: Option, + users: Vec, + purge: Option, reason: Option, + quiet: Option, ) -> Result<()> { - let days = delete_messages_days.unwrap_or(1); - let guild = ctx - .guild() - .ok_or_else(|| eyre!("Couldn't get guild from message; Unable to ban!"))?; - - guild - .ban_with_reason(ctx, &user, days, reason.clone().unwrap_or_default()) - .await?; - - let embed = create_moderation_embed("User banned!".to_string(), &user, Some(days), reason); - - ctx.send(|m| m.embed(embed)).await?; - - Ok(()) + moderate( + &ctx, + &users, + &Action { + reason: reason.unwrap_or_default(), + data: ActionData::Ban { + purge: purge.unwrap_or(0), + }, + }, + quiet.unwrap_or(false), + ) + .await } /// Kick a user @@ -58,18 +127,20 @@ pub async fn ban_user( default_member_permissions = "KICK_MEMBERS", required_permissions = "KICK_MEMBERS" )] -pub async fn kick_user(ctx: Context<'_>, user: User, reason: Option) -> Result<()> { - let guild = ctx - .guild() - .ok_or_else(|| eyre!("Couldn't get guild from message; Unable to ban!"))?; - - guild - .kick_with_reason(ctx, &user, &reason.clone().unwrap_or_default()) - .await?; - - let embed = create_moderation_embed("User kicked!".to_string(), &user, None, reason); - - ctx.send(|m| m.embed(embed)).await?; - - Ok(()) +pub async fn kick( + ctx: Context<'_>, + users: Vec, + reason: Option, + quiet: Option, +) -> Result<()> { + moderate( + &ctx, + &users, + &Action { + reason: reason.unwrap_or_default(), + data: ActionData::Kick {}, + }, + quiet.unwrap_or(false), + ) + .await } diff --git a/src/main.rs b/src/main.rs index a4e5daf4..533ac89e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -62,7 +62,7 @@ async fn main() -> Result<()> { Box::pin(handlers::handle_event(ctx, event, framework, data)) }, prefix_options: PrefixFrameworkOptions { - prefix: Some("!".into()), + prefix: Some("r".into()), edit_tracker: Some(EditTracker::for_timespan(Duration::from_secs(3600))), ..Default::default() },