Compare commits

...

23 Commits

Author SHA1 Message Date
semantic-release-bot
e748a4da92 chore(release): 1.0.0-dev.7 [skip ci]
# @revanced/discord-bot [1.0.0-dev.7](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.6...@revanced/discord-bot@1.0.0-dev.7) (2024-07-25)

### Bug Fixes

* **bot/discord:** start remove preset timeout for `role-preset` command ([cbf9116](cbf91162e2))
* **bots/discord:** only check for member permissions when specified while correcting responses ([b79a1c7](b79a1c7575))
* **bots/discord:** set timeout for eligible mutes to unmute faster ([1f5c5a9](1f5c5a92a6))

### Features

* **bots/discord:** add `replyToReplied` option in response config ([27662ed](27662ed91a))
2024-07-25 18:38:09 +00:00
PalmDevs
300e5cff3b ci(bots/discord): fix freezing after generating db schemas 2024-07-26 01:36:52 +07:00
PalmDevs
6685ffb855 chore: update lockfile 2024-07-26 01:30:33 +07:00
PalmDevs
cbf91162e2 fix(bot/discord): start remove preset timeout for role-preset command 2024-07-26 01:25:52 +07:00
PalmDevs
bd906fbf54 fix(bots/discord)!: remove guilds config in favor of upcoming impl 2024-07-26 01:25:51 +07:00
PalmDevs
27662ed91a feat(bots/discord): add replyToReplied option in response config 2024-07-26 01:25:50 +07:00
PalmDevs
d0acab1915 feat(bots/discord)!: add admin config 2024-07-26 01:25:49 +07:00
PalmDevs
e86180fe29 feat(bots/discord)!: allow message scan response to be message payloads 2024-07-26 01:25:48 +07:00
PalmDevs
1f5c5a92a6 fix(bots/discord): set timeout for eligible mutes to unmute faster 2024-07-26 01:25:47 +07:00
PalmDevs
b79a1c7575 fix(bots/discord): only check for member permissions when specified while correcting responses 2024-07-26 01:25:46 +07:00
PalmDevs
3559ed1cb5 ci(bots/discord): patch drizzle-kit to stop using node, decreases image size 2024-07-26 01:25:45 +07:00
semantic-release-bot
042b155b5e chore(release): 1.0.0-dev.6 [skip ci]
# @revanced/discord-bot [1.0.0-dev.6](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.5...@revanced/discord-bot@1.0.0-dev.6) (2024-07-23)

### Bug Fixes

* **bots/discord:** ci issues causing database to not be auto generated ([673aa18](673aa189be))
2024-07-23 20:59:49 +00:00
PalmDevs
673aa189be fix(bots/discord): ci issues causing database to not be auto generated 2024-07-24 03:57:33 +07:00
PalmDevs
c503a86c53 ci(release): also update bun lockfile to prevent install freezes 2024-07-24 03:36:32 +07:00
semantic-release-bot
1bd973ea6c chore(release): 1.0.0-dev.5 [skip ci]
# @revanced/discord-bot [1.0.0-dev.5](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.4...@revanced/discord-bot@1.0.0-dev.5) (2024-07-23)
2024-07-23 20:31:04 +00:00
semantic-release-bot
4bb965e9ff chore(release): 1.0.0-dev.5 [skip ci]
# @revanced/bot-websocket-api [1.0.0-dev.5](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.4...@revanced/bot-websocket-api@1.0.0-dev.5) (2024-07-23)
2024-07-23 20:30:32 +00:00
PalmDevs
a8ceeb29ae chore: update lockfile 2024-07-24 03:29:32 +07:00
PalmDevs
96a6540434 build(Needs bump): revert building with bun explicitly
Building with only Bun causes compatibility issues, like Drizzle Kit not being to generate any schema for the database of the Discord bot.
2024-07-24 03:25:30 +07:00
PalmDevs
e02c86a9c4 ci(release): add time limit for job 2024-07-24 03:05:54 +07:00
semantic-release-bot
e82f2ab34b chore(release): 1.0.0-dev.4 [skip ci]
# @revanced/discord-bot [1.0.0-dev.4](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.3...@revanced/discord-bot@1.0.0-dev.4) (2024-07-23)

### Bug Fixes

* **bots/discord:** wrong database schema path ([875bd20](875bd209b2))
2024-07-23 20:04:31 +00:00
PalmDevs
2a6f3c3013 chore: update lockfile 2024-07-24 03:03:19 +07:00
PalmDevs
875bd209b2 fix(bots/discord): wrong database schema path 2024-07-24 01:12:30 +07:00
semantic-release-bot
2b601b1a1d chore(release): 1.0.0-dev.3 [skip ci]
# @revanced/discord-bot [1.0.0-dev.3](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.2...@revanced/discord-bot@1.0.0-dev.3) (2024-07-23)

### Bug Fixes

* **bots/discord:** revert dist denesting, fixes config not found ([0d4898d](0d4898dae8))
2024-07-23 15:42:43 +00:00
29 changed files with 231 additions and 135 deletions

View File

@@ -14,6 +14,7 @@ jobs:
permissions: permissions:
contents: read contents: read
packages: write packages: write
timeout-minutes: 10
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4

View File

@@ -1,3 +1,5 @@
# @revanced/bot-websocket-api [1.0.0-dev.5](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.4...@revanced/bot-websocket-api@1.0.0-dev.5) (2024-07-23)
# @revanced/bot-websocket-api [1.0.0-dev.4](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.3...@revanced/bot-websocket-api@1.0.0-dev.4) (2024-07-23) # @revanced/bot-websocket-api [1.0.0-dev.4](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.3...@revanced/bot-websocket-api@1.0.0-dev.4) (2024-07-23)

View File

@@ -6,7 +6,7 @@ FROM base AS build
WORKDIR /build WORKDIR /build
COPY . . COPY . .
RUN bun install --frozen-lockfile RUN bun install --frozen-lockfile
RUN cd apis/websocket && bun --bun run build RUN cd apis/websocket && bun run build
FROM base AS release FROM base AS release

View File

@@ -2,7 +2,7 @@
"name": "@revanced/bot-websocket-api", "name": "@revanced/bot-websocket-api",
"type": "module", "type": "module",
"private": true, "private": true,
"version": "1.0.0-dev.4", "version": "1.0.0-dev.5",
"description": "🧦 WebSocket API server for bots assisting ReVanced", "description": "🧦 WebSocket API server for bots assisting ReVanced",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {

View File

@@ -1,3 +1,40 @@
# @revanced/discord-bot [1.0.0-dev.7](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.6...@revanced/discord-bot@1.0.0-dev.7) (2024-07-25)
### Bug Fixes
* **bot/discord:** start remove preset timeout for `role-preset` command ([cbf9116](https://github.com/revanced/revanced-helper/commit/cbf91162e27dd4c1ecb976927ab708f1d882abca))
* **bots/discord:** only check for member permissions when specified while correcting responses ([b79a1c7](https://github.com/revanced/revanced-helper/commit/b79a1c7575e94c3e62654c87775cac497be4a50a))
* **bots/discord:** set timeout for eligible mutes to unmute faster ([1f5c5a9](https://github.com/revanced/revanced-helper/commit/1f5c5a92a639973b83a1204355538936e69a4454))
### Features
* **bots/discord:** add `replyToReplied` option in response config ([27662ed](https://github.com/revanced/revanced-helper/commit/27662ed91a79bfac7d3f091834e859a7b57366ce))
# @revanced/discord-bot [1.0.0-dev.6](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.5...@revanced/discord-bot@1.0.0-dev.6) (2024-07-23)
### Bug Fixes
* **bots/discord:** ci issues causing database to not be auto generated ([673aa18](https://github.com/revanced/revanced-helper/commit/673aa189bef1009a3e32ba3b1291a5ee84f2def3))
# @revanced/discord-bot [1.0.0-dev.5](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.4...@revanced/discord-bot@1.0.0-dev.5) (2024-07-23)
# @revanced/discord-bot [1.0.0-dev.4](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.3...@revanced/discord-bot@1.0.0-dev.4) (2024-07-23)
### Bug Fixes
* **bots/discord:** wrong database schema path ([875bd20](https://github.com/revanced/revanced-helper/commit/875bd209b252566414bf89349839cabc01697e1c))
# @revanced/discord-bot [1.0.0-dev.3](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.2...@revanced/discord-bot@1.0.0-dev.3) (2024-07-23)
### Bug Fixes
* **bots/discord:** revert dist denesting, fixes config not found ([0d4898d](https://github.com/revanced/revanced-helper/commit/0d4898dae8b26f8466d3f6b8f62875866f581644))
# @revanced/discord-bot [1.0.0-dev.2](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.1...@revanced/discord-bot@1.0.0-dev.2) (2024-07-23) # @revanced/discord-bot [1.0.0-dev.2](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.1...@revanced/discord-bot@1.0.0-dev.2) (2024-07-23)

View File

@@ -6,7 +6,7 @@ FROM base AS build
WORKDIR /build WORKDIR /build
COPY . . COPY . .
RUN bun install --frozen-lockfile RUN bun install --frozen-lockfile
RUN cd bots/discord && bun --bun run build RUN cd bots/discord && bun run build
FROM base AS release FROM base AS release

View File

@@ -4,8 +4,12 @@
* @type {import('./config.schema').Config} * @type {import('./config.schema').Config}
*/ */
export default { export default {
owners: ['USER_ID_HERE'], admin: {
guilds: ['GUILD_ID_HERE'], users: ['USER_ID_HERE'],
roles: {
GUILD_ID_HERE: ['ROLE_ID_HERE'],
},
},
moderation: { moderation: {
cure: { cure: {
defaultName: 'Server member', defaultName: 'Server member',
@@ -55,6 +59,8 @@ export default {
text: [/^regexp?$/, { label: 'label', threshold: 0.85 }], text: [/^regexp?$/, { label: 'label', threshold: 0.85 }],
}, },
response: { response: {
embeds: [
{
title: 'Embed title', title: 'Embed title',
description: 'Embed description', description: 'Embed description',
fields: [ fields: [
@@ -64,6 +70,8 @@ export default {
}, },
], ],
}, },
],
},
}, },
], ],
}, },

View File

@@ -1,8 +1,10 @@
import type { APIEmbed } from 'discord.js' import type { BaseMessageOptions } from 'discord.js'
export type Config = { export type Config = {
owners: string[] admin?: {
guilds: string[] users?: string[]
roles?: Record<string, string[]>
}
moderation?: { moderation?: {
roles: string[] roles: string[]
cure?: { cure?: {
@@ -57,6 +59,7 @@ export type ConfigMessageScanResponse = {
} }
filterOverride?: NonNullable<Config['messageScan']>['filter'] filterOverride?: NonNullable<Config['messageScan']>['filter']
response: ConfigMessageScanResponseMessage | null response: ConfigMessageScanResponseMessage | null
replyToReplied?: boolean
} }
export type ConfigMessageScanResponseLabelConfig = { export type ConfigMessageScanResponseLabelConfig = {
@@ -70,4 +73,4 @@ export type ConfigMessageScanResponseLabelConfig = {
threshold: number threshold: number
} }
export type ConfigMessageScanResponseMessage = APIEmbed export type ConfigMessageScanResponseMessage = BaseMessageOptions

View File

@@ -2,7 +2,7 @@
"name": "@revanced/discord-bot", "name": "@revanced/discord-bot",
"type": "module", "type": "module",
"private": true, "private": true,
"version": "1.0.0-dev.2", "version": "1.0.0-dev.7",
"description": "🤖 Discord bot assisting ReVanced", "description": "🤖 Discord bot assisting ReVanced",
"main": "src/index.ts", "main": "src/index.ts",
"scripts": { "scripts": {
@@ -11,7 +11,7 @@
"dev": "bun prepare && bun --watch src/index.ts", "dev": "bun prepare && bun --watch src/index.ts",
"build": "bun prepare && bun run scripts/build.ts", "build": "bun prepare && bun run scripts/build.ts",
"watch": "bun dev", "watch": "bun dev",
"prepare": "bun run scripts/generate-indexes.ts && drizzle-kit generate --name=schema" "prepare": "bun run scripts/generate-indexes.ts && bunx drizzle-kit generate --name=schema"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -1,50 +1,53 @@
import { REST } from '@discordjs/rest' console.log('Deprecated. New implementation to be done.')
import { getMissingEnvironmentVariables } from '@revanced/bot-shared' process.exit(1)
import { Routes } from 'discord-api-types/v9'
import type {
RESTGetCurrentApplicationResult,
RESTPutAPIApplicationCommandsResult,
RESTPutAPIApplicationGuildCommandsResult,
} from 'discord.js'
import { config, discord, logger } from '../src/context'
// Check if token exists // import { REST } from '@discordjs/rest'
// import { getMissingEnvironmentVariables } from '@revanced/bot-shared'
// import { Routes } from 'discord-api-types/v9'
// import type {
// RESTGetCurrentApplicationResult,
// RESTPutAPIApplicationCommandsResult,
// RESTPutAPIApplicationGuildCommandsResult,
// } from 'discord.js'
// import { config, discord, logger } from '../src/context'
const missingEnvs = getMissingEnvironmentVariables(['DISCORD_TOKEN']) // // Check if token exists
if (missingEnvs.length) {
for (const env of missingEnvs) logger.fatal(`${env} is not defined in environment variables`)
process.exit(1)
}
// Group commands by global and guild // const missingEnvs = getMissingEnvironmentVariables(['DISCORD_TOKEN'])
// if (missingEnvs.length) {
// for (const env of missingEnvs) logger.fatal(`${env} is not defined in environment variables`)
// process.exit(1)
// }
const { global: globalCommands = [], guild: guildCommands = [] } = Object.groupBy(Object.values(discord.commands), c => // // Group commands by global and guild
c.global ? 'global' : 'guild',
)
// Set commands // const { global: globalCommands = [], guild: guildCommands = [] } = Object.groupBy(Object.values(discord.commands), c =>
// c.global ? 'global' : 'guild',
// )
const rest = new REST({ version: '10' }).setToken(process.env['DISCORD_TOKEN']!) // // Set commands
try { // const rest = new REST({ version: '10' }).setToken(process.env['DISCORD_TOKEN']!)
const app = (await rest.get(Routes.currentApplication())) as RESTGetCurrentApplicationResult
const data = (await rest.put(Routes.applicationCommands(app.id), {
body: globalCommands.map(({ data }) => {
if (!data.dm_permission) data.dm_permission = true
logger.warn(`Command ${data.name} has no dm_permission set, forcing to true as it is a global command`)
return data
}),
})) as RESTPutAPIApplicationCommandsResult
logger.info(`Reloaded ${data.length} global commands`) // try {
// const app = (await rest.get(Routes.currentApplication())) as RESTGetCurrentApplicationResult
// const data = (await rest.put(Routes.applicationCommands(app.id), {
// body: globalCommands.map(({ data }) => {
// if (!data.dm_permission) data.dm_permission = true
// logger.warn(`Command ${data.name} has no dm_permission set, forcing to true as it is a global command`)
// return data
// }),
// })) as RESTPutAPIApplicationCommandsResult
for (const guildId of config.guilds) { // logger.info(`Reloaded ${data.length} global commands`)
const data = (await rest.put(Routes.applicationGuildCommands(app.id, guildId), {
body: guildCommands.map(x => x.data),
})) as RESTPutAPIApplicationGuildCommandsResult
logger.info(`Reloaded ${data.length} guild commands for guild ${guildId}`) // for (const guildId of config.guilds) {
} // const data = (await rest.put(Routes.applicationGuildCommands(app.id, guildId), {
} catch (e) { // body: guildCommands.map(x => x.data),
logger.fatal(e) // })) as RESTPutAPIApplicationGuildCommandsResult
}
// logger.info(`Reloaded ${data.length} guild commands for guild ${guildId}`)
// }
// } catch (e) {
// logger.fatal(e)
// }

View File

@@ -12,7 +12,7 @@ export default {
.setDMPermission(true) .setDMPermission(true)
.toJSON(), .toJSON(),
ownerOnly: true, adminOnly: true,
global: true, global: true,
async execute(_, interaction) { async execute(_, interaction) {

View File

@@ -25,7 +25,7 @@ export default {
.setDMPermission(true) .setDMPermission(true)
.toJSON(), .toJSON(),
ownerOnly: true, adminOnly: true,
global: true, global: true,
async execute(_, interaction) { async execute(_, interaction) {

View File

@@ -11,7 +11,7 @@ export default {
.setDMPermission(true) .setDMPermission(true)
.toJSON(), .toJSON(),
ownerOnly: true, adminOnly: true,
global: true, global: true,
async execute({ api, logger }, interaction) { async execute({ api, logger }, interaction) {

View File

@@ -1,7 +1,7 @@
import { SlashCommandBuilder } from 'discord.js' import { SlashCommandBuilder } from 'discord.js'
import CommandError, { CommandErrorType } from '$/classes/CommandError' import CommandError, { CommandErrorType } from '$/classes/CommandError'
import { applyRolePreset } from '$/utils/discord/rolePresets' import { applyRolePreset, removeRolePreset } from '$/utils/discord/rolePresets'
import type { Command } from '../types' import type { Command } from '../types'
import { config } from '$/context' import { config } from '$/context'
@@ -24,7 +24,7 @@ export default {
global: false, global: false,
async execute({ logger }, interaction, { userIsOwner }) { async execute({ logger }, interaction, { isExecutorBotAdmin: isExecutorAdmin }) {
const user = interaction.options.getUser('member', true) const user = interaction.options.getUser('member', true)
const reason = interaction.options.getString('reason') ?? 'No reason provided' const reason = interaction.options.getString('reason') ?? 'No reason provided'
const duration = interaction.options.getString('duration') const duration = interaction.options.getString('duration')
@@ -48,7 +48,7 @@ export default {
if (!member.manageable) if (!member.manageable)
throw new CommandError(CommandErrorType.Generic, 'This user cannot be managed by the bot.') throw new CommandError(CommandErrorType.Generic, 'This user cannot be managed by the bot.')
if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0 && !userIsOwner) if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0 && !isExecutorAdmin)
throw new CommandError( throw new CommandError(
CommandErrorType.InvalidUser, CommandErrorType.InvalidUser,
'You cannot mute a user with a role equal to or higher than yours.', 'You cannot mute a user with a role equal to or higher than yours.',
@@ -60,6 +60,11 @@ export default {
createModerationActionEmbed('Muted', user, interaction.user, reason, durationMs), createModerationActionEmbed('Muted', user, interaction.user, reason, durationMs),
) )
if (durationMs)
setTimeout(() => {
removeRolePreset(member, 'mute')
}, durationMs)
logger.info( logger.info(
`Moderator ${interaction.user.tag} (${interaction.user.id}) muted ${user.tag} (${user.id}) until ${expires} because ${reason}`, `Moderator ${interaction.user.tag} (${interaction.user.id}) muted ${user.tag} (${user.id}) until ${expires} because ${reason}`,
) )

View File

@@ -35,7 +35,7 @@ export default {
global: false, global: false,
async execute({ logger }, interaction, { userIsOwner }) { async execute({ logger }, interaction, { isExecutorBotAdmin: isExecutorAdmin }) {
const action = interaction.options.getString('action', true) as 'apply' | 'remove' const action = interaction.options.getString('action', true) as 'apply' | 'remove'
const user = interaction.options.getUser('member', true) const user = interaction.options.getUser('member', true)
const preset = interaction.options.getString('preset', true) const preset = interaction.options.getString('preset', true)
@@ -61,7 +61,7 @@ export default {
'The duration must be at least 1 millisecond long.', 'The duration must be at least 1 millisecond long.',
) )
if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0 && !userIsOwner) if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0 && !isExecutorAdmin)
throw new CommandError( throw new CommandError(
CommandErrorType.InvalidUser, CommandErrorType.InvalidUser,
'You cannot apply a role preset to a user with a role equal to or higher than yours.', 'You cannot apply a role preset to a user with a role equal to or higher than yours.',
@@ -79,6 +79,11 @@ export default {
) )
} }
if (expires)
setTimeout(() => {
removeRolePreset(member, preset)
}, expires)
await sendPresetReplyAndLogs(action, interaction, user, preset, expires) await sendPresetReplyAndLogs(action, interaction, user, preset, expires)
}, },
} satisfies Command } satisfies Command

View File

@@ -39,10 +39,10 @@ export type Command = {
roles?: string[] roles?: string[]
} }
/** /**
* Whether this command can only be used by bot owners. * Whether this command can only be used by bot admins.
* @default false * @default false
*/ */
ownerOnly?: boolean adminOnly?: boolean
/** /**
* Whether to register this command as a global slash command. * Whether to register this command as a global slash command.
* This is set to `false` and commands will be registered in allowed guilds only by default. * This is set to `false` and commands will be registered in allowed guilds only by default.
@@ -52,5 +52,5 @@ export type Command = {
} }
export interface Info { export interface Info {
userIsOwner: boolean isExecutorBotAdmin: boolean
} }

View File

@@ -32,7 +32,7 @@ export const api = {
} }
const DatabasePath = process.env['DATABASE_PATH'] const DatabasePath = process.env['DATABASE_PATH']
const DatabaseSchemaDir = join(import.meta.dir, '.drizzle') const DatabaseSchemaDir = join(import.meta.dir, '..', '.drizzle')
let dbSchemaFileName: string | undefined let dbSchemaFileName: string | undefined

View File

@@ -1,4 +1,5 @@
import CommandError from '$/classes/CommandError' import CommandError from '$/classes/CommandError'
import { isAdmin } from '$/utils/discord/permissions'
import { createErrorEmbed, createStackTraceEmbed } from '$utils/discord/embeds' import { createErrorEmbed, createStackTraceEmbed } from '$utils/discord/embeds'
import { on, withContext } from '$utils/discord/events' import { on, withContext } from '$utils/discord/events'
@@ -11,14 +12,14 @@ withContext(on, 'interactionCreate', async (context, interaction) => {
logger.debug(`Command ${interaction.commandName} being invoked by ${interaction.user.tag}`) logger.debug(`Command ${interaction.commandName} being invoked by ${interaction.user.tag}`)
if (!command) return void logger.error(`Command ${interaction.commandName} not implemented but registered!!!`) if (!command) return void logger.error(`Command ${interaction.commandName} not implemented but registered!!!`)
const isOwner = config.owners.includes(interaction.user.id) const isExecutorBotAdmin = isAdmin(await interaction.guild?.members.fetch(interaction.user.id) || interaction.user, config.admin)
/** /**
* Owner check * Admin check
*/ */
if (command.ownerOnly && !isOwner) if (command.adminOnly && !isExecutorBotAdmin)
return void (await interaction.reply({ return void (await interaction.reply({
embeds: [createErrorEmbed('Massive skill issue', 'This command can only be used by the bot owners.')], embeds: [createErrorEmbed('Massive skill issue', 'This command can only be used by the bot admins.')],
ephemeral: true, ephemeral: true,
})) }))
@@ -39,7 +40,7 @@ withContext(on, 'interactionCreate', async (context, interaction) => {
*/ */
if (interaction.inGuild()) { if (interaction.inGuild()) {
// Bot owners get bypass // Bot owners get bypass
if (command.memberRequirements && !isOwner) { if (command.memberRequirements && !isExecutorBotAdmin) {
const { permissions = 0n, roles = [], mode } = command.memberRequirements const { permissions = 0n, roles = [], mode } = command.memberRequirements
const member = await interaction.guild!.members.fetch(interaction.user.id) const member = await interaction.guild!.members.fetch(interaction.user.id)
@@ -69,7 +70,7 @@ withContext(on, 'interactionCreate', async (context, interaction) => {
try { try {
logger.debug(`Command ${interaction.commandName} being executed`) logger.debug(`Command ${interaction.commandName} being executed`)
await command.execute(context, interaction, { userIsOwner: isOwner }) await command.execute(context, interaction, { isExecutorBotAdmin })
} catch (err) { } catch (err) {
logger.error(`Error while executing command ${interaction.commandName}:`, err) logger.error(`Error while executing command ${interaction.commandName}:`, err)
await interaction[interaction.replied ? 'followUp' : 'reply']({ await interaction[interaction.replied ? 'followUp' : 'reply']({

View File

@@ -21,13 +21,15 @@ withContext(on, 'messageCreate', async (context, msg) => {
try { try {
logger.debug(`Classifying message ${msg.id}`) logger.debug(`Classifying message ${msg.id}`)
const { response, label } = await getResponseFromText(msg.content, filteredResponses, context) const { response, label, replyToReplied } = await getResponseFromText(msg.content, filteredResponses, context)
if (response) { if (response) {
logger.debug('Response found') logger.debug('Response found')
const reply = await msg.reply({ const toReply = replyToReplied ? await msg.fetchReference() : msg
embeds: [createMessageScanResponseEmbed(response, label ? 'nlp' : 'match')], const reply = await toReply.reply({
...response,
embeds: response.embeds?.map(it => createMessageScanResponseEmbed(it, label ? 'nlp' : 'match')),
}) })
if (label) if (label)
@@ -64,7 +66,8 @@ withContext(on, 'messageCreate', async (context, msg) => {
if (response) { if (response) {
logger.debug(`Response found for attachment: ${attachment.url}`) logger.debug(`Response found for attachment: ${attachment.url}`)
await msg.reply({ await msg.reply({
embeds: [createMessageScanResponseEmbed(response, 'ocr')], ...response,
embeds: response.embeds?.map(it => createMessageScanResponseEmbed(it, 'ocr')),
}) })
break break

View File

@@ -14,6 +14,7 @@ import type { ConfigMessageScanResponseLabelConfig } from '$/../config.schema'
import { responses } from '$/database/schemas' import { responses } from '$/database/schemas'
import { handleUserResponseCorrection } from '$/utils/discord/messageScan' import { handleUserResponseCorrection } from '$/utils/discord/messageScan'
import { eq } from 'drizzle-orm' import { eq } from 'drizzle-orm'
import { isAdmin } from '$/utils/discord/permissions'
const PossibleReactions = Object.values(Reactions) as string[] const PossibleReactions = Object.values(Reactions) as string[]
@@ -32,7 +33,7 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => {
if (reactionMessage.author.id !== reaction.client.user!.id) return if (reactionMessage.author.id !== reaction.client.user!.id) return
if (!PossibleReactions.includes(reaction.emoji.name!)) return if (!PossibleReactions.includes(reaction.emoji.name!)) return
if (!config.owners.includes(user.id)) { if (!isAdmin(reactionMessage.member || reactionMessage.author, config.admin)) {
// User is in guild, and config has member requirements // User is in guild, and config has member requirements
if ( if (
reactionMessage.inGuild() && reactionMessage.inGuild() &&
@@ -46,7 +47,12 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => {
const member = await reactionMessage.guild.members.fetch(user.id) const member = await reactionMessage.guild.members.fetch(user.id)
const { permissions, roles } = allowedMembers const { permissions, roles } = allowedMembers
if (!(member.permissions.has(permissions ?? 0n) || roles?.some(role => member.roles.cache.has(role)))) if (
!(
(permissions ? member.permissions.has(permissions) : false) ||
roles?.some(role => member.roles.cache.has(role))
)
)
return return
} else if (allowedUsers) { } else if (allowedUsers) {
if (!allowedUsers.includes(user.id)) return if (!allowedUsers.includes(user.id)) return

View File

@@ -1,19 +0,0 @@
import type { Command } from '$commands/types'
import { listAllFilesRecursive } from '$utils/fs'
export const loadCommands = async (dir: string) => {
const commandsMap: Record<string, Command> = {}
const files = listAllFilesRecursive(dir)
const commands = await Promise.all(
files.map(async file => {
const command = await import(file)
return command.default
}),
)
for (const command of commands) {
if (command) commandsMap[command.data.name] = command
}
return commandsMap
}

View File

@@ -24,16 +24,19 @@ export const createSuccessEmbed = (title: string | null, description?: string) =
) )
export const createMessageScanResponseEmbed = ( export const createMessageScanResponseEmbed = (
response: ConfigMessageScanResponseMessage, response: NonNullable<ConfigMessageScanResponseMessage['embeds']>[number],
mode: 'ocr' | 'nlp' | 'match', mode: 'ocr' | 'nlp' | 'match',
) => { ) => {
// biome-ignore lint/style/noParameterAssign: While this is confusing, it is fine for this purpose
if ('toJSON' in response) response = response.toJSON()
const embed = new EmbedBuilder().setTitle(response.title ?? null) const embed = new EmbedBuilder().setTitle(response.title ?? null)
if (response.description) embed.setDescription(response.description) if (response.description) embed.setDescription(response.description)
if (response.fields) embed.addFields(response.fields) if (response.fields) embed.addFields(response.fields)
embed.setFooter({ embed.setFooter({
text: `ReVanced • Done via ${MessageScanHumanizedMode[mode]}`, text: `ReVanced • Via ${MessageScanHumanizedMode[mode]}`,
iconURL: ReVancedLogoURL, iconURL: ReVancedLogoURL,
}) })

View File

@@ -2,8 +2,7 @@ import { type Response, responses } from '$/database/schemas'
import type { import type {
Config, Config,
ConfigMessageScanResponse, ConfigMessageScanResponse,
ConfigMessageScanResponseLabelConfig, ConfigMessageScanResponseLabelConfig
ConfigMessageScanResponseMessage,
} from 'config.schema' } from 'config.schema'
import type { Message, PartialUser, User } from 'discord.js' import type { Message, PartialUser, User } from 'discord.js'
import { eq } from 'drizzle-orm' import { eq } from 'drizzle-orm'
@@ -15,9 +14,12 @@ export const getResponseFromText = async (
// Just to be safe that we will never use data from the context parameter // Just to be safe that we will never use data from the context parameter
{ api, logger }: Omit<typeof import('src/context'), 'config'>, { api, logger }: Omit<typeof import('src/context'), 'config'>,
ocrMode = false, ocrMode = false,
) => { ): Promise<ConfigMessageScanResponse & { label?: string }> => {
let label: string | undefined let responseConfig: Awaited<ReturnType<typeof getResponseFromText>> = {
let response: ConfigMessageScanResponseMessage | undefined | null triggers: {},
response: null
}
const firstLabelIndexes: number[] = [] const firstLabelIndexes: number[] = []
// Test if all regexes before a label trigger is matched // Test if all regexes before a label trigger is matched
@@ -25,29 +27,28 @@ export const getResponseFromText = async (
const trigger = responses[i]! const trigger = responses[i]!
// Filter override check is not neccessary here, we are already passing responses that match the filter // Filter override check is not neccessary here, we are already passing responses that match the filter
// from the messageCreate handler // from the messageCreate handler, see line 17 of messageCreate handler
const { const {
triggers: { text: textTriggers, image: imageTriggers }, triggers: { text: textTriggers, image: imageTriggers }
response: resp,
} = trigger } = trigger
if (response) break if (responseConfig) break
if (ocrMode) { if (ocrMode) {
if (imageTriggers) if (imageTriggers)
for (const regex of imageTriggers) for (const regex of imageTriggers)
if (regex.test(content)) { if (regex.test(content)) {
logger.debug(`Message matched regex (OCR mode): ${regex.source}`) logger.debug(`Message matched regex (OCR mode): ${regex.source}`)
response = resp responseConfig = trigger
break break
} }
} else } else
for (let j = 0; j < textTriggers!.length; j++) { for (let j = 0; j < textTriggers!.length; j++) {
const trigger = textTriggers![j]! const regex = textTriggers![j]!
if (trigger instanceof RegExp) { if (regex instanceof RegExp) {
if (trigger.test(content)) { if (regex.test(content)) {
logger.debug(`Message matched regex (before mode): ${trigger.source}`) logger.debug(`Message matched regex (before mode): ${regex.source}`)
response = resp responseConfig = trigger
break break
} }
} else { } else {
@@ -58,7 +59,7 @@ export const getResponseFromText = async (
} }
// If none of the regexes match, we can search for labels immediately // If none of the regexes match, we can search for labels immediately
if (!response && !ocrMode) { if (!responseConfig && !ocrMode) {
logger.debug('No match from before regexes, doing NLP') logger.debug('No match from before regexes, doing NLP')
const scan = await api.client.parseText(content) const scan = await api.client.parseText(content)
if (scan.labels.length) { if (scan.labels.length) {
@@ -76,24 +77,22 @@ export const getResponseFromText = async (
if (!labelConfig) { if (!labelConfig) {
logger.warn(`No label config found for label ${matchedLabel.name}`) logger.warn(`No label config found for label ${matchedLabel.name}`)
return { response: null, label: undefined } return responseConfig
} }
if (matchedLabel.confidence >= triggerConfig!.threshold) { if (matchedLabel.confidence >= triggerConfig!.threshold) {
logger.debug('Label confidence is enough') logger.debug('Label confidence is enough')
label = matchedLabel.name responseConfig = labelConfig
response = labelConfig.response
} }
} }
} }
// If we still don't have a label, we can match all regexes after the initial label trigger // If we still don't have a response config, we can match all regexes after the initial label trigger
if (!response) { if (!responseConfig) {
logger.debug('No match from NLP, doing after regexes') logger.debug('No match from NLP, doing after regexes')
for (let i = 0; i < responses.length; i++) { for (let i = 0; i < responses.length; i++) {
const { const {
triggers: { text: textTriggers }, triggers: { text: textTriggers }
response: resp,
} = responses[i]! } = responses[i]!
const firstLabelIndex = firstLabelIndexes[i] ?? -1 const firstLabelIndex = firstLabelIndexes[i] ?? -1
@@ -103,7 +102,7 @@ export const getResponseFromText = async (
if (trigger instanceof RegExp) { if (trigger instanceof RegExp) {
if (trigger.test(content)) { if (trigger.test(content)) {
logger.debug(`Message matched regex (after mode): ${trigger.source}`) logger.debug(`Message matched regex (after mode): ${trigger.source}`)
response = resp responseConfig = responses[i]!
break break
} }
} }
@@ -111,10 +110,7 @@ export const getResponseFromText = async (
} }
} }
return { return responseConfig
response,
label,
}
} }
export const shouldScanMessage = ( export const shouldScanMessage = (
@@ -158,8 +154,10 @@ export const handleUserResponseCorrection = async (
correctedById: user.id, correctedById: user.id,
}) })
.where(eq(responses.replyId, response.replyId)) .where(eq(responses.replyId, response.replyId))
await reply.edit({ await reply.edit({
embeds: [createMessageScanResponseEmbed(correctLabelResponse.response, 'nlp')], ...correctLabelResponse.response,
embeds: correctLabelResponse.response.embeds?.map(it => createMessageScanResponseEmbed(it, 'nlp')),
}) })
} }

View File

@@ -0,0 +1,11 @@
import { GuildMember, type User } from 'discord.js'
import type { Config } from 'config.schema'
export const isAdmin = (userOrMember: User | GuildMember, adminConfig: Config['admin']) => {
return adminConfig?.users?.includes(userOrMember.id) || (userOrMember instanceof GuildMember && isMemberAdmin(userOrMember, adminConfig))
}
export const isMemberAdmin = (member: GuildMember, adminConfig: Config['admin']) => {
const roles = new Set(member.roles.cache.keys())
return Boolean(adminConfig?.roles?.[member.guild.id]?.some(role => roles.has(role)))
}

BIN
bun.lockb

Binary file not shown.

View File

@@ -54,6 +54,7 @@
"lefthook" "lefthook"
], ],
"patchedDependencies": { "patchedDependencies": {
"@semantic-release/npm@12.0.1": "patches/@semantic-release%2Fnpm@12.0.1.patch" "@semantic-release/npm@12.0.1": "patches/@semantic-release%2Fnpm@12.0.1.patch",
"drizzle-kit@0.22.8": "patches/drizzle-kit@0.22.8.patch"
} }
} }

View File

@@ -1,8 +1,14 @@
diff --git a/node_modules/@semantic-release/npm/.bun-tag-3853154e196b7721 b/.bun-tag-3853154e196b7721
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/node_modules/@semantic-release/npm/.bun-tag-550461f23a8ec245 b/.bun-tag-550461f23a8ec245
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/node_modules/@semantic-release/npm/.bun-tag-c9c8130945517add b/.bun-tag-c9c8130945517add diff --git a/node_modules/@semantic-release/npm/.bun-tag-c9c8130945517add b/.bun-tag-c9c8130945517add
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/prepare.js b/lib/prepare.js diff --git a/lib/prepare.js b/lib/prepare.js
index 3e76bec44cf595a1b4141728336bed904d4d518d..c6baf4e8de9bdf7536f9ad2e9eb9c360e031c7b5 100644 index 3e76bec44cf595a1b4141728336bed904d4d518d..4b25ca64879bbee2a600f2b23b738c86136ad9c6 100644
--- a/lib/prepare.js --- a/lib/prepare.js
+++ b/lib/prepare.js +++ b/lib/prepare.js
@@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
@@ -14,7 +20,7 @@ index 3e76bec44cf595a1b4141728336bed904d4d518d..c6baf4e8de9bdf7536f9ad2e9eb9c360
export default async function ( export default async function (
npmrc, npmrc,
@@ -11,19 +12,12 @@ export default async function ( @@ -11,19 +12,13 @@ export default async function (
logger.log("Write version %s to package.json in %s", version, basePath); logger.log("Write version %s to package.json in %s", version, basePath);
@@ -36,10 +42,11 @@ index 3e76bec44cf595a1b4141728336bed904d4d518d..c6baf4e8de9bdf7536f9ad2e9eb9c360
- await versionResult; - await versionResult;
+ await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, detectIndent(pkgJsonRaw).indent)) + await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, detectIndent(pkgJsonRaw).indent))
+ await execa("bun", ["install"]);
if (tarballDir) { if (tarballDir) {
logger.log("Creating npm package version %s", version); logger.log("Creating npm package version %s", version);
@@ -38,7 +32,7 @@ export default async function ( @@ -38,7 +33,7 @@ export default async function (
// Only move the tarball if we need to // Only move the tarball if we need to
// Fixes: https://github.com/semantic-release/npm/issues/169 // Fixes: https://github.com/semantic-release/npm/issues/169
if (tarballSource !== tarballDestination) { if (tarballSource !== tarballDestination) {

View File

@@ -0,0 +1,21 @@
diff --git a/bin.cjs b/bin.cjs
index 142ed9c20f28dc1080bebfb52325fa308c6cb771..9d3bea0787f6c05df11567c6821bc85743286340 100644
--- a/bin.cjs
+++ b/bin.cjs
@@ -22053,7 +22053,7 @@ var init_sqliteImports = __esm({
const { unregister } = await safeRegister();
for (let i2 = 0; i2 < imports.length; i2++) {
const it = imports[i2];
- const i0 = require(`${it}`);
+ const i0 = await import(`${it}`);
const prepared = prepareFromExports3(i0);
tables.push(...prepared.tables);
}
@@ -129572,6 +129572,7 @@ var generateCommand = new Command("generate").option("--dialect <dialect>", "Dat
} else {
assertUnreachable(dialect7);
}
+ process.exit(0);
});
var migrateCommand = new Command("migrate").option(
"--config <config>",

View File

@@ -31,7 +31,7 @@ const Options = {
[ [
'@semantic-release/git', '@semantic-release/git',
{ {
assets: ['CHANGELOG.md', 'package.json'], assets: ['CHANGELOG.md', 'package.json', '../../bun.lockb'],
}, },
], ],
[ [