Compare commits

...

10 Commits

Author SHA1 Message Date
semantic-release-bot
dfa08397d4 chore(release): 1.5.3 [skip ci]
## @revanced/discord-bot [1.5.3](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.5.2...@revanced/discord-bot@1.5.3) (2026-01-17)

### Bug Fixes

* **bots/discord:** more logging during apply role preset on member join ([77626c9](77626c9aed))
2026-01-17 14:22:11 +00:00
PalmDevs
9626c4d9ba chore(bots/discord): revert c68cfd1
Revert "feat(bots/discord): support training without label"
2026-01-17 21:21:20 +07:00
PalmDevs
5f74f2dcf8 chore(bots/discord): revert fbd9480
Revert "fix(bots/discord): pass non-empty out of scope label to discord"
2026-01-17 21:21:19 +07:00
PalmDevs
77626c9aed fix(bots/discord): more logging during apply role preset on member join
It seems to keep skipping some members for some reason...
2026-01-17 21:14:22 +07:00
semantic-release-bot
2c8740e489 chore(release): 1.5.2 [skip ci]
## @revanced/discord-bot [1.5.2](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.5.1...@revanced/discord-bot@1.5.2) (2025-09-25)

### Bug Fixes

* **bots/discord:** correct `respondToReply` logic ([6fe1530](6fe15301a2))
2025-09-25 16:19:07 +00:00
PalmDevs
6fe15301a2 fix(bots/discord): correct respondToReply logic 2025-09-25 23:17:29 +07:00
semantic-release-bot
6885e18976 chore(release): 1.5.1 [skip ci]
## @revanced/discord-bot [1.5.1](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.5.0...@revanced/discord-bot@1.5.1) (2025-09-13)

### Bug Fixes

* **bots/discord:** only fetch reference when it exists when `respondToReply` is set ([42038e6](42038e6b38))
2025-09-13 17:34:48 +00:00
Palm
42038e6b38 fix(bots/discord): only fetch reference when it exists when respondToReply is set
whoops, testing in production
2025-09-14 00:34:00 +07:00
semantic-release-bot
51c0252b44 chore(release): 1.5.0 [skip ci]
# @revanced/discord-bot [1.5.0](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.4.1...@revanced/discord-bot@1.5.0) (2025-09-13)

### Features

* **bots/discord:** add additional options for `respondToReply` ([399c201](399c201f8c))
2025-09-13 13:43:19 +00:00
PalmDevs
399c201f8c feat(bots/discord): add additional options for respondToReply 2025-09-13 20:42:34 +07:00
12 changed files with 74 additions and 46 deletions

View File

@@ -1,3 +1,31 @@
## @revanced/discord-bot [1.5.3](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.5.2...@revanced/discord-bot@1.5.3) (2026-01-17)
### Bug Fixes
* **bots/discord:** more logging during apply role preset on member join ([77626c9](https://github.com/revanced/revanced-bots/commit/77626c9aeddceb25c2f0d418bf764efc66669baf))
## @revanced/discord-bot [1.5.2](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.5.1...@revanced/discord-bot@1.5.2) (2025-09-25)
### Bug Fixes
* **bots/discord:** correct `respondToReply` logic ([6fe1530](https://github.com/revanced/revanced-bots/commit/6fe15301a21fdc196fded8d6fb13236a7bb826f5))
## @revanced/discord-bot [1.5.1](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.5.0...@revanced/discord-bot@1.5.1) (2025-09-13)
### Bug Fixes
* **bots/discord:** only fetch reference when it exists when `respondToReply` is set ([42038e6](https://github.com/revanced/revanced-bots/commit/42038e6b38983fefe79481359bad300dcb5e83b4))
# @revanced/discord-bot [1.5.0](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.4.1...@revanced/discord-bot@1.5.0) (2025-09-13)
### Features
* **bots/discord:** add additional options for `respondToReply` ([399c201](https://github.com/revanced/revanced-bots/commit/399c201f8c3e9e116050b49c2ffccdd79b02f39b))
## @revanced/discord-bot [1.4.1](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.4.0...@revanced/discord-bot@1.4.1) (2025-09-13) ## @revanced/discord-bot [1.4.1](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.4.0...@revanced/discord-bot@1.4.1) (2025-09-13)

View File

@@ -66,6 +66,7 @@ export default {
}, },
}, },
humanCorrections: { humanCorrections: {
falsePositiveLabel: 'false_positive',
allow: { allow: {
members: { members: {
permissions: 8n, permissions: 8n,

View File

@@ -36,6 +36,7 @@ export type Config = {
blacklist?: Filter blacklist?: Filter
} }
humanCorrections: { humanCorrections: {
falsePositiveLabel: string
allow?: { allow?: {
users?: string[] users?: string[]
members?: { members?: {
@@ -71,8 +72,8 @@ export type ConfigMessageScanResponse = {
image?: Array<RegExp> image?: Array<RegExp>
} }
filterOverride?: NonNullable<Config['messageScan']>['filter'] filterOverride?: NonNullable<Config['messageScan']>['filter']
response: ConfigMessageScanResponseMessage response: ConfigMessageScanResponseMessage | null
respondToReply?: boolean respondToReply?: boolean | 'only_regex' | 'only_labeled'
} }
export type ConfigMessageScanResponseLabelConfig = { export type ConfigMessageScanResponseLabelConfig = {

View File

@@ -2,7 +2,7 @@
"name": "@revanced/discord-bot", "name": "@revanced/discord-bot",
"type": "module", "type": "module",
"private": true, "private": true,
"version": "1.4.1", "version": "1.5.3",
"description": "🤖 Discord bot assisting ReVanced", "description": "🤖 Discord bot assisting ReVanced",
"main": "src/index.ts", "main": "src/index.ts",
"scripts": { "scripts": {

View File

@@ -9,7 +9,7 @@ const msRcConfig = config.messageScan?.humanCorrections?.allow
export default new Command({ export default new Command({
name: 'train', name: 'train',
description: 'Train a specific message to a specific label', description: 'Train a specific message or text to a specific label',
type: Command.Type.ChatGuild, type: Command.Type.ChatGuild,
requirements: { requirements: {
users: msRcConfig?.users, users: msRcConfig?.users,
@@ -26,9 +26,9 @@ export default new Command({
required: true, required: true,
}, },
label: { label: {
description: 'The label to train the message as (leave empty for out of scope)', description: 'The label to train the message as',
type: Command.OptionType.String, type: Command.OptionType.String,
required: false, required: true,
}, },
}, },
allowMessageCommand: true, allowMessageCommand: true,
@@ -49,7 +49,7 @@ export default new Command({
'This command can only be used in or on text channels', 'This command can only be used in or on text channels',
) )
if (label && !labels.includes(label)) if (!labels.includes(label))
throw new CommandError( throw new CommandError(
CommandErrorType.InvalidArgument, CommandErrorType.InvalidArgument,
`The provided label is invalid.\nValid labels are:${labels.map(l => `\n- \`${l}\``).join('')}`, `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.') 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 ?? 'out of scope'}`) logger.debug(`User ${context.executor.id} is training message ${refMsg?.id} as ${label}`)
await context.api.client.trainMessage(refMsg.content, label) await context.api.client.trainMessage(refMsg.content, label)
await trigger.reply({ await trigger.reply({
embeds: [ embeds: [
createSuccessEmbed( createSuccessEmbed(
'Message trained', 'Message trained',
`The provided message has been trained as ${label ? `\`${label}\`` : 'out of scope'}. Thank you for your contribution!`, `The provided message has been trained as \`${label}\`. Thank you for your contribution!`,
), ),
], ],
flags: MessageFlags.Ephemeral, flags: MessageFlags.Ephemeral,

View File

@@ -37,10 +37,7 @@ export default new Command({
components: [ components: [
{ {
custom_id: `tr_${trigger.targetMessage.channelId}_${trigger.targetId}`, custom_id: `tr_${trigger.targetMessage.channelId}_${trigger.targetId}`,
options: [ options: labels.map(label => ({ label, value: label })),
...labels.map(label => ({ label, value: label })),
{ label: 'Out of scope', value: OutOfScopeLabel, emoji: { name: '❌' } },
],
type: ComponentType.StringSelect, type: ComponentType.StringSelect,
} satisfies APIStringSelectComponent, } satisfies APIStringSelectComponent,
], ],
@@ -51,5 +48,3 @@ export default new Command({
}) })
}, },
}) })
export const OutOfScopeLabel = '<out of scope>'

View File

@@ -2,6 +2,7 @@ import { and, eq, gt } from 'drizzle-orm'
import { appliedPresets } from '$/database/schemas' import { appliedPresets } from '$/database/schemas'
import { on, withContext } from '$/utils/discord/events' import { on, withContext } from '$/utils/discord/events'
import { applyRolesUsingPreset } from '$/utils/discord/rolePresets' import { applyRolesUsingPreset } from '$/utils/discord/rolePresets'
import { logger } from '$/context'
withContext(on, 'guildMemberAdd', async ({ database }, member) => { withContext(on, 'guildMemberAdd', async ({ database }, member) => {
const applieds = await database.query.appliedPresets.findMany({ const applieds = await database.query.appliedPresets.findMany({
@@ -12,5 +13,12 @@ withContext(on, 'guildMemberAdd', async ({ database }, member) => {
), ),
}) })
if (!applieds.length) return
logger.info(
`Re-applying role presets for member ${member.id} in guild ${member.guild.id}:`,
applieds.map(x => x.preset),
)
for (const { preset } of applieds) await applyRolesUsingPreset(preset, member) for (const { preset } of applieds) await applyRolesUsingPreset(preset, member)
}) })

View File

@@ -58,8 +58,8 @@ withContext(on, 'interactionCreate', async (context, interaction) => {
const editMessage = (content: string, description?: string) => const editMessage = (content: string, description?: string) =>
editInteractionMessage(interaction, msg.url, content, description) editInteractionMessage(interaction, msg.url, content, description)
const handleCorrection = (label?: string) => const handleCorrection = (label: string) =>
handleUserResponseCorrection(context, response, msg, interaction.user, label) handleUserResponseCorrection(context, response, msg, label, interaction.user)
if (response.correctedById) if (response.correctedById)
return await editMessage( return await editMessage(
@@ -82,7 +82,7 @@ withContext(on, 'interactionCreate', async (context, interaction) => {
await editMessage('Canceled', 'You canceled this interaction. 😞') await editMessage('Canceled', 'You canceled this interaction. 😞')
break break
case 'delete': case 'delete':
await handleCorrection() await handleCorrection(msConfig.humanCorrections.falsePositiveLabel)
await editMessage( await editMessage(
'Marked as false positive', 'Marked as false positive',
'The response has been deleted and marked as a false positive. Thank you for your feedback. 🎉', 'The response has been deleted and marked as a false positive. Thank you for your feedback. 🎉',

View File

@@ -1,5 +1,4 @@
import { MessageFlags, type TextBasedChannel } from 'discord.js' import { MessageFlags, type TextBasedChannel } from 'discord.js'
import { OutOfScopeLabel } from '$/commands/support/train/context-menu'
import { createErrorEmbed, createStackTraceEmbed, createSuccessEmbed } from '$utils/discord/embeds' import { createErrorEmbed, createStackTraceEmbed, createSuccessEmbed } from '$utils/discord/embeds'
import { on, withContext } from '$utils/discord/events' import { on, withContext } from '$utils/discord/events'
@@ -31,11 +30,8 @@ withContext(on, 'interactionCreate', async (context, interaction) => {
flags: MessageFlags.Ephemeral, flags: MessageFlags.Ephemeral,
})) }))
const selectedLabel = interaction.values[0] const selectedLabel = interaction.values[0]!
await context.api.client.trainMessage( await context.api.client.trainMessage(msg.content, selectedLabel)
msg.content,
selectedLabel === OutOfScopeLabel ? undefined : selectedLabel,
)
await interaction.reply({ await interaction.reply({
embeds: [ embeds: [
createSuccessEmbed( createSuccessEmbed(

View File

@@ -33,7 +33,13 @@ withContext(on, 'messageCreate', async (context, msg) => {
if (response) { if (response) {
logger.debug('Response found') logger.debug('Response found')
const toReply = respondToReply ? (msg.reference?.messageId ? await msg.fetchReference() : msg) : msg const toReply =
msg.reference?.messageId &&
(respondToReply === true ||
(label === undefined ? respondToReply === 'only_regex' : respondToReply === 'only_labeled'))
? await msg.fetchReference()
: msg
const reply = await toReply.reply({ const reply = await toReply.reply({
...response, ...response,
embeds: response.embeds?.map(createMessageScanResponseEmbed), embeds: response.embeds?.map(createMessageScanResponseEmbed),

View File

@@ -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}`) logger.debug(`User ${user.id} is trying to correct the response ${rct.message.id}`)
const handleCorrection = (label?: string) => const handleCorrection = (label: string) =>
handleUserResponseCorrection(context, response, reactionMessage, user, label) handleUserResponseCorrection(context, response, reactionMessage, label, user)
try { try {
if (reaction.emoji.name === Reactions.train) { if (reaction.emoji.name === Reactions.train) {
@@ -106,7 +106,7 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => {
.setCustomId(`${componentPrefix}_cancel`), .setCustomId(`${componentPrefix}_cancel`),
new ButtonBuilder() new ButtonBuilder()
.setEmoji(Reactions.delete) .setEmoji(Reactions.delete)
.setLabel('Delete (mark as out of scope)') .setLabel('Delete (mark as false positive)')
.setStyle(ButtonStyle.Danger) .setStyle(ButtonStyle.Danger)
.setCustomId(`${componentPrefix}_delete`), .setCustomId(`${componentPrefix}_delete`),
), ),
@@ -117,8 +117,8 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => {
components: rows, components: rows,
}) })
} else if (reaction.emoji.name === Reactions.delete) { } else if (reaction.emoji.name === Reactions.delete) {
await handleCorrection() await handleCorrection(msConfig.humanCorrections.falsePositiveLabel)
await user.send({ content: 'The response has been deleted and marked as out of scope.' }) await user.send({ content: 'The response has been deleted and marked as a false positive.' })
} }
} catch (e) { } catch (e) {
logger.error('Failed to correct response:', e) logger.error('Failed to correct response:', e)

View File

@@ -17,7 +17,7 @@ export const getResponseFromText = async (
type ResponseConfig = Awaited<ReturnType<typeof getResponseFromText>> type ResponseConfig = Awaited<ReturnType<typeof getResponseFromText>>
let responseConfig: Omit<ResponseConfig, 'triggers'> & { triggers?: ResponseConfig['triggers'] } = { let responseConfig: Omit<ResponseConfig, 'triggers'> & { triggers?: ResponseConfig['triggers'] } = {
triggers: undefined, triggers: undefined,
response: null!, response: null,
} }
const firstLabelIndexes: number[] = [] const firstLabelIndexes: number[] = []
@@ -143,22 +143,15 @@ export const handleUserResponseCorrection = async (
{ api, database: db, config: { messageScan: msConfig }, logger }: typeof import('$/context'), { api, database: db, config: { messageScan: msConfig }, logger }: typeof import('$/context'),
response: Response, response: Response,
reply: Message, reply: Message,
label: string,
user: User | PartialUser, 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 => const correctLabelResponse = msConfig!.responses!.find(r =>
r.triggers.text!.some(t => 'label' in t && t.label === label), 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) throw new Error('Cannot find label config for the selected label')
if (!correctLabelResponse.response) return void (await reply.delete())
if (response.label !== label) { if (response.label !== label) {
db.update(responses) db.update(responses)
@@ -175,12 +168,12 @@ export const handleUserResponseCorrection = async (
})) }))
} }
await Promise.all([ await api.client.trainMessage(response.content, label)
api.client.trainMessage(response.content, label), logger.debug(`User ${user.id} trained message ${response.replyId} as ${label} (positive)`)
reply.edit({
components: [], await reply.edit({
}), components: [],
]).finally(() => logger.debug(`User ${user.id} trained message ${response.replyId} as ${label}`)) })
} }
export const createMessageScanResponseComponents = (reply: Message<true>) => [ export const createMessageScanResponseComponents = (reply: Message<true>) => [