mirror of
https://github.com/ReVanced/revanced-bots.git
synced 2026-01-12 14:16:19 +00:00
Compare commits
34 Commits
@revanced/
...
@revanced/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e748a4da92 | ||
|
|
300e5cff3b | ||
|
|
6685ffb855 | ||
|
|
cbf91162e2 | ||
|
|
bd906fbf54 | ||
|
|
27662ed91a | ||
|
|
d0acab1915 | ||
|
|
e86180fe29 | ||
|
|
1f5c5a92a6 | ||
|
|
b79a1c7575 | ||
|
|
3559ed1cb5 | ||
|
|
042b155b5e | ||
|
|
673aa189be | ||
|
|
c503a86c53 | ||
|
|
1bd973ea6c | ||
|
|
4bb965e9ff | ||
|
|
a8ceeb29ae | ||
|
|
96a6540434 | ||
|
|
e02c86a9c4 | ||
|
|
e82f2ab34b | ||
|
|
2a6f3c3013 | ||
|
|
875bd209b2 | ||
|
|
2b601b1a1d | ||
|
|
164570d176 | ||
|
|
0d4898dae8 | ||
|
|
38e00eb4e5 | ||
|
|
3117af5497 | ||
|
|
6a87464b40 | ||
|
|
5db076aee5 | ||
|
|
12f5aaf70f | ||
|
|
94d0dcc32b | ||
|
|
4834685186 | ||
|
|
ffe0a3ddf5 | ||
|
|
89d8ab1ee5 |
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -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
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
# @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)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **apis/websocket:** hardcoded paths in tesseract worker builds ([38e00eb](https://github.com/revanced/revanced-helper/commit/38e00eb4e59c763bd74d27b9b9b482ea66e4dcf4))
|
||||||
|
|
||||||
|
# @revanced/bot-websocket-api [1.0.0-dev.3](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.2...@revanced/bot-websocket-api@1.0.0-dev.3) (2024-07-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **apis/websocket:** build and runtime issues ([89d8ab1](https://github.com/revanced/revanced-helper/commit/89d8ab1ee58278a9a96cdc31c679d0a0a0d865af))
|
||||||
|
|
||||||
# @revanced/bot-websocket-api [1.0.0-dev.2](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.1...@revanced/bot-websocket-api@1.0.0-dev.2) (2024-07-22)
|
# @revanced/bot-websocket-api [1.0.0-dev.2](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.1...@revanced/bot-websocket-api@1.0.0-dev.2) (2024-07-22)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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.2",
|
"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": {
|
||||||
|
|||||||
@@ -1,29 +1,34 @@
|
|||||||
import { createLogger } from '@revanced/bot-shared'
|
import { createLogger } from '@revanced/bot-shared'
|
||||||
import { cp, rm } from 'fs/promises'
|
import { cp, rm } from 'fs/promises'
|
||||||
|
|
||||||
async function build(): Promise<void> {
|
|
||||||
const logger = createLogger()
|
const logger = createLogger()
|
||||||
|
|
||||||
logger.info('Cleaning previous build...')
|
logger.info('Cleaning previous build...')
|
||||||
await rm('./dist', { recursive: true })
|
await rm('./dist', { recursive: true })
|
||||||
|
|
||||||
logger.info('Building Tesseract.js worker...')
|
|
||||||
await Bun.build({
|
|
||||||
entrypoints: ['../../node_modules/tesseract.js/src/worker-script/node/index.js'],
|
|
||||||
target: 'bun',
|
|
||||||
outdir: './dist/worker',
|
|
||||||
})
|
|
||||||
|
|
||||||
logger.info('Copying Tesseract.js WASM...')
|
|
||||||
await cp('../../node_modules/tesseract.js-core', './dist/worker/core', { recursive: true })
|
|
||||||
|
|
||||||
logger.info('Building WebSocket API...')
|
logger.info('Building WebSocket API...')
|
||||||
await Bun.build({
|
await Bun.build({
|
||||||
entrypoints: ['./src/index.ts'],
|
entrypoints: ['./src/index.ts'],
|
||||||
outdir: './dist',
|
outdir: './dist',
|
||||||
target: 'bun',
|
target: 'bun',
|
||||||
|
minify: true,
|
||||||
|
sourcemap: 'external',
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
await build()
|
logger.info('Building Tesseract.js worker...')
|
||||||
|
await Bun.build({
|
||||||
|
entrypoints: ['../../node_modules/tesseract.js/src/worker-script/node/index.js'],
|
||||||
|
external: ['tesseract.js-core/*'],
|
||||||
|
target: 'bun',
|
||||||
|
outdir: './dist/worker',
|
||||||
|
minify: true,
|
||||||
|
sourcemap: 'external',
|
||||||
|
})
|
||||||
|
|
||||||
|
// Tesseract.js is really bad for minification
|
||||||
|
// It forcefully requires this core module to be present which contains the WASM files
|
||||||
|
logger.info('Copying Tesseract.js Core...')
|
||||||
|
await cp('../../node_modules/tesseract.js-core', './dist/node_modules/tesseract.js-core', { recursive: true })
|
||||||
|
|
||||||
|
logger.info('Copying config...')
|
||||||
await cp('config.json', 'dist/config.json')
|
await cp('config.json', 'dist/config.json')
|
||||||
|
|||||||
@@ -55,12 +55,9 @@ export interface WitMessageResponse {
|
|||||||
|
|
||||||
const TesseractWorkerDirPath = joinPath(import.meta.dir, 'worker')
|
const TesseractWorkerDirPath = joinPath(import.meta.dir, 'worker')
|
||||||
const TesseractWorkerPath = joinPath(TesseractWorkerDirPath, 'index.js')
|
const TesseractWorkerPath = joinPath(TesseractWorkerDirPath, 'index.js')
|
||||||
const TesseractCorePath = joinPath(TesseractWorkerDirPath, 'core')
|
|
||||||
|
|
||||||
export const tesseract = await createTesseractWorker(
|
export const tesseract = await createTesseractWorker(
|
||||||
'eng',
|
'eng',
|
||||||
OEM.DEFAULT,
|
OEM.DEFAULT,
|
||||||
(await pathExists(TesseractWorkerDirPath))
|
(await pathExists(TesseractWorkerDirPath)) ? { workerPath: TesseractWorkerPath } : undefined,
|
||||||
? { workerPath: TesseractWorkerPath, corePath: TesseractCorePath }
|
|
||||||
: undefined,
|
|
||||||
)
|
)
|
||||||
|
|||||||
1
bots/discord/.gitignore
vendored
1
bots/discord/.gitignore
vendored
@@ -181,6 +181,7 @@ config.ts
|
|||||||
*.db
|
*.db
|
||||||
*.sqlite
|
*.sqlite
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
|
.drizzle
|
||||||
|
|
||||||
# Auto-generated files
|
# Auto-generated files
|
||||||
src/commands/index.ts
|
src/commands/index.ts
|
||||||
|
|||||||
@@ -1,3 +1,47 @@
|
|||||||
|
# @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)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **bots/discord:** don't nest builds in src directory, autogen db when missing ([4834685](https://github.com/revanced/revanced-helper/commit/48346851864c4d4b6276388644dd24ce16222b3e))
|
||||||
|
|
||||||
# @revanced/discord-bot 1.0.0-dev.1 (2024-07-22)
|
# @revanced/discord-bot 1.0.0-dev.1 (2024-07-22)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
import type { Config } from './config.schema'
|
// @ts-check
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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',
|
||||||
@@ -52,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: [
|
||||||
@@ -61,6 +70,8 @@ export default {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -70,4 +81,4 @@ export default {
|
|||||||
disconnectLimit: 3,
|
disconnectLimit: 3,
|
||||||
disconnectRetryInterval: 10000,
|
disconnectRetryInterval: 10000,
|
||||||
},
|
},
|
||||||
} satisfies Config as Config
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -29,9 +29,8 @@ The distribution files will be placed inside the `dist` directory. Inside will i
|
|||||||
|
|
||||||
To deploy the bot, you'll need to:
|
To deploy the bot, you'll need to:
|
||||||
|
|
||||||
1. Replace the `config.ts` file with your own configuration _(optional)_
|
1. [Build the bot as seen in the previous step](#-building)
|
||||||
2. [Build the bot as seen in the previous step](#-building)
|
2. Run the `reload-slash-commands` script
|
||||||
3. Run the `reload-slash-commands` script
|
|
||||||
This is to ensure all commands are registered, so they can be used.
|
This is to ensure all commands are registered, so they can be used.
|
||||||
**It may take up to 2 hours until **global** commands are updated. This is a Discord limitation.**
|
**It may take up to 2 hours until **global** commands are updated. This is a Discord limitation.**
|
||||||
|
|
||||||
@@ -40,7 +39,7 @@ To deploy the bot, you'll need to:
|
|||||||
bun run scripts/reload-slash-commands.ts
|
bun run scripts/reload-slash-commands.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Copy contents of the `dist` directory
|
3. Copy contents of the `dist` directory
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# For instance, we'll copy them both to /usr/src/discord-bot
|
# For instance, we'll copy them both to /usr/src/discord-bot
|
||||||
@@ -48,18 +47,10 @@ To deploy the bot, you'll need to:
|
|||||||
cp -R ./dist/* /usr/src/discord-bot
|
cp -R ./dist/* /usr/src/discord-bot
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Copy the empty database (or use your own existing database)
|
4. Configure environment variables
|
||||||
|
|
||||||
```sh
|
|
||||||
# By default, the build script creates the database called "db.sqlite3"
|
|
||||||
# Unless you specify otherwise via the "DATABASE_PATH" environment variable
|
|
||||||
cp ./db.sqlite3 /usr/src/discord-bot
|
|
||||||
```
|
|
||||||
|
|
||||||
6. Configure environment variables
|
|
||||||
As seen in [`.env.example`](../.env.example). You can also optionally use a `.env` file which **Bun will automatically load**.
|
As seen in [`.env.example`](../.env.example). You can also optionally use a `.env` file which **Bun will automatically load**.
|
||||||
|
|
||||||
7. Finally, run the bot
|
5. Finally, run the bot
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd /usr/src/discord-bot
|
cd /usr/src/discord-bot
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { defineConfig } from 'drizzle-kit'
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
dialect: 'sqlite',
|
dialect: 'sqlite',
|
||||||
schema: './src/database/schemas.ts',
|
schema: './src/database/schemas.ts',
|
||||||
|
out: './.drizzle',
|
||||||
dbCredentials: {
|
dbCredentials: {
|
||||||
url: process.env['DATABASE_PATH'] ? `file:./${process.env['DATABASE_PATH']}` : 'file:./db.sqlite3',
|
url: process.env['DATABASE_PATH'] ? `file:./${process.env['DATABASE_PATH']}` : 'file:./db.sqlite3',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,17 +2,16 @@
|
|||||||
"name": "@revanced/discord-bot",
|
"name": "@revanced/discord-bot",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0-dev.1",
|
"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": {
|
||||||
"register": "bun run scripts/reload-slash-commands.ts",
|
"register": "bun run scripts/reload-slash-commands.ts",
|
||||||
"start": "bun prepare && bun run src/index.ts",
|
"start": "bun prepare && bun run src/index.ts",
|
||||||
"dev": "bun prepare && bun --watch src/index.ts",
|
"dev": "bun prepare && bun --watch src/index.ts",
|
||||||
"build:config": "bun build config.ts --outdir=dist",
|
"build": "bun prepare && bun run scripts/build.ts",
|
||||||
"build": "bun prepare && bun build:config && bun build src/index.ts -e ./config.js --target=bun --outdir=dist/src",
|
|
||||||
"watch": "bun dev",
|
"watch": "bun dev",
|
||||||
"prepare": "bun run scripts/generate-indexes.ts && drizzle-kit push"
|
"prepare": "bun run scripts/generate-indexes.ts && bunx drizzle-kit generate --name=schema"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
23
bots/discord/scripts/build.ts
Normal file
23
bots/discord/scripts/build.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { createLogger } from '@revanced/bot-shared'
|
||||||
|
import { cp, rename, rm } from 'fs/promises'
|
||||||
|
|
||||||
|
const logger = createLogger()
|
||||||
|
|
||||||
|
logger.warn('Cleaning previous build...')
|
||||||
|
await rm('./dist', { recursive: true })
|
||||||
|
|
||||||
|
logger.info('Building bot...')
|
||||||
|
await Bun.build({
|
||||||
|
entrypoints: ['./src/index.ts'],
|
||||||
|
outdir: './dist/src',
|
||||||
|
target: 'bun',
|
||||||
|
external: ['./config.js'],
|
||||||
|
minify: true,
|
||||||
|
sourcemap: 'external',
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.info('Copying config...')
|
||||||
|
await cp('config.js', 'dist/config.js')
|
||||||
|
|
||||||
|
logger.info('Copying database schema...')
|
||||||
|
await rename('.drizzle', 'dist/.drizzle')
|
||||||
@@ -1,50 +1,53 @@
|
|||||||
import { REST } from '@discordjs/rest'
|
console.log('Deprecated. New implementation to be done.')
|
||||||
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'
|
|
||||||
|
|
||||||
// Check if token exists
|
|
||||||
|
|
||||||
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)
|
process.exit(1)
|
||||||
}
|
|
||||||
|
|
||||||
// Group commands by global and guild
|
// 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 { global: globalCommands = [], guild: guildCommands = [] } = Object.groupBy(Object.values(discord.commands), c =>
|
// // Check if token exists
|
||||||
c.global ? 'global' : 'guild',
|
|
||||||
)
|
|
||||||
|
|
||||||
// Set commands
|
// 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 rest = new REST({ version: '10' }).setToken(process.env['DISCORD_TOKEN']!)
|
// // Group commands by global and guild
|
||||||
|
|
||||||
try {
|
// const { global: globalCommands = [], guild: guildCommands = [] } = Object.groupBy(Object.values(discord.commands), c =>
|
||||||
const app = (await rest.get(Routes.currentApplication())) as RESTGetCurrentApplicationResult
|
// c.global ? 'global' : 'guild',
|
||||||
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`)
|
// // Set commands
|
||||||
|
|
||||||
for (const guildId of config.guilds) {
|
// const rest = new REST({ version: '10' }).setToken(process.env['DISCORD_TOKEN']!)
|
||||||
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}`)
|
// try {
|
||||||
}
|
// const app = (await rest.get(Routes.currentApplication())) as RESTGetCurrentApplicationResult
|
||||||
} catch (e) {
|
// const data = (await rest.put(Routes.applicationCommands(app.id), {
|
||||||
logger.fatal(e)
|
// 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`)
|
||||||
|
|
||||||
|
// 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)
|
.setDMPermission(true)
|
||||||
.toJSON(),
|
.toJSON(),
|
||||||
|
|
||||||
ownerOnly: true,
|
adminOnly: true,
|
||||||
global: true,
|
global: true,
|
||||||
|
|
||||||
async execute(_, interaction) {
|
async execute(_, interaction) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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}`,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { Database } from 'bun:sqlite'
|
import { Database } from 'bun:sqlite'
|
||||||
|
import { existsSync, readFileSync, readdirSync } from 'fs'
|
||||||
|
import { join } from 'path'
|
||||||
import { Client as APIClient } from '@revanced/bot-api'
|
import { Client as APIClient } from '@revanced/bot-api'
|
||||||
import { createLogger } from '@revanced/bot-shared'
|
import { createLogger } from '@revanced/bot-shared'
|
||||||
import { ActivityType, Client as DiscordClient, Partials } from 'discord.js'
|
import { ActivityType, Client as DiscordClient, Partials } from 'discord.js'
|
||||||
@@ -29,7 +31,33 @@ export const api = {
|
|||||||
disconnectCount: 0,
|
disconnectCount: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = new Database(process.env['DATABASE_PATH'])
|
const DatabasePath = process.env['DATABASE_PATH']
|
||||||
|
const DatabaseSchemaDir = join(import.meta.dir, '..', '.drizzle')
|
||||||
|
|
||||||
|
let dbSchemaFileName: string | undefined
|
||||||
|
|
||||||
|
if (DatabasePath && !existsSync(DatabasePath)) {
|
||||||
|
logger.warn('Database file not found, trying to create from schema...')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const file = readdirSync(DatabaseSchemaDir, { withFileTypes: true })
|
||||||
|
.filter(file => file.isFile() && file.name.endsWith('.sql'))
|
||||||
|
.sort()
|
||||||
|
.at(-1)
|
||||||
|
|
||||||
|
if (!file) throw new Error('No schema file found')
|
||||||
|
|
||||||
|
dbSchemaFileName = file.name
|
||||||
|
logger.debug(`Using schema file: ${dbSchemaFileName}`)
|
||||||
|
} catch (e) {
|
||||||
|
logger.fatal('Could not create database from schema, check if the schema file exists and is accessible')
|
||||||
|
logger.fatal(e)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = new Database(DatabasePath)
|
||||||
|
if (dbSchemaFileName) db.run(readFileSync(join(DatabaseSchemaDir, dbSchemaFileName)).toString())
|
||||||
|
|
||||||
export const database = drizzle(db, {
|
export const database = drizzle(db, {
|
||||||
schema: schemas,
|
schema: schemas,
|
||||||
|
|||||||
@@ -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']({
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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 = (
|
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,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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')),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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 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)))
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
"composite": false,
|
"composite": false,
|
||||||
"exactOptionalPropertyTypes": false,
|
"exactOptionalPropertyTypes": false,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
"allowJs": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"$/*": ["./src/*"],
|
"$/*": ["./src/*"],
|
||||||
"$constants": ["./src/constants"],
|
"$constants": ["./src/constants"],
|
||||||
|
|||||||
@@ -41,11 +41,11 @@
|
|||||||
"@tsconfig/strictest": "^2.0.5",
|
"@tsconfig/strictest": "^2.0.5",
|
||||||
"@types/bun": "^1.1.6",
|
"@types/bun": "^1.1.6",
|
||||||
"conventional-changelog-conventionalcommits": "^7.0.2",
|
"conventional-changelog-conventionalcommits": "^7.0.2",
|
||||||
"lefthook": "^1.7.4",
|
"lefthook": "^1.7.5",
|
||||||
"portainer-service-webhook": "https://github.com/newarifrh/portainer-service-webhook#v1",
|
"portainer-service-webhook": "https://github.com/newarifrh/portainer-service-webhook#v1",
|
||||||
"semantic-release": "^24.0.0",
|
"semantic-release": "^24.0.0",
|
||||||
"turbo": "^2.0.9",
|
"turbo": "^2.0.9",
|
||||||
"typescript": "^5.5.3"
|
"typescript": "^5.5.4"
|
||||||
},
|
},
|
||||||
"trustedDependencies": [
|
"trustedDependencies": [
|
||||||
"@biomejs/biome",
|
"@biomejs/biome",
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,23 +53,34 @@ export class ClientWebSocketManager {
|
|||||||
}
|
}
|
||||||
}, this.timeout)
|
}, this.timeout)
|
||||||
|
|
||||||
this.#socket.on('open', () => {
|
const errorBeforeReadyHandler = (err: Error) => {
|
||||||
this.disconnected = false
|
cleanup()
|
||||||
clearTimeout(timeout)
|
|
||||||
this.#listen()
|
|
||||||
rs()
|
|
||||||
})
|
|
||||||
|
|
||||||
this.#socket.on('error', err => {
|
|
||||||
clearTimeout(timeout)
|
|
||||||
throw err
|
throw err
|
||||||
})
|
}
|
||||||
|
|
||||||
this.#socket.on('close', (code, reason) => {
|
const closeBeforeReadyHandler = (code: number, reason: Buffer) => {
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
this._handleDisconnect(code, reason.toString())
|
this._handleDisconnect(code, reason.toString())
|
||||||
throw new Error('WebSocket connection closed before ready')
|
throw new Error('WebSocket connection closed before ready')
|
||||||
})
|
}
|
||||||
|
|
||||||
|
const readyHandler = () => {
|
||||||
|
this.disconnected = false
|
||||||
|
cleanup()
|
||||||
|
this.#listen()
|
||||||
|
rs()
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
this.#socket.off('open', readyHandler)
|
||||||
|
this.#socket.off('close', closeBeforeReadyHandler)
|
||||||
|
this.#socket.off('error', errorBeforeReadyHandler)
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#socket.on('open', readyHandler)
|
||||||
|
this.#socket.on('error', errorBeforeReadyHandler)
|
||||||
|
this.#socket.on('close', closeBeforeReadyHandler)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rj(e)
|
rj(e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
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',
|
'@semantic-release/git',
|
||||||
{
|
{
|
||||||
assets: ['CHANGELOG.md', 'package.json'],
|
assets: ['CHANGELOG.md', 'package.json', '../../bun.lockb'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|||||||
Reference in New Issue
Block a user