rendering to channel + simplified adapter interface

This commit is contained in:
MapleLeaf
2021-12-27 20:57:04 -06:00
parent 3682f67bfe
commit ef26b66cb8
17 changed files with 408 additions and 425 deletions

View File

@@ -0,0 +1,147 @@
import type * as Discord from "discord.js"
import { raise } from "../../helpers/raise"
import { toUpper } from "../../helpers/to-upper"
import type { ComponentInteraction } from "../internal/interaction"
import type { Message, MessageOptions } from "../internal/message"
import type { ReacordConfig, ReacordInstance } from "./reacord"
import { Reacord } from "./reacord"
export class ReacordDiscordJs extends Reacord {
constructor(client: Discord.Client, config: ReacordConfig = {}) {
super(config)
client.on("interactionCreate", (interaction) => {
if (interaction.isMessageComponent()) {
this.handleComponentInteraction(
createReacordComponentInteraction(interaction),
)
}
})
}
override send(channel: Discord.TextBasedChannel): ReacordInstance {
return this.createChannelRendererInstance({
send: async (options) => {
const message = await channel.send(getDiscordMessageOptions(options))
return createReacordMessage(message)
},
})
}
override reply(interaction: Discord.CommandInteraction): ReacordInstance {
return this.createCommandReplyRendererInstance({
type: "command",
id: interaction.id,
channelId: interaction.channelId,
reply: async (options) => {
const message = await interaction.reply({
...getDiscordMessageOptions(options),
fetchReply: true,
})
return createReacordMessage(message as Discord.Message)
},
followUp: async (options) => {
const message = await interaction.followUp({
...getDiscordMessageOptions(options),
fetchReply: true,
})
return createReacordMessage(message as Discord.Message)
},
})
}
}
function createReacordComponentInteraction(
interaction: Discord.MessageComponentInteraction,
): ComponentInteraction {
if (interaction.isButton()) {
return {
type: "button",
id: interaction.id,
channelId: interaction.channelId,
customId: interaction.customId,
update: async (options) => {
await interaction.update(getDiscordMessageOptions(options))
},
}
}
if (interaction.isSelectMenu()) {
return {
type: "select",
id: interaction.id,
channelId: interaction.channelId,
customId: interaction.customId,
values: interaction.values,
update: async (options) => {
await interaction.update(getDiscordMessageOptions(options))
},
}
}
raise(`Unsupported component interaction type: ${interaction.type}`)
}
// TODO: this could be a part of the core library,
// and also handle some edge cases, e.g. empty messages
function getDiscordMessageOptions(
options: MessageOptions,
): Discord.MessageOptions {
return {
// eslint-disable-next-line unicorn/no-null
content: options.content || null,
embeds: options.embeds,
components: options.actionRows.map((row) => ({
type: "ACTION_ROW",
components: row.map(
(component): Discord.MessageActionRowComponentOptions => {
if (component.type === "button") {
return {
type: "BUTTON",
customId: component.customId,
label: component.label ?? "",
style: toUpper(component.style ?? "secondary"),
disabled: component.disabled,
emoji: component.emoji,
}
}
if (component.type === "select") {
return {
...component,
type: "SELECT_MENU",
options: component.options.map((option) => ({
...option,
default: component.values?.includes(option.value),
})),
}
}
raise(`Unsupported component type: ${component.type}`)
},
),
})),
}
}
function createReacordMessage(message: Discord.Message): Message {
return {
edit: async (options) => {
await message.edit(getDiscordMessageOptions(options))
},
disableComponents: async () => {
for (const actionRow of message.components) {
for (const component of actionRow.components) {
component.setDisabled(true)
}
}
await message.edit({
components: message.components,
})
},
delete: async () => {
await message.delete()
},
}
}