added item shop, nerf lootbox, fix bugs

This commit is contained in:
2025-09-19 02:06:18 +02:00
parent 6a76c52acc
commit 9ce00d7a11
16 changed files with 171 additions and 93 deletions

View File

@@ -31,14 +31,12 @@ export default cheers;
export { namedcheers };
import { sendMessage } from 'commands';
import logger from 'lib/logger';
import { getUserRecord } from 'db/dbUser';
import { changeItemCount, type items } from 'items';
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

@@ -17,7 +17,7 @@ export default new Command({
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 (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

@@ -20,7 +20,7 @@ export default new Command({
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 (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) {

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";
@@ -24,7 +23,7 @@ export default new Command({
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

@@ -49,14 +49,13 @@ export default new Command({
grenade: 0,
blaster: 0,
tnt: 0,
silverbullet: 0
};
for (let i = 0; i < 5; i++) {
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() * 250) === 0) itemDiff.silverbullet! += 1;
if (Math.floor(Math.random() * 1000) === 0) itemDiff.silverbullet! += 1;
};
for (const [item, amount] of Object.entries(itemDiff) as [items, number][]) {

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";
@@ -26,7 +25,7 @@ export default new Command({
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,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

@@ -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

@@ -10,6 +10,7 @@ type itemOptions = {
description: string;
execution: (message: EventSubChannelChatMessageEvent, sender: User, args?: specialExecuteArgs) => Promise<void>;
specialaliases?: string[];
price: number;
};
export class Item {
@@ -20,6 +21,7 @@ export class Item {
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;
@@ -34,12 +36,14 @@ 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 { updateUserRecord, type inventoryUpdate } from "db/dbUser";
const itemMap = new Map<string, Item>;
const itemAliasMap = new Map<string, Item>;
const itemObjectArray: Item[] = []
const specialAliasItems = new Map<string, Item>;
const emptyInventory: inventory = {};
const itemarray: items[] = [];
@@ -51,16 +55,17 @@ 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) {
itemMap.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 itemMap;
export { emptyInventory, itemarray, specialAliasItems };
export default itemAliasMap;
export { emptyInventory, itemarray, specialAliasItems, itemObjectArray };
export type items = "blaster" | "silverbullet" | "grenade" | "tnt";
export type inventory = {

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, 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

61
src/migrate.ts Normal file
View File

@@ -0,0 +1,61 @@
import PocketBase from "pocketbase";
import { type inventory, type items } from "items"
const pb = new PocketBase('qweribot-main:8091');
import db from "db/connection";
import * as schema from "db/schema";
export type userRecord = {
id: string;
username: string;
balance: number;
inventory: inventory;
lastlootbox: string;
};
export type usedItemRecord = {
id?: string;
user: string;
item: items;
created: string;
};
export type timeoutRecord = {
id?: string;
user: string;
target: string;
item: items;
created: string;
};
const pbusers = await pb.collection<userRecord>('users').getFullList();
for (const pbuser of pbusers) {
await db.insert(schema.users).values({
id: parseInt(pbuser.id),
username: pbuser.username,
balance: pbuser.balance,
inventory: pbuser.inventory,
lastlootbox: new Date(Date.parse(pbuser.lastlootbox))
});
console.log(`Migrated users row of ${pbuser.username} to the new database`)
};
const pbuseditems = await pb.collection<usedItemRecord>('usedItems').getFullList();
for (const pbuseditem of pbuseditems) {
await db.insert(schema.usedItems).values({
user: parseInt(pbuseditem.user),
item: pbuseditem.item,
created: new Date(Date.parse(pbuseditem.created))
});
console.log(`Migrated usedItem row of user: ${pbuseditem.user} and item: ${pbuseditem.item} to the new database`)
};
const pbtimeouts = await pb.collection<timeoutRecord>('timeouts').getFullList();
for (const pbtimeout of pbtimeouts) {
await db.insert(schema.timeouts).values({
user: parseInt(pbtimeout.user),
target: parseInt(pbtimeout.target),
item: pbtimeout.item,
created: new Date(Date.parse(pbtimeout.created))
});
console.log(`Migrated timeout row of user: ${pbtimeout.user} shooting user: ${pbtimeout.target} with item: ${pbtimeout.item}`)
};