feat: lock & unlock commands

This commit is contained in:
oSumAtrIX 2022-08-30 02:27:15 +02:00
parent 4ab4c7e00c
commit abdc0922eb
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
4 changed files with 262 additions and 70 deletions

View File

@ -1,10 +1,17 @@
use bson::{doc, Document}; 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::{self as serenity, Member, RoleId, User}; use poise::serenity_prelude::{
self as serenity,
Member,
PermissionOverwrite,
Permissions,
RoleId,
User,
};
use tracing::{debug, trace}; use tracing::{debug, trace};
use crate::db::model::Muted; use crate::db::model::{LockedChannel, Muted};
use crate::utils::moderation::{ use crate::utils::moderation::{
ban_moderation, ban_moderation,
queue_unmute_member, queue_unmute_member,
@ -14,6 +21,135 @@ use crate::utils::moderation::{
}; };
use crate::{Context, Error}; 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. /// Unmute a member.
#[poise::command(slash_command)] #[poise::command(slash_command)]
pub async fn unmute( pub async fn unmute(
@ -30,21 +166,20 @@ pub async fn unmute(
pending_unmute.abort(); pending_unmute.abort();
} }
let queue = queue_unmute_member(
&ctx.discord().http,
&data.database,
&member,
configuration.general.mute.role,
0,
)
.await
.unwrap();
respond_moderation( respond_moderation(
&ctx, &ctx,
&ModerationKind::Unmute( &ModerationKind::Unmute(member.user, queue),
queue_unmute_member( configuration,
&ctx.discord().http,
&data.database,
&member,
configuration.general.mute.role,
0,
)
.await
.unwrap(),
),
&member.user,
&configuration,
) )
.await .await
} }
@ -175,9 +310,13 @@ pub async fn mute(
respond_moderation( respond_moderation(
&ctx, &ctx,
&ModerationKind::Mute(reason, format!("<t:{}:F>", unmute_time.timestamp()), result), &ModerationKind::Mute(
&member.user, member.user,
&configuration, reason,
format!("<t:{}:F>", unmute_time.timestamp()),
result,
),
configuration,
) )
.await .await
} }
@ -207,9 +346,7 @@ pub async fn purge(
let too_old_timestamp = Utc::now().timestamp() - MAX_BULK_DELETE_AGO_SECS; let too_old_timestamp = Utc::now().timestamp() - MAX_BULK_DELETE_AGO_SECS;
let current_user = ctx.discord().http.get_current_user().await?; let current_user = ctx.discord().http.get_current_user().await?;
let image = current_user let image = current_user.face();
.avatar_url()
.unwrap_or_else(|| current_user.default_avatar_url());
let handle = ctx let handle = ctx
.send(|f| { .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> { async fn handle_ban(ctx: &Context<'_>, kind: &BanKind) -> Result<(), Error> {
let data = ctx.data().read().await; 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( respond_moderation(
&ctx, ctx,
&match kind { &match kind {
BanKind::Ban(user, _, reason) => { BanKind::Ban(user, _, reason) => {
moderated_user = user; ModerationKind::Ban(user.clone(), reason.clone(), ban_result)
ModerationKind::Ban(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, &data.configuration,
) )
.await .await

View File

@ -1,6 +1,7 @@
use std::fmt::Display; use std::fmt::Display;
use bson::Document; use bson::Document;
use poise::serenity_prelude::{PermissionOverwrite};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with_macros::skip_serializing_none; use serde_with_macros::skip_serializing_none;
@ -15,12 +16,32 @@ pub struct Muted {
pub reason: Option<String>, 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 { impl From<Muted> for Document {
fn from(muted: Muted) -> Self { 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 // Display trait
impl Display for Muted { impl Display for Muted {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

View File

@ -50,6 +50,8 @@ async fn main() {
moderation::purge(), moderation::purge(),
moderation::ban(), moderation::ban(),
moderation::unban(), moderation::unban(),
moderation::lock(),
moderation::unlock(),
misc::reply(), misc::reply(),
]; ];
poise::set_qualified_names(&mut commands); poise::set_qualified_names(&mut commands);

View File

@ -15,10 +15,12 @@ use crate::serenity::SerenityError;
use crate::{Context, Error}; use crate::{Context, Error};
pub enum ModerationKind { pub enum ModerationKind {
Mute(String, String, Option<Error>), // Reason, Expires, Error Mute(User, String, String, Option<Error>), // User, Reason, Expires, Error
Unmute(Option<Error>), // Error Unmute(User, Option<Error>), // User, Error
Ban(Option<String>, Option<SerenityError>), // Reason, Error Ban(User, Option<String>, Option<SerenityError>), // User, Reason, Error
Unban(Option<SerenityError>), // Error Unban(User, Option<SerenityError>), // User, Error
Lock(String, Option<Error>), // Channel name, Error
Unlock(String, Option<Error>), // Channel name, Error
} }
pub enum BanKind { pub enum BanKind {
Ban(User, Option<u8>, Option<String>), // User, Amount of days to delete messages, Reason 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>( pub async fn respond_moderation<'a>(
ctx: &Context<'_>, ctx: &Context<'_>,
moderation: &ModerationKind, moderation: &ModerationKind,
user: &serenity::User,
configuration: &Configuration, configuration: &Configuration,
) -> Result<(), Error> { ) -> Result<(), Error> {
let current_user = ctx.discord().http.get_current_user().await?;
let create_embed = |f: &mut serenity::CreateEmbed| { let create_embed = |f: &mut serenity::CreateEmbed| {
let tag = user.tag(); let mut moderated_user: Option<&User> = None;
match moderation {
ModerationKind::Mute(reason, expires, error) => match error { let result = match moderation {
Some(err) => f.title(format!("Failed to mute {}", tag)).field( ModerationKind::Mute(user, reason, expires, error) => {
"Exception", moderated_user = Some(user);
err.to_string(),
false, match error {
), Some(err) => f.title(format!("Failed to mute {}", user.tag())).field(
None => f.title(format!("Muted {}", 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)),
},
ModerationKind::Ban(reason, error) => {
let f = match error {
Some(err) => f.title(format!("Failed to ban {}", tag)).field(
"Exception", "Exception",
err.to_string(), err.to_string(),
false, false,
), ),
None => f.title(format!("Banned {}", tag)), None => f.title(format!("Muted {}", user.tag())),
}
.field("Reason", reason, false)
.field("Expires", expires, false)
},
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!("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 { if let Some(reason) = reason {
f.field("Reason", reason, false) f.field("Reason", reason, false)
@ -156,21 +168,47 @@ pub async fn respond_moderation<'a>(
f f
} }
}, },
ModerationKind::Unban(error) => match error { ModerationKind::Unban(user, error) => {
Some(err) => f.title(format!("Failed to unban {}", tag)).field( 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 {}", user.tag())),
}
},
ModerationKind::Lock(channel, error) => match error {
Some(err) => f.title(format!("Failed to lock {} ", channel)).field(
"Exception", "Exception",
err.to_string(), err.to_string(),
false, false,
), ),
None => f.title(format!("Unbanned {}", tag)), 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) .color(configuration.general.embed_color);
.thumbnail(
&user let user = if let Some(user) = moderated_user {
.avatar_url() user.face()
.unwrap_or_else(|| user.default_avatar_url()), } else {
); current_user.face()
};
result.thumbnail(&user);
}; };
let reply = ctx let reply = ctx