@somehiddenkey/discord-command-utils 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -144,6 +144,30 @@ The same exists for:
144
144
  - Selection menu : `OnSelectMenu`
145
145
  - Message : `OnMessageCommand`
146
146
  - Modal submission : `OnModal`
147
+
148
+ A slash command with a specific subcommand can be defined as follows:
149
+ ```js
150
+ Interaction.OnSlashCommand(
151
+ {name: "ping", subcommand: "webping"},
152
+ InteractionScope.Main,
153
+ async (interaction) => await interaction.reply("pong")
154
+ );
155
+ ```
156
+ ### Callback arguments
157
+ #### Message arguments
158
+ Message commands start with a prefix, after which the proceeding arguments are the arguments. E.g. the message `!ping "google.com"` would trigger `async (interaction, website) => //...`
159
+
160
+ #### Interaction IDs
161
+ You can burry hidden information in the custom ID of a selection menu, button and modal submission by concatting them to them to the custom ID. The helper function `Interaction.makeCustomId()` exists for this. This is handy to e.g. hide session IDs as not to have to recompute them. Example:
162
+ ```js
163
+ new ButtonBuilder().setCustomId(Interaction.makeCustomId("ping", session_id))
164
+
165
+ Interaction.OnButton(
166
+ "ping", InteractionScope.Main,
167
+ async (interaction, session_id) => await interaction.reply(`pong from ${session_id}`)
168
+ );
169
+ ```
170
+
147
171
  ### Call Interaction
148
172
  All interactions get stored in a so-called `InteractionContainer` that still needs to be called:
149
173
  ```js
@@ -164,7 +188,7 @@ For slash commands to work, you need to register them to a bot. There are two ki
164
188
 
165
189
  You can add a set of slash commands through the following abstraction:
166
190
  ```js
167
- const rest = interactionContainer.Rest
191
+ const rest = await interactionContainer.Rest
168
192
  .add([firstSlashCommand, secondSlashCommand], localConfig.guilds.tutor)
169
193
  .add([firstSlashCommand, secondSlashCommand], localConfig.guilds.community)
170
194
  .add([somePublicSlashCommand])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@somehiddenkey/discord-command-utils",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "A utility library for building Discord bot commands using discord.js",
5
5
  "author": {
6
6
  "email": "k3y.throwaway@gmail.com",
@@ -35,7 +35,7 @@ export default class Fetchable {
35
35
  };
36
36
  }
37
37
 
38
- if (prop === "fetch") {
38
+ if (prop === "fetchFromBot") {
39
39
  return async () => {
40
40
  if (!cachedObject){
41
41
  cachedObject = await fetcher(id);
@@ -60,13 +60,13 @@ export default class GlobalConfig {
60
60
  */
61
61
  get_scope(guild_id) {
62
62
  switch(guild_id) {
63
- case this.main:
63
+ case this.main.guild.id:
64
64
  return InteractionScope.Main;
65
- case this.community:
65
+ case this.community.guild.id:
66
66
  return InteractionScope.Community;
67
- case this.tutor:
67
+ case this.tutor.guild.id:
68
68
  return InteractionScope.Tutor;
69
- case this.staff:
69
+ case this.staff.guild.id:
70
70
  return InteractionScope.Staff;
71
71
  default:
72
72
  return InteractionScope.OtherGuild;
@@ -15,11 +15,11 @@ const {
15
15
  /**
16
16
  * @typedef { (interaction: DiscordBaseInteraction<CacheType>) => Promise<any> } InteractionFunction
17
17
  * @typedef { (message: OmitPartialGroupDMChannel<Message<boolean>>) => Promise<any> } InteractionMsgFunction
18
- * @typedef { string } InteractionID
18
+ * @typedef { {name: string, subcommand: string | null} | string } InteractionID
19
19
  */
20
20
 
21
21
  export default class Interaction {
22
- /** @type {string} */
22
+ /** @type {InteractionID} */
23
23
  id;
24
24
  /** @type {InteractionType} */
25
25
  type;
@@ -94,4 +94,13 @@ export default class Interaction {
94
94
  static OnModal(custom_id, scope, target) {
95
95
  return Interaction.On(custom_id, InteractionType.Modal, scope, target)
96
96
  }
97
+
98
+ /**
99
+ * Hides arbitrary number of arguments in the custom ID
100
+ * @param {...any} args
101
+ * @returns {string}
102
+ */
103
+ static makeCustomId(...args) {
104
+ return args.map(arg => String(arg)).join("?");
105
+ }
97
106
  }
@@ -5,22 +5,20 @@ import LocalConfig from "../Configs/LocalConfig.js";
5
5
  import consola from "consola";
6
6
  import CommandError from "./CommandError.js";
7
7
  import RestCommands from './RestCommands.js';
8
- import pkg, { ChannelType } from 'discord.js';
9
- const {
10
- Interaction: DiscordBaseInteraction,
11
- OmitPartialGroupDMChannel,
12
- Message,
13
- CacheType
14
- } = pkg;
8
+ import { ChannelType } from 'discord.js';
15
9
 
16
10
  /**
11
+ * @typedef { import("discord.js").Interaction } DiscordBaseInteraction
12
+ * @typedef { import("discord.js").OmitPartialGroupDMChannel } OmitPartialGroupDMChannel
13
+ * @typedef { import("discord.js").Message } Message
14
+ * @typedef { import("discord.js").CacheType } CacheType
17
15
  * @typedef { (interaction: DiscordBaseInteraction<CacheType>) => Promise<any> } InteractionFunction
18
16
  * @typedef { (message: OmitPartialGroupDMChannel<Message<boolean>>) => Promise<any> } InteractionMsgFunction
19
- * @typedef { string } InteractionID
17
+ * @typedef { import("./Interaction.js").InteractionID } InteractionID
20
18
  */
21
19
 
22
20
  export default class InteractionContainer {
23
- /** @type {Map<InteractionID, Interaction>} */
21
+ /** @type {Map<string, Interaction>} */
24
22
  static #interaction_container = new Map();
25
23
 
26
24
  /** @type {GlobalConfig} */
@@ -29,42 +27,55 @@ export default class InteractionContainer {
29
27
  #local_config;
30
28
 
31
29
  /** @type {RestCommands} */
32
- Rest = new RestCommands(this.#global_config, this.#local_config);
30
+ Rest;
33
31
 
34
32
  /**
35
33
  * @param {InteractionType} type
36
34
  * @param {InteractionScope} scope
37
- * @param {string} id
38
- * @returns {InteractionID}
35
+ * @param {InteractionID} id
36
+ * @returns {string} interction ID
39
37
  */
40
38
  static #make_key(type, scope, id) {
41
- return `${type}__${scope}__${id}`;
39
+ return `${type}__${scope}__${id.subcommand ? `${id.name}>${id.subcommand}` : (id.name || id)}`;
42
40
  }
43
41
 
44
42
  /**
45
- * @param {InteractionType} type
46
- * @param {InteractionScope} scope
47
- * @param {string} id
48
43
  * @param {Interaction} base_interaction
49
44
  */
50
45
  static add(base_interaction) {
51
46
  const key = InteractionContainer.#make_key(base_interaction.type, base_interaction.scope, base_interaction.id);
47
+ consola.debug(`Registered interaction with key ${key}`);
52
48
  InteractionContainer.#interaction_container.set(key, base_interaction);
53
49
  }
54
50
 
55
51
  /**
56
52
  * @param {InteractionType} type
57
53
  * @param {InteractionScope} scope
58
- * @param {string} id
54
+ * @param {InteractionID} id
59
55
  * @returns {Interaction?}
60
56
  */
61
57
  static get(type, scope, id) {
62
- return InteractionContainer.#interaction_container.get(InteractionContainer.#make_key(type, scope, id));
58
+ const key = InteractionContainer.#make_key(type, scope, id);
59
+ consola.debug(`Fetching interaction with key ${key}`);
60
+ return InteractionContainer.#interaction_container.get(key);
61
+ }
62
+
63
+ static dump() {
64
+ return Array.from(InteractionContainer.#interaction_container.entries()).map(([key, interaction]) => {
65
+ return {
66
+ key,
67
+ type: interaction.type,
68
+ scope: interaction.scope,
69
+ id: interaction.id,
70
+ function: interaction.interaction_command_function.name
71
+ }
72
+ });
63
73
  }
64
74
 
65
75
  constructor(local_config, global_config) {
66
76
  this.#local_config = local_config;
67
77
  this.#global_config = global_config;
78
+ this.Rest = new RestCommands(global_config, local_config);
68
79
  }
69
80
 
70
81
  /**
@@ -82,10 +93,10 @@ export default class InteractionContainer {
82
93
  (interaction.channel.type === ChannelType.DM) ?
83
94
  InteractionScope.DM : this.#global_config.get_scope(interaction.guild.id);
84
95
 
85
- const customId = interaction.content.slice(this.#local_config.prefix?.length)
96
+ const [customId, ...command_arguments] = interaction.content.slice(this.#local_config.prefix?.length)?.split("?")
86
97
 
87
98
  return InteractionContainer
88
- .#call(InteractionType.Message, scope, [customId], interaction)
99
+ .#call(interaction, InteractionType.Message, scope, customId, command_arguments)
89
100
  .catch(error => {
90
101
  if (error instanceof CommandError)
91
102
  return error.error_as_message(interaction)
@@ -107,8 +118,10 @@ export default class InteractionContainer {
107
118
  const scope =
108
119
  (interaction.channel.type === ChannelType.DM) ?
109
120
  InteractionScope.DM : this.#global_config.get_scope(interaction.guild.id);
110
-
111
- const customId = (interaction.commandName || interaction.customId)?.split(/\?(.+)/, 2)
121
+ console.log(scope)
122
+
123
+ const [customId, ...command_arguments] = (interaction.commandName || interaction.customId)?.split("?")
124
+ console.log(customId)
112
125
 
113
126
  var type;
114
127
  if (interaction.isChatInputCommand())
@@ -121,9 +134,10 @@ export default class InteractionContainer {
121
134
  type = InteractionType.Modal;
122
135
  else
123
136
  return;
137
+ console.log(type)
124
138
 
125
139
  return InteractionContainer
126
- .#call(type, scope, customId, interaction)
140
+ .#call(interaction, type, scope, {name: customId, subcommand: interaction.options?.getSubcommand()}, command_arguments)
127
141
  .catch(error => {
128
142
  if (error instanceof CommandError)
129
143
  return error.error_as_command(interaction)
@@ -138,18 +152,19 @@ export default class InteractionContainer {
138
152
 
139
153
  /**
140
154
  *
155
+ * @param {DiscordBaseInteraction} interaction
141
156
  * @param {InteractionType} type
142
157
  * @param {InteractionScope} scope
143
- * @param {InteractionID[]} customId
144
- * @param {DiscordBaseInteraction} interaction
158
+ * @param {InteractionID} customId
159
+ * @param {string[]} command_arguments
145
160
  * @returns {Promise<any>}
146
161
  * @throws {CommandError}
147
162
  */
148
- static async #call(type, scope, customId, interaction) {
149
- const interaction_command = InteractionContainer.get(type, scope, customId?.[0]);
163
+ static async #call(interaction, type, scope, customId, command_arguments = []) {
164
+ const interaction_command = InteractionContainer.get(type, scope, customId);
150
165
  if(!interaction_command)
151
- return consola.error(`No interaction found for id ${customId.join('?')} with type ${type} and scope ${scope}`);
166
+ return consola.error(`No interaction found:\n${JSON.stringify({customId, type, scope, command_arguments: command_arguments.join(',')}, null, 2)}`);
152
167
 
153
- return await interaction_command.interaction_command_function.apply(null, [interaction, ...customId.slice(1)]);
168
+ return await interaction_command.interaction_command_function.apply(null, [interaction, ...command_arguments]);
154
169
  }
155
170
  }
@@ -1,6 +1,11 @@
1
1
  import GlobalConfig from "../Configs/GlobalConfig/GlobalConfig.js";
2
2
  import LocalConfig from "../Configs/LocalConfig.js";
3
3
  import { REST, Routes } from "discord.js";
4
+ import { InteractionScope } from "../Utils/enums.js";
5
+
6
+ /**
7
+ * @typedef {import("discord.js").SlashCommandSubcommandsOnlyBuilder} SlashCommandSubcommandsOnlyBuilder
8
+ */
4
9
 
5
10
  export default class RestCommands {
6
11
  #commands = new Map();
@@ -20,6 +25,12 @@ export default class RestCommands {
20
25
  this.#local_config = local_config;
21
26
  }
22
27
 
28
+ /**
29
+ *
30
+ * @param {SlashCommandSubcommandsOnlyBuilder[]} commands
31
+ * @param {InteractionScope} scope
32
+ * @returns {RestCommands}
33
+ */
23
34
  add(commands, scope) {
24
35
  if(!!scope)
25
36
  this.#commands.set(scope, commands);
@@ -29,6 +40,10 @@ export default class RestCommands {
29
40
  return this;
30
41
  }
31
42
 
43
+ /**
44
+ * @param {string} secret_token
45
+ * @returns {Promise<REST>}
46
+ */
32
47
  async register(secret_token){
33
48
  const rest = new REST(this.#local_config.restOptions).setToken(secret_token);
34
49