Prototyping application commands & base command handler

This commit is contained in:
2024-11-16 11:55:10 -06:00
parent f3b1546d54
commit 119ddfefd1
6 changed files with 236 additions and 17 deletions
+4 -3
View File
@@ -1,7 +1,6 @@
// Base classes
export { Client } from './services/client';
export { Intent } from './types/common';
export { BaseHandler, type Handler } from './types/hander';
export { Intent, Trait } from './types/common';
// Consumable types
export type { ClientConfig } from './types/common';
export type { Context } from './types/context';
@@ -13,4 +12,6 @@ export { Member } from './types/member';
export { User } from './types/user';
export { Message, type MessagePayload } from './types/message';
export { Embed } from './types/embed';
export { Emoji } from './types/emoji';
export { Emoji } from './types/emoji';
// Other
export { createCommandHandler } from './types/hander';
+2 -2
View File
@@ -4,7 +4,7 @@ import { Processor } from './processor';
import { Expectation, fromDiscord, Intent, type ClientConfig } from '../types/common';
import { exists, isNil } from '../utils/index';
import type { Handler } from '../types/hander';
import { DiscordMessage, Message } from '../types/message';
import { type DiscordMessage, Message } from '../types/message';
import { Guilds } from './caches/guilds';
import { Messages } from './caches/messages';
import { Users } from './caches/users';
@@ -103,7 +103,7 @@ export class Client {
message = new Message().setContent(message);
}
return Message[fromDiscord](DiscordMessage.fromAPIResponse(await this.api.post(`/channels/${channel_id}/messages`, message.toJSON())));
return Message[fromDiscord](await this.api.post<DiscordMessage>(`/channels/${channel_id}/messages`, message.toJSON()));
}
chatCommands = {
+131
View File
@@ -0,0 +1,131 @@
import { Trait } from "./common";
// Enum for Application Command Types
export enum ApplicationCommandType {
CHAT_INPUT = 1,
USER = 2,
MESSAGE = 3,
PRIMARY_ENTRY_POINT = 4,
}
// Enum for Application Command Option Types
export enum ApplicationCommandOptionType {
SUB_COMMAND = 1,
SUB_COMMAND_GROUP = 2,
STRING = 3,
INTEGER = 4,
BOOLEAN = 5,
USER = 6,
CHANNEL = 7,
ROLE = 8,
MENTIONABLE = 9,
NUMBER = 10,
ATTACHMENT = 11,
}
// Enum for Channel Types (if applicable for options)
export enum ChannelType {
GUILD_TEXT = 0,
DM = 1,
GUILD_VOICE = 2,
GROUP_DM = 3,
GUILD_CATEGORY = 4,
GUILD_NEWS = 5,
GUILD_STORE = 6,
GUILD_NEWS_THREAD = 10,
GUILD_PUBLIC_THREAD = 11,
GUILD_PRIVATE_THREAD = 12,
GUILD_STAGE_VOICE = 13,
GUILD_DIRECTORY = 14,
GUILD_FORUM = 15,
}
// Enum for Application Command Handler Types
export enum ApplicationCommandHandlerType {
APP_HANDLER = 1,
DISCORD_LAUNCH_ACTIVITY = 2,
}
// Define the ApplicationCommandOption type
export type ApplicationCommandOption = {
type: ApplicationCommandOptionType;
name: string;
name_localizations?: Record<string, string> | null;
description: string;
description_localizations?: Record<string, string> | null;
required?: boolean;
choices?: Array<{ name: string; value: string | number }>;
options?: ApplicationCommandOption[]; // Recursive structure for subcommands
channel_types?: ChannelType[];
min_value?: number;
max_value?: number;
min_length?: number;
max_length?: number;
autocomplete?: boolean;
};
// Define the DiscordApplicationCommand type
export type DiscordApplicationCommand = {
id: string;
type?: ApplicationCommandType;
application_id: string;
guild_id?: string;
name: string;
name_localizations?: Record<string, string> | null;
description: string;
description_localizations?: Record<string, string> | null;
options?: ApplicationCommandOption[];
default_member_permissions?: string | null;
dm_permission?: boolean;
default_permission?: boolean;
nsfw?: boolean;
integration_types?: number[];
contexts?: number[];
version: string;
handler?: ApplicationCommandHandlerType;
};
// Define the ApplicationCommand class
export class ApplicationCommand {
id: string;
type?: ApplicationCommandType;
application_id: string;
guild_id?: string;
name: string;
name_localizations?: Record<string, string> | null;
description: string;
description_localizations?: Record<string, string> | null;
options?: ApplicationCommandOption[];
default_member_permissions?: string | null;
dm_permission?: boolean;
default_permission?: boolean;
nsfw?: boolean;
integration_types?: number[];
contexts?: number[];
version: string;
handler?: ApplicationCommandHandlerType;
constructor(command: DiscordApplicationCommand) {
this.id = command.id;
this.type = command.type;
this.application_id = command.application_id;
this.guild_id = command.guild_id;
this.name = command.name;
this.name_localizations = command.name_localizations;
this.description = command.description;
this.description_localizations = command.description_localizations;
this.options = command.options;
this.default_member_permissions = command.default_member_permissions;
this.dm_permission = command.dm_permission;
this.default_permission = command.default_permission;
this.nsfw = command.nsfw;
this.integration_types = command.integration_types;
this.contexts = command.contexts;
this.version = command.version;
this.handler = command.handler;
}
static [Trait.fromDiscord](command: DiscordApplicationCommand): ApplicationCommand {
return new ApplicationCommand(command);
}
}
+4 -1
View File
@@ -22,11 +22,14 @@ export enum HandlerType {
export type Expects<T extends Array<Expectation>, K extends Expectation> = Extract<K, T[number]> extends never ? false : true;
export const fromDiscord: unique symbol = Symbol('fromDiscord')
export const execute: unique symbol = Symbol('execute')
export const Trait: {
fromDiscord: typeof fromDiscord
execute: typeof execute
} = {
fromDiscord
fromDiscord,
execute
}
export type EventPayload = any;
+62 -5
View File
@@ -1,14 +1,71 @@
import type { EventPayload } from "./common";
import type { ApplicationCommandOption, ApplicationCommandOptionType } from "./applicationCommand";
import { Trait, type EventPayload } from "./common";
import type { Context } from "./context";
export class BaseHandler {
constructor() {};
type PrimitiveTypeMap = {
[ApplicationCommandOptionType.STRING]: string;
[ApplicationCommandOptionType.INTEGER]: number;
[ApplicationCommandOptionType.BOOLEAN]: boolean;
[ApplicationCommandOptionType.NUMBER]: number;
[ApplicationCommandOptionType.USER]: string; // Could be a user ID or object depending on your use case
[ApplicationCommandOptionType.CHANNEL]: string; // Could be a channel ID or object
[ApplicationCommandOptionType.ROLE]: string; // Could be a role ID or object
[ApplicationCommandOptionType.MENTIONABLE]: string; // Could be a mentionable ID or object
[ApplicationCommandOptionType.ATTACHMENT]: string; // Could represent attachment IDs
[ApplicationCommandOptionType.SUB_COMMAND]: never; // Handled by `options`
[ApplicationCommandOptionType.SUB_COMMAND_GROUP]: never; // Handled by `options`
};
public async execute(context: Context | null, args: any[]) {};
// Recursive type to map ApplicationCommandOption to arguments
type ArgsFromOptions<T extends readonly ApplicationCommandOption[]> = {
[Option in T[number] as Option['name']]: Option extends { options: readonly ApplicationCommandOption[] }
? ArgsFromOptions<Option['options']> // Recursive inference for subcommands
: Option['choices'] extends readonly { value: infer U }[]
? U // Infer from choices if defined
: Option['type'] extends keyof PrimitiveTypeMap
? PrimitiveTypeMap[Option['type']] // Otherwise, map from ApplicationCommandOptionType
: unknown;
};
export type BaseHandler<T extends readonly ApplicationCommandOption[]> = {
args: T;
[Trait.execute]: (context: Context, args: ArgsFromOptions<T>) => Promise<void> | void;
};
// Helper function to infer type of `args`
export function createCommandHandler<T extends readonly ApplicationCommandOption[]>(
handler: BaseHandler<T>
): BaseHandler<T> {
return handler as BaseHandler<T>;
}
const handler = createCommandHandler({
args: [
{
type: 3,
name: "name",
description: "The name of the user",
required: true
},
{
type: 3,
name: "age",
description: "The age of the user",
required: true,
choices: [
{ name: "18", value: "18" },
{ name: "19", value: "19" },
{ name: "20", value: "20" }
]
}
] as const,
[Trait.execute]: async (context, args) => {
console.log(args);
}
});
export type CommandHandlerFunction = (context: Context, args: any[]) => Promise<void> | void;
export type EventHandlerFunction = (context: Context | null, payload: EventPayload) => Promise<void> | void;
export type HandlerFunction = CommandHandlerFunction | EventHandlerFunction;
export type Handler = HandlerFunction | BaseHandler;
export type Handler = HandlerFunction | BaseHandler<any>;