WIP: market offers close handle

This commit is contained in:
Milo
2025-12-11 11:59:05 +01:00
parent fddb07d8eb
commit 2b4e26e89e
3 changed files with 536 additions and 149 deletions

View File

@@ -1,5 +1,5 @@
import { handleMessageCreate } from "./handlers/messageCreate.js"; 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. * 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..."); console.log("[Startup] Bot is ready, performing initial data sync...");
await getAkhys(client); await getAkhys(client);
console.log("[Startup] Setting up scheduled tasks..."); console.log("[Startup] Setting up scheduled tasks...");
setupCronJobs(client, io); //setupCronJobs(client, io);
console.log("--- FlopoBOT is fully operational ---"); console.log("--- FlopoBOT is fully operational ---");
}); });

View File

@@ -8,110 +8,266 @@ export const flopoDB = new Database("flopobot.db");
flopoDB.exec(` flopoDB.exec(`
CREATE TABLE IF NOT EXISTS users CREATE TABLE IF NOT EXISTS users
( (
id TEXT PRIMARY KEY, id
username TEXT NOT NULL, TEXT
globalName TEXT, PRIMARY
warned BOOLEAN DEFAULT 0, KEY,
warns INTEGER DEFAULT 0, username
allTimeWarns INTEGER DEFAULT 0, TEXT
totalRequests INTEGER DEFAULT 0, NOT
coins INTEGER DEFAULT 0, NULL,
dailyQueried BOOLEAN DEFAULT 0, globalName
avatarUrl TEXT DEFAULT NULL, TEXT,
isAkhy BOOLEAN DEFAULT 0 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 CREATE TABLE IF NOT EXISTS skins
( (
uuid TEXT PRIMARY KEY, uuid
displayName TEXT, TEXT
contentTierUuid TEXT, PRIMARY
displayIcon TEXT, KEY,
user_id TEXT REFERENCES users, displayName
tierRank TEXT, TEXT,
tierColor TEXT, contentTierUuid
tierText TEXT, TEXT,
basePrice TEXT, displayIcon
currentLvl INTEGER DEFAULT NULL, TEXT,
currentChroma INTEGER DEFAULT NULL, user_id
currentPrice INTEGER DEFAULT NULL, TEXT
maxPrice INTEGER DEFAULT NULL 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 CREATE TABLE IF NOT EXISTS market_offers
( (
id PRIMARY KEY, id
skin_uuid TEXT REFERENCES skins, PRIMARY
seller_id TEXT REFERENCES users, KEY,
starting_price INTEGER NOT NULL, skin_uuid
buyout_price INTEGER DEFAULT NULL, TEXT
final_price INTEGER DEFAULT NULL, REFERENCES
status TEXT NOT NULL, skins,
posted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, seller_id
opening_at TIMESTAMP NOT NULL, TEXT
closing_at TIMESTAMP NOT NULL, REFERENCES
buyer_id TEXT REFERENCES users DEFAULT NULL 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 CREATE TABLE IF NOT EXISTS bids
( (
id PRIMARY KEY, id
bidder_id TEXT REFERENCES users, PRIMARY
market_offer_id REFERENCES market_offers, KEY,
offer_amount INTEGER, bidder_id
offered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP TEXT
REFERENCES
users,
market_offer_id
REFERENCES
market_offers,
offer_amount
INTEGER,
offered_at
TIMESTAMP
DEFAULT
CURRENT_TIMESTAMP
); );
CREATE TABLE IF NOT EXISTS logs CREATE TABLE IF NOT EXISTS logs
( (
id PRIMARY KEY, id
user_id TEXT REFERENCES users, PRIMARY
action TEXT, KEY,
target_user_id TEXT REFERENCES users, user_id
coins_amount INTEGER, TEXT
user_new_amount INTEGER, REFERENCES
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 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 CREATE TABLE IF NOT EXISTS games
( (
id PRIMARY KEY, id
p1 TEXT REFERENCES users, PRIMARY
p2 TEXT REFERENCES users, KEY,
p1_score INTEGER, p1
p2_score INTEGER, TEXT
p1_elo INTEGER, REFERENCES
p2_elo INTEGER, users,
p1_new_elo INTEGER, p2
p2_new_elo INTEGER, TEXT
type TEXT, REFERENCES
timestamp TIMESTAMP 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 CREATE TABLE IF NOT EXISTS elos
( (
id PRIMARY KEY REFERENCES users, id
elo INTEGER PRIMARY
KEY
REFERENCES
users,
elo
INTEGER
); );
CREATE TABLE IF NOT EXISTS sotd CREATE TABLE IF NOT EXISTS sotd
( (
id INT PRIMARY KEY, id
tableauPiles TEXT, INT
foundationPiles TEXT, PRIMARY
stockPile TEXT, KEY,
wastePile TEXT, tableauPiles
isDone BOOLEAN DEFAULT false, TEXT,
seed TEXT foundationPiles
TEXT,
stockPile
TEXT,
wastePile
TEXT,
isDone
BOOLEAN
DEFAULT
false,
seed
TEXT
); );
CREATE TABLE IF NOT EXISTS sotd_stats CREATE TABLE IF NOT EXISTS sotd_stats
( (
id TEXT PRIMARY KEY, id
user_id TEXT REFERENCES users, TEXT
time INTEGER, PRIMARY
moves INTEGER, KEY,
score INTEGER user_id
TEXT
REFERENCES
users,
time
INTEGER,
moves
INTEGER,
score
INTEGER
); );
`); `);
@@ -122,17 +278,48 @@ flopoDB.exec(`
export const stmtUsers = flopoDB.prepare(` export const stmtUsers = flopoDB.prepare(`
CREATE TABLE IF NOT EXISTS users CREATE TABLE IF NOT EXISTS users
( (
id TEXT PRIMARY KEY, id
username TEXT NOT NULL, TEXT
globalName TEXT, PRIMARY
warned BOOLEAN DEFAULT 0, KEY,
warns INTEGER DEFAULT 0, username
allTimeWarns INTEGER DEFAULT 0, TEXT
totalRequests INTEGER DEFAULT 0, NOT
coins INTEGER DEFAULT 0, NULL,
dailyQueried BOOLEAN DEFAULT 0, globalName
avatarUrl TEXT DEFAULT NULL, TEXT,
isAkhy BOOLEAN DEFAULT 0 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(); stmtUsers.run();
@@ -140,19 +327,44 @@ stmtUsers.run();
export const stmtSkins = flopoDB.prepare(` export const stmtSkins = flopoDB.prepare(`
CREATE TABLE IF NOT EXISTS skins CREATE TABLE IF NOT EXISTS skins
( (
uuid TEXT PRIMARY KEY, uuid
displayName TEXT, TEXT
contentTierUuid TEXT, PRIMARY
displayIcon TEXT, KEY,
user_id TEXT REFERENCES users, displayName
tierRank TEXT, TEXT,
tierColor TEXT, contentTierUuid
tierText TEXT, TEXT,
basePrice TEXT, displayIcon
currentLvl INTEGER DEFAULT NULL, TEXT,
currentChroma INTEGER DEFAULT NULL, user_id
currentPrice INTEGER DEFAULT NULL, TEXT
maxPrice INTEGER DEFAULT NULL 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(); stmtSkins.run();
@@ -160,17 +372,51 @@ stmtSkins.run();
export const stmtMarketOffers = flopoDB.prepare(` export const stmtMarketOffers = flopoDB.prepare(`
CREATE TABLE IF NOT EXISTS market_offers CREATE TABLE IF NOT EXISTS market_offers
( (
id PRIMARY KEY, id
skin_uuid TEXT REFERENCES skins, PRIMARY
seller_id TEXT REFERENCES users, KEY,
starting_price INTEGER NOT NULL, skin_uuid
buyout_price INTEGER DEFAULT NULL, TEXT
final_price INTEGER DEFAULT NULL, REFERENCES
status TEXT NOT NULL, skins,
posted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, seller_id
opening_at TIMESTAMP NOT NULL, TEXT
closing_at TIMESTAMP NOT NULL, REFERENCES
buyer_id TEXT REFERENCES users DEFAULT NULL 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(); stmtMarketOffers.run();
@@ -178,11 +424,22 @@ stmtMarketOffers.run();
export const stmtBids = flopoDB.prepare(` export const stmtBids = flopoDB.prepare(`
CREATE TABLE IF NOT EXISTS bids CREATE TABLE IF NOT EXISTS bids
( (
id PRIMARY KEY, id
bidder_id TEXT REFERENCES users, PRIMARY
market_offer_id REFERENCES market_offers, KEY,
offer_amount INTEGER, bidder_id
offered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP TEXT
REFERENCES
users,
market_offer_id
REFERENCES
market_offers,
offer_amount
INTEGER,
offered_at
TIMESTAMP
DEFAULT
CURRENT_TIMESTAMP
) )
`); `);
stmtBids.run(); stmtBids.run();
@@ -190,13 +447,27 @@ stmtBids.run();
export const stmtLogs = flopoDB.prepare(` export const stmtLogs = flopoDB.prepare(`
CREATE TABLE IF NOT EXISTS logs CREATE TABLE IF NOT EXISTS logs
( (
id PRIMARY KEY, id
user_id TEXT REFERENCES users, PRIMARY
action TEXT, KEY,
target_user_id TEXT REFERENCES users, user_id
coins_amount INTEGER, TEXT
user_new_amount INTEGER, REFERENCES
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP users,
action
TEXT,
target_user_id
TEXT
REFERENCES
users,
coins_amount
INTEGER,
user_new_amount
INTEGER,
created_at
TIMESTAMP
DEFAULT
CURRENT_TIMESTAMP
) )
`); `);
stmtLogs.run(); stmtLogs.run();
@@ -204,17 +475,33 @@ stmtLogs.run();
export const stmtGames = flopoDB.prepare(` export const stmtGames = flopoDB.prepare(`
CREATE TABLE IF NOT EXISTS games CREATE TABLE IF NOT EXISTS games
( (
id PRIMARY KEY, id
p1 TEXT REFERENCES users, PRIMARY
p2 TEXT REFERENCES users, KEY,
p1_score INTEGER, p1
p2_score INTEGER, TEXT
p1_elo INTEGER, REFERENCES
p2_elo INTEGER, users,
p1_new_elo INTEGER, p2
p2_new_elo INTEGER, TEXT
type TEXT, REFERENCES
timestamp TIMESTAMP 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(); stmtGames.run();
@@ -222,8 +509,13 @@ stmtGames.run();
export const stmtElos = flopoDB.prepare(` export const stmtElos = flopoDB.prepare(`
CREATE TABLE IF NOT EXISTS elos CREATE TABLE IF NOT EXISTS elos
( (
id PRIMARY KEY REFERENCES users, id
elo INTEGER PRIMARY
KEY
REFERENCES
users,
elo
INTEGER
) )
`); `);
stmtElos.run(); stmtElos.run();
@@ -231,13 +523,24 @@ stmtElos.run();
export const stmtSOTD = flopoDB.prepare(` export const stmtSOTD = flopoDB.prepare(`
CREATE TABLE IF NOT EXISTS sotd CREATE TABLE IF NOT EXISTS sotd
( (
id INT PRIMARY KEY, id
tableauPiles TEXT, INT
foundationPiles TEXT, PRIMARY
stockPile TEXT, KEY,
wastePile TEXT, tableauPiles
isDone BOOLEAN DEFAULT false, TEXT,
seed TEXT foundationPiles
TEXT,
stockPile
TEXT,
wastePile
TEXT,
isDone
BOOLEAN
DEFAULT
false,
seed
TEXT
) )
`); `);
stmtSOTD.run(); stmtSOTD.run();
@@ -245,11 +548,20 @@ stmtSOTD.run();
export const stmtSOTDStats = flopoDB.prepare(` export const stmtSOTDStats = flopoDB.prepare(`
CREATE TABLE IF NOT EXISTS sotd_stats CREATE TABLE IF NOT EXISTS sotd_stats
( (
id TEXT PRIMARY KEY, id
user_id TEXT REFERENCES users, TEXT
time INTEGER, PRIMARY
moves INTEGER, KEY,
score INTEGER user_id
TEXT
REFERENCES
users,
time
INTEGER,
moves
INTEGER,
score
INTEGER
) )
`); `);
stmtSOTDStats.run(); 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) 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 BIDS
----------------------------*/ ----------------------------*/

View File

@@ -8,12 +8,18 @@ import { initTodaysSOTD } from "../game/points.js";
import { import {
getAllAkhys, getAllAkhys,
getAllUsers, getAllUsers,
getMarketOffers,
getOfferBids,
getUser,
insertManySkins, insertManySkins,
insertUser, insertUser,
resetDailyReward, resetDailyReward,
updateMarketOffer,
updateSkin,
updateUserAvatar, updateUserAvatar,
updateUserCoins,
} from "../database/index.js"; } 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) { export async function InstallGlobalCommands(appId, commands) {
// API endpoint to overwrite global commands // API endpoint to overwrite global commands
@@ -111,6 +117,12 @@ export async function getAkhys(client) {
* @param {object} io - The Socket.IO server instance. * @param {object} io - The Socket.IO server instance.
*/ */
export function setupCronJobs(client, io) { 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 // Every 10 minutes: Clean up expired interactive sessions
cron.schedule("*/10 * * * *", () => { cron.schedule("*/10 * * * *", () => {
const now = Date.now(); const now = Date.now();
@@ -130,9 +142,28 @@ export function setupCronJobs(client, io) {
cleanup(activeInventories, "inventory"); cleanup(activeInventories, "inventory");
cleanup(activeSearchs, "search"); 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 // Daily at midnight: Reset daily rewards and init SOTD
@@ -223,6 +254,42 @@ export async function postAPOBuy(userId, amount) {
// --- Miscellaneous Helpers --- // --- 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) { export async function getOnlineUsersWithRole(guild, roleId) {
if (!guild || !roleId) return new Map(); if (!guild || !roleId) return new Map();
try { try {