@hyperneutrino/djs-lite 1.0.2 → 1.1.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.
Files changed (3) hide show
  1. package/bun.lock +1 -0
  2. package/index.ts +86 -18
  3. package/package.json +1 -1
package/bun.lock CHANGED
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "lockfileVersion": 1,
3
+ "configVersion": 0,
3
4
  "workspaces": {
4
5
  "": {
5
6
  "name": "bot-framework",
package/index.ts CHANGED
@@ -1,9 +1,17 @@
1
1
  import {
2
2
  ApplicationCommandType,
3
3
  AutocompleteInteraction,
4
+ ButtonInteraction,
5
+ ChannelSelectMenuInteraction,
4
6
  Client,
5
7
  CommandInteraction,
6
8
  Events,
9
+ MentionableSelectMenuInteraction,
10
+ MessageComponentInteraction,
11
+ ModalSubmitInteraction,
12
+ RoleSelectMenuInteraction,
13
+ StringSelectMenuInteraction,
14
+ UserSelectMenuInteraction,
7
15
  type Awaitable,
8
16
  type BaseApplicationCommandData,
9
17
  type ChatInputApplicationCommandData,
@@ -17,11 +25,16 @@ import {
17
25
  import fs from "node:fs/promises";
18
26
  import path from "node:path";
19
27
 
28
+ // TODO: comments (requires some refactoring of types to do right)
29
+
20
30
  process.on("uncaughtException", (err) => console.error(err));
21
31
 
22
- type Handler<T extends CommandInteraction | AutocompleteInteraction> = (interaction: T) => Awaitable<unknown>;
32
+ type Handler<T extends CommandInteraction | AutocompleteInteraction | MessageComponentInteraction | ModalSubmitInteraction> = (
33
+ interaction: T,
34
+ ...args: any[]
35
+ ) => Awaitable<unknown>;
23
36
 
24
- class Command<T extends BaseApplicationCommandData, U extends CommandInteraction, Z extends boolean = false> {
37
+ abstract class Command<T extends BaseApplicationCommandData, U extends CommandInteraction, Z extends boolean = false> {
25
38
  data: T;
26
39
  handler: Handler<U>;
27
40
  autocomplete: Handler<AutocompleteInteraction> | null;
@@ -44,6 +57,22 @@ export class SlashCommand extends Command<ChatInputApplicationCommandData & { ty
44
57
  export class UserCommand extends Command<UserApplicationCommandData, UserContextMenuCommandInteraction> {}
45
58
  export class MessageCommand extends Command<MessageApplicationCommandData, MessageContextMenuCommandInteraction> {}
46
59
 
60
+ abstract class ComponentResponder<T extends ModalSubmitInteraction | MessageComponentInteraction> {
61
+ handler: Handler<T>;
62
+
63
+ constructor(handler: Handler<T>) {
64
+ this.handler = handler;
65
+ }
66
+ }
67
+
68
+ export class ModalResponder extends ComponentResponder<ModalSubmitInteraction> {}
69
+ export class ButtonResponder extends ComponentResponder<ButtonInteraction> {}
70
+ export class StringSelectResponder extends ComponentResponder<StringSelectMenuInteraction> {}
71
+ export class UserSelectResponder extends ComponentResponder<UserSelectMenuInteraction> {}
72
+ export class RoleSelectResponder extends ComponentResponder<RoleSelectMenuInteraction> {}
73
+ export class MentionSelectResponder extends ComponentResponder<MentionableSelectMenuInteraction> {}
74
+ export class ChannelSelectResponder extends ComponentResponder<ChannelSelectMenuInteraction> {}
75
+
47
76
  export class EventHandler<T extends keyof ClientEvents> {
48
77
  event: T;
49
78
  handler: (...args: ClientEvents[T]) => unknown;
@@ -54,7 +83,7 @@ export class EventHandler<T extends keyof ClientEvents> {
54
83
  }
55
84
  }
56
85
 
57
- export async function loadCommands(client: Client<true>, directory: string) {
86
+ export async function loadCommands(client: Client<true>, directory: string, guildId?: string) {
58
87
  const files = await fs.readdir(path.resolve(directory), { recursive: false, withFileTypes: true });
59
88
 
60
89
  const commandData: (ChatInputApplicationCommandData | UserApplicationCommandData | MessageApplicationCommandData)[] = [];
@@ -67,37 +96,54 @@ export async function loadCommands(client: Client<true>, directory: string) {
67
96
 
68
97
  await Promise.all(
69
98
  files.map(async (file) => {
70
- const { default: item } = await import(path.resolve(file.parentPath, file.name));
99
+ const absolutePath = path.resolve(file.parentPath, file.name);
100
+
101
+ const { default: item } = await import(absolutePath);
71
102
 
72
103
  if (item instanceof SlashCommand) {
73
- commandData.push(item.data);
74
104
  slashCommandHandlers.set(item.data.name, item.handler);
75
105
  if (item.autocomplete) slashCommandAutocompletes.set(item.data.name, item.autocomplete);
76
106
  } else if (item instanceof UserCommand) {
77
- commandData.push(item.data);
78
107
  userCommandHandlers.set(item.data.name, item.handler);
79
108
  } else if (item instanceof MessageCommand) {
80
- commandData.push(item.data);
81
109
  messageCommandHandlers.set(item.data.name, item.handler);
82
110
  } else {
83
- console.warn(`WARN Command loader did not recognize the export from ${file.name} as a command.`);
111
+ throw new Error(
112
+ `Loading commands failed: export from ${path.relative(path.resolve(directory), absolutePath)} was not an instance of <Type>Command.`,
113
+ );
84
114
  }
115
+
116
+ commandData.push(item.data);
85
117
  }),
86
118
  );
87
119
 
88
- await client.application.commands.set(commandData);
89
-
90
120
  client.on(Events.InteractionCreate, (interaction) => {
91
121
  if (interaction.isChatInputCommand()) slashCommandHandlers.get(interaction.commandName)?.(interaction);
92
122
  else if (interaction.isUserContextMenuCommand()) userCommandHandlers.get(interaction.commandName)?.(interaction);
93
123
  else if (interaction.isMessageContextMenuCommand()) messageCommandHandlers.get(interaction.commandName)?.(interaction);
94
124
  else if (interaction.isAutocomplete()) slashCommandAutocompletes.get(interaction.commandName)?.(interaction);
95
125
  });
126
+
127
+ if (guildId) {
128
+ const testGuild = client.guilds.resolve(guildId);
129
+ if (!testGuild)
130
+ throw new Error(`Provided test guild (${guildId}) can not be found, please make sure the bot you started this project on is in this guild.`);
131
+ await testGuild.commands.set(commandData);
132
+ } else {
133
+ await client.application.commands.set(commandData);
134
+ }
96
135
  }
97
136
 
98
137
  export async function loadInteractions(client: Client<true>, directory: string, argumentSeparator: string = ":") {
99
138
  const files = await fs.readdir(path.resolve(directory), { recursive: true, withFileTypes: true });
100
- const handlers = new Map<string, Function>();
139
+
140
+ const modalHandlers = new Map<string, Handler<ModalSubmitInteraction>>();
141
+ const buttonHandlers = new Map<string, Handler<ButtonInteraction>>();
142
+ const stringHandlers = new Map<string, Handler<StringSelectMenuInteraction>>();
143
+ const userHandlers = new Map<string, Handler<UserSelectMenuInteraction>>();
144
+ const roleHandlers = new Map<string, Handler<RoleSelectMenuInteraction>>();
145
+ const mentionHandlers = new Map<string, Handler<MentionableSelectMenuInteraction>>();
146
+ const channelHandlers = new Map<string, Handler<ChannelSelectMenuInteraction>>();
101
147
 
102
148
  await Promise.all(
103
149
  files.map(async (file) => {
@@ -105,11 +151,18 @@ export async function loadInteractions(client: Client<true>, directory: string,
105
151
 
106
152
  const absolutePath = path.resolve(file.parentPath, file.name);
107
153
  const relativePath = path.relative(path.resolve(directory), absolutePath);
108
-
109
- const { default: handler } = await import(absolutePath).catch(() => null);
110
- if (typeof handler !== "function") return console.warn(`WARN Command loader did not recognize the export from ${relativePath} as a command.`);
111
-
112
- handlers.set(relativePath.replace(/\.[^/.]+$/, ""), handler);
154
+ const handlerKey = relativePath.replace(/\.[^/.]+$/, "");
155
+
156
+ const { default: item } = await import(absolutePath).catch(() => null);
157
+
158
+ if (item instanceof ModalResponder) modalHandlers.set(handlerKey, item.handler);
159
+ else if (item instanceof ButtonResponder) buttonHandlers.set(handlerKey, item.handler);
160
+ else if (item instanceof StringSelectResponder) stringHandlers.set(handlerKey, item.handler);
161
+ else if (item instanceof UserSelectResponder) userHandlers.set(handlerKey, item.handler);
162
+ else if (item instanceof RoleSelectResponder) roleHandlers.set(handlerKey, item.handler);
163
+ else if (item instanceof MentionSelectResponder) mentionHandlers.set(handlerKey, item.handler);
164
+ else if (item instanceof ChannelSelectResponder) channelHandlers.set(handlerKey, item.handler);
165
+ else throw new Error(`Loading interactions failed: export from ${relativePath} was not an instance of <InteractionType>Responder.`);
113
166
  }),
114
167
  );
115
168
 
@@ -119,7 +172,13 @@ export async function loadInteractions(client: Client<true>, directory: string,
119
172
  const [, userId, path, ...args] = interaction.customId.split(argumentSeparator);
120
173
  if (!path || (userId && interaction.user.id !== userId)) return;
121
174
 
122
- handlers.get(path)?.(interaction, ...args);
175
+ if (interaction.isModalSubmit()) modalHandlers.get(path)?.(interaction, ...args);
176
+ else if (interaction.isButton()) buttonHandlers.get(path)?.(interaction, ...args);
177
+ else if (interaction.isStringSelectMenu()) stringHandlers.get(path)?.(interaction, ...args);
178
+ else if (interaction.isUserSelectMenu()) userHandlers.get(path)?.(interaction, ...args);
179
+ else if (interaction.isRoleSelectMenu()) roleHandlers.get(path)?.(interaction, ...args);
180
+ else if (interaction.isMentionableSelectMenu()) mentionHandlers.get(path)?.(interaction, ...args);
181
+ else if (interaction.isChannelSelectMenu()) channelHandlers.get(path)?.(interaction, ...args);
123
182
  });
124
183
  }
125
184
 
@@ -130,8 +189,17 @@ export async function loadEvents(client: Client<true>, directory: string, recurs
130
189
  await Promise.all(
131
190
  files.map(async (file) => {
132
191
  if (file.isDirectory()) return;
133
- const { default: item } = await import(path.resolve(file.parentPath, file.name));
192
+
193
+ const absolutePath = path.resolve(file.parentPath, file.name);
194
+
195
+ const { default: item } = await import(absolutePath);
196
+
134
197
  if (item instanceof EventHandler) (handlers[item.event as keyof ClientEvents] ??= []).push(item.handler);
198
+ else {
199
+ throw new Error(
200
+ `Loading events failed: export from ${path.relative(path.resolve(directory), absolutePath)} was not an instance of EventHandler<T>.`,
201
+ );
202
+ }
135
203
  }),
136
204
  );
137
205
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hyperneutrino/djs-lite",
3
3
  "private": false,
4
- "version": "1.0.2",
4
+ "version": "1.1.0",
5
5
  "module": "index.ts",
6
6
  "type": "module",
7
7
  "devDependencies": {