diff --git a/bot/events/index.ts b/bot/events/index.ts index 0979635..3e5cd11 100644 --- a/bot/events/index.ts +++ b/bot/events/index.ts @@ -1,5 +1,9 @@ import { eventSub } from ".."; +eventSub.onRevoke(event => { + console.info(`Successfully revoked EventSub subscription: ${event.id}`); +}); + eventSub.onSubscriptionCreateSuccess(event => { console.info(`Successfully created EventSub subscription: ${event.id}`); }); diff --git a/bot/events/message.ts b/bot/events/message.ts index a0be2f5..76465bc 100644 --- a/bot/events/message.ts +++ b/bot/events/message.ts @@ -1,13 +1,16 @@ import { redis } from "bun"; -import { chatterId, streamerId, eventSub, commandPrefix } from ".."; +import { chatterId, streamerId, eventSub, commandPrefix, singleUserMode } from ".."; import { User } from "../user"; import commands, { sendMessage } from "../commands"; -console.info(`Loaded the ${commands.keys().toArray().join(', ')} commands`); +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 + // Get user from cache or place user in cache - const user = await User.init(msg.chatterName); + const user = await User.initUserId(msg.chatterId); // Manage vulnerable chatters if (![chatterId, streamerId].includes(msg.chatterId)) {// Don't add the chatter or streamer to the vulnerable chatters diff --git a/bot/user.ts b/bot/user.ts index 72cb237..bf5a187 100644 --- a/bot/user.ts +++ b/bot/user.ts @@ -1,45 +1,82 @@ import { redis } from "bun"; import { chatterApi } from "."; +import { HelixUser } from "@twurple/api" + +const EXPIRETIME = 60 * 15 // 15 minutes export class User { - constructor(username: string) { - this.username = username; - }; + public username: string | undefined; + public id: string | undefined; + public displayName: string | undefined; - public username: string; - public id: string | undefined | null; // The null here and below serves the function to make typescript shut the fuck up - public displayName: string | undefined | null; - - static async init(username: string): Promise { - const userObj = new User(username); - const cachedata = await redis.exists(`user:${username}`); - if (!cachedata) { - const twitchdata = await chatterApi.users.getUserByName(username!); - if (!twitchdata) return null; // User does not exist in redis and twitch api can't get them: return null - await redis.set(`user:${username}:id`, twitchdata.id); - await redis.set(`user:${username}:displayName`, twitchdata.displayName); - await redis.set(`user:${username}:itemlock`, '0'); + static async initUsername(username: string): Promise { + const userObj = new User(); + userObj.username = username; + const userid = await redis.get(`userlookup:${username}`); + if (!userid) { + const userdata = await chatterApi.users.getUserByName(username); + if (!userdata) return null; + userObj._setCache(userdata); + userObj.id = userdata.id; + userObj.displayName = userdata.displayName; + } else { + const displayname = await redis.get(`user:${userid}:displayName`); + userObj._setExpire(userid, username); + userObj.id = userid; + userObj.displayName = displayname!; }; - await userObj._setOptions(); return userObj; }; - private async _setOptions(): Promise { - this.id = await redis.get(`user:${this.username}:id`); - this.displayName = await redis.get(`user:${this.username}:displayName`); + static async initUserId(userId: string): Promise { + const userObj = new User(); + userObj.id = userId; + if (!await redis.exists(`user:${userId}:displayName`)) { + const userdata = await chatterApi.users.getUserById(userId); + if (!userdata) return null; + userObj._setCache(userdata); + userObj.username = userdata.name; + userObj.displayName = userdata.displayName; + } else { + const [displayName, username] = await Promise.all([ + redis.get(`user:${userId}:displayName`), + redis.get(`user:${userId}:username`) + ]); + userObj._setExpire(userId, username!); + userObj.username = username!; + userObj.displayName = displayName!; + }; + return userObj; + }; + + private async _setCache(userdata: HelixUser) { + await Promise.all([ + redis.set(`user:${userdata.id}:displayName`, userdata.displayName), + redis.set(`user:${userdata.id}:username`, userdata.name), + redis.set(`userlookup:${userdata.name}`, userdata.id) + ]); + await this._setExpire(userdata.id, userdata.name); + }; + + private async _setExpire(userId: string, userName: string) { + await Promise.all([ + redis.expire(`user:${userId}:displayName`, EXPIRETIME), + redis.expire(`user:${userId}:username`, EXPIRETIME), + redis.expire(`userlookup:${userName}`, EXPIRETIME) + ]); }; public async itemLock(): Promise { - const lock = await redis.get(`user:${this.username}:itemlock`); + const lock = await redis.get(`user:${this.id}:itemlock`); if (lock === '0') return false; return true; }; public async setLock(): Promise { - await redis.set(`user:${this.username}:itemlock`, '1'); + await redis.set(`user:${this.id}:itemlock`, '1'); }; public async clearLock(): Promise { - await redis.set(`user:${this.username}:itemlock`, '0'); + await redis.set(`user:${this.id}:itemlock`, '0'); }; };