zumito-framework 1.1.71 → 1.1.73

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.
@@ -7,6 +7,7 @@ import { FrameworkSettings } from './types/FrameworkSettings.js';
7
7
  import { Module } from './types/Module.js';
8
8
  import { StatusManager } from './managers/StatusManager.js';
9
9
  import { TranslationManager } from './TranslationManager.js';
10
+ import { EventManager } from './managers/EventManager.js';
10
11
  /**
11
12
  * @class ZumitoFramework
12
13
  * @description The main class of the framework.
@@ -99,6 +100,13 @@ export declare class ZumitoFramework {
99
100
  * @see {@link https://nodejs.org/api/events.html#events_class_eventemitter}
100
101
  */
101
102
  eventEmitter: EventEmitter;
103
+ /**
104
+ * Event manager for the framework.
105
+ * All events related to the framework and discord.js are handled here.
106
+ * @type {EventManager}
107
+ * @private
108
+ */
109
+ eventManager: EventManager;
102
110
  /**
103
111
  * @constructor
104
112
  * @param {FrameworkSettings} settings - The settings to use for the framework.
@@ -17,6 +17,7 @@ import cors from 'cors';
17
17
  import express from 'express';
18
18
  import http from 'http';
19
19
  import path from 'path';
20
+ import { EventManager } from './managers/EventManager.js';
20
21
  // import better-logging
21
22
  betterLogging(console);
22
23
  /**
@@ -111,6 +112,13 @@ export class ZumitoFramework {
111
112
  * @see {@link https://nodejs.org/api/events.html#events_class_eventemitter}
112
113
  */
113
114
  eventEmitter = new EventEmitter();
115
+ /**
116
+ * Event manager for the framework.
117
+ * All events related to the framework and discord.js are handled here.
118
+ * @type {EventManager}
119
+ * @private
120
+ */
121
+ eventManager;
114
122
  /**
115
123
  * @constructor
116
124
  * @param {FrameworkSettings} settings - The settings to use for the framework.
@@ -123,6 +131,7 @@ export class ZumitoFramework {
123
131
  this.events = new Map();
124
132
  this.translations = new TranslationManager();
125
133
  this.models = [];
134
+ this.eventManager = new EventManager();
126
135
  if (settings.logLevel) {
127
136
  console.logLevel = settings.logLevel;
128
137
  }
@@ -145,8 +154,10 @@ export class ZumitoFramework {
145
154
  */
146
155
  async initialize() {
147
156
  await this.initializeDatabase();
148
- this.initializeDiscordClient();
157
+ await this.initializeDiscordClient();
149
158
  this.startApiServer();
159
+ this.eventManager.addEventEmitter('discord', this.client);
160
+ this.eventManager.addEventEmitter('framework', this.eventEmitter);
150
161
  await this.registerModules();
151
162
  await this.refreshSlashCommands();
152
163
  if (this.settings.statusOptions) {
@@ -301,14 +312,17 @@ export class ZumitoFramework {
301
312
  * Logs in to the Discord API using the provided token and logs a message when the client is ready.
302
313
  * @private
303
314
  */
304
- initializeDiscordClient() {
315
+ async initializeDiscordClient() {
305
316
  this.client = new Client({
306
317
  intents: this.settings.discordClientOptions.intents,
307
318
  });
308
319
  this.client.login(this.settings.discordClientOptions.token);
309
- this.client.on('ready', () => {
310
- // Bot emoji
311
- console.log('[🤖🟢] Discord client ready');
320
+ await new Promise((resolve) => {
321
+ this.client.on('ready', () => {
322
+ // Bot emoji
323
+ console.log('[🤖🟢] Discord client ready');
324
+ resolve();
325
+ });
312
326
  });
313
327
  }
314
328
  /**
@@ -0,0 +1,8 @@
1
+ import { Command } from '../../../types/Command.js';
2
+ import { EventParameters } from '../../../types/EventParameters.js';
3
+ import { FrameworkEvent } from '../../../types/FrameworkEvent.js';
4
+ export declare class InteractionCreate extends FrameworkEvent {
5
+ once: boolean;
6
+ execute({ interaction, client, framework, }: EventParameters): Promise<void>;
7
+ getTransMethod(commandInstance: Command, framework: any, guildSettings: any): (key: string, params?: any) => any;
8
+ }
@@ -0,0 +1,128 @@
1
+ import { CommandType } from '../../../types/CommandType.js';
2
+ import { FrameworkEvent } from '../../../types/FrameworkEvent.js';
3
+ export class InteractionCreate extends FrameworkEvent {
4
+ once = false;
5
+ async execute({ interaction, client, framework, }) {
6
+ let guildSettings;
7
+ if (interaction.guildId) {
8
+ guildSettings = await framework.getGuildSettings(interaction.guildId);
9
+ }
10
+ if (interaction.isCommand()) {
11
+ if (!framework.commands.has(interaction.commandName))
12
+ return;
13
+ const commandInstance = framework.commands.get(interaction.commandName);
14
+ const args = new Map();
15
+ commandInstance.args.forEach((arg) => {
16
+ const option = interaction.options.get(arg.name);
17
+ if (option) {
18
+ switch (arg.type) {
19
+ case 'user':
20
+ args.set(arg.name, option.user);
21
+ break;
22
+ case 'member':
23
+ args.set(arg.name, option.member);
24
+ break;
25
+ default:
26
+ args.set(arg.name, option.value ||
27
+ option.user ||
28
+ option.role ||
29
+ option.channel ||
30
+ option.options ||
31
+ option.message ||
32
+ option.member ||
33
+ option.focused ||
34
+ option.autocomplete ||
35
+ option.attachment);
36
+ break;
37
+ }
38
+ }
39
+ });
40
+ if (![CommandType.any, CommandType.separated, CommandType.slash,].includes(commandInstance.type))
41
+ return;
42
+ const trans = this.getTransMethod(commandInstance, framework, guildSettings);
43
+ if (commandInstance.type === CommandType.separated ||
44
+ commandInstance.type === CommandType.slash) {
45
+ await commandInstance.executeSlashCommand({
46
+ client, interaction, args, framework, guildSettings, trans,
47
+ });
48
+ }
49
+ else {
50
+ await commandInstance.execute({
51
+ client, interaction, args, framework, guildSettings, trans,
52
+ });
53
+ }
54
+ }
55
+ else if (interaction.isButton()) {
56
+ interaction = interaction;
57
+ const path = interaction.customId.split('.');
58
+ const commandInstance = framework.commands.get(path[0]);
59
+ if (!commandInstance)
60
+ throw new Error(`Command ${path[0]} not found or button id bad formatted`);
61
+ // If the command has impements ButtonPress class then execute the method
62
+ if (commandInstance.constructor.prototype.hasOwnProperty('buttonPressed')) {
63
+ commandInstance.buttonPressed({
64
+ path,
65
+ interaction,
66
+ client,
67
+ framework,
68
+ guildSettings,
69
+ });
70
+ }
71
+ }
72
+ else if (interaction.isStringSelectMenu()) {
73
+ const path = interaction.customId.split('.');
74
+ const commandInstance = framework.commands.get(path[0]);
75
+ if (!commandInstance)
76
+ throw new Error(`Command ${path[0]} not found or select menu id bad formatted`);
77
+ const trans = (key, params) => {
78
+ if (key.startsWith('$')) {
79
+ return framework.translations.get(key.replace('$', ''), guildSettings.lang, params);
80
+ }
81
+ else {
82
+ return framework.translations.get('command.' + commandInstance.name + '.' + key, guildSettings.lang, params);
83
+ }
84
+ };
85
+ if (commandInstance.selectMenu) {
86
+ commandInstance.selectMenu({
87
+ path,
88
+ interaction,
89
+ client,
90
+ framework,
91
+ guildSettings,
92
+ trans,
93
+ });
94
+ }
95
+ }
96
+ else if (interaction.isModalSubmit()) {
97
+ const path = interaction.customId.split('.');
98
+ const commandInstance = framework.commands.get(path[0]);
99
+ if (!commandInstance) {
100
+ throw new Error(`Command ${path[0]} not found or modal id bad formatted`);
101
+ }
102
+ if (commandInstance.modalSubmit) {
103
+ commandInstance.modalSubmit({
104
+ path,
105
+ interaction,
106
+ client,
107
+ framework,
108
+ guildSettings,
109
+ });
110
+ }
111
+ else {
112
+ framework.eventManager.emitEvent('modalSubmit', 'framework', {
113
+ path, interaction, client, framework, guildSettings,
114
+ });
115
+ }
116
+ }
117
+ }
118
+ getTransMethod(commandInstance, framework, guildSettings) {
119
+ return (key, params) => {
120
+ if (key.startsWith('$')) {
121
+ return framework.translations.get(key.replace('$', ''), guildSettings.lang, params);
122
+ }
123
+ else {
124
+ return framework.translations.get('command.' + commandInstance.name + '.' + key, guildSettings.lang, params);
125
+ }
126
+ };
127
+ }
128
+ }
@@ -0,0 +1,16 @@
1
+ import { ActionRowBuilder, EmbedBuilder } from 'discord.js';
2
+ import { EventParameters } from '../../../types/EventParameters.js';
3
+ import { FrameworkEvent } from '../../../types/FrameworkEvent.js';
4
+ export declare class MessageCreate extends FrameworkEvent {
5
+ once: boolean;
6
+ execute({ message, framework }: EventParameters): Promise<import("discord.js").Message<boolean>>;
7
+ autocorrect(str: string, words: string[]): any;
8
+ getErrorEmbed(error: any, parse: any): {
9
+ embeds: EmbedBuilder[];
10
+ components: ActionRowBuilder<import("@discordjs/builders").AnyComponentBuilder>[];
11
+ allowedMentions: {
12
+ repliedUser: boolean;
13
+ };
14
+ };
15
+ parseError(error: any): any;
16
+ }
@@ -0,0 +1,269 @@
1
+ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelType, EmbedBuilder, PermissionsBitField, } from 'discord.js';
2
+ import ErrorStackParser from 'error-stack-parser';
3
+ import { FrameworkEvent } from '../../../types/FrameworkEvent.js';
4
+ import { ZumitoFramework } from '../../../ZumitoFramework.js';
5
+ import leven from 'leven';
6
+ import path from 'path';
7
+ import { InteractionIdGenerator } from '../../../utils/InteractionIdGenerator.js';
8
+ export class MessageCreate extends FrameworkEvent {
9
+ once = false;
10
+ async execute({ message, framework }) {
11
+ const channel = message.channel;
12
+ const prefix = framework.settings.defaultPrefix;
13
+ const args = ZumitoFramework.splitCommandLine(message.content.slice(prefix.length));
14
+ const command = args.shift().toLowerCase();
15
+ let commandInstance;
16
+ if (message.content.startsWith(prefix)) {
17
+ if (!framework.commands.has(command)) {
18
+ const commandNames = Array.from(framework.commands.keys());
19
+ const correctedCommand = this.autocorrect(command, commandNames);
20
+ if (framework.commands.has(correctedCommand)) {
21
+ commandInstance = framework.commands.get(correctedCommand);
22
+ }
23
+ else {
24
+ return; // Command not found
25
+ }
26
+ }
27
+ else {
28
+ commandInstance = framework.commands.get(command);
29
+ }
30
+ if (message.guild == null && commandInstance.dm == false)
31
+ return;
32
+ if (commandInstance.adminOnly ||
33
+ commandInstance.userPermissions.length > 0) {
34
+ let denied = false;
35
+ if (framework.memberHasPermission(message.member, message.channel, PermissionsBitField.Flags.Administrator) ||
36
+ message.member.id != message.guild.ownerId) {
37
+ if (commandInstance.userPermissions.length > 0) {
38
+ commandInstance.userPermissions.forEach((permission) => {
39
+ if (!framework.memberHasPermission(message.member, message.channel, permission)) {
40
+ denied = true;
41
+ }
42
+ });
43
+ }
44
+ }
45
+ if (denied) {
46
+ return message.reply({
47
+ content: 'You do not have permission to use this command.',
48
+ allowedMentions: {
49
+ repliedUser: false,
50
+ },
51
+ });
52
+ }
53
+ }
54
+ if (message.channel.isTextBased) {
55
+ const channel = message.channel;
56
+ // Check command is nsfw and if channel is allowed
57
+ if (commandInstance.nsfw &&
58
+ !channel.nsfw &&
59
+ !channel
60
+ .permissionsFor(message.member)
61
+ .has(PermissionsBitField.Flags.Administrator) &&
62
+ message.member.id != message.guild.ownerId) {
63
+ return message.reply({
64
+ content: 'This command is nsfw and this channel is not nsfw.',
65
+ allowedMentions: {
66
+ repliedUser: false,
67
+ },
68
+ });
69
+ }
70
+ }
71
+ try {
72
+ const guildSettings = await framework.getGuildSettings(message.guildId);
73
+ const parsedArgs = new Map();
74
+ for (let i = 0; i < args.length; i++) {
75
+ const arg = args[i];
76
+ const type = commandInstance.args[i]?.type;
77
+ if (type) {
78
+ if (type == 'member' || type == 'user') {
79
+ const member = await message.guild.members.cache.get(arg.replace(/[<@!>]/g, ''));
80
+ if (member) {
81
+ if (type == 'user') {
82
+ parsedArgs.set(commandInstance.args[i].name, member.user);
83
+ }
84
+ else {
85
+ parsedArgs.set(commandInstance.args[i].name, member);
86
+ }
87
+ }
88
+ else {
89
+ return message.reply({
90
+ content: 'Invalid user.',
91
+ allowedMentions: {
92
+ repliedUser: false,
93
+ },
94
+ });
95
+ }
96
+ }
97
+ else if (type == 'string') {
98
+ parsedArgs.set(commandInstance.args?.[i]?.name || i.toString(), arg);
99
+ }
100
+ }
101
+ }
102
+ const interactionIdGenerator = new InteractionIdGenerator(undefined, commandInstance.name);
103
+ await commandInstance.execute({
104
+ message,
105
+ args: parsedArgs,
106
+ client: framework.client,
107
+ framework: framework,
108
+ guildSettings: guildSettings,
109
+ trans: (key, params) => {
110
+ if (key.startsWith('$')) {
111
+ return framework.translations.get(key.replace('$', ''), guildSettings.lang, params);
112
+ }
113
+ else {
114
+ return framework.translations.get('command.' + commandInstance.name + '.' + key, guildSettings.lang, params);
115
+ }
116
+ },
117
+ });
118
+ if (!message.channel.isDMBased && !message.deletable) {
119
+ return; // TODO: test if this works
120
+ // false = settings.deleteCommands
121
+ try {
122
+ message.delete().catch(function () {
123
+ console.error("can't delete user command");
124
+ });
125
+ }
126
+ catch (err) {
127
+ console.error(err.name, err.message);
128
+ }
129
+ }
130
+ }
131
+ catch (error) {
132
+ const content = await this.getErrorEmbed({
133
+ name: error.name,
134
+ message: error.message,
135
+ command: commandInstance,
136
+ args: args,
137
+ stack: error.stack,
138
+ }, true);
139
+ try {
140
+ message.reply(content);
141
+ }
142
+ catch (e) {
143
+ if (channel.type !== ChannelType.GuildStageVoice) {
144
+ channel.send(content);
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+ autocorrect(str, words) {
151
+ let distance, bestWord, i, word, min;
152
+ const dictionary = words || [];
153
+ const len = dictionary.length;
154
+ for (i = 0; i < len; i++) {
155
+ word = dictionary[i];
156
+ distance = leven(str, word);
157
+ if (distance === 0) {
158
+ return word;
159
+ }
160
+ else if (min === undefined || distance < min) {
161
+ min = distance;
162
+ bestWord = word;
163
+ }
164
+ }
165
+ return bestWord;
166
+ }
167
+ getErrorEmbed(error, parse) {
168
+ let parsedError;
169
+ if (parse) {
170
+ parsedError = this.parseError(error);
171
+ }
172
+ else {
173
+ parsedError = error;
174
+ }
175
+ const embed = new EmbedBuilder()
176
+ .setTitle('Error')
177
+ .setDescription('An error has occured while executing this command.')
178
+ .setTimestamp()
179
+ .addFields([
180
+ {
181
+ name: 'Command:',
182
+ value: error.command.name || 'Not defined',
183
+ },
184
+ ])
185
+ .addFields([
186
+ {
187
+ name: 'Arguments:',
188
+ value: error.args.toString() || 'None',
189
+ },
190
+ ])
191
+ .addFields([
192
+ {
193
+ name: 'Error name:',
194
+ value: error.name || 'Not defined',
195
+ },
196
+ ])
197
+ .addFields([
198
+ {
199
+ name: 'Error message:',
200
+ value: error.message || 'Not defined',
201
+ },
202
+ ]);
203
+ if (error.possibleSolutions !== undefined) {
204
+ error.possibleSolutions.forEach((solution) => {
205
+ embed.addFields([
206
+ {
207
+ name: 'Posible solution:',
208
+ value: solution,
209
+ },
210
+ ]);
211
+ });
212
+ }
213
+ const stackFrames = ErrorStackParser.parse(error).filter((e) => !e.fileName.includes('node_modules') &&
214
+ !e.fileName.includes('node:internal'));
215
+ let stack = '';
216
+ const path1 = path.resolve('./');
217
+ const path2 = path1.replaceAll('\\', '/');
218
+ stackFrames.forEach((frame) => {
219
+ stack += `[${frame.fileName
220
+ .replace(path1, '')
221
+ .replace(path2, '')
222
+ .replace('file://', '')}:${frame.lineNumber}](https://zumito.ga/redirect?url=vscode://file/${frame.fileName.replace('file://', '')}:${frame.lineNumber}) ${frame.functionName}()\n`;
223
+ });
224
+ if (error.stack !== undefined) {
225
+ embed.addFields([
226
+ {
227
+ name: 'Call stack:',
228
+ value: stack || error.stack || error.stack.toString(),
229
+ },
230
+ ]);
231
+ }
232
+ if (error.details !== undefined) {
233
+ error.details.forEach((detail) => {
234
+ embed.addFields([
235
+ {
236
+ name: 'Detail:',
237
+ value: detail,
238
+ },
239
+ ]);
240
+ });
241
+ }
242
+ const body = `\n\n\n---\nComand:\`\`\`${error.command.name || 'not defined'}\`\`\`\nArguments:\`\`\`${error.args.toString() || 'none'}\`\`\`\nError:\`\`\`${error.name || 'not defined'}\`\`\`\nError message:\`\`\`${error.message || 'not defined'}\`\`\`\n`;
243
+ const requestUrl = `https://github.com/ZumitoTeam/Zumito/issues/new?body=${encodeURIComponent(body)}`;
244
+ const row = new ActionRowBuilder().addComponents(new ButtonBuilder()
245
+ .setStyle(ButtonStyle.Link)
246
+ .setLabel('Report error')
247
+ .setEmoji('975645505302437978')
248
+ .setURL(requestUrl));
249
+ return {
250
+ embeds: [embed],
251
+ components: [row],
252
+ allowedMentions: {
253
+ repliedUser: false,
254
+ },
255
+ };
256
+ }
257
+ parseError(error) {
258
+ error.possibleSolutions = [];
259
+ if (/(?:^|(?<= ))(EmbedBuilder|Discord|ActionRowBuilder|ButtonBuilder|MessageSelectMenu)(?:(?= )|$) is not defined/gm.test(error.message)) {
260
+ error.possibleSolutions.push('const { ' +
261
+ error.message.split(' ')[0] +
262
+ " } = require('discord.js');");
263
+ }
264
+ else if (error.message.includes('A custom id and url cannot both be specified')) {
265
+ error.possibleSolutions.push('Remove .setCustomId(...) or .setURL(...)');
266
+ }
267
+ return error;
268
+ }
269
+ }
@@ -1,6 +1,7 @@
1
+ /* eslint-disable check-file/filename-naming-convention */
1
2
  import { Module } from '../types/Module.js';
2
- import { InteractionCreate } from './events/discord/interactionCreate.js';
3
- import { MessageCreate } from './events/discord/messageCreate.js';
3
+ import { InteractionCreate } from './events/discord/InteractionCreate.js';
4
+ import { MessageCreate } from './events/discord/MessageCreate.js';
4
5
  import { Guild } from './models/Guild.js';
5
6
  export class baseModule extends Module {
6
7
  constructor(modulePath, framework) {
@@ -10,7 +11,7 @@ export class baseModule extends Module {
10
11
  this.events.set('interactionCreate', new InteractionCreate());
11
12
  this.events.set('messageCreate', new MessageCreate());
12
13
  this.events.forEach((event) => {
13
- this.registerDiscordEvent(event);
14
+ this.registerEvent(event, 'discord');
14
15
  });
15
16
  }
16
17
  async registerModels() {
@@ -0,0 +1,8 @@
1
+ export declare class EventManager {
2
+ eventEmitters: Map<string, any>;
3
+ addEventEmitter(name: string, eventEmitter: any): void;
4
+ getEventEmitter(name: string): any;
5
+ removeEventEmitter(name: string): void;
6
+ emitEvent(eventName: string, eventEmitterName: string, ...args: any): void;
7
+ addEventListener(eventEmitterName: string, eventName: string, callback: any, params?: any): void;
8
+ }
@@ -0,0 +1,27 @@
1
+ export class EventManager {
2
+ eventEmitters = new Map();
3
+ addEventEmitter(name, eventEmitter) {
4
+ this.eventEmitters.set(name, eventEmitter);
5
+ }
6
+ getEventEmitter(name) {
7
+ return this.eventEmitters.get(name);
8
+ }
9
+ removeEventEmitter(name) {
10
+ this.eventEmitters.delete(name);
11
+ }
12
+ emitEvent(eventName, eventEmitterName, ...args) {
13
+ const eventEmitter = this.getEventEmitter(eventEmitterName);
14
+ if (!eventEmitter)
15
+ throw new Error(`EventEmitter ${eventEmitterName} not found`);
16
+ eventEmitter.emit(eventName, ...args);
17
+ }
18
+ addEventListener(eventEmitterName, eventName, callback, params) {
19
+ const eventEmitter = this.getEventEmitter(eventEmitterName);
20
+ if (!eventEmitter)
21
+ throw new Error(`EventEmitter ${eventEmitterName} not found`);
22
+ const method = params?.once ? 'once' : 'on';
23
+ eventEmitter[method](eventName, (...args) => {
24
+ callback(...args);
25
+ });
26
+ }
27
+ }
@@ -16,7 +16,8 @@ export declare abstract class Module {
16
16
  onErrorLoadingCommand(error: Error): void;
17
17
  getCommands(): Map<string, Command>;
18
18
  registerEvents(): Promise<void>;
19
- registerDiscordEvent(frameworkEvent: FrameworkEvent): void;
19
+ registerEventsFolder(folder: string): Promise<void>;
20
+ registerEvent(frameworkEvent: FrameworkEvent, emitterName: string): void;
20
21
  parseEventArgs(args: any[]): any;
21
22
  getEvents(): Map<string, FrameworkEvent>;
22
23
  registerTranslations(subpath?: string): Promise<void>;
@@ -3,7 +3,7 @@ import chalk from 'chalk';
3
3
  import boxen from 'boxen';
4
4
  import * as fs from 'fs';
5
5
  import path from 'path';
6
- import { ButtonInteraction, CommandInteraction, StringSelectMenuInteraction, } from 'discord.js';
6
+ import { ButtonInteraction, CommandInteraction, ModalSubmitInteraction, StringSelectMenuInteraction, } from 'discord.js';
7
7
  export class Module {
8
8
  path;
9
9
  framework;
@@ -90,42 +90,40 @@ export class Module {
90
90
  return;
91
91
  const files = fs.readdirSync(path.join(this.path, 'events'));
92
92
  for (const file of files) {
93
- if (file == 'discord') {
94
- const moduleFileNames = fs.readdirSync(path.join(this.path, 'events', 'discord'));
95
- for (const moduleFileName of moduleFileNames) {
96
- if (moduleFileName.endsWith('.js') ||
97
- moduleFileName.endsWith('.ts')) {
98
- let event = await import('file://' +
99
- path.join(this.path, 'events', 'discord', moduleFileName)).catch((e) => {
100
- console.error(`[🔄🔴 ] Error loading ${moduleFileName.slice(0, -3)} event on module ${this.constructor.name}`);
101
- console.log(boxen(e + '\n' + e.name + '\n' + e.stack, {
102
- padding: 1,
103
- }));
104
- });
105
- event = Object.values(event)[0];
106
- event = new event();
107
- this.events.set(event.constructor.name.toLowerCase(), event);
108
- this.registerDiscordEvent(event);
109
- }
110
- }
93
+ // if file is folder
94
+ if (fs.lstatSync(path.join(this.path, 'events', file)).isDirectory()) {
95
+ console.log('registering events folder ' + file);
96
+ this.registerEventsFolder(file);
97
+ }
98
+ }
99
+ }
100
+ async registerEventsFolder(folder) {
101
+ const folderPath = path.join(this.path, 'events', folder);
102
+ if (!fs.existsSync(folderPath))
103
+ throw new Error(`Folder ${folder} doesn't exist`);
104
+ const files = fs.readdirSync(folderPath);
105
+ for (const file of files) {
106
+ if (file.endsWith('.js') || file.endsWith('.ts')) {
107
+ let event = await import('file://' + path.join(folderPath, file)).catch((e) => {
108
+ console.error(`[🔄🔴 ] Error loading ${file.slice(0, -3)} event on module ${this.constructor.name}`);
109
+ });
110
+ event = Object.values(event)[0];
111
+ event = new event();
112
+ this.events.set(event.constructor.name.toLowerCase(), event);
113
+ this.registerEvent(event, folder);
111
114
  }
112
115
  }
113
116
  }
114
- registerDiscordEvent(frameworkEvent) {
117
+ registerEvent(frameworkEvent, emitterName) {
115
118
  if (frameworkEvent.disabled)
116
119
  return;
120
+ const once = frameworkEvent.once;
117
121
  const eventName = frameworkEvent.constructor.name.charAt(0).toLowerCase() +
118
122
  frameworkEvent.constructor.name.slice(1);
119
- const emitter = this.framework.client;
120
- const once = frameworkEvent.once; // A simple variable which returns if the event should run once
121
- // Try catch block to throw an error if the code in try{} doesn't work
122
- try {
123
- emitter[once ? 'once' : 'on'](eventName, (...args) => frameworkEvent.execute(this.parseEventArgs(args))); // Run the event using the above defined emitter (client)
124
- }
125
- catch (error) {
126
- console.log(error, error.message, error, name);
127
- console.error(error.stack); // If there is an error, console log the error stack message
128
- }
123
+ this.framework.eventManager.addEventListener(emitterName, eventName, (...args) => {
124
+ const finalArgs = this.parseEventArgs(args);
125
+ frameworkEvent.execute(finalArgs);
126
+ }, { once });
129
127
  }
130
128
  parseEventArgs(args) {
131
129
  const finalArgs = {
@@ -137,7 +135,8 @@ export class Module {
137
135
  });
138
136
  const interaction = args.find((arg) => arg instanceof StringSelectMenuInteraction ||
139
137
  arg instanceof CommandInteraction ||
140
- arg instanceof ButtonInteraction);
138
+ arg instanceof ButtonInteraction ||
139
+ arg instanceof ModalSubmitInteraction);
141
140
  if (interaction) {
142
141
  finalArgs['interaction'] = interaction;
143
142
  }
@@ -0,0 +1,13 @@
1
+ export interface SelectMenuIdParams {
2
+ id: string;
3
+ params?: string[];
4
+ }
5
+ export declare class InteractionIdGenerator {
6
+ module: string;
7
+ command: string;
8
+ constructor(module?: string, command?: string);
9
+ protected generatePrefix(): string;
10
+ generateSelectMenuId(id: string, params?: string[]): string;
11
+ generateButtonId(id: string, params?: string[]): string;
12
+ generateModalId(id: string, params?: string[]): string;
13
+ }
@@ -0,0 +1,40 @@
1
+ export class InteractionIdGenerator {
2
+ module;
3
+ command;
4
+ constructor(module, command) {
5
+ this.module = module;
6
+ this.command = command;
7
+ }
8
+ generatePrefix() {
9
+ const components = [];
10
+ if (this.module)
11
+ components.push(this.module);
12
+ if (this.command)
13
+ components.push(this.command);
14
+ return components.join('.');
15
+ }
16
+ generateSelectMenuId(id, params) {
17
+ let components = [this.generatePrefix()];
18
+ if (id)
19
+ components.push(id);
20
+ if (params)
21
+ components = components.concat(params);
22
+ return components.join('.');
23
+ }
24
+ generateButtonId(id, params) {
25
+ let components = [this.generatePrefix()];
26
+ if (id)
27
+ components.push(id);
28
+ if (params)
29
+ components = components.concat(params);
30
+ return components.join('.');
31
+ }
32
+ generateModalId(id, params) {
33
+ let components = [this.generatePrefix()];
34
+ if (id)
35
+ components.push(id);
36
+ if (params)
37
+ components = components.concat(params);
38
+ return components.join('.');
39
+ }
40
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zumito-framework",
3
- "version": "1.1.71",
3
+ "version": "1.1.73",
4
4
  "description": "Discord.js bot framework",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -34,6 +34,7 @@
34
34
  "express": "^4.18.1",
35
35
  "leven": "^4.0.0",
36
36
  "mongoose": "^6.6.5",
37
+ "semver": "^7.5.0",
37
38
  "tingodb": "^0.6.1"
38
39
  },
39
40
  "devDependencies": {
@@ -42,6 +43,7 @@
42
43
  "@typescript-eslint/parser": "^5.48.2",
43
44
  "eslint": "^8.32.0",
44
45
  "eslint-config-prettier": "^8.6.0",
46
+ "eslint-plugin-check-file": "^2.2.0",
45
47
  "eslint-plugin-prettier": "^4.2.1",
46
48
  "prettier": "^2.8.3",
47
49
  "typedoc": "^0.23.14",
@@ -54,8 +56,12 @@
54
56
  },
55
57
  "typesVersions": {
56
58
  "*": {
57
- ".": ["dist/index.d.ts"],
58
- "discord": ["dist/discord/index.d.ts"]
59
+ ".": [
60
+ "dist/index.d.ts"
61
+ ],
62
+ "discord": [
63
+ "dist/discord/index.d.ts"
64
+ ]
59
65
  }
60
66
  },
61
67
  "repository": {