diff --git a/packages/biscuit/Routes.ts b/packages/biscuit/Routes.ts index dd3fa06..030d86a 100644 --- a/packages/biscuit/Routes.ts +++ b/packages/biscuit/Routes.ts @@ -347,3 +347,24 @@ export function GUILD_APPLICATION_COMMANDS_PERMISSIONS(appId: Snowflake, guildId if (commandId) return `/applications/${appId}/guilds/${guildId}/commands/${commandId}/permissions`; return `/applications/${appId}/guilds/${guildId}/commands/permissions`; } + + +export function APPLICATION_COMMANDS_LOCALIZATIONS(appId: Snowflake, commandId: Snowflake, withLocalizations?: boolean) { + let url = `/applications/${appId}/commands/${commandId}?`; + + if (withLocalizations !== undefined) { + url += `withLocalizations=${withLocalizations}`; + } + + return url; +} + +export function GUILD_APPLICATION_COMMANDS_LOCALIZATIONS(appId: Snowflake, guildId: Snowflake, commandId: Snowflake, withLocalizations?: boolean) { + let url = `/applications/${appId}/guilds/${guildId}/commands/${commandId}?`; + + if (withLocalizations !== undefined) { + url += `with_localizations=${withLocalizations}`; + } + + return url; +} diff --git a/packages/biscuit/Session.ts b/packages/biscuit/Session.ts index 420d3d7..a0039c3 100644 --- a/packages/biscuit/Session.ts +++ b/packages/biscuit/Session.ts @@ -1,16 +1,84 @@ -import type { DiscordGetGatewayBot, GatewayBot, GatewayIntents } from "../discordeno/mod.ts"; +import type { + ApplicationCommandPermissionTypes, + AtLeastOne, + Localization, + DiscordApplicationCommand, + DiscordApplicationCommandOption, + DiscordGuildApplicationCommandPermissions, + DiscordGetGatewayBot, + GatewayBot, + GatewayIntents, +} from "../discordeno/mod.ts"; + import type { DiscordGatewayPayload, Shard } from "../discordeno/mod.ts"; import type { Events } from "./Actions.ts"; +import type { PermissionResolvable } from "./structures/Permissions.ts"; +import { Permissions } from "./structures/Permissions.ts"; import { Snowflake } from "./Snowflake.ts"; import { EventEmitter } from "./util/EventEmmiter.ts"; -import { createGatewayManager, createRestManager, getBotIdFromToken } from "../discordeno/mod.ts"; +import { ApplicationCommandTypes, createGatewayManager, createRestManager, getBotIdFromToken } from "../discordeno/mod.ts"; import * as Routes from "./Routes.ts"; import * as Actions from "./Actions.ts"; export type DiscordRawEventHandler = (shard: Shard, data: DiscordGatewayPayload) => unknown; +// INTERACTIONS + +/** + * @link https://discord.com/developers/docs/interactions/application-commands#endpoints-json-params + * */ +export interface CreateApplicationCommand { + name: string; + nameLocalizations?: Localization; + description: string; + descriptionLocalizations?: Localization; + type?: ApplicationCommandTypes; + options?: DiscordApplicationCommandOption[]; + defaultMemberPermissions?: PermissionResolvable; + dmPermission?: boolean; +} + +/** + * @link https://discord.com/developers/docs/interactions/application-commands#endpoints-json-params + * */ +export interface CreateContextApplicationCommand extends Omit { + type: ApplicationCommandTypes.Message | ApplicationCommandTypes.User; +} + +/** + * @link https://discord.com/developers/docs/interactions/application-commands#endpoints-query-string-params + * */ +export interface GetApplicationCommand { + guildId?: Snowflake; + withLocalizations?: boolean; +} + +export interface UpsertApplicationCommands extends CreateApplicationCommand { + id?: Snowflake; +} + +/** + * @link https://discord.com/developers/docs/interactions/application-commands#edit-application-command-permissions + * */ +export interface ApplicationCommandPermissions { + id: Snowflake; + type: ApplicationCommandPermissionTypes; + permission: boolean; +} + +/** + * @link https://discord.com/developers/docs/interactions/application-commands#edit-application-command-permissions + * */ +export interface ApplicationCommandPermissions { + id: Snowflake; + type: ApplicationCommandPermissionTypes; + permission: boolean; +} + +// END INTERACTIONS + export interface RestOptions { secretKey?: string; applicationId?: Snowflake; @@ -31,11 +99,11 @@ export interface SessionOptions { /** * Receives a Token, connects + * Most of the command implementations were adapted from Discordeno (https://github.com/discordeno/discordeno) */ export class Session extends EventEmitter { options: SessionOptions; - // TODO: improve this with CreateShardManager etc rest: ReturnType; gateway: ReturnType; @@ -114,6 +182,142 @@ export class Session extends EventEmitter { return super.emit(event, ...params); } + createApplicationCommand(options: CreateApplicationCommand | CreateContextApplicationCommand, guildId: Snowflake) { + return this.rest.runMethod( + this.rest, + "POST", + guildId + ? Routes.GUILD_APPLICATION_COMMANDS(this.applicationId, guildId) + : Routes.APPLICATION_COMMANDS(this.applicationId), + this.isContextApplicationCommand(options) ? { + name: options.name, + name_localizations: options.nameLocalizations, + type: options.type, + } : { + name: options.name, + name_localizations: options.nameLocalizations, + description: options.description, + description_localizations: options.descriptionLocalizations, + type: options.type, + options: options.options, + default_member_permissions: options.defaultMemberPermissions + ? new Permissions(options.defaultMemberPermissions).bitfield.toString() + : undefined, + dm_permission: options.dmPermission, + }, + ); + } + + deleteApplicationCommand(id: Snowflake, guildId?: Snowflake) { + return this.rest.runMethod( + this.rest, + "DELETE", + guildId + ? Routes.GUILD_APPLICATION_COMMANDS(this.applicationId, guildId, id) + : Routes.APPLICATION_COMMANDS(this.applicationId, id), + ); + } + + updateApplicationCommandPermissions( + guildId: Snowflake, + id: Snowflake, + bearerToken: string, + options: ApplicationCommandPermissions[], + ) { + return this.rest.runMethod( + this.rest, + "PUT", + Routes.GUILD_APPLICATION_COMMANDS_PERMISSIONS(this.applicationId, guildId, id), + { + permissions: options, + }, + { + headers: { authorization: `Bearer ${bearerToken}` } + } + ); + } + + fetchApplicationCommand(id: Snowflake, options?: GetApplicationCommand) { + return this.rest.runMethod( + this.rest, + "GET", + options?.guildId + ? Routes.GUILD_APPLICATION_COMMANDS_LOCALIZATIONS( + this.applicationId, + options.guildId, + id, + options?.withLocalizations, + ) + : Routes.APPLICATION_COMMANDS(this.applicationId, id), + ); + } + + fetchApplicationCommandPermissions(guildId: Snowflake) { + return this.rest.runMethod( + this.rest, + "GET", + Routes.GUILD_APPLICATION_COMMANDS_PERMISSIONS(this.applicationId, guildId), + ); + } + + fetchApplicationCommandPermission(guildId: Snowflake, id: Snowflake) { + return this.rest.runMethod( + this.rest, + "GET", + Routes.GUILD_APPLICATION_COMMANDS_PERMISSIONS(this.applicationId, guildId, id), + ); + } + + upsertApplicationCommand( + id: Snowflake, + options: AtLeastOne | AtLeastOne, + guildId?: Snowflake, + ) { + return this.rest.runMethod( + this.rest, + "PATCH", + guildId + ? Routes.GUILD_APPLICATION_COMMANDS(this.applicationId, guildId) + : Routes.APPLICATION_COMMANDS(this.applicationId, id), + this.isContextApplicationCommand(options) ? { + name: options.name, + type: options.type, + } : { + name: options.name, + description: options.description, + type: options.type, + options: options.options, + }, + ); + } + + upsertApplicationCommands(options: Array, guildId?: Snowflake) { + return this.rest.runMethod( + this.rest, + "PUT", + guildId + ? Routes.GUILD_APPLICATION_COMMANDS(this.applicationId, guildId) + : Routes.APPLICATION_COMMANDS(this.applicationId), + options.map((o) => this.isContextApplicationCommand(o) ? { + name: o.name, + type: o.type, + } : { + name: o.name, + description: o.description, + type: o.type, + options: o.options, + } + ), + ); + } + + fetchCommands() {} + + // deno-fmt-ignore + isContextApplicationCommand(cmd: AtLeastOne | AtLeastOne): cmd is AtLeastOne { + return cmd.type === ApplicationCommandTypes.Message || cmd.type === ApplicationCommandTypes.User; + } + async start() { const getGatewayBot = () => this.rest.runMethod(this.rest, "GET", Routes.GATEWAY_BOT()); diff --git a/packages/biscuit/structures/interactions/CommandInteraction.ts b/packages/biscuit/structures/interactions/CommandInteraction.ts index ea80ee9..74c4d5f 100644 --- a/packages/biscuit/structures/interactions/CommandInteraction.ts +++ b/packages/biscuit/structures/interactions/CommandInteraction.ts @@ -134,7 +134,7 @@ export class CommandInteraction extends BaseInteraction implements Model { title: options?.title, }; - if (!this.respond) { + if (!this.responded) { await this.session.rest.sendRequest(this.session.rest, { url: Routes.INTERACTION_ID_TOKEN(this.id, this.token), method: "POST", diff --git a/packages/biscuit/structures/interactions/ComponentInteraction.ts b/packages/biscuit/structures/interactions/ComponentInteraction.ts index c59b98e..95dd40e 100644 --- a/packages/biscuit/structures/interactions/ComponentInteraction.ts +++ b/packages/biscuit/structures/interactions/ComponentInteraction.ts @@ -2,7 +2,9 @@ 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 { InteractionResponse, InteractionApplicationCommandCallbackData } from "./CommandInteraction.ts"; import { MessageComponentTypes } from "../../../discordeno/mod.ts"; +import CommandInteraction from "./CommandInteraction.ts"; import BaseInteraction from "./BaseInteraction.ts"; import Message from "../Message.ts"; @@ -40,6 +42,14 @@ export class ComponentInteraction extends BaseInteraction implements Model { isSelectMenu() { return this.componentType === MessageComponentTypes.SelectMenu; } + + sendFollowUp(options: InteractionApplicationCommandCallbackData) { + return CommandInteraction.prototype.sendFollowUp.call(this, options); + } + + respond(options: InteractionResponse): Promise { + return CommandInteraction.prototype.respond.call(this, options); + } } export default ComponentInteraction; diff --git a/packages/discordeno/types/shared.ts b/packages/discordeno/types/shared.ts index 6c90cdf..8d86e7a 100644 --- a/packages/discordeno/types/shared.ts +++ b/packages/discordeno/types/shared.ts @@ -1229,7 +1229,6 @@ export interface GatewayBot { // UTILS export type AtLeastOne }> = Partial & U[keyof U]; - export type MakeRequired = T & { [P in K]-?: T[P] }; // THANK YOU YUI FOR SHARING THIS! @@ -1243,87 +1242,6 @@ export type Camelize = { : never; }; -/** Non object primitives */ -export type Primitive = - | string - | number - | symbol - | bigint - | boolean - | undefined - | null; -// | object <- don't make object a primitive - -/** - * alternative to 'object' or '{}' - * @example: - * export const o: ObjectLiteral = [] as object; // error - * export const o: object = []; // no error - */ -export type ObjectLiteral = { - [K in PropertyKey]: T; -}; - -/** Array with no utilty methods, aka Object.create(null) */ -export type ArrayWithNoPrototype = { - [index: number]: T | ArrayWithNoPrototype; -}; - -/** - * Allows any type but T - * it is recursive - * @example - * export type RequestData = Record>; - */ -export type AnythingBut = Exclude< - | Primitive - | { - [K in PropertyKey]: AnythingBut; - } - | ArrayWithNoPrototype< - | Primitive - | { - [K in PropertyKey]: AnythingBut; - } - >, - T ->; - -/** - * object identity type - */ -export type Id = T extends infer U ? { - [K in keyof U]: U[K]; -} - : never; - -export type KeysWithUndefined = { - [K in keyof T]-?: undefined extends T[K] ? K - : null extends T[K] ? K - : never; -}[keyof T]; - -type OptionalizeAux = Id< - & { - [K in KeysWithUndefined]?: Optionalize; - } - & { - [K in Exclude>]: T[K] extends ObjectLiteral ? Optionalize : T[K]; - } ->; - -/** - * Makes all of properties in T optional when they're null | undefined - * it is recursive - */ -export type Optionalize = T extends object - ? T extends Array - ? number extends T["length"] ? T[number] extends object ? Array> - : T - : Partial - : OptionalizeAux - : T; - export type PickPartial = & { [P in keyof T]?: T[P] | undefined;