large permission changes and ping command change

This commit is contained in:
nolan 2020-05-17 09:07:39 -04:00
parent a71cb26522
commit 94bdb19853
13 changed files with 415 additions and 88 deletions

View File

@ -1,2 +1,15 @@
//Grant Command
[A_CHANNEL_GRANT_DESCRIPTION]
Specify channels to grant specific permissions to.
//Revoke Command
[A_CHANNEL_REVOKE_DESCRIPTION]
Specify channels to revoke permissions from.
//Permissions Command
[A_USER_PERMISSIONS_DESCRIPTION]
Enable viewing all user's permissions.
[A_JSON_PERMISSIONS_DESCRIPTION]
Upload a raw JSON file of all of the permissions in the guild.

View File

@ -1,7 +1,8 @@
//Grant Command
[C_GRANT_DESCRIPTION]
Grant roles or users permissions for commands or modules.
Grant roles or users permissions to use commands or modules.
To view all grantable permissions, use the command `{prefix}perms list`.
[C_GRANT_RESOLVEERROR]
Unable to find a role or member, view `{prefix}cmd grant` for more help.
@ -15,16 +16,14 @@ There was an issue pushing the permissions to the database. Contact a bot develo
[C_GRANT_SUCCESS]
Successfully granted **{resolveable}** the following permissions: {permissions}
[C_GRANT_SUCCESSALT]S
[C_GRANT_SUCCESSALT]
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.
Some permissions failed to grant, likely because they already have 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.
Failed to grant the permissions provided, view `{prefix}cmd grant` for more help.
//Revoke Command
@ -41,7 +40,52 @@ 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.
[C_REVOKE_SUCCESS]
Successfully revoked **{resolveable}** the following permissions: {removed}
[C_REVOKE_SUCCESSALT]
in channel(s) {channels}.
[C_REVOKE_SUCCESSFAILED]
Some permissions failed to revoke, likely because they did not have the permissions in the first place.
[C_REVOKE_FAILED]
Failed to revoke the permissions provided, try double-checking the permissions with `{prefix}perms {resolveable}`.
//Permissions Command
[C_PERMISSIONS_DESCRIPTION]
View permissions granted to roles or users.
[C_PERMISSIONS_LIST]
You can **grant**/**revoke** the following permissions: {permissions}.
[C_PERMISSIONS_SHOWTITLE]
switch({user}) {
case true:
"User Permissions"
break;
case false:
"Role Permissions"
break;
}
[C_PERMISSIONS_SHOWDESCRIPTION]
An overview of all {resolve}'s permissions in the guild. If you would like to see an in-depth view of the role or user's permissions, use `{prefix}perms [user|role]`.
[C_PERMISSIONS_MAXFIELDS]
:warning: **You have met the max amount of fields and you will need to use `{prefix}perms --json` to view them.** :warning:
[C_PERMISSIONS_NOTFOUND]
Unable to find any roles or users with those arguments.
[C_PERMISSIONS_JSON]
Attached the JSON-formatted permission file in the file below.
You may want to format this file for your viewing pleasure.
[C_PERMISSIONS_GLOBAL]
**Global Permissions:** {permissions}
Global permissions can be used in all channels, but you can add specific channels if needed.
[C_PERMISSIONS_GLOBALALT]
Channel-specific permissions are listed below.

View File

@ -1,8 +1,5 @@
//Ping Command
[C_PING_RESPONSE]
Pong!
[C_PING_DESCRIPTION]
Shows the millisecond delay between the bot and the discord server.
@ -73,5 +70,18 @@ Found {matches} matches, displaying {count}
To search server members with similar names use `{prefix}user search <arguments..>`
//Avatar Command
[C_AVATAR_DESCRIPTION]
Fetches avatars for various users in different formats and sizes.
[C_AVATAR_FORMATERROR]
Unable to find an avatar with those arguments, try a different size or format.
//Lookup Command
[C_LOOKUP_DESCRIPTION]
Looks up a discord invite code and returns the information of the invite.
[C_LOOKUP_FAILEDMATCH]
Couldn't find a discord invite code, try again.
[C_LOOKUP_NONEXISTANT]
Unable to find any data about that invite code.

View File

@ -1,19 +0,0 @@
[M_UTILITY_NAME]
Utility
[M_MODERATION_NAME]
Moderation
[M_DEVELOPER_NAME]
Developer
[M_ADMINISTRATOR_NAME]
Administrator
[M_INFORMATION_NAME]
Information
[M_MUSIC_NAME]
Music
[

View File

@ -14,9 +14,10 @@ class GrantCommand extends Command {
"\"Server Moderators\" module:moderation",
"@nolan#2887 command:kick"
],
memberPermissions: ['ADMINISTRATOR', 'MANAGE_SERVER'],
// memberPermissions: ['ADMINISTRATOR', 'MANAGE_GUILD'],
showUsage: true,
guildOnly: true,
grantable: true,
arguments: [
{
name: 'channel',
@ -34,8 +35,6 @@ class GrantCommand extends Command {
async execute(message, { params, args }) {
// console.log(args.channel);
const _permissions = await message.guild.permissions();
const [ parse, ...perms ] = params;
@ -54,18 +53,16 @@ class GrantCommand extends Command {
let parsed = [];
if(perms.join(' ') === 'all') {
parsed = this.client.registry.components.filter(c=>c.grantable && c.type === 'command').map(c=>c.resolveable);
parsed = this.client.registry.components.filter(c=>c.type === 'command').map(c=>c.resolveable); //filter for grantable
} else {
for(const perm of perms) {
const search = permissions.filter(filterInexact(perm)).first();
if(!search) failed.push(perm);
if(!search) continue;
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);
}
}
@ -79,32 +76,31 @@ class GrantCommand extends Command {
}
let existing = _permissions[resolveable.id];
let pushed = [];
let failed = [];
let granted = [];
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);
continue;
} else {
existingChannel.push(perm);
pushed.push(perm);
granted.push(perm);
}
}
} else {
existing.channels[channel.id] = parsed;
pushed.concat(parsed)
granted = [ ...granted, ...parsed ];
}
}
} else {
for(const perm of parsed) {
if(existing.global.includes(perm)) {
failed.push(perm);
continue;
} else {
existing.global.push(perm);
pushed.push(perm);
granted.push(perm);
}
}
}
@ -128,14 +124,13 @@ class GrantCommand extends Command {
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' });
if(granted.length > 0) {
await message.respond(stripIndents`${message.format('C_GRANT_SUCCESS', { permissions: granted.map(g=>`\`${g}\``).join(', '), resolveable: resolveable.user ? resolveable.user.tag : resolveable.name })}${args.channel ? ` ${message.format('C_GRANT_SUCCESSALT', { channels: args.channel.value.map(c=>`\`#${c.name}\``).join(', ')})}` : '.'}
${granted.length < parsed.length ? message.format('C_GRANT_SUCCESSFAILED'): ''}`, { 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' })
await message.respond(message.format('C_GRANT_FAILED', { resolveable: resolveable.user ? resolveable.user.tag : resolveable.name }), { emoji: 'failure' });
}
}
async _parseResolveable(message, resolveable) {

View File

@ -1,40 +1,211 @@
const { Role, MessageAttachment } = require('discord.js');
const { Command } = require('../../../../interfaces/');
const { stripIndents } = require('common-tags');
class GrantCommand extends Command {
class PermissionsCommand extends Command {
constructor(client) {
super(client, {
name: 'permissions',
module: 'administration',
usage: "<role|user>",
usage: "<list|role-name|user-name>",
aliases: [
'perms',
'permission',
'perm'
],
examples: [
"list",
"Server Moderators",
"@nolan#2887"
],
memberPermissions: ['ADMINISTRATOR', 'MANAGE_SERVER'],
arguments: [
{
name: 'user',
aliases: ['users'],
type: 'BOOLEAN',
types: ['VERBAL', 'FLAG'],
default: true
},
{
name: 'json',
aliases: ['raw'],
type: 'BOOLEAN',
types: ['VERBAL', 'FLAG'],
default: true
}
],
// memberPermissions: ['ADMINISTRATOR', 'MANAGE_GUILD'],
guildOnly: true
});
}
async execute(message) {
async execute(message, { params, args }) {
await message.guild.permissions();
const permissions = await message.guild.permissions();
if(args.json) {
await this._displayRaw(message, permissions);
return undefined;
}
message.respond(`\`\`\`js
${JSON.stringify(message.guild._permissions)}\`\`\``);
if(params.length === 0) {
await this._showPermissions(message, Boolean(args.user));
return undefined;
}
if(params[0] === 'list') {
await this._listAvailablePermissions(message);
return undefined;
} else {
const parameters = params.join(' ');
const resolveable = await this._parseResolveable(message, parameters);
const permission = permissions[resolveable?.id || parameters];
if(!permission) {
await message.respond(message.format('C_PERMISSIONS_NOTFOUND'), { emoji: 'failure' });
return undefined;
}
const embed = {
author: {
name: `${resolveable?.user?.tag || resolveable?.tag || resolveable?.name || parameters} Permissions`,
// icon_url: resolveable?.user?.displayAvatarURL() || resolveable?.displayAvatarURL() || message.guild.iconURL()
},
description: `${message.format('C_PERMISSIONS_GLOBAL', { permissions: permissions.global.length > 0 ? this._displayNames(permission.global).map(p=>`\`${p}\``).join(', ') : "`N/A`" })} ${Object.values(permission.channels).length > 0 ? message.format('C_PERMISSIONS_GLOBALALT') : ''}`,
fields: []
};
let update = false;
for(const [channelId, perms] of Object.entries(permission.channels)) {
const channel = this.client.resolver.resolveChannels(channelId, message.guild, true)[0];
if(!channel) {
delete permission.channels[channelId];
update = true;
continue;
} else {
if(embed.fields.length === 25) {
embed.description += `\n${message.format('C_PERMISSIONS_MAXFIELDS')}`;
break;
}
embed.fields.push({
name: `#${channel.name}`,
value: this._displayNames(perms).map(p=>`\`${p}\``).join(', ')
});
}
}
if(update) {
delete permissions._id
try {
await this.client.transactionHandler.send({
provider: 'mongodb',
request: {
type: 'updateOne',
collection: 'permissions',
query: {
guildId: message.guild.id
},
data: permissions
}
});
} catch(error) {
this.client.logger.error(`Error removing channel permissions to ${message.guild.id}:\n${error.stack || error}`);
}
}
return await message.embed(embed);
}
}
async _showPermissions(message, user = false) {
const embed = {
author: {
name: message.format('C_PERMISSIONS_SHOWTITLE', { user }, true),
icon_url: message.guild.iconURL()
},
description: message.format('C_PERMISSIONS_SHOWDESCRIPTION', { resolve: user ? 'user' : 'role' }),
fields: []
};
const permissions = message.guild._permissions;
for(const [id, value] of Object.entries(permissions)) {
if(id === '_id' || id === 'guildId') continue;
const item = await this.client.resolver[user ? 'resolveMemberAndUser' : 'resolveRoles'](id, message.guild); //dont kill me
if(item instanceof Role && user) continue;
else if(!user && !(item instanceof Role)) continue;
if(embed.fields.length === 25) {
embed.description += `\n${message.format('C_PERMISSIONS_MAXFIELDS')}`;
break;
}
const name = item?.user?.tag || item?.tag || item?.name || id; //please dont kill me again
const channels = Object.values(value.channels).length;
embed.fields.push({
name,
value: stripIndents`${this._displayNames(value.global).map(n=>`\`${n}\``).join('\n')}
${channels > 0 ? `\`..${channels} channel${channels === 1 ? '' : 's'}\`` : ''}`
});
}
return await message.embed(embed);
}
async _listAvailablePermissions(message) {
const components = this.client.registry.components.filter(c=>(c.type === 'command' && c.grantable) || (c.type === 'module' && c.components.some(c=>c.type === 'command' && c.grantable)))
.sort((a, b) => a - b);
return await message.respond(message.format('C_PERMISSIONS_LIST', { permissions: components.map(c => `\`${c.resolveable}\``).join(', ') }), { emoji: 'success' });
}
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) {
parsed = await this.client.resolver.resolveUsers(resolveable);
if(!parsed) return null;
}
}
return parsed[0];
}
async _displayRaw(message, permissions) {
const string = JSON.stringify(permissions);
const attachment = new MessageAttachment(Buffer.from(string), "permissions.json");
return await message.respond(message.format('C_PERMISSIONS_JSON'), { emoji: 'success', attachments: [ attachment ] })
}
_displayNames(permissions) {
const modules = this.client.registry.components.filter(c=>c.type === 'module');
let names = [];
let temp = [];
for(const module of modules.values()) {
for(const component of module.components.filter(c=>c.type === 'command').values()) {
if(permissions.includes(component.resolveable)) {
temp.push(component.resolveable);
}
}
temp.length === module.components.filter(c=>c.type === 'command').size
? names.push(module.resolveable)
: names = names.concat(temp);
temp = [];
}
return names;
}
}
module.exports = GrantCommand;
module.exports = PermissionsCommand;

View File

@ -1,3 +1,6 @@
const { User, GuildMember } = require('discord.js');
const { stripIndents } = require('common-tags')
const { Command } = require('../../../../interfaces/');
class RevokeCommand extends Command {
@ -13,7 +16,7 @@ class RevokeCommand extends Command {
"@nolan#2887 command:kick",
"132620781791346688 moderation"
],
memberPermissions: ['ADMINISTRATOR'],
// memberPermissions: ['ADMINISTRATOR', 'MANAGE_GUILD'],
showUsage: true,
guildOnly: true,
arguments: [
@ -107,30 +110,10 @@ class RevokeCommand extends Command {
}
}
//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..
//check for deletion, saves DB space.
//NOTE: DO NOT REMOVE THE _PERMISSIONS VARIABLE.
if(permission.global.length === 0 && Object.keys(permission.channels).length === 0) {
try {
await this.client.transactionHandler.send({
@ -165,7 +148,18 @@ class RevokeCommand extends Command {
}
}
const name = resolveable instanceof GuildMember
? resolveable?.user?.tag
: resolveable instanceof User
? resolveable?.tag
: resolveable?.name;
if(removed.length > 0) {
await message.respond(stripIndents`${message.format('C_REVOKE_SUCCESS', { removed: removed.map(r=>`\`${r}\``).join(', '), resolveable: name || parsed })}${args.channel ? ` ${message.format('C_REVOKE_SUCCESSALT', { channels: args.channel.value.map(c=>`\`#${c.name}\``).join(', ')})}` : '.'}
${removed.length < parsed.length ? message.format('C_REVOKE_SUCCESSFAILED'): ''}`, { emoji: 'success' });
} else {
await message.respond(message.format('C_REVOKE_FAILED', { resolveable: name || parsed }), { emoji: 'failure' });
}
}

View File

@ -41,7 +41,7 @@ class AvatarCommand extends Command {
if (!user) user = message.author;
let avatar = null;
try {
avatar = user.displayAvatarURL({ format: args.format?.value || 'webp', size: args.size?.value || 512, dynamic: true });
avatar = user.displayAvatarURL({ format: args.format?.value || 'webp', size: args.size?.value || 128, dynamic: true });
} catch(error) {
message.respond(message.format('C_AVATAR_FORMATERROR'), { emoji: 'failure' });
return undefined;

View File

@ -0,0 +1,110 @@
const fetch = require('node-fetch');
const { Command } = require('../../../../interfaces/');
//Apparently hit ratelimits pretty damn quick and doesn't expire often.
class LookupCommand extends Command {
constructor(client) {
super(client, {
name: 'lookup',
module: 'utility',
usage: "<invite-code>",
showUsage: true,
parameterType: 'PLAIN',
examples: [
"SvJgtEj",
"discord.gg/SvJgtEj"
],
restricted: true, //For now
throttling: {
usages: 1,
duration: 30
}
});
this.client = client;
}
async execute(message, { params }) {
const regex = /discord(?:app\.com\/invite|\.gg(?:\/invite)?)\/([\w-]{2,255})/i;
const match = regex.exec(params);
let code = null;
if(match && match[1]) {
code = match[1];
} else {
const restrict = /([\w-]{2,255})/i.exec(params);
if(restrict && restrict[1]) {
code = restrict[1];
} else {
await message.respond(message.format('C_LOOKUP_FAILEDMATCH'));
return undefined;
}
}
let data = null;
try {
const request = await fetch(`https://discord.com/api/invite/${code}`);
data = await request.json();
if(data.code && data.message) {
await message.respond(message.format('C_LOOKUP_NONEXISTANT'));
return undefined;
}
} catch(error) {
await message.respond(message.format('C_LOOKUP_NONEXISTANT'));
return undefined;
}
let fields = [];
if(data.inviter) {
fields.push({
name: "Inviter",
value: `${data.inviter.username}#${data.inviter.discriminator} \`(${data.inviter.id})\``,
inline: true
});
}
if(data.channel) {
fields.push({
name: "Default Channel",
value: `#${data.channel.name} \`(${data.channel.id})\``,
inline: true
});
}
if(data.guild.features.length > 0) {
fields.push({
name: "Features",
value: data.guild.features.map(f=>`\`${f}\``).join(', '),
inline: true,
});
}
const embed = {
author: {
name: `${data.guild.name} (${data.guild.id})`,
icon_url: `https://cdn.discordapp.com/icons/${data.guild.id}/${data.guild.icon}.webp`
},
thumbnail: {
url: data.guild.splash ? `https://cdn.discordapp.com/splashes/${data.guild.id}/${data.guild.splash}.webp` : null
},
description: data.guild.description || null,
fields,
footer: {
text: `https://discord.gg/${data.code}`
}
};
return message.embed(embed);
}
}
module.exports = LookupCommand;

View File

@ -16,7 +16,9 @@ class PingCommand extends Command {
async execute(message) {
const ping = this.client.ws.ping.toFixed(0);
return message.respond(`${message.format('C_PING_RESPONSE')} \`${ping}ms\``, { emoji: 'success' });
const number = (ping/40).toFixed(0);
const repeat = number > 1 ? number : 1;
return message.respond(`P${'o'.repeat(repeat)}ng! \`${ping}ms\``, { emoji: 'success' });
}
}

View File

@ -296,6 +296,10 @@ class CommandHandler extends Observer {
const fff = {};
parsedArguments.map(a=>fff[a.name] = a);
if(command.parameterType === 'PLAIN') {
params = params.join(' ');
}
return { parsedArguments: fff, newArgs: params };
}

View File

@ -78,7 +78,7 @@ const Message = Structures.extend('Message', (Message) => {
}
async respond(str, opts = {}) {
async respond(str, opts = { attachments: [] }) {
if(typeof str === 'string') {
if(opts.emoji) {
@ -90,7 +90,7 @@ const Message = Structures.extend('Message', (Message) => {
}
//console.log(str)
this._pending = await this.channel.send(str);
this._pending = await this.channel.send(str, { files: opts.attachments });
return this._pending;
}

View File

@ -28,6 +28,9 @@ class Command extends Component {
this.archivable = opts.archivable === undefined ? false : Boolean(opts.archivable);
this.arguments = opts.arguments || [];
this.parameterType = opts.parameterType || 'SPLIT'; //SPLIT or PLAIN, PLAIN = string, SPLIT = split into array, includes quotes
this.grantable = Boolean(opts.grantable);
this.clientPermissions = opts.clientPermissions || [];
this.memberPermissions = opts.memberPermissions || [];