forked from Galactic/galactic-bot
major moderation changes
This commit is contained in:
parent
a258522e5f
commit
9dc6fddc79
@ -79,7 +79,6 @@
|
||||
"max-classes-per-file": "warn",
|
||||
"max-nested-callbacks": "warn",
|
||||
"new-parens": "warn",
|
||||
"newline-per-chained-call": "warn",
|
||||
"no-alert": "warn",
|
||||
"no-array-constructor": "warn",
|
||||
"no-bitwise": "warn",
|
||||
|
@ -168,12 +168,15 @@ Failed to {infraction} **{target}** because {reason}.
|
||||
an error occured.
|
||||
|
||||
[INFRACTION_DESCRIPTION]
|
||||
**Action:** {type}
|
||||
**{type}**
|
||||
**Moderator:** {moderator}
|
||||
**Reason:** {reason}
|
||||
|
||||
[INFRACTION_DESCRIPTIONPOINTS]
|
||||
**Points:** {points} | **Total Points**: {total}
|
||||
|
||||
[INFRACTION_DESCRIPTIONJUMPTO]
|
||||
**Jump To:** [{type}]({link})
|
||||
**[Jump To {name}]({link})**
|
||||
|
||||
[INFRACTION_DESCRIPTIONDURATION]
|
||||
**Duration:** {duration}
|
@ -27,6 +27,7 @@
|
||||
"mysql": "^2.18.1",
|
||||
"node-fetch": "^2.6.0",
|
||||
"similarity": "^1.2.1",
|
||||
"timestring": "^6.0.0",
|
||||
"winston": "^3.2.1",
|
||||
"winston-transport": "^4.3.0"
|
||||
}
|
||||
|
@ -316,7 +316,21 @@ class MongoDBProvider extends Provider {
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
aggregate({ collection, query }) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if(!this._initialized) reject(new Error('MongoDB not connected'));
|
||||
this.db.collection(collection).aggregate(query, (err, item) => {
|
||||
if(err) return reject(err);
|
||||
return resolve(item.toArray());
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ const ModerationManager = require('../moderation/ModerationManager.js');
|
||||
|
||||
const { Guild, GuildMember, User, Message } = require('../../structure/extensions/'); //eslint-disable-line no-unused-vars
|
||||
const { Command, Observer, Inhibitor, Setting } = require('../../structure/interfaces/');
|
||||
const { DefaultGuild } = require('../../util/defaults/');
|
||||
|
||||
class DiscordClient extends Client {
|
||||
|
||||
@ -68,7 +69,7 @@ class DiscordClient extends Client {
|
||||
get defaultConfig() {
|
||||
if(this._defaultConfig) return this._defaultConfig;
|
||||
const settings = this.registry.components.filter((c) => c.type === 'setting' && c.resolve === 'GUILD');
|
||||
let def = {};
|
||||
let def = DefaultGuild;
|
||||
for(const setting of settings.values()) {
|
||||
if(setting.default !== null) {
|
||||
def = {
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* eslint-disable no-useless-escape */
|
||||
const timestring = require('timestring');
|
||||
|
||||
class Resolver {
|
||||
|
||||
constructor(client) {
|
||||
@ -380,8 +381,7 @@ class Resolver {
|
||||
const channel = CM.cache.filter(filter).filter((c) => {
|
||||
if (!strict) return c.name.toLowerCase().includes(ch);
|
||||
return c.name.toLowerCase() === ch;
|
||||
})
|
||||
.first();
|
||||
}).first();
|
||||
|
||||
if(channel) resolved.push(channel);
|
||||
|
||||
@ -456,6 +456,16 @@ class Resolver {
|
||||
return result ? result[0] : false;
|
||||
}
|
||||
|
||||
resolveTime(string) {
|
||||
let time = null;
|
||||
try {
|
||||
time = timestring(string);
|
||||
} catch(err) {
|
||||
return null;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
|
||||
async infinite(args = [], resolvers = [], strict, guild) {
|
||||
let parsed = [], //eslint-disable-line prefer-const
|
||||
|
@ -1,6 +1,7 @@
|
||||
const { inspect } = require('util');
|
||||
const { username } = require('os').userInfo();
|
||||
|
||||
|
||||
let _storage = null; //eslint-disable-line
|
||||
|
||||
const { Command } = require('../../../../interfaces/');
|
||||
@ -32,7 +33,8 @@ class Evaluate extends Command {
|
||||
description: 'Hides the output from the channel.'
|
||||
}
|
||||
],
|
||||
showUsage: true
|
||||
showUsage: true,
|
||||
keepQuotes: true
|
||||
});
|
||||
|
||||
}
|
||||
@ -42,6 +44,7 @@ class Evaluate extends Command {
|
||||
params = params.join(' ');
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { guild, author, member } = message;
|
||||
console.log(params);
|
||||
|
||||
try {
|
||||
let evaled = eval(params); //eslint-disable-line no-eval
|
||||
|
@ -20,7 +20,7 @@ class KickCommand extends Command {
|
||||
name: 'points',
|
||||
aliases: ['point', 'modpoints', 'modpoint', 'pts', 'pt'],
|
||||
type: 'INTEGER',
|
||||
types: ['VERBAL'],
|
||||
types: ['VERBAL', 'FLAG'],
|
||||
usage: '<amount>',
|
||||
default: (guild) => {
|
||||
return guild._settings.moderationPoints.points.KICK;
|
||||
@ -29,6 +29,16 @@ class KickCommand extends Command {
|
||||
ignoreInvalid: true,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'expiration',
|
||||
aliases: ['expire', 'expires', 'expirations'],
|
||||
type: 'TIME',
|
||||
types: ['FLAG'],
|
||||
usage: '<time>',
|
||||
default: (guild) => {
|
||||
return guild._settings.moderationPoints.expirations.KICK;
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'prune',
|
||||
aliases: ['purge'],
|
||||
@ -44,6 +54,12 @@ class KickCommand extends Command {
|
||||
type: 'BOOLEAN',
|
||||
types: ['FLAG'],
|
||||
default: true
|
||||
},
|
||||
{
|
||||
name: 'silent',
|
||||
type: 'BOOLEAN',
|
||||
types: ['FLAG'],
|
||||
default: true
|
||||
}
|
||||
],
|
||||
guildOnly: true,
|
||||
|
@ -2,7 +2,7 @@ const { stripIndents } = require('common-tags');
|
||||
|
||||
const { Command } = require('../../../../interfaces/');
|
||||
|
||||
class PingCommand extends Command {
|
||||
class ArgumentsCommand extends Command {
|
||||
|
||||
constructor(client) {
|
||||
|
||||
@ -12,57 +12,21 @@ class PingCommand extends Command {
|
||||
aliases: ['args', 'arg', 'argument'],
|
||||
arguments: [
|
||||
{
|
||||
name: 'apple',
|
||||
name: 'silent',
|
||||
type: 'BOOLEAN',
|
||||
types: ['VERBAL', 'FLAG'],
|
||||
default: true
|
||||
},
|
||||
{
|
||||
name: 'banana',
|
||||
aliases: ['bans', 'bananas'],
|
||||
type: 'INTEGER',
|
||||
types: ['FLAG', 'VERBAL'],
|
||||
min: 0,
|
||||
max: 10,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'channels',
|
||||
aliases: ['cars', 'carrots'],
|
||||
type: 'CHANNEL',
|
||||
types: ['FLAG', 'VERBAL'],
|
||||
infinite: true,
|
||||
default: 'test'
|
||||
},
|
||||
{
|
||||
name: 'format',
|
||||
type: 'STRING',
|
||||
types: ['VERBAL', 'FLAG'],
|
||||
options: [ 'webp', 'png', 'jpeg', 'jpg', 'gif' ],
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'raw',
|
||||
aliases: ['json'],
|
||||
type: 'BOOLEAN',
|
||||
types: ['VERBAL', 'FLAG'],
|
||||
default: true
|
||||
},
|
||||
{
|
||||
name: 'points',
|
||||
aliases: ['modpoints', 'modpoint', 'point', 'pt', 'pts'],
|
||||
types: ['FLAG', 'VERBAL'],
|
||||
type: 'INTEGER',
|
||||
usage: '<amount>',
|
||||
min: 0, max: 100
|
||||
},
|
||||
{
|
||||
name: 'prune',
|
||||
aliases: ['purge'],
|
||||
types: ['FLAG'],
|
||||
usage: '<amount>',
|
||||
type: 'INTEGER',
|
||||
min: 2, max: 50
|
||||
default: true
|
||||
},
|
||||
{
|
||||
name: 'expiration',
|
||||
aliases: ['expire', 'expires', 'expirations'],
|
||||
type: 'TIME',
|
||||
types: ['FLAG'],
|
||||
usage: '<time>',
|
||||
default: (guild) => {
|
||||
return guild._settings.moderationPoints.expirations.KICK;
|
||||
},
|
||||
required: true
|
||||
}
|
||||
],
|
||||
restricted: true,
|
||||
@ -82,4 +46,4 @@ class PingCommand extends Command {
|
||||
|
||||
}
|
||||
|
||||
module.exports = PingCommand;
|
||||
module.exports = ArgumentsCommand;
|
@ -54,25 +54,17 @@ class CommandHandler extends Observer {
|
||||
const response = await this._parseArguments(parameters, command.arguments, message.guild);
|
||||
if(response.error) {
|
||||
return this.handleError(message, { type: 'argument', ...response });
|
||||
}
|
||||
}
|
||||
|
||||
if(command.keepQuotes) {
|
||||
let temporaryParameters = [];
|
||||
for(let parameter of response.parameters) {
|
||||
if(parameter.includes(" ")) {
|
||||
parameter = `"${parameter}"`;
|
||||
temporaryParameters = [ ...temporaryParameters, ...parameter.split(' ') ];
|
||||
} else {
|
||||
temporaryParameters.push(parameter);
|
||||
}
|
||||
}
|
||||
response.parameters = temporaryParameters;
|
||||
message.parameters = response.parameters.map(([param, isQuote]) => (isQuote ? `"${param}"` : param));
|
||||
} else {
|
||||
message.parameters = response.parameters.map((p) => p[0]);
|
||||
}
|
||||
|
||||
// const timestamp2 = new Date().getTime();
|
||||
// this.client.logger.debug(`Client took ${timestamp2-timestamp1}ms to parse arguments.`);
|
||||
|
||||
message.parameters = response.parameters;
|
||||
message.arguments = response.args;
|
||||
|
||||
return this.handleCommand(message, response.parameters);
|
||||
@ -113,10 +105,16 @@ class CommandHandler extends Observer {
|
||||
} else if(matches && (matches[1] || matches[3])) {
|
||||
const [,, name] = matches;
|
||||
const number = matches[1] || matches[3];
|
||||
arg = longArgs[name];
|
||||
value = number;
|
||||
const argument = longArgs[name];
|
||||
if(argument && argument.types.includes('VERBAL')) {
|
||||
arg = longArgs[name];
|
||||
value = number;
|
||||
}
|
||||
} else if(longArgs[word.toLowerCase()]) {
|
||||
arg = longArgs[word.toLowerCase()];
|
||||
const argument = longArgs[word.toLowerCase()];
|
||||
if(argument && argument.types.includes('VERBAL')) {
|
||||
arg = longArgs[word.toLowerCase()];
|
||||
}
|
||||
}
|
||||
return { arg, value };
|
||||
};
|
||||
@ -128,8 +126,8 @@ class CommandHandler extends Observer {
|
||||
const lookBehind = async (argument) => { //Checks previous argument for an integer or float value, "15 points".
|
||||
let response = {};
|
||||
if(newParameters.length > 0 && ['INTEGER', 'FLOAT'].includes(argument.type)) {
|
||||
const lastItem = newParameters[newParameters.length-1];
|
||||
response = await this._parseArgumentType(argument, lastItem, guild);
|
||||
const [lastWord] = newParameters[newParameters.length-1];
|
||||
response = await this._parseArgumentType(argument, lastWord, guild);
|
||||
if(!response.error) {
|
||||
newParameters.pop(); //Deletes latest parameter.
|
||||
newArguments.push(argument); //Adds argument with value of the latest parameter.
|
||||
@ -140,8 +138,9 @@ class CommandHandler extends Observer {
|
||||
};
|
||||
let error = null,
|
||||
currentArgument = null;
|
||||
|
||||
for(let i = 0; i < parameters.length; i++) {
|
||||
const word = parameters[i];
|
||||
const [word, isQuote] = parameters[i];
|
||||
if(currentArgument) { //One of the previous words had an argument, trying to parse the type until error.
|
||||
let response = await this._parseArgumentType(currentArgument, word, guild);
|
||||
if(response.error) {
|
||||
@ -165,7 +164,7 @@ class CommandHandler extends Observer {
|
||||
newArguments.push(currentArgument);
|
||||
currentArgument = null;
|
||||
}
|
||||
newParameters.push(word);
|
||||
newParameters.push([word, isQuote]);
|
||||
} else {
|
||||
newArguments.push(currentArgument);
|
||||
if(!currentArgument.infinite) currentArgument = null;
|
||||
@ -191,7 +190,7 @@ class CommandHandler extends Observer {
|
||||
currentArgument = arg;
|
||||
}
|
||||
} else {
|
||||
newParameters.push(word);
|
||||
newParameters.push([ word, isQuote ]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -229,7 +228,7 @@ class CommandHandler extends Observer {
|
||||
|
||||
const parse = async(argument, string, guild) => {
|
||||
|
||||
let { error, value } = await this.parseType(argument.type, string, guild);
|
||||
let { error, value } = await this.parseType(argument.type, string, guild); //eslint-disable-line prefer-const
|
||||
if(error) {
|
||||
return {
|
||||
index: 'COMMANDHANDLER_TYPE_ERROR',
|
||||
@ -452,6 +451,11 @@ class CommandHandler extends Observer {
|
||||
const role = await this.client.resolver.resolveRole(str, true, guild);
|
||||
if(!role) return { error: true };
|
||||
return { error: false, value: role };
|
||||
},
|
||||
TIME: async(str) => {
|
||||
const time = await this.client.resolver.resolveTime(str);
|
||||
if(!time) return { error: true };
|
||||
return { error: false, value: time };
|
||||
}
|
||||
};
|
||||
|
||||
@ -463,15 +467,15 @@ class CommandHandler extends Observer {
|
||||
// I'm not entirely sure how this works, except for the fact that it loops through each CHARACTER and tries to match quotes together.
|
||||
// Supposedly quicker than regex, and I'd agree with that statement. Big, messy, but quick.
|
||||
_getQuotes(string) {
|
||||
if(!string) return [];
|
||||
|
||||
let quoted = false,
|
||||
wordStart = true,
|
||||
startQuote = '',
|
||||
endQuote = false,
|
||||
isQuote = false,
|
||||
word = '';
|
||||
|
||||
const words = [],
|
||||
const words = [],
|
||||
chars = string.split('');
|
||||
|
||||
chars.forEach((char) => {
|
||||
@ -479,11 +483,13 @@ class CommandHandler extends Observer {
|
||||
if(endQuote) {
|
||||
quoted = false;
|
||||
endQuote = false;
|
||||
isQuote = true;
|
||||
}
|
||||
if(quoted) {
|
||||
word += char;
|
||||
} else if(word !== '') {
|
||||
words.push(word);
|
||||
words.push([ word, isQuote ]);
|
||||
isQuote = false;
|
||||
startQuote = '';
|
||||
word = '';
|
||||
wordStart = true;
|
||||
@ -516,13 +522,13 @@ class CommandHandler extends Observer {
|
||||
});
|
||||
|
||||
if (endQuote) {
|
||||
words.push(word);
|
||||
words.push([ word, true ]);
|
||||
} else {
|
||||
word.split(/\s/u).forEach((subWord, i) => {
|
||||
if (i === 0) {
|
||||
words.push(startQuote+subWord);
|
||||
words.push([ startQuote+subWord, false ]);
|
||||
} else {
|
||||
words.push(subWord);
|
||||
words.push([ subWord, false ]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
const { Setting } = require('../../../../interfaces/');
|
||||
|
||||
class DmInfractionSetting extends Setting {
|
||||
|
||||
constructor(client) {
|
||||
|
||||
super(client, {
|
||||
name: 'dmInfraction',
|
||||
module: 'moderation',
|
||||
aliases: [
|
||||
'directMessageInfraction',
|
||||
'privateLog',
|
||||
'dmLog'// idk...
|
||||
],
|
||||
default: {
|
||||
dmInfraction: {
|
||||
enabled: true,
|
||||
onRemoved: true, //only Kick, Softban, and Ban
|
||||
custom: {
|
||||
default: "You were **{infraction}** on the server `{server}`, your infraction details are below."
|
||||
//BAN: etc
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.client = client;
|
||||
|
||||
}
|
||||
|
||||
async handle(message, args) {
|
||||
|
||||
//to do
|
||||
return {
|
||||
msg: 'fuck off',
|
||||
error: false
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
async fields(guild) {
|
||||
const setting = guild._settings[this.index];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = DmInfractionSetting;
|
@ -36,14 +36,15 @@ class ModerationLogsSetting extends Setting {
|
||||
|
||||
}
|
||||
|
||||
async handle(message, args) {
|
||||
async handle(message, params) {
|
||||
|
||||
const setting = message.guild._settings.modlogs || this.default.modlogs;
|
||||
const response = this.resolveMethod(args, CONSTANTS.INFRACTIONS, setting.infractions);
|
||||
const setting = message.guild._settings.moderationLog;
|
||||
const response = await this.resolveMethod(params, CONSTANTS.INFRACTIONS, setting.infractions);
|
||||
|
||||
if (response) {
|
||||
console.log(response);
|
||||
|
||||
if (args.length < 2 && response.method !== 'list') return {
|
||||
if (params.length < 2 && response.method !== 'list') return {
|
||||
msg: message.format('MISSING_ARGS'),
|
||||
error: true
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ class SilentSetting extends Setting {
|
||||
|
||||
fields(guild) {
|
||||
return {
|
||||
name: "》Silent",
|
||||
name: "》Enabled",
|
||||
value: `\`${guild._settings.silent}\``
|
||||
};
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ const Guild = Structures.extend('Guild', (Guild) => {
|
||||
|
||||
this._settings = null; //internal cache of current guild's settings; should ALWAYS stay the same as database.
|
||||
this._permissions = null; //internal cache, should always match database.
|
||||
|
||||
this.callbacks = [];
|
||||
|
||||
}
|
||||
@ -32,6 +33,11 @@ const Guild = Structures.extend('Guild', (Guild) => {
|
||||
return this._permissions;
|
||||
}
|
||||
|
||||
async caseId() {
|
||||
if(!this._settings) await this.settings();
|
||||
return this._caseId = this._settings.caseId; //eslint-disable-line no-return-assign
|
||||
}
|
||||
|
||||
/* Settings Wrapper */
|
||||
|
||||
async _deleteSettings() { //Delete whole entry - remove
|
||||
|
@ -8,12 +8,37 @@ const User = Structures.extend('User', (User) => {
|
||||
|
||||
super(...args);
|
||||
|
||||
this._totalPoints = null;
|
||||
|
||||
this._settings = null; //internal cache of current users' settings; should ALWAYS stay the same as database.
|
||||
this._cached = Date.now();
|
||||
|
||||
}
|
||||
|
||||
async settings() {
|
||||
if (!this._settings) this._settings = this.client.transactionHandler.send({ provider: 'mongodb', request: { collection: 'users', type: 'findOne', query: { user: this.id } } });
|
||||
if (this._settings instanceof Promise) this._settings = await this._settings || {};
|
||||
return this._settings;
|
||||
}
|
||||
|
||||
async totalPoints(guild, points) {
|
||||
if(this._totalPoints === null) {
|
||||
const query = [{ $match: { $or: [{ expiration: { $gt: Math.floor(Date.now()/1000) } }, { expiration: 0 }], guild: guild.id, target: this.id } },
|
||||
{ $group: { _id: null, pointSum: { $sum: "$points" } } }];
|
||||
const aggregate = await this.client.transactionHandler.send({
|
||||
provider: 'mongodb',
|
||||
request: {
|
||||
collection: 'infractions',
|
||||
type: 'aggregate',
|
||||
query
|
||||
}
|
||||
});
|
||||
this._totalPoints = aggregate[0].pointSum;
|
||||
}
|
||||
this._totalPoints += points;
|
||||
return this._totalPoints;
|
||||
}
|
||||
|
||||
get developer() {
|
||||
return this.client._options.bot.owners.includes(this.id);
|
||||
}
|
||||
@ -22,12 +47,6 @@ const User = Structures.extend('User', (User) => {
|
||||
return Date.now()-this._cached;
|
||||
}
|
||||
|
||||
async settings() {
|
||||
if (!this._settings) this._settings = this.client.transactionHandler.send({ provider: 'mongodb', request: { collection: 'users', type: 'findOne', query: { user: this.id } } });
|
||||
if (this._settings instanceof Promise) this._settings = await this._settings || {};
|
||||
return this._settings;
|
||||
}
|
||||
|
||||
get prefix() {
|
||||
return this.client._options.bot.prefix;
|
||||
}
|
||||
|
@ -13,7 +13,8 @@ const Constants = {
|
||||
'MEMBER',
|
||||
'CHANNEL',
|
||||
'TEXTCHANNEL',
|
||||
'VOICECHANNEL'
|
||||
'VOICECHANNEL',
|
||||
'TIME'
|
||||
],
|
||||
ArgumentTypes: [
|
||||
'FLAG',
|
||||
|
@ -1,6 +1,6 @@
|
||||
const { stripIndents } = require('common-tags');
|
||||
|
||||
const { Collection } = require('../../util/');
|
||||
const { Collection, Util } = require('../../util/');
|
||||
|
||||
const Constants = {
|
||||
MaxTargets: 10 //10+(10*premium-tier), theoretical max = 40
|
||||
@ -24,6 +24,9 @@ class ModerationManager {
|
||||
${maxTargets < 40 ? message.format('MODERATIONMANAGER_INFRACTION_MAXTARGETSALT') : ''}`, { emoji: 'failure' });
|
||||
}
|
||||
|
||||
const silent = Boolean(message.guild._settings.silent || message.arguments.silent);
|
||||
this.client.logger.debug(`Silent infraction: ${silent}`);
|
||||
|
||||
const promises = [];
|
||||
for(const target of targets) {
|
||||
promises.push(new Infraction(this.client, {
|
||||
@ -34,13 +37,14 @@ class ModerationManager {
|
||||
message,
|
||||
target,
|
||||
reason,
|
||||
duration
|
||||
duration,
|
||||
silent
|
||||
}).execute());
|
||||
}
|
||||
|
||||
const responses = await Promise.all(promises);
|
||||
|
||||
const success = Boolean(responses.some((r) => !r.error));
|
||||
let success = Boolean(responses.some((r) => !r.error));
|
||||
const succeeded = responses.filter((r) => !r.error);
|
||||
const failed = responses.filter((r) => r.error);
|
||||
|
||||
@ -52,19 +56,20 @@ class ModerationManager {
|
||||
const { dictionary, targetType } = responses[0].infraction;
|
||||
|
||||
let string = "";
|
||||
if(success) {
|
||||
if(success && !silent) {
|
||||
string = message.format('MODERATIONMANAGER_INFRACTION_SUCCESS', {
|
||||
infraction: dictionary.past,
|
||||
targetType: `${targetType}${succeeded.length > 1 ? 's' : ''}`,
|
||||
target: succeeded.map((s) => `**${s.infraction.targetName}**`).join(', '),
|
||||
target: succeeded.map((s) => `**${Util.escapeMarkdown(s.infraction.targetName)}**`).join(', '),
|
||||
action: actions.prune ? ` and pruned \`${actions.prune}\` message${actions.prune > 1 ? 's' : ''}` : ''
|
||||
});
|
||||
} else {
|
||||
} else if((silent && failed.length > 0)) {
|
||||
if(silent) success = false;
|
||||
const format = failed.length === 1 ? "MODERATIONMANAGER_INFRACTION_SINGULARFAIL" : "MODERATIONMANAGER_INFRACTION_MULTIPLEFAIL";
|
||||
string = message.format(format, {
|
||||
infraction: dictionary.present,
|
||||
targetType: `${targetType}${failed.length > 1 ? 's' : ''}`,
|
||||
target: failed.length === 1 ? `**${failed[0].infraction.targetName}**` : failed.map((f) => `**${f.infraction.targetName}**`).join(', '),
|
||||
target: failed.length === 1 ? `**${Util.escapeMarkdown(failed[0].infraction.targetName)}**` : failed.map((f) => `**${f.infraction.targetName}**`).join(', '),
|
||||
reason: failed[0].reason
|
||||
});
|
||||
}
|
||||
@ -74,13 +79,14 @@ class ModerationManager {
|
||||
for(const fail of failed) {
|
||||
string += `\n${message.format('MODERATIONMANAGER_INFRACTION_FAIL', {
|
||||
infraction: dictionary.present,
|
||||
target: fail.infraction.targetName,
|
||||
target: Util.escapeMarkdown(fail.infraction.targetName),
|
||||
reason: fail.reason
|
||||
})}`;
|
||||
}
|
||||
}
|
||||
|
||||
return message.respond(string, { emoji: success ? 'success' : 'failure' });
|
||||
if(string) message.respond(string, { emoji: success ? 'success' : 'failure' });
|
||||
return succeeded;
|
||||
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,8 @@ class Kick extends Infraction {
|
||||
guild: opts.guild,
|
||||
channel: opts.channel,
|
||||
arguments: opts.arguments,
|
||||
color: 0xe8d54e,
|
||||
silent: opts.silent,
|
||||
color: 0xf7b045,
|
||||
dictionary: {
|
||||
past: 'kicked',
|
||||
present: 'kick'
|
||||
@ -33,12 +34,12 @@ class Kick extends Infraction {
|
||||
}
|
||||
|
||||
try {
|
||||
// await this.member.kick(this._reason);
|
||||
await this.member.kick(this._reason);
|
||||
} catch(error) {
|
||||
return this._fail('INFRACTION_ERROR');
|
||||
}
|
||||
|
||||
// await this.log();
|
||||
await this.log();
|
||||
return this._succeed();
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,8 @@
|
||||
const { stripIndents } = require('common-tags');
|
||||
const { Util } = 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.
|
||||
};
|
||||
|
||||
class Infraction {
|
||||
|
||||
@ -19,51 +23,93 @@ class Infraction {
|
||||
this.type = opts.type; //What type of infraction (mute, kick, etc.)
|
||||
|
||||
this.timestamp = new Date().getTime();
|
||||
this.duration = opts.duration || null; //How long the event will last. Must be in milliseconds.
|
||||
this.expiration = opts.duration ? opts.duration + this.timestamp : null; //When the event will expire
|
||||
this.reason = opts.reason;
|
||||
this.duration = opts.duration || null; //How long the action will last. Must be in milliseconds.
|
||||
|
||||
this.reason = opts.reason.length ? opts.reason : 'N/A';
|
||||
|
||||
this.silent = opts.silent;
|
||||
|
||||
this.points = 0;
|
||||
this.totalPoints = 0;
|
||||
this.expiration = 0;
|
||||
|
||||
this.color = opts.color; //Infraction-defined hexadecimal value to dictate what color the embed is.
|
||||
this.dictionary = opts.dictionary || {}; // { past: 'banned', present: 'ban' } Infraction-defined object for the correct spellings.
|
||||
|
||||
this._logMessage = null; //The message embed sent in the moderation-log. Full message content, not the ID.
|
||||
|
||||
this._resolved = false;
|
||||
this._logMessage = null; //The message embed sent in the moderation-log. Full message, not the ID.
|
||||
this._dmMessage = null; //The message embed sent in the target's direct messages. Full message, not the ID.
|
||||
|
||||
}
|
||||
|
||||
async log(opts) {
|
||||
async log() {
|
||||
|
||||
if(this.silent) {
|
||||
try {
|
||||
await this.message.delete();
|
||||
} catch(e) {} //eslint-disable-line no-empty
|
||||
}
|
||||
|
||||
const { moderationLog, moderationPoints } = this.guild._settings;
|
||||
|
||||
/* Handling */
|
||||
if(moderationPoints.enabled) {
|
||||
if(this.arguments.points) {
|
||||
this.points = parseInt(this.arguments.points.value);
|
||||
} else {
|
||||
this.points = moderationPoints.points[this.type] || 0;
|
||||
}
|
||||
if(this.arguments.expiration) {
|
||||
this.expiration = parseInt(this.arguments.expiration.value);
|
||||
} else {
|
||||
this.expiration = moderationPoints.expirations[this.type] || 0;
|
||||
}
|
||||
if(this.targetType === 'user') {
|
||||
this.totalPoints = await this.target.totalPoints(this.guild, this.points);
|
||||
}
|
||||
}
|
||||
|
||||
this.guild._settings.caseId++;
|
||||
this.case = this.guild._settings.caseId;
|
||||
await this.guild._updateSettings({
|
||||
caseId: this.case
|
||||
});
|
||||
|
||||
|
||||
console.log(`points: ${this.points}, total points: ${this.totalPoints}, expiration: ${this.expiration}`);
|
||||
|
||||
/* Logging */
|
||||
if(moderationLog.channel) {
|
||||
if(moderationLog.infractions.includes(this.type.toLowerCase())) {
|
||||
const channel = this.client.resolver.resolveChannel(moderationLog.channel, true, this.guild);
|
||||
if(!channel) return undefined;
|
||||
this._logMessage = await channel.send('', { embed: this.embed() });
|
||||
} else {
|
||||
this.client.logger.debug(`Did not log infraction ${this.type} because it is not in the infractions.`);
|
||||
}
|
||||
}
|
||||
|
||||
if(moderationPoints.enabled) {
|
||||
if(this.arguments.points) {
|
||||
this.points = parseInt(this.arguments.points.value);
|
||||
} else {
|
||||
this.points = moderationPoints.points[this.type] || 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(this.guild._settings.dmInfraction.value
|
||||
if(this.guild._settings.dmInfraction.enabled
|
||||
&& this.targetType === 'user') {
|
||||
let message = this.guild._settings.dmInfraction.custom[this.type] || this.guild._settings.dmInfraction.custom.default;
|
||||
if(!message) message = "";
|
||||
message = message
|
||||
.replace(/\{(guild|server)\}/ugim, this.guild.name)
|
||||
.replace(/\{user\}/ugim, this.target.tag)
|
||||
.replace(/\{infraction\}/ugim, this.dictionary.past); //add more if you want i should probably add a better system for this...
|
||||
try {
|
||||
await this.target.send(this.message.format('INFRACTION_DIRECTMESSAGE', { guild: this.guild.name }), {
|
||||
this._dmMessage = await this.target.send(message, {
|
||||
embed: this.embed()
|
||||
});
|
||||
} catch(e) {} //eslint-disable-line no-empty
|
||||
}
|
||||
|
||||
await this.save();
|
||||
//might be useful for automod related stuff
|
||||
// this.client.emit('infractionUpdate', {
|
||||
// action: 'new',
|
||||
// infraction: this
|
||||
// });
|
||||
|
||||
return this.save();
|
||||
|
||||
}
|
||||
|
||||
@ -72,6 +118,7 @@ class Infraction {
|
||||
provider: 'mongodb',
|
||||
request: {
|
||||
type: 'insertOne',
|
||||
collection: 'infractions',
|
||||
data: this.json
|
||||
}
|
||||
}).catch((error) => {
|
||||
@ -79,20 +126,24 @@ class Infraction {
|
||||
});
|
||||
}
|
||||
|
||||
embed(jumpTo = false) {
|
||||
embed() {
|
||||
|
||||
let description = this.message.format('INFRACTION_DESCRIPTION', {
|
||||
type: this.type,
|
||||
moderator: `${this.executor.tag} (${this.executor.id})`,
|
||||
reason: this.reason
|
||||
type: this.dictionary.past.toUpperCase(),
|
||||
moderator: `${Util.escapeMarkdown(this.executor.tag)}`,
|
||||
reason: this.reason.length > Constants.MaxCharacters ? `${this.reason.substring(0, Constants.MaxCharacters-3)}...` : this.reason
|
||||
});
|
||||
|
||||
if(this.duration) {
|
||||
description += this.message.format('INFRACTION_DESCRIPTIONDURATION', { duration: this._duration() });
|
||||
description += `\n${this.message.format('INFRACTION_DESCRIPTIONDURATION', { duration: this._duration() })}`;
|
||||
}
|
||||
|
||||
if(jumpTo) {
|
||||
description += this.message.format;
|
||||
if(this.points) {
|
||||
description += `\n${this.message.format('INFRACTION_DESCRIPTIONPOINTS', { points: this.points, total: this.totalPoints })}`;
|
||||
}
|
||||
|
||||
if(!this.silent) {
|
||||
description += `\n${this.message.format('INFRACTION_DESCRIPTIONJUMPTO', { name: 'Message', link: `https://discord.com/channels/${this.guild.id}/${this.channel.id}/${this.message.id}` })}`;
|
||||
}
|
||||
|
||||
return {
|
||||
@ -122,9 +173,10 @@ class Infraction {
|
||||
type: this.type,
|
||||
case: this.case,
|
||||
duration: this.duration,
|
||||
expiration: this.expiration,
|
||||
reason: this.reason,
|
||||
timestamp: this.timestamp
|
||||
timestamp: this.timestamp,
|
||||
points: this.points,
|
||||
expiration: this.expiration
|
||||
};
|
||||
}
|
||||
|
||||
@ -140,6 +192,12 @@ class Infraction {
|
||||
: this.guild.iconURL();
|
||||
}
|
||||
|
||||
get _reason() {
|
||||
let str = `[targetId:${this.target.id}] Executed by ${this.executor.tag} (${this.executor.id}) because: ${this.reason}`;
|
||||
if(str.length > 512) str = `${this.reason.substring(0, 509)}...`;
|
||||
return str;
|
||||
}
|
||||
|
||||
//Super Functions
|
||||
_succeed() {
|
||||
return {
|
||||
|
@ -55,7 +55,7 @@ class Util {
|
||||
if(!token) throw new Error("[util] Token missing.");
|
||||
return fetch("https://discordapp.com/api/v7/gateway/bot", {
|
||||
method: 'GET',
|
||||
headers: { Authorization: `Bot ${token.replace(/^Bot\s*/i, '')}` }
|
||||
headers: { Authorization: `Bot ${token.replace(/^Bot\s*/iu, '')}` }
|
||||
}).then((res) => {
|
||||
if (res.ok) return res.json();
|
||||
throw res;
|
||||
@ -71,6 +71,12 @@ class Util {
|
||||
};
|
||||
}
|
||||
|
||||
static escapeMarkdown(text, onlyCodeBlock = false, onlyInlineCode = false) {
|
||||
if(onlyCodeBlock) return text.replace(/```/gu, '`\u200b``');
|
||||
if(onlyInlineCode) return text.replace(/\\(`|\\)/gu, '$1').replace(/(`|\\)/gu, '\\$1');
|
||||
return text.replace(/\\(\*|_|`|~|\\)/gu, '$1').replace(/(\*|_|`|~|\\)/gu, '\\$1');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
3
util/defaults/defaultGuild.json
Normal file
3
util/defaults/defaultGuild.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"caseId": 0
|
||||
}
|
0
util/defaults/defaultUser.json
Normal file
0
util/defaults/defaultUser.json
Normal file
3
util/defaults/index.js
Normal file
3
util/defaults/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
DefaultGuild: require('./defaultGuild.json')
|
||||
};
|
@ -1179,6 +1179,11 @@ through@^2.3.6:
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
||||
|
||||
timestring@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/timestring/-/timestring-6.0.0.tgz#b0c7c331981ecf2066ce88bcfb8ee3ae32e7a0f6"
|
||||
integrity sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA==
|
||||
|
||||
tmp@^0.0.33:
|
||||
version "0.0.33"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||
|
Loading…
Reference in New Issue
Block a user