Skip to content
This repository has been archived by the owner on Aug 7, 2024. It is now read-only.

Commit

Permalink
fix(moderation): UserId instead of Member parameter
Browse files Browse the repository at this point in the history
Context: #38
  • Loading branch information
Axelen123 committed Jan 3, 2023
1 parent 5e7939a commit 5fcad81
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 107 deletions.
181 changes: 82 additions & 99 deletions src/commands/moderation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ use bson::{doc, Document};
use chrono::{Duration, Utc};
use mongodb::options::{UpdateModifications, UpdateOptions};
use poise::serenity_prelude::{
self as serenity, Member, Mentionable, PermissionOverwrite, Permissions, RoleId, User,
self as serenity, Mentionable, PermissionOverwrite, Permissions, UserId,
};
use tracing::log::error;
use tracing::{debug, trace, warn};
use tracing::{debug, error, trace};

use crate::db::model::{LockedChannel, Muted};
use crate::utils::macros::to_user;
use crate::utils::moderation::{
ban_moderation, queue_unmute_member, respond_moderation, BanKind, ModerationKind,
};
Expand Down Expand Up @@ -159,24 +159,28 @@ pub async fn unlock(ctx: Context<'_>) -> Result<(), Error> {
#[poise::command(slash_command)]
pub async fn unmute(
ctx: Context<'_>,
#[description = "The member to unmute"] member: Member,
#[description = "The member to unmute"] member: UserId,
) -> Result<(), Error> {
let user = to_user!(member, ctx);
let id = user.id;
ctx.defer().await.expect("Failed to defer");

let data = &ctx.data().read().await;
let configuration = &data.configuration;

if let Some(pending_unmute) = data.pending_unmutes.get(&member.user.id.0) {
trace!("Cancelling pending unmute for {}", member.user.id.0);
if let Some(pending_unmute) = data.pending_unmutes.get(&id.0) {
trace!("Cancelling pending unmute for {}", id.0);
pending_unmute.abort();
}

let author = ctx.author();

let queue = queue_unmute_member(
&ctx.discord().http,
&ctx.discord().cache,
&data.database,
&member,
ctx.guild_id().unwrap(),
id,
configuration.general.mute.role,
0,
)
Expand All @@ -185,7 +189,7 @@ pub async fn unmute(

respond_moderation(
&ctx,
&ModerationKind::Unmute(member.user, author.clone(), queue),
&ModerationKind::Unmute(user, author.clone(), queue),
configuration,
)
.await
Expand All @@ -196,14 +200,16 @@ pub async fn unmute(
#[poise::command(slash_command)]
pub async fn mute(
ctx: Context<'_>,
#[description = "The member to mute"] mut member: Member,
#[description = "The member to mute"] member: UserId,
#[description = "Seconds"] seconds: Option<i64>,
#[description = "Minutes"] minutes: Option<i64>,
#[description = "Hours"] hours: Option<i64>,
#[description = "Days"] days: Option<i64>,
#[description = "Months"] months: Option<i64>,
#[description = "The reason of the mute"] reason: String,
) -> Result<(), Error> {
let user = to_user!(member, ctx);
let id = user.id;
let now = Utc::now();
let mut mute_duration = Duration::zero();

Expand Down Expand Up @@ -232,26 +238,26 @@ pub async fn mute(

let data = &mut *ctx.data().write().await;
let configuration = &data.configuration;
let mute = &configuration.general.mute;
let mute_role_id = mute.role;
let take = &mute.take;
let is_currently_muted = member.roles.iter().any(|r| r.0 == mute_role_id);

let author = ctx.author();

if let Some(pending_unmute) = data.pending_unmutes.get(&member.user.id.0) {
trace!("Cancelling pending unmute for {}", member.user.id.0);
let mute = &configuration.general.mute;
let guild_id = ctx.guild_id().unwrap();

if let Some(pending_unmute) = data.pending_unmutes.get(&id.0) {
trace!("Cancelling pending unmute for {}", id.0);
pending_unmute.abort();
}

let unmute_time = if !mute_duration.is_zero() {
data.pending_unmutes.insert(
member.user.id.0,
id.0,
queue_unmute_member(
&ctx.discord().http,
&ctx.discord().cache,
&data.database,
&member,
mute_role_id,
guild_id,
id,
mute.role,
mute_duration.num_seconds() as u64,
),
);
Expand All @@ -260,107 +266,81 @@ pub async fn mute(
None
};

let result =
if let Err(add_role_result) = member.add_role(&ctx.discord().http, mute_role_id).await {
Some(Error::from(add_role_result))
} else {
// accumulate all roles to take from the member
let removed_roles = member
.roles
.iter()
.filter(|r| take.contains(&r.0))
.map(|r| r.to_string())
.collect::<Vec<_>>();
// take them from the member, get remaining roles
let remaining_roles = member
.remove_roles(
&ctx.discord().http,
&take.iter().map(|&r| RoleId::from(r)).collect::<Vec<_>>(),
)
.await;
let mut updated = Muted {
guild_id: Some(guild_id.0.to_string()),
expires: unmute_time,
reason: Some(reason.clone()),
..Default::default()
};

if let Err(remove_role_result) = remaining_roles {
Some(Error::from(remove_role_result))
} else {
// Roles which were removed from the user
let updated: Document = Muted {
guild_id: Some(member.guild_id.0.to_string()),
expires: unmute_time,
reason: Some(reason.clone()),
taken_roles: if is_currently_muted {
// Prevent the bot from overriding the "take" field.
// This would happen otherwise, because the bot would accumulate the users roles and then override the value in the database
// resulting in the user being muted to have no roles to add back later.
None
} else {
Some(removed_roles)
},
..Default::default()
}
.into();

if let Err(database_update_result) = data
.database
.update::<Muted>(
"muted",
Muted {
user_id: Some(member.user.id.0.to_string()),
..Default::default()
}
.into(),
UpdateModifications::Document(doc! { "$set": updated}),
Some(UpdateOptions::builder().upsert(true).build()),
)
.await
{
Some(database_update_result)
} else if unmute_time.is_none() {
data.database
.update::<Muted>(
"muted",
Muted {
user_id: Some(member.user.id.0.to_string()),
..Default::default()
}
.into(),
UpdateModifications::Document(doc! { "$unset": { "expires": "" } }),
None,
)
.await
.err()
} else {
None
}
let result = async {
if let Some(mut member) = ctx.discord().cache.member(guild_id, id) {
let (is_currently_muted, removed_roles) =
crate::utils::moderation::mute_moderation(&ctx, &mut member, mute).await?;
// Prevent the bot from overriding the "take" field.
// This would happen otherwise, because the bot would accumulate the users roles and then override the value in the database
// resulting in the user being muted to have no roles to add back later.
if !is_currently_muted {
updated.taken_roles = Some(removed_roles.iter().map(ToString::to_string).collect());
}
};
}

if result.is_none() {
if let Err(e) = member.disconnect_from_voice(&ctx.discord().http).await {
warn!("Could not disconnect member from voice channel: {}", e);
let query: Document = Muted {
user_id: Some(id.to_string()),
..Default::default()
}
.into();

let updated: Document = updated.into();
data.database
.update::<Muted>(
"muted",
query.clone(),
UpdateModifications::Document(doc! { "$set": updated }),
Some(UpdateOptions::builder().upsert(true).build()),
)
.await?;

if unmute_time.is_none() {
data.database
.update::<Muted>(
"muted",
query,
UpdateModifications::Document(doc! { "$unset": { "expires": "" } }),
None,
)
.await?;
}
Ok(())
}
.await;

let duration = unmute_time.map(|time| format!("<t:{}:F>", time));

respond_moderation(
&ctx,
&ModerationKind::Mute(member.user, author.clone(), reason, duration, result),
&ModerationKind::Mute(user, author.clone(), reason, duration, result.err()),
configuration,
)
.await
}

/// Delete recent messages of a user. Cannot delete messages older than 14 days.
/// Delete recent messages of a member. Cannot delete messages older than 14 days.
#[poise::command(slash_command)]
pub async fn purge(
ctx: Context<'_>,
#[description = "User"] user: Option<User>,
#[description = "Member"] user: Option<UserId>,
#[description = "Until message"] until: Option<String>,
#[min = 1]
#[max = 1000]
#[description = "Count"]
count: Option<i64>,
) -> Result<(), Error> {
let user = if let Some(id) = user {
Some(to_user!(id, ctx))
} else {
None
};
// The maximum amount of times to page through messages. If paged over MAX_PAGES amount of times without deleting messages, break.
const MAX_PAGES: i8 = 2;
// The maximal amount of messages that we can fetch at all
Expand Down Expand Up @@ -467,20 +447,23 @@ pub async fn purge(
Ok(())
}

/// Ban a user.
/// Ban a member.
#[poise::command(slash_command)]
pub async fn ban(
ctx: Context<'_>,
#[description = "User"] user: User,
#[description = "Member"] user: UserId,
#[description = "Amount of days to delete messages"] dmd: Option<u8>,
#[description = "Reason for the ban"] reason: Option<String>,
) -> Result<(), Error> {
// We cannot use `User` as a parameter for the moderation commands because of a bug in serenity. See: https://github.com/revanced/revanced-discord-bot/issues/38
let user = to_user!(user, ctx);
handle_ban(&ctx, &BanKind::Ban(user, dmd, reason)).await
}

/// Unban a user.
#[poise::command(slash_command)]
pub async fn unban(ctx: Context<'_>, #[description = "User"] user: User) -> Result<(), Error> {
pub async fn unban(ctx: Context<'_>, #[description = "User"] user: UserId) -> Result<(), Error> {
let user = to_user!(user, ctx);
handle_ban(&ctx, &BanKind::Unban(user)).await
}

Expand Down
4 changes: 3 additions & 1 deletion src/events/ready.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ pub async fn load_muted_members(ctx: &serenity::Context, _: &serenity::Ready) {
member.user.id.0,
queue_unmute_member(
&ctx.http,
&ctx.cache,
&data.database,
&member,
member.guild_id,
member.user.id,
mute_role_id,
amount_left as u64, // i64 as u64 is handled properly here
),
Expand Down
7 changes: 7 additions & 0 deletions src/utils/macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
macro_rules! to_user {
($user:ident, $ctx:ident) => {{
$user.to_user(&$ctx.discord().http).await?
}};
}

pub(crate) use to_user;
1 change: 1 addition & 0 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod autorespond;
pub mod bot;
pub mod decancer;
pub mod embed;
pub mod macros;
pub mod media_channel;
pub mod moderation;
pub mod poll;
Loading

0 comments on commit 5fcad81

Please sign in to comment.