reciple 5.4.0-pre.2 → 5.4.1-pre.2
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/bin/{bin.js → cjs/bin.js} +0 -0
- package/bin/{index.js → cjs/index.js} +0 -0
- package/bin/cjs/package.json +3 -0
- package/bin/{reciple → cjs/reciple}/classes/CommandCooldownManager.js +0 -0
- package/bin/{reciple → cjs/reciple}/classes/MessageCommandOptionManager.js +0 -0
- package/bin/{reciple → cjs/reciple}/classes/RecipleClient.js +3 -3
- package/bin/{reciple → cjs/reciple}/classes/RecipleConfig.js +0 -0
- package/bin/{reciple → cjs/reciple}/classes/builders/MessageCommandBuilder.js +2 -3
- package/bin/{reciple → cjs/reciple}/classes/builders/MessageCommandOptionBuilder.js +0 -0
- package/bin/{reciple → cjs/reciple}/classes/builders/SlashCommandBuilder.js +15 -16
- package/bin/{reciple → cjs/reciple}/flags.js +0 -0
- package/bin/{reciple → cjs/reciple}/logger.js +0 -0
- package/bin/{reciple → cjs/reciple}/modules.js +2 -2
- package/bin/{reciple → cjs/reciple}/permissions.js +0 -0
- package/bin/{reciple → cjs/reciple}/registerApplicationCommands.js +0 -0
- package/bin/{reciple → cjs/reciple}/types/builders.js +0 -0
- package/bin/{reciple → cjs/reciple}/types/commands.js +0 -0
- package/bin/{reciple → cjs/reciple}/types/paramOptions.js +0 -0
- package/bin/cjs/reciple/util.js +11 -0
- package/bin/{reciple → cjs/reciple}/version.js +0 -0
- package/bin/mjs/bin.js +46 -0
- package/bin/{index.d.ts → mjs/index.js} +0 -0
- package/bin/mjs/package.json +3 -0
- package/bin/mjs/reciple/classes/CommandCooldownManager.js +87 -0
- package/bin/mjs/reciple/classes/MessageCommandOptionManager.js +21 -0
- package/bin/mjs/reciple/classes/RecipleClient.js +363 -0
- package/bin/mjs/reciple/classes/RecipleConfig.js +92 -0
- package/bin/mjs/reciple/classes/builders/MessageCommandBuilder.js +218 -0
- package/bin/mjs/reciple/classes/builders/MessageCommandOptionBuilder.js +67 -0
- package/bin/mjs/reciple/classes/builders/SlashCommandBuilder.js +204 -0
- package/bin/mjs/reciple/flags.js +28 -0
- package/bin/mjs/reciple/logger.js +28 -0
- package/bin/mjs/reciple/modules.js +81 -0
- package/bin/mjs/reciple/permissions.js +23 -0
- package/bin/mjs/reciple/registerApplicationCommands.js +47 -0
- package/bin/mjs/reciple/types/builders.js +8 -0
- package/bin/mjs/reciple/types/commands.js +12 -0
- package/bin/mjs/reciple/types/paramOptions.js +1 -0
- package/bin/mjs/reciple/util.js +7 -0
- package/bin/mjs/reciple/version.js +38 -0
- package/bin/{bin.d.ts → types/bin.d.ts} +0 -0
- package/bin/types/index.d.ts +17 -0
- package/bin/{reciple → types/reciple}/classes/CommandCooldownManager.d.ts +0 -0
- package/bin/{reciple → types/reciple}/classes/MessageCommandOptionManager.d.ts +0 -0
- package/bin/{reciple → types/reciple}/classes/RecipleClient.d.ts +1 -1
- package/bin/{reciple → types/reciple}/classes/RecipleConfig.d.ts +0 -0
- package/bin/{reciple → types/reciple}/classes/builders/MessageCommandBuilder.d.ts +0 -0
- package/bin/{reciple → types/reciple}/classes/builders/MessageCommandOptionBuilder.d.ts +0 -0
- package/bin/{reciple → types/reciple}/classes/builders/SlashCommandBuilder.d.ts +0 -0
- package/bin/{reciple → types/reciple}/flags.d.ts +0 -0
- package/bin/{reciple → types/reciple}/logger.d.ts +0 -0
- package/bin/{reciple → types/reciple}/modules.d.ts +0 -0
- package/bin/{reciple → types/reciple}/permissions.d.ts +0 -0
- package/bin/{reciple → types/reciple}/registerApplicationCommands.d.ts +0 -0
- package/bin/{reciple → types/reciple}/types/builders.d.ts +30 -34
- package/bin/{reciple → types/reciple}/types/commands.d.ts +0 -0
- package/bin/{reciple → types/reciple}/types/paramOptions.d.ts +0 -0
- package/bin/{reciple → types/reciple}/util.d.ts +0 -0
- package/bin/{reciple → types/reciple}/version.d.ts +0 -0
- package/package.json +15 -8
- package/bin/reciple/util.js +0 -11
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { MessageCommandBuilder, validateMessageCommandOptions } from './builders/MessageCommandBuilder';
|
|
2
|
+
import { SlashCommandBuilder } from './builders/SlashCommandBuilder';
|
|
3
|
+
import { CommandBuilderType } from '../types/builders';
|
|
4
|
+
import { registerApplicationCommands } from '../registerApplicationCommands';
|
|
5
|
+
import { CommandHaltReason } from '../types/commands';
|
|
6
|
+
import { botHasExecutePermissions, userHasCommandPermissions } from '../permissions';
|
|
7
|
+
import { CommandCooldownManager } from './CommandCooldownManager';
|
|
8
|
+
import { MessageCommandOptionManager } from './MessageCommandOptionManager';
|
|
9
|
+
import { getCommand } from 'fallout-utility';
|
|
10
|
+
import { RecipleConfig } from './RecipleConfig';
|
|
11
|
+
import { getModules } from '../modules';
|
|
12
|
+
import { createLogger } from '../logger';
|
|
13
|
+
import { version } from '../version';
|
|
14
|
+
import { cwd } from '../flags';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { ChannelType, Client, normalizeArray } from 'discord.js';
|
|
17
|
+
export class RecipleClient extends Client {
|
|
18
|
+
config = RecipleConfig.getDefaultConfig();
|
|
19
|
+
commands = { slashCommands: {}, messageCommands: {} };
|
|
20
|
+
additionalApplicationCommands = [];
|
|
21
|
+
cooldowns = new CommandCooldownManager();
|
|
22
|
+
modules = [];
|
|
23
|
+
logger;
|
|
24
|
+
version = version;
|
|
25
|
+
/**
|
|
26
|
+
* @param options Client options
|
|
27
|
+
*/
|
|
28
|
+
constructor(options) {
|
|
29
|
+
super(options);
|
|
30
|
+
this.logger = createLogger(!!options.config?.fileLogging.stringifyLoggedJSON, options.config?.fileLogging.debugmode);
|
|
31
|
+
if (!options.config)
|
|
32
|
+
throw new Error('Config is not defined.');
|
|
33
|
+
this.config = { ...this.config, ...(options.config ?? {}) };
|
|
34
|
+
if (this.config.fileLogging.enabled)
|
|
35
|
+
this.logger.logFile(path.join(cwd, this.config.fileLogging.logFilePath ?? 'logs/latest.log'), false);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Load modules from modules folder
|
|
39
|
+
* @param folders List of folders that contains the modules you want to load
|
|
40
|
+
*/
|
|
41
|
+
async startModules(...folders) {
|
|
42
|
+
folders = normalizeArray(folders).map(f => path.join(cwd, f));
|
|
43
|
+
for (const folder of folders) {
|
|
44
|
+
if (this.isClientLogsEnabled())
|
|
45
|
+
this.logger.info(`Loading Modules from ${folder}`);
|
|
46
|
+
const modules = await getModules(this, folder).catch(() => null);
|
|
47
|
+
if (!modules) {
|
|
48
|
+
if (this.isClientLogsEnabled())
|
|
49
|
+
this.logger.error(`Failed to load modules from ${folder}`);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
this.modules.push(...modules.modules);
|
|
53
|
+
}
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Execute `onLoad()` from client modules and register application commands if enabled
|
|
58
|
+
*/
|
|
59
|
+
async loadModules() {
|
|
60
|
+
for (const m in this.modules) {
|
|
61
|
+
const module_ = this.modules[m];
|
|
62
|
+
if (typeof module_.script?.onLoad === 'function') {
|
|
63
|
+
await Promise.resolve(module_.script.onLoad(this)).catch(err => {
|
|
64
|
+
if (this.isClientLogsEnabled()) {
|
|
65
|
+
this.logger.error(`Error loading ${module_.info.filename ?? 'unknown module'}:`);
|
|
66
|
+
this.logger.error(err);
|
|
67
|
+
}
|
|
68
|
+
this.modules = this.modules.filter((_r, i) => i.toString() !== m.toString());
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
if (typeof module_.script?.commands !== 'undefined') {
|
|
72
|
+
for (const command of module_.script.commands) {
|
|
73
|
+
this.addCommand(command);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (this.isClientLogsEnabled()) {
|
|
78
|
+
this.logger.info(`${this.modules.length} modules loaded.`);
|
|
79
|
+
this.logger.info(`${Object.keys(this.commands.messageCommands).length} message commands loaded.`);
|
|
80
|
+
this.logger.info(`${Object.keys(this.commands.slashCommands).length} slash commands loaded.`);
|
|
81
|
+
}
|
|
82
|
+
if (this.config.commands.slashCommand.registerCommands) {
|
|
83
|
+
await registerApplicationCommands({
|
|
84
|
+
client: this,
|
|
85
|
+
commands: [...Object.values(this.commands.slashCommands), ...this.additionalApplicationCommands],
|
|
86
|
+
guilds: this.config.commands.slashCommand.guilds
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Add module
|
|
93
|
+
* @param options Module options
|
|
94
|
+
*/
|
|
95
|
+
async addModule(options) {
|
|
96
|
+
const { script } = options;
|
|
97
|
+
const registerCommands = options.registerApplicationCommands;
|
|
98
|
+
const info = options.moduleInfo;
|
|
99
|
+
this.modules.push({
|
|
100
|
+
script,
|
|
101
|
+
info: {
|
|
102
|
+
filename: undefined,
|
|
103
|
+
versions: typeof script.versions == 'string' ? [script.versions] : script.versions,
|
|
104
|
+
path: undefined,
|
|
105
|
+
...info
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
if (typeof script?.onLoad === 'function')
|
|
109
|
+
await Promise.resolve(script.onLoad(this));
|
|
110
|
+
if (this.isClientLogsEnabled())
|
|
111
|
+
this.logger.info(`${this.modules.length} modules loaded.`);
|
|
112
|
+
for (const command of script.commands ?? []) {
|
|
113
|
+
if (!command.name)
|
|
114
|
+
continue;
|
|
115
|
+
this.addCommand(command);
|
|
116
|
+
}
|
|
117
|
+
if (registerCommands)
|
|
118
|
+
await registerApplicationCommands({
|
|
119
|
+
client: this,
|
|
120
|
+
commands: [...Object.values(this.commands.slashCommands), ...this.additionalApplicationCommands],
|
|
121
|
+
guilds: this.config.commands.slashCommand.guilds
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Add slash or message command to client
|
|
126
|
+
* @param command Slash/Message command builder
|
|
127
|
+
*/
|
|
128
|
+
addCommand(command) {
|
|
129
|
+
if (command.type === CommandBuilderType.SlashCommand) {
|
|
130
|
+
this.commands.slashCommands[command.name] = SlashCommandBuilder.resolveSlashCommand(command);
|
|
131
|
+
}
|
|
132
|
+
else if (command.type === CommandBuilderType.MessageCommand) {
|
|
133
|
+
this.commands.messageCommands[command.name] = MessageCommandBuilder.resolveMessageCommand(command);
|
|
134
|
+
}
|
|
135
|
+
else if (this.isClientLogsEnabled()) {
|
|
136
|
+
this.logger.error(`Unknow command "${typeof command ?? 'unknown'}".`);
|
|
137
|
+
}
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Listed to command executions
|
|
142
|
+
*/
|
|
143
|
+
addCommandListeners() {
|
|
144
|
+
if (this.config.commands.messageCommand.enabled)
|
|
145
|
+
this.on('messageCreate', (message) => { this.messageCommandExecute(message); });
|
|
146
|
+
if (this.config.commands.slashCommand.enabled)
|
|
147
|
+
this.on('interactionCreate', (interaction) => { this.slashCommandExecute(interaction); });
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Execute a slash command
|
|
152
|
+
* @param interaction Slash command interaction
|
|
153
|
+
*/
|
|
154
|
+
async slashCommandExecute(interaction) {
|
|
155
|
+
if (!interaction || !interaction.isChatInputCommand() || !this.isReady())
|
|
156
|
+
return;
|
|
157
|
+
const command = this.findCommand(interaction.commandName, CommandBuilderType.SlashCommand);
|
|
158
|
+
if (!command)
|
|
159
|
+
return;
|
|
160
|
+
const executeData = {
|
|
161
|
+
interaction,
|
|
162
|
+
builder: command,
|
|
163
|
+
client: this
|
|
164
|
+
};
|
|
165
|
+
if (userHasCommandPermissions({
|
|
166
|
+
builder: command,
|
|
167
|
+
memberPermissions: interaction.memberPermissions ?? undefined,
|
|
168
|
+
commandPermissions: this.config.commands.slashCommand.permissions
|
|
169
|
+
})) {
|
|
170
|
+
if (!command)
|
|
171
|
+
return;
|
|
172
|
+
if (interaction.guild && !botHasExecutePermissions(interaction.guild, command.requiredBotPermissions)) {
|
|
173
|
+
if (!await this._haltCommand(command, { executeData, reason: CommandHaltReason.MissingBotPermissions })) {
|
|
174
|
+
await interaction.reply(this.getConfigMessage('insufficientBotPerms', 'Insufficient bot permissions.')).catch(er => this._replyError(er));
|
|
175
|
+
}
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const userCooldown = {
|
|
179
|
+
user: interaction.user,
|
|
180
|
+
command: command.name,
|
|
181
|
+
channel: interaction.channel ?? undefined,
|
|
182
|
+
guild: interaction.guild,
|
|
183
|
+
type: CommandBuilderType.SlashCommand
|
|
184
|
+
};
|
|
185
|
+
if (this.config.commands.slashCommand.enableCooldown && command.cooldown && !this.cooldowns.isCooledDown(userCooldown)) {
|
|
186
|
+
this.cooldowns.add({ ...userCooldown, expireTime: Date.now() + command.cooldown });
|
|
187
|
+
}
|
|
188
|
+
else if (this.config.commands.slashCommand.enableCooldown && command.cooldown) {
|
|
189
|
+
if (!await this._haltCommand(command, { executeData, reason: CommandHaltReason.Cooldown, ...this.cooldowns.get(userCooldown) })) {
|
|
190
|
+
await interaction.reply(this.getConfigMessage('cooldown', 'You cannot execute this command right now due to the cooldown.')).catch(er => this._replyError(er));
|
|
191
|
+
}
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
return this._executeCommand(command, executeData);
|
|
195
|
+
}
|
|
196
|
+
else if (!await this._haltCommand(command, { executeData, reason: CommandHaltReason.MissingMemberPermissions })) {
|
|
197
|
+
await interaction.reply(this.getConfigMessage('noPermissions', 'You do not have permission to use this command.')).catch(er => this._replyError(er));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Execute a Message command
|
|
202
|
+
* @param message Message command executor
|
|
203
|
+
* @param prefix Message command prefix
|
|
204
|
+
*/
|
|
205
|
+
async messageCommandExecute(message, prefix) {
|
|
206
|
+
if (!message.content || !this.isReady())
|
|
207
|
+
return;
|
|
208
|
+
const parseCommand = getCommand(message.content, prefix || this.config.commands.messageCommand.prefix || '!', this.config.commands.messageCommand.commandArgumentSeparator || ' ');
|
|
209
|
+
if (!parseCommand || !parseCommand?.command)
|
|
210
|
+
return;
|
|
211
|
+
const command = this.findCommand(parseCommand.command, CommandBuilderType.MessageCommand);
|
|
212
|
+
if (!command)
|
|
213
|
+
return;
|
|
214
|
+
const commandOptions = await validateMessageCommandOptions(command, parseCommand);
|
|
215
|
+
const executeData = {
|
|
216
|
+
message: message,
|
|
217
|
+
options: commandOptions,
|
|
218
|
+
command: parseCommand,
|
|
219
|
+
builder: command,
|
|
220
|
+
client: this
|
|
221
|
+
};
|
|
222
|
+
if (userHasCommandPermissions({
|
|
223
|
+
builder: command,
|
|
224
|
+
memberPermissions: message.member?.permissions,
|
|
225
|
+
commandPermissions: this.config.commands.messageCommand.permissions
|
|
226
|
+
})) {
|
|
227
|
+
if (!command.allowExecuteInDM && message.channel.type === ChannelType.DM || !command.allowExecuteByBots && (message.author.bot || message.author.system))
|
|
228
|
+
return;
|
|
229
|
+
if (command.validateOptions) {
|
|
230
|
+
if (commandOptions.some(o => o.invalid)) {
|
|
231
|
+
if (!await this._haltCommand(command, { executeData, reason: CommandHaltReason.InvalidArguments, invalidArguments: new MessageCommandOptionManager(...executeData.options.filter(o => o.invalid)) })) {
|
|
232
|
+
message.reply(this.getConfigMessage('invalidArguments', 'Invalid argument(s) given.')).catch(er => this._replyError(er));
|
|
233
|
+
}
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (commandOptions.some(o => o.missing)) {
|
|
237
|
+
if (!await this._haltCommand(command, { executeData, reason: CommandHaltReason.MissingArguments, missingArguments: new MessageCommandOptionManager(...executeData.options.filter(o => o.missing)) })) {
|
|
238
|
+
message.reply(this.getConfigMessage('missingArguments', 'Not enough arguments.')).catch(er => this._replyError(er));
|
|
239
|
+
}
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (message.guild && !botHasExecutePermissions(message.guild, command.requiredBotPermissions)) {
|
|
244
|
+
if (!await this._haltCommand(command, { executeData, reason: CommandHaltReason.MissingBotPermissions })) {
|
|
245
|
+
message.reply(this.getConfigMessage('insufficientBotPerms', 'Insufficient bot permissions.')).catch(er => this._replyError(er));
|
|
246
|
+
}
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const userCooldown = {
|
|
250
|
+
user: message.author,
|
|
251
|
+
command: command.name,
|
|
252
|
+
channel: message.channel,
|
|
253
|
+
guild: message.guild,
|
|
254
|
+
type: CommandBuilderType.MessageCommand
|
|
255
|
+
};
|
|
256
|
+
if (this.config.commands.messageCommand.enableCooldown && command.cooldown && !this.cooldowns.isCooledDown(userCooldown)) {
|
|
257
|
+
this.cooldowns.add({ ...userCooldown, expireTime: Date.now() + command.cooldown });
|
|
258
|
+
}
|
|
259
|
+
else if (this.config.commands.messageCommand.enableCooldown && command.cooldown) {
|
|
260
|
+
if (!await this._haltCommand(command, { executeData, reason: CommandHaltReason.Cooldown, ...this.cooldowns.get(userCooldown) })) {
|
|
261
|
+
await message.reply(this.getConfigMessage('cooldown', 'You cannot execute this command right now due to the cooldown.')).catch(er => this._replyError(er));
|
|
262
|
+
}
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
return this._executeCommand(command, executeData);
|
|
266
|
+
}
|
|
267
|
+
else if (!await this._haltCommand(command, { executeData, reason: CommandHaltReason.MissingMemberPermissions })) {
|
|
268
|
+
message.reply(this.getConfigMessage('noPermissions', 'You do not have permission to use this command.')).catch(er => this._replyError(er));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Get a message from config
|
|
273
|
+
* @param messageKey Config messages key
|
|
274
|
+
* @param defaultMessage Default message when the key does not exists
|
|
275
|
+
*/
|
|
276
|
+
getConfigMessage(messageKey, defaultMessage) {
|
|
277
|
+
return this.config.messages[messageKey] ?? defaultMessage ?? messageKey;
|
|
278
|
+
}
|
|
279
|
+
findCommand(command, type) {
|
|
280
|
+
switch (type) {
|
|
281
|
+
case CommandBuilderType.SlashCommand:
|
|
282
|
+
return this.commands.slashCommands[command];
|
|
283
|
+
case CommandBuilderType.MessageCommand:
|
|
284
|
+
return this.commands.messageCommands[command.toLowerCase()]
|
|
285
|
+
?? (this.config.commands.messageCommand.allowCommandAlias
|
|
286
|
+
? Object.values(this.commands.messageCommands).find(c => c.aliases.some(a => a == command?.toLowerCase()))
|
|
287
|
+
: undefined);
|
|
288
|
+
default:
|
|
289
|
+
throw new TypeError('Unknown command type');
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Returns true if client logs is enabled
|
|
294
|
+
*/
|
|
295
|
+
isClientLogsEnabled() {
|
|
296
|
+
return !!this.config.fileLogging.clientLogs;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Emits the "recipleReplyError" event
|
|
300
|
+
* @param error Received Error
|
|
301
|
+
*/
|
|
302
|
+
_replyError(error) {
|
|
303
|
+
this.emit('recipleReplyError', error);
|
|
304
|
+
}
|
|
305
|
+
async _haltCommand(command, haltData) {
|
|
306
|
+
try {
|
|
307
|
+
const haltResolved = (command.halt
|
|
308
|
+
? await (command.type == CommandBuilderType.SlashCommand
|
|
309
|
+
? Promise.resolve(command.halt(haltData))
|
|
310
|
+
: Promise.resolve(command.halt(haltData))).catch(err => { throw err; })
|
|
311
|
+
: false) ?? false;
|
|
312
|
+
this.emit('recipleCommandHalt', haltData);
|
|
313
|
+
return haltResolved;
|
|
314
|
+
}
|
|
315
|
+
catch (err) {
|
|
316
|
+
if (this.isClientLogsEnabled()) {
|
|
317
|
+
this.logger.error(`An error occured executing command halt for "${command.name}"`);
|
|
318
|
+
this.logger.error(err);
|
|
319
|
+
}
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
async _executeCommand(command, executeData) {
|
|
324
|
+
try {
|
|
325
|
+
await Promise.resolve(command.type === CommandBuilderType.SlashCommand
|
|
326
|
+
? command.execute(executeData)
|
|
327
|
+
: command.execute(executeData))
|
|
328
|
+
.then(() => this.emit('recipleCommandExecute', executeData))
|
|
329
|
+
.catch(async (err) => await this._haltCommand(command, { executeData: executeData, reason: CommandHaltReason.Error, error: err })
|
|
330
|
+
? this._commandExecuteError(err, executeData)
|
|
331
|
+
: void 0);
|
|
332
|
+
return executeData;
|
|
333
|
+
}
|
|
334
|
+
catch (err) {
|
|
335
|
+
if (!await this._haltCommand(command, { executeData: executeData, reason: CommandHaltReason.Error, error: err })) {
|
|
336
|
+
this._commandExecuteError(err, executeData);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Error message when a command fails to execute
|
|
342
|
+
* @param err Received error
|
|
343
|
+
* @param command Slash/Message command execute data
|
|
344
|
+
*/
|
|
345
|
+
async _commandExecuteError(err, command) {
|
|
346
|
+
if (this.isClientLogsEnabled()) {
|
|
347
|
+
this.logger.error(`An error occured executing ${command.builder.type == CommandBuilderType.MessageCommand ? 'message' : 'slash'} command "${command.builder.name}"`);
|
|
348
|
+
this.logger.error(err);
|
|
349
|
+
}
|
|
350
|
+
if (!err || !command)
|
|
351
|
+
return;
|
|
352
|
+
if (SlashCommandBuilder.isSlashCommandExecuteData(command)) {
|
|
353
|
+
if (!this.config.commands.slashCommand.replyOnError)
|
|
354
|
+
return;
|
|
355
|
+
await command.interaction.followUp(this.getConfigMessage('error', 'An error occurred.')).catch(er => this._replyError(er));
|
|
356
|
+
}
|
|
357
|
+
else if (MessageCommandBuilder.isMessageCommandExecuteData(command)) {
|
|
358
|
+
if (!this.config.commands.messageCommand.replyOnError)
|
|
359
|
+
return;
|
|
360
|
+
await command.message.reply(this.getConfigMessage('error', 'An error occurred.')).catch(er => this._replyError(er));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { isSupportedVersion, version } from '../version';
|
|
3
|
+
import { input, replaceAll } from 'fallout-utility';
|
|
4
|
+
import { cwd, token as __token } from '../flags';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import yaml from 'yaml';
|
|
7
|
+
/**
|
|
8
|
+
* Create/parse reciple config
|
|
9
|
+
*/
|
|
10
|
+
export class RecipleConfig {
|
|
11
|
+
config = RecipleConfig.getDefaultConfig();
|
|
12
|
+
configPath = path.join(cwd, 'reciple.yml');
|
|
13
|
+
static defaultConfigPath = path.join(__dirname, '../../../resource/reciple.yml');
|
|
14
|
+
/**
|
|
15
|
+
* @param configPath Path to config
|
|
16
|
+
*/
|
|
17
|
+
constructor(configPath) {
|
|
18
|
+
if (!configPath)
|
|
19
|
+
throw new Error('Config path is not defined');
|
|
20
|
+
this.configPath = configPath;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parse the config file
|
|
24
|
+
*/
|
|
25
|
+
parseConfig() {
|
|
26
|
+
if (!existsSync(this.configPath)) {
|
|
27
|
+
const defaultConfigPath = RecipleConfig.defaultConfigPath;
|
|
28
|
+
if (!existsSync(defaultConfigPath))
|
|
29
|
+
throw new Error('Default Config file not found. Please reinstall Reciple.');
|
|
30
|
+
const defaultConfig = replaceAll(readFileSync(defaultConfigPath, 'utf-8'), 'VERSION', version);
|
|
31
|
+
writeFileSync(this.configPath, defaultConfig, 'utf-8');
|
|
32
|
+
if (!existsSync(this.configPath))
|
|
33
|
+
throw new Error('Failed to create config file.');
|
|
34
|
+
this.config = yaml.parse(defaultConfig);
|
|
35
|
+
if (this.config && this.config.token === 'TOKEN') {
|
|
36
|
+
this.config.token = this._askToken() || this.config.token;
|
|
37
|
+
writeFileSync(this.configPath, replaceAll(defaultConfig, ' TOKEN', ` ${this.config.token}`), 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
if (!existsSync(this.configPath))
|
|
42
|
+
throw new Error('Failed to read config file.');
|
|
43
|
+
const config = readFileSync(this.configPath, 'utf-8');
|
|
44
|
+
this.config = yaml.parse(config);
|
|
45
|
+
if (!this._isSupportedConfig())
|
|
46
|
+
throw new Error('Unsupported config version. Your config version: ' + (this.config?.version || 'No version specified.') + ', Reciple version: ' + version);
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Returns the parsed config file
|
|
51
|
+
*/
|
|
52
|
+
getConfig() {
|
|
53
|
+
if (!this.config)
|
|
54
|
+
throw new Error('Config is not parsed.');
|
|
55
|
+
this.config.token = this.parseToken() || 'TOKEN';
|
|
56
|
+
return this.config;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Parse token from config
|
|
60
|
+
* @param askIfNull Ask for token if the token is null/undefined
|
|
61
|
+
*/
|
|
62
|
+
parseToken(askIfNull = true) {
|
|
63
|
+
let token = __token || this.config?.token || null;
|
|
64
|
+
if (!token)
|
|
65
|
+
return token || (askIfNull ? this._askToken() : null);
|
|
66
|
+
const envToken = token.toString().split(':');
|
|
67
|
+
if (envToken.length === 2 && envToken[0].toLocaleLowerCase() === 'env' && envToken[1]) {
|
|
68
|
+
token = process.env[envToken[1]] || null;
|
|
69
|
+
}
|
|
70
|
+
return token || (askIfNull ? this._askToken() : null);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Check if the config version is supported
|
|
74
|
+
*/
|
|
75
|
+
_isSupportedConfig() {
|
|
76
|
+
return isSupportedVersion(this.config?.version || '0.0.0', version);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Ask for a token
|
|
80
|
+
*/
|
|
81
|
+
_askToken() {
|
|
82
|
+
return __token || input({ text: 'Bot Token >>> ', echo: '*', repeatIfEmpty: true, sigint: true }) || null;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get default config
|
|
86
|
+
*/
|
|
87
|
+
static getDefaultConfig() {
|
|
88
|
+
if (!existsSync(this.defaultConfigPath))
|
|
89
|
+
throw new Error("Default config file does not exists.");
|
|
90
|
+
return yaml.parse(readFileSync(this.defaultConfigPath, 'utf-8'));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { CommandBuilderType } from '../../types/builders';
|
|
2
|
+
import { normalizeArray } from 'discord.js';
|
|
3
|
+
import { MessageCommandOptionManager } from '../MessageCommandOptionManager';
|
|
4
|
+
import { MessageCommandOptionBuilder } from './MessageCommandOptionBuilder';
|
|
5
|
+
/**
|
|
6
|
+
* Reciple builder for message command
|
|
7
|
+
*/
|
|
8
|
+
export class MessageCommandBuilder {
|
|
9
|
+
type = CommandBuilderType.MessageCommand;
|
|
10
|
+
name = '';
|
|
11
|
+
description = '';
|
|
12
|
+
cooldown = 0;
|
|
13
|
+
aliases = [];
|
|
14
|
+
validateOptions = false;
|
|
15
|
+
options = [];
|
|
16
|
+
requiredBotPermissions = [];
|
|
17
|
+
requiredMemberPermissions = [];
|
|
18
|
+
allowExecuteInDM = true;
|
|
19
|
+
allowExecuteByBots = false;
|
|
20
|
+
halt;
|
|
21
|
+
execute = () => { };
|
|
22
|
+
constructor(data) {
|
|
23
|
+
if (data?.name !== undefined)
|
|
24
|
+
this.setName(data.name);
|
|
25
|
+
if (data?.description !== undefined)
|
|
26
|
+
this.setDescription(data.description);
|
|
27
|
+
if (data?.cooldown !== undefined)
|
|
28
|
+
this.setCooldown(Number(data?.cooldown));
|
|
29
|
+
if (data?.requiredBotPermissions !== undefined)
|
|
30
|
+
this.setRequiredBotPermissions(data.requiredBotPermissions);
|
|
31
|
+
if (data?.requiredMemberPermissions !== undefined)
|
|
32
|
+
this.setRequiredMemberPermissions(data.requiredMemberPermissions);
|
|
33
|
+
if (data?.halt !== undefined)
|
|
34
|
+
this.setHalt(this.halt);
|
|
35
|
+
if (data?.execute !== undefined)
|
|
36
|
+
this.setExecute(data.execute);
|
|
37
|
+
if (data?.aliases !== undefined)
|
|
38
|
+
this.addAliases(data.aliases);
|
|
39
|
+
if (data?.allowExecuteByBots)
|
|
40
|
+
this.setAllowExecuteByBots(true);
|
|
41
|
+
if (data?.allowExecuteInDM)
|
|
42
|
+
this.setAllowExecuteInDM(true);
|
|
43
|
+
if (data?.validateOptions)
|
|
44
|
+
this.setValidateOptions(true);
|
|
45
|
+
if (data?.options !== undefined)
|
|
46
|
+
this.options = data.options.map(o => o instanceof MessageCommandOptionBuilder ? o : new MessageCommandOptionBuilder(o));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Sets the command name
|
|
50
|
+
* @param name Command name
|
|
51
|
+
*/
|
|
52
|
+
setName(name) {
|
|
53
|
+
if (!name || typeof name !== 'string' || !name.match(/^[\w-]{1,32}$/))
|
|
54
|
+
throw new TypeError('name must be a string and match the regex /^[\\w-]{1,32}$/');
|
|
55
|
+
this.name = name;
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Sets the command description
|
|
60
|
+
* @param description Command description
|
|
61
|
+
*/
|
|
62
|
+
setDescription(description) {
|
|
63
|
+
if (!description || typeof description !== 'string')
|
|
64
|
+
throw new TypeError('description must be a string.');
|
|
65
|
+
this.description = description;
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Add aliases to the command
|
|
70
|
+
* @param aliases Command aliases
|
|
71
|
+
*/
|
|
72
|
+
addAliases(...aliases) {
|
|
73
|
+
aliases = normalizeArray(aliases);
|
|
74
|
+
if (!aliases.length)
|
|
75
|
+
throw new TypeError('Provide atleast one alias');
|
|
76
|
+
if (aliases.some(a => !a || typeof a !== 'string' || a.match(/^\s+$/)))
|
|
77
|
+
throw new TypeError('aliases must be strings and should not contain whitespaces');
|
|
78
|
+
if (this.name && aliases.some(a => a == this.name))
|
|
79
|
+
throw new TypeError('alias cannot have same name to its real command name');
|
|
80
|
+
this.aliases = [...new Set(aliases.map(s => s.toLowerCase()))];
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Set if command can be executed in dms
|
|
85
|
+
* @param allowExecuteInDM `true` if the command can execute in DMs
|
|
86
|
+
*/
|
|
87
|
+
setAllowExecuteInDM(allowExecuteInDM) {
|
|
88
|
+
if (typeof allowExecuteInDM !== 'boolean')
|
|
89
|
+
throw new TypeError('allowExecuteInDM must be a boolean.');
|
|
90
|
+
this.allowExecuteInDM = allowExecuteInDM;
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Allow command to be executed by bots
|
|
95
|
+
* @param allowExecuteByBots `true` if the command can be executed by bots
|
|
96
|
+
*/
|
|
97
|
+
setAllowExecuteByBots(allowExecuteByBots) {
|
|
98
|
+
if (typeof allowExecuteByBots !== 'boolean')
|
|
99
|
+
throw new TypeError('allowExecuteByBots must be a boolean.');
|
|
100
|
+
this.allowExecuteByBots = allowExecuteByBots;
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Add option to the command
|
|
105
|
+
* @param option Message option builder
|
|
106
|
+
*/
|
|
107
|
+
addOption(option) {
|
|
108
|
+
if (!option)
|
|
109
|
+
throw new TypeError('option must be a MessageOption.');
|
|
110
|
+
option = typeof option === 'function' ? option(new MessageCommandOptionBuilder()) : option;
|
|
111
|
+
if (this.options.find(o => o.name === option.name))
|
|
112
|
+
throw new TypeError('option with name "' + option.name + '" already exists.');
|
|
113
|
+
if (this.options.length > 0 && !this.options[this.options.length - 1 < 0 ? 0 : this.options.length - 1].required && option.required)
|
|
114
|
+
throw new TypeError('All required options must be before optional options.');
|
|
115
|
+
this.options = [...this.options, option];
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Validate options before executing
|
|
120
|
+
* @param validateOptions `true` if the command options needs to be validated before executing
|
|
121
|
+
*/
|
|
122
|
+
setValidateOptions(validateOptions) {
|
|
123
|
+
if (typeof validateOptions !== 'boolean')
|
|
124
|
+
throw new TypeError('validateOptions must be a boolean.');
|
|
125
|
+
this.validateOptions = validateOptions;
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
setCooldown(cooldown) {
|
|
129
|
+
this.cooldown = cooldown;
|
|
130
|
+
return this;
|
|
131
|
+
}
|
|
132
|
+
setRequiredBotPermissions(...permissions) {
|
|
133
|
+
this.requiredBotPermissions = normalizeArray(permissions);
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
setRequiredMemberPermissions(...permissions) {
|
|
137
|
+
this.requiredMemberPermissions = normalizeArray(permissions);
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
setHalt(halt) {
|
|
141
|
+
this.halt = halt ?? undefined;
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
setExecute(execute) {
|
|
145
|
+
if (!execute || typeof execute !== 'function')
|
|
146
|
+
throw new TypeError('execute must be a function.');
|
|
147
|
+
this.execute = execute;
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Returns JSON object of this builder
|
|
152
|
+
*/
|
|
153
|
+
toJSON() {
|
|
154
|
+
return {
|
|
155
|
+
type: this.type,
|
|
156
|
+
name: this.name,
|
|
157
|
+
description: this.description,
|
|
158
|
+
aliases: this.aliases,
|
|
159
|
+
cooldown: this.cooldown,
|
|
160
|
+
requiredBotPermissions: this.requiredBotPermissions,
|
|
161
|
+
requiredMemberPermissions: this.requiredMemberPermissions,
|
|
162
|
+
halt: this.halt,
|
|
163
|
+
execute: this.execute,
|
|
164
|
+
allowExecuteByBots: this.allowExecuteByBots,
|
|
165
|
+
allowExecuteInDM: this.allowExecuteInDM,
|
|
166
|
+
validateOptions: this.validateOptions,
|
|
167
|
+
options: this.options.map(o => o.toJSON()),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
static resolveMessageCommand(commandData) {
|
|
171
|
+
return this.isMessageCommandBuilder(commandData) ? commandData : new MessageCommandBuilder(commandData);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Is a message command builder
|
|
175
|
+
*/
|
|
176
|
+
static isMessageCommandBuilder(builder) {
|
|
177
|
+
return builder instanceof MessageCommandBuilder;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Is a message command execute data
|
|
181
|
+
*/
|
|
182
|
+
static isMessageCommandExecuteData(executeData) {
|
|
183
|
+
return executeData.builder !== undefined && this.isMessageCommandBuilder(executeData.builder);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
export async function validateMessageCommandOptions(builder, options) {
|
|
187
|
+
const args = options.args || [];
|
|
188
|
+
const required = builder.options.filter(o => o.required);
|
|
189
|
+
const optional = builder.options.filter(o => !o.required);
|
|
190
|
+
const allOptions = [...required, ...optional];
|
|
191
|
+
const result = [];
|
|
192
|
+
let i = 0;
|
|
193
|
+
for (const option of allOptions) {
|
|
194
|
+
const arg = args[i];
|
|
195
|
+
const value = {
|
|
196
|
+
name: option.name,
|
|
197
|
+
value: arg ?? undefined,
|
|
198
|
+
required: option.required,
|
|
199
|
+
invalid: false,
|
|
200
|
+
missing: false
|
|
201
|
+
};
|
|
202
|
+
if (arg == undefined && option.required) {
|
|
203
|
+
value.missing = true;
|
|
204
|
+
result.push(value);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (arg == undefined && !option.required) {
|
|
208
|
+
result.push(value);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const validate = option.validator ? await Promise.resolve(option.validator(arg)) : true;
|
|
212
|
+
if (!validate)
|
|
213
|
+
value.invalid = true;
|
|
214
|
+
result.push(value);
|
|
215
|
+
i++;
|
|
216
|
+
}
|
|
217
|
+
return new MessageCommandOptionManager(...result);
|
|
218
|
+
}
|