mirror of
https://github.com/revanced/revanced-discord-bot.git
synced 2025-05-01 06:54:27 +02:00
fix(moderation): UserId instead of Member parameter
Context: https://github.com/revanced/revanced-discord-bot/issues/38
This commit is contained in:
parent
5e7939a512
commit
5fcad81483
@ -2,12 +2,12 @@ use bson::{doc, Document};
|
|||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
use mongodb::options::{UpdateModifications, UpdateOptions};
|
use mongodb::options::{UpdateModifications, UpdateOptions};
|
||||||
use poise::serenity_prelude::{
|
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, error, trace};
|
||||||
use tracing::{debug, trace, warn};
|
|
||||||
|
|
||||||
use crate::db::model::{LockedChannel, Muted};
|
use crate::db::model::{LockedChannel, Muted};
|
||||||
|
use crate::utils::macros::to_user;
|
||||||
use crate::utils::moderation::{
|
use crate::utils::moderation::{
|
||||||
ban_moderation, queue_unmute_member, respond_moderation, BanKind, ModerationKind,
|
ban_moderation, queue_unmute_member, respond_moderation, BanKind, ModerationKind,
|
||||||
};
|
};
|
||||||
@ -159,15 +159,17 @@ pub async fn unlock(ctx: Context<'_>) -> Result<(), Error> {
|
|||||||
#[poise::command(slash_command)]
|
#[poise::command(slash_command)]
|
||||||
pub async fn unmute(
|
pub async fn unmute(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "The member to unmute"] member: Member,
|
#[description = "The member to unmute"] member: UserId,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
let user = to_user!(member, ctx);
|
||||||
|
let id = user.id;
|
||||||
ctx.defer().await.expect("Failed to defer");
|
ctx.defer().await.expect("Failed to defer");
|
||||||
|
|
||||||
let data = &ctx.data().read().await;
|
let data = &ctx.data().read().await;
|
||||||
let configuration = &data.configuration;
|
let configuration = &data.configuration;
|
||||||
|
|
||||||
if let Some(pending_unmute) = data.pending_unmutes.get(&member.user.id.0) {
|
if let Some(pending_unmute) = data.pending_unmutes.get(&id.0) {
|
||||||
trace!("Cancelling pending unmute for {}", member.user.id.0);
|
trace!("Cancelling pending unmute for {}", id.0);
|
||||||
pending_unmute.abort();
|
pending_unmute.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,8 +177,10 @@ pub async fn unmute(
|
|||||||
|
|
||||||
let queue = queue_unmute_member(
|
let queue = queue_unmute_member(
|
||||||
&ctx.discord().http,
|
&ctx.discord().http,
|
||||||
|
&ctx.discord().cache,
|
||||||
&data.database,
|
&data.database,
|
||||||
&member,
|
ctx.guild_id().unwrap(),
|
||||||
|
id,
|
||||||
configuration.general.mute.role,
|
configuration.general.mute.role,
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
@ -185,7 +189,7 @@ pub async fn unmute(
|
|||||||
|
|
||||||
respond_moderation(
|
respond_moderation(
|
||||||
&ctx,
|
&ctx,
|
||||||
&ModerationKind::Unmute(member.user, author.clone(), queue),
|
&ModerationKind::Unmute(user, author.clone(), queue),
|
||||||
configuration,
|
configuration,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@ -196,7 +200,7 @@ pub async fn unmute(
|
|||||||
#[poise::command(slash_command)]
|
#[poise::command(slash_command)]
|
||||||
pub async fn mute(
|
pub async fn mute(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "The member to mute"] mut member: Member,
|
#[description = "The member to mute"] member: UserId,
|
||||||
#[description = "Seconds"] seconds: Option<i64>,
|
#[description = "Seconds"] seconds: Option<i64>,
|
||||||
#[description = "Minutes"] minutes: Option<i64>,
|
#[description = "Minutes"] minutes: Option<i64>,
|
||||||
#[description = "Hours"] hours: Option<i64>,
|
#[description = "Hours"] hours: Option<i64>,
|
||||||
@ -204,6 +208,8 @@ pub async fn mute(
|
|||||||
#[description = "Months"] months: Option<i64>,
|
#[description = "Months"] months: Option<i64>,
|
||||||
#[description = "The reason of the mute"] reason: String,
|
#[description = "The reason of the mute"] reason: String,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
let user = to_user!(member, ctx);
|
||||||
|
let id = user.id;
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
let mut mute_duration = Duration::zero();
|
let mut mute_duration = Duration::zero();
|
||||||
|
|
||||||
@ -232,26 +238,26 @@ pub async fn mute(
|
|||||||
|
|
||||||
let data = &mut *ctx.data().write().await;
|
let data = &mut *ctx.data().write().await;
|
||||||
let configuration = &data.configuration;
|
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();
|
let author = ctx.author();
|
||||||
|
|
||||||
if let Some(pending_unmute) = data.pending_unmutes.get(&member.user.id.0) {
|
let mute = &configuration.general.mute;
|
||||||
trace!("Cancelling pending unmute for {}", member.user.id.0);
|
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();
|
pending_unmute.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
let unmute_time = if !mute_duration.is_zero() {
|
let unmute_time = if !mute_duration.is_zero() {
|
||||||
data.pending_unmutes.insert(
|
data.pending_unmutes.insert(
|
||||||
member.user.id.0,
|
id.0,
|
||||||
queue_unmute_member(
|
queue_unmute_member(
|
||||||
&ctx.discord().http,
|
&ctx.discord().http,
|
||||||
|
&ctx.discord().cache,
|
||||||
&data.database,
|
&data.database,
|
||||||
&member,
|
guild_id,
|
||||||
mute_role_id,
|
id,
|
||||||
|
mute.role,
|
||||||
mute_duration.num_seconds() as u64,
|
mute_duration.num_seconds() as u64,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -260,107 +266,81 @@ pub async fn mute(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let result =
|
let mut updated = Muted {
|
||||||
if let Err(add_role_result) = member.add_role(&ctx.discord().http, mute_role_id).await {
|
guild_id: Some(guild_id.0.to_string()),
|
||||||
Some(Error::from(add_role_result))
|
expires: unmute_time,
|
||||||
} else {
|
reason: Some(reason.clone()),
|
||||||
// accumulate all roles to take from the member
|
..Default::default()
|
||||||
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;
|
|
||||||
|
|
||||||
if let Err(remove_role_result) = remaining_roles {
|
let result = async {
|
||||||
Some(Error::from(remove_role_result))
|
if let Some(mut member) = ctx.discord().cache.member(guild_id, id) {
|
||||||
} else {
|
let (is_currently_muted, removed_roles) =
|
||||||
// Roles which were removed from the user
|
crate::utils::moderation::mute_moderation(&ctx, &mut member, mute).await?;
|
||||||
let updated: Document = Muted {
|
// Prevent the bot from overriding the "take" field.
|
||||||
guild_id: Some(member.guild_id.0.to_string()),
|
// This would happen otherwise, because the bot would accumulate the users roles and then override the value in the database
|
||||||
expires: unmute_time,
|
// resulting in the user being muted to have no roles to add back later.
|
||||||
reason: Some(reason.clone()),
|
if !is_currently_muted {
|
||||||
taken_roles: if is_currently_muted {
|
updated.taken_roles = Some(removed_roles.iter().map(ToString::to_string).collect());
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
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));
|
let duration = unmute_time.map(|time| format!("<t:{}:F>", time));
|
||||||
|
|
||||||
respond_moderation(
|
respond_moderation(
|
||||||
&ctx,
|
&ctx,
|
||||||
&ModerationKind::Mute(member.user, author.clone(), reason, duration, result),
|
&ModerationKind::Mute(user, author.clone(), reason, duration, result.err()),
|
||||||
configuration,
|
configuration,
|
||||||
)
|
)
|
||||||
.await
|
.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)]
|
#[poise::command(slash_command)]
|
||||||
pub async fn purge(
|
pub async fn purge(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "User"] user: Option<User>,
|
#[description = "Member"] user: Option<UserId>,
|
||||||
#[description = "Until message"] until: Option<String>,
|
#[description = "Until message"] until: Option<String>,
|
||||||
#[min = 1]
|
#[min = 1]
|
||||||
#[max = 1000]
|
#[max = 1000]
|
||||||
#[description = "Count"]
|
#[description = "Count"]
|
||||||
count: Option<i64>,
|
count: Option<i64>,
|
||||||
) -> Result<(), Error> {
|
) -> 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.
|
// 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;
|
const MAX_PAGES: i8 = 2;
|
||||||
// The maximal amount of messages that we can fetch at all
|
// The maximal amount of messages that we can fetch at all
|
||||||
@ -467,20 +447,23 @@ pub async fn purge(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ban a user.
|
/// Ban a member.
|
||||||
#[poise::command(slash_command)]
|
#[poise::command(slash_command)]
|
||||||
pub async fn ban(
|
pub async fn ban(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "User"] user: User,
|
#[description = "Member"] user: UserId,
|
||||||
#[description = "Amount of days to delete messages"] dmd: Option<u8>,
|
#[description = "Amount of days to delete messages"] dmd: Option<u8>,
|
||||||
#[description = "Reason for the ban"] reason: Option<String>,
|
#[description = "Reason for the ban"] reason: Option<String>,
|
||||||
) -> Result<(), Error> {
|
) -> 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
|
handle_ban(&ctx, &BanKind::Ban(user, dmd, reason)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unban a user.
|
/// Unban a user.
|
||||||
#[poise::command(slash_command)]
|
#[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
|
handle_ban(&ctx, &BanKind::Unban(user)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,8 +40,10 @@ pub async fn load_muted_members(ctx: &serenity::Context, _: &serenity::Ready) {
|
|||||||
member.user.id.0,
|
member.user.id.0,
|
||||||
queue_unmute_member(
|
queue_unmute_member(
|
||||||
&ctx.http,
|
&ctx.http,
|
||||||
|
&ctx.cache,
|
||||||
&data.database,
|
&data.database,
|
||||||
&member,
|
member.guild_id,
|
||||||
|
member.user.id,
|
||||||
mute_role_id,
|
mute_role_id,
|
||||||
amount_left as u64, // i64 as u64 is handled properly here
|
amount_left as u64, // i64 as u64 is handled properly here
|
||||||
),
|
),
|
||||||
|
7
src/utils/macros.rs
Normal file
7
src/utils/macros.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
macro_rules! to_user {
|
||||||
|
($user:ident, $ctx:ident) => {{
|
||||||
|
$user.to_user(&$ctx.discord().http).await?
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use to_user;
|
@ -4,6 +4,7 @@ pub mod autorespond;
|
|||||||
pub mod bot;
|
pub mod bot;
|
||||||
pub mod decancer;
|
pub mod decancer;
|
||||||
pub mod embed;
|
pub mod embed;
|
||||||
|
pub mod macros;
|
||||||
pub mod media_channel;
|
pub mod media_channel;
|
||||||
pub mod moderation;
|
pub mod moderation;
|
||||||
pub mod poll;
|
pub mod poll;
|
||||||
|
@ -2,15 +2,17 @@ use std::cmp;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use mongodb::options::FindOptions;
|
use mongodb::options::FindOptions;
|
||||||
use poise::serenity_prelude::{ChannelId, GuildChannel, Http, Mentionable, User};
|
use poise::serenity_prelude::{
|
||||||
|
Cache, ChannelId, GuildChannel, GuildId, Http, Mentionable, User, UserId,
|
||||||
|
};
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error, warn};
|
||||||
|
|
||||||
use super::bot::get_data_lock;
|
use super::bot::get_data_lock;
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::db::database::Database;
|
use crate::db::database::Database;
|
||||||
use crate::db::model::Muted;
|
use crate::db::model::Muted;
|
||||||
use crate::model::application::Configuration;
|
use crate::model::application::{Configuration, Mute};
|
||||||
use crate::serenity::SerenityError;
|
use crate::serenity::SerenityError;
|
||||||
use crate::{Context, Error};
|
use crate::{Context, Error};
|
||||||
|
|
||||||
@ -72,23 +74,24 @@ pub async fn mute_on_join(ctx: &serenity::Context, new_member: &mut serenity::Me
|
|||||||
|
|
||||||
pub fn queue_unmute_member(
|
pub fn queue_unmute_member(
|
||||||
http: &Arc<Http>,
|
http: &Arc<Http>,
|
||||||
|
cache: &Arc<Cache>,
|
||||||
database: &Arc<Database>,
|
database: &Arc<Database>,
|
||||||
member: &Member,
|
guild_id: GuildId,
|
||||||
|
user_id: UserId,
|
||||||
mute_role_id: u64,
|
mute_role_id: u64,
|
||||||
mute_duration: u64,
|
mute_duration: u64,
|
||||||
) -> JoinHandle<Option<Error>> {
|
) -> JoinHandle<Option<Error>> {
|
||||||
|
let cache = cache.clone();
|
||||||
let http = http.clone();
|
let http = http.clone();
|
||||||
let database = database.clone();
|
let database = database.clone();
|
||||||
let mut member = member.clone();
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(mute_duration)).await;
|
tokio::time::sleep(std::time::Duration::from_secs(mute_duration)).await;
|
||||||
|
|
||||||
let delete_result = database
|
let delete_result = database
|
||||||
.find_and_delete::<Muted>(
|
.find_and_delete::<Muted>(
|
||||||
"muted",
|
"muted",
|
||||||
Muted {
|
Muted {
|
||||||
user_id: Some(member.user.id.0.to_string()),
|
user_id: Some(user_id.0.to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
@ -106,6 +109,8 @@ pub fn queue_unmute_member(
|
|||||||
.map(|r| RoleId::from(r.parse::<u64>().unwrap()))
|
.map(|r| RoleId::from(r.parse::<u64>().unwrap()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Update roles if they didn't leave the guild.
|
||||||
|
let mut member = cache.member(guild_id, user_id)?;
|
||||||
if let Err(add_role_result) = member.add_roles(&http, &taken_roles).await {
|
if let Err(add_role_result) = member.add_roles(&http, &taken_roles).await {
|
||||||
Some(Error::from(add_role_result))
|
Some(Error::from(add_role_result))
|
||||||
} else if let Err(remove_result) = member.remove_role(http, mute_role_id).await {
|
} else if let Err(remove_result) = member.remove_role(http, mute_role_id).await {
|
||||||
@ -363,3 +368,37 @@ pub async fn ban_moderation(ctx: &Context<'_>, kind: &BanKind) -> Option<Serenit
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn mute_moderation(
|
||||||
|
ctx: &Context<'_>,
|
||||||
|
member: &mut Member,
|
||||||
|
config: &Mute,
|
||||||
|
) -> Result<(bool, Vec<serenity::RoleId>), SerenityError> {
|
||||||
|
let mute_role_id = config.role;
|
||||||
|
let take = &config.take;
|
||||||
|
|
||||||
|
let is_currently_muted = member.roles.iter().any(|r| r.0 == mute_role_id);
|
||||||
|
|
||||||
|
member.add_role(&ctx.discord().http, mute_role_id).await?;
|
||||||
|
|
||||||
|
// accumulate all roles to take from the member
|
||||||
|
let removed_roles = member
|
||||||
|
.roles
|
||||||
|
.iter()
|
||||||
|
.filter(|r| take.contains(&r.0))
|
||||||
|
.copied()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
// take them from the member.
|
||||||
|
member
|
||||||
|
.remove_roles(
|
||||||
|
&ctx.discord().http,
|
||||||
|
&take.iter().map(|&r| RoleId::from(r)).collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Err(e) = member.disconnect_from_voice(&ctx.discord().http).await {
|
||||||
|
warn!("Could not disconnect member from voice channel: {}", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((is_currently_muted, removed_roles))
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user