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