diff --git a/src/structure/DiscordClient.js b/src/structure/DiscordClient.js index cb9be26..c42b0f9 100644 --- a/src/structure/DiscordClient.js +++ b/src/structure/DiscordClient.js @@ -1,14 +1,15 @@ -const { Client } = require('discord.js'); +const { Client, Collection } = require('discord.js'); const chalk = require('chalk'); -const { Logger, Intercom, EventHooker, LocaleLoader, Registry, Dispatcher, Resolver } = require('./client/'); +const { Intercom, EventHooker, LocaleLoader, Registry, Dispatcher, Resolver, ModerationManager, RateLimiter } = require('./client/'); const { Observer, Command, Setting, Inhibitor } = require('./interfaces/'); +const { Logger } = require('../utilities'); const StorageManager = require('./storage/StorageManager.js'); -const { DefaultGuild } = require('../constants/'); +const { DefaultGuild, DefaultUser } = require('../constants/'); const options = require('../../options.json'); -const RateLimiter = require('./client/RateLimiter'); +const { GuildWrapper, UserWrapper } = require('./client/wrappers'); const Constants = { ComponentTypes: { @@ -29,7 +30,7 @@ class DiscordClient extends Client { ...options.discord.clientOptions }); - this.logger = new Logger(this); + this.logger = new Logger({ name: 'Client' }); this.eventHooker = new EventHooker(this); this.intercom = new Intercom(this); this.dispatcher = new Dispatcher(this); @@ -38,12 +39,16 @@ class DiscordClient extends Client { this.registry = new Registry(this); this.resolver = new Resolver(this); this.rateLimiter = new RateLimiter(this); + this.moderationManager = new ModerationManager(this); + // Originally used for evals, but being replaced by the wrappers collections this.wrapperClasses = { ...require('./client/wrappers') }; + this.guildWrappers = new Collection(); + this.userWrappers = new Collection(); - this._defaultConfig = null; + this._defaultConfig = {}; this._activity = 0; this._options = options; this._built = false; @@ -58,6 +63,14 @@ class DiscordClient extends Client { this._loadEevents(); + process.on('uncaughtException', (err) => { + this.logger.error(`Uncaught exception:\n${err.stack || err}`); + }); + + process.on('unhandledRejection', (err) => { + this.logger.error(`Unhandled rejection:\n${err.stack || err}`); + }); + } async build() { @@ -85,17 +98,31 @@ class DiscordClient extends Client { this.logger.info(`Built client in ${Date.now()-beforeTime}ms.`); await super.login(); + await this.ready(); - this.emit('built'); - this._built = true; return this; } - defaultConfig() { - if(this._defaultConfig) return this._defaultConfig; - const settings = this.registry.components.filter((c) => c._type === 'setting'); - let def = DefaultGuild; + // Wait until the client is actually ready, i.e. all structures from discord are created + ready() { + + return new Promise((resolve) => { + if (this._built) return resolve(); + this.once('ready', () => { + this._createWrappers(); + this._built = true; + this.emit('built'); + resolve(); + }); + }); + + } + + defaultConfig(type) { + if(this._defaultConfig[type]) return this._defaultConfig[type]; + const settings = this.registry.components.filter((c) => c._type === 'setting' && c.resolve === type); + let def = type === 'GUILD' ? DefaultGuild : DefaultUser; for(const setting of settings.values()) { if(setting.default !== null) { def = { @@ -104,7 +131,7 @@ class DiscordClient extends Client { }; } } - this._defaultConfig = def; + this._defaultConfig[type] = def; return def; } @@ -140,7 +167,7 @@ class DiscordClient extends Client { this.eventHooker.hook('ready', () => { const guilds = this.guilds.cache.size; - this.logger.info(`Client connected to ${chalk.bold(this.user.tag)} with ${chalk.bold(`${guilds} guild${guilds === 1 ? '' : 's'}`)}.`); + this.logger.status(`Client ready, connected to ${chalk.bold(this.user.tag)} with ${chalk.bold(`${guilds} guild${guilds === 1 ? '' : 's'}`)}.`); }); this.eventHooker.hook('componentUpdate', ({ component, type }) => { @@ -149,10 +176,12 @@ class DiscordClient extends Client { this.eventHooker.hook('guildCreate', (guild) => { this.logger.debug(`${chalk.bold('[GUILD]')} Joined guild ${chalk.bold(guild.name)} (${guild.id}).`); + this.guildWrappers.set(guild.id, new GuildWrapper(this, guild)); }); this.eventHooker.hook('guildDelete', (guild) => { this.logger.debug(`${chalk.bold('[GUILD]')} Left guild ${chalk.bold(guild.name)} (${guild.id}).`); + this.guildWrappers.delete(guild.id); }); this.eventHooker.hook('shardDisconect', () => { @@ -177,6 +206,39 @@ class DiscordClient extends Client { } + /** + * @private + */ + _createWrappers() { + + this.guilds.cache.forEach((guild) => { + this.guildWrappers.set(guild.id, new GuildWrapper(this, guild)); + }); + this.logger.info(`Created guild wrappers`); + + } + + getGuildWrapper(id) { + + if (this.guildWrappers.has(id)) return this.guildWrappers.get(id); + + const wrapper = new GuildWrapper(this, this.guilds.cache.get(id)); + this.guildWrappers.set(id, wrapper); + return wrapper; + + } + + async getUserWrapper(id) { + + if (this.userWrappers.has(id)) return this.userWrappers.get(id); + + const user = await this.users.fetch(id); + const wrapper = new UserWrapper(this, user); + this.userWrappers.set(id, wrapper); + return wrapper; + + } + } module.exports = DiscordClient; diff --git a/src/structure/client/ModerationManager.js b/src/structure/client/ModerationManager.js index 18245dd..82c5bc3 100644 --- a/src/structure/client/ModerationManager.js +++ b/src/structure/client/ModerationManager.js @@ -1,11 +1,11 @@ const { stripIndents } = require('common-tags'); -const { User } = require('discord.js'); +const { User, GuildMember } = require('discord.js'); const { Collection } = require('@discordjs/collection'); const { Emojis, Constants } = require('../../constants'); -const Util = require('../../Util.js'); +const { Util, Logger } = require('../../utilities'); const { Warn, Unmute, Mute, Kick, Softban, Unban, Ban, Addrole, Removerole, Lockdown, Unlockdown } = require('../components/infractions'); -const Logger = require('./Logger'); + const Constant = { MaxTargets: 10, //10+(10*premium-tier), theoretical max = 40 Infractions: { @@ -36,7 +36,7 @@ class ModerationManager { this.client = client; this.callbacks = new Collection(); - this.logger = new Logger(this); + this.logger = new Logger({ name: 'ModMngr' }); } @@ -189,39 +189,45 @@ class ModerationManager { } + async handleAutomod(Infraction, info) { + + } + async _handleTarget(Infraction, target, info) { - const { guild, reason, force } = info; - const { autoModeration, moderationPoints } = guild._settings; + + const { reason, force, wrapper } = info; + const { automod, modpoints } = wrapper._settings; const { type } = Infraction; + const { guild } = wrapper; + const targetWrapper = target instanceof User || target instanceof GuildMember ? + await this.client.getUserWrapper(target.id) : + target; // TODO: Channel wrapper if necessary let points = 0, expiration = 0; - if (moderationPoints.enabled) { - points = info.points || moderationPoints.points[type]; - expiration = info.expiration || moderationPoints.expirations[type]; - for (const [phrase, amount] of Object.entries(moderationPoints.associations)) { + if (modpoints.enabled) { + points = info.points || modpoints.points[type]; + expiration = info.expiration || modpoints.expirations[type]; + for (const [phrase, amount] of Object.entries(modpoints.associations)) { if (reason.toLowerCase().includes(phrase)) points = amount; } } const verify = async (infraction, escalated = false) => { - let verification = infraction.verify(info.executor, target, info.channel); - if (verification instanceof Promise) verification = await verification; + const verification = await infraction.verify(info.executor, target, info.channel); if (verification.error) return verification; if (infraction.targetType === 'USER') { - const userTarget = target instanceof User ? target : target.user; - const oldPoints = await userTarget.totalPoints(guild); - + const oldPoints = await targetWrapper.totalPoints(guild); const newPoints = oldPoints + infraction.points; - if (autoModeration.enabled && points > 0 && !force && !escalated) { + if (automod.enabled && points > 0 && !force && !escalated) { let result = null; - for (let [threshold, action] of Object.entries(autoModeration.thresholds)) { //eslint-disable-line prefer-const + for (let [threshold, action] of Object.entries(automod.thresholds)) { //eslint-disable-line prefer-const threshold = parseInt(threshold); if (oldPoints >= threshold) { - if (autoModeration.usePrevious) { + if (automod.usePrevious) { result = { threshold, ...action @@ -255,7 +261,7 @@ class ModerationManager { type, message: info.message || null, arguments: info.arguments, - guild: info.guild, + guild: info.wrapper, channel: info.channel, executor: info.executor, reason: info.reason, @@ -276,11 +282,11 @@ class ModerationManager { message: info.message || null, arguments: info.arguments, type: escalationClass.type, - guild: info.guild, + guild: info.wrapper, channel: info.channel, executor: info.executor, reason: stripIndents`${reason} - *${guild.format('INFRACTION_AUTOMODESCALATION')}*`, + *${wrapper.format('INFRACTION_AUTOMODESCALATION')}*`, duration: info.duration, data: info.data, points, @@ -294,7 +300,7 @@ class ModerationManager { if (response.error) return response; if (response.infraction.targetType === 'USER') { - response.infraction.totalPoints = await response.infraction.target.totalPoints(guild, { + response.infraction.totalPoints = await targetWrapper.totalPoints(guild, { points, expiration, timestamp: response.infraction.timestamp }); } @@ -324,8 +330,7 @@ class ModerationManager { for (const arg of Object.values(message.arguments)) { // console.log(arg, targets); if (actions[arg.name]) { - let action = actions[arg.name](message, arg, targets); - if (action instanceof Promise) action = await action; + const action = await actions[arg.name](message, arg, targets); responses[arg.name] = action; } }