diff --git a/bots/discord/config.js b/bots/discord/config.js index 1b04f24..8744260 100644 --- a/bots/discord/config.js +++ b/bots/discord/config.js @@ -66,7 +66,6 @@ export default { }, }, humanCorrections: { - falsePositiveLabel: 'false_positive', allow: { members: { permissions: 8n, diff --git a/bots/discord/config.schema.ts b/bots/discord/config.schema.ts index cb118d4..300d11d 100644 --- a/bots/discord/config.schema.ts +++ b/bots/discord/config.schema.ts @@ -36,7 +36,6 @@ export type Config = { blacklist?: Filter } humanCorrections: { - falsePositiveLabel: string allow?: { users?: string[] members?: { @@ -72,7 +71,7 @@ export type ConfigMessageScanResponse = { image?: Array } filterOverride?: NonNullable['filter'] - response: ConfigMessageScanResponseMessage | null + response: ConfigMessageScanResponseMessage respondToReply?: boolean } diff --git a/bots/discord/src/commands/support/train/chat.ts b/bots/discord/src/commands/support/train/chat.ts index f3ec56d..0aef886 100644 --- a/bots/discord/src/commands/support/train/chat.ts +++ b/bots/discord/src/commands/support/train/chat.ts @@ -9,7 +9,7 @@ const msRcConfig = config.messageScan?.humanCorrections?.allow export default new Command({ name: 'train', - description: 'Train a specific message or text to a specific label', + description: 'Train a specific message to a specific label', type: Command.Type.ChatGuild, requirements: { users: msRcConfig?.users, @@ -26,9 +26,9 @@ export default new Command({ required: true, }, label: { - description: 'The label to train the message as', + description: 'The label to train the message as (leave empty for out of scope)', type: Command.OptionType.String, - required: true, + required: false, }, }, allowMessageCommand: true, @@ -49,7 +49,7 @@ export default new Command({ 'This command can only be used in or on text channels', ) - if (!labels.includes(label)) + if (label && !labels.includes(label)) throw new CommandError( CommandErrorType.InvalidArgument, `The provided label is invalid.\nValid labels are:${labels.map(l => `\n- \`${l}\``).join('')}`, @@ -60,14 +60,14 @@ export default new Command({ ) if (!refMsg) throw new CommandError(CommandErrorType.InvalidArgument, 'The provided message does not exist.') - logger.debug(`User ${context.executor.id} is training message ${refMsg?.id} as ${label}`) + logger.debug(`User ${context.executor.id} is training message ${refMsg?.id} as ${label ?? 'out of scope'}`) await context.api.client.trainMessage(refMsg.content, label) await trigger.reply({ embeds: [ createSuccessEmbed( 'Message trained', - `The provided message has been trained as \`${label}\`. Thank you for your contribution!`, + `The provided message has been trained as ${label ? `\`${label}\`` : 'out of scope'}. Thank you for your contribution!`, ), ], flags: MessageFlags.Ephemeral, diff --git a/bots/discord/src/commands/support/train/context-menu.ts b/bots/discord/src/commands/support/train/context-menu.ts index a865b5a..3e5256f 100644 --- a/bots/discord/src/commands/support/train/context-menu.ts +++ b/bots/discord/src/commands/support/train/context-menu.ts @@ -37,7 +37,10 @@ export default new Command({ components: [ { custom_id: `tr_${trigger.targetMessage.channelId}_${trigger.targetId}`, - options: labels.map(label => ({ label, value: label })), + options: [ + ...labels.map(label => ({ label, value: label })), + { label: 'Out of scope', value: '', emoji: { name: '❌' } }, + ], type: ComponentType.StringSelect, } satisfies APIStringSelectComponent, ], diff --git a/bots/discord/src/events/discord/interactionCreate/correctResponse.ts b/bots/discord/src/events/discord/interactionCreate/correctResponse.ts index 4cbc4ac..53eac71 100644 --- a/bots/discord/src/events/discord/interactionCreate/correctResponse.ts +++ b/bots/discord/src/events/discord/interactionCreate/correctResponse.ts @@ -58,8 +58,8 @@ withContext(on, 'interactionCreate', async (context, interaction) => { const editMessage = (content: string, description?: string) => editInteractionMessage(interaction, msg.url, content, description) - const handleCorrection = (label: string) => - handleUserResponseCorrection(context, response, msg, label, interaction.user) + const handleCorrection = (label?: string) => + handleUserResponseCorrection(context, response, msg, interaction.user, label) if (response.correctedById) return await editMessage( @@ -82,7 +82,7 @@ withContext(on, 'interactionCreate', async (context, interaction) => { await editMessage('Canceled', 'You canceled this interaction. 😞') break case 'delete': - await handleCorrection(msConfig.humanCorrections.falsePositiveLabel) + await handleCorrection() await editMessage( 'Marked as false positive', 'The response has been deleted and marked as a false positive. Thank you for your feedback. 🎉', diff --git a/bots/discord/src/events/discord/interactionCreate/trainMessage.ts b/bots/discord/src/events/discord/interactionCreate/trainMessage.ts index 0cfda78..19f45dd 100644 --- a/bots/discord/src/events/discord/interactionCreate/trainMessage.ts +++ b/bots/discord/src/events/discord/interactionCreate/trainMessage.ts @@ -30,7 +30,8 @@ withContext(on, 'interactionCreate', async (context, interaction) => { flags: MessageFlags.Ephemeral, })) - const selectedLabel = interaction.values[0]! + // If selectedLabel is empty, it means "out of scope", so we pass undefined + const selectedLabel = interaction.values[0] || undefined await context.api.client.trainMessage(msg.content, selectedLabel) await interaction.reply({ embeds: [ diff --git a/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts index db79463..f8ca312 100644 --- a/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts +++ b/bots/discord/src/events/discord/messageReactionAdd/correctResponse.ts @@ -63,8 +63,8 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => { logger.debug(`User ${user.id} is trying to correct the response ${rct.message.id}`) - const handleCorrection = (label: string) => - handleUserResponseCorrection(context, response, reactionMessage, label, user) + const handleCorrection = (label?: string) => + handleUserResponseCorrection(context, response, reactionMessage, user, label) try { if (reaction.emoji.name === Reactions.train) { @@ -106,7 +106,7 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => { .setCustomId(`${componentPrefix}_cancel`), new ButtonBuilder() .setEmoji(Reactions.delete) - .setLabel('Delete (mark as false positive)') + .setLabel('Delete (mark as out of scope)') .setStyle(ButtonStyle.Danger) .setCustomId(`${componentPrefix}_delete`), ), @@ -117,8 +117,8 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => { components: rows, }) } else if (reaction.emoji.name === Reactions.delete) { - await handleCorrection(msConfig.humanCorrections.falsePositiveLabel) - await user.send({ content: 'The response has been deleted and marked as a false positive.' }) + await handleCorrection() + await user.send({ content: 'The response has been deleted and marked as out of scope.' }) } } catch (e) { logger.error('Failed to correct response:', e) diff --git a/bots/discord/src/utils/discord/messageScan.ts b/bots/discord/src/utils/discord/messageScan.ts index c62e9f2..67636ce 100644 --- a/bots/discord/src/utils/discord/messageScan.ts +++ b/bots/discord/src/utils/discord/messageScan.ts @@ -17,7 +17,7 @@ export const getResponseFromText = async ( type ResponseConfig = Awaited> let responseConfig: Omit & { triggers?: ResponseConfig['triggers'] } = { triggers: undefined, - response: null, + response: null!, } const firstLabelIndexes: number[] = [] @@ -143,15 +143,22 @@ export const handleUserResponseCorrection = async ( { api, database: db, config: { messageScan: msConfig }, logger }: typeof import('$/context'), response: Response, reply: Message, - label: string, user: User | PartialUser, + label?: string, ) => { + if (!label) { + await Promise.all([reply.delete(), api.client.trainMessage(response.content, label)]).finally(() => + logger.debug(`User ${user.id} trained message ${response.replyId} as out of scope`), + ) + + return + } + const correctLabelResponse = msConfig!.responses!.find(r => r.triggers.text!.some(t => 'label' in t && t.label === label), ) if (!correctLabelResponse) throw new Error('Cannot find label config for the selected label') - if (!correctLabelResponse.response) return void (await reply.delete()) if (response.label !== label) { db.update(responses) @@ -168,12 +175,12 @@ export const handleUserResponseCorrection = async ( })) } - await api.client.trainMessage(response.content, label) - logger.debug(`User ${user.id} trained message ${response.replyId} as ${label} (positive)`) - - await reply.edit({ - components: [], - }) + await Promise.all([ + api.client.trainMessage(response.content, label), + reply.edit({ + components: [], + }), + ]).finally(() => logger.debug(`User ${user.id} trained message ${response.replyId} as ${label}`)) } export const createMessageScanResponseComponents = (reply: Message) => [