diff --git a/CHANGELOG.md b/CHANGELOG.md index 2edf671..4f9b853 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. * Config option `prefix` renamed to `messageCommandPrefix` * Config option `game` renamed to `activity` +* Config option `role` renamed to `userRoleIds`. Changed from string to array of strings. * Docker internal volume path moved from `/usr/src/markbot/config` to `/usr/app/config` * Database changed from JSON files to a SQLite database. You'll need to retrain the bot to use it again. * The bot must be explicitly granted permission to listen to a list of channels before using it. Configure it with `/listen`. diff --git a/src/config/classes.ts b/src/config/classes.ts index 60a6008..4b93cfb 100644 --- a/src/config/classes.ts +++ b/src/config/classes.ts @@ -84,6 +84,21 @@ export class AppConfig { @IsOptional() ownerIds = process.env.OWNER_IDS ? process.env.OWNER_IDS.split(',').map((id) => id.trim()) : []; + /** + * If provided, the standard "generate response" command will only work for a user in this list of role IDs. + * Moderators and owners configured in `ownerIds` do not bypass this check, so make sure to add them to a valid role as well. + * @example ["734548250895319070"] + * @default [] + * @env USER_ROLE_IDS (comma separated) + */ + @IsArray() + @IsString({ each: true }) + @Type(() => String) + @IsOptional() + userRoleIds = process.env.USER_ROLE_IDS + ? process.env.USER_ROLE_IDS.split(',').map((id) => id.trim()) + : []; + /** * TZ name from this list: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List * @example America/Chicago diff --git a/src/index.ts b/src/index.ts index ca29730..3adf757 100644 --- a/src/index.ts +++ b/src/index.ts @@ -130,7 +130,7 @@ async function removeValidChannels( } /** - * Checks if the author of a message as moderator-like permissions. + * Checks if the author of a command has moderator-like permissions. * @param {GuildMember} member Sender of the message * @return {Boolean} True if the sender is a moderator. * @@ -154,6 +154,23 @@ function isModerator(member: Discord.GuildMember | APIInteractionGuildMember | n return true; } +/** + * Checks if the author of a command has a role in the `userRoleIds` config option (if present). + * @param {GuildMember} member Sender of the message + * @return {Boolean} True if the sender is a moderator. + * + */ +function isAllowedUser(member: Discord.GuildMember | APIInteractionGuildMember | null): boolean { + if (!config.userRoleIds.length) return true; + if (!member) return false; + if (member instanceof Discord.GuildMember) { + return config.userRoleIds.some((p) => member.roles.cache.has(p)); + } + // TODO: How to parse API permissions? + L.debug({ permissions: member.permissions }); + return true; +} + type MessageCommands = 'respond' | 'train' | 'help' | 'invite' | 'debug' | 'tts' | null; /** @@ -359,11 +376,15 @@ async function generateResponse( const { tts = false, debug = false, startSeed } = options || {}; if (!interaction.guildId) { L.warn('Received an interaction without a guildId'); - return { message: { content: INVALID_GUILD_MESSAGE } }; + return { error: { content: INVALID_GUILD_MESSAGE } }; } if (!interaction.channelId) { L.warn('Received an interaction without a channelId'); - return { message: { content: 'This action must be performed within a text channel.' } }; + return { error: { content: 'This action must be performed within a text channel.' } }; + } + if (!isAllowedUser(interaction.member)) { + L.info('Member does not have permissions to generate a response'); + return { error: { content: INVALID_PERMISSIONS_MESSAGE } }; } const markov = await getMarkovByGuildId(interaction.guildId);