@hyperneutrino/djs-lite 1.2.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +114 -3
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
RoleSelectMenuInteraction,
|
|
13
13
|
StringSelectMenuInteraction,
|
|
14
14
|
UserSelectMenuInteraction,
|
|
15
|
+
type ApplicationCommandSubCommandData,
|
|
16
|
+
type ApplicationCommandSubGroupData,
|
|
15
17
|
type Awaitable,
|
|
16
18
|
type BaseApplicationCommandData,
|
|
17
19
|
type ChatInputApplicationCommandData,
|
|
@@ -22,6 +24,7 @@ import {
|
|
|
22
24
|
type UserApplicationCommandData,
|
|
23
25
|
type UserContextMenuCommandInteraction,
|
|
24
26
|
} from "discord.js";
|
|
27
|
+
import type { Dirent } from "node:fs";
|
|
25
28
|
import fs from "node:fs/promises";
|
|
26
29
|
import path from "node:path";
|
|
27
30
|
|
|
@@ -57,6 +60,32 @@ export class SlashCommand extends Command<ChatInputApplicationCommandData & { ty
|
|
|
57
60
|
export class UserCommand extends Command<UserApplicationCommandData, UserContextMenuCommandInteraction> {}
|
|
58
61
|
export class MessageCommand extends Command<MessageApplicationCommandData, MessageContextMenuCommandInteraction> {}
|
|
59
62
|
|
|
63
|
+
export class SlashCommandWithSubcommands {
|
|
64
|
+
data: Omit<ChatInputApplicationCommandData, "options"> & { type: ApplicationCommandType.ChatInput };
|
|
65
|
+
|
|
66
|
+
constructor(data: typeof this.data) {
|
|
67
|
+
this.data = data;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export class SubcommandGroup {
|
|
72
|
+
data: Omit<ApplicationCommandSubGroupData, "options">;
|
|
73
|
+
|
|
74
|
+
constructor(data: typeof this.data) {
|
|
75
|
+
this.data = data;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export class Subcommand {
|
|
80
|
+
data: ApplicationCommandSubCommandData;
|
|
81
|
+
handler: Handler<ChatInputCommandInteraction>;
|
|
82
|
+
|
|
83
|
+
constructor({ handler, ...data }: typeof this.data & { handler: Subcommand["handler"] }) {
|
|
84
|
+
this.data = data;
|
|
85
|
+
this.handler = handler;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
60
89
|
abstract class ComponentHandler<T extends ModalSubmitInteraction | MessageComponentInteraction> {
|
|
61
90
|
handler: Handler<T>;
|
|
62
91
|
|
|
@@ -83,6 +112,80 @@ export class EventHandler<T extends keyof ClientEvents> {
|
|
|
83
112
|
}
|
|
84
113
|
}
|
|
85
114
|
|
|
115
|
+
async function importAll(
|
|
116
|
+
{ directory, recursive }: { directory: string; recursive: boolean },
|
|
117
|
+
consumer: (data: { file: Dirent<string>; relativePath: string; absolutePath: string; item: unknown }) => unknown,
|
|
118
|
+
) {
|
|
119
|
+
const files = await fs.readdir(path.resolve(directory), { recursive, withFileTypes: true });
|
|
120
|
+
|
|
121
|
+
await Promise.all(
|
|
122
|
+
files.map(async (file) => {
|
|
123
|
+
if (file.isDirectory()) return;
|
|
124
|
+
|
|
125
|
+
const absolutePath = path.resolve(file.parentPath, file.name);
|
|
126
|
+
const relativePath = path.relative(path.resolve(directory), absolutePath);
|
|
127
|
+
|
|
128
|
+
const { default: item } = await import(absolutePath);
|
|
129
|
+
|
|
130
|
+
await consumer({ file, relativePath, absolutePath, item });
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function loadSubcommands(directory: string): Promise<{
|
|
136
|
+
options: ApplicationCommandSubCommandData[];
|
|
137
|
+
handlers: Map<string, Handler<ChatInputCommandInteraction>>;
|
|
138
|
+
}> {
|
|
139
|
+
if (!(await fs.exists(directory))) throw new Error(`Loading subcommands within a group failed: ${directory} is required but could not be found.`);
|
|
140
|
+
|
|
141
|
+
const options: ApplicationCommandSubCommandData[] = [];
|
|
142
|
+
const handlers = new Map<string, Handler<ChatInputCommandInteraction>>();
|
|
143
|
+
|
|
144
|
+
await importAll({ directory, recursive: false }, async ({ file, relativePath, item }) => {
|
|
145
|
+
if (item instanceof Subcommand) {
|
|
146
|
+
options.push(item.data);
|
|
147
|
+
handlers.set(item.data.name, item.handler);
|
|
148
|
+
} else throw new Error(`Loading commands failed: export from ${relativePath} (third-level in commands folder) was not an instance of Subcommand.`);
|
|
149
|
+
|
|
150
|
+
if (item.data.name !== file.name.replace(/.[^/.]+$/, ""))
|
|
151
|
+
throw new Error(`Code style enforcement: name exported from ${relativePath} does not match the filename`);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return { options, handlers };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function loadSubcommandsAndGroups(directory: string): Promise<{
|
|
158
|
+
options: (ApplicationCommandSubGroupData | ApplicationCommandSubCommandData)[];
|
|
159
|
+
handler: Handler<ChatInputCommandInteraction>;
|
|
160
|
+
}> {
|
|
161
|
+
if (!(await fs.exists(directory))) throw new Error(`Loading subcomands/groups failed: ${directory} is required but could not be found.`);
|
|
162
|
+
|
|
163
|
+
const options: (ApplicationCommandSubGroupData | ApplicationCommandSubCommandData)[] = [];
|
|
164
|
+
const handlers = new Map<string, Handler<ChatInputCommandInteraction>>();
|
|
165
|
+
|
|
166
|
+
await importAll({ directory, recursive: false }, async ({ file, relativePath, item }) => {
|
|
167
|
+
if (item instanceof Subcommand) {
|
|
168
|
+
options.push(item.data);
|
|
169
|
+
handlers.set(`/${item.data.name}`, item.handler);
|
|
170
|
+
} else if (item instanceof SubcommandGroup) {
|
|
171
|
+
const subcommands = await loadSubcommands(relativePath.replace(/\.[^/.]+$/, ""));
|
|
172
|
+
options.push({ ...item.data, options: subcommands.options });
|
|
173
|
+
subcommands.handlers.entries().forEach(([key, handler]) => handlers.set(`${item.data.name}/${key}`, handler));
|
|
174
|
+
} else
|
|
175
|
+
throw new Error(
|
|
176
|
+
`Loading commands failed: export from ${relativePath} (second-level in commands folder) was not an instance of SubcommandGroup or Subcommand.`,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
if (item.data.name !== file.name.replace(/.[^/.]+$/, ""))
|
|
180
|
+
throw new Error(`Code style enforcement: name exported from ${relativePath} does not match the filename`);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
options,
|
|
185
|
+
handler: (cmd) => handlers.get(`${cmd.options.getSubcommandGroup(false) ?? ""}/${cmd.options.getSubcommand(true)}`)?.(cmd),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
86
189
|
export async function loadCommands(client: Client<true>, directory: string, guildId?: string) {
|
|
87
190
|
const files = await fs.readdir(path.resolve(directory), { recursive: false, withFileTypes: true });
|
|
88
191
|
|
|
@@ -96,7 +199,10 @@ export async function loadCommands(client: Client<true>, directory: string, guil
|
|
|
96
199
|
|
|
97
200
|
await Promise.all(
|
|
98
201
|
files.map(async (file) => {
|
|
202
|
+
if (file.isDirectory()) return;
|
|
203
|
+
|
|
99
204
|
const absolutePath = path.resolve(file.parentPath, file.name);
|
|
205
|
+
const relativePath = path.relative(path.resolve(directory), absolutePath);
|
|
100
206
|
|
|
101
207
|
const { default: item } = await import(absolutePath);
|
|
102
208
|
|
|
@@ -107,12 +213,17 @@ export async function loadCommands(client: Client<true>, directory: string, guil
|
|
|
107
213
|
userCommandHandlers.set(item.data.name, item.handler);
|
|
108
214
|
} else if (item instanceof MessageCommand) {
|
|
109
215
|
messageCommandHandlers.set(item.data.name, item.handler);
|
|
216
|
+
} else if (item instanceof SlashCommandWithSubcommands) {
|
|
217
|
+
const { options, handler } = await loadSubcommandsAndGroups(relativePath.replace(/\.[^/.]+$/, ""));
|
|
218
|
+
commandData.push({ ...item.data, options });
|
|
219
|
+
slashCommandHandlers.set(item.data.name, handler);
|
|
110
220
|
} else {
|
|
111
|
-
throw new Error(
|
|
112
|
-
`Loading commands failed: export from ${path.relative(path.resolve(directory), absolutePath)} was not an instance of <Type>Command.`,
|
|
113
|
-
);
|
|
221
|
+
throw new Error(`Loading commands failed: export from ${relativePath} was not an instance of <Type>Command.`);
|
|
114
222
|
}
|
|
115
223
|
|
|
224
|
+
if (item.data.name !== file.name.replace(/.[^/.]+$/, ""))
|
|
225
|
+
throw new Error(`Code style enforcement: name exported from ${relativePath} does not match the filename`);
|
|
226
|
+
|
|
116
227
|
commandData.push(item.data);
|
|
117
228
|
}),
|
|
118
229
|
);
|