mirror of
https://github.com/ReVanced/revanced-bots.git
synced 2026-01-12 06:06:21 +00:00
Compare commits
26 Commits
@revanced/
...
@revanced/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8168f79ac6 | ||
|
|
38c06997b4 | ||
|
|
cdb6001955 | ||
|
|
e748a4da92 | ||
|
|
300e5cff3b | ||
|
|
6685ffb855 | ||
|
|
cbf91162e2 | ||
|
|
bd906fbf54 | ||
|
|
27662ed91a | ||
|
|
d0acab1915 | ||
|
|
e86180fe29 | ||
|
|
1f5c5a92a6 | ||
|
|
b79a1c7575 | ||
|
|
3559ed1cb5 | ||
|
|
042b155b5e | ||
|
|
673aa189be | ||
|
|
c503a86c53 | ||
|
|
1bd973ea6c | ||
|
|
4bb965e9ff | ||
|
|
a8ceeb29ae | ||
|
|
96a6540434 | ||
|
|
e02c86a9c4 | ||
|
|
e82f2ab34b | ||
|
|
2a6f3c3013 | ||
|
|
875bd209b2 | ||
|
|
2b601b1a1d |
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -14,6 +14,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ FROM base AS build
|
||||
WORKDIR /build
|
||||
COPY . .
|
||||
RUN bun install --frozen-lockfile
|
||||
RUN cd apis/websocket && bun --bun run build
|
||||
RUN cd apis/websocket && bun run build
|
||||
|
||||
FROM base AS release
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@revanced/bot-websocket-api",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"version": "1.0.0-dev.4",
|
||||
"version": "1.0.0-dev.5",
|
||||
"description": "🧦 WebSocket API server for bots assisting ReVanced",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,3 +1,52 @@
|
||||
# @revanced/discord-bot [1.0.0-dev.8](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.7...@revanced/discord-bot@1.0.0-dev.8) (2024-07-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** cross-device link build errors ([38c0699](https://github.com/revanced/revanced-helper/commit/38c06997b4d0f7bb3f1e62618a5e3f088c522e30))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bots/discord:** blacklist and whitelist for filters ([cdb6001](https://github.com/revanced/revanced-helper/commit/cdb600195520dba33110c40841629259e317055e))
|
||||
|
||||
# @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)
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ FROM base AS build
|
||||
WORKDIR /build
|
||||
COPY . .
|
||||
RUN bun install --frozen-lockfile
|
||||
RUN cd bots/discord && bun --bun run build
|
||||
RUN cd bots/discord && bun run build
|
||||
|
||||
FROM base AS release
|
||||
|
||||
|
||||
@@ -4,8 +4,12 @@
|
||||
* @type {import('./config.schema').Config}
|
||||
*/
|
||||
export default {
|
||||
owners: ['USER_ID_HERE'],
|
||||
guilds: ['GUILD_ID_HERE'],
|
||||
admin: {
|
||||
users: ['USER_ID_HERE'],
|
||||
roles: {
|
||||
GUILD_ID_HERE: ['ROLE_ID_HERE'],
|
||||
},
|
||||
},
|
||||
moderation: {
|
||||
cure: {
|
||||
defaultName: 'Server member',
|
||||
@@ -33,11 +37,19 @@ export default {
|
||||
checkExpiredEvery: 3600,
|
||||
},
|
||||
messageScan: {
|
||||
scanBots: false,
|
||||
scanOutsideGuilds: false,
|
||||
filter: {
|
||||
channels: ['CHANNEL_ID_HERE'],
|
||||
roles: ['ROLE_ID_HERE'],
|
||||
users: ['USER_ID_HERE'],
|
||||
whitelist: false,
|
||||
whitelist: {
|
||||
channels: ['CHANNEL_ID_HERE'],
|
||||
roles: ['ROLE_ID_HERE'],
|
||||
users: ['USER_ID_HERE'],
|
||||
},
|
||||
blacklist: {
|
||||
channels: ['CHANNEL_ID_HERE'],
|
||||
roles: ['ROLE_ID_HERE'],
|
||||
users: ['USER_ID_HERE'],
|
||||
},
|
||||
},
|
||||
humanCorrections: {
|
||||
falsePositiveLabel: 'false_positive',
|
||||
@@ -51,16 +63,32 @@ export default {
|
||||
allowedAttachmentMimeTypes: ['image/jpeg', 'image/png', 'image/webp'],
|
||||
responses: [
|
||||
{
|
||||
filterOverride: {
|
||||
whitelist: {
|
||||
channels: ['CHANNEL_ID_HERE'],
|
||||
roles: ['ROLE_ID_HERE'],
|
||||
users: ['USER_ID_HERE'],
|
||||
},
|
||||
blacklist: {
|
||||
channels: ['CHANNEL_ID_HERE'],
|
||||
roles: ['ROLE_ID_HERE'],
|
||||
users: ['USER_ID_HERE'],
|
||||
},
|
||||
},
|
||||
triggers: {
|
||||
text: [/^regexp?$/, { label: 'label', threshold: 0.85 }],
|
||||
},
|
||||
response: {
|
||||
title: 'Embed title',
|
||||
description: 'Embed description',
|
||||
fields: [
|
||||
embeds: [
|
||||
{
|
||||
name: 'Field name',
|
||||
value: 'Field value',
|
||||
title: 'Embed title',
|
||||
description: 'Embed description',
|
||||
fields: [
|
||||
{
|
||||
name: 'Field name',
|
||||
value: 'Field value',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { APIEmbed } from 'discord.js'
|
||||
import type { BaseMessageOptions } from 'discord.js'
|
||||
|
||||
export type Config = {
|
||||
owners: string[]
|
||||
guilds: string[]
|
||||
admin?: {
|
||||
users?: string[]
|
||||
roles?: Record<string, string[]>
|
||||
}
|
||||
moderation?: {
|
||||
roles: string[]
|
||||
cure?: {
|
||||
@@ -18,12 +20,12 @@ export type Config = {
|
||||
guilds: Record<string, Record<string, RolePresetConfig>>
|
||||
}
|
||||
messageScan?: {
|
||||
scanBots?: boolean
|
||||
scanOutsideGuilds?: boolean
|
||||
allowedAttachmentMimeTypes: string[]
|
||||
filter: {
|
||||
roles?: string[]
|
||||
users?: string[]
|
||||
channels?: string[]
|
||||
whitelist: boolean
|
||||
filter?: {
|
||||
whitelist?: Filter
|
||||
blacklist?: Filter
|
||||
}
|
||||
humanCorrections: {
|
||||
falsePositiveLabel: string
|
||||
@@ -57,6 +59,7 @@ export type ConfigMessageScanResponse = {
|
||||
}
|
||||
filterOverride?: NonNullable<Config['messageScan']>['filter']
|
||||
response: ConfigMessageScanResponseMessage | null
|
||||
replyToReplied?: boolean
|
||||
}
|
||||
|
||||
export type ConfigMessageScanResponseLabelConfig = {
|
||||
@@ -70,4 +73,10 @@ export type ConfigMessageScanResponseLabelConfig = {
|
||||
threshold: number
|
||||
}
|
||||
|
||||
export type ConfigMessageScanResponseMessage = APIEmbed
|
||||
export type Filter = {
|
||||
roles?: string[]
|
||||
users?: string[]
|
||||
channels?: string[]
|
||||
}
|
||||
|
||||
export type ConfigMessageScanResponseMessage = BaseMessageOptions
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@revanced/discord-bot",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"version": "1.0.0-dev.2",
|
||||
"version": "1.0.0-dev.8",
|
||||
"description": "🤖 Discord bot assisting ReVanced",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
@@ -11,7 +11,7 @@
|
||||
"dev": "bun prepare && bun --watch src/index.ts",
|
||||
"build": "bun prepare && bun run scripts/build.ts",
|
||||
"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": {
|
||||
"type": "git",
|
||||
|
||||
@@ -17,7 +17,8 @@ await Bun.build({
|
||||
})
|
||||
|
||||
logger.info('Copying config...')
|
||||
await cp('config.js', 'dist/config.js')
|
||||
await cp('./config.js', './dist/config.js')
|
||||
|
||||
logger.info('Copying database schema...')
|
||||
await rename('.drizzle', 'dist/.drizzle')
|
||||
await cp('./.drizzle', './dist/.drizzle', { recursive: true })
|
||||
await rm('./.drizzle', { recursive: true })
|
||||
|
||||
@@ -1,50 +1,53 @@
|
||||
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'
|
||||
console.log('Deprecated. New implementation to be done.')
|
||||
process.exit(1)
|
||||
|
||||
// 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'])
|
||||
if (missingEnvs.length) {
|
||||
for (const env of missingEnvs) logger.fatal(`${env} is not defined in environment variables`)
|
||||
process.exit(1)
|
||||
}
|
||||
// // Check if token exists
|
||||
|
||||
// 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 =>
|
||||
c.global ? 'global' : 'guild',
|
||||
)
|
||||
// // Group commands by global and 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 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
|
||||
// const rest = new REST({ version: '10' }).setToken(process.env['DISCORD_TOKEN']!)
|
||||
|
||||
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) {
|
||||
const data = (await rest.put(Routes.applicationGuildCommands(app.id, guildId), {
|
||||
body: guildCommands.map(x => x.data),
|
||||
})) as RESTPutAPIApplicationGuildCommandsResult
|
||||
// logger.info(`Reloaded ${data.length} global commands`)
|
||||
|
||||
logger.info(`Reloaded ${data.length} guild commands for guild ${guildId}`)
|
||||
}
|
||||
} catch (e) {
|
||||
logger.fatal(e)
|
||||
}
|
||||
// for (const guildId of config.guilds) {
|
||||
// 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}`)
|
||||
// }
|
||||
// } catch (e) {
|
||||
// logger.fatal(e)
|
||||
// }
|
||||
|
||||
@@ -12,7 +12,7 @@ export default {
|
||||
.setDMPermission(true)
|
||||
.toJSON(),
|
||||
|
||||
ownerOnly: true,
|
||||
adminOnly: true,
|
||||
global: true,
|
||||
|
||||
async execute(_, interaction) {
|
||||
|
||||
@@ -25,7 +25,7 @@ export default {
|
||||
.setDMPermission(true)
|
||||
.toJSON(),
|
||||
|
||||
ownerOnly: true,
|
||||
adminOnly: true,
|
||||
global: true,
|
||||
|
||||
async execute(_, interaction) {
|
||||
|
||||
@@ -11,7 +11,7 @@ export default {
|
||||
.setDMPermission(true)
|
||||
.toJSON(),
|
||||
|
||||
ownerOnly: true,
|
||||
adminOnly: true,
|
||||
global: true,
|
||||
|
||||
async execute({ api, logger }, interaction) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SlashCommandBuilder } from 'discord.js'
|
||||
|
||||
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 { config } from '$/context'
|
||||
@@ -24,7 +24,7 @@ export default {
|
||||
|
||||
global: false,
|
||||
|
||||
async execute({ logger }, interaction, { userIsOwner }) {
|
||||
async execute({ logger }, interaction, { isExecutorBotAdmin: isExecutorAdmin }) {
|
||||
const user = interaction.options.getUser('member', true)
|
||||
const reason = interaction.options.getString('reason') ?? 'No reason provided'
|
||||
const duration = interaction.options.getString('duration')
|
||||
@@ -48,7 +48,7 @@ export default {
|
||||
if (!member.manageable)
|
||||
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(
|
||||
CommandErrorType.InvalidUser,
|
||||
'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),
|
||||
)
|
||||
|
||||
if (durationMs)
|
||||
setTimeout(() => {
|
||||
removeRolePreset(member, 'mute')
|
||||
}, durationMs)
|
||||
|
||||
logger.info(
|
||||
`Moderator ${interaction.user.tag} (${interaction.user.id}) muted ${user.tag} (${user.id}) until ${expires} because ${reason}`,
|
||||
)
|
||||
|
||||
@@ -35,7 +35,7 @@ export default {
|
||||
|
||||
global: false,
|
||||
|
||||
async execute({ logger }, interaction, { userIsOwner }) {
|
||||
async execute({ logger }, interaction, { isExecutorBotAdmin: isExecutorAdmin }) {
|
||||
const action = interaction.options.getString('action', true) as 'apply' | 'remove'
|
||||
const user = interaction.options.getUser('member', true)
|
||||
const preset = interaction.options.getString('preset', true)
|
||||
@@ -61,7 +61,7 @@ export default {
|
||||
'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(
|
||||
CommandErrorType.InvalidUser,
|
||||
'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)
|
||||
},
|
||||
} satisfies Command
|
||||
|
||||
@@ -39,10 +39,10 @@ export type Command = {
|
||||
roles?: string[]
|
||||
}
|
||||
/**
|
||||
* Whether this command can only be used by bot owners.
|
||||
* Whether this command can only be used by bot admins.
|
||||
* @default false
|
||||
*/
|
||||
ownerOnly?: boolean
|
||||
adminOnly?: boolean
|
||||
/**
|
||||
* 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.
|
||||
@@ -52,5 +52,5 @@ export type Command = {
|
||||
}
|
||||
|
||||
export interface Info {
|
||||
userIsOwner: boolean
|
||||
isExecutorBotAdmin: boolean
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { existsSync, readFileSync, readdirSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
import { Client as APIClient } from '@revanced/bot-api'
|
||||
import { createLogger } from '@revanced/bot-shared'
|
||||
import { ActivityType, Client as DiscordClient, Partials } from 'discord.js'
|
||||
import { Client as DiscordClient, Partials } from 'discord.js'
|
||||
import { drizzle } from 'drizzle-orm/bun-sqlite'
|
||||
|
||||
// Export config first, as commands require them
|
||||
@@ -27,12 +27,12 @@ export const api = {
|
||||
},
|
||||
},
|
||||
}),
|
||||
isStopping: false,
|
||||
intentionallyDisconnecting: false,
|
||||
disconnectCount: 0,
|
||||
}
|
||||
|
||||
const DatabasePath = process.env['DATABASE_PATH']
|
||||
const DatabaseSchemaDir = join(import.meta.dir, '.drizzle')
|
||||
const DatabaseSchemaDir = join(import.meta.dir, '..', '.drizzle')
|
||||
|
||||
let dbSchemaFileName: string | undefined
|
||||
|
||||
@@ -80,14 +80,6 @@ export const discord = {
|
||||
repliedUser: true,
|
||||
},
|
||||
partials: [Partials.Message, Partials.Reaction],
|
||||
presence: {
|
||||
activities: [
|
||||
{
|
||||
type: ActivityType.Watching,
|
||||
name: 'cat videos',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
commands: Object.fromEntries(Object.values<Command>(commands).map(cmd => [cmd.data.name, cmd])) as Record<
|
||||
string,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { on, withContext } from '$utils/api/events'
|
||||
import { DisconnectReason, HumanizedDisconnectReason } from '@revanced/bot-shared'
|
||||
|
||||
withContext(on, 'disconnect', ({ api, config, logger }, reason, msg) => {
|
||||
if (reason === DisconnectReason.PlannedDisconnect && api.isStopping) return
|
||||
if (reason === DisconnectReason.PlannedDisconnect && api.intentionallyDisconnecting) return
|
||||
|
||||
const ws = api.client.ws
|
||||
if (!ws.disconnected) ws.disconnect()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import CommandError from '$/classes/CommandError'
|
||||
import { isAdmin } from '$/utils/discord/permissions'
|
||||
import { createErrorEmbed, createStackTraceEmbed } from '$utils/discord/embeds'
|
||||
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}`)
|
||||
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({
|
||||
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,
|
||||
}))
|
||||
|
||||
@@ -39,7 +40,7 @@ withContext(on, 'interactionCreate', async (context, interaction) => {
|
||||
*/
|
||||
if (interaction.inGuild()) {
|
||||
// Bot owners get bypass
|
||||
if (command.memberRequirements && !isOwner) {
|
||||
if (command.memberRequirements && !isExecutorBotAdmin) {
|
||||
const { permissions = 0n, roles = [], mode } = command.memberRequirements
|
||||
|
||||
const member = await interaction.guild!.members.fetch(interaction.user.id)
|
||||
@@ -69,7 +70,7 @@ withContext(on, 'interactionCreate', async (context, interaction) => {
|
||||
|
||||
try {
|
||||
logger.debug(`Command ${interaction.commandName} being executed`)
|
||||
await command.execute(context, interaction, { userIsOwner: isOwner })
|
||||
await command.execute(context, interaction, { isExecutorBotAdmin })
|
||||
} catch (err) {
|
||||
logger.error(`Error while executing command ${interaction.commandName}:`, err)
|
||||
await interaction[interaction.replied ? 'followUp' : 'reply']({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MessageScanLabeledResponseReactions } from '$/constants'
|
||||
import { responses } from '$/database/schemas'
|
||||
import { getResponseFromText, shouldScanMessage } from '$/utils/discord/messageScan'
|
||||
import { getResponseFromText, messageMatchesFilter } from '$/utils/discord/messageScan'
|
||||
import { createMessageScanResponseEmbed } from '$utils/discord/embeds'
|
||||
import { on, withContext } from '$utils/discord/events'
|
||||
|
||||
@@ -13,21 +13,26 @@ withContext(on, 'messageCreate', async (context, msg) => {
|
||||
} = context
|
||||
|
||||
if (!config || !config.responses) return
|
||||
if (msg.author.bot && !config.scanBots)
|
||||
if (!msg.inGuild() && !config.scanOutsideGuilds) return
|
||||
if (msg.inGuild() && msg.member?.partial) await msg.member.fetch()
|
||||
|
||||
const filteredResponses = config.responses.filter(x => shouldScanMessage(msg, x.filterOverride ?? config.filter))
|
||||
const filteredResponses = config.responses.filter(x => messageMatchesFilter(msg, x.filterOverride ?? config.filter))
|
||||
if (!filteredResponses.length) return
|
||||
|
||||
if (msg.content.length) {
|
||||
try {
|
||||
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) {
|
||||
logger.debug('Response found')
|
||||
|
||||
const reply = await msg.reply({
|
||||
embeds: [createMessageScanResponseEmbed(response, label ? 'nlp' : 'match')],
|
||||
const toReply = replyToReplied ? await msg.fetchReference() : msg
|
||||
const reply = await toReply.reply({
|
||||
...response,
|
||||
embeds: response.embeds?.map(it => createMessageScanResponseEmbed(it, label ? 'nlp' : 'match')),
|
||||
})
|
||||
|
||||
if (label)
|
||||
@@ -64,7 +69,8 @@ withContext(on, 'messageCreate', async (context, msg) => {
|
||||
if (response) {
|
||||
logger.debug(`Response found for attachment: ${attachment.url}`)
|
||||
await msg.reply({
|
||||
embeds: [createMessageScanResponseEmbed(response, 'ocr')],
|
||||
...response,
|
||||
embeds: response.embeds?.map(it => createMessageScanResponseEmbed(it, 'ocr')),
|
||||
})
|
||||
|
||||
break
|
||||
@@ -14,6 +14,7 @@ import type { ConfigMessageScanResponseLabelConfig } from '$/../config.schema'
|
||||
import { responses } from '$/database/schemas'
|
||||
import { handleUserResponseCorrection } from '$/utils/discord/messageScan'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { isAdmin } from '$/utils/discord/permissions'
|
||||
|
||||
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 (!PossibleReactions.includes(reaction.emoji.name!)) return
|
||||
|
||||
if (!config.owners.includes(user.id)) {
|
||||
if (!isAdmin(reactionMessage.member || reactionMessage.author)) {
|
||||
// User is in guild, and config has member requirements
|
||||
if (
|
||||
reactionMessage.inGuild() &&
|
||||
@@ -46,7 +47,12 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => {
|
||||
const member = await reactionMessage.guild.members.fetch(user.id)
|
||||
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
|
||||
} else if (allowedUsers) {
|
||||
if (!allowedUsers.includes(user.id)) return
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -24,16 +24,19 @@ export const createSuccessEmbed = (title: string | null, description?: string) =
|
||||
)
|
||||
|
||||
export const createMessageScanResponseEmbed = (
|
||||
response: ConfigMessageScanResponseMessage,
|
||||
response: NonNullable<ConfigMessageScanResponseMessage['embeds']>[number],
|
||||
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)
|
||||
|
||||
if (response.description) embed.setDescription(response.description)
|
||||
if (response.fields) embed.addFields(response.fields)
|
||||
|
||||
embed.setFooter({
|
||||
text: `ReVanced • Done via ${MessageScanHumanizedMode[mode]}`,
|
||||
text: `ReVanced • Via ${MessageScanHumanizedMode[mode]}`,
|
||||
iconURL: ReVancedLogoURL,
|
||||
})
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@ import { type Response, responses } from '$/database/schemas'
|
||||
import type {
|
||||
Config,
|
||||
ConfigMessageScanResponse,
|
||||
ConfigMessageScanResponseLabelConfig,
|
||||
ConfigMessageScanResponseMessage,
|
||||
ConfigMessageScanResponseLabelConfig
|
||||
} from 'config.schema'
|
||||
import type { Message, PartialUser, User } from 'discord.js'
|
||||
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
|
||||
{ api, logger }: Omit<typeof import('src/context'), 'config'>,
|
||||
ocrMode = false,
|
||||
) => {
|
||||
let label: string | undefined
|
||||
let response: ConfigMessageScanResponseMessage | undefined | null
|
||||
): Promise<ConfigMessageScanResponse & { label?: string }> => {
|
||||
let responseConfig: Awaited<ReturnType<typeof getResponseFromText>> = {
|
||||
triggers: {},
|
||||
response: null
|
||||
}
|
||||
|
||||
const firstLabelIndexes: number[] = []
|
||||
|
||||
// Test if all regexes before a label trigger is matched
|
||||
@@ -25,29 +27,28 @@ export const getResponseFromText = async (
|
||||
const trigger = responses[i]!
|
||||
|
||||
// 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 {
|
||||
triggers: { text: textTriggers, image: imageTriggers },
|
||||
response: resp,
|
||||
triggers: { text: textTriggers, image: imageTriggers }
|
||||
} = trigger
|
||||
if (response) break
|
||||
if (responseConfig) break
|
||||
|
||||
if (ocrMode) {
|
||||
if (imageTriggers)
|
||||
for (const regex of imageTriggers)
|
||||
if (regex.test(content)) {
|
||||
logger.debug(`Message matched regex (OCR mode): ${regex.source}`)
|
||||
response = resp
|
||||
responseConfig = trigger
|
||||
break
|
||||
}
|
||||
} else
|
||||
for (let j = 0; j < textTriggers!.length; j++) {
|
||||
const trigger = textTriggers![j]!
|
||||
const regex = textTriggers![j]!
|
||||
|
||||
if (trigger instanceof RegExp) {
|
||||
if (trigger.test(content)) {
|
||||
logger.debug(`Message matched regex (before mode): ${trigger.source}`)
|
||||
response = resp
|
||||
if (regex instanceof RegExp) {
|
||||
if (regex.test(content)) {
|
||||
logger.debug(`Message matched regex (before mode): ${regex.source}`)
|
||||
responseConfig = trigger
|
||||
break
|
||||
}
|
||||
} else {
|
||||
@@ -58,7 +59,7 @@ export const getResponseFromText = async (
|
||||
}
|
||||
|
||||
// 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')
|
||||
const scan = await api.client.parseText(content)
|
||||
if (scan.labels.length) {
|
||||
@@ -76,24 +77,22 @@ export const getResponseFromText = async (
|
||||
|
||||
if (!labelConfig) {
|
||||
logger.warn(`No label config found for label ${matchedLabel.name}`)
|
||||
return { response: null, label: undefined }
|
||||
return responseConfig
|
||||
}
|
||||
|
||||
if (matchedLabel.confidence >= triggerConfig!.threshold) {
|
||||
logger.debug('Label confidence is enough')
|
||||
label = matchedLabel.name
|
||||
response = labelConfig.response
|
||||
responseConfig = labelConfig
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we still don't have a label, we can match all regexes after the initial label trigger
|
||||
if (!response) {
|
||||
// If we still don't have a response config, we can match all regexes after the initial label trigger
|
||||
if (!responseConfig) {
|
||||
logger.debug('No match from NLP, doing after regexes')
|
||||
for (let i = 0; i < responses.length; i++) {
|
||||
const {
|
||||
triggers: { text: textTriggers },
|
||||
response: resp,
|
||||
triggers: { text: textTriggers }
|
||||
} = responses[i]!
|
||||
const firstLabelIndex = firstLabelIndexes[i] ?? -1
|
||||
|
||||
@@ -103,7 +102,7 @@ export const getResponseFromText = async (
|
||||
if (trigger instanceof RegExp) {
|
||||
if (trigger.test(content)) {
|
||||
logger.debug(`Message matched regex (after mode): ${trigger.source}`)
|
||||
response = resp
|
||||
responseConfig = responses[i]!
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -111,30 +110,21 @@ export const getResponseFromText = async (
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
response,
|
||||
label,
|
||||
}
|
||||
return responseConfig
|
||||
}
|
||||
|
||||
export const shouldScanMessage = (
|
||||
export const messageMatchesFilter = (
|
||||
message: Message,
|
||||
filter: NonNullable<Config['messageScan']>['filter'],
|
||||
): message is Message<true> => {
|
||||
if (message.author.bot) return false
|
||||
if (!message.guild) return false
|
||||
) => {
|
||||
if (!filter) return true
|
||||
|
||||
const filters = [
|
||||
filter.users?.includes(message.author.id),
|
||||
message.member?.roles.cache.some(x => filter.roles?.includes(x.id)),
|
||||
filter.channels?.includes(message.channel.id),
|
||||
]
|
||||
const memberRoles = new Set(message.member?.roles.cache.keys())
|
||||
const blFilter = filter.blacklist
|
||||
|
||||
if (filter.whitelist && filters.every(x => !x)) return false
|
||||
if (!filter.whitelist && filters.some(x => x)) return false
|
||||
|
||||
return true
|
||||
// If matches blacklist, will return false
|
||||
// Any other case, will return true
|
||||
return !(blFilter && (blFilter.channels?.includes(message.channelId) || blFilter.roles?.some(role => memberRoles.has(role)) || blFilter.users?.includes(message.author.id)))
|
||||
}
|
||||
|
||||
export const handleUserResponseCorrection = async (
|
||||
@@ -158,8 +148,10 @@ export const handleUserResponseCorrection = async (
|
||||
correctedById: user.id,
|
||||
})
|
||||
.where(eq(responses.replyId, response.replyId))
|
||||
|
||||
await reply.edit({
|
||||
embeds: [createMessageScanResponseEmbed(correctLabelResponse.response, 'nlp')],
|
||||
...correctLabelResponse.response,
|
||||
embeds: correctLabelResponse.response.embeds?.map(it => createMessageScanResponseEmbed(it, 'nlp')),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
11
bots/discord/src/utils/discord/permissions.ts
Normal file
11
bots/discord/src/utils/discord/permissions.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { GuildMember, type User } from 'discord.js'
|
||||
import config from '../../../config'
|
||||
|
||||
export const isAdmin = (userOrMember: User | GuildMember) => {
|
||||
return config.admin?.users?.includes(userOrMember.id) || (userOrMember instanceof GuildMember && isMemberAdmin(userOrMember))
|
||||
}
|
||||
|
||||
export const isMemberAdmin = (member: GuildMember) => {
|
||||
const roles = new Set(member.roles.cache.keys())
|
||||
return Boolean(config?.admin?.roles?.[member.guild.id]?.some(role => roles.has(role)))
|
||||
}
|
||||
@@ -54,6 +54,7 @@
|
||||
"lefthook"
|
||||
],
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
|
||||
diff --git a/lib/prepare.js b/lib/prepare.js
|
||||
index 3e76bec44cf595a1b4141728336bed904d4d518d..c6baf4e8de9bdf7536f9ad2e9eb9c360e031c7b5 100644
|
||||
index 3e76bec44cf595a1b4141728336bed904d4d518d..4b25ca64879bbee2a600f2b23b738c86136ad9c6 100644
|
||||
--- a/lib/prepare.js
|
||||
+++ b/lib/prepare.js
|
||||
@@ -1,6 +1,7 @@
|
||||
@@ -14,7 +20,7 @@ index 3e76bec44cf595a1b4141728336bed904d4d518d..c6baf4e8de9bdf7536f9ad2e9eb9c360
|
||||
|
||||
export default async function (
|
||||
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);
|
||||
|
||||
@@ -36,10 +42,11 @@ index 3e76bec44cf595a1b4141728336bed904d4d518d..c6baf4e8de9bdf7536f9ad2e9eb9c360
|
||||
|
||||
- await versionResult;
|
||||
+ await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, detectIndent(pkgJsonRaw).indent))
|
||||
+ await execa("bun", ["install"]);
|
||||
|
||||
if (tarballDir) {
|
||||
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
|
||||
// Fixes: https://github.com/semantic-release/npm/issues/169
|
||||
if (tarballSource !== tarballDestination) {
|
||||
|
||||
21
patches/drizzle-kit@0.22.8.patch
Normal file
21
patches/drizzle-kit@0.22.8.patch
Normal 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>",
|
||||
@@ -31,7 +31,7 @@ const Options = {
|
||||
[
|
||||
'@semantic-release/git',
|
||||
{
|
||||
assets: ['CHANGELOG.md', 'package.json'],
|
||||
assets: ['CHANGELOG.md', 'package.json', '../../bun.lockb'],
|
||||
},
|
||||
],
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user