From 2b4e26e89e999335b9845e85ae8da249d9628116 Mon Sep 17 00:00:00 2001 From: Milo Date: Thu, 11 Dec 2025 11:59:05 +0100 Subject: [PATCH] WIP: market offers close handle --- src/bot/events.js | 4 +- src/database/index.js | 608 ++++++++++++++++++++++++++++++++---------- src/utils/index.js | 73 ++++- 3 files changed, 536 insertions(+), 149 deletions(-) diff --git a/src/bot/events.js b/src/bot/events.js index 2ca60cc..e08e02e 100644 --- a/src/bot/events.js +++ b/src/bot/events.js @@ -1,5 +1,5 @@ import { handleMessageCreate } from "./handlers/messageCreate.js"; -import { getAkhys, setupCronJobs } from "../utils/index.js"; +import { getAkhys } from "../utils/index.js"; /** * Initializes and attaches all necessary event listeners to the Discord client. @@ -17,7 +17,7 @@ export function initializeEvents(client, io) { console.log("[Startup] Bot is ready, performing initial data sync..."); await getAkhys(client); console.log("[Startup] Setting up scheduled tasks..."); - setupCronJobs(client, io); + //setupCronJobs(client, io); console.log("--- FlopoBOT is fully operational ---"); }); diff --git a/src/database/index.js b/src/database/index.js index 0979c10..374b353 100644 --- a/src/database/index.js +++ b/src/database/index.js @@ -8,110 +8,266 @@ export const flopoDB = new Database("flopobot.db"); flopoDB.exec(` CREATE TABLE IF NOT EXISTS users ( - id TEXT PRIMARY KEY, - username TEXT NOT NULL, - globalName TEXT, - warned BOOLEAN DEFAULT 0, - warns INTEGER DEFAULT 0, - allTimeWarns INTEGER DEFAULT 0, - totalRequests INTEGER DEFAULT 0, - coins INTEGER DEFAULT 0, - dailyQueried BOOLEAN DEFAULT 0, - avatarUrl TEXT DEFAULT NULL, - isAkhy BOOLEAN DEFAULT 0 + id + TEXT + PRIMARY + KEY, + username + TEXT + NOT + NULL, + globalName + TEXT, + warned + BOOLEAN + DEFAULT + 0, + warns + INTEGER + DEFAULT + 0, + allTimeWarns + INTEGER + DEFAULT + 0, + totalRequests + INTEGER + DEFAULT + 0, + coins + INTEGER + DEFAULT + 0, + dailyQueried + BOOLEAN + DEFAULT + 0, + avatarUrl + TEXT + DEFAULT + NULL, + isAkhy + BOOLEAN + DEFAULT + 0 ); CREATE TABLE IF NOT EXISTS skins ( - uuid TEXT PRIMARY KEY, - displayName TEXT, - contentTierUuid TEXT, - displayIcon TEXT, - user_id TEXT REFERENCES users, - tierRank TEXT, - tierColor TEXT, - tierText TEXT, - basePrice TEXT, - currentLvl INTEGER DEFAULT NULL, - currentChroma INTEGER DEFAULT NULL, - currentPrice INTEGER DEFAULT NULL, - maxPrice INTEGER DEFAULT NULL + uuid + TEXT + PRIMARY + KEY, + displayName + TEXT, + contentTierUuid + TEXT, + displayIcon + TEXT, + user_id + TEXT + REFERENCES + users, + tierRank + TEXT, + tierColor + TEXT, + tierText + TEXT, + basePrice + TEXT, + currentLvl + INTEGER + DEFAULT + NULL, + currentChroma + INTEGER + DEFAULT + NULL, + currentPrice + INTEGER + DEFAULT + NULL, + maxPrice + INTEGER + DEFAULT + NULL ); CREATE TABLE IF NOT EXISTS market_offers ( - id PRIMARY KEY, - skin_uuid TEXT REFERENCES skins, - seller_id TEXT REFERENCES users, - starting_price INTEGER NOT NULL, - buyout_price INTEGER DEFAULT NULL, - final_price INTEGER DEFAULT NULL, - status TEXT NOT NULL, - posted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - opening_at TIMESTAMP NOT NULL, - closing_at TIMESTAMP NOT NULL, - buyer_id TEXT REFERENCES users DEFAULT NULL + id + PRIMARY + KEY, + skin_uuid + TEXT + REFERENCES + skins, + seller_id + TEXT + REFERENCES + users, + starting_price + INTEGER + NOT + NULL, + buyout_price + INTEGER + DEFAULT + NULL, + final_price + INTEGER + DEFAULT + NULL, + status + TEXT + NOT + NULL, + posted_at + TIMESTAMP + DEFAULT + CURRENT_TIMESTAMP, + opening_at + TIMESTAMP + NOT + NULL, + closing_at + TIMESTAMP + NOT + NULL, + buyer_id + TEXT + REFERENCES + users + DEFAULT + NULL ); CREATE TABLE IF NOT EXISTS bids ( - id PRIMARY KEY, - bidder_id TEXT REFERENCES users, - market_offer_id REFERENCES market_offers, - offer_amount INTEGER, - offered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + id + PRIMARY + KEY, + bidder_id + TEXT + REFERENCES + users, + market_offer_id + REFERENCES + market_offers, + offer_amount + INTEGER, + offered_at + TIMESTAMP + DEFAULT + CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS logs ( - id PRIMARY KEY, - user_id TEXT REFERENCES users, - action TEXT, - target_user_id TEXT REFERENCES users, - coins_amount INTEGER, - user_new_amount INTEGER, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + id + PRIMARY + KEY, + user_id + TEXT + REFERENCES + users, + action + TEXT, + target_user_id + TEXT + REFERENCES + users, + coins_amount + INTEGER, + user_new_amount + INTEGER, + created_at + TIMESTAMP + DEFAULT + CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS games ( - id PRIMARY KEY, - p1 TEXT REFERENCES users, - p2 TEXT REFERENCES users, - p1_score INTEGER, - p2_score INTEGER, - p1_elo INTEGER, - p2_elo INTEGER, - p1_new_elo INTEGER, - p2_new_elo INTEGER, - type TEXT, - timestamp TIMESTAMP + id + PRIMARY + KEY, + p1 + TEXT + REFERENCES + users, + p2 + TEXT + REFERENCES + users, + p1_score + INTEGER, + p2_score + INTEGER, + p1_elo + INTEGER, + p2_elo + INTEGER, + p1_new_elo + INTEGER, + p2_new_elo + INTEGER, + type + TEXT, + timestamp + TIMESTAMP ); CREATE TABLE IF NOT EXISTS elos ( - id PRIMARY KEY REFERENCES users, - elo INTEGER + id + PRIMARY + KEY + REFERENCES + users, + elo + INTEGER ); CREATE TABLE IF NOT EXISTS sotd ( - id INT PRIMARY KEY, - tableauPiles TEXT, - foundationPiles TEXT, - stockPile TEXT, - wastePile TEXT, - isDone BOOLEAN DEFAULT false, - seed TEXT + id + INT + PRIMARY + KEY, + tableauPiles + TEXT, + foundationPiles + TEXT, + stockPile + TEXT, + wastePile + TEXT, + isDone + BOOLEAN + DEFAULT + false, + seed + TEXT ); CREATE TABLE IF NOT EXISTS sotd_stats ( - id TEXT PRIMARY KEY, - user_id TEXT REFERENCES users, - time INTEGER, - moves INTEGER, - score INTEGER + id + TEXT + PRIMARY + KEY, + user_id + TEXT + REFERENCES + users, + time + INTEGER, + moves + INTEGER, + score + INTEGER ); `); @@ -122,17 +278,48 @@ flopoDB.exec(` export const stmtUsers = flopoDB.prepare(` CREATE TABLE IF NOT EXISTS users ( - id TEXT PRIMARY KEY, - username TEXT NOT NULL, - globalName TEXT, - warned BOOLEAN DEFAULT 0, - warns INTEGER DEFAULT 0, - allTimeWarns INTEGER DEFAULT 0, - totalRequests INTEGER DEFAULT 0, - coins INTEGER DEFAULT 0, - dailyQueried BOOLEAN DEFAULT 0, - avatarUrl TEXT DEFAULT NULL, - isAkhy BOOLEAN DEFAULT 0 + id + TEXT + PRIMARY + KEY, + username + TEXT + NOT + NULL, + globalName + TEXT, + warned + BOOLEAN + DEFAULT + 0, + warns + INTEGER + DEFAULT + 0, + allTimeWarns + INTEGER + DEFAULT + 0, + totalRequests + INTEGER + DEFAULT + 0, + coins + INTEGER + DEFAULT + 0, + dailyQueried + BOOLEAN + DEFAULT + 0, + avatarUrl + TEXT + DEFAULT + NULL, + isAkhy + BOOLEAN + DEFAULT + 0 ) `); stmtUsers.run(); @@ -140,19 +327,44 @@ stmtUsers.run(); export const stmtSkins = flopoDB.prepare(` CREATE TABLE IF NOT EXISTS skins ( - uuid TEXT PRIMARY KEY, - displayName TEXT, - contentTierUuid TEXT, - displayIcon TEXT, - user_id TEXT REFERENCES users, - tierRank TEXT, - tierColor TEXT, - tierText TEXT, - basePrice TEXT, - currentLvl INTEGER DEFAULT NULL, - currentChroma INTEGER DEFAULT NULL, - currentPrice INTEGER DEFAULT NULL, - maxPrice INTEGER DEFAULT NULL + uuid + TEXT + PRIMARY + KEY, + displayName + TEXT, + contentTierUuid + TEXT, + displayIcon + TEXT, + user_id + TEXT + REFERENCES + users, + tierRank + TEXT, + tierColor + TEXT, + tierText + TEXT, + basePrice + TEXT, + currentLvl + INTEGER + DEFAULT + NULL, + currentChroma + INTEGER + DEFAULT + NULL, + currentPrice + INTEGER + DEFAULT + NULL, + maxPrice + INTEGER + DEFAULT + NULL ) `); stmtSkins.run(); @@ -160,17 +372,51 @@ stmtSkins.run(); export const stmtMarketOffers = flopoDB.prepare(` CREATE TABLE IF NOT EXISTS market_offers ( - id PRIMARY KEY, - skin_uuid TEXT REFERENCES skins, - seller_id TEXT REFERENCES users, - starting_price INTEGER NOT NULL, - buyout_price INTEGER DEFAULT NULL, - final_price INTEGER DEFAULT NULL, - status TEXT NOT NULL, - posted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - opening_at TIMESTAMP NOT NULL, - closing_at TIMESTAMP NOT NULL, - buyer_id TEXT REFERENCES users DEFAULT NULL + id + PRIMARY + KEY, + skin_uuid + TEXT + REFERENCES + skins, + seller_id + TEXT + REFERENCES + users, + starting_price + INTEGER + NOT + NULL, + buyout_price + INTEGER + DEFAULT + NULL, + final_price + INTEGER + DEFAULT + NULL, + status + TEXT + NOT + NULL, + posted_at + TIMESTAMP + DEFAULT + CURRENT_TIMESTAMP, + opening_at + TIMESTAMP + NOT + NULL, + closing_at + TIMESTAMP + NOT + NULL, + buyer_id + TEXT + REFERENCES + users + DEFAULT + NULL ) `); stmtMarketOffers.run(); @@ -178,11 +424,22 @@ stmtMarketOffers.run(); export const stmtBids = flopoDB.prepare(` CREATE TABLE IF NOT EXISTS bids ( - id PRIMARY KEY, - bidder_id TEXT REFERENCES users, - market_offer_id REFERENCES market_offers, - offer_amount INTEGER, - offered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + id + PRIMARY + KEY, + bidder_id + TEXT + REFERENCES + users, + market_offer_id + REFERENCES + market_offers, + offer_amount + INTEGER, + offered_at + TIMESTAMP + DEFAULT + CURRENT_TIMESTAMP ) `); stmtBids.run(); @@ -190,13 +447,27 @@ stmtBids.run(); export const stmtLogs = flopoDB.prepare(` CREATE TABLE IF NOT EXISTS logs ( - id PRIMARY KEY, - user_id TEXT REFERENCES users, - action TEXT, - target_user_id TEXT REFERENCES users, - coins_amount INTEGER, - user_new_amount INTEGER, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + id + PRIMARY + KEY, + user_id + TEXT + REFERENCES + users, + action + TEXT, + target_user_id + TEXT + REFERENCES + users, + coins_amount + INTEGER, + user_new_amount + INTEGER, + created_at + TIMESTAMP + DEFAULT + CURRENT_TIMESTAMP ) `); stmtLogs.run(); @@ -204,17 +475,33 @@ stmtLogs.run(); export const stmtGames = flopoDB.prepare(` CREATE TABLE IF NOT EXISTS games ( - id PRIMARY KEY, - p1 TEXT REFERENCES users, - p2 TEXT REFERENCES users, - p1_score INTEGER, - p2_score INTEGER, - p1_elo INTEGER, - p2_elo INTEGER, - p1_new_elo INTEGER, - p2_new_elo INTEGER, - type TEXT, - timestamp TIMESTAMP + id + PRIMARY + KEY, + p1 + TEXT + REFERENCES + users, + p2 + TEXT + REFERENCES + users, + p1_score + INTEGER, + p2_score + INTEGER, + p1_elo + INTEGER, + p2_elo + INTEGER, + p1_new_elo + INTEGER, + p2_new_elo + INTEGER, + type + TEXT, + timestamp + TIMESTAMP ) `); stmtGames.run(); @@ -222,8 +509,13 @@ stmtGames.run(); export const stmtElos = flopoDB.prepare(` CREATE TABLE IF NOT EXISTS elos ( - id PRIMARY KEY REFERENCES users, - elo INTEGER + id + PRIMARY + KEY + REFERENCES + users, + elo + INTEGER ) `); stmtElos.run(); @@ -231,13 +523,24 @@ stmtElos.run(); export const stmtSOTD = flopoDB.prepare(` CREATE TABLE IF NOT EXISTS sotd ( - id INT PRIMARY KEY, - tableauPiles TEXT, - foundationPiles TEXT, - stockPile TEXT, - wastePile TEXT, - isDone BOOLEAN DEFAULT false, - seed TEXT + id + INT + PRIMARY + KEY, + tableauPiles + TEXT, + foundationPiles + TEXT, + stockPile + TEXT, + wastePile + TEXT, + isDone + BOOLEAN + DEFAULT + false, + seed + TEXT ) `); stmtSOTD.run(); @@ -245,11 +548,20 @@ stmtSOTD.run(); export const stmtSOTDStats = flopoDB.prepare(` CREATE TABLE IF NOT EXISTS sotd_stats ( - id TEXT PRIMARY KEY, - user_id TEXT REFERENCES users, - time INTEGER, - moves INTEGER, - score INTEGER + id + TEXT + PRIMARY + KEY, + user_id + TEXT + REFERENCES + users, + time + INTEGER, + moves + INTEGER, + score + INTEGER ) `); stmtSOTDStats.run(); @@ -371,6 +683,14 @@ export const insertMarketOffer = flopoDB.prepare(` VALUES (@id, @skin_uuid, @seller_id, @starting_price, @buyout_price, @status, @opening_at, @closing_at) `); +export const updateMarketOffer = flopoDB.prepare(` + UPDATE market_offers + SET final_price = @final_price, + status = @status, + buyer_id = @buyer_id + WHERE id = @id +`); + /* ------------------------- BIDS ----------------------------*/ diff --git a/src/utils/index.js b/src/utils/index.js index 429fa35..686e567 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -8,12 +8,18 @@ import { initTodaysSOTD } from "../game/points.js"; import { getAllAkhys, getAllUsers, + getMarketOffers, + getOfferBids, + getUser, insertManySkins, insertUser, resetDailyReward, + updateMarketOffer, + updateSkin, updateUserAvatar, + updateUserCoins, } from "../database/index.js"; -import { activeInventories, activeSearchs, skins } from "../game/state.js"; +import { activeInventories, activePredis, activeSearchs, pokerRooms, skins } from "../game/state.js"; export async function InstallGlobalCommands(appId, commands) { // API endpoint to overwrite global commands @@ -111,6 +117,12 @@ export async function getAkhys(client) { * @param {object} io - The Socket.IO server instance. */ export function setupCronJobs(client, io) { + // Every 5 minutes: Update market offers + cron.schedule("*/1 * * * *", () => { + console.log("[Cron] Checking market offers for updates..."); + handleMarketOffersUpdate(); + }); + // Every 10 minutes: Clean up expired interactive sessions cron.schedule("*/10 * * * *", () => { const now = Date.now(); @@ -130,9 +142,28 @@ export function setupCronJobs(client, io) { cleanup(activeInventories, "inventory"); cleanup(activeSearchs, "search"); + for (const id in pokerRooms) { + if (pokerRooms[id].last_move_at !== null) { + if (now >= pokerRooms[id].last_move_at + FIVE_MINUTES * 3) { + delete pokerRooms[id]; + console.log(`[Cron] Cleaned up expired poker room ID: ${id}`); + } + } else { + if (now >= pokerRooms[id].created_at + FIVE_MINUTES * 6) { + delete pokerRooms[id]; + console.log(`[Cron] Cleaned up expired poker room ID: ${id}`); + } + } + } - // TODO: Cleanup for predis and poker rooms... - // ... + let cleanedCount = 0; + for (const id in activePredis) { + if (now >= (activePredis[id].endTime || 0)) { + delete activePredis[id]; + cleanedCount++; + } + } + if (cleanedCount > 0) console.log(`[Cron] Cleaned up ${cleanedCount} expired predictions.`); }); // Daily at midnight: Reset daily rewards and init SOTD @@ -223,6 +254,42 @@ export async function postAPOBuy(userId, amount) { // --- Miscellaneous Helpers --- +function handleMarketOffersUpdate() { + const now = Date.now(); + const offers = getMarketOffers.all(); + offers.forEach((offer) => { + console.log(`[Market Cron] Checking offer ID: ${offer.id}, Status: ${offer.status}`); + console.log(`Now: ${now}, Closing At: ${offer.closing_at}, ${now >= offer.closing_at}`); + if (now >= offer.closing_at && offer.status !== "closed") { + const bids = getOfferBids.all(offer.id); + console.log(bids.length); + const lastBid = bids[0]; + console.log(lastBid); + const seller = getUser.get(offer.seller_id); + console.log(seller); + const buyer = getUser.get(offer.buyer_id); + console.log(buyer); + + console.log(offer.id, buyer.id, lastBid.offer_amount); + + updateMarketOffer.run({ + id: offer.id, + buyer_id: buyer.id, + final_price: lastBid.offer_amount, + status: "closed", + }); + + const newUserCoins = seller.coins + lastBid.offer_amount; + updateUserCoins.run({ id: seller.id, coins: newUserCoins }); + + // Change skin ownership + updateSkin.run({ user_id: buyer.id, uuid: offer.skin_uuid }); + + //TODO: Notify users in DMs + } + }); +} + export async function getOnlineUsersWithRole(guild, roleId) { if (!guild || !roleId) return new Map(); try {