diff --git a/packages/biscuit/Routes.ts b/packages/biscuit/Routes.ts index c2dd3b3..20f5f28 100644 --- a/packages/biscuit/Routes.ts +++ b/packages/biscuit/Routes.ts @@ -155,8 +155,29 @@ export function INTERACTION_ID_TOKEN(interactionId: Snowflake, token: string): s return `/interactions/${interactionId}/${token}/callback`; } -export function WEBHOOK_MESSAGE(webhookId: Snowflake, token: string, messageId: Snowflake): string { - return `/webhooks/${webhookId}/${token}/messages/${messageId}`; +export function WEBHOOK_MESSAGE_ORIGINAL(webhookId: Snowflake, token: string, options?: { threadId?: bigint }): string { + let url = `/webhooks/${webhookId}/${token}/messages/@original?`; + + if (options) { + if (options.threadId) url += `threadId=${options.threadId}`; + } + + return url; +} + +export function WEBHOOK_MESSAGE( + webhookId: Snowflake, + token: string, + messageId: Snowflake, + options?: { threadId?: Snowflake } +): string { + let url = `/webhooks/${webhookId}/${token}/messages/${messageId}?`; + + if (options) { + if (options.threadId) url += `threadId=${options.threadId}`; + } + + return url; } export function WEBHOOK_TOKEN(webhookId: Snowflake, token?: string): string { diff --git a/packages/biscuit/structures/Webhook.ts b/packages/biscuit/structures/Webhook.ts index 9f319f7..3183064 100644 --- a/packages/biscuit/structures/Webhook.ts +++ b/packages/biscuit/structures/Webhook.ts @@ -1,14 +1,27 @@ import type { Model } from "./Base.ts"; import type { Session } from "../Session.ts"; import type { Snowflake } from "../Snowflake.ts"; -import type { DiscordMessage, DiscordWebhook, WebhookTypes } from "../../discordeno/mod.ts"; +import type { DiscordMessageComponents, DiscordEmbed, DiscordMessage, DiscordWebhook, FileContent, WebhookTypes } from "../../discordeno/mod.ts"; import type { WebhookOptions } from "../Routes.ts"; -import type { CreateMessage } from "./Message.ts"; +import type { Attachment } from "./Attachment.ts"; +import type { CreateMessage, AllowedMentions } from "./Message.ts"; import Util from "../Util.ts"; import User from "./User.ts"; import Message from "./Message.ts"; import * as Routes from "../Routes.ts"; +/** + * @link https://discord.com/developers/docs/resources/webhook#edit-webhook-message-jsonform-params + * */ +export interface EditWebhookMessage { + content?: string; + embeds?: DiscordEmbed[]; + files?: FileContent[]; + allowedMentions?: AllowedMentions; + attachments?: Attachment[]; + components?: DiscordMessageComponents; +} + export class Webhook implements Model { constructor(session: Session, data: DiscordWebhook) { this.session = session; @@ -88,7 +101,7 @@ export class Webhook implements Model { return new Webhook(this.session, message); } - async fetchMessage(messageId: Snowflake): Promise { + async fetchMessage(messageId: Snowflake, options?: { threadId?: Snowflake }): Promise { if (!this.token) { return; } @@ -96,7 +109,60 @@ export class Webhook implements Model { const message = await this.session.rest.runMethod( this.session.rest, "GET", - Routes.WEBHOOK_MESSAGE(this.id, this.token, messageId), + Routes.WEBHOOK_MESSAGE(this.id, this.token, messageId, options), + ); + + return new Message(this.session, message); + } + + async deleteMessage(messageId: Snowflake, options?: { threadId?: Snowflake }): Promise { + if (!this.token) { + throw new Error("No token found"); + } + + await this.session.rest.runMethod( + this.session.rest, + "DELETE", + Routes.WEBHOOK_MESSAGE(this.id, this.token, messageId, options), + ); + } + + async editMessage(messageId?: Snowflake, options?: EditWebhookMessage & { threadId?: Snowflake }): Promise { + if (!this.token) { + throw new Error("No token found"); + } + + const message = await this.session.rest.runMethod( + this.session.rest, + "PATCH", + messageId + ? Routes.WEBHOOK_MESSAGE(this.id, this.token, messageId) + : Routes.WEBHOOK_MESSAGE_ORIGINAL(this.id, this.token), + { + content: options?.content, + embeds: options?.embeds, + file: options?.files, + components: options?.components, + allowed_mentions: options?.allowedMentions || { + parse: options?.allowedMentions!.parse, + replied_user: options?.allowedMentions!.repliedUser, + users: options?.allowedMentions!.users, + roles: options?.allowedMentions!.roles, + }, + attachments: options?.attachments?.map((attachment) => { + return { + id: attachment.id, + filename: attachment.name, + content_type: attachment.contentType, + size: attachment.size, + url: attachment.attachment, + proxy_url: attachment.proxyUrl, + height: attachment.height, + width: attachment.width, + ephemeral: attachment.ephemeral, + }; + }), + } ); return new Message(this.session, message); diff --git a/packages/biscuit/structures/builders/components/InputTextComponentBuilder.ts b/packages/biscuit/structures/builders/components/InputTextComponentBuilder.ts index 892e3b7..9e94b10 100644 --- a/packages/biscuit/structures/builders/components/InputTextComponentBuilder.ts +++ b/packages/biscuit/structures/builders/components/InputTextComponentBuilder.ts @@ -8,38 +8,38 @@ export class InputTextBuilder { #data: DiscordInputTextComponent; type: MessageComponentTypes.InputText; - setStyle(style: TextStyles): InputTextBuilder { + setStyle(style: TextStyles): this { this.#data.style = style; return this; } - setLabel(label: string): InputTextBuilder { + setLabel(label: string): this { this.#data.label = label; return this; } - setPlaceholder(placeholder: string): InputTextBuilder { + setPlaceholder(placeholder: string): this { this.#data.placeholder = placeholder; return this; } - setLength(max?: number, min?: number): InputTextBuilder { + setLength(max?: number, min?: number): this { this.#data.max_length = max; this.#data.min_length = min; return this; } - setCustomId(id: string): InputTextBuilder { + setCustomId(id: string): this { this.#data.custom_id = id; return this; } - setValue(value: string): InputTextBuilder { + setValue(value: string): this { this.#data.value = value; return this; } - setRequired(required = true): InputTextBuilder { + setRequired(required = true): this { this.#data.required = required; return this; } diff --git a/packages/biscuit/structures/builders/components/MessageActionRow.ts b/packages/biscuit/structures/builders/components/MessageActionRow.ts index 1c1bb82..796d53d 100644 --- a/packages/biscuit/structures/builders/components/MessageActionRow.ts +++ b/packages/biscuit/structures/builders/components/MessageActionRow.ts @@ -9,12 +9,12 @@ export class ActionRowBuilder { components: T[]; type: MessageComponentTypes.ActionRow; - addComponents(...components: T[]): ActionRowBuilder { + addComponents(...components: T[]): this { this.components.push(...components); return this; } - setComponents(...components: T[]): ActionRowBuilder { + setComponents(...components: T[]): this { this.components.splice( 0, this.components.length, diff --git a/packages/biscuit/structures/builders/components/MessageButton.ts b/packages/biscuit/structures/builders/components/MessageButton.ts index e1f1577..6f1c64f 100644 --- a/packages/biscuit/structures/builders/components/MessageButton.ts +++ b/packages/biscuit/structures/builders/components/MessageButton.ts @@ -8,32 +8,33 @@ export class ButtonBuilder { } #data: DiscordButtonComponent; type: MessageComponentTypes.Button; - setStyle(style: ButtonStyles) { + + setStyle(style: ButtonStyles): this { this.#data.style = style; return this; } - setLabel(label: string): ButtonBuilder { + setLabel(label: string): this { this.#data.label = label; return this; } - setCustomId(id: string): ButtonBuilder { + setCustomId(id: string): this { this.#data.custom_id = id; return this; } - setEmoji(emoji: ComponentEmoji): ButtonBuilder { + setEmoji(emoji: ComponentEmoji): this { this.#data.emoji = emoji; return this; } - setDisabled(disabled = true): ButtonBuilder { + setDisabled(disabled = true): this { this.#data.disabled = disabled; return this; } - setURL(url: string): ButtonBuilder { + setURL(url: string): this { this.#data.url = url; return this; } diff --git a/packages/biscuit/structures/builders/components/MessageSelectMenu.ts b/packages/biscuit/structures/builders/components/MessageSelectMenu.ts index 9160e1a..a612ff5 100644 --- a/packages/biscuit/structures/builders/components/MessageSelectMenu.ts +++ b/packages/biscuit/structures/builders/components/MessageSelectMenu.ts @@ -11,28 +11,28 @@ export class SelectMenuBuilder { type: MessageComponentTypes.SelectMenu; options: SelectMenuOptionBuilder[]; - setPlaceholder(placeholder: string): SelectMenuBuilder { + setPlaceholder(placeholder: string): this { this.#data.placeholder = placeholder; return this; } - setValues(max?: number, min?: number): SelectMenuBuilder { + setValues(max?: number, min?: number): this { this.#data.max_values = max; this.#data.min_values = min; return this; } - setDisabled(disabled = true): SelectMenuBuilder { + setDisabled(disabled = true): this { this.#data.disabled = disabled; return this; } - setCustomId(id: string): SelectMenuBuilder { + setCustomId(id: string): this { this.#data.custom_id = id; return this; } - setOptions(...options: SelectMenuOptionBuilder[]): SelectMenuBuilder { + setOptions(...options: SelectMenuOptionBuilder[]): this { this.options.splice( 0, this.options.length, @@ -41,7 +41,7 @@ export class SelectMenuBuilder { return this; } - addOptions(...options: SelectMenuOptionBuilder[]): SelectMenuBuilder { + addOptions(...options: SelectMenuOptionBuilder[]): this { this.options.push( ...options, ); diff --git a/packages/biscuit/structures/builders/slash/ApplicationCommand.ts b/packages/biscuit/structures/builders/slash/ApplicationCommand.ts index 1d81e6e..fbace16 100644 --- a/packages/biscuit/structures/builders/slash/ApplicationCommand.ts +++ b/packages/biscuit/structures/builders/slash/ApplicationCommand.ts @@ -25,31 +25,31 @@ export abstract class ApplicationCommandBuilder implements CreateApplicationComm this.dmPermission = dmPermission; } - public setType(type: ApplicationCommandTypes): ApplicationCommandBuilder { + public setType(type: ApplicationCommandTypes): this { return (this.type = type), this; } - public setName(name: string): ApplicationCommandBuilder { + public setName(name: string): this { return (this.name = name), this; } - public setDescription(description: string): ApplicationCommandBuilder { + public setDescription(description: string): this { return (this.description = description), this; } - public setDefaultMemberPermission(perm: PermissionStrings[]): ApplicationCommandBuilder { + public setDefaultMemberPermission(perm: PermissionStrings[]): this { return (this.defaultMemberPermissions = perm), this; } - public setNameLocalizations(l: Localization): ApplicationCommandBuilder { + public setNameLocalizations(l: Localization): this { return (this.nameLocalizations = l), this; } - public setDescriptionLocalizations(l: Localization): ApplicationCommandBuilder { + public setDescriptionLocalizations(l: Localization): this { return (this.descriptionLocalizations = l), this; } - public setDmPermission(perm: boolean): ApplicationCommandBuilder { + public setDmPermission(perm: boolean): this { return (this.dmPermission = perm), this; } } @@ -64,7 +64,7 @@ export class MessageApplicationCommandBuilder { this.name = name; } - public setName(name: string): MessageApplicationCommandBuilder { + public setName(name: string): this { return (this.name = name), this; } diff --git a/packages/biscuit/structures/builders/slash/ApplicationCommandOption.ts b/packages/biscuit/structures/builders/slash/ApplicationCommandOption.ts index ffb1e65..b81596a 100644 --- a/packages/biscuit/structures/builders/slash/ApplicationCommandOption.ts +++ b/packages/biscuit/structures/builders/slash/ApplicationCommandOption.ts @@ -1,5 +1,5 @@ import { ApplicationCommandOptionTypes, type ChannelTypes, type Localization } from "../../../../discordeno/mod.ts"; -import { ApplicationCommandOptionChoice } from "../../interactions/CommandInteraction.ts"; +import { ApplicationCommandOptionChoice } from "../../interactions/BaseInteraction.ts"; export class ChoiceBuilder { public name?: string; @@ -10,7 +10,7 @@ export class ChoiceBuilder { return this; } - public setValue(value: string): ChoiceBuilder { + public setValue(value: string): this { this.value = value; return this; } @@ -36,19 +36,19 @@ export class OptionBuilder { this.description = description; } - public setType(type: ApplicationCommandOptionTypes): OptionBuilder { + public setType(type: ApplicationCommandOptionTypes): this { return (this.type = type), this; } - public setName(name: string): OptionBuilder { + public setName(name: string): this { return (this.name = name), this; } - public setDescription(description: string): OptionBuilder { + public setDescription(description: string): this { return (this.description = description), this; } - public setRequired(required: boolean): OptionBuilder { + public setRequired(required: boolean): this { return (this.required = required), this; } @@ -86,15 +86,15 @@ export class OptionBuilderLimitedValues extends OptionBuilder { this.description = description; } - public setMinValue(n: number): OptionBuilderLimitedValues { + public setMinValue(n: number): this { return (this.minValue = n), this; } - public setMaxValue(n: number): OptionBuilderLimitedValues { + public setMaxValue(n: number): this { return (this.maxValue = n), this; } - public addChoice(fn: (choice: ChoiceBuilder) => ChoiceBuilder): OptionBuilderLimitedValues { + public addChoice(fn: (choice: ChoiceBuilder) => ChoiceBuilder): this { const choice = fn(new ChoiceBuilder()); this.choices ??= []; this.choices.push(choice); @@ -125,7 +125,7 @@ export class OptionBuilderString extends OptionBuilder { this; } - public addChoice(fn: (choice: ChoiceBuilder) => ChoiceBuilder): OptionBuilderString { + public addChoice(fn: (choice: ChoiceBuilder) => ChoiceBuilder): this { const choice = fn(new ChoiceBuilder()); this.choices ??= []; this.choices.push(choice); @@ -154,7 +154,7 @@ export class OptionBuilderChannel extends OptionBuilder { this; } - public addChannelTypes(...channels: ChannelTypes[]): OptionBuilderChannel { + public addChannelTypes(...channels: ChannelTypes[]): this { this.channelTypes ??= []; this.channelTypes.push(...channels); return this; @@ -183,80 +183,80 @@ export class OptionBased { ) & OptionBuilderLike[]; - public addOption(fn: (option: OptionBuilder) => OptionBuilder, type?: ApplicationCommandOptionTypes): OptionBased { + public addOption(fn: (option: OptionBuilder) => OptionBuilder, type?: ApplicationCommandOptionTypes): this { const option = fn(new OptionBuilder(type)); this.options ??= []; this.options.push(option); return this; } - public addNestedOption(fn: (option: OptionBuilder) => OptionBuilder): OptionBased { + public addNestedOption(fn: (option: OptionBuilder) => OptionBuilder): this { const option = fn(new OptionBuilder(ApplicationCommandOptionTypes.SubCommand)); this.options ??= []; this.options.push(option); return this; } - public addStringOption(fn: (option: OptionBuilderString) => OptionBuilderString): OptionBased { + public addStringOption(fn: (option: OptionBuilderString) => OptionBuilderString): this { const option = fn(new OptionBuilderString(ApplicationCommandOptionTypes.String)); this.options ??= []; this.options.push(option); return this; } - public addIntegerOption(fn: (option: OptionBuilderLimitedValues) => OptionBuilderLimitedValues): OptionBased { + public addIntegerOption(fn: (option: OptionBuilderLimitedValues) => OptionBuilderLimitedValues): this { const option = fn(new OptionBuilderLimitedValues(ApplicationCommandOptionTypes.Integer)); this.options ??= []; this.options.push(option); return this; } - public addNumberOption(fn: (option: OptionBuilderLimitedValues) => OptionBuilderLimitedValues): OptionBased { + public addNumberOption(fn: (option: OptionBuilderLimitedValues) => OptionBuilderLimitedValues): this { const option = fn(new OptionBuilderLimitedValues(ApplicationCommandOptionTypes.Number)); this.options ??= []; this.options.push(option); return this; } - public addBooleanOption(fn: (option: OptionBuilder) => OptionBuilder): OptionBased { + public addBooleanOption(fn: (option: OptionBuilder) => OptionBuilder): this { return this.addOption(fn, ApplicationCommandOptionTypes.Boolean); } - public addSubCommand(fn: (option: OptionBuilderNested) => OptionBuilderNested): OptionBased { + public addSubCommand(fn: (option: OptionBuilderNested) => OptionBuilderNested): this { const option = fn(new OptionBuilderNested(ApplicationCommandOptionTypes.SubCommand)); this.options ??= []; this.options.push(option); return this; } - public addSubCommandGroup(fn: (option: OptionBuilderNested) => OptionBuilderNested): OptionBased { + public addSubCommandGroup(fn: (option: OptionBuilderNested) => OptionBuilderNested): this { const option = fn(new OptionBuilderNested(ApplicationCommandOptionTypes.SubCommandGroup)); this.options ??= []; this.options.push(option); return this; } - public addUserOption(fn: (option: OptionBuilder) => OptionBuilder): OptionBased { + public addUserOption(fn: (option: OptionBuilder) => OptionBuilder): this { return this.addOption(fn, ApplicationCommandOptionTypes.User); } - public addChannelOption(fn: (option: OptionBuilderChannel) => OptionBuilderChannel): OptionBased { + public addChannelOption(fn: (option: OptionBuilderChannel) => OptionBuilderChannel): this { const option = fn(new OptionBuilderChannel(ApplicationCommandOptionTypes.Channel)); this.options ??= []; this.options.push(option); return this; } - public addRoleOption(fn: (option: OptionBuilder) => OptionBuilder): OptionBased { + public addRoleOption(fn: (option: OptionBuilder) => OptionBuilder): this { return this.addOption(fn, ApplicationCommandOptionTypes.Role); } - public addMentionableOption(fn: (option: OptionBuilder) => OptionBuilder): OptionBased { + public addMentionableOption(fn: (option: OptionBuilder) => OptionBuilder): this { return this.addOption(fn, ApplicationCommandOptionTypes.Mentionable); } // deno-lint-ignore ban-types - public static applyTo(klass: Function, ignore: Array = []) { + public static applyTo(klass: Function, ignore: Array = []): void { const methods: Array = [ "addOption", "addNestedOption", diff --git a/packages/biscuit/structures/components/ActionRowComponent.ts b/packages/biscuit/structures/components/ActionRowComponent.ts index f14feac..d6e5d46 100644 --- a/packages/biscuit/structures/components/ActionRowComponent.ts +++ b/packages/biscuit/structures/components/ActionRowComponent.ts @@ -1,5 +1,5 @@ import type { Session } from "../../Session.ts"; -import type { DiscordComponent } from "../../../discordeno/mod.ts"; +import type { DiscordComponent, DiscordInputTextComponent } from "../../../discordeno/mod.ts"; import type { ActionRowComponent, Component } from "./Component.ts"; import { ButtonStyles, MessageComponentTypes } from "../../../discordeno/mod.ts"; import BaseComponent from "./Component.ts"; @@ -24,7 +24,7 @@ export class ActionRow extends BaseComponent implements ActionRowComponent { case MessageComponentTypes.SelectMenu: return new SelectMenu(session, component); case MessageComponentTypes.InputText: - return new InputText(session, component); + return new InputText(session, component as DiscordInputTextComponent); case MessageComponentTypes.ActionRow: throw new Error("Cannot have an action row inside an action row"); } diff --git a/packages/biscuit/structures/components/ComponentFactory.ts b/packages/biscuit/structures/components/ComponentFactory.ts index 395f453..6d1f570 100644 --- a/packages/biscuit/structures/components/ComponentFactory.ts +++ b/packages/biscuit/structures/components/ComponentFactory.ts @@ -1,5 +1,5 @@ import type { Session } from "../../Session.ts"; -import type { DiscordComponent } from "../../../discordeno/mod.ts"; +import type { DiscordComponent, DiscordInputTextComponent } from "../../../discordeno/mod.ts"; import type { Component } from "./Component.ts"; import { ButtonStyles, MessageComponentTypes } from "../../../discordeno/mod.ts"; import ActionRow from "./ActionRowComponent.ts"; @@ -18,14 +18,12 @@ export class ComponentFactory { case MessageComponentTypes.ActionRow: return new ActionRow(session, component); case MessageComponentTypes.Button: - if (component.style === ButtonStyles.Link) { - return new LinkButton(session, component); - } + if (component.style === ButtonStyles.Link) return new LinkButton(session, component); return new Button(session, component); case MessageComponentTypes.SelectMenu: return new SelectMenu(session, component); case MessageComponentTypes.InputText: - return new TextInput(session, component); + return new TextInput(session, component as DiscordInputTextComponent); } } } diff --git a/packages/biscuit/structures/interactions/AutoCompleteInteraction.ts b/packages/biscuit/structures/interactions/AutoCompleteInteraction.ts index 409db4b..2e42647 100644 --- a/packages/biscuit/structures/interactions/AutoCompleteInteraction.ts +++ b/packages/biscuit/structures/interactions/AutoCompleteInteraction.ts @@ -2,7 +2,7 @@ import type { Model } from "../Base.ts"; import type { Snowflake } from "../../Snowflake.ts"; import type { Session } from "../../Session.ts"; import type { ApplicationCommandTypes, DiscordInteraction, InteractionTypes } from "../../../discordeno/mod.ts"; -import type { ApplicationCommandOptionChoice } from "./CommandInteraction.ts"; +import type { ApplicationCommandOptionChoice } from "./BaseInteraction.ts"; import { InteractionResponseTypes } from "../../../discordeno/mod.ts"; import BaseInteraction from "./BaseInteraction.ts"; import * as Routes from "../../Routes.ts"; @@ -23,7 +23,7 @@ export class AutoCompleteInteraction extends BaseInteraction implements Model { commandType: ApplicationCommandTypes; commandGuildId?: Snowflake; - async respond(choices: ApplicationCommandOptionChoice[]): Promise { + async respondWithChoices(choices: ApplicationCommandOptionChoice[]): Promise { await this.session.rest.runMethod( this.session.rest, "POST", diff --git a/packages/biscuit/structures/interactions/BaseInteraction.ts b/packages/biscuit/structures/interactions/BaseInteraction.ts index f4ec944..6657b1d 100644 --- a/packages/biscuit/structures/interactions/BaseInteraction.ts +++ b/packages/biscuit/structures/interactions/BaseInteraction.ts @@ -1,16 +1,55 @@ import type { Model } from "../Base.ts"; import type { Session } from "../../Session.ts"; -import type { DiscordInteraction } from "../../../discordeno/mod.ts"; +import type { + DiscordInteraction, + DiscordMessage, + DiscordMessageComponents, +} from "../../../discordeno/mod.ts"; import type CommandInteraction from "./CommandInteraction.ts"; import type PingInteraction from "./PingInteraction.ts"; import type ComponentInteraction from "./ComponentInteraction.ts"; import type ModalSubmitInteraction from "./ModalSubmitInteraction.ts"; import type AutoCompleteInteraction from "./AutoCompleteInteraction.ts"; -import { InteractionTypes } from "../../../discordeno/mod.ts"; +import type { CreateMessage } from "../Message.ts"; +import type { MessageFlags } from "../../Util.ts"; +import type { EditWebhookMessage } from "../Webhook.ts"; +import { InteractionTypes, InteractionResponseTypes } from "../../../discordeno/mod.ts"; import { Snowflake } from "../../Snowflake.ts"; import User from "../User.ts"; import Member from "../Member.ts"; +import Message from "../Message.ts"; import Permsisions from "../Permissions.ts"; +import Webhook from "../Webhook.ts"; +import * as Routes from "../../Routes.ts"; + + +/** + * @link https://discord.com/developers/docs/interactions/slash-commands#interaction-response + */ +export interface InteractionResponse { + type: InteractionResponseTypes; + data?: InteractionApplicationCommandCallbackData; +} + +/** + * @link https://discord.com/developers/docs/interactions/slash-commands#interaction-response-interactionapplicationcommandcallbackdata + */ +export interface InteractionApplicationCommandCallbackData + extends Pick { + customId?: string; + title?: string; + components?: DiscordMessageComponents; + flags?: MessageFlags; + choices?: ApplicationCommandOptionChoice[]; +} + +/** + * @link https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptionchoice + */ +export interface ApplicationCommandOptionChoice { + name: string; + value: string | number; +} export abstract class BaseInteraction implements Model { constructor(session: Session, data: DiscordInteraction) { @@ -51,6 +90,8 @@ export abstract class BaseInteraction implements Model { readonly version: 1; + responded = false; + get createdTimestamp(): number { return Snowflake.snowflakeToTimestamp(this.id); } @@ -82,6 +123,153 @@ export abstract class BaseInteraction implements Model { inGuild(): this is this & { guildId: Snowflake } { return !!this.guildId; } + + // webhooks methods: + + async editReply(options: EditWebhookMessage & { messageId?: Snowflake }): Promise { + const message = await this.session.rest.runMethod( + this.session.rest, + "PATCH", + options.messageId + ? Routes.WEBHOOK_MESSAGE(this.id, this.token, options.messageId) + : Routes.WEBHOOK_MESSAGE_ORIGINAL(this.id, this.token), + { + content: options.content, + embeds: options.embeds, + file: options.files, + components: options.components, + allowed_mentions: options.allowedMentions || { + parse: options.allowedMentions!.parse, + replied_user: options.allowedMentions!.repliedUser, + users: options.allowedMentions!.users, + roles: options.allowedMentions!.roles, + }, + attachments: options.attachments?.map((attachment) => { + return { + id: attachment.id, + filename: attachment.name, + content_type: attachment.contentType, + size: attachment.size, + url: attachment.attachment, + proxy_url: attachment.proxyUrl, + height: attachment.height, + width: attachment.width, + }; + }), + message_id: options.messageId, + } + ); + + if (!message || !options.messageId) { + return message as undefined; + } + + return new Message(this.session, message); + } + + async sendFollowUp(options: InteractionApplicationCommandCallbackData): Promise { + const message = await Webhook.prototype.execute.call({ + id: this.applicationId!, + token: this.token, + session: this.session, + }, options); + + return message!; + } + + async editFollowUp(messageId: Snowflake, options?: { threadId: Snowflake }): Promise { + const message = await Webhook.prototype.editMessage.call( + { + id: this.session.applicationId, + token: this.token, + }, + messageId, + options + ); + + return message; + } + + async deleteFollowUp(messageId: Snowflake, options?: { threadId?: Snowflake }): Promise { + await Webhook.prototype.deleteMessage.call( + { + id: this.session.applicationId, + token: this.token + }, + messageId, + options + ); + } + + async fetchFollowUp(messageId: Snowflake, options?: { threadId?: Snowflake }): Promise { + const message = await Webhook.prototype.fetchMessage.call( + { + id: this.session.applicationId, + token: this.token, + }, + messageId, + options + ); + + return message; + } + + // end webhook methods + + // deno-fmt-ignore + async respond(resp: InteractionResponse): Promise; + async respond(resp: { with: InteractionApplicationCommandCallbackData }): Promise; + async respond(resp: InteractionResponse | { with: InteractionApplicationCommandCallbackData }): Promise { + const options = "with" in resp ? resp.with : resp.data; + const type = "type" in resp ? resp.type : InteractionResponseTypes.ChannelMessageWithSource; + + const data = { + content: options?.content, + custom_id: options?.customId, + file: options?.files, + allowed_mentions: options?.allowedMentions, + flags: options?.flags, + chocies: options?.choices, + embeds: options?.embeds, + title: options?.title, + components: options?.components, + }; + + if (!this.responded) { + await this.session.rest.sendRequest(this.session.rest, { + url: Routes.INTERACTION_ID_TOKEN(this.id, this.token), + method: "POST", + payload: this.session.rest.createRequestBody(this.session.rest, { + method: "POST", + body: { type, data, file: options?.files }, + headers: { "Authorization": "" }, + }), + }); + + this.responded = true; + return; + } + + return this.sendFollowUp(data); + } + + // start custom methods + + async respondWith(resp: InteractionApplicationCommandCallbackData): Promise { + const m = await this.respond({ with: resp }); + + return m; + } + + async defer() { + await this.respond({ type: InteractionResponseTypes.DeferredChannelMessageWithSource }); + } + + async autocomplete() { + await this.respond({ type: InteractionResponseTypes.ApplicationCommandAutocompleteResult }); + } + + // end custom methods } export default BaseInteraction; diff --git a/packages/biscuit/structures/interactions/CommandInteraction.ts b/packages/biscuit/structures/interactions/CommandInteraction.ts index bf15d85..d570fa5 100644 --- a/packages/biscuit/structures/interactions/CommandInteraction.ts +++ b/packages/biscuit/structures/interactions/CommandInteraction.ts @@ -6,11 +6,7 @@ import type { DiscordInteraction, DiscordMemberWithUser, InteractionTypes, - DiscordMessageComponents, } from "../../../discordeno/mod.ts"; -import type { CreateMessage } from "../Message.ts"; -import type { MessageFlags } from "../../Util.ts"; -import { InteractionResponseTypes } from "../../../discordeno/mod.ts"; import BaseInteraction from "./BaseInteraction.ts"; import CommandInteractionOptionResolver from "./CommandInteractionOptionResolver.ts"; import Attachment from "../Attachment.ts"; @@ -18,36 +14,6 @@ import User from "../User.ts"; import Member from "../Member.ts"; import Message from "../Message.ts"; import Role from "../Role.ts"; -import Webhook from "../Webhook.ts"; -import * as Routes from "../../Routes.ts"; - -/** - * @link https://discord.com/developers/docs/interactions/slash-commands#interaction-response - */ -export interface InteractionResponse { - type: InteractionResponseTypes; - data?: InteractionApplicationCommandCallbackData; -} - -/** - * @link https://discord.com/developers/docs/interactions/slash-commands#interaction-response-interactionapplicationcommandcallbackdata - */ -export interface InteractionApplicationCommandCallbackData - extends Pick { - customId?: string; - title?: string; - components?: DiscordMessageComponents; - flags?: MessageFlags; - choices?: ApplicationCommandOptionChoice[]; -} - -/** - * @link https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptionchoice - */ -export interface ApplicationCommandOptionChoice { - name: string; - value: string | number; -} export class CommandInteraction extends BaseInteraction implements Model { constructor(session: Session, data: DiscordInteraction) { @@ -111,47 +77,6 @@ export class CommandInteraction extends BaseInteraction implements Model { messages: Map; }; options: CommandInteractionOptionResolver; - responded = false; - - async sendFollowUp(options: InteractionApplicationCommandCallbackData): Promise { - const message = await Webhook.prototype.execute.call({ - id: this.applicationId!, - token: this.token, - session: this.session, - }, options); - - return message!; - } - - async respond({ type, data: options }: InteractionResponse): Promise { - const data = { - content: options?.content, - custom_id: options?.customId, - file: options?.files, - allowed_mentions: options?.allowedMentions, - flags: options?.flags, - chocies: options?.choices, - embeds: options?.embeds, - title: options?.title, - }; - - if (!this.responded) { - await this.session.rest.sendRequest(this.session.rest, { - url: Routes.INTERACTION_ID_TOKEN(this.id, this.token), - method: "POST", - payload: this.session.rest.createRequestBody(this.session.rest, { - method: "POST", - body: { type, data, file: options?.files }, - headers: { "Authorization": "" }, - }), - }); - - this.responded = true; - return; - } - - return this.sendFollowUp(data); - } } export default CommandInteraction; diff --git a/packages/biscuit/structures/interactions/ComponentInteraction.ts b/packages/biscuit/structures/interactions/ComponentInteraction.ts index b602055..e150fff 100644 --- a/packages/biscuit/structures/interactions/ComponentInteraction.ts +++ b/packages/biscuit/structures/interactions/ComponentInteraction.ts @@ -2,9 +2,7 @@ import type { Model } from "../Base.ts"; import type { Snowflake } from "../../Snowflake.ts"; import type { Session } from "../../Session.ts"; import type { DiscordInteraction, InteractionTypes } from "../../../discordeno/mod.ts"; -import type { InteractionApplicationCommandCallbackData, InteractionResponse } from "./CommandInteraction.ts"; -import { MessageComponentTypes } from "../../../discordeno/mod.ts"; -import CommandInteraction from "./CommandInteraction.ts"; +import { MessageComponentTypes, InteractionResponseTypes } from "../../../discordeno/mod.ts"; import BaseInteraction from "./BaseInteraction.ts"; import Message from "../Message.ts"; @@ -25,7 +23,6 @@ export class ComponentInteraction extends BaseInteraction implements Model { targetId?: Snowflake; values?: string[]; message: Message; - responded = false; //TODO: create interface/class for components types isButton(): boolean { @@ -44,12 +41,8 @@ export class ComponentInteraction extends BaseInteraction implements Model { return this.componentType === MessageComponentTypes.SelectMenu; } - sendFollowUp(options: InteractionApplicationCommandCallbackData): Promise { - return CommandInteraction.prototype.sendFollowUp.call(this, options); - } - - respond(options: InteractionResponse): Promise { - return CommandInteraction.prototype.respond.call(this, options); + async deferUpdate() { + await this.respond({ type: InteractionResponseTypes.DeferredUpdateMessage }); } } diff --git a/packages/biscuit/util/EventEmmiter.ts b/packages/biscuit/util/EventEmmiter.ts index a093047..83684c4 100644 --- a/packages/biscuit/util/EventEmmiter.ts +++ b/packages/biscuit/util/EventEmmiter.ts @@ -14,11 +14,11 @@ export class EventEmitter { return this; } - on(event: string, func: Function) { + on(event: string, func: Function): this { return this.#addListener(event, func); } - #removeListener(event: string, func: Function): EventEmitter { + #removeListener(event: string, func: Function): this { if (this.listeners.has(event)) { const listener = this.listeners.get(event); @@ -34,11 +34,11 @@ export class EventEmitter { return this; } - off(event: string, func: Function): EventEmitter { + off(event: string, func: Function): this { return this.#removeListener(event, func); } - once(event: string, func: Function): EventEmitter { + once(event: string, func: Function): this { // it is important for this to be an arrow function const closure = () => { func();