Compare commits

...

10 Commits

Author SHA1 Message Date
semantic-release-bot
9897f244e0 chore(release): 1.0.0-dev.21 [skip ci]
# @revanced/discord-bot [1.0.0-dev.21](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.20...@revanced/discord-bot@1.0.0-dev.21) (2024-08-04)

### Bug Fixes

* **bots/discord:** correct sticky messages logic ([de8bef6](de8bef6520))
* **bots/discord:** make `/eval` work ([eaa25f2](eaa25f2eb5))
* **bots/discord:** some configuration values not applying after running `/reload` ([a976dd2](a976dd2acc))
2024-08-04 17:45:52 +00:00
PalmDevs
eaa25f2eb5 fix(bots/discord): make /eval work 2024-08-05 00:44:35 +07:00
PalmDevs
a976dd2acc fix(bots/discord): some configuration values not applying after running /reload 2024-08-05 00:44:34 +07:00
PalmDevs
c567ef25c6 feat(packages/api): allow setting new options for ClientWebsocketManager 2024-08-05 00:44:33 +07:00
PalmDevs
de8bef6520 fix(bots/discord): correct sticky messages logic 2024-08-05 00:44:32 +07:00
semantic-release-bot
98dea81eeb chore(release): 1.0.0-dev.20 [skip ci]
# @revanced/discord-bot [1.0.0-dev.20](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.19...@revanced/discord-bot@1.0.0-dev.20) (2024-08-03)

### Bug Fixes

* **bots/discord:** await when putting entries into db ([4da6175](4da6175cf5))
2024-08-03 19:53:58 +00:00
semantic-release-bot
9f3295cc0f chore(release): 1.0.0-dev.9 [skip ci]
# @revanced/bot-websocket-api [1.0.0-dev.9](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.8...@revanced/bot-websocket-api@1.0.0-dev.9) (2024-08-03)

### Features

* **apis/websocket:** return `true` for data on a `TrainedMessage` packet ([65add4d](65add4dfee))
2024-08-03 19:53:18 +00:00
PalmDevs
4da6175cf5 fix(bots/discord): await when putting entries into db 2024-08-04 02:52:07 +07:00
PalmDevs
d90ad5c955 fix(packages/api): handle close event as a disconnect 2024-08-04 02:52:06 +07:00
PalmDevs
65add4dfee feat(apis/websocket): return true for data on a TrainedMessage packet 2024-08-04 02:52:04 +07:00
14 changed files with 149 additions and 57 deletions

View File

@@ -1,3 +1,10 @@
# @revanced/bot-websocket-api [1.0.0-dev.9](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.8...@revanced/bot-websocket-api@1.0.0-dev.9) (2024-08-03)
### Features
* **apis/websocket:** return `true` for data on a `TrainedMessage` packet ([65add4d](https://github.com/revanced/revanced-helper/commit/65add4dfeed2fa067c2c8e2377f7d01d505ade54))
# @revanced/bot-websocket-api [1.0.0-dev.8](https://github.com/revanced/revanced-helper/compare/@revanced/bot-websocket-api@1.0.0-dev.7...@revanced/bot-websocket-api@1.0.0-dev.8) (2024-07-31)

View File

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

View File

@@ -18,7 +18,7 @@ const trainMessageEventHandler: EventHandler<ClientOperation.TrainMessage> = asy
client.send(
{
op: ServerOperation.TrainedMessage,
d: null,
d: true,
},
nextSeq,
)

View File

@@ -1,3 +1,19 @@
# @revanced/discord-bot [1.0.0-dev.21](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.20...@revanced/discord-bot@1.0.0-dev.21) (2024-08-04)
### Bug Fixes
* **bots/discord:** correct sticky messages logic ([de8bef6](https://github.com/revanced/revanced-helper/commit/de8bef6520d53a1299f0478458320a7eb75c5e1d))
* **bots/discord:** make `/eval` work ([eaa25f2](https://github.com/revanced/revanced-helper/commit/eaa25f2eb58a9e2d25bb98633ad668485e099714))
* **bots/discord:** some configuration values not applying after running `/reload` ([a976dd2](https://github.com/revanced/revanced-helper/commit/a976dd2accc4b74914651245acde0979c30c92f5))
# @revanced/discord-bot [1.0.0-dev.20](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.19...@revanced/discord-bot@1.0.0-dev.20) (2024-08-03)
### Bug Fixes
* **bots/discord:** await when putting entries into db ([4da6175](https://github.com/revanced/revanced-helper/commit/4da6175cf58b1fa6144bdc71ec806766d32c1025))
# @revanced/discord-bot [1.0.0-dev.19](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.18...@revanced/discord-bot@1.0.0-dev.19) (2024-08-03)

View File

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

View File

@@ -1,5 +1,4 @@
import { inspect } from 'util'
import { runInThisContext } from 'vm'
import { ApplicationCommandOptionType } from 'discord.js'
import { AdminCommand } from '$/classes/Command'
@@ -26,7 +25,14 @@ export default new AdminCommand({
embeds: [
createSuccessEmbed('Evaluate', `\`\`\`js\n${code}\`\`\``).addFields({
name: 'Result',
value: `\`\`\`js\n${inspect(runInThisContext(code), { depth: 1, showHidden, getters: true, numericSeparator: true, showProxy: true })}\`\`\``,
// biome-ignore lint/security/noGlobalEval: This is fine as it's an admin command
value: `\`\`\`js\n${inspect(eval(code), {
depth: 1,
showHidden,
getters: true,
numericSeparator: true,
showProxy: true,
})}\`\`\``,
}),
],
})

View File

@@ -7,11 +7,29 @@ export default new AdminCommand({
name: 'reload',
description: 'Reload configuration',
async execute(context, trigger) {
const { api, logger, discord } = context
context.config = ((await import(join(dirname(Bun.main), '..', 'config.js'))) as { default: Config }).default
await trigger.reply({
content: 'Reloaded configuration',
ephemeral: true,
})
if ('deferReply' in trigger) await trigger.deferReply({ ephemeral: true })
logger.info('Reinitializing API client to reload configuration...')
await api.client.ws.setOptions(
{
url: context.config.api.url,
},
false,
)
api.intentionallyDisconnecting = true
api.client.disconnect(true)
api.disconnectCount = 0
api.intentionallyDisconnecting = false
await api.client.connect()
logger.info('Reinitializing Discord client to reload configuration...')
await discord.client.destroy()
await discord.client.login()
// @ts-expect-error: TypeScript dum
await trigger[('deferReply' in trigger ? 'editReply' : 'reply')]({ content: 'Reloaded configuration' })
},
})

View File

@@ -90,13 +90,20 @@ export const discord = {
Record<
string,
{
forceSendTimerActive?: boolean
timeoutMs: number
forceSendMs?: number
/**
* Chat is active, so force send timer is also active
*/
forceTimerActive: boolean
/**
* There was a message sent, so the timer is active
*/
timerActive: boolean
timerMs: number
forceTimerMs?: number
send: (forced?: boolean) => Promise<void>
currentMessage?: Message<true>
interval?: NodeJS.Timeout
forceSendInterval?: NodeJS.Timeout
timer?: NodeJS.Timeout
forceTimer?: NodeJS.Timeout
}
>
>,

View File

@@ -40,7 +40,7 @@ withContext(on, 'messageCreate', async (context, msg) => {
})
if (label) {
db.insert(responses).values({
await db.insert(responses).values({
replyId: reply.id,
channelId: reply.channel.id,
guildId: reply.guild!.id,

View File

@@ -7,24 +7,31 @@ withContext(on, 'messageCreate', async ({ discord, logger }, msg) => {
const store = discord.stickyMessages[msg.guildId]?.[msg.channelId]
if (!store) return
if (!store.interval) store.interval = setTimeout(store.send, store.timeoutMs) as NodeJS.Timeout
// If there isn't a timer, start it up
store.timerActive = true
if (!store.timer) store.timer = setTimeout(store.send, store.timerMs) as NodeJS.Timeout
else {
store.interval.refresh()
// If there is a timer, but it isn't active, restart it
if (!store.timerActive) store.timer.refresh()
// If there is a timer and it is active, but the force timer isn't active...
else if (!store.forceTimerActive && store.forceTimerMs) {
logger.debug(`Channel ${msg.channelId} in guild ${msg.guildId} is active, starting force send timer and clearing existing timer`)
if (!store.forceSendTimerActive && store.forceSendMs) {
logger.debug(`Channel ${msg.channelId} in guild ${msg.guildId} is active, starting force send timer`)
// Clear the timer
clearTimeout(store.timer)
store.timerActive = false
store.forceTimerActive = true
store.forceSendTimerActive = true
if (!store.forceSendInterval)
store.forceSendInterval = setTimeout(
// (Re)start the force timer
if (!store.forceTimer)
store.forceTimer = setTimeout(
() =>
store.send(true).then(() => {
store.forceSendTimerActive = false
store.forceTimerActive = false
}),
store.forceSendMs,
store.forceTimerMs,
) as NodeJS.Timeout
else store.forceSendInterval.refresh()
else store.forceTimer.refresh()
}
}
})

View File

@@ -20,7 +20,8 @@ const PossibleReactions = Object.values(Reactions) as string[]
withContext(on, 'messageReactionAdd', async (context, rct, user) => {
if (user.bot) return
await rct.users.remove(user.id)
const { database: db, logger, config } = context
const { messageScan: msConfig } = config
@@ -35,10 +36,7 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => {
if (!isAdmin(reactionMessage.member || reactionMessage.author)) {
// User is in guild, and config has member requirements
if (
reactionMessage.inGuild() &&
(msConfig.humanCorrections.allow?.members || msConfig.humanCorrections.allow?.users)
) {
if (reactionMessage.inGuild() && msConfig.humanCorrections.allow) {
const {
allow: { users: allowedUsers, members: allowedMembers },
} = msConfig.humanCorrections
@@ -54,20 +52,19 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => {
)
)
return
} else if (allowedUsers) {
if (!allowedUsers.includes(user.id)) return
} else {
return void logger.warn(
'No member or user requirements set for human corrections, all requests will be ignored',
)
}
}
} else if (!allowedUsers?.includes(user.id)) return
} else
return void logger.warn(
'No member or user requirements set for human corrections, all requests will be ignored',
)
}
// Sanity check
const response = await db.query.responses.findFirst({ where: eq(responses.replyId, rct.message.id) })
if (!response || response.correctedById) return
logger.debug(`User ${user.id} is trying to correct the response ${rct.message.id}`)
const handleCorrection = (label: string) =>
handleUserResponseCorrection(context, response, reactionMessage, label, user)

View File

@@ -14,11 +14,16 @@ export default withContext(on, 'ready', async ({ config, discord, logger }, clie
if (config.stickyMessages)
for (const [guildId, channels] of Object.entries(config.stickyMessages)) {
const guild = await client.guilds.fetch(guildId)
// In case of configuration refresh, this will not be nullable
const oldStore = discord.stickyMessages[guildId]
discord.stickyMessages[guildId] = {}
for (const [channelId, { message, timeout, forceSendTimeout }] of Object.entries(channels)) {
const channel = await guild.channels.fetch(channelId)
if (!channel?.isTextBased()) return
if (!channel?.isTextBased())
return void logger.warn(
`Channel ${channelId} in guild ${guildId} is not a text channel, sticky messages will not be sent`,
)
const send = async (forced = false) => {
try {
@@ -33,17 +38,19 @@ export default withContext(on, 'ready', async ({ config, discord, logger }, clie
await store.currentMessage?.delete().catch()
store.currentMessage = msg
if (!forced) {
clearTimeout(store.forceSendInterval)
// Clear any remaining timers
clearTimeout(store.timer)
clearTimeout(store.forceTimer)
store.forceTimerActive = store.timerActive = false
if (!forced)
logger.debug(
`Timeout ended for sticky message in channel ${channelId} in guild ${guildId}, channel is inactive`,
)
} else {
clearTimeout(store.interval)
else
logger.debug(
`Forced send timeout for sticky message in channel ${channelId} in guild ${guildId} ended, channel is too active`,
)
}
logger.debug(`Sent sticky message to channel ${channelId} in guild ${guildId}`)
} catch (e) {
@@ -53,13 +60,20 @@ export default withContext(on, 'ready', async ({ config, discord, logger }, clie
)
}
}
// Set up the store
discord.stickyMessages[guildId]![channelId] = {
forceSendMs: forceSendTimeout,
timeoutMs: timeout,
forceTimerActive: false,
timerActive: false,
forceTimerMs: forceSendTimeout,
timerMs: timeout,
send,
forceSendTimerActive: false,
// If the store exists before the configuration refresh, take its current message
currentMessage: oldStore?.[channelId]?.currentMessage
}
// Send a new sticky message immediately, as well as deleting the old/outdated message, if it exists
await send()
}
}

View File

@@ -17,7 +17,7 @@ import { type RawData, WebSocket } from 'ws'
* This is the only relevant class for the time being. But in the future, there may be more classes to handle different protocols of the API.
*/
export class ClientWebSocketManager {
readonly url: string
url: string
timeout: number
connecting = false
@@ -33,6 +33,21 @@ export class ClientWebSocketManager {
this.timeout = options.timeout ?? 10000
}
/**
* Sets the URL to connect to
*
* **Requires a reconnect to take effect**
*/
async setOptions({ url, timeout }: Partial<ClientWebSocketManagerOptions>, autoReconnect = true) {
if (url) this.url = url
this.timeout = timeout ?? this.timeout
if (autoReconnect) {
this.disconnect(true)
await this.connect()
}
}
/**
* Connects to the WebSocket API
* @returns A promise that resolves when the client is ready
@@ -77,9 +92,13 @@ export class ClientWebSocketManager {
} catch (e) {
rj(e)
}
}).finally(() => {
this.connecting = false
})
.then(() => {
this.#socket.on('close', (code, reason) => this._handleDisconnect(code, reason.toString()))
})
.finally(() => {
this.connecting = false
})
}
/**

View File

@@ -1,9 +1,9 @@
import {
url,
type AnySchema,
type NullSchema,
type ObjectSchema,
type Output,
type BooleanSchema,
array,
enum_,
null_,
@@ -11,6 +11,8 @@ import {
parse,
special,
string,
boolean,
url,
// merge
} from 'valibot'
import DisconnectReason from '../constants/DisconnectReason'
@@ -26,8 +28,7 @@ export const PacketSchema = special<Packet>(input => {
'op' in input &&
typeof input.op === 'number' &&
input.op in Operation &&
'd' in input &&
typeof input.d === 'object'
'd' in input
) {
if (input.op in ServerOperation && !('s' in input && typeof input.s === 'number')) return false
@@ -62,7 +63,7 @@ export const PacketDataSchemas = {
[ServerOperation.Disconnect]: object({
reason: enum_(DisconnectReason),
}),
[ServerOperation.TrainedMessage]: null_(),
[ServerOperation.TrainedMessage]: boolean(),
[ServerOperation.TrainMessageFailed]: null_(),
[ClientOperation.ParseText]: object({
@@ -78,7 +79,7 @@ export const PacketDataSchemas = {
} as const satisfies Record<
Operation,
// biome-ignore lint/suspicious/noExplicitAny: This is a schema, it's not possible to type it
ObjectSchema<any> | AnySchema | NullSchema
ObjectSchema<any> | AnySchema | NullSchema | BooleanSchema
>
export type Packet<TOp extends Operation = Operation> = TOp extends ServerOperation