From 60484dab40680e79dcca7f3f4a4a3dee7c80a489 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Fri, 1 Jul 2022 09:49:04 -0500 Subject: [PATCH] feat: interaction.Respond --- session/Session.ts | 17 +++++- structures/Guild.ts | 2 +- structures/Interaction.ts | 109 +++++++++++++++++++++++++++++++++++--- structures/Message.ts | 10 ++-- util/Routes.ts | 15 ++++++ vendor/external.ts | 1 + 6 files changed, 139 insertions(+), 15 deletions(-) diff --git a/session/Session.ts b/session/Session.ts index fe7a188..aa7477c 100644 --- a/session/Session.ts +++ b/session/Session.ts @@ -4,7 +4,7 @@ import type { Events } from "../handlers/Actions.ts"; import { Snowflake } from "../util/Snowflake.ts"; import { EventEmitter } from "../util/EventEmmiter.ts"; -import { createGatewayManager, createRestManager } from "../vendor/external.ts"; +import { createGatewayManager, createRestManager, getBotIdFromToken } from "../vendor/external.ts"; import * as Routes from "../util/Routes.ts"; import * as Actions from "../handlers/Actions.ts"; @@ -39,6 +39,18 @@ export class Session extends EventEmitter { rest: ReturnType; gateway: ReturnType; + unrepliedInteractions: Set = new Set(); + + #botId: Snowflake; + + set botId(id: Snowflake) { + this.#botId = id; + } + + get botId() { + return this.#botId; + } + constructor(options: SessionOptions) { super(); this.options = options; @@ -71,7 +83,8 @@ export class Session extends EventEmitter { }, handleDiscordPayload: this.options.rawHandler ?? defHandler, }); - // TODO: set botId in Session.botId or something + + this.#botId = getBotIdFromToken(options.token).toString(); } override on(event: K, func: Events[K]): this; diff --git a/structures/Guild.ts b/structures/Guild.ts index ef265cb..8cf752d 100644 --- a/structures/Guild.ts +++ b/structures/Guild.ts @@ -62,7 +62,7 @@ export class Guild extends BaseGuild implements Model { this.vefificationLevel = data.verification_level; this.defaultMessageNotificationLevel = data.default_message_notifications; this.explicitContentFilterLevel = data.explicit_content_filter; - this.members = data.members?.map((member) => new Member(session, { ...member, user: member.user! })) ?? []; + this.members = data.members?.map((member) => new Member(session, { ...member, user: member.user! }, data.id)) ?? []; this.roles = data.roles.map((role) => new Role(session, role, data.id)); this.emojis = data.emojis.map((guildEmoji) => new GuildEmoji(session, guildEmoji, data.id)); } diff --git a/structures/Interaction.ts b/structures/Interaction.ts index d120f93..29a6744 100644 --- a/structures/Interaction.ts +++ b/structures/Interaction.ts @@ -1,9 +1,42 @@ import type { Model } from "./Base.ts"; import type { Snowflake } from "../util/Snowflake.ts"; import type { Session } from "../session/Session.ts"; -import type { DiscordInteraction, InteractionTypes } from "../vendor/external.ts"; +import type { + DiscordMessage, + DiscordInteraction, + InteractionTypes, + InteractionResponseTypes, + FileContent, +} from "../vendor/external.ts"; +import type { MessageFlags } from "../util/shared/flags.ts"; +import type { AllowedMentions } from "./Message.ts"; import User from "./User.ts"; -// import Member from "./Member.ts"; +import Message from "./Message.ts"; +import Member from "./Member.ts"; +import * as Routes from "../util/Routes.ts"; + +export interface InteractionResponse { + type: InteractionResponseTypes; + data?: InteractionApplicationCommandCallbackData; +} + +export interface InteractionApplicationCommandCallbackData { + content?: string; + tts?: boolean; + allowedMentions?: AllowedMentions; + files?: FileContent[]; + customId?: string; + title?: string; + // components?: Component[]; + flags?: MessageFlags; + choices?: ApplicationCommandOptionChoice[]; +} + +/** https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptionchoice */ +export interface ApplicationCommandOptionChoice { + name: string; + value: string | number; +} export class Interaction implements Model { constructor(session: Session, data: DiscordInteraction) { @@ -21,8 +54,7 @@ export class Interaction implements Model { this.user = new User(session, data.user!); } else { - // TODO: member transformer - // pass + this.member = new Member(session, data.member!, data.guild_id); } } @@ -38,9 +70,74 @@ export class Interaction implements Model { // deno-lint-ignore no-explicit-any data: any; user?: User; + member?: Member; - // TODO: do methods - async respond() { + async respond({ type, data }: InteractionResponse) { + const toSend = { + tts: data?.tts, + title: data?.title, + flags: data?.flags, + content: data?.content, + choices: data?.choices, + custom_id: data?.customId, + allowed_mentions: data?.allowedMentions + ? { + users: data.allowedMentions.users, + roles: data.allowedMentions.roles, + parse: data.allowedMentions.parse, + replied_user: data.allowedMentions.repliedUser, + } + : { parse: [] }, + }; + + if (this.session.unrepliedInteractions.delete(BigInt(this.id))) { + 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: type, + data: toSend, + file: data?.files, + }, + headers: { + // remove authorization header + Authorization: "", + }, + }), + } + ); + + return; + } + + const result = await this.session.rest.sendRequest( + this.session.rest, + { + url: Routes.WEBHOOK(this.session.botId, this.token), + method: "POST", + payload: this.session.rest.createRequestBody(this.session.rest, { + method: "POST", + body: { + ...toSend, + file: data?.files + }, + headers: { + // remove authorization header + Authorization: "", + }, + }), + } + ); + + return new Message(this.session, result); + } + + inGuild(): this is Interaction & { user: undefined, guildId: Snowflake, member: Member } { + return "guildId" in this; } } diff --git a/structures/Message.ts b/structures/Message.ts index 2f101b0..0505daf 100644 --- a/structures/Message.ts +++ b/structures/Message.ts @@ -63,12 +63,10 @@ export class Message implements Model { this.attachments = data.attachments.map((attachment) => new Attachment(session, attachment)); // user is always null on MessageCreate and its replaced with author - this.member = data.member - ? new Member(session, { - ...data.member, - user: data.author, - }) - : undefined; + + if (data.guild_id && data.member) { + this.member = new Member(session, { ...data.member, user: data.author }, data.guild_id); + } } readonly session: Session; diff --git a/util/Routes.ts b/util/Routes.ts index 03442a7..035042d 100644 --- a/util/Routes.ts +++ b/util/Routes.ts @@ -139,3 +139,18 @@ export function INVITE(inviteCode: string, options?: GetInvite) { export function GUILD_INVITES(guildId: Snowflake) { return `/guilds/${guildId}/invites`; } + +export function INTERACTION_ID_TOKEN(interactionId: Snowflake, token: string) { + return `/interactions/${interactionId}/${token}/callback`; +} + +export function WEBHOOK(webhookId: Snowflake, token: string, options?: { wait?: boolean; threadId?: Snowflake }) { + let url = `/webhooks/${webhookId}/${token}?`; + + if (options) { + if (options.wait !== undefined) url += `wait=${options.wait}`; + if (options.threadId) url += `threadId=${options.threadId}`; + } + + return url; +} diff --git a/vendor/external.ts b/vendor/external.ts index efb9d06..b96e207 100644 --- a/vendor/external.ts +++ b/vendor/external.ts @@ -2,3 +2,4 @@ export * from "./gateway/mod.ts"; export * from "./rest/mod.ts"; export * from "./types/mod.ts"; export * from "./util/constants.ts"; +export * from "./util/token.ts";