zumito-framework 1.1.78 → 1.1.80

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.
@@ -21,6 +21,9 @@ export declare abstract class Module {
21
21
  parseEventArgs(args: any[]): any;
22
22
  getEvents(): Map<string, FrameworkEvent>;
23
23
  registerTranslations(subpath?: string): Promise<void>;
24
+ onTranslationCreated(filePath: string): Promise<void>;
25
+ onTranslationChanged(filePath: string): Promise<void>;
26
+ onErrorLoadingTranslation(error: Error): void;
24
27
  loadTranslationFile(subpath: string, file: string): Promise<any>;
25
28
  parseTranslation(path: string, lang: string, json: any): any;
26
29
  registerModels(): Promise<void>;
@@ -37,7 +37,7 @@ export class Module {
37
37
  // register watcher
38
38
  if (process.env.DEBUG) {
39
39
  /*
40
- Debug only cause in prod environment commands should't be changed.
40
+ Debug only cause in prod environment commands should't be changed in runtime.
41
41
  Appart from that, esm module cache invalidation is not working properly
42
42
  and can cause memory leaks and crashes.
43
43
  */
@@ -164,11 +164,59 @@ export class Module {
164
164
  await this.registerTranslations(path.join(subpath, file));
165
165
  }
166
166
  }
167
+ // register watcher
168
+ if (process.env.DEBUG) {
169
+ /*
170
+ Debug only cause in prod environment translations should't be changed on runtime.
171
+ Appart from that, esm module cache invalidation is not working properly
172
+ and can cause memory leaks and crashes.
173
+ */
174
+ chokidar
175
+ .watch(path.resolve(path.join(this.path, 'translations')), {
176
+ ignored: /^\./,
177
+ persistent: true,
178
+ ignoreInitial: true,
179
+ })
180
+ .on('add', this.onTranslationCreated.bind(this))
181
+ .on('change', this.onTranslationChanged.bind(this))
182
+ //.on('unlink', function(path) {console.log('File', path, 'has been removed');})
183
+ .on('error', this.onErrorLoadingTranslation.bind(this));
184
+ }
185
+ }
186
+ async onTranslationCreated(filePath) {
187
+ if (filePath.endsWith('.json')) {
188
+ const subpath = path.dirname(filePath).replace(this.path + '/translations', '');
189
+ const fileName = path.basename(filePath);
190
+ const json = await this.loadTranslationFile(subpath, fileName);
191
+ const lang = fileName.slice(0, -5);
192
+ const baseKey = subpath
193
+ ? subpath.substring(1).replaceAll('/', '.').replaceAll('\\', '.') + '.'
194
+ : '';
195
+ this.parseTranslation(baseKey, lang, json);
196
+ console.debug('[🆕🟢 ] translations file ' + chalk.blue(filePath.replace(/^.*[\\\/]/, '').split('.').slice(0, -1).join('.')) + ' loaded');
197
+ }
198
+ }
199
+ async onTranslationChanged(filePath) {
200
+ if (filePath.endsWith('.json')) {
201
+ const subpath = path.dirname(filePath).replace(this.path + '/translations', '');
202
+ const fileName = path.basename(filePath);
203
+ const json = await this.loadTranslationFile(subpath, fileName);
204
+ const lang = fileName.slice(0, -5);
205
+ const baseKey = subpath
206
+ ? subpath.substring(1).replaceAll('/', '.').replaceAll('\\', '.') + '.'
207
+ : '';
208
+ this.parseTranslation(baseKey, lang, json);
209
+ console.debug('[🆕🟢 ] translations file ' + chalk.blue(filePath.replace(/^.*[\\\/]/, '').split('.').slice(0, -1).join('.')) + ' reloaded');
210
+ }
211
+ }
212
+ onErrorLoadingTranslation(error) {
213
+ console.error('[🔄🔴 ] Error reloading translation file');
214
+ console.log(boxen(error + '\n' + error.stack, { padding: 1 }));
167
215
  }
168
216
  async loadTranslationFile(subpath, file) {
169
217
  if (subpath)
170
218
  subpath = subpath + '/';
171
- const json = await import('file://' + `${this.path}/translations/${subpath}${file}`, {
219
+ const json = await import('file://' + `${this.path}/translations/${subpath}${file}?update=${Date.now().toString()}`, {
172
220
  assert: {
173
221
  type: 'json',
174
222
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zumito-framework",
3
- "version": "1.1.78",
3
+ "version": "1.1.80",
4
4
  "description": "Discord.js bot framework",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -34,7 +34,7 @@
34
34
  "express": "^4.18.1",
35
35
  "leven": "^4.0.0",
36
36
  "mongoose": "^6.6.5",
37
- "semver": "^7.5.3",
37
+ "semver": "^7.5.4",
38
38
  "tingodb": "^0.6.1"
39
39
  },
40
40
  "devDependencies": {
@@ -1,6 +0,0 @@
1
- import { Module } from "../types/Module";
2
- import { ZumitoFramework } from "../ZumitoFramework";
3
- export declare class baseModule extends Module {
4
- constructor(modulePath: string, framework: ZumitoFramework);
5
- registerEvents(): void;
6
- }
@@ -1,16 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.baseModule = void 0;
4
- const Module_1 = require("../types/Module");
5
- const interactionCreate_1 = require("./events/discord/interactionCreate");
6
- const messageCreate_1 = require("./events/discord/messageCreate");
7
- class baseModule extends Module_1.Module {
8
- constructor(modulePath, framework) {
9
- super(modulePath, framework);
10
- }
11
- registerEvents() {
12
- this.events.set('interactionCreate', new interactionCreate_1.InteractionCreate());
13
- this.events.set('messageCreate', new messageCreate_1.MessageCreate());
14
- }
15
- }
16
- exports.baseModule = baseModule;
@@ -1,8 +0,0 @@
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
- }
@@ -1,121 +0,0 @@
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 (![
41
- CommandType.any,
42
- CommandType.separated,
43
- CommandType.slash,
44
- ].includes(commandInstance.type))
45
- return;
46
- const trans = this.getTransMethod(commandInstance, framework, guildSettings);
47
- if (commandInstance.type === CommandType.separated ||
48
- commandInstance.type === CommandType.slash) {
49
- await commandInstance.executeSlashCommand({
50
- client,
51
- interaction,
52
- args,
53
- framework,
54
- guildSettings,
55
- trans,
56
- });
57
- }
58
- else {
59
- await commandInstance.execute({
60
- client,
61
- interaction,
62
- args,
63
- framework,
64
- guildSettings,
65
- trans,
66
- });
67
- }
68
- }
69
- else if (interaction.isButton()) {
70
- interaction = interaction;
71
- const path = interaction.customId.split('.');
72
- const commandInstance = framework.commands.get(path[0]);
73
- if (!commandInstance)
74
- throw new Error(`Command ${path[0]} not found or button id bad formatted`);
75
- // If the command has impements ButtonPress class then execute the method
76
- if (commandInstance.constructor.prototype.hasOwnProperty('buttonPressed')) {
77
- commandInstance.buttonPressed({
78
- path,
79
- interaction,
80
- client,
81
- framework,
82
- guildSettings,
83
- });
84
- }
85
- }
86
- else if (interaction.isSelectMenu()) {
87
- const path = interaction.customId.split('.');
88
- const commandInstance = framework.commands.get(path[0]);
89
- if (!commandInstance)
90
- throw new Error(`Command ${path[0]} not found or select menu id bad formatted`);
91
- const trans = (key, params) => {
92
- if (key.startsWith('$')) {
93
- return framework.translations.get(key.replace('$', ''), guildSettings.lang, params);
94
- }
95
- else {
96
- return framework.translations.get('command.' + commandInstance.name + '.' + key, guildSettings.lang, params);
97
- }
98
- };
99
- if (commandInstance.selectMenu) {
100
- commandInstance.selectMenu({
101
- path,
102
- interaction,
103
- client,
104
- framework,
105
- guildSettings,
106
- trans,
107
- });
108
- }
109
- }
110
- }
111
- getTransMethod(commandInstance, framework, guildSettings) {
112
- return (key, params) => {
113
- if (key.startsWith('$')) {
114
- return framework.translations.get(key.replace('$', ''), guildSettings.lang, params);
115
- }
116
- else {
117
- return framework.translations.get('command.' + commandInstance.name + '.' + key, guildSettings.lang, params);
118
- }
119
- };
120
- }
121
- }
@@ -1,16 +0,0 @@
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, client, 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
- }
@@ -1,271 +0,0 @@
1
- import * as url from 'url';
2
- import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelType, EmbedBuilder, PermissionsBitField, } from 'discord.js';
3
- import ErrorStackParser from 'error-stack-parser';
4
- import { FrameworkEvent } from '../../../types/FrameworkEvent.js';
5
- import { ZumitoFramework } from '../../../ZumitoFramework.js';
6
- import leven from 'leven';
7
- import path from 'path';
8
- export class MessageCreate extends FrameworkEvent {
9
- once = false;
10
- async execute({ message, client, 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
- const userMentionCount = 0;
75
- for (let i = 0; i < args.length; i++) {
76
- const arg = args[i];
77
- const type = commandInstance.args[i]?.type;
78
- if (type) {
79
- if (type == 'member' || type == 'user') {
80
- const member = await message.guild.members.cache.get(arg.replace(/[<@!>]/g, ''));
81
- if (member) {
82
- if (type == 'user') {
83
- parsedArgs.set(commandInstance.args[i].name, member.user);
84
- }
85
- else {
86
- parsedArgs.set(commandInstance.args[i].name, member);
87
- }
88
- }
89
- else {
90
- return message.reply({
91
- content: 'Invalid user.',
92
- allowedMentions: {
93
- repliedUser: false,
94
- },
95
- });
96
- }
97
- }
98
- else if (type == 'string') {
99
- parsedArgs.set(commandInstance.args?.[i]?.name || i.toString(), arg);
100
- }
101
- }
102
- }
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
- const metadata = await fetch('https://tulipo.ga/api/last_command/' + command).then((res) => res.json());
126
- }
127
- catch (err) {
128
- console.error(err.name, err.message);
129
- }
130
- }
131
- }
132
- catch (error) {
133
- const content = await this.getErrorEmbed({
134
- name: error.name,
135
- message: error.message,
136
- command: commandInstance,
137
- args: args,
138
- stack: error.stack,
139
- }, true);
140
- try {
141
- message.reply(content);
142
- }
143
- catch (e) {
144
- if (channel.type !== ChannelType.GuildStageVoice) {
145
- channel.send(content);
146
- }
147
- }
148
- }
149
- }
150
- }
151
- autocorrect(str, words) {
152
- let distance, bestWord, i, word, min;
153
- const dictionary = words || [];
154
- const len = dictionary.length;
155
- for (i = 0; i < len; i++) {
156
- word = dictionary[i];
157
- distance = leven(str, word);
158
- if (distance === 0) {
159
- return word;
160
- }
161
- else if (min === undefined || distance < min) {
162
- min = distance;
163
- bestWord = word;
164
- }
165
- }
166
- return bestWord;
167
- }
168
- getErrorEmbed(error, parse) {
169
- let parsedError;
170
- if (parse) {
171
- parsedError = this.parseError(error);
172
- }
173
- else {
174
- parsedError = error;
175
- }
176
- const embed = new EmbedBuilder()
177
- .setTitle('Error')
178
- .setDescription('An error has occured while executing this command.')
179
- .setTimestamp()
180
- .addFields([
181
- {
182
- name: 'Command:',
183
- value: error.command.name || 'Not defined',
184
- },
185
- ])
186
- .addFields([
187
- {
188
- name: 'Arguments:',
189
- value: error.args.toString() || 'None',
190
- },
191
- ])
192
- .addFields([
193
- {
194
- name: 'Error name:',
195
- value: error.name || 'Not defined',
196
- },
197
- ])
198
- .addFields([
199
- {
200
- name: 'Error message:',
201
- value: error.message || 'Not defined',
202
- },
203
- ]);
204
- if (error.possibleSolutions !== undefined) {
205
- error.possibleSolutions.forEach((solution) => {
206
- embed.addFields([
207
- {
208
- name: 'Posible solution:',
209
- value: solution,
210
- },
211
- ]);
212
- });
213
- }
214
- const stackFrames = ErrorStackParser.parse(error).filter((e) => !e.fileName.includes('node_modules') &&
215
- !e.fileName.includes('node:internal'));
216
- let stack = '';
217
- const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
218
- const path1 = path.resolve('./');
219
- const path2 = path1.replaceAll('\\', '/');
220
- stackFrames.forEach((frame) => {
221
- stack += `[${frame.fileName
222
- .replace(path1, '')
223
- .replace(path2, '')
224
- .replace('file://', '')}:${frame.lineNumber}](https://zumito.ga/redirect?url=vscode://file/${frame.fileName.replace('file://', '')}:${frame.lineNumber}) ${frame.functionName}()\n`;
225
- });
226
- if (error.stack !== undefined) {
227
- embed.addFields([
228
- {
229
- name: 'Call stack:',
230
- value: stack || error.stack || error.stack.toString(),
231
- },
232
- ]);
233
- }
234
- if (error.details !== undefined) {
235
- error.details.forEach((detail) => {
236
- embed.addFields([
237
- {
238
- name: 'Detail:',
239
- value: detail,
240
- },
241
- ]);
242
- });
243
- }
244
- 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`;
245
- const requestUrl = `https://github.com/ZumitoTeam/Zumito/issues/new?body=${encodeURIComponent(body)}`;
246
- const row = new ActionRowBuilder().addComponents(new ButtonBuilder()
247
- .setStyle(ButtonStyle.Link)
248
- .setLabel('Report error')
249
- .setEmoji('975645505302437978')
250
- .setURL(requestUrl));
251
- return {
252
- embeds: [embed],
253
- components: [row],
254
- allowedMentions: {
255
- repliedUser: false,
256
- },
257
- };
258
- }
259
- parseError(error) {
260
- error.possibleSolutions = [];
261
- if (/(?:^|(?<= ))(EmbedBuilder|Discord|ActionRowBuilder|ButtonBuilder|MessageSelectMenu)(?:(?= )|$) is not defined/gm.test(error.message)) {
262
- error.possibleSolutions.push('const { ' +
263
- error.message.split(' ')[0] +
264
- " } = require('discord.js');");
265
- }
266
- else if (error.message.includes('A custom id and url cannot both be specified')) {
267
- error.possibleSolutions.push('Remove .setCustomId(...) or .setURL(...)');
268
- }
269
- return error;
270
- }
271
- }
@@ -1,23 +0,0 @@
1
- import { CommandParameters } from "./CommandParameters";
2
- import { SelectMenuParameters } from "./SelectMenuParameters";
3
- export declare abstract class Command {
4
- name: string;
5
- categories: string[];
6
- aliases?: string[];
7
- examples?: string[];
8
- permissions?: bigint[];
9
- botPermissions?: string[];
10
- hidden?: boolean;
11
- adminOnly?: boolean;
12
- nsfw?: boolean;
13
- cooldown?: number;
14
- slashCommand?: boolean;
15
- dm: boolean;
16
- args: CommandArgDefinition[];
17
- type: string;
18
- constructor();
19
- abstract execute({ message, interaction, args, client, framework }: CommandParameters): void;
20
- executePrefixCommand({ message, interaction, args, client, framework }: CommandParameters): void;
21
- executeSlashCommand({ message, interaction, args, client, framework }: CommandParameters): void;
22
- selectMenu({ path, interaction, client, framework }: SelectMenuParameters): void;
23
- }
@@ -1,26 +0,0 @@
1
- import { CommandType } from "./CommandType.js";
2
- export class Command {
3
- name = this.constructor.name.toLowerCase();
4
- categories = [];
5
- aliases = [];
6
- examples = [];
7
- permissions = [];
8
- botPermissions = [];
9
- hidden = false;
10
- adminOnly = false;
11
- nsfw = false;
12
- cooldown = 0;
13
- slashCommand = false;
14
- dm = false;
15
- args = [];
16
- type = CommandType.prefix;
17
- constructor() {
18
- }
19
- executePrefixCommand({ message, interaction, args, client, framework }) {
20
- this.execute({ message, interaction, args, client, framework });
21
- }
22
- executeSlashCommand({ message, interaction, args, client, framework }) {
23
- this.execute({ message, interaction, args, client, framework });
24
- }
25
- selectMenu({ path, interaction, client, framework }) { }
26
- }