aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorIRONM00N <64110067+IRONM00N@users.noreply.github.com>2022-07-06 22:10:58 +0200
committerIRONM00N <64110067+IRONM00N@users.noreply.github.com>2022-07-06 22:10:58 +0200
commitadbb5e939cebcf4c0479d66162377957f2a845af (patch)
treee69b237c79fac2701f162fce6076ec1f85f617a9 /src/lib
parent22b3a8af49dc16bf5d8f9c3ce48b4770505ea33b (diff)
downloadtanzanite-adbb5e939cebcf4c0479d66162377957f2a845af.tar.gz
tanzanite-adbb5e939cebcf4c0479d66162377957f2a845af.tar.bz2
tanzanite-adbb5e939cebcf4c0479d66162377957f2a845af.zip
feat(automod): unmute button
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/common/AutoMod.ts148
-rw-r--r--src/lib/common/util/Moderation.ts24
-rw-r--r--src/lib/extensions/discord.js/ExtendedGuildMember.ts32
-rw-r--r--src/lib/utils/BushUtils.ts1
4 files changed, 137 insertions, 68 deletions
diff --git a/src/lib/common/AutoMod.ts b/src/lib/common/AutoMod.ts
index 970fecd..21bcb00 100644
--- a/src/lib/common/AutoMod.ts
+++ b/src/lib/common/AutoMod.ts
@@ -1,4 +1,4 @@
-import { banResponse, colors, emojis, format, formatError, Moderation } from '#lib';
+import { colors, emojis, format, formatError, Moderation, unmuteResponse } from '#lib';
import assert from 'assert';
import chalk from 'chalk';
import {
@@ -10,8 +10,10 @@ import {
PermissionFlagsBits,
type ButtonInteraction,
type Message,
+ type Snowflake,
type TextChannel
} from 'discord.js';
+import UnmuteCommand from '../../commands/moderation/unmute.js';
/**
* Handles auto moderation functionality.
@@ -165,21 +167,14 @@ export class AutoMod {
.setDescription(
`**User:** ${this.message.author} (${this.message.author.tag})\n**Sent From:** <#${this.message.channel.id}> [Jump to context](${this.message.url})`
)
- .addFields([
- { name: 'Message Content', value: `${await this.message.client.utils.codeblock(this.message.content, 1024)}` }
- ])
+ .addFields({
+ name: 'Message Content',
+ value: `${await this.message.client.utils.codeblock(this.message.content, 1024)}`
+ })
.setColor(color)
.setTimestamp()
],
- components: [
- new ActionRowBuilder<ButtonBuilder>().addComponents([
- new ButtonBuilder({
- style: ButtonStyle.Danger,
- label: 'Ban User',
- customId: `automod;ban;${this.message.author.id};everyone mention and scam phrase`
- })
- ])
- ]
+ components: [this.buttons(this.message.author.id, 'everyone mention and scam phrase')]
});
}
}
@@ -332,28 +327,33 @@ export class AutoMod {
this.message.channel.id
}> [Jump to context](${this.message.url})\n**Blacklisted Words:** ${offenses.map((o) => `\`${o.match}\``).join(', ')}`
)
- .addFields([
- { name: 'Message Content', value: `${await this.message.client.utils.codeblock(this.message.content, 1024)}` }
- ])
+ .addFields({
+ name: 'Message Content',
+ value: `${await this.message.client.utils.codeblock(this.message.content, 1024)}`
+ })
.setColor(color)
.setTimestamp()
.setAuthor({ name: this.message.author.tag, url: this.message.author.displayAvatarURL() })
],
- components:
- highestOffence.severity >= 2
- ? [
- new ActionRowBuilder<ButtonBuilder>().addComponents([
- new ButtonBuilder({
- style: ButtonStyle.Danger,
- label: 'Ban User',
- customId: `automod;ban;${this.message.author.id};${highestOffence.reason}`
- })
- ])
- ]
- : undefined
+ components: highestOffence.severity >= 2 ? [this.buttons(this.message.author.id, highestOffence.reason)] : undefined
});
}
+ private buttons(userId: Snowflake, reason: string): ActionRowBuilder<ButtonBuilder> {
+ return new ActionRowBuilder<ButtonBuilder>().addComponents(
+ new ButtonBuilder({
+ style: ButtonStyle.Danger,
+ label: 'Ban User',
+ customId: `automod;ban;${userId};${reason}`
+ }),
+ new ButtonBuilder({
+ style: ButtonStyle.Success,
+ label: 'Unmute User',
+ customId: `automod;unmute;${userId}`
+ })
+ );
+ }
+
/**
* Handles the ban button in the automod log.
* @param interaction The button interaction.
@@ -364,23 +364,31 @@ export class AutoMod {
content: `${emojis.error} You are missing the **Ban Members** permission.`,
ephemeral: true
});
- const [action, userId, reason] = interaction.customId.replace('automod;', '').split(';');
- switch (action) {
- case 'ban': {
- const victim = await interaction.guild!.members.fetch(userId).catch(() => null);
- const moderator =
- interaction.member instanceof GuildMember
- ? interaction.member
- : await interaction.guild!.members.fetch(interaction.user.id);
+ const [action, userId, reason] = interaction.customId.replace('automod;', '').split(';') as [
+ 'ban' | 'unmute',
+ string,
+ string
+ ];
- const check = victim ? await Moderation.permissionCheck(moderator, victim, 'ban', true) : true;
+ if ((['ban', 'unmute'] as const).includes(action)) throw new TypeError(`Invalid automod button action: ${action}`);
+
+ const victim = await interaction.guild!.members.fetch(userId).catch(() => null);
+ const moderator =
+ interaction.member instanceof GuildMember
+ ? interaction.member
+ : await interaction.guild!.members.fetch(interaction.user.id);
- if (check !== true)
+ switch (action) {
+ case 'ban': {
+ if (!interaction.guild?.members.me?.permissions.has('BanMembers'))
return interaction.reply({
- content: check,
+ content: `${emojis.error} I do not have permission to ${action} members.`,
ephemeral: true
});
+ const check = victim ? await Moderation.permissionCheck(moderator, victim, 'ban', true) : true;
+ if (check !== true) return interaction.reply({ content: check, ephemeral: true });
+
const result = await interaction.guild?.bushBan({
user: userId,
reason,
@@ -389,21 +397,65 @@ export class AutoMod {
});
const victimUserFormatted = (await interaction.client.utils.resolveNonCachedUser(userId))?.tag ?? userId;
- if (result === banResponse.SUCCESS)
- return interaction.reply({
- content: `${emojis.success} Successfully banned **${victimUserFormatted}**.`,
- ephemeral: true
- });
- else if (result === banResponse.DM_ERROR)
+
+ const content = (() => {
+ if (result === unmuteResponse.SUCCESS) {
+ return `${emojis.success} Successfully banned ${format.input(victimUserFormatted)}.`;
+ } else if (result === unmuteResponse.DM_ERROR) {
+ return `${emojis.warn} Banned ${format.input(victimUserFormatted)} however I could not send them a dm.`;
+ } else {
+ return `${emojis.error} Could not ban ${format.input(victimUserFormatted)}: \`${result}\` .`;
+ }
+ })();
+
+ return interaction.reply({
+ content: content,
+ ephemeral: true
+ });
+ }
+
+ case 'unmute': {
+ if (!victim)
return interaction.reply({
- content: `${emojis.warn} Banned ${victimUserFormatted} however I could not send them a dm.`,
+ content: `${emojis.error} Cannot find member, they may have left the server.`,
ephemeral: true
});
- else
+
+ if (!interaction.guild)
return interaction.reply({
- content: `${emojis.error} Could not ban **${victimUserFormatted}**: \`${result}\` .`,
+ content: `${emojis.error} This is weird, I don't seem to be in the server...`,
ephemeral: true
});
+
+ const check = await Moderation.permissionCheck(moderator, victim, 'unmute', true);
+ if (check !== true) return interaction.reply({ content: check, ephemeral: true });
+
+ const check2 = await Moderation.checkMutePermissions(interaction.guild);
+ if (check2 !== true)
+ return interaction.reply({ content: UnmuteCommand.formatCode('/', victim!, check2), ephemeral: true });
+
+ const result = await victim.bushUnmute({
+ reason,
+ moderator: interaction.member as GuildMember,
+ evidence: (interaction.message as Message).url ?? undefined
+ });
+
+ const victimUserFormatted = victim.user.tag;
+
+ const content = (() => {
+ if (result === unmuteResponse.SUCCESS) {
+ return `${emojis.success} Successfully unmuted ${format.input(victimUserFormatted)}.`;
+ } else if (result === unmuteResponse.DM_ERROR) {
+ return `${emojis.warn} Unmuted ${format.input(victimUserFormatted)} however I could not send them a dm.`;
+ } else {
+ return `${emojis.error} Could not unmute ${format.input(victimUserFormatted)}: \`${result}\` .`;
+ }
+ })();
+
+ return interaction.reply({
+ content: content,
+ ephemeral: true
+ });
}
}
}
diff --git a/src/lib/common/util/Moderation.ts b/src/lib/common/util/Moderation.ts
index cb6b4db..fc01602 100644
--- a/src/lib/common/util/Moderation.ts
+++ b/src/lib/common/util/Moderation.ts
@@ -1,13 +1,16 @@
import {
ActivePunishment,
ActivePunishmentType,
+ baseMuteResponse,
colors,
emojis,
format,
Guild as GuildDB,
humanizeDuration,
ModLog,
- type ModLogType
+ permissionsResponse,
+ type ModLogType,
+ type ValueOf
} from '#lib';
import assert from 'assert';
import {
@@ -119,6 +122,25 @@ export async function permissionCheck(
}
/**
+ * Performs permission checks that are required in order to (un)mute a member.
+ * @param guild The guild to check the mute permissions in.
+ * @returns A {@link MuteResponse} or true if nothing failed.
+ */
+export async function checkMutePermissions(
+ guild: Guild
+): Promise<ValueOf<typeof baseMuteResponse> | ValueOf<typeof permissionsResponse> | true> {
+ if (!guild.members.me!.permissions.has('ManageRoles')) return permissionsResponse.MISSING_PERMISSIONS;
+ const muteRoleID = await guild.getSetting('muteRole');
+ if (!muteRoleID) return baseMuteResponse.NO_MUTE_ROLE;
+ const muteRole = guild.roles.cache.get(muteRoleID);
+ if (!muteRole) return baseMuteResponse.MUTE_ROLE_INVALID;
+ if (muteRole.position >= guild.members.me!.roles.highest.position || muteRole.managed)
+ return baseMuteResponse.MUTE_ROLE_NOT_MANAGEABLE;
+
+ return true;
+}
+
+/**
* Creates a modlog entry for a punishment.
* @param options Options for creating a modlog entry.
* @param getCaseNumber Whether or not to get the case number of the entry.
diff --git a/src/lib/extensions/discord.js/ExtendedGuildMember.ts b/src/lib/extensions/discord.js/ExtendedGuildMember.ts
index 947f9cd..f8add83 100644
--- a/src/lib/extensions/discord.js/ExtendedGuildMember.ts
+++ b/src/lib/extensions/discord.js/ExtendedGuildMember.ts
@@ -1,11 +1,11 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
-import { BushClientEvents, formatError, Moderation, ModLogType, PunishmentTypeDM, Time } from '#lib';
+import { formatError, Moderation, ModLogType, Time, type BushClientEvents, type PunishmentTypeDM, type ValueOf } from '#lib';
import {
ChannelType,
- GuildChannelResolvable,
GuildMember,
- GuildTextBasedChannel,
PermissionFlagsBits,
+ type GuildChannelResolvable,
+ type GuildTextBasedChannel,
type Role
} from 'discord.js';
/* eslint-enable @typescript-eslint/no-unused-vars */
@@ -358,13 +358,11 @@ export class ExtendedGuildMember extends GuildMember {
*/
public override async bushMute(options: BushTimedPunishmentOptions): Promise<MuteResponse> {
// checks
- if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return muteResponse.MISSING_PERMISSIONS;
- const muteRoleID = await this.guild.getSetting('muteRole');
- if (!muteRoleID) return muteResponse.NO_MUTE_ROLE;
- const muteRole = this.guild.roles.cache.get(muteRoleID);
- if (!muteRole) return muteResponse.MUTE_ROLE_INVALID;
- if (muteRole.position >= this.guild.members.me!.roles.highest.position || muteRole.managed)
- return muteResponse.MUTE_ROLE_NOT_MANAGEABLE;
+ const checks = await Moderation.checkMutePermissions(this.guild);
+ if (checks !== true) return checks;
+
+ const muteRoleID = (await this.guild.getSetting('muteRole'))!;
+ const muteRole = this.guild.roles.cache.get(muteRoleID)!;
let caseID: string | undefined = undefined;
let dmSuccessEvent: boolean | undefined = undefined;
@@ -446,13 +444,11 @@ export class ExtendedGuildMember extends GuildMember {
*/
public override async bushUnmute(options: BushPunishmentOptions): Promise<UnmuteResponse> {
// checks
- if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return unmuteResponse.MISSING_PERMISSIONS;
- const muteRoleID = await this.guild.getSetting('muteRole');
- if (!muteRoleID) return unmuteResponse.NO_MUTE_ROLE;
- const muteRole = this.guild.roles.cache.get(muteRoleID);
- if (!muteRole) return unmuteResponse.MUTE_ROLE_INVALID;
- if (muteRole.position >= this.guild.members.me!.roles.highest.position || muteRole.managed)
- return unmuteResponse.MUTE_ROLE_NOT_MANAGEABLE;
+ const checks = await Moderation.checkMutePermissions(this.guild);
+ if (checks !== true) return checks;
+
+ const muteRoleID = (await this.guild.getSetting('muteRole'))!;
+ const muteRole = this.guild.roles.cache.get(muteRoleID)!;
let caseID: string | undefined = undefined;
let dmSuccessEvent: boolean | undefined = undefined;
@@ -1090,8 +1086,6 @@ export interface BushTimeoutOptions extends BushPunishmentOptions {
duration: number;
}
-type ValueOf<T> = T[keyof T];
-
export const basePunishmentResponse = Object.freeze({
SUCCESS: 'success',
MODLOG_ERROR: 'error creating modlog entry',
diff --git a/src/lib/utils/BushUtils.ts b/src/lib/utils/BushUtils.ts
index a6463cf..059d001 100644
--- a/src/lib/utils/BushUtils.ts
+++ b/src/lib/utils/BushUtils.ts
@@ -32,6 +32,7 @@ import { inspect as inspectUtil, promisify } from 'util';
import * as Format from '../common/util/Format.js';
export type StripPrivate<T> = { [K in keyof T]: T[K] extends Record<string, any> ? StripPrivate<T[K]> : T[K] };
+export type ValueOf<T> = T[keyof T];
/**
* Capitalizes the first letter of the given text