diff --git a/bot/auth.ts b/bot/auth.ts index 2dcc37c..f714777 100644 --- a/bot/auth.ts +++ b/bot/auth.ts @@ -38,12 +38,12 @@ async function initAuth(userId: string, clientId: string, clientSecret: string, await deleteAuthRecord(userId); const code = await codepromise; - await server.stop(false); + await server.stop(true); console.info(`Authentication code received.`); const tokenData = await exchangeCode(clientId, clientSecret, code, redirectURL); console.info(`Successfully authenticated code.`); await createAuthRecord(tokenData, userId); - console.info(`Successfully saved auth data in the database.`) + console.info(`Successfully saved auth data in the database.`); return tokenData; }; diff --git a/bot/commands/vulnchatters.ts b/bot/commands/vulnchatters.ts new file mode 100644 index 0000000..7360d84 --- /dev/null +++ b/bot/commands/vulnchatters.ts @@ -0,0 +1,12 @@ +import { redis } from "bun"; +import { Command, sendMessage } from "."; + +export default new Command('vulnchatters', + ['vulnchatters', 'vulnc'], + [], + async msg => { + const data = await redis.keys('vulnchatters:*'); + const one = data.length === 1; + await sendMessage(`There ${one ? 'is' : 'are'} ${data.length} vulnerable chatter${one ? '' : 's'}`, msg.messageId); + } +); diff --git a/bot/events/index.ts b/bot/events/index.ts index 00daa05..c73eaeb 100644 --- a/bot/events/index.ts +++ b/bot/events/index.ts @@ -13,8 +13,6 @@ eventSub.onSubscriptionCreateSuccess(event => { eventSub.onSubscriptionCreateFailure(event => { console.error(`Failed to create EventSub subscription: ${event.id}`); - event.stop() - event.start() }); eventSub.onSubscriptionDeleteSuccess(event => { diff --git a/bot/events/message.ts b/bot/events/message.ts index eafcf64..7d2985c 100644 --- a/bot/events/message.ts +++ b/bot/events/message.ts @@ -1,4 +1,4 @@ -import { chatterId, streamerId, eventSub, commandPrefix, singleUserMode } from ".."; +import { chatterId, streamerId, eventSub, commandPrefix, singleUserMode, unbannableUsers } from ".."; import { User } from "../user"; import commands, { sendMessage } from "../commands"; @@ -6,11 +6,18 @@ console.info(`Loaded the following commands: ${commands.keys().toArray().join(', eventSub.onChannelChatMessage(streamerId, streamerId, async msg => { // return if double user mode is on and the chatter says something, we don't need them - if (!singleUserMode && msg.chatterId === chatterId) return + if (!singleUserMode && msg.chatterId === chatterId) return; // Get user from cache or place user in cache + // Given the fact that this is the user that chats, this user object always exists and cannot be null + // + // One of the flaws with the user object is solved by creating the object with the name. + // 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 to timeout) + // and both are usable to target the same user (id is the same) const user = await User.initUsername(msg.chatterName); + if (!unbannableUsers.includes(msg.chatterId)) user?.makeVulnerable(); // Make the user vulnerable to explosions + // Parse commands: if (msg.messageText.startsWith(commandPrefix)) { const commandSelection = msg.messageText.slice(commandPrefix.length).split(' ')[0]!; diff --git a/bot/lib/timeout.ts b/bot/lib/timeout.ts index b229e1d..80f5dbe 100644 --- a/bot/lib/timeout.ts +++ b/bot/lib/timeout.ts @@ -21,7 +21,7 @@ export const timeout = async (target: string | User, reason: string, duration?: if (banStatus[0]) return { status: false, reason: 'banned' }; if (await streamerApi.moderation.checkUserMod(streamerId, user.id!)) { - if (!duration) duration = 60; + if (!duration) duration = 60; // make sure that mods don't get perma-banned remodMod(user, duration); await streamerApi.moderation.removeModerator(streamerId, user.id!); }; diff --git a/bot/user.ts b/bot/user.ts index b021383..87774fe 100644 --- a/bot/user.ts +++ b/bot/user.ts @@ -2,7 +2,17 @@ import { redis } from "bun"; import { chatterApi } from "."; import { HelixUser } from "@twurple/api" -const EXPIRETIME = 60 * 30 // 30 minutes +const EXPIRETIME = 60 * 60 // 60 minutes + +// The objective of this class is to: +// store displayname, username and id to reduce api calls +// keep track of temporary user specific flags (vulnerable to explosives, locked from using items) +// +// The userlookup key is used to find id's based on the username. +// +// The vulnchatters and userlookup look similar, but they're not the same +// userlookup expiration gets set when user chats or is targeted by another user +// vulnchatters only gets set when user chats export class User { public username!: string; @@ -79,4 +89,13 @@ export class User { public async clearLock(): Promise { await redis.set(`user:${this.id}:itemlock`, '0'); }; + + public async makeVulnerable(): Promise { + await redis.set(`vulnchatters:${this.id}`, this.displayName); + await redis.expire(`vulnchatters:${this.id}`, Math.floor(EXPIRETIME / 2)); // Vulnerable chatter gets removed from the pool after 30 minutes + }; + + public async makeInvulnerable(): Promise { + await redis.del(`vulnchatters:${this.id}`); + }; };