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
.eslintrc.json
logs
permissionExample.json

View File

@ -25,11 +25,11 @@ class LocaleLoader {
const _directory = path.join(process.cwd(), 'language/languages');
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)}`);
const directory = path.join(process.cwd(), `language/languages/${language}`);
@ -76,7 +76,7 @@ class LocaleLoader {
}
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;

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__**
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**
**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.
**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**
For help with moderation, see `{prefix}help modhelp`.
@ -35,4 +42,23 @@ __**{component} HELP**__
[C_HELP_HELP]
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]
Pong!
[C_PING_DESCRIPTION]
Shows the millisecond delay between the bot and the discord server.
//Settings Command
[C_SETTINGS_DESCRIPTION]
Configure your guild and user settings.
[C_SETTINGS_ADMINISTRATORERROR]
You must have the `ADMINISTRATOR` permission to reset the {type} settings.
@ -43,6 +49,9 @@ That setting does not exist!
//User Command
[C_USER_DESCRIPTION]
Search for users or view user information.
[C_USER]
**Nickname:** {nickname}
**User:** <@{id}>

View File

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

View File

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

View File

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

View File

@ -10,7 +10,7 @@ const Logger = require('./Logger.js');
const TransactionHandler = require('./TransactionHandler.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/');
class DiscordClient extends Client {
@ -47,7 +47,7 @@ class DiscordClient extends Client {
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/commands/', Command);
@ -69,7 +69,7 @@ class DiscordClient extends Client {
def = {
...def,
...setting.default
}
};
}
}
this._defaultConfig = def;

View File

@ -24,12 +24,14 @@ class Registry {
const func = require(path);
if(typeof func !== 'function') {
this.client.logger.warn("Attempted to index an invalid function as a component.");
delete require.cache[path];
continue;
}
const component = new func(this.client); //Instantiates the component class.
if(classToHandle && !(component instanceof classToHandle)) {
this.client.logger.warn("Attempted to load an invalid class.");
delete require.cache[path];
continue;
}
@ -44,6 +46,7 @@ class Registry {
async loadComponent(component, directory) {
if(!(component instanceof Component)) {
delete require.cache[directory];
this.client.logger.warn("Attempted to load an invalid component.");
return null;
}
@ -69,7 +72,10 @@ class Registry {
}
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)) {
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);
} else if(/(id\:)?([0-9]{17,21})/.test(resolveable)) {
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);
} 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 username = m[1].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;
}).first(1);
}).first();
if(member) resolved.push(member);
} else if(/^\@?([\S\s]{1,32})/.test(resolveable) && guild && !strict) {
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) &&
((!m.nickname ? false : m.nickname.toLowerCase() == nickname ) ||
(!m.nickname ? false : m.nickname.toLowerCase().includes(nickname)) ||
m.user.username.toLowerCase().includes(nickname) ||
m.user.username.toLowerCase() == nickname);
}).first(1);
}).first();
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
*
@ -227,12 +234,12 @@ class Resolver {
let name = /^\#?([a-z0-9\-\_0]*)/i;
let id = /^\<\#([0-9]*)\>/i;
if(name.test(resolveable)) {
if (name.test(resolveable)) {
let match = resolveable.match(name);
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);
return c.name.toLowerCase() === ch;
}).first(1);
@ -287,7 +294,7 @@ class Resolver {
} else {
let role = roles.cache.filter(r => {
let [ role ] = roles.cache.filter(r => {
if(!strict) return r.name.toLowerCase().includes(resolveable.toLowerCase());
return r.name.toLowerCase() === resolveable.toLowerCase();
}).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',
'e'
],
usage: '<code>',
restricted: true,
description: "Evaluates javascript code.",
arguments: [
new Argument(client, {
name: 'log',
@ -33,12 +33,12 @@ class Evaluate extends Command {
types: ['FLAG'],
description: "Hides the output from the channel."
})
]
],
showUsage: true
});
}
async execute(message, { params, args }) {
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, {
name: 'help',
module: 'information',
description: 'Get help!',
showUsage: true
module: 'information'
});
this.client = client;
}
async execute(message, { params }) {
if (!params.length)
return await message.embed({
description: message.format('C_HELP')
});
const [ key ] = params;
let [ result ] = this.client.resolver.components(key, 'command');
if (!result) [ result ] = this.client.resolver.components(key, 'setting');

View File

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

View File

@ -28,9 +28,7 @@ class SettingCommand extends Command {
default: true
})
],
memberPermissions: ['ADMINISTRATOR'],
showUsage: true,
showUsage: true
});
this.client = client;
@ -67,17 +65,16 @@ class SettingCommand extends Command {
//Setting permission handling
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) {
await message.respond(message.format('C_SETTINGS_CLIENTPERMISSIONERROR', { setting: setting.moduleResolveable, missing: missing.join(', ')}), { emoji: 'failure' });
return undefined;
}
} else if(setting.memberPermissions.length > 0) {
const missing = message.channel.permissionsFor(message.member).missing(command.memberPermissions);
if(missing.length > 0) {
await message.respond(message.format('C_SETTINGS_MEMBERPERMISSIONERROR', { setting: setting.moduleResolveable, missing: missing.join(', ')}), { emoji: 'failure' });
return undefined;
}
}
if(message.channel.permissionsFor(message.member).missing('ADMINISTRATOR').length > 0 && setting.resolve === 'GUILD') {
await message.respond(message.format('C_SETTINGS_ADMINISTRATORERROR', { type: type.toLowerCase() }), { emoji: 'failure' });
return undefined;
}
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' });
type === 'USER'
? await message.author._deleteSettings()
: await message.guild._deleteSettings();
? await message.author._delete()
: await message.guild._delete('guilds');
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);
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() {
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) {
this._settings = this.client.defaultConfig;
}
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 {
await this.client.transactionHandler.send({
provider: 'mongodb',
request: {
type: 'remove',
collection: 'guilds',
collection,
query: {
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 {
await this.client.transactionHandler.send({
provider: 'mongodb',
request: {
type: 'updateOne',
collection: 'guilds',
collection,
query: {
guildId: this.id
},
data
}
});
this._settings = {
...this._settings,
index = {
...index,
...data
};
this._storageLog(`Database Update (guild:${this.id}).`);
@ -69,18 +77,18 @@ const Guild = Structures.extend('Guild', (Guild) => {
}
}
async _removeSettings(value) { //Remove property
if(this.client.defaultConfig[value]) {
async _dbRemoveProperty(value, collection) { //Remove property
const index = this.dbIndex(collection);
if(collection === 'guild' && this.client.defaultConfig[value]) {
await this._updateSettings(this.client.defaultConfig[value]);
return undefined;
}
try {
await this.client.transactionHandler.send({
provider: 'mongodb',
request: {
type: 'removeProperty',
collection: 'guilds',
collection,
query: {
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}).`);
} catch(error) {
this._storageError(error);
}
}
/*
async _createSettings(data) {
try {
this.client.transactionHandler.send({
provider: 'mongodb',
request: {
type: 'insertOne',
collection: 'guilds',
data: {
guildId: this.id,
...data
}
}
});
this._storageLog(`Database Create (guild:${this.id}).`);
} catch(error) {
this._storageError(error);
}
dbIndex(collection) {
return {
'guilds': this._settings,
'permissions': this._permissions
}[collection];
}
*/
/* Permissions Wrapper */
/* 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 { Util } = require('../../util/');
const { stripIndents } = require('common-tags')
const Message = Structures.extend('Message', (Message) => {
@ -26,7 +27,7 @@ const Message = Structures.extend('Message', (Message) => {
let language = this.author._settings.locale || 'en_us';
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];
if(!template) {
@ -118,18 +119,46 @@ const Message = Structures.extend('Message', (Message) => {
});
}
async _showUsage() {
//TODO: format this
return await this.embed({
title: `**${this.command.name.toUpperCase()} USAGE**`,
description: this.format(`C_${this.command.name.toUpperCase()}_USAGE`),
fields: [
{
name: this.format('EXAMPLES'),
value: this.format(`C_${this.command.name.toUpperCase()}_EXAMPLES`)
}
]
});
async _showUsage(comp = null) {
const component = comp
? comp
: this.command;
const prefix = this.guild?.prefix
|| this.client._options.bot.prefix;
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 = {
Message: require('./Message.js'),
Guild: require('./Guild.js'),
GuildMember: require('./GuildMember.js'),
User: require('./User.js')
};

View File

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

View File

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