From a17420e87a2d8bfb391bc1bf15247bcf4d714777 Mon Sep 17 00:00:00 2001 From: "Navy.gif" Date: Mon, 11 Dec 2023 15:07:08 +0200 Subject: [PATCH] Bugfixes + improvements Stronger setting typings Fixed bug with running settings current in dms Stronger property checks in reply method Locale loader bugfix that prevented certain files from loading properly Make sure bot has permissions in modlog channel before trying to send message --- @types/Client.ts | 4 +- src/client/components/LocaleLoader.ts | 20 +- .../commands/administration/Settings.ts | 11 +- .../components/inhibitors/ChannelIgnore.ts | 2 - .../settings/administration/IgnoreChannels.ts | 6 +- .../components/wrappers/InvokerWrapper.ts | 4 + src/client/interfaces/Infraction.ts | 57 +++--- src/client/interfaces/Setting.ts | 8 +- .../en_gb/commands/en_gb_administration.lang | 175 +++++++++--------- 9 files changed, 157 insertions(+), 130 deletions(-) diff --git a/@types/Client.ts b/@types/Client.ts index fb99421..7652380 100644 --- a/@types/Client.ts +++ b/@types/Client.ts @@ -297,10 +297,10 @@ export type SettingApiDefinitions = { // } export type BaseSetting = object -export type SettingOptions = { +export type SettingOptions = { name?: string, aliases?: string[], - resolve?: SettingTypeResolve, + resolve?: If, description?: string, display?: string, emoji?: SettingEmojiOption, diff --git a/src/client/components/LocaleLoader.ts b/src/client/components/LocaleLoader.ts index d1c4892..240460c 100644 --- a/src/client/components/LocaleLoader.ts +++ b/src/client/components/LocaleLoader.ts @@ -16,10 +16,11 @@ import { Emojis } from '../../constants/index.js'; import DiscordClient from '../DiscordClient.js'; import { FormatParams } from '../../../@types/Client.js'; +type Language = { + [key: string]: string +} type Languages = { - [key: string]: { - [key: string]: string - } + [key: string]: Language } class LocaleLoader @@ -84,14 +85,19 @@ class LocaleLoader const directory = path.join(root, language); const files = Util.readdirRecursive(directory); - const combined = {}; + const combined: Language = {}; for (let file of files) { file = fs.readFileSync(file, { encoding: 'utf8' }); const result = this._loadFile(file); - Object.assign(combined, result); + for (const [ key, string ] of Object.entries(result)) + { + if (combined[key]) + throw new Error('Duplicate key ' + key); + combined[key] = string; + } } this.#languages[language] = combined; @@ -102,8 +108,8 @@ class LocaleLoader { if (process.platform === 'win32') { - file = file.split('\n').join(''); - file = file.replace(/\r/gu, '\n'); + // file = file.split('\n').join(''); + file = file.replace(/\r/gu, ''); } const lines = file.split('\n'); diff --git a/src/client/components/commands/administration/Settings.ts b/src/client/components/commands/administration/Settings.ts index 407eb3b..721cff6 100644 --- a/src/client/components/commands/administration/Settings.ts +++ b/src/client/components/commands/administration/Settings.ts @@ -75,7 +75,7 @@ class SettingsCommand extends SlashCommand return invoker.reply({ embeds: [ embed ] }); } - async #currentSettings (invoker: InvokerWrapper) + async #currentSettings (invoker: InvokerWrapper) { const { guild } = invoker; const { settings } = this.client.registry; @@ -83,7 +83,9 @@ class SettingsCommand extends SlashCommand for (const setting of settings.values()) { - const settingFields = await setting.fields(guild); + if (setting.resolve === 'GUILD' && !guild) + continue; + const settingFields = await setting.fields(guild!); if (!settingFields.length) continue; let _field = { @@ -94,7 +96,7 @@ class SettingsCommand extends SlashCommand { if (field.name === ZeroWidthChar) continue; - const str = `**${guild.format(field.name)}**\n${field.value}`; + const str = `**${guild!.format(field.name)}**\n${field.value}`; if (_field.value.length + str.length >= EmbedLimits.fieldValue) { fields.push(_field); @@ -126,6 +128,9 @@ class SettingsCommand extends SlashCommand } } + if (!embeds.length) + return { index: 'COMMAND_SETTINGS_NONE' }; + await invoker.reply({ embeds: [ embeds.shift()! ] }); for (const emb of embeds) { diff --git a/src/client/components/inhibitors/ChannelIgnore.ts b/src/client/components/inhibitors/ChannelIgnore.ts index 3b3937b..dd56b61 100644 --- a/src/client/components/inhibitors/ChannelIgnore.ts +++ b/src/client/components/inhibitors/ChannelIgnore.ts @@ -37,10 +37,8 @@ class ChannelIgnore extends Inhibitor } return super._fail({ error: true, silent: true }); } - return super._succeed(); } - } export default ChannelIgnore; \ No newline at end of file diff --git a/src/client/components/settings/administration/IgnoreChannels.ts b/src/client/components/settings/administration/IgnoreChannels.ts index 1d8adf1..a80ea59 100644 --- a/src/client/components/settings/administration/IgnoreChannels.ts +++ b/src/client/components/settings/administration/IgnoreChannels.ts @@ -88,9 +88,10 @@ class IgnoreSetting extends Setting }; } - fields (guild: GuildWrapper) + async fields (guild: GuildWrapper) { - const setting = guild._settings[this.name] as IgnoreSettings; + const settings = await guild.settings(); + const setting = settings[this.name] as IgnoreSettings; return [ { name: 'GENERAL_CHANNELS', @@ -101,7 +102,6 @@ class IgnoreSetting extends Setting value: setting.bypass.map((r) => `<@&${r}>`).join(', ') || '**N/A**' } ]; - } } diff --git a/src/client/components/wrappers/InvokerWrapper.ts b/src/client/components/wrappers/InvokerWrapper.ts index e9b0d3f..6701316 100644 --- a/src/client/components/wrappers/InvokerWrapper.ts +++ b/src/client/components/wrappers/InvokerWrapper.ts @@ -244,9 +244,13 @@ class InvokerWrapper { + if (!embed) + throw new Error('Embed cannot be undefined'); if (embed instanceof EmbedBuilder && !embed.data.color) embed.setColor(Constants.EmbedDefaultColor); else if (!(embed instanceof EmbedBuilder) && !embed.color) diff --git a/src/client/interfaces/Infraction.ts b/src/client/interfaces/Infraction.ts index 857d0fe..c0d38a1 100644 --- a/src/client/interfaces/Infraction.ts +++ b/src/client/interfaces/Infraction.ts @@ -189,11 +189,15 @@ class Infraction { if (moderation.infractions.includes(this.#type!)) { - this.#moderationLog = await this.#client.resolver.resolveChannel(moderation.channel, true, this.#guild); - if (this.#moderationLog) + this.#moderationLog = await this.#guild.resolveChannel(moderation.channel, true); + const perms = this.#moderationLog?.permissionsFor(this.client.user!); + const missing = perms?.missing([ 'SendMessages', 'EmbedLinks' ]); + if (this.#moderationLog && !missing?.length) { this.#modLogId = this.#moderationLog.id; - this.#dmLogMessage = await this.#moderationLog.send({ embeds: [ await this.#embed() ] }).catch(null); + this.#dmLogMessage = await this.#moderationLog + .send({ embeds: [ await this.#embed() ] }) + .catch((err) => this.logger.error(err)) ?? null; this.#modLogMessageId = this.#dmLogMessage?.id || null; } } @@ -203,27 +207,24 @@ class Infraction } } - if (dminfraction.enabled && !this.#silent) + if (dminfraction.enabled && !this.#silent && this.#targetType === 'USER' && dminfraction.infractions.includes(this.#type!)) { - if (this.#targetType === 'USER' && dminfraction.infractions.includes(this.#type!)) - { - let message = dminfraction.messages[this.#type!] || dminfraction.messages.default; - if (!message) - message = ''; - message = message - .replace(/\{(guild|server)\}/ugim, this.#guild.name) - .replace(/\{user\}/ugim, (this.#target as UserWrapper)?.tag) - .replace(/\{infraction\}/ugim, this.dictionary.past) - .replace(/\{from\|on\}/ugim, Constants.RemovedInfractions.includes(this.#type!) ? 'from' : 'on'); // add more if you want i should probably add a better system for this... + let message = dminfraction.messages[this.#type!] || dminfraction.messages.default; + if (!message) + message = ''; + message = message + .replace(/\{(guild|server)\}/ugim, this.#guild.name) + .replace(/\{user\}/ugim, (this.#target as UserWrapper)?.tag) + .replace(/\{infraction\}/ugim, this.dictionary.past) + .replace(/\{from\|on\}/ugim, Constants.RemovedInfractions.includes(this.#type!) ? 'from' : 'on'); // add more if you want i should probably add a better system for this... - if (Util.isSendable(this.#target)) - { - const logMessage = await this.#target.send({ - content: message, - embeds: [ await this.#embed(true) ] - }).catch(() => null); - this.#dmLogMessageId = logMessage?.id ?? null; - } + if (Util.isSendable(this.#target)) + { + const logMessage = await this.#target.send({ + content: message, + embeds: [ await this.#embed(true) ] + }).catch(() => null); + this.#dmLogMessageId = logMessage?.id ?? null; } } @@ -606,7 +607,17 @@ class Infraction protected async _verify () { - const { protection } = await this.#guild.settings(); + const { + protection, + // moderation + } = await this.#guild.settings(); + // const modlog = moderation.channel ? await this.guild.resolveChannel(moderation.channel).catch(() => null) : null; + // const perms = modlog?.permissionsFor(this.client.user!); + // if (modlog && perms) + // { + // if (perms.missing([ 'SendMessages', 'EmbedLinks' ]).length) + // return this._fail('INFRACTION_MISSING_MODLOG_PERMS'); + // } if (this.#executor?.id === this.#guild.ownerId) return this._succeed(); if ( diff --git a/src/client/interfaces/Setting.ts b/src/client/interfaces/Setting.ts index e820035..c47c00c 100644 --- a/src/client/interfaces/Setting.ts +++ b/src/client/interfaces/Setting.ts @@ -1,5 +1,5 @@ import Component from './Component.js'; -import { EmbedBuilder, PermissionsString, TextChannel, APIEmbed, APIEmbedField, User } from 'discord.js'; +import { EmbedBuilder, PermissionsString, TextChannel, APIEmbed, APIEmbedField, User, If } from 'discord.js'; import { GuildWrapper, InvokerWrapper, MemberWrapper, UserWrapper } from '../components/wrappers/index.js'; import DiscordClient from '../DiscordClient.js'; @@ -58,7 +58,7 @@ type HelperResult = { * @class Setting * @extends {Component} */ -abstract class Setting extends Component +abstract class Setting extends Component { #name: string; #resolve: SettingTypeResolve; @@ -86,7 +86,7 @@ abstract class Setting extends Component * @param {Object} [options={}] * @memberof Setting */ - constructor (client: DiscordClient, options: SettingOptions) + constructor (client: DiscordClient, options: SettingOptions) { if (!options) throw new Error('No options provided to setting'); @@ -266,7 +266,7 @@ abstract class Setting extends Component * @return {Array} * @memberof Setting */ - fields (_guild: GuildWrapper): Promise | APIEmbedField[] + fields (_guild: If): Promise | APIEmbedField[] { return Promise.resolve([]); } diff --git a/src/localization/en_gb/commands/en_gb_administration.lang b/src/localization/en_gb/commands/en_gb_administration.lang index c559b22..c8f1b22 100644 --- a/src/localization/en_gb/commands/en_gb_administration.lang +++ b/src/localization/en_gb/commands/en_gb_administration.lang @@ -1,87 +1,90 @@ -// Permissions management -[COMMAND_PERMISSIONS_HELP] -Grant roles or users permissions to use commands or modules. -To view all grantable permissions, use the command `permissions list`. - -Permissions look like `command:` or `module:` - -[COMMAND_PERMISSIONS_NO_TARGET] -Missing either member or role. - -[COMMAND_PERMISSIONS_NO_RESOLVE] -Failed to resolve any permissions. - -[COMMAND_PERMISSIONS_DB_ERROR] -Database error while updating permissions. -Permissions in memory should be updated but they will not persist. -**Please report this** - -[COMMAND_PERMISSIONS_SUCCESS_GRANT] -Successfully granted **{targets}** the following permissions: `{permissions}` - -[COMMAND_PERMISSIONS_SUCCESS_REVOKE] -Successfully revoked the following permissions from **{targets}**: `{permissions}` - -[COMMAND_PERMISSIONS_WARNING] -You granted a command from the **Administration** module. This is potentially dangerous, as whoever you granted the administration commands to could have access to vital parts of the server. Only grant administration commands to people you trust. - -[COMMAND_PERMISSIONS_LIST] -You can **grant**/**revoke** the following permissions -`{permissions}` - -[COMMAND_PERMISSIONS_RESET] -Successfully reset permissions{for}{in} - -[COMMAND_PERMISSIONS_RESET_FOR] -__ __for **{targets}** -// ... avoiding a trim that clears the space -[COMMAND_PERMISSIONS_RESET_IN] -__ __in {channels} - -[COMMAND_PERMISSIONS_GLOBAL_PERMS] -Global permissions - -[COMMAND_PERMISSIONS_SHOW_TITLE] -Granted permissions - -[COMMAND_PERMISSIONS_DESC] -Showing permissions {forin} **{target}** - -[COMMAND_PERMISSIONS_NO_PERMS] -No permissions granted - -// Settings -[COMMAND_SETTINGS_HELP] -Configure bot settings for the server. - -[COMMAND_MODERATION_HELP] -Configure moderation related settings. - -[COMMAND_LOGGING_HELP] -Configure logging settings. - -[COMMAND_UTILITY_HELP] -Configure utility settings. - -[COMMAND_ADMINISTRATION_HELP] -Configure administrative settings. - -// Import -[COMMAND_IMPORT_HELP] -Import configuration and data from old versions of the bot. - -[COMMAND_IMPORT_ERROR] -Error during import, contact a developer about this issue: -{message} - -[COMMAND_SETTINGS_IMPORT_SUCCESS] -Successfully imported settings! - -[COMMAND_INFRACTIONS_IMPORT_SUCCESS] -Successfully imported {cases} infractions. - -[COMMAND_IMPORT_IMPORTED] -**{thing}** are already imported - -[COMMAND_IMPORT_WORKING] +// Permissions management +[COMMAND_PERMISSIONS_HELP] +Grant roles or users permissions to use commands or modules. +To view all grantable permissions, use the command `permissions list`. + +Permissions look like `command:` or `module:` + +[COMMAND_PERMISSIONS_NO_TARGET] +Missing either member or role. + +[COMMAND_PERMISSIONS_NO_RESOLVE] +Failed to resolve any permissions. + +[COMMAND_PERMISSIONS_DB_ERROR] +Database error while updating permissions. +Permissions in memory should be updated but they will not persist. +**Please report this** + +[COMMAND_PERMISSIONS_SUCCESS_GRANT] +Successfully granted **{targets}** the following permissions: `{permissions}` + +[COMMAND_PERMISSIONS_SUCCESS_REVOKE] +Successfully revoked the following permissions from **{targets}**: `{permissions}` + +[COMMAND_PERMISSIONS_WARNING] +You granted a command from the **Administration** module. This is potentially dangerous, as whoever you granted the administration commands to could have access to vital parts of the server. Only grant administration commands to people you trust. + +[COMMAND_PERMISSIONS_LIST] +You can **grant**/**revoke** the following permissions +`{permissions}` + +[COMMAND_PERMISSIONS_RESET] +Successfully reset permissions{for}{in} + +[COMMAND_PERMISSIONS_RESET_FOR] +__ __for **{targets}** +// ... avoiding a trim that clears the space +[COMMAND_PERMISSIONS_RESET_IN] +__ __in {channels} + +[COMMAND_PERMISSIONS_GLOBAL_PERMS] +Global permissions + +[COMMAND_PERMISSIONS_SHOW_TITLE] +Granted permissions + +[COMMAND_PERMISSIONS_DESC] +Showing permissions {forin} **{target}** + +[COMMAND_PERMISSIONS_NO_PERMS] +No permissions granted + +// Settings +[COMMAND_SETTINGS_HELP] +Configure bot settings for the server. + +[COMMAND_SETTINGS_NONE] +No settings found. + +[COMMAND_MODERATION_HELP] +Configure moderation related settings. + +[COMMAND_LOGGING_HELP] +Configure logging settings. + +[COMMAND_UTILITY_HELP] +Configure utility settings. + +[COMMAND_ADMINISTRATION_HELP] +Configure administrative settings. + +// Import +[COMMAND_IMPORT_HELP] +Import configuration and data from old versions of the bot. + +[COMMAND_IMPORT_ERROR] +Error during import, contact a developer about this issue: +{message} + +[COMMAND_SETTINGS_IMPORT_SUCCESS] +Successfully imported settings! + +[COMMAND_INFRACTIONS_IMPORT_SUCCESS] +Successfully imported {cases} infractions. + +[COMMAND_IMPORT_IMPORTED] +**{thing}** are already imported + +[COMMAND_IMPORT_WORKING] Working... \ No newline at end of file