aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.eslintrc.json22
-rw-r--r--package.json26
-rw-r--r--src/arguments/discordEmoji.ts4
-rw-r--r--src/commands/config/config.ts1
-rw-r--r--src/commands/config/features.ts2
-rw-r--r--src/commands/dev/servers.ts4
-rw-r--r--src/commands/dev/test.ts10
-rw-r--r--src/commands/info/guildInfo.ts2
-rw-r--r--src/commands/info/help.ts3
-rw-r--r--src/commands/info/links.ts3
-rw-r--r--src/commands/info/userInfo.ts2
-rw-r--r--src/commands/moulberry-bush/capes.ts2
-rw-r--r--src/commands/utilities/highlight-!.ts2
-rw-r--r--src/commands/utilities/reminders.ts2
-rw-r--r--src/lib/common/AutoMod.ts2
-rw-r--r--src/lib/common/ButtonPaginator.ts7
-rw-r--r--src/lib/common/ConfirmationPrompt.ts2
-rw-r--r--src/lib/common/DeleteButton.ts1
-rw-r--r--src/lib/common/util/Moderation.ts124
-rw-r--r--src/lib/extensions/discord-akairo/BushClientUtil.ts2
-rw-r--r--src/lib/extensions/discord-akairo/BushCommand.ts6
-rw-r--r--src/lib/extensions/discord.js/BushGuild.ts29
-rw-r--r--src/lib/extensions/discord.js/BushGuildMember.ts127
-rw-r--r--src/lib/models/instance/Guild.ts9
-rw-r--r--src/lib/utils/BushConstants.ts4
-rw-r--r--src/listeners/client/interactionCreate.ts2
-rw-r--r--src/listeners/track-manual-punishments/modlogSyncBan.ts2
-rw-r--r--src/listeners/track-manual-punishments/modlogSyncKick.ts2
-rw-r--r--src/listeners/track-manual-punishments/modlogSyncTimeout.ts2
-rw-r--r--src/listeners/track-manual-punishments/modlogSyncUnban.ts2
-rw-r--r--src/listeners/ws/INTERACTION_CREATE.ts246
-rw-r--r--yarn.lock337
32 files changed, 694 insertions, 297 deletions
diff --git a/.eslintrc.json b/.eslintrc.json
index 1d9ffcf..5209b65 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -10,7 +10,7 @@
"sourceType": "module",
"project": "./tsconfig.json"
},
- "plugins": ["@typescript-eslint", "deprecation", "node", "import"],
+ "plugins": ["@typescript-eslint", "deprecation", "import"],
"ignorePatterns": ["dist"],
"rules": {
"no-return-await": "off",
@@ -61,8 +61,24 @@
"deprecation/deprecation": "warn",
"@typescript-eslint/explicit-member-accessibility": ["warn", { "accessibility": "explicit" }],
"@typescript-eslint/switch-exhaustiveness-check": "warn",
- "node/file-extension-in-import": ["error", "always", { "tryExtensions": [".js", ".json"] }],
"import/no-commonjs": "error",
- "import/extensions": ["error", "ignorePackages"]
+ "import/extensions": ["error", "ignorePackages"],
+ "@typescript-eslint/no-restricted-imports": [
+ "error",
+ {
+ "paths": [
+ {
+ "name": "discord-api-types",
+ "message": "Please use discord-api-types/v9 instead.",
+ "allowTypeImports": true
+ },
+ {
+ "name": "discord-api-types-next",
+ "message": "Please use discord-api-types-next/v9 instead.",
+ "allowTypeImports": true
+ }
+ ]
+ }
+ ]
}
}
diff --git a/package.json b/package.json
index 3489b35..c3ea3e6 100644
--- a/package.json
+++ b/package.json
@@ -34,10 +34,11 @@
"build:esbuild": "yarn rimraf dist && yarn esbuild --sourcemap=inline --outdir=dist --platform=node --target=es2020 --format=esm --log-level=warning src/**/*.ts",
"build:tsc": "yarn rimraf dist && yarn tsc",
"build:tsc:no-emit": "yarn rimraf dist && yarn tsc --noEmit",
- "_start": "yarn build:esbuild && node --enable-source-maps --experimental-json-modules --no-warnings dist/src/bot.js",
- "start": "yarn build:tsc && node --enable-source-maps --experimental-json-modules --no-warnings dist/src/bot.js",
+ "start:raw": "node --enable-source-maps --experimental-json-modules --no-warnings dist/src/bot.js",
+ "start:esbuild": "yarn build:esbuild && yarn start:raw",
+ "start": "yarn build:tsc && yarn start:raw",
"start:dry": "yarn start dry",
- "dev": "yarn build:tsc && node --enable-source-maps --experimental-json-modules --no-warnings dist/src/bot.js",
+ "dev": "yarn build:tsc && yarn start:raw",
"test": "yarn lint && yarn tsc --noEmit",
"format": "yarn prettier . --write",
"lint": "yarn eslint --ext js,jsx,ts,tsx src",
@@ -55,14 +56,15 @@
"@notenoughupdates/humanize-duration": "^4.0.1",
"@notenoughupdates/simplify-number": "^1.0.1",
"@notenoughupdates/wolfram-alpha-api": "^1.0.1",
- "@sentry/integrations": "^6.17.6",
- "@sentry/node": "^6.17.6",
- "@sentry/tracing": "^6.17.6",
+ "@sentry/integrations": "^6.17.7",
+ "@sentry/node": "^6.17.7",
+ "@sentry/tracing": "^6.17.7",
"canvas": "^2.9.0",
"chalk": "^5.0.0",
"deep-lock": "^1.0.0",
"discord-akairo": "npm:@notenoughupdates/discord-akairo@dev",
- "discord-api-types": "0.26.1",
+ "discord-api-types": "0.27.0",
+ "discord-api-types-next": "npm:discord-api-types@next",
"discord.js": "npm:@notenoughupdates/discord.js@dev",
"fuse.js": "^6.5.3",
"got": "^12.0.1",
@@ -74,7 +76,7 @@
"pg": "^8.7.3",
"pg-hstore": "^2.3.4",
"prettier": "^2.5.1",
- "pretty-bytes": "^5.6.0",
+ "pretty-bytes": "^6.0.0",
"rimraf": "^3.0.2",
"sequelize": "6.16.1",
"tinycolor2": "^1.4.2",
@@ -83,7 +85,7 @@
},
"devDependencies": {
"@sapphire/snowflake": "^3.1.0",
- "@sentry/types": "^6.17.6",
+ "@sentry/types": "^6.17.7",
"@types/eslint": "^8.4.1",
"@types/express": "^4.17.13",
"@types/lodash": "^4.14.178",
@@ -97,12 +99,14 @@
"@types/validator": "^13.7.1",
"@typescript-eslint/eslint-plugin": "^5.11.0",
"@typescript-eslint/parser": "^5.11.0",
- "eslint": "^8.8.0",
+ "eslint": "^8.9.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-deprecation": "^1.3.2",
"eslint-plugin-import": "^2.25.4",
- "eslint-plugin-node": "^11.1.0",
"node-fetch": "^3.2.0"
},
+ "resolutions": {
+ "@discordjs/rest@npm:^0.4.0-dev": "0.3.0"
+ },
"packageManager": "yarn@3.1.1"
}
diff --git a/src/arguments/discordEmoji.ts b/src/arguments/discordEmoji.ts
index d9428e1..efaa4dd 100644
--- a/src/arguments/discordEmoji.ts
+++ b/src/arguments/discordEmoji.ts
@@ -1,5 +1,5 @@
-import { type BushArgumentTypeCaster } from '#lib';
-import { type Snowflake } from 'discord-api-types';
+import type { BushArgumentTypeCaster } from '#lib';
+import type { Snowflake } from 'discord-api-types';
export const discordEmoji: BushArgumentTypeCaster<DiscordEmojiInfo | null> = (_, phrase) => {
if (!phrase) return null;
diff --git a/src/commands/config/config.ts b/src/commands/config/config.ts
index 2fae2fd..5346924 100644
--- a/src/commands/config/config.ts
+++ b/src/commands/config/config.ts
@@ -354,6 +354,7 @@ export default class ConfigCommand extends BushCommand {
};
const components = new ActionRow().addComponents(
+ // @ts-expect-error: outdated @discord.js/builders
new ButtonComponent().setStyle(ButtonStyle.Primary).setCustomId('command_settingsBack').setLabel('Back')
);
settingsEmbed.setDescription(
diff --git a/src/commands/config/features.ts b/src/commands/config/features.ts
index 2e7d623..7fa82a9 100644
--- a/src/commands/config/features.ts
+++ b/src/commands/config/features.ts
@@ -90,7 +90,7 @@ export default class FeaturesCommand extends BushCommand {
.setMaxValues(1)
.setMinValues(1)
.setOptions(
- guildFeatures.map((f) =>
+ ...guildFeatures.map((f) =>
new SelectMenuOption().setLabel(guildFeaturesObj[f].name).setValue(f).setDescription(guildFeaturesObj[f].description)
)
)
diff --git a/src/commands/dev/servers.ts b/src/commands/dev/servers.ts
index 173970b..378893e 100644
--- a/src/commands/dev/servers.ts
+++ b/src/commands/dev/servers.ts
@@ -1,6 +1,6 @@
import { BushCommand, ButtonPaginator, type BushMessage, type BushSlashMessage } from '#lib';
-import { APIEmbed } from 'discord-api-types';
-import { type Guild } from 'discord.js';
+import type { APIEmbed } from 'discord-api-types';
+import type { Guild } from 'discord.js';
export default class ServersCommand extends BushCommand {
public constructor() {
diff --git a/src/commands/dev/test.ts b/src/commands/dev/test.ts
index 669001f..9784412 100644
--- a/src/commands/dev/test.ts
+++ b/src/commands/dev/test.ts
@@ -1,5 +1,4 @@
import { BushCommand, ButtonPaginator, Shared, type BushMessage } from '#lib';
-// eslint-disable-next-line node/file-extension-in-import
import { Routes } from 'discord-api-types/rest/v9';
import {
ActionRow,
@@ -55,10 +54,15 @@ export default class TestCommand extends BushCommand {
if (['button', 'buttons'].includes(args?.feature?.toLowerCase())) {
const ButtonRow = new ActionRow().addComponents(
+ // @ts-expect-error: outdated @discord.js/builders
new ButtonComponent().setStyle(ButtonStyle.Primary).setCustomId('primaryButton').setLabel('Primary'),
+ // @ts-expect-error: outdated @discord.js/builders
new ButtonComponent().setStyle(ButtonStyle.Secondary).setCustomId('secondaryButton').setLabel('Secondary'),
+ // @ts-expect-error: outdated @discord.js/builders
new ButtonComponent().setStyle(ButtonStyle.Success).setCustomId('successButton').setLabel('Success'),
+ // @ts-expect-error: outdated @discord.js/builders
new ButtonComponent().setStyle(ButtonStyle.Danger).setCustomId('dangerButton').setLabel('Danger'),
+ // @ts-expect-error: outdated @discord.js/builders
new ButtonComponent().setStyle(ButtonStyle.Link).setLabel('Link').setURL('https://www.youtube.com/watch?v=dQw4w9WgXcQ')
);
return await message.util.reply({ content: 'buttons', components: [ButtonRow] });
@@ -78,6 +82,7 @@ export default class TestCommand extends BushCommand {
.setTitle('Title');
const buttonRow = new ActionRow().addComponents(
+ // @ts-expect-error: outdated @discord.js/builders
new ButtonComponent().setStyle(ButtonStyle.Link).setLabel('Link').setURL('https://google.com/')
);
return await message.util.reply({ content: 'Test', embeds: [embed], components: [buttonRow] });
@@ -87,6 +92,7 @@ export default class TestCommand extends BushCommand {
const row = new ActionRow();
for (let b = 1; b <= 5; b++) {
const id = (a + 5 * (b - 1)).toString();
+ // @ts-expect-error: outdated @discord.js/builders
const button = new ButtonComponent().setStyle(ButtonStyle.Primary).setCustomId(id).setLabel(id);
row.addComponents(button);
}
@@ -119,6 +125,7 @@ export default class TestCommand extends BushCommand {
const row = new ActionRow();
for (let b = 1; b <= 5; b++) {
const id = (a + 5 * (b - 1)).toString();
+ // @ts-expect-error: outdated @discord.js/builders
const button = new ButtonComponent().setStyle(ButtonStyle.Secondary).setCustomId(id).setLabel(id);
row.addComponents(button);
}
@@ -151,6 +158,7 @@ export default class TestCommand extends BushCommand {
content: 'Click for modal',
components: [
new ActionRow().addComponents(
+ // @ts-expect-error: outdated @discord.js/builders
new ButtonComponent().setStyle(ButtonStyle.Primary).setLabel('Modal').setCustomId('test;modal')
)
]
diff --git a/src/commands/info/guildInfo.ts b/src/commands/info/guildInfo.ts
index a4b7fb9..ed8a973 100644
--- a/src/commands/info/guildInfo.ts
+++ b/src/commands/info/guildInfo.ts
@@ -1,6 +1,6 @@
import { BushCommand, type ArgType, type BushMessage, type BushSlashMessage, type OptionalArgType } from '#lib';
import assert from 'assert';
-import { GuildDefaultMessageNotifications, GuildExplicitContentFilter } from 'discord-api-types';
+import { GuildDefaultMessageNotifications, GuildExplicitContentFilter } from 'discord-api-types/v9';
import {
ApplicationCommandOptionType,
Embed,
diff --git a/src/commands/info/help.ts b/src/commands/info/help.ts
index 2383566..ef3ef30 100644
--- a/src/commands/info/help.ts
+++ b/src/commands/info/help.ts
@@ -143,14 +143,17 @@ export default class HelpCommand extends BushCommand {
const row = new ActionRow();
if (!client.config.isDevelopment && !client.guilds.cache.some((guild) => guild.ownerId === message.author.id)) {
+ // @ts-expect-error: outdated @discord.js/builders
row.addComponents(new ButtonComponent().setStyle(ButtonStyle.Link).setLabel('Invite Me').setURL(util.invite));
}
if (!client.guilds.cache.get(client.config.supportGuild.id)?.members.cache.has(message.author.id)) {
row.addComponents(
+ // @ts-expect-error: outdated @discord.js/builders
new ButtonComponent().setStyle(ButtonStyle.Link).setLabel('Support Server').setURL(client.config.supportGuild.invite)
);
}
if (packageDotJSON?.repository)
+ // @ts-expect-error: outdated @discord.js/builders
row.addComponents(new ButtonComponent().setStyle(ButtonStyle.Link).setLabel('GitHub').setURL(packageDotJSON.repository));
else void message.channel?.send('Error importing package.json, please report this to my developer.');
diff --git a/src/commands/info/links.ts b/src/commands/info/links.ts
index 25b040c..d9d5b8a 100644
--- a/src/commands/info/links.ts
+++ b/src/commands/info/links.ts
@@ -22,10 +22,13 @@ export default class LinksCommand extends BushCommand {
public override async exec(message: BushMessage | BushSlashMessage) {
const buttonRow = new ActionRow();
if (!client.config.isDevelopment || message.author.isOwner()) {
+ // @ts-expect-error: outdated @discord.js/builders
buttonRow.addComponents(new ButtonComponent().setStyle(ButtonStyle.Link).setLabel('Invite Me').setURL(util.invite));
}
buttonRow.addComponents(
+ // @ts-expect-error: outdated @discord.js/builders
new ButtonComponent().setStyle(ButtonStyle.Link).setLabel('Support Server').setURL(client.config.supportGuild.invite),
+ // @ts-expect-error: outdated @discord.js/builders
new ButtonComponent().setStyle(ButtonStyle.Link).setLabel('GitHub').setURL(packageDotJSON.repository)
);
return await message.util.reply({ content: 'Here are some useful links:', components: [buttonRow] });
diff --git a/src/commands/info/userInfo.ts b/src/commands/info/userInfo.ts
index 97cdc30..02a3be6 100644
--- a/src/commands/info/userInfo.ts
+++ b/src/commands/info/userInfo.ts
@@ -7,7 +7,7 @@ import {
type BushSlashMessage,
type BushUser
} from '#lib';
-import { APIApplication, TeamMemberMembershipState } from 'discord-api-types';
+import { TeamMemberMembershipState, type APIApplication } from 'discord-api-types/v9';
import {
ActivityType,
ApplicationCommandOptionType,
diff --git a/src/commands/moulberry-bush/capes.ts b/src/commands/moulberry-bush/capes.ts
index 032f62d..47a4ea6 100644
--- a/src/commands/moulberry-bush/capes.ts
+++ b/src/commands/moulberry-bush/capes.ts
@@ -1,6 +1,6 @@
import { BushCommand, ButtonPaginator, DeleteButton, type BushMessage, type OptionalArgType } from '#lib';
import assert from 'assert';
-import { APIEmbed } from 'discord-api-types';
+import { APIEmbed } from 'discord-api-types/v9';
import { ApplicationCommandOptionType, AutocompleteInteraction, PermissionFlagsBits } from 'discord.js';
import Fuse from 'fuse.js';
import got from 'got';
diff --git a/src/commands/utilities/highlight-!.ts b/src/commands/utilities/highlight-!.ts
index 332af03..687990f 100644
--- a/src/commands/utilities/highlight-!.ts
+++ b/src/commands/utilities/highlight-!.ts
@@ -1,6 +1,6 @@
import { BushCommand, Highlight, HighlightWord, type BushSlashMessage } from '#lib';
import { Flag, type ArgumentGeneratorReturn, type SlashOption } from 'discord-akairo';
-import { ApplicationCommandOptionType } from 'discord-api-types';
+import { ApplicationCommandOptionType } from 'discord-api-types/v9';
import { ApplicationCommandSubCommandData, AutocompleteInteraction, CacheType } from 'discord.js';
type Unpacked<T> = T extends (infer U)[] ? U : T;
diff --git a/src/commands/utilities/reminders.ts b/src/commands/utilities/reminders.ts
index 509da67..10206c1 100644
--- a/src/commands/utilities/reminders.ts
+++ b/src/commands/utilities/reminders.ts
@@ -1,6 +1,6 @@
import { BushCommand, ButtonPaginator, Reminder, type BushMessage, type BushSlashMessage } from '#lib';
import assert from 'assert';
-import { APIEmbed } from 'discord-api-types';
+import { APIEmbed } from 'discord-api-types/v9';
import { PermissionFlagsBits } from 'discord.js';
import { Op } from 'sequelize';
diff --git a/src/lib/common/AutoMod.ts b/src/lib/common/AutoMod.ts
index 784085d..9024260 100644
--- a/src/lib/common/AutoMod.ts
+++ b/src/lib/common/AutoMod.ts
@@ -156,6 +156,7 @@ export class AutoMod {
? [
new ActionRow().addComponents(
new ButtonComponent()
+ // @ts-expect-error: outdated @discord.js/builders
.setStyle(ButtonStyle.Danger)
.setLabel('Ban User')
.setCustomId(`automod;ban;${this.message.author.id};everyone mention and scam phrase`)
@@ -277,6 +278,7 @@ export class AutoMod {
? [
new ActionRow().addComponents(
new ButtonComponent()
+ // @ts-expect-error: outdated @discord.js/builders
.setStyle(ButtonStyle.Danger)
.setLabel('Ban User')
.setCustomId(`automod;ban;${this.message.author.id};${highestOffence.reason}`)
diff --git a/src/lib/common/ButtonPaginator.ts b/src/lib/common/ButtonPaginator.ts
index 0399e74..09e059c 100644
--- a/src/lib/common/ButtonPaginator.ts
+++ b/src/lib/common/ButtonPaginator.ts
@@ -1,6 +1,6 @@
import { DeleteButton, type BushMessage, type BushSlashMessage } from '#lib';
import { CommandUtil } from 'discord-akairo';
-import { APIEmbed } from 'discord-api-types';
+import { APIEmbed } from 'discord-api-types/v9';
import { ActionRow, ActionRowComponent, ButtonComponent, ButtonStyle, Embed, type MessageComponentInteraction } from 'discord.js';
/**
@@ -173,26 +173,31 @@ export class ButtonPaginator {
protected getPaginationRow(disableAll = false): ActionRow<ActionRowComponent> {
return new ActionRow().addComponents(
new ButtonComponent()
+ // @ts-expect-error: outdated @discord.js/builders
.setStyle(ButtonStyle.Primary)
.setCustomId('paginate_beginning')
.setEmoji(PaginateEmojis.BEGINNING)
.setDisabled(disableAll || this.curPage === 0),
new ButtonComponent()
+ // @ts-expect-error: outdated @discord.js/builders
.setStyle(ButtonStyle.Primary)
.setCustomId('paginate_back')
.setEmoji(PaginateEmojis.BACK)
.setDisabled(disableAll || this.curPage === 0),
new ButtonComponent()
+ // @ts-expect-error: outdated @discord.js/builders
.setStyle(ButtonStyle.Primary)
.setCustomId('paginate_stop')
.setEmoji(PaginateEmojis.STOP)
.setDisabled(disableAll),
new ButtonComponent()
+ // @ts-expect-error: outdated @discord.js/builders
.setStyle(ButtonStyle.Primary)
.setCustomId('paginate_next')
.setEmoji(PaginateEmojis.FORWARD)
.setDisabled(disableAll || this.curPage === this.embeds.length - 1),
new ButtonComponent()
+ // @ts-expect-error: outdated @discord.js/builders
.setStyle(ButtonStyle.Primary)
.setCustomId('paginate_end')
.setEmoji(PaginateEmojis.END)
diff --git a/src/lib/common/ConfirmationPrompt.ts b/src/lib/common/ConfirmationPrompt.ts
index bd11c5c..1f027ef 100644
--- a/src/lib/common/ConfirmationPrompt.ts
+++ b/src/lib/common/ConfirmationPrompt.ts
@@ -31,11 +31,13 @@ export class ConfirmationPrompt {
this.messageOptions.components = [
new ActionRow().addComponents(
new ButtonComponent()
+ // @ts-expect-error: outdated @discord.js/builders
.setStyle(ButtonStyle.Primary)
.setCustomId('confirmationPrompt_confirm')
.setEmoji({ id: util.emojisRaw.successFull, name: 'successFull', animated: false })
.setLabel('Yes'),
new ButtonComponent()
+ // @ts-expect-error: outdated @discord.js/builders
.setStyle(ButtonStyle.Danger)
.setCustomId('confirmationPrompt_cancel')
.setEmoji({ id: util.emojisRaw.errorFull, name: 'errorFull', animated: false })
diff --git a/src/lib/common/DeleteButton.ts b/src/lib/common/DeleteButton.ts
index cf3b416..f2e0ff3 100644
--- a/src/lib/common/DeleteButton.ts
+++ b/src/lib/common/DeleteButton.ts
@@ -68,6 +68,7 @@ export class DeleteButton {
this.messageOptions.components = [
new ActionRow().addComponents(
new ButtonComponent()
+ // @ts-expect-error: outdated @discord.js/builders
.setStyle(ButtonStyle.Primary)
.setCustomId('paginate__stop')
.setEmoji(PaginateEmojis.STOP)
diff --git a/src/lib/common/util/Moderation.ts b/src/lib/common/util/Moderation.ts
index 0ba6fca..c2236ab 100644
--- a/src/lib/common/util/Moderation.ts
+++ b/src/lib/common/util/Moderation.ts
@@ -10,7 +10,33 @@ import {
type BushUserResolvable,
type ModLogType
} from '#lib';
-import { Embed, PermissionFlagsBits, type Snowflake } from 'discord.js';
+import assert from 'assert';
+import { ActionRow, ButtonComponent, ButtonStyle, ComponentType, Embed, PermissionFlagsBits, type Snowflake } from 'discord.js';
+
+enum punishMap {
+ 'warned' = 'warn',
+ 'muted' = 'mute',
+ 'unmuted' = 'unmute',
+ 'kicked' = 'kick',
+ 'banned' = 'ban',
+ 'unbanned' = 'unban',
+ 'timedout' = 'timeout',
+ 'untimedout' = 'untimeout',
+ 'blocked' = 'block',
+ 'unblocked' = 'unblock'
+}
+enum reversedPunishMap {
+ 'warn' = 'warned',
+ 'mute' = 'muted',
+ 'unmute' = 'unmuted',
+ 'kick' = 'kicked',
+ 'ban' = 'banned',
+ 'unban' = 'unbanned',
+ 'timeout' = 'timedout',
+ 'untimeout' = 'untimedout',
+ 'block' = 'blocked',
+ 'unblock' = 'unblocked'
+}
/**
* A utility class with moderation-related methods.
@@ -204,6 +230,19 @@ export class Moderation {
return typeMap[type];
}
+ public static punishmentToPresentTense(punishment: PunishmentTypeDM): PunishmentTypePresent {
+ return punishMap[punishment];
+ }
+
+ public static punishmentToPastTense(punishment: PunishmentTypePresent): PunishmentTypeDM {
+ return reversedPunishMap[punishment];
+ }
+
+ /**
+ * Notifies the specified user of their punishment.
+ * @param options Options for notifying the user.
+ * @returns Whether or not the dm was successfully sent.
+ */
public static async punishDM(options: PunishDMOptions): Promise<boolean> {
const ending = await options.guild.getSetting('punishmentEnding');
const dmEmbed =
@@ -211,16 +250,45 @@ export class Moderation {
? new Embed().setDescription(ending).setColor(util.colors.newBlurple)
: undefined;
+ const appealsEnabled = !!(
+ (await options.guild.hasFeature('punishmentAppeals')) && (await options.guild.getLogChannel('appeals'))
+ );
+
+ let content = `You have been ${options.punishment} `;
+ if (options.punishment.includes('blocked')) {
+ assert(options.channel);
+ content += `from <#${options.channel}> `;
+ }
+ content += `in ${util.format.input(options.guild.name)} `;
+ if (options.duration !== null && options.duration !== undefined)
+ content += options.duration ? `for ${util.humanizeDuration(options.duration)} ` : 'permanently ';
+ const reason = options.reason?.trim() ? options.reason?.trim() : 'No reason provided';
+ content += `for ${util.format.input(reason)}.`;
+
+ let components;
+ if (appealsEnabled && options.modlog)
+ components = [
+ new ActionRow({
+ type: ComponentType.ActionRow,
+ components: [
+ // @ts-expect-error: outdated @discord.js/builders
+ new ButtonComponent({
+ custom_id: `appeal;${this.punishmentToPresentTense(options.punishment)};${
+ options.guild.id
+ };${client.users.resolveId(options.user)};${options.modlog}`,
+ style: ButtonStyle.Primary,
+ type: ComponentType.Button,
+ label: 'Appeal'
+ })
+ ]
+ })
+ ];
+
const dmSuccess = await client.users
.send(options.user, {
- content: `You have been ${options.punishment} in **${options.guild.name}** ${
- options.duration !== null && options.duration !== undefined
- ? options.duration
- ? `for ${util.humanizeDuration(options.duration)} `
- : 'permanently '
- : ''
- }for **${options.reason?.trim() ? options.reason?.trim() : 'No reason provided'}**.`,
- embeds: dmEmbed ? [dmEmbed] : undefined
+ content,
+ embeds: dmEmbed ? [dmEmbed] : undefined,
+ components
})
.catch(() => false);
return !!dmSuccess;
@@ -342,6 +410,11 @@ export interface RemovePunishmentEntryOptions {
*/
export interface PunishDMOptions {
/**
+ * The modlog case id so the user can make an appeal.
+ */
+ modlog?: string;
+
+ /**
* The guild that the punishment is taking place in.
*/
guild: BushGuild;
@@ -354,7 +427,7 @@ export interface PunishDMOptions {
/**
* The punishment that the user has received.
*/
- punishment: string;
+ punishment: PunishmentTypeDM;
/**
* The reason the user's punishment.
@@ -371,4 +444,35 @@ export interface PunishDMOptions {
* @default true
*/
sendFooter: boolean;
+
+ /**
+ * The channel that the user was (un)blocked from.
+ */
+ channel?: Snowflake;
}
+
+export type PunishmentTypeDM =
+ | 'warned'
+ | 'muted'
+ | 'unmuted'
+ | 'kicked'
+ | 'banned'
+ | 'unbanned'
+ | 'timedout'
+ | 'untimedout'
+ | 'blocked'
+ | 'unblocked';
+
+export type PunishmentTypePresent =
+ | 'warn'
+ | 'mute'
+ | 'unmute'
+ | 'kick'
+ | 'ban'
+ | 'unban'
+ | 'timeout'
+ | 'untimeout'
+ | 'block'
+ | 'unblock';
+
+export type AppealButtonId = `appeal;${PunishmentTypePresent};${Snowflake};${Snowflake};${string}`;
diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts
index 41d16f7..bf4dfaf 100644
--- a/src/lib/extensions/discord-akairo/BushClientUtil.ts
+++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts
@@ -21,7 +21,7 @@ import assert from 'assert';
import { exec } from 'child_process';
import deepLock from 'deep-lock';
import { ClientUtil, Util as AkairoUtil } from 'discord-akairo';
-import { APIMessage } from 'discord-api-types';
+import type { APIMessage } from 'discord-api-types/v9';
import {
Constants as DiscordConstants,
GuildMember,
diff --git a/src/lib/extensions/discord-akairo/BushCommand.ts b/src/lib/extensions/discord-akairo/BushCommand.ts
index 650b538..ff3748e 100644
--- a/src/lib/extensions/discord-akairo/BushCommand.ts
+++ b/src/lib/extensions/discord-akairo/BushCommand.ts
@@ -44,7 +44,7 @@ import {
type ContextMenuCommand,
type MissingPermissionSupplier,
type SlashOption,
- type SlashResolveTypes
+ type SlashResolveType
} from 'discord-akairo';
import {
type ApplicationCommandOptionChoice,
@@ -147,7 +147,7 @@ interface BaseBushArgumentOptions extends Omit<ArgumentOptions, 'type' | 'prompt
*
* ex. get the resolved member object when the type is `USER`
*/
- slashResolve?: SlashResolveTypes;
+ slashResolve?: SlashResolveType;
/**
* The choices of the option for the user to pick from
@@ -340,7 +340,7 @@ export interface ArgsInfo {
description: string;
optional?: boolean;
slashType: AkairoApplicationCommandOptionData['type'] | false;
- slashResolve?: SlashResolveTypes;
+ slashResolve?: SlashResolveType;
only?: 'slash' | 'text';
type: string;
}
diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts
index 93875b8..80799fd 100644
--- a/src/lib/extensions/discord.js/BushGuild.ts
+++ b/src/lib/extensions/discord.js/BushGuild.ts
@@ -173,8 +173,22 @@ export class BushGuild extends Guild {
if ((await this.bans.fetch()).has(user.id)) return banResponse.ALREADY_BANNED;
const ret = await (async () => {
+ // add modlog entry
+ const { log: modlog } = await Moderation.createModLogEntry({
+ type: options.duration ? ModLogType.TEMP_BAN : ModLogType.PERM_BAN,
+ user: user,
+ moderator: moderator.id,
+ reason: options.reason,
+ duration: options.duration,
+ guild: this,
+ evidence: options.evidence
+ });
+ if (!modlog) return banResponse.MODLOG_ERROR;
+ caseID = modlog.id;
+
// dm user
dmSuccessEvent = await Moderation.punishDM({
+ modlog: modlog.id,
guild: this,
user: user,
punishment: 'banned',
@@ -187,24 +201,11 @@ export class BushGuild extends Guild {
const banSuccess = await this.bans
.create(user?.id ?? options.user, {
reason: `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`,
- days: options.deleteDays
+ deleteMessageDays: options.deleteDays
})
.catch(() => false);
if (!banSuccess) return banResponse.ACTION_ERROR;
- // add modlog entry
- const { log: modlog } = await Moderation.createModLogEntry({
- type: options.duration ? ModLogType.TEMP_BAN : ModLogType.PERM_BAN,
- user: user,
- moderator: moderator.id,
- reason: options.reason,
- duration: options.duration,
- guild: this,
- evidence: options.evidence
- });
- if (!modlog) return banResponse.MODLOG_ERROR;
- caseID = modlog.id;
-
// add punishment entry so they can be unbanned later
const punishmentEntrySuccess = await Moderation.createPunishmentEntry({
type: 'ban',
diff --git a/src/lib/extensions/discord.js/BushGuildMember.ts b/src/lib/extensions/discord.js/BushGuildMember.ts
index 84fdf13..5d7144b 100644
--- a/src/lib/extensions/discord.js/BushGuildMember.ts
+++ b/src/lib/extensions/discord.js/BushGuildMember.ts
@@ -3,6 +3,8 @@ import {
BushClientEvents,
Moderation,
ModLogType,