Move Database from pocketbase to postgres

Move Database from pocketbase to postgres
This commit is contained in:
2025-09-19 02:08:25 +02:00
43 changed files with 694 additions and 474 deletions

View File

@@ -45,5 +45,5 @@ export default new Cheer('execute', 6666, async (msg, user) => {
break;
};
};
});
}, true);

View File

@@ -30,4 +30,4 @@ export default new Cheer('grenade', 99, async (msg, user) => {
target: target?.displayName!
})
]);
});
}, true);

View File

@@ -5,10 +5,12 @@ export class Cheer {
public readonly name: string;
public readonly amount: number;
public readonly execute: (msg: EventSubChannelChatMessageEvent, sender: User) => Promise<void>;
constructor(name: string, amount: number, execution: (msg: EventSubChannelChatMessageEvent, sender: User) => Promise<void>) {
public readonly isItem: boolean;
constructor(name: string, amount: number, execution: (msg: EventSubChannelChatMessageEvent, sender: User) => Promise<void>, isItem = false) {
this.name = name.toLowerCase();
this.amount = amount;
this.execute = execution;
this.isItem = isItem;
};
};
@@ -29,14 +31,12 @@ export default cheers;
export { namedcheers };
import { sendMessage } from 'commands';
import logger from 'lib/logger';
import { getUserRecord } from 'db/dbUser';
import { changeItemCount } from 'items';
import { changeItemCount, type items } from 'items';
export async function handleNoTarget(msg: EventSubChannelChatMessageEvent, user: User, itemname: string, silent = true) {
export async function handleNoTarget(msg: EventSubChannelChatMessageEvent, user: User, itemname: items, silent = true) {
if (await user.itemLock()) {
await sendMessage(`Cannot give ${user.displayName} a ${itemname}`, msg.messageId);
logger.err(`Failed to give ${user.displayName} a ${itemname} for their cheer`);
await sendMessage(`Cannot give ${user.displayName} a ${itemname} (itemlock)`, msg.messageId);
return;
};
await user.setLock();

View File

@@ -46,4 +46,4 @@ export default new Cheer('timeout', 100, async (msg, user) => {
break;
};
};
});
}, true);

View File

@@ -23,16 +23,18 @@ export default new Cheer('tnt', 1000, async (msg, user) => {
timeout(target!, `You got hit by ${user.displayName}'s TNT!`, 60),
redis.del(`user:${targetid}:vulnerable`),
sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s TNT wybuh`),
createTimeoutRecord(user, target!, ITEMNAME),
createCheerEventRecord(user, ITEMNAME),
createTimeoutRecord(user, target!, ITEMNAME)
]);
}));
await playAlert({
name: 'tntExplosion',
user: user.displayName,
targets
})
await Promise.all([
createCheerEventRecord(user, ITEMNAME),
playAlert({
name: 'tntExplosion',
user: user.displayName,
targets
})
]);
await sendMessage(`RIPBOZO ${user.displayName} exploded ${targets.length} chatter${targets.length === 1 ? '' : 's'} with their TNT RIPBOZO`);
});
}, true);

View File

@@ -16,8 +16,8 @@ export default new Command({
const userRecord = await getUserRecord(target);
if (!args[1]) { await sendMessage('Please specify the amount qweribucks you want to give', msg.messageId); return; };
const amount = parseInt(args[1]);
if (isNaN(amount)) { await sendMessage(`${args[1]} is not a valid amount`); return; };
if (await target.itemLock()) { await sendMessage('Cannot give qweribucks: item lock is set', msg.messageId); return; };
if (isNaN(amount)) { await sendMessage(`'${args[1]}' is not a valid amount`); return; };
if (await target.itemLock()) { await sendMessage('Cannot give qweribucks (itemlock)', msg.messageId); return; };
await target.setLock();
const data = await changeBalance(target, userRecord, amount);
if (!data) {

View File

@@ -19,8 +19,8 @@ export default new Command({
if (!item) { await sendMessage(`Item ${args[1]} doesn't exist`, msg.messageId); return; };
if (!args[2]) { await sendMessage('Please specify the amount of the item you want to give', msg.messageId); return; };
const amount = parseInt(args[2]);
if (isNaN(amount)) { await sendMessage(`${args[2]} is not a valid amount`); return; };
if (await target.itemLock()) { await sendMessage('Cannot give item: item lock is set', msg.messageId); return; };
if (isNaN(amount)) { await sendMessage(`'${args[2]}' is not a valid amount`); return; };
if (await target.itemLock()) { await sendMessage('Cannot give item (itemlock)', msg.messageId); return; };
await target.setLock();
const data = await changeItemCount(target, userRecord, item.name, amount);
if (data) {

View File

@@ -1,6 +1,5 @@
import { Command, sendMessage } from "commands";
import { getAllUserRecords } from "db/dbUser";
import { getTimeoutStats } from "lib/getStats";
import { getKDLeaderboard } from "db/dbUser";
import User from "user";
type KD = { user: User; kd: number; };
@@ -10,27 +9,19 @@ export default new Command({
aliases: ['alltimeleaderboard', 'alltimekdleaderboard'],
usertype: 'chatter',
execution: async msg => {
const users = await getAllUserRecords();
if (!users) return;
const userKDs: KD[] = [];
await Promise.all(users.map(async userRecord => {
const user = await User.initUserId(userRecord.id);
if (!user) return;
const data = await getTimeoutStats(user, false);
if (!data) return;
if (data.hit.blaster < 5) return;
let kd = data.shot.blaster / data.hit.blaster;
if (isNaN(kd)) kd = 0;
userKDs.push({ user, kd });
}));
if (userKDs.length === 0) {
const rawKD = await getKDLeaderboard();
if (rawKD.length === 0) {
await sendMessage(`No users on leaderboard yet!`, msg.messageId);
return;
};
const userKDs: KD[] = [];
await Promise.all(rawKD.map(async userRecord => {
const user = await User.initUserId(userRecord.userId.toString());
if (!user) return;
userKDs.push({ user, kd: userRecord.KD })
}));
userKDs.sort((a, b) => b.kd - a.kd);
const txt: string[] = [];

37
src/commands/buyitem.ts Normal file
View File

@@ -0,0 +1,37 @@
import { Command, sendMessage } from "commands";
import parseCommandArgs from "lib/parseCommandArgs";
import items from "items";
import { getUserRecord, updateUserRecord } from "db/dbUser";
export default new Command({
name: 'buyitem',
aliases: ['buyitem', 'buy', 'purchase'],
usertype: 'chatter',
execution: async (msg, user) => {
const args = parseCommandArgs(msg.messageText);
if (!args[0]) { await sendMessage(`Specify the item you'd like to buy`, msg.messageId); return; };
const selecteditem = items.get(args[0].toLowerCase());
if (!selecteditem) { await sendMessage(`'${args[0]}' is not a valid item`, msg.messageId); return; };
const amount = args[1] ? parseInt(args[1]) : 1;
if (isNaN(amount) || amount < 1) { await sendMessage(`'${args[1]}' is not a valid amount to buy`, msg.messageId); return; };
const totalcost = amount * selecteditem.price;
if (await user.itemLock()) { await sendMessage('Cannot buy item (itemlock)', msg.messageId); return; };
await user.setLock();
const userRecord = await getUserRecord(user);
if (userRecord.balance < totalcost) { await sendMessage(`You don't have enough qbucks to buy ${amount} ${selecteditem.prettyName}${amount === 1 ? '' : selecteditem.plural}! You have ${userRecord.balance}, need ${totalcost}`); await user.clearLock(); return; };
if (userRecord.inventory[selecteditem.name]) userRecord.inventory[selecteditem.name]! += amount
else userRecord.inventory[selecteditem.name] = amount;
userRecord.balance -= totalcost;
await Promise.all([
updateUserRecord(user, userRecord),
sendMessage(`${user.displayName} bought ${amount} ${selecteditem.prettyName}${amount === 1 ? '' : selecteditem.plural} for ${totalcost} qbucks. They now have ${userRecord.inventory[selecteditem.name]} ${selecteditem.prettyName}${userRecord.inventory[selecteditem.name] === 1 ? '' : selecteditem.plural} and ${userRecord.balance} qbucks`, msg.messageId)
]);
await user.clearLock();
}
});

View File

@@ -1,5 +1,4 @@
import { Command, sendMessage } from "commands";
import type { userRecord } from "db/connection";
import { getUserRecord } from "db/dbUser";
import parseCommandArgs from "lib/parseCommandArgs";
import { changeBalance } from "lib/changeBalance";
@@ -19,12 +18,12 @@ export default new Command({
const targetRecord = await getUserRecord(target);
if (!args[1]) { await sendMessage('Please specify the amount of the item you want to give', msg.messageId); return; };
const amount = parseInt(args[1]);
if (isNaN(amount) || amount < 1) { await sendMessage(`${args[1]} is not a valid amount`); return; };
if (isNaN(amount) || amount < 1) { await sendMessage(`'${args[1]}' is not a valid amount`); return; };
const userRecord = await getUserRecord(user);
if (userRecord.balance < amount) { await sendMessage(`You can't give qweribucks you don't have!`, msg.messageId); return; };
if (await user.itemLock() || await target.itemLock()) { await sendMessage('Cannot give qweribucks', msg.messageId); return; };
if (await user.itemLock() || await target.itemLock()) { await sendMessage('Cannot give qweribucks (itemlock)', msg.messageId); return; };
await Promise.all([
user.setLock(),
@@ -37,7 +36,7 @@ export default new Command({
]);
if (!data.includes(false)) {
const { balance: newamount } = data[0] as userRecord;
const { balance: newamount } = data[0];
await sendMessage(`${user.displayName} gave ${amount} qweribuck${amount === 1 ? '' : 's'} to ${target.displayName}. They now have ${newamount} qweribuck${newamount === 1 ? '' : 's'}`, msg.messageId);
} else {
// TODO: Rewrite this section

View File

@@ -8,7 +8,7 @@ export default new Command({
execution: async (_msg, user) => {
await Promise.all([
timeout(user, "NO MODME", 60),
sendMessage(`NO MODME COMMAND!!! UltraMad`)
sendMessage(`NO MODME COMMAND!!! UltraMad UltraMad UltraMad`)
]);
}
});

View File

@@ -1,7 +1,7 @@
import { redis } from "bun";
import { Command, sendMessage } from "commands";
import { getUserRecord, updateUserRecord } from "db/dbUser";
import items from "items";
import itemMap, { type inventory, type items } from "items";
import { buildTimeString } from "lib/dateManager";
import { timeout } from "lib/timeout";
import { isInvuln, removeInvuln } from "lib/invuln";
@@ -19,7 +19,7 @@ export default new Command({
if (await isInvuln(msg.chatterId) && !streamerUsers.includes(msg.chatterId)) { await sendMessage(`You're no longer an invuln because used a lootbox.`, msg.messageId); await removeInvuln(msg.chatterId); };
if (await user.itemLock()) { await sendMessage(`Cannot get loot (itemlock)`, msg.messageId); return; };
const userData = await getUserRecord(user);
const lastlootbox = Date.parse(userData.lastlootbox);
const lastlootbox = userData.lastlootbox.getTime();
const now = Date.now();
if ((lastlootbox + COOLDOWN) > now) {
if (await user.greedy()) {
@@ -40,26 +40,25 @@ export default new Command({
await user.clearGreed();
await user.setLock();
userData.lastlootbox = new Date(now).toISOString();
userData.lastlootbox = new Date(now);
const gainedqbucks = Math.floor(Math.random() * 100) + 50; // range from 50 to 150
userData.balance += gainedqbucks;
const itemDiff = {
const itemDiff: inventory = {
grenade: 0,
blaster: 0,
tnt: 0,
silverbullet: 0
};
for (let i = 0; i < 5; i++) {
if (Math.floor(Math.random() * 5) === 0) itemDiff.grenade += 1;
if (Math.floor(Math.random() * 5) === 0) itemDiff.blaster += 1;
if (Math.floor(Math.random() * 25) === 0) itemDiff.tnt += 1;
if (Math.floor(Math.random() * 250) === 0) itemDiff.silverbullet += 1;
for (let i = 0; i < 3; i++) {
if (Math.floor(Math.random() * 5) === 0) itemDiff.grenade! += 1;
if (Math.floor(Math.random() * 5) === 0) itemDiff.blaster! += 1;
if (Math.floor(Math.random() * 25) === 0) itemDiff.tnt! += 1;
if (Math.floor(Math.random() * 1000) === 0) itemDiff.silverbullet! += 1;
};
for (const [item, amount] of Object.entries(itemDiff)) {
for (const [item, amount] of Object.entries(itemDiff) as [items, number][]) {
if (userData.inventory[item]) userData.inventory[item] += amount;
else userData.inventory[item] = amount;
};
@@ -68,7 +67,7 @@ export default new Command({
for (const [item, amount] of Object.entries(itemDiff)) {
if (amount === 0) continue;
const selection = items.get(item);
const selection = itemMap.get(item);
if (!selection) continue;
itemstrings.push(`${amount} ${selection.prettyName + (amount === 1 ? '' : selection.plural)}`);
};

12
src/commands/getprices.ts Normal file
View File

@@ -0,0 +1,12 @@
import { Command, sendMessage } from "commands";
import { itemObjectArray } from "items";
export default new Command({
name: 'getprices',
aliases: ['getprices', 'prices', 'shop'],
usertype: 'chatter',
execution: async msg => {
const txt = itemObjectArray.toSorted((a, b) => a.price - b.price).map(item => `${item.prettyName}: ${item.price}`);
await sendMessage(`Prices: ${txt.join(' | ')}`, msg.messageId);
}
});

View File

@@ -1,5 +1,4 @@
import { Command, sendMessage } from "commands";
import type { userRecord } from "db/connection";
import { getUserRecord } from "db/dbUser";
import items, { changeItemCount } from "items";
import parseCommandArgs from "lib/parseCommandArgs";
@@ -22,11 +21,11 @@ export default new Command({
if (!item) { await sendMessage(`Item ${args[1]} doesn't exist`, msg.messageId); return; };
if (!args[2]) { await sendMessage('Please specify the amount of the item you want to give', msg.messageId); return; };
const amount = parseInt(args[2]);
if (isNaN(amount) || amount < 1) { await sendMessage(`${args[2]} is not a valid amount`); return; };
if (isNaN(amount) || amount < 1) { await sendMessage(`'${args[2]}' is not a valid amount`); return; };
const userRecord = await getUserRecord(user);
if (userRecord.inventory[item.name]! < amount) { await sendMessage(`You can't give items you don't have!`, msg.messageId); return; };
if (await user.itemLock() || await target.itemLock()) { await sendMessage('Cannot give item', msg.messageId); return; };
if (await user.itemLock() || await target.itemLock()) { await sendMessage('Cannot give item (itemlock)', msg.messageId); return; };
await Promise.all([
user.setLock(),
@@ -38,14 +37,14 @@ export default new Command({
await changeItemCount(user, userRecord, item.name, -amount)
]);
if (!data.includes(false)) {
const tempdata = data[0] as userRecord;
if (data[0] !== false && data[1] !== false) {
const tempdata = data[0];
const newamount = tempdata.inventory[item.name]!;
await sendMessage(`${user.displayName} gave ${amount} ${item.prettyName + (amount === 1 ? '' : item.plural)} to ${target.displayName}. They now have ${newamount} ${item.prettyName + (newamount === 1 ? '' : item.plural)}`, msg.messageId);
} else {
// TODO: Rewrite this section
await sendMessage(`Failed to give ${target.displayName} ${amount} ${item.prettyName + (amount === 1 ? '' : item.plural)}`, msg.messageId);
logger.warn(`WARNING: Item donation failed: target success: ${data[0] !== false}, donator success: ${data[1] !== false}`);
logger.warn(`WARNING: Item donation failed: target success: ${data[0] !== false ? "yes" : "no"}, donator success: ${data[1] !== false ? "yes" : "no"}`);
};
await user.clearLock();
await target.clearLock();

View File

@@ -1,6 +1,5 @@
import { Command, sendMessage } from "commands";
import { getAllUserRecords } from "db/dbUser";
import { getTimeoutStats } from "lib/getStats";
import { getKDLeaderboard } from "db/dbUser";
import User from "user";
type KD = { user: User; kd: number; };
@@ -10,27 +9,21 @@ export default new Command({
aliases: ['monthlyleaderboard', 'kdleaderboard', 'leaderboard'],
usertype: 'chatter',
execution: async msg => {
const users = await getAllUserRecords();
if (!users) return;
const monthdata = new Date().toISOString().slice(0, 7);
const userKDs: KD[] = [];
await Promise.all(users.map(async userRecord => {
const user = await User.initUserId(userRecord.id);
if (!user) return;
const data = await getTimeoutStats(user, true);
if (!data) return;
if (data.hit.blaster < 5) return;
let kd = data.shot.blaster / data.hit.blaster;
if (isNaN(kd)) kd = 0;
userKDs.push({ user, kd });
}));
if (userKDs.length === 0) {
const rawKD = await getKDLeaderboard(monthdata);
if (rawKD.length === 0) {
await sendMessage(`No users on leaderboard yet!`, msg.messageId);
return;
};
const userKDs: KD[] = [];
await Promise.all(rawKD.map(async userRecord => {
const user = await User.initUserId(userRecord.userId.toString());
if (!user) return;
userKDs.push({ user, kd: userRecord.KD })
}));
userKDs.sort((a, b) => b.kd - a.kd);
const txt: string[] = [];

View File

@@ -14,7 +14,7 @@ export default new Command({
const txt: string[] = [];
for (const userRecord of data) {
if (userRecord.balance === 0) continue;
const user = await User.initUserId(userRecord.id);
const user = await User.initUserId(userRecord.id.toString());
if (!user) continue;
txt.push(`${index}. ${user.displayName}: ${userRecord.balance}`);
index++;

View File

@@ -1,46 +0,0 @@
import { Command, sendMessage } from "commands";
import { getUserRecord } from "db/dbUser";
import parseCommandArgs from "lib/parseCommandArgs";
import User from "user";
import { timeout } from "lib/timeout";
import { changeBalance } from "lib/changeBalance";
import { createTimeoutRecord } from "db/dbTimeouts";
export default new Command({
name: 'timeout',
aliases: ['timeout'],
usertype: 'chatter',
execution: async (msg, user) => {
const userObj = await getUserRecord(user);
if (userObj.balance < 100) { await sendMessage(`You don't have enough qweribucks (need 100, have ${userObj.balance})`, msg.messageId); return; };
const messagequery = parseCommandArgs(msg.messageText);
if (!messagequery[0]) { await sendMessage('Please specify a target'); return; };
const target = await User.initUsername(messagequery[0].toLowerCase());
if (!target) { await sendMessage(`${messagequery[0]} doesn't exist`); return; };
await getUserRecord(target); // make sure the user record exist in the database
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`),
changeBalance(user, userObj, -100),
createTimeoutRecord(user, target, 'blaster')
]);
} else {
switch (result.reason) {
case "banned":
await sendMessage(`${target.displayName} is already timed out/banned`, msg.messageId);
break;
case "illegal":
await Promise.all([
sendMessage(`${user.displayName} Nou Nou Nou`),
timeout(user, 'nah', 60)
]);
break;
case "unknown":
await sendMessage('Something went wrong...', msg.messageId);
break;
};
};
}
});

View File

@@ -1,12 +1,13 @@
import pocketbase from "db/connection";
import { RedisClient } from "bun";
import db from "db/connection";
import { users } from "db/schema";
import logger from "lib/logger";
export async function connectionCheck() {
let pbstatus = false;
let pgstatus = false;
try {
await pocketbase.health.check().then(a => a.code === 200);
pbstatus = true;
await db.select().from(users); // The query doesn't matter, only that it fails. This also fails if the migration hasn't taken place
pgstatus = true;
} catch { };
const tempclient = new RedisClient(undefined, {
connectionTimeout: 100,
@@ -18,7 +19,7 @@ export async function connectionCheck() {
redisstatus = true;
} catch { };
logger.info(`Currently using the "${process.env.NODE_ENV ?? "production"}" database`);
pbstatus ? logger.ok(`Pocketbase status: good`) : logger.err(`Pocketbase status: bad`);
pgstatus ? logger.ok(`Postgresql status: good`) : logger.err(`Postgresql status: bad`);
redisstatus ? logger.ok(`Redis/Valkey status: good`) : logger.err(`Redis/Valkey status: bad`);
if (!pbstatus || !redisstatus) process.exit(1);
if (!pgstatus || !redisstatus) process.exit(1);
};

View File

@@ -1,75 +1,15 @@
import type { AccessToken } from "@twurple/auth";
import PocketBase, { RecordService } from "pocketbase";
import type { inventory } from "items";
import * as schema from "db/schema";
import logger from "lib/logger";
const pocketbaseurl = process.env.POCKETBASE_URL ?? "localhost:8090";
if (pocketbaseurl === "") { logger.enverr("POCKETBASE_URL"); process.exit(1); };
const host = process.env.POSTGRES_HOST ?? "";
if (!host) { logger.enverr("POSTGRES_HOST"); process.exit(1); };
const user = process.env.POSTGRES_USER ?? "";
if (!user) { logger.enverr("POSTGRES_USER"); process.exit(1); };
const password = process.env.POSTGRES_PASSWORD ?? "";
if (!password) { logger.enverr("POSTGRES_USER"); process.exit(1); };
const database = process.env.POSTGRES_DB ?? "";
if (!database) { logger.enverr("POSTGRES_DB"); process.exit(1); };
const url = `postgresql://${user}:${password}@${host}/${database}`;
export type authRecord = {
id: string;
accesstoken: AccessToken;
};
export type userRecord = {
id: string;
username: string; // Don't use this, Use User.username or User.displayName. This is just to make the pocketbase data easier to read.
balance: number;
inventory: inventory;
lastlootbox: string;
};
export type usedItemRecord = {
id?: string;
user: string;
item: string;
created: string;
};
export type timeoutRecord = {
id?: string;
user: string;
target: string;
item: string;
created: string;
};
export type cheerEventRecord = {
id?: string;
user: string;
cheer: string;
created: string;
};
export type cheerRecord = {
id?: string;
user: string;
amount: number;
};
export type anivTimeoutRecord = {
id?: string;
message: string;
user: string;
duration: number;
};
export type getLootRecord = {
id?: string;
user: string;
qbucks: number;
items: inventory;
};
interface TypedPocketBase extends PocketBase {
collection(idOrName: 'auth'): RecordService<authRecord>;
collection(idOrName: 'users'): RecordService<userRecord>;
collection(idOrName: 'usedItems'): RecordService<usedItemRecord>;
collection(idOrName: 'timeouts'): RecordService<timeoutRecord>;
collection(idOrName: 'cheerEvents'): RecordService<cheerEventRecord>;
collection(idOrName: 'cheers'): RecordService<cheerRecord>;
collection(idOrName: 'anivTimeouts'): RecordService<anivTimeoutRecord>;
collection(idOrName: 'getLoots'): RecordService<getLootRecord>;
};
export default new PocketBase(pocketbaseurl).autoCancellation(false) as TypedPocketBase;
import { drizzle } from 'drizzle-orm/bun-sql';
export default drizzle(url, { schema });

View File

@@ -1,14 +1,13 @@
import pocketbase from "db/connection";
import db from "db/connection";
import User from "user";
import logger from "lib/logger";
import { anivTimeouts } from "db/schema";
import { type anivBots } from "lib/handleAnivMessage";
const pb = pocketbase.collection('anivTimeouts');
export async function createAnivTimeoutRecord(message: string, user: User, duration: number) {
try {
await pb.create({ message, user: user.id, duration });
} catch (e) {
logger.err(`Failed to create anivTimeoutRecord: user: ${user.displayName} message: "${message}" duration: ${duration}`);
logger.err(e as string);
};
export async function createAnivTimeoutRecord(message: string, anivBot: anivBots, user: User, duration: number) {
await db.insert(anivTimeouts).values({
message,
anivBot,
user: parseInt(user.id),
duration
});
};

View File

@@ -1,38 +1,28 @@
import type { AccessToken } from "@twurple/auth";
import pocketbase, { type authRecord } from "db/connection";
const pb = pocketbase.collection('auth');
import db from "db/connection";
import { auth } from "db/schema";
import { eq } from "drizzle-orm";
export async function createAuthRecord(token: AccessToken, userId: string) {
try {
const data: authRecord = {
accesstoken: token,
id: userId
};
await pb.create(data);
} catch (err) { };
await db.insert(auth).values({
id: parseInt(userId),
accesstoken: token
});
};
export async function getAuthRecord(userId: string, requiredIntents: string[]) {
try {
const data = await pb.getOne(userId);
if (!requiredIntents.every(intent => data.accesstoken.scope.includes(intent))) return undefined;
return { accesstoken: data.accesstoken };
} catch (err) {
return undefined;
};
const data = await db.query.auth.findFirst({
where: eq(auth.id, parseInt(userId))
});
if (!data) return undefined;
if (!requiredIntents.every(intent => data.accesstoken.scope.includes(intent))) return undefined;
return { accesstoken: data.accesstoken };
};
export async function updateAuthRecord(userId: string, newtoken: AccessToken) {
try {
const newrecord = {
accesstoken: newtoken,
};
await pb.update(userId, newrecord);
} catch (err) { };
await db.update(auth).set({ accesstoken: newtoken }).where(eq(auth.id, parseInt(userId)));
};
export async function deleteAuthRecord(userId: string): Promise<void> {
try {
await pb.delete(userId);
} catch (err) { };
await db.delete(auth).where(eq(auth.id, parseInt(userId)));
};

View File

@@ -1,26 +1,20 @@
import pocketbase from "db/connection";
import db from "db/connection";
import { cheerEvents } from "db/schema";
import { and, between, eq, SQL } from "drizzle-orm";
import type { items } from "items";
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 createCheerEventRecord(user: User, cheer: items): Promise<void> {
await db.insert(cheerEvents).values({ user: parseInt(user.id), event: cheer });
};
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);
let condition: SQL<unknown> | undefined = eq(cheerEvents.user, parseInt(user.id));
if (monthData) {
const begin = Date.parse(monthData);
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
condition = and(condition, between(cheerEvents.created, new Date(begin), new Date(end)));
};
const data = await db.select().from(cheerEvents).where(condition);
return data;
};

View File

@@ -1,26 +1,19 @@
import pocketbase from "db/connection";
import db from "db/connection";
import { cheers } from "db/schema";
import User from "user";
import logger from "lib/logger";
const pb = pocketbase.collection('cheers');
import { and, between, eq, SQL } from "drizzle-orm";
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);
};
await db.insert(cheers).values({ user: parseInt(user.id), amount });
};
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);
let condition: SQL<unknown> | undefined = eq(cheers.user, parseInt(user.id));
if (monthData) {
const begin = Date.parse(monthData);
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
condition = and(condition, between(cheers.created, new Date(begin), new Date(end)));
};
const data = await db.select().from(cheers).where(condition);
return data;
};

View File

@@ -1,19 +1,12 @@
import pocketbase from "db/connection";
import db from "db/connection";
import { getLoots } from "db/schema";
import type { inventory } from "items";
import logger from "lib/logger";
import type User from "user";
const pb = pocketbase.collection('getLoots');
export async function createGetLootRecord(user: User, qbucks: number, inventory: inventory) {
try {
await pb.create({
user: user.id,
qbucks,
items: inventory
});
} catch (e) {
logger.err(`Failed to create getLoot record for ${user.displayName}: ${inventory}`);
logger.err(e as string);
};
await db.insert(getLoots).values({
user: parseInt(user.id),
qbucks: qbucks,
items: inventory
});
};

View File

@@ -1,39 +1,35 @@
import pocketbase from "db/connection";
import db from "db/connection";
import { timeouts } from "db/schema";
import User from "user";
import logger from "lib/logger";
const pb = pocketbase.collection('timeouts');
import type { items } from "items";
import { and, between, eq, type SQL } from "drizzle-orm";
export async function createTimeoutRecord(user: User, target: User, item: string): Promise<void> {
try {
await pb.create({ user: user.id, target: target.id, item });
} catch (err) {
logger.err(`Failed to create timeout record in database: user: ${user.id}, target: ${target.id}, item: ${item}`);
logger.err(err as string);
};
export async function createTimeoutRecord(user: User, target: User, item: items): Promise<void> {
await db.insert(timeouts).values({
user: parseInt(user.id),
target: parseInt(target.id),
item
});
};
export async function getTimeoutsAsUser(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 timeouts as user: ${user.id}, month: ${monthData}`);
logger.err(e as string);
let condition: SQL<unknown> | undefined = eq(timeouts.user, parseInt(user.id));
if (monthData) {
const begin = Date.parse(monthData);
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
condition = and(condition, between(timeouts.created, new Date(begin), new Date(end)));
};
const data = await db.select().from(timeouts).where(condition);
return data;
};
export async function getTimeoutsAsTarget(user: User, monthData?: string) {
try {
const monthquery = monthData ? ` && created~"${monthData}"` : '';
const data = await pb.getFullList({
filter: `target="${user.id}"${monthquery}`
});
return data;
} catch (e) {
logger.err(`Failed to get timeouts as target: ${user.id}, month: ${monthData}`);
logger.err(e as string);
let condition: SQL<unknown> | undefined = eq(timeouts.target, parseInt(user.id));
if (monthData) {
const begin = Date.parse(monthData);
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
condition = and(condition, between(timeouts.created, new Date(begin), new Date(end)));
};
const data = await db.select().from(timeouts).where(condition);
return data;
};

View File

@@ -1,27 +1,20 @@
import pocketbase from "db/connection";
import db from "db/connection";
import { usedItems } from "db/schema";
import User from "user";
import logger from "lib/logger";
const pb = pocketbase.collection('usedItems');
import type { items } from "items";
import { and, between, eq, type SQL } from "drizzle-orm";
export async function createUsedItemRecord(user: User, item: string): Promise<void> {
try {
await pb.create({ user: user.id, item });
} catch (err) {
logger.err(`Failed to create usedItem record in database: user: ${user.id}, item: ${item}`);
logger.err(err as string);
};
export async function createUsedItemRecord(user: User, item: items): Promise<void> {
await db.insert(usedItems).values({ user: parseInt(user.id), item });
};
export async function getItemsUsed(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 items used for user: ${user.id}, month: ${monthData}`);
logger.err(e as string);
let condition: SQL<unknown> | undefined = eq(usedItems.user, parseInt(user.id));
if (monthData) {
const begin = Date.parse(monthData);
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
condition = and(condition, between(usedItems.created, new Date(begin), new Date(end)));
};
const data = await db.select().from(usedItems).where(condition);
return data;
};

View File

@@ -1,58 +1,92 @@
import pocketbase, { type userRecord } from "db/connection";
import { emptyInventory, itemarray } from "items";
import db from "db/connection";
import { timeouts, users } from "db/schema";
import { itemarray, type inventory } from "items";
import type User from "user";
import logger from "lib/logger";
const pb = pocketbase.collection('users');
import { count, desc, eq, inArray, sql, ne, between, and, SQL } from "drizzle-orm";
/** Use this function to both ensure existance and to retreive data */
export async function getUserRecord(user: User): Promise<userRecord> {
try {
const data = await pb.getOne(user.id);
export async function getUserRecord(user: User) {
const data = await db.query.users.findFirst({ where: eq(users.id, parseInt(user.id)) });
if (!data) return createUserRecord(user);
if (Object.keys(data.inventory).sort().toString() !== itemarray.sort().toString()) { // If the items in the user inventory are missing an item.
itemarray.forEach(key => {
if (!(key in data.inventory)) data.inventory[key] = 0;
});
};
return data;
} catch (err) {
// This gets triggered if the user doesn't exist in the database
return await createUserRecord(user);
if (Object.keys(data.inventory).sort().toString() !== itemarray.sort().toString()) { // If the items in the user inventory are missing an item.
itemarray.forEach(key => {
if (!(key in data.inventory)) data.inventory[key] = 0;
});
};
};
export async function getAllUserRecords(): Promise<userRecord[]> {
return await pb.getFullList();
};
async function createUserRecord(user: User): Promise<userRecord> {
const data = await pb.create({
id: user.id,
username: user.username,
balance: 0,
inventory: emptyInventory,
lastlootbox: new Date(0).toISOString()
});
return data;
};
export async function updateUserRecord(user: User, newData: userRecord): Promise<boolean> {
try {
await pb.update(user.id, newData);
return true;
} catch (err) {
logger.err(err as string);
return false;
};
export async function getAllUserRecords() {
return await db.select().from(users);
};
async function createUserRecord(user: User) {
return await db.insert(users).values({
id: parseInt(user.id),
username: user.username
}).returning().then(a => {
if (!a[0]) throw Error('Something went horribly wrong');
return a[0]
});
};
export type balanceUpdate = { balance: number; };
export type inventoryUpdate = { inventory: inventory; };
type updateUser = balanceUpdate | inventoryUpdate;
export async function updateUserRecord(user: User, newData: updateUser) {
await db.update(users).set(newData).where(eq(users.id, parseInt(user.id)));
return true;
};
export async function getBalanceLeaderboard() {
try {
return await pb.getList(1, 10, { sort: '-balance,id' }).then(a => a.items);
} catch (err) {
logger.err(err as string);
};
return await db.select().from(users).orderBy(desc(users.balance)).limit(10);
};
export async function getKDLeaderboard(monthData?: string) {
let condition: SQL<unknown> | undefined = ne(timeouts.item, 'silverbullet');
if (monthData) {
const begin = Date.parse(monthData);
const end = new Date(begin).setMonth(new Date(begin).getMonth() + 1);
condition = and(condition, between(timeouts.created, new Date(begin), new Date(end)));
};
const usersGotShot = await db.select({
userId: users.id,
amount: count(timeouts.target),
})
.from(users)
.innerJoin(timeouts, eq(users.id, timeouts.target))
.groupBy(users.id)
.having(sql`count(${timeouts.id}) > 5`)
.where(condition);
const usersThatShot = await db.select({
userId: users.id,
amount: count(timeouts.user)
})
.from(users)
.innerJoin(timeouts, eq(users.id, timeouts.user))
.groupBy(users.id)
.where(
and(
condition,
inArray(users.id, usersGotShot.map(a => a.userId))
)
);
const lookup = new Map(usersThatShot.map(a => [a.userId, a.amount]));
const result = usersGotShot.map(user => ({
userId: user.userId,
KD: lookup.get(user.userId)! / user.amount
}));
result.map(user => {
if (isNaN(user.KD)) user.KD = 0;
return user
});
return result;
};

122
src/db/schema.ts Normal file
View File

@@ -0,0 +1,122 @@
import type { AccessToken } from "@twurple/auth";
import type { inventory, items } from "items";
import { integer, jsonb, pgTable, timestamp, uuid, varchar } from "drizzle-orm/pg-core";
import type { anivBots } from "lib/handleAnivMessage";
import { relations } from "drizzle-orm";
export const auth = pgTable('auth', {
id: integer().primaryKey(),
accesstoken: jsonb().$type<AccessToken>().notNull()
});
export const users = pgTable('users', {
id: integer().primaryKey().notNull(),
username: varchar().notNull(),
balance: integer().default(0).notNull(),
inventory: jsonb().$type<inventory>().default({}).notNull(),
lastlootbox: timestamp().default(new Date(0)).notNull()
});
export const usersRelations = relations(users, ({ many }) => ({
timeouts_target: many(timeouts),
timeouts_shooter: many(timeouts),
usedItems: many(usedItems),
cheerEvents: many(cheerEvents),
cheers: many(cheers),
anivTimeouts: many(anivTimeouts),
getLoots: many(getLoots)
}));
export const timeouts = pgTable('timeouts', {
id: uuid().defaultRandom().primaryKey(),
user: integer().notNull().references(() => users.id),
target: integer().notNull().references(() => users.id),
item: varchar().$type<items>().notNull(),
created: timestamp().defaultNow().notNull()
});
export const timeoutsRelations = relations(timeouts, ({ one }) => ({
user: one(users, {
fields: [timeouts.user],
references: [users.id],
relationName: 'shooter'
}),
target: one(users, {
fields: [timeouts.target],
references: [users.id],
relationName: 'target'
})
}))
export const usedItems = pgTable('usedItems', {
id: uuid().defaultRandom().primaryKey(),
user: integer().notNull().references(() => users.id),
item: varchar().$type<items>().notNull(),
created: timestamp().defaultNow().notNull()
});
export const usedItemsRelations = relations(usedItems, ({ one }) => ({
user: one(users, {
fields: [usedItems.user],
references: [users.id]
})
}));
export const cheerEvents = pgTable('cheerEvents', {
id: uuid().defaultRandom().primaryKey(),
user: integer().notNull().references(() => users.id),
event: varchar().$type<items>().notNull(),
created: timestamp().defaultNow().notNull()
});
export const cheerEventsRelations = relations(cheerEvents, ({ one }) => ({
user: one(users, {
fields: [cheerEvents.user],
references: [users.id]
})
}));
export const cheers = pgTable('cheers', {
id: uuid().defaultRandom().primaryKey(),
user: integer().notNull().references(() => users.id),
amount: integer().notNull(),
created: timestamp().defaultNow().notNull()
});
export const cheersRelations = relations(cheers, ({ one }) => ({
user: one(users, {
fields: [cheers.user],
references: [users.id]
})
}));
export const anivTimeouts = pgTable('anivTimeouts', {
id: uuid().defaultRandom().primaryKey(),
user: integer().notNull().references(() => users.id),
message: varchar().notNull(),
anivBot: varchar().$type<anivBots>().notNull(),
duration: integer().notNull(),
created: timestamp().defaultNow().notNull()
});
export const anivTimeoutsRelations = relations(anivTimeouts, ({ one }) => ({
user: one(users, {
fields: [anivTimeouts.user],
references: [users.id]
})
}));
export const getLoots = pgTable('getLoots', {
id: uuid().defaultRandom().primaryKey(),
user: integer().notNull().references(() => users.id),
qbucks: integer().notNull(),
items: jsonb().$type<inventory>().notNull(),
created: timestamp().defaultNow().notNull()
});
export const getLootsRelations = relations(getLoots, ({ one }) => ({
user: one(users, {
fields: [getLoots.user],
references: [users.id]
})
}));

View File

@@ -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) return;
if (msg.chatterId === chatterId && chatterId !== streamerId) return;
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`);
@@ -68,9 +68,7 @@ async function handleChatMessage(msg: EventSubChannelChatMessageEvent, user: Use
};
try {
await selection.execute(msg, user, {
activation
});
await selection.execute(msg, user, { activation });
}
catch (err) {
logger.err(err as string);
@@ -105,9 +103,11 @@ export async function handleCheer(msg: EventSubChannelChatMessageEvent, bits: nu
if (!selection) return;
if (await redis.sismember('disabledcheers', selection.name)) { await sendMessage(`The ${selection.name} cheer is disabled! Sorry!`, msg.messageId); return; };
if (selection.isItem && await isInvuln(user.id) && !streamerUsers.includes(user.id)) { await sendMessage(`${user.displayName} Is no longer an invuln`); await removeInvuln(user.id); };
try {
selection.execute(msg, user);
await selection.execute(msg, user);
} catch (err) {
await sendMessage(`[ERROR]: Something went wrong with cheer execution`);
logger.err(err as string);
};
};

View File

@@ -16,17 +16,20 @@ export default new Item({
plural: 's',
description: 'Times a specific person out for 60 seconds',
aliases: ['blaster', 'blast'],
price: 100,
execution: async (msg, user) => {
const userObj = await getUserRecord(user);
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any blasters!`, msg.messageId); return; };
const messagequery = parseCommandArgs(msg.messageText);
if (!messagequery[0]) { await sendMessage('Please specify a target'); return; };
const target = await User.initUsername(messagequery[0].toLowerCase());
if (!target) { await sendMessage(`${messagequery[0]} doesn't exist`); return; };
await getUserRecord(target); // make sure the user record exist in the database
if (await user.itemLock()) { await sendMessage('Cannot use an item right now', msg.messageId); return; };
if (await user.itemLock()) { await sendMessage('Cannot use an item (itemlock)', msg.messageId); return; };
await user.setLock();
const userObj = await getUserRecord(user);
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any blasters!`, msg.messageId); await user.clearLock(); return; };
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`),

View File

@@ -16,9 +16,8 @@ export default new Item({
plural: 's',
description: 'Give a random chatter a 60s timeout',
aliases: ['grenade'],
price: 99,
execution: async (msg, user) => {
const userObj = await getUserRecord(user);
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any grenades!`, msg.messageId); return; };
const targets = await redis.keys(`user:*:vulnerable`);
if (targets.length === 0) { await sendMessage('No vulnerable chatters to blow up', msg.messageId); return; };
const selection = targets[Math.floor(Math.random() * targets.length)]!;
@@ -26,8 +25,12 @@ export default new Item({
await getUserRecord(target!); // make sure the user record exist in the database
if (await user.itemLock()) { await sendMessage('Cannot use an item right now', msg.messageId); return; };
if (await user.itemLock()) { await sendMessage('Cannot use an item (itemlock)', msg.messageId); return; };
await user.setLock();
const userObj = await getUserRecord(user);
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any grenades!`, msg.messageId); await user.clearLock(); return; };
await Promise.all([
timeout(target!, `You got hit by ${user.displayName}'s grenade!`, 60),
sendMessage(`wybuh ${target?.displayName} got hit by ${user.displayName}'s grenade wybuh`),

View File

@@ -3,29 +3,31 @@ import User from "user";
import { type userType, type specialExecuteArgs } from "commands";
type itemOptions = {
name: string;
name: items;
aliases: string[];
prettyName: string;
plural: string;
description: string;
execution: (message: EventSubChannelChatMessageEvent, sender: User, args?: specialExecuteArgs) => Promise<void>;
specialaliases?: string[];
price: number;
};
export class Item {
public readonly name: string;
public readonly name: items;
public readonly prettyName: string;
public readonly plural: string;
public readonly description: string;
public readonly aliases: string[];
public readonly specialaliases: string[];
public readonly usertype: userType;
public readonly price: number;
public readonly execute: (message: EventSubChannelChatMessageEvent, sender: User, args?: specialExecuteArgs) => Promise<void>;
public readonly disableable: boolean;
/** Creates an item object */
constructor(options: itemOptions) {
this.name = options.name.toLowerCase();
this.name = options.name;
this.prettyName = options.prettyName;
this.plural = options.plural;
this.description = options.description;
@@ -34,16 +36,17 @@ export class Item {
this.execute = options.execution;
this.disableable = true;
this.specialaliases = options.specialaliases ?? [];
this.price = options.price;
};
};
import { readdir } from 'node:fs/promises';
import type { userRecord } from "db/connection";
import { updateUserRecord } from "db/dbUser";
const items = new Map<string, Item>;
import { updateUserRecord, type inventoryUpdate } from "db/dbUser";
const itemAliasMap = new Map<string, Item>;
const itemObjectArray: Item[] = []
const specialAliasItems = new Map<string, Item>;
const emptyInventory: inventory = {};
const itemarray: string[] = [];
const itemarray: items[] = [];
const files = await readdir(import.meta.dir);
for (const file of files) {
@@ -52,21 +55,24 @@ for (const file of files) {
const item: Item = await import(import.meta.dir + '/' + file.slice(0, -3)).then(a => a.default);
emptyInventory[item.name] = 0;
itemarray.push(item.name);
itemObjectArray.push(item);
for (const alias of item.aliases) {
items.set(alias, item); // Since it's not a primitive type the map is filled with references to the item, not the actual object
itemAliasMap.set(alias, item); // Since it's not a primitive type the map is filled with references to the item, not the actual object
};
for (const alias of item.specialaliases) {
specialAliasItems.set(alias, item);
};
};
export default items;
export { emptyInventory, itemarray, specialAliasItems };
export default itemAliasMap;
export { emptyInventory, itemarray, specialAliasItems, itemObjectArray };
export type items = "blaster" | "silverbullet" | "grenade" | "tnt";
export type inventory = {
[key: string]: number;
[key in items]?: number;
};
export async function changeItemCount(user: User, userRecord: userRecord, itemname: string, amount = -1): Promise<false | userRecord> {
export async function changeItemCount(user: User, userRecord: inventoryUpdate, itemname: items, amount = -1): Promise<false | inventoryUpdate> {
userRecord.inventory[itemname] = userRecord.inventory[itemname]! += amount;
if (userRecord.inventory[itemname] < 0) return false;
await updateUserRecord(user, userRecord);

View File

@@ -17,17 +17,20 @@ export default new Item({
description: 'Times a specific person out for 24 hours',
aliases: ['execute', 'silverbullet'],
specialaliases: ['blastin'],
price: 6666,
execution: async (msg, user, specialargs) => {
const userObj = await getUserRecord(user);
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any silver bullets!`, msg.messageId); return; };
const messagequery = parseCommandArgs(msg.messageText);
const messagequery = parseCommandArgs(msg.messageText, specialargs?.activation);
if (!messagequery[0]) { await sendMessage('Please specify a target'); return; };
const target = await User.initUsername(messagequery[0].toLowerCase());
if (!target) { await sendMessage(`${messagequery[0]} doesn't exist`); return; };
await getUserRecord(target); // make sure the user record exist in the database
if (await user.itemLock()) { await sendMessage('Cannot use an item right now', msg.messageId); return; };
if (await user.itemLock()) { await sendMessage('Cannot use an item (itemlock)', msg.messageId); return; };
await user.setLock();
const userObj = await getUserRecord(user);
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any silver bullets!`, msg.messageId); await user.clearLock(); return; };
const result = await timeout(target, `You got blasted by ${user.displayName}!`, 60 * 60 * 24);
if (result.status) await Promise.all([
sendMessage(`${target.displayName} RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO RIPBOZO`),

View File

@@ -16,16 +16,18 @@ export default new Item({
plural: 's',
description: 'Give 5-10 random chatters 60 second timeouts',
aliases: ['tnt'],
price: 1000,
execution: async (msg, user) => {
const userObj = await getUserRecord(user);
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any TNTs!`, msg.messageId); return; };
const vulntargets = await redis.keys('user:*:vulnerable').then(a => a.map(b => b.slice(5, -11)));
if (vulntargets.length === 0) { await sendMessage('No vulnerable chatters to blow up', msg.messageId); return; };
const targets = getTNTTargets(vulntargets);
if (await user.itemLock()) { await sendMessage('Cannot use an item right now', msg.messageId); return; };
if (await user.itemLock()) { await sendMessage('Cannot use an item (itemlock)', msg.messageId); return; };
await user.setLock();
const userObj = await getUserRecord(user);
if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any TNTs!`, msg.messageId); await user.clearLock(); return; };
await Promise.all(targets.map(async targetid => {
const target = await User.initUserId(targetid);
await getUserRecord(target!); // make sure the user record exist in the database
@@ -45,6 +47,7 @@ export default new Item({
}),
changeItemCount(user, userObj, ITEMNAME)
]);
await user.clearLock();
await sendMessage(`RIPBOZO ${user.displayName} exploded ${targets.length} chatter${targets.length === 1 ? '' : 's'} with their TNT RIPBOZO`);
}

View File

@@ -40,7 +40,12 @@ export async function getItemStats(target: User, thismonth: boolean) {
]);
if (!items || !cheers) return;
const returnObj: inventory = {};
const returnObj: inventory = {
blaster: 0,
silverbullet: 0,
grenade: 0,
tnt: 0,
};
for (const item of items) {
if (!returnObj[item.item]) returnObj[item.item] = 0;
@@ -48,8 +53,8 @@ export async function getItemStats(target: User, thismonth: boolean) {
};
for (const cheer of cheers) {
if (!returnObj[cheer.cheer]) returnObj[cheer.cheer] = 0;
returnObj[cheer.cheer]! += 1
if (!returnObj[cheer.event]) returnObj[cheer.event] = 0;
returnObj[cheer.event]! += 1
};
return returnObj;

View File

@@ -5,7 +5,7 @@ import { timeout } from "lib/timeout";
import { sendMessage } from "commands";
import { createAnivTimeoutRecord } from "db/dbAnivTimeouts";
const ANIVNAMES = ['a_n_e_e_v', 'a_n_i_v'];
const ANIVNAMES: anivBots[] = ['a_n_e_e_v', 'a_n_i_v'];
type anivMessageStore = {
[key: string]: string;
@@ -14,13 +14,15 @@ type anivMessageStore = {
type IsAnivMessage = {
isAnivMessage: true;
message: string;
anivbot: string;
anivbot: anivBots;
};
type isNotAnivMessage = {
isAnivMessage: false;
};
export type anivBots = 'a_n_i_v' | 'a_n_e_e_v';
type anivMessageResult = IsAnivMessage | isNotAnivMessage;
async function isAnivMessage(message: string): Promise<anivMessageResult> {
@@ -34,7 +36,7 @@ async function isAnivMessage(message: string): Promise<anivMessageResult> {
};
export default async function handleMessage(msg: EventSubChannelChatMessageEvent, user: User) {
if (ANIVNAMES.includes(user.displayName)) {
if (ANIVNAMES.map(a => a.toLowerCase()).includes(user.username)) {
const data: anivMessageStore = await redis.get('anivmessages').then(a => a === null ? {} : JSON.parse(a));
data[user.displayName] = msg.messageText;
await redis.set('anivmessages', JSON.stringify(data));
@@ -43,7 +45,7 @@ export default async function handleMessage(msg: EventSubChannelChatMessageEvent
if (data.isAnivMessage) await Promise.all([
timeout(user, 'copied an aniv message', 30),
sendMessage(`${user.displayName} got timed out for copying an ${data.anivbot} message`),
createAnivTimeoutRecord(msg.messageText, user, 30)
createAnivTimeoutRecord(msg.messageText, data.anivbot, user, 30)
]);
};
};