From a71cb26522f06952ecf5bf664b6e9d59cf7ac0e5 Mon Sep 17 00:00:00 2001 From: nolan Date: Fri, 8 May 2020 02:50:54 -0400 Subject: [PATCH] grant/revoke commands, command handler improvements, avatar command --- language/LocaleLoader.js | 3 +- .../en_us/arguments/en_us_administrator.lang | 2 + .../en_us/commands/en_us_administration.lang | 47 +++++ .../en_us/commands/en_us_administrator.lang | 20 -- .../en_us/commands/en_us_utility.lang | 6 +- language/languages/en_us/en_us_general.lang | 2 +- package.json | 2 +- structure/client/Resolver.js | 55 ++--- .../commands/administration/Grant.js | 162 +++++++++++++++ .../commands/administration/Permissions.js | 40 ++++ .../commands/administration/Revoke.js | 189 ++++++++++++++++++ .../commands/administrator/Grant.js | 101 ---------- .../commands/administrator/Revoke.js | 37 ---- .../commands/developer/Component.js | 6 +- .../components/commands/developer/Evaluate.js | 10 +- .../commands/information/Commands.js | 4 +- .../components/commands/utility/Arguments.js | 26 ++- .../components/commands/utility/Avatar.js | 63 ++++++ .../components/commands/utility/Settings.js | 10 +- .../components/commands/utility/User.js | 7 +- .../components/observers/CommandHandler.js | 55 +++-- structure/extensions/Guild.js | 24 +-- structure/extensions/Message.js | 6 +- structure/interfaces/Argument.js | 4 +- structure/interfaces/Command.js | 2 +- 25 files changed, 626 insertions(+), 257 deletions(-) create mode 100644 language/languages/en_us/arguments/en_us_administrator.lang create mode 100644 language/languages/en_us/commands/en_us_administration.lang delete mode 100644 language/languages/en_us/commands/en_us_administrator.lang create mode 100644 structure/client/components/commands/administration/Grant.js create mode 100644 structure/client/components/commands/administration/Permissions.js create mode 100644 structure/client/components/commands/administration/Revoke.js delete mode 100644 structure/client/components/commands/administrator/Grant.js delete mode 100644 structure/client/components/commands/administrator/Revoke.js create mode 100644 structure/client/components/commands/utility/Avatar.js diff --git a/language/LocaleLoader.js b/language/LocaleLoader.js index 7a1b878..5b06f98 100644 --- a/language/LocaleLoader.js +++ b/language/LocaleLoader.js @@ -16,7 +16,7 @@ class LocaleLoader { template(locale, index) { // console.log(String.raw`${this.languages[locale][index]}`); - return (this.languages[locale] ? this.languages[locale][index] : `Locale ${locale} does not exist.`) || `Entry ${locale}:${index} does not exist.`; + return (this.languages[locale] ? this.languages[locale][index] : `Locale \`${locale}\` does not exist.`) || `Entry \`${locale}:${index}\` does not exist.`; } @@ -74,6 +74,7 @@ class LocaleLoader { } } } + parsed[matched] = text; for(const [ key, value ] of Object.entries(parsed)) { parsed[key] = value.replace(/^(\r)|(\r){1,}$/g, ''); diff --git a/language/languages/en_us/arguments/en_us_administrator.lang b/language/languages/en_us/arguments/en_us_administrator.lang new file mode 100644 index 0000000..d961b01 --- /dev/null +++ b/language/languages/en_us/arguments/en_us_administrator.lang @@ -0,0 +1,2 @@ +[A_CHANNEL_GRANT_DESCRIPTION] +Specify channels to grant specific permissions to. \ No newline at end of file diff --git a/language/languages/en_us/commands/en_us_administration.lang b/language/languages/en_us/commands/en_us_administration.lang new file mode 100644 index 0000000..ff72ef5 --- /dev/null +++ b/language/languages/en_us/commands/en_us_administration.lang @@ -0,0 +1,47 @@ +//Grant Command + +[C_GRANT_DESCRIPTION] +Grant roles or users permissions for commands or modules. + +[C_GRANT_RESOLVEERROR] +Unable to find a role or member, view `{prefix}cmd grant` for more help. + +[C_GRANT_MISSINGPERMPARAM] +You must provide permissions to grant, view `{prefix}cmd grant` for more help. + +[C_GRANT_DATABASEERROR] +There was an issue pushing the permissions to the database. Contact a bot developer. + +[C_GRANT_SUCCESS] +Successfully granted **{resolveable}** the following permissions: {permissions} + +[C_GRANT_SUCCESSALT]S +in channel(s) {channels}. + +[C_GRANT_SUCCESSFAILED] +Some permissions have failed to add, likely because the {resolveable} already has the permissions or you did not supply valid permissions. + +[C_GRANT_FAILED] +Failed to grant the following permissions: {failed}. +This is likely because the {resolveable} already has the permissions or you did not supply valid permissions. + + +//Revoke Command + +[C_REVOKE_DESCRIPTION] +Revoke permissions granted to roles or users. +NOTE: If a role is deleted or a user left the guild, you must use the ID to revoke the permissions. + +[C_REVOKE_RESOLVEERROR] +Unable to find a role or user, view `{prefix}cmd revoke` for more help. + +[C_REVOKE_MISSINGPERMPARAM] +You must provide permissions to revoke, view `{prefix}cmd grant` for more help. + +[C_REVOKE_DATABASEERROR] +There was an issue removing the permissions from the database. Contact a bot developer. + +//Permissions Command + +[C_PERMISSIONS_DESCRIPTION] +View permissions granted to roles or users. \ No newline at end of file diff --git a/language/languages/en_us/commands/en_us_administrator.lang b/language/languages/en_us/commands/en_us_administrator.lang deleted file mode 100644 index 7ff1fce..0000000 --- a/language/languages/en_us/commands/en_us_administrator.lang +++ /dev/null @@ -1,20 +0,0 @@ -//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. \ No newline at end of file diff --git a/language/languages/en_us/commands/en_us_utility.lang b/language/languages/en_us/commands/en_us_utility.lang index 9ecacbb..0ee3c6b 100644 --- a/language/languages/en_us/commands/en_us_utility.lang +++ b/language/languages/en_us/commands/en_us_utility.lang @@ -70,4 +70,8 @@ Search result for keyword: `{key}` Found {matches} matches, displaying {count} [C_USER_HELP] -To search server members with similar names use `{prefix}user search ` \ No newline at end of file +To search server members with similar names use `{prefix}user search ` + +//Avatar Command +[C_AVATAR_FORMATERROR] +Unable to find an avatar with those arguments, try a different size or format. \ No newline at end of file diff --git a/language/languages/en_us/en_us_general.lang b/language/languages/en_us/en_us_general.lang index 5b78486..3ef3f15 100644 --- a/language/languages/en_us/en_us_general.lang +++ b/language/languages/en_us/en_us_general.lang @@ -18,4 +18,4 @@ Example Usage Aliases [GENERAL_ARGUMENTS] -Arguments \ No newline at end of file +Arguments diff --git a/package.json b/package.json index bd96a1c..065fd82 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "New iteration of GalacticBot", "main": "index.js", "scripts": { - "start": "node --trace-warnings --inspect index.js" + "start": "node --trace-warnings index.js" }, "repository": { "type": "git", diff --git a/structure/client/Resolver.js b/structure/client/Resolver.js index 99696be..5120bc9 100644 --- a/structure/client/Resolver.js +++ b/structure/client/Resolver.js @@ -43,7 +43,7 @@ class Resolver { async resolveMemberAndUser(string, guild) { const str = string.toLowerCase(); - const index = guild ? guild.members : this.client.users; + const index = guild ? guild.members.cache : this.client.users.cache; let member = null; if(/<@!?(\d{17,21})>/iy.test(str)) { //mentions @@ -54,7 +54,7 @@ class Resolver { member = await index.fetch(matches[1]); } catch(e) { try { - member = await this.client.users.fetch(matches[1]); + member = await this.client.users.cache.fetch(matches[1]); } catch(e) {} //eslint-disable-line no-empty } //eslint-disable-line no-empty } @@ -66,16 +66,17 @@ class Resolver { member = await index.fetch(matches[1]); } catch(e) { try { - member = await this.client.users.fetch(matches[1]); + member = await this.client.users.cache.fetch(matches[1]); } catch(e) {} //eslint-disable-line no-empty } //eslint-disable-line no-empty } } else if(/(.{2,32})#(\d{4})/iy.test(str)) { //username#discrim const matches = /(.{2,32})#(\d{4})/iy.exec(str); member = guild - ? guild.members.filter(m=>m.user.username === matches[1] && m.user.discriminator === matches[2]).first() - : this.client.users.filter(u=>u.username === matches[1] && u.discriminator === matches[2]).first(); + ? guild.members.cache.filter(m=>m.user.username === matches[1] && m.user.discriminator === matches[2]).first() + : this.client.users.cache.filter(u=>u.username === matches[1] && u.discriminator === matches[2]).first(); } + return member || null; } @@ -100,13 +101,13 @@ class Resolver { if(/<@!?([0-9]{17,21})>/.test(resolveable)) { let id = resolveable.match(/<@!?([0-9]{17,21})>/)[1]; - let user = await users.fetch(id).catch(err => { if(err.code === 10013) return false; else { console.warn(err); return false; } }); + let user = await users.fetch(id).catch(err => { if(err.code === 10013) return false; else { this.client.logger.warn(err); return false; } }); if(user) resolved.push(user); } else if(/(id\:)?([0-9]{17,21})/.test(resolveable)) { let id = resolveable.match(/(id\:)?([0-9]{17,21})/)[2]; - let user = await users.fetch(id).catch(err => { if(err.code === 10013) return false; else { console.warn(err); return false; } }); + let user = await users.fetch(id).catch(err => { if(err.code === 10013) return false; else { this.client.logger.warn(err); return false; } }); if(user) resolved.push(user); } else if(/^\@?([\S\s]{1,32})\#([0-9]{4})/.test(resolveable)) { @@ -114,29 +115,29 @@ class Resolver { let m = resolveable.match(/^\@?([\S\s]{1,32})\#([0-9]{4})/); let username = m[1].toLowerCase(); let discrim = m[2].toLowerCase(); - let [ user ] = users.cache.filter(u => { + let user = users.cache.filter(u => { return u.username.toLowerCase() === username && u.discriminator === discrim; - }).first(1); + }).first(); if(user) resolved.push(user); } else if(!strict) { let name = resolveable.toLowerCase(); - let [ user ] = users.cache.filter(u => { + let user = users.cache.filter(u => { return u.username.toLowerCase().includes(name); - }).first(1); + }).first(); if(user) resolved.push(user); } } - return resolved.length > 0 ? resolved : false; + return resolved.length ? resolved : false; } async resolveUser(resolveable, strict) { - let [ result ] = await this.resolveUsers([ resolveable ], strict); + let result = await this.resolveUsers([ resolveable ], strict); return result; } @@ -234,7 +235,16 @@ class Resolver { let name = /^\#?([a-z0-9\-\_0]*)/i; let id = /^\<\#([0-9]*)\>/i; - if (name.test(resolveable)) { + if (id.test(resolveable)) { + + let match = resolveable.match(id); + let ch = match[1]; + + let channel = channels.resolve(ch); + + if (channel) resolved.push(channel); + + } else if (name.test(resolveable)) { let match = resolveable.match(name); let ch = match[1].toLowerCase(); @@ -246,16 +256,7 @@ class Resolver { if(channel) resolved.push(channel); - } else if(id.test(resolveable)) { - - let match = resolveable.match(id); - let ch = match[1]; - - let channel = channels.resolve(ch); - - if(channel) resolved.push(channel); - - } + } } @@ -288,16 +289,16 @@ class Resolver { let match = resolveable.match(id); let r_id = match[2]; - let role = await roles.fetch(r_id).catch(console.error); + let role = await roles.fetch(r_id).catch(this.client.logger.error); if(role) resolved.push(role); } 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); + }).first(); if(role) resolved.push(role); diff --git a/structure/client/components/commands/administration/Grant.js b/structure/client/components/commands/administration/Grant.js new file mode 100644 index 0000000..da2d3d0 --- /dev/null +++ b/structure/client/components/commands/administration/Grant.js @@ -0,0 +1,162 @@ +const { Command } = require('../../../../interfaces/'); + +const { stripIndents } = require('common-tags'); + +class GrantCommand extends Command { + + constructor(client) { + + super(client, { + name: 'grant', + module: 'administration', + usage: " ", + examples: [ + "\"Server Moderators\" module:moderation", + "@nolan#2887 command:kick" + ], + memberPermissions: ['ADMINISTRATOR', 'MANAGE_SERVER'], + showUsage: true, + guildOnly: true, + arguments: [ + { + name: 'channel', + aliases: [ + 'channels' + ], + type: 'CHANNEL', + types: ['FLAG', 'VERBAL'], + infinite: true + } + ] + }); + + } + + async execute(message, { params, args }) { + + // console.log(args.channel); + + const _permissions = await message.guild.permissions(); + + const [ parse, ...perms ] = params; + const resolveable = await this._parseResolveable(message, parse); + if(!resolveable) return undefined; + + 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 = []; + if(perms.join(' ') === 'all') { + parsed = this.client.registry.components.filter(c=>c.grantable && c.type === 'command').map(c=>c.resolveable); + } else { + 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); + } + } + } + + if(!_permissions[resolveable.id]) { + _permissions[resolveable.id] = { + global: [], + channels: {} + }; + } + let existing = _permissions[resolveable.id]; + + let pushed = []; + let failed = []; + if(args.channel) { + for(const channel of args.channel.value) { + const existingChannel = existing.channels[channel.id]; + if(existingChannel) { + for(const perm of parsed) { + if(existingChannel.includes(perm)) { + failed.push(perm); + } else { + existingChannel.push(perm); + pushed.push(perm); + } + } + } else { + existing.channels[channel.id] = parsed; + pushed.concat(parsed) + } + } + } else { + for(const perm of parsed) { + if(existing.global.includes(perm)) { + failed.push(perm); + } else { + existing.global.push(perm); + pushed.push(perm); + } + } + } + + delete _permissions._id; //some bullshit.. + + try { + await this.client.transactionHandler.send({ + provider: 'mongodb', + request: { + type: 'updateOne', + collection: 'permissions', + query: { + guildId: message.guild.id + }, + data: _permissions + } + }); + } catch(error) { + await message.respond(message.format('C_GRANT_DATABASEERROR'), { emoji: 'failure' }); + return undefined; + } + + if(pushed.length > 0) { + return await message.respond(stripIndents`${message.format('C_GRANT_SUCCESS', { resolveable: resolveable.name || resolveable.user?.tag, permissions: pushed.map(p=>`\`${p}\``).join(', ') })}${args.channel ? ` ${message.format('C_GRANT_SUCCESSALT', { channels: args.channel.value.map(c=>`\`#${c.name}\``).join(', ')})}`: '.'} + ${failed.length > 0 ? message.format('C_GRANT_SUCCESSFAILED', { resolveable: resolveable.user ? 'user' : 'role' }) : ''}`, { emoji: 'success' }); + } else { + return await message.respond(message.format('C_GRANT_FAILED', { failed: failed.map(f=>`\`${f}\``).join(', '), resolveable: resolveable.user ? 'user' : 'role' }), { emoji: 'failure' }) + } + + + } + + 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)))); +}; \ No newline at end of file diff --git a/structure/client/components/commands/administration/Permissions.js b/structure/client/components/commands/administration/Permissions.js new file mode 100644 index 0000000..b0fccfe --- /dev/null +++ b/structure/client/components/commands/administration/Permissions.js @@ -0,0 +1,40 @@ +const { Command } = require('../../../../interfaces/'); + +const { stripIndents } = require('common-tags'); + +class GrantCommand extends Command { + + constructor(client) { + + super(client, { + name: 'permissions', + module: 'administration', + usage: "", + aliases: [ + 'perms', + 'permission', + 'perm' + ], + examples: [ + "Server Moderators", + "@nolan#2887" + ], + memberPermissions: ['ADMINISTRATOR', 'MANAGE_SERVER'], + guildOnly: true + }); + + } + + async execute(message) { + + await message.guild.permissions(); + + message.respond(`\`\`\`js +${JSON.stringify(message.guild._permissions)}\`\`\``); + + } + + +} + +module.exports = GrantCommand; \ No newline at end of file diff --git a/structure/client/components/commands/administration/Revoke.js b/structure/client/components/commands/administration/Revoke.js new file mode 100644 index 0000000..c520dca --- /dev/null +++ b/structure/client/components/commands/administration/Revoke.js @@ -0,0 +1,189 @@ +const { Command } = require('../../../../interfaces/'); + +class RevokeCommand extends Command { + + constructor(client) { + + super(client, { + name: 'revoke', + module: 'administration', + usage: " ", + examples: [ + "\"Server Moderators\" module:moderation", + "@nolan#2887 command:kick", + "132620781791346688 moderation" + ], + memberPermissions: ['ADMINISTRATOR'], + showUsage: true, + guildOnly: true, + arguments: [ + { + 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((resolveable && !_permissions[resolveable?.id]) && !_permissions[parse]) { + await message.respond(message.format('C_REVOKE_RESOLVEERROR', { id: resolveable.id }), { emoji: 'failure' }); + return undefined; + } + + if(perms.length === 0) { + await message.respond(message.format('C_REVOKE_MISSINGPERMPARAM'), { emoji: 'failure' }); + return undefined; + } + + const permission = resolveable ? _permissions[resolveable.id] : _permissions[parse]; + + const permissions = this.client.registry.components.filter(c=> + c.type === 'command' + || c.type === 'module' + ); + + let parsed = []; + if(perms[0] === 'all') { + parsed = this.client.registry.components.filter(c=>c.grantable && c.type === 'command').map(c=>c.resolveable); + } else { + for(const perm of perms) { + const search = permissions.filter(filterInexact(perm)).first(); + if(search?.type === 'module') { + for(const component of search.components.values()) { + if(component.type === 'command') parsed.push(component.resolveable); + //add check for grantable + } + } else if (search?.type === 'command'){ + //add check for grantable + parsed.push(search.resolveable); + } else { + if(args.channel) { + for(let channel of args.channel.value) { + if(permission.channels[channel.id].includes(perm)) parsed.push(perm); + } + } else { + if(permission.global.includes(perm)) parsed.push(perm); + } + } + } + } + + let removed = []; + if(args.channel) { + for(const channel of args.channel.value) { + const existingChannel = permission.channels[channel.id]; + if(existingChannel) { + for(const parse of parsed) { + const index = existingChannel.indexOf(parse); + if(index > -1) { + removed.push(parse); + permission.channels[channel.id].splice(index, 1); + } + } + if(existingChannel.length === 0) delete permission.channels[channel.id]; + } else { + continue; + } + } + } else { + for(const parse of parsed) { + const index = permission.global.indexOf(parse); + if(index > -1) { + removed.push(parse); + permission.global.splice(index, 1); + } + } + } + + //check for deletion, saves DB space. + //NOTE: DO NOT REMOVE THE _PERMISSIONS VARIABLE. + console.log(permission.global.length, Object.keys(permission.channels).length); + if(permission.global.length === 0 + && Object.keys(permission.channels).length === 0) { + try { + const blah = await this.client.transactionHandler.send({ + provider: 'mongodb', + request: { + type: 'remove', + collection: 'permissions', + query: { + guildId: message.guild.id + } + } + }); + console.log(blah); + } catch(error) { + this.client.logger.warn(`Attempted to delete collection permissions:${message.guild.id} but failed.`); + } + } + + delete _permissions._id; //some bullshit.. + + if(permission.global.length === 0 && Object.keys(permission.channels).length === 0) { + try { + await this.client.transactionHandler.send({ + provider: 'mongodb', + request: { + type: 'remove', + collection: 'permissions', + query: { + guildId: message.guild.id + } + } + }); + } catch(error) { + this.client.logger.warn(`Attempted to delete collection permissions:${message.guild.id} but failed.`); + } + } else { + try { + await this.client.transactionHandler.send({ + provider: 'mongodb', + request: { + type: 'updateOne', + collection: 'permissions', + query: { + guildId: message.guild.id + }, + data: _permissions + } + }); + } catch(error) { + await message.respond(message.format('C_REVOKE_DATABASEERROR'), { emoji: 'failure' }); + return undefined; + } + } + + + + } + + async _parseResolveable(message, resolveable) { + let parsed = await this.client.resolver.resolveRoles(resolveable, message.guild)[0]; + if(!parsed) { + parsed = await this.client.resolver.resolveMemberAndUser(resolveable, message.guild); + } + return parsed || null; + } + +} + +module.exports = RevokeCommand; + +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)))); +}; \ No newline at end of file diff --git a/structure/client/components/commands/administrator/Grant.js b/structure/client/components/commands/administrator/Grant.js deleted file mode 100644 index aac86ac..0000000 --- a/structure/client/components/commands/administrator/Grant.js +++ /dev/null @@ -1,101 +0,0 @@ -const { Command, Argument } = require('../../../../interfaces/'); - -class GrantCommand extends Command { - - constructor(client) { - - super(client, { - name: 'grant', - module: 'administrator', - usage: " ", - 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)))); -}; \ No newline at end of file diff --git a/structure/client/components/commands/administrator/Revoke.js b/structure/client/components/commands/administrator/Revoke.js deleted file mode 100644 index b04925d..0000000 --- a/structure/client/components/commands/administrator/Revoke.js +++ /dev/null @@ -1,37 +0,0 @@ -const { Command, Argument } = require('../../../../interfaces/'); - -class RevokeCommand extends Command { - - constructor(client) { - - super(client, { - name: 'revoke', - module: 'administrator', - usage: " ", - 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; \ No newline at end of file diff --git a/structure/client/components/commands/developer/Component.js b/structure/client/components/commands/developer/Component.js index 55327cc..4a4f5ca 100644 --- a/structure/client/components/commands/developer/Component.js +++ b/structure/client/components/commands/developer/Component.js @@ -16,14 +16,14 @@ class ComponentCommand extends Command { ], usage: '[component..]', arguments: [ - // new Argument(client, { + // { // name: 'reload', // type: 'STRING', // types: ['FLAG'], // aliases: [ 'r' ], // description: "Reloads the language library", // default: 'all' - // }) + // } ], }); @@ -38,7 +38,7 @@ class ComponentCommand extends Command { const method = params.shift().toLowerCase(); let response; - if (method === 'reload') + if (method === 'reload' || method === 'r') response = this._handleReload(params); else if (method === 'enable') response = this._handleDisableEnable(params.shift().toLowerCase(), true); diff --git a/structure/client/components/commands/developer/Evaluate.js b/structure/client/components/commands/developer/Evaluate.js index 300fad1..f0676b4 100644 --- a/structure/client/components/commands/developer/Evaluate.js +++ b/structure/client/components/commands/developer/Evaluate.js @@ -3,7 +3,7 @@ const { username } = require('os').userInfo(); let _storage = null; //eslint-disable-line no-unused-vars -const { Command, Argument } = require('../../../../interfaces/'); +const { Command } = require('../../../../interfaces/'); @@ -21,18 +21,18 @@ class Evaluate extends Command { usage: '', restricted: true, arguments: [ - new Argument(client, { + { name: 'log', type: 'BOOLEAN', types: ['FLAG'], description: "Logs the output in the console." - }), - new Argument(client, { + }, + { name: 'hide', type: 'BOOLEAN', types: ['FLAG'], description: "Hides the output from the channel." - }) + } ], showUsage: true }); diff --git a/structure/client/components/commands/information/Commands.js b/structure/client/components/commands/information/Commands.js index faab6a5..76a1a3b 100644 --- a/structure/client/components/commands/information/Commands.js +++ b/structure/client/components/commands/information/Commands.js @@ -14,12 +14,12 @@ class CommandsCommand extends Command { ], usage: '[module]', arguments: [ - // new Argument(client, { + // { // name: 'user', // type: 'BOOLEAN', // types: ['VERBAL', 'FLAG'], // default: true - // }), + // }, ] }); diff --git a/structure/client/components/commands/utility/Arguments.js b/structure/client/components/commands/utility/Arguments.js index 8c725f3..559359a 100644 --- a/structure/client/components/commands/utility/Arguments.js +++ b/structure/client/components/commands/utility/Arguments.js @@ -1,6 +1,6 @@ const { stripIndents } = require('common-tags'); -const { Command, Argument } = require('../../../../interfaces/'); +const { Command } = require('../../../../interfaces/'); class PingCommand extends Command { @@ -11,26 +11,34 @@ class PingCommand extends Command { module: 'utility', aliases: ['args', 'arg', 'argument'], arguments: [ - new Argument(client, { + { name: 'apple', type: 'BOOLEAN', types: ['VERBAL', 'FLAG'], default: true - }), - new Argument(client, { + }, + { name: 'banana', aliases: ['bans', 'bananas'], type: 'INTEGER', types: ['FLAG', 'VERBAL'], default: 0 - }), - new Argument(client, { + }, + { name: 'carrot', aliases: ['cars', 'carrots'], - type: 'STRING', + type: 'CHANNEL', required: true, - types: ['FLAG', 'VERBAL'] - }) + types: ['FLAG', 'VERBAL'], + infinite: true + }, + { + name: 'format', + type: 'STRING', + types: ['VERBAL', 'FLAG'], + options: [ 'webp', 'png', 'jpeg', 'jpg', 'gif' ], + default: 'webp' + } ], restricted: true, archivable: false diff --git a/structure/client/components/commands/utility/Avatar.js b/structure/client/components/commands/utility/Avatar.js new file mode 100644 index 0000000..9477e01 --- /dev/null +++ b/structure/client/components/commands/utility/Avatar.js @@ -0,0 +1,63 @@ +const { Command } = require('../../../../interfaces/'); + +class AvatarCommand extends Command { + + constructor(client) { + + super(client, { + name: 'avatar', + module: 'utility', + aliases: [ + 'pfp' + ], + arguments: [ + { + name: 'size', + type: 'INTEGER', + types: ['VERBAL', 'FLAG'], + options: [ 16, 32, 64, 128, 256, 512, 1024, 2048 ], + default: 512, + required: true + }, + { + name: 'format', + type: 'STRING', + types: ['VERBAL', 'FLAG'], + options: [ 'webp', 'png', 'jpeg', 'jpg', 'gif' ], + default: 'webp', + required: true + } + ] + }); + + this.client = client; + + + } + + async execute(message, { params, args }) { + + let user = params.length ? await this.client.resolver.resolveUser(params[0]) : message.author; + if (!user) user = message.author; + let avatar = null; + try { + avatar = user.displayAvatarURL({ format: args.format?.value || 'webp', size: args.size?.value || 512, dynamic: true }); + } catch(error) { + message.respond(message.format('C_AVATAR_FORMATERROR'), { emoji: 'failure' }); + return undefined; + } + + return await message.embed({ + author: { + name: user.tag + }, + image: { + url: avatar + } + }); + + } + +} + +module.exports = AvatarCommand; \ No newline at end of file diff --git a/structure/client/components/commands/utility/Settings.js b/structure/client/components/commands/utility/Settings.js index 8f63ee0..b6dd932 100644 --- a/structure/client/components/commands/utility/Settings.js +++ b/structure/client/components/commands/utility/Settings.js @@ -1,4 +1,4 @@ -const { Command, Argument } = require('../../../../interfaces/'); +const { Command } = require('../../../../interfaces/'); const { stripIndents } = require('common-tags'); @@ -15,18 +15,18 @@ class SettingCommand extends Command { 'set' ], arguments: [ - new Argument(client, { + { name: 'user', type: 'BOOLEAN', types: ['VERBAL', 'FLAG'], default: true - }), - new Argument(client, { + }, + { name: 'all', type: 'BOOLEAN', types: ['VERBAL', 'FLAG'], default: true - }) + } ], showUsage: true }); diff --git a/structure/client/components/commands/utility/User.js b/structure/client/components/commands/utility/User.js index b6ca2bd..b1e354d 100644 --- a/structure/client/components/commands/utility/User.js +++ b/structure/client/components/commands/utility/User.js @@ -1,4 +1,4 @@ -const { Command, Argument } = require('../../../../interfaces/'); +const { Command } = require('../../../../interfaces/'); const similarity = require('similarity'); class UserCommand extends Command { @@ -11,13 +11,12 @@ class UserCommand extends Command { description: "Display information about user.", guildOnly: true, arguments: [ - new Argument(client, { + { name: 'search', type: 'STRING', types: ['FLAG', 'VERBAL'], requiredValue: true - }), - + } ] }); diff --git a/structure/client/components/observers/CommandHandler.js b/structure/client/components/observers/CommandHandler.js index 9f47065..c83b76d 100644 --- a/structure/client/components/observers/CommandHandler.js +++ b/structure/client/components/observers/CommandHandler.js @@ -1,7 +1,7 @@ const { stripIndents } = require('common-tags'); const escapeRegex = require('escape-string-regexp'); -const { Observer } = require('../../../interfaces/'); +const { Argument, Observer } = require('../../../interfaces/'); class CommandHandler extends Observer { @@ -32,11 +32,8 @@ class CommandHandler extends Observer { || message.webhookID || message.author.bot || (message.guild && (!message.guild.available || message.guild.banned))) return undefined; - - await message.author.settings(); if (message.guild) { - await message.guild.settings(); if (!message.member) await message.guild.members.fetch(message.author.id); } @@ -80,6 +77,8 @@ class CommandHandler extends Observer { const command = this.client.resolver.components(commandName, 'command', true)[0]; if(!command) return null; + + message._caller = commandName; //Used for hidden commands as aliases. //Eventually search for custom commands here. @@ -156,7 +155,7 @@ class CommandHandler extends Observer { const { shortFlags, longFlags, keys } = await this._createFlags(command.arguments); const regex = new RegExp(`([0-9]*)(${Object.keys(longFlags).map(k=>escapeRegex(k)).join('|')})([0-9]*)`, 'i'); - + let parsedArguments = []; let params = []; @@ -207,7 +206,7 @@ class CommandHandler extends Observer { currentArgument = longFlags[match[2]]; if(params.length > 0 && ['INTEGER', 'FLOAT'].includes(currentArgument.type)) { //15 pts const lastItem = params[params.length-1]; - const beforeError = await this._handleTypeParsing(currentArgument, lastItem); + const beforeError = await this._handleTypeParsing(currentArgument, lastItem, message.guild); if(beforeError) { continue; } else { @@ -219,7 +218,7 @@ class CommandHandler extends Observer { } } const value = match[1] || match[3]; - const error = await this._handleTypeParsing(currentArgument, value); + const error = await this._handleTypeParsing(currentArgument, value, message.guild); if(value) { if(error) { if(currentArgument.required) { @@ -240,7 +239,7 @@ class CommandHandler extends Observer { } } else { if(currentArgument) { - const error = await this._handleTypeParsing(currentArgument, word); + const error = await this._handleTypeParsing(currentArgument, word, message.guild); if(error) { if(currentArgument.default !== null) { params.push(word); @@ -276,7 +275,7 @@ class CommandHandler extends Observer { } else { const lastArgument = parsedArguments[parsedArguments.length-1]; if(lastArgument && lastArgument.type === 'BOOLEAN' && lastArgument.value === lastArgument.default) { - const error = await this._handleTypeParsing(lastArgument, word); + const error = await this._handleTypeParsing(lastArgument, word, message.guild); if(!error) continue; } params.push(word); @@ -286,6 +285,8 @@ class CommandHandler extends Observer { } } + if(currentArgument) parsedArguments.push(currentArgument); + const blah = parsedArguments.filter(a=>a.requiredArgument && !a.value); const missingArgument = blah[0]; if(missingArgument) return this._handleError({ type: 'argument', info: { argument: missingArgument, missing: true }, message }); @@ -299,11 +300,11 @@ class CommandHandler extends Observer { } - async _handleTypeParsing(argument, string) { + async _handleTypeParsing(argument, string, guild) { - const parse = async (argument, string) => { + const parse = async (argument, string, guild) => { - const { error, value } = await this.constructor.parseType(argument.type, string); //Cannot access static functions through "this". + const { error, value } = await this.constructor.parseType(argument.type, string, this.client.resolver, guild); //Cannot access static functions through "this". if(error) return { error: true }; if(['INTEGER', 'FLOAT'].includes(argument.type)) { @@ -316,13 +317,26 @@ class CommandHandler extends Observer { } } + if(argument.options.length > 0) { + let found = null; + for(const option of argument.options) { + if(option !== value) { + continue; + } else { + found = option; + } + } + if(!found) return { error: true }; + else return { error: false, value: found }; + } + return { error: false, value }; }; - const { error, value } = await parse(argument, string); + const { error, value } = await parse(argument, string, guild); - if(!error) { + if(!error && value !== undefined) { argument.infinite ? argument.value.push(value) : argument.value = value; @@ -338,7 +352,8 @@ class CommandHandler extends Observer { let longFlags = {}; let keys = []; - for(const arg of args) { + for(let arg of args) { + arg = new Argument(this.client, arg) let letters = []; let names = [ arg.name, ...arg.aliases ]; @@ -436,7 +451,7 @@ class CommandHandler extends Observer { } - static async parseType(type, str) { //this is in the class for a reason, will soon reference to a user resolver etc. + static async parseType(type, str, resolver, guild) { //this is in the class for a reason, will soon reference to a user resolver etc. //INTEGER AND FLOAT ARE SAME FUNCTION @@ -471,13 +486,15 @@ class CommandHandler extends Observer { MEMBER: (str) => { //eslint-disable-line no-unused-vars }, - CHANNEL: (str) => { //eslint-disable-line no-unused-vars - + CHANNEL: async (str, resolver, guild) => { //eslint-disable-line no-unused-vars + const channels = await resolver.resolveChannels(str, guild, true); + if(channels.length === 0) return { error: true }; + else return { error: false, value: channels[0] } } }; - return await types[type](str); + return await types[type](str, resolver, guild); } diff --git a/structure/extensions/Guild.js b/structure/extensions/Guild.js index f774a14..9292bfa 100644 --- a/structure/extensions/Guild.js +++ b/structure/extensions/Guild.js @@ -16,23 +16,17 @@ const Guild = Structures.extend('Guild', (Guild) => { //Fetch and cache 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 instanceof Promise) - this._settings = await this._settings || null; - if (!this._settings) - this._settings = this.client.defaultConfig; + 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) this._settings = { guildId: this.id, ...this.client.defaultConfig } ; return this._settings; } //Fetch and cache perms - 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 = {}; + 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 = { guildId: this.id }; return this._permissions; } @@ -58,7 +52,7 @@ const Guild = Structures.extend('Guild', (Guild) => { } async _dbUpdateOne(data, collection) { //Update property (upsert true) - updateOne - var index = this.dbIndex(collection); + var index = this.dbIndex(collection); //eslint-disable-line no-unused-vars try { await this.client.transactionHandler.send({ provider: 'mongodb', @@ -108,7 +102,7 @@ const Guild = Structures.extend('Guild', (Guild) => { } } - dbIndex(collection) { + _dbIndex(collection) { return { 'guilds': this._settings, 'permissions': this._permissions diff --git a/structure/extensions/Message.js b/structure/extensions/Message.js index fcb9cc1..e05ffa9 100644 --- a/structure/extensions/Message.js +++ b/structure/extensions/Message.js @@ -24,7 +24,7 @@ const Message = Structures.extend('Message', (Message) => { format(index, parameters = { }, code = false) { - let language = this.author._settings.locale || 'en_us'; + let language = 'en_us'; if(this.guild && this.guild._settings.locale) language = this.guild._settings.locale; parameters.prefix = this.guild?.prefix || this.client._options.bot.prefix; @@ -144,7 +144,7 @@ const Message = Structures.extend('Message', (Message) => { 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`)}`) + value: component.arguments.map(a=>`${a.name}: ${this.format(`A_${a.name.toUpperCase()}_${component.name.toUpperCase()}_DESCRIPTION`)}`) }); } @@ -154,7 +154,7 @@ const Message = Structures.extend('Message', (Message) => { 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)*' : ''}`, + ${this.format(component.description)}${component.guildOnly ? ' *(guild-only)*' : ''}`, fields }; diff --git a/structure/interfaces/Argument.js b/structure/interfaces/Argument.js index d2ec410..6d9e25d 100644 --- a/structure/interfaces/Argument.js +++ b/structure/interfaces/Argument.js @@ -24,6 +24,8 @@ class Argument { this.infinite = Boolean(options.infinite); //Accepts infinite amount of arguments e.g. -u @nolan @navy. If false, will only detect one user. // Will turn value into an array instead of a string!!! + this.options = options.options || []; + this.min = typeof options.min === 'number' ? options.min : null; //Min/max will only be used for INTEGER/FLOAT types. this.max = typeof options.max === 'number' ? options.max : null; @@ -31,10 +33,8 @@ class Argument { this.value = this.infinite ? [] : null; //The value provided to the flag; assigned in the command handler. - } - } module.exports = Argument; diff --git a/structure/interfaces/Command.js b/structure/interfaces/Command.js index 27ed734..32b9e3b 100644 --- a/structure/interfaces/Command.js +++ b/structure/interfaces/Command.js @@ -18,7 +18,7 @@ class Command extends Component { this.module = opts.module; this.aliases = opts.aliases || []; - this.description = `C_${opts.name}_DESCRIPTION`; + this.description = `C_${opts.name.toUpperCase()}_DESCRIPTION`; this.usage = opts.usage || ''; this.examples = opts.examples || [];