grant/revoke basework, component command, guild wrapper rework (broke settings)

This commit is contained in:
nolan 2020-05-06 19:26:16 -04:00
parent b9cfd331c5
commit a630bf37ed
29 changed files with 639 additions and 191 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ node_modules
yarn-error.log yarn-error.log
.eslintrc.json .eslintrc.json
logs logs
permissionExample.json

View File

@ -25,11 +25,11 @@ class LocaleLoader {
const _directory = path.join(process.cwd(), 'language/languages'); const _directory = path.join(process.cwd(), 'language/languages');
const directories = fs.readdirSync(_directory); //locale directories, ex. en_us, fi const directories = fs.readdirSync(_directory); //locale directories, ex. en_us, fi
for (const directory of directories) await this.loadLanguage(directory); for (const directory of directories) this.loadLanguage(directory);
} }
async loadLanguage(language) { loadLanguage(language) {
this.client.logger.info(`Loading locale ${chalk.bold(language)}`); this.client.logger.info(`Loading locale ${chalk.bold(language)}`);
const directory = path.join(process.cwd(), `language/languages/${language}`); const directory = path.join(process.cwd(), `language/languages/${language}`);
@ -76,7 +76,7 @@ class LocaleLoader {
} }
for(const [ key, value ] of Object.entries(parsed)) { for(const [ key, value ] of Object.entries(parsed)) {
parsed[key] = value.replace(/^(\r)|(\r){1,}$/g, '') parsed[key] = value.replace(/^(\r)|(\r){1,}$/g, '');
} }
return parsed; return parsed;

View File

@ -0,0 +1,20 @@
//Grant Command
[C_GRANT_DESCRIPTION]
Grant roles or users permissions for commands or modules.
[C_GRANT_RESOLVEERROR]
Unable to find any roles or members, view `{prefix}cmd grant` for more help.
[C_GRANT_MISSINGPERMPARAM]
You must provide permissions to grant, view `{prefix}cmd grant` for more help.
//Revoke Command
[C_REVOKE_DESCRIPTION]
Revoke permissions granted to roles or users.
//Permissions Command
[C_PERMISSIONS_DESCRIPTION]
View permissions granted to roles or users.

View File

@ -0,0 +1,5 @@
[C_EVALUATE_DESCRIPTION]
Evaluates javascript code.
[C_RELOAD_DESCRIPTION]
Reloads components and language files.

View File

@ -1,3 +0,0 @@
[C_HELP_404]
**ERROR:**
`{component}` was not found!

View File

@ -1,4 +1,9 @@
[C_HELP_USAGE] //Help Command
[C_HELP_DESCRIPTION]
Shows helpful information for commands, settings, and moderation.
[C_HELP]
**__HELP MENU__** **__HELP MENU__**
For a list of all available commands, use `{prefix}commands [ group ]`. For a list of all available commands, use `{prefix}commands [ group ]`.
@ -12,6 +17,8 @@ The bot splits arguments by space unless specified otherwise. To pass an argumen
** Documentation notation** ** Documentation notation**
**Optional** arguments are denoted by being encapsulated in brackets `[ ]` - means that the command will run either with default values or show usage prompt. **Optional** arguments are denoted by being encapsulated in brackets `[ ]` - means that the command will run either with default values or show usage prompt.
**Required** arguments are denoted by less and greater than `< >` - means that the command will not run and return an error. **Required** arguments are denoted by less and greater than `< >` - means that the command will not run and return an error.
**Infinite** arguments (ones you can list several) are denoted by `..` after the argument. Ex `< argument.. >` - means you can pass more than one argument.
**Alternatives** are denoted by being separated by a `|`.
** Moderation** ** Moderation**
For help with moderation, see `{prefix}help modhelp`. For help with moderation, see `{prefix}help modhelp`.
@ -35,4 +42,23 @@ __**{component} HELP**__
[C_HELP_HELP] [C_HELP_HELP]
Shows information about the bot and component usage. Shows information about the bot and component usage.
To show help for commands `{prefix}help [ command ]` To show help for commands `{prefix}help [command]`
//Commands Command
[C_COMMANDS_DESCRIPTION]
Displays all commands and help.
[C_COMMANDS_TITLE]
Available commands
[C_COMMANDS_TEMPLATE]
Displaying commands for module `{mod}`.
{text}
[C_COMMANDS]
__Commands that are struck through are disabled.__
[C_COMMANDS_FOOTER]
To search by specific module, use {prefix}commands [ module ]

View File

@ -3,8 +3,14 @@
[C_PING_RESPONSE] [C_PING_RESPONSE]
Pong! Pong!
[C_PING_DESCRIPTION]
Shows the millisecond delay between the bot and the discord server.
//Settings Command //Settings Command
[C_SETTINGS_DESCRIPTION]
Configure your guild and user settings.
[C_SETTINGS_ADMINISTRATORERROR] [C_SETTINGS_ADMINISTRATORERROR]
You must have the `ADMINISTRATOR` permission to reset the {type} settings. You must have the `ADMINISTRATOR` permission to reset the {type} settings.
@ -43,6 +49,9 @@ That setting does not exist!
//User Command //User Command
[C_USER_DESCRIPTION]
Search for users or view user information.
[C_USER] [C_USER]
**Nickname:** {nickname} **Nickname:** {nickname}
**User:** <@{id}> **User:** <@{id}>

View File

@ -11,5 +11,11 @@ switch({component}) {
break; break;
} }
[EXAMPLES] [GENERAL_EXAMPLES]
Example usage Example Usage
[GENERAL_ALIASES]
Aliases
[GENERAL_ARGUMENTS]
Arguments

View File

@ -15,3 +15,5 @@ Information
[M_MUSIC_NAME] [M_MUSIC_NAME]
Music Music
[

View File

@ -19,7 +19,7 @@ class DiscordWebhook extends Transport {
log(info, callback) { log(info, callback) {
setImmediate(() => { setImmediate(() => {
this.emit('logged', info); this.emit('logged', info);
}); });
const message = info.message.replace(regex, '') const message = info.message.replace(regex, '')

View File

@ -10,7 +10,7 @@ const Logger = require('./Logger.js');
const TransactionHandler = require('./TransactionHandler.js'); const TransactionHandler = require('./TransactionHandler.js');
const LocaleLoader = require('../../language/LocaleLoader.js'); const LocaleLoader = require('../../language/LocaleLoader.js');
const { Guild, User, Message } = require('../../structure/extensions/'); //eslint-disable-line const { Guild, GuildMember, User, Message } = require('../../structure/extensions/'); //eslint-disable-line no-unused-vars
const { Command, Observer, Inhibitor, Setting } = require('../../structure/interfaces/'); const { Command, Observer, Inhibitor, Setting } = require('../../structure/interfaces/');
class DiscordClient extends Client { class DiscordClient extends Client {
@ -47,7 +47,7 @@ class DiscordClient extends Client {
await super.login(this._options.bot.token); await super.login(this._options.bot.token);
await this.localeLoader.loadLanguages(); this.localeLoader.loadLanguages();
await this.registry.loadComponents('components/inhibitors/', Inhibitor); await this.registry.loadComponents('components/inhibitors/', Inhibitor);
await this.registry.loadComponents('components/commands/', Command); await this.registry.loadComponents('components/commands/', Command);
@ -69,7 +69,7 @@ class DiscordClient extends Client {
def = { def = {
...def, ...def,
...setting.default ...setting.default
} };
} }
} }
this._defaultConfig = def; this._defaultConfig = def;

View File

@ -24,12 +24,14 @@ class Registry {
const func = require(path); const func = require(path);
if(typeof func !== 'function') { if(typeof func !== 'function') {
this.client.logger.warn("Attempted to index an invalid function as a component."); this.client.logger.warn("Attempted to index an invalid function as a component.");
delete require.cache[path];
continue; continue;
} }
const component = new func(this.client); //Instantiates the component class. const component = new func(this.client); //Instantiates the component class.
if(classToHandle && !(component instanceof classToHandle)) { if(classToHandle && !(component instanceof classToHandle)) {
this.client.logger.warn("Attempted to load an invalid class."); this.client.logger.warn("Attempted to load an invalid class.");
delete require.cache[path];
continue; continue;
} }
@ -44,6 +46,7 @@ class Registry {
async loadComponent(component, directory) { async loadComponent(component, directory) {
if(!(component instanceof Component)) { if(!(component instanceof Component)) {
delete require.cache[directory];
this.client.logger.warn("Attempted to load an invalid component."); this.client.logger.warn("Attempted to load an invalid component.");
return null; return null;
} }
@ -69,7 +72,10 @@ class Registry {
} }
async unloadComponent(component) { async unloadComponent(component) {
this.components.delete(component.id); if(component.module) {
component.module.components.delete(component.resolveable);
}
this.components.delete(component.resolveable);
} }
} }

View File

@ -161,13 +161,13 @@ class Resolver {
if(/<@!?([0-9]{17,21})>/.test(resolveable)) { if(/<@!?([0-9]{17,21})>/.test(resolveable)) {
let id = resolveable.match(/<@!?([0-9]{17,21})>/)[1]; let id = resolveable.match(/<@!?([0-9]{17,21})>/)[1];
let member = await members.fetch(id).catch(err => { if(err.code === 10007) return false; else { console.warn(err); return false; } }); let member = await members.fetch(id).catch(err => { if(err.code === 10007) return false; else { this.client.logger.warn(err); return false; } });
if(member) resolved.push(member); if(member) resolved.push(member);
} else if(/(id\:)?([0-9]{17,21})/.test(resolveable)) { } else if(/(id\:)?([0-9]{17,21})/.test(resolveable)) {
let id = resolveable.match(/(id\:)?([0-9]{17,21})/)[2]; let id = resolveable.match(/(id\:)?([0-9]{17,21})/)[2];
let member = await members.fetch(id).catch(err => { if(err.code === 10007) return false; else { console.warn(err); return false; } }); let member = await members.fetch(id).catch(err => { if(err.code === 10007) return false; else { this.client.logger.warn(err); return false; } });
if(member) resolved.push(member); if(member) resolved.push(member);
} else if(/^\@?([\S\s]{1,32})\#([0-9]{4})/.test(resolveable)) { } else if(/^\@?([\S\s]{1,32})\#([0-9]{4})/.test(resolveable)) {
@ -175,21 +175,21 @@ class Resolver {
let m = resolveable.match(/^\@?([\S\s]{1,32})\#([0-9]{4})/); let m = resolveable.match(/^\@?([\S\s]{1,32})\#([0-9]{4})/);
let username = m[1].toLowerCase(); let username = m[1].toLowerCase();
let discrim = m[2].toLowerCase(); let discrim = m[2].toLowerCase();
let [ member ] = members.cache.filter(m => { let member = members.cache.filter(m => {
return m.user.username.toLowerCase() === username && m.user.discriminator === discrim; return m.user.username.toLowerCase() === username && m.user.discriminator === discrim;
}).first(1); }).first();
if(member) resolved.push(member); if(member) resolved.push(member);
} else if(/^\@?([\S\s]{1,32})/.test(resolveable) && guild && !strict) { } else if(/^\@?([\S\s]{1,32})/.test(resolveable) && guild && !strict) {
let nickname = resolveable.match(/^\@?([\S\s]{1,32})/)[0].toLowerCase(); let nickname = resolveable.match(/^\@?([\S\s]{1,32})/)[0].toLowerCase();
let [ member ] = members.cache.filter((m) => { let member = members.cache.filter((m) => {
return (m && m.user) && return (m && m.user) &&
((!m.nickname ? false : m.nickname.toLowerCase() == nickname ) || ((!m.nickname ? false : m.nickname.toLowerCase() == nickname ) ||
(!m.nickname ? false : m.nickname.toLowerCase().includes(nickname)) || (!m.nickname ? false : m.nickname.toLowerCase().includes(nickname)) ||
m.user.username.toLowerCase().includes(nickname) || m.user.username.toLowerCase().includes(nickname) ||
m.user.username.toLowerCase() == nickname); m.user.username.toLowerCase() == nickname);
}).first(1); }).first();
if(member) resolved.push(member); if(member) resolved.push(member);
} }
@ -200,6 +200,13 @@ class Resolver {
} }
async resolveMember(resolveable, guild, strict) {
let result = await this.resolveMembers([ resolveable ], guild, strict);
return result;
}
/** /**
* Resolve multiple channels * Resolve multiple channels
* *
@ -227,12 +234,12 @@ class Resolver {
let name = /^\#?([a-z0-9\-\_0]*)/i; let name = /^\#?([a-z0-9\-\_0]*)/i;
let id = /^\<\#([0-9]*)\>/i; let id = /^\<\#([0-9]*)\>/i;
if(name.test(resolveable)) { if (name.test(resolveable)) {
let match = resolveable.match(name); let match = resolveable.match(name);
let ch = match[1].toLowerCase(); let ch = match[1].toLowerCase();
let channel = channels.cache.filter(c => { let [ channel ] = channels.cache.filter(c => {
if(!strict) return c.name.toLowerCase().includes(ch); if(!strict) return c.name.toLowerCase().includes(ch);
return c.name.toLowerCase() === ch; return c.name.toLowerCase() === ch;
}).first(1); }).first(1);
@ -287,7 +294,7 @@ class Resolver {
} else { } else {
let role = roles.cache.filter(r => { let [ role ] = roles.cache.filter(r => {
if(!strict) return r.name.toLowerCase().includes(resolveable.toLowerCase()); if(!strict) return r.name.toLowerCase().includes(resolveable.toLowerCase());
return r.name.toLowerCase() === resolveable.toLowerCase(); return r.name.toLowerCase() === resolveable.toLowerCase();
}).first(1); }).first(1);

View File

@ -0,0 +1,101 @@
const { Command, Argument } = require('../../../../interfaces/');
class GrantCommand extends Command {
constructor(client) {
super(client, {
name: 'grant',
module: 'administrator',
usage: "<role|user> <permissions..>",
examples: [
"\"Server Moderators\" module:moderation",
"@nolan#2887 command:kick"
],
memberPermissions: ['ADMINISTRATOR'],
showUsage: true,
guildOnly: true,
arguments: [
new Argument(client, {
name: 'channel',
aliases: [
'channels'
],
type: 'CHANNEL',
types: ['FLAG', 'VERBAL'],
infinite: true
})
]
});
}
async execute(message, { params, args }) {
const _permissions = await message.guild.permissions_();
const [ parse, ...perms ] = params;
const resolveable = await this._parseResolveable(message, parse);
if(perms.length === 0) {
await message.respond(message.format('C_GRANT_MISSINGPERMPARAM'), { emoji: 'failure' });
return undefined;
}
const permissions = this.client.registry.components.filter(c=>
c.type === 'command'
|| c.type === 'module'
);
let parsed = [];
let failed = [];
for(const perm of perms) {
const search = permissions.filter(filterInexact(perm)).first();
if(!search) failed.push(perm);
if(search.type === 'module') {
for(const component of search.components.values()) {
if(component.type === 'command') parsed.push(component.resolveable);
//add check for grantable
}
} else {
//add check for grantable
parsed.push(search.resolveable);
}
}
let data = {};
let existing = _permissions[resolveable.id];
if(existing) {
for(let perm of parsed) {
if(existing.includes(perm)) failed.push(perm);
else existing.push(perm);
}
} else {
existing = parsed;
}
data = existing;
}
async _parseResolveable(message, resolveable) {
let parsed = await this.client.resolver.resolveRoles(resolveable, message.guild);
if(!parsed) {
parsed = await this.client.resolver.resolveMembers(resolveable, message.guild);
if(!parsed) {
await message.respond(message.format('C_GRANT_RESOLVEERROR'), { emoji: 'failure' });
return null;
}
}
return parsed[0];
}
}
module.exports = GrantCommand;
const filterInexact = (search) => {
return comp => comp.id.toLowerCase().includes(search) ||
comp.resolveable.toLowerCase().includes(search) ||
(comp.aliases && (comp.aliases.some(ali => `${comp.type}:${ali}`.toLowerCase().includes(search)) ||
comp.aliases.some(ali => ali.toLowerCase().includes(search))));
};

View File

@ -0,0 +1,37 @@
const { Command, Argument } = require('../../../../interfaces/');
class RevokeCommand extends Command {
constructor(client) {
super(client, {
name: 'revoke',
module: 'administrator',
usage: "<role|user> <permissions..>",
examples: [
"\"Server Moderators\" module:moderation",
"@nolan#2887 command:kick"
],
memberPermissions: ['ADMINISTRATOR'],
showUsage: true,
guildOnly: true,
arguments: [
new Argument(client, {
name: 'channel',
type: 'CHANNEL',
types: ['FLAG', 'VERBAL'],
})
]
});
}
async execute(message, { params, args }) {
}
}
module.exports = RevokeCommand;

View File

@ -0,0 +1,158 @@
const { Command } = require('../../../../interfaces/');
const path = require('path');
const fs = require('fs');
class ComponentCommand extends Command {
constructor(client) {
super(client, {
name: 'component',
module: 'developer',
restricted: true,
aliases: [
'c',
'comp'
],
usage: '[component..]',
arguments: [
// new Argument(client, {
// name: 'reload',
// type: 'STRING',
// types: ['FLAG'],
// aliases: [ 'r' ],
// description: "Reloads the language library",
// default: 'all'
// })
],
});
this.client = client;
}
async execute(message, { params }) {
//<load|unload|reload|disable|enable> <component>
const method = params.shift().toLowerCase();
let response;
if (method === 'reload')
response = this._handleReload(params);
else if (method === 'enable')
response = this._handleDisableEnable(params.shift().toLowerCase(), true);
else if (method === 'disable')
response = this._handleDisableEnable(params.shift().toLowerCase(), false);
else if (method === 'load')
response = this._handleLoadUnload(params.shift().toLowerCase(), true);
else if (method === 'unload')
response = this._handleLoadUnload(params.shift().toLowerCase(), false);
else return await message.respond('Invalid method. Can only be `reload`, `enable`, `disable`, `load`, `unload`', 'failure');
return await message.respond(response.msg, { emoji: response.error ? 'failure' : 'success' });
}
_handleReload(params) {
const name = params.length ? params.shift().toLowerCase() : 'all'; //ex. language
const value = params.length ? params.shift().toLowerCase() : 'all'; //ex. en_us --> combined: -component reload language en_us
if (name === 'language' || name === 'lang' || name === 'l') {
if (value === 'all') {
this.client.localeLoader.loadLanguages();
return { msg: 'Reloaded all languages' };
} else {
try {
this.client.localeLoader.loadLanguage(value);
return { msg: `Reloaded locale \`${value}\`` };
} catch (err) {
return { error: true, msg: err.message };
}
}
} else {
if (name === 'all') {
const errors = [];
const components = this.client.registry.components;
for (let component of components.values()) {
const result = component.reload();
if (result.error) errors.push(`Component ${component.id} errored while reloading with code \`${result.code}\``);
}
if (errors.length) return { error: true, msg: `The following errors occurred during reload:\n${errors.join('\n')}` };
return { msg: `Successfully reloaded all components` };
} else {
const component = this.client.registry.components.get(name);
if (!component) return { error: true, msg: `Component ${name} doesn't exist.` };
const result = component.reload();
if (result.error) return { error: true, msg: `Component ${name} errored while reloading with code \`${result.code}\`` };
else return { msg: `Successfully reloaded ${name}` };
}
}
}
_handleDisableEnable(name, enable) {
const component = this.client.registry.components.get(name);
let result;
if (!component) return { error: true, msg: `Component ${name} doesn't exist.` };
if (enable) result = component.enable();
else result = component.disable();
if (result.error) return { error: true, msg: `Cannot ${enable ? 'enable' : 'disable'} ${name} due to ${result.code}` };
else return { msg: `Successfully ${enable ? 'enabled' : 'disabled'} component ${name}` };
}
_handleLoadUnload(name, load) {
let result;
if (load) {
const directory = path.join(process.cwd(), 'structure/client/components', name);
try {
fs.accessSync(directory);
} catch(err) {
return { error: true, msg: `\`${name}\` is an invalid path!` };
}
// components/commands/utility/Ping.js
const func = require(directory); //directory
if (typeof func !== 'function') {
delete require.cache[directory];
return { error: true, msg: 'Attempted to index an invalid function as a component.' };
}
const component = new func(this.client);
result = this.client.registry.loadComponent(component, directory);
if (!result) return { error: true, msg: `Failed to load component ${name}, see console.` };
return { msg: `Successfully loaded component: ${component.resolveable}` };
} else {
const component = this.client.registry.components.filter(filterInexact(name)).first();
if (!component) return { error: true, msg: `Component ${name} doesn't exist.` };
result = component.unload();
}
if (result.error) return { error: true, msg: `Cannot ${load ? 'load' : 'unload'} ${name} due to ${result.code}` };
else return { msg: `Successfully ${load ? 'loaded' : 'unloaded'} component ${name}` };
}
}
module.exports = ComponentCommand;
const filterInexact = (search) => {
return comp => comp.id.toLowerCase().includes(search) ||
comp.resolveable.toLowerCase().includes(search) ||
(comp.aliases && (comp.aliases.some(ali => `${comp.type}:${ali}`.toLowerCase().includes(search)) ||
comp.aliases.some(ali => ali.toLowerCase().includes(search))));
};

View File

@ -18,8 +18,8 @@ class Evaluate extends Command {
'eval', 'eval',
'e' 'e'
], ],
usage: '<code>',
restricted: true, restricted: true,
description: "Evaluates javascript code.",
arguments: [ arguments: [
new Argument(client, { new Argument(client, {
name: 'log', name: 'log',
@ -33,12 +33,12 @@ class Evaluate extends Command {
types: ['FLAG'], types: ['FLAG'],
description: "Hides the output from the channel." description: "Hides the output from the channel."
}) })
] ],
showUsage: true
}); });
} }
async execute(message, { params, args }) { async execute(message, { params, args }) {
params = params.join(' '); params = params.join(' ');

View File

@ -1,52 +0,0 @@
const { Command, Argument } = require('../../../../interfaces/');
class ReloadCommand extends Command {
constructor(client) {
super(client, {
name: 'reload',
module: 'developer',
description: 'Reloads components and locales.',
restricted: true,
aliases: ['r'],
arguments: [
new Argument(client, {
name: 'language',
type: 'STRING',
types: ['FLAG'],
aliases: [ 'lang' ],
description: "Reloads the language library",
default: 'all'
})
]
});
this.client = client;
}
async execute(message, { args }) {
if (args.language) {
if (args.language.value === 'all') {
await this.client.localeLoader.loadLanguages();
return message.respond('Reloaded all languages');
} else {
try {
await this.client.localeLoader.loadLanguage(args.language.value);
return message.respond(`Reloaded locale \`${args.language.value}\``);
} catch (err) {
return message.respond(err.message);
}
}
}
}
}
module.exports = ReloadCommand;

View File

@ -0,0 +1,110 @@
const { Command } = require('../../../../interfaces/');
class CommandsCommand extends Command {
constructor(client) {
super(client, {
name: 'commands',
module: 'information',
aliases: [
'cmd',
'cmds',
'command'
],
usage: '[module]',
arguments: [
// new Argument(client, {
// name: 'user',
// type: 'BOOLEAN',
// types: ['VERBAL', 'FLAG'],
// default: true
// }),
]
});
this.client = client;
}
async execute(message, { params }) {
if (!params.length) // list all commands
return this._listCommands(message);
params = params.join(' ');
const [ mod ] = this.client.resolver.components(params, 'module', false);
if(!mod) {
const [ command ] = this.client.resolver.components(params, 'command', false);
if (!command) return message.format('C_COMMAND_INVALID');
return await message._showUsage(command);
}
//list module's commands
const commands = mod.components.filter(c=>c.type === 'command');
let text = '';
for(let command of commands.values()) {
text += command.disabled ? `~~${command.name}~~\n` : `${command.name}`; //TODO: Denote disabled commands somehow
}
const embed = {
author: {
name: message.format('C_COMMANDS_TITLE'),
icon_url: this.client.user.avatarURL()
},
description: message.format('C_COMMANDS') + '\n' + message.format('C_COMMANDS_TEMPLATE', { mod: mod.name, text }),
footer: {
text: message.format('C_COMMANDS_FOOTER')
}
};
return message.embed(embed);
}
_listCommands(message) {
let fields = [];
const sortedModules = this.client.registry.components
.filter(c => c.type === 'module')
.sort((a, b) => {
const filter = c => c.type === 'command';
return b.components.filter(filter) - a.components.filter(filter);
});
for (const mod of sortedModules.values()) {
let field = {
name: mod.id,
value: '',
inline: true
};
for (const command of mod.components.values()) {
if (command.type !== 'command'
|| (command.restricted && !this.client._options.bot.owners.includes(message.author.id))) continue;
field.value += `${command.name}\n`;
}
if (field.value) fields.push(field);
}
const embed = {
author: {
name: message.format('C_COMMANDS_TITLE'),
icon_url: this.client.user.avatarURL()
},
description: message.format('C_COMMANDS'),
fields,
footer: {
text: message.format('C_COMMANDS_FOOTER')
}
};
return message.embed(embed);
}
}
module.exports = CommandsCommand;

View File

@ -6,18 +6,20 @@ class HelpCommand extends Command {
super(client, { super(client, {
name: 'help', name: 'help',
module: 'information', module: 'information'
description: 'Get help!',
showUsage: true
}); });
this.client = client; this.client = client;
} }
async execute(message, { params }) { async execute(message, { params }) {
if (!params.length)
return await message.embed({
description: message.format('C_HELP')
});
const [ key ] = params; const [ key ] = params;
let [ result ] = this.client.resolver.components(key, 'command'); let [ result ] = this.client.resolver.components(key, 'command');
if (!result) [ result ] = this.client.resolver.components(key, 'setting'); if (!result) [ result ] = this.client.resolver.components(key, 'setting');

View File

@ -9,7 +9,6 @@ class PingCommand extends Command {
super(client, { super(client, {
name: 'arguments', name: 'arguments',
module: 'utility', module: 'utility',
description: "Tests the argument parsing of the command handler.",
aliases: ['args', 'arg', 'argument'], aliases: ['args', 'arg', 'argument'],
arguments: [ arguments: [
new Argument(client, { new Argument(client, {
@ -32,7 +31,9 @@ class PingCommand extends Command {
required: true, required: true,
types: ['FLAG', 'VERBAL'] types: ['FLAG', 'VERBAL']
}) })
] ],
restricted: true,
archivable: false
}); });
this.client = client; this.client = client;

View File

@ -28,9 +28,7 @@ class SettingCommand extends Command {
default: true default: true
}) })
], ],
memberPermissions: ['ADMINISTRATOR'], showUsage: true
showUsage: true,
}); });
this.client = client; this.client = client;
@ -67,17 +65,16 @@ class SettingCommand extends Command {
//Setting permission handling //Setting permission handling
if(setting.clientPermissions.length > 0) { if(setting.clientPermissions.length > 0) {
const missing = message.channel.permissionsFor(message.guild.me).missing(command.clientPermissions); const missing = message.channel.permissionsFor(message.guild.me).missing(setting.clientPermissions);
if(missing.length > 0) { if(missing.length > 0) {
await message.respond(message.format('C_SETTINGS_CLIENTPERMISSIONERROR', { setting: setting.moduleResolveable, missing: missing.join(', ')}), { emoji: 'failure' }); await message.respond(message.format('C_SETTINGS_CLIENTPERMISSIONERROR', { setting: setting.moduleResolveable, missing: missing.join(', ')}), { emoji: 'failure' });
return undefined; return undefined;
} }
} else if(setting.memberPermissions.length > 0) { }
const missing = message.channel.permissionsFor(message.member).missing(command.memberPermissions);
if(missing.length > 0) { if(message.channel.permissionsFor(message.member).missing('ADMINISTRATOR').length > 0 && setting.resolve === 'GUILD') {
await message.respond(message.format('C_SETTINGS_MEMBERPERMISSIONERROR', { setting: setting.moduleResolveable, missing: missing.join(', ')}), { emoji: 'failure' }); await message.respond(message.format('C_SETTINGS_ADMINISTRATORERROR', { type: type.toLowerCase() }), { emoji: 'failure' });
return undefined; return undefined;
}
} }
const response = await setting.handle(message, params.splice(1)); const response = await setting.handle(message, params.splice(1));
@ -138,8 +135,8 @@ class SettingCommand extends Command {
if(!bool) return message.respond(message.format('C_SETTINGS_RESETABORT'), { emoji: 'success' }); if(!bool) return message.respond(message.format('C_SETTINGS_RESETABORT'), { emoji: 'success' });
type === 'USER' type === 'USER'
? await message.author._deleteSettings() ? await message.author._delete()
: await message.guild._deleteSettings(); : await message.guild._delete('guilds');
return message.respond(message.format('C_SETTINGS_RESETSUCCESS', { type: type.toLowerCase() }), { emoji: 'success' }) return message.respond(message.format('C_SETTINGS_RESETSUCCESS', { type: type.toLowerCase() }), { emoji: 'success' })

View File

@ -1,33 +0,0 @@
const { Command } = require('../../../../interfaces/');
class PingCommand extends Command {
constructor(client) {
super(client, {
name: 'test',
module: 'utility',
description: "Determines the ping of the bot.",
arguments: [
]
});
this.client = client;
}
async execute(message) {
const time1 = new Date().getTime();
let response = await this.client.transactionHandler.send({ provider: 'mongodb', request: { collection: 'infractions', type: 'find', query: { case: 1 } } }).catch(err => { return err; });
const time2 = new Date().getTime();
console.log(time2-time1);
message.reply(JSON.stringify(response));
}
}
module.exports = PingCommand;

View File

@ -10,30 +10,37 @@ const Guild = Structures.extend('Guild', (Guild) => {
super(...args); super(...args);
this._settings = null; //internal cache of current guild's settings; should ALWAYS stay the same as database. this._settings = null; //internal cache of current guild's settings; should ALWAYS stay the same as database.
this._permissions = null; //internal cache, should always match database.
} }
async settings() { async settings() {
if(!this._settings) this._settings = this.client.transactionHandler.send({ provider: 'mongodb', request: { collection: 'guilds', type: 'findOne', query: { guildId: this.id } } }); if(!this._settings) this._settings = this.client.transactionHandler.send({ provider: 'mongodb', request: { collection: 'guilds', type: 'findOne', query: { guildId: this.id } } });
if(this._settings instanceof Promise) this._settings = await this._settings || null if(this._settings instanceof Promise) this._settings = await this._settings || null;
if(!this._settings) { if(!this._settings) {
this._settings = this.client.defaultConfig; this._settings = this.client.defaultConfig;
} }
return this._settings; return this._settings;
} }
/* Database Shortcuts */ async permissions_() {
if(!this._permissions) this._permissions = this.client.transactionHandler.send({ provider: 'mongodb', request: { collection: 'permissions', type: 'findOne', query: { guildId: this.id } } });
if(this._permissions instanceof Promise) this._permissions = await this._permissions || null;
if(!this._permissions) {
this._permissions = {};
}
return this._permissions;
}
async _deleteSettings() { //Delete whole entry - remove /* Settings Wrapper */
async _dbDelete(collection) { //Delete whole entry - remove
try { try {
await this.client.transactionHandler.send({ await this.client.transactionHandler.send({
provider: 'mongodb', provider: 'mongodb',
request: { request: {
type: 'remove', type: 'remove',
collection: 'guilds', collection,
query: { query: {
guildId: this.id guildId: this.id
} }
@ -46,21 +53,22 @@ const Guild = Structures.extend('Guild', (Guild) => {
} }
} }
async _updateSettings(data) { //Update property (upsert true) - updateOne async _dbUpdateOne(data, collection) { //Update property (upsert true) - updateOne
var index = this.dbIndex(collection);
try { try {
await this.client.transactionHandler.send({ await this.client.transactionHandler.send({
provider: 'mongodb', provider: 'mongodb',
request: { request: {
type: 'updateOne', type: 'updateOne',
collection: 'guilds', collection,
query: { query: {
guildId: this.id guildId: this.id
}, },
data data
} }
}); });
this._settings = { index = {
...this._settings, ...index,
...data ...data
}; };
this._storageLog(`Database Update (guild:${this.id}).`); this._storageLog(`Database Update (guild:${this.id}).`);
@ -69,18 +77,18 @@ const Guild = Structures.extend('Guild', (Guild) => {
} }
} }
async _removeSettings(value) { //Remove property async _dbRemoveProperty(value, collection) { //Remove property
if(this.client.defaultConfig[value]) { const index = this.dbIndex(collection);
if(collection === 'guild' && this.client.defaultConfig[value]) {
await this._updateSettings(this.client.defaultConfig[value]); await this._updateSettings(this.client.defaultConfig[value]);
return undefined; return undefined;
} }
try { try {
await this.client.transactionHandler.send({ await this.client.transactionHandler.send({
provider: 'mongodb', provider: 'mongodb',
request: { request: {
type: 'removeProperty', type: 'removeProperty',
collection: 'guilds', collection,
query: { query: {
guildId: this.id guildId: this.id
}, },
@ -89,33 +97,23 @@ const Guild = Structures.extend('Guild', (Guild) => {
] ]
} }
}); });
delete this._settings[value]; delete index[value];
this._storageLog(`Database Remove (guild:${this.id}).`); this._storageLog(`Database Remove (guild:${this.id}).`);
} catch(error) { } catch(error) {
this._storageError(error); this._storageError(error);
} }
} }
/* dbIndex(collection) {
async _createSettings(data) { return {
try { 'guilds': this._settings,
this.client.transactionHandler.send({ 'permissions': this._permissions
provider: 'mongodb', }[collection];
request: {
type: 'insertOne',
collection: 'guilds',
data: {
guildId: this.id,
...data
}
}
});
this._storageLog(`Database Create (guild:${this.id}).`);
} catch(error) {
this._storageError(error);
}
} }
*/
/* Permissions Wrapper */
/* Language Formatting */ /* Language Formatting */

View File

@ -0,0 +1,26 @@
const { Structures } = require('discord.js');
const GuildMember = Structures.extend('GuildMember', (GuildMember) => {
class ExtendedGuildMember extends GuildMember {
constructor(...args) {
super(...args);
this._cached = Date.now();
}
get timeSinceCached() {
return Date.now()-this._cached;
}
}
return ExtendedGuildMember;
});
module.exports = GuildMember;

View File

@ -4,6 +4,7 @@ const escapeRegex = require('escape-string-regexp');
const emojis = require('../../util/emojis.json'); const emojis = require('../../util/emojis.json');
const { Util } = require('../../util/'); const { Util } = require('../../util/');
const { stripIndents } = require('common-tags')
const Message = Structures.extend('Message', (Message) => { const Message = Structures.extend('Message', (Message) => {
@ -26,7 +27,7 @@ const Message = Structures.extend('Message', (Message) => {
let language = this.author._settings.locale || 'en_us'; let language = this.author._settings.locale || 'en_us';
if(this.guild && this.guild._settings.locale) language = this.guild._settings.locale; if(this.guild && this.guild._settings.locale) language = this.guild._settings.locale;
parameters.prefix = this.guild ? this.guild.prefix : this.client._options.bot.prefix; parameters.prefix = this.guild?.prefix || this.client._options.bot.prefix;
let template = this.client.localeLoader.template(language, index); //.languages[language][index]; let template = this.client.localeLoader.template(language, index); //.languages[language][index];
if(!template) { if(!template) {
@ -118,18 +119,46 @@ const Message = Structures.extend('Message', (Message) => {
}); });
} }
async _showUsage() { async _showUsage(comp = null) {
//TODO: format this
return await this.embed({ const component = comp
title: `**${this.command.name.toUpperCase()} USAGE**`, ? comp
description: this.format(`C_${this.command.name.toUpperCase()}_USAGE`), : this.command;
fields: [
{ const prefix = this.guild?.prefix
name: this.format('EXAMPLES'), || this.client._options.bot.prefix;
value: this.format(`C_${this.command.name.toUpperCase()}_EXAMPLES`)
} let fields = [];
] if(component.examples.length > 0) {
}); fields.push({
name: `${this.format('GENERAL_EXAMPLES')}`,
value: component.examples.map(e=>`\`${prefix}${component.name} ${e}\``).join('\n')
});
}
if(component.aliases.length > 0) {
fields.push({
name: `${this.format('GENERAL_ALIASES')}`,
value: component.aliases.map(a=>`\`${a}\``).join(', ')
});
}
if(component.arguments.length > 0) {
fields.push({
name: `${this.format('GENERAL_ARGUMENTS')}`,
value: component.arguments.map(a=>`${a.name}: ${this.format(`A_${a.name.toUpperCase}_DESCRIPTION`)}`)
});
}
let embed = {
author: {
name: `${component.name}${component.module ? ` (${component.module.resolveable})` : ''}`,
icon_url: this.client.user.avatarURL()
},
description: stripIndents`\`${prefix}${component.name}${component.usage ? ` ${component.usage}` : ''}\`
${this.format(`C_${component.name.toUpperCase()}_DESCRIPTION`)}${component.guildOnly ? ' *(guild-only)*' : ''}`,
fields
};
return await this.embed(embed);
} }
} }

View File

@ -1,5 +1,6 @@
module.exports = { module.exports = {
Message: require('./Message.js'), Message: require('./Message.js'),
Guild: require('./Guild.js'), Guild: require('./Guild.js'),
GuildMember: require('./GuildMember.js'),
User: require('./User.js') User: require('./User.js')
}; };

View File

@ -19,14 +19,15 @@ class Command extends Component {
this.aliases = opts.aliases || []; this.aliases = opts.aliases || [];
this.description = `C_${opts.name}_DESCRIPTION`; this.description = `C_${opts.name}_DESCRIPTION`;
this.examples = `C_${opts.name}_EXAMPLES`; this.usage = opts.usage || '';
this.usage = `C_${opts.name}_USAGE`; this.examples = opts.examples || [];
this.restricted = Boolean(opts.restricted); this.restricted = Boolean(opts.restricted);
this.archivable = opts.archivable === undefined ? false : Boolean(opts.archivable);
this.guildOnly = Boolean(opts.guildOnly);
this.arguments = opts.arguments || [];
this.showUsage = Boolean(opts.showUsage); this.showUsage = Boolean(opts.showUsage);
this.guildOnly = Boolean(opts.guildOnly);
this.archivable = opts.archivable === undefined ? false : Boolean(opts.archivable);
this.arguments = opts.arguments || [];
this.clientPermissions = opts.clientPermissions || []; this.clientPermissions = opts.clientPermissions || [];
this.memberPermissions = opts.memberPermissions || []; this.memberPermissions = opts.memberPermissions || [];

View File

@ -41,15 +41,8 @@ class Setting extends Component {
} }
async _parseArguments(params) { async _parseArguments(params) {
const { parsedArguments, newArgs } = await this.commandHandler._parseArguments(this.arguments, params); const { parsedArguments, newArgs } = await this.commandHandler._parseArguments(this.arguments, params);
return { parsedArguments, params: newArgs }; return { parsedArguments, params: newArgs };
}
async _handleReset(message, params) {
const response = await message.prompt(message.format('UHHH'), { error: 'warning' });
} }
get commandHandler() { get commandHandler() {