From 85b584c87e74f6ab9ee99f0a6a37288a472ec2d5 Mon Sep 17 00:00:00 2001 From: qwerinope Date: Sun, 21 Sep 2025 17:21:50 +0200 Subject: [PATCH] add bot status and minor changes --- README.md | 10 +++++++++- src/commands/addbot.ts | 23 +++++++++++++++++++++++ src/commands/removebot.ts | 22 ++++++++++++++++++++++ src/events/message.ts | 2 +- src/events/raid.ts | 5 +---- src/index.ts | 4 +++- src/lib/timeout.ts | 2 +- 7 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 src/commands/addbot.ts create mode 100644 src/commands/removebot.ts diff --git a/README.md b/README.md index 94f0eab..ece71b8 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,18 @@ Admins have the power to destroy the item economy. Be very careful with admin po Invulns, or invulnerable chatters cannot be shot with items and cannot get hit by explosives. When an invuln uses an item or a lootbox they lose their invuln status. -The intended use for invulns is for when you need to talk to a chatter, or for using other bots. +The intended use for invulns is for when a chatter doesn't want to participate in pvp or for when you need to talk to someone. Invulns don't need moderator or vip status in the channel. The chatterbot and streamer always are invuln and cannot be stripped of this status. Moderators can add and remove invulns. On your first message in chat you will recieve 10 minutes of invuln status. +### Bots + +Bots are ignored by the program. +Bots cannot be timed out. +Bots don't need moderator or vip status. + ### Commands Commands are functions that are triggered by typing an instruction in the chat. @@ -147,6 +153,8 @@ COMMAND|FUNCTION|USER|ALIASES|DISABLEABLE `testcheer {amount} [args]`|Create a fake cheering event|streamer/chatterbot|`testcheer`|:x: `addinvuln {target}`|Adds an invuln user|moderator|`addinvuln`|:x: `removeinvuln {target}`|Removes an invuln user|moderator|`removeinvuln`|:x: +`addbot {target}`|Adds bot status to a specific chatter|streamer/chatterbot|`addbot`|:x: +`removebot {target}`|Removes bot status from a specific chatter|streamer/chatterbot|`removebot`|:x: `addadmin {target}`|Adds an admin|streamer/chatterbot|`addadmin`|:x: `removeadmin {target}`|Removes an admin|streamer/chatterbot|`removeadmin`|:x: diff --git a/src/commands/addbot.ts b/src/commands/addbot.ts new file mode 100644 index 0000000..df70d09 --- /dev/null +++ b/src/commands/addbot.ts @@ -0,0 +1,23 @@ +import { redis } from "bun"; +import { Command, sendMessage } from "commands"; +import parseCommandArgs from "lib/parseCommandArgs"; +import { streamerUsers } from "main"; +import User from "user"; + +export default new Command({ + name: 'addbot', + aliases: ['addbot'], + usertype: 'streamer', + disableable: false, + execution: async msg => { + const args = parseCommandArgs(msg.messageText); + if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; }; + const target = await User.initUsername(args[0].toLowerCase()); + if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; }; + if (streamerUsers.includes(target.id)) { await sendMessage(`Cannot change bot status of qweribot managed user`, msg.messageId); return; }; + const data = await redis.set(`user:${target.id}:bot`, '1'); + await target.clearVulnerable(); + if (data === "OK") await sendMessage(`${target.displayName} is now a bot`, msg.messageId); + else await sendMessage(`${target.displayName} is already a bot`, msg.messageId); + } +}); diff --git a/src/commands/removebot.ts b/src/commands/removebot.ts new file mode 100644 index 0000000..c2a1961 --- /dev/null +++ b/src/commands/removebot.ts @@ -0,0 +1,22 @@ +import { Command, sendMessage } from "commands"; +import { streamerUsers } from "main"; +import { redis } from "bun"; +import parseCommandArgs from "lib/parseCommandArgs"; +import User from "user"; + +export default new Command({ + name: 'removebot', + aliases: ['removebot'], + usertype: 'streamer', + disableable: false, + execution: async msg => { + const args = parseCommandArgs(msg.messageText); + if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; }; + const target = await User.initUsername(args[0].toLowerCase()); + if (!target) { await sendMessage(`Chatter ${args[0]} doesn't exist`, msg.messageId); return; }; + if (streamerUsers.includes(target.id)) { await sendMessage(`Cannot change bot status of qweribot managed user`, msg.messageId); return; }; + const data = await redis.del(`user:${target.id}:bot`); + if (data === 1) await sendMessage(`${target.displayName} is no longer a bot`, msg.messageId); + else await sendMessage(`${target.displayName} isn't a bot`, msg.messageId); + } +}); diff --git a/src/events/message.ts b/src/events/message.ts index 941cd34..8993a9a 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -28,7 +28,7 @@ async function parseChatMessage(msg: EventSubChannelChatMessageEvent) { // 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 - if (msg.chatterId === chatterId && chatterId !== streamerId) return; + if (await redis.exists(`user:${user?.id}:bot`)) return; // Ignore all bot commands if (!await redis.exists(`user:${user?.id}:haschatted`) && !msg.sourceMessageId) { const message = await sendMessage(`Welcome ${user?.displayName}. Please note: This chat has PvP, if you get timed out that's part of the qwerinope experience. You have 10 minutes of invincibility. A full list of commands and items can be found here: https://github.com/qwerinope/qweribot/#qweribot`); diff --git a/src/events/raid.ts b/src/events/raid.ts index 68e5ba2..6ce24e4 100644 --- a/src/events/raid.ts +++ b/src/events/raid.ts @@ -1,4 +1,3 @@ -import { redis } from "bun"; import { sendMessage } from "commands"; import { getUserRecord } from "db/dbUser"; import { changeItemCount } from "items"; @@ -6,10 +5,8 @@ import { eventSub, streamerApi, streamerId } from "main"; import User from "user"; eventSub.onChannelRaidTo(streamerId, async msg => { - await sendMessage(`Ty for raiding ${msg.raidingBroadcasterDisplayName}. You get 10 minutes of invulnerability and 3 pieces of TNT. Enjoy!`); + await sendMessage(`Ty for raiding ${msg.raidingBroadcasterDisplayName}. You get 3 pieces of TNT. Enjoy!`); await streamerApi.chat.shoutoutUser(streamerId, msg.raidingBroadcasterId); - await redis.set(`user:${msg.raidingBroadcasterId}:invuln`, '1'); - await redis.expire(`user:${msg.raidingBroadcasterId}:invuln`, 600); const raider = await User.initUsername(msg.raidingBroadcasterName); const result = await changeItemCount(raider!, await getUserRecord(raider!), 'tnt', 3); if (!result) await sendMessage("oopsies, no tnt for you!"); diff --git a/src/index.ts b/src/index.ts index 91892b7..7f59c0e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,7 @@ import { remodMod, timeoutDuration } from "lib/timeout"; import User from "user"; import { connectionCheck } from "connectionCheck"; -await connectionCheck() +await connectionCheck(); const CHATTERINTENTS = ["user:read:chat", "user:write:chat", "user:bot", "user:manage:whispers"]; const STREAMERINTENTS = ["channel:bot", "user:read:chat", "moderation:read", "channel:manage:moderators", "moderator:manage:chat_messages", "moderator:manage:banned_users", "bits:read", "channel:moderate", "moderator:manage:shoutouts"]; @@ -36,6 +36,8 @@ export const chatterEventSub = singleUserMode ? eventSub : new EventSubWsListene export const commandPrefix = process.env.COMMAND_PREFIX ?? "!"; +if (!singleUserMode) await redis.set(`user:${chatterId}:bot`, '1'); + export const streamerUsers = [chatterId, streamerId]; streamerUsers.forEach(async id => await Promise.all([addAdmin(id), addInvuln(id), redis.set(`user:${id}:mod`, '1')])); diff --git a/src/lib/timeout.ts b/src/lib/timeout.ts index e8e50bc..6cd51eb 100644 --- a/src/lib/timeout.ts +++ b/src/lib/timeout.ts @@ -13,7 +13,7 @@ type TimeoutResult = SuccessfulTimeout | UnSuccessfulTimeout; * @param reason - reason for timeout/ban * @param duration - duration of timeout. don't specifiy for ban */ export const timeout = async (user: User, reason: string, duration?: number): Promise => { - if (await isInvuln(user.id) && duration) return { status: false, reason: 'illegal' }; // Don't timeout invulnerable chatters + if (await isInvuln(user.id) && duration || await redis.exists(`user:${user.id}:bot`)) return { status: false, reason: 'illegal' }; // Don't timeout invulnerable chatters and bots // Check if user already has a timeout and handle stacking const banStatus = await timeoutDuration(user);