discord-bot-shared 0.15.0 → 0.16.1

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/dist/bot.d.ts CHANGED
@@ -1,8 +1,19 @@
1
- import { BotOptions } from "./types.js";
2
1
  import { CommandManager } from "./command-manager.js";
3
2
  import { EventManager } from "./event-manager.js";
3
+ import { Client, ClientOptions, REST } from "discord.js";
4
4
 
5
5
  //#region src/bot.d.ts
6
+ interface BotOptions {
7
+ applicationId: string;
8
+ token: string;
9
+ clientOptions: ClientOptions;
10
+ }
11
+ interface DiscordContext {
12
+ applicationId: string;
13
+ token: string;
14
+ client: Client;
15
+ rest: REST;
16
+ }
6
17
  declare class Bot {
7
18
  #private;
8
19
  readonly commands: CommandManager;
@@ -11,5 +22,5 @@ declare class Bot {
11
22
  login(): Promise<void>;
12
23
  }
13
24
  //#endregion
14
- export { Bot };
25
+ export { Bot, DiscordContext };
15
26
  //# sourceMappingURL=bot.d.ts.map
package/dist/bot.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bot.d.ts","names":[],"sources":["../src/bot.ts"],"sourcesContent":[],"mappings":";;;;;cAMa,GAAA;;EAAA,SAAG,QAAA,EAGY,cAHZ;EAGY,SAAA,MAAA,EACF,YADE;EACF,WAAA,CAAA,OAAA,EAEI,UAFJ;EAEI,KAAA,CAAA,CAAA,EAYN,OAZM,CAAA,IAAA,CAAA"}
1
+ {"version":3,"file":"bot.d.ts","names":[],"sources":["../src/bot.ts"],"sourcesContent":[],"mappings":";;;;;UAOU,UAAA;EAAA,aAAU,EAAA,MAAA;EAMH,KAAA,EAAA,MAAA;EAOJ,aAAG,EAVC,aAUD;;AAIU,UAXT,cAAA,CAWS;EAEI,aAAA,EAAA,MAAA;EAYN,KAAA,EAAA,MAAA;EAAO,MAAA,EAtBrB,MAsBqB;QArBvB;;cAGK,GAAA;;qBAGe;mBACF;uBAEI;WAYN"}
package/dist/bot.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { CommandManager } from "./command-manager.js";
2
2
  import { EventManager } from "./event-manager.js";
3
3
  import { Client, Events, REST } from "discord.js";
4
+ import { attempt, isErr } from "ts-explicit-errors";
4
5
  var Bot = class {
5
6
  #discord;
6
7
  commands;
@@ -16,12 +17,13 @@ var Bot = class {
16
17
  this.events = new EventManager(this.#discord);
17
18
  }
18
19
  async login() {
19
- this.#discord.client.once(Events.ClientReady, () => {
20
- console.log("Client is ready.");
20
+ this.#discord.client.once(Events.ClientReady, (readyClient) => {
21
+ console.log(`Client is ready. Logged in as '${readyClient.user.tag}'.`);
21
22
  });
22
23
  this.commands._listen();
23
24
  this.events._listen();
24
- await this.#discord.client.login(this.#discord.token);
25
+ const loginResult = await attempt(() => this.#discord.client.login(this.#discord.token));
26
+ if (isErr(loginResult)) console.error(`failed to login: ${loginResult.messageChain}`);
25
27
  }
26
28
  };
27
29
  export { Bot };
package/dist/bot.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bot.js","names":["#discord"],"sources":["../src/bot.ts"],"sourcesContent":["import { Client, Events, REST } from \"discord.js\"\n\nimport { CommandManager } from \"~/command-manager.ts\"\nimport { EventManager } from \"~/event-manager.ts\"\nimport type { BotOptions, DiscordContext } from \"~/types.ts\"\n\nexport class Bot {\n readonly #discord: DiscordContext\n\n public readonly commands: CommandManager\n public readonly events: EventManager\n\n public constructor(options: BotOptions) {\n this.#discord = {\n applicationId: options.applicationId,\n token: options.token,\n client: new Client(options.clientOptions),\n rest: new REST().setToken(options.token),\n }\n\n this.commands = new CommandManager(this.#discord)\n this.events = new EventManager(this.#discord)\n }\n\n public async login(): Promise<void> {\n this.#discord.client.once(Events.ClientReady, () => {\n console.log(\"Client is ready.\")\n })\n\n this.commands._listen()\n this.events._listen()\n await this.#discord.client.login(this.#discord.token)\n }\n}\n"],"mappings":";;;AAMA,IAAa,MAAb,MAAiB;CACf;CAEA;CACA;CAEA,YAAmB,SAAqB;AACtC,QAAA,UAAgB;GACd,eAAe,QAAQ;GACvB,OAAO,QAAQ;GACf,QAAQ,IAAI,OAAO,QAAQ,cAAc;GACzC,MAAM,IAAI,MAAM,CAAC,SAAS,QAAQ,MAAM;GACzC;AAED,OAAK,WAAW,IAAI,eAAe,MAAA,QAAc;AACjD,OAAK,SAAS,IAAI,aAAa,MAAA,QAAc;;CAG/C,MAAa,QAAuB;AAClC,QAAA,QAAc,OAAO,KAAK,OAAO,mBAAmB;AAClD,WAAQ,IAAI,mBAAmB;IAC/B;AAEF,OAAK,SAAS,SAAS;AACvB,OAAK,OAAO,SAAS;AACrB,QAAM,MAAA,QAAc,OAAO,MAAM,MAAA,QAAc,MAAM"}
1
+ {"version":3,"file":"bot.js","names":["#discord"],"sources":["../src/bot.ts"],"sourcesContent":["import type { ClientOptions } from \"discord.js\"\nimport { Client, Events, REST } from \"discord.js\"\nimport { attempt, isErr } from \"ts-explicit-errors\"\n\nimport { CommandManager } from \"~/command-manager.ts\"\nimport { EventManager } from \"~/event-manager.ts\"\n\ninterface BotOptions {\n applicationId: string\n token: string\n clientOptions: ClientOptions\n}\n\nexport interface DiscordContext {\n applicationId: string\n token: string\n client: Client\n rest: REST\n}\n\nexport class Bot {\n readonly #discord: DiscordContext\n\n public readonly commands: CommandManager\n public readonly events: EventManager\n\n public constructor(options: BotOptions) {\n this.#discord = {\n applicationId: options.applicationId,\n token: options.token,\n client: new Client(options.clientOptions),\n rest: new REST().setToken(options.token),\n }\n\n this.commands = new CommandManager(this.#discord)\n this.events = new EventManager(this.#discord)\n }\n\n public async login(): Promise<void> {\n this.#discord.client.once(Events.ClientReady, (readyClient) => {\n console.log(`Client is ready. Logged in as '${readyClient.user.tag}'.`)\n })\n\n this.commands._listen()\n this.events._listen()\n\n const loginResult = await attempt(() => this.#discord.client.login(this.#discord.token))\n if (isErr(loginResult)) console.error(`failed to login: ${loginResult.messageChain}`)\n }\n}\n"],"mappings":";;;;AAoBA,IAAa,MAAb,MAAiB;CACf;CAEA;CACA;CAEA,YAAmB,SAAqB;AACtC,QAAA,UAAgB;GACd,eAAe,QAAQ;GACvB,OAAO,QAAQ;GACf,QAAQ,IAAI,OAAO,QAAQ,cAAc;GACzC,MAAM,IAAI,MAAM,CAAC,SAAS,QAAQ,MAAM;GACzC;AAED,OAAK,WAAW,IAAI,eAAe,MAAA,QAAc;AACjD,OAAK,SAAS,IAAI,aAAa,MAAA,QAAc;;CAG/C,MAAa,QAAuB;AAClC,QAAA,QAAc,OAAO,KAAK,OAAO,cAAc,gBAAgB;AAC7D,WAAQ,IAAI,kCAAkC,YAAY,KAAK,IAAI,IAAI;IACvE;AAEF,OAAK,SAAS,SAAS;AACvB,OAAK,OAAO,SAAS;EAErB,MAAM,cAAc,MAAM,cAAc,MAAA,QAAc,OAAO,MAAM,MAAA,QAAc,MAAM,CAAC;AACxF,MAAI,MAAM,YAAY,CAAE,SAAQ,MAAM,oBAAoB,YAAY,eAAe"}
@@ -1,28 +1,35 @@
1
- import { DiscordContext } from "./types.js";
2
- import { ChatInputCommandInteraction, RESTPostAPIChatInputApplicationCommandsJSONBody } from "discord.js";
1
+ import { DiscordContext } from "./bot.js";
2
+ import { ChatInputCommandInteraction, SlashCommandBuilder, SlashCommandOptionsOnlyBuilder } from "discord.js";
3
3
 
4
4
  //#region src/command-manager.d.ts
5
+ type CommandRunFn = (interaction: ChatInputCommandInteraction<"cached">) => void | Promise<void>;
5
6
  interface Command {
6
7
  requiredRoles?: string[];
7
- command: RESTPostAPIChatInputApplicationCommandsJSONBody;
8
- run: (interaction: ChatInputCommandInteraction<"cached">) => void | Promise<void>;
8
+ command: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder;
9
+ run: CommandRunFn;
9
10
  }
10
- type CommandHook = (interaction: ChatInputCommandInteraction<"cached">) => boolean | Promise<boolean>;
11
+ type CommandHookResult = {
12
+ success: true;
13
+ } | {
14
+ success: false;
15
+ message?: string;
16
+ };
17
+ type CommandHook = (interaction: ChatInputCommandInteraction<"cached">) => CommandHookResult | Promise<CommandHookResult>;
11
18
  declare class CommandManager {
12
19
  #private;
13
20
  constructor(discord: DiscordContext);
21
+ private getCommandPayload;
22
+ private onReady;
14
23
  add(command: Command): void;
15
24
  setGlobalCommandHook(commandHook: CommandHook): void;
16
25
  register(): Promise<void>;
17
26
  unregister(): Promise<void>;
18
- registerGuildCommands(): Promise<void>;
19
- private _registerGuildCommands;
20
- unregisterGuildCommands(): Promise<void>;
21
- private _unregisterGuildCommands;
27
+ guildRegister(): Promise<void>;
28
+ guildUnregister(): Promise<void>;
22
29
  _listen(): void;
23
30
  private static checkRoles;
24
- private static interactionReply;
31
+ private static interactionErrorReply;
25
32
  }
26
33
  //#endregion
27
- export { Command, CommandHook, CommandManager };
34
+ export { Command, CommandHook, CommandManager, CommandRunFn };
28
35
  //# sourceMappingURL=command-manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"command-manager.d.ts","names":[],"sources":["../src/command-manager.ts"],"sourcesContent":[],"mappings":";;;;UAYiB,OAAA;;EAAA,OAAA,EAEN,+CAFa;EAEb,GAAA,EAAA,CAAA,WAAA,EACU,2BADV,CAAA,QAAA,CAAA,EAAA,GAAA,IAAA,GAC2D,OAD3D,CAAA,IAAA,CAAA;;AAC2D,KAG1D,WAAA,GAH0D,CAAA,WAAA,EAG9B,2BAH8B,CAAA,QAAA,CAAA,EAAA,GAAA,OAAA,GAGsB,OAHtB,CAAA,OAAA,CAAA;AAAO,cAKhE,cAAA,CALgE;EAGjE,CAAA,OAAA;EAEC,WAAA,CAAA,OAAc,EAKG,cALH;EAKG,GAAA,CAAA,OAAA,EAOR,OAPQ,CAAA,EAAA,IAAA;EAOR,oBAAA,CAAA,WAAA,EAIqB,WAJrB,CAAA,EAAA,IAAA;EAIqB,QAAA,CAAA,CAAA,EAIhB,OAJgB,CAAA,IAAA,CAAA;EAIhB,UAAA,CAAA,CAAA,EAQE,OARF,CAAA,IAAA,CAAA;EAQE,qBAAA,CAAA,CAAA,EAOO,OAPP,CAAA,IAAA,CAAA;EAOO,QAAA,sBAAA;EA6BM,uBAAA,CAAA,CAAA,EAAA,OAAA,CAAA,IAAA,CAAA;EAAO,QAAA,wBAAA"}
1
+ {"version":3,"file":"command-manager.d.ts","names":[],"sources":["../src/command-manager.ts"],"sourcesContent":[],"mappings":";;;;KAcY,YAAA,iBAA6B,iDAAiD;UAEzE,OAAA;EAFL,aAAA,CAAA,EAAY,MAAA,EAAA;EAEP,OAAA,EAEN,mBAFa,GAES,8BAFT;EAEb,GAAA,EACJ,YADI;;KAIN,iBAAA,GAHE;EAAY,OAAA,EAAA,IAAA;AAClB,CAAA,GAEI;EASO,OAAA,EAAA,KAAW;EACR,OAAA,CAAA,EAAA,MAAA;CACV;AAA4B,KAFrB,WAAA,GAEqB,CAAA,WAAA,EADlB,2BACkB,CAAA,QAAA,CAAA,EAAA,GAA5B,iBAA4B,GAAR,OAAQ,CAAA,iBAAA,CAAA;AAAR,cAEZ,cAAA,CAFY;EAAO,CAAA,OAAA;EAEnB,WAAA,CAAA,OAAc,EAKG,cALH;EAKG,QAAA,iBAAA;EAgBR,QAAA,OAAA;EAIqB,GAAA,CAAA,OAAA,EAJrB,OAIqB,CAAA,EAAA,IAAA;EAIhB,oBAAA,CAAA,WAAA,EAJgB,WAIhB,CAAA,EAAA,IAAA;EAWE,QAAA,CAAA,CAAA,EAXF,OAWE,CAAA,IAAA,CAAA;EAWD,UAAA,CAAA,CAAA,EAXC,OAWD,CAAA,IAAA,CAAA;EAkCM,aAAA,CAAA,CAAA,EAlCN,OAkCM,CAAA,IAAA,CAAA;EAAO,eAAA,CAAA,CAAA,EAAP,OAAO,CAAA,IAAA,CAAA"}
@@ -1,5 +1,7 @@
1
- import { UserError, throwUserError } from "./util.js";
1
+ import { components } from "./components.js";
2
+ import { handleCallback } from "./util.js";
2
3
  import { Collection, Events, MessageFlags, Routes } from "discord.js";
4
+ import { CtxError, attempt, err, filterMap, isErr } from "ts-explicit-errors";
3
5
  var CommandManager = class CommandManager {
4
6
  #commands = new Collection();
5
7
  #globalCommandHook;
@@ -7,6 +9,13 @@ var CommandManager = class CommandManager {
7
9
  constructor(discord) {
8
10
  this.#discord = discord;
9
11
  }
12
+ getCommandPayload() {
13
+ return this.#commands.map((c) => c.command.toJSON());
14
+ }
15
+ async onReady(fn) {
16
+ if (this.#discord.client.isReady()) await handleCallback(fn);
17
+ else this.#discord.client.once(Events.ClientReady, async () => await handleCallback(fn));
18
+ }
10
19
  add(command) {
11
20
  this.#commands.set(command.command.name, command);
12
21
  }
@@ -14,113 +23,98 @@ var CommandManager = class CommandManager {
14
23
  this.#globalCommandHook = commandHook;
15
24
  }
16
25
  async register() {
17
- const commandPayload = this.#commands.map((c) => c.command);
18
- const route = Routes.applicationCommands(this.#discord.applicationId);
19
- await this.#discord.rest.put(route, { body: commandPayload });
20
- console.log(`Globally registered ${this.#commands.size.toString()} (/) commands.`);
26
+ const registerResult = await attempt(async () => {
27
+ const route = Routes.applicationCommands(this.#discord.applicationId);
28
+ await this.#discord.rest.put(route, { body: this.getCommandPayload() });
29
+ console.log(`Globally registered ${this.#commands.size.toString()} (/) commands.`);
30
+ });
31
+ if (isErr(registerResult)) console.error(`failed to register global commands: ${registerResult.messageChain}`);
21
32
  }
22
33
  async unregister() {
23
- const route = Routes.applicationCommands(this.#discord.applicationId);
24
- await this.#discord.rest.put(route, { body: [] });
25
- console.log("Unregistered global commands.");
26
- }
27
- async registerGuildCommands() {
28
- if (this.#discord.client.readyAt) await this._registerGuildCommands();
29
- else this.#discord.client.once(Events.ClientReady, () => void this._registerGuildCommands());
30
- }
31
- async _registerGuildCommands() {
32
- let guilds;
33
- try {
34
- guilds = await this.#discord.client.guilds.fetch();
35
- } catch (error) {
36
- console.error("Unable to register guild commands. Failed to fetch guilds.");
37
- throw error;
38
- }
39
- const commandPayload = this.#commands.map((c) => c.command);
40
- const registerPromises = [];
41
- for (const guild of guilds.values()) {
42
- const route = Routes.applicationGuildCommands(this.#discord.applicationId, guild.id);
43
- const registerCommandsInGuild = async () => {
44
- await this.#discord.rest.put(route, { body: commandPayload });
45
- console.log(`Registered ${this.#commands.size.toString()} (/) commands in guild: ${guild.name}`);
46
- };
47
- registerPromises.push(registerCommandsInGuild());
48
- }
49
- await Promise.all(registerPromises);
34
+ const unregisterResult = await attempt(async () => {
35
+ const route = Routes.applicationCommands(this.#discord.applicationId);
36
+ await this.#discord.rest.put(route, { body: [] });
37
+ console.log("Unregistered global commands.");
38
+ });
39
+ if (isErr(unregisterResult)) console.error(`failed to unregister global commands: ${unregisterResult.messageChain}`);
50
40
  }
51
- async unregisterGuildCommands() {
52
- if (this.#discord.client.readyAt) await this._unregisterGuildCommands();
53
- else this.#discord.client.once(Events.ClientReady, () => void this._unregisterGuildCommands());
41
+ async guildRegister() {
42
+ const registerGuildCommands = async () => {
43
+ const guilds = await attempt(() => this.#discord.client.guilds.fetch());
44
+ if (isErr(guilds)) return err("failed to fetch guilds", guilds);
45
+ const commandPayload = this.getCommandPayload();
46
+ const { errors } = await filterMap(guilds.values(), async (guild) => {
47
+ const registerResult = await attempt(async () => {
48
+ const route = Routes.applicationGuildCommands(this.#discord.applicationId, guild.id);
49
+ await this.#discord.rest.put(route, { body: commandPayload });
50
+ console.log(`Registered ${this.#commands.size.toString()} (/) commands in guild '${guild.name}'`);
51
+ });
52
+ if (isErr(registerResult)) return err(`failed to register guild commands in guild '${guild.name}'`, registerResult);
53
+ });
54
+ if (errors) return err(`failed to register guild commands in all guilds:\n${errors.map((error) => error.message).join("\n")}`, void 0);
55
+ };
56
+ await this.onReady(async () => {
57
+ const registerGuildCommandsResult = await registerGuildCommands();
58
+ if (isErr(registerGuildCommandsResult)) return err("failed to register guild commands", registerGuildCommandsResult);
59
+ });
54
60
  }
55
- async _unregisterGuildCommands() {
56
- let guilds;
57
- try {
58
- guilds = await this.#discord.client.guilds.fetch();
59
- } catch (error) {
60
- console.error("Unable to unregister guild commands. Failed to fetch guilds.");
61
- throw error;
62
- }
63
- const unregisterPromises = [];
64
- for (const guild of guilds.values()) {
65
- const route = Routes.applicationGuildCommands(this.#discord.applicationId, guild.id);
66
- const unregister = async () => {
67
- await this.#discord.rest.put(route, { body: [] });
68
- console.log(`Unregistered commands from guild: ${guild.name}`);
69
- };
70
- unregisterPromises.push(unregister());
71
- }
72
- await Promise.all(unregisterPromises);
61
+ async guildUnregister() {
62
+ const unregisterGuildCommands = async () => {
63
+ const guilds = await attempt(() => this.#discord.client.guilds.fetch());
64
+ if (isErr(guilds)) return err("failed to fetch guilds", guilds);
65
+ const { errors } = await filterMap(guilds.values(), async (guild) => {
66
+ const registerResult = await attempt(async () => {
67
+ const route = Routes.applicationGuildCommands(this.#discord.applicationId, guild.id);
68
+ await this.#discord.rest.put(route, { body: [] });
69
+ console.log(`Unregistered commands in guild '${guild.name}'`);
70
+ });
71
+ if (isErr(registerResult)) return err(`failed to unregister guild commands in guild '${guild.name}'`, registerResult);
72
+ });
73
+ if (errors) return err(`failed to unregister guild commands in all guilds:\n${errors.map((error) => error.message).join("\n")}`, void 0);
74
+ };
75
+ await this.onReady(async () => {
76
+ const unregisterGuildCommandsResult = await unregisterGuildCommands();
77
+ if (isErr(unregisterGuildCommandsResult)) return err("failed to unregister guild commands", unregisterGuildCommandsResult);
78
+ });
73
79
  }
74
80
  _listen() {
75
- const listen = async (interaction) => {
81
+ const handleInteraction = async (interaction) => {
76
82
  if (!interaction.isChatInputCommand()) return;
77
83
  if (!interaction.guildId) return;
78
- if (!interaction.inCachedGuild()) await interaction.client.guilds.fetch(interaction.guildId).catch(console.error);
79
- if (!interaction.inCachedGuild()) {
80
- CommandManager.interactionReply(interaction, "Guild is not cached. Try again.");
81
- return;
82
- }
84
+ const guild = await interaction.client.guilds.fetch(interaction.guildId);
85
+ if (isErr(guild)) return await CommandManager.interactionErrorReply(interaction, err(`failed to fetch guild with id '${interaction.guildId}'`, guild));
86
+ if (!interaction.inCachedGuild()) return await CommandManager.interactionErrorReply(interaction, "Guild is not cached. Try again.");
83
87
  const command = this.#commands.get(interaction.commandName);
84
- if (!command) {
85
- CommandManager.interactionReply(interaction, `Failed to get command with name: ${interaction.commandName}`);
86
- return;
87
- }
88
- if (!await CommandManager.checkRoles(command, interaction)) {
89
- CommandManager.interactionReply(interaction, "You do not have one of the required roles to run this command.");
90
- return;
91
- }
92
- try {
93
- if (!(this.#globalCommandHook ? await this.#globalCommandHook(interaction) : true)) throwUserError("The global command hook returned false.");
94
- await command.run(interaction);
95
- } catch (error) {
96
- CommandManager.interactionReply(interaction, error);
97
- }
88
+ if (!command) return await CommandManager.interactionErrorReply(interaction, err(`failed to get command with name '${interaction.commandName}'`, void 0));
89
+ const hasRequiredRole = await CommandManager.checkRoles(command, interaction);
90
+ if (isErr(hasRequiredRole)) return await CommandManager.interactionErrorReply(interaction, err("failed to check roles", hasRequiredRole));
91
+ if (!hasRequiredRole) return await CommandManager.interactionErrorReply(interaction, "You do not have one of the required roles to run this command.", "warn");
92
+ const globalCommandHookResult = await attempt(() => this.#globalCommandHook ? this.#globalCommandHook(interaction) : { success: true });
93
+ if (isErr(globalCommandHookResult)) return await CommandManager.interactionErrorReply(interaction, err("failed to run global command hook", globalCommandHookResult));
94
+ if (!globalCommandHookResult.success) return await CommandManager.interactionErrorReply(interaction, globalCommandHookResult.message ?? "The global command hook did not succeed.", "warn");
95
+ const commandRunResult = await attempt(() => command.run(interaction));
96
+ if (isErr(commandRunResult)) return await CommandManager.interactionErrorReply(interaction, err(`failed to run command \`${command.command.name}\``, commandRunResult));
98
97
  };
99
- this.#discord.client.on(Events.InteractionCreate, (interaction) => void listen(interaction));
98
+ this.#discord.client.on(Events.InteractionCreate, async (interaction) => await handleCallback(() => handleInteraction(interaction)));
100
99
  console.log("Listening for commands.");
101
100
  }
102
- static async checkRoles(command, interaction) {
103
- if (!command.requiredRoles) return true;
104
- if (command.requiredRoles.length > 0) {
105
- const member = await interaction.guild.members.fetch(interaction.user).catch(console.error);
106
- if (!member) return false;
107
- return member.roles.cache.some((role) => command.requiredRoles ? command.requiredRoles.includes(role.name) : false);
101
+ static async checkRoles({ requiredRoles }, interaction) {
102
+ if (!requiredRoles) return true;
103
+ if (requiredRoles.length > 0) {
104
+ const member = await attempt(() => interaction.guild.members.fetch(interaction.user));
105
+ if (isErr(member)) return err(`failed to fetch member '${interaction.user.id}'`, member);
106
+ return member.roles.cache.some((role) => requiredRoles.includes(role.name));
108
107
  }
109
108
  return false;
110
109
  }
111
- static interactionReply(interaction, error) {
112
- let errorMessage;
113
- if (error instanceof UserError) errorMessage = error.message;
114
- else if (error instanceof Error && error.stack) errorMessage = error.stack;
115
- else errorMessage = String(error);
116
- const message = `There was an error while running this command.\n\`\`\`${errorMessage}\`\`\``;
117
- const handleInteractionReply = async () => {
118
- await (interaction.deferred ? interaction.editReply(message).catch(console.error) : interaction.reply({
119
- content: message,
120
- flags: MessageFlags.Ephemeral
121
- }).catch(console.error));
122
- };
123
- handleInteractionReply();
110
+ static async interactionErrorReply(interaction, error, type = "error") {
111
+ const errorMessage = error instanceof CtxError ? error.messageChain : error;
112
+ const errorComponent = components[type](errorMessage);
113
+ const reply = await attempt(async () => interaction.deferred ? interaction.editReply(errorComponent) : interaction.reply({
114
+ components: errorComponent.components,
115
+ flags: [...errorComponent.flags, MessageFlags.Ephemeral]
116
+ }));
117
+ if (isErr(reply)) return err(`failed to reply to interaction from command '${interaction.commandName}'`, reply);
124
118
  }
125
119
  };
126
120
  export { CommandManager };
@@ -1 +1 @@
1
- {"version":3,"file":"command-manager.js","names":["#commands","#discord","#globalCommandHook","guilds: Collection<Snowflake, OAuth2Guild>","registerPromises: Promise<void>[]","unregisterPromises: Promise<void>[]","errorMessage: string"],"sources":["../src/command-manager.ts"],"sourcesContent":["import type {\n ChatInputCommandInteraction,\n Interaction,\n OAuth2Guild,\n RESTPostAPIChatInputApplicationCommandsJSONBody,\n Snowflake,\n} from \"discord.js\"\nimport { Collection, Events, MessageFlags, Routes } from \"discord.js\"\n\nimport type { DiscordContext } from \"~/types.ts\"\nimport { throwUserError, UserError } from \"~/util.ts\"\n\nexport interface Command {\n requiredRoles?: string[]\n command: RESTPostAPIChatInputApplicationCommandsJSONBody\n run: (interaction: ChatInputCommandInteraction<\"cached\">) => void | Promise<void>\n}\n\nexport type CommandHook = (interaction: ChatInputCommandInteraction<\"cached\">) => boolean | Promise<boolean>\n\nexport class CommandManager {\n readonly #commands = new Collection<string, Command>()\n #globalCommandHook?: CommandHook\n readonly #discord: DiscordContext\n\n public constructor(discord: DiscordContext) {\n this.#discord = discord\n }\n\n /*\n * Add a command\n */\n public add(command: Command): void {\n this.#commands.set(command.command.name, command)\n }\n\n public setGlobalCommandHook(commandHook: CommandHook): void {\n this.#globalCommandHook = commandHook\n }\n\n public async register(): Promise<void> {\n const commandPayload = this.#commands.map((c) => c.command)\n const route = Routes.applicationCommands(this.#discord.applicationId)\n await this.#discord.rest.put(route, { body: commandPayload })\n\n console.log(`Globally registered ${this.#commands.size.toString()} (/) commands.`)\n }\n\n public async unregister(): Promise<void> {\n const route = Routes.applicationCommands(this.#discord.applicationId)\n await this.#discord.rest.put(route, { body: [] })\n console.log(\"Unregistered global commands.\")\n }\n\n // We can't fetch guilds before the client is ready.\n public async registerGuildCommands() {\n if (this.#discord.client.readyAt) await this._registerGuildCommands()\n else this.#discord.client.once(Events.ClientReady, () => void this._registerGuildCommands())\n }\n\n private async _registerGuildCommands(): Promise<void> {\n let guilds: Collection<Snowflake, OAuth2Guild>\n try {\n guilds = await this.#discord.client.guilds.fetch()\n } catch (error) {\n console.error(\"Unable to register guild commands. Failed to fetch guilds.\")\n throw error\n }\n\n const commandPayload = this.#commands.map((c) => c.command)\n\n const registerPromises: Promise<void>[] = []\n for (const guild of guilds.values()) {\n const route = Routes.applicationGuildCommands(this.#discord.applicationId, guild.id)\n const registerCommandsInGuild = async () => {\n await this.#discord.rest.put(route, { body: commandPayload })\n console.log(`Registered ${this.#commands.size.toString()} (/) commands in guild: ${guild.name}`)\n }\n registerPromises.push(registerCommandsInGuild())\n }\n\n await Promise.all(registerPromises)\n }\n\n public async unregisterGuildCommands(): Promise<void> {\n if (this.#discord.client.readyAt) await this._unregisterGuildCommands()\n else this.#discord.client.once(Events.ClientReady, () => void this._unregisterGuildCommands())\n }\n\n private async _unregisterGuildCommands(): Promise<void> {\n let guilds: Collection<Snowflake, OAuth2Guild>\n try {\n guilds = await this.#discord.client.guilds.fetch()\n } catch (error) {\n console.error(\"Unable to unregister guild commands. Failed to fetch guilds.\")\n throw error\n }\n\n const unregisterPromises: Promise<void>[] = []\n for (const guild of guilds.values()) {\n const route = Routes.applicationGuildCommands(this.#discord.applicationId, guild.id)\n const unregister = async () => {\n await this.#discord.rest.put(route, { body: [] })\n console.log(`Unregistered commands from guild: ${guild.name}`)\n }\n unregisterPromises.push(unregister())\n }\n\n await Promise.all(unregisterPromises)\n }\n\n public _listen(): void {\n const listen = async (interaction: Interaction) => {\n if (!interaction.isChatInputCommand()) return\n if (!interaction.guildId) return\n if (!interaction.inCachedGuild()) await interaction.client.guilds.fetch(interaction.guildId).catch(console.error)\n if (!interaction.inCachedGuild()) {\n CommandManager.interactionReply(interaction, \"Guild is not cached. Try again.\")\n return\n }\n\n const command = this.#commands.get(interaction.commandName)\n if (!command) {\n CommandManager.interactionReply(interaction, `Failed to get command with name: ${interaction.commandName}`)\n return\n }\n\n if (!(await CommandManager.checkRoles(command, interaction))) {\n CommandManager.interactionReply(interaction, \"You do not have one of the required roles to run this command.\")\n return\n }\n\n try {\n const shouldContinue = this.#globalCommandHook ? await this.#globalCommandHook(interaction) : true\n if (!shouldContinue) throwUserError(\"The global command hook returned false.\")\n\n await command.run(interaction)\n } catch (error) {\n CommandManager.interactionReply(interaction, error)\n }\n }\n\n this.#discord.client.on(Events.InteractionCreate, (interaction) => void listen(interaction))\n console.log(\"Listening for commands.\")\n }\n\n private static async checkRoles(command: Command, interaction: ChatInputCommandInteraction<\"cached\">) {\n if (!command.requiredRoles) return true\n\n if (command.requiredRoles.length > 0) {\n const member = await interaction.guild.members.fetch(interaction.user).catch(console.error)\n if (!member) return false\n\n return member.roles.cache.some((role) =>\n command.requiredRoles ? command.requiredRoles.includes(role.name) : false,\n )\n }\n\n return false\n }\n\n private static interactionReply(interaction: ChatInputCommandInteraction, error: unknown) {\n let errorMessage: string\n if (error instanceof UserError) errorMessage = error.message\n else if (error instanceof Error && error.stack) errorMessage = error.stack\n else errorMessage = String(error)\n\n const message = `There was an error while running this command.\\n\\`\\`\\`${errorMessage}\\`\\`\\``\n const handleInteractionReply = async () => {\n await (interaction.deferred\n ? interaction.editReply(message).catch(console.error)\n : interaction.reply({ content: message, flags: MessageFlags.Ephemeral }).catch(console.error))\n }\n void handleInteractionReply()\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"command-manager.js","names":["#commands","#discord","#globalCommandHook"],"sources":["../src/command-manager.ts"],"sourcesContent":["import type {\n ChatInputCommandInteraction,\n Interaction,\n SlashCommandBuilder,\n SlashCommandOptionsOnlyBuilder,\n} from \"discord.js\"\nimport { Collection, Events, MessageFlags, Routes } from \"discord.js\"\nimport type { Result } from \"ts-explicit-errors\"\nimport { attempt, CtxError, err, filterMap, isErr } from \"ts-explicit-errors\"\n\nimport type { DiscordContext } from \"~/bot.ts\"\nimport { components } from \"~/components.ts\"\nimport { handleCallback } from \"~/util.ts\"\n\nexport type CommandRunFn = (interaction: ChatInputCommandInteraction<\"cached\">) => void | Promise<void>\n\nexport interface Command {\n requiredRoles?: string[]\n command: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder\n run: CommandRunFn\n}\n\ntype CommandHookResult =\n | {\n success: true\n }\n | {\n success: false\n message?: string\n }\n\nexport type CommandHook = (\n interaction: ChatInputCommandInteraction<\"cached\">,\n) => CommandHookResult | Promise<CommandHookResult>\n\nexport class CommandManager {\n readonly #commands = new Collection<string, Command>()\n #globalCommandHook?: CommandHook\n readonly #discord: DiscordContext\n\n public constructor(discord: DiscordContext) {\n this.#discord = discord\n }\n\n private getCommandPayload() {\n return this.#commands.map((c) => c.command.toJSON())\n }\n\n private async onReady(fn: () => Result | Promise<Result>) {\n if (this.#discord.client.isReady()) await handleCallback(fn)\n else this.#discord.client.once(Events.ClientReady, async () => await handleCallback(fn))\n }\n\n /*\n * Add a command\n */\n public add(command: Command): void {\n this.#commands.set(command.command.name, command)\n }\n\n public setGlobalCommandHook(commandHook: CommandHook): void {\n this.#globalCommandHook = commandHook\n }\n\n public async register(): Promise<void> {\n const registerResult = await attempt(async () => {\n const route = Routes.applicationCommands(this.#discord.applicationId)\n await this.#discord.rest.put(route, { body: this.getCommandPayload() })\n\n console.log(`Globally registered ${this.#commands.size.toString()} (/) commands.`)\n })\n\n if (isErr(registerResult)) console.error(`failed to register global commands: ${registerResult.messageChain}`)\n }\n\n public async unregister(): Promise<void> {\n const unregisterResult = await attempt(async () => {\n const route = Routes.applicationCommands(this.#discord.applicationId)\n await this.#discord.rest.put(route, { body: [] })\n\n console.log(\"Unregistered global commands.\")\n })\n\n if (isErr(unregisterResult)) console.error(`failed to unregister global commands: ${unregisterResult.messageChain}`)\n }\n\n public async guildRegister() {\n const registerGuildCommands = async (): Promise<Result> => {\n const guilds = await attempt(() => this.#discord.client.guilds.fetch())\n if (isErr(guilds)) return err(\"failed to fetch guilds\", guilds)\n\n const commandPayload = this.getCommandPayload()\n\n const { errors } = await filterMap(guilds.values(), async (guild) => {\n const registerResult = await attempt(async () => {\n const route = Routes.applicationGuildCommands(this.#discord.applicationId, guild.id)\n await this.#discord.rest.put(route, { body: commandPayload })\n console.log(`Registered ${this.#commands.size.toString()} (/) commands in guild '${guild.name}'`)\n })\n\n if (isErr(registerResult))\n return err(`failed to register guild commands in guild '${guild.name}'`, registerResult)\n\n return\n })\n\n if (errors)\n return err(\n `failed to register guild commands in all guilds:\\n${errors.map((error) => error.message).join(\"\\n\")}`,\n undefined,\n )\n }\n\n await this.onReady(async (): Promise<Result> => {\n const registerGuildCommandsResult = await registerGuildCommands()\n if (isErr(registerGuildCommandsResult))\n return err(\"failed to register guild commands\", registerGuildCommandsResult)\n })\n }\n\n public async guildUnregister(): Promise<void> {\n const unregisterGuildCommands = async (): Promise<Result> => {\n const guilds = await attempt(() => this.#discord.client.guilds.fetch())\n if (isErr(guilds)) return err(\"failed to fetch guilds\", guilds)\n\n const { errors } = await filterMap(guilds.values(), async (guild) => {\n const registerResult = await attempt(async () => {\n const route = Routes.applicationGuildCommands(this.#discord.applicationId, guild.id)\n await this.#discord.rest.put(route, { body: [] })\n console.log(`Unregistered commands in guild '${guild.name}'`)\n })\n\n if (isErr(registerResult))\n return err(`failed to unregister guild commands in guild '${guild.name}'`, registerResult)\n\n return\n })\n\n if (errors)\n return err(\n `failed to unregister guild commands in all guilds:\\n${errors.map((error) => error.message).join(\"\\n\")}`,\n undefined,\n )\n }\n\n await this.onReady(async (): Promise<Result> => {\n const unregisterGuildCommandsResult = await unregisterGuildCommands()\n if (isErr(unregisterGuildCommandsResult))\n return err(\"failed to unregister guild commands\", unregisterGuildCommandsResult)\n })\n }\n\n public _listen(): void {\n const handleInteraction = async (interaction: Interaction): Promise<Result> => {\n if (!interaction.isChatInputCommand()) return\n if (!interaction.guildId) return\n\n const guild = await interaction.client.guilds.fetch(interaction.guildId)\n if (isErr(guild))\n return await CommandManager.interactionErrorReply(\n interaction,\n err(`failed to fetch guild with id '${interaction.guildId}'`, guild),\n )\n\n if (!interaction.inCachedGuild())\n return await CommandManager.interactionErrorReply(interaction, \"Guild is not cached. Try again.\")\n\n const command = this.#commands.get(interaction.commandName)\n if (!command)\n return await CommandManager.interactionErrorReply(\n interaction,\n err(`failed to get command with name '${interaction.commandName}'`, undefined),\n )\n\n const hasRequiredRole = await CommandManager.checkRoles(command, interaction)\n if (isErr(hasRequiredRole))\n return await CommandManager.interactionErrorReply(interaction, err(\"failed to check roles\", hasRequiredRole))\n\n if (!hasRequiredRole)\n return await CommandManager.interactionErrorReply(\n interaction,\n \"You do not have one of the required roles to run this command.\",\n \"warn\",\n )\n\n const globalCommandHookResult = await attempt(() =>\n this.#globalCommandHook ? this.#globalCommandHook(interaction) : ({ success: true } as const),\n )\n if (isErr(globalCommandHookResult))\n return await CommandManager.interactionErrorReply(\n interaction,\n err(\"failed to run global command hook\", globalCommandHookResult),\n )\n\n if (!globalCommandHookResult.success)\n return await CommandManager.interactionErrorReply(\n interaction,\n globalCommandHookResult.message ?? \"The global command hook did not succeed.\",\n \"warn\",\n )\n\n const commandRunResult = await attempt(() => command.run(interaction))\n if (isErr(commandRunResult))\n return await CommandManager.interactionErrorReply(\n interaction,\n err(`failed to run command \\`${command.command.name}\\``, commandRunResult),\n )\n }\n\n this.#discord.client.on(\n Events.InteractionCreate,\n async (interaction) => await handleCallback(() => handleInteraction(interaction)),\n )\n\n console.log(\"Listening for commands.\")\n }\n\n private static async checkRoles({ requiredRoles }: Command, interaction: ChatInputCommandInteraction<\"cached\">) {\n if (!requiredRoles) return true\n\n if (requiredRoles.length > 0) {\n const member = await attempt(() => interaction.guild.members.fetch(interaction.user))\n if (isErr(member)) return err(`failed to fetch member '${interaction.user.id}'`, member)\n\n return member.roles.cache.some((role) => requiredRoles.includes(role.name))\n }\n\n return false\n }\n\n private static async interactionErrorReply(\n interaction: ChatInputCommandInteraction,\n error: string | CtxError,\n type: keyof typeof components = \"error\",\n ): Promise<Result> {\n const errorMessage = error instanceof CtxError ? error.messageChain : error\n const errorComponent = components[type](errorMessage)\n\n const reply = await attempt(async () =>\n interaction.deferred\n ? interaction.editReply(errorComponent)\n : interaction.reply({\n components: errorComponent.components,\n flags: [...errorComponent.flags, MessageFlags.Ephemeral],\n }),\n )\n\n if (isErr(reply)) return err(`failed to reply to interaction from command '${interaction.commandName}'`, reply)\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { ContainerBuilder, MessageFlags } from "discord.js";
2
+
3
+ //#region src/components.d.ts
4
+ declare const components: {
5
+ readonly error: (message: string) => {
6
+ readonly components: readonly [ContainerBuilder];
7
+ readonly flags: readonly [MessageFlags.IsComponentsV2];
8
+ };
9
+ readonly warn: (message: string) => {
10
+ readonly components: readonly [ContainerBuilder];
11
+ readonly flags: readonly [MessageFlags.IsComponentsV2];
12
+ };
13
+ };
14
+ //#endregion
15
+ export { components };
16
+ //# sourceMappingURL=components.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"components.d.ts","names":[],"sources":["../src/components.ts"],"sourcesContent":[],"mappings":";;;cA0Ba;;IAAA,SAGH,UAAA,EAAA,SAAA,iBAAA,CAAA"}
@@ -0,0 +1,16 @@
1
+ import { ContainerBuilder, MessageFlags, SeparatorSpacingSize } from "discord.js";
2
+ const error = (message) => ({
3
+ components: [new ContainerBuilder().setAccentColor(16711680).addTextDisplayComponents((t) => t.setContent("**Error**")).addSeparatorComponents((s) => s.setSpacing(SeparatorSpacingSize.Small)).addTextDisplayComponents((t) => t.setContent(message))],
4
+ flags: [MessageFlags.IsComponentsV2]
5
+ });
6
+ const warn = (message) => ({
7
+ components: [new ContainerBuilder().setAccentColor(16760576).addTextDisplayComponents((t) => t.setContent("**Warning**")).addSeparatorComponents((s) => s.setSpacing(SeparatorSpacingSize.Small)).addTextDisplayComponents((t) => t.setContent(message))],
8
+ flags: [MessageFlags.IsComponentsV2]
9
+ });
10
+ const components = {
11
+ error,
12
+ warn
13
+ };
14
+ export { components };
15
+
16
+ //# sourceMappingURL=components.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"components.js","names":[],"sources":["../src/components.ts"],"sourcesContent":["import { ContainerBuilder, MessageFlags, SeparatorSpacingSize } from \"discord.js\"\n\nconst error = (message: string) =>\n ({\n components: [\n new ContainerBuilder()\n .setAccentColor(0xff_00_00)\n .addTextDisplayComponents((t) => t.setContent(\"**Error**\"))\n .addSeparatorComponents((s) => s.setSpacing(SeparatorSpacingSize.Small))\n .addTextDisplayComponents((t) => t.setContent(message)),\n ],\n flags: [MessageFlags.IsComponentsV2],\n }) as const\n\nconst warn = (message: string) =>\n ({\n components: [\n new ContainerBuilder()\n .setAccentColor(0xff_bf_00)\n .addTextDisplayComponents((t) => t.setContent(\"**Warning**\"))\n .addSeparatorComponents((s) => s.setSpacing(SeparatorSpacingSize.Small))\n .addTextDisplayComponents((t) => t.setContent(message)),\n ],\n flags: [MessageFlags.IsComponentsV2],\n }) as const\n\nexport const components = {\n error,\n warn,\n} as const\n"],"mappings":";AAEA,MAAM,SAAS,aACZ;CACC,YAAY,CACV,IAAI,kBAAkB,CACnB,eAAe,SAAW,CAC1B,0BAA0B,MAAM,EAAE,WAAW,YAAY,CAAC,CAC1D,wBAAwB,MAAM,EAAE,WAAW,qBAAqB,MAAM,CAAC,CACvE,0BAA0B,MAAM,EAAE,WAAW,QAAQ,CAAC,CAC1D;CACD,OAAO,CAAC,aAAa,eAAe;CACrC;AAEH,MAAM,QAAQ,aACX;CACC,YAAY,CACV,IAAI,kBAAkB,CACnB,eAAe,SAAW,CAC1B,0BAA0B,MAAM,EAAE,WAAW,cAAc,CAAC,CAC5D,wBAAwB,MAAM,EAAE,WAAW,qBAAqB,MAAM,CAAC,CACvE,0BAA0B,MAAM,EAAE,WAAW,QAAQ,CAAC,CAC1D;CACD,OAAO,CAAC,aAAa,eAAe;CACrC;AAEH,MAAa,aAAa;CACxB;CACA;CACD"}
@@ -1,4 +1,4 @@
1
- import { DiscordContext } from "./types.js";
1
+ import { DiscordContext } from "./bot.js";
2
2
  import { Client, ClientEvents } from "discord.js";
3
3
 
4
4
  //#region src/event-manager.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"event-manager.d.ts","names":[],"sources":["../src/event-manager.ts"],"sourcesContent":[],"mappings":";;;;KAIK,WAAA,SAAoB;KAEpB,yBAAuB,cAAc,wBAChC,iBACC,aAAa,gBACZ;AAPoC,KAS3C,eAAA,GAPW,QAQR,WANS,GAMK,YANL,CAMkB,CANlB,CAAA,EAAW;UASlB,WATgC,CAAA,YASV,WATU,GASI,WATJ,CAAA,CAAA;EAChC,KAAA,EASD,GATC;EACC,OAAA,EASA,eATA,CASgB,GAThB,CAAA;;AACC,cAWC,YAAA,CAXD;EAAO,CAAA,OAAA;EAEd,WAAA,CAAA,OAAe,EAaU,cAbV;EACZ,GAAA,CAAA,UAmBe,WAnBf,CAAA,CAAA,KAAA,EAmBmC,WAnBnC,CAmB+C,CAnB/C,CAAA,CAAA,EAAA,IAAA;EAA2B,OAAA,CAAA,CAAA,EAAA,IAAA;;AAAD,KAuCtB,KAAA,GAvCsB,QAwC1B,WArCa,GAqCC,WArCD,CAqCa,CArCb,CAAA,EAAW,CAsC9B,WAtC8B,CAAA"}
1
+ {"version":3,"file":"event-manager.d.ts","names":[],"sources":["../src/event-manager.ts"],"sourcesContent":[],"mappings":";;;;KAOK,WAAA,SAAoB;KAEpB,yBAAuB,cAAc,wBAChC,iBACC,aAAa,gBACZ;AARkC,KAUzC,eAAA,GAPW,QAQR,WANS,GAMK,YANL,CAMkB,CANlB,CAAA,EAAW;UASlB,WATgC,CAAA,YASV,WATU,GASI,WATJ,CAAA,CAAA;EAChC,KAAA,EASD,GATC;EACC,OAAA,EASA,eATA,CASgB,GAThB,CAAA;;AACC,cAWC,YAAA,CAXD;EAAO,CAAA,OAAA;EAEd,WAAA,CAAA,OAAe,EAaU,cAbV;EACZ,GAAA,CAAA,UAmBe,WAnBf,CAAA,CAAA,KAAA,EAmBmC,WAnBnC,CAmB+C,CAnB/C,CAAA,CAAA,EAAA,IAAA;EAA2B,OAAA,CAAA,CAAA,EAAA,IAAA;;AAAD,KAuCtB,KAAA,GAvCsB,QAwC1B,WArCa,GAqCC,WArCD,CAqCa,CArCb,CAAA,EAAW,CAsC9B,WAtC8B,CAAA"}
@@ -1,3 +1,5 @@
1
+ import { handleCallback } from "./util.js";
2
+ import { attempt, err, isErr } from "ts-explicit-errors";
1
3
  var EventManager = class {
2
4
  #events = [];
3
5
  #discord;
@@ -8,16 +10,13 @@ var EventManager = class {
8
10
  this.#events.push(event);
9
11
  }
10
12
  _listen() {
11
- for (const event of this.#events) {
12
- const listen = async (...args) => {
13
- try {
14
- await event.handler(this.#discord.client, ...args);
15
- } catch (error) {
16
- console.error(error);
17
- }
13
+ this.#events.forEach((singleEvent) => {
14
+ const handleEvent = async (...args) => {
15
+ const eventHandlerResult = await attempt(() => singleEvent.handler(this.#discord.client, ...args));
16
+ if (isErr(eventHandlerResult)) return err(`failed to handle event '${singleEvent.event}'`, eventHandlerResult);
18
17
  };
19
- this.#discord.client.on(event.event, (...args) => void listen(...args));
20
- }
18
+ this.#discord.client.on(singleEvent.event, async (...args) => await handleCallback(() => handleEvent(...args)));
19
+ });
21
20
  console.log(`Listening for (${this.#events.length.toString()}) events.`);
22
21
  }
23
22
  };
@@ -1 +1 @@
1
- {"version":3,"file":"event-manager.js","names":["#events","#discord"],"sources":["../src/event-manager.ts"],"sourcesContent":["import type { Client, ClientEvents } from \"discord.js\"\n\nimport type { DiscordContext } from \"~/types.ts\"\n\ntype ValidEvents = keyof ClientEvents\n\ntype EventHandler<E extends ValidEvents = ValidEvents> = (\n client: Client,\n ...args: ClientEvents[E]\n) => void | Promise<void>\n\ntype EventHandlerMap = {\n [E in ValidEvents]: EventHandler<E>\n}\n\ninterface SingleEvent<E extends ValidEvents = ValidEvents> {\n event: E\n handler: EventHandlerMap[E]\n}\n\nexport class EventManager {\n readonly #events: SingleEvent[] = []\n readonly #discord: DiscordContext\n\n public constructor(discord: DiscordContext) {\n this.#discord = discord\n }\n\n /*\n * Add an event listener\n */\n public add<N extends ValidEvents>(event: SingleEvent<N>): void {\n this.#events.push(event)\n }\n\n public _listen(): void {\n for (const event of this.#events) {\n const listen = async (...args: ClientEvents[typeof event.event]) => {\n try {\n await (event.handler as EventHandler)(this.#discord.client, ...args)\n } catch (error) {\n console.error(error)\n }\n }\n\n this.#discord.client.on(event.event, (...args) => void listen(...args))\n }\n console.log(`Listening for (${this.#events.length.toString()}) events.`)\n }\n}\n\nexport type Event = {\n [E in ValidEvents]: SingleEvent<E>\n}[ValidEvents]\n"],"mappings":"AAoBA,IAAa,eAAb,MAA0B;CACxB,UAAkC,EAAE;CACpC;CAEA,YAAmB,SAAyB;AAC1C,QAAA,UAAgB;;CAMlB,IAAkC,OAA6B;AAC7D,QAAA,OAAa,KAAK,MAAM;;CAG1B,UAAuB;AACrB,OAAK,MAAM,SAAS,MAAA,QAAc;GAChC,MAAM,SAAS,OAAO,GAAG,SAA2C;AAClE,QAAI;AACF,WAAO,MAAM,QAAyB,MAAA,QAAc,QAAQ,GAAG,KAAK;aAC7D,OAAO;AACd,aAAQ,MAAM,MAAM;;;AAIxB,SAAA,QAAc,OAAO,GAAG,MAAM,QAAQ,GAAG,SAAS,KAAK,OAAO,GAAG,KAAK,CAAC;;AAEzE,UAAQ,IAAI,kBAAkB,MAAA,OAAa,OAAO,UAAU,CAAC,WAAW"}
1
+ {"version":3,"file":"event-manager.js","names":["#events","#discord"],"sources":["../src/event-manager.ts"],"sourcesContent":["import type { Client, ClientEvents } from \"discord.js\"\nimport type { Result } from \"ts-explicit-errors\"\nimport { attempt, err, isErr } from \"ts-explicit-errors\"\n\nimport type { DiscordContext } from \"~/bot.ts\"\nimport { handleCallback } from \"~/util.ts\"\n\ntype ValidEvents = keyof ClientEvents\n\ntype EventHandler<E extends ValidEvents = ValidEvents> = (\n client: Client,\n ...args: ClientEvents[E]\n) => void | Promise<void>\n\ntype EventHandlerMap = {\n [E in ValidEvents]: EventHandler<E>\n}\n\ninterface SingleEvent<E extends ValidEvents = ValidEvents> {\n event: E\n handler: EventHandlerMap[E]\n}\n\nexport class EventManager {\n readonly #events: SingleEvent[] = []\n readonly #discord: DiscordContext\n\n public constructor(discord: DiscordContext) {\n this.#discord = discord\n }\n\n /*\n * Add an event listener\n */\n public add<N extends ValidEvents>(event: SingleEvent<N>): void {\n this.#events.push(event)\n }\n\n public _listen(): void {\n this.#events.forEach((singleEvent) => {\n const handleEvent = async (...args: ClientEvents[typeof singleEvent.event]): Promise<Result> => {\n const eventHandlerResult = await attempt(() =>\n (singleEvent.handler as EventHandler)(this.#discord.client, ...args),\n )\n if (isErr(eventHandlerResult)) return err(`failed to handle event '${singleEvent.event}'`, eventHandlerResult)\n }\n\n this.#discord.client.on(singleEvent.event, async (...args) => await handleCallback(() => handleEvent(...args)))\n })\n\n console.log(`Listening for (${this.#events.length.toString()}) events.`)\n }\n}\n\nexport type Event = {\n [E in ValidEvents]: SingleEvent<E>\n}[ValidEvents]\n"],"mappings":";;AAuBA,IAAa,eAAb,MAA0B;CACxB,UAAkC,EAAE;CACpC;CAEA,YAAmB,SAAyB;AAC1C,QAAA,UAAgB;;CAMlB,IAAkC,OAA6B;AAC7D,QAAA,OAAa,KAAK,MAAM;;CAG1B,UAAuB;AACrB,QAAA,OAAa,SAAS,gBAAgB;GACpC,MAAM,cAAc,OAAO,GAAG,SAAkE;IAC9F,MAAM,qBAAqB,MAAM,cAC9B,YAAY,QAAyB,MAAA,QAAc,QAAQ,GAAG,KAAK,CACrE;AACD,QAAI,MAAM,mBAAmB,CAAE,QAAO,IAAI,2BAA2B,YAAY,MAAM,IAAI,mBAAmB;;AAGhH,SAAA,QAAc,OAAO,GAAG,YAAY,OAAO,OAAO,GAAG,SAAS,MAAM,qBAAqB,YAAY,GAAG,KAAK,CAAC,CAAC;IAC/G;AAEF,UAAQ,IAAI,kBAAkB,MAAA,OAAa,OAAO,UAAU,CAAC,WAAW"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { Command, CommandHook } from "./command-manager.js";
1
+ import { Command, CommandHook, CommandRunFn } from "./command-manager.js";
2
2
  import { Event } from "./event-manager.js";
3
3
  import { Bot } from "./bot.js";
4
- import { getChannel, throwError, throwUserError } from "./util.js";
5
- export { Bot, type Command, type CommandHook, type Event, getChannel, throwError, throwUserError };
4
+ import { components } from "./components.js";
5
+ export { Bot, type Command, type CommandHook, type CommandRunFn, type Event, components };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { getChannel, throwError, throwUserError } from "./util.js";
1
+ import { components } from "./components.js";
2
2
  import { Bot } from "./bot.js";
3
- export { Bot, getChannel, throwError, throwUserError };
3
+ export { Bot, components };
package/dist/util.js CHANGED
@@ -1,25 +1,8 @@
1
- async function getChannel(guild, channelNameOrId, channelType) {
2
- const channels = await guild.channels.fetch();
3
- let channel;
4
- channel = channels.find((chan) => chan ? chan.name === channelNameOrId : false);
5
- if (channel && channel.type === channelType) return channel;
6
- channel = channels.get(channelNameOrId);
7
- if (channel && channel.type === channelType) return channel;
8
- throwError(`Failed to get channel: ${channelNameOrId}`);
1
+ import { isErr } from "ts-explicit-errors";
2
+ async function handleCallback(fn) {
3
+ const result = await fn();
4
+ if (isErr(result)) console.error(result.messageChain);
9
5
  }
10
- function throwError(message) {
11
- throw new Error(message);
12
- }
13
- function throwUserError(message) {
14
- throw new UserError(message);
15
- }
16
- var UserError = class extends Error {
17
- constructor(message) {
18
- super(message);
19
- this.name = "UserError";
20
- Object.setPrototypeOf(this, new.target.prototype);
21
- }
22
- };
23
- export { UserError, getChannel, throwError, throwUserError };
6
+ export { handleCallback };
24
7
 
25
8
  //# sourceMappingURL=util.js.map
package/dist/util.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"util.js","names":["channel: NonThreadGuildBasedChannel | undefined | null"],"sources":["../src/util.ts"],"sourcesContent":["import type {\n CategoryChannel,\n ChannelType,\n ForumChannel,\n Guild,\n NewsChannel,\n NonThreadGuildBasedChannel,\n StageChannel,\n TextChannel,\n VoiceChannel,\n} from \"discord.js\"\n\ninterface ChannelTypeToChannelMap {\n [ChannelType.GuildCategory]: CategoryChannel\n [ChannelType.GuildAnnouncement]: NewsChannel\n [ChannelType.GuildStageVoice]: StageChannel\n [ChannelType.GuildText]: TextChannel\n [ChannelType.GuildVoice]: VoiceChannel\n [ChannelType.GuildForum]: ForumChannel\n}\n\n/**\n * Returns the guild channel of the given name/ID and type, otherwise throws.\n *\n * @param guild The Guild to fetch the channel from\n * @param channelNameOrId The name or ID of the channel to fetch\n * @param channelType The type of channel to fetch\n * @returns The channel of the given name/ID and type\n * @example\n * ```ts\n * import { getChannel } from \"discord-bot-shared\"\n * import { ChannelType } from \"discord.js\"\n *\n * // guild is of type Guild from discord.js\n * const someTextChannel = await getChannel(guild, \"some-text-channel\", ChannelType.GuildText)\n * ```\n *\n * Getting a properly typed channel with discord.js can be a bit of a pain, so this is an alternative.\n */\nexport async function getChannel<T extends keyof ChannelTypeToChannelMap>(\n guild: Guild,\n channelNameOrId: string,\n channelType: T,\n): Promise<ChannelTypeToChannelMap[T]> {\n const channels = await guild.channels.fetch()\n\n let channel: NonThreadGuildBasedChannel | undefined | null\n channel = channels.find((chan) => (chan ? chan.name === channelNameOrId : false))\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion\n if (channel && channel.type === channelType) return channel as ChannelTypeToChannelMap[T]\n\n channel = channels.get(channelNameOrId)\n // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion\n if (channel && channel.type === channelType) return channel as ChannelTypeToChannelMap[T]\n\n throwError(`Failed to get channel: ${channelNameOrId}`)\n}\n\n/**\n * Throws an error with the given message.\n *\n * @param message The message to throw\n * @throws Error\n */\nexport function throwError(message: string): never {\n throw new Error(message)\n}\n\n/**\n * Throws a UserError with the given message.\n *\n * @param message The message to throw\n * @throws UserError\n */\nexport function throwUserError(message: string): never {\n throw new UserError(message)\n}\n\nexport class UserError extends Error {\n public constructor(message: string) {\n super(message)\n this.name = \"UserError\"\n\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n"],"mappings":"AAuCA,eAAsB,WACpB,OACA,iBACA,aACqC;CACrC,MAAM,WAAW,MAAM,MAAM,SAAS,OAAO;CAE7C,IAAIA;AACJ,WAAU,SAAS,MAAM,SAAU,OAAO,KAAK,SAAS,kBAAkB,MAAO;AAEjF,KAAI,WAAW,QAAQ,SAAS,YAAa,QAAO;AAEpD,WAAU,SAAS,IAAI,gBAAgB;AAEvC,KAAI,WAAW,QAAQ,SAAS,YAAa,QAAO;AAEpD,YAAW,0BAA0B,kBAAkB;;AASzD,SAAgB,WAAW,SAAwB;AACjD,OAAM,IAAI,MAAM,QAAQ;;AAS1B,SAAgB,eAAe,SAAwB;AACrD,OAAM,IAAI,UAAU,QAAQ;;AAG9B,IAAa,YAAb,cAA+B,MAAM;CACnC,YAAmB,SAAiB;AAClC,QAAM,QAAQ;AACd,OAAK,OAAO;AAEZ,SAAO,eAAe,MAAM,IAAI,OAAO,UAAU"}
1
+ {"version":3,"file":"util.js","names":[],"sources":["../src/util.ts"],"sourcesContent":["import type { Result } from \"ts-explicit-errors\"\nimport { isErr } from \"ts-explicit-errors\"\n\nexport async function handleCallback(fn: () => Result | Promise<Result>) {\n const result = await fn()\n if (isErr(result)) console.error(result.messageChain)\n}\n"],"mappings":";AAGA,eAAsB,eAAe,IAAoC;CACvE,MAAM,SAAS,MAAM,IAAI;AACzB,KAAI,MAAM,OAAO,CAAE,SAAQ,MAAM,OAAO,aAAa"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discord-bot-shared",
3
- "version": "0.15.0",
3
+ "version": "0.16.1",
4
4
  "type": "module",
5
5
  "description": "Modules for creating discord bots.",
6
6
  "repository": {
@@ -36,13 +36,16 @@
36
36
  "devDependencies": {
37
37
  "@adamhl8/configs": "^0.18.0",
38
38
  "@arethetypeswrong/core": "^0.18.2",
39
- "@biomejs/biome": "^2.3.8",
40
- "@types/bun": "^1.3.4",
39
+ "@biomejs/biome": "^2.3.10",
40
+ "@types/bun": "^1.3.5",
41
41
  "discord.js": "^14.25.1",
42
- "knip": "^5.73.1",
42
+ "knip": "^5.76.1",
43
43
  "markdown-toc": "^1.2.0",
44
44
  "publint": "^0.3.16",
45
- "tsdown": "^0.17.2",
45
+ "tsdown": "^0.18.2",
46
46
  "typescript": "^5.9.3"
47
+ },
48
+ "dependencies": {
49
+ "ts-explicit-errors": "^4.1.1"
47
50
  }
48
51
  }
package/dist/types.d.ts DELETED
@@ -1,17 +0,0 @@
1
- import { Client, ClientOptions, REST } from "discord.js";
2
-
3
- //#region src/types.d.ts
4
- interface BotOptions {
5
- applicationId: string;
6
- token: string;
7
- clientOptions: ClientOptions;
8
- }
9
- interface DiscordContext {
10
- applicationId: string;
11
- token: string;
12
- client: Client;
13
- rest: REST;
14
- }
15
- //#endregion
16
- export { BotOptions, DiscordContext };
17
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;;UAEiB,UAAA;;EAAA,KAAA,EAAA,MAAU;EAMV,aAAA,EAHA,aAGc;;UAAd,cAAA;;;UAGP;QACF"}
package/dist/util.d.ts DELETED
@@ -1,47 +0,0 @@
1
- import { CategoryChannel, ChannelType, ForumChannel, Guild, NewsChannel, StageChannel, TextChannel, VoiceChannel } from "discord.js";
2
-
3
- //#region src/util.d.ts
4
- interface ChannelTypeToChannelMap {
5
- [ChannelType.GuildCategory]: CategoryChannel;
6
- [ChannelType.GuildAnnouncement]: NewsChannel;
7
- [ChannelType.GuildStageVoice]: StageChannel;
8
- [ChannelType.GuildText]: TextChannel;
9
- [ChannelType.GuildVoice]: VoiceChannel;
10
- [ChannelType.GuildForum]: ForumChannel;
11
- }
12
- /**
13
- * Returns the guild channel of the given name/ID and type, otherwise throws.
14
- *
15
- * @param guild The Guild to fetch the channel from
16
- * @param channelNameOrId The name or ID of the channel to fetch
17
- * @param channelType The type of channel to fetch
18
- * @returns The channel of the given name/ID and type
19
- * @example
20
- * ```ts
21
- * import { getChannel } from "discord-bot-shared"
22
- * import { ChannelType } from "discord.js"
23
- *
24
- * // guild is of type Guild from discord.js
25
- * const someTextChannel = await getChannel(guild, "some-text-channel", ChannelType.GuildText)
26
- * ```
27
- *
28
- * Getting a properly typed channel with discord.js can be a bit of a pain, so this is an alternative.
29
- */
30
- declare function getChannel<T extends keyof ChannelTypeToChannelMap>(guild: Guild, channelNameOrId: string, channelType: T): Promise<ChannelTypeToChannelMap[T]>;
31
- /**
32
- * Throws an error with the given message.
33
- *
34
- * @param message The message to throw
35
- * @throws Error
36
- */
37
- declare function throwError(message: string): never;
38
- /**
39
- * Throws a UserError with the given message.
40
- *
41
- * @param message The message to throw
42
- * @throws UserError
43
- */
44
- declare function throwUserError(message: string): never;
45
- //#endregion
46
- export { getChannel, throwError, throwUserError };
47
- //# sourceMappingURL=util.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"util.d.ts","names":[],"sources":["../src/util.ts"],"sourcesContent":[],"mappings":";;;UAYU,uBAAA;GACP,WAAA,CAAY,aAAA,GAAgB;EADrB,CAEP,WAAA,CAAY,iBAAA,CAFkB,EAEE,WAFF;EACF,CAE5B,WAAA,CAAY,eAAA,CAFgB,EAEE,YAFF;EAA5B,CAGA,WAAA,CAAY,SAAA,CAHA,EAGY,WAHZ;EACoB,CAGhC,WAAA,CAAY,UAAA,CAHoB,EAGP,YAHO;EAAhC,CAIA,WAAA,CAAY,UAAA,CAJA,EAIa,YAJb;;;;;;;;;;AAyBf;;;;;;;;AAyBA;AAUA;iBAnCsB,2BAA2B,gCACxC,6CAEM,IACZ,QAAQ,wBAAwB;;;;;;;iBAqBnB,UAAA;;;;;;;iBAUA,cAAA"}