Compare commits

..

14 Commits

Author SHA1 Message Date
semantic-release-bot
c83e219088 chore(release): 1.1.0 [skip ci]
# @revanced/bot-websocket-api [1.1.0](https://github.com/revanced/revanced-bots/compare/@revanced/bot-websocket-api@1.0.2...@revanced/bot-websocket-api@1.1.0) (2025-07-11)

### Features

* **apis/websocket:** support training without label ([670cc70](670cc70057))
2025-07-11 18:07:46 +00:00
PalmDevs
6164e6c1a5 chore: format 2025-07-12 01:06:45 +07:00
PalmDevs
47d2f8c015 chore: update dependencies and remove outdated patches 2025-07-12 01:06:43 +07:00
PalmDevs
c68cfd1c01 feat(bots/discord): support training without label 2025-07-12 01:06:41 +07:00
PalmDevs
2b1928e116 feat(packages/api): support training without label 2025-07-12 01:06:39 +07:00
PalmDevs
670cc70057 feat(apis/websocket): support training without label 2025-07-12 01:06:37 +07:00
Palm
439f301872 chore: merge dev to main 2025-07-11 21:05:10 +07:00
Pun Butrach
aeccf12906 ci: Use default fetch-depth of 1 for checkout step (#47) 2025-07-08 18:14:54 +02:00
semantic-release-bot
b4ac8433f4 chore(release): 1.2.3 [skip ci]
## @revanced/discord-bot [1.2.3](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.2.2...@revanced/discord-bot@1.2.3) (2025-06-23)

### Bug Fixes

* **bots/discord:** disable unneeded cache, enable message cache sweeping ([3a0f0fe](3a0f0fe786))
2025-06-23 13:33:41 +00:00
semantic-release-bot
b01a4accee chore(release): 1.0.2 [skip ci]
## @revanced/bot-websocket-api [1.0.2](https://github.com/revanced/revanced-bots/compare/@revanced/bot-websocket-api@1.0.1...@revanced/bot-websocket-api@1.0.2) (2025-06-23)
2025-06-23 13:32:58 +00:00
PalmDevs
858e989bdc build(Needs bump): update dependencies 2025-06-23 20:32:18 +07:00
PalmDevs
3a0f0fe786 fix(bots/discord): disable unneeded cache, enable message cache sweeping 2025-06-23 20:30:28 +07:00
semantic-release-bot
409e850768 chore(release): 1.2.2 [skip ci]
## @revanced/discord-bot [1.2.2](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.2.1...@revanced/discord-bot@1.2.2) (2025-06-10)

### Bug Fixes

* **bots/discord:** use intervals for checking expired presets ([6e89b87](6e89b874cd))
2025-06-10 17:40:44 +00:00
PalmDevs
6e89b874cd fix(bots/discord): use intervals for checking expired presets 2025-06-11 00:40:08 +07:00
59 changed files with 1051 additions and 485 deletions

View File

@@ -20,11 +20,6 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with:
# Make sure the release step uses its own credentials:
# https://github.com/cycjimmy/semantic-release-action#private-packages
persist-credentials: false
fetch-depth: 0
- name: Cache dependencies - name: Cache dependencies
uses: actions/cache@v4 uses: actions/cache@v4

View File

@@ -1,3 +1,12 @@
# @revanced/bot-websocket-api [1.1.0](https://github.com/revanced/revanced-bots/compare/@revanced/bot-websocket-api@1.0.2...@revanced/bot-websocket-api@1.1.0) (2025-07-11)
### Features
* **apis/websocket:** support training without label ([670cc70](https://github.com/revanced/revanced-bots/commit/670cc700570b32924738c3f24acb9f5312d2dcdb))
## @revanced/bot-websocket-api [1.0.2](https://github.com/revanced/revanced-bots/compare/@revanced/bot-websocket-api@1.0.1...@revanced/bot-websocket-api@1.0.2) (2025-06-23)
## @revanced/bot-websocket-api [1.0.1](https://github.com/revanced/revanced-bots/compare/@revanced/bot-websocket-api@1.0.0...@revanced/bot-websocket-api@1.0.1) (2025-04-09) ## @revanced/bot-websocket-api [1.0.1](https://github.com/revanced/revanced-bots/compare/@revanced/bot-websocket-api@1.0.0...@revanced/bot-websocket-api@1.0.1) (2025-04-09)

View File

@@ -2,7 +2,7 @@
"name": "@revanced/bot-websocket-api", "name": "@revanced/bot-websocket-api",
"type": "module", "type": "module",
"private": true, "private": true,
"version": "1.0.1", "version": "1.1.0",
"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": {
@@ -30,11 +30,11 @@
"@revanced/bot-shared": "workspace:*", "@revanced/bot-shared": "workspace:*",
"@sapphire/async-queue": "^1.5.5", "@sapphire/async-queue": "^1.5.5",
"chalk": "^5.4.1", "chalk": "^5.4.1",
"tesseract.js": "^5.1.1", "tesseract.js": "^6.0.1",
"ws": "^8.18.1" "ws": "^8.18.3"
}, },
"devDependencies": { "devDependencies": {
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"typed-emitter": "^2.1.0" "typed-emitter": "^2.1.0"
} }
} }

View File

@@ -1,15 +1,14 @@
import { EventEmitter } from 'events'
import { import {
ClientOperation, ClientOperation,
DisconnectReason, DisconnectReason,
type Packet,
ServerOperation,
deserializePacket, deserializePacket,
isClientPacket, isClientPacket,
type Packet,
ServerOperation,
serializePacket, serializePacket,
uncapitalize, uncapitalize,
} from '@revanced/bot-shared' } from '@revanced/bot-shared'
import { EventEmitter } from 'events'
import type TypedEmitter from 'typed-emitter' import type TypedEmitter from 'typed-emitter'
import type { RawData, WebSocket } from 'ws' import type { RawData, WebSocket } from 'ws'
@@ -100,7 +99,7 @@ export default class Client {
// @ts-expect-error TypeScript doesn't know that the above line will negate the type enough // @ts-expect-error TypeScript doesn't know that the above line will negate the type enough
packet, packet,
) )
} catch (e) { } catch (_e) {
// TODO: add error fields to sent packet so we can log what went wrong // TODO: add error fields to sent packet so we can log what went wrong
this.disconnect(DisconnectReason.InvalidPacket) this.disconnect(DisconnectReason.InvalidPacket)
} }

View File

@@ -1,8 +1,7 @@
import { OEM, createWorker as createTesseractWorker } from 'tesseract.js'
import { join as joinPath } from 'path'
import { createLogger } from '@revanced/bot-shared' import { createLogger } from '@revanced/bot-shared'
import { exists as pathExists } from 'fs/promises' import { exists as pathExists } from 'fs/promises'
import { join as joinPath } from 'path'
import { createWorker as createTesseractWorker, OEM } from 'tesseract.js'
import { getConfig } from './utils/config' import { getConfig } from './utils/config'
export const config = getConfig() export const config = getConfig()
@@ -24,12 +23,12 @@ export const wit = {
if (!res.ok) throw new Error(`Failed to fetch from Wit.ai: ${res.statusText} (${res.status})`) if (!res.ok) throw new Error(`Failed to fetch from Wit.ai: ${res.statusText} (${res.status})`)
return await res.json() return (await res.json()) as WitMessageResponse
}, },
message(text: string) { message(text: string) {
return this.fetch(`/message?q=${encodeURIComponent(text)}&n=8`) as Promise<WitMessageResponse> return this.fetch(`/message?q=${encodeURIComponent(text)}&n=8`) as Promise<WitMessageResponse>
}, },
async train(text: string, label: string) { async train(text: string, label?: string) {
await this.fetch('/utterances', { await this.fetch('/utterances', {
body: JSON.stringify([ body: JSON.stringify([
{ {
@@ -42,7 +41,14 @@ export const wit = {
method: 'POST', method: 'POST',
}) })
}, },
} as const } satisfies Wit
export interface Wit {
token: string
fetch(route: string, options?: RequestInit): Promise<WitMessageResponse>
message(text: string): Promise<WitMessageResponse>
train(text: string, label?: string): Promise<void>
}
export interface WitMessageResponse { export interface WitMessageResponse {
text: string text: string

View File

@@ -1,12 +1,11 @@
import type { ClientOperation } from '@revanced/bot-shared' import type { ClientOperation, Logger } from '@revanced/bot-shared'
import type { Logger } from '@revanced/bot-shared'
import type { Worker as TesseractWorker } from 'tesseract.js' import type { Worker as TesseractWorker } from 'tesseract.js'
import type { ClientPacketObject } from '../classes/Client' import type { ClientPacketObject } from '../classes/Client'
import type { WitMessageResponse } from '../context' import type { Wit } from '../context'
import type { Config } from '../utils/config' import type { Config } from '../utils/config'
export { default as parseTextEventHandler } from './parseText'
export { default as parseImageEventHandler } from './parseImage' export { default as parseImageEventHandler } from './parseImage'
export { default as parseTextEventHandler } from './parseText'
export { default as trainMessageEventHandler } from './trainMessage' export { default as trainMessageEventHandler } from './trainMessage'
export type EventHandler<POp extends ClientOperation> = ( export type EventHandler<POp extends ClientOperation> = (
@@ -15,10 +14,7 @@ export type EventHandler<POp extends ClientOperation> = (
) => void | Promise<void> ) => void | Promise<void>
export type EventContext = { export type EventContext = {
wit: { wit: Wit
train(text: string, label: string): Promise<void>
message(text: string): Promise<WitMessageResponse>
}
tesseract: TesseractWorker tesseract: TesseractWorker
logger: Logger logger: Logger
config: Config config: Config

View File

@@ -1,6 +1,5 @@
import { type ClientOperation, ServerOperation } from '@revanced/bot-shared' import { type ClientOperation, ServerOperation } from '@revanced/bot-shared'
import { AsyncQueue } from '@sapphire/async-queue' import { AsyncQueue } from '@sapphire/async-queue'
import type { EventHandler } from '.' import type { EventHandler } from '.'
const queue = new AsyncQueue() const queue = new AsyncQueue()

View File

@@ -1,5 +1,4 @@
import { type ClientOperation, ServerOperation } from '@revanced/bot-shared' import { type ClientOperation, ServerOperation } from '@revanced/bot-shared'
import type { EventHandler } from '.' import type { EventHandler } from '.'
const parseTextEventHandler: EventHandler<ClientOperation.ParseText> = async (packet, { wit, logger }) => { const parseTextEventHandler: EventHandler<ClientOperation.ParseText> = async (packet, { wit, logger }) => {

View File

@@ -1,5 +1,4 @@
import { type ClientOperation, ServerOperation } from '@revanced/bot-shared' import { type ClientOperation, ServerOperation } from '@revanced/bot-shared'
import type { EventHandler } from '.' import type { EventHandler } from '.'
const trainMessageEventHandler: EventHandler<ClientOperation.TrainMessage> = async (packet, { wit, logger }) => { const trainMessageEventHandler: EventHandler<ClientOperation.TrainMessage> = async (packet, { wit, logger }) => {
@@ -11,7 +10,7 @@ const trainMessageEventHandler: EventHandler<ClientOperation.TrainMessage> = asy
const nextSeq = client.currentSequence++ const nextSeq = client.currentSequence++
const actualText = text.slice(0, 279) const actualText = text.slice(0, 279)
logger.debug(`${client.id} requested to train label ${label} (${nextSeq}) with:`, actualText) logger.debug(`${client.id} requested to train label ${label ?? '<out of scope>'} (${nextSeq}) with:`, actualText)
try { try {
await wit.train(actualText, label) await wit.train(actualText, label)

View File

@@ -1,14 +1,10 @@
import { inspect as inspectObject } from 'util'
import Client from './classes/Client'
import { type EventContext, parseImageEventHandler, parseTextEventHandler, trainMessageEventHandler } from './events'
import { DisconnectReason, HumanizedDisconnectReason } from '@revanced/bot-shared' import { DisconnectReason, HumanizedDisconnectReason } from '@revanced/bot-shared'
import { createServer } from 'http' import { createServer } from 'http'
import { inspect as inspectObject } from 'util'
import { type WebSocket, WebSocketServer } from 'ws' import { type WebSocket, WebSocketServer } from 'ws'
import Client from './classes/Client'
import { config, logger, tesseract, wit } from './context' import { config, logger, tesseract, wit } from './context'
import { type EventContext, parseImageEventHandler, parseTextEventHandler, trainMessageEventHandler } from './events'
// Load config, init logger, check environment // Load config, init logger, check environment

View File

@@ -1,6 +1,20 @@
{ {
"$schema": "https://biomejs.dev/schemas/1.8.2/schema.json", "$schema": "https://biomejs.dev/schemas/2.0.5/schema.json",
"organizeImports": { "assist": {
"actions": {
"source": {
"organizeImports": {
"level": "on",
"options": {
"groups": [
{
"type": false
}
]
}
}
}
},
"enabled": true "enabled": true
}, },
"linter": { "linter": {
@@ -24,7 +38,15 @@
}, },
"useNumberNamespace": { "useNumberNamespace": {
"level": "off" "level": "off"
} },
"noParameterAssign": "error",
"useAsConstAssertion": "error",
"useDefaultParameterLast": "error",
"useSelfClosingElements": "error",
"useSingleVarDeclarator": "error",
"noUnusedTemplateLiteral": "error",
"noInferrableTypes": "error",
"noUselessElse": "error"
} }
} }
}, },
@@ -54,7 +76,12 @@
}, },
"files": { "files": {
"ignoreUnknown": true, "ignoreUnknown": true,
"include": ["*.js", "*.json", "*.ts"], "includes": [
"ignore": ["dist/**/*", "node_modules/**/*"] "**/*.js",
"**/*.json",
"**/*.ts",
"!**/dist/**/*",
"!**/node_modules/**/*"
]
} }
} }

View File

@@ -1,3 +1,17 @@
## @revanced/discord-bot [1.2.3](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.2.2...@revanced/discord-bot@1.2.3) (2025-06-23)
### Bug Fixes
* **bots/discord:** disable unneeded cache, enable message cache sweeping ([3a0f0fe](https://github.com/revanced/revanced-bots/commit/3a0f0fe7861d73a4d81ecaba0e12bd60c06f8eb8))
## @revanced/discord-bot [1.2.2](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.2.1...@revanced/discord-bot@1.2.2) (2025-06-10)
### Bug Fixes
* **bots/discord:** use intervals for checking expired presets ([6e89b87](https://github.com/revanced/revanced-bots/commit/6e89b874cdfee8a1c215559271c741f43ba578ce))
## @revanced/discord-bot [1.2.1](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.2.0...@revanced/discord-bot@1.2.1) (2025-05-02) ## @revanced/discord-bot [1.2.1](https://github.com/revanced/revanced-bots/compare/@revanced/discord-bot@1.2.0...@revanced/discord-bot@1.2.1) (2025-05-02)

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
"name": "@revanced/discord-bot", "name": "@revanced/discord-bot",
"type": "module", "type": "module",
"private": true, "private": true,
"version": "1.2.1", "version": "1.2.3",
"description": "🤖 Discord bot assisting ReVanced", "description": "🤖 Discord bot assisting ReVanced",
"main": "src/index.ts", "main": "src/index.ts",
"scripts": { "scripts": {
@@ -28,19 +28,19 @@
}, },
"homepage": "https://github.com/revanced/revanced-bots#readme", "homepage": "https://github.com/revanced/revanced-bots#readme",
"dependencies": { "dependencies": {
"@discordjs/builders": "^1.10.1", "@discordjs/builders": "^1.11.2",
"@discordjs/rest": "^2.4.3", "@discordjs/rest": "^2.5.1",
"@revanced/bot-api": "workspace:*", "@revanced/bot-api": "workspace:*",
"@revanced/bot-shared": "workspace:*", "@revanced/bot-shared": "workspace:*",
"@sapphire/duration": "^1.2.0", "@sapphire/duration": "^1.2.0",
"chalk": "^5.4.1", "chalk": "^5.4.1",
"decancer": "^3.2.8", "decancer": "^3.3.2",
"discord.js": "^14.18.0", "discord.js": "^14.21.0",
"drizzle-orm": "^0.31.4" "drizzle-orm": "^0.44.2"
}, },
"devDependencies": { "devDependencies": {
"@libsql/client": "^0.7.0", "@libsql/client": "^0.15.9",
"discord-api-types": "^0.37.119", "discord-api-types": "^0.38.15",
"drizzle-kit": "^0.22.8" "drizzle-kit": "^0.31.4"
} }
} }

View File

@@ -1,8 +1,7 @@
import { ApplicationCommandOptionType, ApplicationCommandType } from 'discord.js' import { ApplicationCommandOptionType, ApplicationCommandType } from 'discord.js'
import { config } from '../context'
import { isAdmin } from '../utils/discord/permissions' import { isAdmin } from '../utils/discord/permissions'
import CommandError, { CommandErrorType } from './CommandError' import CommandError, { CommandErrorType } from './CommandError'
import type { import type {
APIApplicationCommandChannelOption, APIApplicationCommandChannelOption,
CacheType, CacheType,
@@ -20,7 +19,6 @@ import type {
UserContextMenuCommandInteraction, UserContextMenuCommandInteraction,
UserResolvable, UserResolvable,
} from 'discord.js' } from 'discord.js'
import { config } from '../context'
export enum CommandType { export enum CommandType {
ChatGlobal = 1, ChatGlobal = 1,

View File

@@ -1,9 +1,8 @@
import { ApplicationCommandOptionType, MessageFlags } from 'discord.js'
import { unlinkSync, writeFileSync } from 'fs' import { unlinkSync, writeFileSync } from 'fs'
import { join } from 'path' import { join } from 'path'
import { inspect } from 'util' import { inspect } from 'util'
import { createContext, runInContext } from 'vm' import { createContext, runInContext } from 'vm'
import { ApplicationCommandOptionType, MessageFlags } from 'discord.js'
import { AdminCommand } from '$/classes/Command' import { AdminCommand } from '$/classes/Command'
import { createSuccessEmbed } from '$/utils/discord/embeds' import { createSuccessEmbed } from '$/utils/discord/embeds'
import { parseDuration } from '$/utils/duration' import { parseDuration } from '$/utils/duration'

View File

@@ -1,5 +1,4 @@
import { ApplicationCommandOptionType } from 'discord.js' import { ApplicationCommandOptionType } from 'discord.js'
import { AdminCommand } from '$/classes/Command' import { AdminCommand } from '$/classes/Command'
import CommandError, { CommandErrorType } from '$/classes/CommandError' import CommandError, { CommandErrorType } from '$/classes/CommandError'

View File

@@ -1,5 +1,5 @@
import { AdminCommand } from '$/classes/Command'
import { type CommandInteraction, MessageFlags } from 'discord.js' import { type CommandInteraction, MessageFlags } from 'discord.js'
import { AdminCommand } from '$/classes/Command'
export default new AdminCommand({ export default new AdminCommand({
name: 'reload', name: 'reload',

View File

@@ -1,8 +1,6 @@
import { ApplicationCommandOptionType, Routes } from 'discord.js' import { ApplicationCommandOptionType, Routes } from 'discord.js'
import { AdminCommand } from '$/classes/Command' import { AdminCommand } from '$/classes/Command'
import CommandError, { CommandErrorType } from '$/classes/CommandError' import CommandError, { CommandErrorType } from '$/classes/CommandError'
import { createSuccessEmbed } from '$/utils/discord/embeds' import { createSuccessEmbed } from '$/utils/discord/embeds'
const SubcommandOptions = { const SubcommandOptions = {

View File

@@ -1,5 +1,5 @@
import { AdminCommand } from '$/classes/Command'
import { MessageFlags } from 'discord.js' import { MessageFlags } from 'discord.js'
import { AdminCommand } from '$/classes/Command'
export default new AdminCommand({ export default new AdminCommand({
name: 'stop', name: 'stop',

View File

@@ -1,5 +1,4 @@
import { EmbedBuilder, MessageFlags } from 'discord.js' import { EmbedBuilder, MessageFlags } from 'discord.js'
import Command from '$/classes/Command' import Command from '$/classes/Command'
import { applyCommonEmbedStyles } from '$/utils/discord/embeds' import { applyCommonEmbedStyles } from '$/utils/discord/embeds'

View File

@@ -1,5 +1,5 @@
import CommandError, { CommandErrorType } from '$/classes/CommandError'
import { ApplicationCommandOptionType, Message, MessageFlags } from 'discord.js' import { ApplicationCommandOptionType, Message, MessageFlags } from 'discord.js'
import CommandError, { CommandErrorType } from '$/classes/CommandError'
import { ModerationCommand } from '../../classes/Command' import { ModerationCommand } from '../../classes/Command'
export default new ModerationCommand({ export default new ModerationCommand({

View File

@@ -1,7 +1,7 @@
import { MessageFlags } from 'discord.js'
import { ModerationCommand } from '$/classes/Command' import { ModerationCommand } from '$/classes/Command'
import { createSuccessEmbed } from '$/utils/discord/embeds' import { createSuccessEmbed } from '$/utils/discord/embeds'
import { cureNickname } from '$/utils/discord/moderation' import { cureNickname } from '$/utils/discord/moderation'
import { MessageFlags } from 'discord.js'
export default new ModerationCommand({ export default new ModerationCommand({
name: 'cure', name: 'cure',

View File

@@ -1,5 +1,4 @@
import { EmbedBuilder } from 'discord.js' import { EmbedBuilder } from 'discord.js'
import { ModerationCommand } from '$/classes/Command' import { ModerationCommand } from '$/classes/Command'
import CommandError, { CommandErrorType } from '$/classes/CommandError' import CommandError, { CommandErrorType } from '$/classes/CommandError'
import { applyCommonEmbedStyles } from '$/utils/discord/embeds' import { applyCommonEmbedStyles } from '$/utils/discord/embeds'

View File

@@ -1,9 +1,8 @@
import { createSuccessEmbed } from '$/utils/discord/embeds' import { ChannelType } from 'discord.js'
import { durationToString, parseDuration } from '$/utils/duration'
import { ModerationCommand } from '$/classes/Command' import { ModerationCommand } from '$/classes/Command'
import CommandError, { CommandErrorType } from '$/classes/CommandError' import CommandError, { CommandErrorType } from '$/classes/CommandError'
import { ChannelType } from 'discord.js' import { createSuccessEmbed } from '$/utils/discord/embeds'
import { durationToString, parseDuration } from '$/utils/duration'
export default new ModerationCommand({ export default new ModerationCommand({
name: 'slowmode', name: 'slowmode',

View File

@@ -1,10 +1,10 @@
import { and, eq } from 'drizzle-orm'
import { ModerationCommand } from '$/classes/Command' import { ModerationCommand } from '$/classes/Command'
import CommandError, { CommandErrorType } from '$/classes/CommandError' import CommandError, { CommandErrorType } from '$/classes/CommandError'
import { appliedPresets } from '$/database/schemas' import { appliedPresets } from '$/database/schemas'
import { createModerationActionEmbed } from '$/utils/discord/embeds' import { createModerationActionEmbed } from '$/utils/discord/embeds'
import { sendModerationReplyAndLogs } from '$/utils/discord/moderation' import { sendModerationReplyAndLogs } from '$/utils/discord/moderation'
import { removeRolePreset } from '$/utils/discord/rolePresets' import { removeRolePreset } from '$/utils/discord/rolePresets'
import { and, eq } from 'drizzle-orm'
export default new ModerationCommand({ export default new ModerationCommand({
name: 'unmute', name: 'unmute',

View File

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

View File

@@ -1,8 +1,8 @@
import { type APIStringSelectComponent, ComponentType, MessageFlags } from 'discord.js'
import Command from '$/classes/Command' import Command from '$/classes/Command'
import CommandError, { CommandErrorType } from '$/classes/CommandError' import CommandError, { CommandErrorType } from '$/classes/CommandError'
import type { ConfigMessageScanResponseLabelConfig } from 'config.schema'
import { type APIStringSelectComponent, ComponentType, MessageFlags } from 'discord.js'
import { config } from '../../../context' import { config } from '../../../context'
import type { ConfigMessageScanResponseLabelConfig } from 'config.schema'
const msRcConfig = config.messageScan?.humanCorrections?.allow const msRcConfig = config.messageScan?.humanCorrections?.allow
@@ -37,7 +37,10 @@ export default new Command({
components: [ components: [
{ {
custom_id: `tr_${trigger.targetMessage.channelId}_${trigger.targetId}`, custom_id: `tr_${trigger.targetMessage.channelId}_${trigger.targetId}`,
options: labels.map(label => ({ label, value: label })), options: [
...labels.map(label => ({ label, value: label })),
{ label: 'Out of scope', value: '', emoji: { name: '❌' } },
],
type: ComponentType.StringSelect, type: ComponentType.StringSelect,
} satisfies APIStringSelectComponent, } satisfies APIStringSelectComponent,
], ],

View File

@@ -1,16 +1,13 @@
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 { Client as DiscordClient, type Message, Partials } from 'discord.js' import { Client as DiscordClient, type Message, Options, Partials } from 'discord.js'
import { drizzle } from 'drizzle-orm/bun-sqlite' import { drizzle } from 'drizzle-orm/bun-sqlite'
import { existsSync, readdirSync, readFileSync } from 'fs'
import * as schemas from './database/schemas' import { join } from 'path'
import type { default as Command, CommandOptionsOptions, CommandType } from './classes/Command'
import { __getConfig, config } from './config' import { __getConfig, config } from './config'
import * as schemas from './database/schemas'
import type { default as Command, CommandOptionsOptions, CommandType } from './classes/Command'
export { config, __getConfig } export { config, __getConfig }
export const logger = createLogger({ export const logger = createLogger({
@@ -80,6 +77,51 @@ export const discord = {
parse: ['users'], parse: ['users'],
repliedUser: true, repliedUser: true,
}, },
sweepers: {
...Options.DefaultSweeperSettings,
messages: {
interval: 1_800, // Every 30m
lifetime: 3_600, // Remove messages older than 1h
},
},
makeCache: Options.cacheWithLimits({
...Options.DefaultMakeCacheSettings,
UserManager: 50,
GuildMemberManager: {
maxSize: 50,
// Always keep client guild member in cache
keepOverLimit: member => member.id === member.client.user.id,
},
ThreadManager: {
maxSize: 0,
// Always keep threads that are used for moderation logging
keepOverLimit: thread => config.moderation?.log?.thread === thread.id,
},
GuildMessageManager: {
maxSize: 0,
// Always keep messages posted by the client in cache
keepOverLimit: message => message.author.id === message.client.user.id,
},
// Unneeded cache
MessageManager: 0,
ReactionManager: 0,
VoiceStateManager: 0,
ThreadMemberManager: 0,
StageInstanceManager: 0,
ReactionUserManager: 0,
PresenceManager: 0,
GuildTextThreadManager: 0,
GuildStickerManager: 0,
DMMessageManager: 0,
GuildEmojiManager: 0,
GuildBanManager: 0,
GuildScheduledEventManager: 0,
EntitlementManager: 0,
AutoModerationRuleManager: 0,
GuildForumThreadManager: 0,
BaseGuildEmojiManager: 0,
GuildInviteManager: 0,
}),
partials: [Partials.Message, Partials.Reaction, Partials.GuildMember], partials: [Partials.Message, Partials.Reaction, Partials.GuildMember],
}), }),
commands: Object.fromEntries(Object.values(commands).map(cmd => [cmd.name, cmd])) as Record< commands: Object.fromEntries(Object.values(commands).map(cmd => [cmd.name, cmd])) as Record<

View File

@@ -1,5 +1,5 @@
import type { InferSelectModel } from 'drizzle-orm'
import { integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core' import { integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'
import type { InferSelectModel } from 'drizzle-orm'
export const responses = sqliteTable('responses', { export const responses = sqliteTable('responses', {
replyId: text('reply').primaryKey().notNull(), replyId: text('reply').primaryKey().notNull(),

View File

@@ -1,5 +1,5 @@
import { on, withContext } from '$utils/api/events'
import { DisconnectReason, HumanizedDisconnectReason } from '@revanced/bot-shared' import { DisconnectReason, HumanizedDisconnectReason } from '@revanced/bot-shared'
import { on, withContext } from '$utils/api/events'
withContext(on, 'disconnect', ({ api, config, logger }, reason, msg) => { withContext(on, 'disconnect', ({ api, config, logger }, reason, msg) => {
if (reason === DisconnectReason.PlannedDisconnect && api.intentionallyDisconnecting) return if (reason === DisconnectReason.PlannedDisconnect && api.intentionallyDisconnecting) return

View File

@@ -1,7 +1,7 @@
import { and, eq, gt } from 'drizzle-orm'
import { appliedPresets } from '$/database/schemas' import { appliedPresets } from '$/database/schemas'
import { on, withContext } from '$/utils/discord/events' import { on, withContext } from '$/utils/discord/events'
import { applyRolesUsingPreset } from '$/utils/discord/rolePresets' import { applyRolesUsingPreset } from '$/utils/discord/rolePresets'
import { and, eq, gt } from 'drizzle-orm'
withContext(on, 'guildMemberAdd', async ({ database }, member) => { withContext(on, 'guildMemberAdd', async ({ database }, member) => {
const applieds = await database.query.appliedPresets.findMany({ const applieds = await database.query.appliedPresets.findMany({

View File

@@ -1,7 +1,7 @@
import { MessageFlags } from 'discord.js'
import CommandError from '$/classes/CommandError' import CommandError from '$/classes/CommandError'
import { createStackTraceEmbed } from '$utils/discord/embeds' import { createStackTraceEmbed } from '$utils/discord/embeds'
import { on, withContext } from '$utils/discord/events' import { on, withContext } from '$utils/discord/events'
import { MessageFlags } from 'discord.js'
withContext(on, 'interactionCreate', async (context, interaction) => { withContext(on, 'interactionCreate', async (context, interaction) => {
if (!interaction.isChatInputCommand()) return if (!interaction.isChatInputCommand()) return

View File

@@ -1,7 +1,7 @@
import { MessageFlags } from 'discord.js'
import CommandError from '$/classes/CommandError' import CommandError from '$/classes/CommandError'
import { createStackTraceEmbed } from '$utils/discord/embeds' import { createStackTraceEmbed } from '$utils/discord/embeds'
import { on, withContext } from '$utils/discord/events' import { on, withContext } from '$utils/discord/events'
import { MessageFlags } from 'discord.js'
withContext(on, 'interactionCreate', async (context, interaction) => { withContext(on, 'interactionCreate', async (context, interaction) => {
if (!interaction.isContextMenuCommand()) return if (!interaction.isContextMenuCommand()) return

View File

@@ -1,8 +1,3 @@
import { responses } from '$/database/schemas'
import { handleUserResponseCorrection } from '$/utils/discord/messageScan'
import { createErrorEmbed, createStackTraceEmbed, createSuccessEmbed } from '$utils/discord/embeds'
import { on, withContext } from '$utils/discord/events'
import { import {
type ButtonInteraction, type ButtonInteraction,
MessageFlags, MessageFlags,
@@ -10,6 +5,10 @@ import {
type TextBasedChannel, type TextBasedChannel,
} from 'discord.js' } from 'discord.js'
import { eq } from 'drizzle-orm' import { eq } from 'drizzle-orm'
import { responses } from '$/database/schemas'
import { handleUserResponseCorrection } from '$/utils/discord/messageScan'
import { createErrorEmbed, createStackTraceEmbed, createSuccessEmbed } from '$utils/discord/embeds'
import { on, withContext } from '$utils/discord/events'
// No permission check required as it is already done when the user reacts to a bot response // No permission check required as it is already done when the user reacts to a bot response
withContext(on, 'interactionCreate', async (context, interaction) => { withContext(on, 'interactionCreate', async (context, interaction) => {
@@ -59,8 +58,8 @@ withContext(on, 'interactionCreate', async (context, interaction) => {
const editMessage = (content: string, description?: string) => const editMessage = (content: string, description?: string) =>
editInteractionMessage(interaction, msg.url, content, description) editInteractionMessage(interaction, msg.url, content, description)
const handleCorrection = (label: string) => const handleCorrection = (label?: string) =>
handleUserResponseCorrection(context, response, msg, label, interaction.user) handleUserResponseCorrection(context, response, msg, interaction.user, label)
if (response.correctedById) if (response.correctedById)
return await editMessage( return await editMessage(
@@ -83,7 +82,7 @@ withContext(on, 'interactionCreate', async (context, interaction) => {
await editMessage('Canceled', 'You canceled this interaction. 😞') await editMessage('Canceled', 'You canceled this interaction. 😞')
break break
case 'delete': case 'delete':
await handleCorrection(msConfig.humanCorrections.falsePositiveLabel) await handleCorrection()
await editMessage( await editMessage(
'Marked as false positive', 'Marked as false positive',
'The response has been deleted and marked as a false positive. Thank you for your feedback. 🎉', 'The response has been deleted and marked as a false positive. Thank you for your feedback. 🎉',

View File

@@ -1,6 +1,6 @@
import { MessageFlags, type TextBasedChannel } from 'discord.js'
import { createErrorEmbed, createStackTraceEmbed, createSuccessEmbed } from '$utils/discord/embeds' import { createErrorEmbed, createStackTraceEmbed, createSuccessEmbed } from '$utils/discord/embeds'
import { on, withContext } from '$utils/discord/events' import { on, withContext } from '$utils/discord/events'
import { MessageFlags, type TextBasedChannel } from 'discord.js'
withContext(on, 'interactionCreate', async (context, interaction) => { withContext(on, 'interactionCreate', async (context, interaction) => {
const { const {
@@ -30,7 +30,8 @@ withContext(on, 'interactionCreate', async (context, interaction) => {
flags: MessageFlags.Ephemeral, flags: MessageFlags.Ephemeral,
})) }))
const selectedLabel = interaction.values[0]! // If selectedLabel is empty, it means "out of scope", so we pass undefined
const selectedLabel = interaction.values[0] || undefined
await context.api.client.trainMessage(msg.content, selectedLabel) await context.api.client.trainMessage(msg.content, selectedLabel)
await interaction.reply({ await interaction.reply({
embeds: [ embeds: [

View File

@@ -1,7 +1,3 @@
import { MessageScanLabeledResponseReactions as Reactions } from '$/constants'
import { createErrorEmbed, createStackTraceEmbed, createSuccessEmbed } from '$/utils/discord/embeds'
import { on, withContext } from '$/utils/discord/events'
import { import {
ActionRowBuilder, ActionRowBuilder,
ButtonBuilder, ButtonBuilder,
@@ -9,12 +5,14 @@ import {
StringSelectMenuBuilder, StringSelectMenuBuilder,
StringSelectMenuOptionBuilder, StringSelectMenuOptionBuilder,
} from 'discord.js' } from 'discord.js'
import { eq } from 'drizzle-orm'
import type { ConfigMessageScanResponseLabelConfig } from '$/../config.schema' import { MessageScanLabeledResponseReactions as Reactions } from '$/constants'
import { responses } from '$/database/schemas' import { responses } from '$/database/schemas'
import { createErrorEmbed, createStackTraceEmbed, createSuccessEmbed } from '$/utils/discord/embeds'
import { on, withContext } from '$/utils/discord/events'
import { handleUserResponseCorrection } from '$/utils/discord/messageScan' import { handleUserResponseCorrection } from '$/utils/discord/messageScan'
import { isAdmin } from '$/utils/discord/permissions' import { isAdmin } from '$/utils/discord/permissions'
import { eq } from 'drizzle-orm' import type { ConfigMessageScanResponseLabelConfig } from '$/../config.schema'
const PossibleReactions = Object.values(Reactions) as string[] const PossibleReactions = Object.values(Reactions) as string[]
@@ -65,8 +63,8 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => {
logger.debug(`User ${user.id} is trying to correct the response ${rct.message.id}`) logger.debug(`User ${user.id} is trying to correct the response ${rct.message.id}`)
const handleCorrection = (label: string) => const handleCorrection = (label?: string) =>
handleUserResponseCorrection(context, response, reactionMessage, label, user) handleUserResponseCorrection(context, response, reactionMessage, user, label)
try { try {
if (reaction.emoji.name === Reactions.train) { if (reaction.emoji.name === Reactions.train) {
@@ -108,7 +106,7 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => {
.setCustomId(`${componentPrefix}_cancel`), .setCustomId(`${componentPrefix}_cancel`),
new ButtonBuilder() new ButtonBuilder()
.setEmoji(Reactions.delete) .setEmoji(Reactions.delete)
.setLabel('Delete (mark as false positive)') .setLabel('Delete (mark as out of scope)')
.setStyle(ButtonStyle.Danger) .setStyle(ButtonStyle.Danger)
.setCustomId(`${componentPrefix}_delete`), .setCustomId(`${componentPrefix}_delete`),
), ),
@@ -119,8 +117,8 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => {
components: rows, components: rows,
}) })
} else if (reaction.emoji.name === Reactions.delete) { } else if (reaction.emoji.name === Reactions.delete) {
await handleCorrection(msConfig.humanCorrections.falsePositiveLabel) await handleCorrection()
await user.send({ content: 'The response has been deleted and marked as a false positive.' }) await user.send({ content: 'The response has been deleted and marked as out of scope.' })
} }
} catch (e) { } catch (e) {
logger.error('Failed to correct response:', e) logger.error('Failed to correct response:', e)

View File

@@ -1,15 +1,14 @@
import { type Client, DiscordAPIError } from 'discord.js'
import { and, eq, lt } from 'drizzle-orm'
import { database, logger } from '$/context' import { database, logger } from '$/context'
import { appliedPresets } from '$/database/schemas' import { appliedPresets } from '$/database/schemas'
import { on, withContext } from '$/utils/discord/events' import { on, withContext } from '$/utils/discord/events'
import { removeRolePreset } from '$/utils/discord/rolePresets' import { removeRolePreset } from '$/utils/discord/rolePresets'
import { and, eq, lt } from 'drizzle-orm'
import { type Client, DiscordAPIError } from 'discord.js'
export default withContext(on, 'ready', async ({ config }, client) => { export default withContext(on, 'ready', async ({ config }, client) => {
if (config.rolePresets) { if (config.rolePresets) {
removeExpiredPresets(client) removeExpiredPresets(client)
setTimeout(() => removeExpiredPresets(client), config.rolePresets.checkExpiredEvery) setInterval(() => removeExpiredPresets(client), config.rolePresets.checkExpiredEvery)
} }
}) })

View File

@@ -1,5 +1,5 @@
import { api, discord, logger } from '$/context'
import { getMissingEnvironmentVariables } from '@revanced/bot-shared' import { getMissingEnvironmentVariables } from '@revanced/bot-shared'
import { api, discord, logger } from '$/context'
import './events/register' import './events/register'

View File

@@ -1,5 +1,5 @@
import type { ClientWebSocketEvents } from '@revanced/bot-api'
import * as context from '../../context' import * as context from '../../context'
import type { ClientWebSocketEvents } from '@revanced/bot-api'
const { client } = context.api const { client } = context.api

View File

@@ -1,5 +1,5 @@
import { DefaultEmbedColor, ReVancedLogoURL } from '$/constants'
import { type APIEmbed, EmbedBuilder, type EmbedField, type JSONEncodable, type User } from 'discord.js' import { type APIEmbed, EmbedBuilder, type EmbedField, type JSONEncodable, type User } from 'discord.js'
import { DefaultEmbedColor, ReVancedLogoURL } from '$/constants'
import type { ConfigMessageScanResponseMessage } from '../../../config.schema' import type { ConfigMessageScanResponseMessage } from '../../../config.schema'
export const createErrorEmbed = (title: string | null, description?: string) => export const createErrorEmbed = (title: string | null, description?: string) =>

View File

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

View File

@@ -1,7 +1,7 @@
import { config, logger } from '$/context'
import decancer from 'decancer' import decancer from 'decancer'
import type { CommandInteraction, EmbedBuilder, Guild, GuildMember, Message, User } from 'discord.js' import { config, logger } from '$/context'
import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from './embeds' import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from './embeds'
import type { CommandInteraction, EmbedBuilder, Guild, GuildMember, Message, User } from 'discord.js'
const PresetLogAction = { const PresetLogAction = {
apply: 'Applied role preset to', apply: 'Applied role preset to',

View File

@@ -1,7 +1,7 @@
import { and, eq } from 'drizzle-orm'
import { config, database } from '$/context' import { config, database } from '$/context'
import { appliedPresets } from '$/database/schemas' import { appliedPresets } from '$/database/schemas'
import type { GuildMember } from 'discord.js' import type { GuildMember } from 'discord.js'
import { and, eq } from 'drizzle-orm'
// TODO: Fix this type // TODO: Fix this type
type PresetKey = string type PresetKey = string

1026
bun.lock

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,11 @@
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"type": "module", "type": "module",
"author": "Palm <contact@palmdevs.me> (https://palmdevs.me)", "author": "Palm <contact@palmdevs.me> (https://palmdevs.me)",
"workspaces": ["packages/*", "apis/*", "bots/*"], "workspaces": [
"packages/*",
"apis/*",
"bots/*"
],
"scripts": { "scripts": {
"build:all": "turbo run build", "build:all": "turbo run build",
"build:packages": "turbo build --filter=\"./packages/*\"", "build:packages": "turbo build --filter=\"./packages/*\"",
@@ -27,25 +31,25 @@
"Palm <contact@palmdevs.me> (https://palmdevs.me)", "Palm <contact@palmdevs.me> (https://palmdevs.me)",
"ReVanced <nosupport@revanced.app> (https://revanced.app)" "ReVanced <nosupport@revanced.app> (https://revanced.app)"
], ],
"packageManager": "bun@1.1.20", "packageManager": "bun@1.2.17",
"devDependencies": { "devDependencies": {
"@anolilab/multi-semantic-release": "^1.1.10", "@anolilab/multi-semantic-release": "^2.0.2",
"@biomejs/biome": "^1.9.4", "@biomejs/biome": "^2.1.1",
"@codedependant/semantic-release-docker": "^5.1.0", "@codedependant/semantic-release-docker": "^5.1.1",
"@commitlint/cli": "^19.8.0", "@commitlint/cli": "^19.8.1",
"@commitlint/config-conventional": "^19.8.0", "@commitlint/config-conventional": "^19.8.1",
"@saithodev/semantic-release-backmerge": "^4.0.1", "@saithodev/semantic-release-backmerge": "^4.0.1",
"@semantic-release/changelog": "^6.0.3", "@semantic-release/changelog": "^6.0.3",
"@semantic-release/exec": "^6.0.3", "@semantic-release/exec": "^7.1.0",
"@semantic-release/git": "^10.0.1", "@semantic-release/git": "^10.0.1",
"@tsconfig/strictest": "^2.0.5", "@tsconfig/strictest": "^2.0.5",
"@types/bun": "^1.2.8", "@types/bun": "^1.2.18",
"conventional-changelog-conventionalcommits": "^7.0.2", "conventional-changelog-conventionalcommits": "^9.1.0",
"lefthook": "^1.11.6", "lefthook": "^1.12.2",
"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.2.3", "semantic-release": "^24.2.6",
"turbo": "^2.5.0", "turbo": "^2.5.4",
"typescript": "^5.8.2" "typescript": "^5.8.3"
}, },
"trustedDependencies": [ "trustedDependencies": [
"@biomejs/biome", "@biomejs/biome",
@@ -54,9 +58,6 @@
"lefthook" "lefthook"
], ],
"patchedDependencies": { "patchedDependencies": {
"@semantic-release/npm@12.0.1": "patches/@semantic-release%2Fnpm@12.0.1.patch", "@semantic-release/npm@12.0.2": "patches/@semantic-release%2Fnpm@12.0.2.patch"
"drizzle-kit@0.22.8": "patches/drizzle-kit@0.22.8.patch",
"decancer@3.2.4": "patches/decancer@3.2.4.patch",
"discord.js@14.18.0": "patches/discord.js@14.18.0.patch"
} }
} }

View File

@@ -28,7 +28,7 @@
"homepage": "https://github.com/revanced/revanced-bots#readme", "homepage": "https://github.com/revanced/revanced-bots#readme",
"dependencies": { "dependencies": {
"@revanced/bot-shared": "workspace:*", "@revanced/bot-shared": "workspace:*",
"ws": "^8.18.1" "ws": "^8.18.3"
}, },
"devDependencies": { "devDependencies": {
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",

View File

@@ -101,7 +101,7 @@ export default class Client {
return packet return packet
} }
async trainMessage(text: string, label: string) { async trainMessage(text: string, label?: string) {
this.#throwIfNotReady() this.#throwIfNotReady()
this.ws.send({ this.ws.send({

View File

@@ -1,16 +1,16 @@
import { EventEmitter } from 'events'
import { import {
type ClientOperation, type ClientOperation,
DisconnectReason, DisconnectReason,
type Packet,
ServerOperation,
deserializePacket, deserializePacket,
isServerPacket, isServerPacket,
type Packet,
ServerOperation,
serializePacket, serializePacket,
uncapitalize, uncapitalize,
} from '@revanced/bot-shared' } from '@revanced/bot-shared'
import type TypedEmitter from 'typed-emitter' import { EventEmitter } from 'events'
import { type RawData, WebSocket } from 'ws' import { type RawData, WebSocket } from 'ws'
import type TypedEmitter from 'typed-emitter'
/** /**
* The class that handles the WebSocket connection to the server. * The class that handles the WebSocket connection to the server.

View File

@@ -1,3 +1,3 @@
export { default as Client } from './Client'
export * from './Client' export * from './Client'
export { default as Client } from './Client'
export * from './ClientWebSocket' export * from './ClientWebSocket'

View File

@@ -30,9 +30,9 @@
}, },
"homepage": "https://github.com/revanced/revanced-bots#readme", "homepage": "https://github.com/revanced/revanced-bots#readme",
"dependencies": { "dependencies": {
"bson": "^6.10.3", "bson": "^6.10.4",
"chalk": "^5.4.1", "chalk": "^5.4.1",
"tracer": "^1.3.0", "tracer": "^1.3.0",
"valibot": "^0.30.0" "valibot": "^1.1.0"
} }
} }

View File

@@ -1,18 +1,16 @@
import { import {
url,
type AnySchema,
type BooleanSchema,
type NullSchema,
type ObjectSchema,
type Output,
array, array,
boolean, boolean,
custom,
enum_, enum_,
type InferOutput,
null_, null_,
object, object,
optional,
parse, parse,
special, pipe,
string, string,
url,
// merge // merge
} from 'valibot' } from 'valibot'
import DisconnectReason from '../constants/DisconnectReason' import DisconnectReason from '../constants/DisconnectReason'
@@ -21,7 +19,7 @@ import { ClientOperation, Operation, ServerOperation } from '../constants/Operat
/** /**
* Schema to validate packets * Schema to validate packets
*/ */
export const PacketSchema = special<Packet>(input => { export const PacketSchema = custom<Packet>(input => {
if ( if (
typeof input === 'object' && typeof input === 'object' &&
input && input &&
@@ -51,7 +49,7 @@ export const PacketDataSchemas = {
labels: array( labels: array(
object({ object({
name: string(), name: string(),
confidence: special<number>(input => typeof input === 'number' && input >= 0 && input <= 1), confidence: custom<number>(input => typeof input === 'number' && input >= 0 && input <= 1),
}), }),
), ),
}), }),
@@ -70,17 +68,13 @@ export const PacketDataSchemas = {
text: string(), text: string(),
}), }),
[ClientOperation.ParseImage]: object({ [ClientOperation.ParseImage]: object({
image_url: string([url()]), image_url: pipe(string(), url()),
}), }),
[ClientOperation.TrainMessage]: object({ [ClientOperation.TrainMessage]: object({
text: string(), text: string(),
label: string(), label: optional(string()),
}), }),
} as const satisfies Record< } as const
Operation,
// biome-ignore lint/suspicious/noExplicitAny: This is a schema, it's not possible to type it
ObjectSchema<any> | AnySchema | NullSchema | BooleanSchema
>
export type Packet<TOp extends Operation = Operation> = TOp extends ServerOperation export type Packet<TOp extends Operation = Operation> = TOp extends ServerOperation
? PacketWithSequenceNumber<TOp> ? PacketWithSequenceNumber<TOp>
@@ -88,6 +82,6 @@ export type Packet<TOp extends Operation = Operation> = TOp extends ServerOperat
type PacketWithSequenceNumber<TOp extends Operation> = { type PacketWithSequenceNumber<TOp extends Operation> = {
op: TOp op: TOp
d: Output<(typeof PacketDataSchemas)[TOp]> d: InferOutput<(typeof PacketDataSchemas)[TOp]>
s: number s: number
} }

View File

@@ -1,5 +1,5 @@
import { Chalk, supportsColor, supportsColorStderr } from 'chalk' import { Chalk, supportsColor, supportsColorStderr } from 'chalk'
import { type Tracer, colorConsole, console as uncoloredConsole } from 'tracer' import { colorConsole, type Tracer, console as uncoloredConsole } from 'tracer'
const chalk = new Chalk() const chalk = new Chalk()
const DefaultConfig = { const DefaultConfig = {

View File

@@ -1,7 +1,7 @@
import * as BSON from 'bson' import * as BSON from 'bson'
import { parse } from 'valibot' import { parse } from 'valibot'
import type { Operation } from '../constants'
import { type Packet, PacketSchema } from '../schemas' import { type Packet, PacketSchema } from '../schemas'
import type { Operation } from '../constants'
/** /**
* Compresses a packet into a buffer * Compresses a packet into a buffer

View File

@@ -1,12 +1,3 @@
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 diff --git a/lib/prepare.js b/lib/prepare.js
index 3e76bec44cf595a1b4141728336bed904d4d518d..4b25ca64879bbee2a600f2b23b738c86136ad9c6 100644 index 3e76bec44cf595a1b4141728336bed904d4d518d..4b25ca64879bbee2a600f2b23b738c86136ad9c6 100644
--- a/lib/prepare.js --- a/lib/prepare.js

View File

@@ -1,13 +0,0 @@
diff --git a/src/lib.js b/src/lib.js
index de45d7dbe82975b09eff3742d0718accae2107fc..0575daa03dfabdd5c96928458ff4270cb8f7188a 100644
--- a/src/lib.js
+++ b/src/lib.js
@@ -42,7 +42,7 @@ function isMusl() {
}
function getBinding(name) {
- const path = join(__dirname, '..', `decancer.${name}.node`)
+ const path = join(import.meta.dir, '..', `decancer.${name}.node`)
return require(existsSync(path) ? path : `@vierofernando/decancer-${name}`)
}

View File

@@ -1,17 +0,0 @@
# Make Message#reply work with { flags: MessageFlags.Ephemeral } in typings
# So our Command system doesn't break
diff --git a/typings/index.d.mts b/typings/index.d.mts
index 645b870..fa93158 100644
--- a/typings/index.d.mts
+++ b/typings/index.d.mts
@@ -6764,8 +6764,8 @@ export interface MessageCreateOptions extends BaseMessageOptionsWithPoll {
stickers?: readonly StickerResolvable[];
flags?:
| BitFieldResolvable<
- Extract<MessageFlagsString, 'SuppressEmbeds' | 'SuppressNotifications'>,
- MessageFlags.SuppressEmbeds | MessageFlags.SuppressNotifications
+ Extract<MessageFlagsString, 'SuppressEmbeds' | 'SuppressNotifications' | 'Ephemeral'>,
+ MessageFlags.SuppressEmbeds | MessageFlags.SuppressNotifications | MessageFlags.Ephemeral
>
| undefined;
}

View File

@@ -1,21 +0,0 @@
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>",