diff --git a/README.md b/README.md index b47b732..a779feb 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ They can however use items. The intended use for invulns is for when you need to talk to a chatter, or for using other bots. 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. -Only the streamer and chatterbot have the power to add and remove invulns. +Admins can add and remove invulns. ### Commands @@ -105,8 +105,8 @@ COMMAND|FUNCTION|USER|ALIASES|DISABLEABLE `getadmins`|Get a list of every admin in the channel|anyone|`getadmins`|:x: `itemlock {target}`|Toggle the itemlock on the specified target|admins|`itemlock`|:x: `testcheer {amount} [args]`|Create a fake cheering event|streamer/chatterbot|`testcheer`|:x: -`addinvuln {target}`|Adds an invuln user|streamer/chatterbot|`addinvuln`|:x: -`removeinvuln {target}`|Removes an invuln user| streamer/chatterbot|`removeinvuln`|:x: +`addinvuln {target}`|Adds an invuln user|admins|`addinvuln`|:x: +`removeinvuln {target}`|Removes an invuln user|admins|`removeinvuln`|:x: `addadmin {target}`|Adds an admin|streamer/chatterbot|`addadmin`|:x: `removeadmin {target}`|Removes an admin|streamer/chatterbot|`removeadmin`|:x: diff --git a/src/cheers/execute.ts b/src/cheers/execute.ts index 59f6d25..fb6ed21 100644 --- a/src/cheers/execute.ts +++ b/src/cheers/execute.ts @@ -5,6 +5,7 @@ import User from "user"; import { timeout } from "lib/timeout"; import { createTimeoutRecord } from "db/dbTimeouts"; import { parseCheerArgs } from "lib/parseCommandArgs"; +import { createCheerEventRecord } from "db/dbCheerEvents"; const ITEMNAME = 'silverbullet'; @@ -19,6 +20,7 @@ export default new Cheer('execute', 6666, async (msg, user) => { if (result.status) await Promise.all([ sendMessage(`${target.displayName} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`), createTimeoutRecord(user, target, ITEMNAME), + createCheerEventRecord(user, ITEMNAME) ]); else { await handleNoTarget(msg, user, ITEMNAME); diff --git a/src/cheers/grenade.ts b/src/cheers/grenade.ts index d795cf7..21282c7 100644 --- a/src/cheers/grenade.ts +++ b/src/cheers/grenade.ts @@ -4,11 +4,12 @@ import { timeout } from "lib/timeout"; import User from "user"; import { getUserRecord } from "db/dbUser"; import { createTimeoutRecord } from "db/dbTimeouts"; +import { createCheerEventRecord } from "db/dbCheerEvents"; import { Cheer, handleNoTarget } from "cheers"; const ITEMNAME = 'grenade'; -export default new Cheer(ITEMNAME, 99, async (msg, user) => { +export default new Cheer('grenade', 99, async (msg, user) => { const targets = await redis.keys(`user:*:vulnerable`); if (targets.length === 0) { await sendMessage('No vulnerable chatters to blow up!', msg.messageId); await handleNoTarget(msg, user, ITEMNAME); return; }; const selection = targets[Math.floor(Math.random() * targets.length)]!; @@ -20,6 +21,7 @@ export default new Cheer(ITEMNAME, 99, async (msg, user) => { timeout(target!, `You got hit by ${user.displayName}'s grenade!`, 60), redis.del(selection), sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`), - createTimeoutRecord(user, target!, ITEMNAME) + createTimeoutRecord(user, target!, ITEMNAME), + createCheerEventRecord(user, ITEMNAME) ]); }); diff --git a/src/cheers/timeout.ts b/src/cheers/timeout.ts index 7b21243..5d24ccb 100644 --- a/src/cheers/timeout.ts +++ b/src/cheers/timeout.ts @@ -4,6 +4,7 @@ import { getUserRecord } from "db/dbUser"; import User from "user"; import { timeout } from "lib/timeout"; import { createTimeoutRecord } from "db/dbTimeouts"; +import { createCheerEventRecord } from "db/dbCheerEvents"; import { parseCheerArgs } from "lib/parseCommandArgs"; const ITEMNAME = 'blaster'; @@ -18,7 +19,8 @@ export default new Cheer('timeout', 100, async (msg, user) => { const result = await timeout(target, `You got blasted by ${user.displayName}!`, 60); if (result.status) await Promise.all([ sendMessage(`GOTTEM ${target.displayName} got BLASTED by ${user.displayName} GOTTEM`), - createTimeoutRecord(user, target, ITEMNAME) + createTimeoutRecord(user, target, ITEMNAME), + createCheerEventRecord(user, ITEMNAME) ]); else { await handleNoTarget(msg, user, ITEMNAME); diff --git a/src/cheers/tnt.ts b/src/cheers/tnt.ts index 8de92e7..6162970 100644 --- a/src/cheers/tnt.ts +++ b/src/cheers/tnt.ts @@ -4,6 +4,7 @@ import { getUserRecord } from "db/dbUser"; import User from "user"; import { timeout } from "lib/timeout"; import { createTimeoutRecord } from "db/dbTimeouts"; +import { createCheerEventRecord } from "db/dbCheerEvents"; import { getTNTTargets } from "items/tnt"; import { redis } from "bun"; @@ -22,6 +23,7 @@ export default new Cheer('tnt', 1000, async (msg, user) => { redis.del(`user:${targetid}:vulnerable`), sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`), createTimeoutRecord(user, target!, ITEMNAME), + createCheerEventRecord(user, ITEMNAME) ]); })); diff --git a/src/commands/addinvuln.ts b/src/commands/addinvuln.ts index b8fedf4..ea5ba03 100644 --- a/src/commands/addinvuln.ts +++ b/src/commands/addinvuln.ts @@ -3,7 +3,7 @@ import { addInvuln } from "lib/invuln"; import parseCommandArgs from "lib/parseCommandArgs"; import User from "user"; -export default new Command('addinvuln', ['addinvuln'], 'streamer', async msg => { +export default new Command('addinvuln', ['addinvuln'], 'admin', 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()); diff --git a/src/commands/removeinvuln.ts b/src/commands/removeinvuln.ts index 9a38084..744dbc3 100644 --- a/src/commands/removeinvuln.ts +++ b/src/commands/removeinvuln.ts @@ -4,7 +4,7 @@ import { removeInvuln } from "lib/invuln"; import parseCommandArgs from "lib/parseCommandArgs"; import User from "user"; -export default new Command('removeinvuln', ['removeinvuln'], 'streamer', async msg => { +export default new Command('removeinvuln', ['removeinvuln'], 'admin', 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()); diff --git a/src/db/connection.ts b/src/db/connection.ts index edafe99..7b07425 100644 --- a/src/db/connection.ts +++ b/src/db/connection.ts @@ -34,11 +34,26 @@ export type timeoutRecord = { created: string; }; +export type cheerEventRecord = { + id?: string; + user: string; + cheer: string; + created: string; +}; + +export type cheerRecord = { + id?: string; + user?: string; + amount: number; +}; + interface TypedPocketBase extends PocketBase { collection(idOrName: 'auth'): RecordService; collection(idOrName: 'users'): RecordService; collection(idOrName: 'usedItems'): RecordService; collection(idOrName: 'timeouts'): RecordService; + collection(idOrName: 'cheerEvents'): RecordService; + collection(idOrName: 'cheers'): RecordService; }; export default new PocketBase(pocketbaseurl).autoCancellation(false) as TypedPocketBase; diff --git a/src/db/dbCheerEvents.ts b/src/db/dbCheerEvents.ts new file mode 100644 index 0000000..3da6ff5 --- /dev/null +++ b/src/db/dbCheerEvents.ts @@ -0,0 +1,26 @@ +import pocketbase from "db/connection"; +import User from "user"; +import logger from "lib/logger"; +const pb = pocketbase.collection('cheerEvents'); + +export async function createCheerEventRecord(user: User, cheer: string): Promise { + try { + await pb.create({ user: user.id, cheer }); + } catch (e) { + logger.err(`Failed to create cheerEvent record in database: user: ${user.id}, cheer: ${cheer}`); + logger.err(e as string); + }; +}; + +export async function getCheerEvents(user: User, monthData?: string) { + try { + const monthquery = monthData ? ` && created~"${monthData}"` : ''; + const data = await pb.getFullList({ + filter: `user="${user.id}"${monthquery}` + }); + return data; + } catch (e) { + logger.err(`Failed to get cheerEvents for user: ${user.id}, month: ${monthData}`); + logger.err(e as string); + }; +}; diff --git a/src/db/dbCheers.ts b/src/db/dbCheers.ts new file mode 100644 index 0000000..44cb29f --- /dev/null +++ b/src/db/dbCheers.ts @@ -0,0 +1,26 @@ +import pocketbase from "db/connection"; +import User from "user"; +import logger from "lib/logger"; +const pb = pocketbase.collection('cheers'); + +export async function createCheerRecord(user: User, amount: number): Promise { + try { + await pb.create({ user: user.id, amount }) + } catch (e) { + logger.err(`Failed to create cheer record in database: user: ${user.id}, amount: ${amount}`); + logger.err(e as string); + }; +}; + +export async function getCheers(user: User, monthData?: string) { + try { + const monthquery = monthData ? ` && created~"${monthData}"` : ''; + const data = await pb.getFullList({ + filter: `user="${user.id}"${monthquery}` + }); + return data; + } catch (e) { + logger.err(`Failed to get cheers for user: ${user.id}, month: ${monthData}`); + logger.err(e as string); + }; +}; diff --git a/src/events/message.ts b/src/events/message.ts index 607ccdb..e1743cf 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -8,6 +8,8 @@ import cheers from "cheers"; import logger from "lib/logger"; import { addMessageToChatWidget } from "web/chatWidget/message"; import { isInvuln, setTemporaryInvuln } from "lib/invuln"; +import { getUserRecord } from "db/dbUser"; +import { createCheerRecord } from "db/dbCheers"; logger.info(`Loaded the following commands: ${commands.keys().toArray().join(', ')}`); @@ -65,6 +67,11 @@ async function handleChatMessage(msg: EventSubChannelChatMessageEvent, user: Use }; export async function handleCheer(msg: EventSubChannelChatMessageEvent, bits: number, user: User) { + if (msg.isCheer) { + await getUserRecord(user); // ensure they exist in the database + await createCheerRecord(user, bits); + }; // If this is not triggered it's because of the testcheer command. these fake bits should not be added to the database + const selection = cheers.get(bits); if (!selection) return; diff --git a/src/lib/getStats.ts b/src/lib/getStats.ts index 193757c..43e5890 100644 --- a/src/lib/getStats.ts +++ b/src/lib/getStats.ts @@ -1,3 +1,4 @@ +import { getCheerEvents } from "db/dbCheerEvents"; import { getTimeoutsAsTarget, getTimeoutsAsUser } from "db/dbTimeouts"; import { getItemsUsed } from "db/dbUsedItems"; import type { inventory } from "items"; @@ -33,12 +34,22 @@ export async function getTimeoutStats(target: User, thismonth: boolean) { export async function getItemStats(target: User, thismonth: boolean) { const monthdata = thismonth ? new Date().toISOString().slice(0, 7) : undefined; - const data = await getItemsUsed(target, monthdata); - if (!data) return; + const [items, cheers] = await Promise.all([ + getItemsUsed(target, monthdata), + getCheerEvents(target, monthdata) + ]); + if (!items || !cheers) return; - const returnObj: inventory = { - grenade: data.filter(use => use.item === 'grenade').length, - tnt: data.filter(use => use.item === 'tnt').length + const returnObj: inventory = {}; + + for (const item of items) { + if (!returnObj[item.item]) returnObj[item.item] = 0; + returnObj[item.item]! += 1; + }; + + for (const cheer of cheers) { + if (!returnObj[cheer.cheer]) returnObj[cheer.cheer] = 0; + returnObj[cheer.cheer]! += 1 }; return returnObj; diff --git a/src/lib/invuln.ts b/src/lib/invuln.ts index 439c73e..c150f3a 100644 --- a/src/lib/invuln.ts +++ b/src/lib/invuln.ts @@ -14,7 +14,7 @@ export async function addInvuln(userid: string) { export async function removeInvuln(userid: string) { return await redis.del(`user:${userid}:invulnerable`); }; -export async function setTemporaryInvuln(userid: string) { +export async function setTemporaryInvuln(userid: string, duration = 600) { await redis.set(`user:${userid}:invulnerable`, '1'); - await redis.expire(`user:${userid}:invulnerable`, 600); + await redis.expire(`user:${userid}:invulnerable`, duration); };