mirror of
https://github.com/revanced/revanced-discord-bot.git
synced 2025-04-30 06:24:27 +02:00
feat: lock & unlock commands
This commit is contained in:
parent
4ab4c7e00c
commit
abdc0922eb
@ -1,10 +1,17 @@
|
||||
use bson::{doc, Document};
|
||||
use chrono::{Duration, Utc};
|
||||
use mongodb::options::{UpdateModifications, UpdateOptions};
|
||||
use poise::serenity_prelude::{self as serenity, Member, RoleId, User};
|
||||
use poise::serenity_prelude::{
|
||||
self as serenity,
|
||||
Member,
|
||||
PermissionOverwrite,
|
||||
Permissions,
|
||||
RoleId,
|
||||
User,
|
||||
};
|
||||
use tracing::{debug, trace};
|
||||
|
||||
use crate::db::model::Muted;
|
||||
use crate::db::model::{LockedChannel, Muted};
|
||||
use crate::utils::moderation::{
|
||||
ban_moderation,
|
||||
queue_unmute_member,
|
||||
@ -14,6 +21,135 @@ use crate::utils::moderation::{
|
||||
};
|
||||
use crate::{Context, Error};
|
||||
|
||||
/// Lock a channel.
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn lock(ctx: Context<'_>) -> Result<(), Error> {
|
||||
let data = &ctx.data().read().await;
|
||||
let configuration = &data.configuration;
|
||||
let database = &data.database;
|
||||
let discord = &ctx.discord();
|
||||
let cache = &discord.cache;
|
||||
let http = &discord.http;
|
||||
|
||||
let channel_id = ctx.channel_id().0;
|
||||
let channel = &cache.guild_channel(channel_id).unwrap();
|
||||
|
||||
let query: Document = LockedChannel {
|
||||
channel_id: Some(channel_id.to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
.into();
|
||||
|
||||
// Check if channel is already muted, if so succeed.
|
||||
if let Ok(mut cursor) = database
|
||||
.find::<LockedChannel>("locked", query.clone(), None)
|
||||
.await
|
||||
{
|
||||
if cursor.advance().await.unwrap() {
|
||||
respond_moderation(
|
||||
&ctx,
|
||||
&ModerationKind::Lock(
|
||||
channel.name.clone(),
|
||||
Some(Error::from("Channel already locked")),
|
||||
),
|
||||
configuration,
|
||||
)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// accumulate all roles with write permissions
|
||||
let permission_overwrites: Vec<_> = channel
|
||||
.permission_overwrites
|
||||
.iter()
|
||||
.filter_map(|r| {
|
||||
if r.allow.send_messages() || !r.deny.send_messages() {
|
||||
Some(r.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// lock the channel by and creating the new permission overwrite
|
||||
for permission_overwrite in &permission_overwrites {
|
||||
let permission = Permissions::SEND_MESSAGES & Permissions::ADD_REACTIONS;
|
||||
channel
|
||||
.create_permission(http, &PermissionOverwrite {
|
||||
allow: permission_overwrite.allow & !permission,
|
||||
deny: permission_overwrite.deny | permission,
|
||||
kind: permission_overwrite.kind,
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
|
||||
// save the original overwrites
|
||||
let updated: Document = LockedChannel {
|
||||
overwrites: Some(permission_overwrites),
|
||||
..Default::default()
|
||||
}
|
||||
.into();
|
||||
|
||||
database
|
||||
.update::<LockedChannel>(
|
||||
"locked",
|
||||
query,
|
||||
UpdateModifications::Document(doc! { "$set": updated}),
|
||||
Some(UpdateOptions::builder().upsert(true).build()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
respond_moderation(
|
||||
&ctx,
|
||||
&ModerationKind::Lock(channel.name.clone(), None),
|
||||
configuration,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Unlock a channel.
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn unlock(ctx: Context<'_>) -> Result<(), Error> {
|
||||
let data = &ctx.data().read().await;
|
||||
let configuration = &data.configuration;
|
||||
let database = &data.database;
|
||||
let discord = &ctx.discord();
|
||||
let cache = &discord.cache;
|
||||
let http = &discord.http;
|
||||
|
||||
let channel_id = ctx.channel_id().0;
|
||||
|
||||
let delete_result = database
|
||||
.find_and_delete::<LockedChannel>(
|
||||
"locked",
|
||||
LockedChannel {
|
||||
channel_id: Some(channel_id.to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
.into(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
let channel = cache.guild_channel(channel_id).unwrap();
|
||||
let mut error = None;
|
||||
if let Ok(Some(locked_channel)) = delete_result {
|
||||
for overwrite in &locked_channel.overwrites.unwrap() {
|
||||
channel.create_permission(http, overwrite).await?;
|
||||
}
|
||||
} else {
|
||||
error = Some(Error::from("Channel already unlocked"))
|
||||
}
|
||||
|
||||
respond_moderation(
|
||||
&ctx,
|
||||
&ModerationKind::Unlock(channel.name.clone(), error), // TODO: handle error
|
||||
configuration,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Unmute a member.
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn unmute(
|
||||
@ -30,10 +166,7 @@ pub async fn unmute(
|
||||
pending_unmute.abort();
|
||||
}
|
||||
|
||||
respond_moderation(
|
||||
&ctx,
|
||||
&ModerationKind::Unmute(
|
||||
queue_unmute_member(
|
||||
let queue = queue_unmute_member(
|
||||
&ctx.discord().http,
|
||||
&data.database,
|
||||
&member,
|
||||
@ -41,10 +174,12 @@ pub async fn unmute(
|
||||
0,
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
),
|
||||
&member.user,
|
||||
&configuration,
|
||||
.unwrap();
|
||||
|
||||
respond_moderation(
|
||||
&ctx,
|
||||
&ModerationKind::Unmute(member.user, queue),
|
||||
configuration,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@ -175,9 +310,13 @@ pub async fn mute(
|
||||
|
||||
respond_moderation(
|
||||
&ctx,
|
||||
&ModerationKind::Mute(reason, format!("<t:{}:F>", unmute_time.timestamp()), result),
|
||||
&member.user,
|
||||
&configuration,
|
||||
&ModerationKind::Mute(
|
||||
member.user,
|
||||
reason,
|
||||
format!("<t:{}:F>", unmute_time.timestamp()),
|
||||
result,
|
||||
),
|
||||
configuration,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@ -207,9 +346,7 @@ pub async fn purge(
|
||||
let too_old_timestamp = Utc::now().timestamp() - MAX_BULK_DELETE_AGO_SECS;
|
||||
|
||||
let current_user = ctx.discord().http.get_current_user().await?;
|
||||
let image = current_user
|
||||
.avatar_url()
|
||||
.unwrap_or_else(|| current_user.default_avatar_url());
|
||||
let image = current_user.face();
|
||||
|
||||
let handle = ctx
|
||||
.send(|f| {
|
||||
@ -314,22 +451,16 @@ pub async fn unban(ctx: Context<'_>, #[description = "User"] user: User) -> Resu
|
||||
async fn handle_ban(ctx: &Context<'_>, kind: &BanKind) -> Result<(), Error> {
|
||||
let data = ctx.data().read().await;
|
||||
|
||||
let ban_result = ban_moderation(&ctx, &kind).await;
|
||||
let ban_result = ban_moderation(ctx, kind).await;
|
||||
|
||||
let moderated_user;
|
||||
respond_moderation(
|
||||
&ctx,
|
||||
ctx,
|
||||
&match kind {
|
||||
BanKind::Ban(user, _, reason) => {
|
||||
moderated_user = user;
|
||||
ModerationKind::Ban(reason.clone(), ban_result)
|
||||
ModerationKind::Ban(user.clone(), reason.clone(), ban_result)
|
||||
},
|
||||
BanKind::Unban(user) => {
|
||||
moderated_user = user;
|
||||
ModerationKind::Unban(ban_result)
|
||||
BanKind::Unban(user) => ModerationKind::Unban(user.clone(), ban_result),
|
||||
},
|
||||
},
|
||||
&moderated_user,
|
||||
&data.configuration,
|
||||
)
|
||||
.await
|
||||
|
@ -1,6 +1,7 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use bson::Document;
|
||||
use poise::serenity_prelude::{PermissionOverwrite};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with_macros::skip_serializing_none;
|
||||
|
||||
@ -15,12 +16,32 @@ pub struct Muted {
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||
pub struct LockedChannel {
|
||||
pub channel_id: Option<String>,
|
||||
pub overwrites: Option<Vec<PermissionOverwrite>>,
|
||||
}
|
||||
|
||||
impl From<Muted> for Document {
|
||||
fn from(muted: Muted) -> Self {
|
||||
bson::to_document(&muted).unwrap()
|
||||
to_document(&muted)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LockedChannel> for Document {
|
||||
fn from(locked: LockedChannel) -> Self {
|
||||
to_document(&locked)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_document<T>(t: &T) -> Document
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
bson::to_document(t).unwrap()
|
||||
}
|
||||
|
||||
// Display trait
|
||||
impl Display for Muted {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
|
@ -50,6 +50,8 @@ async fn main() {
|
||||
moderation::purge(),
|
||||
moderation::ban(),
|
||||
moderation::unban(),
|
||||
moderation::lock(),
|
||||
moderation::unlock(),
|
||||
misc::reply(),
|
||||
];
|
||||
poise::set_qualified_names(&mut commands);
|
||||
|
@ -15,10 +15,12 @@ use crate::serenity::SerenityError;
|
||||
use crate::{Context, Error};
|
||||
|
||||
pub enum ModerationKind {
|
||||
Mute(String, String, Option<Error>), // Reason, Expires, Error
|
||||
Unmute(Option<Error>), // Error
|
||||
Ban(Option<String>, Option<SerenityError>), // Reason, Error
|
||||
Unban(Option<SerenityError>), // Error
|
||||
Mute(User, String, String, Option<Error>), // User, Reason, Expires, Error
|
||||
Unmute(User, Option<Error>), // User, Error
|
||||
Ban(User, Option<String>, Option<SerenityError>), // User, Reason, Error
|
||||
Unban(User, Option<SerenityError>), // User, Error
|
||||
Lock(String, Option<Error>), // Channel name, Error
|
||||
Unlock(String, Option<Error>), // Channel name, Error
|
||||
}
|
||||
pub enum BanKind {
|
||||
Ban(User, Option<u8>, Option<String>), // User, Amount of days to delete messages, Reason
|
||||
@ -117,38 +119,48 @@ pub fn queue_unmute_member(
|
||||
pub async fn respond_moderation<'a>(
|
||||
ctx: &Context<'_>,
|
||||
moderation: &ModerationKind,
|
||||
user: &serenity::User,
|
||||
configuration: &Configuration,
|
||||
) -> Result<(), Error> {
|
||||
let current_user = ctx.discord().http.get_current_user().await?;
|
||||
|
||||
let create_embed = |f: &mut serenity::CreateEmbed| {
|
||||
let tag = user.tag();
|
||||
match moderation {
|
||||
ModerationKind::Mute(reason, expires, error) => match error {
|
||||
Some(err) => f.title(format!("Failed to mute {}", tag)).field(
|
||||
let mut moderated_user: Option<&User> = None;
|
||||
|
||||
let result = match moderation {
|
||||
ModerationKind::Mute(user, reason, expires, error) => {
|
||||
moderated_user = Some(user);
|
||||
|
||||
match error {
|
||||
Some(err) => f.title(format!("Failed to mute {}", user.tag())).field(
|
||||
"Exception",
|
||||
err.to_string(),
|
||||
false,
|
||||
),
|
||||
None => f.title(format!("Muted {}", tag)),
|
||||
None => f.title(format!("Muted {}", user.tag())),
|
||||
}
|
||||
.field("Reason", reason, false)
|
||||
.field("Expires", expires, false),
|
||||
ModerationKind::Unmute(error) => match error {
|
||||
Some(err) => f.title(format!("Failed to unmute {}", tag)).field(
|
||||
"Exception",
|
||||
err.to_string(),
|
||||
false,
|
||||
),
|
||||
None => f.title(format!("Unmuted {}", tag)),
|
||||
.field("Expires", expires, false)
|
||||
},
|
||||
ModerationKind::Ban(reason, error) => {
|
||||
let f = match error {
|
||||
Some(err) => f.title(format!("Failed to ban {}", tag)).field(
|
||||
ModerationKind::Unmute(user, error) => {
|
||||
moderated_user = Some(user);
|
||||
match error {
|
||||
Some(err) => f.title(format!("Failed to unmute {}", user.tag())).field(
|
||||
"Exception",
|
||||
err.to_string(),
|
||||
false,
|
||||
),
|
||||
None => f.title(format!("Banned {}", tag)),
|
||||
None => f.title(format!("Unmuted {}", user.tag())),
|
||||
}
|
||||
},
|
||||
ModerationKind::Ban(user, reason, error) => {
|
||||
moderated_user = Some(user);
|
||||
let f = match error {
|
||||
Some(err) => f.title(format!("Failed to ban {}", user.tag())).field(
|
||||
"Exception",
|
||||
err.to_string(),
|
||||
false,
|
||||
),
|
||||
None => f.title(format!("Banned {}", user.tag())),
|
||||
};
|
||||
if let Some(reason) = reason {
|
||||
f.field("Reason", reason, false)
|
||||
@ -156,21 +168,47 @@ pub async fn respond_moderation<'a>(
|
||||
f
|
||||
}
|
||||
},
|
||||
ModerationKind::Unban(error) => match error {
|
||||
Some(err) => f.title(format!("Failed to unban {}", tag)).field(
|
||||
ModerationKind::Unban(user, error) => {
|
||||
moderated_user = Some(user);
|
||||
match error {
|
||||
Some(err) => f.title(format!("Failed to unban {}", user.tag())).field(
|
||||
"Exception",
|
||||
err.to_string(),
|
||||
false,
|
||||
),
|
||||
None => f.title(format!("Unbanned {}", tag)),
|
||||
None => f.title(format!("Unbanned {}", user.tag())),
|
||||
}
|
||||
},
|
||||
ModerationKind::Lock(channel, error) => match error {
|
||||
Some(err) => f.title(format!("Failed to lock {} ", channel)).field(
|
||||
"Exception",
|
||||
err.to_string(),
|
||||
false,
|
||||
),
|
||||
None => f.title(format!("Locked {}", channel)).description(
|
||||
"Unlocking the channel will restore the original permission overwrites.",
|
||||
),
|
||||
},
|
||||
ModerationKind::Unlock(channel, error) => match error {
|
||||
Some(err) => f.title(format!("Failed to unlock {}", channel)).field(
|
||||
"Exception",
|
||||
err.to_string(),
|
||||
false,
|
||||
),
|
||||
None => f
|
||||
.title(format!("Unlocked {}", channel))
|
||||
.description("Restored original permission overwrites."),
|
||||
},
|
||||
}
|
||||
.color(configuration.general.embed_color)
|
||||
.thumbnail(
|
||||
&user
|
||||
.avatar_url()
|
||||
.unwrap_or_else(|| user.default_avatar_url()),
|
||||
);
|
||||
.color(configuration.general.embed_color);
|
||||
|
||||
let user = if let Some(user) = moderated_user {
|
||||
user.face()
|
||||
} else {
|
||||
current_user.face()
|
||||
};
|
||||
|
||||
result.thumbnail(&user);
|
||||
};
|
||||
|
||||
let reply = ctx
|
||||
|
Loading…
x
Reference in New Issue
Block a user