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