Skip to content

Commit f6d3cc1

Browse files
authored
feat: add commands.defaultContext to define a default context for all commands (#211)
* feat: add commands.defaultContext to define a default context for all commands * biome: check * chore: knip config fix
1 parent 5029bfa commit f6d3cc1

File tree

8 files changed

+85
-17
lines changed

8 files changed

+85
-17
lines changed

.changeset/deep-wolves-thank.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@djs-core/runtime": minor
3+
"@djs-core/dev": minor
4+
---
5+
6+
Add `commands.defaultContext` array to define a default context for all commands (command.setContext overrides config)

app/djs.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Config } from "@djs-core/dev";
2+
import { InteractionContextType } from "discord.js";
23

34
if (!process.env.TOKEN) {
45
throw new Error("TOKEN environment variable is required");
@@ -7,4 +8,7 @@ if (!process.env.TOKEN) {
78
export default {
89
token: process.env.TOKEN,
910
servers: ["1333211545920077896"],
11+
commands: {
12+
defaultContext: [InteractionContextType.Guild],
13+
},
1014
} satisfies Config;

knip.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"tags": ["-lintignore"],
88
"workspaces": {
99
".": {
10-
"ignore": ["app/**", ".github/workflows/**"]
10+
"ignore": ["app/**", ".github/workflows/**", "packages/utils/**"]
1111
},
1212
"app": {
1313
"ignoreDependencies": ["@djs-core/runtime", "discord.js"]

packages/dev/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { registerBuildCommand } from "./commands/build";
55
import { registerDevCommand } from "./commands/dev";
66
import { registerStartCommand } from "./commands/start";
77

8-
const cli = cac("djs-core").version("1.0.0").help();
8+
const cli = cac("djs-core").version("2.0.0").help();
99

1010
registerStartCommand(cli);
1111
registerDevCommand(cli);

packages/dev/utils/common.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,6 @@ export const banner = `
3030
${pc.bold(pc.blue("djs-core"))} ${pc.dim(`v1.0.0`)}
3131
`;
3232

33-
// Path constants matching TypeScript aliases in tsconfig.json
34-
// @components/* -> src/components/*
35-
// @interactions/* -> src/interactions/*
36-
// @events/* -> src/events/*
3733
export const PATH_ALIASES = {
3834
components: "src/components",
3935
interactions: "src/interactions",
@@ -127,7 +123,7 @@ export async function runBot(projectPath: string) {
127123
);
128124
console.log(`${pc.green("✓")} Loaded ${pc.bold(modals.length)} modals`);
129125

130-
const client = new DjsClient({ servers: config.servers });
126+
const client = new DjsClient({ djsConfig: config });
131127

132128
client.eventsHandler.set(events);
133129

@@ -228,7 +224,6 @@ async function scanCommands(
228224
): Promise<Route[]> {
229225
const routes: Route[] = [];
230226

231-
// Create dir if not exists to avoid error
232227
try {
233228
await fs.access(dir);
234229
} catch {

packages/runtime/DjsClient.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
type StringSelectMenuInteraction,
1515
type UserSelectMenuInteraction,
1616
} from "discord.js";
17+
import type { Config } from "../utils/types/config";
1718
import ApplicationCommandHandler from "./handler/ApplicationCommandHandler";
1819
import ButtonHandler from "./handler/ButtonHandler";
1920
import CommandHandler from "./handler/CommandHandler";
@@ -32,8 +33,9 @@ export default class DjsClient extends Client {
3233
public modalsHandler: ModalHandler = new ModalHandler(this);
3334
public applicationCommandHandler: ApplicationCommandHandler =
3435
new ApplicationCommandHandler(this);
36+
private readonly djsConfig: Config;
3537

36-
constructor({ servers }: { servers: string[] }) {
38+
constructor({ djsConfig }: { djsConfig: Config }) {
3739
super({
3840
intents: [
3941
IntentsBitField.Flags.Guilds,
@@ -42,10 +44,13 @@ export default class DjsClient extends Client {
4244
IntentsBitField.Flags.GuildVoiceStates,
4345
],
4446
});
47+
this.djsConfig = djsConfig;
4548

46-
this.commandsHandler.setGuilds(servers);
47-
this.contextMenusHandler.setGuilds(servers);
48-
this.applicationCommandHandler.setGuilds(servers);
49+
if (djsConfig.servers && djsConfig.servers.length > 0) {
50+
this.commandsHandler.setGuilds(djsConfig.servers);
51+
this.contextMenusHandler.setGuilds(djsConfig.servers);
52+
this.applicationCommandHandler.setGuilds(djsConfig.servers);
53+
}
4954

5055
this.once(Events.ClientReady, () => {
5156
const deleted = cleanupExpiredTokens();
@@ -103,4 +108,8 @@ export default class DjsClient extends Client {
103108
}
104109
});
105110
}
111+
112+
public getDjsConfig(): Config {
113+
return this.djsConfig;
114+
}
106115
}

packages/runtime/handler/ApplicationCommandHandler.ts

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
import type {
22
ApplicationCommand,
33
ApplicationCommandDataResolvable,
4-
Client,
54
Collection,
65
} from "discord.js";
76
import {
87
SlashCommandBuilder,
98
type SlashCommandSubcommandBuilder,
109
} from "discord.js";
10+
import type DjsClient from "../DjsClient";
1111
import type Command from "../interaction/Command";
1212
import type ContextMenu from "../interaction/ContextMenu";
1313
import type { Route } from "./CommandHandler";
1414

1515
export default class ApplicationCommandHandler {
16-
private readonly client: Client;
16+
private readonly client: DjsClient;
1717
private commands: Route[] = [];
1818
private contextMenus: ContextMenu[] = [];
1919
private guilds: string[] = [];
2020
private rootIdCache = new Map<string, Map<string, string>>();
21+
private hasWarnedEmptyContext = false;
2122

22-
constructor(client: Client) {
23+
constructor(client: DjsClient) {
2324
this.client = client;
2425
}
2526

@@ -156,7 +157,12 @@ export default class ApplicationCommandHandler {
156157
if (!cmd.name) {
157158
cmd.setName(root);
158159
}
159-
return cmd.toJSON();
160+
this.applyDefaultContext(cmd, routes);
161+
const json = cmd.toJSON();
162+
if (json.contexts && json.contexts.length === 0) {
163+
delete json.contexts;
164+
}
165+
return json;
160166
}
161167

162168
for (const [name, cmd] of subcommands) {
@@ -191,7 +197,50 @@ export default class ApplicationCommandHandler {
191197
});
192198
}
193199

194-
return builder.toJSON();
200+
this.applyDefaultContext(builder, routes);
201+
const json = builder.toJSON();
202+
if (json.contexts && json.contexts.length === 0) {
203+
delete json.contexts;
204+
}
205+
return json;
206+
}
207+
208+
private applyDefaultContext(
209+
target: Command | SlashCommandBuilder,
210+
routes: Array<{ parts: string[]; cmd: Command }>,
211+
): void {
212+
const defaultContext = this.client.getDjsConfig()?.commands?.defaultContext;
213+
if (!defaultContext) {
214+
return;
215+
}
216+
if (!Array.isArray(defaultContext) || defaultContext.length === 0) {
217+
if (!this.hasWarnedEmptyContext) {
218+
console.warn(
219+
"⚠️ config.commands.defaultContext is defined but empty. Default context will not be applied.",
220+
);
221+
this.hasWarnedEmptyContext = true;
222+
}
223+
return;
224+
}
225+
226+
try {
227+
const targetJson = target.toJSON();
228+
if (targetJson.contexts && targetJson.contexts.length > 0) {
229+
return;
230+
}
231+
} catch {}
232+
233+
for (const r of routes) {
234+
try {
235+
const cmdJson = r.cmd.toJSON();
236+
if (cmdJson.contexts && cmdJson.contexts.length > 0) {
237+
target.setContexts(cmdJson.contexts);
238+
return;
239+
}
240+
} catch {}
241+
}
242+
243+
target.setContexts(defaultContext);
195244
}
196245

197246
private getRootDescription(root: string): string | undefined {

packages/utils/types/config.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
import type { InteractionContextType } from "discord.js";
2+
13
export interface Config {
24
token: string;
35
servers: string[];
6+
commands?: {
7+
defaultContext?: InteractionContextType[];
8+
};
49
}

0 commit comments

Comments
 (0)