feat: Entry Points (#256)

* feat: entry points types

* fix: update attachment

* feat: more types and command

* chore: apply formatting

* feat: entry interaction

* chore: apply formatting

* feat: entry commands

* feat: end

* fix: build

* fix: typing

* fix: entry point in handler

* fix: build

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Marcos Susaña 2024-08-27 21:18:16 -04:00 committed by GitHub
parent fc4c7ef3da
commit a9d14c4c01
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 594 additions and 110 deletions

View File

@ -28,6 +28,3 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile
- name: Build
run: npx tsc

View File

@ -1,11 +1,21 @@
import type { RESTPostAPIInteractionCallbackJSONBody } from '../../types'; import type {
RESTPostAPIInteractionCallbackJSONBody,
RESTPostAPIInteractionCallbackQuery,
RESTPostAPIInteractionCallbackResult,
} from '../../types';
import type { ProxyRequestMethod } from '../Router'; import type { ProxyRequestMethod } from '../Router';
import type { RestArguments } from '../api'; import type { RestArguments } from '../api';
export interface InteractionRoutes { export interface InteractionRoutes {
interactions: (id: string) => (token: string) => { interactions: (id: string) => (token: string) => {
callback: { callback: {
post(args: RestArguments<ProxyRequestMethod.Post, RESTPostAPIInteractionCallbackJSONBody>): Promise<never>; post(
args: RestArguments<
ProxyRequestMethod.Post,
RESTPostAPIInteractionCallbackJSONBody,
RESTPostAPIInteractionCallbackQuery
>,
): Promise<RESTPostAPIInteractionCallbackResult | undefined>;
}; };
}; };
} }

View File

@ -347,9 +347,9 @@ export class ApiHandler {
const fileKey = file.key ?? `files[${index}]`; const fileKey = file.key ?? `files[${index}]`;
if (isBufferLike(file.data)) { if (isBufferLike(file.data)) {
formData.append(fileKey, new Blob([file.data], { type: file.contentType }), file.name); formData.append(fileKey, new Blob([file.data], { type: file.contentType }), file.filename);
} else { } else {
formData.append(fileKey, new Blob([`${file.data}`], { type: file.contentType }), file.name); formData.append(fileKey, new Blob([`${file.data}`], { type: file.contentType }), file.filename);
} }
} }

View File

@ -24,7 +24,7 @@ export interface RawFile {
contentType?: string; contentType?: string;
data: Buffer | Uint8Array | boolean | number | string; data: Buffer | Uint8Array | boolean | number | string;
key?: string; key?: string;
name: string; filename: string;
} }
export interface ApiRequestOptions { export interface ApiRequestOptions {

View File

@ -17,7 +17,7 @@ export type AttachmentResolvable =
| Attachment; | Attachment;
export type AttachmentDataType = keyof AttachmentResolvableMap; export type AttachmentDataType = keyof AttachmentResolvableMap;
export interface AttachmentData { export interface AttachmentData {
name: string; filename: string;
description: string; description: string;
resolvable: AttachmentResolvable; resolvable: AttachmentResolvable;
type: AttachmentDataType; type: AttachmentDataType;
@ -40,7 +40,7 @@ export class AttachmentBuilder {
* @param data - The partial attachment data. * @param data - The partial attachment data.
*/ */
constructor( constructor(
public data: Partial<AttachmentData> = { name: `${randomBytes?.(8)?.toString('base64url') || 'default'}.jpg` }, public data: Partial<AttachmentData> = { filename: `${randomBytes?.(8)?.toString('base64url') || 'default'}.jpg` },
) {} ) {}
/** /**
@ -51,7 +51,7 @@ export class AttachmentBuilder {
* attachment.setName('example.jpg'); * attachment.setName('example.jpg');
*/ */
setName(name: string): this { setName(name: string): this {
this.data.name = name; this.data.filename = name;
return this; return this;
} }
@ -93,10 +93,10 @@ export class AttachmentBuilder {
setSpoiler(spoiler: boolean): this { setSpoiler(spoiler: boolean): this {
if (spoiler === this.spoiler) return this; if (spoiler === this.spoiler) return this;
if (!spoiler) { if (!spoiler) {
this.data.name = this.data.name!.slice('SPOILER_'.length); this.data.filename = this.data.filename!.slice('SPOILER_'.length);
return this; return this;
} }
this.data.name = `SPOILER_${this.data.name}`; this.data.filename = `SPOILER_${this.data.filename}`;
return this; return this;
} }
@ -104,7 +104,7 @@ export class AttachmentBuilder {
* Gets whether the attachment is a spoiler. * Gets whether the attachment is a spoiler.
*/ */
get spoiler(): boolean { get spoiler(): boolean {
return this.data.name?.startsWith('SPOILER_') ?? false; return this.data.filename?.startsWith('SPOILER_') ?? false;
} }
/** /**
@ -127,11 +127,11 @@ export function resolveAttachment(
if ('id' in resolve) return resolve; if ('id' in resolve) return resolve;
if (resolve instanceof AttachmentBuilder) { if (resolve instanceof AttachmentBuilder) {
const data = resolve.toJSON(); const { filename, description } = resolve.toJSON();
return { filename: data.name, description: data.description }; return { filename, description };
} }
return { filename: resolve.name, description: resolve.description }; return { filename: resolve.filename, description: resolve.description };
} }
/** /**
@ -143,9 +143,9 @@ export async function resolveFiles(resources: (AttachmentBuilder | RawFile | Att
const data = await Promise.all( const data = await Promise.all(
resources.map(async (resource, i) => { resources.map(async (resource, i) => {
if (resource instanceof AttachmentBuilder) { if (resource instanceof AttachmentBuilder) {
const { type, resolvable, name } = resource.toJSON(); const { type, resolvable, filename } = resource.toJSON();
const resolve = await resolveAttachmentData(resolvable, type); const resolve = await resolveAttachmentData(resolvable, type);
return { ...resolve, key: `files[${i}]`, name } as RawFile; return { ...resolve, key: `files[${i}]`, filename } as RawFile;
} }
if (resource instanceof Attachment) { if (resource instanceof Attachment) {
const resolve = await resolveAttachmentData(resource.url, 'url'); const resolve = await resolveAttachmentData(resource.url, 'url');
@ -153,14 +153,14 @@ export async function resolveFiles(resources: (AttachmentBuilder | RawFile | Att
data: resolve.data, data: resolve.data,
contentType: resolve.contentType, contentType: resolve.contentType,
key: `files[${i}]`, key: `files[${i}]`,
name: resource.filename, filename: resource.filename,
} as RawFile; } as RawFile;
} }
return { return {
data: resource.data, data: resource.data,
contentType: resource.contentType, contentType: resource.contentType,
key: `files[${i}]`, key: `files[${i}]`,
name: resource.name, filename: resource.filename,
} as RawFile; } as RawFile;
}), }),
); );

View File

@ -43,6 +43,7 @@ import { LangsHandler } from '../langs/handler';
import type { import type {
ChatInputCommandInteraction, ChatInputCommandInteraction,
ComponentInteraction, ComponentInteraction,
EntryPointInteraction,
MessageCommandInteraction, MessageCommandInteraction,
ModalSubmitInteraction, ModalSubmitInteraction,
UserCommandInteraction, UserCommandInteraction,
@ -320,6 +321,11 @@ export class BaseClient {
const commands = this.commands!.values; const commands = this.commands!.values;
const filter = filterSplit(commands, command => !command.guildId); const filter = filterSplit(commands, command => !command.guildId);
if (this.commands?.entryPoint) {
// @ts-expect-error
filter.expect.push(this.commands.entryPoint);
}
if (!cachePath || (await this.shouldUploadCommands(cachePath))) if (!cachePath || (await this.shouldUploadCommands(cachePath)))
await this.proxy.applications(applicationId).commands.put({ await this.proxy.applications(applicationId).commands.put({
body: filter.expect body: filter.expect
@ -419,6 +425,7 @@ export interface BaseClientOptions {
| MessageCommandInteraction<boolean> | MessageCommandInteraction<boolean>
| ComponentInteraction | ComponentInteraction
| ModalSubmitInteraction | ModalSubmitInteraction
| EntryPointInteraction<boolean>
| When<InferWithPrefix, MessageStructure, never>, | When<InferWithPrefix, MessageStructure, never>,
) => {}; ) => {};
globalMiddlewares?: readonly (keyof RegisteredMiddlewares)[]; globalMiddlewares?: readonly (keyof RegisteredMiddlewares)[];

View File

@ -28,9 +28,9 @@ export class HttpClient extends BaseClient {
for (const [index, file] of files.entries()) { for (const [index, file] of files.entries()) {
const fileKey = file.key ?? `files[${index}]`; const fileKey = file.key ?? `files[${index}]`;
if (isBufferLike(file.data)) { if (isBufferLike(file.data)) {
response.append(fileKey, new Blob([file.data], { type: file.contentType }), file.name); response.append(fileKey, new Blob([file.data], { type: file.contentType }), file.filename);
} else { } else {
response.append(fileKey, new Blob([`${file.data}`], { type: file.contentType }), file.name); response.append(fileKey, new Blob([`${file.data}`], { type: file.contentType }), file.filename);
} }
} }
if (body) { if (body) {

View File

@ -10,6 +10,7 @@ import {
} from '../../types'; } from '../../types';
import type { import type {
ComponentContext, ComponentContext,
EntryPointContext,
MenuCommandContext, MenuCommandContext,
ModalContext, ModalContext,
PermissionStrings, PermissionStrings,
@ -201,7 +202,7 @@ export class BaseCommand {
/** @internal */ /** @internal */
static __runMiddlewares( static __runMiddlewares(
context: CommandContext<{}, never> | ComponentContext | MenuCommandContext<any> | ModalContext, context: CommandContext<{}, never> | ComponentContext | MenuCommandContext<any> | ModalContext | EntryPointContext,
middlewares: (keyof RegisteredMiddlewares)[], middlewares: (keyof RegisteredMiddlewares)[],
global: boolean, global: boolean,
): Promise<{ error?: string; pass?: boolean }> { ): Promise<{ error?: string; pass?: boolean }> {

View File

@ -0,0 +1,71 @@
import { magicImport, type PermissionStrings } from '../../common';
import {
ApplicationCommandType,
type EntryPointCommandHandlerType,
type ApplicationIntegrationType,
type InteractionContextType,
type LocaleString,
} from '../../types';
import type { RegisteredMiddlewares } from '../decorators';
import type { EntryPointContext } from './entrycontext';
import type { ExtraProps, UsingClient } from './shared';
export abstract class EntryPointCommand {
middlewares: (keyof RegisteredMiddlewares)[] = [];
__filePath?: string;
__t?: { name: string | undefined; description: string | undefined };
name!: string;
type = ApplicationCommandType.PrimaryEntryPoint;
nsfw?: boolean;
integrationTypes: ApplicationIntegrationType[] = [];
contexts: InteractionContextType[] = [];
description!: string;
botPermissions?: bigint;
dm?: boolean;
handler!: EntryPointCommandHandlerType;
name_localizations?: Partial<Record<LocaleString, string>>;
description_localizations?: Partial<Record<LocaleString, string>>;
props: ExtraProps = {};
toJSON() {
return {
handler: this.handler,
name: this.name,
type: this.type,
nsfw: this.nsfw,
default_member_permissions: null,
guild_id: null,
description: this.description,
name_localizations: this.name_localizations,
description_localizations: this.description_localizations,
dm_permission: this.dm,
contexts: this.contexts,
integration_types: this.integrationTypes,
};
}
async reload() {
delete require.cache[this.__filePath!];
const __tempCommand = await magicImport(this.__filePath!).then(x => x.default ?? x);
Object.setPrototypeOf(this, __tempCommand.prototype);
}
abstract run?(context: EntryPointContext): any;
onAfterRun?(context: EntryPointContext, error: unknown | undefined): any;
onRunError(context: EntryPointContext<never>, error: unknown): any {
context.client.logger.fatal(`${this.name}.<onRunError>`, context.author.id, error);
}
onMiddlewaresError(context: EntryPointContext<never>, error: string): any {
context.client.logger.fatal(`${this.name}.<onMiddlewaresError>`, context.author.id, error);
}
onBotPermissionsFail(context: EntryPointContext<never>, permissions: PermissionStrings): any {
context.client.logger.fatal(`${this.name}.<onBotPermissionsFail>`, context.author.id, permissions);
}
onInternalError(client: UsingClient, command: EntryPointCommand, error?: unknown): any {
client.logger.fatal(command.name, error);
}
}

View File

@ -0,0 +1,130 @@
import { MessageFlags } from '../../types';
import type { ReturnCache } from '../..';
import type {
InteractionCreateBodyRequest,
InteractionMessageUpdateBodyRequest,
ModalCreateBodyRequest,
UnionToTuple,
When,
} from '../../common';
import type { AllChannels, EntryPointInteraction } from '../../structures';
import { BaseContext } from '../basecontext';
import type { RegisteredMiddlewares } from '../decorators';
import type { CommandMetadata, ExtendContext, GlobalMetadata, UsingClient } from './shared';
import type {
GuildMemberStructure,
GuildStructure,
MessageStructure,
WebhookMessageStructure,
} from '../../client/transformers';
import type { EntryPointCommand } from './entryPoint';
export interface EntryPointContext<M extends keyof RegisteredMiddlewares = never> extends BaseContext, ExtendContext {}
export class EntryPointContext<M extends keyof RegisteredMiddlewares = never> extends BaseContext {
constructor(
readonly client: UsingClient,
readonly interaction: EntryPointInteraction,
readonly shardId: number,
readonly command: EntryPointCommand,
) {
super(client);
}
metadata: CommandMetadata<UnionToTuple<M>> = {} as never;
globalMetadata: GlobalMetadata = {};
get t() {
return this.client.t(this.interaction.locale ?? this.client.langs!.defaultLang ?? 'en-US');
}
get fullCommandName() {
return this.command.name;
}
write<FR extends boolean = false>(
body: InteractionCreateBodyRequest,
fetchReply?: FR,
): Promise<When<FR, WebhookMessageStructure, void | WebhookMessageStructure>> {
return this.interaction.write(body, fetchReply);
}
modal(body: ModalCreateBodyRequest) {
return this.interaction.modal(body);
}
deferReply(ephemeral = false) {
return this.interaction.deferReply(ephemeral ? MessageFlags.Ephemeral : undefined);
}
editResponse(body: InteractionMessageUpdateBodyRequest) {
return this.interaction.editResponse(body);
}
deleteResponse() {
return this.interaction.deleteResponse();
}
editOrReply<FR extends boolean = false>(
body: InteractionCreateBodyRequest | InteractionMessageUpdateBodyRequest,
fetchReply?: FR,
): Promise<When<FR, WebhookMessageStructure | MessageStructure, void | WebhookMessageStructure | MessageStructure>> {
return this.interaction.editOrReply(body as InteractionCreateBodyRequest, fetchReply);
}
fetchResponse() {
return this.interaction.fetchResponse();
}
channel(mode?: 'rest' | 'flow'): Promise<AllChannels>;
channel(mode?: 'cache'): ReturnCache<AllChannels>;
channel(mode: 'cache' | 'rest' | 'flow' = 'cache') {
if (this.interaction?.channel && mode === 'cache')
return this.client.cache.adapter.isAsync ? Promise.resolve(this.interaction.channel) : this.interaction.channel;
return this.client.channels.fetch(this.channelId, mode === 'rest');
}
me(mode?: 'rest' | 'flow'): Promise<GuildMemberStructure>;
me(mode?: 'cache'): ReturnCache<GuildMemberStructure | undefined>;
me(mode: 'cache' | 'rest' | 'flow' = 'cache') {
if (!this.guildId)
return mode === 'cache' ? (this.client.cache.adapter.isAsync ? Promise.resolve() : undefined) : Promise.resolve();
switch (mode) {
case 'cache':
return this.client.cache.members?.get(this.client.botId, this.guildId);
default:
return this.client.members.fetch(this.guildId, this.client.botId, mode === 'rest');
}
}
guild(mode?: 'rest' | 'flow'): Promise<GuildStructure<'cached' | 'api'> | undefined>;
guild(mode?: 'cache'): ReturnCache<GuildStructure<'cached'> | undefined>;
guild(mode: 'cache' | 'rest' | 'flow' = 'cache') {
if (!this.guildId)
return (
mode === 'cache' ? (this.client.cache.adapter.isAsync ? Promise.resolve() : undefined) : Promise.resolve()
) as any;
switch (mode) {
case 'cache':
return this.client.cache.guilds?.get(this.guildId);
default:
return this.client.guilds.fetch(this.guildId, mode === 'rest');
}
}
get guildId() {
return this.interaction.guildId;
}
get channelId() {
return this.interaction.channelId!;
}
get author() {
return this.interaction.user;
}
get member() {
return this.interaction.member;
}
}

View File

@ -1,6 +1,7 @@
import { import {
ApplicationCommandType, ApplicationCommandType,
ApplicationIntegrationType, ApplicationIntegrationType,
type EntryPointCommandHandlerType,
InteractionContextType, InteractionContextType,
PermissionFlagsBits, PermissionFlagsBits,
type LocaleString, type LocaleString,
@ -11,36 +12,28 @@ import type { DefaultLocale, ExtraProps, IgnoreCommand, MiddlewareContext } from
export interface RegisteredMiddlewares {} export interface RegisteredMiddlewares {}
type DeclareOptions = export type CommandDeclareOptions =
| { | DecoratorDeclareOptions
name: string; | (Omit<DecoratorDeclareOptions, 'description'> & {
description: string;
botPermissions?: PermissionStrings | bigint;
defaultMemberPermissions?: PermissionStrings | bigint;
guildId?: string[];
nsfw?: boolean;
integrationTypes?: (keyof typeof ApplicationIntegrationType)[];
contexts?: (keyof typeof InteractionContextType)[];
ignore?: IgnoreCommand;
aliases?: string[];
props?: ExtraProps;
}
| (Omit<
{
name: string;
description: string;
botPermissions?: PermissionStrings | bigint;
defaultMemberPermissions?: PermissionStrings | bigint;
guildId?: string[];
nsfw?: boolean;
integrationTypes?: (keyof typeof ApplicationIntegrationType)[];
contexts?: (keyof typeof InteractionContextType)[];
props?: ExtraProps;
},
'type' | 'description'
> & {
type: ApplicationCommandType.User | ApplicationCommandType.Message; type: ApplicationCommandType.User | ApplicationCommandType.Message;
})
| (Omit<DecoratorDeclareOptions, 'ignore' | 'aliases' | 'guildId'> & {
type: ApplicationCommandType.PrimaryEntryPoint;
handler: EntryPointCommandHandlerType;
}); });
export interface DecoratorDeclareOptions {
name: string;
description: string;
botPermissions?: PermissionStrings | bigint;
defaultMemberPermissions?: PermissionStrings | bigint;
guildId?: string[];
nsfw?: boolean;
integrationTypes?: (keyof typeof ApplicationIntegrationType)[];
contexts?: (keyof typeof InteractionContextType)[];
ignore?: IgnoreCommand;
aliases?: string[];
props?: ExtraProps;
}
export function Locales({ export function Locales({
name: names, name: names,
@ -154,7 +147,7 @@ export function Middlewares(cbs: readonly (keyof RegisteredMiddlewares)[]) {
}; };
} }
export function Declare(declare: DeclareOptions) { export function Declare(declare: CommandDeclareOptions) {
return <T extends { new (...args: any[]): {} }>(target: T) => return <T extends { new (...args: any[]): {} }>(target: T) =>
class extends target { class extends target {
name = declare.name; name = declare.name;
@ -177,6 +170,7 @@ export function Declare(declare: DeclareOptions) {
guildId?: string[]; guildId?: string[];
ignore?: IgnoreCommand; ignore?: IgnoreCommand;
aliases?: string[]; aliases?: string[];
handler?: EntryPointCommandHandlerType;
constructor(...args: any[]) { constructor(...args: any[]) {
super(...args); super(...args);
if ('description' in declare) this.description = declare.description; if ('description' in declare) this.description = declare.description;
@ -184,6 +178,7 @@ export function Declare(declare: DeclareOptions) {
if ('guildId' in declare) this.guildId = declare.guildId; if ('guildId' in declare) this.guildId = declare.guildId;
if ('ignore' in declare) this.ignore = declare.ignore; if ('ignore' in declare) this.ignore = declare.ignore;
if ('aliases' in declare) this.aliases = declare.aliases; if ('aliases' in declare) this.aliases = declare.aliases;
if ('handler' in declare) this.handler = declare.handler;
// check if all properties are valid // check if all properties are valid
} }
}; };

View File

@ -28,6 +28,8 @@ import {
type SeyfertIntegerOption, type SeyfertIntegerOption,
type SeyfertNumberOption, type SeyfertNumberOption,
type SeyfertStringOption, type SeyfertStringOption,
EntryPointContext,
type EntryPointCommand,
} from '.'; } from '.';
import { import {
AutocompleteInteraction, AutocompleteInteraction,
@ -38,6 +40,7 @@ import {
type MessageCommandInteraction, type MessageCommandInteraction,
type UserCommandInteraction, type UserCommandInteraction,
type __InternalReplyFunction, type __InternalReplyFunction,
type EntryPointInteraction,
} from '../structures'; } from '../structures';
import type { PermissionsBitField } from '../structures/extra/Permissions'; import type { PermissionsBitField } from '../structures/extra/Permissions';
import { ComponentContext, ModalContext } from '../components'; import { ComponentContext, ModalContext } from '../components';
@ -132,6 +135,34 @@ export class HandleCommand {
} }
} }
async entryPoint(command: EntryPointCommand, interaction: EntryPointInteraction, context: EntryPointContext) {
if (command.botPermissions && interaction.appPermissions) {
const permissions = this.checkPermissions(interaction.appPermissions, command.botPermissions);
if (permissions) return command.onBotPermissionsFail?.(context, permissions);
}
const resultGlobal = await this.runGlobalMiddlewares(command, context);
if (typeof resultGlobal === 'boolean') return;
const resultMiddle = await this.runMiddlewares(command, context);
if (typeof resultMiddle === 'boolean') return;
try {
try {
await command.run!(context);
await command.onAfterRun?.(context, undefined);
} catch (error) {
await command.onRunError(context, error);
await command.onAfterRun?.(context, error);
}
} catch (error) {
try {
await command.onInternalError(this.client, command, error);
} catch {
// pass
}
}
}
async chatInput( async chatInput(
command: Command | SubCommand, command: Command | SubCommand,
interaction: ChatInputCommandInteraction, interaction: ChatInputCommandInteraction,
@ -214,6 +245,16 @@ export class HandleCommand {
this.contextMenuUser(data.command, data.interaction, data.context); this.contextMenuUser(data.command, data.interaction, data.context);
break; break;
} }
case ApplicationCommandType.PrimaryEntryPoint: {
const command = this.client.commands?.entryPoint;
if (!command?.run) return;
const interaction = BaseInteraction.from(this.client, body, __reply) as EntryPointInteraction;
const context = new EntryPointContext(this.client, interaction, shardId, command);
const extendContext = this.client.options?.context?.(interaction) ?? {};
Object.assign(context, extendContext);
await this.entryPoint(command, interaction, context);
break;
}
case ApplicationCommandType.ChatInput: { case ApplicationCommandType.ChatInput: {
const parentCommand = this.getCommand<Command>(body.data); const parentCommand = this.getCommand<Command>(body.data);
const optionsResolver = this.makeResolver( const optionsResolver = this.makeResolver(
@ -442,7 +483,7 @@ export class HandleCommand {
); );
} }
getCommand<T extends Command | ContextMenuCommand>(data: { getCommand<T extends Command | ContextMenuCommand | EntryPointCommand>(data: {
guild_id?: string; guild_id?: string;
name: string; name: string;
}): T | undefined { }): T | undefined {
@ -487,8 +528,8 @@ export class HandleCommand {
} }
async runGlobalMiddlewares( async runGlobalMiddlewares(
command: Command | ContextMenuCommand | SubCommand, command: Command | ContextMenuCommand | SubCommand | EntryPointCommand,
context: CommandContext<{}, never> | MenuCommandContext<any>, context: CommandContext<{}, never> | MenuCommandContext<any> | EntryPointContext,
) { ) {
try { try {
const resultRunGlobalMiddlewares = await BaseCommand.__runMiddlewares( const resultRunGlobalMiddlewares = await BaseCommand.__runMiddlewares(
@ -513,8 +554,8 @@ export class HandleCommand {
} }
async runMiddlewares( async runMiddlewares(
command: Command | ContextMenuCommand | SubCommand, command: Command | ContextMenuCommand | SubCommand | EntryPointCommand,
context: CommandContext<{}, never> | MenuCommandContext<any>, context: CommandContext<{}, never> | MenuCommandContext<any> | EntryPointContext,
) { ) {
try { try {
const resultRunMiddlewares = await BaseCommand.__runMiddlewares( const resultRunMiddlewares = await BaseCommand.__runMiddlewares(

View File

@ -17,9 +17,11 @@ import { Command, type CommandOption, SubCommand } from './applications/chat';
import { ContextMenuCommand } from './applications/menu'; import { ContextMenuCommand } from './applications/menu';
import type { UsingClient } from './applications/shared'; import type { UsingClient } from './applications/shared';
import { promises } from 'node:fs'; import { promises } from 'node:fs';
import type { EntryPointCommand } from '.';
export class CommandHandler extends BaseHandler { export class CommandHandler extends BaseHandler {
values: (Command | ContextMenuCommand)[] = []; values: (Command | ContextMenuCommand)[] = [];
entryPoint: EntryPointCommand | null = null;
protected filter = (path: string) => path.endsWith('.js') || (!path.endsWith('.d.ts') && path.endsWith('.ts')); protected filter = (path: string) => path.endsWith('.js') || (!path.endsWith('.d.ts') && path.endsWith('.ts'));
@ -290,15 +292,17 @@ export class CommandHandler extends BaseHandler {
} }
} }
this.stablishContextCommandDefaults(commandInstance); this.stablishContextCommandDefaults(commandInstance);
this.values.push(commandInstance);
this.parseLocales(commandInstance); this.parseLocales(commandInstance);
if ('handler' in commandInstance) {
this.entryPoint = commandInstance as EntryPointCommand;
} else this.values.push(commandInstance);
} }
} }
return this.values; return this.values;
} }
parseLocales(command: Command | SubCommand | ContextMenuCommand) { parseLocales(command: InstanceType<HandleableCommand>) {
this.parseGlobalLocales(command); this.parseGlobalLocales(command);
if (command instanceof ContextMenuCommand) { if (command instanceof ContextMenuCommand) {
this.parseContextMenuLocales(command); this.parseContextMenuLocales(command);
@ -322,7 +326,7 @@ export class CommandHandler extends BaseHandler {
return command; return command;
} }
parseGlobalLocales(command: Command | SubCommand | ContextMenuCommand) { parseGlobalLocales(command: InstanceType<HandleableCommand>) {
if (command.__t) { if (command.__t) {
command.name_localizations = {}; command.name_localizations = {};
command.description_localizations = {}; command.description_localizations = {};
@ -488,7 +492,7 @@ export class CommandHandler extends BaseHandler {
return file.default ? [file.default] : undefined; return file.default ? [file.default] : undefined;
} }
onCommand(file: HandleableCommand): Command | SubCommand | ContextMenuCommand | false { onCommand(file: HandleableCommand): InstanceType<HandleableCommand> | false {
return new file(); return new file();
} }
@ -501,6 +505,6 @@ export type FileLoaded<T = null> = {
default?: NulleableCoalising<T, HandleableCommand>; default?: NulleableCoalising<T, HandleableCommand>;
} & Record<string, NulleableCoalising<T, HandleableCommand>>; } & Record<string, NulleableCoalising<T, HandleableCommand>>;
export type HandleableCommand = new () => Command | SubCommand | ContextMenuCommand; export type HandleableCommand = new () => Command | SubCommand | ContextMenuCommand | EntryPointCommand;
export type SeteableCommand = new () => Extract<InstanceType<HandleableCommand>, SubCommand>; export type SeteableCommand = new () => Extract<InstanceType<HandleableCommand>, SubCommand>;
export type HandleableSubCommand = new () => SubCommand; export type HandleableSubCommand = new () => SubCommand;

View File

@ -5,5 +5,7 @@ export * from './applications/chatcontext';
export * from './applications/menu'; export * from './applications/menu';
export * from './applications/menucontext'; export * from './applications/menucontext';
export * from './applications/options'; export * from './applications/options';
export * from './applications/entryPoint';
export * from './applications/entrycontext';
export * from './decorators'; export * from './decorators';
export * from './optionresolver'; export * from './optionresolver';

View File

@ -36,23 +36,28 @@ import {
type MessageFlags, type MessageFlags,
type RESTPostAPIInteractionCallbackJSONBody, type RESTPostAPIInteractionCallbackJSONBody,
type RESTAPIAttachment, type RESTAPIAttachment,
type APIEntryPointCommandInteraction,
type InteractionCallbackData,
type InteractionCallbackResourceActivity,
type RESTPostAPIInteractionCallbackResult,
} from '../types'; } from '../types';
import type { RawFile } from '../api'; import type { RawFile } from '../api';
import { ActionRow, Embed, Modal, PollBuilder, resolveAttachment, resolveFiles } from '../builders'; import { ActionRow, Embed, Modal, PollBuilder, resolveAttachment, resolveFiles } from '../builders';
import type { ContextOptionsResolved, UsingClient } from '../commands'; import type { ContextOptionsResolved, UsingClient } from '../commands';
import type { import {
ObjectToLower, type ObjectToLower,
OmitInsert, type OmitInsert,
ToClass, type ToClass,
When, type When,
ComponentInteractionMessageUpdate, type ComponentInteractionMessageUpdate,
InteractionCreateBodyRequest, type InteractionCreateBodyRequest,
InteractionMessageUpdateBodyRequest, type InteractionMessageUpdateBodyRequest,
MessageCreateBodyRequest, type MessageCreateBodyRequest,
MessageUpdateBodyRequest, type MessageUpdateBodyRequest,
MessageWebhookCreateBodyRequest, type MessageWebhookCreateBodyRequest,
ModalCreateBodyRequest, type ModalCreateBodyRequest,
toCamelCase,
} from '../common'; } from '../common';
import { channelFrom, type AllChannels } from './'; import { channelFrom, type AllChannels } from './';
import { DiscordBase } from './extra/DiscordBase'; import { DiscordBase } from './extra/DiscordBase';
@ -75,6 +80,7 @@ export type ReplyInteractionBody =
type: InteractionResponseType.ChannelMessageWithSource | InteractionResponseType.UpdateMessage; type: InteractionResponseType.ChannelMessageWithSource | InteractionResponseType.UpdateMessage;
data: InteractionCreateBodyRequest | InteractionMessageUpdateBodyRequest | ComponentInteractionMessageUpdate; data: InteractionCreateBodyRequest | InteractionMessageUpdateBodyRequest | ComponentInteractionMessageUpdate;
} }
| { type: InteractionResponseType.LaunchActivity }
| Exclude<RESTPostAPIInteractionCallbackJSONBody, APIInteractionResponsePong>; | Exclude<RESTPostAPIInteractionCallbackJSONBody, APIInteractionResponsePong>;
export type __InternalReplyFunction = (_: { body: APIInteractionResponse; files?: RawFile[] }) => Promise<any>; export type __InternalReplyFunction = (_: { body: APIInteractionResponse; files?: RawFile[] }) => Promise<any>;
@ -161,6 +167,8 @@ export class BaseInteraction<
: [], : [],
}, },
}; };
case InteractionResponseType.LaunchActivity:
return body;
default: default:
return body; return body;
} }
@ -168,6 +176,7 @@ export class BaseInteraction<
static transformBody<T>( static transformBody<T>(
body: body:
| InteractionCreateBodyRequest
| InteractionMessageUpdateBodyRequest | InteractionMessageUpdateBodyRequest
| MessageUpdateBodyRequest | MessageUpdateBodyRequest
| MessageCreateBodyRequest | MessageCreateBodyRequest
@ -192,9 +201,9 @@ export class BaseInteraction<
...resolveAttachment(x), ...resolveAttachment(x),
})) ?? undefined; })) ?? undefined;
} else if (files?.length) { } else if (files?.length) {
payload.attachments = files?.map((x, id) => ({ payload.attachments = files?.map(({ filename }, id) => ({
id, id,
filename: x.name, filename,
})) as RESTAPIAttachment[]; })) as RESTAPIAttachment[];
} }
return payload as T; return payload as T;
@ -279,6 +288,10 @@ export class BaseInteraction<
return false; return false;
} }
isEntryPoint(): this is EntryPointInteraction {
return false;
}
static from(client: UsingClient, gateway: GatewayInteractionCreateDispatchData, __reply?: __InternalReplyFunction) { static from(client: UsingClient, gateway: GatewayInteractionCreateDispatchData, __reply?: __InternalReplyFunction) {
switch (gateway.type) { switch (gateway.type) {
case InteractionType.ApplicationCommandAutocomplete: case InteractionType.ApplicationCommandAutocomplete:
@ -296,6 +309,8 @@ export class BaseInteraction<
return new UserCommandInteraction(client, gateway as APIUserApplicationCommandInteraction, __reply); return new UserCommandInteraction(client, gateway as APIUserApplicationCommandInteraction, __reply);
case ApplicationCommandType.Message: case ApplicationCommandType.Message:
return new MessageCommandInteraction(client, gateway as APIMessageApplicationCommandInteraction, __reply); return new MessageCommandInteraction(client, gateway as APIMessageApplicationCommandInteraction, __reply);
case ApplicationCommandType.PrimaryEntryPoint:
return new EntryPointInteraction(client, gateway as APIEntryPointCommandInteraction, __reply);
} }
// biome-ignore lint/suspicious/noFallthroughSwitchClause: bad interaction between biome and ts-server // biome-ignore lint/suspicious/noFallthroughSwitchClause: bad interaction between biome and ts-server
case InteractionType.MessageComponent: case InteractionType.MessageComponent:
@ -345,6 +360,7 @@ export type AllInteractions =
| ComponentInteraction | ComponentInteraction
| SelectMenuInteraction | SelectMenuInteraction
| ModalSubmitInteraction | ModalSubmitInteraction
| EntryPointInteraction
| BaseInteraction; | BaseInteraction;
export interface AutocompleteInteraction export interface AutocompleteInteraction
@ -478,6 +494,64 @@ export class ApplicationCommandInteraction<
} }
} }
/**
* Seyfert don't support activities, so this interaction is blank
*/
export class EntryPointInteraction<FromGuild extends boolean = boolean> extends ApplicationCommandInteraction<
FromGuild,
APIEntryPointCommandInteraction
> {
async withReponse(data?: InteractionCreateBodyRequest) {
let body = { type: InteractionResponseType.LaunchActivity } as const;
if (data) {
let { files, ...rest } = data;
files = files ? await resolveFiles(files) : undefined;
body = BaseInteraction.transformBody(rest, files, this.client);
}
const response = (await this.client.proxy
.interactions(this.id)(this.token)
.callback.post({
body,
query: { with_response: true },
})) as RESTPostAPIInteractionCallbackResult;
const result: Partial<EntryPointWithResponseResult> = {
interaction: toCamelCase(response.interaction),
};
if (response.resource) {
if (response.resource.type !== InteractionResponseType.LaunchActivity) {
result.resource = {
type: response.resource.type,
message: Transformers.WebhookMessage(this.client, response.resource.message as any, this.id, this.token),
};
} else {
result.resource = {
type: response.resource.type,
activityInstance: response.resource.activity_instance!,
};
}
}
return result as EntryPointWithResponseResult;
}
isEntryPoint(): this is EntryPointInteraction {
return true;
}
}
export interface EntryPointWithResponseResult {
interaction: ObjectToLower<InteractionCallbackData>;
resource?:
| { type: InteractionResponseType.LaunchActivity; activityInstance: InteractionCallbackResourceActivity }
| {
type: Exclude<InteractionResponseType, InteractionResponseType.LaunchActivity>;
message: WebhookMessageStructure;
};
}
export interface ComponentInteraction export interface ComponentInteraction
extends ObjectToLower< extends ObjectToLower<
Omit< Omit<

View File

@ -294,9 +294,9 @@ export class MessagesMethods extends DiscordBase {
...resolveAttachment(x), ...resolveAttachment(x),
})) ?? undefined; })) ?? undefined;
} else if (files?.length) { } else if (files?.length) {
payload.attachments = files?.map((x, id) => ({ payload.attachments = files?.map(({ filename }, id) => ({
id, id,
filename: x.name, filename,
})) as RESTAPIAttachment[]; })) as RESTAPIAttachment[];
} }
return payload as T; return payload as T;

View File

@ -1,5 +1,9 @@
import type { APIInteractionDataResolved } from '../../index'; import type { APIInteractionDataResolved } from '../../index';
import type { APIApplicationCommandInteractionWrapper, ApplicationCommandType } from '../applicationCommands'; import type {
APIApplicationCommandInteractionWrapper,
APIEntryPointInteractionData,
ApplicationCommandType,
} from '../applicationCommands';
import type { APIDMInteractionWrapper, APIGuildInteractionWrapper } from '../base'; import type { APIDMInteractionWrapper, APIGuildInteractionWrapper } from '../base';
import type { import type {
APIApplicationCommandAttachmentOption, APIApplicationCommandAttachmentOption,
@ -128,3 +132,9 @@ export type APIChatInputApplicationCommandDMInteraction =
*/ */
export type APIChatInputApplicationCommandGuildInteraction = export type APIChatInputApplicationCommandGuildInteraction =
APIGuildInteractionWrapper<APIChatInputApplicationCommandInteraction>; APIGuildInteractionWrapper<APIChatInputApplicationCommandInteraction>;
/**
* Documentation goes brrrrrr
* @unstable
*/
export type APIEntryPointCommandInteraction = APIApplicationCommandInteractionWrapper<APIEntryPointInteractionData>;

View File

@ -5,6 +5,7 @@ import type {
APIChatInputApplicationCommandGuildInteraction, APIChatInputApplicationCommandGuildInteraction,
APIChatInputApplicationCommandInteraction, APIChatInputApplicationCommandInteraction,
APIChatInputApplicationCommandInteractionData, APIChatInputApplicationCommandInteractionData,
APIEntryPointCommandInteraction,
} from './_applicationCommands/chatInput'; } from './_applicationCommands/chatInput';
import type { import type {
APIContextMenuDMInteraction, APIContextMenuDMInteraction,
@ -12,6 +13,7 @@ import type {
APIContextMenuInteraction, APIContextMenuInteraction,
APIContextMenuInteractionData, APIContextMenuInteractionData,
} from './_applicationCommands/contextMenu'; } from './_applicationCommands/contextMenu';
import type { APIBaseApplicationCommandInteractionData } from './_applicationCommands/internals';
import type { APIBaseInteraction } from './base'; import type { APIBaseInteraction } from './base';
import type { InteractionType } from './responses'; import type { InteractionType } from './responses';
@ -92,28 +94,58 @@ export interface APIApplicationCommand {
/** /**
* Installation context(s) where the command is available, only for globally-scoped commands. Defaults to `GUILD_INSTALL ([0])` * Installation context(s) where the command is available, only for globally-scoped commands. Defaults to `GUILD_INSTALL ([0])`
* *
* @unstable
*/ */
integration_types?: ApplicationIntegrationType[]; integration_types?: ApplicationIntegrationType[];
/** /**
* Interaction context(s) where the command can be used, only for globally-scoped commands. By default, all interaction context types included for new commands `[0,1,2]`. * Interaction context(s) where the command can be used, only for globally-scoped commands. By default, all interaction context types included for new commands `[0,1,2]`.
* *
* @unstable
*/ */
contexts?: InteractionContextType[] | null; contexts?: InteractionContextType[] | null;
/** /**
* Autoincrementing version identifier updated during substantial record changes * Autoincrementing version identifier updated during substantial record changes
*/ */
version: Snowflake; version: Snowflake;
/**
* Determines whether the interaction is handled by the app's interactions handler or by Discord
*/
handler?: EntryPointCommandHandlerType;
} }
/** /**
* https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types * https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types
*/ */
export enum ApplicationCommandType { export enum ApplicationCommandType {
/**
* Slash commands; a text-based command that shows up when a user types /
*/
ChatInput = 1, ChatInput = 1,
/**
* A UI-based command that shows up when you right click or tap on a user
*/
User, User,
/**
* A UI-based command that shows up when you right click or tap on a message
*/
Message, Message,
/**
* A UI-based command that represents the primary way to invoke an app's Activity
*/
PrimaryEntryPoint,
}
/**
* https://discord.com/developers/docs/interactions/application-commands#application-command-object-entry-point-command-handler-types
*/
export enum EntryPointCommandHandlerType {
/**
* The app handles the interaction using an interaction token
*/
AppHandler = 1,
/**
* Discord handles the interaction by launching an Activity and sending a follow-up message without coordinating with the app
*/
DiscordLaunchActivity,
} }
/** /**
@ -148,12 +180,20 @@ export enum InteractionContextType {
PrivateChannel = 2, PrivateChannel = 2,
} }
/**
* Documentation goes brrrrrr
* @unstable
*/
export interface APIEntryPointInteractionData
extends Omit<APIBaseApplicationCommandInteractionData<ApplicationCommandType.PrimaryEntryPoint>, 'guild_id'> {}
/** /**
* https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-data * https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-data
*/ */
export type APIApplicationCommandInteractionData = export type APIApplicationCommandInteractionData =
| APIChatInputApplicationCommandInteractionData | APIChatInputApplicationCommandInteractionData
| APIContextMenuInteractionData; | APIContextMenuInteractionData
| APIEntryPointInteractionData;
/** /**
* https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object * https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object
@ -170,7 +210,10 @@ export type APIApplicationCommandInteractionWrapper<Data extends APIApplicationC
/** /**
* https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object * https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object
*/ */
export type APIApplicationCommandInteraction = APIChatInputApplicationCommandInteraction | APIContextMenuInteraction; export type APIApplicationCommandInteraction =
| APIChatInputApplicationCommandInteraction
| APIContextMenuInteraction
| APIEntryPointCommandInteraction;
/** /**
* https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object * https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object

View File

@ -1,3 +1,4 @@
import type { MakeRequired } from '../../../common';
import type { RESTPostAPIWebhookWithTokenJSONBody } from '../../index'; import type { RESTPostAPIWebhookWithTokenJSONBody } from '../../index';
import type { APIActionRowComponent, APIModalActionRowComponent } from '../channel'; import type { APIActionRowComponent, APIModalActionRowComponent } from '../channel';
import type { MessageFlags } from '../index'; import type { MessageFlags } from '../index';
@ -25,6 +26,7 @@ export type APIInteractionResponse =
| APIInteractionResponsePong | APIInteractionResponsePong
| APIInteractionResponseUpdateMessage | APIInteractionResponseUpdateMessage
| APIModalInteractionResponse | APIModalInteractionResponse
| APIInteractionResponseLaunchActivity
| APIPremiumRequiredInteractionResponse; | APIPremiumRequiredInteractionResponse;
export interface APIInteractionResponsePong { export interface APIInteractionResponsePong {
@ -64,6 +66,10 @@ export interface APIInteractionResponseUpdateMessage {
data?: APIInteractionResponseCallbackData; data?: APIInteractionResponseCallbackData;
} }
export interface APIInteractionResponseLaunchActivity {
type: InteractionResponseType.LaunchActivity;
}
/** /**
* https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-interaction-callback-type * https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-interaction-callback-type
*/ */
@ -102,6 +108,10 @@ export enum InteractionResponseType {
* @deprecated See https://discord.com/developers/docs/change-log#premium-apps-new-premium-button-style-deep-linking-url-schemes * @deprecated See https://discord.com/developers/docs/change-log#premium-apps-new-premium-button-style-deep-linking-url-schemes
*/ */
PremiumRequired, PremiumRequired,
/**
* Launch the Activity associated with the app. Only available for apps with Activities enabled
*/
LaunchActivity = 12,
} }
/** /**
@ -133,3 +143,71 @@ export interface APIModalInteractionResponseCallbackData {
*/ */
components: APIActionRowComponent<APIModalActionRowComponent>[]; components: APIActionRowComponent<APIModalActionRowComponent>[];
} }
/**
* https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-callback-interaction-callback-object
*/
export interface InteractionCallbackData<T extends InteractionType = InteractionType> {
id: string;
type: T;
/**
* Instance ID of the Activity if one was launched or joined
*/
activity_instance_id?: string;
/**
* ID of the message that was created by the interaction
*/
response_message_id?: string;
/**
* Whether or not the message is in a loading state
*/
response_message_loading?: boolean;
/**
* Whether or not the response message was ephemeral
*/
response_message_ephemeral?: boolean;
}
/**
* https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-callback-interaction-callback-resource-object
*/
export interface InteractionCallbackResourceActivity {
/**
* Instance ID of the Activity if one was launched or joined.
*/
id: string;
}
/**
* https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-callback-interaction-callback-activity-instance-resource
*/
export interface InteractionCallbackResource<T extends InteractionResponseType = InteractionResponseType> {
type: T;
/**
* Represents the Activity launched by this interaction.
*/
activity_instance?: InteractionCallbackResourceActivity;
/**
* Message created by the interaction.
*/
message?: Omit<RESTPostAPIWebhookWithTokenJSONBody, 'avatar_url' | 'username'> & { flags?: MessageFlags };
}
/**
* https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-callback-interaction-callback-response-object
*/
export interface InteractionCallbackResponse {
interaction: InteractionCallbackData;
resource?: InteractionCallbackResource;
}
/**
* https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-callback-interaction-callback-response-object
*/
export type APIInteractionCallbackLaunchActivity = InteractionCallbackResponse & {
resource?: Omit<MakeRequired<InteractionCallbackResource, 'activity_instance'>, 'message'>;
};
export type APIInteractionCallbackMessage = InteractionCallbackResponse & {
resource?: Omit<MakeRequired<InteractionCallbackResource, 'message'>, 'activity_instance'>;
};

View File

@ -22,6 +22,7 @@ import type {
ThreadChannelType, ThreadChannelType,
APIThreadMember, APIThreadMember,
APIThreadList, APIThreadList,
APIAttachment,
} from '../payloads'; } from '../payloads';
import type { AddUndefinedToPossiblyUndefinedPropertiesOfInterface, StrictPartial } from '../utils'; import type { AddUndefinedToPossiblyUndefinedPropertiesOfInterface, StrictPartial } from '../utils';
import type { RESTAPIPollCreate } from './poll'; import type { RESTAPIPollCreate } from './poll';
@ -248,22 +249,11 @@ export type APIMessageReferenceSend = AddUndefinedToPossiblyUndefinedPropertiesO
}; };
/** /**
* https://discord.com/developers/docs/resources/channel#attachment-object * https://discord.com/developers/docs/resources/message#attachment-object
*/ */
export interface RESTAPIAttachment { export type RESTAPIAttachment = Partial<
/** Pick<APIAttachment, 'description' | 'duration_secs' | 'filename' | 'title' | 'waveform'>
* Attachment id or a number that matches `n` in `files[n]` >;
*/
id: Snowflake | number;
/**
* Name of the file
*/
filename?: string | undefined;
/**
* Description of the file
*/
description?: string | undefined;
}
/** /**
* https://discord.com/developers/docs/resources/channel#create-message * https://discord.com/developers/docs/resources/channel#create-message
@ -444,7 +434,7 @@ export interface RESTPatchAPIChannelMessageJSONBody {
* *
* Starting with API v10, the `attachments` array must contain all attachments that should be present after edit, including **retained and new** attachments provided in the request body. * Starting with API v10, the `attachments` array must contain all attachments that should be present after edit, including **retained and new** attachments provided in the request body.
* *
* See https://discord.com/developers/docs/resources/channel#attachment-object * See https://discord.com/developers/docs/resources/message#attachment-object
*/ */
attachments?: RESTAPIAttachment[] | undefined; attachments?: RESTAPIAttachment[] | undefined;
/** /**

View File

@ -2,9 +2,12 @@ import type {
APIApplicationCommand, APIApplicationCommand,
APIApplicationCommandPermission, APIApplicationCommandPermission,
APIGuildApplicationCommandPermissions, APIGuildApplicationCommandPermissions,
APIInteractionCallbackLaunchActivity,
APIInteractionCallbackMessage,
APIInteractionResponse, APIInteractionResponse,
APIInteractionResponseCallbackData, APIInteractionResponseCallbackData,
ApplicationCommandType, ApplicationCommandType,
EntryPointCommandHandlerType,
} from '../payloads'; } from '../payloads';
import type { AddUndefinedToPossiblyUndefinedPropertiesOfInterface, NonNullableFields, StrictPartial } from '../utils'; import type { AddUndefinedToPossiblyUndefinedPropertiesOfInterface, NonNullableFields, StrictPartial } from '../utils';
import type { import type {
@ -53,6 +56,7 @@ type RESTPostAPIBaseApplicationCommandsJSONBody = AddUndefinedToPossiblyUndefine
| 'name_localized' | 'name_localized'
| 'type' | 'type'
| 'version' | 'version'
| 'handler'
> & > &
Partial< Partial<
NonNullableFields<Pick<APIApplicationCommand, 'contexts'>> & NonNullableFields<Pick<APIApplicationCommand, 'contexts'>> &
@ -75,12 +79,22 @@ export interface RESTPostAPIContextMenuApplicationCommandsJSONBody extends RESTP
type: ApplicationCommandType.Message | ApplicationCommandType.User; type: ApplicationCommandType.Message | ApplicationCommandType.User;
} }
/**
* https://discord.com/developers/docs/interactions/application-commands#create-global-application-command
*/
export interface RESTPostAPIEntryPointApplicationCommandsJSONBody extends RESTPostAPIBaseApplicationCommandsJSONBody {
type: ApplicationCommandType.PrimaryEntryPoint;
description: string;
handler: EntryPointCommandHandlerType;
}
/** /**
* https://discord.com/developers/docs/interactions/application-commands#create-global-application-command * https://discord.com/developers/docs/interactions/application-commands#create-global-application-command
*/ */
export type RESTPostAPIApplicationCommandsJSONBody = export type RESTPostAPIApplicationCommandsJSONBody =
| RESTPostAPIChatInputApplicationCommandsJSONBody | RESTPostAPIChatInputApplicationCommandsJSONBody
| RESTPostAPIContextMenuApplicationCommandsJSONBody; | RESTPostAPIContextMenuApplicationCommandsJSONBody
| RESTPostAPIEntryPointApplicationCommandsJSONBody;
/** /**
* https://discord.com/developers/docs/interactions/application-commands#create-global-application-command * https://discord.com/developers/docs/interactions/application-commands#create-global-application-command
@ -112,15 +126,17 @@ export type RESTPutAPIApplicationCommandsResult = APIApplicationCommand[];
*/ */
export type RESTGetAPIApplicationGuildCommandsQuery = RESTGetAPIApplicationCommandsQuery; export type RESTGetAPIApplicationGuildCommandsQuery = RESTGetAPIApplicationCommandsQuery;
/** export type RESTAPIApplicationGuildCommand = Omit<APIApplicationCommand, 'dm_permission' | 'handler'>;
* https://discord.com/developers/docs/interactions/application-commands#get-guild-application-commands
*/
export type RESTGetAPIApplicationGuildCommandsResult = Omit<APIApplicationCommand, 'dm_permission'>[];
/** /**
* https://discord.com/developers/docs/interactions/application-commands#get-guild-application-commands * https://discord.com/developers/docs/interactions/application-commands#get-guild-application-commands
*/ */
export type RESTGetAPIApplicationGuildCommandResult = Omit<APIApplicationCommand, 'dm_permission'>; export type RESTGetAPIApplicationGuildCommandsResult = RESTAPIApplicationGuildCommand[];
/**
* https://discord.com/developers/docs/interactions/application-commands#get-guild-application-commands
*/
export type RESTGetAPIApplicationGuildCommandResult = RESTAPIApplicationGuildCommand;
/** /**
* https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command * https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command
@ -132,7 +148,7 @@ export type RESTPostAPIApplicationGuildCommandsJSONBody =
/** /**
* https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command * https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command
*/ */
export type RESTPostAPIApplicationGuildCommandsResult = Omit<APIApplicationCommand, 'dm_permission'>; export type RESTPostAPIApplicationGuildCommandsResult = RESTAPIApplicationGuildCommand;
/** /**
* https://discord.com/developers/docs/interactions/application-commands#edit-guild-application-command * https://discord.com/developers/docs/interactions/application-commands#edit-guild-application-command
@ -145,7 +161,7 @@ export type RESTPatchAPIApplicationGuildCommandJSONBody = StrictPartial<
/** /**
* https://discord.com/developers/docs/interactions/application-commands#edit-guild-application-command * https://discord.com/developers/docs/interactions/application-commands#edit-guild-application-command
*/ */
export type RESTPatchAPIApplicationGuildCommandResult = Omit<APIApplicationCommand, 'dm_permission'>; export type RESTPatchAPIApplicationGuildCommandResult = RESTAPIApplicationGuildCommand;
/** /**
* https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-guild-application-commands * https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-guild-application-commands
@ -160,13 +176,28 @@ export type RESTPutAPIApplicationGuildCommandsJSONBody = (
/** /**
* https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-guild-application-commands * https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-guild-application-commands
*/ */
export type RESTPutAPIApplicationGuildCommandsResult = Omit<APIApplicationCommand, 'dm_permission'>[]; export type RESTPutAPIApplicationGuildCommandsResult = RESTAPIApplicationGuildCommand[];
/** /**
* https://discord.com/developers/docs/interactions/receiving-and-responding#create-interaction-response * https://discord.com/developers/docs/interactions/receiving-and-responding#create-interaction-response
*/ */
export type RESTPostAPIInteractionCallbackJSONBody = APIInteractionResponse; export type RESTPostAPIInteractionCallbackJSONBody = APIInteractionResponse;
/**
* https://discord.com/developers/docs/interactions/receiving-and-responding#create-interaction-response-query-string-params
*/
export type RESTPostAPIInteractionCallbackQuery = {
/**
* Whether to include a RESTPostAPIInteractionCallbackResult as the response instead of a 204.
*/
with_response?: boolean;
};
/**
* https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-callback
*/
export type RESTPostAPIInteractionCallbackResult = APIInteractionCallbackLaunchActivity | APIInteractionCallbackMessage;
/** /**
* https://discord.com/developers/docs/interactions/receiving-and-responding#create-interaction-response * https://discord.com/developers/docs/interactions/receiving-and-responding#create-interaction-response
*/ */

View File

@ -264,7 +264,7 @@ export type RESTPatchAPIWebhookWithTokenMessageJSONBody = AddUndefinedToPossibly
* *
* Starting with API v10, the `attachments` array must contain all attachments that should be present after edit, including **retained and new** attachments provided in the request body. * Starting with API v10, the `attachments` array must contain all attachments that should be present after edit, including **retained and new** attachments provided in the request body.
* *
* See https://discord.com/developers/docs/resources/channel#attachment-object * See https://discord.com/developers/docs/resources/message#attachment-object
*/ */
attachments?: RESTAPIAttachment[] | undefined; attachments?: RESTAPIAttachment[] | undefined;
}; };