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 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,21 +166,20 @@ pub async fn unmute(
pending_unmute.abort();
}
let queue = queue_unmute_member(
&ctx.discord().http,
&data.database,
&member,
configuration.general.mute.role,
0,
)
.await
.unwrap();
respond_moderation(
&ctx,
&ModerationKind::Unmute(
queue_unmute_member(
&ctx.discord().http,
&data.database,
&member,
configuration.general.mute.role,
0,
)
.await
.unwrap(),
),
&member.user,
&configuration,
&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)
},
BanKind::Unban(user) => {
moderated_user = user;
ModerationKind::Unban(ban_result)
ModerationKind::Ban(user.clone(), reason.clone(), ban_result)
},
BanKind::Unban(user) => ModerationKind::Unban(user.clone(), ban_result),
},
&moderated_user,
&data.configuration,
)
.await

View File

@ -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 {

View File

@ -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);

View File

@ -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(
"Exception",
err.to_string(),
false,
),
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(
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!("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 {
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 {}", 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!("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)
.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