feat: improve whitelist

This commit is contained in:
2024-08-05 13:06:05 +03:00
parent 4ef4c5d891
commit caab066ff8
9 changed files with 117 additions and 79 deletions

View File

@@ -9,15 +9,7 @@ import {
setMuted,
} from '@/core/megahal'
import { CHAT_TRIGGERS, DEFAULT_COMMAND_PREFIX } from '@/env'
import {
blacklistChannel,
blacklistGuild,
isBlacklisted,
isWhitelisted,
whitelistChannel,
whitelistGuild,
} from '@/lib/blacklist'
import { setSetting } from '@/lib/settings'
import { isWhitelisted, manipulateWhitelist } from '@/lib/blacklist'
export default command({
command: 'chat',
@@ -33,6 +25,7 @@ export default command({
`\`${DEFAULT_COMMAND_PREFIX}chat save\` - backs up the brain immediately`,
`\`${DEFAULT_COMMAND_PREFIX}chat size\` - shows the current brain size`,
`\`${DEFAULT_COMMAND_PREFIX}chat <anything else>\` - chat with Venom and immediately get a reply.`,
`\`${DEFAULT_COMMAND_PREFIX}chat whitelist <add|remove> <guild|channel>\` - update whitelist for guild/channel.`,
`\`${CHAT_TRIGGERS[1]}hi!\` - You can also just prefix it with one of the chat prefixes to chat more naturally: \`${CHAT_TRIGGERS.join('`, `')}\`, `,
],
execute: async (message, args) => {
@@ -43,43 +36,11 @@ export default command({
const [sub] = args
switch (sub.toLowerCase()) {
case 'whitelist': {
const guild = message.guild!
const channel = message.channel!
const type = args[1]?.trim().toLowerCase()
if (!type || !['guild', 'channel'].includes(type)) {
return message.reply(
'You need to provide a type to whitelist, either "guild" or "channel"',
)
}
if (type === 'guild') {
await whitelistGuild('chat', guild)
logger.info(`Whitelisted guild ${guild.id}`)
message.reply(`Guild ${guild.toString()} whitelisted`)
} else {
logger.info(`Whitelisting channel ${channel.id} in guild ${guild.id}`)
await whitelistChannel('chat', guild, channel)
message.reply(`Channel ${channel.toString()} on ${guild.toString()} whitelisted`)
}
break
}
case 'blacklist': {
const guild = message.guild!
const channel = message.channel!
const type = args[1]?.trim().toLowerCase()
if (!type || !['guild', 'channel'].includes(type)) {
return message.reply(
'You need to provide a type to blacklist, either "guild" or "channel"',
)
}
if (type === 'guild') {
await blacklistGuild('chat', guild)
logger.info(`Blacklisted guild ${guild.id}`)
message.reply(`Guild ${guild.toString()} blacklisted`)
} else {
logger.info(`Blacklisting channel ${channel.id} in guild ${guild.id}`)
await blacklistChannel('chat', guild, channel)
message.reply(`Channel ${channel.toString()} on ${guild.toString()} blacklisted`)
}
const action = args[1]?.trim().toLowerCase() as 'add' | 'remove' | undefined
const type = args[2]?.trim().toLowerCase() as 'guild' | 'channel' | undefined
message.reply(
await manipulateWhitelist('commands', action, type, message.guild!, message.channel),
)
break
}
case 'mute':

View File

@@ -2,6 +2,7 @@ import Discord from 'discord.js'
import { command, commands } from '@/core/commands'
import { DEFAULT_COMMAND_PREFIX } from '@/env'
import { logger } from '@/core/logger'
import { isWhitelisted } from '@/lib/blacklist'
interface HelpMessage {
command: string
@@ -13,17 +14,21 @@ export default command({
aliases: ['h'],
description: 'Lists available commands and their usage.',
examples: ['`!help`', '`!help ping`', '`!help chat`'],
global: true,
async execute(message, args) {
const whitelisted = await isWhitelisted('commands', message.guild!, message.channel)
let output = ''
const data: HelpMessage[] = []
const name = args[0]
const commandList = Object.values(commands()).filter((c) =>
name
const commandList = Object.values(commands()).filter((c) => {
const nameMatches = name
? c.command.toLowerCase() === name.toLowerCase() ||
c.aliases.some((a) => a.toLowerCase() === name.toLowerCase())
: true,
)
c.aliases.some((a) => a.toLowerCase() === name.toLowerCase())
: true
const isGlobal = c.global ?? false
return [nameMatches, whitelisted || isGlobal].every(Boolean)
})
for (const cmd of commandList) {
let description = `\`${DEFAULT_COMMAND_PREFIX}${cmd.command}\``

View File

@@ -25,7 +25,7 @@ export default command({
description: 'Manage quotes',
examples: [
`\`${DEFAULT_COMMAND_PREFIX}quote\` - Get a random quote`,
`\`${DEFAULT_COMMAND_PREFIX}quote <id>\` - Get a specific quote`,
`\`${DEFAULT_COMMAND_PREFIX}quote #<id>\` - Get a specific quote`,
`\`${DEFAULT_COMMAND_PREFIX}quote search <query>\` - Search for a quote`,
`\`${DEFAULT_COMMAND_PREFIX}quote count\` - See how many quotes have been stored so far!`,
`\`${DEFAULT_COMMAND_PREFIX}quote add @author <quote>\` - Add a new quote (@author can be a user mention, a plain nickname, or left out)`,
@@ -158,24 +158,22 @@ async function addNewQuote(message: Discord.Message, args: string[]): Promise<vo
`Are you serious? This is the best quote ever${differentAuthor ? `, ${authorName}` : ''}!`,
'OH. MY. GOD. Perfection.',
'I am putting this on my wall. This is a quote I will hold dear to me always.',
`Is that real? Woah! Hey,${
differentAuthor ? ` ${authorName},` : ''
`Is that real? Woah! Hey,${differentAuthor ? ` ${authorName},` : ''
} did you ever consider writing a book?! This will sell for millions.`,
clean(
`Okay, this is spooky. I definitely dreamt of ${!hasAuthor ? 'a person' : differentAuthor ? authorName : 'you'} ` +
`saying exactly that this week. ${!hasAuthor ? 'Is someone' : differentAuthor ? `Is ${authorName}` : 'Are you'} prying into my subconscious?`,
`saying exactly that this week. ${!hasAuthor ? 'Is someone' : differentAuthor ? `Is ${authorName}` : 'Are you'} prying into my subconscious?`,
),
'Consider me floored. If there was an award for amazing quotes, it would be named after this exact one.',
'Why did no one say this earlier? It HAS to be said!',
"I can't believe you withold that quote from me until now. It's way too good to just remain unshared!",
'I have a pretty large memory capacity for a bot, and I gotta say, I scanned all my other quotes, this one is definitely on the top 10.',
clean(`Oh, I am DEFINITELY saving this. One day someone will interview me about
${
!hasAuthor ? 'The best quote I can recall,' : differentAuthor ? authorName + ', ' : 'you,'
} and I will refer to this moment precisely.`),
${!hasAuthor ? 'The best quote I can recall,' : differentAuthor ? authorName + ', ' : 'you,'
} and I will refer to this moment precisely.`),
clean(
`You're not serious. Are you serious? You can't be serious. It's impossible there's **this** good a quote ` +
`just floating around
`just floating around
out there. It's probably fictional. Yeah.`,
),
]

View File

@@ -0,0 +1,18 @@
import { command } from '@/core/commands'
import { manipulateWhitelist } from '@/lib/blacklist'
export default command({
command: 'whitelist',
aliases: ['wl'],
description: 'Whitelist a channel or guild for commands.',
examples: ['`!whitelist add channel`', '`!whitelist add guild`'],
global: true,
adminOnly: true,
async execute(message, args) {
const action = args[0]?.trim().toLowerCase() as 'add' | 'remove' | undefined
const type = args[1]?.trim().toLowerCase() as 'guild' | 'channel' | undefined
return message.reply(
await manipulateWhitelist('commands', action, type, message.guild!, message.channel),
)
},
})

View File

@@ -5,10 +5,19 @@ import { COMMAND_TRIGGERS } from '@/env'
import { logger } from './logger'
export interface Command {
/** Command name */
command: string
/** Command aliases */
aliases: string[]
/** Command description - shown in help */
description: string
/** Command examples - shown in help */
examples: string[]
/** Global commands are available on non-whitelisted channels (default: false) */
global?: boolean
/** Admin-only commands are only available to whitelisted users/permissions/roles (default: false) */
adminOnly?: boolean
/** Function that executes the command */
execute(_message: Message, _args: string[]): void
}

View File

@@ -57,7 +57,10 @@ export function isMuted() {
export async function trainMegahal(message: Discord.Message, replyChance: number) {
const whitelisted = await isWhitelisted('chat', message.guild!, message.channel)
if (!whitelisted) return
if (!whitelisted) {
logger.debug('Not whitelisted, ignoring message:', JSON.stringify(message.content))
return
}
const key = msgCountKey(message)
msgCount[key]! += 1

View File

@@ -3,10 +3,13 @@ import { CHAT_TRIGGERS, COMMAND_TRIGGERS } from '@/env'
import { parseArguments, parseCommand } from '@/core/commands'
import { logger } from '@/core/logger'
import { CHATTER_REPLY_CHANCE, trainMegahal } from '@/core/megahal'
import { isWhitelisted } from '@/lib/blacklist'
import { isAdministrator } from '@/utils/discord_utils'
export async function handleMessage(message: Discord.Message) {
// ignore bot/own messages
if (message.author.bot) return
const whitelisted = await isWhitelisted('commands', message.guild!, message.channel)
logger.debug(
'Message received:',
@@ -30,6 +33,22 @@ export async function handleMessage(message: Discord.Message) {
}
const command = parseCommand(message.content)
const [cmdName, ...args] = parseArguments(message.content)
if (!whitelisted && !command?.global) {
logger.debug(
'Command received in non-whitelisted channel/guild:',
message.channel.id,
'guild:',
message.guild!.id,
)
return
}
if (command?.adminOnly) {
const isAdmin = await isAdministrator(message.member!)
if (!isAdmin) {
logger.debug('Non-administrator tried to use admin command:', message.author.id)
return
}
}
if (command) {
command.execute(message, args)
} else {

View File

@@ -17,26 +17,45 @@ export async function isWhitelisted(
return channelValue ?? guildValue
}
export async function whitelistChannel(
export async function whitelist(prefix: string, guild: Discord.Guild, channel?: Discord.Channel) {
const key = channel
? `${prefix}.whitelist.${guild.id}.${channel.id}`
: `${prefix}.whitelist.${guild.id}`
return setSetting(key, true)
}
export async function blacklist(prefix: string, guild: Discord.Guild, channel?: Discord.Channel) {
const key = channel
? `${prefix}.whitelist.${guild.id}.${channel.id}`
: `${prefix}.whitelist.${guild.id}`
return setSetting(key, false)
}
export async function manipulateWhitelist(
prefix: string,
guild: Discord.Guild,
channel: Discord.Channel,
) {
return setSetting(`${prefix}.whitelist.${guild.id}.${channel.id}`, true)
}
action: 'add' | 'remove' | undefined,
type: 'guild' | 'channel' | undefined,
gulid: Discord.Guild,
channel?: Discord.Channel,
): Promise<string> {
if (!action || !['add', 'remove'].includes(action)) {
return 'You need to provide an action to whitelist, either "add" or "remove"'
}
if (!type || !['guild', 'channel'].includes(type)) {
return 'You need to provide a type to whitelist, either "guild" or "channel"'
}
export async function whitelistGuild(prefix: string, guild: Discord.Guild) {
return setSetting(`${prefix}.whitelist.${guild.id}`, true)
}
const actionMap = {
add: whitelist,
remove: blacklist,
} as const
export async function blacklistChannel(
prefix: string,
guild: Discord.Guild,
channel: Discord.Channel,
) {
return setSetting(`${prefix}.whitelist.${guild.id}.${channel.id}`, false)
}
export async function blacklistGuild(prefix: string, guild: Discord.Guild) {
return setSetting(`${prefix}.whitelist.${guild.id}`, false)
if (type === 'guild') {
actionMap[action](prefix, gulid)
return `Guild ${gulid.toString()} ${action === 'add' ? 'whitelisted' : 'blacklisted'} for ${prefix}`
} else {
if (!channel) return 'You need to provide a channel to whitelist'
actionMap[action](prefix, gulid, channel)
return `Channel ${channel.toString()} on ${gulid.toString()} ${action === 'add' ? 'whitelisted' : 'blacklisted'} for ${prefix}`
}
}

View File

@@ -0,0 +1,6 @@
import Discord, { PermissionsBitField } from 'discord.js'
export async function isAdministrator(member: Discord.GuildMember): Promise<boolean> {
// TODO check role permissions
return member.permissions.has(PermissionsBitField.Flags.ManageGuild)
}