Skip to content

Commit 1b24de2

Browse files
committed
feat: context menu commands and user installables
1 parent 9f0c30c commit 1b24de2

File tree

15 files changed

+143
-43
lines changed

15 files changed

+143
-43
lines changed

apps/test-bot/src/app/commands/avatar/command.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { MessageCommandContext, SlashCommandContext } from 'commandkit';
1+
import {
2+
MessageCommandContext,
3+
SlashCommandContext,
4+
UserContextMenuCommandContext,
5+
} from 'commandkit';
26
import { ApplicationCommandOptionType } from 'discord.js';
37

48
export const command = {
@@ -13,6 +17,26 @@ export const command = {
1317
],
1418
};
1519

20+
export async function userContextMenu(ctx: UserContextMenuCommandContext) {
21+
const target = ctx.interaction.targetUser;
22+
23+
const { t } = ctx.locale();
24+
25+
const msg = await t('avatar', { user: target.username });
26+
27+
await ctx.interaction.reply({
28+
embeds: [
29+
{
30+
title: msg,
31+
image: {
32+
url: target.displayAvatarURL({ size: 2048 }),
33+
},
34+
color: 0x7289da,
35+
},
36+
],
37+
});
38+
}
39+
1640
export async function chatInput(ctx: SlashCommandContext) {
1741
const user = ctx.options.getUser('user') ?? ctx.interaction.user;
1842

apps/test-bot/src/app/commands/cat/command.ts

+30-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,36 @@
1-
import type { CommandData, SlashCommand, MessageCommand } from 'commandkit';
1+
import type {
2+
CommandData,
3+
SlashCommand,
4+
MessageCommand,
5+
MessageContextMenuCommand,
6+
} from 'commandkit';
7+
import {
8+
ApplicationIntegrationType,
9+
InteractionContextType,
10+
MessageFlags,
11+
} from 'discord.js';
212

3-
export const data: CommandData = {
13+
export const command: CommandData = {
414
name: 'cat',
515
description: 'cat command',
16+
integration_types: [
17+
ApplicationIntegrationType.GuildInstall,
18+
ApplicationIntegrationType.UserInstall,
19+
],
20+
contexts: [
21+
InteractionContextType.Guild,
22+
InteractionContextType.BotDM,
23+
InteractionContextType.PrivateChannel,
24+
],
25+
};
26+
27+
export const messageContextMenu: MessageContextMenuCommand = async (ctx) => {
28+
const content = ctx.interaction.targetMessage.content || 'No content found';
29+
30+
await ctx.interaction.reply({
31+
content,
32+
flags: MessageFlags.Ephemeral,
33+
});
634
};
735

836
export const chatInput: SlashCommand = async (ctx) => {

apps/test-bot/src/app/commands/dog/command.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { CommandData, SlashCommand, MessageCommand } from 'commandkit';
22

3-
export const data: CommandData = {
3+
export const command: CommandData = {
44
name: 'dog',
55
description: 'dog command',
66
};

apps/test-bot/src/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ const commandkit = new CommandKit({
1313
commandsPath: `${__dirname}/commands`,
1414
eventsPath: `${__dirname}/events`,
1515
validationsPath: `${__dirname}/validations`,
16-
devGuildIds: process.env.DEV_GUILD_ID?.split(',') ?? [],
17-
devUserIds: process.env.DEV_USER_ID?.split(',') ?? [],
16+
// devGuildIds: process.env.DEV_GUILD_ID?.split(',') ?? [],
17+
// devUserIds: process.env.DEV_USER_ID?.split(',') ?? [],
1818
bulkRegister: true,
1919
});
2020

packages/commandkit/src/app/command-handler/AppCommandHandler.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,7 @@ export class AppCommandHandler {
7474
public constructor(public readonly commandkit: CommandKit) {}
7575

7676
public getCommandsArray() {
77-
return Array.from(this.loadedCommands.values()).map((v) => {
78-
if ('toJSON' in v && typeof v.toJSON === 'function') return v.toJSON();
79-
return v.data.command;
80-
});
77+
return Array.from(this.loadedCommands.values());
8178
}
8279

8380
public async prepareCommandRun(

packages/commandkit/src/app/router/CommandsRouter.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ export class CommandsRouter {
206206
if (commandSegments.length !== segments.length) return false;
207207

208208
return commandSegments.every((segment, index) => {
209-
if (segment === '[name]') return true;
209+
if (/^\[.+\]$/.test(segment)) return true;
210210
return segment === segments[index];
211211
});
212212
});
@@ -266,7 +266,7 @@ export class CommandsRouter {
266266
});
267267

268268
const parent = parentSegments.join(' ');
269-
const name = parts[parts.length - 1].replace(/\.(m|c)?(j|t)sx?$/, '');
269+
const name = parts[parts.length - 2];
270270

271271
const command: ParsedCommand = {
272272
name,

packages/commandkit/src/cli/build.ts

+10-8
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { BuildOptions } from './types';
1717

1818
export async function bootstrapProductionBuild(configPath: string) {
1919
const config = await findCommandKitConfig(configPath);
20-
const spinner = createSpinner('Creating optimized production build...');
20+
const spinner = await createSpinner('Creating optimized production build...');
2121
const start = performance.now();
2222

2323
try {
@@ -104,13 +104,15 @@ async function buildProject(options: BuildOptions) {
104104
!!polyfillRequire,
105105
);
106106

107-
write(
108-
colors.green(
109-
`\nRun ${colors.magenta(`commandkit start`)} ${colors.green(
110-
'to start your bot.',
111-
)}`,
112-
),
113-
);
107+
if (!options.isDevelopment) {
108+
write(
109+
colors.green(
110+
`\nRun ${colors.magenta(`commandkit start`)} ${colors.green(
111+
'to start your bot.',
112+
)}`,
113+
),
114+
);
115+
}
114116
} catch (e) {
115117
panic(e);
116118
}

packages/commandkit/src/cli/development.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export async function bootstrapDevelopmentServer(opts: any) {
1010
const config = await findCommandKitConfig(opts.config);
1111
const { watch = true, nodeOptions = [], clearRestartLogs = true } = config;
1212

13-
const spinner = createSpinner('Starting development server...');
13+
const spinner = await createSpinner('Starting development server...');
1414
const start = performance.now();
1515

1616
try {

packages/commandkit/src/cli/generators.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export async function generateCommand(name: string, customPath?: string) {
2424
const commandFile = `
2525
import type { CommandData, SlashCommand, MessageCommand } from 'commandkit';
2626
27-
export const data: CommandData = {
27+
export const command: CommandData = {
2828
name: '${name}',
2929
description: '${name} command',
3030
};
@@ -34,7 +34,7 @@ export const chatInput: SlashCommand = async (ctx) => {
3434
};
3535
3636
export const message: MessageCommand = async (ctx) => {
37-
await ctx.message.reply('Hello from ${name}!');
37+
await ctx.message.reply('Hello from ${name}!');
3838
};
3939
`.trim();
4040

packages/commandkit/src/cli/production.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export async function bootstrapProductionServer(configPath: string) {
1515
panic('Could not find production build, run `commandkit build` first');
1616
}
1717

18-
const spinner = createSpinner('Starting production server...');
18+
const spinner = await createSpinner('Starting production server...');
1919

2020
try {
2121
const processEnv = {};

packages/commandkit/src/cli/utils.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import { parseEnv } from './parse-env';
44
import { ChildProcessWithoutNullStreams, spawn } from 'node:child_process';
55
import colors from '../utils/colors';
66
import { CLIOptions } from './types';
7-
import ora, { Ora } from 'ora';
7+
import type { Ora } from 'ora';
8+
9+
let ora: typeof import('ora') | undefined;
810

911
export function createNodeProcess(
1012
options: CLIOptions,
@@ -50,8 +52,10 @@ export function loadEnvFiles(options: CLIOptions) {
5052
return processEnv;
5153
}
5254

53-
export function createSpinner(text: string): Ora {
54-
return ora({
55+
export async function createSpinner(text: string): Promise<Ora> {
56+
if (!ora) ora = await import('ora');
57+
58+
return (ora.default || ora)({
5559
text,
5660
color: 'cyan',
5761
});

packages/commandkit/src/legacy/handlers/command-handler/CommandHandler.ts

+57-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { CacheType, Events, Interaction, Message } from 'discord.js';
1+
import {
2+
ApplicationCommandType,
3+
CacheType,
4+
Events,
5+
Interaction,
6+
Message,
7+
} from 'discord.js';
28
import type {
39
CommandData,
410
CommandFileObject,
@@ -185,6 +191,8 @@ export class CommandHandler {
185191
commandObj.category = commandCategory;
186192
}
187193

194+
Logger.info(`Loaded command: ${commandObj.data.name}`);
195+
188196
this.#data.commands.push(commandObj);
189197
}
190198

@@ -193,19 +201,56 @@ export class CommandHandler {
193201

194202
const commands = handler.getCommandsArray();
195203

196-
// this.#data.commands.push(...commands);
197-
198204
for (const cmd of commands) {
199-
const idx = this.#data.commands.findIndex(
200-
(c) => c.data.name === cmd.name,
201-
);
205+
const json: CommandData =
206+
'toJSON' in cmd.data.command
207+
? cmd.data.command.toJSON()
208+
: cmd.data.command;
202209

203-
if (idx !== -1) {
204-
// @ts-ignore
205-
this.#data.commands[idx] = { data: cmd };
206-
} else {
207-
// @ts-ignore
208-
this.#data.commands.push({ data: cmd });
210+
const additionals: CommandData[] = [json];
211+
212+
if (
213+
cmd.data.userContextMenu &&
214+
json.type !== ApplicationCommandType.User
215+
) {
216+
additionals.push({
217+
...json,
218+
type: ApplicationCommandType.User,
219+
options: undefined,
220+
description_localizations: undefined,
221+
// @ts-ignore
222+
description: undefined,
223+
});
224+
}
225+
226+
if (
227+
cmd.data.messageContextMenu &&
228+
json.type !== ApplicationCommandType.Message
229+
) {
230+
additionals.push({
231+
...json,
232+
type: ApplicationCommandType.Message,
233+
description_localizations: undefined,
234+
// @ts-ignore
235+
description: undefined,
236+
options: undefined,
237+
});
238+
}
239+
240+
for (const variant of additionals) {
241+
const idx = this.#data.commands.findIndex(
242+
(c) => c.data.name === variant.name && c.data.type === variant.type,
243+
);
244+
245+
if (idx !== -1) {
246+
// @ts-ignore
247+
this.#data.commands[idx] = { data: variant };
248+
} else {
249+
// @ts-ignore
250+
this.#data.commands.push({ data: variant });
251+
}
252+
253+
Logger.info(`(app ✨) Loaded command: ${variant.name}`);
209254
}
210255
}
211256
}

packages/commandkit/src/legacy/handlers/command-handler/functions/loadCommandsWithRest.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,9 @@ async function loadGlobalCommands(
115115
`Error ${
116116
reloading ? 'reloading' : 'loading'
117117
} global application commands.\n`,
118-
),
119-
error,
118+
) +
119+
'\n' +
120+
error?.stack || error,
120121
);
121122
});
122123

packages/commandkit/src/utils/utilities.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function findAppDirectory(): string | null {
1515

1616
if (!existsSync(root)) root = process.cwd();
1717

18-
const dirs = ['src/app'].map((dir) => join(root, dir));
18+
const dirs = ['app'].map((dir) => join(root, dir));
1919

2020
for (const dir of dirs) {
2121
if (existsSync(dir)) {

packages/commandkit/tsup.config.ts

-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ export default defineConfig({
1111
keepNames: true,
1212
dts: true,
1313
shims: true,
14-
splitting: false,
1514
skipNodeModulesBundle: true,
1615
clean: true,
1716
target: 'node16',

0 commit comments

Comments
 (0)