@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 +63 -4
- package/dist/Env.js +2 -2
- package/dist/cli/BaseCLI.js +7 -6
- package/dist/cli/GenerationCLI/ContextMenuGeneratorCLI.js +1 -0
- package/dist/cli/GenerationCLI/InteractionGeneratorCLI.js +7 -2
- package/dist/cli/GenerationCLI/SlashCommandsGeneratorCLI.js +10 -3
- package/dist/cli/InteractionCLI/InteractionCLI.js +3 -3
- package/dist/cli/InteractionCLI/InteractionCLIManager.js +33 -8
- package/dist/cli/InteractionCLI/InteractionListManagerCLI.js +38 -0
- package/dist/cli/InteractionCLI/InteractionManagerCLI.js +91 -0
- package/dist/cli/interactions/BaseInteractionManager.js +35 -30
- package/package.json +5 -6
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 +
|
|
11
|
+
🔍 Guild Discovery: Auto-detect all guilds + interaction counts per guild
|
|
12
12
|
|
|
13
|
-
🎛️ Interactive CLI: Rich menus, input validation,
|
|
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
|
|
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
|
-
|
|
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
|
|
18
|
-
return process.env.
|
|
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;
|
package/dist/cli/BaseCLI.js
CHANGED
|
@@ -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
|
|
103
|
+
console.log(' • Let you generate interactions files');
|
|
103
104
|
console.log('');
|
|
104
|
-
console.log('How
|
|
105
|
+
console.log('How generated interaction files are stored');
|
|
105
106
|
console.log('📁 Folder Structure:');
|
|
106
|
-
console.log(
|
|
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(
|
|
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(
|
|
157
|
-
console.log(`File saved:
|
|
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 = ['
|
|
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(
|
|
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
|
|
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 >=
|
|
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
|
|
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
|
|
17
|
-
{ label: "ContextMenu Manager", action: () => new
|
|
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,
|
|
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
|
-
|
|
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
|
|
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,
|
|
84
|
+
async selectCommands(manager, commands) {
|
|
59
85
|
const handlerManagerType = `${manager.folderPath}(s)`;
|
|
60
|
-
|
|
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
|
|
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 =
|
|
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
|
|
57
|
-
console.
|
|
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(
|
|
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 [
|
|
66
|
-
const cmd = await this.readInteraction(
|
|
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)
|
|
77
|
-
|
|
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,
|
|
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
|
-
|
|
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:
|
|
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 =
|
|
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(
|
|
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 =
|
|
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.
|
|
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
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
25
|
+
"slash commands",
|
|
26
|
+
"context menu",
|
|
27
|
+
"interaction",
|
|
28
|
+
"cli"
|
|
30
29
|
]
|
|
31
30
|
}
|