|
|
|
|
import { TextInputBuilder } from "@discordjs/builders";
|
|
|
|
|
import Discord, {
|
|
|
|
|
CommandInteraction,
|
|
|
|
|
Interaction,
|
|
|
|
|
Message,
|
|
|
|
|
ActionRowBuilder,
|
|
|
|
|
ButtonBuilder,
|
|
|
|
|
MessageComponentInteraction,
|
|
|
|
|
ModalSubmitInteraction,
|
|
|
|
|
ButtonStyle,
|
|
|
|
|
TextInputStyle
|
|
|
|
|
} from "discord.js";
|
|
|
|
|
import { modalInteractionCollector } from "./dualCollector.js";
|
|
|
|
|
import EmojiEmbed from "./generateEmojiEmbed.js";
|
|
|
|
|
import getEmojiByName from "./getEmojiByName.js";
|
|
|
|
|
|
|
|
|
|
interface CustomBoolean<T> {
|
|
|
|
|
title: string;
|
|
|
|
|
disabled: boolean;
|
|
|
|
|
value: string | null;
|
|
|
|
|
notValue: string | null;
|
|
|
|
|
emoji: string | undefined;
|
|
|
|
|
active: boolean;
|
|
|
|
|
onClick: () => Promise<T>;
|
|
|
|
|
response: T | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class confirmationMessage {
|
|
|
|
|
interaction: CommandInteraction;
|
|
|
|
|
title = "";
|
|
|
|
|
emoji = "";
|
|
|
|
|
redEmoji: string | null = null;
|
|
|
|
|
timeoutText: string | null = null;
|
|
|
|
|
description = "";
|
|
|
|
|
color: "Danger" | "Warning" | "Success" = "Success";
|
|
|
|
|
customButtons: Record<string, CustomBoolean<unknown>> = {};
|
|
|
|
|
inverted = false;
|
|
|
|
|
reason: string | null = null;
|
|
|
|
|
|
|
|
|
|
constructor(interaction: CommandInteraction) {
|
|
|
|
|
this.interaction = interaction;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setTitle(title: string) {
|
|
|
|
|
this.title = title;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
setEmoji(emoji: string, redEmoji?: string) {
|
|
|
|
|
this.emoji = emoji;
|
|
|
|
|
if (redEmoji) this.redEmoji = redEmoji; // TODO: go through all confirmation messages and change them to use this, and not do their own edits
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
setDescription(description: string, timedOut?: string) {
|
|
|
|
|
this.description = description;
|
|
|
|
|
if (timedOut) this.timeoutText = timedOut; // TODO also this
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
setColor(color: "Danger" | "Warning" | "Success") {
|
|
|
|
|
this.color = color;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
setInverted(inverted: boolean) {
|
|
|
|
|
this.inverted = inverted;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
addCustomBoolean(
|
|
|
|
|
customId: string,
|
|
|
|
|
title: string,
|
|
|
|
|
disabled: boolean,
|
|
|
|
|
callback: (() => Promise<unknown>) | null = async () => null,
|
|
|
|
|
callbackClicked: string | null,
|
|
|
|
|
callbackNotClicked: string | null,
|
|
|
|
|
emoji?: string,
|
|
|
|
|
initial?: boolean
|
|
|
|
|
) {
|
|
|
|
|
this.customButtons[customId] = {
|
|
|
|
|
title: title,
|
|
|
|
|
disabled: disabled,
|
|
|
|
|
value: callbackClicked,
|
|
|
|
|
notValue: callbackNotClicked,
|
|
|
|
|
emoji: emoji,
|
|
|
|
|
active: initial ?? false,
|
|
|
|
|
onClick: callback ?? (async () => null),
|
|
|
|
|
response: null
|
|
|
|
|
};
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
addReasonButton(reason: string) {
|
|
|
|
|
this.reason = reason;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
async send(editOnly?: boolean): Promise<{
|
|
|
|
|
success?: boolean;
|
|
|
|
|
cancelled?: boolean;
|
|
|
|
|
components?: Record<string, CustomBoolean<unknown>>;
|
|
|
|
|
newReason?: string;
|
|
|
|
|
}> {
|
|
|
|
|
let cancelled = false;
|
|
|
|
|
let success: boolean | undefined = undefined;
|
|
|
|
|
let returnComponents = false;
|
|
|
|
|
let newReason = undefined;
|
|
|
|
|
|
|
|
|
|
while (!cancelled && success === undefined && !returnComponents && !newReason) {
|
|
|
|
|
const fullComponents = [
|
|
|
|
|
new Discord.ButtonBuilder()
|
|
|
|
|
.setCustomId("yes")
|
|
|
|
|
.setLabel("Confirm")
|
|
|
|
|
.setStyle(this.inverted ? ButtonStyle.Success : ButtonStyle.Danger)
|
|
|
|
|
.setEmoji(getEmojiByName("CONTROL.TICK", "id")),
|
|
|
|
|
new Discord.ButtonBuilder()
|
|
|
|
|
.setCustomId("no")
|
|
|
|
|
.setLabel("Cancel")
|
|
|
|
|
.setStyle(ButtonStyle.Secondary)
|
|
|
|
|
.setEmoji(getEmojiByName("CONTROL.CROSS", "id"))
|
|
|
|
|
];
|
|
|
|
|
Object.entries(this.customButtons).forEach(([k, v]) => {
|
|
|
|
|
const button = new Discord.ButtonBuilder()
|
|
|
|
|
.setCustomId(k)
|
|
|
|
|
.setLabel(v.title)
|
|
|
|
|
.setStyle(v.active ? ButtonStyle.Success : ButtonStyle.Primary)
|
|
|
|
|
.setDisabled(v.disabled);
|
|
|
|
|
if (v.emoji !== undefined) button.setEmoji(getEmojiByName(v.emoji, "id"));
|
|
|
|
|
fullComponents.push(button);
|
|
|
|
|
});
|
|
|
|
|
if (this.reason !== null)
|
|
|
|
|
fullComponents.push(
|
|
|
|
|
new Discord.ButtonBuilder()
|
|
|
|
|
.setCustomId("reason")
|
|
|
|
|
.setLabel("Edit Reason")
|
|
|
|
|
.setStyle(ButtonStyle.Primary)
|
|
|
|
|
.setEmoji(getEmojiByName("ICONS.EDIT", "id"))
|
|
|
|
|
.setDisabled(false)
|
|
|
|
|
);
|
|
|
|
|
const components = [];
|
|
|
|
|
for (let i = 0; i < fullComponents.length; i += 5) {
|
|
|
|
|
components.push(new ActionRowBuilder<
|
|
|
|
|
Discord.ButtonBuilder | Discord.StringSelectMenuBuilder |
|
|
|
|
|
Discord.RoleSelectMenuBuilder | Discord.UserSelectMenuBuilder
|
|
|
|
|
>().addComponents(fullComponents.slice(i, i + 5)));
|
|
|
|
|
}
|
|
|
|
|
const object = {
|
|
|
|
|
embeds: [
|
|
|
|
|
new EmojiEmbed()
|
|
|
|
|
.setEmoji(this.emoji)
|
|
|
|
|
.setTitle(this.title)
|
|
|
|
|
.setDescription(
|
|
|
|
|
this.description +
|
|
|
|
|
"\n\n" +
|
|
|
|
|
Object.values(this.customButtons)
|
|
|
|
|
.map((v) => {
|
|
|
|
|
if (v.active) {
|
|
|
|
|
return v.value ? `*${v.value}*\n` : "";
|
|
|
|
|
} else {
|
|
|
|
|
return v.notValue ? `*${v.notValue}*\n` : "";
|
|
|
|
|
}
|
|
|
|
|
}).join("")
|
|
|
|
|
)
|
|
|
|
|
.setStatus(this.color)
|
|
|
|
|
],
|
|
|
|
|
components: components,
|
|
|
|
|
ephemeral: true,
|
|
|
|
|
fetchReply: true
|
|
|
|
|
};
|
|
|
|
|
let m: Message;
|
|
|
|
|
try {
|
|
|
|
|
if (editOnly) {
|
|
|
|
|
m = (await this.interaction.editReply(object)) as unknown as Message;
|
|
|
|
|
} else {
|
|
|
|
|
m = (await this.interaction.reply(object)) as unknown as Message;
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.log(e);
|
|
|
|
|
cancelled = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
let component;
|
|
|
|
|
try {
|
|
|
|
|
component = await m.awaitMessageComponent({
|
|
|
|
|
filter: (m) => m.user.id === this.interaction.user.id,
|
|
|
|
|
time: 300000
|
|
|
|
|
});
|
|
|
|
|
} catch (e) {
|
|
|
|
|
success = false;
|
|
|
|
|
returnComponents = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (component.customId === "yes") {
|
|
|
|
|
component.deferUpdate();
|
|
|
|
|
for (const v of Object.values(this.customButtons)) {
|
|
|
|
|
if (!v.active) continue;
|
|
|
|
|
try {
|
|
|
|
|
v.response = await v.onClick();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.log(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
success = true;
|
|
|
|
|
returnComponents = true;
|
|
|
|
|
continue;
|
|
|
|
|
} else if (component.customId === "no") {
|
|
|
|
|
component.deferUpdate();
|
|
|
|
|
success = false;
|
|
|
|
|
returnComponents = true;
|
|
|
|
|
continue;
|
|
|
|
|
} else if (component.customId === "reason") {
|
|
|
|
|
await component.showModal(
|
|
|
|
|
new Discord.ModalBuilder()
|
|
|
|
|
.setCustomId("modal")
|
|
|
|
|
.setTitle("Editing reason")
|
|
|
|
|
.addComponents(
|
|
|
|
|
new ActionRowBuilder<TextInputBuilder>().addComponents(
|
|
|
|
|
new TextInputBuilder()
|
|
|
|
|
.setCustomId("reason")
|
|
|
|
|
.setLabel("Reason")
|
|
|
|
|
.setMaxLength(2000)
|
|
|
|
|
.setRequired(false)
|
|
|
|
|
.setStyle(TextInputStyle.Paragraph)
|
|
|
|
|
.setPlaceholder("Spammed in #general")
|
|
|
|
|
.setValue(this.reason ? this.reason : "")
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
await this.interaction.editReply({
|
|
|
|
|
embeds: [
|
|
|
|
|
new EmojiEmbed()
|
|
|
|
|
.setTitle(this.title)
|
|
|
|
|
.setDescription("Modal opened. If you can't see it, click back and try again.")
|
|
|
|
|
.setStatus(this.color)
|
|
|
|
|
.setEmoji(this.emoji)
|
|
|
|
|
],
|
|
|
|
|
components: [
|
|
|
|
|
new ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
|
|
|
|
|
new ButtonBuilder()
|
|
|
|
|
.setLabel("Back")
|
|
|
|
|
.setEmoji(getEmojiByName("CONTROL.LEFT", "id"))
|
|
|
|
|
.setStyle(ButtonStyle.Primary)
|
|
|
|
|
.setCustomId("back")
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
});
|
|
|
|
|
let out;
|
|
|
|
|
try {
|
|
|
|
|
out = await modalInteractionCollector(
|
|
|
|
|
m,
|
|
|
|
|
(m: Interaction) =>
|
|
|
|
|
(m as MessageComponentInteraction | ModalSubmitInteraction).channelId ===
|
|
|
|
|
this.interaction.channelId,
|
|
|
|
|
(m) => m.customId === "reason"
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
cancelled = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (out === null) {
|
|
|
|
|
cancelled = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (out instanceof ModalSubmitInteraction) {
|
|
|
|
|
newReason = out.fields.getTextInputValue("reason");
|
|
|
|
|
continue;
|
|
|
|
|
} else {
|
|
|
|
|
returnComponents = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
component.deferUpdate();
|
|
|
|
|
this.customButtons[component.customId]!.active = !this.customButtons[component.customId]!.active;
|
|
|
|
|
returnComponents = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const returnValue: Awaited<ReturnType<typeof this.send>> = {};
|
|
|
|
|
|
|
|
|
|
if (returnComponents || success !== undefined) returnValue.components = this.customButtons;
|
|
|
|
|
if (success !== undefined) returnValue.success = success;
|
|
|
|
|
if (cancelled) {
|
|
|
|
|
await this.timeoutError()
|
|
|
|
|
returnValue.cancelled = true;
|
|
|
|
|
}
|
|
|
|
|
if (newReason) returnValue.newReason = newReason;
|
|
|
|
|
|
|
|
|
|
const typedReturnValue = returnValue as {cancelled: true} |
|
|
|
|
|
{ success: boolean, components: Record<string, CustomBoolean<unknown>>, newReason?: string} |
|
|
|
|
|
{ newReason: string, components: Record<string, CustomBoolean<unknown>> } |
|
|
|
|
|
{ components: Record<string, CustomBoolean<unknown>> };
|
|
|
|
|
|
|
|
|
|
return typedReturnValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async timeoutError(): Promise<void> {
|
|
|
|
|
await this.interaction.editReply({
|
|
|
|
|
embeds: [
|
|
|
|
|
new EmojiEmbed()
|
|
|
|
|
.setTitle(this.title)
|
|
|
|
|
.setDescription("We closed this message because it was not used for a while.")
|
|
|
|
|
.setStatus("Danger")
|
|
|
|
|
.setEmoji(this.redEmoji ?? this.emoji)
|
|
|
|
|
],
|
|
|
|
|
components: []
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default confirmationMessage;
|