CASE OPENING AND MARKETPLACE

This commit is contained in:
Milo
2025-12-19 17:05:16 +01:00
parent 8290e00310
commit 738e21aaf7
6 changed files with 490 additions and 31 deletions

View File

@@ -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: [],
},
});

View File

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

View File

@@ -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 });

View File

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

View File

@@ -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);
}
});
}

401
src/utils/marketNotifs.js Normal file
View 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 loffre",
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 loffre",
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 loffre",
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 loffre",
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 lenchère",
value: `\`${bid.offer_amount} coins\``,
inline: true,
},
{
name: "⏰ Fermeture",
value: `<t:${Math.floor(offer.closing_at / 1000)}:F>`,
},
{
name: "🆔 ID de loffre",
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 lenchè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 lenchè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);
}
}