fix #3, now tracking cheers and cheerEvents in database, minor tweaks to existing code

This commit is contained in:
2025-08-24 20:20:52 +02:00
parent 97a6a599a8
commit 594d154cab
13 changed files with 108 additions and 15 deletions

View File

@@ -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. 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. 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. 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 ### Commands
@@ -105,8 +105,8 @@ COMMAND|FUNCTION|USER|ALIASES|DISABLEABLE
`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:
`itemlock {target}`|Toggle the itemlock on the specified target|admins|`itemlock`|: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: `testcheer {amount} [args]`|Create a fake cheering event|streamer/chatterbot|`testcheer`|:x:
`addinvuln {target}`|Adds an invuln user|streamer/chatterbot|`addinvuln`|:x: `addinvuln {target}`|Adds an invuln user|admins|`addinvuln`|:x:
`removeinvuln {target}`|Removes an invuln user| streamer/chatterbot|`removeinvuln`|:x: `removeinvuln {target}`|Removes an invuln user|admins|`removeinvuln`|:x:
`addadmin {target}`|Adds an admin|streamer/chatterbot|`addadmin`|:x: `addadmin {target}`|Adds an admin|streamer/chatterbot|`addadmin`|:x:
`removeadmin {target}`|Removes an admin|streamer/chatterbot|`removeadmin`|:x: `removeadmin {target}`|Removes an admin|streamer/chatterbot|`removeadmin`|:x:

View File

@@ -5,6 +5,7 @@ import User from "user";
import { timeout } from "lib/timeout"; import { timeout } from "lib/timeout";
import { createTimeoutRecord } from "db/dbTimeouts"; import { createTimeoutRecord } from "db/dbTimeouts";
import { parseCheerArgs } from "lib/parseCommandArgs"; import { parseCheerArgs } from "lib/parseCommandArgs";
import { createCheerEventRecord } from "db/dbCheerEvents";
const ITEMNAME = 'silverbullet'; const ITEMNAME = 'silverbullet';
@@ -19,6 +20,7 @@ export default new Cheer('execute', 6666, async (msg, user) => {
if (result.status) await Promise.all([ if (result.status) await Promise.all([
sendMessage(`${target.displayName} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`), sendMessage(`${target.displayName} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`),
createTimeoutRecord(user, target, ITEMNAME), createTimeoutRecord(user, target, ITEMNAME),
createCheerEventRecord(user, ITEMNAME)
]); ]);
else { else {
await handleNoTarget(msg, user, ITEMNAME); await handleNoTarget(msg, user, ITEMNAME);

View File

@@ -4,11 +4,12 @@ import { timeout } from "lib/timeout";
import User from "user"; import User from "user";
import { getUserRecord } from "db/dbUser"; import { getUserRecord } from "db/dbUser";
import { createTimeoutRecord } from "db/dbTimeouts"; import { createTimeoutRecord } from "db/dbTimeouts";
import { createCheerEventRecord } from "db/dbCheerEvents";
import { Cheer, handleNoTarget } from "cheers"; import { Cheer, handleNoTarget } from "cheers";
const ITEMNAME = 'grenade'; 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`); 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; }; 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)]!; 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), timeout(target!, `You got hit by ${user.displayName}'s grenade!`, 60),
redis.del(selection), redis.del(selection),
sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`), sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`),
createTimeoutRecord(user, target!, ITEMNAME) createTimeoutRecord(user, target!, ITEMNAME),
createCheerEventRecord(user, ITEMNAME)
]); ]);
}); });

View File

@@ -4,6 +4,7 @@ import { getUserRecord } from "db/dbUser";
import User from "user"; import User from "user";
import { timeout } from "lib/timeout"; import { timeout } from "lib/timeout";
import { createTimeoutRecord } from "db/dbTimeouts"; import { createTimeoutRecord } from "db/dbTimeouts";
import { createCheerEventRecord } from "db/dbCheerEvents";
import { parseCheerArgs } from "lib/parseCommandArgs"; import { parseCheerArgs } from "lib/parseCommandArgs";
const ITEMNAME = 'blaster'; 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); const result = await timeout(target, `You got blasted by ${user.displayName}!`, 60);
if (result.status) await Promise.all([ if (result.status) await Promise.all([
sendMessage(`GOTTEM ${target.displayName} got BLASTED by ${user.displayName} GOTTEM`), sendMessage(`GOTTEM ${target.displayName} got BLASTED by ${user.displayName} GOTTEM`),
createTimeoutRecord(user, target, ITEMNAME) createTimeoutRecord(user, target, ITEMNAME),
createCheerEventRecord(user, ITEMNAME)
]); ]);
else { else {
await handleNoTarget(msg, user, ITEMNAME); await handleNoTarget(msg, user, ITEMNAME);

View File

@@ -4,6 +4,7 @@ import { getUserRecord } from "db/dbUser";
import User from "user"; import User from "user";
import { timeout } from "lib/timeout"; import { timeout } from "lib/timeout";
import { createTimeoutRecord } from "db/dbTimeouts"; import { createTimeoutRecord } from "db/dbTimeouts";
import { createCheerEventRecord } from "db/dbCheerEvents";
import { getTNTTargets } from "items/tnt"; import { getTNTTargets } from "items/tnt";
import { redis } from "bun"; import { redis } from "bun";
@@ -22,6 +23,7 @@ export default new Cheer('tnt', 1000, async (msg, user) => {
redis.del(`user:${targetid}:vulnerable`), redis.del(`user:${targetid}:vulnerable`),
sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`), sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`),
createTimeoutRecord(user, target!, ITEMNAME), createTimeoutRecord(user, target!, ITEMNAME),
createCheerEventRecord(user, ITEMNAME)
]); ]);
})); }));

View File

@@ -3,7 +3,7 @@ import { addInvuln } from "lib/invuln";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import User from "user"; 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); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; }; if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; };
const target = await User.initUsername(args[0].toLowerCase()); const target = await User.initUsername(args[0].toLowerCase());

View File

@@ -4,7 +4,7 @@ import { removeInvuln } from "lib/invuln";
import parseCommandArgs from "lib/parseCommandArgs"; import parseCommandArgs from "lib/parseCommandArgs";
import User from "user"; 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); const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; }; if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; };
const target = await User.initUsername(args[0].toLowerCase()); const target = await User.initUsername(args[0].toLowerCase());

View File

@@ -34,11 +34,26 @@ export type timeoutRecord = {
created: string; 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 { interface TypedPocketBase extends PocketBase {
collection(idOrName: 'auth'): RecordService<authRecord>; collection(idOrName: 'auth'): RecordService<authRecord>;
collection(idOrName: 'users'): RecordService<userRecord>; collection(idOrName: 'users'): RecordService<userRecord>;
collection(idOrName: 'usedItems'): RecordService<usedItemRecord>; collection(idOrName: 'usedItems'): RecordService<usedItemRecord>;
collection(idOrName: 'timeouts'): RecordService<timeoutRecord>; collection(idOrName: 'timeouts'): RecordService<timeoutRecord>;
collection(idOrName: 'cheerEvents'): RecordService<cheerEventRecord>;
collection(idOrName: 'cheers'): RecordService<cheerRecord>;
}; };
export default new PocketBase(pocketbaseurl).autoCancellation(false) as TypedPocketBase; export default new PocketBase(pocketbaseurl).autoCancellation(false) as TypedPocketBase;

26
src/db/dbCheerEvents.ts Normal file
View File

@@ -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<void> {
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);
};
};

26
src/db/dbCheers.ts Normal file
View File

@@ -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<void> {
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);
};
};

View File

@@ -8,6 +8,8 @@ import cheers from "cheers";
import logger from "lib/logger"; import logger from "lib/logger";
import { addMessageToChatWidget } from "web/chatWidget/message"; import { addMessageToChatWidget } from "web/chatWidget/message";
import { isInvuln, setTemporaryInvuln } from "lib/invuln"; 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(', ')}`); 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) { 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); const selection = cheers.get(bits);
if (!selection) return; if (!selection) return;

View File

@@ -1,3 +1,4 @@
import { getCheerEvents } from "db/dbCheerEvents";
import { getTimeoutsAsTarget, getTimeoutsAsUser } from "db/dbTimeouts"; import { getTimeoutsAsTarget, getTimeoutsAsUser } from "db/dbTimeouts";
import { getItemsUsed } from "db/dbUsedItems"; import { getItemsUsed } from "db/dbUsedItems";
import type { inventory } from "items"; 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) { export async function getItemStats(target: User, thismonth: boolean) {
const monthdata = thismonth ? new Date().toISOString().slice(0, 7) : undefined; const monthdata = thismonth ? new Date().toISOString().slice(0, 7) : undefined;
const data = await getItemsUsed(target, monthdata); const [items, cheers] = await Promise.all([
if (!data) return; getItemsUsed(target, monthdata),
getCheerEvents(target, monthdata)
]);
if (!items || !cheers) return;
const returnObj: inventory = { const returnObj: inventory = {};
grenade: data.filter(use => use.item === 'grenade').length,
tnt: data.filter(use => use.item === 'tnt').length 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; return returnObj;

View File

@@ -14,7 +14,7 @@ export async function addInvuln(userid: string) {
export async function removeInvuln(userid: string) { export async function removeInvuln(userid: string) {
return await redis.del(`user:${userid}:invulnerable`); 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.set(`user:${userid}:invulnerable`, '1');
await redis.expire(`user:${userid}:invulnerable`, 600); await redis.expire(`user:${userid}:invulnerable`, duration);
}; };