major moderation changes

This commit is contained in:
nolan 2020-06-04 12:59:09 -05:00
parent a258522e5f
commit 9dc6fddc79
24 changed files with 314 additions and 141 deletions

View File

@ -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",

View File

@ -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}

View File

@ -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"
}

View File

@ -318,6 +318,20 @@ 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());
});
});
}
}
module.exports = MongoDBProvider;

View File

@ -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 = {

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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;

View File

@ -57,22 +57,14 @@ class CommandHandler extends Observer {
}
if(command.keepQuotes) {
let temporaryParameters = [];
for(let parameter of response.parameters) {
if(parameter.includes(" ")) {
parameter = `"${parameter}"`;
temporaryParameters = [ ...temporaryParameters, ...parameter.split(' ') ];
message.parameters = response.parameters.map(([param, isQuote]) => (isQuote ? `"${param}"` : param));
} else {
temporaryParameters.push(parameter);
}
}
response.parameters = temporaryParameters;
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,11 +105,17 @@ class CommandHandler extends Observer {
} else if(matches && (matches[1] || matches[3])) {
const [,, name] = matches;
const number = matches[1] || matches[3];
const argument = longArgs[name];
if(argument && argument.types.includes('VERBAL')) {
arg = longArgs[name];
value = number;
}
} else if(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,12 +467,12 @@ 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 = [],
@ -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 ]);
}
});
}

View File

@ -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;

View File

@ -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
}

View File

@ -35,7 +35,7 @@ class SilentSetting extends Setting {
fields(guild) {
return {
name: "》Silent",
name: "》Enabled",
value: `\`${guild._settings.silent}\``
};
}

View File

@ -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

View File

@ -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;
}

View File

@ -13,7 +13,8 @@ const Constants = {
'MEMBER',
'CHANNEL',
'TEXTCHANNEL',
'VOICECHANNEL'
'VOICECHANNEL',
'TIME'
],
ArgumentTypes: [
'FLAG',

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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;
if(moderationLog.channel) {
if(moderationLog.infractions.includes(this.type.toLowerCase())) {
const channel = this.client.resolver.resolveChannel(moderationLog.channel, true, this.guild);
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.`);
}
}
/* 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);
}
}
if(this.guild._settings.dmInfraction.value
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(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 {

View File

@ -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');
}
}

View File

@ -0,0 +1,3 @@
{
"caseId": 0
}

View File

3
util/defaults/index.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
DefaultGuild: require('./defaultGuild.json')
};

View File

@ -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"