feat(bot): add modals

This commit is contained in:
xHyroM 2023-04-08 14:05:40 +02:00
parent 5cb18619ad
commit ae5e39d08d
No known key found for this signature in database
GPG key ID: BE0423F386C436AA
11 changed files with 216 additions and 33 deletions

View file

@ -25,6 +25,7 @@ Promise.all([
charset: "utf8",
minify: !dev,
watch: watch,
external: ["node:events"],
}),
])
.catch((err) => {

View file

@ -1,17 +1,37 @@
import { Command } from "../structs/Command";
import { ChannelType } from "discord-api-types/v10";
import { ActionRowBuilder, ChannelSelectMenuBuilder } from "builders";
import { MessageFlags, TextInputStyle } from "discord-api-types/v10";
import { ActionRowBuilder, TextInputBuilder } from "builders";
new Command({
name: "setup",
flags: MessageFlags.Ephemeral,
acknowledge: false,
run: async (ctx) => {
await ctx.editReply({
content: "Setup",
return ctx.returnModal({
title: "Setup",
custom_id: "test",
components: [
new ActionRowBuilder<TextInputBuilder>()
.addComponents(
new TextInputBuilder()
.setLabel("ASDSAD")
.setCustomId("prefix")
.setPlaceholder("Prefix")
.setStyle(TextInputStyle.Paragraph)
.setRequired(true),
)
.toJSON(),
],
});
/**
* await ctx.editReply({
content: "Select the channel to which the panel will be sent.",
components: [
new ActionRowBuilder<ChannelSelectMenuBuilder>()
.addComponents(
new ChannelSelectMenuBuilder()
.setCustomId("channel")
.setCustomId("setup:part-channel")
.addChannelTypes(
ChannelType.GuildAnnouncement,
ChannelType.GuildText,
@ -20,5 +40,6 @@ new Command({
.toJSON(),
],
});
*/
},
});

View file

@ -0,0 +1,35 @@
import { ActionRowBuilder, RoleSelectMenuBuilder } from "builders";
import { Component } from "../structs/Component";
import { MessageFlags } from "discord-api-types/v10";
new Component({
id: "setup:part-channel",
flags: MessageFlags.Ephemeral,
run: async (ctx) => {
await ctx.editReply({
content: "Select the roles that will be available in the menu.",
components: [
new ActionRowBuilder<RoleSelectMenuBuilder>()
.addComponents(
new RoleSelectMenuBuilder()
.setCustomId("setup:part-roles")
.setPlaceholder("Select roles")
.setMinValues(1)
.setMaxValues(25),
)
.toJSON(),
],
});
},
});
new Component({
id: "setup:part-roles",
flags: MessageFlags.Ephemeral,
run: async (ctx) => {
await ctx.editReply({
content: "done",
components: [],
});
},
});

View file

@ -1,18 +1,21 @@
import "./commands/setup";
import "./components/setup";
import "./modals/setup";
import {
APIApplicationCommandInteraction,
APIMessageComponentInteraction,
APIModalSubmitInteraction,
APIPingInteraction,
InteractionResponseType,
InteractionType,
} from "discord-api-types/v10";
import { COMMANDS, COMPONENTS } from "./registers";
import { COMMANDS, COMPONENTS, MODALS } from "./registers";
import { verify } from "./utils/verify";
import respond from "./utils/respond";
import { CommandContext } from "./structs/contexts/CommandContext";
import { ComponentContext } from "./structs/contexts/ComponentContext";
console.log(COMMANDS);
import { ModalContext } from "./structs/contexts/ModalContext";
export default {
fetch: async (request: Request, env: Env) => {
@ -27,6 +30,7 @@ export default {
const interaction = (await request.json()) as
| APIPingInteraction
| APIApplicationCommandInteraction
| APIModalSubmitInteraction
| APIMessageComponentInteraction;
if (interaction.type === InteractionType.Ping)
@ -34,7 +38,8 @@ export default {
type: InteractionResponseType.Pong,
});
if (interaction.type === InteractionType.ApplicationCommand) {
switch (interaction.type) {
case InteractionType.ApplicationCommand: {
const command = COMMANDS.find(
(cmd) => cmd.name === interaction.data.name,
);
@ -42,26 +47,57 @@ export default {
if (!command) return new Response("Unknown command", { status: 404 });
try {
if (command.acknowledge)
return respond({
type: InteractionResponseType.DeferredChannelMessageWithSource,
data: {
flags: command.flags,
},
});
} finally {
if (command.acknowledge)
command.run(new CommandContext(interaction, env));
}
// rome-ignore lint/correctness/noUnsafeFinally: it works, must do better typings etc...
else return command.run(new CommandContext(interaction, env));
}
const component = COMPONENTS.find(
(cmp) => cmp.id === interaction.data.custom_id,
);
break;
}
case InteractionType.ModalSubmit: {
const modal = MODALS.find((md) => md.id === interaction.data.custom_id);
if (!component) return new Response("Unknown component", { status: 404 });
if (!modal) return new Response("Unknown modal", { status: 404 });
try {
return respond({
type: InteractionResponseType.DeferredChannelMessageWithSource,
data: {
flags: modal.flags,
},
});
} finally {
modal.run(new ModalContext(interaction, env));
}
}
case InteractionType.MessageComponent: {
const component = COMPONENTS.find(
(cmp) => cmp.id === interaction.data.custom_id,
);
if (!component)
return new Response("Unknown component", { status: 404 });
try {
return respond({
type: InteractionResponseType.DeferredChannelMessageWithSource,
data: {
flags: component.flags,
},
});
} finally {
component.run(new ComponentContext(interaction, env));
}
}
}
},
};

View file

@ -0,0 +1,8 @@
import { Modal } from "../structs/Modal";
new Modal({
id: "test",
run: async (ctx) => {
// TODO: finish
},
});

View file

@ -1,8 +1,10 @@
import { Command } from "./structs/Command";
import { Component } from "./structs/Component";
import { Modal } from "./structs/Modal";
export const COMMANDS: Command[] = [];
export const COMPONENTS: Component[] = [];
export const MODALS: Modal[] = [];
export const registerCommand = (command: Command) => {
COMMANDS.push(command);
@ -11,3 +13,7 @@ export const registerCommand = (command: Command) => {
export const registerComponent = (component: Component) => {
COMPONENTS.push(component);
};
export const registerModal = (modal: Modal) => {
MODALS.push(modal);
};

View file

@ -1,17 +1,24 @@
import { MessageFlags } from "discord-api-types/v10";
import { registerCommand } from "../registers";
import { CommandContext } from "./contexts/CommandContext";
interface CommandOptions {
name: string;
acknowledge?: boolean;
flags?: MessageFlags;
run: (interaction: CommandContext) => void;
}
export class Command {
export class Command<A> {
public name: string;
public run: (interaction: CommandContext) => void;
public acknowledge: boolean;
public flags: MessageFlags | undefined;
public run: (interaction: CommandContext) => void | Response;
constructor(options: CommandOptions) {
this.name = options.name;
this.acknowledge = options.acknowledge ?? true;
this.flags = options.flags;
this.run = options.run;
registerCommand(this);

View file

@ -1,17 +1,21 @@
import { MessageFlags } from "discord-api-types/v10";
import { registerComponent } from "../registers";
import { ComponentContext } from "./contexts/ComponentContext";
interface ComponentOptions {
id: string;
flags?: MessageFlags;
run: (interaction: ComponentContext) => void;
}
export class Component {
public id: string;
public flags: MessageFlags | undefined;
public run: (interaction: ComponentContext) => void;
constructor(options: ComponentOptions) {
this.id = options.id;
this.flags = options.flags;
this.run = options.run;
registerComponent(this);

View file

@ -0,0 +1,23 @@
import { MessageFlags } from "discord-api-types/v10";
import { registerModal } from "../registers";
import { ModalContext } from "./contexts/ModalContext";
interface ModalOptions {
id: string;
flags?: MessageFlags;
run: (interaction: ModalContext) => void;
}
export class Modal {
public id: string;
public flags: MessageFlags | undefined;
public run: (interaction: ModalContext) => void;
constructor(options: ModalOptions) {
this.id = options.id;
this.flags = options.flags;
this.run = options.run;
registerModal(this);
}
}

View file

@ -1,9 +1,12 @@
import {
APIInteraction,
APIInteractionResponseCallbackData,
APIModalInteractionResponseCallbackData,
InteractionResponseType,
RouteBases,
Routes,
} from "discord-api-types/v10";
import respond from "../../utils/respond";
export class Context {
public interaction: APIInteraction;
@ -17,7 +20,7 @@ export class Context {
public async editReply(content: APIInteractionResponseCallbackData) {
return await fetch(
`${RouteBases.api}${Routes.webhookMessage(
this.interaction.application_id,
this.interaction.id,
this.interaction.token,
)}`,
{
@ -30,4 +33,31 @@ export class Context {
},
);
}
public async showModal(content: APIModalInteractionResponseCallbackData) {
return await fetch(
`${RouteBases.api}${Routes.interactionCallback(
this.interaction.id,
this.interaction.token,
)}`,
{
method: "POST",
body: JSON.stringify({
type: InteractionResponseType.Modal,
data: content,
}),
headers: {
"Content-Type": "application/json",
Authorization: `Bot ${this.env.token}`,
},
},
);
}
public async returnModal(content: APIModalInteractionResponseCallbackData) {
return respond({
type: InteractionResponseType.Modal,
data: content,
});
}
}

View file

@ -0,0 +1,12 @@
import { APIModalSubmitInteraction } from "discord-api-types/v10";
import { Context } from "./Context";
export class ModalContext extends Context {
public interaction: APIModalSubmitInteraction;
constructor(interaction: APIModalSubmitInteraction, env: Env) {
super(interaction, env);
this.interaction = interaction;
}
}