From 738e21aaf7b86c6560223dc853628ceb8b237c1d Mon Sep 17 00:00:00 2001 From: Milo Date: Fri, 19 Dec 2025 17:05:16 +0100 Subject: [PATCH] CASE OPENING AND MARKETPLACE --- src/bot/commands/valorant.js | 17 +- src/database/index.js | 12 ++ src/server/routes/api.js | 42 ++-- src/server/routes/market.js | 22 +- src/utils/index.js | 27 ++- src/utils/marketNotifs.js | 401 +++++++++++++++++++++++++++++++++++ 6 files changed, 490 insertions(+), 31 deletions(-) create mode 100644 src/utils/marketNotifs.js diff --git a/src/bot/commands/valorant.js b/src/bot/commands/valorant.js index 748b4e5..de25bef 100644 --- a/src/bot/commands/valorant.js +++ b/src/bot/commands/valorant.js @@ -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: [], }, }); diff --git a/src/database/index.js b/src/database/index.js index 0f00aa6..6dac74b 100644 --- a/src/database/index.js +++ b/src/database/index.js @@ -691,6 +691,12 @@ export const updateMarketOffer = flopoDB.prepare(` WHERE id = @id `); +export const deleteMarketOffer = flopoDB.prepare(` + DELETE + FROM market_offers + WHERE id = ? +`); + /* ------------------------- BIDS ----------------------------*/ @@ -721,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) ----------------------------*/ diff --git a/src/server/routes/api.js b/src/server/routes/api.js index 04e92d9..9b7e702 100644 --- a/src/server/routes/api.js +++ b/src/server/routes/api.js @@ -4,12 +4,12 @@ import { sleep } from "openai/core"; // --- Database Imports --- import { getAllAkhys, + getAllAvailableSkins, getAllUsers, getLogs, getMarketOffersBySkin, getOfferBids, getSkin, - getAllAvailableSkins, getUser, getUserElo, getUserGames, @@ -20,8 +20,8 @@ import { insertUser, pruneOldLogs, queryDailyReward, - updateUserCoins, updateSkin, + updateUserCoins, } from "../../database/index.js"; // --- Game State Imports --- @@ -34,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(); @@ -116,12 +117,12 @@ export function apiRoutes(client, io) { } }); - router.post("/open-case", (req, res) => { + router.post("/open-case", async (req, res) => { const { userId, caseType } = req.body; let caseTypeVal, tierWeights; switch (caseType) { - case "standard": + case "standard": caseTypeVal = 1; tierWeights = { "12683d76-48d7-84a3-4e09-6985794f0445": 50, // Select @@ -129,9 +130,9 @@ export function apiRoutes(client, io) { "60bca009-4182-7998-dee7-b8a2558dc369": 15, // Premium "e046854e-406c-37f4-6607-19a9ba8426fc": 4, // Exclusive "411e4a55-4e59-7757-41f0-86a53f101bb5": 1, // Ultra - } + }; break; - case "premium": + case "premium": caseTypeVal = 2; tierWeights = { "12683d76-48d7-84a3-4e09-6985794f0445": 35, // Select @@ -139,9 +140,9 @@ export function apiRoutes(client, io) { "60bca009-4182-7998-dee7-b8a2558dc369": 30, // Premium "e046854e-406c-37f4-6607-19a9ba8426fc": 4, // Exclusive "411e4a55-4e59-7757-41f0-86a53f101bb5": 1, // Ultra - } + }; break; - case "ultra": + case "ultra": caseTypeVal = 4; tierWeights = { "12683d76-48d7-84a3-4e09-6985794f0445": 33, // Select @@ -149,22 +150,19 @@ export function apiRoutes(client, io) { "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), - ); + 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; @@ -252,8 +250,11 @@ export function apiRoutes(client, io) { currentPrice: finalPrice, }); - console.log(`[${Date.now()}] ${userId} opened a Valorant case and received skin ${randomSelectedSkinUuid}`); + 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); @@ -343,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 }); diff --git a/src/server/routes/market.js b/src/server/routes/market.js index 03e117f..7326448 100644 --- a/src/server/routes/market.js +++ b/src/server/routes/market.js @@ -8,6 +8,7 @@ import { ButtonStyle } from "discord.js"; import { getMarketOfferById, getMarketOffers, + getMarketOffersBySkin, getOfferBids, getSkin, getUser, @@ -17,6 +18,7 @@ import { 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(); @@ -78,11 +80,20 @@ export function marketRoutes(client, io) { 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: Date.now() + '-' + seller.id + '-' + skin.uuid, + id: offerId, skin_uuid: skin.uuid, seller_id: seller.id, starting_price: starting_price, @@ -91,12 +102,11 @@ export function marketRoutes(client, io) { opening_at: opening_at, closing_at: closing_at, }); - // Placeholder for placing an offer logic - // Extract data from req.body and process accordingly await emitMarketUpdate(); + await handleNewMarketOffer(offerId, client); res.status(200).send({ message: "Offre créée avec succès" }); } catch (e) { - console.log(e) + console.log(e); return res.status(500).send({ error: e }); } }); @@ -130,8 +140,9 @@ 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" }); + const bidId = Date.now() + "-" + buyer_id + "-" + offer.id; insertBid.run({ - id: Date.now(), + id: bidId, bidder_id: buyer_id, market_offer_id: offer.id, offer_amount: bid_amount, @@ -162,6 +173,7 @@ export function marketRoutes(client, io) { }); } + await handleNewMarketOfferBid(offer.id, bidId, client); await emitMarketUpdate(); res.status(200).send({ error: "Bid placed successfully" }); } catch (e) { diff --git a/src/utils/index.js b/src/utils/index.js index 67b0edc..24841c8 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -6,6 +6,8 @@ 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, @@ -22,6 +24,8 @@ import { } from "../database/index.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 @@ -121,7 +125,6 @@ export async function getAkhys(client) { export function setupCronJobs(client, io) { // Every 5 minutes: Update market offers cron.schedule("* * * * *", () => { - console.log("[Cron] Checking market offers for updates..."); handleMarketOffersUpdate(); }); @@ -180,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 @@ -262,7 +282,7 @@ function handleMarketOffersUpdate() { 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" }); - //TODO: Maybe notify seller that their offer is now open + await handleMarketOfferOpening(offer.id, client); await emitMarketUpdate(); } if (now >= offer.closing_at && offer.status !== "closed") { @@ -301,13 +321,12 @@ function handleMarketOffersUpdate() { }); const newUserCoins = seller.coins + lastBid.offer_amount; updateUserCoins.run({ id: seller.id, coins: newUserCoins }); - //TODO: Notify users in DMs await emitMarketUpdate(); } catch (e) { console.error(`[Market Cron] Error processing offer ID: ${offer.id}`, e); } } - + await handleMarketOfferClosing(offer.id, client); } }); } diff --git a/src/utils/marketNotifs.js b/src/utils/marketNotifs.js new file mode 100644 index 0000000..6f9baec --- /dev/null +++ b/src/utils/marketNotifs.js @@ -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: ``, + }, + { + name: "⏰ Fermeture", + value: ``, + }, + { + 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: ``, + }, + { + name: "⏰ Fermeture", + value: ``, + }, + { + 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: ``, + }, + { + 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: ``, + }, + ) + .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: ``, + }, + { + 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); + } +}