diff --git a/src/structure/components/settings/moderation/WordFilterSetting.js b/src/structure/components/settings/moderation/WordFilterSetting.js index 162a7b9..7c4c07f 100644 --- a/src/structure/components/settings/moderation/WordFilterSetting.js +++ b/src/structure/components/settings/moderation/WordFilterSetting.js @@ -137,7 +137,7 @@ class WordFilterSetting extends FilterSetting { if (!response) { if (actions.find((ac) => ac.trigger === 'generic')) return { error: true, - msg: interaction.format('SETTING_FILTER_ACTIONS_EXISTING', { trigger: 'generic' }) + message: interaction.format('SETTING_FILTER_ACTIONS_EXISTING', { trigger: 'generic' }) }; actions.push(actionObject); actionObject.trigger = 'generic'; @@ -146,7 +146,7 @@ class WordFilterSetting extends FilterSetting { await response.delete(); if (['cancel', 'abort', 'exit'].includes(action.toLowerCase())) return { error: true, - msg: interaction.format('ERR_CANCEL') + message: interaction.format('ERR_CANCEL') }; const params = Util.parseQuotes(response.content).map(([param]) => param); @@ -159,7 +159,7 @@ class WordFilterSetting extends FilterSetting { const existing = actions.find((ac) => ac.trigger === first); if (existing) return { error: true, - msg: interaction.format('SETTING_FILTER_ACTIONS_EXISTING', { trigger: first }) + message: interaction.format('SETTING_FILTER_ACTIONS_EXISTING', { trigger: first }) }; actionObject.trigger = first; } else { @@ -178,7 +178,7 @@ class WordFilterSetting extends FilterSetting { ); } - if (!actionObject.trigger.length) return { error: true, msg: interaction.format('S_FILTER_INVALID_TRIGGER') }; + if (!actionObject.trigger.length) return { error: true, message: interaction.format('S_FILTER_INVALID_TRIGGER') }; } diff --git a/src/structure/interfaces/FilterSetting.js b/src/structure/interfaces/FilterSetting.js index e7bc84e..936d5b0 100644 --- a/src/structure/interfaces/FilterSetting.js +++ b/src/structure/interfaces/FilterSetting.js @@ -1,5 +1,6 @@ const Setting = require("./Setting.js"); const Util = require('../../Util.js'); +const { inspect } = require('util'); const validInfractions = ['WARN', 'MUTE', 'KICK', 'BAN', 'SOFTBAN']; @@ -8,31 +9,33 @@ class FilterSetting extends Setting { async _action(interaction, method, actions, { _wordWatcher } = {}) { if (!['add', 'remove', 'edit', 'list', 'reset'].includes(method)) - return { error: true, msg: interaction.format('ERR_INVALID_METHOD', { method }) }; + return { error: true, message: interaction.format('ERR_INVALID_METHOD', { method }) }; + this.client.logger.debug(`_action method:${method}`); let result = null, index = null; if (method === 'add') { if (actions.length >= 5 && _wordWatcher) return { error: true, - msg: interaction.format('SETTING_WORDWATCHER_ACTION_LIMIT') + message: interaction.format('SETTING_WORDWATCHER_ACTION_LIMIT') }; result = await this._createAction(interaction, actions, { _wordWatcher }); index = 'SETTING_FILTER_ACTION_ADD'; } else if (method === 'edit') { result = await this._editAction(interaction, actions); - index = 'S_FILTER_ACTION_EDIT'; + index = 'SETTING_FILTER_ACTION_EDIT'; } else if (method === 'remove') { result = await this._removeAction(interaction, actions); - index = 'S_FILTER_ACTION_REMOVE'; + index = 'SETTING_FILTER_ACTION_REMOVE'; } else if (method === 'list') { this._listActions(interaction, actions); return; } else if (method === 'reset') { result = {}; actions = []; - index = 'S_FILTER_ACTION_RESET'; + index = 'SETTING_FILTER_ACTION_RESET'; } + this.client.logger.debug(`_action result: ${inspect(result)}`); if (result.error) return result; @@ -58,19 +61,15 @@ class FilterSetting extends Setting { const valid = [...validInfractions]; if (_wordWatcher) valid.push('DELETE'); //Used for wordwatcher quick actions to just delete the flagged message - let response = await interaction.promptMessage(interaction.format('SETTING_FILTER_ACTION_ADD_START', - { + const action = await this._prompt(interaction, { + index: 'SETTING_FILTER_ACTION_ADD_START', + params: { valid: valid.join('`, `'), wordwatcher: _wordWatcher ? interaction.format('SETTING_WORDWATCHER_ACTION_ADD_START', { amount: actions.length }) : '' - }), { editInteraction: true }); - if (!response) return { error: true, msg: interaction.format('ERR_TIMEOUT') }; - - const [action] = response.content.toLowerCase().split(' '); - await response.delete(); - if (['cancel', 'abort', 'exit'].includes(action)) return { - error: true, - msg: interaction.format('ERR_CANCEL') - }; + } + }); + if (action.error) return action; + this.client.logger.debug(`_createAction content: ${action}`); const { resolver } = this.client; const infType = resolver.resolveInfraction(action); @@ -78,22 +77,16 @@ class FilterSetting extends Setting { || !validInfractions.includes(infType)) && !['del', 'delete'].includes(action)) return { error: true, - msg: interaction.format('SETTING_FILTER_INVALID_INFRACTION', { valid: validInfractions.join('`, `') }) + message: interaction.format('SETTING_FILTER_INVALID_INFRACTION', { valid: validInfractions.join('`, `') }) }; actionObject.type = infType; //Add a duration to the action if (['MUTE', 'BAN'].includes(infType)) { - response = await interaction.promptMessage(interaction.format('SETTING_FILTER_ACTION_ADD_TIMER', - { action: infType }), { editInteraction: true }); - if (!response) return { error: true, msg: interaction.format('ERR_TIMEOUT') }; - const content = response.content.toLowerCase(); - await response.delete(); - if (['cancel', 'abort', 'exit'].includes(content)) return { - error: true, - msg: interaction.format('ERR_CANCEL') - }; + const content = await this._prompt(interaction, { index: 'SETTING_FILTER_ACTION_ADD_TIMER', params: { action: infType } }); + if (content.error) return content; + this.client.logger.debug(`Duration: ${content}`); if (!['no', 'n'].includes(content)) { const time = resolver.resolveTime(content); @@ -108,14 +101,9 @@ class FilterSetting extends Setting { if (settings.modpoints?.enabled) { // Points - response = await interaction.promptMessage(interaction.format('SETTING_FILTER_ACTION_ADD_POINTS'), { editInteraction: true }); - if (!response) return { error: true, msg: interaction.format('ERR_TIMEOUT') }; - const content = response.content.toLowerCase(); - await response.delete(); - if (['cancel', 'abort', 'exit'].includes(content)) return { - error: true, - msg: interaction.format('ERR_CANCEL') - }; + const content = await this._prompt(interaction, { index: 'SETTING_FILTER_ACTION_ADD_POINTS' }); + if (content.error) return content; + this.client.logger.debug(`Points: ${content}`); if (!['no', 'n'].includes(content)) { @@ -136,21 +124,14 @@ class FilterSetting extends Setting { // Expiration if (actionObject.points) { - response = await interaction.promptMessage( - interaction.format('SETTING_FILTER_ACTION_ADD_EXPIRATION'), - { editInteraction: true } - ); - if (!response) return { error: true, msg: interaction.format('ERR_TIMEOUT') }; - const content = response.content.toLowerCase(); - await response.delete(); - if (['cancel', 'abort', 'exit'].includes(content)) return { - error: true, - msg: interaction.format('ERR_CANCEL') - }; + + const content = await this._prompt(interaction, { index: 'SETTING_FILTER_ACTION_ADD_EXPIRATION' }); + if (content.error) return content; + this.client.logger.debug(`Expiration: ${content}`); if (!['no', 'n'].includes(content)) { - const time = resolver.resolveTime(response.content); + const time = resolver.resolveTime(content); if (!time) interaction.channel.send(interaction.format('SETTING_FILTER_ACTION_ADD_EXPIRATION_FAIL')); else actionObject.expiration = time; @@ -162,14 +143,9 @@ class FilterSetting extends Setting { //Should it force the action if automod is enabled if (settings.autoModeration?.enabled) { - response = await interaction.promptMessage(interaction.format('SETTING_FILTER_ACTION_ADD_FORCE'), { editInteraction: true }); - if (!response) return { error: true, msg: interaction.format('ERR_TIMEOUT') }; - const content = response.content.toLowerCase(); - await response.delete(); - if (['cancel', 'abort', 'exit'].includes(content)) return { - error: true, - msg: interaction.format('ERR_CANCEL') - }; + const content = await this._prompt(interaction, { index: 'SETTING_FILTER_ACTION_ADD_FORCE' }); + if (content.error) return content; + this.client.logger.debug(`Automod: ${content}`); if (['yes', 'ye', 'y'].includes(content)) { actionObject.force = true; @@ -179,14 +155,10 @@ class FilterSetting extends Setting { //Should the action also prune the user's messages if (infType !== 'SOFTBAN') { - response = await interaction.promptMessage(interaction.format('SETTING_FILTER_ACTION_ADD_PRUNE'), { editInteraction: true }); - if (!response) return { error: true, msg: interaction.format('ERR_TIMEOUT') }; - const content = response.content.toLowerCase(); - await response.delete(); - if (['cancel', 'abort', 'exit'].includes(content)) return { - error: true, - msg: interaction.format('ERR_CANCEL') - }; + + const content = await this._prompt(interaction, { index: 'SETTING_FILTER_ACTION_ADD_PRUNE' }); + if (content.error) return content; + this.client.logger.debug(`Softban: ${content}`); if (['yes', 'ye', 'y'].includes(content)) { actionObject.prune = true; @@ -197,12 +169,227 @@ class FilterSetting extends Setting { //Implemented in the subclass const result = await this._createTrigger(interaction, action, actionObject, actions); if (result && result.error) return result; + this.client.logger.debug(`Trigger result: ${result}`); actions.push(actionObject); return actionObject; } + async _createTrigger() { + throw new Error(`${this.name} requires an implementation for \`_createTrigger\``); + } + + async _removeAction(interaction, actions) { + + if (!actions.length) return { error: true, message: interaction.format('SETTING_FILTER_ACTION_REMOVE_NO_ACTIONS') }; + const embed = this._createActionEmbed(interaction, actions); + + const content = await this._prompt(interaction, { index: 'SETTING_FILTER_ACTION_REMOVE_START', time: 60, embeds: [embed] }); + if (content.error) return content; + + if (['cancel', 'abort', 'exit'].includes(content)) return { + error: true, + message: interaction.format('ERR_CANCEL') + }; + + const index = parseInt(content); + if (isNaN(index)) return { error: true, message: interaction.format('ERR_NAN') }; + if (index < 0 || index > actions.length - 1) return { error: true, message: interaction.format('ERR_INDEX_OUT_OF_BOUNDS') }; + + return actions.splice(index, 1)[0]; + + } + + async _editAction(interaction, actions) { + + const embed = this._createActionEmbed(interaction, actions); + const content = await this._prompt(interaction, { embed, index: 'SETTING_FILTER_ACTION_EDIT_START', time: 60 }); + if (content.error) return content; + + const index = parseInt(content); + if (isNaN(index)) return { error: true, message: interaction.format('ERR_NAN') }; + if (index < 0 || index > actions.length - 1) return { error: true, message: interaction.format('ERR_INDEX_OUT_OF_BOUNDS') }; + + const action = actions[index]; + + //Which property do you want to edit? + const prop = await this._prompt(interaction, { index: 'SETTING_FILTER_ACTION_EDIT_SELECT' }); + if (content.error) return content; + + const properties = Object.keys(action); + if (!properties.includes(prop)) return { + error: true, + message: interaction.format('SETTING_FILTER_ACTION_EDIT_BADPROP') + }; + + if (prop === 'trigger') return this._editTrigger(interaction, actions, action); + else if (prop === 'duration') return this._editDuration(interaction, actions, action); + else if (prop === 'points') return this._editPoints(interaction, actions, action); + else if (prop === 'expiration') return this._editExpiration(interaction, actions, action); + else if (prop === 'action') return this._editType(interaction, actions, action); + else if (['force', 'prune'].includes(prop)) return this._editBool(interaction, actions, action, prop); + + } + + async _editExpiration(interaction, actions, action) { + + if (!interaction.guild._settings.moderationPoints?.enabled) return { + error: true, + message: interaction.format('SETTING_FILTER_ACTION_EDIT_POINTS_DISABLED') + }; + + const content = await this._prompt(interaction, { index: 'SETTING_FILTER_ACTION_EDIT_EXPIRATION', time: 60 }); + if (content.error) return content; + + const { resolver } = this.client; + + const time = resolver.resolveTime(content); + if (time === null) interaction.formattedRespond('SETTING_FILTER_ACTION_ADD_EXPIRATION_FAIL'); + else action.expiration = time; + + } + + async _editBool(interaction, actions, action, prop) { + + const content = await this._prompt(interaction, { params: { prop }, time: 120 }); + if (content.error) return content; + + const { resolver } = this.client; + const bool = resolver.resolveBoolean(content); + if (bool === null) return { + error: true, + message: interaction.format('SETTING_FILTER_ACTION_EDIT_BOOL_INVALID') + }; + + action[prop] = bool; + return action; + + } + + async _editPoints(interaction, actions, action) { + + if (!interaction.guild._settings.moderationPoints?.enabled) return { + error: true, + message: interaction.format('SETTING_FILTER_ACTION_EDIT_POINTS_DISABLED') + }; + + const content = await this._prompt(interaction, { index: 'SETTING_FILTER_ACTION_EDIT_POINTS', time: 60 }); + if (content.error) return content; + + const reg = /(\d{1,3})\s?(points?|pts?|p)?/iu; + if (!reg.test(content)) return { + error: true, + message: interaction.format('SETTING_FILTER_ACTION_EDIT_POINTS_FAIL') + }; + + const match = content.match(reg); + let points = parseInt(match[1]); + if (points < 0 || points > 100) { + if (points < 0) points = 0; + else if (points > 100) points = 100; + interaction.channel.send(interaction.format('SETTING_FILTER_ACTION_ADD_POINTS_RANGE', { value: points })); + } + + action.points = points; + return action; + + } + + async _editType(interaction, actions, action) { + + const content = await this._prompt(interaction, { + index: 'SETTING_FILTER_ACTION_EDIT_TYPE', + params: { valid: validInfractions.join('`, `') }, + time: 120 + }); + + if (!validInfractions.includes(content.toUpperCase())) return { + error: true, + message: interaction.format('SETTING_FILTER_ACTION_EDIT_INVALID_TYPE') + }; + + action.type = content.toUpperCase(); + if (['BAN', 'MUTE'].includes(action.type) && !action.duration) return this._editDuration(interaction, actions, action); + + action.duration = 0; + return action; + + } + + async _editDuration(interaction, actions, action) { + + if (!['MUTE', 'BAN'].includes(action.type)) return { + error: true, + message: interaction.format('SETTING_FILTER_ACTION_EDIT_DURATION_ERR', { action: action.type }) + }; + + const content = await this._prompt(interaction, { index: 'SETTING_FILTER_ACTION_EDIT_DURATION', time: 120 }); + if (content.error) return content; + + if (content === '0') action.duration = 0; + else { + const { resolver } = this.client; + const time = resolver.resolveTime(content); + if (!time) return { + error: true, + message: interaction.format('SETTING_FILTER_ACTION_EDIT_DURATION_ERR2') + }; + action.duration = time; + } + + return action; + + } + + async _prompt(interaction, { params = {}, embeds = [], index, time = 120 }) { + + const response = await interaction.promptMessage( + interaction.format(index, params), + { time: time * 1000, editInteraction: true, embeds } + ); + + if (!response) return { error: true, message: interaction.format('ERR_TIMEOUT') }; + const content = response.content.toLowerCase(); + await response.delete(); + if (['cancel', 'abort', 'exit'].includes(content)) return { + error: true, + message: interaction.format('ERR_CANCEL') + }; + + return content; + + } + + //Create embed to display current actions + _createActionEmbed(interaction, actions) { + + const { resolver } = this.client; + const embed = { + fields: actions.map((action) => { + return { + name: `**[${actions.indexOf(action)}]** ${action.type}`, + value: interaction.format('SETTING_FILTER_ACTION_PROPERTIES', { + duration: action.duration ? resolver.timeAgo(action.duration) : 'INF', + points: action.points || interaction.format('ON_OFF_TOGGLE', { toggle: false }, true), + prune: interaction.format('ON_OFF_TOGGLE', { toggle: action.prune }, true), + force: interaction.format('ON_OFF_TOGGLE', { toggle: action.force }, true), + trigger: action.trigger instanceof Array ? action.trigger.join(', ') : '`' + action.trigger + '`' + }), + inline: true + }; + }), + color: 619452 + }; + if (embed.fields.length % 3 === 2) embed.fields.push({ + name: '\u200b', + value: '\u200b', + inline: true + }); + return embed; + + } + } module.exports = FilterSetting; \ No newline at end of file diff --git a/src/structure/interfaces/Setting.js b/src/structure/interfaces/Setting.js index 1ac487a..eb4fe2d 100644 --- a/src/structure/interfaces/Setting.js +++ b/src/structure/interfaces/Setting.js @@ -174,10 +174,6 @@ class Setting extends Component { return list; } - edit(list, params) { - - } - // Functions for message component based settings /** * Abstract components method, should be overridden by the implementing class and called, returns the select menu component.