diff --git a/src/commands/moderation.rs b/src/commands/moderation.rs index 7905b01..87386fd 100644 --- a/src/commands/moderation.rs +++ b/src/commands/moderation.rs @@ -1,13 +1,17 @@ -use std::cmp; - use bson::{doc, Document}; use chrono::{Duration, Utc}; use mongodb::options::{UpdateModifications, UpdateOptions}; -use poise::serenity_prelude::{self as serenity, Member, RoleId}; +use poise::serenity_prelude::{self as serenity, Member, RoleId, User}; use tracing::{debug, trace}; use crate::db::model::Muted; -use crate::utils::moderation::{queue_unmute_member, respond_mute_command, ModerationKind}; +use crate::utils::moderation::{ + ban_moderation, + queue_unmute_member, + respond_moderation, + BanKind, + ModerationKind, +}; use crate::{Context, Error}; /// Unmute a member. @@ -26,7 +30,7 @@ pub async fn unmute( pending_unmute.abort(); } - respond_mute_command( + respond_moderation( &ctx, ModerationKind::Unmute( queue_unmute_member( @@ -40,7 +44,6 @@ pub async fn unmute( .unwrap(), ), &member.user, - configuration.general.embed_color, ) .await } @@ -88,7 +91,6 @@ pub async fn mute( let data = &mut *ctx.data().write().await; let configuration = &data.configuration; - let embed_color = configuration.general.embed_color; let mute = &configuration.general.mute; let mute_role_id = mute.role; let take = &mute.take; @@ -170,11 +172,10 @@ pub async fn mute( ), ); - respond_mute_command( + respond_moderation( &ctx, ModerationKind::Mute(reason, format!("", unmute_time.timestamp()), result), &member.user, - embed_color, ) .await } @@ -183,7 +184,7 @@ pub async fn mute( #[poise::command(slash_command)] pub async fn purge( ctx: Context<'_>, - #[description = "User"] member: Option, + #[description = "User"] user: Option, #[description = "Until message"] until: Option, #[min = 1] #[max = 1000] @@ -203,10 +204,10 @@ pub async fn purge( let channel = ctx.channel_id(); let too_old_timestamp = Utc::now().timestamp() - MAX_BULK_DELETE_AGO_SECS; - let user = ctx.discord().http.get_current_user().await?; - let image = user + let current_user = ctx.discord().http.get_current_user().await?; + let image = current_user .avatar_url() - .unwrap_or_else(|| user.default_avatar_url()); + .unwrap_or_else(|| current_user.default_avatar_url()); let handle = ctx .send(|f| { @@ -238,13 +239,13 @@ pub async fn purge( .collect::>(); // Filter for messages from the user - if let Some(ref member) = member { + if let Some(ref user) = user { messages = messages .into_iter() - .filter(|msg| msg.author.id == member.user.id) + .filter(|msg| msg.author.id == user.id) .collect::>(); - debug!("Filtered messages by {}. Left: {}", member, messages.len()); + debug!("Filtered messages by {}. Left: {}", user, messages.len()); } // Filter for messages until the g/mutiven id @@ -291,41 +292,32 @@ pub async fn purge( Ok(()) } -/// Ban a member. +/// Ban a user. #[poise::command(slash_command)] pub async fn ban( ctx: Context<'_>, - #[description = "User"] member: Member, + #[description = "User"] user: User, #[description = "Amount of days to delete messages"] dmd: Option, #[description = "Reason for the ban"] reason: Option, ) -> Result<(), Error> { - let reason = &reason - .or_else(|| Some("None specified".to_string())) - .unwrap(); - - let ban_result = member - .ban_with_reason(&ctx.discord().http, cmp::min(dmd.unwrap_or(0), 7), reason) - .await; - - let embed_color = ctx.data().read().await.configuration.general.embed_color; - - ctx.send(|f| { - f.embed(|e| { - if let Err(error) = ban_result { - e.title(format!("Failed to ban {}", member.user.tag())) - .field("Error", error, false) - } else { - e.title(format!("Banned {}", member.user.tag())) - .thumbnail( - member - .avatar_url() - .unwrap_or_else(|| member.user.default_avatar_url()), - ) - .field("Reason", reason, false) - } - .color(embed_color) - }) - }) - .await?; - Ok(()) + respond_moderation( + &ctx, + ModerationKind::Ban( + reason.clone(), + ban_moderation(&ctx, BanKind::Ban(user.clone(), dmd, reason)).await, + ), + &user, + ) + .await +} + +/// Unban a user. +#[poise::command(slash_command)] +pub async fn unban(ctx: Context<'_>, #[description = "User"] user: User) -> Result<(), Error> { + respond_moderation( + &ctx, + ModerationKind::Unban(ban_moderation(&ctx, BanKind::Unban(user.clone())).await), + &user, + ) + .await } diff --git a/src/utils/moderation.rs b/src/utils/moderation.rs index ba1e5c5..8ccabe0 100644 --- a/src/utils/moderation.rs +++ b/src/utils/moderation.rs @@ -1,7 +1,8 @@ +use std::cmp; use std::sync::Arc; use mongodb::options::FindOptions; -use poise::serenity_prelude::Http; +use poise::serenity_prelude::{Http, User}; use tokio::task::JoinHandle; use tracing::{debug, error, trace}; @@ -9,13 +10,18 @@ use super::bot::get_data_lock; use super::*; use crate::db::database::Database; use crate::db::model::Muted; +use crate::serenity::SerenityError; use crate::{Context, Error}; - pub enum ModerationKind { Mute(String, String, Option), // Reason, Expires, Error Unmute(Option), // Error + Ban(Option, Option), // Reason, Error + Unban(Option), // Error +} +pub enum BanKind { + Ban(User, Option, Option), // User, Amount of days to delete messages, Reason + Unban(User), // User } - pub async fn mute_on_join(ctx: &serenity::Context, new_member: &mut serenity::Member) { let data = get_data_lock(ctx).await; let data = data.read().await; @@ -105,17 +111,19 @@ pub fn queue_unmute_member( }) } -pub async fn respond_mute_command( +// TODO: refactor +pub async fn respond_moderation( ctx: &Context<'_>, moderation: ModerationKind, user: &serenity::User, - embed_color: i32, ) -> Result<(), Error> { let tag = user.tag(); let image = user .avatar_url() .unwrap_or_else(|| user.default_avatar_url()); + let embed_color = ctx.data().read().await.configuration.general.embed_color; + ctx.send(|f| { f.embed(|f| { match moderation { @@ -137,6 +145,29 @@ pub async fn respond_mute_command( ), None => f.title(format!("Unmuted {}", tag)), }, + ModerationKind::Ban(reason, error) => { + let f = match error { + Some(err) => f.title(format!("Failed to ban {}", tag)).field( + "Exception", + err.to_string(), + false, + ), + None => f.title(format!("Banned {}", tag)), + }; + if let Some(reason) = reason { + f.field("Reason", reason, false) + } else { + f + } + }, + ModerationKind::Unban(error) => match error { + Some(err) => f.title(format!("Failed to unban {}", tag)).field( + "Exception", + err.to_string(), + false, + ), + None => f.title(format!("Unbanned {}", tag)), + }, } .color(embed_color) .thumbnail(image) @@ -146,3 +177,37 @@ pub async fn respond_mute_command( Ok(()) } + +pub async fn ban_moderation(ctx: &Context<'_>, kind: BanKind) -> Option { + let guild_id = ctx.guild_id().unwrap().0; + let http = &ctx.discord().http; + + match kind { + BanKind::Ban(user, dmd, reason) => { + let reason = &reason + .or_else(|| Some("None specified".to_string())) + .unwrap(); + + let ban_result = http + .ban_user(guild_id, user.id.0, cmp::min(dmd.unwrap_or(0), 7), reason) + .await; + + if let Err(err) = ban_result { + error!("Failed to ban user {}: {}", user.id.0, err); + Some(err) + } else { + None + } + }, + BanKind::Unban(user) => { + let unban_result = http.remove_ban(guild_id, user.id.0, None).await; + + if let Err(err) = unban_result { + error!("Failed to unban user {}: {}", user.id.0, err); + Some(err) + } else { + None + } + }, + } +}