@spatulox/discord-interaction-manager 1.0.1 → 1.0.3

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
@@ -8,13 +8,24 @@ A lightweight CLI tool to manage Discord bot interactions (slash commands, conte
8
8
 
9
9
  ⚙️ File Generation: Interactive JSON generators for slash commands & context menus
10
10
 
11
- 🔍 Guild Discovery: Auto-detect all guilds + command counts per guild
11
+ 🔍 Guild Discovery: Auto-detect all guilds + interaction counts per guild
12
12
 
13
- 🎛️ Interactive CLI: Rich menus, input validation, command selection
13
+ 🎛️ Interactive CLI: Rich menus, input validation, interaction selection
14
14
 
15
15
  📦 Only one runtime deps: Only discord.js is required in production
16
16
 
17
- 🛡️ Never deprecated: Uses discord.js for all Discord requests - no breaking changes, always up-to-date with Discord API
17
+ 🛡️ Never deprecated: Uses discord.js for all Discord requests and enum, always up-to-date with Discord API
18
+
19
+
20
+ > This documentation assume that you know how interactions are built on Discord, if not, please see :
21
+ > - [Overview](https://docs.discord.com/developers/interactions/overview)
22
+ > - [Receiving and responding](https://docs.discord.com/developers/interactions/receiving-and-responding)
23
+ > - [Application Commands](https://docs.discord.com/developers/interactions/application-commands)
24
+ > - [Slash Commands](https://docs.discord.com/developers/interactions/application-commands#slash-commands)
25
+ > - [User Commands](https://docs.discord.com/developers/interactions/application-commands#user-commands)
26
+ > - [Message Commands](https://docs.discord.com/developers/interactions/application-commands#message-commands)
27
+
28
+ ### ⚠️ Discord "Activities" are not supported, even if it's a type of interaction, because it's a complete game feature.
18
29
 
19
30
  ## Quick Start
20
31
  ```bash
@@ -26,6 +37,7 @@ You can use dotenv or set them by hand
26
37
  ```
27
38
  DISCORD_BOT_TOKEN="" // Discord Bot Token
28
39
  DISCORD_BOT_CLIENTID="" // The clientID of your bot
40
+ DISCORD_INTERACTION_FOLDER="" // Optionnal, redirect where the generated interactions files are stored
29
41
  ```
30
42
 
31
43
  ## Run the tool
@@ -33,4 +45,51 @@ Simply type "dim" in your console (which stands for DiscordInteractionManager)
33
45
  ```bash
34
46
  dim
35
47
  ```
36
- You will be greet by a CLI
48
+
49
+ You will be greet by a CLI :
50
+ ```
51
+ 💠 SimpleDiscordBot CLI
52
+ ════════════════════════════════════════
53
+ 1. Manage Interactions
54
+ 2. Generate Files
55
+ 3. Help
56
+ 4. Exit
57
+ ════════════════════════════════════════
58
+ Choose an option:
59
+ ```
60
+
61
+ # How it works
62
+ ## Creating an interaction
63
+ > - You can generate slash commands and context menu with the cli
64
+
65
+ > - When generating files with the cli, you should be able to see generated files in the "**./handlers**"*
66
+
67
+ ## Deploy an interaction
68
+ > - Once you have deployed an interaction, you can update/delete it using the cli
69
+
70
+ > - The scope** of the interaction is determined while generating the file
71
+
72
+ ## List interactions
73
+ > - List global interaction
74
+
75
+ > - list interaction locked to a specific guild
76
+
77
+ > - Count the number of interaction by scope**
78
+
79
+ ## Update an interaction
80
+ > - You can update any interaction, by updating the generated file inside the "**./handlers**" folder*
81
+
82
+ > - Once you updated the generated file, you can use the cli to update the interaction
83
+
84
+ > - If you want to change the scope** of an interaction you can delete/add the "**guild_ids**" field in the generated interaction files, just don't forget to delete the old one and deploy the new one. **Normal update will not work**
85
+
86
+ > - If you want to update the permission of the interaction, you need to update the "**default_member_permissions_string**" field with the keyof **PermissionBitFields** of discordjs (or let it empty for everyone), it will automatically update the "**default_member_permissions**" field
87
+
88
+ > - If the "**default_member_permissions_string**" field doesn't exist for some reason, you can create it or go to the [Discord Dev potal](https://discord.com/developers/applications/), in any of your app, go to the "Bot" tab and then check any of the "BotPermission" you want for the interaction and then copy the "Permission Integer" to paste it inside the "**default_member_permissions**" field inside the json of the interaction
89
+
90
+ ## Delete an interation
91
+ > - You can delete any interaction, in any scope**
92
+
93
+ Key:
94
+ * Folder at the root of your project, if not, make sure you didn't overwrite the path with the DISCORD_INTERACTION_FOLDER variable
95
+ * ** Scope refer to "global" or "guild specific"
package/dist/Env.js CHANGED
@@ -14,8 +14,8 @@ exports.Env = {
14
14
  throw new Error('Missing environment variable : DISCORD_BOT_TOKEN');
15
15
  return token;
16
16
  },
17
- get dev() {
18
- return process.env.DEV === 'true';
17
+ get interactionFolderPath() {
18
+ return process.env.DISCORD_INTERACTION_FOLDER ? process.env.DISCORD_INTERACTION_FOLDER : "./handlers";
19
19
  },
20
20
  get clientId() {
21
21
  const token = process.env.DISCORD_BOT_CLIENTID;
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.BaseCLI = void 0;
8
8
  const readline_1 = __importDefault(require("readline"));
9
9
  const FileManager_1 = require("../utils/FileManager");
10
+ const Env_1 = require("../Env");
10
11
  /**
11
12
  * --- BaseCLI ---
12
13
  */
@@ -99,11 +100,11 @@ class BaseCLI {
99
100
  console.log('🤖 What it does:');
100
101
  console.log(' • Manage your Discord interactions (slash commands & context menus) via an interactive CLI');
101
102
  console.log(' • Let you deploy/update/delete any interaction');
102
- console.log(' • Let you generate an interaction files');
103
+ console.log(' • Let you generate interactions files');
103
104
  console.log('');
104
- console.log('How you need to save your interaction files');
105
+ console.log('How generated interaction files are stored');
105
106
  console.log('📁 Folder Structure:');
106
- console.log(' ├── handlers/ ← In the root folder of your project');
107
+ console.log(` ├── ${Env_1.Env.interactionFolderPath}/`);
107
108
  console.log(' │ ├── commands/ ← Slash Commands (type 1)');
108
109
  console.log(' │ └── context_menu/ ← Context Menus (type 2/3)');
109
110
  console.log('');
@@ -136,7 +137,7 @@ class BaseCLI {
136
137
  }
137
138
  async saveFile(folderName, filename, data) {
138
139
  let finalFilename = filename;
139
- if (await FileManager_1.FileManager.readJsonFile(`./handlers/${folderName}/${filename.split(".json")[0] + ".json"}`)) {
140
+ if (await FileManager_1.FileManager.readJsonFile(`${Env_1.Env.interactionFolderPath}/${folderName}/${filename.split(".json")[0] + ".json"}`)) {
140
141
  if (!await this.yesNoInput(`"${finalFilename}" already exists. Overwrite? (y/n): `)) {
141
142
  const timestamp = Date.now();
142
143
  finalFilename = `${filename.replace('.json', '')}-${timestamp}`;
@@ -153,8 +154,8 @@ class BaseCLI {
153
154
  return this.showMainMenu();
154
155
  }
155
156
  try {
156
- await FileManager_1.FileManager.writeJsonFile(`./handlers/${folderName}`, finalFilename, data);
157
- console.log(`File saved: ./handlers/${folderName}/${finalFilename}`);
157
+ await FileManager_1.FileManager.writeJsonFile(`${Env_1.Env.interactionFolderPath}/${folderName}`, finalFilename, data);
158
+ console.log(`File saved: ${Env_1.Env.interactionFolderPath}/${folderName}/${finalFilename}`);
158
159
  }
159
160
  catch (error) {
160
161
  console.error("Error saving file:", error);
@@ -28,6 +28,7 @@ class ContextMenuGeneratorCLI extends InteractionGeneratorCLI_1.InteractionGener
28
28
  config.type = parseInt(await this.requireInput("Type (2 or 3): ", val => ["2", "3"].includes(val)));
29
29
  console.clear();
30
30
  config.name = await this.requireInput("Name (1-32 chars): ", val => val.length >= 1 && val.length <= 32);
31
+ await this.nsfw(config);
31
32
  // 2. Permissions
32
33
  console.clear();
33
34
  console.log("🔐 2/5 - Command Permissions");
@@ -5,6 +5,11 @@ const BaseCLI_1 = require("../BaseCLI");
5
5
  const discord_js_1 = require("discord.js");
6
6
  const DiscordRegex_1 = require("../../utils/DiscordRegex");
7
7
  class InteractionGeneratorCLI extends BaseCLI_1.BaseCLI {
8
+ async nsfw(config) {
9
+ if (await this.yesNoInput("NSFW ? (y/n)")) {
10
+ config.nsfw = true;
11
+ }
12
+ }
8
13
  async addPermissions(config) {
9
14
  console.clear();
10
15
  const permEntries = Object.entries(discord_js_1.PermissionFlagsBits);
@@ -19,7 +24,7 @@ class InteractionGeneratorCLI extends BaseCLI_1.BaseCLI {
19
24
  });
20
25
  }, true);
21
26
  if (!input.trim() || input.toLowerCase() === 'everyone') {
22
- config.default_member_permissions_string = ['everyone'];
27
+ config.default_member_permissions_string = [''];
23
28
  config.default_member_permissions = 0n.toString();
24
29
  return;
25
30
  }
@@ -32,7 +37,7 @@ class InteractionGeneratorCLI extends BaseCLI_1.BaseCLI {
32
37
  }
33
38
  }
34
39
  config.default_member_permissions_string = selectedPermNames;
35
- config.default_member_permissions = this.permissionsToBitfield(input.split(","));
40
+ config.default_member_permissions = this.permissionsToBitfield(selectedPermNames);
36
41
  }
37
42
  async optionalGuildIds() {
38
43
  const input = await this.prompt("Guild IDs (separated by comma, or 'none' to cancel): ");
@@ -29,6 +29,7 @@ class SlashCommandGeneratorCLI extends InteractionGeneratorCLI_1.InteractionGene
29
29
  console.log("📝 1/6 - Base");
30
30
  config.name = await this.requireInput("Name (a-z0-9_-, 1-32 chars): ", val => /^[a-z0-9_-]{1,32}$/.test(val));
31
31
  config.description = await this.requireInput("Description (1-100 chars): ", val => val.length >= 1 && val.length <= 100);
32
+ await this.nsfw(config);
32
33
  console.clear();
33
34
  console.log("🔐 2/6 - Command Permissions");
34
35
  await this.addPermissions(config);
@@ -60,9 +61,12 @@ class SlashCommandGeneratorCLI extends InteractionGeneratorCLI_1.InteractionGene
60
61
  .filter(([, value]) => typeof value === 'number')
61
62
  .map(([key, value]) => `${value}.${key}`)
62
63
  .join(', '));
63
- const type = parseInt(await this.requireInput("Type (1-11): ", val => {
64
+ const numericValues = Object.values(InteractionType_1.DiscordOptionType).filter((v) => typeof v === 'number');
65
+ const maxType = Math.max(...numericValues);
66
+ const minType = Math.min(...numericValues);
67
+ const type = parseInt(await this.requireInput(`Type (${minType}-${maxType}): `, val => {
64
68
  const n = parseInt(val);
65
- return n >= 1 && n <= 11;
69
+ return n >= minType && n <= maxType;
66
70
  }));
67
71
  const option = await this.buildOption(type);
68
72
  options.push(option);
@@ -109,7 +113,7 @@ class SlashCommandGeneratorCLI extends InteractionGeneratorCLI_1.InteractionGene
109
113
  return input.trim() ? parseFloat(input) : undefined;
110
114
  }
111
115
  async addChoices() {
112
- if (!await this.yesNoInput("Add Choices ? "))
116
+ if (!await this.yesNoInput("Add Choices (25 max) ? "))
113
117
  return undefined;
114
118
  const choices = [];
115
119
  while (choices.length < 25) {
@@ -119,6 +123,9 @@ class SlashCommandGeneratorCLI extends InteractionGeneratorCLI_1.InteractionGene
119
123
  if (!await this.yesNoInput("Another choice ? "))
120
124
  break;
121
125
  }
126
+ if (choices.length >= 25) {
127
+ console.log("You can't have 25+ choices");
128
+ }
122
129
  return choices;
123
130
  }
124
131
  async addChannelTypes() {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.InteractionCLI = void 0;
4
4
  const BaseCLI_1 = require("../BaseCLI");
5
5
  const InteractionManager_1 = require("../interactions/InteractionManager");
6
- const InteractionCLIManager_1 = require("./InteractionCLIManager");
6
+ const InteractionManagerCLI_1 = require("./InteractionManagerCLI");
7
7
  const Env_1 = require("../../Env");
8
8
  class InteractionCLI extends BaseCLI_1.BaseCLI {
9
9
  getTitle() {
@@ -13,8 +13,8 @@ class InteractionCLI extends BaseCLI_1.BaseCLI {
13
13
  super(parent);
14
14
  this.managers = {};
15
15
  this.menuSelection = [
16
- { label: "Command Manager", action: () => new InteractionCLIManager_1.InteractionManagerCLI(this, this.managers["CommandManager"], "CommandManager") },
17
- { label: "ContextMenu Manager", action: () => new InteractionCLIManager_1.InteractionManagerCLI(this, this.managers["ContextMenuManager"], "ContextMenuManager") },
16
+ { label: "Command Manager", action: () => new InteractionManagerCLI_1.InteractionManagerCLI(this, this.managers["CommandManager"], "CommandManager") },
17
+ { label: "ContextMenu Manager", action: () => new InteractionManagerCLI_1.InteractionManagerCLI(this, this.managers["ContextMenuManager"], "ContextMenuManager") },
18
18
  //{ label: "All Interaction Manager", action: () => new InteractionManagerCLI(this, this.managers["InteractionManager"], "InteractionManager") },
19
19
  { label: 'Back', action: () => this.goBack() },
20
20
  ];
@@ -38,37 +38,62 @@ class InteractionManagerCLI extends BaseCLI_1.BaseCLI {
38
38
  await this.manager.listAllGuilds(await new GuildListManager_1.GuildListManager(Env_1.Env.clientId, Env_1.Env.token).list(false));
39
39
  }
40
40
  async handleDeploy() {
41
- const selected = await this.selectCommands(this.manager, false);
41
+ const selected = await this.selectCommands(this.manager, await this.manager.listFromFile());
42
42
  if (selected.length === 0)
43
43
  return;
44
44
  await this.manager.deploy(selected);
45
45
  }
46
46
  async handleUpdate() {
47
- const selected = await this.selectCommands(this.manager);
47
+ let guild;
48
+ const rep = await this.yesNoInput("Do you want to update a global command or a specific guild command (y=global/n=specific): ");
49
+ console.log('═'.repeat(80));
50
+ console.log(`ACTUAL DEPLOYED ${this.manager.folderPath?.toUpperCase()}`);
51
+ if (rep) {
52
+ await this.listRemote();
53
+ }
54
+ else {
55
+ guild = await new GuildListManager_1.GuildListManager(Env_1.Env.clientId, Env_1.Env.token).chooseGuild();
56
+ await this.guildListRemote(guild);
57
+ }
58
+ console.log('═'.repeat(80));
59
+ console.log(`ACTUAL LOCAL ${this.manager.folderPath?.toUpperCase()}`);
60
+ const selected = await this.selectCommands(this.manager, await this.manager.listFromFile(false, guild?.id));
48
61
  if (selected.length === 0)
49
62
  return;
50
63
  await this.manager.update(selected);
51
64
  }
52
65
  async handleDelete() {
53
- const selected = await this.selectCommands(this.manager);
66
+ const rep = await this.yesNoInput("Do you want to delete a global command or a specific guild command (y=global/n=specific): ");
67
+ let commands;
68
+ if (rep) {
69
+ commands = await this.manager.list();
70
+ }
71
+ else {
72
+ let guild = await new GuildListManager_1.GuildListManager(Env_1.Env.clientId, Env_1.Env.token).chooseGuild();
73
+ if (!guild) {
74
+ console.log("Error, cannot find guild");
75
+ return;
76
+ }
77
+ commands = await this.manager.listGuild(guild?.id);
78
+ }
79
+ const selected = await this.selectCommands(this.manager, commands);
54
80
  if (selected.length === 0)
55
81
  return;
56
82
  await this.manager.delete(selected);
57
83
  }
58
- async selectCommands(manager, remote = true) {
84
+ async selectCommands(manager, commands) {
59
85
  const handlerManagerType = `${manager.folderPath}(s)`;
60
- const commandList = remote ? await manager.list() : await manager.listFromFile();
61
- if (!commandList?.length) {
86
+ if (!commands?.length) {
62
87
  console.log(`No ${handlerManagerType} found`);
63
88
  return [];
64
89
  }
65
90
  const input = await this.prompt('Enter numbers (ex: 1,3,5 or "all" or "exit"): ');
66
91
  if (input.toLowerCase() === 'all')
67
- return commandList;
92
+ return commands;
68
93
  if (input.toLowerCase() === 'exit')
69
94
  return [];
70
95
  const indices = input.split(',').map(i => parseInt(i.trim())).filter(i => !isNaN(i));
71
- const selected = commandList.filter((cmd) => indices.includes(cmd.index));
96
+ const selected = commands.filter((cmd) => indices.includes(cmd.index));
72
97
  if (selected.length === 0) {
73
98
  console.log('Invalid number');
74
99
  return [];
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InteractionListManagerCLI = void 0;
4
+ const BaseCLI_1 = require("../BaseCLI");
5
+ const GuildListManager_1 = require("../GuildListManager");
6
+ const Env_1 = require("../../Env");
7
+ class InteractionListManagerCLI extends BaseCLI_1.BaseCLI {
8
+ getTitle() {
9
+ return `${this.managerKey} - ${this.manager.folderPath}`;
10
+ }
11
+ constructor(parent, manager, managerKey) {
12
+ super(parent);
13
+ this.manager = manager;
14
+ this.managerKey = managerKey;
15
+ this.menuSelection = [
16
+ { label: `List Global ${this.manager.folderPath}`, action: () => this.listRemote() },
17
+ { label: `List Specific ${this.manager.folderPath} for a Guild`, action: async () => this.guildListRemote(await new GuildListManager_1.GuildListManager(Env_1.Env.clientId, Env_1.Env.token).chooseGuild()) },
18
+ { label: `Count ${this.manager.folderPath} per Guilds`, action: async () => this.guildListAllRemote() },
19
+ { label: 'Back', action: () => this.goBack() },
20
+ ];
21
+ }
22
+ execute() {
23
+ throw new Error("Method not implemented.");
24
+ }
25
+ async listRemote() {
26
+ await this.manager.list();
27
+ }
28
+ async guildListRemote(guild) {
29
+ if (!guild) {
30
+ return;
31
+ }
32
+ await this.manager.listGuild(guild.id);
33
+ }
34
+ async guildListAllRemote() {
35
+ await this.manager.listAllGuilds(await new GuildListManager_1.GuildListManager(Env_1.Env.clientId, Env_1.Env.token).list(false));
36
+ }
37
+ }
38
+ exports.InteractionListManagerCLI = InteractionListManagerCLI;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InteractionManagerCLI = void 0;
4
+ const GuildListManager_1 = require("../GuildListManager");
5
+ const Env_1 = require("../../Env");
6
+ const InteractionListManagerCLI_1 = require("./InteractionListManagerCLI");
7
+ class InteractionManagerCLI extends InteractionListManagerCLI_1.InteractionListManagerCLI {
8
+ getTitle() {
9
+ return `${this.managerKey} - ${this.manager.folderPath}`;
10
+ }
11
+ constructor(parent, manager, managerKey) {
12
+ super(parent, manager, managerKey);
13
+ this.manager = manager;
14
+ this.managerKey = managerKey;
15
+ this.menuSelection = [
16
+ { label: `List ${this.manager.folderPath}`, action: () => new InteractionListManagerCLI_1.InteractionListManagerCLI(this, manager, this.managerKey) },
17
+ { label: "Deploy local", action: () => this.handleDeploy() },
18
+ { label: "Update remote", action: () => this.handleUpdate() },
19
+ { label: "Delete remote", action: () => this.handleDelete() },
20
+ { label: 'Back', action: () => this.goBack() },
21
+ ];
22
+ }
23
+ execute() {
24
+ throw new Error("Method not implemented.");
25
+ }
26
+ async handleDeploy() {
27
+ const selected = await this.selectCommands(this.manager, await this.manager.listFromFile());
28
+ if (selected.length === 0)
29
+ return;
30
+ await this.manager.deploy(selected);
31
+ }
32
+ async handleUpdate() {
33
+ let guild;
34
+ const rep = await this.yesNoInput("Do you want to update a global command or a specific guild command (y=global/n=specific): ");
35
+ console.log('═'.repeat(80));
36
+ console.log(`ACTUAL DEPLOYED ${this.manager.folderPath?.toUpperCase()}`);
37
+ if (rep) {
38
+ await this.listRemote();
39
+ }
40
+ else {
41
+ guild = await new GuildListManager_1.GuildListManager(Env_1.Env.clientId, Env_1.Env.token).chooseGuild();
42
+ await this.guildListRemote(guild);
43
+ }
44
+ console.log('═'.repeat(80));
45
+ console.log(`ACTUAL LOCAL ${this.manager.folderPath?.toUpperCase()}`);
46
+ const selected = await this.selectCommands(this.manager, await this.manager.listFromFile(false, guild?.id));
47
+ if (selected.length === 0)
48
+ return;
49
+ await this.manager.update(selected);
50
+ }
51
+ async handleDelete() {
52
+ const rep = await this.yesNoInput("Do you want to delete a global command or a specific guild command (y=global/n=specific): ");
53
+ let commands;
54
+ if (rep) {
55
+ commands = await this.manager.list();
56
+ }
57
+ else {
58
+ let guild = await new GuildListManager_1.GuildListManager(Env_1.Env.clientId, Env_1.Env.token).chooseGuild();
59
+ if (!guild) {
60
+ console.log("Error, cannot find guild");
61
+ return;
62
+ }
63
+ commands = await this.manager.listGuild(guild?.id);
64
+ }
65
+ const selected = await this.selectCommands(this.manager, commands);
66
+ if (selected.length === 0)
67
+ return;
68
+ await this.manager.delete(selected);
69
+ }
70
+ async selectCommands(manager, commands) {
71
+ const handlerManagerType = `${manager.folderPath}(s)`;
72
+ if (!commands?.length) {
73
+ console.log(`No ${handlerManagerType} found`);
74
+ return [];
75
+ }
76
+ const input = await this.prompt('Enter numbers (ex: 1,3,5 or "all" or "exit"): ');
77
+ if (input.toLowerCase() === 'all')
78
+ return commands;
79
+ if (input.toLowerCase() === 'exit')
80
+ return [];
81
+ const indices = input.split(',').map(i => parseInt(i.trim())).filter(i => !isNaN(i));
82
+ const selected = commands.filter((cmd) => indices.includes(cmd.index));
83
+ if (selected.length === 0) {
84
+ console.log('Invalid number');
85
+ return [];
86
+ }
87
+ console.log(`${selected.length} selected ${handlerManagerType}`);
88
+ return selected;
89
+ }
90
+ }
91
+ exports.InteractionManagerCLI = InteractionManagerCLI;
@@ -41,6 +41,7 @@ const discord_js_1 = require("discord.js");
41
41
  const fs = __importStar(require("fs/promises"));
42
42
  const Log_1 = require("../../utils/Log");
43
43
  const FileManager_1 = require("../../utils/FileManager");
44
+ const Env_1 = require("../../Env");
44
45
  var CommandType;
45
46
  (function (CommandType) {
46
47
  CommandType[CommandType["SLASH"] = 1] = "SLASH";
@@ -53,34 +54,41 @@ class BaseInteractionManager {
53
54
  this.token = token;
54
55
  this.rest = new rest_1.REST({ version: '10' }).setToken(token);
55
56
  }
56
- async listFromFile() {
57
- console.log(`Listing Handlers (${this.folderPath}) not deployed on discord`);
57
+ async printInteraction(cmdList) {
58
+ console.table(cmdList.map((cmd) => ({
59
+ Nom: cmd.name,
60
+ Type: cmd.type === CommandType.SLASH ? 'Slash' :
61
+ cmd.type === CommandType.USER_CONTEXT_MENU ? 'User Context Menu' : 'Message Context Menu',
62
+ Description: cmd.description,
63
+ Permissions: cmd.default_member_permissions_string?.join(", "),
64
+ ID: cmd.id
65
+ })));
66
+ }
67
+ async listFromFile(avoidDeployedInteraction = true, guildID) {
68
+ let scopeMessage = guildID ? `(guild ${guildID})` : (global);
69
+ console.log(`Listing Local Handlers (${this.folderPath})${avoidDeployedInteraction ? " not" : ""} deployed on Discord ${scopeMessage}`);
58
70
  try {
59
- const files = await FileManager_1.FileManager.listJsonFiles(`./handlers/${this.folderPath}`);
71
+ const files = await FileManager_1.FileManager.listJsonFiles(`${Env_1.Env.interactionFolderPath}/${this.folderPath}`);
60
72
  if (!files || files.length === 0) {
61
73
  console.log('No files found');
62
74
  return [];
63
75
  }
64
76
  const commandList = [];
65
- for (const [index, file] of files.entries()) {
66
- const cmd = await this.readInteraction(`./handlers/${this.folderPath}/${file}`);
67
- if (!cmd || cmd.id)
77
+ for (const [_index, file] of files.entries()) {
78
+ const cmd = await this.readInteraction(`${Env_1.Env.interactionFolderPath}/${this.folderPath}/${file}`);
79
+ if (!cmd || (cmd.id && avoidDeployedInteraction))
80
+ continue;
81
+ if (guildID && !cmd.guildID?.includes(guildID))
68
82
  continue;
69
83
  const commandWithIndex = {
70
84
  ...cmd,
71
- index: index,
85
+ //index: index,
72
86
  filename: file
73
87
  };
74
88
  commandList.push(commandWithIndex);
75
89
  }
76
- console.log(`✅ ${commandList.length} local ${this.folderPath}(s) not deployed\n`);
77
- console.table(commandList.map((cmd) => ({
78
- Nom: cmd.name,
79
- Type: cmd.type === CommandType.SLASH ? 'Slash' :
80
- cmd.type === CommandType.USER_CONTEXT_MENU ? 'User' : 'Message',
81
- Description: cmd.description,
82
- Fichier: cmd.filename
83
- })));
90
+ console.log(`✅ ${commandList.length} local ${this.folderPath}(s) found\n`);
91
+ await this.printInteraction(commandList);
84
92
  return commandList;
85
93
  }
86
94
  catch (error) {
@@ -90,12 +98,12 @@ class BaseInteractionManager {
90
98
  }
91
99
  async fetchCommands(endpoint, scope, guildId, printResult = true) {
92
100
  const scopeLabel = scope === 'global' ? 'global' : `guild ${scope}`;
93
- console.log(`Handlers ${this.folderPath} on Discord (${scopeLabel})...`);
101
+ console.log(`Listing Deployed Handlers ${this.folderPath} on Discord (${scopeLabel})`);
94
102
  try {
95
103
  const rawCmds = await this.rest.get(endpoint);
96
104
  const commands = rawCmds.filter(cmd => this.commandType.includes(cmd.type));
97
- const commandList = commands.map((cmd, index) => ({
98
- index: index,
105
+ const commandList = commands.map((cmd, _index) => ({
106
+ //index: index,
99
107
  name: cmd.name,
100
108
  type: cmd.type,
101
109
  description: cmd.description || 'N/A',
@@ -106,14 +114,7 @@ class BaseInteractionManager {
106
114
  }));
107
115
  if (printResult) {
108
116
  console.log(`✅ ${commandList.length} ${this.folderPath}(s) found\n`);
109
- console.table(commandList.map((cmd) => ({
110
- Nom: cmd.name,
111
- Type: cmd.type === CommandType.SLASH ? 'Slash' :
112
- cmd.type === CommandType.USER_CONTEXT_MENU ? 'User Context Menu' : 'Message Context Menu',
113
- Description: cmd.description,
114
- Permissions: cmd.default_member_permissions_string?.join(", "),
115
- ID: cmd.id
116
- })));
117
+ await this.printInteraction(commandList);
117
118
  }
118
119
  return commandList;
119
120
  }
@@ -217,10 +218,14 @@ class BaseInteractionManager {
217
218
  Log_1.Log.error(`${cmd.name}: No Discord ID, cannot update the ${this.folderPath}`);
218
219
  continue;
219
220
  }
221
+ cmd.default_member_permissions = this.permissionsToBitfield(cmd.default_member_permissions_string);
220
222
  try {
221
223
  await this.rest.patch(v10_1.Routes.applicationCommand(this.clientId, cmd.id), {
222
- body: { description: `${cmd.description} (Updated ${new Date().toISOString()})` }
224
+ body: cmd
223
225
  });
226
+ if (cmd.filename) { // should be a thing since we list files with the this.listFromFile()
227
+ await this.saveInteraction(cmd.filename, cmd);
228
+ }
224
229
  console.log(`${cmd.name} updated`);
225
230
  }
226
231
  catch (error) {
@@ -295,17 +300,17 @@ class BaseInteractionManager {
295
300
  }
296
301
  async saveInteraction(fileName, cmd) {
297
302
  delete cmd.filename;
298
- const filePath = `./handlers/${this.folderPath}/${fileName}`;
303
+ const filePath = `${Env_1.Env.interactionFolderPath}/${this.folderPath}/${fileName}`;
299
304
  await fs.writeFile(filePath, JSON.stringify(cmd, null, 2));
300
305
  }
301
306
  async removeLocalIdFromFile(idListToDelete) {
302
- const files = await FileManager_1.FileManager.listJsonFiles(`./handlers/${this.folderPath}`);
307
+ const files = await FileManager_1.FileManager.listJsonFiles(`${Env_1.Env.interactionFolderPath}/${this.folderPath}`);
303
308
  if (!files || files.length === 0) {
304
309
  console.log('No local files to clean');
305
310
  return;
306
311
  }
307
312
  for (const file of files) {
308
- const filePath = `./handlers/${this.folderPath}/${file}`;
313
+ const filePath = `${Env_1.Env.interactionFolderPath}/${this.folderPath}/${file}`;
309
314
  const localCmd = await this.readInteraction(filePath);
310
315
  if (localCmd && localCmd.id && idListToDelete.includes(localCmd.id)) {
311
316
  delete localCmd.id;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spatulox/discord-interaction-manager",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "author": "Spatulox",
5
5
  "description": "discord-interaction-manager is a lightweight CLI tool to manage Discord bot interactions (slash commands, context menus) in under 30 seconds. Deploy, update, delete, and generate interaction files with an interactive terminal interface.",
6
6
  "bin": {
@@ -16,16 +16,15 @@
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/node": "^22.14.0",
19
- "@types/node-schedule": "^2.1.7",
20
19
  "dotenv": "^17.2.4",
21
- "nodemon": "^3.1.9",
22
20
  "tsx": "^4.20.3",
23
21
  "typescript": "^5.9.3"
24
22
  },
25
23
  "keywords": [
26
24
  "discord",
27
- "discord bot",
28
- "framework",
29
- "bot"
25
+ "slash commands",
26
+ "context menu",
27
+ "interaction",
28
+ "cli"
30
29
  ]
31
30
  }