Skip to content

Commit

Permalink
feat: lots of important stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
twlite committed Nov 25, 2023
1 parent d50ec89 commit 93ca2ba
Show file tree
Hide file tree
Showing 16 changed files with 164 additions and 46 deletions.
2 changes: 1 addition & 1 deletion packages/commandkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ CommandKit is a library that makes it easy to handle commands and events in your
- Automatic command registration, edits, and deletion 🤖
- Supports multiple development servers 🤝
- Supports multiple users as bot developers 👥
- Object oriented 💻
- User friendly CLI 🖥️

## Documentation

Expand Down
15 changes: 9 additions & 6 deletions packages/commandkit/bin/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export async function bootstrapProductionBuild(config) {
entry: [src, '!dist', '!.commandkit', `!${outDir}`],
});

if (antiCrash) await injectAntiCrash(outDir, main);
await injectShims(outDir, main, antiCrash);

status.succeed(
Colors.green(`Build completed in ${(performance.now() - start).toFixed(2)}ms!`),
Expand All @@ -55,9 +55,10 @@ export async function bootstrapProductionBuild(config) {
}
}

function injectAntiCrash(outDir, main) {
async function injectShims(outDir, main, antiCrash) {
const path = join(process.cwd(), outDir, main);
const snippet = [

const antiCrashScript = antiCrash ? [
'\n\n// --- CommandKit Anti-Crash Monitor ---',
';(()=>{',
" 'use strict';",
Expand All @@ -66,7 +67,7 @@ function injectAntiCrash(outDir, main) {
' // But it exists here due to compatibility reasons with discord bot ecosystem.',
" const p = (t) => `\\x1b[33m${t}\\x1b[0m`, b = '[CommandKit Anti-Crash Monitor]', l = console.log, e1 = 'uncaughtException', e2 = 'unhandledRejection';",
' if (!process.eventNames().includes(e1)) // skip if it is already handled',
' process.on(e1, (e, o) => {',
' process.on(e1, (e) => {',
' l(p(`${b} Uncaught Exception`)); l(p(b), p(e.stack || e));',
' })',
' if (!process.eventNames().includes(e2)) // skip if it is already handled',
Expand All @@ -75,7 +76,9 @@ function injectAntiCrash(outDir, main) {
' });',
'})();',
'// --- CommandKit Anti-Crash Monitor ---\n',
].join('\n');
].join('\n') : '';

const finalScript = [antiCrashScript].join('\n');

return appendFile(path, snippet);
return appendFile(path, finalScript);
}
11 changes: 10 additions & 1 deletion packages/commandkit/bin/parse-env.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,23 @@ const VALUE_PREFIXES = {
DATE: 'DATE::',
};

function catcher(fn) {
try {
fn();
return true;
} catch {
return false;
}
}

export function parseEnv(src) {
for (const key in src) {
const value = src[key];

if (typeof value !== 'string') continue;

if (value.startsWith(VALUE_PREFIXES.JSON)) {
src[key] = JSON.parse(value.replace(VALUE_PREFIXES.JSON, ''));
catcher(() => src[key] = JSON.parse(value.replace(VALUE_PREFIXES.JSON, '')));
continue;
}

Expand Down
7 changes: 6 additions & 1 deletion packages/commandkit/src/components/ButtonKit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ import {
ComponentType,
} from 'discord.js';

/**
* The handler to run when a button is clicked. This handler is called with the interaction as the first argument.
* If the first argument is null, it means that the interaction collector has been destroyed.
*/
export type CommandKitButtonBuilderInteractionCollectorDispatch = (
interaction: ButtonInteraction,
interaction: ButtonInteraction | null,
) => Awaitable<void>;

export type CommandKitButtonBuilderInteractionCollectorDispatchContextData = {
Expand Down Expand Up @@ -133,6 +137,7 @@ export class ButtonKit extends ButtonBuilder {
}

#destroyCollector() {
this.#onClickHandler?.(null);
this.#collector?.stop('end');
this.#collector?.removeAllListeners();
this.#collector = null;
Expand Down
27 changes: 18 additions & 9 deletions packages/commandkit/src/handlers/command-handler/CommandHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ import loadCommandsWithRest from './functions/loadCommandsWithRest';
import registerCommands from './functions/registerCommands';
import builtInValidations from './validations';
import colors from '../../utils/colors';

import rdfc from 'rfdc';

const clone = rdfc();
import { clone } from '../../utils/clone';

/**
* A handler for client application commands.
Expand Down Expand Up @@ -80,7 +77,7 @@ export class CommandHandler {
for (const commandFilePath of commandFilePaths) {
const modulePath = toFileURL(commandFilePath);

let importedObj = await import(`${modulePath}?t=${Date.now()}`);
const importedObj = await import(`${modulePath}?t=${Date.now()}`);
let commandObj: CommandFileObject = clone(importedObj); // Make commandObj extensible

// If it's CommonJS, invalidate the import cache
Expand Down Expand Up @@ -161,15 +158,25 @@ export class CommandHandler {

handleCommands() {
this.#data.client.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand() && !interaction.isContextMenuCommand()) return;
if (
!interaction.isChatInputCommand() &&
!interaction.isContextMenuCommand() &&
!interaction.isAutocomplete()
)
return;

const isAutocomplete = interaction.isAutocomplete();

const targetCommand = this.#data.commands.find(
(cmd) => cmd.data.name === interaction.commandName,
);

if (!targetCommand) return;

const { data, options, run, ...rest } = targetCommand;
const { data, options, run, autocompleteRun, ...rest } = targetCommand;

// skip if autocomplete handler is not defined
if (isAutocomplete && !autocompleteRun) return;

const commandObj = {
data: targetCommand.data,
Expand Down Expand Up @@ -217,11 +224,13 @@ export class CommandHandler {

if (!canRun) return;

targetCommand.run({
const context = {
interaction,
client: this.#data.client,
handler: this.#data.commandkitInstance,
});
};

await targetCommand[isAutocomplete ? 'autocompleteRun' : 'run']!(context);
});
}

Expand Down
12 changes: 10 additions & 2 deletions packages/commandkit/src/handlers/command-handler/typings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { ChatInputCommandInteraction, Client, ContextMenuCommandInteraction } from 'discord.js';
import {
AutocompleteInteraction,
ChatInputCommandInteraction,
Client,
ContextMenuCommandInteraction,
} from 'discord.js';
import { CommandKit } from '../../CommandKit';
import { CommandFileObject } from '../../typings';
import { ValidationHandler } from '../validation-handler/ValidationHandler';
Expand Down Expand Up @@ -86,7 +91,10 @@ export interface BuiltInValidationParams {
/**
* The interaction of the target command.
*/
interaction: ChatInputCommandInteraction | ContextMenuCommandInteraction;
interaction:
| ChatInputCommandInteraction
| ContextMenuCommandInteraction
| AutocompleteInteraction;

/**
* The command handler's data.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BuiltInValidationParams } from '../typings';

export default function ({ interaction, targetCommand, handlerData }: BuiltInValidationParams) {
if (interaction.isAutocomplete()) return;
if (targetCommand.options?.devOnly) {
if (interaction.inGuild() && !handlerData.devGuildIds.includes(interaction.guildId)) {
interaction.reply({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { BuiltInValidationParams } from '../typings';
import { EmbedBuilder } from 'discord.js';

export default function ({ interaction, targetCommand }: BuiltInValidationParams) {
if (interaction.isAutocomplete()) return;
const userPermissions = interaction.memberPermissions;
let userPermissionsRequired = targetCommand.options?.userPermissions;
let missingUserPermissions: string[] = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import type { CommandHandler } from '../command-handler/CommandHandler';
import { getFilePaths, getFolderPaths } from '../../utils/get-paths';
import { toFileURL } from '../../utils/resolve-file-url';
import colors from '../../utils/colors';
import rdfc from 'rfdc';

const clone = rdfc();
import { clone } from '../../utils/clone';

/**
* A handler for client events.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import type { ValidationHandlerData, ValidationHandlerOptions } from './typings'
import { toFileURL } from '../../utils/resolve-file-url';
import { getFilePaths } from '../../utils/get-paths';
import colors from '../../utils/colors';
import rdfc from 'rfdc';

const clone = rdfc();
import { clone } from '../../utils/clone';

/**
* A handler for command validations.
Expand Down
14 changes: 13 additions & 1 deletion packages/commandkit/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
type ChatInputCommandInteraction,
type PermissionsString,
type Client,
AutocompleteInteraction,
} from 'discord.js';
import type { CommandKit } from '../CommandKit';

Expand All @@ -20,7 +21,8 @@ export interface CommandProps {
| ChatInputCommandInteraction
| ContextMenuCommandInteraction
| UserContextMenuCommandInteraction
| MessageContextMenuCommandInteraction;
| MessageContextMenuCommandInteraction
| AutocompleteInteraction;

/**
* The Discord.js client object that CommandKit is handling.
Expand All @@ -33,6 +35,16 @@ export interface CommandProps {
handler: CommandKit;
}

/**
* Props for autocomplete command run functions.
*/
export interface AutocompleteCommandProps extends CommandProps {
/**
* The current autocomplete command interaction object.
*/
interaction: AutocompleteInteraction;
}

/**
* Props for slash (chat input) command run functions.
*/
Expand Down
27 changes: 25 additions & 2 deletions packages/commandkit/src/typings.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// This types file is for development
// For exported types use ./types/index.ts

import type { Client, Interaction } from 'discord.js';
import type { AutocompleteInteraction, CacheType, Client, Interaction } from 'discord.js';
import type { CommandData, CommandKit, CommandOptions, ReloadType } from './index';
import type { CommandHandler, EventHandler, ValidationHandler } from './handlers';

Expand Down Expand Up @@ -64,13 +64,36 @@ export interface CommandKitData extends CommandKitOptions {
validationHandler?: ValidationHandler;
}

/**
* Represents a command context.
*/
export interface CommandContext<T extends Interaction, Cached extends CacheType> {
/**
* The interaction that triggered this command.
*/
interaction: Interaction<CacheType>;
/**
* The client that instantiated this command.
*/
client: Client;
/**
* The command data.
*/
handler: CommandKit;
}

/**
* Represents a command file.
*/
export interface CommandFileObject {
data: CommandData;
options?: CommandOptions;
run: ({}: { interaction: Interaction; client: Client; handler: CommandKit }) => void;
run: <Cached extends CacheType = CacheType>(
ctx: CommandContext<Interaction, Cached>,
) => Awaited<void>;
autocompleteRun?: <Cached extends CacheType = CacheType>(
ctx: CommandContext<Interaction, Cached>,
) => Awaited<void>;
filePath: string;
category: string | null;
[key: string]: any;
Expand Down
3 changes: 3 additions & 0 deletions packages/commandkit/src/utils/clone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import rfdc from 'rfdc';

export const clone = rfdc();
32 changes: 21 additions & 11 deletions packages/commandkit/tests/src/commands/misc/count.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,37 +57,47 @@ export async function run({ interaction }: SlashCommandProps) {
inter?.update(`Count is ${value}`);
});

const disposeButtons = async (interaction: ButtonInteraction | null = null) => {
const disposed = row.setComponents(
row.components.map((button) => button.onClick(null).setDisabled(true)),
);

// Dispose the count's subscribers
disposeCountSubscribers();

const data = {
content: 'Finished counting!',
components: [disposed],
};

if (interaction) await interaction.update(data);
else await message.edit(data);
};

// prettier-ignore
dec.onClick((interaction) => {
if (!interaction) return disposeButtons();
inter = interaction;
setCount((prev) => prev - 1);
}, { message });

// prettier-ignore
reset.onClick((interaction) => {
if (!interaction) return disposeButtons();
inter = interaction;
setCount(0);
}, { message });

// prettier-ignore
inc.onClick((interaction) => {
if (!interaction) return disposeButtons();
inter = interaction;
setCount((prev) => prev + 1);
}, { message });

// prettier-ignore
trash.onClick(async (interaction) => {
const disposed = row.setComponents(
row.components.map((button) => button.onClick(null).setDisabled(true)),
);

// Dispose the count's subscribers
disposeCountSubscribers();

await interaction.update({
content: 'Finished counting!',
components: [disposed],
});
disposeButtons(interaction);
}, { message });
}

Expand Down
Loading

0 comments on commit 93ca2ba

Please sign in to comment.