feat(commands): closes #39

This commit is contained in:
Yuzu 2022-07-13 11:57:28 -05:00
parent 527b09e3e4
commit b4ad1865e1
5 changed files with 239 additions and 86 deletions

View File

@ -347,3 +347,24 @@ export function GUILD_APPLICATION_COMMANDS_PERMISSIONS(appId: Snowflake, guildId
if (commandId) return `/applications/${appId}/guilds/${guildId}/commands/${commandId}/permissions`; if (commandId) return `/applications/${appId}/guilds/${guildId}/commands/${commandId}/permissions`;
return `/applications/${appId}/guilds/${guildId}/commands/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;
}

View File

@ -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 { DiscordGatewayPayload, Shard } from "../discordeno/mod.ts";
import type { Events } from "./Actions.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 { Snowflake } from "./Snowflake.ts";
import { EventEmitter } from "./util/EventEmmiter.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 Routes from "./Routes.ts";
import * as Actions from "./Actions.ts"; import * as Actions from "./Actions.ts";
export type DiscordRawEventHandler = (shard: Shard, data: DiscordGatewayPayload) => unknown; 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<CreateApplicationCommand, "options"> {
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 { export interface RestOptions {
secretKey?: string; secretKey?: string;
applicationId?: Snowflake; applicationId?: Snowflake;
@ -31,11 +99,11 @@ export interface SessionOptions {
/** /**
* Receives a Token, connects * Receives a Token, connects
* Most of the command implementations were adapted from Discordeno (https://github.com/discordeno/discordeno)
*/ */
export class Session extends EventEmitter { export class Session extends EventEmitter {
options: SessionOptions; options: SessionOptions;
// TODO: improve this with CreateShardManager etc
rest: ReturnType<typeof createRestManager>; rest: ReturnType<typeof createRestManager>;
gateway: ReturnType<typeof createGatewayManager>; gateway: ReturnType<typeof createGatewayManager>;
@ -114,6 +182,142 @@ export class Session extends EventEmitter {
return super.emit(event, ...params); return super.emit(event, ...params);
} }
createApplicationCommand(options: CreateApplicationCommand | CreateContextApplicationCommand, guildId: Snowflake) {
return this.rest.runMethod<DiscordApplicationCommand>(
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<undefined>(
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<DiscordGuildApplicationCommandPermissions>(
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<DiscordApplicationCommand>(
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<DiscordGuildApplicationCommandPermissions[]>(
this.rest,
"GET",
Routes.GUILD_APPLICATION_COMMANDS_PERMISSIONS(this.applicationId, guildId),
);
}
fetchApplicationCommandPermission(guildId: Snowflake, id: Snowflake) {
return this.rest.runMethod<DiscordGuildApplicationCommandPermissions>(
this.rest,
"GET",
Routes.GUILD_APPLICATION_COMMANDS_PERMISSIONS(this.applicationId, guildId, id),
);
}
upsertApplicationCommand(
id: Snowflake,
options: AtLeastOne<CreateApplicationCommand> | AtLeastOne<CreateContextApplicationCommand>,
guildId?: Snowflake,
) {
return this.rest.runMethod<DiscordApplicationCommand>(
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<UpsertApplicationCommands | CreateContextApplicationCommand>, guildId?: Snowflake) {
return this.rest.runMethod<DiscordApplicationCommand[]>(
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<CreateContextApplicationCommand> | AtLeastOne<CreateApplicationCommand>): cmd is AtLeastOne<CreateContextApplicationCommand> {
return cmd.type === ApplicationCommandTypes.Message || cmd.type === ApplicationCommandTypes.User;
}
async start() { async start() {
const getGatewayBot = () => this.rest.runMethod<DiscordGetGatewayBot>(this.rest, "GET", Routes.GATEWAY_BOT()); const getGatewayBot = () => this.rest.runMethod<DiscordGetGatewayBot>(this.rest, "GET", Routes.GATEWAY_BOT());

View File

@ -134,7 +134,7 @@ export class CommandInteraction extends BaseInteraction implements Model {
title: options?.title, title: options?.title,
}; };
if (!this.respond) { if (!this.responded) {
await this.session.rest.sendRequest<undefined>(this.session.rest, { await this.session.rest.sendRequest<undefined>(this.session.rest, {
url: Routes.INTERACTION_ID_TOKEN(this.id, this.token), url: Routes.INTERACTION_ID_TOKEN(this.id, this.token),
method: "POST", method: "POST",

View File

@ -2,7 +2,9 @@ import type { Model } from "../Base.ts";
import type { Snowflake } from "../../Snowflake.ts"; import type { Snowflake } from "../../Snowflake.ts";
import type { Session } from "../../Session.ts"; import type { Session } from "../../Session.ts";
import type { DiscordInteraction, InteractionTypes } from "../../../discordeno/mod.ts"; import type { DiscordInteraction, InteractionTypes } from "../../../discordeno/mod.ts";
import type { InteractionResponse, InteractionApplicationCommandCallbackData } from "./CommandInteraction.ts";
import { MessageComponentTypes } from "../../../discordeno/mod.ts"; import { MessageComponentTypes } from "../../../discordeno/mod.ts";
import CommandInteraction from "./CommandInteraction.ts";
import BaseInteraction from "./BaseInteraction.ts"; import BaseInteraction from "./BaseInteraction.ts";
import Message from "../Message.ts"; import Message from "../Message.ts";
@ -40,6 +42,14 @@ export class ComponentInteraction extends BaseInteraction implements Model {
isSelectMenu() { isSelectMenu() {
return this.componentType === MessageComponentTypes.SelectMenu; return this.componentType === MessageComponentTypes.SelectMenu;
} }
sendFollowUp(options: InteractionApplicationCommandCallbackData) {
return CommandInteraction.prototype.sendFollowUp.call(this, options);
}
respond(options: InteractionResponse): Promise<Message | undefined> {
return CommandInteraction.prototype.respond.call(this, options);
}
} }
export default ComponentInteraction; export default ComponentInteraction;

View File

@ -1229,7 +1229,6 @@ export interface GatewayBot {
// UTILS // UTILS
export type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U]; export type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U];
export type MakeRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] }; export type MakeRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
// THANK YOU YUI FOR SHARING THIS! // THANK YOU YUI FOR SHARING THIS!
@ -1243,87 +1242,6 @@ export type Camelize<T> = {
: never; : 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<T = unknown> = {
[K in PropertyKey]: T;
};
/** Array with no utilty methods, aka Object.create(null) */
export type ArrayWithNoPrototype<T> = {
[index: number]: T | ArrayWithNoPrototype<T>;
};
/**
* Allows any type but T
* it is recursive
* @example
* export type RequestData = Record<string, AnythingBut<bigint>>;
*/
export type AnythingBut<T> = Exclude<
| Primitive
| {
[K in PropertyKey]: AnythingBut<T>;
}
| ArrayWithNoPrototype<
| Primitive
| {
[K in PropertyKey]: AnythingBut<T>;
}
>,
T
>;
/**
* object identity type
*/
export type Id<T> = T extends infer U ? {
[K in keyof U]: U[K];
}
: never;
export type KeysWithUndefined<T> = {
[K in keyof T]-?: undefined extends T[K] ? K
: null extends T[K] ? K
: never;
}[keyof T];
type OptionalizeAux<T extends object> = Id<
& {
[K in KeysWithUndefined<T>]?: Optionalize<T[K]>;
}
& {
[K in Exclude<keyof T, KeysWithUndefined<T>>]: T[K] extends ObjectLiteral ? Optionalize<T[K]> : T[K];
}
>;
/**
* Makes all of properties in T optional when they're null | undefined
* it is recursive
*/
export type Optionalize<T> = T extends object
? T extends Array<unknown>
? number extends T["length"] ? T[number] extends object ? Array<OptionalizeAux<T[number]>>
: T
: Partial<T>
: OptionalizeAux<T>
: T;
export type PickPartial<T, K extends keyof T> = export type PickPartial<T, K extends keyof T> =
& { & {
[P in keyof T]?: T[P] | undefined; [P in keyof T]?: T[P] | undefined;