mirror of
https://github.com/cassoule/flopobot_v2.git
synced 2026-01-18 16:37:40 +01:00
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,4 +4,4 @@ flopobot.db
|
||||
flopobot.db-shm
|
||||
flopobot.db-wal
|
||||
.idea
|
||||
flopobot_bc.db
|
||||
*.db
|
||||
BIN
flopobot.db
BIN
flopobot.db
Binary file not shown.
@@ -1,7 +1,5 @@
|
||||
import { InteractionResponseType, InteractionResponseFlags } from "discord-interactions";
|
||||
import { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
|
||||
|
||||
import { postAPOBuy } from "../../utils/index.js";
|
||||
import { InteractionResponseFlags, InteractionResponseType } from "discord-interactions";
|
||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
||||
import { DiscordRequest } from "../../api/discord.js";
|
||||
import { getAllAvailableSkins, getUser, insertLog, updateSkin, updateUserCoins } from "../../database/index.js";
|
||||
import { skins } from "../../game/state.js";
|
||||
@@ -14,6 +12,14 @@ import { skins } from "../../game/state.js";
|
||||
* @param {object} client - The Discord.js client instance.
|
||||
*/
|
||||
export async function handleValorantCommand(req, res, client) {
|
||||
return res.send({
|
||||
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
|
||||
data: {
|
||||
content: `L'ouverture de caisses Valorant en commande discord est désactivée. Tu peux aller en ouvrir sur FlopoSite.`,
|
||||
flags: InteractionResponseFlags.EPHEMERAL,
|
||||
},
|
||||
});
|
||||
|
||||
const { member, token } = req.body;
|
||||
const userId = member.user.id;
|
||||
const valoPrice = parseInt(process.env.VALO_PRICE, 10) || 500;
|
||||
@@ -125,8 +131,7 @@ export async function handleValorantCommand(req, res, client) {
|
||||
await DiscordRequest(webhookEndpoint, {
|
||||
method: "PATCH",
|
||||
body: {
|
||||
content:
|
||||
"Oups, il y a eu un petit problème lors de l'ouverture de la caisse. L'administrateur a été notifié.",
|
||||
content: "Oups, il y a eu un petit problème lors de l'ouverture de la caisse.",
|
||||
embeds: [],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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 ---");
|
||||
});
|
||||
|
||||
|
||||
@@ -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();
|
||||
@@ -291,9 +603,9 @@ export const getAllAkhys = flopoDB.prepare(
|
||||
----------------------------*/
|
||||
export const insertSkin = flopoDB.prepare(
|
||||
`INSERT INTO skins (uuid, displayName, contentTierUuid, displayIcon, user_id, tierRank, tierColor, tierText,
|
||||
basePrice, currentLvl, currentChroma, currentPrice, maxPrice)
|
||||
basePrice, maxPrice)
|
||||
VALUES (@uuid, @displayName, @contentTierUuid, @displayIcon, @user_id, @tierRank, @tierColor, @tierText,
|
||||
@basePrice, @currentLvl, @currentChroma, @currentPrice, @maxPrice)`,
|
||||
@basePrice, @maxPrice)`,
|
||||
);
|
||||
export const updateSkin = flopoDB.prepare(
|
||||
`UPDATE skins
|
||||
@@ -371,6 +683,20 @@ 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
|
||||
`);
|
||||
|
||||
export const deleteMarketOffer = flopoDB.prepare(`
|
||||
DELETE
|
||||
FROM market_offers
|
||||
WHERE id = ?
|
||||
`);
|
||||
|
||||
/* -------------------------
|
||||
BIDS
|
||||
----------------------------*/
|
||||
@@ -401,6 +727,12 @@ export const insertBid = flopoDB.prepare(`
|
||||
VALUES (@id, @bidder_id, @market_offer_id, @offer_amount)
|
||||
`);
|
||||
|
||||
export const deleteBid = flopoDB.prepare(`
|
||||
DELETE
|
||||
FROM bids
|
||||
WHERE id = ?
|
||||
`);
|
||||
|
||||
/* -------------------------
|
||||
BULK TRANSACTIONS (synchronous)
|
||||
----------------------------*/
|
||||
|
||||
@@ -4,6 +4,7 @@ import { sleep } from "openai/core";
|
||||
// --- Database Imports ---
|
||||
import {
|
||||
getAllAkhys,
|
||||
getAllAvailableSkins,
|
||||
getAllUsers,
|
||||
getLogs,
|
||||
getMarketOffersBySkin,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
insertUser,
|
||||
pruneOldLogs,
|
||||
queryDailyReward,
|
||||
updateSkin,
|
||||
updateUserCoins,
|
||||
} from "../../database/index.js";
|
||||
|
||||
@@ -32,6 +34,7 @@ import { DiscordRequest } from "../../api/discord.js";
|
||||
// --- Discord.js Builder Imports ---
|
||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
||||
import { emitDataUpdated, socketEmit } from "../socket.js";
|
||||
import { handleCaseOpening } from "../../utils/marketNotifs.js";
|
||||
|
||||
// Create a new router instance
|
||||
const router = express.Router();
|
||||
@@ -114,6 +117,151 @@ export function apiRoutes(client, io) {
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/open-case", async (req, res) => {
|
||||
const { userId, caseType } = req.body;
|
||||
|
||||
let caseTypeVal, tierWeights;
|
||||
switch (caseType) {
|
||||
case "standard":
|
||||
caseTypeVal = 1;
|
||||
tierWeights = {
|
||||
"12683d76-48d7-84a3-4e09-6985794f0445": 50, // Select
|
||||
"0cebb8be-46d7-c12a-d306-e9907bfc5a25": 30, // Deluxe
|
||||
"60bca009-4182-7998-dee7-b8a2558dc369": 15, // Premium
|
||||
"e046854e-406c-37f4-6607-19a9ba8426fc": 4, // Exclusive
|
||||
"411e4a55-4e59-7757-41f0-86a53f101bb5": 1, // Ultra
|
||||
};
|
||||
break;
|
||||
case "premium":
|
||||
caseTypeVal = 2;
|
||||
tierWeights = {
|
||||
"12683d76-48d7-84a3-4e09-6985794f0445": 35, // Select
|
||||
"0cebb8be-46d7-c12a-d306-e9907bfc5a25": 30, // Deluxe
|
||||
"60bca009-4182-7998-dee7-b8a2558dc369": 30, // Premium
|
||||
"e046854e-406c-37f4-6607-19a9ba8426fc": 4, // Exclusive
|
||||
"411e4a55-4e59-7757-41f0-86a53f101bb5": 1, // Ultra
|
||||
};
|
||||
break;
|
||||
case "ultra":
|
||||
caseTypeVal = 4;
|
||||
tierWeights = {
|
||||
"12683d76-48d7-84a3-4e09-6985794f0445": 33, // Select
|
||||
"0cebb8be-46d7-c12a-d306-e9907bfc5a25": 28, // Deluxe
|
||||
"60bca009-4182-7998-dee7-b8a2558dc369": 28, // Premium
|
||||
"e046854e-406c-37f4-6607-19a9ba8426fc": 8, // Exclusive
|
||||
"411e4a55-4e59-7757-41f0-86a53f101bb5": 3, // Ultra
|
||||
};
|
||||
break;
|
||||
default:
|
||||
return res.status(400).json({ error: "Invalid case type." });
|
||||
}
|
||||
const commandUser = getUser.get(userId);
|
||||
if (!commandUser) return res.status(404).json({ error: "User not found." });
|
||||
const valoPrice = (parseInt(process.env.VALO_PRICE, 10) || 500) * caseTypeVal;
|
||||
if (commandUser.coins < valoPrice) return res.status(403).json({ error: "Not enough FlopoCoins." });
|
||||
|
||||
try {
|
||||
const dbSkins = getAllAvailableSkins.all();
|
||||
const filteredSkins = skins.filter((s) => dbSkins.find((dbSkin) => dbSkin.uuid === s.uuid));
|
||||
filteredSkins.forEach((s) => {
|
||||
let dbSkin = getSkin.get(s.uuid);
|
||||
s.tierColor = dbSkin?.tierColor;
|
||||
});
|
||||
filteredSkins.forEach((s) => {
|
||||
s.weight = tierWeights[s.tierUuid] ?? 1; // fallback if missing
|
||||
});
|
||||
|
||||
function weightedSample(arr, count) {
|
||||
let totalWeight = arr.reduce((sum, x) => sum + x.weight, 0);
|
||||
const list = [...arr];
|
||||
const result = [];
|
||||
|
||||
for (let i = 0; i < count && list.length > 0; i++) {
|
||||
let r = Math.random() * totalWeight;
|
||||
let running = 0;
|
||||
let pickIndex = -1;
|
||||
|
||||
for (let j = 0; j < list.length; j++) {
|
||||
running += list[j].weight;
|
||||
if (r <= running) {
|
||||
pickIndex = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pickIndex < 0) break;
|
||||
|
||||
const picked = list.splice(pickIndex, 1)[0];
|
||||
result.push(picked);
|
||||
|
||||
// Subtract removed weight
|
||||
totalWeight -= picked.weight;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const selectedSkins = weightedSample(filteredSkins, 100);
|
||||
|
||||
const randomSelectedSkinIndex = Math.floor(Math.random() * (selectedSkins.length - 1));
|
||||
const randomSelectedSkinUuid = selectedSkins[randomSelectedSkinIndex].uuid;
|
||||
|
||||
const dbSkin = getSkin.get(randomSelectedSkinUuid);
|
||||
const randomSkinData = skins.find((skin) => skin.uuid === dbSkin.uuid);
|
||||
if (!randomSkinData) {
|
||||
throw new Error(`Could not find skin data for UUID: ${dbSkin.uuid}`);
|
||||
}
|
||||
|
||||
// --- Randomize Level and Chroma ---
|
||||
const randomLevel = Math.floor(Math.random() * randomSkinData.levels.length) + 1;
|
||||
let randomChroma = 1;
|
||||
if (randomLevel === randomSkinData.levels.length && randomSkinData.chromas.length > 1) {
|
||||
// Ensure chroma is at least 1 and not greater than the number of chromas
|
||||
randomChroma = Math.floor(Math.random() * randomSkinData.chromas.length) + 1;
|
||||
}
|
||||
|
||||
// --- Calculate Price ---
|
||||
const calculatePrice = () => {
|
||||
let result = parseFloat(dbSkin.basePrice);
|
||||
result *= 1 + randomLevel / Math.max(randomSkinData.levels.length, 2);
|
||||
result *= 1 + randomChroma / 4;
|
||||
return parseFloat(result.toFixed(0));
|
||||
};
|
||||
const finalPrice = calculatePrice();
|
||||
|
||||
// --- Update Database ---
|
||||
insertLog.run({
|
||||
id: `${userId}-${Date.now()}`,
|
||||
user_id: userId,
|
||||
action: "VALO_CASE_OPEN",
|
||||
target_user_id: null,
|
||||
coins_amount: -valoPrice,
|
||||
user_new_amount: commandUser.coins - valoPrice,
|
||||
});
|
||||
updateUserCoins.run({
|
||||
id: userId,
|
||||
coins: commandUser.coins - valoPrice,
|
||||
});
|
||||
updateSkin.run({
|
||||
uuid: randomSkinData.uuid,
|
||||
user_id: userId,
|
||||
currentLvl: randomLevel,
|
||||
currentChroma: randomChroma,
|
||||
currentPrice: finalPrice,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`[${Date.now()}] ${userId} opened a ${caseType} Valorant case and received skin ${randomSelectedSkinUuid}`,
|
||||
);
|
||||
const updatedSkin = getSkin.get(randomSkinData.uuid);
|
||||
await handleCaseOpening(caseType, userId, randomSelectedSkinUuid, client);
|
||||
res.json({ selectedSkins, randomSelectedSkinUuid, randomSelectedSkinIndex, updatedSkin });
|
||||
} catch (error) {
|
||||
console.error("Error fetching skins:", error);
|
||||
res.status(500).json({ error: "Failed to fetch skins." });
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/skin/:id", (req, res) => {
|
||||
try {
|
||||
const skinData = skins.find((s) => s.uuid === req.params.id);
|
||||
@@ -168,6 +316,14 @@ export function apiRoutes(client, io) {
|
||||
});
|
||||
|
||||
// --- User-Specific Routes ---
|
||||
router.get("/user/:id", async (req, res) => {
|
||||
try {
|
||||
const user = getUser.get(req.params.id);
|
||||
res.json({ user });
|
||||
} catch (error) {
|
||||
res.status(404).json({ error: "User not found." });
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/user/:id/avatar", async (req, res) => {
|
||||
try {
|
||||
@@ -188,6 +344,15 @@ export function apiRoutes(client, io) {
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/user/:id/coins", async (req, res) => {
|
||||
try {
|
||||
const user = getUser.get(req.params.id);
|
||||
res.json({ coins: user.coins });
|
||||
} catch (error) {
|
||||
res.status(404).json({ error: "User not found." });
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/user/:id/sparkline", (req, res) => {
|
||||
try {
|
||||
const logs = getUserLogs.all({ user_id: req.params.id });
|
||||
|
||||
@@ -8,13 +8,17 @@ import { ButtonStyle } from "discord.js";
|
||||
import {
|
||||
getMarketOfferById,
|
||||
getMarketOffers,
|
||||
getMarketOffersBySkin,
|
||||
getOfferBids,
|
||||
getSkin,
|
||||
getUser,
|
||||
insertBid,
|
||||
insertLog,
|
||||
insertMarketOffer,
|
||||
updateUserCoins,
|
||||
} from "../../database/index.js";
|
||||
import { emitMarketUpdate } from "../socket.js";
|
||||
import { handleNewMarketOffer, handleNewMarketOfferBid } from "../../utils/marketNotifs.js";
|
||||
|
||||
// Create a new router instance
|
||||
const router = express.Router();
|
||||
@@ -67,12 +71,43 @@ export function marketRoutes(client, io) {
|
||||
});
|
||||
|
||||
router.post("/place-offer", async (req, res) => {
|
||||
const { seller_id, skin_uuid, starting_price, delay, duration, timestamp } = req.body;
|
||||
const now = Date.now();
|
||||
try {
|
||||
// Placeholder for placing an offer logic
|
||||
// Extract data from req.body and process accordingly
|
||||
res.status(200).send({ message: "Offer placed successfully" });
|
||||
const skin = getSkin.get(skin_uuid);
|
||||
if (!skin) return res.status(404).send({ error: "Skin not found" });
|
||||
const seller = getUser.get(seller_id);
|
||||
if (!seller) return res.status(404).send({ error: "Seller not found" });
|
||||
if (skin.user_id !== seller.id) return res.status(403).send({ error: "You do not own this skin" });
|
||||
|
||||
const existingOffers = getMarketOffersBySkin.all(skin.uuid);
|
||||
if (
|
||||
existingOffers.length > 0 &&
|
||||
existingOffers.some((offer) => offer.status === "open" || offer.status === "pending")
|
||||
) {
|
||||
return res.status(403).send({ error: "This skin already has an open or pending offer." });
|
||||
}
|
||||
|
||||
const opening_at = now + delay;
|
||||
const closing_at = opening_at + duration;
|
||||
|
||||
const offerId = Date.now() + "-" + seller.id + "-" + skin.uuid;
|
||||
insertMarketOffer.run({
|
||||
id: offerId,
|
||||
skin_uuid: skin.uuid,
|
||||
seller_id: seller.id,
|
||||
starting_price: starting_price,
|
||||
buyout_price: null,
|
||||
status: delay > 0 ? "pending" : "open",
|
||||
opening_at: opening_at,
|
||||
closing_at: closing_at,
|
||||
});
|
||||
await emitMarketUpdate();
|
||||
await handleNewMarketOffer(offerId, client);
|
||||
res.status(200).send({ message: "Offre créée avec succès" });
|
||||
} catch (e) {
|
||||
res.status(500).send({ error: e });
|
||||
console.log(e);
|
||||
return res.status(500).send({ error: e });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -92,11 +127,11 @@ export function marketRoutes(client, io) {
|
||||
if (lastBid?.bidder_id === buyer_id)
|
||||
return res.status(403).send({ error: "You are already the highest bidder" });
|
||||
if (bid_amount < lastBid?.offer_amount + 10) {
|
||||
return res.status(403).send({ message: "Bid amount is below minimum" });
|
||||
return res.status(403).send({ error: "Bid amount is below minimum" });
|
||||
}
|
||||
} else {
|
||||
if (bid_amount < offer.starting_price + 10) {
|
||||
return res.status(403).send({ message: "Bid amount is below minimum" });
|
||||
return res.status(403).send({ error: "Bid amount is below minimum" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,16 +140,15 @@ export function marketRoutes(client, io) {
|
||||
if (bidder.coins < bid_amount)
|
||||
return res.status(403).send({ error: "You do not have enough coins to place this bid" });
|
||||
|
||||
// TODO:
|
||||
// buyer must refunded on outbid
|
||||
|
||||
const bidId = Date.now() + "-" + buyer_id + "-" + offer.id;
|
||||
insertBid.run({
|
||||
id: bidId,
|
||||
bidder_id: buyer_id,
|
||||
market_offer_id: offer.id,
|
||||
offer_amount: bid_amount,
|
||||
});
|
||||
const newCoinsAmount = bidder.coins - bid_amount;
|
||||
updateUserCoins.run({ buyer_id, coins: newCoinsAmount });
|
||||
updateUserCoins.run({ id: buyer_id, coins: newCoinsAmount });
|
||||
insertLog.run({
|
||||
id: `${buyer_id}-bid-${offer.id}-${Date.now()}`,
|
||||
user_id: buyer_id,
|
||||
@@ -124,7 +158,24 @@ export function marketRoutes(client, io) {
|
||||
user_new_amount: newCoinsAmount,
|
||||
});
|
||||
|
||||
res.status(200).send({ message: "Bid placed successfully" });
|
||||
// Refund the previous highest bidder
|
||||
if (lastBid) {
|
||||
const previousBidder = getUser.get(lastBid.bidder_id);
|
||||
const refundedCoinsAmount = previousBidder.coins + lastBid.offer_amount;
|
||||
updateUserCoins.run({ id: previousBidder.id, coins: refundedCoinsAmount });
|
||||
insertLog.run({
|
||||
id: `${previousBidder.id}-bid-refund-${offer.id}-${Date.now()}`,
|
||||
user_id: previousBidder.id,
|
||||
action: "BID_REFUNDED",
|
||||
target_user_id: null,
|
||||
coins_amount: lastBid.offer_amount,
|
||||
user_new_amount: refundedCoinsAmount,
|
||||
});
|
||||
}
|
||||
|
||||
await handleNewMarketOfferBid(offer.id, bidId, client);
|
||||
await emitMarketUpdate();
|
||||
res.status(200).send({ error: "Bid placed successfully" });
|
||||
} catch (e) {
|
||||
console.log(`[${Date.now()}]`, e);
|
||||
res.status(500).send({ error: e });
|
||||
|
||||
@@ -459,3 +459,5 @@ export const emitUpdate = (type, room) => io.emit("blackjack:update", { type, ro
|
||||
export const emitToast = (payload) => io.emit("blackjack:toast", payload);
|
||||
|
||||
export const emitSolitaireUpdate = (userId, moves) => io.emit("solitaire:update", { userId, moves });
|
||||
|
||||
export const emitMarketUpdate = () => io.emit("market:update");
|
||||
|
||||
@@ -6,14 +6,26 @@ import { getSkinTiers, getValorantSkins } from "../api/valorant.js";
|
||||
import { DiscordRequest } from "../api/discord.js";
|
||||
import { initTodaysSOTD } from "../game/points.js";
|
||||
import {
|
||||
deleteBid,
|
||||
deleteMarketOffer,
|
||||
getAllAkhys,
|
||||
getAllUsers,
|
||||
getMarketOffers,
|
||||
getOfferBids,
|
||||
getSkin,
|
||||
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";
|
||||
import { emitMarketUpdate } from "../server/socket.js";
|
||||
import { handleMarketOfferClosing, handleMarketOfferOpening } from "./marketNotifs.js";
|
||||
import { client } from "../bot/client.js";
|
||||
|
||||
export async function InstallGlobalCommands(appId, commands) {
|
||||
// API endpoint to overwrite global commands
|
||||
@@ -111,6 +123,11 @@ 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("* * * * *", () => {
|
||||
handleMarketOffersUpdate();
|
||||
});
|
||||
|
||||
// Every 10 minutes: Clean up expired interactive sessions
|
||||
cron.schedule("*/10 * * * *", () => {
|
||||
const now = Date.now();
|
||||
@@ -130,9 +147,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
|
||||
@@ -147,6 +183,23 @@ export function setupCronJobs(client, io) {
|
||||
} catch (e) {
|
||||
console.error("[Cron] Error during daily reset:", e);
|
||||
}
|
||||
try {
|
||||
const offers = getMarketOffers.all();
|
||||
const now = Date.now();
|
||||
const TWO_DAYS = 2 * 24 * 60 * 60 * 1000;
|
||||
for (const offer of offers) {
|
||||
if (now >= offer.closing_at + TWO_DAYS) {
|
||||
const offerBids = getOfferBids.all(offer.id);
|
||||
for (const bid of offerBids) {
|
||||
deleteBid.run(bid.id);
|
||||
}
|
||||
deleteMarketOffer.run(offer.id);
|
||||
console.log(`[Cron] Deleted expired market offer ID: ${offer.id}`);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[Cron] Error during Market Offers clean up:", e);
|
||||
}
|
||||
});
|
||||
|
||||
// Daily at 7 AM: Re-sync users and skins
|
||||
@@ -223,6 +276,61 @@ export async function postAPOBuy(userId, amount) {
|
||||
|
||||
// --- Miscellaneous Helpers ---
|
||||
|
||||
function handleMarketOffersUpdate() {
|
||||
const now = Date.now();
|
||||
const offers = getMarketOffers.all();
|
||||
offers.forEach(async (offer) => {
|
||||
if (now >= offer.opening_at && offer.status === "pending") {
|
||||
updateMarketOffer.run({ id: offer.id, final_price: null, buyer_id: null, status: "open" });
|
||||
await handleMarketOfferOpening(offer.id, client);
|
||||
await emitMarketUpdate();
|
||||
}
|
||||
if (now >= offer.closing_at && offer.status !== "closed") {
|
||||
const bids = getOfferBids.all(offer.id);
|
||||
|
||||
if (bids.length === 0) {
|
||||
// No bids placed, mark as closed without a sale
|
||||
updateMarketOffer.run({
|
||||
id: offer.id,
|
||||
buyer_id: null,
|
||||
final_price: null,
|
||||
status: "closed",
|
||||
});
|
||||
await emitMarketUpdate();
|
||||
} else {
|
||||
const lastBid = bids[0];
|
||||
const seller = getUser.get(offer.seller_id);
|
||||
const buyer = getUser.get(lastBid.bidder_id);
|
||||
|
||||
try {
|
||||
// Change skin ownership
|
||||
const skin = getSkin.get(offer.skin_uuid);
|
||||
if (!skin) throw new Error(`Skin not found for offer ID: ${offer.id}`);
|
||||
updateSkin.run({
|
||||
user_id: buyer.id,
|
||||
currentLvl: skin.currentLvl,
|
||||
currentChroma: skin.currentChroma,
|
||||
currentPrice: skin.currentPrice,
|
||||
uuid: skin.uuid,
|
||||
});
|
||||
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 });
|
||||
await emitMarketUpdate();
|
||||
} catch (e) {
|
||||
console.error(`[Market Cron] Error processing offer ID: ${offer.id}`, e);
|
||||
}
|
||||
}
|
||||
await handleMarketOfferClosing(offer.id, client);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function getOnlineUsersWithRole(guild, roleId) {
|
||||
if (!guild || !roleId) return new Map();
|
||||
try {
|
||||
|
||||
401
src/utils/marketNotifs.js
Normal file
401
src/utils/marketNotifs.js
Normal file
@@ -0,0 +1,401 @@
|
||||
import { getMarketOfferById, getOfferBids, getSkin, getUser } from "../database/index.js";
|
||||
import { EmbedBuilder } from "discord.js";
|
||||
|
||||
export async function handleNewMarketOffer(offerId, client) {
|
||||
const offer = getMarketOfferById.get(offerId);
|
||||
if (!offer) return;
|
||||
const skin = getSkin.get(offer.skin_uuid);
|
||||
|
||||
const discordUserSeller = await client.users.fetch(offer.seller_id);
|
||||
try {
|
||||
const userSeller = getUser.get(offer.seller_id);
|
||||
if (discordUserSeller && userSeller?.isAkhy) {
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("🔔 Offre créée")
|
||||
.setDescription(`Ton offre pour le skin **${skin ? skin.displayName : offer.skin_uuid}** a bien été créée !`)
|
||||
.setThumbnail(skin.displayIcon)
|
||||
.setColor(0x5865f2) // Discord blurple
|
||||
.addFields(
|
||||
{
|
||||
name: "📌 Statut",
|
||||
value: `\`${offer.status}\``,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "💰 Prix de départ",
|
||||
value: `\`${offer.starting_price} coins\``,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "⏰ Ouverture",
|
||||
value: `<t:${Math.floor(offer.opening_at / 1000)}:F>`,
|
||||
},
|
||||
{
|
||||
name: "⏰ Fermeture",
|
||||
value: `<t:${Math.floor(offer.closing_at / 1000)}:F>`,
|
||||
},
|
||||
{
|
||||
name: "🆔 ID de l’offre",
|
||||
value: `\`${offer.id}\``,
|
||||
inline: false,
|
||||
},
|
||||
)
|
||||
.setTimestamp();
|
||||
|
||||
discordUserSeller.send({ embeds: [embed] }).catch(console.error);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
// Send notification in guild channel
|
||||
|
||||
try {
|
||||
const guildChannel = await client.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("🔔 Nouvelle offre")
|
||||
.setDescription(`Une offre pour le skin **${skin ? skin.displayName : offer.skin_uuid}** a été créée !`)
|
||||
.setThumbnail(skin.displayIcon)
|
||||
.setColor(0x5865f2) // Discord blurple
|
||||
.addFields(
|
||||
{
|
||||
name: "💰 Prix de départ",
|
||||
value: `\`${offer.starting_price} coins\``,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "⏰ Ouverture",
|
||||
value: `<t:${Math.floor(offer.opening_at / 1000)}:F>`,
|
||||
},
|
||||
{
|
||||
name: "⏰ Fermeture",
|
||||
value: `<t:${Math.floor(offer.closing_at / 1000)}:F>`,
|
||||
},
|
||||
{
|
||||
name: "Créée par",
|
||||
value: `<@${offer.seller_id}> ${discordUserSeller ? "(" + discordUserSeller.username + ")" : ""}`,
|
||||
},
|
||||
)
|
||||
.setTimestamp();
|
||||
guildChannel.send({ embeds: [embed] }).catch(console.error);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleMarketOfferOpening(offerId, client) {
|
||||
const offer = getMarketOfferById.get(offerId);
|
||||
if (!offer) return;
|
||||
const skin = getSkin.get(offer.skin_uuid);
|
||||
|
||||
try {
|
||||
const discordUserSeller = await client.users.fetch(offer.seller_id);
|
||||
const userSeller = getUser.get(offer.seller_id);
|
||||
if (discordUserSeller && userSeller?.isAkhy) {
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("🔔 Début des enchères")
|
||||
.setDescription(
|
||||
`Les enchères sur ton offre pour le skin **${skin ? skin.displayName : offer.skin_uuid}** viennent de commencer !`,
|
||||
)
|
||||
.setThumbnail(skin.displayIcon)
|
||||
.setColor(0x5865f2) // Discord blurple
|
||||
.addFields(
|
||||
{
|
||||
name: "📌 Statut",
|
||||
value: `\`${offer.status}\``,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "💰 Prix de départ",
|
||||
value: `\`${offer.starting_price} coins\``,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "⏰ Fermeture",
|
||||
value: `<t:${Math.floor(offer.closing_at / 1000)}:F>`,
|
||||
},
|
||||
{
|
||||
name: "🆔 ID de l’offre",
|
||||
value: `\`${offer.id}\``,
|
||||
inline: false,
|
||||
},
|
||||
)
|
||||
.setTimestamp();
|
||||
|
||||
discordUserSeller.send({ embeds: [embed] }).catch(console.error);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
// Send notification in guild channel
|
||||
|
||||
try {
|
||||
const guildChannel = await client.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("🔔 Début des enchères")
|
||||
.setDescription(
|
||||
`Les enchères sur l'offre pour le skin **${skin ? skin.displayName : offer.skin_uuid}** viennent de commencer !`,
|
||||
)
|
||||
.setThumbnail(skin.displayIcon)
|
||||
.setColor(0x5865f2) // Discord blurple
|
||||
.addFields(
|
||||
{
|
||||
name: "💰 Prix de départ",
|
||||
value: `\`${offer.starting_price} coins\``,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "⏰ Fermeture",
|
||||
value: `<t:${Math.floor(offer.closing_at / 1000)}:F>`,
|
||||
},
|
||||
)
|
||||
.setTimestamp();
|
||||
guildChannel.send({ embeds: [embed] }).catch(console.error);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleMarketOfferClosing(offerId, client) {
|
||||
const offer = getMarketOfferById.get(offerId);
|
||||
if (!offer) return;
|
||||
const skin = getSkin.get(offer.skin_uuid);
|
||||
const bids = getOfferBids.all(offer.id);
|
||||
|
||||
const discordUserSeller = await client.users.fetch(offer.seller_id);
|
||||
try {
|
||||
const userSeller = getUser.get(offer.seller_id);
|
||||
if (discordUserSeller && userSeller?.isAkhy) {
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("🔔 Fin des enchères")
|
||||
.setDescription(
|
||||
`Les enchères sur ton offre pour le skin **${skin ? skin.displayName : offer.skin_uuid}** viennent de se terminer !`,
|
||||
)
|
||||
.setThumbnail(skin.displayIcon)
|
||||
.setColor(0x5865f2) // Discord blurple
|
||||
.setTimestamp();
|
||||
|
||||
if (bids.length === 0) {
|
||||
embed.addFields(
|
||||
{
|
||||
name: "❌ Aucune enchère n'a été placée sur cette offre.",
|
||||
value: "Tu conserves ce skin dans ton inventaire.",
|
||||
},
|
||||
{
|
||||
name: "🆔 ID de l’offre",
|
||||
value: `\`${offer.id}\``,
|
||||
inline: false,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
const highestBid = bids[0];
|
||||
const highestBidderUser = await client.users.fetch(highestBid.bidder_id);
|
||||
embed.addFields(
|
||||
{
|
||||
name: "✅ Enchères terminées avec succès !",
|
||||
value: `Ton skin a été vendu pour \`${highestBid.offer_amount} coins\` à <@${highestBid.bidder_id}> ${highestBidderUser ? "(" + highestBidderUser.username + ")" : ""}.`,
|
||||
},
|
||||
{
|
||||
name: "🆔 ID de l’offre",
|
||||
value: `\`${offer.id}\``,
|
||||
inline: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
discordUserSeller.send({ embeds: [embed] }).catch(console.error);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
// Send notification in guild channel
|
||||
|
||||
try {
|
||||
const guildChannel = await client.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("🔔 Fin des enchères")
|
||||
.setDescription(
|
||||
`Les enchères sur l'offre pour le skin **${skin ? skin.displayName : offer.skin_uuid}** viennent de se terminer !`,
|
||||
)
|
||||
.setThumbnail(skin.displayIcon)
|
||||
.setColor(0x5865f2) // Discord blurple
|
||||
.setTimestamp();
|
||||
|
||||
if (bids.length === 0) {
|
||||
embed.addFields({
|
||||
name: "❌ Aucune enchère n'a été placée sur cette offre.",
|
||||
value: "",
|
||||
});
|
||||
} else {
|
||||
const highestBid = bids[0];
|
||||
const highestBidderUser = await client.users.fetch(highestBid.bidder_id);
|
||||
embed.addFields({
|
||||
name: "✅ Enchères terminées avec succès !",
|
||||
value: `Le skin de <@${offer.seller_id}> ${discordUserSeller ? "(" + discordUserSeller.username + ")" : ""} a été vendu pour \`${highestBid.offer_amount} coins\` à <@${highestBid.bidder_id}> ${highestBidderUser ? "(" + highestBidderUser.username + ")" : ""}.`,
|
||||
});
|
||||
const discordUserBidder = await client.users.fetch(highestBid.bidder_id);
|
||||
const userBidder = getUser.get(highestBid.bidder_id);
|
||||
if (discordUserBidder && userBidder?.isAkhy) {
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("🔔 Fin des enchères")
|
||||
.setDescription(
|
||||
`Les enchères sur l'offre pour le skin **${skin ? skin.displayName : offer.skin_uuid}** viennent de se terminer !`,
|
||||
)
|
||||
.setThumbnail(skin.displayIcon)
|
||||
.setColor(0x5865f2) // Discord blurple
|
||||
.setTimestamp();
|
||||
const highestBid = bids[0];
|
||||
embed.addFields({
|
||||
name: "✅ Enchères terminées avec succès !",
|
||||
value: `Tu as acheté ce skin pour \`${highestBid.offer_amount} coins\` à <@${offer.seller_id}> ${discordUserSeller ? "(" + discordUserSeller.username + ")" : ""}. Il a été ajouté à ton inventaire.`,
|
||||
});
|
||||
|
||||
discordUserBidder.send({ embeds: [embed] }).catch(console.error);
|
||||
}
|
||||
}
|
||||
guildChannel.send({ embeds: [embed] }).catch(console.error);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleNewMarketOfferBid(offerId, bidId, client) {
|
||||
// Notify Seller and Bidder
|
||||
const offer = getMarketOfferById.get(offerId);
|
||||
if (!offer) return;
|
||||
const bid = getOfferBids.get(offerId);
|
||||
if (!bid) return;
|
||||
const skin = getSkin.get(offer.skin_uuid);
|
||||
|
||||
const bidderUser = client.users.fetch(bid.bidder_id);
|
||||
try {
|
||||
const discordUserSeller = await client.users.fetch(offer.seller_id);
|
||||
const userSeller = getUser.get(offer.seller_id);
|
||||
|
||||
if (discordUserSeller && userSeller?.isAkhy) {
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("🔔 Nouvelle enchère")
|
||||
.setDescription(
|
||||
`Il y a eu une nouvelle enchère sur ton offre pour le skin **${skin ? skin.displayName : offer.skin_uuid}**.`,
|
||||
)
|
||||
.setThumbnail(skin.displayIcon)
|
||||
.setColor(0x5865f2) // Discord blurple
|
||||
.addFields(
|
||||
{
|
||||
name: "👤 Enchérisseur",
|
||||
value: `<@${bid.bidder_id}> ${bidderUser ? "(" + bidderUser.username + ")" : ""}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "💰 Montant de l’enchère",
|
||||
value: `\`${bid.offer_amount} coins\``,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "⏰ Fermeture",
|
||||
value: `<t:${Math.floor(offer.closing_at / 1000)}:F>`,
|
||||
},
|
||||
{
|
||||
name: "🆔 ID de l’offre",
|
||||
value: `\`${offer.id}\``,
|
||||
inline: false,
|
||||
},
|
||||
)
|
||||
.setTimestamp();
|
||||
|
||||
discordUserSeller.send({ embeds: [embed] }).catch(console.error);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Erreur lors de la notification du vendeur : ${e}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const discordUserNewBidder = await client.users.fetch(bid.bidder_id);
|
||||
const userNewBidder = getUser.get(bid.bidder_id);
|
||||
if (discordUserNewBidder && userNewBidder?.isAkhy) {
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("🔔 Nouvelle enchère")
|
||||
.setDescription(
|
||||
`Ton enchère sur l'offre pour le skin **${skin ? skin.displayName : offer.skin_uuid}** a bien été placée!`,
|
||||
)
|
||||
.setThumbnail(skin.displayIcon)
|
||||
.setColor(0x5865f2) // Discord blurple
|
||||
.addFields({
|
||||
name: "💰 Montant de l’enchère",
|
||||
value: `\`${bid.offer_amount} coins\``,
|
||||
inline: true,
|
||||
})
|
||||
.setTimestamp();
|
||||
|
||||
discordUserNewBidder.send({ embeds: [embed] }).catch(console.error);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Erreur lors de la notification de l'enchérriseur : ${e}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const offerBids = getOfferBids.all(offer.id);
|
||||
if (offerBids.length < 2) return; // No previous bidder to notify
|
||||
|
||||
const discordUserPreviousBidder = await client.users.fetch(offerBids[1].bidder_id);
|
||||
const userPreviousBidder = getUser.get(offerBids[1].bidder_id);
|
||||
if (discordUserPreviousBidder && userPreviousBidder?.isAkhy) {
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("🔔 Nouvelle enchère")
|
||||
.setDescription(
|
||||
`Quelqu'un a surenchéri sur l'offre pour le skin **${skin ? skin.displayName : offer.skin_uuid}**, tu n'es plus le meilleur enchérisseur !`,
|
||||
)
|
||||
.setThumbnail(skin.displayIcon)
|
||||
.setColor(0x5865f2) // Discord blurple
|
||||
.addFields(
|
||||
{
|
||||
name: "👤 Enchérisseur",
|
||||
value: `<@${bid.bidder_id}> ${bidderUser ? "(" + bidderUser.username + ")" : ""}`,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "💰 Montant de l’enchère",
|
||||
value: `\`${bid.offer_amount} coins\``,
|
||||
inline: true,
|
||||
},
|
||||
)
|
||||
.setTimestamp();
|
||||
|
||||
discordUserPreviousBidder.send({ embeds: [embed] }).catch(console.error);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
// Notify previous highest bidder
|
||||
}
|
||||
|
||||
export async function handleCaseOpening(caseType, userId, skinUuid, client) {
|
||||
const discordUser = await client.users.fetch(userId);
|
||||
const skin = getSkin.get(skinUuid);
|
||||
try {
|
||||
const guildChannel = await client.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("🔔 Ouverture de caisse")
|
||||
.setDescription(
|
||||
`${discordUser ? discordUser.username : "Un utilisateur"} vient d'ouvrir une caisse **${caseType}** et a obtenu le skin **${skin.displayName}** !`,
|
||||
)
|
||||
.setThumbnail(skin.displayIcon)
|
||||
.setColor(skin.tierColor) // Discord blurple
|
||||
.addFields(
|
||||
{
|
||||
name: "💰 Valeur estimée",
|
||||
value: `\`${skin.currentPrice} coins\``,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "Level",
|
||||
value: `${skin.currentLvl}`,
|
||||
},
|
||||
)
|
||||
.setTimestamp();
|
||||
guildChannel.send({ embeds: [embed] }).catch(console.error);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user