@spatulox/simplediscordbot 1.0.2 → 1.0.4

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.
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ModalManager = void 0;
4
+ const discord_js_1 = require("discord.js");
5
+ const FolderName_1 = require("../../../type/FolderName");
6
+ const FileManager_1 = require("../../FileManager");
7
+ class ModalManager {
8
+ /**
9
+ * Load modal from JSON file and return ModalBuilder
10
+ */
11
+ static async load(filename) {
12
+ try {
13
+ const file = await FileManager_1.FileManager.readJsonFile(`./handlers/${FolderName_1.FolderName.MODAL}/${filename}`);
14
+ if (!file)
15
+ return false;
16
+ return ModalManager.jsonToBuilder(file);
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
22
+ /**
23
+ * List all modal files
24
+ */
25
+ static async list() {
26
+ try {
27
+ const files = await FileManager_1.FileManager.listJsonFiles(`./handlers/${FolderName_1.FolderName.MODAL}`);
28
+ return files || [];
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
34
+ static jsonToBuilder(json) {
35
+ const modal = new discord_js_1.ModalBuilder()
36
+ .setCustomId(json.customId)
37
+ .setTitle(json.title.slice(0, 45));
38
+ const actionRows = [];
39
+ for (const fieldJson of json.fields) {
40
+ const input = this.fieldJsonToInput(fieldJson);
41
+ const row = new discord_js_1.ActionRowBuilder()
42
+ .addComponents(input);
43
+ actionRows.push(row);
44
+ }
45
+ return modal.addComponents(...actionRows);
46
+ }
47
+ static fieldJsonToInput(fieldJson) {
48
+ const input = new discord_js_1.TextInputBuilder()
49
+ .setCustomId(fieldJson.customId)
50
+ .setLabel(fieldJson.title.slice(0, 45))
51
+ .setPlaceholder(fieldJson.placeholder || 'Enter value...')
52
+ .setRequired(fieldJson.required !== false)
53
+ .setMinLength(fieldJson.minLength || 0)
54
+ .setMaxLength(fieldJson.maxLength || 400);
55
+ // Convertir style JSON (1, 2, "Number") → TextInputStyle
56
+ const style = fieldJson.style;
57
+ if (typeof style === 'number') {
58
+ // 1=Short, 2=Paragraph
59
+ input.setStyle(style);
60
+ }
61
+ else {
62
+ // "Number", "Phone", "Date"
63
+ switch (style) {
64
+ case 'Number':
65
+ case 'Phone':
66
+ case 'Date':
67
+ input.setStyle(discord_js_1.TextInputStyle.Short);
68
+ input.setPlaceholder(style === 'Number' ? '123' :
69
+ style === 'Phone' ? '+33 6 12 34 56 78' :
70
+ '2024-02-05');
71
+ break;
72
+ default:
73
+ input.setStyle(discord_js_1.TextInputStyle.Short);
74
+ }
75
+ }
76
+ return input;
77
+ }
78
+ static parseNumber(value) {
79
+ if (!/^\d+$/.test(value))
80
+ return null;
81
+ const num = parseInt(value);
82
+ return isNaN(num) ? null : num;
83
+ }
84
+ static parsePhone(value) {
85
+ // 0604050359, +33604050359, 06 40 50 35 59, (06) 40-50-35-59
86
+ const clean = value.replace(/[\s\-\(\)]/g, '');
87
+ // Français : 06/07/09 + 8 chiffres OU +33 + 9 chiffres
88
+ if (/^(06|07|09)\d{8}$/.test(clean) || /^(\+33|0033)?[6-9]\d{8}$/.test(clean)) {
89
+ // Normalise au format international
90
+ if (clean.startsWith('06') || clean.startsWith('07') || clean.startsWith('09')) {
91
+ return '+33' + clean.slice(2);
92
+ }
93
+ return clean.startsWith('0033') ? '+33' + clean.slice(4) : clean;
94
+ }
95
+ return null;
96
+ }
97
+ static parseDate(value) {
98
+ // yyyy-mm-dd → Date
99
+ if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
100
+ const [year, month, day] = value.split('-').map(Number);
101
+ const date = new Date(year, month - 1, day);
102
+ return isNaN(date.getTime()) ? null : date;
103
+ }
104
+ // dd/mm/yyyy → Date
105
+ if (/^\d{2}\/\d{2}\/\d{4}$/.test(value)) {
106
+ const [day, month, year] = value.split('/').map(Number);
107
+ const date = new Date(year, month - 1, day);
108
+ return isNaN(date.getTime()) ? null : date;
109
+ }
110
+ return null;
111
+ }
112
+ }
113
+ exports.ModalManager = ModalManager;
@@ -0,0 +1,338 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.BaseInteractionManager = exports.CommandType = void 0;
38
+ const rest_1 = require("@discordjs/rest");
39
+ const v10_1 = require("discord-api-types/v10");
40
+ const discord_js_1 = require("discord.js");
41
+ const fs = __importStar(require("fs/promises"));
42
+ const FileManager_1 = require("../../FileManager");
43
+ const Log_1 = require("../../../utils/Log");
44
+ var CommandType;
45
+ (function (CommandType) {
46
+ CommandType[CommandType["SLASH"] = 1] = "SLASH";
47
+ CommandType[CommandType["USER_CONTEXT_MENU"] = 2] = "USER_CONTEXT_MENU";
48
+ CommandType[CommandType["MESSAGE_CONTEXT_MENU"] = 3] = "MESSAGE_CONTEXT_MENU";
49
+ })(CommandType || (exports.CommandType = CommandType = {}));
50
+ class BaseInteractionManager {
51
+ constructor(clientId, token) {
52
+ this.clientId = clientId;
53
+ this.token = token;
54
+ this.rest = new rest_1.REST({ version: '10' }).setToken(token);
55
+ }
56
+ async listFromFile() {
57
+ console.log(`Listing Handlers (${this.folderPath}) not deployed on discord`);
58
+ try {
59
+ const files = await FileManager_1.FileManager.listJsonFiles(`./handlers/${this.folderPath}`);
60
+ if (!files || files.length === 0) {
61
+ console.log('No files found');
62
+ return [];
63
+ }
64
+ 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)
68
+ continue;
69
+ const commandWithIndex = {
70
+ ...cmd,
71
+ index: index,
72
+ filename: file
73
+ };
74
+ commandList.push(commandWithIndex);
75
+ }
76
+ console.log(`✅ ${commandList.length} local ${this.folderPath}(s) not deployed\n`);
77
+ console.table(commandList.map((cmd) => ({
78
+ '#': cmd.index,
79
+ Nom: cmd.name,
80
+ Type: cmd.type === CommandType.SLASH ? 'Slash' :
81
+ cmd.type === CommandType.USER_CONTEXT_MENU ? 'User' : 'Message',
82
+ Description: cmd.description,
83
+ Fichier: cmd.filename
84
+ })));
85
+ return commandList;
86
+ }
87
+ catch (error) {
88
+ Log_1.Log.error(`${error.message}`);
89
+ return [];
90
+ }
91
+ }
92
+ async fetchCommands(endpoint, scope, guildId, printResult = true) {
93
+ const scopeLabel = scope === 'global' ? 'global' : `guild ${scope}`;
94
+ console.log(`Handlers ${this.folderPath} on Discord (${scopeLabel})...`);
95
+ try {
96
+ const rawCmds = await this.rest.get(endpoint);
97
+ const commands = rawCmds.filter(cmd => this.commandType.includes(cmd.type));
98
+ const commandList = commands.map((cmd, index) => ({
99
+ index: index,
100
+ name: cmd.name,
101
+ type: cmd.type,
102
+ description: cmd.description || 'N/A',
103
+ default_member_permissions: cmd.default_member_permissions,
104
+ default_member_permissions_string: this.bitfieldToPermissions(cmd.default_member_permissions),
105
+ id: cmd.id,
106
+ ...(guildId && { guildID: [guildId] })
107
+ }));
108
+ if (printResult) {
109
+ console.log(`✅ ${commandList.length} ${this.folderPath}(s) found\n`);
110
+ console.table(commandList.map((cmd) => ({
111
+ Nom: cmd.name,
112
+ Type: cmd.type === CommandType.SLASH ? 'Slash' :
113
+ cmd.type === CommandType.USER_CONTEXT_MENU ? 'User Context Menu' : 'Message Context Menu',
114
+ Description: cmd.description,
115
+ Permissions: cmd.default_member_permissions_string?.join(", "),
116
+ ID: cmd.id
117
+ })));
118
+ }
119
+ return commandList;
120
+ }
121
+ catch (error) {
122
+ const errorMsg = scope === 'global'
123
+ ? `❌ Error: ${error.message}`
124
+ : `❌ Guild error ${scope}: ${error.message}`;
125
+ Log_1.Log.error(errorMsg);
126
+ return [];
127
+ }
128
+ }
129
+ async list() {
130
+ return this.fetchCommands(v10_1.Routes.applicationCommands(this.clientId), 'global');
131
+ }
132
+ async listGuild(guildID) {
133
+ return this.fetchCommands(v10_1.Routes.applicationGuildCommands(this.clientId, guildID), guildID, guildID);
134
+ }
135
+ async listAllGuilds(guilds) {
136
+ console.log("📡 Getting all guilds...\n");
137
+ console.log(`📋 ${guilds.length} guild(s) found\n`);
138
+ if (!guilds.length)
139
+ return [];
140
+ const guildCommandPromises = guilds.map(async (guild) => {
141
+ try {
142
+ const commands = await this.fetchCommands(v10_1.Routes.applicationGuildCommands(this.clientId, guild.id), guild.id, guild.id, false);
143
+ return {
144
+ guild: `${guild.name} (${guild.id})`,
145
+ guildId: guild.id,
146
+ commands,
147
+ count: commands.length
148
+ };
149
+ }
150
+ catch (error) {
151
+ console.error(`⚠️ Guild ${guild.id}: ${error.message}`);
152
+ return {
153
+ guild: `${guild.name} (${guild.id})`,
154
+ guildId: guild.id,
155
+ commands: [],
156
+ count: 0
157
+ };
158
+ }
159
+ });
160
+ const results = await Promise.all(guildCommandPromises);
161
+ console.log("\n📊 INTERACTION PER GUILD :\n");
162
+ console.table(results.map(r => ({
163
+ "Guild": r.guild,
164
+ "Interactions": r.count,
165
+ "Total": r.commands.length
166
+ })));
167
+ return results.filter(r => r.count > 0);
168
+ }
169
+ async deploy(commands) {
170
+ console.log(`Deploying ${commands.length} ${this.folderPath}(s)...`);
171
+ let updatedCount = 0;
172
+ for (const cmd of commands) {
173
+ const file = cmd.filename;
174
+ if (!file) {
175
+ Log_1.Log.error(`${cmd.name}: Not linked to a file (wtf)`);
176
+ continue;
177
+ }
178
+ try {
179
+ await this.deploySingleInteraction(cmd, file);
180
+ updatedCount++;
181
+ }
182
+ catch (error) {
183
+ Log_1.Log.error(`Error ${file}: ${error.message}`);
184
+ }
185
+ }
186
+ console.log(`✅ ${updatedCount}/${commands.length} deployed`);
187
+ }
188
+ async delete(commands) {
189
+ console.log(`Deleting ${commands.length} ${this.folderPath}(s)...`);
190
+ const IDList = [];
191
+ for (const cmd of commands) {
192
+ if (!cmd.id) {
193
+ Log_1.Log.error(`${cmd.name}: No Discord ID, cannot delete the ${this.folderPath}`);
194
+ continue;
195
+ }
196
+ IDList.push(cmd.id);
197
+ try {
198
+ await this.rest.delete(v10_1.Routes.applicationCommand(this.clientId, cmd.id));
199
+ console.log(`${cmd.name} (${cmd.id.slice(-8)}) deleted`);
200
+ }
201
+ catch (error) {
202
+ Log_1.Log.error(`${cmd.name} (${cmd.id.slice(-8)}): ${error.message}`);
203
+ }
204
+ }
205
+ await this.removeLocalIdFromFile(IDList);
206
+ }
207
+ async update(commands) {
208
+ console.log(`Updating ${commands.length} ${this.folderPath}(s)...`);
209
+ for (const cmd of commands) {
210
+ if (!cmd.id) {
211
+ Log_1.Log.error(`${cmd.name}: No Discord ID, cannot update the ${this.folderPath}`);
212
+ continue;
213
+ }
214
+ try {
215
+ await this.rest.patch(v10_1.Routes.applicationCommand(this.clientId, cmd.id), {
216
+ body: { description: `${cmd.description} (Updated ${new Date().toISOString()})` }
217
+ });
218
+ console.log(`${cmd.name} updated`);
219
+ }
220
+ catch (error) {
221
+ Log_1.Log.error(`${cmd.name}: ${error.message}`);
222
+ }
223
+ }
224
+ }
225
+ async deploySingleInteraction(cmd, file) {
226
+ const deployToGuilds = cmd.guildID?.length ? cmd.guildID : [];
227
+ const dataToSend = { ...cmd };
228
+ delete dataToSend.guildID;
229
+ if (cmd.default_member_permissions_string && Array.isArray(cmd.default_member_permissions_string)) {
230
+ const bitfield = this.permissionsToBitfield(cmd.default_member_permissions_string);
231
+ if (bitfield !== undefined) {
232
+ dataToSend.default_member_permissions = bitfield;
233
+ cmd.default_member_permissions = bitfield;
234
+ }
235
+ else {
236
+ delete dataToSend.default_member_permissions;
237
+ }
238
+ }
239
+ if (cmd.type === CommandType.MESSAGE_CONTEXT_MENU || cmd.type === CommandType.USER_CONTEXT_MENU) {
240
+ delete dataToSend.options;
241
+ }
242
+ // Guild deployment
243
+ if (deployToGuilds.length > 0) {
244
+ for (const guildId of deployToGuilds) {
245
+ try {
246
+ const guildCmds = await this.rest.get(v10_1.Routes.applicationGuildCommands(this.clientId, guildId));
247
+ const found = guildCmds.find((c) => c.name === cmd.name);
248
+ if (!cmd.id || !found) {
249
+ const resp = await this.rest.post(v10_1.Routes.applicationGuildCommands(this.clientId, guildId), { body: dataToSend });
250
+ cmd.id = resp.id;
251
+ await this.saveInteraction(file, cmd);
252
+ }
253
+ else {
254
+ await this.rest.patch(v10_1.Routes.applicationGuildCommand(this.clientId, guildId, found.id), { body: dataToSend });
255
+ }
256
+ }
257
+ catch (error) {
258
+ console.error(`⚠️ Guild ${guildId}: ${error.message}`);
259
+ }
260
+ }
261
+ }
262
+ else {
263
+ // Global deployment
264
+ try {
265
+ const globalCmds = await this.rest.get(v10_1.Routes.applicationCommands(this.clientId));
266
+ const found = globalCmds.find((c) => c.name === cmd.name);
267
+ if (!cmd.id || !found) {
268
+ const resp = await this.rest.post(v10_1.Routes.applicationCommands(this.clientId), { body: dataToSend });
269
+ cmd.id = resp.id;
270
+ await this.saveInteraction(file, cmd);
271
+ }
272
+ else {
273
+ await this.rest.patch(v10_1.Routes.applicationCommand(this.clientId, found.id), { body: dataToSend });
274
+ }
275
+ }
276
+ catch (error) {
277
+ console.error(`⚠️ Global: ${error.message}`);
278
+ }
279
+ }
280
+ }
281
+ async readInteraction(filePath) {
282
+ try {
283
+ const data = await fs.readFile(filePath, 'utf8');
284
+ return JSON.parse(data);
285
+ }
286
+ catch {
287
+ return null;
288
+ }
289
+ }
290
+ async saveInteraction(fileName, cmd) {
291
+ delete cmd.filename;
292
+ const filePath = `./handlers/${this.folderPath}/${fileName}`;
293
+ await fs.writeFile(filePath, JSON.stringify(cmd, null, 2));
294
+ }
295
+ async removeLocalIdFromFile(idListToDelete) {
296
+ const files = await FileManager_1.FileManager.listJsonFiles(`./handlers/${this.folderPath}`);
297
+ if (!files || files.length === 0) {
298
+ console.log('No local files to clean');
299
+ return;
300
+ }
301
+ for (const file of files) {
302
+ const filePath = `./handlers/${this.folderPath}/${file}`;
303
+ const localCmd = await this.readInteraction(filePath);
304
+ if (localCmd && localCmd.id && idListToDelete.includes(localCmd.id)) {
305
+ delete localCmd.id;
306
+ await this.saveInteraction(file, localCmd);
307
+ break;
308
+ }
309
+ }
310
+ }
311
+ permissionsToBitfield(perms) {
312
+ if (!perms || perms.length === 0)
313
+ return undefined;
314
+ let bits = 0n;
315
+ for (const name of perms) {
316
+ const value = discord_js_1.PermissionFlagsBits[name];
317
+ if (!value) {
318
+ console.warn(`Unknow permission in default_member_permissions: ${name}`);
319
+ continue;
320
+ }
321
+ bits |= value;
322
+ }
323
+ return bits.toString();
324
+ }
325
+ bitfieldToPermissions(bitfield) {
326
+ if (!bitfield)
327
+ return [];
328
+ const bits = BigInt(bitfield);
329
+ const result = [];
330
+ for (const [name, value] of Object.entries(discord_js_1.PermissionFlagsBits)) {
331
+ if ((bits & value) === value) {
332
+ result.push(name);
333
+ }
334
+ }
335
+ return result;
336
+ }
337
+ }
338
+ exports.BaseInteractionManager = BaseInteractionManager;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AllInteractionManager = exports.ContextMenuManager = exports.CommandManager = void 0;
4
+ const BaseInteractionManager_1 = require("./BaseInteractionManager");
5
+ const FolderName_1 = require("../../../type/FolderName");
6
+ class CommandManager extends BaseInteractionManager_1.BaseInteractionManager {
7
+ constructor() {
8
+ super(...arguments);
9
+ this.commandType = [BaseInteractionManager_1.CommandType.SLASH];
10
+ this.folderPath = FolderName_1.FolderName.SLASH_COMMANDS;
11
+ }
12
+ }
13
+ exports.CommandManager = CommandManager;
14
+ class ContextMenuManager extends BaseInteractionManager_1.BaseInteractionManager {
15
+ constructor() {
16
+ super(...arguments);
17
+ this.commandType = [BaseInteractionManager_1.CommandType.USER_CONTEXT_MENU, BaseInteractionManager_1.CommandType.MESSAGE_CONTEXT_MENU];
18
+ this.folderPath = FolderName_1.FolderName.CONTEXT_MENU;
19
+ }
20
+ }
21
+ exports.ContextMenuManager = ContextMenuManager;
22
+ class AllInteractionManager extends BaseInteractionManager_1.BaseInteractionManager {
23
+ constructor() {
24
+ super(...arguments);
25
+ this.commandType = [BaseInteractionManager_1.CommandType.SLASH, BaseInteractionManager_1.CommandType.USER_CONTEXT_MENU, BaseInteractionManager_1.CommandType.MESSAGE_CONTEXT_MENU];
26
+ this.folderPath = undefined;
27
+ }
28
+ }
29
+ exports.AllInteractionManager = AllInteractionManager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spatulox/simplediscordbot",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "author": "Spatulox",
5
5
  "description": "Simple discord bot framework to set up a bot under 30 secondes",
6
6
  "main": "./dist/index.js",
Binary file