const Setting = require('./Setting.js'); const validInfractions = ['WARN', 'MUTE', 'KICK', 'BAN', 'SOFTBAN']; module.exports = class FilterSetting extends Setting { async _action(message, args, setting, { _wordWatcher } = {}) { const { resolver } = this.client; const submethod = args.shift().toLowerCase(); const resolved = await resolver.resolveMethod([submethod], { allowedMethods: ['add', 'remove', 'edit', 'list', 'reset'] }); if (!resolved) return { error: true, msg: message.format('ERR_INVALID_METHOD', { method: submethod }) }; let result = null, index = null; if (resolved.method === 'add') { if (setting.actions.length >= 5 && _wordWatcher) return { error: true, msg: message.format('S_WORDWATCHER_ACTION_LIMIT') }; result = await this._createAction(message, setting, { _wordWatcher }); index = 'S_FILTER_ACTION_ADD'; } else if (resolved.method === 'edit') { result = await this._editAction(message, setting); index = 'S_FILTER_ACTION_EDIT'; } else if (resolved.method === 'remove') { result = await this._removeAction(message, setting); index = 'S_FILTER_ACTION_REMOVE'; } else if (resolved.method === 'list') { this._listActions(message, setting); return; } else if (resolved.method === 'reset') { result = {}; setting.actions = []; index = 'S_FILTER_ACTION_RESET'; } if (result.error) return result; //setting.actions.push(result); //console.log(result); return { index, type: result.type, duration: result.duration ? resolver.timeAgo(result.duration) : 'INF', points: result.points || message.format('ON_OFF_TOGGLE', { toggle: false }, true), force: message.format('ON_OFF_TOGGLE', { toggle: result.force }, true), prune: message.format('ON_OFF_TOGGLE', { toggle: result.prune }, true), trigger: result.trigger instanceof Array ? '||' + result.trigger.join(', ') + '||' : '`' + result.trigger + '`' }; } async _createAction(message, setting, { _wordWatcher }) { const actionObject = { type: null, duration: null, points: null, expiration: null, force: false, prune: false }; const valid = [...validInfractions]; if (_wordWatcher) valid.push('DELETE'); //Used for wordwatcher quick actions to just delete the flagged message let response = await message.prompt(message.format('S_FILTER_ACTION_ADD_START', { valid: valid.join('`, `'), wordwatcher: _wordWatcher ? message.format('S_WORDWATCHER_ACTION_ADD_START', { amount: setting.actions.length }) : '' })); if (!response) return { error: true, msg: message.format('ERR_TIMEOUT') }; const { resolver } = this.client; const [action] = response.content.toLowerCase().split(' '); if (['cancel', 'abort', 'exit'].includes(response.content.toLowerCase())) return { error: true, msg: message.format('ERR_CANCEL') }; const infType = resolver.resolveInfraction(action); if ((!infType || !validInfractions.includes(infType)) && !['del', 'delete'].includes(action)) return { error: true, msg: message.format('S_FILTER_INVALID_INFRACTION', { valid: validInfractions.join('`, `') }) }; actionObject.type = infType; //Add a duration to the action if (['MUTE', 'BAN'].includes(infType)) { response = await message.prompt(message.format('S_FILTER_ACTION_ADD_TIMER', { action: infType })); if (!response) return { error: true, msg: message.format('ERR_TIMEOUT') }; if (['cancel', 'abort', 'exit'].includes(response.content.toLowerCase())) return { error: true, msg: message.format('ERR_CANCEL') }; if (!['no', 'n'].includes(response.content.toLowerCase())) { const time = resolver.resolveTime(response.content); if (!time) message.formattedRespond('S_FILTER_ACTION_ADD_TIMER_FAIL'); else actionObject.duration = time; } } const settings = message.guild._settings; //Add points & expiration to action if modpoints are enabled if (settings.moderationPoints?.enabled) { // Points response = await message.prompt(message.format('S_FILTER_ACTION_ADD_POINTS')); if (!response) return { error: true, msg: message.format('ERR_TIMEOUT') }; if (['cancel', 'abort', 'exit'].includes(response.content.toLowerCase())) return { error: true, msg: message.format('ERR_CANCEL') }; if (!['no', 'n'].includes(response.content.toLowerCase())) { const points = /(\d{1,3})\s?(points?|pts?|p)?/iu; const match = response.content.match(points); if (!match) message.formattedRespond('S_FILTER_ACTION_ADD_POINTS_FAIL'); else { let value = parseInt(match[1]); if (value < 0 || value > 100) { if (value < 0) value = 0; else value = 100; message.formattedRespond('S_FILTER_ACTION_ADD_POINTS_RANGE', { params: { value } }); } actionObject.points = value; } } // Expiration if (actionObject.points) { response = await message.prompt(message.format('S_FILTER_ACTION_ADD_EXPIRATION')); if (!response) return { error: true, msg: message.format('ERR_TIMEOUT') }; if (['cancel', 'abort', 'exit'].includes(response.content.toLowerCase())) return { error: true, msg: message.format('ERR_CANCEL') }; if (!['no', 'n'].includes(response.content.toLowerCase())) { const time = resolver.resolveTime(response.content); if (!time) message.formattedRespond('S_FILTER_ACTION_ADD_EXPIRATION_FAIL'); else actionObject.expiration = time; } } } //Should it force the action if automod is enabled if (settings.autoModeration?.enabled) { response = await message.prompt(message.format('S_FILTER_ACTION_ADD_FORCE')); if (!response) return { error: true, msg: message.format('ERR_TIMEOUT') }; if (['cancel', 'abort', 'exit'].includes(response.content.toLowerCase())) return { error: true, msg: message.format('ERR_CANCEL') }; if (['yes', 'ye', 'y'].includes(response.content.toLowerCase())) { actionObject.force = true; } } //Should the action also prune the user's messages if (infType !== 'SOFTBAN') { response = await message.prompt(message.format('S_FILTER_ACTION_ADD_PRUNE')); if (!response) return { error: true, msg: message.format('ERR_TIMEOUT') }; if (['cancel', 'abort', 'exit'].includes(response.content.toLowerCase())) return { error: true, msg: message.format('ERR_CANCEL') }; if (['yes', 'ye', 'y'].includes(response.content.toLowerCase())) { actionObject.prune = true; } } //What should trigger the action? //Implemented in the subclass const result = await this._createTrigger(message, action, actionObject, setting); if (result && result.error) return result; setting.actions.push(actionObject); return actionObject; } async _createTrigger() { throw new Error(`${this.name} requires an implementation for \`_createTrigger\``); } async _removeAction(message, setting) { const { actions } = setting; if (!actions.length) return { error: true, msg: message.format('S_FILTER_ACTION_REMOVE_NO_ACTIONS') }; const embed = this._createActionEmbed(message, setting); const response = await message.prompt(message.format('S_FILTER_ACTION_REMOVE_START'), { embed }, { time: 60 * 1000 }); if (!response) return { error: true, msg: message.format('ERR_TIMEOUT') }; if (['cancel', 'abort', 'exit'].includes(response.content.toLowerCase())) return { error: true, msg: message.format('ERR_CANCEL') }; const index = parseInt(response.content); if (isNaN(index)) return { error: true, msg: message.format('ERR_NAN') }; if (index < 0 || index > actions.length - 1) return { error: true, msg: message.format('ERR_INDEX_OUT_OF_BOUNDS') }; return setting.actions.splice(index, 1)[0]; } async _editAction(message, setting) { const { actions } = setting; const embed = this._createActionEmbed(message, setting); let response = await message.prompt({ content: message.format('S_FILTER_ACTION_EDIT_START'), embed }, { time: 60 * 1000 }); if (!response) return { error: true, msg: message.format('ERR_TIMEOUT') }; if (['cancel', 'abort', 'exit'].includes(response.content.toLowerCase())) return { error: true, msg: message.format('ERR_CANCEL') }; const index = parseInt(response.content); if (isNaN(index)) return { error: true, msg: message.format('ERR_NAN') }; if (index < 0 || index > actions.length - 1) return { error: true, msg: message.format('ERR_INDEX_OUT_OF_BOUNDS') }; const action = actions[index]; //Which property do you want to edit? const properties = Object.keys(action); response = await message.prompt(message.format('S_FILTER_ACTION_EDIT_SELECT', { properties: properties.join('`, `') })); if (!response) return { error: true, msg: message.format('ERR_TIMEOUT') }; if (['cancel', 'abort', 'exit'].includes(response.content.toLowerCase())) return { error: true, msg: message.format('ERR_CANCEL') }; const prop = response.content.toLowerCase(); if (!properties.includes(prop)) return { error: true, msg: message.format('S_FILTER_ACTION_EDIT_BADPROP') }; if (prop === 'trigger') return this._editTrigger(message, setting, action); else if (prop === 'duration') return this._editDuration(message, setting, action); else if (prop === 'points') return this._editPoints(message, setting, action); else if (prop === 'expiration') return this._editExpiration(message, setting, action); else if (prop === 'action') return this._editType(message, setting, action); else if (['force', 'prune'].includes(prop)) return this._editBool(message, setting, action, prop); } async _editExpiration(message, setting, action) { if (!message.guild._settings.moderationPoints?.enabled) return { error: true, msg: message.format('S_FILTER_ACTION_EDIT_POINTS_DISABLED') }; const response = await message.prompt(message.format('S_FILTER_ACTION_EDIT_EXPIRATION', {}), { time: 60 * 1000 }); if (!response) return { error: true, msg: message.format('ERR_TIMEOUT') }; if (['cancel', 'abort', 'exit'].includes(response.content.toLowerCase())) return { error: true, msg: message.format('ERR_CANCEL') }; const { resolver } = this.client; const time = resolver.resolveTime(response.content); if (time === null) message.formattedRespond('S_FILTER_ACTION_ADD_EXPIRATION_FAIL'); else action.expiration = time; } async _editBool(message, setting, action, prop) { const response = await message.prompt(message.format('S_FILTER_ACTION_EDIT_BOOL', { prop }), { time: 120 * 1000 }); const { resolver } = this.client; if (!response) return { error: true, msg: message.format('ERR_TIMEOUT') }; if (['cancel', 'abort', 'exit'].includes(response.content.toLowerCase())) return { error: true, msg: message.format('ERR_CANCEL') }; const bool = resolver.resolveBoolean(response.content); if (bool === null) return { error: true, msg: message.format('S_FILTER_ACTION_EDIT_BOOL_INVALID') }; action[prop] = bool; return action; } async _editPoints(message, setting, action) { if (!message.guild._settings.moderationPoints?.enabled) return { error: true, msg: message.format('S_FILTER_ACTION_EDIT_POINTS_DISABLED') }; const response = await message.prompt(message.format('S_FILTER_ACTION_EDIT_POINTS', {}), { time: 60 * 1000 }); if (!response) return { error: true, msg: message.format('ERR_TIMEOUT') }; if (['cancel', 'abort', 'exit'].includes(response.content.toLowerCase())) return { error: true, msg: message.format('ERR_CANCEL') }; const reg = /(\d{1,3})\s?(points?|pts?|p)?/iu; if (!reg.test(response.content)) return { error: true, msg: message.format('S_FILTER_ACTION_EDIT_POINTS_FAIL') }; const match = response.content.match(reg); let points = parseInt(match[1]); if (points < 0 || points > 100) { if (points < 0) points = 0; else if (points > 100) points = 100; message.respond('S_FILTER_ACTION_ADD_POINTS_RANGE', { value: points }); } action.points = points; return action; } async _editType(message, setting, action) { const response = await message.prompt(message.format('S_FILTER_ACTION_EDIT_TYPE', { valid: validInfractions.join('`, `') }), { time: 120 * 1000 }); if (!response) return { error: true, msg: message.format('ERR_TIMEOUT') }; if (['cancel', 'abort', 'exit'].includes(response.content.toLowerCase())) return { error: true, msg: message.format('ERR_CANCEL') }; if (!validInfractions.includes(response.content.toUpperCase())) return { error: true, msg: message.format('S_FILTER_ACTION_EDIT_INVALID_TYPE') }; action.type = response.content.toUpperCase(); if (['BAN', 'MUTE'].includes(action.type) && !action.duration) return this._editDuration(message, setting, action); action.duration = 0; return action; } async _editDuration(message, setting, action) { if (!['MUTE', 'BAN'].includes(action.type)) return { error: true, msg: message.format('S_FILTER_ACTION_EDIT_DURATION_ERR', { action: action.type }) }; const response = await message.prompt(message.format('S_FILTER_ACTION_EDIT_DURATION', {}), { time: 120 * 1000 }); const { resolver } = this.client; if (!response) return { error: true, msg: message.format('ERR_TIMEOUT') }; if (['cancel', 'abort', 'exit'].includes(response.content.toLowerCase())) return { error: true, msg: message.format('ERR_CANCEL') }; if (response.content === '0') { action.duration = 0; } else { const time = resolver.resolveTime(response.content); if (!time) return { error: true, msg: message.format('S_FILTER_ACTION_EDIT_DURATION_ERR2') }; action.duration = time; } return action; } async _listActions(message, setting) { if (!setting.actions.length) return message.formattedRespond('S_FILTER_NOACTIONS'); return message.respond(message.format('S_FILTER_CURRENT_ACTIONS'), { embed: this._createActionEmbed(message, setting) }); } //Create embed to display current actions _createActionEmbed(message, setting) { const { resolver } = this.client; const { actions } = setting; const embed = { fields: actions.map((action) => { return { name: `**[${actions.indexOf(action)}]** ${action.type}`, value: message.format('S_FILTER_ACTION_PROPERTIES', { duration: action.duration ? resolver.timeAgo(action.duration) : 'INF', points: action.points || message.format('ON_OFF_TOGGLE', { toggle: false }, true), prune: message.format('ON_OFF_TOGGLE', { toggle: action.prune }, true), force: message.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; } };