aboutsummaryrefslogtreecommitdiff
path: root/src/listeners
diff options
context:
space:
mode:
Diffstat (limited to 'src/listeners')
-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
6 files changed, 241 insertions, 15 deletions
diff --git a/src/listeners/client/interactionCreate.ts b/src/listeners/client/interactionCreate.ts
index 636bb6e..40315a0 100644
--- a/src/listeners/client/interactionCreate.ts
+++ b/src/listeners/client/interactionCreate.ts
@@ -20,7 +20,7 @@ export default class InteractionCreateListener extends BushListener {
return;
} else if (interaction.isButton()) {
const id = interaction.customId;
- if (id.startsWith('paginate_') || id.startsWith('command_') || id.startsWith('confirmationPrompt_')) return;
+ if (['paginate_', 'command_', 'confirmationPrompt_', 'appeal'].some((s) => id.startsWith(s))) return;
else if (id.startsWith('automod;')) void AutoMod.handleInteraction(interaction as BushButtonInteraction);
else return await interaction.reply({ content: 'Buttons go brrr', ephemeral: true });
} else if (interaction.isSelectMenu()) {
diff --git a/src/listeners/track-manual-punishments/modlogSyncBan.ts b/src/listeners/track-manual-punishments/modlogSyncBan.ts
index 9886530..b68de7c 100644
--- a/src/listeners/track-manual-punishments/modlogSyncBan.ts
+++ b/src/listeners/track-manual-punishments/modlogSyncBan.ts
@@ -1,5 +1,5 @@
import { BushListener, BushUser, Moderation, ModLogType, Time, type BushClientEvents } from '#lib';
-import { AuditLogEvent } from 'discord-api-types';
+import { AuditLogEvent } from 'discord-api-types/v9';
import { Embed, PermissionFlagsBits } from 'discord.js';
export default class ModlogSyncBanListener extends BushListener {
diff --git a/src/listeners/track-manual-punishments/modlogSyncKick.ts b/src/listeners/track-manual-punishments/modlogSyncKick.ts
index b7762db..6ff9bd6 100644
--- a/src/listeners/track-manual-punishments/modlogSyncKick.ts
+++ b/src/listeners/track-manual-punishments/modlogSyncKick.ts
@@ -1,5 +1,5 @@
import { BushListener, BushUser, Moderation, ModLogType, Time, type BushClientEvents } from '#lib';
-import { AuditLogEvent } from 'discord-api-types';
+import { AuditLogEvent } from 'discord-api-types/v9';
import { Embed, PermissionFlagsBits } from 'discord.js';
export default class ModlogSyncKickListener extends BushListener {
diff --git a/src/listeners/track-manual-punishments/modlogSyncTimeout.ts b/src/listeners/track-manual-punishments/modlogSyncTimeout.ts
index 21dde1a..993002e 100644
--- a/src/listeners/track-manual-punishments/modlogSyncTimeout.ts
+++ b/src/listeners/track-manual-punishments/modlogSyncTimeout.ts
@@ -1,5 +1,5 @@
import { BushListener, BushUser, Moderation, ModLogType, Time, type BushClientEvents } from '#lib';
-import { AuditLogEvent } from 'discord-api-types';
+import { AuditLogEvent } from 'discord-api-types/v9';
import { Embed, PermissionFlagsBits } from 'discord.js';
export default class ModlogSyncTimeoutListener extends BushListener {
diff --git a/src/listeners/track-manual-punishments/modlogSyncUnban.ts b/src/listeners/track-manual-punishments/modlogSyncUnban.ts
index a268ef4..366d072 100644
--- a/src/listeners/track-manual-punishments/modlogSyncUnban.ts
+++ b/src/listeners/track-manual-punishments/modlogSyncUnban.ts
@@ -1,5 +1,5 @@
import { BushListener, BushUser, Moderation, ModLogType, Time, type BushClientEvents } from '#lib';
-import { AuditLogEvent } from 'discord-api-types';
+import { AuditLogEvent } from 'discord-api-types/v9';
import { Embed, PermissionFlagsBits } from 'discord.js';
export default class ModlogSyncUnbanListener extends BushListener {
diff --git a/src/listeners/ws/INTERACTION_CREATE.ts b/src/listeners/ws/INTERACTION_CREATE.ts
index a7c8a45..fd79529 100644
--- a/src/listeners/ws/INTERACTION_CREATE.ts
+++ b/src/listeners/ws/INTERACTION_CREATE.ts
@@ -1,6 +1,29 @@
-import { BushListener } from '#lib';
-// eslint-disable-next-line node/file-extension-in-import
-import { GatewayDispatchEvents, Routes } from 'discord-api-types/v9';
+import { BushListener, BushUser, Moderation, ModLog, PunishmentTypePresent } from '#lib';
+import assert from 'assert';
+import { TextInputStyle } from 'discord-api-types-next/v9';
+import {
+ APIBaseInteraction,
+ APIEmbed,
+ APIInteraction as DiscordAPITypesAPIInteraction,
+ APIInteractionResponseChannelMessageWithSource,
+ APIInteractionResponseDeferredMessageUpdate,
+ APIInteractionResponseUpdateMessage,
+ APIModalInteractionResponse,
+ APIModalSubmission,
+ ButtonStyle,
+ ComponentType,
+ GatewayDispatchEvents,
+ InteractionResponseType,
+ InteractionType,
+ Routes
+} from 'discord-api-types/v9';
+import { ActionRow, ButtonComponent, Embed, Snowflake } from 'discord.js';
+
+// todo: use from discord-api-types once updated
+export type APIModalSubmitInteraction = APIBaseInteraction<InteractionType.ModalSubmit, APIModalSubmission> &
+ Required<Pick<APIBaseInteraction<InteractionType.ModalSubmit, APIModalSubmission>, 'data'>>;
+
+export type APIInteraction = DiscordAPITypesAPIInteraction | APIModalSubmitInteraction;
export default class WsInteractionCreateListener extends BushListener {
public constructor() {
@@ -11,15 +34,218 @@ export default class WsInteractionCreateListener extends BushListener {
});
}
- public override async exec(interaction: any) {
- // console.dir(interaction);
+ public override async exec(interaction: APIInteraction) {
+ console.dir(interaction);
- if (interaction.type === 5) {
- await this.client.rest.post(Routes.interactionCallback(interaction.id, interaction.token), {
- body: {
- type: 6
- }
+ const respond = (
+ options:
+ | APIModalInteractionResponse
+ | APIInteractionResponseDeferredMessageUpdate
+ | APIInteractionResponseChannelMessageWithSource
+ | APIInteractionResponseUpdateMessage
+ ) => {
+ return this.client.rest.post(
+ Routes.interactionCallback(interaction.id, interaction.token),
+ options ? { body: options } : undefined
+ );
+ };
+
+ const deferredMessageUpdate = () => {
+ return respond({
+ type: InteractionResponseType.DeferredMessageUpdate
});
+ };
+
+ if (interaction.type === InteractionType.MessageComponent) {
+ if (interaction.data.custom_id.startsWith('appeal;')) {
+ const [, punishment, guildId, userId, modlogCase] = interaction.data.custom_id.split(';') as [
+ 'appeal',
+ PunishmentTypePresent,
+ Snowflake,
+ Snowflake,
+ string
+ ];
+
+ const guild = client.guilds.resolve(guildId);
+ if (!guild)
+ return respond({
+ type: InteractionResponseType.ChannelMessageWithSource,
+ data: {
+ content: `${util.emojis.error} I am no longer in that server.`
+ }
+ });
+
+ const modal: APIModalInteractionResponse = {
+ type: InteractionResponseType.Modal,
+ data: {
+ custom_id: `appeal_submit;${punishment};${guildId};${userId};${modlogCase}`,
+ title: `${util.capitalize(punishment)} Appeal`,
+ components: [
+ {
+ type: ComponentType.ActionRow,
+ components: [
+ {
+ type: ComponentType.TextInput,
+ style: TextInputStyle.Paragraph,
+ max_length: 1024,
+ required: true,
+ label: `Why were you ${Moderation.punishmentToPastTense(punishment)}?`,
+ placeholder: `Why do you think you received a ${punishment}?`,
+ custom_id: 'appeal_reason'
+ }
+ ]
+ },
+ {
+ type: ComponentType.ActionRow,
+ components: [
+ {
+ type: ComponentType.TextInput,
+ style: TextInputStyle.Paragraph,
+ max_length: 1024,
+ required: true,
+ label: 'Do you believe it was fair?',
+ placeholder: `Why do you think you received a ${punishment}?`,
+ custom_id: 'appeal_fair'
+ }
+ ]
+ },
+ {
+ type: ComponentType.ActionRow,
+ components: [
+ {
+ type: ComponentType.TextInput,
+ style: TextInputStyle.Paragraph,
+ max_length: 1024,
+ required: true,
+ label: `Why should your ${punishment} be removed?`,
+ placeholder: `Why should your ${punishment} be removed?`,
+ custom_id: 'appeal_why'
+ }
+ ]
+ }
+ ]
+ }
+ };
+
+ return respond(modal);
+ } else if (
+ interaction.data.custom_id.startsWith('appeal_accept;') ||
+ interaction.data.custom_id.startsWith('appeal_deny;')
+ ) {
+ const [action, punishment, guildId, userId, modlogCase] = interaction.data.custom_id.split(';') as [
+ 'appeal_accept' | 'appeal_deny',
+ PunishmentTypePresent,
+ Snowflake,
+ Snowflake,
+ string
+ ];
+
+ if (action === 'appeal_deny') {
+ await client.users
+ .send(userId, `Your ${punishment} appeal has been denied in ${client.guilds.resolve(guildId)!}.`)
+ .catch(() => {});
+
+ void respond({
+ type: InteractionResponseType.ChannelMessageWithSource,
+ data: {
+ components: [
+ {
+ type: 1,
+ components: [
+ {
+ type: ComponentType.Button,
+ style: ButtonStyle.Danger,
+ label: 'Close',
+ custom_id: 'appeal_denied'
+ }
+ ]
+ }
+ ]
+ }
+ });
+ }
+ }
+ } else if (interaction.type === InteractionType.ModalSubmit) {
+ if (interaction.data.custom_id.startsWith('appeal_submit;')) {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const [, punishment, guildId, userId, modlogCase] = interaction.data.custom_id.split(';') as [
+ 'appeal_submit',
+ PunishmentTypePresent,
+ Snowflake,
+ Snowflake,
+ string
+ ];
+
+ const guild = client.guilds.resolve(guildId);
+ if (!guild)
+ return respond({
+ type: InteractionResponseType.ChannelMessageWithSource,
+ data: {
+ content: `${util.emojis.error} I am no longer in that server.`
+ }
+ });
+
+ const channel = await guild.getLogChannel('appeals');
+ if (!channel)
+ return respond({
+ type: InteractionResponseType.ChannelMessageWithSource,
+ data: {
+ content: `${util.emojis.error} ${guild.name} has misconfigured their appeals channel.`
+ }
+ });
+
+ assert(interaction.user);
+ const user = new BushUser(client, interaction.user as any);
+ assert(user);
+
+ const caseId = await ModLog.findOne({ where: { user: userId, guild: guildId, id: modlogCase } });
+
+ const embed = new Embed()
+ .setTitle(`${util.capitalize(punishment)} Appeal`)
+ .setColor(util.colors.newBlurple)
+ .setTimestamp()
+ .setFooter({ text: `CaseID: ${modlogCase}` })
+ .setAuthor({ name: user.tag, iconURL: user.displayAvatarURL() })
+ .addField({
+ name: `Why were you ${Moderation.punishmentToPastTense(punishment)}?`,
+ value: interaction.data.components![0].components[0]!.value.substring(0, 1024)
+ })
+ .addField({
+ name: 'Do you believe it was fair?',
+ value: interaction.data.components![1].components[0]!.value.substring(0, 1024)
+ })
+ .addField({
+ name: `Why should your ${punishment} be removed?`,
+ value: interaction.data.components![2].components[0]!.value.substring(0, 1024)
+ })
+ .toJSON() as APIEmbed;
+
+ const components = [
+ new ActionRow({
+ type: 1,
+ components: [
+ // @ts-expect-error: outdated @discord.js/builders
+ new ButtonComponent({
+ type: 2,
+ custom_id: `appeal_accept;${punishment};${guildId};${userId};${modlogCase}`,
+ label: 'Accept',
+ style: 3 /* Success */
+ }).toJSON(),
+ // @ts-expect-error: outdated @discord.js/builders
+ new ButtonComponent({
+ type: 2,
+ custom_id: `appeal_deny;${punishment};${guildId};${userId};${modlogCase}`,
+ label: 'Deny',
+ style: 4 /* Danger */
+ }).toJSON()
+ ]
+ })
+ ];
+
+ await channel.send({ embeds: [embed], components });
+ } else {
+ return deferredMessageUpdate();
+ }
}
}
}