Compare commits

..

2 Commits

Author SHA1 Message Date
773a694714 fix typo 2025-06-27 15:30:29 +02:00
de492718fe add command enabling/disabling, only by admins 2025-06-27 15:28:51 +02:00
7 changed files with 74 additions and 7 deletions

View File

@@ -12,7 +12,7 @@ Arguments like `{this}` are required.
Commands and items can be disabled and enabled by admins with the [`enable` and `disable` commands](#administrative-commands). Commands and items can be disabled and enabled by admins with the [`enable` and `disable` commands](#administrative-commands).
Not all Commands can be disabled, the `DISABLEABLE` field shows if they can or can't. Items can always be disabled. Not all Commands can be disabled, the `DISABLEABLE` field shows if they can or can't. Items can always be disabled.
Admins are defined by the streamer and can use special administrative command on the bot. Admins are defined by the streamer and can use special administrative commands on the bot.
Admins don't need to have moderator status in the channel. Admins don't need to have moderator status in the channel.
The chatterbot and streamer always have admin status and cannot be stripped of admin powers. The chatterbot and streamer always have admin status and cannot be stripped of admin powers.
Only the streamer and chatterbot have the power to add and remove admins. Only the streamer and chatterbot have the power to add and remove admins.
@@ -39,7 +39,10 @@ COMMAND|FUNCTION|USER|ALIASES|DISABLEABLE
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: `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: `getadmins`|Get a list of every admin in the channel|anyone|`getadmins`|:x:
`addadmin {target}`|Adds an admin|streamer/botchatter|`addadmin`|:x: `addadmin {target}`|Adds an admin|streamer/botchatter|`addadmin`|:x:
`removeadmin {target}`|Removes an admin|streamer/botchatter|`removeadmin`|:x: `removeadmin {target}`|Removes an admin|streamer/botchatter|`removeadmin`|:x:

View File

@@ -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 sendMessage(`Failed to give ${target.displayName} ${amount} ${item.prettyName + (amount === 1 ? '' : item.plural)}`, msg.messageId);
}; };
await target.clearLock(); await target.clearLock();
}); }, false);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -6,18 +6,21 @@ export class Command {
public readonly name: string; public readonly name: string;
public readonly aliases: string[]; public readonly aliases: string[];
public readonly requiredIntents: string[]; public readonly requiredIntents: string[];
public readonly disableable: boolean;
public readonly execute: (message: EventSubChannelChatMessageEvent, sender: User) => Promise<void>; public readonly execute: (message: EventSubChannelChatMessageEvent, sender: User) => Promise<void>;
constructor(name: string, aliases: string[], requiredIntents: string[], execution: (message: EventSubChannelChatMessageEvent, sender: User) => Promise<void>) { constructor(name: string, aliases: string[], requiredIntents: string[], execution: (message: EventSubChannelChatMessageEvent, sender: User) => Promise<void>, disableable?: boolean) {
this.name = name; this.name = name.toLowerCase();
this.aliases = aliases; this.aliases = aliases;
this.requiredIntents = requiredIntents; this.requiredIntents = requiredIntents;
this.execute = execution; this.execute = execution;
this.disableable = disableable ?? true;
}; };
}; };
import { readdir } from 'node:fs/promises'; import { readdir } from 'node:fs/promises';
const commands = new Map<string, Command>; const commands = new Map<string, Command>;
const commandintents: string[] = []; const commandintents: string[] = [];
const basecommands = new Map<string, Command>;
const files = await readdir(import.meta.dir); const files = await readdir(import.meta.dir);
for (const file of files) { for (const file of files) {
@@ -25,6 +28,7 @@ for (const file of files) {
if (file === import.meta.file) continue; if (file === import.meta.file) continue;
const command: Command = await import(import.meta.dir + '/' + file.slice(0, -3)).then(a => a.default); const command: Command = await import(import.meta.dir + '/' + file.slice(0, -3)).then(a => a.default);
commandintents.push(...command.requiredIntents); commandintents.push(...command.requiredIntents);
basecommands.set(command.name, command);
for (const alias of command.aliases) { 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 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 default commands;
export { commandintents }; export { commandintents, basecommands };
import { singleUserMode, chatterApi, chatterId, streamerId } from ".."; import { singleUserMode, chatterApi, chatterId, streamerId } from "..";

View File

@@ -1,6 +1,7 @@
import { chatterId, streamerId, eventSub, commandPrefix, singleUserMode, unbannableUsers } from ".."; import { chatterId, streamerId, eventSub, commandPrefix, singleUserMode, unbannableUsers } from "..";
import { User } from "../user"; 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(', ')}`); 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) // 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) // 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 // 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 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 commandSelection = msg.messageText.slice(commandPrefix.length).split(' ')[0]!;
const selected = commands.get(commandSelection.toLowerCase()); const selected = commands.get(commandSelection.toLowerCase());
if (!selected) return; if (!selected) return;
if (disabledcommands.includes(selected.name)) return;
try { await selected.execute(msg, user!); } try { await selected.execute(msg, user!); }
catch (err) { console.error(err); }; catch (err) { console.error(err); };
}; };