diff --git a/src/structure/components/settings/moderation/MuteSetting.js b/src/structure/components/settings/moderation/MuteSetting.js index eb4cab9..e8eed9a 100644 --- a/src/structure/components/settings/moderation/MuteSetting.js +++ b/src/structure/components/settings/moderation/MuteSetting.js @@ -1,5 +1,25 @@ const { Setting, CommandOption } = require('../../../interfaces/'); const { inspect } = require('util'); +const Util = require('../../../../Util'); + +const { Emojis, Constants: { PermissionNames, EmbedLimits } } = require('../../../../constants'); + +const CONSTANTS = { + MaxChars: 98 +}; + +const addField = (array, type) => { + const field = { + name: type === 'text' ? 'Text Channels' : 'Voice Channels', + value: '' + }; + const sorted = array.sort((a, b) => a.position - b.position) || []; + for (const i of sorted) { + const text = `${Emojis.role} **${i.role}** has ${i.permissions.map((p) => `\`${PermissionNames[p]}\``).join(', ')} in ${Emojis[i.type === 'text' ? 'text-channel' : 'voice-channel']} **${i.channel}**\n`; + if (field.value.length + text.length <= EmbedLimits.fieldValue) field.value += text; + } + return field; +}; class MuteSetting extends Setting { @@ -7,7 +27,7 @@ class MuteSetting extends Setting { super(client, { name: 'mute', - description: 'Assign a mute role or configure mute type.', + description: 'Assign or create a mute role oan configure mute options.', module: 'moderation', display: 'Mute', emoji: { @@ -17,14 +37,19 @@ class MuteSetting extends Setting { default: { type: 0, role: null, - defaultDuration: 3600, - allowPermanent: false + default: 3600, // 1h + permanent: false }, commandOptions: [ + new CommandOption({ + type: 'STRING', + name: 'create', + description: 'Create a mute role, mutually exclusive with role' + }), new CommandOption({ type: 'ROLE', name: 'role', - description: 'Select the role to use for mutes' + description: 'Select the role to use for mutes, mutually exclusive with create' }), new CommandOption({ type: 'STRING', @@ -60,10 +85,209 @@ class MuteSetting extends Setting { } - async execute() { - console.log('\n\nDINGUS\n\n'); + async execute(interaction, opts, setting) { + + if (opts.some((opt) => opt.name === 'role') && + opts.some((opt) => opt.name === 'create')) return { success: false, index: 'SETTING_MUTE_EXCLUSIVE' }; + + const response = { error: false }; + let created = false, + updatedPermissions = false, + roleName = null, + _create = false; + + for (const opt of opts) { + if (opt.name === 'create') { + const result = await this._createRole(interaction, opt.value); + if (result.error) return result; + + const { role, issues } = result; + ({ created, updatedPermissions } = result); + setting.role = role.id; + roleName = role.name; + _create = true; + + if (!issues.length) continue; + + const embed = { + author: { + name: `${Emojis.warning} Warning` + }, + color: 0xffe15c, + description: interaction.format('SETTING_MUTE_CREATESUCCESSWARNING'), + fields: [] + }; + + const textIssues = issues.filter((i) => i.type === 'GUILD_TEXT'); + const voiceIssues = issues.filter((i) => i.type === 'GUILD_VOICE'); + if (textIssues.length) embed.fields.push(addField(textIssues, 'text')); + if (voiceIssues.length) embed.fields.push(addField(voiceIssues, 'voice')); + + response.embeds = [embed]; + + } else setting[opt.name] = opt.value; + } + + response.message = interaction.format('SETTING_SUCCESS', { setting: this.name }); + if(_create) response.message += '\n' + interaction.format(`SETTING_MUTE_CREATESUCCESS${created ? 'ALT' : ''}`, { + permissions: interaction.format(`SETTING_MUTE_${updatedPermissions ? '' : 'UN'}GENERATEDPERMISSIONS`), + role: roleName + }); + + return response; + } + async _createRole(interaction, name = 'Muted') { + + const { guild, user } = interaction; + + const createRole = async (name) => { + let role = null; + if (name.length > CONSTANTS.MaxChars) name = name.slice(0, CONSTANTS.MaxChars); + try { + role = await guild.roles.create({ + name, + reason: super.reason(user) + }); + } catch (error) { + return { + index: 'SETTING_MUTE_ROLECREATEERROR', + error: true + }; + } + return role; + }; + + const hasPermission = guild.me.permissions.has('MANAGE_ROLES'); + if (!hasPermission) return { + index: 'SETTING_MUTE_ROLEMISSINGPERMISSION', + error: true + }; + + let role = null, + created = false; + const existing = await this.client.resolver.resolveRole(name, true, guild); + + if (existing) { + + const prompt = await interaction.promptInteraction({ + content: interaction.format('SETTING_MUTE_ROLEPROMPT', { + name: existing.name, + id: existing.id + }), + emoji: 'loading', + components: [ + { + type: 'ACTION_ROW', + components: [ + { + type: 'BUTTON', + label: 'Yes', + custom_id: 'yes', + style: 3 + }, + { + type: 'BUTTON', + label: 'No', + custom_id: 'no', + style: 4 + } + ] + } + ] + }); + + if (!prompt) return { + index: 'SETTING_MUTE_ROLEPROMPTERROR', + error: true + }; + + await prompt.update({ content: `${Emojis.working} Working...`, components: [] }); + const boolean = this.client.resolver.resolveBoolean(prompt.customId); + + if (boolean) { + role = existing; + } + } else interaction.editReply({ content: 'Working...', emoji: 'working' }); + + if (!role) { + role = await createRole(name); + if (role.error) return role; + created = true; + } + + let updatedPermissions = false; + const issues = []; + const channels = guild.channels.cache.filter((ch) => ['GUILD_TEXT', 'GUILD_VOICE'].includes(ch.type)); + + for (const channel of channels.values()) { + + const configuration = channel.type === 'GUILD_TEXT' + ? { permissions: { SEND_MESSAGES: false, ADD_REACTIONS: false }, bitwise: 0x800n } + : { permissions: { CONNECT: false }, bitwise: 0x100000n }; + + try { + await channel.permissionOverwrites.create(role, configuration.permissions, { reason: super.reason(user) }); + for (const permission of channel.permissionOverwrites.cache.values()) { + if (permission.type !== 'role') continue; + + const permissionRole = await this.client.resolver.resolveRole(permission, true, guild); + // eslint-disable-next-line no-bitwise + if ((permission.allow & configuration.bitwise) === configuration.bitwise) issues.push({ + role: permissionRole.name, + permissions: Object.keys(configuration.permissions), + channel: channel.name, + type: channel.type, + position: permissionRole.rawPosition + }); + } + updatedPermissions = true; + } catch (err) { + this.client.logger.error(err.stack); + } + + } + + return { + error: false, + updatedPermissions, + created, + issues, + role + }; + + } + + async fields(guild) { + const setting = guild._settings[this.name] || {}; + return [ + { + name: guild.format('SETTING_MUTE_ROLE'), + value: setting.role ? `<@&${setting.role}>` : '`N/A`', + inline: true + }, + { + name: guild.format('SETTING_MUTE_TYPE'), + value: `\`${setting.type || 0}\``, + inline: true + }, + //{ name: '\u200b', value: '\u200b', inline: true }, + { + name: guild.format('SETTING_MUTE_PERMANENT'), + value: guild.format('GENERAL_STATE', { bool: setting.permanent }, { code: true }), + inline: true + }, + { + name: guild.format('SETTING_MUTE_DEFAULT'), + value: setting.default ? Util.humanise(setting.default) : '`N/A`', + inline: true + }, + //{ name: '\u200b', value: '\u200b', inline: true } + ]; + } + + // Msg component based settings async onCollect(guild, selectMenu) { const settings = await guild.settings();