discordthing 0.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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +121 -0
  3. package/build/bot.d.ts +37 -0
  4. package/build/bot.js +166 -0
  5. package/build/bot.js.map +1 -0
  6. package/build/commands/command.d.ts +111 -0
  7. package/build/commands/command.js +176 -0
  8. package/build/commands/command.js.map +1 -0
  9. package/build/commands/diff.d.ts +5 -0
  10. package/build/commands/diff.js +42 -0
  11. package/build/commands/diff.js.map +1 -0
  12. package/build/commands/index.d.ts +2 -0
  13. package/build/commands/index.js +2 -0
  14. package/build/commands/index.js.map +1 -0
  15. package/build/commands/registration.d.ts +10 -0
  16. package/build/commands/registration.js +40 -0
  17. package/build/commands/registration.js.map +1 -0
  18. package/build/context.d.ts +9 -0
  19. package/build/context.js +9 -0
  20. package/build/context.js.map +1 -0
  21. package/build/events/index.d.ts +2 -0
  22. package/build/events/index.js +2 -0
  23. package/build/events/index.js.map +1 -0
  24. package/build/events/listener.d.ts +24 -0
  25. package/build/events/listener.js +14 -0
  26. package/build/events/listener.js.map +1 -0
  27. package/build/index.d.ts +7 -0
  28. package/build/index.js +4 -0
  29. package/build/index.js.map +1 -0
  30. package/build/logger.d.ts +8 -0
  31. package/build/logger.js +2 -0
  32. package/build/logger.js.map +1 -0
  33. package/build/options/index.d.ts +3 -0
  34. package/build/options/index.js +2 -0
  35. package/build/options/index.js.map +1 -0
  36. package/build/options/option.d.ts +116 -0
  37. package/build/options/option.js +101 -0
  38. package/build/options/option.js.map +1 -0
  39. package/build/options/options.d.ts +78 -0
  40. package/build/options/options.js +169 -0
  41. package/build/options/options.js.map +1 -0
  42. package/build/permissions.d.ts +17 -0
  43. package/build/permissions.js +38 -0
  44. package/build/permissions.js.map +1 -0
  45. package/build/plugin.d.ts +20 -0
  46. package/build/plugin.js +22 -0
  47. package/build/plugin.js.map +1 -0
  48. package/build/utility-types.d.ts +15 -0
  49. package/build/utility-types.js +2 -0
  50. package/build/utility-types.js.map +1 -0
  51. package/build/utils.d.ts +2 -0
  52. package/build/utils.js +19 -0
  53. package/build/utils.js.map +1 -0
  54. package/package.json +56 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Leon Semmens
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # discordthing
2
+
3
+ ## Description
4
+
5
+ A lightweight framework and collection of plugins for building a discord bot with [`discord.js`](https://www.npmjs.com/package/discord.js)
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install discordthing
11
+ ```
12
+
13
+ ```bash
14
+ pnpm add discordthing
15
+ ```
16
+
17
+ ## Create a bot
18
+
19
+ ```ts
20
+ import { createBot, createCommandExecutor, okMessage } from "discordthing";
21
+ import { EmbedBuilder, Colors } from "discord.js";
22
+
23
+ import { commands } from "./commands";
24
+ import { eventListeners } from "./eventListeners";
25
+
26
+ const commandExecutor = createCommandExecutor({
27
+ getErrorMessage(msg, _err, _interaction) {
28
+ const embed = new EmbedBuilder()
29
+ .setColor(Colors.Red)
30
+ .setTitle(":warning: Error")
31
+ .setDescription(msg);
32
+
33
+ return okMessage({
34
+ embeds: [embed]
35
+ });
36
+ }
37
+ });
38
+
39
+ const client = createBot({
40
+ clientOptions: {
41
+ intents: ["Guilds", "GuildMembers"]
42
+ },
43
+ commandExecutor,
44
+ commands,
45
+ eventListeners
46
+ });
47
+
48
+ client.login(process.env.DISCORD_TOKEN);
49
+ ```
50
+
51
+ ### Define a command
52
+
53
+ ```ts
54
+ // hello-world.command.ts
55
+
56
+ import { defineCommand } from "discordthing";
57
+ import { Result } from "@l3dev/result";
58
+
59
+ export default defineCommand({
60
+ name: "hello-world",
61
+ define(builder) {
62
+ return builder.setName(this.name).setDescription("Say hello!");
63
+ },
64
+ async execute(interaction) {
65
+ return await Result.fromPromise(
66
+ { onError: { type: "HELLO_WORLD_REPLY" } },
67
+ interaction.reply("Hello world!")
68
+ );
69
+ }
70
+ });
71
+ ```
72
+
73
+ Export commands as a single object:
74
+
75
+ ```ts
76
+ // commands.ts
77
+
78
+ export const commands = {
79
+ helloWorld: await import("./hello-world.command")
80
+ };
81
+
82
+ // or if you are using a bundler like Vite:
83
+
84
+ export const commands = import.meta.glob<true, string>("./*.command.ts", { eager: true });
85
+ ```
86
+
87
+ ### Define an event listener
88
+
89
+ ```ts
90
+ // welcome.event.ts
91
+
92
+ import { defineEventListener } from "@l3dev/discord.js-helpers";
93
+ import { Result } from "@l3dev/result";
94
+ import { Events } from "discord.js";
95
+
96
+ export default defineEventListener({
97
+ event: Events.GuildMemberAdd,
98
+ async listener(member) {
99
+ const joinChannel = await getJoinChannel(member.guild);
100
+
101
+ return await Result.fromPromise(
102
+ { onError: { type: "WELCOME_MESSAGE" } },
103
+ joinChannel.send(`Welcome <@${member.user.id}>!`)
104
+ );
105
+ }
106
+ });
107
+ ```
108
+
109
+ Export event listeners as a single object:
110
+
111
+ ```ts
112
+ // eventListeners.ts
113
+
114
+ export const eventListeners = {
115
+ welcome: await import("./welcome.event")
116
+ };
117
+
118
+ // or if you are using a bundler like Vite:
119
+
120
+ export const eventListeners = import.meta.glob<true, string>("./*.event.ts", { eager: true });
121
+ ```
package/build/bot.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { type ReturnResult } from "@l3dev/result";
2
+ import { Client, REST, SlashCommandBuilder, type ClientOptions, type Guild, type RestOrArray } from "discord.js";
3
+ import type { Command, CommandFilter } from "./commands/command.js";
4
+ import type { Listener } from "./events/listener.js";
5
+ import type { ILogger } from "./logger.js";
6
+ import type { Plugin } from "./plugin.js";
7
+ export type BotOptions = {
8
+ name?: string;
9
+ plugins?: Plugin[];
10
+ commands?: Command<string, SlashCommandBuilder>[];
11
+ listeners?: Listener<any>[];
12
+ logger?: ILogger;
13
+ };
14
+ export declare class Bot {
15
+ readonly client: Client<boolean>;
16
+ private _rest;
17
+ get rest(): REST;
18
+ private readonly plugins;
19
+ private readonly commands;
20
+ private readonly commandFilters;
21
+ private readonly readyListeners;
22
+ private readonly logger;
23
+ constructor(clientOptions: ClientOptions, options?: BotOptions);
24
+ addCommands(...commands: RestOrArray<Command<string, SlashCommandBuilder>>): void;
25
+ addCommand(command: Command<string, SlashCommandBuilder>): void;
26
+ addCommandFilter(filter: CommandFilter): void;
27
+ addListeners(...listeners: RestOrArray<Listener<any>>): void;
28
+ addListener(listener: Listener<any>): void;
29
+ private createListenerFn;
30
+ start(token: string): Promise<void>;
31
+ getMe(guild: Guild): Promise<ReturnResult<import("discord.js").GuildMember, import("@l3dev/result").ResultErrorDefinition<"GET_ME", object & {
32
+ error: Error;
33
+ }>>>;
34
+ private registerCommands;
35
+ private onReady;
36
+ private onInteraction;
37
+ }
package/build/bot.js ADDED
@@ -0,0 +1,166 @@
1
+ import { err, ok, Result } from "@l3dev/result";
2
+ import { Client, Events, InteractionContextType, REST, SlashCommandBuilder } from "discord.js";
3
+ import { Logger } from "tslog";
4
+ import { CommandRegistration } from "./commands/registration.js";
5
+ import { createContext } from "./context.js";
6
+ export class Bot {
7
+ client;
8
+ _rest;
9
+ get rest() {
10
+ return this._rest;
11
+ }
12
+ plugins;
13
+ commands;
14
+ commandFilters = [];
15
+ readyListeners = [];
16
+ logger;
17
+ constructor(clientOptions, options = {}) {
18
+ this._rest = new REST({ version: "10" });
19
+ this.client = new Client(clientOptions);
20
+ this.plugins = (options.plugins ?? []).map((plugin) => plugin(options));
21
+ this.logger =
22
+ options.logger ??
23
+ new Logger({
24
+ name: options.name ?? "discordthing-bot"
25
+ });
26
+ for (const plugin of this.plugins) {
27
+ plugin.resolved(this);
28
+ }
29
+ this.commands = [
30
+ ...(options.commands ?? []),
31
+ ...this.plugins.flatMap((plugin) => plugin.commands)
32
+ ].reduce((acc, command) => ({
33
+ ...acc,
34
+ [command.name]: command
35
+ }), {});
36
+ this.client.on(Events.ClientReady, this.onReady.bind(this));
37
+ this.client.on(Events.InteractionCreate, this.onInteraction.bind(this));
38
+ const allListeners = [
39
+ ...(options.listeners ?? []),
40
+ ...this.plugins.flatMap((plugin) => plugin.listeners)
41
+ ];
42
+ for (const listener of allListeners) {
43
+ if (listener.event === Events.ClientReady) {
44
+ this.readyListeners.push(listener);
45
+ continue;
46
+ }
47
+ this.client.on(listener.event, this.createListenerFn(listener));
48
+ }
49
+ }
50
+ addCommands(...commands) {
51
+ for (const command of commands.flat()) {
52
+ this.addCommand(command);
53
+ }
54
+ }
55
+ addCommand(command) {
56
+ if (this.client.isReady()) {
57
+ throw new Error("Cannot add command after bot has started");
58
+ }
59
+ this.commands[command.name] = command;
60
+ }
61
+ addCommandFilter(filter) {
62
+ this.commandFilters.push(filter);
63
+ }
64
+ addListeners(...listeners) {
65
+ for (const listener of listeners.flat()) {
66
+ this.addListener(listener);
67
+ }
68
+ }
69
+ addListener(listener) {
70
+ if (this.client.isReady()) {
71
+ throw new Error("Cannot add listener after bot has started");
72
+ }
73
+ if (listener.event === Events.ClientReady) {
74
+ this.readyListeners.push(listener);
75
+ return;
76
+ }
77
+ (listener.once ? this.client.once : this.client.on)(listener.event, this.createListenerFn(listener));
78
+ }
79
+ createListenerFn(listener) {
80
+ return async (...args) => {
81
+ const ctx = createContext(this, this.logger);
82
+ listener.handler(ctx, ...args);
83
+ };
84
+ }
85
+ async start(token) {
86
+ this._rest = this._rest.setToken(token);
87
+ await this.client.login(token);
88
+ }
89
+ async getMe(guild) {
90
+ if (guild.members.me) {
91
+ return ok(guild.members.me);
92
+ }
93
+ return await Result.fromPromise(guild.members.fetchMe(), {
94
+ onError: { type: "GET_ME" }
95
+ });
96
+ }
97
+ async registerCommands(client) {
98
+ const commandRegistration = new CommandRegistration(this._rest, client);
99
+ const globalCommands = Object.values(this.commands).filter((command) => {
100
+ const contexts = command.getContexts();
101
+ return (contexts.includes(InteractionContextType.BotDM) ||
102
+ contexts.includes(InteractionContextType.PrivateChannel));
103
+ });
104
+ const guildCommands = Object.values(this.commands).filter((command) => {
105
+ const contexts = command.getContexts();
106
+ return contexts.includes(InteractionContextType.Guild);
107
+ });
108
+ this.logger.debug(`Registering global commands...`);
109
+ await commandRegistration.registerGlobalCommands(globalCommands);
110
+ const guilds = await client.guilds.fetch();
111
+ for (const guild of guilds.values()) {
112
+ this.logger.debug(`Registering guild commands for guild ${guild.id}...`);
113
+ await commandRegistration.registerGuildCommands(guild.id, guildCommands);
114
+ }
115
+ }
116
+ async onReady(client) {
117
+ const startMs = performance.now();
118
+ await this.registerCommands(client);
119
+ // Make a copy so we can remove _once_ listeners
120
+ const listeners = [...this.readyListeners];
121
+ for (const listener of listeners) {
122
+ const handler = this.createListenerFn(listener);
123
+ handler(client);
124
+ if (listener.once) {
125
+ const index = this.readyListeners.indexOf(listener);
126
+ if (index === -1) {
127
+ return;
128
+ }
129
+ this.readyListeners.splice(index, 1);
130
+ }
131
+ }
132
+ const endMs = performance.now();
133
+ const elapsedMs = endMs - startMs;
134
+ this.logger.info(`Ready! (${elapsedMs > 1000 ? `${(elapsedMs / 1000).toFixed(2)}s` : `${elapsedMs}ms`})`);
135
+ }
136
+ async onInteraction(interaction) {
137
+ if (interaction.user.bot || !interaction.isChatInputCommand())
138
+ return;
139
+ const command = this.commands[interaction.commandName];
140
+ if (!command) {
141
+ this.logger.error(`Unknown command '${interaction.commandName}'`);
142
+ return;
143
+ }
144
+ if (!this.commandFilters.some((filter) => filter(interaction, command))) {
145
+ return;
146
+ }
147
+ const ctx = createContext(this, this.logger);
148
+ ctx.interaction = interaction;
149
+ let result;
150
+ try {
151
+ result = await command.execute(ctx);
152
+ }
153
+ catch (error) {
154
+ result = err("COMMAND_FAILED", {
155
+ command: interaction.commandName,
156
+ interactionId: interaction.id,
157
+ error
158
+ });
159
+ }
160
+ const unwrapped = Result.unwrap(result);
161
+ if (!unwrapped.ok) {
162
+ this.logger.error(`Error while executing command '${interaction.commandName}':`, result);
163
+ }
164
+ }
165
+ }
166
+ //# sourceMappingURL=bot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bot.js","sourceRoot":"","sources":["../src/bot.ts"],"names":[],"mappings":"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","sourcesContent":["import { err, ok, Result, type ReturnResult } from \"@l3dev/result\";\nimport {\n\tClient,\n\tEvents,\n\tInteractionContextType,\n\tREST,\n\tSlashCommandBuilder,\n\ttype ClientOptions,\n\ttype Guild,\n\ttype Interaction,\n\ttype RestOrArray\n} from \"discord.js\";\nimport { Logger } from \"tslog\";\n\nimport type { Command, CommandCtx, CommandFilter } from \"./commands/command.js\";\nimport { CommandRegistration } from \"./commands/registration.js\";\nimport { createContext } from \"./context.js\";\nimport type { Listener } from \"./events/listener.js\";\nimport type { ILogger } from \"./logger.js\";\nimport type { Plugin, ResolvedPlugin } from \"./plugin.js\";\n\nexport type BotOptions = {\n\tname?: string;\n\tplugins?: Plugin[];\n\tcommands?: Command<string, SlashCommandBuilder>[];\n\tlisteners?: Listener<any>[];\n\tlogger?: ILogger;\n};\n\nexport class Bot {\n\treadonly client: Client<boolean>;\n\tprivate _rest: REST;\n\n\tget rest() {\n\t\treturn this._rest;\n\t}\n\n\tprivate readonly plugins: ResolvedPlugin[];\n\tprivate readonly commands: Record<string, Command<string, SlashCommandBuilder>>;\n\tprivate readonly commandFilters: CommandFilter[] = [];\n\tprivate readonly readyListeners: Listener<\"clientReady\">[] = [];\n\tprivate readonly logger: ILogger;\n\n\tconstructor(clientOptions: ClientOptions, options: BotOptions = {}) {\n\t\tthis._rest = new REST({ version: \"10\" });\n\t\tthis.client = new Client(clientOptions);\n\n\t\tthis.plugins = (options.plugins ?? []).map((plugin) => plugin(options));\n\t\tthis.logger =\n\t\t\toptions.logger ??\n\t\t\tnew Logger({\n\t\t\t\tname: options.name ?? \"discordthing-bot\"\n\t\t\t});\n\n\t\tfor (const plugin of this.plugins) {\n\t\t\tplugin.resolved(this);\n\t\t}\n\n\t\tthis.commands = [\n\t\t\t...(options.commands ?? []),\n\t\t\t...this.plugins.flatMap((plugin) => plugin.commands)\n\t\t].reduce(\n\t\t\t(acc, command) => ({\n\t\t\t\t...acc,\n\t\t\t\t[command.name]: command\n\t\t\t}),\n\t\t\t{}\n\t\t);\n\n\t\tthis.client.on(Events.ClientReady, this.onReady.bind(this));\n\t\tthis.client.on(Events.InteractionCreate, this.onInteraction.bind(this));\n\n\t\tconst allListeners = [\n\t\t\t...(options.listeners ?? []),\n\t\t\t...this.plugins.flatMap((plugin) => plugin.listeners)\n\t\t];\n\t\tfor (const listener of allListeners) {\n\t\t\tif (listener.event === Events.ClientReady) {\n\t\t\t\tthis.readyListeners.push(listener);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tthis.client.on(listener.event, this.createListenerFn(listener));\n\t\t}\n\t}\n\n\taddCommands(...commands: RestOrArray<Command<string, SlashCommandBuilder>>) {\n\t\tfor (const command of commands.flat()) {\n\t\t\tthis.addCommand(command);\n\t\t}\n\t}\n\n\taddCommand(command: Command<string, SlashCommandBuilder>) {\n\t\tif (this.client.isReady()) {\n\t\t\tthrow new Error(\"Cannot add command after bot has started\");\n\t\t}\n\n\t\tthis.commands[command.name] = command;\n\t}\n\n\taddCommandFilter(filter: CommandFilter) {\n\t\tthis.commandFilters.push(filter);\n\t}\n\n\taddListeners(...listeners: RestOrArray<Listener<any>>) {\n\t\tfor (const listener of listeners.flat()) {\n\t\t\tthis.addListener(listener);\n\t\t}\n\t}\n\n\taddListener(listener: Listener<any>) {\n\t\tif (this.client.isReady()) {\n\t\t\tthrow new Error(\"Cannot add listener after bot has started\");\n\t\t}\n\n\t\tif (listener.event === Events.ClientReady) {\n\t\t\tthis.readyListeners.push(listener);\n\t\t\treturn;\n\t\t}\n\n\t\t(listener.once ? this.client.once : this.client.on)(\n\t\t\tlistener.event,\n\t\t\tthis.createListenerFn(listener)\n\t\t);\n\t}\n\n\tprivate createListenerFn(listener: Listener<any>) {\n\t\treturn async (...args: any[]) => {\n\t\t\tconst ctx = createContext(this, this.logger);\n\t\t\tlistener.handler(ctx, ...args);\n\t\t};\n\t}\n\n\tasync start(token: string) {\n\t\tthis._rest = this._rest.setToken(token);\n\t\tawait this.client.login(token);\n\t}\n\n\tasync getMe(guild: Guild) {\n\t\tif (guild.members.me) {\n\t\t\treturn ok(guild.members.me);\n\t\t}\n\n\t\treturn await Result.fromPromise(guild.members.fetchMe(), {\n\t\t\tonError: { type: \"GET_ME\" }\n\t\t});\n\t}\n\n\tprivate async registerCommands(client: Client<true>) {\n\t\tconst commandRegistration = new CommandRegistration(this._rest, client);\n\n\t\tconst globalCommands = Object.values(this.commands).filter((command) => {\n\t\t\tconst contexts = command.getContexts();\n\t\t\treturn (\n\t\t\t\tcontexts.includes(InteractionContextType.BotDM) ||\n\t\t\t\tcontexts.includes(InteractionContextType.PrivateChannel)\n\t\t\t);\n\t\t});\n\n\t\tconst guildCommands = Object.values(this.commands).filter((command) => {\n\t\t\tconst contexts = command.getContexts();\n\t\t\treturn contexts.includes(InteractionContextType.Guild);\n\t\t});\n\n\t\tthis.logger.debug(`Registering global commands...`);\n\t\tawait commandRegistration.registerGlobalCommands(globalCommands);\n\n\t\tconst guilds = await client.guilds.fetch();\n\t\tfor (const guild of guilds.values()) {\n\t\t\tthis.logger.debug(`Registering guild commands for guild ${guild.id}...`);\n\t\t\tawait commandRegistration.registerGuildCommands(guild.id, guildCommands);\n\t\t}\n\t}\n\n\tprivate async onReady(client: Client<true>) {\n\t\tconst startMs = performance.now();\n\n\t\tawait this.registerCommands(client);\n\n\t\t// Make a copy so we can remove _once_ listeners\n\t\tconst listeners = [...this.readyListeners];\n\n\t\tfor (const listener of listeners) {\n\t\t\tconst handler = this.createListenerFn(listener);\n\t\t\thandler(client);\n\n\t\t\tif (listener.once) {\n\t\t\t\tconst index = this.readyListeners.indexOf(listener);\n\t\t\t\tif (index === -1) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.readyListeners.splice(index, 1);\n\t\t\t}\n\t\t}\n\n\t\tconst endMs = performance.now();\n\t\tconst elapsedMs = endMs - startMs;\n\n\t\tthis.logger.info(\n\t\t\t`Ready! (${elapsedMs > 1000 ? `${(elapsedMs / 1000).toFixed(2)}s` : `${elapsedMs}ms`})`\n\t\t);\n\t}\n\n\tprivate async onInteraction(interaction: Interaction) {\n\t\tif (interaction.user.bot || !interaction.isChatInputCommand()) return;\n\n\t\tconst command = this.commands[interaction.commandName];\n\t\tif (!command) {\n\t\t\tthis.logger.error(`Unknown command '${interaction.commandName}'`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.commandFilters.some((filter) => filter(interaction, command))) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst ctx = createContext(this, this.logger) as CommandCtx;\n\t\tctx.interaction = interaction;\n\n\t\tlet result;\n\t\ttry {\n\t\t\tresult = await command.execute(ctx);\n\t\t} catch (error) {\n\t\t\tresult = err(\"COMMAND_FAILED\", {\n\t\t\t\tcommand: interaction.commandName,\n\t\t\t\tinteractionId: interaction.id,\n\t\t\t\terror\n\t\t\t});\n\t\t}\n\n\t\tconst unwrapped = Result.unwrap(result) as ReturnResult<any, any>;\n\t\tif (!unwrapped.ok) {\n\t\t\tthis.logger.error(`Error while executing command '${interaction.commandName}':`, result);\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,111 @@
1
+ import { type ReturnResult } from "@l3dev/result";
2
+ import { InteractionContextType, SlashCommandBuilder, SlashCommandSubcommandBuilder, type ChatInputCommandInteraction, type SharedNameAndDescription, type SharedSlashCommandOptions } from "discord.js";
3
+ import type { GenericCtx } from "../context.js";
4
+ import type { Args, OptionDefinitions } from "../options/index.js";
5
+ import type { Expand, MaybePromise } from "../utility-types.js";
6
+ export type CommandCtx = Expand<GenericCtx & {
7
+ interaction: ChatInputCommandInteraction;
8
+ }>;
9
+ export type ArgsArrayForOptions<Options extends OptionDefinitions> = [Options] extends [
10
+ OptionDefinitions
11
+ ] ? [args: Args<Options>] : [];
12
+ export type CommandMeta<Name extends string> = {
13
+ /**
14
+ * The name of the command.
15
+ */
16
+ name: Name;
17
+ /**
18
+ * The description of the command.
19
+ */
20
+ description?: string;
21
+ /**
22
+ * Whether the command is NSFW.
23
+ */
24
+ nsfw?: boolean;
25
+ /**
26
+ * The contexts in which the command can be used.
27
+ *
28
+ * @default [InteractionContextType.Guild]
29
+ */
30
+ contexts?: InteractionContextType[];
31
+ };
32
+ export type SubcommandMeta<Name extends string> = {
33
+ /**
34
+ * The name of the command.
35
+ */
36
+ name: Name;
37
+ /**
38
+ * The description of the command.
39
+ */
40
+ description?: string;
41
+ };
42
+ export declare class Command<Name extends string, Builder extends SharedNameAndDescription & SharedSlashCommandOptions<any>> {
43
+ protected builder: Builder;
44
+ readonly name: Name;
45
+ private options;
46
+ private subcommands;
47
+ private handler;
48
+ constructor(builder: Builder, meta: CommandMeta<Name>);
49
+ execute(ctx: CommandCtx): Promise<ReturnResult<any, any>>;
50
+ }
51
+ export declare function command<Name extends string, Options extends OptionDefinitions>(command: {
52
+ /**
53
+ * Metadata for this command.
54
+ */
55
+ meta: CommandMeta<Name>;
56
+ /**
57
+ * Options for the command.
58
+ *
59
+ * Examples:
60
+ *
61
+ * ```
62
+ * options: {}
63
+ * options: { message: o.string() }
64
+ * options: { message: o.string().union(o.choice("hello"), o.choice("world")) }
65
+ * options: { message: o.string(), count: o.integer() }
66
+ * ```
67
+ */
68
+ options?: Options;
69
+ /**
70
+ * The implementation for this command.
71
+ */
72
+ handler: (ctx: CommandCtx, ...args: ArgsArrayForOptions<Options>) => MaybePromise<ReturnResult<any, any>>;
73
+ }): Command<Name, SlashCommandBuilder>;
74
+ export declare function command<Name extends string>(command: {
75
+ /**
76
+ * Metadata for this command.
77
+ */
78
+ meta: CommandMeta<Name>;
79
+ /**
80
+ * Subcommands for the command.
81
+ */
82
+ subcommands: Command<string, SlashCommandSubcommandBuilder>[];
83
+ /**
84
+ * The middleware for this command with subcommands.
85
+ */
86
+ middleware?: (ctx: CommandCtx) => MaybePromise<ReturnResult<boolean, any>>;
87
+ }): Command<Name, SlashCommandBuilder>;
88
+ export declare function subcommand<Name extends string, Options extends OptionDefinitions>(command: {
89
+ /**
90
+ * Metadata for this command.
91
+ */
92
+ meta: SubcommandMeta<Name>;
93
+ /**
94
+ * Options for the command.
95
+ *
96
+ * Examples:
97
+ *
98
+ * ```
99
+ * options: {}
100
+ * options: { message: o.string() }
101
+ * options: { message: o.string().union(o.choice("hello"), o.choice("world")) }
102
+ * options: { message: o.string(), count: o.integer() }
103
+ * ```
104
+ */
105
+ options?: Options;
106
+ /**
107
+ * The implementation for this command.
108
+ */
109
+ handler: (ctx: CommandCtx, ...args: ArgsArrayForOptions<Options>) => MaybePromise<ReturnResult<any, any>>;
110
+ }): Command<Name, SlashCommandSubcommandBuilder>;
111
+ export type CommandFilter = (interaction: ChatInputCommandInteraction, command: Command<string, SlashCommandBuilder>) => boolean;
@@ -0,0 +1,176 @@
1
+ import { err, NONE } from "@l3dev/result";
2
+ import { ApplicationCommandOptionType, InteractionContextType, SlashCommandBuilder, SlashCommandSubcommandBuilder } from "discord.js";
3
+ import { AttachmentOption, BooleanOption, BuilderOption, ChannelOption, IntegerOption, MentionableOption, NumberOption, RoleOption, StringOption, UserOption } from "../options/options.js";
4
+ export class Command {
5
+ builder;
6
+ name;
7
+ options = null;
8
+ subcommands = null;
9
+ handler = null;
10
+ constructor(builder, meta) {
11
+ this.builder = builder;
12
+ this.name = meta.name;
13
+ builder = builder.setName(meta.name);
14
+ if (meta.description) {
15
+ builder = builder.setDescription(meta.description);
16
+ }
17
+ if (builder instanceof SlashCommandBuilder) {
18
+ let commandBuilder = builder;
19
+ if (meta.nsfw) {
20
+ commandBuilder = commandBuilder.setNSFW(meta.nsfw);
21
+ }
22
+ commandBuilder = commandBuilder.setContexts(meta.contexts ?? [InteractionContextType.Guild]);
23
+ builder = commandBuilder;
24
+ }
25
+ this.builder = builder;
26
+ }
27
+ async execute(ctx) {
28
+ if (this.subcommands) {
29
+ const continueResult = this.handler ? await this.handler(ctx) : NONE;
30
+ if (!continueResult.ok || !continueResult.value) {
31
+ return continueResult;
32
+ }
33
+ const subcommandName = ctx.interaction.options.getSubcommand(true);
34
+ const subcommand = this.subcommands[subcommandName];
35
+ if (!subcommand) {
36
+ return err("UNKNOWN_SUBCOMMAND", {
37
+ name: subcommandName
38
+ });
39
+ }
40
+ return await subcommand.execute(ctx);
41
+ }
42
+ const args = this.parseOptions(ctx.interaction.options);
43
+ return await this.handler(ctx, args);
44
+ }
45
+ /** @internal */
46
+ parseOptions(options) {
47
+ const args = {};
48
+ for (const [name, option] of Object.entries(this.options ?? {})) {
49
+ const required = option.isOptional === "required";
50
+ let value = null;
51
+ if (options.getAttachment && option instanceof AttachmentOption) {
52
+ value = options.getAttachment(name, required);
53
+ }
54
+ else if (options.getBoolean && option instanceof BooleanOption) {
55
+ value = options.getBoolean(name, required);
56
+ }
57
+ else if (options.getChannel && option instanceof ChannelOption) {
58
+ value = options.getChannel(name, required, option.getBuilder().channel_types);
59
+ }
60
+ else if (options.getInteger && option instanceof IntegerOption) {
61
+ value = options.getInteger(name, required);
62
+ }
63
+ else if (options.getMentionable && option instanceof MentionableOption) {
64
+ value = options.getMentionable(name, required);
65
+ }
66
+ else if (options.getNumber && option instanceof NumberOption) {
67
+ value = options.getNumber(name, required);
68
+ }
69
+ else if (options.getRole && option instanceof RoleOption) {
70
+ value = options.getRole(name, required);
71
+ }
72
+ else if (options.getString && option instanceof StringOption) {
73
+ value = options.getString(name, required);
74
+ }
75
+ else if (options.getUser && option instanceof UserOption) {
76
+ value = options.getUser(name, required);
77
+ }
78
+ if (value === null || value === undefined) {
79
+ continue;
80
+ }
81
+ args[name] = value;
82
+ }
83
+ return args;
84
+ }
85
+ /** @internal */
86
+ getContexts() {
87
+ if (!(this.builder instanceof SlashCommandBuilder)) {
88
+ return [];
89
+ }
90
+ return this.builder.contexts ?? [InteractionContextType.Guild];
91
+ }
92
+ /** @internal */
93
+ getBuilder() {
94
+ return this.builder;
95
+ }
96
+ /** @internal */
97
+ setOptions(options) {
98
+ this.options = options;
99
+ for (const [name, option] of Object.entries(options)) {
100
+ if (option instanceof BuilderOption) {
101
+ this.addOption(name, option);
102
+ }
103
+ }
104
+ }
105
+ /** @internal */
106
+ addOption(name, option) {
107
+ let optionBuilder = option.getBuilder().setName(name);
108
+ if (!optionBuilder.description) {
109
+ optionBuilder = optionBuilder.setDescription(`${name} option.`);
110
+ }
111
+ if (optionBuilder.type === ApplicationCommandOptionType.Attachment) {
112
+ this.builder = this.builder.addAttachmentOption(optionBuilder);
113
+ }
114
+ else if (optionBuilder.type === ApplicationCommandOptionType.Boolean) {
115
+ this.builder = this.builder.addBooleanOption(optionBuilder);
116
+ }
117
+ else if (optionBuilder.type === ApplicationCommandOptionType.Channel) {
118
+ this.builder = this.builder.addChannelOption(optionBuilder);
119
+ }
120
+ else if (optionBuilder.type === ApplicationCommandOptionType.Integer) {
121
+ this.builder = this.builder.addIntegerOption(optionBuilder);
122
+ }
123
+ else if (optionBuilder.type === ApplicationCommandOptionType.Mentionable) {
124
+ this.builder = this.builder.addMentionableOption(optionBuilder);
125
+ }
126
+ else if (optionBuilder.type === ApplicationCommandOptionType.Number) {
127
+ this.builder = this.builder.addNumberOption(optionBuilder);
128
+ }
129
+ else if (optionBuilder.type === ApplicationCommandOptionType.Role) {
130
+ this.builder = this.builder.addRoleOption(optionBuilder);
131
+ }
132
+ else if (optionBuilder.type === ApplicationCommandOptionType.String) {
133
+ this.builder = this.builder.addStringOption(optionBuilder);
134
+ }
135
+ else if (optionBuilder.type === ApplicationCommandOptionType.User) {
136
+ this.builder = this.builder.addUserOption(optionBuilder);
137
+ }
138
+ }
139
+ /** @internal */
140
+ setSubcommands(subcommands) {
141
+ if (!(this.builder instanceof SlashCommandBuilder)) {
142
+ throw new Error("Subcommands can only be set on a SlashCommandBuilder");
143
+ }
144
+ this.subcommands = subcommands.reduce((acc, subcommand) => ({
145
+ ...acc,
146
+ [subcommand.name]: subcommand
147
+ }), {});
148
+ }
149
+ /** @internal */
150
+ setHandler(handler) {
151
+ this.handler = handler;
152
+ }
153
+ }
154
+ export function command(data) {
155
+ const command = new Command(new SlashCommandBuilder(), data.meta);
156
+ if ("options" in data && data.options) {
157
+ command.setOptions(data.options);
158
+ command.setHandler(data.handler);
159
+ }
160
+ else if ("subcommands" in data) {
161
+ command.setSubcommands(data.subcommands);
162
+ if (data.middleware) {
163
+ command.setHandler(data.middleware);
164
+ }
165
+ }
166
+ return command;
167
+ }
168
+ export function subcommand(command) {
169
+ const subcommand = new Command(new SlashCommandSubcommandBuilder(), command.meta);
170
+ if (command.options) {
171
+ subcommand.setOptions(command.options);
172
+ }
173
+ subcommand.setHandler(command.handler);
174
+ return subcommand;
175
+ }
176
+ //# sourceMappingURL=command.js.map