diff --git a/package.json b/package.json index ad00d84..fd6125c 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "@ghaiklor/x509": "^1.0.0", "axios": "^0.19.0", "body-parser": "^1.19.0", - "eris": "^0.10.1", + "eris": "abalabahaha/eris#dev", "eris-pagination": "bsian03/eris-pagination", "express": "^4.17.1", "fs-extra": "^8.1.0", diff --git a/src/Client.ts b/src/Client.ts index 2547f71..8e1b637 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -28,7 +28,9 @@ export default class Client extends Eris.Client { public server: Server; - public updating: Boolean; + public updating: boolean; + + public buildError: boolean constructor() { super(config.token, { getAllUsers: true, restMode: true, defaultImageFormat: 'png' }); @@ -47,6 +49,7 @@ export default class Client extends Eris.Client { displayFilename: true, }); this.updating = false; + this.buildError = false; this.events(); this.loadFunctions(); this.init(); @@ -115,6 +118,8 @@ export default class Client extends Eris.Client { this.signale.complete(`Loaded interval ${interval.split('.')[0]}`); }); this.server = new Server(this, { port: this.config.port }); + + require.cache = Object.create(null); } } diff --git a/src/class/Util.ts b/src/class/Util.ts index 24b0cd1..1f7481f 100644 --- a/src/class/Util.ts +++ b/src/class/Util.ts @@ -2,7 +2,7 @@ import { promisify } from 'util'; import childProcess from 'child_process'; import nodemailer from 'nodemailer'; -import { Message, PrivateChannel, Member, User } from 'eris'; +import { Message, PrivateChannel, GroupChannel, Member, User } from 'eris'; import uuid from 'uuid/v4'; import moment from 'moment'; import fs from 'fs'; @@ -38,50 +38,29 @@ export default class Util { /** * Resolves a command - * @param command Parent command label - * @param args Use to resolve subcommands + * @param query Command input * @param message Only used to check for errors */ - public resolveCommand(command: string, args?: string[], message?: Message): Promise<{cmd: Command, args: string[] }> { + public resolveCommand(query: string | string[], message?: Message): Promise<{cmd: Command, args: string[] }> { try { let resolvedCommand: Command; + if (typeof query === 'string') query = query.split(' '); + const commands = this.client.commands.toArray(); + resolvedCommand = commands.find((c) => c.name === query[0].toLowerCase() || c.aliases.includes(query[0].toLowerCase())); - if (this.client.commands.has(command)) resolvedCommand = this.client.commands.get(command); - else { - for (const cmd of this.client.commands.toArray()) { - if (cmd.aliases.includes(command)) { resolvedCommand = cmd; break; } - } + if (!resolvedCommand) return Promise.resolve(null); + query.shift(); + while (resolvedCommand.subcommands.size && query.length) { + const subCommands = resolvedCommand.subcommands.toArray(); + const found = subCommands.find((c) => c.name === query[0].toLowerCase() || c.aliases.includes(query[0].toLowerCase())); + if (!found) break; + resolvedCommand = found; + query.shift(); } - if (!resolvedCommand) return Promise.resolve({ cmd: null, args }); - - let parentLabel = ''; - let hasSubCommands = true; - while (hasSubCommands) { - if (!resolvedCommand.subcommands.size) { - hasSubCommands = false; break; - } else if (!args[0]) { - hasSubCommands = false; break; - } else if (resolvedCommand.subcommands.has(args[0])) { - parentLabel += `${resolvedCommand.name} `; - resolvedCommand = resolvedCommand.subcommands.get(args[0]); args.shift(); - } else { - const subcommandArray = resolvedCommand.subcommands.toArray(); - for (const subCmd of subcommandArray) { - if (subCmd.aliases.includes(args[0])) { - parentLabel += `${resolvedCommand.name} `; resolvedCommand = subCmd; args.shift(); break; - } - if (subcommandArray.findIndex((v) => v === subCmd) === subcommandArray.length - 1) { - hasSubCommands = false; break; - } - } - } - } - const finalCommand = resolvedCommand; - finalCommand.parentName = parentLabel; - - return Promise.resolve({ cmd: finalCommand, args }); + return Promise.resolve({ cmd: resolvedCommand, args: query }); } catch (error) { - this.handleError(error, message); + if (message) this.handleError(error, message); + else this.handleError(error); return Promise.reject(error); } } @@ -99,7 +78,7 @@ export default class Util { embed.addField('User', `${message.author.mention} (\`${message.author.id}\`)`, true); embed.addField('Channel', message.channel.mention, true); let guild: string; - if (message.channel instanceof PrivateChannel) guild = '@me'; + if (message.channel instanceof PrivateChannel || message.channel instanceof GroupChannel) guild = '@me'; else guild = message.channel.guild.id; embed.addField('Message link', `[Click here](https://discordapp.com/channels/${guild}/${message.channel.id}/${message.id})`, true); embed.setTimestamp(new Date(message.timestamp)); @@ -107,9 +86,7 @@ export default class Util { } await this.client.createMessage('595788220764127272', info); const msg = message.content.slice(this.client.config.prefix.length).trim().split(/ +/g); - const label = msg[0]; - const args = msg.slice(1); - if (command) this.resolveCommand(label, args).then((c) => { c.cmd.enabled = false; }); + if (command) this.resolveCommand(msg).then((c) => { c.cmd.enabled = false; }); if (message) message.channel.createMessage(`***${this.client.stores.emojis.error} An unexpected error has occured - please contact a member of the Engineering Team.${command ? ' This command has been disabled.' : ''}***`); } catch (err) { this.client.signale.error(err); diff --git a/src/commands/createaccount.ts b/src/commands/createaccount.ts index 69ae920..4546c61 100644 --- a/src/commands/createaccount.ts +++ b/src/commands/createaccount.ts @@ -1,4 +1,4 @@ -import { Message, PrivateChannel } from 'eris'; +import { Message, PrivateChannel, GroupChannel } from 'eris'; import uuid from 'uuid/v4'; import { Client } from '..'; import { Command, RichEmbed } from '../class'; @@ -22,7 +22,7 @@ export default class CreateAccount extends Command { public async run(message: Message, args: string[]) { try { - if (message.channel instanceof PrivateChannel) return message; // Stop TS being gay + if (message.channel instanceof PrivateChannel || message.channel instanceof GroupChannel) return message; // Stop TS being gay if (!args[2]) return this.client.commands.get('help').run(message, [this.name]); if (!message.channel.guild.members.has(args[0])) return message.channel.createMessage(`${this.client.stores.emojis.error} ***User not found***`); if (message.channel.guild.members.get(args[0]).bot) return message.channel.createMessage(`${this.client.stores.emojis.error} ***I cannot create accounts for bots***`); diff --git a/src/commands/deleteaccount.ts b/src/commands/deleteaccount.ts index 7254235..e620d3e 100644 --- a/src/commands/deleteaccount.ts +++ b/src/commands/deleteaccount.ts @@ -1,4 +1,3 @@ -/* eslint-disable consistent-return */ import { Message, PrivateChannel } from 'eris'; import uuid from 'uuid/v4'; import { Command } from '../class'; @@ -57,7 +56,7 @@ export default class DeleteAccount extends Command { `, }); - deleting.edit(`${this.client.stores.emojis.success} ***Account ${username} has been deleted by Engineer ${message.author.username}#${message.author.discriminator}***`); + return deleting.edit(`${this.client.stores.emojis.success} ***Account ${username} has been deleted by Engineer ${message.author.username}#${message.author.discriminator}***`); } catch (error) { return this.client.util.handleError(error, message, this); } diff --git a/src/commands/help.ts b/src/commands/help.ts index d597f3f..9db8143 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -44,7 +44,7 @@ export default class Help extends Command { if (cmdPages.length === 1) return message.channel.createMessage({ embed: cmdPages[0] }); return createPaginationEmbed(message, this.client, cmdPages); } - const { cmd } = await this.client.util.resolveCommand(args[0], args.slice(1), message); + const { cmd } = await this.client.util.resolveCommand(args, message); if (!cmd) return message.channel.createMessage(`${this.client.stores.emojis.error} **Command not found!**`); const perms: string[] = []; let allowedRoles = cmd.permissions && cmd.permissions.roles && cmd.permissions.roles.map((r) => `<@&${r}>`).join(', '); diff --git a/src/commands/index.ts b/src/commands/index.ts index ee72450..7312475 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -1,23 +1,24 @@ -export { default as Announce } from './announce'; -export { default as Bearer } from './bearer'; -export { default as CreateAccount } from './createaccount'; -export { default as CWG } from './cwg'; -export { default as DeleteAccount } from './deleteaccount'; -export { default as Disk } from './disk'; -export { default as Eval } from './eval'; -export { default as Exec } from './exec'; -export { default as Help } from './help'; -export { default as Lock } from './lock'; -export { default as Modlogs } from './modlogs'; -export { default as Notify } from './notify'; -export { default as Parse } from './parse'; -export { default as Parseall } from './parseall'; -export { default as Ping } from './ping'; -export { default as Pull } from './pull'; -export { default as Restart } from './restart'; -export { default as SecureSign } from './securesign'; -export { default as Sysinfo } from './sysinfo'; -export { default as Unban } from './unban'; -export { default as Unlock } from './unlock'; -export { default as Warn } from './warn'; -export { default as Whois } from './whois'; +export { default as announce } from './announce'; +export { default as bearer } from './bearer'; +export { default as createAccount } from './createaccount'; +export { default as cwg } from './cwg'; +export { default as deleteaccount } from './deleteaccount'; +export { default as disk } from './disk'; +export { default as eval } from './eval'; +export { default as exec } from './exec'; +export { default as help } from './help'; +export { default as load } from './load'; +export { default as lock } from './lock'; +export { default as modlogs } from './modlogs'; +export { default as notify } from './notify'; +export { default as parse } from './parse'; +export { default as parseall } from './parseall'; +export { default as ping } from './ping'; +export { default as pull } from './pull'; +export { default as restart } from './restart'; +export { default as securesign } from './securesign'; +export { default as sysinfo } from './sysinfo'; +export { default as unban } from './unban'; +export { default as unlock } from './unlock'; +export { default as warn } from './warn'; +export { default as whois } from './whois'; diff --git a/src/commands/load.ts b/src/commands/load.ts new file mode 100644 index 0000000..b639c3b --- /dev/null +++ b/src/commands/load.ts @@ -0,0 +1,45 @@ +import { Message } from 'eris'; +import { Client } from '..'; +import { Command } from '../class'; + +export default class Load extends Command { + constructor(client: Client) { + super(client); + this.name = 'load'; + this.description = '(Re)loads command, config or util'; + this.aliases = ['reload']; + this.permissions = { users: ['253600545972027394', '278620217221971968'] }; + this.enabled = false; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + const allowed = ['config', 'util', 'command', 'function']; + const type = args[0].toLowerCase(); + if (!allowed.includes(type)) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Invalid type to (re)load***`); + + const corepath = '/var/CloudServices/dist'; + if (type === 'config') this.client.config = require(`${corepath}/config.json`); + else if (type === 'util') { + const Util = require(`${corepath}/class/Util`); + this.client.util = new Util(this.client); + } else { + try { + const cmdIndex = require('../commands'); + let Cmd = cmdIndex[args[1]]; + if (!Cmd) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Could not find file***`); + Cmd = require(`${corepath}/commands/${args[1]}`).default; + this.client.commands.remove(args[1]); + this.client.loadCommand(Cmd); + } catch (error) { + if (error.message.includes('Cannot find module')) return message.channel.createMessage(`${this.client.stores.emojis} ***Cannot find file***`); + throw error; + } + } + return message.channel.createMessage(`${this.client.stores.emojis.success} Reloaded ${type}`); + } catch (error) { + return this.client.util.handleError(error, message, this); + } + } +} diff --git a/src/commands/pull.ts b/src/commands/pull.ts index 4ad34da..6a861ca 100644 --- a/src/commands/pull.ts +++ b/src/commands/pull.ts @@ -14,6 +14,7 @@ export default class Pull extends Command { public async run(message: Message) { try { + if (this.client.updating) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Update in progress***`); this.client.updating = true; const updateMessage = await message.channel.createMessage(`${this.client.stores.emojis.loading} ***Fetching latest commit...***\n\`\`\`sh\ngit pull\n\`\`\``); let pull: string; @@ -74,12 +75,14 @@ export default class Pull extends Command { } catch (error) { const updatedMessage = updatedPackages.content.replace(`${this.client.stores.emojis.loading} ***Rebuilding files...***`, `${this.client.stores.emojis.error} ***Failed to rebuild files***`) .replace(/```$/, `${error.message}\n\`\`\``); + this.client.buildError = true; this.client.updating = false; return updateMessage.edit(updatedMessage); } const finalMessage = updatedPackages.content.replace(`${this.client.stores.emojis.loading} ***Rebuilding files...***`, `${this.client.stores.emojis.success} ***Files rebuilt***`) .replace(/```$/, `${build}\n\`\`\``); this.client.updating = false; + this.client.buildError = false; return updateMessage.edit(finalMessage); } catch (error) { this.client.updating = false; diff --git a/src/commands/restart.ts b/src/commands/restart.ts index 0f80600..80e3a33 100644 --- a/src/commands/restart.ts +++ b/src/commands/restart.ts @@ -14,6 +14,7 @@ export default class Restart extends Command { public async run(message: Message, args: string[]) { try { if (this.client.updating && args[0] !== '-f') return message.channel.createMessage(`${this.client.stores.emojis.error} ***Update in progress***`); + if (this.client.buildError) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Build error, resolve before restarting***`); await message.channel.createMessage(`${this.client.stores.emojis.loading} ***Restarting...***`); return process.exit(1); } catch (error) { diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index 69dda08..b9048cb 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -14,10 +14,8 @@ export default class { if (message.author.bot) return; if (message.content.indexOf(this.client.config.prefix) !== 0) return; const noPrefix: string[] = message.content.slice(this.client.config.prefix.length).trim().split(/ +/g); - const command: string = noPrefix[0].toLowerCase(); - const args: string[] = noPrefix.slice(1); - const resolved = await this.client.util.resolveCommand(command, args, message); - if (!resolved.cmd) return; + const resolved = await this.client.util.resolveCommand(noPrefix, message); + if (!resolved) return; if (resolved.cmd.guildOnly && !(message.channel instanceof TextChannel)) return; let hasUserPerms: boolean; if (resolved.cmd.permissions.users) {