forked from Galactic/galactic-bot
Merge branch 'message-commands' into slash-commands
This commit is contained in:
commit
a88cb49c56
@ -5,7 +5,7 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"dev": "node --trace-warnings --unhandled-rejections=strict index.js",
|
||||
"dev": "nodemon --delay 10 --trace-warnings --unhandled-rejections=strict index.js",
|
||||
"debug": "node --trace-warnings --inspect index.js",
|
||||
"update": "git pull && cd api && yarn update",
|
||||
"test": "jest --detectOpenHandles",
|
||||
|
@ -416,6 +416,8 @@
|
||||
"seamen",
|
||||
"coming",
|
||||
"seaman",
|
||||
"seus",
|
||||
"seuss",
|
||||
"rock",
|
||||
"dock",
|
||||
"lock",
|
||||
|
@ -44,6 +44,9 @@ Global permissions
|
||||
[COMMAND_PERMISSIONS_SHOW_TITLE]
|
||||
Granted permissions
|
||||
|
||||
[COMMAND_PERMISSIONS_DESC]
|
||||
Showing permissions {forin} **{target}**
|
||||
|
||||
[COMMAND_PERMISSIONS_NO_PERMS]
|
||||
No permissions granted
|
||||
|
||||
|
@ -71,6 +71,9 @@ It seems like the mute role was deleted, use the command `-set mute` for more in
|
||||
[COMMAND_MUTE_MISSING_MODERATE_PERM]
|
||||
Missing permissions to timeout users (Moderate Member)
|
||||
|
||||
[COMMAND_MUTE_HIERARCHY_ERROR]
|
||||
the bot cannot timeout a user above its highest role
|
||||
|
||||
[COMMAND_MUTE_MISSING_MANAGEROLE_PERM]
|
||||
Missing permissions to manage roles.
|
||||
|
||||
@ -303,6 +306,10 @@ the provided role(s) are higher than the bot, I cannot add them
|
||||
the provided role(s) are not on the grantable list
|
||||
|
||||
//History Command
|
||||
[COMMAND_HISTORY_HELP]
|
||||
Display moderation history in the server.
|
||||
Narrow the search down by using the parameters below.
|
||||
|
||||
[COMMAND_HISTORY_HISTORY]
|
||||
Display moderation history for the server or for certain users.
|
||||
|
||||
@ -332,6 +339,9 @@ Failed to display cases in one embed, try a smaller page size.
|
||||
for {targets}
|
||||
//target{plural}:
|
||||
|
||||
[COMMAND_HISTORY_SUCCESSMODERATOR]
|
||||
by {moderator}
|
||||
|
||||
[COMMAND_HISTORY_NO_EXPORT_PERMS]
|
||||
Must be admin to export moderation history.
|
||||
|
||||
|
@ -1,3 +1,10 @@
|
||||
[COMMAND_PING_HELP]
|
||||
Check if the bot is online.
|
||||
|
||||
[COMMAND_AVATAR_HELP]
|
||||
Display a user's avatar.
|
||||
Use the member option to display their server avatar.
|
||||
|
||||
[COMMAND_AVATAR_FORMATERROR]
|
||||
Unable to find an avatar with those arguments, try a different size or format.
|
||||
|
||||
@ -37,6 +44,8 @@ You have no active reminders.
|
||||
The content in your reminder was filtered.
|
||||
|
||||
// Poll command
|
||||
[COMMAND_POLL_HELP]
|
||||
Have the bot send a poll message in a channel with an optional duration.
|
||||
|
||||
[COMMAND_POLL_QUESTIONS]
|
||||
Please respond with question {number}.
|
||||
|
@ -20,6 +20,9 @@ Required Permissions
|
||||
Command takes no arguments
|
||||
|
||||
// Generic setting field names
|
||||
[GENERAL_PREFIX]
|
||||
》 Prefix
|
||||
|
||||
[GENERAL_STATUS]
|
||||
》 Status
|
||||
|
||||
@ -131,3 +134,11 @@ switch({toggle}) {
|
||||
'off';
|
||||
break;
|
||||
}
|
||||
|
||||
[FOR_IN_TOGGLE]
|
||||
switch({toggle}) {
|
||||
case true:
|
||||
'for'; break;
|
||||
case false:
|
||||
'in'; break;
|
||||
}
|
@ -7,6 +7,9 @@ You can increase the amount of targets by upgrading to premium.
|
||||
[INFRACTION_ERROR]
|
||||
an error occured
|
||||
|
||||
[INFRACTION_PROTECTIONPOSITIONERROR_SAME]
|
||||
they have the same role position as you
|
||||
|
||||
[INFRACTION_PROTECTIONPOSITIONERROR]
|
||||
they have hierarchy over you
|
||||
|
||||
|
@ -5,15 +5,24 @@ This is an issue that should be reported to a bot developer.
|
||||
[O_COMMANDHANDLER_GUILDONLY]
|
||||
This command can only be run in servers.
|
||||
|
||||
[O_COMMANDHANDLER_GUILDONLY_OPT]
|
||||
The option `{option}` is only valid in servers.
|
||||
|
||||
[O_COMMANDHANDLER_TYPEINTEGER]
|
||||
The command option {option} requires an integer between `{min}` and `{max}`.
|
||||
|
||||
[O_COMMANDHANDLER_TYPEBOOLEAN]
|
||||
The command option {option} requires a boolean resolveable (e.g. anything that can be interpreted as true or false)
|
||||
|
||||
[O_COMMANDHANDLER_TYPECOMMANDS]
|
||||
The command option {option} requires command resolveables (e.g. `command:ping`, `command:history`)
|
||||
|
||||
[O_COMMANDHANDLER_TYPECOMMAND]
|
||||
The command option {option} requires a command resolveable (e.g. `command:ping`)
|
||||
|
||||
[O_COMMANDHANDLER_TYPEMODULE]
|
||||
The command option {option} requires a module resolveable (e.g. moderation)
|
||||
|
||||
[O_COMMANDHANDLER_TYPESTRING]
|
||||
The command option {option} requires a string.
|
||||
|
||||
@ -23,6 +32,9 @@ The command option {option} requires a date in the format `YYYY/MM/DD`
|
||||
[O_COMMANDHANDLER_TYPETIME]
|
||||
The command option {option} requires a timestring (e.g. 5 min, 2w).
|
||||
|
||||
[O_COMMANDHANDLER_TYPEUSER]
|
||||
The command option {option} requires a user.
|
||||
|
||||
[O_COMMANDHANDLER_TYPEUSERS]
|
||||
The command option {option} requires users.
|
||||
|
||||
@ -80,3 +92,14 @@ Command Error
|
||||
[O_COMMANDHANDLER_COMMAND_NORESPONSE]
|
||||
Command returned no response. This should not happen and is likely a bug.
|
||||
|
||||
[O_COMMANDHANDLER_UNRECOGNISED_FLAG]
|
||||
Unrecognised flag: `{flag}`
|
||||
See command help for valid flags.
|
||||
|
||||
[O_COMMANDHANDLER_UNRECOGNISED_OPTIONS]
|
||||
Unrecognised options: `{opts}`
|
||||
See command help for valid options.
|
||||
|
||||
[O_COMMANDHANDLER_INVALID_CHOICE]
|
||||
`{value}` is an invalid choice for **{option}**.
|
||||
Valid choices are `{choices}`.
|
@ -1,3 +1,6 @@
|
||||
[SETTING_TEXTCOMMANDS_HELP]
|
||||
Enable or disable text commands or change the prefix.
|
||||
|
||||
[SETTING_PERMISSIONS_HELP]
|
||||
Configure which set of permissions the bot works with.
|
||||
|
||||
|
@ -94,15 +94,17 @@ class ApiClientUtil {
|
||||
|
||||
const { guildId } = message;
|
||||
const evalFunc = (client, { guildId }) => {
|
||||
const guild = client.guilds.cache.get(guildId);
|
||||
if (!guild) return null;
|
||||
const wrapper = new client.wrapperClasses.GuildWrapper(client, guild);
|
||||
try {
|
||||
const wrapper = client.getGuildWrapper(guildId);
|
||||
return wrapper.toJSON();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
this.client.logger.debug(`guild-live request - shard: ${message.shard}, message id ${message.id}`);
|
||||
const result = await this.client.shardingManager.broadcastEval(evalFunc, { context: { guildId } });
|
||||
const guild = result.find((elem) => elem !== undefined);
|
||||
const guild = result.find((elem) => elem !== null);
|
||||
return guild;
|
||||
|
||||
}
|
||||
|
@ -86,6 +86,7 @@ class Logger {
|
||||
|
||||
webhook(text, type) {
|
||||
|
||||
if (!this._webhook) return;
|
||||
const message = text.replace(new RegExp(process.env.DISCORD_TOKEN, 'gu'), '<redacted>')
|
||||
.replace(new RegExp(username, 'gu'), '<redacted>');
|
||||
|
||||
|
@ -3,6 +3,7 @@ const { Routes } = require('discord-api-types/v9');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const hash = require('object-hash');
|
||||
const { inspect } = require('util');
|
||||
|
||||
class SlashCommandManager {
|
||||
|
||||
@ -43,7 +44,7 @@ class SlashCommandManager {
|
||||
this.client.logger.write('info', `Commands hash: ${cmdHash}, ${guilds.length} out of date`);
|
||||
if (!guilds.length) return;
|
||||
const promises = [];
|
||||
//console.log(JSON.stringify(commands));
|
||||
//fs.writeFileSync(path.join(process.cwd(), 'commands.json'), JSON.stringify(commands));
|
||||
for(const guild of guilds) {
|
||||
promises.push(this.rest.put(
|
||||
Routes.applicationGuildCommands(clientId, guild),
|
||||
@ -72,6 +73,10 @@ class SlashCommandManager {
|
||||
str += `${command.name}: `;
|
||||
const options = Object.keys(invalid[key].options);
|
||||
for (const optKey of options) {
|
||||
if (!command.options[optKey]) {
|
||||
this.client.logger.warn(`Missing properties for ${command.name}: ${optKey}\nOptions: ${inspect(command.options)}`);
|
||||
continue;
|
||||
}
|
||||
str += `${command.options[optKey].name}\t`;
|
||||
}
|
||||
str += `\n\n`;
|
||||
|
@ -91,8 +91,8 @@ class DiscordClient extends Client {
|
||||
// this.logger.error(`Uncaught exception:\n${err.stack || err}`);
|
||||
// });
|
||||
|
||||
process.on('unhandledRejection', (err, reason) => {
|
||||
this.logger.error(`Unhandled rejection:\n${err?.stack || err}\n${inspect(reason)}`);
|
||||
process.on('unhandledRejection', (err) => {
|
||||
this.logger.error(`Unhandled rejection:\n${err?.stack || err}`);
|
||||
});
|
||||
|
||||
process.on('message', this._handleMessage.bind(this));
|
||||
@ -309,6 +309,14 @@ class DiscordClient extends Client {
|
||||
|
||||
}
|
||||
|
||||
format(index, params = {}, opts = {}) {
|
||||
const {
|
||||
code = false,
|
||||
language = 'en_gb'
|
||||
} = opts;
|
||||
return this.localeLoader.format(language, index, params, code);
|
||||
}
|
||||
|
||||
getGuildWrapper(id) {
|
||||
|
||||
if (this.guildWrappers.has(id)) return this.guildWrappers.get(id);
|
||||
|
@ -37,7 +37,7 @@ class RateLimiter {
|
||||
|
||||
if (!channel || !(channel instanceof TextChannel)) reject(new Error('Missing channel'));
|
||||
if (!message || !(message instanceof Message)) reject(new Error('Missing message'));
|
||||
if (!channel.permissionsFor(channel.guild.members.me).has('ManageMessages')) reject(new Error('Missing permission ManageMessages'));
|
||||
if (!channel.permissionsFor(this.client.user).has('ManageMessages')) reject(new Error('Missing permission ManageMessages'));
|
||||
|
||||
if (!this.deleteQueue[channel.id]) this.deleteQueue[channel.id] = [];
|
||||
this.deleteQueue[channel.id].push({ message, resolve, reject });
|
||||
@ -106,7 +106,7 @@ class RateLimiter {
|
||||
|
||||
if (!channel || !(channel instanceof TextChannel)) reject(new Error('Missing channel.'));
|
||||
if (!message || !message.length) reject(new Error('Missing message.'));
|
||||
if (!channel.permissionsFor(channel.guild.members.me).has('SendMessages')) reject(new Error('Missing permission SendMessages'));
|
||||
if (!channel.permissionsFor(this.client.user).has('SendMessages')) reject(new Error('Missing permission SendMessages'));
|
||||
|
||||
//Initiate queue
|
||||
if (!this.sendQueue[channel.id]) this.sendQueue[channel.id] = [];
|
||||
@ -173,7 +173,7 @@ class RateLimiter {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!channel || !(channel instanceof TextChannel)) reject(new Error('Missing channel'));
|
||||
if (!channel.permissionsFor(channel.guild.members.me).has('SendMessages')) reject(new Error('Missing permission SendMessages'));
|
||||
if (!channel.permissionsFor(this.client.user).has('SendMessages')) reject(new Error('Missing permission SendMessages'));
|
||||
if (!message) reject(new Error('Missing message'));
|
||||
if (limit === null) limit = 15;
|
||||
|
||||
|
@ -346,7 +346,7 @@ class Resolver {
|
||||
|
||||
const match = resolveable.match(id);
|
||||
const [, ch] = match;
|
||||
const channel = await this.client.channels.fetch(ch).catch((e) => { }); //eslint-disable-line no-empty, no-empty-function, no-unused-vars
|
||||
const channel = await CM.fetch(ch).catch((e) => { }); //eslint-disable-line no-empty, no-empty-function, no-unused-vars
|
||||
if (channel && filter(channel)) resolved.push(channel);
|
||||
|
||||
} else if (name.test(resolveable)) {
|
||||
|
@ -72,7 +72,7 @@ class GuildWrapper {
|
||||
startedIn = await this.resolveChannel(startedIn);
|
||||
const pollChannel = await this.resolveChannel(channel);
|
||||
if (pollChannel) {
|
||||
const msg = await pollChannel.messages.fetch(message);
|
||||
const msg = await pollChannel.messages.fetch(message).catch(() => null);
|
||||
if (msg) {
|
||||
const { reactions } = msg;
|
||||
const reactionEmojis = questions.length ? PollReactions.Multi : PollReactions.Single;
|
||||
@ -360,7 +360,7 @@ class GuildWrapper {
|
||||
}
|
||||
|
||||
get prefix() {
|
||||
return this._settings.prefix || this.client.prefix;
|
||||
return this._settings.textcommands.prefix || this.client.prefix;
|
||||
}
|
||||
|
||||
get available() {
|
||||
|
@ -217,7 +217,7 @@ class InteractionWrapper {
|
||||
}
|
||||
|
||||
isContextMenu() {
|
||||
return this.interaction.isContextMenu();
|
||||
return this.interaction.isContextMenuCommand();
|
||||
}
|
||||
|
||||
isSelectMenu() {
|
||||
|
@ -118,7 +118,7 @@ class InvokerWrapper {
|
||||
disableMentions: opts.disableMentions
|
||||
};
|
||||
|
||||
if (opts.editReply) await this.editReply(data);
|
||||
if (opts.editReply || this.deferred) await this.editReply(data);
|
||||
else await this.reply(data) //this.channel.send(data)
|
||||
.then((msg) => {
|
||||
if (opts.delete) msg.delete();
|
||||
|
@ -90,6 +90,7 @@ class MessageWrapper {
|
||||
|
||||
async editReply(options) {
|
||||
if (!this._reply) throw new Error('Message not replied to');
|
||||
if (!options.allowedMentions) options.allowedMentions = { repliedUser: false }; // Disables the mention in the inline reply
|
||||
return this._reply.edit(options);
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ class AdministrationModule extends SettingsCommand {
|
||||
constructor(client) {
|
||||
super(client, {
|
||||
name: 'administration',
|
||||
aliases: ['admin'],
|
||||
description: 'Configure the administrative settings',
|
||||
module: 'administration'
|
||||
});
|
||||
|
@ -39,7 +39,8 @@ class ImportCommand extends SlashCommand {
|
||||
}, {
|
||||
name: 'overwrite',
|
||||
description: 'Whether any existing logs should be overwritten by the imports. By default new ones are bumped',
|
||||
type: 'BOOLEAN'
|
||||
type: 'BOOLEAN',
|
||||
flag: true, valueOptional: true, defaultValue: true
|
||||
}]
|
||||
}],
|
||||
clientPermissions: ['ManageWebhooks']
|
||||
|
@ -46,7 +46,7 @@ class ModstatsCommand extends SlashCommand {
|
||||
|
||||
const data = await this.client.mongodb.infractions.find(query, { projection: { executor: 1, type: 1 } });
|
||||
for (const log of data) {
|
||||
if (log.executor === guild.members.me.id) continue;
|
||||
if (log.executor === this.client.user.id) continue;
|
||||
if (!result[log.executor]) {
|
||||
const user = await this.client.resolveUser(log.executor);
|
||||
result[log.executor] = { name: user.tag };
|
||||
|
@ -27,7 +27,8 @@ class PermissionsCommand extends SlashCommand {
|
||||
name: 'channel',
|
||||
description: 'The channel(s) in which this permission is granted to user or role',
|
||||
type: 'TEXT_CHANNELS',
|
||||
dependsOn: ['permission']
|
||||
dependsOn: ['permission'],
|
||||
flag: true
|
||||
},
|
||||
{
|
||||
name: 'role',
|
||||
@ -64,6 +65,7 @@ class PermissionsCommand extends SlashCommand {
|
||||
name: 'channel',
|
||||
description: 'The channel(s) to reset',
|
||||
type: 'TEXT_CHANNELS',
|
||||
flag: true
|
||||
},
|
||||
{
|
||||
name: 'role',
|
||||
@ -233,6 +235,7 @@ class PermissionsCommand extends SlashCommand {
|
||||
|
||||
const fields = [];
|
||||
let update = false;
|
||||
const target = member?.value || role?.value || channel?.value;
|
||||
|
||||
if (member || role) {
|
||||
|
||||
@ -340,6 +343,7 @@ class PermissionsCommand extends SlashCommand {
|
||||
|
||||
if(!fields.length) return { index: 'COMMAND_PERMISSIONS_NO_PERMS' };
|
||||
const embed = {
|
||||
description: target ? interaction.format('COMMAND_PERMISSIONS_DESC', { target: target.tag || target.name || 'any', forin: interaction.format('FOR_IN_TOGGLE', { toggle: target.type === undefined }, { code: true }) }) : '',
|
||||
title: interaction.format('COMMAND_PERMISSIONS_SHOW_TITLE'),
|
||||
fields: [ ]
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { SlashCommand, CommandOption } = require("../../../interfaces");
|
||||
const { SlashCommand } = require("../../../interfaces");
|
||||
const { Util } = require('../../../../utilities');
|
||||
|
||||
class SettingsCommand extends SlashCommand {
|
||||
@ -10,11 +10,11 @@ class SettingsCommand extends SlashCommand {
|
||||
description: 'View settings',
|
||||
options: [
|
||||
// Probably add reset options here too
|
||||
new CommandOption({
|
||||
{
|
||||
name: 'list',
|
||||
description: 'List available settings',
|
||||
type: 'SUB_COMMAND'
|
||||
})
|
||||
}
|
||||
],
|
||||
memberPermissions: ['ManageGuild']
|
||||
});
|
||||
|
@ -13,16 +13,20 @@ class EvalCommand extends Command {
|
||||
name: 'eval',
|
||||
aliases: ['e', 'evaluate'],
|
||||
restricted: true,
|
||||
module: 'developer'
|
||||
module: 'developer',
|
||||
options: [
|
||||
{ name: 'code' },
|
||||
{ name: 'async', flag: true, valueOptional: true, defaultValue: true }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
async execute(invoker, { parameters: params }) {
|
||||
async execute(invoker, { code, async }) {
|
||||
|
||||
const args = {}; // Temporary args until I figure out how I want to implement them
|
||||
|
||||
params = params.join(' ');
|
||||
if (args.async) params = `(async () => {${params}})()`;
|
||||
let params = code.value;
|
||||
if (async?.value) params = `(async () => {${params}})()`;
|
||||
const { guild, author, member, client } = invoker; //eslint-disable-line no-unused-vars
|
||||
|
||||
let response = null;
|
||||
|
@ -17,7 +17,8 @@ class StatsCommand extends SlashCommand {
|
||||
name: 'log',
|
||||
type: 'BOOLEAN',
|
||||
types: ['FLAG'],
|
||||
description: 'Logs the output in the console.'
|
||||
description: 'Logs the output in the console.',
|
||||
flag: true, valueOptional: true, defaultValue: true
|
||||
}
|
||||
],
|
||||
clientPermissions: ['SendMessages', 'EmbedLinks'],
|
||||
|
@ -15,7 +15,7 @@ class Commands extends SlashCommand {
|
||||
type: 'MODULE',
|
||||
description: ['List commands from a specific module']
|
||||
}],
|
||||
memberPermissions: ['ManageGuild'],
|
||||
memberPermissions: [],
|
||||
guildOnly: true
|
||||
});
|
||||
}
|
||||
|
@ -18,7 +18,8 @@ class BanCommand extends ModerationCommand {
|
||||
type: 'INTEGER',
|
||||
description: 'How many days worth of messages to prune',
|
||||
minimum: 1,
|
||||
maximum: 7
|
||||
maximum: 7,
|
||||
flag: true
|
||||
}, {
|
||||
name: 'users',
|
||||
type: 'USERS',
|
||||
|
@ -23,7 +23,8 @@ class CaseCommand extends SlashCommand {
|
||||
'Print out more detailed information about the case',
|
||||
'List changes to the case'
|
||||
],
|
||||
depeondsOn: ['id']
|
||||
depeondsOn: ['id'],
|
||||
flag: true, valueOptional: true, defaultValue: true
|
||||
}],
|
||||
guildOnly: true,
|
||||
showUsage: true,
|
||||
|
@ -24,14 +24,15 @@ class EditCommand extends SlashCommand {
|
||||
name: 'points',
|
||||
type: 'INTEGER',
|
||||
description: 'New point value for case',
|
||||
minimum: 0, maximum: 100
|
||||
minimum: 0, maximum: 100, flag: true
|
||||
}, {
|
||||
name: ['expiration', 'duration'],
|
||||
type: 'TIME',
|
||||
description: [
|
||||
'New expiration for points, starts from the time the infraction was issued',
|
||||
'Duration if the infraction is timed'
|
||||
]
|
||||
],
|
||||
flag: true
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
@ -24,7 +24,8 @@ class HistoryCommand extends SlashCommand {
|
||||
options: [{
|
||||
name: ['before', 'after'],
|
||||
type: 'DATE',
|
||||
description: 'Filter by a date, must be in YYYY/MM/DD or YYYY-MM-DD format'
|
||||
description: 'Filter by a date, must be in YYYY/MM/DD or YYYY-MM-DD format',
|
||||
flag: true
|
||||
}, {
|
||||
name: ['verbose', 'oldest', 'export', 'private'],
|
||||
description: [
|
||||
@ -33,26 +34,35 @@ class HistoryCommand extends SlashCommand {
|
||||
'Export the list of infractions',
|
||||
'DM the command response'
|
||||
],
|
||||
type: 'BOOLEAN'
|
||||
type: 'BOOLEAN',
|
||||
flag: true, valueOptional: true, defaultValue: true
|
||||
}, {
|
||||
name: 'type',
|
||||
description: 'Filter infractions by type',
|
||||
choices: Infractions.map((inf) => {
|
||||
return { name: inf.toLowerCase(), value: inf };
|
||||
})
|
||||
}),
|
||||
flag: true
|
||||
}, {
|
||||
name: ['pagesize', 'page'],
|
||||
description: ['Amount of infractions to list per page', 'Page to select'],
|
||||
type: 'INTEGER',
|
||||
minimum: 1
|
||||
minimum: 1,
|
||||
flag: true
|
||||
}, {
|
||||
name: ['user', 'moderator'], //
|
||||
description: ['User whose infractions to query, overrides channel if both are given', 'Query by moderator'],
|
||||
name: 'user',
|
||||
description: 'User whose infractions to query, overrides channel if both are given',
|
||||
type: 'USER'
|
||||
}, {
|
||||
name: 'moderator',
|
||||
description: 'Query by moderator',
|
||||
type: 'USER',
|
||||
flag: true
|
||||
}, {
|
||||
name: 'channel',
|
||||
description: 'Infractions done on channels, e.g. slowmode, lockdown',
|
||||
type: 'TEXT_CHANNEL'
|
||||
type: 'TEXT_CHANNEL',
|
||||
flag: true
|
||||
}]
|
||||
});
|
||||
}
|
||||
@ -95,6 +105,7 @@ class HistoryCommand extends SlashCommand {
|
||||
limit: pageSize
|
||||
});
|
||||
|
||||
const me = await guild.resolveMember(this.client.user);
|
||||
const embed = {
|
||||
author: {
|
||||
name: 'Infraction History',
|
||||
@ -104,7 +115,7 @@ class HistoryCommand extends SlashCommand {
|
||||
footer: {
|
||||
text: `• Page ${_page}/${maxPage} | ${resultsAmt} Results`
|
||||
},
|
||||
color: invoker.guild.members.me.roles.highest.color
|
||||
color: me.roles.highest.color
|
||||
};
|
||||
|
||||
if (invoker.guild._settings.modpoints.enabled) {
|
||||
@ -180,12 +191,18 @@ class HistoryCommand extends SlashCommand {
|
||||
|
||||
const type = invoker.format('COMMAND_HISTORY_SUCCESSTYPE', { old: oldest?.value || false }, { code: true });
|
||||
try {
|
||||
return {
|
||||
content: invoker.format('COMMAND_HISTORY_SUCCESS', {
|
||||
targets: user || channel ? invoker.format('COMMAND_HISTORY_SUCCESSTARGETS', {
|
||||
let targets = '';
|
||||
if (user || channel) targets = invoker.format('COMMAND_HISTORY_SUCCESSTARGETS', {
|
||||
//plural: parsed.length === 1 ? '' : 's',
|
||||
targets: `**${Util.escapeMarkdown(user?.value.tag || channel?.value.name)}**` //parsed.map((p) => `**${Util.escapeMarkdown(p.display)}**`).join(' ')
|
||||
}) : '',
|
||||
});
|
||||
else if (moderator) targets = invoker.format('COMMAND_HISTORY_SUCCESSMODERATOR', {
|
||||
moderator: `**${Util.escapeMarkdown(moderator.value.tag)}**`
|
||||
});
|
||||
|
||||
return {
|
||||
content: invoker.format('COMMAND_HISTORY_SUCCESS', {
|
||||
targets,
|
||||
type
|
||||
}),
|
||||
emoji: 'success',
|
||||
|
@ -27,8 +27,9 @@ class MuteCommand extends ModerationCommand {
|
||||
const { guild } = interaction;
|
||||
const settings = await guild.settings();
|
||||
const { type } = settings.mute;
|
||||
if (type === 3 && !guild.members.me.permissions.has('ModerateMembers')) throw new CommandError(interaction, { index: 'INHIBITOR_CLIENTPERMISSIONS_ERROR', params: { command: this.name, missing: 'ModerateMembers' } });
|
||||
else if (!guild.members.me.permissions.has('ManageRoles')) throw new CommandError(interaction, { index: 'INHIBITOR_CLIENTPERMISSIONS_ERROR', params: { command: this.name, missing: 'ManageRoles' } });
|
||||
const me = await guild.resolveMember(this.client.user);
|
||||
if (type === 3 && !me.permissions.has('ModerateMembers')) throw new CommandError(interaction, { index: 'INHIBITOR_CLIENTPERMISSIONS_ERROR', params: { command: this.name, missing: 'ModerateMembers' } });
|
||||
else if (!me.permissions.has('ManageRoles')) throw new CommandError(interaction, { index: 'INHIBITOR_CLIENTPERMISSIONS_ERROR', params: { command: this.name, missing: 'ManageRoles' } });
|
||||
|
||||
return this.client.moderationManager.handleInfraction(Mute, interaction, {
|
||||
targets: users.value,
|
||||
|
@ -12,7 +12,7 @@ class NicknameCommand extends ModerationCommand {
|
||||
name: 'name',
|
||||
description: 'The new nickname to give',
|
||||
type: 'STRING',
|
||||
required: true
|
||||
required: true, flag: true
|
||||
}],
|
||||
memberPermissions: ['ManageNicknames'],
|
||||
clientPermissions: ['ManageNicknames'],
|
||||
|
@ -1,6 +1,10 @@
|
||||
const { ModerationCommand, CommandError } = require('../../../interfaces');
|
||||
const { Prune } = require('../../../infractions');
|
||||
|
||||
const flag = true,
|
||||
valueOptional = true,
|
||||
defaultValue = true;
|
||||
|
||||
class PruneCommand extends ModerationCommand {
|
||||
|
||||
constructor(client) {
|
||||
@ -26,56 +30,69 @@ class PruneCommand extends ModerationCommand {
|
||||
}, {
|
||||
name: 'silent',
|
||||
type: 'BOOLEAN',
|
||||
description: 'Prune quietly'
|
||||
description: 'Prune quietly',
|
||||
flag, valueOptional, defaultValue
|
||||
}, {
|
||||
name: 'bots',
|
||||
type: 'BOOLEAN',
|
||||
description: 'Prune messages from bots'
|
||||
description: 'Prune messages from bots',
|
||||
flag, valueOptional, defaultValue
|
||||
}, {
|
||||
name: 'humans',
|
||||
type: 'BOOLEAN',
|
||||
description: 'Prune messages from humans'
|
||||
description: 'Prune messages from humans',
|
||||
flag, valueOptional, defaultValue
|
||||
}, {
|
||||
name: 'contains',
|
||||
type: 'STRING',
|
||||
description: 'Text to look for messages by'
|
||||
description: 'Text to look for messages by',
|
||||
flag
|
||||
}, {
|
||||
name: 'startswith',
|
||||
type: 'STRING',
|
||||
description: 'Text the messages to delete start with'
|
||||
description: 'Text the messages to delete start with',
|
||||
flag
|
||||
}, {
|
||||
name: 'endswith',
|
||||
type: 'STRING',
|
||||
description: 'Text the messages to delete end with'
|
||||
description: 'Text the messages to delete end with',
|
||||
flag
|
||||
}, {
|
||||
name: 'text',
|
||||
type: 'BOOLEAN',
|
||||
description: 'Only delete messages containing text'
|
||||
description: 'Only delete messages containing text',
|
||||
flag, valueOptional, defaultValue
|
||||
}, {
|
||||
name: 'invites',
|
||||
type: 'BOOLEAN',
|
||||
description: 'Delete messages containing invites'
|
||||
description: 'Delete messages containing invites',
|
||||
flag, valueOptional, defaultValue
|
||||
}, {
|
||||
name: 'links',
|
||||
type: 'BOOLEAN',
|
||||
description: 'Delete messages containing links'
|
||||
description: 'Delete messages containing links',
|
||||
flag, valueOptional, defaultValue
|
||||
}, {
|
||||
name: 'emojis',
|
||||
type: 'BOOLEAN',
|
||||
description: 'Prune messages cotaining emojis'
|
||||
description: 'Prune messages cotaining emojis',
|
||||
flag, valueOptional, defaultValue
|
||||
}, {
|
||||
name: 'after',
|
||||
type: 'STRING',
|
||||
description: 'ID of message after which to start deleting'
|
||||
description: 'ID of message after which to start deleting',
|
||||
flag
|
||||
}, {
|
||||
name: 'before',
|
||||
type: 'STRING',
|
||||
description: 'ID of message before which to start deleting'
|
||||
description: 'ID of message before which to start deleting',
|
||||
flag
|
||||
}, {
|
||||
name: 'logic',
|
||||
// type: '',
|
||||
choices: [{ name: 'AND', value: 'AND' }, { name: 'OR', value: 'OR' }],
|
||||
description: 'Logic type to use for combining options'
|
||||
description: 'Logic type to use for combining options',
|
||||
flag
|
||||
}, {
|
||||
name: 'reason',
|
||||
type: 'STRING',
|
||||
|
@ -22,7 +22,8 @@ class ResolveCommand extends SlashCommand {
|
||||
}, {
|
||||
name: 'notify',
|
||||
description: 'Attempt to notify the user about the resolve, may not always be possible',
|
||||
type: 'BOOLEAN'
|
||||
type: 'BOOLEAN',
|
||||
flag: true, valueOptional: true, defaultValue: true
|
||||
}] // Potentially add another option to enable a range of cases
|
||||
});
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ class StaffCommand extends SlashCommand {
|
||||
// const role = await guild.resolveRole(staff.role);
|
||||
// if(!role) return invoker.editReply({ index: 'COMMAND_STAFF_ERROR', emoji: 'failure' });
|
||||
|
||||
await channel.send({
|
||||
return channel.send({
|
||||
content: guild.format('COMMAND_STAFF_SUMMON', { author: author.tag, role: staff.role }),
|
||||
allowedMentions: { parse: ['roles'] } // roles: [staff.role],
|
||||
});
|
||||
|
@ -23,8 +23,9 @@ class UnmuteCommand extends ModerationCommand {
|
||||
const { guild } = interaction;
|
||||
const settings = await guild.settings();
|
||||
const { type } = settings.mute;
|
||||
if (type === 3 && !guild.members.me.permissions.has('ModerateMembers')) throw new CommandError(interaction, { index: 'INHIBITOR_CLIENTPERMISSIONS_ERROR', params: { command: this.name, missing: 'ModerateMembers' } });
|
||||
else if (!guild.members.me.permissions.has('ManageRoles')) throw new CommandError(interaction, { index: 'INHIBITOR_CLIENTPERMISSIONS_ERROR', params: { command: this.name, missing: 'ManageRoles' } });
|
||||
const me = await guild.resolveMember(this.client.user);
|
||||
if (type === 3 && !me.permissions.has('ModerateMembers')) throw new CommandError(interaction, { index: 'INHIBITOR_CLIENTPERMISSIONS_ERROR', params: { command: this.name, missing: 'ModerateMembers' } });
|
||||
else if (!me.permissions.has('ManageRoles')) throw new CommandError(interaction, { index: 'INHIBITOR_CLIENTPERMISSIONS_ERROR', params: { command: this.name, missing: 'ManageRoles' } });
|
||||
|
||||
return this.client.moderationManager.handleInfraction(Unmute, interaction, {
|
||||
targets: users.value,
|
||||
|
@ -12,8 +12,9 @@ const { SlashCommand, Infraction } = require("../../../interfaces");
|
||||
}
|
||||
*/
|
||||
|
||||
class ResolveCommand extends SlashCommand {
|
||||
class UnresolveCommand extends SlashCommand {
|
||||
|
||||
// TODO: make unresolving enact the infraction again
|
||||
constructor(client) {
|
||||
super(client, {
|
||||
name: 'unresolve',
|
||||
@ -50,4 +51,4 @@ class ResolveCommand extends SlashCommand {
|
||||
|
||||
}
|
||||
|
||||
module.exports = ResolveCommand;
|
||||
module.exports = UnresolveCommand;
|
@ -13,14 +13,16 @@ class AvatarCommand extends SlashCommand {
|
||||
// type: 'INTEGER',
|
||||
choices: [16, 32, 64, 128, 256, 512, 1024, 2048].map((i) => {
|
||||
return { name: `${i}`, value: `${i}` };
|
||||
})
|
||||
}),
|
||||
flag: true
|
||||
}, {
|
||||
name: 'format',
|
||||
description: 'Image format',
|
||||
// type: 'STRING'
|
||||
choices: ['webp', 'png', 'jpeg', 'jpg', 'gif'].map((i) => {
|
||||
return { name: i, value: i };
|
||||
})
|
||||
}),
|
||||
flag: true
|
||||
}, {
|
||||
name: 'user',
|
||||
description: 'Use this for the user\'s global avatar',
|
||||
@ -28,7 +30,8 @@ class AvatarCommand extends SlashCommand {
|
||||
}, {
|
||||
name: 'member',
|
||||
description: 'Use this for the user\'s server avatar',
|
||||
type: 'MEMBER'
|
||||
type: 'MEMBER',
|
||||
flag: true
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ class PollCommand extends SlashCommand {
|
||||
const questions = [];
|
||||
|
||||
const _channel = channel?.value || invoker.channel;
|
||||
const botMissing = _channel.permissionsFor(guild.members.me).missing(['SendMessages', 'EmbedLinks']);
|
||||
const botMissing = _channel.permissionsFor(this.client.user).missing(['SendMessages', 'EmbedLinks']);
|
||||
const userMissing = _channel.permissionsFor(member).missing(['SendMessages']);
|
||||
if (botMissing.length) return invoker.editReply({ index: 'COMMAND_POLL_BOT_PERMS', params: { missing: botMissing.join(', '), channel: _channel.id } });
|
||||
if (userMissing.length) return invoker.editReply({ index: 'COMMAND_POLL_USER_PERMS', params: { missing: userMissing.join(', '), channel: _channel.id } });
|
||||
@ -65,10 +65,10 @@ class PollCommand extends SlashCommand {
|
||||
for (let i = 0; i < choices.value; i++) {
|
||||
const response = await invoker.promptMessage({
|
||||
content: guild.format(`COMMAND_POLL_QUESTION${choices.value === 1 ? '' : 'S'}`, { number: i + 1 }) + '\n' + guild.format('COMMAND_POLL_ADDENDUM'),
|
||||
time: 90, editReply: true
|
||||
time: 90, editReply: invoker.replied
|
||||
});
|
||||
if (!response || !response.content) return invoker.editReply({ index: 'COMMAND_POLL_TIMEOUT' });
|
||||
if(invoker.channel.permissionsFor(guild.members.me).has('ManageMessages')) await response.delete().catch(() => null);
|
||||
if(invoker.channel.permissionsFor(this.client.user).has('ManageMessages')) await response.delete().catch(() => null);
|
||||
const { content } = response;
|
||||
if (content.toLowerCase() === 'stop') break;
|
||||
if (content.toLowerCase() === 'cancel') return invoker.editReply({ index: 'GENERAL_CANCELLED' });
|
||||
|
@ -34,7 +34,8 @@ class SelfroleCommand extends SlashCommand {
|
||||
const { guild, member } = invoker;
|
||||
const { selfrole } = await guild.settings();
|
||||
if (!selfrole.roles.length) return { index: 'COMMAND_SELFROLE_NONE', emoji: 'failure' };
|
||||
const ownHighest = guild.members.me.roles.highest;
|
||||
const me = await guild.resolveMember(this.client.user);
|
||||
const ownHighest = me.roles.highest;
|
||||
|
||||
const memberRoles = member.roles.cache.map((r) => r.id);
|
||||
const tooHigh = roles?.value.filter((r) => r.position > ownHighest.position);
|
||||
|
@ -13,7 +13,7 @@ class ClientPermissions extends Inhibitor {
|
||||
|
||||
async execute(invoker, command) {
|
||||
|
||||
const missing = invoker.channel.permissionsFor(invoker.guild.members.me).missing(command.clientPermissions);
|
||||
const missing = invoker.channel.permissionsFor(this.client.user).missing(command.clientPermissions);
|
||||
if (missing.length) return super._fail({ error: true, missing: missing.join(', '), silent: true });
|
||||
return super._succeed();
|
||||
|
||||
|
@ -22,10 +22,10 @@ class AuditLogObserver extends Observer {
|
||||
|
||||
}
|
||||
|
||||
async guildBanAdd({ guild, user, guildWrapper: wrapper }) {
|
||||
async guildBanAdd({ user, guildWrapper: wrapper }) {
|
||||
const settings = await wrapper.settings();
|
||||
if (!settings.moderation.channel || !settings.moderation.infractions.includes('BAN')) return undefined; //This is checked by the infraction handling, but it may save resources if checked earlier.
|
||||
const audit = await this._fetchFirstEntry(guild, user, 'MemberBanAdd');
|
||||
const audit = await this._fetchFirstEntry(wrapper, user, 'MemberBanAdd');
|
||||
if (!audit) return undefined;
|
||||
new Infraction(this.client, {
|
||||
type: 'BAN',
|
||||
@ -37,10 +37,10 @@ class AuditLogObserver extends Observer {
|
||||
}).handle();
|
||||
}
|
||||
|
||||
async guildBanRemove({ guild, user, guildWrapper: wrapper }) {
|
||||
async guildBanRemove({ user, guildWrapper: wrapper }) {
|
||||
const settings = await wrapper.settings();
|
||||
if (!settings.moderation.channel || !settings.moderation.infractions.includes('UNBAN')) return undefined; //This is checked by the infraction handling, but it may save resources if checked earlier.
|
||||
const audit = await this._fetchFirstEntry(guild, user, 'MemberBanRemove');
|
||||
const audit = await this._fetchFirstEntry(wrapper, user, 'MemberBanRemove');
|
||||
if (!audit) return undefined;
|
||||
new Infraction(this.client, {
|
||||
type: 'UNBAN',
|
||||
@ -56,7 +56,7 @@ class AuditLogObserver extends Observer {
|
||||
const { guildWrapper: wrapper } = member;
|
||||
const settings = await wrapper.settings();
|
||||
if (!settings.moderation.channel || !settings.moderation.infractions.includes('KICK')) return undefined; //This is checked by the infraction handling, but it may save resources if checked earlier.
|
||||
const audit = await this._fetchFirstEntry(member.guild, member.user, 'MemberKick');
|
||||
const audit = await this._fetchFirstEntry(wrapper, member.user, 'MemberKick');
|
||||
if (!audit) return undefined;
|
||||
new Infraction(this.client, {
|
||||
type: 'KICK',
|
||||
@ -123,7 +123,7 @@ class AuditLogObserver extends Observer {
|
||||
|
||||
const mutedRole = settings.mute.role;
|
||||
if (!mutedRole) return undefined;
|
||||
const audit = await this._fetchFirstEntry(newMember.guild, newMember.user, 'MemberRoleUpdate');
|
||||
const audit = await this._fetchFirstEntry(wrapper, newMember.user, 'MemberRoleUpdate');
|
||||
if (!audit) return undefined;
|
||||
let type = null;
|
||||
|
||||
@ -148,7 +148,8 @@ class AuditLogObserver extends Observer {
|
||||
}
|
||||
|
||||
async _fetchFirstEntry(guild, user, type, subtype = null) {
|
||||
if (!guild.members.me.permissions.has('ViewAuditLog')) return null;
|
||||
const me = await guild.resolveMember(this.client.user);
|
||||
if (!me.permissions.has('ViewAuditLog')) return null;
|
||||
type = AuditLogEvent[type];
|
||||
const audit = await guild.fetchAuditLogs({ limit: 1, type });
|
||||
if (audit.entries.size === 0) return null;
|
||||
|
@ -133,7 +133,7 @@ module.exports = class AutoModeration extends Observer {
|
||||
|
||||
if (!enabled || roles.some((r) => bypass.includes(r)) || ignore.includes(channel.id)) return;
|
||||
|
||||
const missing = channel.permissionsFor(guild.members.me).missing('ManageMessages');
|
||||
const missing = channel.permissionsFor(this.client.user).missing('ManageMessages');
|
||||
if (missing.length) {
|
||||
this.client.emit('filterMissingPermissions', { channel, guild: wrapper, filter: 'word', permissions: missing });
|
||||
return;
|
||||
@ -432,7 +432,7 @@ module.exports = class AutoModeration extends Observer {
|
||||
|
||||
if (roles.some((r) => bypass.includes(r)) || ignore.includes(channel.id)) return;
|
||||
|
||||
const missing = channel.permissionsFor(guild.members.me).missing('ManageMessages');
|
||||
const missing = channel.permissionsFor(this.client.user).missing('ManageMessages');
|
||||
if (missing.length) {
|
||||
this.client.emit('filterMissingPermissions', { channel, guild: wrapper, filter: 'link', permissions: missing });
|
||||
return;
|
||||
@ -454,9 +454,10 @@ module.exports = class AutoModeration extends Observer {
|
||||
let log = `${guild.name} Link filter debug:`;
|
||||
|
||||
for (const match of matches) {
|
||||
const { domain } = match.match(this.regex.linkReg).groups;
|
||||
let { domain } = match.match(this.regex.linkReg).groups;
|
||||
domain = domain.toLowerCase();
|
||||
// Invites are filtered separately
|
||||
if (domain.toLowerCase() === 'discord.gg') continue;
|
||||
if (domain === 'discord.gg') continue;
|
||||
log += `\nMatched link ${match}: `;
|
||||
|
||||
const predicate = (dom) => {
|
||||
@ -550,13 +551,13 @@ module.exports = class AutoModeration extends Observer {
|
||||
const member = message.member || await guild.members.fetch(author.id).catch(() => null);
|
||||
const settings = await wrapper.settings();
|
||||
const { invitefilter: setting } = settings;
|
||||
const { bypass, ignore, actions, silent, enabled, whitelist } = setting;
|
||||
const { bypass, ignore, actions, silent, enabled, whitelist = [] } = setting;
|
||||
if (!enabled) return;
|
||||
const roles = member?.roles.cache.map((r) => r.id) || [];
|
||||
|
||||
if (roles.some((r) => bypass.includes(r)) || ignore.includes(channel.id)) return;
|
||||
|
||||
const missing = channel.permissionsFor(guild.members.me).missing('ManageMessages');
|
||||
const missing = channel.permissionsFor(this.client.user).missing('ManageMessages');
|
||||
if (missing.length) {
|
||||
this.client.emit('filterMissingPermissions', { channel, guild: wrapper, filter: 'invite', permissions: missing });
|
||||
return;
|
||||
@ -613,7 +614,7 @@ module.exports = class AutoModeration extends Observer {
|
||||
|
||||
if (!enabled || roles.some((r) => bypass.includes(r)) || ignore.includes(channel.id)) return;
|
||||
|
||||
const missing = channel.permissionsFor(guild.members.me).missing('ManageMessages');
|
||||
const missing = channel.permissionsFor(this.client.user).missing('ManageMessages');
|
||||
if (missing.length) {
|
||||
this.client.emit('filterMissingPermissions', { channel, guild: wrapper, filter: 'mention', permissions: missing });
|
||||
return;
|
||||
|
@ -2,6 +2,9 @@ const { EmbedBuilder, Message, ChannelType, ComponentType, ButtonStyle } = requi
|
||||
const { Util } = require('../../../utilities');
|
||||
const { InvokerWrapper, MessageWrapper } = require('../../client/wrappers');
|
||||
const { Observer, CommandError } = require('../../interfaces/');
|
||||
// const { inspect } = require('util');
|
||||
|
||||
const flagReg = /(?:^| )(?<flag>(?:--[a-z0-9]{3,})|(?:-[a-z]{1,2}))(?:$| )/iu;
|
||||
|
||||
class CommandHandler extends Observer {
|
||||
|
||||
@ -29,8 +32,12 @@ class CommandHandler extends Observer {
|
||||
|| message.author.bot
|
||||
|| message.guild && !message.guild.available) return undefined;
|
||||
|
||||
|
||||
const userWrapper = await this.client.getUserWrapper(message.author.id);
|
||||
if (!userWrapper.developer) return;
|
||||
if (message.guild) {
|
||||
const settings = await message.guildWrapper.settings();
|
||||
if (!settings.textcommands.enabled && !userWrapper.developer) return;
|
||||
}
|
||||
|
||||
if(message.guild) {
|
||||
if(!message.member) await message.guild.members.fetch(message.author.id);
|
||||
@ -55,7 +62,7 @@ class CommandHandler extends Observer {
|
||||
// There was an error if _parseResponse return value is truthy, i.e. an error message was sent
|
||||
if (await this._parseResponse(invoker, response)) return;
|
||||
|
||||
await this._executeCommand(invoker, command.slash ? response.options.args : response.options);
|
||||
await this._executeCommand(invoker, response.options);
|
||||
|
||||
}
|
||||
|
||||
@ -79,7 +86,7 @@ class CommandHandler extends Observer {
|
||||
if (inhibitors.length) return this._generateError(invoker, { type: 'inhibitor', ...inhibitors[0] });
|
||||
await invoker.deferReply();
|
||||
|
||||
const response = await this._parseInteraction(interaction, command);
|
||||
const response = await this._parseInteraction(invoker, command);
|
||||
if (await this._parseResponse(invoker, response)) return;
|
||||
|
||||
try { // Temp logging
|
||||
@ -91,8 +98,31 @@ class CommandHandler extends Observer {
|
||||
}
|
||||
|
||||
_parseResponse(invoker, response) {
|
||||
|
||||
// Ensure option dependencies
|
||||
outer:
|
||||
if(response.options) for (const opt of Object.values(response.options)) {
|
||||
let hasDep = false;
|
||||
for (const dep of opt.dependsOn) {
|
||||
// AND logic
|
||||
if (!response.options[dep] && opt.dependsOnMode === 'AND') {
|
||||
response = { option: opt, error: true, dependency: dep };
|
||||
break outer;
|
||||
}
|
||||
// OR logic
|
||||
if (response.options[dep]) hasDep = true;
|
||||
}
|
||||
if (!hasDep && opt.dependsOnMode === 'OR') {
|
||||
response = { option: opt, error: true, dependency: opt.dependsOn.join('** OR **') };
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const { command } = invoker;
|
||||
if (response.error) {
|
||||
if (response.error && response.index) {
|
||||
if(!response.emoji) response.emoji = 'failure';
|
||||
return invoker.reply(response);
|
||||
} else if (response.error) {
|
||||
let content = invoker.format(`O_COMMANDHANDLER_TYPE${response.option.type}`, {
|
||||
option: response.option.name, min: response.option.minimum, max: response.option.maximum
|
||||
});
|
||||
@ -133,8 +163,6 @@ class CommandHandler extends Observer {
|
||||
}
|
||||
|
||||
async _executeCommand(invoker, options) {
|
||||
// TODO defer all replies -- need to go through all commands to ensure they're not deferrign them to avoid errors
|
||||
// Why? Occasionally some interacitons don't complete in time due to taking longer than normal to reach the bot -- unsure if deferring all replies will fix it
|
||||
|
||||
let response = null;
|
||||
const now = Date.now();
|
||||
@ -147,7 +175,7 @@ class CommandHandler extends Observer {
|
||||
let debugstr = invoker.command.name;
|
||||
if (invoker.subcommandGroup) debugstr += ` ${invoker.subcommandGroup.name}`;
|
||||
if(invoker.subcommand) debugstr += ` ${invoker.subcommand.name}`;
|
||||
this.logger.info(`${invoker.user.tag} (${invoker.user.id}) is executing ${debugstr} in ${invoker.guild?.name || 'dms'}`);
|
||||
this.logger.info(`[${invoker.type.toUpperCase()}] ${invoker.user.tag} (${invoker.user.id}) is executing ${debugstr} in ${invoker.guild?.name || 'dms'}`);
|
||||
response = await invoker.command.execute(invoker, options);
|
||||
invoker.command.success(now);
|
||||
} catch (error) {
|
||||
@ -174,9 +202,9 @@ class CommandHandler extends Observer {
|
||||
|
||||
}
|
||||
|
||||
async _parseInteraction(interaction, command) {
|
||||
async _parseInteraction(invoker, command) {
|
||||
|
||||
const { subcommand } = interaction;
|
||||
const { subcommand, guild, target: interaction } = invoker;
|
||||
|
||||
let error = null;
|
||||
const options = {};
|
||||
@ -193,14 +221,11 @@ class CommandHandler extends Observer {
|
||||
continue;
|
||||
}
|
||||
|
||||
// const newOption = new CommandOption({
|
||||
// name: matched.name, type: matched.type,
|
||||
// minimum: matched.minimum, maximum: matched.maximum,
|
||||
// _rawValue: option.value,
|
||||
// dependsOn: matched.dependsOn, dependsOnMode: matched.dependsOnMode
|
||||
// });
|
||||
const newOption = matched.clone(option.value);
|
||||
const parsed = await this._parseOption(interaction, newOption);
|
||||
if (matched.guildOnly && !guild) return { error: true, params: { option: matched.name }, index: 'O_COMMANDHANDLER_GUILDONLY_OPT' };
|
||||
const rawValue = matched.plural && typeof option.value === 'string' ? Util.parseQuotes(option.value).map(([x]) => x) : option.value;
|
||||
const newOption = matched.clone(rawValue, guild, true);
|
||||
const parsed = await newOption.parse();
|
||||
// const parsed = await this._parseOption(interaction, newOption);
|
||||
// console.log(parsed);
|
||||
|
||||
if(parsed.error) {
|
||||
@ -211,7 +236,7 @@ class CommandHandler extends Observer {
|
||||
break;
|
||||
}
|
||||
|
||||
newOption.value = parsed.value;
|
||||
// newOption.value = parsed.value;
|
||||
options[matched.name] = newOption;
|
||||
}
|
||||
|
||||
@ -223,18 +248,6 @@ class CommandHandler extends Observer {
|
||||
if(!options[req.name]) return { option: req, error: true, required: true };
|
||||
}
|
||||
|
||||
// Ensure option dependencies
|
||||
for (const opt of Object.values(options)) {
|
||||
let hasDep = false;
|
||||
for (const dep of opt.dependsOn) {
|
||||
// AND logic
|
||||
if (!options[dep] && opt.dependsOnMode === 'AND') return { option: opt, error: true, dependency: dep };
|
||||
// OR logic
|
||||
if (options[dep]) hasDep = true;
|
||||
}
|
||||
if(!hasDep && opt.dependsOnMode === 'OR') return { option: opt, error: true, dependency: opt.dependsOn.join('** OR **') };
|
||||
}
|
||||
|
||||
return {
|
||||
error: false,
|
||||
options
|
||||
@ -244,13 +257,14 @@ class CommandHandler extends Observer {
|
||||
|
||||
async _parseMessage(invoker, params) {
|
||||
|
||||
const { command, target: message } = invoker;
|
||||
const { command, target: message, guild } = invoker;
|
||||
const { subcommands, subcommandGroups } = command;
|
||||
const args = {};
|
||||
// console.log(options);
|
||||
let group = null,
|
||||
subcommand = null;
|
||||
|
||||
// Parse out subcommands
|
||||
if (subcommandGroups.length || subcommands.length) {
|
||||
const [first, second, ...rest] = params;
|
||||
group = command.subcommandGroup(first);
|
||||
@ -258,11 +272,11 @@ class CommandHandler extends Observer {
|
||||
// Depending on how thoroughly I want to support old style commands this might have to try and resolve to other options
|
||||
// But for now I'm followin discord's structure for commands
|
||||
if (!group) {
|
||||
subcommand = command.subcommand(first)?.raw;
|
||||
subcommand = command.subcommand(first);
|
||||
params = [];
|
||||
if (second) params.push(second, ...rest);
|
||||
} else {
|
||||
subcommand = command.subcommand(second)?.raw;
|
||||
subcommand = command.subcommand(second);
|
||||
params = rest;
|
||||
}
|
||||
message.subcommand = subcommand;
|
||||
@ -275,18 +289,166 @@ class CommandHandler extends Observer {
|
||||
const activeCommand = subcommand || command;
|
||||
const flags = activeCommand.options.filter((opt) => opt.flag);
|
||||
|
||||
for (const flag of flags) {
|
||||
params = Util.parseQuotes(params.join(' ')).map(([x]) => x);
|
||||
let currentFlag = null;
|
||||
// console.log('params', params);
|
||||
|
||||
// Parse flags
|
||||
for (let index = 0; index < params.length;) {
|
||||
|
||||
// console.log(params[index]);
|
||||
const match = flagReg.exec(params[index]);
|
||||
if (!match) {
|
||||
// console.log('no match', currentFlag?.name);
|
||||
if (currentFlag) { // Add potential value resolveables to the flag's raw value until next flag is hit, if there is one
|
||||
if (currentFlag.plural) { // The parse function only parses consecutive values
|
||||
if (!currentFlag._rawValue) currentFlag._rawValue = [];
|
||||
currentFlag._rawValue.push(params[index]);
|
||||
} else {
|
||||
currentFlag._rawValue = params[index];
|
||||
currentFlag = null;
|
||||
}
|
||||
}
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// console.log('matched');
|
||||
const _flag = match.groups.flag.replace(/--?/u, '').toLowerCase();
|
||||
let aliased = false;
|
||||
const flag = flags.find((f) => {
|
||||
aliased = f.valueAsAlias && f.choices.some((c) => c.value === _flag);
|
||||
return f.name === _flag || aliased;
|
||||
});
|
||||
if (!flag) return { error: true, index: 'O_COMMANDHANDLER_UNRECOGNISED_FLAG', params: { flag: _flag } };
|
||||
else if (flag.guildOnly && !guild) return { error: true, params: { option: flag.name }, index: 'O_COMMANDHANDLER_GUILDONLY_OPT' };
|
||||
|
||||
// console.log('aliased', aliased);
|
||||
params.splice(index, 1, null);
|
||||
if (aliased) {
|
||||
(args[flag.name] = flag.clone(_flag, guild))._aliased = true;
|
||||
currentFlag = null;
|
||||
} else {
|
||||
currentFlag = flag.clone(null, guild);
|
||||
args[flag.name] = currentFlag;
|
||||
}
|
||||
index++;
|
||||
// console.log('------------------------------');
|
||||
|
||||
}
|
||||
|
||||
return { options: { args, parameters: params }, verbose: true };
|
||||
// Clean up params for option parsing
|
||||
for (const flag of Object.values(args)) {
|
||||
// console.log('flags loop', flag.name, flag._rawValue);
|
||||
const removed = await flag.parse();
|
||||
if (removed.error) {
|
||||
if (flag.choices.length) {
|
||||
return { error: true, index: 'O_COMMANDHANDLER_INVALID_CHOICE', params: { option: flag.name, value: flag._rawValue, choices: flag.choices.map((c) => c.value).join('`, `') } };
|
||||
}
|
||||
return { option: flag, ...removed };
|
||||
}
|
||||
for(const r of removed) params.splice(params.indexOf(r), 1);
|
||||
}
|
||||
|
||||
// console.log('params', params);
|
||||
let options = activeCommand.options.filter((opt) => !opt.flag && (opt.type !== 'STRING' || opt.choices.length));
|
||||
if(!guild) options = options.filter((opt) => !opt.guildOnly);
|
||||
// const choiceOpts = activeCommand.options.filter((opt) => opt.choices.length);
|
||||
const stringOpts = activeCommand.options.filter((opt) => !opt.flag && !opt.choices.length && opt.type === 'STRING');
|
||||
// console.log('non-flag options', options.map((opt) => opt.name));
|
||||
// Parse out non-flag options
|
||||
for (const option of options) { // String options are parsed separately at the end
|
||||
// console.log(1, params);
|
||||
if (!params.some((param) => param !== null)) break;
|
||||
// console.log(2);
|
||||
const cloned = option.clone(null, guild);
|
||||
let removed = null;
|
||||
if (cloned.plural) { // E.g. if the type is CHANNEL**S**, parse out any potential channels from the message
|
||||
// console.log('plural');
|
||||
cloned._rawValue = params;
|
||||
removed = await cloned.parse();
|
||||
} else for (let index = 0; index < params.length;) { // Attempt to parse out a value from each param
|
||||
// console.log('singular');
|
||||
if (params[index] === null) {
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
cloned._rawValue = params[index];
|
||||
removed = await cloned.parse();
|
||||
if (!removed.error) break;
|
||||
index++;
|
||||
}
|
||||
if (removed.error) continue;
|
||||
|
||||
args[cloned.name] = cloned;
|
||||
// Clean up params for string parsing
|
||||
for (const r of removed) params.splice(params.indexOf(r), 1, null);
|
||||
|
||||
}
|
||||
|
||||
const strings = [];
|
||||
let tmpString = '';
|
||||
// console.log('strings loop');
|
||||
// console.log(params);
|
||||
// Compile strings into groups of strings so we don't get odd looking strings from which options have been parsed out of
|
||||
for (let index = 0; index < params.length;) {
|
||||
const str = params[index];
|
||||
// console.log(str);
|
||||
if (!str) {
|
||||
// console.log('null string');
|
||||
if (tmpString.length) {
|
||||
// console.log('pushing', tmpString);
|
||||
strings.push(tmpString);
|
||||
tmpString = '';
|
||||
}
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
params.splice(index, 1);
|
||||
tmpString += ` ${str}`;
|
||||
tmpString = tmpString.trim();
|
||||
}
|
||||
// console.log('tmpString', tmpString);
|
||||
if(tmpString.length) strings.push(tmpString);
|
||||
|
||||
// console.log('params after', params);
|
||||
// console.log('strings', strings);
|
||||
|
||||
if (strings.length) for (const strOpt of stringOpts) {
|
||||
const cloned = strOpt.clone(strings.shift());
|
||||
// console.log(cloned.name, cloned._rawValue);
|
||||
await cloned.parse();
|
||||
args[cloned.name] = cloned;
|
||||
}
|
||||
|
||||
// This part is obsolete now, I think, the string option checks the choice value
|
||||
for (const arg of Object.values(args)) {
|
||||
// console.log(arg.name, arg.value);
|
||||
if (!arg.choices.length) continue;
|
||||
if (!arg.choices.some((choice) => {
|
||||
if (typeof arg.value === 'string') return arg.value.toLowerCase() === choice.value.toLowerCase();
|
||||
return arg.value === choice.value;
|
||||
})) return { error: true, index: 'O_COMMANDHANDLER_INVALID_CHOICE', params: { option: arg.name, value: arg.value, choices: arg.choices.map((c) => c.value).join('`, `') } };
|
||||
}
|
||||
|
||||
for (const req of activeCommand.options.filter((opt) => opt.required))
|
||||
if (!args[req.name]) return { option: req, error: true, required: true };
|
||||
|
||||
// console.log('parsed args final', Object.values(args).map((arg) => `\n${arg.name}: ${arg.value}, ${inspect(arg._rawValue)}`).join(''));
|
||||
if (strings.length) return { error: true, index: 'O_COMMANDHANDLER_UNRECOGNISED_OPTIONS', params: { opts: strings.join('`, `') } };
|
||||
|
||||
return { options: args, verbose: true };
|
||||
|
||||
}
|
||||
|
||||
// Should be unnecessary -- moved to commandoption
|
||||
async _parseOption(interaction, option) {
|
||||
const { guild } = interaction;
|
||||
|
||||
const types = {
|
||||
POINTS: (value) => {
|
||||
return { value };
|
||||
},
|
||||
ROLES: async (string) => {
|
||||
const args = Util.parseQuotes(string).map(([str]) => str);
|
||||
const roles = await guild.resolveRoles(args);
|
||||
@ -372,6 +534,7 @@ class CommandHandler extends Observer {
|
||||
return { error: false, value: parseInt(integer) };
|
||||
},
|
||||
BOOLEAN: (boolean) => {
|
||||
boolean = this.client.resolver.resolveBoolean(boolean);
|
||||
return { error: false, value: boolean };
|
||||
},
|
||||
MEMBER: async (user) => {
|
||||
@ -430,7 +593,7 @@ class CommandHandler extends Observer {
|
||||
async _getCommand(message) {
|
||||
|
||||
if (!this._mentionPattern) this._mentionPattern = new RegExp(`^(<@!?${this.client.user.id}>)`, 'iu');
|
||||
const [arg1, arg2, ...args] = message.content.split(' ');
|
||||
const [arg1, arg2, ...args] = message.content.split(' ').filter((str) => str.length);
|
||||
|
||||
if (message.guild) await message.guild.settings();
|
||||
const userWrapper = await this.client.getUserWrapper(message.author.id);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-labels */
|
||||
const { WebhookClient, AttachmentBuilder } = require('discord.js');
|
||||
const { WebhookClient, AttachmentBuilder, AuditLogEvent } = require('discord.js');
|
||||
const { stripIndents } = require('common-tags');
|
||||
const moment = require('moment');
|
||||
const { inspect } = require('util');
|
||||
@ -103,22 +103,23 @@ class GuildLogger extends Observer {
|
||||
|
||||
const hook = await guild.getWebhook('messages');
|
||||
if (!hook) {
|
||||
this.logger.debug(`Missing messageLog hook in ${guild.name} (${guild.id})`);
|
||||
// this.logger.debug(`Missing messageLog hook in ${guild.name} (${guild.id})`);
|
||||
return this.client.emit('logError', { guild, logger: 'threadLogger', reason: 'MSGLOG_NO_HOOK' });
|
||||
}
|
||||
|
||||
let actor = null;
|
||||
const auditLogPerm = guild.members.me.permissions.has('ViewAuditLog');
|
||||
const me = await this.client.resolver.resolveMember(this.client.user, null, guild);
|
||||
const auditLogPerm = me.permissions.has('ViewAuditLog');
|
||||
if (type === 'CREATE') actor = owner;
|
||||
else if (type === 'DELETE' && auditLogPerm) {
|
||||
const auditLogs = await guild.fetchAuditLogs({ type: 'THREAD_DELETE', limit: 1 });
|
||||
const auditLogs = await guild.fetchAuditLogs({ type: AuditLogEvent.ThreadDelete, limit: 1 });
|
||||
const log = auditLogs.entries.first();
|
||||
if (log) {
|
||||
if (thread.id !== log.target.id) return;
|
||||
actor = log.executor;
|
||||
}
|
||||
} else if (['ARCHIVE', 'UNARCHIVE'].includes(type) && auditLogPerm) {
|
||||
const auditLogs = await guild.fetchAuditLogs({ type: 'THREAD_UPDATE', limit: 1 });
|
||||
const auditLogs = await guild.fetchAuditLogs({ type: AuditLogEvent.ThreadUpdate, limit: 1 });
|
||||
const log = auditLogs.entries.first();
|
||||
if (log) {
|
||||
if (thread.id !== log.target.id) return;
|
||||
@ -178,7 +179,7 @@ class GuildLogger extends Observer {
|
||||
|
||||
const hook = await wrapper.getWebhook('messages');
|
||||
if (!hook) {
|
||||
this.logger.debug(`Missing messageLog hook in ${wrapper.name} (${wrapper.id})`);
|
||||
// this.logger.debug(`Missing messageLog hook in ${wrapper.name} (${wrapper.id})`);
|
||||
return this.client.emit('logError', { guild: wrapper, logger: 'messageLogger', reason: 'MSGLOG_NO_HOOK' });
|
||||
}
|
||||
|
||||
@ -340,13 +341,13 @@ class GuildLogger extends Observer {
|
||||
const { ignore, bypass } = chatlogs;
|
||||
if (ignore.includes(channel.id)) return;
|
||||
|
||||
const missing = logChannel.permissionsFor(guild.members.me).missing(['ViewChannel', 'EmbedLinks', 'SendMessages', 'ManageWebhooks']);
|
||||
const missing = logChannel.permissionsFor(this.client.user).missing(['ViewChannel', 'EmbedLinks', 'SendMessages', 'ManageWebhooks']);
|
||||
if (missing.length)
|
||||
return this.client.emit('logError', { guild: wrapper, logger: 'messageLogger', reason: 'MSGLOG_NO_PERMS', params: { missing: missing.join(', ') } });
|
||||
|
||||
const hook = await wrapper.getWebhook('messages');
|
||||
if (!hook) {
|
||||
this.logger.debug(`Missing messageLog hook in ${guild.name} (${guild.id})`);
|
||||
// this.logger.debug(`Missing messageLog hook in ${guild.name} (${guild.id})`);
|
||||
return this.client.emit('logError', { guild: wrapper, logger: 'messageLogger', reason: 'MSGLOG_NO_HOOK' });
|
||||
}
|
||||
|
||||
@ -364,6 +365,7 @@ class GuildLogger extends Observer {
|
||||
content = Util.escapeMarkdown(content);
|
||||
|
||||
if (author.bot) continue;
|
||||
// TODO: Apparently the cache for this doesn't work properly and the bot hits rate limits occasionally, make own cache at some point
|
||||
if (!member || member.partial) member = await guild.members.fetch(message.author.id).catch(() => {
|
||||
return false;
|
||||
});
|
||||
@ -533,7 +535,7 @@ class GuildLogger extends Observer {
|
||||
|
||||
const hook = await wrapper.getWebhook('messages');
|
||||
if (!hook) {
|
||||
this.logger.debug(`Missing messageLog hook in ${guild.name} (${guild.id})`);
|
||||
// this.logger.debug(`Missing messageLog hook in ${guild.name} (${guild.id})`);
|
||||
return this.client.emit('logError', { guild: wrapper, logger: 'messageLogger', reason: 'MSGLOG_NO_HOOK' });
|
||||
}
|
||||
|
||||
@ -710,7 +712,7 @@ class GuildLogger extends Observer {
|
||||
|
||||
async memberLeave(member) {
|
||||
|
||||
const { guild, guildWrapper: wrapper } = member;
|
||||
const { guildWrapper: wrapper } = member;
|
||||
const settings = await wrapper.settings();
|
||||
const setting = settings.members;
|
||||
if (!setting.channel || !setting.enabled) return;
|
||||
@ -732,7 +734,7 @@ class GuildLogger extends Observer {
|
||||
|
||||
if (oldMember.nickname === newMember.nickname) return;
|
||||
|
||||
const { guild, user, guildWrapper: wrapper } = oldMember;
|
||||
const { user, guildWrapper: wrapper } = oldMember;
|
||||
const settings = await wrapper.settings();
|
||||
const setting = settings.nicknames;
|
||||
if (!setting.channel || !setting.enabled) return;
|
||||
|
31
src/structure/components/observers/Metrics.js
Normal file
31
src/structure/components/observers/Metrics.js
Normal file
@ -0,0 +1,31 @@
|
||||
const { Observer } = require("../../interfaces");
|
||||
|
||||
class Metrics extends Observer {
|
||||
|
||||
constructor(client) {
|
||||
super(client, {
|
||||
name: 'metrics',
|
||||
priority: 10,
|
||||
disabled: false
|
||||
});
|
||||
|
||||
this.hooks = [
|
||||
['apiRequest', this.request.bind(this)],
|
||||
['apiResponse', this.response.bind(this)]
|
||||
];
|
||||
|
||||
this.cache = {};
|
||||
|
||||
}
|
||||
|
||||
async request(request) {
|
||||
// console.log(request);
|
||||
}
|
||||
|
||||
async response(request, response) {
|
||||
// console.log(response, await response.json(), response.status);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Metrics;
|
@ -51,7 +51,8 @@ class UtilityHook extends Observer {
|
||||
const { guildWrapper: guild } = member;
|
||||
const settings = await guild.settings();
|
||||
const setting = settings.mute;
|
||||
if (!guild.members.me.permissions.has('ManageRoles')) return;
|
||||
const me = await guild.resolveMember(this.client.user);
|
||||
if (!me.permissions.has('ManageRoles')) return;
|
||||
|
||||
const infraction = await this.client.storageManager.mongodb.infractions.findOne({
|
||||
duration: { $gt: 0 },
|
||||
@ -79,7 +80,7 @@ class UtilityHook extends Observer {
|
||||
if (managed) {
|
||||
await member.roles.add(setting.role, 'automute upon rejoin, type 1');
|
||||
await member.roles.remove(remove, 'removing excess roles for type 1 mute');
|
||||
} else await member.roles.set(setting.role, 'automute upon join, type 1');
|
||||
} else await member.roles.set([role], 'automute upon join, type 1');
|
||||
|
||||
} else if (infraction.data.muteType === 2) {
|
||||
|
||||
@ -100,7 +101,8 @@ class UtilityHook extends Observer {
|
||||
const settings = await guild.settings();
|
||||
const setting = settings.stickyrole;
|
||||
if (!setting.roles.length || guild.premium < 1) return;
|
||||
if (!guild.members.me.permissions.has('ManageRoles')) return;
|
||||
const me = await guild.resolveMember(this.client.user);
|
||||
if (!me.permissions.has('ManageRoles')) return;
|
||||
|
||||
const data = await this.client.storageManager.mongodb.role_cache.findOne({ guild: guild.id, member: member.id });
|
||||
if (!data) return;
|
||||
@ -115,7 +117,8 @@ class UtilityHook extends Observer {
|
||||
const settings = await guild.settings();
|
||||
const setting = settings.autorole;
|
||||
if (!setting.enabled) return;
|
||||
if (!guild.members.me.permissions.has('ManageRoles')) return;
|
||||
const me = await guild.resolveMember(this.client.user);
|
||||
if (!me.permissions.has('ManageRoles')) return;
|
||||
|
||||
const _roles = await guild.resolveRoles(setting.roles);
|
||||
const roles = _roles.map((r) => r.id);
|
||||
@ -153,7 +156,8 @@ class UtilityHook extends Observer {
|
||||
|
||||
const { guild } = invite;
|
||||
if (!guild) return;
|
||||
if (!guild.members.me.permissions.has('ManageGuild')) return;
|
||||
const me = await this.client.resolver.resolveMember(this.client.user, null, guild);
|
||||
if (!me.permissions.has('ManageGuild')) return;
|
||||
if (!guild.invites) guild.invites = await guild.fetchInvites();
|
||||
guild.invites.set(invite.code, invite);
|
||||
|
||||
@ -163,7 +167,8 @@ class UtilityHook extends Observer {
|
||||
|
||||
const { guild } = invite;
|
||||
if (!guild) return;
|
||||
if (!guild.members.me.permissions.has('ManageGuild')) return;
|
||||
const me = await this.client.resolver.resolveMember(this.client.user, null, guild);
|
||||
if (!me.permissions.has('ManageGuild')) return;
|
||||
if (!guild.invites) guild.invites = await guild.fetchInvites();
|
||||
guild.invites.delete(invite.code);
|
||||
|
||||
@ -203,7 +208,8 @@ class UtilityHook extends Observer {
|
||||
!selfrole.channel || selfrole.channel !== channel.id ||
|
||||
!selfrole.roles.length) return;
|
||||
|
||||
const missing = guild.members.me.permissions.missing(['ManageRoles']);
|
||||
const me = await guild.resolveMember(this.client.user);
|
||||
const missing = me.permissions.missing(['ManageRoles']);
|
||||
if (missing.length)
|
||||
return this.client.emit('utilityError', { guild, utility: 'selfrole', reason: 'UTILITY_SELFROLE_PERMS', params: { missing: missing.join(', ') } });
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
const { Util } = require("../../../../utilities");
|
||||
const { Setting, CommandOption } = require("../../../interfaces");
|
||||
const { Setting } = require("../../../interfaces");
|
||||
|
||||
class IgnoreSetting extends Setting {
|
||||
|
||||
@ -19,7 +19,7 @@ class IgnoreSetting extends Setting {
|
||||
bypass: { ARRAY: 'GUILD_ROLE' }
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
name: 'list',
|
||||
description: 'List to act on',
|
||||
type: 'STRING',
|
||||
@ -27,9 +27,9 @@ class IgnoreSetting extends Setting {
|
||||
{ name: 'channels', value: 'channels' },
|
||||
{ name: 'bypass', value: 'bypass' }
|
||||
],
|
||||
dependsOn: ['method']
|
||||
}),
|
||||
new CommandOption({
|
||||
dependsOn: ['method']//, valueAsAlias: true, flag: true
|
||||
},
|
||||
{
|
||||
name: 'method',
|
||||
description: 'Method of modifying',
|
||||
type: 'STRING',
|
||||
@ -39,8 +39,8 @@ class IgnoreSetting extends Setting {
|
||||
{ name: 'set', value: 'set' },
|
||||
{ name: 'reset', value: 'reset' },
|
||||
],
|
||||
dependsOn: ['list']
|
||||
})
|
||||
dependsOn: ['list']//, valueAsAlias: true, flag: true
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { Setting, CommandOption } = require('../../../interfaces/');
|
||||
const { Setting } = require('../../../interfaces/');
|
||||
|
||||
class PermissionType extends Setting {
|
||||
|
||||
@ -16,15 +16,16 @@ class PermissionType extends Setting {
|
||||
type: 'STRING'
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
type: 'STRING',
|
||||
name: 'type',
|
||||
description: 'Where to read permissions from',
|
||||
choices: [
|
||||
{ name: 'discord', value: 'discord' },
|
||||
{ name: 'both', value: 'both' },
|
||||
{ name: 'grant', value: 'grant' }
|
||||
]
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
const { Util } = require("../../../../utilities");
|
||||
const { Setting, CommandOption } = require("../../../interfaces");
|
||||
const { Setting } = require("../../../interfaces");
|
||||
|
||||
class ProtectionSetting extends Setting {
|
||||
|
||||
@ -21,7 +21,7 @@ class ProtectionSetting extends Setting {
|
||||
enabled: 'BOOLEAN'
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
type: 'STRING',
|
||||
name: 'type',
|
||||
description: 'Select protection type',
|
||||
@ -30,8 +30,8 @@ class ProtectionSetting extends Setting {
|
||||
{ name: 'position', value: 'position' }
|
||||
],
|
||||
dependsOn: []
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
type: 'STRING',
|
||||
name: 'roles',
|
||||
description: 'Method of modifying',
|
||||
@ -42,12 +42,12 @@ class ProtectionSetting extends Setting {
|
||||
{ name: 'reset', value: 'reset' },
|
||||
],
|
||||
dependsOn: []
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
type: 'BOOLEAN',
|
||||
name: 'enabled',
|
||||
description: 'Whether setting is active or not'
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { Setting, CommandOption } = require("../../../interfaces");
|
||||
const { Setting } = require("../../../interfaces");
|
||||
|
||||
|
||||
class SilentSetting extends Setting {
|
||||
@ -17,11 +17,11 @@ class SilentSetting extends Setting {
|
||||
enabled: 'BOOLEAN'
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN',
|
||||
{
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true,
|
||||
name: 'enabled',
|
||||
description: 'Toggle state'
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -0,0 +1,55 @@
|
||||
const { Setting } = require("../../../interfaces");
|
||||
|
||||
class TextCommands extends Setting {
|
||||
|
||||
constructor(client) {
|
||||
super(client, {
|
||||
name: 'textcommands',
|
||||
display: 'Text Commands',
|
||||
description: 'Message based commands configuration',
|
||||
module: 'administration',
|
||||
default: {
|
||||
enabled: false,
|
||||
prefix: '-'
|
||||
},
|
||||
commandOptions: [
|
||||
{
|
||||
name: 'enabled',
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true,
|
||||
description: 'Toggle enable state'
|
||||
},
|
||||
{
|
||||
name: 'prefix',
|
||||
type: 'STRING',
|
||||
description: 'Prefix to use'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
async execute(invoker, { enabled, prefix }, setting) {
|
||||
|
||||
if (enabled) setting.enabled = enabled.value;
|
||||
if (prefix) setting.prefix = prefix.value;
|
||||
return { index: 'SETTING_SUCCESS_ALT' };
|
||||
|
||||
}
|
||||
|
||||
fields(guild) {
|
||||
const setting = guild._settings[this.name];
|
||||
return [{
|
||||
name: 'GENERAL_STATUS',
|
||||
value: guild.format('GENERAL_STATE', {
|
||||
bool: setting.enabled
|
||||
}, { code: true }),
|
||||
inline: true
|
||||
}, {
|
||||
name: 'GENERAL_PREFIX',
|
||||
value: setting.prefix,
|
||||
inline: true
|
||||
}];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = TextCommands;
|
@ -1,4 +1,4 @@
|
||||
const { Setting, CommandOption } = require("../../../interfaces");
|
||||
const { Setting } = require("../../../interfaces");
|
||||
const Infractions = [
|
||||
'NOTE',
|
||||
'WARN',
|
||||
@ -57,13 +57,13 @@ class DmInfraction extends Setting {
|
||||
}
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
name: 'message',
|
||||
description: 'Set the message for an infraction type',
|
||||
type: 'STRING',
|
||||
dependsOn: ['infraction']
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
name: 'infraction',
|
||||
description: 'Choose the infraction for which to modify the message',
|
||||
type: 'STRING',
|
||||
@ -71,8 +71,8 @@ class DmInfraction extends Setting {
|
||||
return { name: inf, value: inf };
|
||||
}),
|
||||
dependsOn: ['message']
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
name: 'infractions',
|
||||
description: 'Modify the list of infractions that are sent',
|
||||
type: 'STRING',
|
||||
@ -82,15 +82,15 @@ class DmInfraction extends Setting {
|
||||
{ name: 'set', value: 'set' },
|
||||
{ name: 'reset', value: 'reset' },
|
||||
]
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
name: 'enabled',
|
||||
description: 'Enable or disable the sending of infractions in DMs',
|
||||
type: 'BOOLEAN'
|
||||
}),
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true
|
||||
},
|
||||
{
|
||||
name: 'anonymous',
|
||||
type: 'BOOLEAN',
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true,
|
||||
description: 'Whether who issued the infraction is shown in moderation logs'
|
||||
}
|
||||
]
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { Setting, CommandOption } = require("../../../interfaces");
|
||||
const { Setting } = require("../../../interfaces");
|
||||
|
||||
class MessageLog extends Setting {
|
||||
|
||||
@ -22,16 +22,16 @@ class MessageLog extends Setting {
|
||||
types: { ARRAY: 'ERROR_TYPES' } // TODO: Error types
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
name: 'channel',
|
||||
description: 'Channel in which to output logs',
|
||||
type: 'TEXT_CHANNEL'
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
name: 'enabled',
|
||||
description: 'Toggle logging on or off',
|
||||
type: 'BOOLEAN'
|
||||
})
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
@ -39,14 +39,12 @@ class MessageLog extends Setting {
|
||||
|
||||
async execute(interaction, opts, setting) {
|
||||
|
||||
const { guild } = interaction;
|
||||
|
||||
if (opts.enabled?.value === false) setting.channel = null;
|
||||
|
||||
if (opts.channel) {
|
||||
|
||||
const channel = opts.channel.value;
|
||||
const perms = channel.permissionsFor(guild.members.me);
|
||||
const perms = channel.permissionsFor(this.client.user);
|
||||
const missingPerms = perms.missing(['ViewChannel', 'EmbedLinks', 'SendMessages']);
|
||||
if (missingPerms.length) return {
|
||||
error: true,
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { Setting, CommandOption } = require("../../../interfaces");
|
||||
const { Setting } = require("../../../interfaces");
|
||||
|
||||
class MemberLog extends Setting {
|
||||
|
||||
@ -21,26 +21,26 @@ class MemberLog extends Setting {
|
||||
leave: 'STRING'
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
name: 'enabled',
|
||||
description: 'Enable/disable member logs',
|
||||
type: 'BOOLEAN'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true
|
||||
},
|
||||
{
|
||||
name: 'channel',
|
||||
description: 'Select the log output channel',
|
||||
type: 'TEXT_CHANNEL'
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
name: 'join',
|
||||
description: 'Set the join message',
|
||||
type: 'STRING'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'STRING', flag: true
|
||||
},
|
||||
{
|
||||
name: 'leave',
|
||||
description: 'Set the leave message',
|
||||
type: 'STRING'
|
||||
})
|
||||
type: 'STRING', flag: true
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { Setting, CommandOption } = require("../../../interfaces");
|
||||
const { Setting } = require("../../../interfaces");
|
||||
const { Util } = require('../../../../utilities');
|
||||
|
||||
class MessageLog extends Setting {
|
||||
@ -26,22 +26,22 @@ class MessageLog extends Setting {
|
||||
attachments: 'BOOLEAN'
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
name: 'channel',
|
||||
description: 'Channel in which to output logs',
|
||||
type: 'TEXT_CHANNEL'
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
name: 'enabled',
|
||||
description: 'Toggle logging on or off',
|
||||
type: 'BOOLEAN'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true
|
||||
},
|
||||
{
|
||||
name: 'attachments',
|
||||
description: 'Whether to log attachments. PREMIUM TIER 1',
|
||||
type: 'BOOLEAN'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true
|
||||
},
|
||||
{
|
||||
name: 'list',
|
||||
description: 'Select which list to modify',
|
||||
type: 'STRING',
|
||||
@ -50,8 +50,8 @@ class MessageLog extends Setting {
|
||||
{ name: 'ignore', value: 'ignore' },
|
||||
],
|
||||
dependsOn: ['method']
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
name: 'method',
|
||||
description: 'Select which modification method to use',
|
||||
type: 'STRING',
|
||||
@ -62,7 +62,7 @@ class MessageLog extends Setting {
|
||||
{ name: 'reset', value: 'reset' },
|
||||
],
|
||||
dependsOn: ['list']
|
||||
}),
|
||||
},
|
||||
]
|
||||
});
|
||||
|
||||
@ -82,7 +82,7 @@ class MessageLog extends Setting {
|
||||
if (opts.channel) {
|
||||
|
||||
const channel = opts.channel.value;
|
||||
const perms = channel.permissionsFor(guild.members.me);
|
||||
const perms = channel.permissionsFor(this.client.user);
|
||||
const missingPerms = perms.missing(['ViewChannel', 'EmbedLinks', 'SendMessages', 'ManageWebhooks']);
|
||||
if (missingPerms.length) return {
|
||||
error: true,
|
||||
|
@ -1,5 +1,5 @@
|
||||
const { Infractions } = require("../../../../constants/Constants");
|
||||
const { Setting, CommandOption } = require("../../../interfaces");
|
||||
const { Setting } = require("../../../interfaces");
|
||||
|
||||
// [
|
||||
// 'NOTE',
|
||||
@ -44,17 +44,17 @@ class ModerationLog extends Setting {
|
||||
}
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
name: 'enabled',
|
||||
description: 'Enable/disable member logs',
|
||||
type: 'BOOLEAN'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true
|
||||
},
|
||||
{
|
||||
name: 'channel',
|
||||
description: 'Logging channel',
|
||||
type: 'TEXT_CHANNEL'
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
name: 'infractions',
|
||||
description: 'Modify the list of infractions that are sent',
|
||||
type: 'STRING',
|
||||
@ -64,10 +64,10 @@ class ModerationLog extends Setting {
|
||||
{ name: 'set', value: 'set' },
|
||||
{ name: 'reset', value: 'reset' },
|
||||
]
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'anonymous',
|
||||
type: 'BOOLEAN',
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true,
|
||||
description: 'Whether who issued the infraction is shown in moderation logs'
|
||||
}
|
||||
]
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { Setting, CommandOption } = require("../../../interfaces");
|
||||
const { Setting } = require("../../../interfaces");
|
||||
|
||||
class Nicknames extends Setting {
|
||||
|
||||
@ -16,16 +16,16 @@ class Nicknames extends Setting {
|
||||
channel: 'GUILD_TEXT'
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
name: 'enabled',
|
||||
description: 'Toggle logging on or off',
|
||||
type: 'BOOLEAN'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true
|
||||
},
|
||||
{
|
||||
name: 'channel',
|
||||
type: 'TEXT_CHANNEL',
|
||||
description: 'Set the channel for nickname logging'
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { Setting, CommandOption } = require("../../../interfaces");
|
||||
const { Setting } = require("../../../interfaces");
|
||||
|
||||
class Voice extends Setting {
|
||||
|
||||
@ -15,16 +15,16 @@ class Voice extends Setting {
|
||||
channel: 'GUILD_TEXT'
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
name: 'enabled',
|
||||
description: 'Toggle logging on or off',
|
||||
type: 'BOOLEAN'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true
|
||||
},
|
||||
{
|
||||
name: 'channel',
|
||||
type: 'TEXT_CHANNEL',
|
||||
description: 'Set the channel for voice join/leave logging'
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
const { Util } = require("../../../../utilities");
|
||||
const { Setting, CommandOption } = require("../../../interfaces");
|
||||
const { Setting } = require("../../../interfaces");
|
||||
const Infractions = [
|
||||
'WARN',
|
||||
'MUTE',
|
||||
@ -17,7 +17,7 @@ class Automod extends Setting {
|
||||
super(client, {
|
||||
name: 'automod',
|
||||
description: 'Define automatic infraction escalation',
|
||||
display: 'Automatic Moderation',
|
||||
display: 'Automod',
|
||||
module: 'moderation',
|
||||
default: {
|
||||
enabled: false,
|
||||
@ -36,17 +36,17 @@ class Automod extends Setting {
|
||||
}
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
name: 'enabled',
|
||||
description: 'Toggle state',
|
||||
type: 'BOOLEAN'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true
|
||||
},
|
||||
{
|
||||
name: 'useprevious',
|
||||
description: 'Use the previously passed threshold if the point total lands between two thresholds',
|
||||
type: 'BOOLEAN'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true
|
||||
},
|
||||
{
|
||||
name: 'threshold',
|
||||
description: 'The threshold at which to issue an infraction',
|
||||
type: 'INTEGER',
|
||||
@ -54,8 +54,8 @@ class Automod extends Setting {
|
||||
maximum: 100,
|
||||
dependsOn: ['infraction', 'length'],
|
||||
dependsOnMode: 'OR'
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
name: 'infraction',
|
||||
description: 'The type of infraction to issue',
|
||||
type: 'STRING',
|
||||
@ -63,13 +63,13 @@ class Automod extends Setting {
|
||||
return { name: inf, value: inf };
|
||||
}),
|
||||
dependsOn: ['threshold']
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
name: 'length',
|
||||
description: 'The duration for a tempban or a mute',
|
||||
type: 'TIME',
|
||||
dependsOn: ['threshold']
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { Setting, CommandOption } = require("../../../interfaces");
|
||||
const { Setting } = require("../../../interfaces");
|
||||
const { Util } = require("../../../../utilities");
|
||||
|
||||
class Grantable extends Setting {
|
||||
@ -18,12 +18,12 @@ class Grantable extends Setting {
|
||||
enabled: 'BOOLEAN'
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN',
|
||||
{
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true,
|
||||
name: 'enabled',
|
||||
description: 'Toggle state'
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
name: 'roles',
|
||||
description: '',
|
||||
type: 'STRING',
|
||||
@ -33,7 +33,7 @@ class Grantable extends Setting {
|
||||
{ name: 'set', value: 'set' },
|
||||
{ name: 'reset', value: 'reset' },
|
||||
]
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { FilterSetting, CommandOption } = require('../../../interfaces/');
|
||||
const { FilterSetting } = require('../../../interfaces/');
|
||||
const { Util } = require("../../../../utilities");
|
||||
|
||||
class InviteFilterSetting extends FilterSetting {
|
||||
@ -36,7 +36,7 @@ class InviteFilterSetting extends FilterSetting {
|
||||
actions: { ARRAY: 'ACTION' }
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
type: 'STRING',
|
||||
name: 'method',
|
||||
description: 'Select which modification method to use',
|
||||
@ -49,8 +49,8 @@ class InviteFilterSetting extends FilterSetting {
|
||||
{ name: 'list', value: 'list' }
|
||||
],
|
||||
dependsOn: ['list']
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
type: 'STRING',
|
||||
name: 'list',
|
||||
description: 'Select which list to modify',
|
||||
@ -61,17 +61,17 @@ class InviteFilterSetting extends FilterSetting {
|
||||
{ name: 'actions', value: 'actions' },
|
||||
],
|
||||
dependsOn: ['method']
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN',
|
||||
},
|
||||
{
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true,
|
||||
name: 'enabled',
|
||||
description: 'Toggle enable state'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN',
|
||||
},
|
||||
{
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true,
|
||||
name: 'silent',
|
||||
description: 'Toggle silent operation'
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { FilterSetting, CommandOption } = require('../../../interfaces/');
|
||||
const { FilterSetting } = require('../../../interfaces/');
|
||||
const { Util } = require("../../../../utilities");
|
||||
const { FilterPresets } = require('../../../../constants');
|
||||
|
||||
@ -43,7 +43,7 @@ class LinkFilterSetting extends FilterSetting {
|
||||
actions: { ARRAY: 'ACTION' }
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
type: 'STRING',
|
||||
name: 'method',
|
||||
description: 'Select which modification method to use',
|
||||
@ -56,8 +56,8 @@ class LinkFilterSetting extends FilterSetting {
|
||||
{ name: 'list', value: 'list' }
|
||||
],
|
||||
dependsOn: ['list']
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
type: 'STRING',
|
||||
name: 'list',
|
||||
description: 'Select which list to modify',
|
||||
@ -71,22 +71,22 @@ class LinkFilterSetting extends FilterSetting {
|
||||
{ name: 'presets', value: 'presets' }
|
||||
],
|
||||
dependsOn: ['method']
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN',
|
||||
},
|
||||
{
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true,
|
||||
name: 'enabled',
|
||||
description: 'Toggle enable state'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN',
|
||||
},
|
||||
{
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true,
|
||||
name: 'whitelist',
|
||||
description: 'Toggle whitelist mode'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN',
|
||||
},
|
||||
{
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true,
|
||||
name: 'silent',
|
||||
description: 'Toggle silent operation'
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { FilterSetting, CommandOption } = require('../../../interfaces/');
|
||||
const { FilterSetting } = require('../../../interfaces/');
|
||||
const { Util } = require("../../../../utilities");
|
||||
|
||||
class MentionFilter extends FilterSetting {
|
||||
@ -28,27 +28,27 @@ class MentionFilter extends FilterSetting {
|
||||
ignore: { ARRAY: 'GUILD_TEXT' }
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
name: 'enabled',
|
||||
description: 'Toggle state',
|
||||
type: 'BOOLEAN'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true
|
||||
},
|
||||
{
|
||||
name: 'silent',
|
||||
description: 'Whether the bot will respond in chat',
|
||||
type: 'BOOLEAN'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true
|
||||
},
|
||||
{
|
||||
name: 'unique',
|
||||
description: 'Mentions for the same user count as one',
|
||||
type: 'BOOLEAN'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
description: 'How many mentions are allowed in a message',
|
||||
type: 'INTEGER'
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
type: 'STRING',
|
||||
name: 'method',
|
||||
description: 'Select which modification method to use',
|
||||
@ -61,8 +61,8 @@ class MentionFilter extends FilterSetting {
|
||||
{ name: 'list', value: 'list' }
|
||||
],
|
||||
dependsOn: ['list']
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
type: 'STRING',
|
||||
name: 'list',
|
||||
description: 'Select which list to modify',
|
||||
@ -72,7 +72,7 @@ class MentionFilter extends FilterSetting {
|
||||
{ name: 'actions', value: 'actions' },
|
||||
],
|
||||
dependsOn: ['method']
|
||||
}),
|
||||
},
|
||||
]
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { Setting, CommandOption } = require("../../../interfaces");
|
||||
const { Setting } = require("../../../interfaces");
|
||||
const { Util } = require("../../../../utilities");
|
||||
|
||||
const INFRACTIONS = ['WARN', 'MUTE', 'KICK', 'SOFTBAN', 'BAN', 'VCMUTE', 'VCKICK', 'VCBAN'];
|
||||
@ -37,22 +37,22 @@ class ModerationPoints extends Setting {
|
||||
multiplier: false
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
name: 'points',
|
||||
description: 'Point value',
|
||||
type: 'INTEGER',
|
||||
dependsOn: ['associate', 'type'],
|
||||
dependsOn: ['associate', 'infraction'],
|
||||
dependsOnMode: 'OR',
|
||||
minimum: 0,
|
||||
maximum: 100
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
name: 'expire',
|
||||
description: 'How long the points are counted for',
|
||||
type: 'TIME',
|
||||
dependsOn: ['type']
|
||||
}),
|
||||
new CommandOption({
|
||||
dependsOn: ['infraction']
|
||||
},
|
||||
{
|
||||
name: 'infraction',
|
||||
description: 'Type of infraction',
|
||||
type: 'STRING',
|
||||
@ -61,36 +61,36 @@ class ModerationPoints extends Setting {
|
||||
}),
|
||||
dependsOn: ['points', 'expire'],
|
||||
dependsOnMode: 'OR'
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
name: 'enabled',
|
||||
description: 'Toggle on or off',
|
||||
type: 'BOOLEAN'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true
|
||||
},
|
||||
{
|
||||
name: 'multiplier',
|
||||
description: 'Use points as a multiplier for the expiration',
|
||||
type: 'BOOLEAN'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true
|
||||
},
|
||||
{
|
||||
name: 'associate',
|
||||
description: 'Associate a word within a reason to a point value',
|
||||
type: 'STRING',
|
||||
dependsOn: ['points']
|
||||
}),
|
||||
dependsOn: ['points'], flag: true
|
||||
},
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
async execute(interaction, opts, setting) {
|
||||
|
||||
const { points, type, enabled, associate, expire, multiplier } = opts;
|
||||
const { points, infraction, enabled, associate, expire, multiplier } = opts;
|
||||
|
||||
if (multiplier) setting.multiplier = multiplier.value;
|
||||
if (enabled) setting.enabled = enabled.value;
|
||||
if (expire) setting.expirations[type.value] = expire.value * 1000;
|
||||
if (expire) setting.expirations[infraction.value] = expire.value * 1000;
|
||||
if (associate) setting.associations[associate.value.toLowerCase()] = points.value;
|
||||
if (type && points) setting.points[type.value] = points.value;
|
||||
if (infraction && points) setting.points[infraction.value] = points.value;
|
||||
|
||||
return { index: 'SETTING_SUCCESS_ALT' };
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { Setting, CommandOption } = require('../../../interfaces/');
|
||||
const { Setting } = require('../../../interfaces/');
|
||||
const { inspect } = require('util');
|
||||
const { Util } = require("../../../../utilities");
|
||||
|
||||
@ -52,28 +52,28 @@ class MuteSetting extends Setting {
|
||||
permanent: 'BOOLEAN'
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
type: 'STRING',
|
||||
{
|
||||
type: 'STRING', flag: true,
|
||||
name: 'create',
|
||||
description: 'Create a mute role, mutually exclusive with role'
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
type: 'ROLE',
|
||||
name: 'role',
|
||||
description: 'Select the role to use for mutes, mutually exclusive with create'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'TIME',
|
||||
},
|
||||
{
|
||||
type: 'TIME', flag: true,
|
||||
name: 'default',
|
||||
description: 'Set the default duration for mutes'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN',
|
||||
},
|
||||
{
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true,
|
||||
name: 'permanent',
|
||||
description: 'Whether to allow permanent mutes or fall back to default mute duration'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
type: 'INTEGER', flag: true,
|
||||
name: 'type',
|
||||
description: 'Select the type of mute behaviour',
|
||||
choices: [ {
|
||||
@ -89,7 +89,7 @@ class MuteSetting extends Setting {
|
||||
name: 'Type 3 (Use Discord timeouts)',
|
||||
value: 3
|
||||
} ]
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
@ -187,7 +187,8 @@ class MuteSetting extends Setting {
|
||||
return role;
|
||||
};
|
||||
|
||||
const hasPermission = guild.members.me.permissions.has('ManageRoles');
|
||||
const me = await guild.resolveMember(this.client.user);
|
||||
const hasPermission = me.permissions.has('ManageRoles');
|
||||
if (!hasPermission) return {
|
||||
index: 'SETTING_MUTE_ROLEMISSINGPERMISSION',
|
||||
error: true
|
||||
@ -251,7 +252,7 @@ class MuteSetting extends Setting {
|
||||
|
||||
for (const channel of channels.values()) {
|
||||
|
||||
if (!channel.permissionsFor(guild.members.me).has('ManageRoles')) {
|
||||
if (!channel.permissionsFor(this.client.user).has('ManageRoles')) {
|
||||
issues.push({ type: 'permission', channel: channel.name });
|
||||
continue;
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ class StaffSetting extends Setting {
|
||||
}, {
|
||||
name: 'enabled',
|
||||
description: 'Whether the staff command is in use',
|
||||
type: 'BOOLEAN'
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* eslint-disable camelcase */
|
||||
const { FilterSetting, CommandOption } = require('../../../interfaces/');
|
||||
const { FilterSetting } = require('../../../interfaces/');
|
||||
const { Util } = require("../../../../utilities");
|
||||
const { FilterPresets } = require('../../../../constants');
|
||||
|
||||
@ -44,7 +44,7 @@ class WordFilterSetting extends FilterSetting {
|
||||
actions: { ARRAY: 'ACTION' }
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
type: 'STRING',
|
||||
name: 'method',
|
||||
description: 'Select which modification method to use',
|
||||
@ -57,8 +57,8 @@ class WordFilterSetting extends FilterSetting {
|
||||
{ name: 'list', value: 'list' }
|
||||
],
|
||||
dependsOn: ['list']
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
type: 'STRING',
|
||||
name: 'list',
|
||||
description: 'Select which list to modify',
|
||||
@ -73,17 +73,17 @@ class WordFilterSetting extends FilterSetting {
|
||||
{ name: 'actions', value: 'actions' },
|
||||
],
|
||||
dependsOn: ['method']
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN',
|
||||
},
|
||||
{
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true,
|
||||
name: 'enabled',
|
||||
description: 'Toggle enable state'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN',
|
||||
},
|
||||
{
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true,
|
||||
name: 'silent',
|
||||
description: 'Toggle silent operation'
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { FilterSetting, CommandOption } = require('../../../interfaces/');
|
||||
const { FilterSetting } = require('../../../interfaces/');
|
||||
const { Util } = require("../../../../utilities");
|
||||
|
||||
class WordWatcher extends FilterSetting {
|
||||
@ -10,6 +10,7 @@ class WordWatcher extends FilterSetting {
|
||||
description: 'Flag messages for potentially offensive content instead of deleting automatically',
|
||||
module: 'moderation',
|
||||
default: {
|
||||
enabled: false,
|
||||
channel: null,
|
||||
words: [],
|
||||
regex: [],
|
||||
@ -18,7 +19,7 @@ class WordWatcher extends FilterSetting {
|
||||
actions: []
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
type: 'STRING',
|
||||
name: 'method',
|
||||
description: 'Select which modification method to use',
|
||||
@ -31,8 +32,8 @@ class WordWatcher extends FilterSetting {
|
||||
{ name: 'list', value: 'list' }
|
||||
],
|
||||
dependsOn: ['list']
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
type: 'STRING',
|
||||
name: 'list',
|
||||
description: 'Select which list to modify',
|
||||
@ -44,12 +45,17 @@ class WordWatcher extends FilterSetting {
|
||||
{ name: 'actions', value: 'actions' },
|
||||
],
|
||||
dependsOn: ['method']
|
||||
}),
|
||||
new CommandOption({
|
||||
},
|
||||
{
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true,
|
||||
name: 'enabled',
|
||||
description: 'Toggle enable state'
|
||||
},
|
||||
{
|
||||
name: 'channel',
|
||||
type: 'TEXT_CHANNEL',
|
||||
description: 'Where to output flagged messages'
|
||||
})
|
||||
description: 'Where to output flagged messages',
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { Setting, CommandOption } = require("../../../interfaces");
|
||||
const { Setting } = require("../../../interfaces");
|
||||
const { Util } = require("../../../../utilities");
|
||||
|
||||
class Autorole extends Setting {
|
||||
@ -18,7 +18,7 @@ class Autorole extends Setting {
|
||||
enabled: 'BOOLEAN'
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
type: 'STRING',
|
||||
name: 'roles',
|
||||
description: 'Modification method for roles',
|
||||
@ -30,12 +30,12 @@ class Autorole extends Setting {
|
||||
{ name: 'edit', value: 'edit' },
|
||||
{ name: 'list', value: 'list' }
|
||||
]
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN',
|
||||
},
|
||||
{
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true,
|
||||
name: 'enabled',
|
||||
description: 'Toggle enable state'
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { Setting, CommandOption } = require("../../../interfaces");
|
||||
const { Setting } = require("../../../interfaces");
|
||||
const { Util } = require("../../../../utilities");
|
||||
|
||||
class Autorole extends Setting {
|
||||
@ -18,7 +18,7 @@ class Autorole extends Setting {
|
||||
enabled: 'BOOLEAN'
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
type: 'STRING',
|
||||
name: 'roles',
|
||||
description: 'Modification method for roles',
|
||||
@ -30,12 +30,12 @@ class Autorole extends Setting {
|
||||
{ name: 'edit', value: 'edit' },
|
||||
{ name: 'list', value: 'list' }
|
||||
]
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN',
|
||||
},
|
||||
{
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true,
|
||||
name: 'enabled',
|
||||
description: 'Toggle enable state'
|
||||
})
|
||||
}
|
||||
],
|
||||
premium: 1
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { Setting, CommandOption } = require("../../../interfaces");
|
||||
const { Setting } = require("../../../interfaces");
|
||||
|
||||
class Autorole extends Setting {
|
||||
|
||||
@ -17,16 +17,16 @@ class Autorole extends Setting {
|
||||
enabled: 'BOOLEAN'
|
||||
},
|
||||
commandOptions: [
|
||||
new CommandOption({
|
||||
{
|
||||
type: 'STRING',
|
||||
name: 'message',
|
||||
description: 'Set the welcome message'
|
||||
}),
|
||||
new CommandOption({
|
||||
type: 'BOOLEAN',
|
||||
},
|
||||
{
|
||||
type: 'BOOLEAN', flag: true, valueOptional: true, defaultValue: true,
|
||||
name: 'enabled',
|
||||
description: 'Toggle enable state'
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
@ -53,7 +53,8 @@ class AddroleInfraction extends Infraction {
|
||||
const { grantable } = await this.guild.settings();
|
||||
|
||||
let filtered = [];
|
||||
const { highest: clientHighest } = this.guild.members.me.roles;
|
||||
const me = await this.guild.resolveMember(this.client.user);
|
||||
const { highest: clientHighest } = me.roles;
|
||||
filtered = this.data.roles.filter((r) => r.comparePositionTo(clientHighest) < 0);
|
||||
if (filtered.length === 0) {
|
||||
return super._fail('C_ADDROLE_ROLEHIERARCHYBOT');
|
||||
|
@ -48,7 +48,7 @@ class BanInfraction extends Infraction {
|
||||
const callbacks = this.client.moderationManager.callbacks.filter((c) => c.infraction.type === 'BAN'
|
||||
&& c.infraction.target === this.target.id);
|
||||
|
||||
if (callbacks.size > 0) callbacks.map((c) => this.client.moderationManager.removeCallback(c.infraction));
|
||||
if (callbacks.size > 0) callbacks.map((c) => this.client.moderationManager.removeCallback(c.infraction, true));
|
||||
|
||||
return this._succeed();
|
||||
|
||||
@ -56,13 +56,14 @@ class BanInfraction extends Infraction {
|
||||
|
||||
async verify() {
|
||||
|
||||
if (this.target instanceof GuildMember) {
|
||||
if (!this.member.bannable) return super._fail('C_BAN_CANNOTBEBANNED');
|
||||
}
|
||||
const member = await this.guild.resolveMember(this.target.id);
|
||||
if (member && !member.bannable)
|
||||
return super._fail('C_BAN_CANNOTBEBANNED');
|
||||
|
||||
|
||||
let alreadyBanned = null;
|
||||
try {
|
||||
alreadyBanned = await this.guild.bans.fetch(this.member.id);
|
||||
alreadyBanned = await this.guild.bans.fetch(this.target.id);
|
||||
} catch (e) { } //eslint-disable-line no-empty
|
||||
|
||||
if (alreadyBanned) return super._fail('C_BAN_ALREADYBANNED');
|
||||
|
@ -140,9 +140,9 @@ class LockdownInfraction extends Infraction {
|
||||
|
||||
async verify() {
|
||||
|
||||
const perms = this.target.permissionsFor(this.guild.members.me);
|
||||
const perms = this.target.permissionsFor(this.client.user);
|
||||
const missing = perms.missing(['ManageRoles', 'SendMessages', 'AddReactions']);
|
||||
if(missing.length) return this._fail('INFRACTION_LOCKDOWN_MISSING_PERMS', { missing: missing.join('**, **') });
|
||||
if (missing.length) return this._fail(this.guild.format('INFRACTION_LOCKDOWN_MISSING_PERMS', { missing: missing.join('**, **') }), null, true);
|
||||
return super._verify();
|
||||
|
||||
}
|
||||
|
@ -48,6 +48,8 @@ class MuteInfraction extends Infraction {
|
||||
role = await this.client.resolver.resolveRole(setting.role, true, this.guild);
|
||||
}
|
||||
|
||||
const me = await this.guild.resolveMember(this.client.user);
|
||||
|
||||
let removed = [];
|
||||
switch (setting.type) {
|
||||
case 0:
|
||||
@ -60,12 +62,12 @@ class MuteInfraction extends Infraction {
|
||||
break;
|
||||
case 1:
|
||||
removed = this.member.roles.cache.filter((r) => !r.managed &&
|
||||
r.comparePositionTo(this.guild.members.me.roles.highest) < 0 &&
|
||||
r.comparePositionTo(me.roles.highest) < 0 &&
|
||||
r.id !== this.guild.id);
|
||||
try {
|
||||
await this.member.roles.set([
|
||||
...this.member.roles.cache.filter((r) => r.managed ||
|
||||
r.comparePositionTo(this.guild.members.me.roles.highest) >= 0 ||
|
||||
r.comparePositionTo(me.roles.highest) >= 0 ||
|
||||
r.id === this.guild.id).values(),
|
||||
role
|
||||
], this._reason);
|
||||
@ -76,11 +78,11 @@ class MuteInfraction extends Infraction {
|
||||
break;
|
||||
case 2:
|
||||
removed = this.member.roles.cache.filter((r) => !r.managed &&
|
||||
r.comparePositionTo(this.guild.members.me.roles.highest) < 0 &&
|
||||
r.comparePositionTo(me.roles.highest) < 0 &&
|
||||
r.id !== this.guild.id);
|
||||
try {
|
||||
await this.member.roles.set(this.member.roles.cache.filter((r) => r.managed ||
|
||||
r.comparePositionTo(this.guild.members.me.roles.highest) >= 0 ||
|
||||
r.comparePositionTo(me.roles.highest) >= 0 ||
|
||||
r.id === this.guild.id), this._reason);
|
||||
} catch (error) {
|
||||
this.client.logger.error(`Mute infraction failed to calculate removeable roles, might want to check this out.\n${error.stack || error}`);
|
||||
@ -96,6 +98,8 @@ class MuteInfraction extends Infraction {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.member.voice.channel) await this.member.voice.disconnect(this._reason);
|
||||
|
||||
this.data = {
|
||||
removedRoles: removed.map((r) => r.id),
|
||||
muteType: setting.type,
|
||||
@ -107,7 +111,7 @@ class MuteInfraction extends Infraction {
|
||||
|
||||
if (callback) {
|
||||
this.data.removedRoles = [...new Set([...this.data.removedRoles, ...callback.infraction.data.removedRoles])];
|
||||
this.client.moderationManager.removeCallback(callback.infraction);
|
||||
this.client.moderationManager.removeCallback(callback.infraction, true);
|
||||
}
|
||||
|
||||
// if(callbacks.size > 0) callbacks.map((c) => this.client.moderationManager._removeExpiration(c));
|
||||
@ -134,10 +138,12 @@ class MuteInfraction extends Infraction {
|
||||
return this._fail('COMMAND_MUTE_INVALIDMUTEROLE', true);
|
||||
}
|
||||
}
|
||||
const me = await this.guild.resolveMember(this.client.user);
|
||||
if (settings.mute.type === 3) {
|
||||
if (this.guild.members.me.permissions.missing('ModerateMembers').length) return this._fail('COMMAND_MUTE_MISSING_MODERATE_PERM', true);
|
||||
if (me.permissions.missing('ModerateMembers').length) return this._fail('COMMAND_MUTE_MISSING_MODERATE_PERM', true);
|
||||
if (me.roles.highest.position <= this.member.roles.highest.position) return this._fail('COMMAND_MUTE_HIERARCHY_ERROR');
|
||||
// if (!this.duration && !settings.mute.default)
|
||||
} else if (this.guild.members.me.permissions.missing('ManageRoles').length) return this._fail('COMMAND_MUTE_MISSING_MANAGEROLE_PERM');
|
||||
} else if (me.permissions.missing('ManageRoles').length) return this._fail('COMMAND_MUTE_MISSING_MANAGEROLE_PERM');
|
||||
|
||||
return super._verify();
|
||||
|
||||
|
@ -82,7 +82,8 @@ class NicknameInfraction extends Infraction {
|
||||
async verify() {
|
||||
|
||||
const { highest } = this.member.roles;
|
||||
if (highest.comparePositionTo(this.guild.members.me.roles.highest) > 0 || !this.guild.members.me.permissions.has('ManageNicknames')) {
|
||||
const me = await this.guild.resolveMember(this.client.user);
|
||||
if (highest.comparePositionTo(me.roles.highest) > 0 || !me.permissions.has('ManageNicknames')) {
|
||||
return this._fail('C_NICKNAME_MISSINGPERMISSIONS');
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,8 @@ class RemoveroleInfraction extends Infraction {
|
||||
const { grantable } = await this.guild.settings();
|
||||
|
||||
let filtered = [];
|
||||
const { highest: clientHighest } = this.guild.members.me.roles;
|
||||
const me = await this.guild.resolveMember(this.client.user);
|
||||
const { highest: clientHighest } = me.roles;
|
||||
filtered = this.data.roles.filter((r) => r.comparePositionTo(clientHighest) < 0);
|
||||
if (filtered.length === 0) {
|
||||
return super._fail('C_REMOVEROLE_ROLEHIERARCHYBOT');
|
||||
|
@ -39,7 +39,7 @@ class UnbanInfraction extends Infraction {
|
||||
const callbacks = this.client.moderationManager.callbacks.filter((c) => c.infraction.type === 'BAN'
|
||||
&& c.infraction.target === this.target.id);
|
||||
|
||||
if (callbacks.size > 0) callbacks.map((c) => this.client.moderationManager.removeCallback(c.infraction));
|
||||
if (callbacks.size > 0) callbacks.map((c) => this.client.moderationManager.removeCallback(c.infraction, true));
|
||||
|
||||
await this.handle();
|
||||
return this._succeed();
|
||||
|
@ -95,9 +95,9 @@ class UnlockdownInfraction extends Infraction {
|
||||
|
||||
async verify() {
|
||||
|
||||
const perms = this.target.permissionsFor(this.guild.members.me);
|
||||
const perms = this.target.permissionsFor(this.client.user);
|
||||
const missing = perms.missing(['ManageRoles', 'SendMessages', 'AddReactions']);
|
||||
if(missing.length) return this._fail('INFRACTION_LOCKDOWN_MISSING_PERMS', { missing: missing.join('**, **') });
|
||||
if (missing.length) this._fail(this.guild.format('INFRACTION_LOCKDOWN_MISSING_PERMS', { missing: missing.join('**, **') }), null, true);
|
||||
return super._verify();
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
const { ChannelType } = require("discord.js");
|
||||
|
||||
const Constants = {
|
||||
CommandOptionTypes: {
|
||||
SUB_COMMAND: 1,
|
||||
@ -28,7 +30,8 @@ const Constants = {
|
||||
ROLE: 8,
|
||||
MENTIONABLE: 9,
|
||||
NUMBER: 10,
|
||||
FLOAT: 10
|
||||
FLOAT: 10,
|
||||
POINTS: 4
|
||||
},
|
||||
ChannelTypes: {
|
||||
TEXT_CHANNEL: 0,
|
||||
@ -36,12 +39,17 @@ const Constants = {
|
||||
}
|
||||
};
|
||||
|
||||
const PointsReg = /^([-+]?[0-9]+) ?(points|point|pts|pt|p)$/iu;
|
||||
|
||||
class CommandOption {
|
||||
|
||||
constructor(options = {}) {
|
||||
|
||||
this._options = options;
|
||||
this.name = options.name;
|
||||
this.description = options.description || "A missing description, let a bot developer know.";
|
||||
if(!options.client) throw new Error(`${this.name} is missing client`);
|
||||
this.client = options.client;
|
||||
|
||||
this.type = Object.keys(Constants.CommandOptionTypes).includes(options.type) ? options.type : 'STRING';
|
||||
this.required = Boolean(options.required);
|
||||
@ -52,8 +60,10 @@ class CommandOption {
|
||||
if (options.options)
|
||||
for (const opt of options.options) {
|
||||
// console.log(opt);
|
||||
if (opt instanceof CommandOption) this.options.push(opt);
|
||||
else if (opt.name instanceof Array) {
|
||||
if (opt instanceof CommandOption) {
|
||||
opt.client = this.client;
|
||||
this.options.push(opt);
|
||||
} else if (opt.name instanceof Array) {
|
||||
const { name: names, description, type, dependsOn, ...opts } = opt;
|
||||
for (const name of names) {
|
||||
// console.log(name);
|
||||
@ -69,9 +79,12 @@ class CommandOption {
|
||||
if (dependsOn instanceof Array) {
|
||||
_dependsOn = dependsOn[index];
|
||||
}
|
||||
this.options.push(new CommandOption({ name, type: _type, description: desc, dependsOn: _dependsOn, ...opts }));
|
||||
this.options.push(new CommandOption({
|
||||
client: this.client, name, type: _type,
|
||||
description: desc, dependsOn: _dependsOn, ...opts
|
||||
}));
|
||||
}
|
||||
} else this.options.push(new CommandOption(opt));
|
||||
} else this.options.push(new CommandOption({ client: this.client, ...opt }));
|
||||
}
|
||||
// this.options = options.options || []; //Used for SUB_COMMAND/SUB_COMMAND_GROUP types.
|
||||
|
||||
@ -83,32 +96,51 @@ class CommandOption {
|
||||
this.minimum = typeof options.minimum === 'number' ? options.minimum : undefined; //Used for INTEGER/NUMBER/FLOAT types.
|
||||
this.maximum = typeof options.maximum === 'number' ? options.maximum : undefined;
|
||||
|
||||
this.flag = true; // used with message based command options
|
||||
this.slashOption = options.slashOption || false;
|
||||
this.flag = options.flag ?? false; // used with message based command options
|
||||
this.valueOptional = options.valueOptional ?? false;
|
||||
this.defaultValue = options.defaultValue ?? null;
|
||||
this.valueAsAlias = options.choices?.length && (options.valueAsAlias ?? false);
|
||||
// this.words = options.words ?? null; // Used when parsing strings if the command has multiple string types that aren't flags
|
||||
|
||||
this.value = undefined;
|
||||
|
||||
// Used in cloned options when parsing final value
|
||||
this.guild = options.guild || null;
|
||||
this._rawValue = options._rawValue ?? null; //Raw value input from Discord. -- use ?? where the value is potentially false, otherwise we end up with false -> null
|
||||
|
||||
}
|
||||
|
||||
usage(guild) {
|
||||
const name = `》 ${this.name.toUpperCase()} [${this.type}]`;
|
||||
get guildOnly() {
|
||||
return ['ROLE', 'MEMBER', 'CHANNEL'].some((t) => this.type.includes(t));
|
||||
}
|
||||
|
||||
usage(guild, verbose = false) {
|
||||
let name = `》 ${this.name.toUpperCase()} [${this.type}]`;
|
||||
let flagProps = ['flag'];
|
||||
if (this.valueOptional) flagProps.push('optional value');
|
||||
if (this.defaultValue !== null) flagProps.push(`default value: \`${this.defaultValue}\``);
|
||||
flagProps = `(${flagProps.join(', ')})`;
|
||||
if(this.flag) name += ` ${flagProps}`;
|
||||
let value = null;
|
||||
|
||||
const format = (...args) => guild ? guild.format(...args) : this.client.format(...args);
|
||||
|
||||
if (this.type === 'SUB_COMMAND_GROUP') {
|
||||
value = this.options.map((opt) => {
|
||||
const usage = opt.usage(guild);
|
||||
const usage = opt.usage(guild, true);
|
||||
return `__${usage.name.replace('》', '').trim()}__\n${usage.value}`;
|
||||
}).join('\n\n');
|
||||
} else if (this.type === 'SUB_COMMAND') {
|
||||
if (!this.options.length) value = guild.format('GENERAL_NO_ARGS');
|
||||
else value = this.options.map((opt) => opt.usage(guild).value).join('\n');
|
||||
if (!this.options.length) value = format('GENERAL_NO_ARGS');
|
||||
else value = this.options.map((opt) => opt.usage(guild, true).value).join('\n');
|
||||
} else {
|
||||
value = `**${this.name} [${this.type}]:** ${this.description}`;
|
||||
value = `${verbose ? `**${this.name} [${this.type}]** ${this.flag ? flagProps : ''}\n` : ''}`;
|
||||
value += `${this.description}`;
|
||||
if (this.choices.length)
|
||||
value += `\n__${guild.format('GENERAL_CHOICES')}__: ${this.choices.map((choice) => choice.name).join(', ')}`;
|
||||
value += `\n__${format('GENERAL_CHOICES')}__: ${this.choices.map((choice) => choice.name).join(', ')}`;
|
||||
if (this.dependsOn.length)
|
||||
value += `\n${guild.format('GENERAL_DEPENDSON', { dependencies: this.dependsOn.join('`, `') })}`;
|
||||
value += `\n${format('GENERAL_DEPENDSON', { dependencies: this.dependsOn.join('`, `') })}`;
|
||||
if (this.minimum !== undefined)
|
||||
value += `\nMIN: \`${this.minimum}\``;
|
||||
if (this.maximum !== undefined) {
|
||||
@ -130,20 +162,268 @@ class CommandOption {
|
||||
* @return {CommandOption}
|
||||
* @memberof CommandOption
|
||||
*/
|
||||
clone(value) {
|
||||
clone(_rawValue, guild, slashOption = false) {
|
||||
return new CommandOption({
|
||||
name: this.name, type: this.type,
|
||||
minimum: this.minimum, maximum: this.maximum,
|
||||
dependsOn: this.dependsOn, dependsOnMode: this.dependsOnMode,
|
||||
_rawValue: value, strict: this.strict
|
||||
...this._options, _rawValue, guild, slashOption
|
||||
});
|
||||
}
|
||||
|
||||
async parse() {
|
||||
if(!this._rawValue && !this.valueOptional) throw new Error(`Null _rawValue`);
|
||||
// console.log('-------PARSE BEGIN---------');
|
||||
// console.log('1', this.name, this._rawValue, this.valueOptional);
|
||||
const { removed, value, error } = await this.types[this.type]();
|
||||
// console.log('2', removed, value, error);
|
||||
// console.log('--------PARSE END----------');
|
||||
if(error) return { error };
|
||||
this.value = value;
|
||||
return removed || [];
|
||||
}
|
||||
|
||||
format(index, params, opts) {
|
||||
if (this.guild) return this.guild.format(index, params, opts);
|
||||
return this.client.format(index, params, opts);
|
||||
}
|
||||
|
||||
get types() {
|
||||
return {
|
||||
POINTS: () => {
|
||||
if(this.slashOption) return { value: this._rawValue };
|
||||
let value = null,
|
||||
removed = null;
|
||||
for (const str of this._rawValue) {
|
||||
const num = parseInt(str);
|
||||
if (isNaN(num)) continue;
|
||||
if(PointsReg.test(str)) {
|
||||
value = num; removed = [str];
|
||||
break;
|
||||
}
|
||||
const index = this._rawValue.indexOf(str);
|
||||
const next = this._rawValue[index + 1];
|
||||
const tmp = str + next;
|
||||
if (PointsReg.test(tmp)) {
|
||||
value = num; removed= [str, next];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.minimum !== undefined && value < this.minimum) return { error: true };
|
||||
if (this.maximum !== undefined && value > this.maximum) return { error: true };
|
||||
return { value, removed };
|
||||
},
|
||||
ROLES: async () => {
|
||||
const roles = [],
|
||||
removed = [];
|
||||
for (const str of this._rawValue) {
|
||||
const role = await this.guild.resolveRole(str, this.strict);
|
||||
if (role) {
|
||||
roles.push(role);
|
||||
removed.push(str);
|
||||
} else if(roles.length) break;
|
||||
}
|
||||
if (!roles.length) return { error: true };
|
||||
return { value: roles, removed };
|
||||
},
|
||||
MEMBERS: async () => {
|
||||
const members = [],
|
||||
removed = [];
|
||||
for (const arg of this._rawValue) {
|
||||
const member = await this.guild.resolveMember(arg, this.strict);
|
||||
if (member) {
|
||||
members.push(member);
|
||||
removed.push(arg);
|
||||
} else if(members.length) break;
|
||||
}
|
||||
if (!members.length) return { error: true, message: this.strict ? this.format('O_COMMANDHANDLER_TYPEMEMBER_STRICT') : null };
|
||||
return { value: members, removed };
|
||||
},
|
||||
USERS: async () => {
|
||||
const users = [],
|
||||
removed = [];
|
||||
for (const arg of this._rawValue) {
|
||||
const user = await this.client.resolveUser(arg, this.strict);
|
||||
if (user) {
|
||||
users.push(user);
|
||||
removed.push(arg);
|
||||
} else if(users.length) break;
|
||||
}
|
||||
if (!users.length) return { error: true, message: this.strict ? this.format('O_COMMANDHANDLER_TYPEUSERS_STRICT') : null };
|
||||
return { value: users, removed };
|
||||
},
|
||||
CHANNELS: async () => {
|
||||
const channels = [],
|
||||
removed = [];
|
||||
for (const arg of this._rawValue) {
|
||||
const channel = await this.guild.resolveChannel(arg, this.strict);
|
||||
if (channel) {
|
||||
channels.push(channel);
|
||||
removed.push(arg);
|
||||
} else if(channels.length) break;
|
||||
}
|
||||
if (!channels.length) return { error: true };
|
||||
return { value: channels, removed };
|
||||
},
|
||||
TEXT_CHANNELS: async () => {
|
||||
const channels = [],
|
||||
removed = [];
|
||||
for(const arg of this._rawValue) {
|
||||
const channel = await this.guild.resolveChannel(arg, this.strict, (channel) => channel.type === ChannelType.GuildText);
|
||||
if (channel) {
|
||||
channels.push(channel);
|
||||
removed.push(arg);
|
||||
} else if(channels.length) break;
|
||||
}
|
||||
if (!channels.length) return { error: true };
|
||||
return { value: channels, removed };
|
||||
},
|
||||
VOICE_CHANNELS: async () => {
|
||||
const channels = [],
|
||||
removed = [];
|
||||
for (const arg of this._rawValue) {
|
||||
const channel = await this.guild.resolveChannel(arg, this.strict, (channel) => channel.type === ChannelType.GuildVoice);
|
||||
if (channel) {
|
||||
channels.push(channel);
|
||||
removed.push(arg);
|
||||
} else if(channels.length) break;
|
||||
}
|
||||
if (!channels.length) return { error: true };
|
||||
return { value: channels, removed };
|
||||
},
|
||||
TIME: () => {
|
||||
const value = this.client.resolver.resolveTime(this._rawValue);
|
||||
if (value === null) return { error: true };
|
||||
return { value, removed: [this._rawValue] };
|
||||
},
|
||||
COMPONENT: () => {
|
||||
const [component] = this.client.resolver.components(this._rawValue, 'any');
|
||||
if (!component) return { error: true };
|
||||
return { value: component, removed: [this._rawValue] };
|
||||
},
|
||||
COMPONENTS: () => {
|
||||
const strings = this._rawValue;
|
||||
const components = [],
|
||||
removed = [];
|
||||
for (const str of strings) {
|
||||
const [component] = this.client.resolver.components(str, 'any');
|
||||
if (component && !components.includes(component)) {
|
||||
components.push(component);
|
||||
removed.push(str);
|
||||
} else if(components.length) break;
|
||||
}
|
||||
if (!components.length) return { error: true };
|
||||
return { value: components, removed };
|
||||
},
|
||||
COMMAND: () => {
|
||||
const [command] = this.client.resolver.components(this._rawValue, 'command');
|
||||
if (!command) return { error: true };
|
||||
return { value: command, removed: [this._rawValue] };
|
||||
},
|
||||
COMMANDS: () => {
|
||||
const strings = this._rawValue;
|
||||
const commands = [],
|
||||
removed = [];
|
||||
for (const str of strings) {
|
||||
const [command] = this.client.resolver.components(str, 'command');
|
||||
if (command && !commands.includes(command)) {
|
||||
commands.push(command);
|
||||
removed.push(str);
|
||||
} else if(commands.length) break;
|
||||
}
|
||||
if (!commands.length) return { error: true };
|
||||
return { value: commands, removed };
|
||||
},
|
||||
MODULE: () => {
|
||||
const [module] = this.client.resolver.components(this._rawValue, 'module');
|
||||
if (!module) return { error: true };
|
||||
return { value: module, removed: [this._rawValue] };
|
||||
},
|
||||
STRING: () => {
|
||||
if (this.slashOption) return { value: this._rawValue };
|
||||
if (this._aliased) return { value: this._rawValue, removed: [] };
|
||||
if (this.choices.length) {
|
||||
const found = this.choices.find((c) => c.value.toLowerCase() === this._rawValue.toLowerCase());
|
||||
if (found) return { value: found.value, removed: [this._rawValue] };
|
||||
return { error: true };
|
||||
}
|
||||
return { value: this._rawValue, removed: [this._rawValue] };
|
||||
},
|
||||
INTEGER: () => {
|
||||
const integer = parseInt(this._rawValue);
|
||||
if(isNaN(integer)) return { error: true };
|
||||
if (this.minimum !== undefined && integer < this.minimum) return { error: true };
|
||||
if (this.maximum !== undefined && integer > this.maximum) return { error: true };
|
||||
return { value: integer, removed: [this._rawValue] };
|
||||
},
|
||||
BOOLEAN: () => {
|
||||
const boolean = this.client.resolver.resolveBoolean(this._rawValue);
|
||||
if (boolean === null && this.valueOptional) return { value: this.defaultValue, removed: [] };
|
||||
else if(boolean === null) return { error: true };
|
||||
return { value: boolean, removed: [this._rawValue] };
|
||||
},
|
||||
MEMBER: async () => {
|
||||
const member = await this.guild.resolveMember(this._rawValue, this.strict);
|
||||
if (!member) return { error: true };
|
||||
return { value: member, removed: [this._rawValue] };
|
||||
},
|
||||
USER: async () => {
|
||||
const user = await this.client.resolver.resolveUser(this._rawValue, this.strict);
|
||||
if(!user) return { error: true };
|
||||
return { value: user, removed: [this._rawValue] };
|
||||
},
|
||||
TEXT_CHANNEL: async () => {
|
||||
const channel = await this.guild.resolveChannel(this._rawValue);
|
||||
if (!channel || channel.type !== ChannelType.GuildText) return { error: true };
|
||||
return { value: channel, removed: [this._rawValue] };
|
||||
},
|
||||
VOICE_CHANNEL: async () => {
|
||||
const channel = await this.guild.resolveChannel(this._rawValue);
|
||||
if (!channel || channel.type !== ChannelType.GuildVoice) return { error: true };
|
||||
return { value: channel, removed: [this._rawValue] };
|
||||
},
|
||||
CHANNEL: async () => {
|
||||
const channel = await this.guild.resolveChannel(this._rawValue);
|
||||
if(!channel) return { error: true };
|
||||
return { value: channel, removed: [this._rawValue] };
|
||||
},
|
||||
ROLE: async () => {
|
||||
const role = await this.guild.resolveRole(this._rawValue);
|
||||
if(!role) return { error: true };
|
||||
return { value: role, removed: [this._rawValue] };
|
||||
},
|
||||
MENTIONABLE: (mentionable) => {
|
||||
return { value: mentionable };
|
||||
},
|
||||
NUMBER: () => {
|
||||
const number = parseFloat(this._rawValue);
|
||||
if(isNaN(number))return { error: true };
|
||||
if (this.minimum !== undefined && number < this.minimum) return { error: true };
|
||||
if (this.maximum !== undefined && number > this.maximum) return { error: true };
|
||||
return { value: number, removed: [this._rawValue] };
|
||||
},
|
||||
FLOAT: () => {
|
||||
const float = parseFloat(this._rawValue);
|
||||
if(isNaN(float)) return { error: true };
|
||||
if (this.minimum !== undefined && float < this.minimum) return { error: true };
|
||||
if (this.maximum !== undefined && float > this.maximum) return { error: true };
|
||||
return { value: parseFloat(float), removed: [this._rawValue] };
|
||||
},
|
||||
DATE: async () => {
|
||||
const date = await this.client.resolver.resolveDate(this._rawValue);
|
||||
if (!date) return { error: true };
|
||||
return { value: date, removed: [this._rawValue] };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
get plural() {
|
||||
return this.type.endsWith('S');
|
||||
}
|
||||
|
||||
get raw() {
|
||||
return {
|
||||
name: this.name,
|
||||
type: this.type,
|
||||
options: []
|
||||
options: this.options.map((opt) => opt.raw)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ const {
|
||||
} = require('../../constants');
|
||||
|
||||
const { Util } = require('../../utilities');
|
||||
const { inspect } = require('util');
|
||||
|
||||
const Constants = {
|
||||
MaxCharacters: 1024, // Max embed description is 2048 characters, however some of those description characters are going to usernames, types, filler text, etc.
|
||||
@ -147,7 +148,7 @@ class Infraction {
|
||||
if(this._mongoId) filter._id = this._mongoId;
|
||||
return this.client.storageManager.mongodb.infractions.updateOne(filter, this.json)
|
||||
.catch((error) => {
|
||||
this.client.logger.error(`There was an issue saving infraction data to the database.\n${error.stack || error}\nInfraction data:\n${this.json}`);
|
||||
this.client.logger.error(`There was an issue saving infraction data to the database.\n${error.stack || error}\nInfraction data:\n${inspect(this.json)}`);
|
||||
});
|
||||
}
|
||||
|
||||
@ -325,9 +326,10 @@ class Infraction {
|
||||
if (protection.type === 'position') {
|
||||
const executorHighest = executor.roles.highest;
|
||||
const targetHighest = target.roles.highest;
|
||||
if (executorHighest.comparePositionTo(targetHighest) < 0) {
|
||||
if (executorHighest.comparePositionTo(targetHighest) === 0)
|
||||
return this._fail('INFRACTION_PROTECTIONPOSITIONERROR_SAME');
|
||||
if (executorHighest.comparePositionTo(targetHighest) < 0)
|
||||
return this._fail('INFRACTION_PROTECTIONPOSITIONERROR');
|
||||
}
|
||||
} else if (protection.type === 'role') {
|
||||
const contains = target.roles.cache.some((r) => protection.roles.includes(r.id));
|
||||
if (contains) {
|
||||
|
@ -7,7 +7,6 @@ const Component = require("./Component.js");
|
||||
// Imports to enable JSDocs typing
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const InteractionWrapper = require("../client/wrappers/InteractionWrapper.js");
|
||||
const CommandOption = require('./CommandOption.js');
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
// const { DiscordClient } = require("../DiscordClient.js");
|
||||
|
||||
@ -70,29 +69,9 @@ class Setting extends Component {
|
||||
this.default = { [this.name]: options.default || {} };
|
||||
this.definitions = options.definitions || {}; // Used for the API for field definitions
|
||||
|
||||
this.commandOptions = [];
|
||||
this.commandOptions = options.commandOptions || [];
|
||||
this.commandType = options.commandType || 'SUB_COMMAND';
|
||||
|
||||
if (options.commandOptions)
|
||||
for (const opt of options.commandOptions) {
|
||||
if (opt instanceof CommandOption) this.commandOptions.push(opt);
|
||||
else if (opt.name instanceof Array) {
|
||||
const { name: names, description, type, ...opts } = opt;
|
||||
for (const name of names) {
|
||||
// console.log(name);
|
||||
const index = names.indexOf(name);
|
||||
let desc = description,
|
||||
_type = type;
|
||||
if (description instanceof Array) desc = description[index] || 'Missing description';
|
||||
if (type instanceof Array) {
|
||||
_type = type[index];
|
||||
if (!_type) throw new Error(`Missing type for option ${name} in command ${this.name}`);
|
||||
}
|
||||
this.commandOptions.push(new CommandOption({ name, type: _type, description: desc, ...opts }));
|
||||
}
|
||||
} else this.commandOptions.push(new CommandOption(opt));
|
||||
}
|
||||
|
||||
this.clientPermissions = options.clientPermissions || [];
|
||||
this.memberPermissions = options.memberPermissions || []; // Idk if we'll end up using this but it's here anyway
|
||||
|
||||
@ -125,22 +104,23 @@ class Setting extends Component {
|
||||
|
||||
fields.push({
|
||||
name: `》 ${guild.format(`GENERAL_OPTIONS`)}`,
|
||||
value: options.map(
|
||||
(opt) => {
|
||||
let msg = `**${opt.name} [${opt.type}]:** ${opt.description}`;
|
||||
if (opt.choices.length)
|
||||
msg += `\n__${guild.format('GENERAL_CHOICES')}__: ${opt.choices.map((choice) => choice.name).join(', ')}`;
|
||||
if (opt.dependsOn.length)
|
||||
msg += `\n${guild.format('GENERAL_DEPENDSON', { dependencies: opt.dependsOn.join('`, `') })}`;
|
||||
if (opt.minimum !== undefined)
|
||||
msg += `\nMIN: \`${opt.minimum}\``;
|
||||
if (opt.maximum !== undefined) {
|
||||
const newline = opt.minimum !== undefined ? ', ' : '\n';
|
||||
msg += `${newline}MAX: \`${opt.maximum}\``;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
).join('\n\n')
|
||||
value: options.map((opt) => opt.usage(guild, true)).map((f) => f.value).join('\n\n')
|
||||
// value: options.map(
|
||||
// (opt) => {
|
||||
// let msg = `**${opt.name} [${opt.type}]:** ${opt.description}`;
|
||||
// if (opt.choices.length)
|
||||
// msg += `\n__${guild.format('GENERAL_CHOICES')}__: ${opt.choices.map((choice) => choice.name).join(', ')}`;
|
||||
// if (opt.dependsOn.length)
|
||||
// msg += `\n${guild.format('GENERAL_DEPENDSON', { dependencies: opt.dependsOn.join('`, `') })}`;
|
||||
// if (opt.minimum !== undefined)
|
||||
// msg += `\nMIN: \`${opt.minimum}\``;
|
||||
// if (opt.maximum !== undefined) {
|
||||
// const newline = opt.minimum !== undefined ? ', ' : '\n';
|
||||
// msg += `${newline}MAX: \`${opt.maximum}\``;
|
||||
// }
|
||||
// return msg;
|
||||
// }
|
||||
// ).join('\n\n')
|
||||
});
|
||||
}
|
||||
|
||||
@ -203,16 +183,16 @@ class Setting extends Component {
|
||||
if (!message.length && !index && !embed) throw new Error('Must declare either message, index or embeds');
|
||||
const response = await invoker.promptMessage(
|
||||
index ? invoker.format(index, params) : message,
|
||||
{ time, editReply: true, embed }
|
||||
{ time, editReply: invoker.replied, embed }
|
||||
);
|
||||
|
||||
if (!response) return { error: true, message: invoker.format('ERR_TIMEOUT') };
|
||||
const content = response.content.toLowerCase();
|
||||
if(invoker.channel.permissionsFor(invoker.guild.members.me).has('ManageMessages'))
|
||||
if (invoker.channel.permissionsFor(this.client.user).has('ManageMessages'))
|
||||
await response.delete().catch(() => null);
|
||||
if (['cancel', 'abort', 'exit'].includes(content)) return {
|
||||
error: true,
|
||||
message: invoker.format('ERR_CANCEL')
|
||||
content: invoker.format('ERR_CANCEL')
|
||||
};
|
||||
else if (!content.length) return { error: true, index: 'SETTING_NOCONTENT' };
|
||||
|
||||
@ -241,7 +221,8 @@ class Setting extends Component {
|
||||
|
||||
for (const param of params) {
|
||||
if (list.includes(param)) {
|
||||
list.splice(list.indexOf(!caseSensitive ? param : param.toLowerCase()), 1);
|
||||
const [removed] = list.splice(list.indexOf(!caseSensitive ? param : param.toLowerCase()), 1);
|
||||
modified.push(removed);
|
||||
} else skipped.push(param);
|
||||
}
|
||||
|
||||
|
@ -51,8 +51,10 @@ class Command extends Component {
|
||||
|
||||
this.options = [];
|
||||
if (options.options) for (const opt of options.options) {
|
||||
if (opt instanceof CommandOption) this.options.push(opt);
|
||||
else if (opt.name instanceof Array) {
|
||||
if (opt instanceof CommandOption) {
|
||||
opt.client = client;
|
||||
this.options.push(opt);
|
||||
} else if (opt.name instanceof Array) {
|
||||
// Allows easy templating of subcommands that share arguments
|
||||
const { name: names, description, type, ...opts } = opt;
|
||||
for (const name of names) {
|
||||
@ -64,9 +66,9 @@ class Command extends Component {
|
||||
_type = type[index];
|
||||
if (!_type) throw new Error(`Missing type for option ${name} in command ${this.name}`);
|
||||
}
|
||||
this.options.push(new CommandOption({ name, type: _type, description: desc, ...opts }));
|
||||
this.options.push(new CommandOption({ name, type: _type, description: desc, ...opts, client }));
|
||||
}
|
||||
} else this.options.push(new CommandOption(opt));
|
||||
} else this.options.push(new CommandOption({ ...opt, client }));
|
||||
}
|
||||
|
||||
this.options.sort((a, b) => {
|
||||
@ -107,7 +109,11 @@ class Command extends Component {
|
||||
|
||||
const fields = [];
|
||||
const { guild, subcommand, subcommandGroup } = invoker;
|
||||
const { permissions: { type } } = guild._settings;
|
||||
|
||||
let type = null;
|
||||
const format = (index) => guild ? guild.format(index) : this.client.format(index);
|
||||
if (guild) ({ permissions: { type } } = guild._settings);
|
||||
|
||||
|
||||
if (this.options.length) {
|
||||
if (verbose) fields.push(...this.options.map((opt) => opt.usage(guild)));
|
||||
@ -126,7 +132,7 @@ class Command extends Component {
|
||||
else if (type === 'grant') required = [this.resolveable];
|
||||
else required = [this.resolveable, ...this.memberPermissions];
|
||||
fields.push({
|
||||
name: `》 ${guild.format('GENERAL_PERMISSIONS')}`,
|
||||
name: `》 ${format('GENERAL_PERMISSIONS')}`,
|
||||
value: `\`${required.join('`, `')}\``
|
||||
});
|
||||
}
|
||||
@ -135,7 +141,7 @@ class Command extends Component {
|
||||
author: {
|
||||
name: `${this.name} [module:${this.module.name}]`
|
||||
},
|
||||
description: guild.format(`COMMAND_${this.name.toUpperCase()}_HELP`),
|
||||
description: format(`COMMAND_${this.name.toUpperCase()}_HELP`),
|
||||
fields
|
||||
});
|
||||
|
||||
|
@ -4,6 +4,7 @@ class ModerationCommand extends SlashCommand {
|
||||
|
||||
constructor(client, opts) {
|
||||
|
||||
const flag = true;
|
||||
let baseOptions = [
|
||||
{
|
||||
name: 'users',
|
||||
@ -13,28 +14,32 @@ class ModerationCommand extends SlashCommand {
|
||||
strict: true
|
||||
}, {
|
||||
name: 'points',
|
||||
type: 'INTEGER',
|
||||
type: 'POINTS',
|
||||
description: 'The amount of points to assign to the infraction',
|
||||
minimum: 0,
|
||||
maximum: 100
|
||||
}, {
|
||||
name: 'expiration',
|
||||
type: 'TIME',
|
||||
description: 'How long until the points expire'
|
||||
description: 'How long until the points expire',
|
||||
flag
|
||||
}, {
|
||||
name: 'prune',
|
||||
type: 'INTEGER',
|
||||
description: 'How many messages to prune',
|
||||
minimum: 2,
|
||||
maximum: 100
|
||||
maximum: 100,
|
||||
flag
|
||||
}, {
|
||||
name: 'force',
|
||||
type: 'BOOLEAN',
|
||||
description: 'Whether to override automod'
|
||||
description: 'Whether to override automod',
|
||||
flag, valueOptional: true, defaultValue: true
|
||||
}, {
|
||||
name: 'silent',
|
||||
type: 'BOOLEAN',
|
||||
description: 'Whether the user should receive the infraction'
|
||||
description: 'Whether the user should receive the infraction',
|
||||
flag, valueOptional: true, defaultValue: true
|
||||
}, {
|
||||
name: 'reason',
|
||||
type: 'STRING',
|
||||
@ -42,7 +47,6 @@ class ModerationCommand extends SlashCommand {
|
||||
}
|
||||
];
|
||||
|
||||
// Probably temporary
|
||||
if(!opts.memberPermissions) throw new Error(`MISSING PERMS ${opts.name}`);
|
||||
|
||||
if (opts.skipOptions) for (const opt of opts.skipOptions) {
|
||||
|
@ -10,26 +10,16 @@ class SettingsCommand extends SlashCommand {
|
||||
super(client, {
|
||||
...options,
|
||||
guildOnly: true,
|
||||
memberPermissions: ['ManageGuild']
|
||||
});
|
||||
|
||||
/*
|
||||
{
|
||||
name: 'settings',
|
||||
description: "Configure the bot's behaviour in your server",
|
||||
module: 'administration',
|
||||
memberPermissions: ['ManageGuild'],
|
||||
options: [
|
||||
|
||||
],
|
||||
guildOnly: true
|
||||
}
|
||||
*/
|
||||
|
||||
this.options.push(new CommandOption({
|
||||
{
|
||||
type: 'SUB_COMMAND',
|
||||
name: 'list',
|
||||
description: 'List available settings'
|
||||
}));
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
this.build();
|
||||
|
||||
}
|
||||
@ -50,9 +40,12 @@ class SettingsCommand extends SlashCommand {
|
||||
name: setting.name,
|
||||
description: setting.description,
|
||||
type: setting.commandType, //'SUB_COMMAND',
|
||||
options: setting.commandOptions
|
||||
options: setting.commandOptions,
|
||||
client: this.client
|
||||
});
|
||||
this.options.push(subCommand);
|
||||
// Overwrite the setting options with the built CommandOption structures
|
||||
setting.commandOptions = subCommand.options;
|
||||
}
|
||||
|
||||
// for (const module of modules) {
|
||||
@ -89,7 +82,8 @@ class SettingsCommand extends SlashCommand {
|
||||
if (!setting) return invoker.reply('Something went wrong, could not find setting');
|
||||
|
||||
if (setting.clientPermissions.length) {
|
||||
const missing = guild.members.me.permissions.missing(setting.clientPermissions);
|
||||
const me = await guild.resolveMember(this.client.user);
|
||||
const missing = me.permissions.missing(setting.clientPermissions);
|
||||
if (missing.length) return invoker.reply({
|
||||
emoji: 'failure',
|
||||
index: 'SETTING_MISSING_CLIENTPERMISSIONS',
|
||||
|
@ -197,6 +197,8 @@ module.exports = class FilterUtility {
|
||||
//Zero width character (UTF-16 8206)
|
||||
content = content.replace(//gu, '');
|
||||
|
||||
content = content.replace(/['"‘’“”]/gu, '');
|
||||
|
||||
//Replace the weird letters with their normal text counterparts
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const match = (/[a-z0-9\w\(\)\.\\\/\?!]+/gimu).exec(content);
|
||||
@ -321,7 +323,9 @@ module.exports = class FilterUtility {
|
||||
matched: true,
|
||||
_matcher: _word,
|
||||
matcher: `fuzzy [\`${_word}\`, \`${sim}\`, \`${threshold}\`]`,
|
||||
type: 'fuzzy'
|
||||
type: 'fuzzy',
|
||||
_threshold: threshold,
|
||||
_sim: sim
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -206,7 +206,7 @@ class SettingsMigrator {
|
||||
stickyrole: result.stickyrole ? { ...result.stickyrole, enabled: Boolean(result.stickyrole.roles.length) } : undefined,
|
||||
welcomer: result.welcomer,
|
||||
commands: { disabled: result.disabledCommands, custom: {} },
|
||||
prefix: result.prefix
|
||||
textcommands: { prefix: result.prefix, enabled: false }
|
||||
};
|
||||
return settings;
|
||||
}
|
||||
@ -287,14 +287,15 @@ class SettingsMigrator {
|
||||
};
|
||||
|
||||
if (linkfilter) settings.linkfilter = {
|
||||
enabled: result.linkfilter?.enabled || false,
|
||||
silent: result.linkfilter?.silent || false,
|
||||
ignore: result.linkfilter?.channels || [],
|
||||
bypass: result.linkfilter?.roles || [],
|
||||
enabled: linkfilter.enabled || false,
|
||||
silent: linkfilter.silent || false,
|
||||
ignore: linkfilter.channels || [],
|
||||
bypass: linkfilter.roles || [],
|
||||
actions: [],
|
||||
whitelist: result.linkfilter?.whitelist === true ? result.linkfilter.filter : [],
|
||||
blacklist: result.linkfilter?.whitelist === false ? result.linkfilter.filter : [],
|
||||
greylist: []
|
||||
whitelist: linkfilter.whitelist === true ? linkfilter.filter : [],
|
||||
blacklist: linkfilter.whitelist === false ? linkfilter.filter : [],
|
||||
greylist: [],
|
||||
whitelistMode: linkfilter.whitelist || false
|
||||
};
|
||||
|
||||
if (ignore) settings.ignore = {
|
||||
@ -334,7 +335,7 @@ class SettingsMigrator {
|
||||
};
|
||||
|
||||
if (autorole) settings.autorole = autorole;
|
||||
if (prefix) settings.prefix = prefix;
|
||||
if (prefix) settings.textcommands = { prefix, enabled: false };
|
||||
if (welcomer) {
|
||||
settings.welcomer.enabled = welcomer.enabled;
|
||||
settings.welcomer.message = welcomer.message || null;
|
||||
@ -398,7 +399,8 @@ class SettingsMigrator {
|
||||
silent: false,
|
||||
bypass: result.invitefilter.roles,
|
||||
enabled: result.invitefilter.enabled,
|
||||
actions: []
|
||||
actions: [],
|
||||
whitelist: []
|
||||
};
|
||||
const channels = Object.entries(invitefilter.channels || {});
|
||||
for (const [id, value] of channels) {
|
||||
|
Loading…
Reference in New Issue
Block a user