Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/test-bot/src/app/commands/(developer)/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import {
type ChatInputCommand,
type MessageCommand,
Logger,
CommandMetadata,
} from 'commandkit';

export const command: CommandData = {
name: 'server',
description: 'server command',
guilds: [process.env.DEV_GUILD_ID!],
};

export const metadata: CommandMetadata = {
aliases: ['s', 'serv'],
guilds: [process.env.DEV_GUILD_ID!],
};

export const chatInput: ChatInputCommand = async (ctx) => {
Expand Down
8 changes: 7 additions & 1 deletion apps/test-bot/src/app/commands/(general)/componentsTest.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
ChatInputCommand,
CommandData,
CommandMetadataFunction,
Container,
MessageCommand,
TextDisplay,
Expand All @@ -10,7 +11,12 @@ import { Colors, MessageFlags } from 'discord.js';
export const command: CommandData = {
name: 'components-test',
description: 'Test components v2 again',
aliases: ['ct'],
};

export const generateMetadata: CommandMetadataFunction = () => {
return {
aliases: ['ct'],
};
};

export const chatInput: ChatInputCommand = async (ctx) => {
Expand Down
6 changes: 6 additions & 0 deletions apps/test-bot/src/app/commands/(general)/gallery.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
ChatInputCommand,
CommandData,
CommandMetadata,
MediaGallery,
MediaGalleryItem,
TextDisplay,
Expand All @@ -12,6 +13,11 @@ export const command: CommandData = {
description: 'Test components v2 gallery',
};

export const metadata: CommandMetadata = {
botPermissions: ['BanMembers'],
userPermissions: ['Administrator'],
};

const mediaItems: string[] = Array.from(
{
length: 6,
Expand Down
7 changes: 4 additions & 3 deletions apps/test-bot/src/events/messageCreate/01-log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
stopEvents,
isErrorType,
CommandKitErrorCodes,
Logger,
} from 'commandkit';

const handler: EventHandler<'messageCreate'> = (message) => {
Expand All @@ -12,15 +13,15 @@ const handler: EventHandler<'messageCreate'> = (message) => {
stopEvents(); // conditionally stop the event chain
} catch (error) {
if (isErrorType(error, CommandKitErrorCodes.StopEvents)) {
console.log('Stopping event chain');
Logger.log('Stopping event chain');
// if stopEvents() is called in the try block, throw it so CommandKit can stop the event chain
throw error;
}

console.log('Not stopping event chain');
Logger.log('Not stopping event chain');
// this means that the code threw the error, and stopEvents() was not called
// the rest of the event handlers will be executed as normal
console.error(error);
Logger.error(error);
}
};

Expand Down
7 changes: 4 additions & 3 deletions apps/test-bot/src/events/messageCreate/02-log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
stopEvents,
isErrorType,
CommandKitErrorCodes,
Logger,
} from 'commandkit';

const handler: EventHandler<'messageCreate'> = (message) => {
Expand All @@ -14,15 +15,15 @@ const handler: EventHandler<'messageCreate'> = (message) => {
// stopEvents(); // conditionally stop the event chain
} catch (error) {
if (isErrorType(error, CommandKitErrorCodes.StopEvents)) {
console.log('Stopping event chain');
Logger.log('Stopping event chain');
// if stopEvents() is called in the try block, throw it so CommandKit can stop the event chain
throw error;
}

console.log('Not stopping event chain');
Logger.log('Not stopping event chain');
// this means that the code threw the error, and stopEvents() was not called
// the rest of the event handlers will be executed as normal
console.error(error);
Logger.error(error);
}
};

Expand Down
132 changes: 116 additions & 16 deletions packages/commandkit/src/app/handlers/AppCommandHandler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
ApplicationCommandType,
AutocompleteInteraction,
Awaitable,
Collection,
Expand All @@ -13,7 +12,11 @@ import {
import type { CommandKit } from '../../commandkit';
import { AsyncFunction, GenericFunction } from '../../context/async-context';
import { Logger } from '../../logger/Logger';
import type { CommandData } from '../../types';
import type {
CommandData,
CommandMetadata,
CommandMetadataFunction,
} from '../../types';
import colors from '../../utils/colors';
import { COMMANDKIT_IS_DEV } from '../../utils/constants';
import { CommandKitErrorCodes, isErrorType } from '../../utils/error-codes';
Expand All @@ -25,6 +28,14 @@ import { MessageCommandParser } from '../commands/MessageCommandParser';
import { CommandRegistrar } from '../register/CommandRegistrar';
import { Command, Middleware } from '../router';
import { getConfig } from '../../config/config';
import { beforeExecute, middlewareId } from '../middlewares/permissions';

const KNOWN_NON_HANDLER_KEYS = [
'command',
'generateMetadata',
'metadata',
'aiConfig',
];

/**
* Function type for wrapping command execution with custom logic.
Expand All @@ -38,6 +49,8 @@ export type RunCommand = <T extends AsyncFunction>(fn: T) => T;
*/
export interface AppCommandNative {
command: CommandData | Record<string, any>;
generateMetadata?: CommandMetadataFunction;
metadata?: CommandMetadata;
chatInput?: (ctx: Context) => Awaitable<unknown>;
autocomplete?: (ctx: Context) => Awaitable<unknown>;
message?: (ctx: Context) => Awaitable<unknown>;
Expand Down Expand Up @@ -73,8 +86,8 @@ interface AppCommandMiddleware {
*/
export interface LoadedCommand {
command: Command;
metadata: CommandMetadata;
data: AppCommand;
guilds?: string[];
}

/**
Expand Down Expand Up @@ -129,8 +142,22 @@ const commandDataSchema = {
userContextMenu: (c: unknown) => typeof c === 'function',
};

/**
* @private
* @internal
*/
export type CommandDataSchema = typeof commandDataSchema;

/**
* @private
* @internal
*/
export type CommandDataSchemaKey = keyof CommandDataSchema;

/**
* @private
* @internal
*/
export type CommandDataSchemaValue = CommandDataSchema[CommandDataSchemaKey];

/**
Expand Down Expand Up @@ -444,8 +471,8 @@ export class AppCommandHandler {

if (
source.guildId &&
loadedCommand.guilds?.length &&
!loadedCommand.guilds.includes(source.guildId!)
loadedCommand.metadata?.guilds?.length &&
!loadedCommand.metadata?.guilds.includes(source.guildId!)
) {
return null;
}
Expand Down Expand Up @@ -499,8 +526,8 @@ export class AppCommandHandler {
(source instanceof CommandInteraction ||
source instanceof AutocompleteInteraction) &&
source.guildId &&
loadedCommand.guilds?.length &&
!loadedCommand.guilds.includes(source.guildId)
loadedCommand.metadata?.guilds?.length &&
!loadedCommand.metadata?.guilds.includes(source.guildId)
) {
return null;
}
Expand All @@ -516,6 +543,24 @@ export class AppCommandHandler {
}
}

if (!getConfig().disablePermissionsMiddleware) {
middlewares.push({
data: {
// @ts-ignore
beforeExecute,
},
middleware: {
command: null,
global: true,
id: middlewareId,
name: 'permissions',
parentPath: '',
path: '',
relativePath: '',
},
});
}

// No middleware for subcommands since they inherit from parent command
return {
command: loadedCommand,
Expand All @@ -536,7 +581,7 @@ export class AppCommandHandler {
}

// Check aliases for prefix commands
const aliases = loadedCommand.data.command.aliases;
const aliases = loadedCommand.data.metadata?.aliases;
if (aliases && Array.isArray(aliases) && aliases.includes(name)) {
return loadedCommand;
}
Expand Down Expand Up @@ -640,7 +685,7 @@ export class AppCommandHandler {
(v) => v.data.command.name,
);
const aliases = Array.from(this.loadedCommands.values()).flatMap(
(v) => v.data.command.aliases || [],
(v) => v.metadata.aliases || [],
);

const allNames = [...commandNames, ...aliases];
Expand Down Expand Up @@ -698,6 +743,12 @@ export class AppCommandHandler {
if (command.path === null) {
this.loadedCommands.set(id, {
command,
metadata: {
guilds: [],
aliases: [],
userPermissions: [],
botPermissions: [],
},
data: {
command: {
name: command.name,
Expand All @@ -719,6 +770,22 @@ export class AppCommandHandler {
);
}

const metadataFunc = commandFileData.generateMetadata;
const metadataObj = commandFileData.metadata;

if (metadataFunc && metadataObj) {
throw new Error(
'A command may only export either `generateMetadata` or `metadata`, not both',
);
}

const metadata = (metadataFunc ? await metadataFunc() : metadataObj) ?? {
aliases: [],
guilds: [],
userPermissions: [],
botPermissions: [],
};

// Apply the specified logic for name and description
const commandName = commandFileData.command.name || command.name;
const commandDescription =
Expand All @@ -729,7 +796,6 @@ export class AppCommandHandler {
...commandFileData.command,
name: commandName,
description: commandDescription,
aliases: commandFileData.command.aliases,
} as CommandData;

let handlerCount = 0;
Expand All @@ -747,7 +813,7 @@ export class AppCommandHandler {
);
}

if (key !== 'command') {
if (!KNOWN_NON_HANDLER_KEYS.includes(key)) {
// command file includes a handler function (chatInput, message, etc)
handlerCount++;
}
Expand All @@ -770,19 +836,53 @@ export class AppCommandHandler {
}
});

const commandJson =
'toJSON' in lastUpdated && typeof lastUpdated.toJSON === 'function'
? lastUpdated.toJSON()
: lastUpdated;

if ('guilds' in commandJson || 'aliases' in commandJson) {
Logger.warn(
`Command \`${command.name}\` uses deprecated metadata properties. Please update to use the new \`metadata\` object or \`generateMetadata\` function.`,
);
}

this.loadedCommands.set(id, {
command,
guilds: commandFileData.command.guilds,
metadata: {
guilds: commandJson.guilds,
aliases: commandJson.aliases,
...metadata,
},
data: {
...commandFileData,
command:
'toJSON' in lastUpdated && typeof lastUpdated.toJSON === 'function'
? lastUpdated.toJSON()
: lastUpdated,
metadata: {
guilds: commandJson.guilds,
aliases: commandJson.aliases,
...metadata,
},
command: commandJson,
},
});
} catch (error) {
Logger.error(`Failed to load command ${command.name} (${id})`, error);
}
}

/**
* Gets the metadata for a command.
* @param command - The command name to get metadata for
* @returns The command metadata or null if not found
*/
public getMetadataFor(command: string): CommandMetadata | null {
const loadedCommand = this.findCommandByName(command);
if (!loadedCommand) return null;

return (loadedCommand.metadata ??= {
aliases: [],
guilds: [],
userPermissions: [],
botPermissions: [],
});
}
}
Loading