mirror of
https://github.com/ReVanced/revanced-bots.git
synced 2026-01-11 21:56:17 +00:00
Compare commits
13 Commits
@revanced/
...
@revanced/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e889d4991 | ||
|
|
6d463df586 | ||
|
|
2d8688bd4c | ||
|
|
bc9951c9b5 | ||
|
|
a7688fa9b9 | ||
|
|
5925d90209 | ||
|
|
5506518635 | ||
|
|
b9d08fff64 | ||
|
|
6875b32fd0 | ||
|
|
c36684091d | ||
|
|
f5939e2528 | ||
|
|
412e00317d | ||
|
|
8fe78e424e |
3
bots/discord/.gitignore
vendored
3
bots/discord/.gitignore
vendored
@@ -174,9 +174,6 @@ dist
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
|
||||
# Config
|
||||
config.ts
|
||||
|
||||
# DB
|
||||
*.db
|
||||
*.sqlite
|
||||
|
||||
@@ -1,3 +1,37 @@
|
||||
# @revanced/discord-bot [1.0.0-dev.18](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.17...@revanced/discord-bot@1.0.0-dev.18) (2024-08-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** set the `label` property correctly for message scans ([6d463df](https://github.com/revanced/revanced-helper/commit/6d463df586dee5dd8fe8d6cff1c5316f7809b32a))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.17](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.16...@revanced/discord-bot@1.0.0-dev.17) (2024-08-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord/commands/eval:** evaluate in current context ([5925d90](https://github.com/revanced/revanced-helper/commit/5925d902095acef5f6396ca03583a9cbb0862498))
|
||||
* **bots/discord:** send right response for after regexes ([a7688fa](https://github.com/revanced/revanced-helper/commit/a7688fa9b91919a87f74071b502cd0a87cd1c1fa))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bots/discord:** update example config file ([bc9951c](https://github.com/revanced/revanced-helper/commit/bc9951c9b5e007c3e1b3076aa0966ccf29bb18bc))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.16](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.15...@revanced/discord-bot@1.0.0-dev.16) (2024-08-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bots/discord:** open database as read-write ([c366840](https://github.com/revanced/revanced-helper/commit/c36684091dddf67880505dc459e4334a8a5492f4))
|
||||
* **bots/discord:** remove bad text channel checks ([f5939e2](https://github.com/revanced/revanced-helper/commit/f5939e25288fea2022fdeec9085ecb9ffada6111))
|
||||
* **bots/discord:** remove redundant footer for response embeds ([412e003](https://github.com/revanced/revanced-helper/commit/412e00317d1eaca23e9c1375e16f94a5f2fa8d86))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **bots/discord/commands:** add `reload` command ([6875b32](https://github.com/revanced/revanced-helper/commit/6875b32fd0c6ce3034da9dc6c704d425afb26f2e))
|
||||
|
||||
# @revanced/discord-bot [1.0.0-dev.15](https://github.com/revanced/revanced-helper/compare/@revanced/discord-bot@1.0.0-dev.14...@revanced/discord-bot@1.0.0-dev.15) (2024-07-31)
|
||||
|
||||
|
||||
|
||||
@@ -11,8 +11,21 @@ export default {
|
||||
GUILD_ID_HERE: ['ROLE_ID_HERE'],
|
||||
},
|
||||
},
|
||||
stickyMessages: {
|
||||
GUILD_ID_HERE: {
|
||||
CHANNEL_ID_HERE: {
|
||||
message: {
|
||||
content: 'This is a sticky message!',
|
||||
},
|
||||
timeout: 60000,
|
||||
forceSendTimeout: 300000,
|
||||
}
|
||||
}
|
||||
},
|
||||
moderation: {
|
||||
cure: {
|
||||
minimumNameLength: 3,
|
||||
removeCharactersRegex: /[^a-zA-Z0-9 \-_]/g,
|
||||
defaultName: 'Server member',
|
||||
},
|
||||
roles: ['ROLE_ID_HERE'],
|
||||
@@ -61,7 +74,11 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
allowedAttachmentMimeTypes: ['image/jpeg', 'image/png', 'image/webp'],
|
||||
attachments: {
|
||||
scanAttachments: true,
|
||||
allowedMimeTypes: ['image/jpeg', 'image/png', 'image/webp', 'text/plain'],
|
||||
maxTextFileSize: 512000
|
||||
},
|
||||
responses: [
|
||||
{
|
||||
filterOverride: {
|
||||
|
||||
@@ -26,7 +26,11 @@ export type Config = {
|
||||
messageScan?: {
|
||||
scanBots?: boolean
|
||||
scanOutsideGuilds?: boolean
|
||||
allowedAttachmentMimeTypes: string[]
|
||||
attachments?: {
|
||||
scanAttachments?: boolean
|
||||
allowedMimeTypes?: string[]
|
||||
maxTextFileSize?: number
|
||||
}
|
||||
filter?: {
|
||||
whitelist?: Filter
|
||||
blacklist?: Filter
|
||||
@@ -69,7 +73,7 @@ export type ConfigMessageScanResponse = {
|
||||
}
|
||||
filterOverride?: NonNullable<Config['messageScan']>['filter']
|
||||
response: ConfigMessageScanResponseMessage | null
|
||||
replyToReplied?: boolean
|
||||
respondToReply?: boolean
|
||||
}
|
||||
|
||||
export type ConfigMessageScanResponseLabelConfig = {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@revanced/discord-bot",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"version": "1.0.0-dev.15",
|
||||
"version": "1.0.0-dev.18",
|
||||
"description": "🤖 Discord bot assisting ReVanced",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { inspect } from 'util'
|
||||
import { runInNewContext } from 'vm'
|
||||
import { runInThisContext } from 'vm'
|
||||
import { ApplicationCommandOptionType } from 'discord.js'
|
||||
|
||||
import { AdminCommand } from '$/classes/Command'
|
||||
@@ -20,13 +20,13 @@ export default new AdminCommand({
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
async execute(context, trigger, { code, 'show-hidden': showHidden }) {
|
||||
async execute(_, trigger, { code, 'show-hidden': showHidden }) {
|
||||
await trigger.reply({
|
||||
ephemeral: true,
|
||||
embeds: [
|
||||
createSuccessEmbed('Evaluate', `\`\`\`js\n${code}\`\`\``).addFields({
|
||||
name: 'Result',
|
||||
value: `\`\`\`js\n${inspect(runInNewContext(code, { client: trigger.client, context, trigger }), { depth: 1, showHidden, getters: true, numericSeparator: true, showProxy: true })}\`\`\``,
|
||||
value: `\`\`\`js\n${inspect(runInThisContext(code), { depth: 1, showHidden, getters: true, numericSeparator: true, showProxy: true })}\`\`\``,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
17
bots/discord/src/commands/admin/reload.ts
Normal file
17
bots/discord/src/commands/admin/reload.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { AdminCommand } from '$/classes/Command'
|
||||
import { join, dirname } from 'path'
|
||||
|
||||
import type { Config } from 'config.schema'
|
||||
|
||||
export default new AdminCommand({
|
||||
name: 'reload',
|
||||
description: 'Reload configuration',
|
||||
async execute(context, trigger) {
|
||||
context.config = ((await import(join(dirname(Bun.main), '..', 'config.js'))) as { default: Config }).default
|
||||
|
||||
await trigger.reply({
|
||||
content: 'Reloaded configuration',
|
||||
ephemeral: true,
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EmbedBuilder, GuildChannel } from 'discord.js'
|
||||
import { EmbedBuilder } from 'discord.js'
|
||||
|
||||
import { ModerationCommand } from '$/classes/Command'
|
||||
import CommandError, { CommandErrorType } from '$/classes/CommandError'
|
||||
@@ -31,7 +31,7 @@ export default new ModerationCommand({
|
||||
throw new CommandError(CommandErrorType.MissingArgument, 'Either `amount` or `until` must be provided.')
|
||||
|
||||
const channel = interaction.channel!
|
||||
if (!(channel.isTextBased() && channel instanceof GuildChannel))
|
||||
if (!channel.isTextBased())
|
||||
throw new CommandError(CommandErrorType.InvalidChannel, 'The supplied channel is not a text channel.')
|
||||
|
||||
const embed = applyCommonEmbedStyles(
|
||||
|
||||
@@ -28,7 +28,7 @@ export default new ModerationCommand({
|
||||
if (!channel?.isTextBased() || channel.isDMBased())
|
||||
throw new CommandError(
|
||||
CommandErrorType.InvalidChannel,
|
||||
'The supplied channel is not a text channel or does not exist.',
|
||||
'The supplied channel is not a text channel.',
|
||||
)
|
||||
|
||||
if (Number.isNaN(duration)) throw new CommandError(CommandErrorType.InvalidDuration, 'Invalid duration.')
|
||||
|
||||
@@ -4,12 +4,6 @@ export const MessageScanLabeledResponseReactions = {
|
||||
delete: '❌',
|
||||
} as const
|
||||
|
||||
export const MessageScanHumanizedMode = {
|
||||
ocr: 'image recognition',
|
||||
nlp: 'text analysis',
|
||||
match: 'pattern matching',
|
||||
} as const
|
||||
|
||||
export const DefaultEmbedColor = '#4E98F0'
|
||||
export const ReVancedLogoURL =
|
||||
'https://media.discordapp.net/attachments/1095487869923119144/1115436493050224660/revanced-logo.png'
|
||||
|
||||
@@ -56,7 +56,7 @@ if (DatabasePath && !existsSync(DatabasePath)) {
|
||||
}
|
||||
}
|
||||
|
||||
const db = new Database(DatabasePath)
|
||||
const db = new Database(DatabasePath, { readwrite: true, create: true })
|
||||
if (dbSchemaFileName) db.run(readFileSync(join(DatabaseSchemaDir, dbSchemaFileName)).toString())
|
||||
|
||||
export const database = drizzle(db, {
|
||||
|
||||
@@ -24,7 +24,7 @@ withContext(on, 'messageCreate', async (context, msg) => {
|
||||
try {
|
||||
logger.debug(`Classifying message ${msg.id}`)
|
||||
|
||||
const { response, label, replyToReplied } = await getResponseFromText(
|
||||
const { response, label, respondToReply } = await getResponseFromText(
|
||||
msg.content,
|
||||
filteredResponses,
|
||||
context,
|
||||
@@ -33,13 +33,13 @@ withContext(on, 'messageCreate', async (context, msg) => {
|
||||
if (response) {
|
||||
logger.debug('Response found')
|
||||
|
||||
const toReply = replyToReplied ? (msg.reference?.messageId ? await msg.fetchReference() : msg) : msg
|
||||
const toReply = respondToReply ? (msg.reference?.messageId ? await msg.fetchReference() : msg) : msg
|
||||
const reply = await toReply.reply({
|
||||
...response,
|
||||
embeds: response.embeds?.map(it => createMessageScanResponseEmbed(it, label ? 'nlp' : 'match')),
|
||||
embeds: response.embeds?.map(createMessageScanResponseEmbed),
|
||||
})
|
||||
|
||||
if (label)
|
||||
if (label) {
|
||||
db.insert(responses).values({
|
||||
replyId: reply.id,
|
||||
channelId: reply.channel.id,
|
||||
@@ -49,7 +49,6 @@ withContext(on, 'messageCreate', async (context, msg) => {
|
||||
content: msg.content,
|
||||
})
|
||||
|
||||
if (label) {
|
||||
for (const reaction of Object.values(MessageScanLabeledResponseReactions)) {
|
||||
await reply.react(reaction)
|
||||
}
|
||||
@@ -60,11 +59,22 @@ withContext(on, 'messageCreate', async (context, msg) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.attachments.size > 0) {
|
||||
if (msg.attachments.size > 0 && config.attachments?.scanAttachments) {
|
||||
logger.debug(`Classifying message attachments for ${msg.id}`)
|
||||
|
||||
for (const attachment of msg.attachments.values()) {
|
||||
if (attachment.contentType && !config.allowedAttachmentMimeTypes.includes(attachment.contentType)) continue
|
||||
if (
|
||||
config.attachments.allowedMimeTypes &&
|
||||
!config.attachments.allowedMimeTypes.includes(attachment.contentType!)
|
||||
) {
|
||||
logger.debug(`Disallowed MIME type for attachment: ${attachment.url}, ${attachment.contentType}`)
|
||||
continue
|
||||
}
|
||||
|
||||
if (attachment.contentType?.startsWith('text/') && attachment.size > (config.attachments.maxTextFileSize ?? 512 * 1000)) {
|
||||
logger.debug(`Attachment ${attachment.url} is too large be to scanned, size is ${attachment.size}`)
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
const { text: content } = await api.client.parseImage(attachment.url)
|
||||
@@ -74,7 +84,7 @@ withContext(on, 'messageCreate', async (context, msg) => {
|
||||
logger.debug(`Response found for attachment: ${attachment.url}`)
|
||||
await msg.reply({
|
||||
...response,
|
||||
embeds: response.embeds?.map(it => createMessageScanResponseEmbed(it, 'ocr')),
|
||||
embeds: response.embeds?.map(createMessageScanResponseEmbed),
|
||||
})
|
||||
|
||||
break
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DefaultEmbedColor, MessageScanHumanizedMode, ReVancedLogoURL } from '$/constants'
|
||||
import { DefaultEmbedColor, ReVancedLogoURL } from '$/constants'
|
||||
import { type APIEmbed, EmbedBuilder, type EmbedField, type JSONEncodable, type User } from 'discord.js'
|
||||
import type { ConfigMessageScanResponseMessage } from '../../../config.schema'
|
||||
|
||||
@@ -25,12 +25,7 @@ export const createSuccessEmbed = (title: string | null, description?: string) =
|
||||
|
||||
export const createMessageScanResponseEmbed = (
|
||||
response: NonNullable<ConfigMessageScanResponseMessage['embeds']>[number],
|
||||
mode: 'ocr' | 'nlp' | 'match',
|
||||
) =>
|
||||
applyCommonEmbedStyles(response, true, true, true).setFooter({
|
||||
text: `ReVanced • Via ${MessageScanHumanizedMode[mode]}`,
|
||||
iconURL: ReVancedLogoURL,
|
||||
})
|
||||
) => applyCommonEmbedStyles(response, true, true, true)
|
||||
|
||||
export const createModerationActionEmbed = (
|
||||
action: string,
|
||||
|
||||
@@ -64,23 +64,24 @@ export const getResponseFromText = async (
|
||||
const matchedLabel = scan.labels[0]!
|
||||
logger.debug(`Message matched label with confidence: ${matchedLabel.name}, ${matchedLabel.confidence}`)
|
||||
|
||||
let triggerConfig: ConfigMessageScanResponseLabelConfig | undefined
|
||||
const labelConfig = responses.find(x => {
|
||||
let trigger: ConfigMessageScanResponseLabelConfig | undefined
|
||||
const response = responses.find(x => {
|
||||
const config = x.triggers.text!.find(
|
||||
(x): x is ConfigMessageScanResponseLabelConfig => 'label' in x && x.label === matchedLabel.name,
|
||||
)
|
||||
if (config) triggerConfig = config
|
||||
if (config) trigger = config
|
||||
return config
|
||||
})
|
||||
|
||||
if (!labelConfig) {
|
||||
logger.warn(`No label config found for label ${matchedLabel.name}`)
|
||||
if (!response) {
|
||||
logger.warn(`No response config found for label ${matchedLabel.name}`)
|
||||
// This returns the default value set in line 17, which means no response matched
|
||||
return responseConfig
|
||||
}
|
||||
|
||||
if (matchedLabel.confidence >= triggerConfig!.threshold) {
|
||||
if (matchedLabel.confidence >= trigger!.threshold) {
|
||||
logger.debug('Label confidence is enough')
|
||||
responseConfig = labelConfig
|
||||
responseConfig = { ...responseConfig, ...response, label: trigger!.label }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,8 +95,8 @@ export const getResponseFromText = async (
|
||||
} = responses[i]!
|
||||
const firstLabelIndex = firstLabelIndexes[i] ?? -1
|
||||
|
||||
for (let i = firstLabelIndex + 1; i < textTriggers!.length; i++) {
|
||||
const trigger = textTriggers![i]!
|
||||
for (let j = firstLabelIndex + 1; j < textTriggers!.length; j++) {
|
||||
const trigger = textTriggers![j]!
|
||||
|
||||
if (trigger instanceof RegExp) {
|
||||
if (trigger.test(content)) {
|
||||
@@ -158,7 +159,7 @@ export const handleUserResponseCorrection = async (
|
||||
|
||||
await reply.edit({
|
||||
...correctLabelResponse.response,
|
||||
embeds: correctLabelResponse.response.embeds?.map(it => createMessageScanResponseEmbed(it, 'nlp')),
|
||||
embeds: correctLabelResponse.response.embeds?.map(createMessageScanResponseEmbed),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ export const getLogChannel = async (guild: Guild) => {
|
||||
|
||||
try {
|
||||
const channel = await guild.channels.fetch(logConfig.thread ?? logConfig.channel)
|
||||
if (!channel || !channel.isTextBased())
|
||||
if (!channel?.isTextBased())
|
||||
return void logger.warn('The moderation log channel does not exist, skipping logging')
|
||||
|
||||
return channel
|
||||
|
||||
Reference in New Issue
Block a user