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

View File

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

View File

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