From de492718fe6a0188aca16c0b65273712361d61c5 Mon Sep 17 00:00:00 2001 From: qwerinope Date: Fri, 27 Jun 2025 15:28:51 +0200 Subject: [PATCH] add command enabling/disabling, only by admins --- README.md | 3 +++ bot/commands/admingive.ts | 2 +- bot/commands/disablecommand.ts | 16 ++++++++++++++++ bot/commands/enablecommand.ts | 15 +++++++++++++++ bot/commands/getcommands.ts | 23 +++++++++++++++++++++++ bot/commands/index.ts | 10 +++++++--- bot/events/message.ts | 10 ++++++++-- 7 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 bot/commands/disablecommand.ts create mode 100644 bot/commands/enablecommand.ts create mode 100644 bot/commands/getcommands.ts diff --git a/README.md b/README.md index 41cd395..e783009 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,10 @@ COMMAND|FUNCTION|USER|ALIASES|DISABLEABLE COMMAND|FUNCTION|USER|ALIASES|DISABLEABLE -|-|-|-|- +`getcommands [enabled/disabled]`|Get a list of all, enabled or disabled commands|anyone|`getcommands` `getc`|:x: `vulnchatters`|Get amount of chatters vulnerable to explosives|anyone|`vulnchatters` `vulnc`|:white_check_mark: +`disablecommand {command/item}`|Disable a specific command/item|admins|`disablecommand`|:x: +`enablecommand {command/item}`|Re-enable a specific command/item|admins|`enablecommand`|:x: `getadmins`|Get a list of every admin in the channel|anyone|`getadmins`|:x: `addadmin {target}`|Adds an admin|streamer/botchatter|`addadmin`|:x: `removeadmin {target}`|Removes an admin|streamer/botchatter|`removeadmin`|:x: diff --git a/bot/commands/admingive.ts b/bot/commands/admingive.ts index d7dfec0..17bf5e3 100644 --- a/bot/commands/admingive.ts +++ b/bot/commands/admingive.ts @@ -28,4 +28,4 @@ export default new Command('admingive', ['admingive'], [], async msg => { await sendMessage(`Failed to give ${target.displayName} ${amount} ${item.prettyName + (amount === 1 ? '' : item.plural)}`, msg.messageId); }; await target.clearLock(); -}); +}, false); diff --git a/bot/commands/disablecommand.ts b/bot/commands/disablecommand.ts new file mode 100644 index 0000000..415c19f --- /dev/null +++ b/bot/commands/disablecommand.ts @@ -0,0 +1,16 @@ +import { redis } from "bun"; +import commands, { Command, sendMessage } from "."; +import { isAdmin } from "../lib/admins"; +import parseCommandArgs from "../lib/parseCommandArgs"; + +export default new Command('disablecommand', ['disablecommand'], [], async msg => { + if (!await isAdmin(msg.chatterId)) return; + const args = parseCommandArgs(msg.messageText); + if (!args[0]) { await sendMessage('Please specify a command to disable', msg.messageId); return; }; + const selection = commands.get(args[0].toLowerCase()); + if (!selection) { await sendMessage(`There is no ${args[0]} command`, msg.messageId); return; }; + if (!selection.disableable) { await sendMessage(`Cannot disable ${selection.name} as the command is not disableable`, msg.messageId); return; }; + const result = await redis.sadd('disabledcommands', selection.name); + if (result === 0) { await sendMessage(`The ${selection.name} command is already disabled`, msg.messageId); return; }; + await sendMessage(`Successfully disabled the ${selection.name} command`, msg.messageId); +}, false); diff --git a/bot/commands/enablecommand.ts b/bot/commands/enablecommand.ts new file mode 100644 index 0000000..bec566b --- /dev/null +++ b/bot/commands/enablecommand.ts @@ -0,0 +1,15 @@ +import { redis } from "bun"; +import commands, { Command, sendMessage } from "."; +import { isAdmin } from "../lib/admins"; +import parseCommandArgs from "../lib/parseCommandArgs"; + +export default new Command('enablecommand', ['enablecommand'], [], async msg => { + if (!await isAdmin(msg.chatterId)) return; + const args = parseCommandArgs(msg.messageText); + if (!args[0]) { await sendMessage('Please specify a command to enable', msg.messageId); return; }; + const selection = commands.get(args[0].toLowerCase()); + if (!selection) { await sendMessage(`There is no ${args[0]} command`, msg.messageId); return; }; + const result = await redis.srem('disabledcommands', selection.name); + if (result === 0) { await sendMessage(`The ${selection.name} command isn't disabled`, msg.messageId); return; }; + await sendMessage(`Successfully enabled the ${selection.name} command`, msg.messageId); +}, false); diff --git a/bot/commands/getcommands.ts b/bot/commands/getcommands.ts new file mode 100644 index 0000000..8dd85ef --- /dev/null +++ b/bot/commands/getcommands.ts @@ -0,0 +1,23 @@ +import { redis } from "bun"; +import { basecommands, Command, sendMessage } from "."; +import parseCommandArgs from "../lib/parseCommandArgs"; + +export default new Command('getcommands', ['getcommands', 'getc'], [], async msg => { + const args = parseCommandArgs(msg.messageText); + if (!args[0]) { await sendMessage(`A list of commands can be found here: https://github.com/qwerinope/qweribot#commands`, msg.messageId); return; }; + const disabledcommands = await redis.smembers('disabledcommands'); + if (args[0].toLowerCase() === 'enabled') { + const commandnames: string[] = []; + for (const [name, command] of Array.from(basecommands.entries())) { + if (!command.disableable) continue; + if (disabledcommands.includes(name)) continue; + commandnames.push(name); + }; + if (commandnames.length === 0) await sendMessage('No commands besides non-disableable commands are enabled', msg.messageId); + else await sendMessage(`Currently enabled commands: ${commandnames.join(', ')}`, msg.messageId); + } else if (args[0].toLowerCase() === 'disabled') { + if (disabledcommands.length === 0) await sendMessage('No commands are disabled', msg.messageId); + else await sendMessage(`Currently disabled commands: ${disabledcommands.join(', ')}`); + } + else await sendMessage('Please specify if you want the enabled or disabled commands', msg.messageId); +}, false); diff --git a/bot/commands/index.ts b/bot/commands/index.ts index 2a0b1dd..45caf4c 100644 --- a/bot/commands/index.ts +++ b/bot/commands/index.ts @@ -6,18 +6,21 @@ export class Command { public readonly name: string; public readonly aliases: string[]; public readonly requiredIntents: string[]; + public readonly disableable: boolean; public readonly execute: (message: EventSubChannelChatMessageEvent, sender: User) => Promise; - constructor(name: string, aliases: string[], requiredIntents: string[], execution: (message: EventSubChannelChatMessageEvent, sender: User) => Promise) { - this.name = name; + constructor(name: string, aliases: string[], requiredIntents: string[], execution: (message: EventSubChannelChatMessageEvent, sender: User) => Promise, disableable?: boolean) { + this.name = name.toLowerCase(); this.aliases = aliases; this.requiredIntents = requiredIntents; this.execute = execution; + this.disableable = disableable ?? true; }; }; import { readdir } from 'node:fs/promises'; const commands = new Map; const commandintents: string[] = []; +const basecommands = new Map; const files = await readdir(import.meta.dir); for (const file of files) { @@ -25,6 +28,7 @@ for (const file of files) { if (file === import.meta.file) continue; const command: Command = await import(import.meta.dir + '/' + file.slice(0, -3)).then(a => a.default); commandintents.push(...command.requiredIntents); + basecommands.set(command.name, command); for (const alias of command.aliases) { commands.set(alias, command); // Since it's not a primitive type the map is filled with references to the command, not the actual object }; @@ -36,7 +40,7 @@ for (const [name, item] of Array.from(items)) { }; export default commands; -export { commandintents }; +export { commandintents, basecommands }; import { singleUserMode, chatterApi, chatterId, streamerId } from ".."; diff --git a/bot/events/message.ts b/bot/events/message.ts index 8f1feda..58d834a 100644 --- a/bot/events/message.ts +++ b/bot/events/message.ts @@ -1,6 +1,7 @@ import { chatterId, streamerId, eventSub, commandPrefix, singleUserMode, unbannableUsers } from ".."; import { User } from "../user"; -import commands, { sendMessage } from "../commands"; +import commands from "../commands"; +import { redis } from "bun"; console.info(`Loaded the following commands: ${commands.keys().toArray().join(', ')}`); @@ -15,7 +16,11 @@ eventSub.onChannelChatMessage(streamerId, streamerId, async msg => { // This way, if a user changes their name, the original name stays in the cache for at least 1 hour (extendable by using that name as target for item) // and both are usable to target the same user (id is the same) // The only problem would be if a user changed their name and someone else took their name right after - const user = await User.initUsername(msg.chatterName); + + const [user, disabledcommands] = await Promise.all([ + User.initUsername(msg.chatterName), + redis.smembers('disabledcommands') + ]); if (!unbannableUsers.includes(msg.chatterId)) user?.makeVulnerable(); // Make the user vulnerable to explosions @@ -24,6 +29,7 @@ eventSub.onChannelChatMessage(streamerId, streamerId, async msg => { const commandSelection = msg.messageText.slice(commandPrefix.length).split(' ')[0]!; const selected = commands.get(commandSelection.toLowerCase()); if (!selected) return; + if (disabledcommands.includes(selected.name)) return; try { await selected.execute(msg, user!); } catch (err) { console.error(err); }; };