feat(bots/discord): blacklist and whitelist for filters

This commit is contained in:
PalmDevs
2024-07-28 20:10:47 +07:00
parent e748a4da92
commit cdb6001955
8 changed files with 58 additions and 43 deletions

View File

@@ -37,11 +37,19 @@ export default {
checkExpiredEvery: 3600,
},
messageScan: {
scanBots: false,
scanOutsideGuilds: false,
filter: {
channels: ['CHANNEL_ID_HERE'],
roles: ['ROLE_ID_HERE'],
users: ['USER_ID_HERE'],
whitelist: false,
whitelist: {
channels: ['CHANNEL_ID_HERE'],
roles: ['ROLE_ID_HERE'],
users: ['USER_ID_HERE'],
},
blacklist: {
channels: ['CHANNEL_ID_HERE'],
roles: ['ROLE_ID_HERE'],
users: ['USER_ID_HERE'],
},
},
humanCorrections: {
falsePositiveLabel: 'false_positive',
@@ -55,6 +63,18 @@ export default {
allowedAttachmentMimeTypes: ['image/jpeg', 'image/png', 'image/webp'],
responses: [
{
filterOverride: {
whitelist: {
channels: ['CHANNEL_ID_HERE'],
roles: ['ROLE_ID_HERE'],
users: ['USER_ID_HERE'],
},
blacklist: {
channels: ['CHANNEL_ID_HERE'],
roles: ['ROLE_ID_HERE'],
users: ['USER_ID_HERE'],
},
},
triggers: {
text: [/^regexp?$/, { label: 'label', threshold: 0.85 }],
},

View File

@@ -20,12 +20,12 @@ export type Config = {
guilds: Record<string, Record<string, RolePresetConfig>>
}
messageScan?: {
scanBots?: boolean
scanOutsideGuilds?: boolean
allowedAttachmentMimeTypes: string[]
filter: {
roles?: string[]
users?: string[]
channels?: string[]
whitelist: boolean
filter?: {
whitelist?: Filter
blacklist?: Filter
}
humanCorrections: {
falsePositiveLabel: string
@@ -73,4 +73,10 @@ export type ConfigMessageScanResponseLabelConfig = {
threshold: number
}
export type Filter = {
roles?: string[]
users?: string[]
channels?: string[]
}
export type ConfigMessageScanResponseMessage = BaseMessageOptions

View File

@@ -3,7 +3,7 @@ import { existsSync, readFileSync, readdirSync } from 'fs'
import { join } from 'path'
import { Client as APIClient } from '@revanced/bot-api'
import { createLogger } from '@revanced/bot-shared'
import { ActivityType, Client as DiscordClient, Partials } from 'discord.js'
import { Client as DiscordClient, Partials } from 'discord.js'
import { drizzle } from 'drizzle-orm/bun-sqlite'
// Export config first, as commands require them
@@ -13,7 +13,7 @@ export { config }
import * as commands from './commands'
import * as schemas from './database/schemas'
import type { Command } from './commands/types'
import type Command from './classes/Command'
export const logger = createLogger({
level: config.logLevel === 'none' ? Number.MAX_SAFE_INTEGER : config.logLevel,
@@ -27,7 +27,7 @@ export const api = {
},
},
}),
isStopping: false,
intentionallyDisconnecting: false,
disconnectCount: 0,
}
@@ -80,16 +80,8 @@ export const discord = {
repliedUser: true,
},
partials: [Partials.Message, Partials.Reaction],
presence: {
activities: [
{
type: ActivityType.Watching,
name: 'cat videos',
},
],
},
}),
commands: Object.fromEntries(Object.values<Command>(commands).map(cmd => [cmd.data.name, cmd])) as Record<
commands: Object.fromEntries(Object.values<Command>(commands).map(cmd => [cmd.name, cmd])) as Record<
string,
Command
>,

View File

@@ -2,7 +2,7 @@ import { on, withContext } from '$utils/api/events'
import { DisconnectReason, HumanizedDisconnectReason } from '@revanced/bot-shared'
withContext(on, 'disconnect', ({ api, config, logger }, reason, msg) => {
if (reason === DisconnectReason.PlannedDisconnect && api.isStopping) return
if (reason === DisconnectReason.PlannedDisconnect && api.intentionallyDisconnecting) return
const ws = api.client.ws
if (!ws.disconnected) ws.disconnect()

View File

@@ -1,6 +1,6 @@
import { MessageScanLabeledResponseReactions } from '$/constants'
import { responses } from '$/database/schemas'
import { getResponseFromText, shouldScanMessage } from '$/utils/discord/messageScan'
import { getResponseFromText, messageMatchesFilter } from '$/utils/discord/messageScan'
import { createMessageScanResponseEmbed } from '$utils/discord/embeds'
import { on, withContext } from '$utils/discord/events'
@@ -13,8 +13,11 @@ withContext(on, 'messageCreate', async (context, msg) => {
} = context
if (!config || !config.responses) return
if (msg.author.bot && !config.scanBots)
if (!msg.inGuild() && !config.scanOutsideGuilds) return
if (msg.inGuild() && msg.member?.partial) await msg.member.fetch()
const filteredResponses = config.responses.filter(x => shouldScanMessage(msg, x.filterOverride ?? config.filter))
const filteredResponses = config.responses.filter(x => messageMatchesFilter(msg, x.filterOverride ?? config.filter))
if (!filteredResponses.length) return
if (msg.content.length) {

View File

@@ -33,7 +33,7 @@ withContext(on, 'messageReactionAdd', async (context, rct, user) => {
if (reactionMessage.author.id !== reaction.client.user!.id) return
if (!PossibleReactions.includes(reaction.emoji.name!)) return
if (!isAdmin(reactionMessage.member || reactionMessage.author, config.admin)) {
if (!isAdmin(reactionMessage.member || reactionMessage.author)) {
// User is in guild, and config has member requirements
if (
reactionMessage.inGuild() &&

View File

@@ -113,24 +113,18 @@ export const getResponseFromText = async (
return responseConfig
}
export const shouldScanMessage = (
export const messageMatchesFilter = (
message: Message,
filter: NonNullable<Config['messageScan']>['filter'],
): message is Message<true> => {
if (message.author.bot) return false
if (!message.guild) return false
) => {
if (!filter) return true
const filters = [
filter.users?.includes(message.author.id),
message.member?.roles.cache.some(x => filter.roles?.includes(x.id)),
filter.channels?.includes(message.channel.id),
]
const memberRoles = new Set(message.member?.roles.cache.keys())
const blFilter = filter.blacklist
if (filter.whitelist && filters.every(x => !x)) return false
if (!filter.whitelist && filters.some(x => x)) return false
return true
// If matches blacklist, will return false
// Any other case, will return true
return !(blFilter && (blFilter.channels?.includes(message.channelId) || blFilter.roles?.some(role => memberRoles.has(role)) || blFilter.users?.includes(message.author.id)))
}
export const handleUserResponseCorrection = async (

View File

@@ -1,11 +1,11 @@
import { GuildMember, type User } from 'discord.js'
import type { Config } from 'config.schema'
import config from '../../../config'
export const isAdmin = (userOrMember: User | GuildMember, adminConfig: Config['admin']) => {
return adminConfig?.users?.includes(userOrMember.id) || (userOrMember instanceof GuildMember && isMemberAdmin(userOrMember, adminConfig))
export const isAdmin = (userOrMember: User | GuildMember) => {
return config.admin?.users?.includes(userOrMember.id) || (userOrMember instanceof GuildMember && isMemberAdmin(userOrMember))
}
export const isMemberAdmin = (member: GuildMember, adminConfig: Config['admin']) => {
export const isMemberAdmin = (member: GuildMember) => {
const roles = new Set(member.roles.cache.keys())
return Boolean(adminConfig?.roles?.[member.guild.id]?.some(role => roles.has(role)))
return Boolean(config?.admin?.roles?.[member.guild.id]?.some(role => roles.has(role)))
}