mirror of
https://github.com/cassoule/flopobot_v2.git
synced 2026-03-18 21:40:27 +01:00
cs skins
This commit is contained in:
44
prisma/migrations/20260301140605_add_cs_skins/migration.sql
Normal file
44
prisma/migrations/20260301140605_add_cs_skins/migration.sql
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "cs_skins" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"market_hash_name" TEXT NOT NULL,
|
||||||
|
"displayName" TEXT,
|
||||||
|
"image_url" TEXT,
|
||||||
|
"rarity" TEXT,
|
||||||
|
"rarity_color" TEXT,
|
||||||
|
"weapon_type" TEXT,
|
||||||
|
"float" REAL,
|
||||||
|
"wear_state" TEXT,
|
||||||
|
"is_stattrak" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"is_souvenir" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"price" INTEGER,
|
||||||
|
"user_id" TEXT,
|
||||||
|
CONSTRAINT "cs_skins_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA defer_foreign_keys=ON;
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_market_offers" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"skin_uuid" TEXT,
|
||||||
|
"cs_skin_id" TEXT,
|
||||||
|
"seller_id" TEXT NOT NULL,
|
||||||
|
"starting_price" INTEGER NOT NULL,
|
||||||
|
"buyout_price" INTEGER,
|
||||||
|
"final_price" INTEGER,
|
||||||
|
"status" TEXT NOT NULL,
|
||||||
|
"posted_at" DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"opening_at" DATETIME NOT NULL,
|
||||||
|
"closing_at" DATETIME NOT NULL,
|
||||||
|
"buyer_id" TEXT,
|
||||||
|
CONSTRAINT "market_offers_skin_uuid_fkey" FOREIGN KEY ("skin_uuid") REFERENCES "skins" ("uuid") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "market_offers_cs_skin_id_fkey" FOREIGN KEY ("cs_skin_id") REFERENCES "cs_skins" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "market_offers_seller_id_fkey" FOREIGN KEY ("seller_id") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "market_offers_buyer_id_fkey" FOREIGN KEY ("buyer_id") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_market_offers" ("buyer_id", "buyout_price", "closing_at", "final_price", "id", "opening_at", "posted_at", "seller_id", "skin_uuid", "starting_price", "status") SELECT "buyer_id", "buyout_price", "closing_at", "final_price", "id", "opening_at", "posted_at", "seller_id", "skin_uuid", "starting_price", "status" FROM "market_offers";
|
||||||
|
DROP TABLE "market_offers";
|
||||||
|
ALTER TABLE "new_market_offers" RENAME TO "market_offers";
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
|
PRAGMA defer_foreign_keys=OFF;
|
||||||
@@ -22,6 +22,7 @@ model User {
|
|||||||
|
|
||||||
elo Elo?
|
elo Elo?
|
||||||
skins Skin[]
|
skins Skin[]
|
||||||
|
csSkins CsSkin[] @relation("CsSkins")
|
||||||
sellerOffers MarketOffer[] @relation("Seller")
|
sellerOffers MarketOffer[] @relation("Seller")
|
||||||
buyerOffers MarketOffer[] @relation("Buyer")
|
buyerOffers MarketOffer[] @relation("Buyer")
|
||||||
bids Bid[]
|
bids Bid[]
|
||||||
@@ -56,9 +57,31 @@ model Skin {
|
|||||||
@@map("skins")
|
@@map("skins")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model CsSkin {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
marketHashName String @map("market_hash_name")
|
||||||
|
displayName String?
|
||||||
|
imageUrl String? @map("image_url")
|
||||||
|
rarity String?
|
||||||
|
rarityColor String? @map("rarity_color")
|
||||||
|
weaponType String? @map("weapon_type")
|
||||||
|
float Float?
|
||||||
|
wearState String? @map("wear_state")
|
||||||
|
isStattrak Boolean @default(false) @map("is_stattrak")
|
||||||
|
isSouvenir Boolean @default(false) @map("is_souvenir")
|
||||||
|
price Int?
|
||||||
|
userId String? @map("user_id")
|
||||||
|
|
||||||
|
owner User? @relation("CsSkins", fields: [userId], references: [id])
|
||||||
|
marketOffers MarketOffer[] @relation("CsSkinOffers")
|
||||||
|
|
||||||
|
@@map("cs_skins")
|
||||||
|
}
|
||||||
|
|
||||||
model MarketOffer {
|
model MarketOffer {
|
||||||
id String @id
|
id String @id
|
||||||
skinUuid String @map("skin_uuid")
|
skinUuid String? @map("skin_uuid")
|
||||||
|
csSkinId String? @map("cs_skin_id")
|
||||||
sellerId String @map("seller_id")
|
sellerId String @map("seller_id")
|
||||||
startingPrice Int @map("starting_price")
|
startingPrice Int @map("starting_price")
|
||||||
buyoutPrice Int? @map("buyout_price")
|
buyoutPrice Int? @map("buyout_price")
|
||||||
@@ -69,9 +92,10 @@ model MarketOffer {
|
|||||||
closingAt DateTime @map("closing_at")
|
closingAt DateTime @map("closing_at")
|
||||||
buyerId String? @map("buyer_id")
|
buyerId String? @map("buyer_id")
|
||||||
|
|
||||||
skin Skin @relation(fields: [skinUuid], references: [uuid])
|
skin Skin? @relation(fields: [skinUuid], references: [uuid])
|
||||||
seller User @relation("Seller", fields: [sellerId], references: [id])
|
csSkin CsSkin? @relation("CsSkinOffers", fields: [csSkinId], references: [id])
|
||||||
buyer User? @relation("Buyer", fields: [buyerId], references: [id])
|
seller User @relation("Seller", fields: [sellerId], references: [id])
|
||||||
|
buyer User? @relation("Buyer", fields: [buyerId], references: [id])
|
||||||
bids Bid[]
|
bids Bid[]
|
||||||
|
|
||||||
@@map("market_offers")
|
@@map("market_offers")
|
||||||
|
|||||||
@@ -36,19 +36,13 @@ export const fetchSkinsData = async () => {
|
|||||||
`https://raw.githubusercontent.com/ByMykel/CSGO-API/main/public/api/en/skins.json`,
|
`https://raw.githubusercontent.com/ByMykel/CSGO-API/main/public/api/en/skins.json`,
|
||||||
);
|
);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
let rarities = {};
|
|
||||||
data.forEach((skin) => {
|
data.forEach((skin) => {
|
||||||
if (skin.market_hash_name) {
|
if (skin.market_hash_name) {
|
||||||
csSkinsData[skin.market_hash_name] = skin;
|
csSkinsData[skin.market_hash_name] = skin;
|
||||||
} else if (skin.name) {
|
} else if (skin.name) {
|
||||||
csSkinsData[skin.name] = skin;
|
csSkinsData[skin.name] = skin;
|
||||||
}
|
}
|
||||||
if (skin.rarity && skin.rarity.name) {
|
|
||||||
rarities[skin.rarity.name] = (rarities[skin.rarity.name] || 0) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
console.log(rarities)
|
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching skins data:", error);
|
console.error("Error fetching skins data:", error);
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ import {
|
|||||||
} from "discord-interactions";
|
} from "discord-interactions";
|
||||||
import { activeInventories, skins } from "../../game/state.js";
|
import { activeInventories, skins } from "../../game/state.js";
|
||||||
import * as skinService from "../../services/skin.service.js";
|
import * as skinService from "../../services/skin.service.js";
|
||||||
|
import * as csSkinService from "../../services/csSkin.service.js";
|
||||||
|
import { RarityToColor } from "../../utils/cs.utils.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the /inventory slash command.
|
* Handles the /inventory slash command.
|
||||||
* Displays a paginated, interactive embed of a user's Valorant skin inventory.
|
* Displays a paginated, interactive embed of a user's skin inventory.
|
||||||
*
|
*
|
||||||
* @param {object} req - The Express request object.
|
* @param {object} req - The Express request object.
|
||||||
* @param {object} res - The Express response object.
|
* @param {object} res - The Express response object.
|
||||||
@@ -26,16 +28,22 @@ export async function handleInventoryCommand(req, res, client, interactionId) {
|
|||||||
});
|
});
|
||||||
const { member, guild_id, token, data } = req.body;
|
const { member, guild_id, token, data } = req.body;
|
||||||
const commandUserId = member.user.id;
|
const commandUserId = member.user.id;
|
||||||
// User can specify another member, otherwise it defaults to themself
|
|
||||||
const targetUserId = data.options && data.options.length > 0 ? data.options[0].value : commandUserId;
|
const targetUserId = data.options && data.options.length > 0 ? data.options[0].value : commandUserId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// --- 1. Fetch Data ---
|
|
||||||
const guild = await client.guilds.fetch(guild_id);
|
const guild = await client.guilds.fetch(guild_id);
|
||||||
const targetMember = await guild.members.fetch(targetUserId);
|
const targetMember = await guild.members.fetch(targetUserId);
|
||||||
const inventorySkins = await skinService.getUserInventory(targetUserId);
|
|
||||||
|
|
||||||
// --- 2. Handle Empty Inventory ---
|
// Fetch both Valorant and CS2 inventories
|
||||||
|
const valoSkins = await skinService.getUserInventory(targetUserId);
|
||||||
|
const csSkins = await csSkinService.getUserCsInventory(targetUserId);
|
||||||
|
|
||||||
|
// Combine into a unified list with a type marker
|
||||||
|
const inventorySkins = [
|
||||||
|
...csSkins.map((s) => ({ ...s, _type: "cs" })),
|
||||||
|
...valoSkins.map((s) => ({ ...s, _type: "valo" })),
|
||||||
|
];
|
||||||
|
|
||||||
if (inventorySkins.length === 0) {
|
if (inventorySkins.length === 0) {
|
||||||
return res.send({
|
return res.send({
|
||||||
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
|
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
|
||||||
@@ -44,64 +52,30 @@ export async function handleInventoryCommand(req, res, client, interactionId) {
|
|||||||
{
|
{
|
||||||
title: `Inventaire de ${targetMember.user.globalName || targetMember.user.username}`,
|
title: `Inventaire de ${targetMember.user.globalName || targetMember.user.username}`,
|
||||||
description: "Cet inventaire est vide.",
|
description: "Cet inventaire est vide.",
|
||||||
color: 0x4f545c, // Discord Gray
|
color: 0x4f545c,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 3. Store Interactive Session State ---
|
|
||||||
// This state is crucial for the component handlers to know which inventory to update.
|
|
||||||
activeInventories[interactionId] = {
|
activeInventories[interactionId] = {
|
||||||
akhyId: targetUserId, // The inventory owner
|
akhyId: targetUserId,
|
||||||
userId: commandUserId, // The user who ran the command
|
userId: commandUserId,
|
||||||
page: 0,
|
page: 0,
|
||||||
amount: inventorySkins.length,
|
amount: inventorySkins.length,
|
||||||
endpoint: `webhooks/${process.env.APP_ID}/${token}/messages/@original`,
|
endpoint: `webhooks/${process.env.APP_ID}/${token}/messages/@original`,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
inventorySkins: inventorySkins, // Cache the skins to avoid re-querying the DB on each page turn
|
inventorySkins: inventorySkins,
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- 4. Prepare Embed Content ---
|
|
||||||
const currentSkin = inventorySkins[0];
|
const currentSkin = inventorySkins[0];
|
||||||
const skinData = skins.find((s) => s.uuid === currentSkin.uuid);
|
const totalPrice = inventorySkins.reduce((sum, skin) => {
|
||||||
if (!skinData) {
|
return sum + (skin._type === "cs" ? skin.price || 0 : skin.currentPrice || 0);
|
||||||
throw new Error(`Skin data not found for UUID: ${currentSkin.uuid}`);
|
}, 0);
|
||||||
}
|
|
||||||
const totalPrice = inventorySkins.reduce((sum, skin) => sum + (skin.currentPrice || 0), 0);
|
|
||||||
|
|
||||||
// --- Helper functions for formatting ---
|
const embed = buildSkinEmbed(currentSkin, targetMember, 1, inventorySkins.length, totalPrice);
|
||||||
const getChromaText = (skin, skinInfo) => {
|
|
||||||
let result = "";
|
|
||||||
for (let i = 1; i <= skinInfo.chromas.length; i++) {
|
|
||||||
result += skin.currentChroma === i ? "💠 " : "◾ ";
|
|
||||||
}
|
|
||||||
return result || "N/A";
|
|
||||||
};
|
|
||||||
|
|
||||||
const getChromaName = (skin, skinInfo) => {
|
|
||||||
if (skin.currentChroma > 1) {
|
|
||||||
const name = skinInfo.chromas[skin.currentChroma - 1]?.displayName
|
|
||||||
.replace(/[\r\n]+/g, " ")
|
|
||||||
.replace(skinInfo.displayName, "")
|
|
||||||
.trim();
|
|
||||||
const match = name.match(/Variante\s*[0-9\s]*-\s*([^)]+)/i);
|
|
||||||
return match ? match[1].trim() : name;
|
|
||||||
}
|
|
||||||
return "Base";
|
|
||||||
};
|
|
||||||
|
|
||||||
const getImageUrl = (skin, skinInfo) => {
|
|
||||||
if (skin.currentLvl === skinInfo.levels.length) {
|
|
||||||
const chroma = skinInfo.chromas[skin.currentChroma - 1];
|
|
||||||
return chroma?.fullRender || chroma?.displayIcon || skinInfo.displayIcon;
|
|
||||||
}
|
|
||||||
const level = skinInfo.levels[skin.currentLvl - 1];
|
|
||||||
return level?.displayIcon || skinInfo.displayIcon || skinInfo.chromas[0].fullRender;
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- 5. Build Initial Components (Buttons) ---
|
|
||||||
const components = [
|
const components = [
|
||||||
{
|
{
|
||||||
type: MessageComponentTypes.BUTTON,
|
type: MessageComponentTypes.BUTTON,
|
||||||
@@ -117,38 +91,10 @@ export async function handleInventoryCommand(req, res, client, interactionId) {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const isUpgradable =
|
|
||||||
currentSkin.currentLvl < skinData.levels.length || currentSkin.currentChroma < skinData.chromas.length;
|
|
||||||
// Only show upgrade button if the skin is upgradable AND the command user owns the inventory
|
|
||||||
if (isUpgradable && targetUserId === commandUserId) {
|
|
||||||
components.push({
|
|
||||||
type: MessageComponentTypes.BUTTON,
|
|
||||||
custom_id: `upgrade_${interactionId}`,
|
|
||||||
label: `Upgrade ⏫ (${process.env.VALO_UPGRADE_PRICE || (currentSkin.maxPrice / 10).toFixed(0)} Flopos)`,
|
|
||||||
style: ButtonStyleTypes.PRIMARY,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 6. Send Final Response ---
|
|
||||||
return res.send({
|
return res.send({
|
||||||
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
|
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
|
||||||
data: {
|
data: {
|
||||||
embeds: [
|
embeds: [embed],
|
||||||
{
|
|
||||||
title: `Inventaire de ${targetMember.user.globalName || targetMember.user.username}`,
|
|
||||||
color: parseInt(currentSkin.tierColor, 16) || 0xf2f3f3,
|
|
||||||
footer: {
|
|
||||||
text: `Page 1/${inventorySkins.length} | Valeur Totale : ${totalPrice.toFixed(0)} Flopos`,
|
|
||||||
},
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: `${currentSkin.displayName} | ${currentSkin.currentPrice.toFixed(0)} Flopos`,
|
|
||||||
value: `${currentSkin.tierText}\nChroma : ${getChromaText(currentSkin, skinData)} | ${getChromaName(currentSkin, skinData)}\nLvl : **${currentSkin.currentLvl}**/${skinData.levels.length}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
image: { url: getImageUrl(currentSkin, skinData) },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
components: [
|
components: [
|
||||||
{ type: MessageComponentTypes.ACTION_ROW, components: components },
|
{ type: MessageComponentTypes.ACTION_ROW, components: components },
|
||||||
{
|
{
|
||||||
@@ -170,3 +116,47 @@ export async function handleInventoryCommand(req, res, client, interactionId) {
|
|||||||
return res.status(500).json({ error: "Failed to generate inventory." });
|
return res.status(500).json({ error: "Failed to generate inventory." });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an embed for a single skin (CS2 or Valorant).
|
||||||
|
*/
|
||||||
|
export function buildSkinEmbed(skin, targetMember, page, total, totalPrice) {
|
||||||
|
if (skin._type === "cs") {
|
||||||
|
const badges = [
|
||||||
|
skin.isStattrak ? "StatTrak™" : null,
|
||||||
|
skin.isSouvenir ? "Souvenir" : null,
|
||||||
|
].filter(Boolean).join(" | ");
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: `Inventaire de ${targetMember.user.globalName || targetMember.user.username}`,
|
||||||
|
color: RarityToColor[skin.rarity] || 0xf2f3f3,
|
||||||
|
footer: {
|
||||||
|
text: `Page ${page}/${total} | Valeur Totale : ${totalPrice} Flopos`,
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: `${skin.displayName} | ${skin.price} Flopos`,
|
||||||
|
value: `${skin.rarity}${badges ? ` | ${badges}` : ""}\n${skin.wearState} (float: ${skin.float?.toFixed(8)})\n${skin.weaponType || ""}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
image: skin.imageUrl ? { url: skin.imageUrl } : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valorant skin fallback
|
||||||
|
const skinData = skins.find((s) => s.uuid === skin.uuid);
|
||||||
|
return {
|
||||||
|
title: `Inventaire de ${targetMember.user.globalName || targetMember.user.username}`,
|
||||||
|
color: parseInt(skin.tierColor, 16) || 0xf2f3f3,
|
||||||
|
footer: {
|
||||||
|
text: `Page ${page}/${total} | Valeur Totale : ${totalPrice} Flopos`,
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: `${skin.displayName} | ${(skin.currentPrice || 0).toFixed(0)} Flopos`,
|
||||||
|
value: `${skin.tierText || "Valorant"}\nLvl : **${skin.currentLvl}**/${skinData?.levels?.length || "?"}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
image: skinData ? { url: skinData.displayIcon } : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import {
|
|||||||
} from "discord-interactions";
|
} from "discord-interactions";
|
||||||
|
|
||||||
import { DiscordRequest } from "../../api/discord.js";
|
import { DiscordRequest } from "../../api/discord.js";
|
||||||
import { activeInventories, skins } from "../../game/state.js";
|
import { activeInventories } from "../../game/state.js";
|
||||||
|
import { buildSkinEmbed } from "../commands/inventory.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles navigation button clicks (Previous/Next) for the inventory embed.
|
* Handles navigation button clicks (Previous/Next) for the inventory embed.
|
||||||
@@ -18,13 +19,10 @@ export async function handleInventoryNav(req, res, client) {
|
|||||||
const { member, data, guild_id } = req.body;
|
const { member, data, guild_id } = req.body;
|
||||||
const { custom_id } = data;
|
const { custom_id } = data;
|
||||||
|
|
||||||
// Extract direction ('prev' or 'next') and the original interaction ID from the custom_id
|
|
||||||
const [direction, page, interactionId] = custom_id.split("_");
|
const [direction, page, interactionId] = custom_id.split("_");
|
||||||
|
|
||||||
// --- 1. Retrieve the interactive session ---
|
|
||||||
const inventorySession = activeInventories[interactionId];
|
const inventorySession = activeInventories[interactionId];
|
||||||
|
|
||||||
// --- 2. Validation Checks ---
|
|
||||||
if (!inventorySession) {
|
if (!inventorySession) {
|
||||||
return res.send({
|
return res.send({
|
||||||
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
|
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
|
||||||
@@ -35,7 +33,6 @@ export async function handleInventoryNav(req, res, client) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the user clicking the button is the one who initiated the command
|
|
||||||
if (inventorySession.userId !== member.user.id) {
|
if (inventorySession.userId !== member.user.id) {
|
||||||
return res.send({
|
return res.send({
|
||||||
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
|
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
|
||||||
@@ -46,7 +43,6 @@ export async function handleInventoryNav(req, res, client) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 3. Update Page Number ---
|
|
||||||
const { amount } = inventorySession;
|
const { amount } = inventorySession;
|
||||||
if (direction === "next") {
|
if (direction === "next") {
|
||||||
inventorySession.page = (inventorySession.page + 1) % amount;
|
inventorySession.page = (inventorySession.page + 1) % amount;
|
||||||
@@ -55,49 +51,18 @@ export async function handleInventoryNav(req, res, client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// --- 4. Rebuild Embed with New Page Content ---
|
const { inventorySkins } = inventorySession;
|
||||||
const { page, inventorySkins } = inventorySession;
|
const currentPage = inventorySession.page;
|
||||||
const currentSkin = inventorySkins[page];
|
const currentSkin = inventorySkins[currentPage];
|
||||||
const skinData = skins.find((s) => s.uuid === currentSkin.uuid);
|
|
||||||
if (!skinData) {
|
|
||||||
throw new Error(`Skin data not found for UUID: ${currentSkin.uuid}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const guild = await client.guilds.fetch(guild_id);
|
const guild = await client.guilds.fetch(guild_id);
|
||||||
const targetMember = await guild.members.fetch(inventorySession.akhyId);
|
const targetMember = await guild.members.fetch(inventorySession.akhyId);
|
||||||
const totalPrice = inventorySkins.reduce((sum, skin) => sum + (skin.currentPrice || 0), 0);
|
const totalPrice = inventorySkins.reduce((sum, skin) => {
|
||||||
|
return sum + (skin._type === "cs" ? skin.price || 0 : skin.currentPrice || 0);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
// --- Helper functions for formatting ---
|
const embed = buildSkinEmbed(currentSkin, targetMember, currentPage + 1, amount, totalPrice);
|
||||||
const getChromaText = (skin, skinInfo) => {
|
|
||||||
let result = "";
|
|
||||||
for (let i = 1; i <= skinInfo.chromas.length; i++) {
|
|
||||||
result += skin.currentChroma === i ? "💠 " : "◾ ";
|
|
||||||
}
|
|
||||||
return result || "N/A";
|
|
||||||
};
|
|
||||||
|
|
||||||
const getChromaName = (skin, skinInfo) => {
|
|
||||||
if (skin.currentChroma > 1) {
|
|
||||||
const name = skinInfo.chromas[skin.currentChroma - 1]?.displayName
|
|
||||||
.replace(/[\r\n]+/g, " ")
|
|
||||||
.replace(skinInfo.displayName, "")
|
|
||||||
.trim();
|
|
||||||
const match = name.match(/Variante\s*[0-9\s]*-\s*([^)]+)/i);
|
|
||||||
return match ? match[1].trim() : name;
|
|
||||||
}
|
|
||||||
return "Base";
|
|
||||||
};
|
|
||||||
|
|
||||||
const getImageUrl = (skin, skinInfo) => {
|
|
||||||
if (skin.currentLvl === skinInfo.levels.length) {
|
|
||||||
const chroma = skinInfo.chromas[skin.currentChroma - 1];
|
|
||||||
return chroma?.fullRender || chroma?.displayIcon || skinInfo.displayIcon;
|
|
||||||
}
|
|
||||||
const level = skinInfo.levels[skin.currentLvl - 1];
|
|
||||||
return level?.displayIcon || skinInfo.displayIcon || skinInfo.chromas[0].fullRender;
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- 5. Rebuild Components (Buttons) ---
|
|
||||||
let components = [
|
let components = [
|
||||||
{
|
{
|
||||||
type: MessageComponentTypes.BUTTON,
|
type: MessageComponentTypes.BUTTON,
|
||||||
@@ -113,38 +78,10 @@ export async function handleInventoryNav(req, res, client) {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const isUpgradable =
|
|
||||||
currentSkin.currentLvl < skinData.levels.length || currentSkin.currentChroma < skinData.chromas.length;
|
|
||||||
// Conditionally add the upgrade button
|
|
||||||
if (isUpgradable && inventorySession.akhyId === inventorySession.userId) {
|
|
||||||
components.push({
|
|
||||||
type: MessageComponentTypes.BUTTON,
|
|
||||||
custom_id: `upgrade_${interactionId}`,
|
|
||||||
label: `Upgrade ⏫ (${process.env.VALO_UPGRADE_PRICE || (currentSkin.maxPrice / 10).toFixed(0)} Flopos)`,
|
|
||||||
style: ButtonStyleTypes.PRIMARY,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 6. Send PATCH Request to Update the Message ---
|
|
||||||
await DiscordRequest(inventorySession.endpoint, {
|
await DiscordRequest(inventorySession.endpoint, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
body: {
|
body: {
|
||||||
embeds: [
|
embeds: [embed],
|
||||||
{
|
|
||||||
title: `Inventaire de ${targetMember.user.globalName || targetMember.user.username}`,
|
|
||||||
color: parseInt(currentSkin.tierColor, 16) || 0xf2f3f3,
|
|
||||||
footer: {
|
|
||||||
text: `Page ${page + 1}/${amount} | Valeur Totale : ${totalPrice.toFixed(0)} Flopos`,
|
|
||||||
},
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: `${currentSkin.displayName} | ${currentSkin.currentPrice.toFixed(0)} Flopos`,
|
|
||||||
value: `${currentSkin.tierText}\nChroma : ${getChromaText(currentSkin, skinData)} | ${getChromaName(currentSkin, skinData)}\nLvl : **${currentSkin.currentLvl}**/${skinData.levels.length}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
image: { url: getImageUrl(currentSkin, skinData) },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
components: [
|
components: [
|
||||||
{ type: MessageComponentTypes.ACTION_ROW, components: components },
|
{ type: MessageComponentTypes.ACTION_ROW, components: components },
|
||||||
{
|
{
|
||||||
@@ -162,14 +99,9 @@ export async function handleInventoryNav(req, res, client) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- 7. Acknowledge the Interaction ---
|
|
||||||
// This tells Discord the interaction was received, and since the message is already updated,
|
|
||||||
// no further action is needed.
|
|
||||||
return res.send({ type: InteractionResponseType.DEFERRED_UPDATE_MESSAGE });
|
return res.send({ type: InteractionResponseType.DEFERRED_UPDATE_MESSAGE });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error handling inventory navigation:", error);
|
console.error("Error handling inventory navigation:", error);
|
||||||
// In case of an error, we should still acknowledge the interaction to prevent it from failing.
|
|
||||||
// We can send a silent, ephemeral error message.
|
|
||||||
return res.send({
|
return res.send({
|
||||||
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
|
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ import { client } from "../client.js";
|
|||||||
import { drawCaseContent, drawCaseSkin, getDummySkinUpgradeProbs } from "../../utils/caseOpening.js";
|
import { drawCaseContent, drawCaseSkin, getDummySkinUpgradeProbs } from "../../utils/caseOpening.js";
|
||||||
import { fetchSuggestedPrices, fetchSkinsData } from "../../api/cs.js";
|
import { fetchSuggestedPrices, fetchSkinsData } from "../../api/cs.js";
|
||||||
import { csSkinsData, csSkinsPrices } from "../../utils/cs.state.js";
|
import { csSkinsData, csSkinsPrices } from "../../utils/cs.state.js";
|
||||||
import { getRandomSkinWithRandomSpecs } from "../../utils/cs.utils.js";
|
import { getRandomSkinWithRandomSpecs, RarityToColor } from "../../utils/cs.utils.js";
|
||||||
|
import * as csSkinService from "../../services/csSkin.service.js";
|
||||||
|
|
||||||
// Constants for the AI rate limiter
|
// Constants for the AI rate limiter
|
||||||
const MAX_REQUESTS_PER_INTERVAL = parseInt(process.env.MAX_REQUESTS || "5");
|
const MAX_REQUESTS_PER_INTERVAL = parseInt(process.env.MAX_REQUESTS || "5");
|
||||||
@@ -431,12 +432,26 @@ async function handleAdminCommands(message) {
|
|||||||
case `${prefix}:open-cs`:
|
case `${prefix}:open-cs`:
|
||||||
try {
|
try {
|
||||||
const randomSkin = getRandomSkinWithRandomSpecs(args[0] ? parseFloat(args[0]) : null);
|
const randomSkin = getRandomSkinWithRandomSpecs(args[0] ? parseFloat(args[0]) : null);
|
||||||
|
const created = await csSkinService.insertCsSkin({
|
||||||
|
marketHashName: randomSkin.name,
|
||||||
|
displayName: randomSkin.data.name || randomSkin.name,
|
||||||
|
imageUrl: randomSkin.data.image || null,
|
||||||
|
rarity: randomSkin.data.rarity.name,
|
||||||
|
rarityColor: RarityToColor[randomSkin.data.rarity.name]?.toString(16) || null,
|
||||||
|
weaponType: randomSkin.data.weapon?.name || null,
|
||||||
|
float: randomSkin.float,
|
||||||
|
wearState: randomSkin.wearState,
|
||||||
|
isStattrak: randomSkin.isStattrak,
|
||||||
|
isSouvenir: randomSkin.isSouvenir,
|
||||||
|
price: parseInt(randomSkin.price),
|
||||||
|
userId: message.author.id,
|
||||||
|
});
|
||||||
message.reply(
|
message.reply(
|
||||||
`You opened a CS:GO case and got: ${randomSkin.name} (${randomSkin.data.rarity.name}, ${
|
`You opened a CS:GO case and got: ${randomSkin.name} (${randomSkin.data.rarity.name}, ${
|
||||||
randomSkin.isStattrak ? "StatTrak, " : ""
|
randomSkin.isStattrak ? "StatTrak, " : ""
|
||||||
}${randomSkin.isSouvenir ? "Souvenir, " : ""}${randomSkin.wearState} - float ${randomSkin.float})\nBase Price: ${
|
}${randomSkin.isSouvenir ? "Souvenir, " : ""}${randomSkin.wearState} - float ${randomSkin.float})\nBase Price: ${
|
||||||
randomSkin.price ?? "N/A"
|
randomSkin.price ?? "N/A"
|
||||||
} Flopos\nImage url: [url](${randomSkin.data.image || "N/A"})`,
|
} Flopos\nSkin ID: ${created.id}\nImage url: [url](${randomSkin.data.image || "N/A"})`,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import * as skinService from "../../services/skin.service.js";
|
|||||||
import * as logService from "../../services/log.service.js";
|
import * as logService from "../../services/log.service.js";
|
||||||
import * as transactionService from "../../services/transaction.service.js";
|
import * as transactionService from "../../services/transaction.service.js";
|
||||||
import * as marketService from "../../services/market.service.js";
|
import * as marketService from "../../services/market.service.js";
|
||||||
|
import * as csSkinService from "../../services/csSkin.service.js";
|
||||||
|
|
||||||
// --- Game State Imports ---
|
// --- Game State Imports ---
|
||||||
import { activePolls, activePredis, activeSlowmodes, skins, activeSnakeGames } from "../../game/state.js";
|
import { activePolls, activePredis, activeSlowmodes, skins, activeSnakeGames } from "../../game/state.js";
|
||||||
@@ -23,6 +24,7 @@ import { emitDataUpdated, socketEmit, onGameOver } from "../socket.js";
|
|||||||
import { handleCaseOpening } from "../../utils/marketNotifs.js";
|
import { handleCaseOpening } from "../../utils/marketNotifs.js";
|
||||||
import { drawCaseContent, drawCaseSkin, getSkinUpgradeProbs } from "../../utils/caseOpening.js";
|
import { drawCaseContent, drawCaseSkin, getSkinUpgradeProbs } from "../../utils/caseOpening.js";
|
||||||
import { requireAuth } from "../middleware/auth.js";
|
import { requireAuth } from "../middleware/auth.js";
|
||||||
|
import { getRandomSkinWithRandomSpecs, RarityToColor, TRADE_UP_MAP } from "../../utils/cs.utils.js";
|
||||||
|
|
||||||
// Create a new router instance
|
// Create a new router instance
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
@@ -181,6 +183,137 @@ export function apiRoutes(client, io) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post("/open-cs-case", requireAuth, async (req, res) => {
|
||||||
|
const userId = req.userId;
|
||||||
|
const casePrice = parseInt(process.env.CS_CASE_PRICE) || 250;
|
||||||
|
|
||||||
|
const commandUser = await userService.getUser(userId);
|
||||||
|
if (!commandUser) return res.status(404).json({ error: "User not found." });
|
||||||
|
if (commandUser.coins < casePrice) return res.status(403).json({ error: "Not enough FlopoCoins." });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const randomSkin = getRandomSkinWithRandomSpecs(null, "Covert");
|
||||||
|
|
||||||
|
const created = await csSkinService.insertCsSkin({
|
||||||
|
marketHashName: randomSkin.name,
|
||||||
|
displayName: randomSkin.data.name || randomSkin.name,
|
||||||
|
imageUrl: randomSkin.data.image || null,
|
||||||
|
rarity: randomSkin.data.rarity.name,
|
||||||
|
rarityColor: RarityToColor[randomSkin.data.rarity.name]?.toString(16) || null,
|
||||||
|
weaponType: randomSkin.data.weapon?.name || null,
|
||||||
|
float: randomSkin.float,
|
||||||
|
wearState: randomSkin.wearState,
|
||||||
|
isStattrak: randomSkin.isStattrak,
|
||||||
|
isSouvenir: randomSkin.isSouvenir,
|
||||||
|
price: parseInt(randomSkin.price),
|
||||||
|
userId: userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
await logService.insertLog({
|
||||||
|
id: `${userId}-${Date.now()}`,
|
||||||
|
userId: userId,
|
||||||
|
action: "CS_CASE_OPEN",
|
||||||
|
targetUserId: null,
|
||||||
|
coinsAmount: -casePrice,
|
||||||
|
userNewAmount: commandUser.coins - casePrice,
|
||||||
|
});
|
||||||
|
await userService.updateUserCoins(userId, commandUser.coins - casePrice);
|
||||||
|
|
||||||
|
// Generate roulette decoy skins for the animation
|
||||||
|
const ROULETTE_SIZE = 50;
|
||||||
|
const resultIndex = 12 + Math.floor(Math.random() * 5); // Place result around index 12-16
|
||||||
|
const rouletteSkins = [];
|
||||||
|
for (let i = 0; i < ROULETTE_SIZE; i++) {
|
||||||
|
if (i === resultIndex) {
|
||||||
|
rouletteSkins.push({
|
||||||
|
displayName: created.displayName,
|
||||||
|
imageUrl: created.imageUrl,
|
||||||
|
rarity: created.rarity,
|
||||||
|
rarityColor: created.rarityColor,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const decoy = getRandomSkinWithRandomSpecs();
|
||||||
|
rouletteSkins.push({
|
||||||
|
displayName: decoy.data.name || decoy.name,
|
||||||
|
imageUrl: decoy.data.image || null,
|
||||||
|
rarity: decoy.data.rarity.name,
|
||||||
|
rarityColor: RarityToColor[decoy.data.rarity.name]?.toString(16) || null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ skin: created, rouletteSkins, resultIndex });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error opening CS case:", error);
|
||||||
|
res.status(500).json({ error: "Failed to open CS case." });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/trade-up", requireAuth, async (req, res) => {
|
||||||
|
const userId = req.userId;
|
||||||
|
const { skinIds } = req.body;
|
||||||
|
|
||||||
|
if (!Array.isArray(skinIds) || skinIds.length !== 10) {
|
||||||
|
return res.status(400).json({ error: "You must provide exactly 10 skin IDs." });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const skins = await Promise.all(skinIds.map((id) => csSkinService.getCsSkin(id)));
|
||||||
|
|
||||||
|
// Validate all skins exist and are owned by the user
|
||||||
|
for (const skin of skins) {
|
||||||
|
if (!skin) return res.status(404).json({ error: "One or more skins not found." });
|
||||||
|
if (skin.userId !== userId) return res.status(403).json({ error: "You don't own all of these skins." });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate all skins are the same rarity
|
||||||
|
const rarity = skins[0].rarity;
|
||||||
|
if (!skins.every((s) => s.rarity === rarity)) {
|
||||||
|
return res.status(400).json({ error: "All 10 skins must be the same rarity." });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate rarity can be traded up
|
||||||
|
const nextRarity = TRADE_UP_MAP[rarity];
|
||||||
|
if (!nextRarity) {
|
||||||
|
return res.status(400).json({ error: `${rarity} skins cannot be used in trade-up contracts.` });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the 10 input skins
|
||||||
|
await csSkinService.deleteManyCsSkins(skinIds);
|
||||||
|
|
||||||
|
// Generate a new skin at the next rarity tier
|
||||||
|
const newSkin = getRandomSkinWithRandomSpecs(null, nextRarity);
|
||||||
|
const created = await csSkinService.insertCsSkin({
|
||||||
|
marketHashName: newSkin.name,
|
||||||
|
displayName: newSkin.data.name || newSkin.name,
|
||||||
|
imageUrl: newSkin.data.image || null,
|
||||||
|
rarity: newSkin.data.rarity.name,
|
||||||
|
rarityColor: RarityToColor[newSkin.data.rarity.name]?.toString(16) || null,
|
||||||
|
weaponType: newSkin.data.weapon?.name || null,
|
||||||
|
float: newSkin.float,
|
||||||
|
wearState: newSkin.wearState,
|
||||||
|
isStattrak: newSkin.isStattrak,
|
||||||
|
isSouvenir: newSkin.isSouvenir,
|
||||||
|
price: parseInt(newSkin.price),
|
||||||
|
userId: userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
await logService.insertLog({
|
||||||
|
id: `${userId}-${Date.now()}`,
|
||||||
|
userId: userId,
|
||||||
|
action: "CS_TRADE_UP",
|
||||||
|
targetUserId: null,
|
||||||
|
coinsAmount: 0,
|
||||||
|
userNewAmount: (await userService.getUser(userId)).coins,
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({ skin: created, consumedRarity: rarity, resultRarity: nextRarity });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error during trade-up:", error);
|
||||||
|
res.status(500).json({ error: "Failed to complete trade-up contract." });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
router.get("/case-content/:type", async (req, res) => {
|
router.get("/case-content/:type", async (req, res) => {
|
||||||
const { type } = req.params;
|
const { type } = req.params;
|
||||||
try {
|
try {
|
||||||
@@ -283,6 +416,44 @@ export function apiRoutes(client, io) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post("/cs-skin/:id/instant-sell", requireAuth, async (req, res) => {
|
||||||
|
const userId = req.userId;
|
||||||
|
try {
|
||||||
|
const skin = await csSkinService.getCsSkin(req.params.id);
|
||||||
|
if (!skin) return res.status(404).json({ error: "CS skin not found." });
|
||||||
|
if (skin.userId !== userId) return res.status(403).json({ error: "User does not own this skin." });
|
||||||
|
|
||||||
|
const marketOffers = await marketService.getMarketOffersByCsSkin(skin.id);
|
||||||
|
const activeOffers = marketOffers.filter((offer) => offer.status === "pending" || offer.status === "open");
|
||||||
|
if (activeOffers.length > 0) {
|
||||||
|
return res
|
||||||
|
.status(403)
|
||||||
|
.json({ error: "Impossible de vendre ce skin, une offre FlopoMarket est déjà en cours." });
|
||||||
|
}
|
||||||
|
|
||||||
|
const commandUser = await userService.getUser(userId);
|
||||||
|
if (!commandUser) return res.status(404).json({ error: "User not found." });
|
||||||
|
|
||||||
|
const sellPrice = skin.price;
|
||||||
|
await logService.insertLog({
|
||||||
|
id: `${userId}-${Date.now()}`,
|
||||||
|
userId: userId,
|
||||||
|
action: "CS_SKIN_INSTANT_SELL",
|
||||||
|
targetUserId: null,
|
||||||
|
coinsAmount: sellPrice,
|
||||||
|
userNewAmount: commandUser.coins + sellPrice,
|
||||||
|
});
|
||||||
|
await userService.updateUserCoins(userId, commandUser.coins + sellPrice);
|
||||||
|
await csSkinService.deleteCsSkin(skin.id);
|
||||||
|
|
||||||
|
console.log(`${commandUser.username} instantly sold CS skin ${skin.displayName} for ${sellPrice} FlopoCoins`);
|
||||||
|
res.status(200).json({ sellPrice });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error selling CS skin:", error);
|
||||||
|
res.status(500).json({ error: "Failed to sell CS skin." });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
router.get("/skin-upgrade/:uuid/fetch", async (req, res) => {
|
router.get("/skin-upgrade/:uuid/fetch", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const skin = await skinService.getSkin(req.params.uuid);
|
const skin = await skinService.getSkin(req.params.uuid);
|
||||||
@@ -501,7 +672,9 @@ export function apiRoutes(client, io) {
|
|||||||
skin.isChampions = isChampionsSkin(skin.displayName);
|
skin.isChampions = isChampionsSkin(skin.displayName);
|
||||||
skin.vctRegion = getVCTRegion(skin.displayName);
|
skin.vctRegion = getVCTRegion(skin.displayName);
|
||||||
}
|
}
|
||||||
res.json({ inventory });
|
|
||||||
|
const csInventory = await csSkinService.getUserCsInventory(req.params.id);
|
||||||
|
res.json({ inventory, csInventory });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
res.status(500).json({ error: "Failed to fetch inventory." });
|
res.status(500).json({ error: "Failed to fetch inventory." });
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import * as userService from "../../services/user.service.js";
|
|||||||
import * as skinService from "../../services/skin.service.js";
|
import * as skinService from "../../services/skin.service.js";
|
||||||
import * as logService from "../../services/log.service.js";
|
import * as logService from "../../services/log.service.js";
|
||||||
import * as marketService from "../../services/market.service.js";
|
import * as marketService from "../../services/market.service.js";
|
||||||
|
import * as csSkinService from "../../services/csSkin.service.js";
|
||||||
import { emitMarketUpdate } from "../socket.js";
|
import { emitMarketUpdate } from "../socket.js";
|
||||||
import { handleNewMarketOffer, handleNewMarketOfferBid } from "../../utils/marketNotifs.js";
|
import { handleNewMarketOffer, handleNewMarketOfferBid } from "../../utils/marketNotifs.js";
|
||||||
import { requireAuth } from "../middleware/auth.js";
|
import { requireAuth } from "../middleware/auth.js";
|
||||||
@@ -27,7 +28,11 @@ export function marketRoutes(client, io) {
|
|||||||
try {
|
try {
|
||||||
const offers = await marketService.getMarketOffers();
|
const offers = await marketService.getMarketOffers();
|
||||||
for (const offer of offers) {
|
for (const offer of offers) {
|
||||||
offer.skin = await skinService.getSkin(offer.skinUuid);
|
if (offer.csSkinId) {
|
||||||
|
offer.csSkin = await csSkinService.getCsSkin(offer.csSkinId);
|
||||||
|
} else if (offer.skinUuid) {
|
||||||
|
offer.skin = await skinService.getSkin(offer.skinUuid);
|
||||||
|
}
|
||||||
offer.seller = await userService.getUser(offer.sellerId);
|
offer.seller = await userService.getUser(offer.sellerId);
|
||||||
offer.buyer = offer.buyerId ? await userService.getUser(offer.buyerId) : null;
|
offer.buyer = offer.buyerId ? await userService.getUser(offer.buyerId) : null;
|
||||||
offer.bids = (await marketService.getOfferBids(offer.id)) || {};
|
offer.bids = (await marketService.getOfferBids(offer.id)) || {};
|
||||||
@@ -66,16 +71,30 @@ export function marketRoutes(client, io) {
|
|||||||
|
|
||||||
router.post("/place-offer", requireAuth, async (req, res) => {
|
router.post("/place-offer", requireAuth, async (req, res) => {
|
||||||
const seller_id = req.userId;
|
const seller_id = req.userId;
|
||||||
const { skin_uuid, starting_price, delay, duration, timestamp } = req.body;
|
const { skin_uuid, cs_skin_id, starting_price, delay, duration, timestamp } = req.body;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
try {
|
try {
|
||||||
const skin = await skinService.getSkin(skin_uuid);
|
|
||||||
if (!skin) return res.status(404).send({ error: "Skin not found" });
|
|
||||||
const seller = await userService.getUser(seller_id);
|
const seller = await userService.getUser(seller_id);
|
||||||
if (!seller) return res.status(404).send({ error: "Seller not found" });
|
if (!seller) return res.status(404).send({ error: "Seller not found" });
|
||||||
if (skin.userId !== seller.id) return res.status(403).send({ error: "You do not own this skin" });
|
|
||||||
|
|
||||||
const existingOffers = await marketService.getMarketOffersBySkin(skin.uuid);
|
let skinRef; // { skinUuid, csSkinId } - one or the other
|
||||||
|
if (cs_skin_id) {
|
||||||
|
const csSkin = await csSkinService.getCsSkin(cs_skin_id);
|
||||||
|
if (!csSkin) return res.status(404).send({ error: "CS skin not found" });
|
||||||
|
if (csSkin.userId !== seller.id) return res.status(403).send({ error: "You do not own this skin" });
|
||||||
|
skinRef = { csSkinId: csSkin.id };
|
||||||
|
} else if (skin_uuid) {
|
||||||
|
const skin = await skinService.getSkin(skin_uuid);
|
||||||
|
if (!skin) return res.status(404).send({ error: "Skin not found" });
|
||||||
|
if (skin.userId !== seller.id) return res.status(403).send({ error: "You do not own this skin" });
|
||||||
|
skinRef = { skinUuid: skin.uuid };
|
||||||
|
} else {
|
||||||
|
return res.status(400).send({ error: "Must provide skin_uuid or cs_skin_id" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingOffers = skinRef.skinUuid
|
||||||
|
? await marketService.getMarketOffersBySkin(skinRef.skinUuid)
|
||||||
|
: await marketService.getMarketOffersByCsSkin(skinRef.csSkinId);
|
||||||
if (
|
if (
|
||||||
existingOffers.length > 0 &&
|
existingOffers.length > 0 &&
|
||||||
existingOffers.some((offer) => offer.status === "open" || offer.status === "pending")
|
existingOffers.some((offer) => offer.status === "open" || offer.status === "pending")
|
||||||
@@ -86,10 +105,11 @@ export function marketRoutes(client, io) {
|
|||||||
const opening_at = now + delay;
|
const opening_at = now + delay;
|
||||||
const closing_at = opening_at + duration;
|
const closing_at = opening_at + duration;
|
||||||
|
|
||||||
const offerId = Date.now() + "-" + seller.id + "-" + skin.uuid;
|
const offerId = Date.now() + "-" + seller.id + "-" + (skinRef.skinUuid || skinRef.csSkinId);
|
||||||
await marketService.insertMarketOffer({
|
await marketService.insertMarketOffer({
|
||||||
id: offerId,
|
id: offerId,
|
||||||
skinUuid: skin.uuid,
|
skinUuid: skinRef.skinUuid || null,
|
||||||
|
csSkinId: skinRef.csSkinId || null,
|
||||||
sellerId: seller.id,
|
sellerId: seller.id,
|
||||||
startingPrice: starting_price,
|
startingPrice: starting_price,
|
||||||
buyoutPrice: null,
|
buyoutPrice: null,
|
||||||
|
|||||||
36
src/services/csSkin.service.js
Normal file
36
src/services/csSkin.service.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import prisma from "../prisma/client.js";
|
||||||
|
|
||||||
|
export async function getCsSkin(id) {
|
||||||
|
return prisma.csSkin.findUnique({ where: { id } });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserCsInventory(userId) {
|
||||||
|
return prisma.csSkin.findMany({
|
||||||
|
where: { userId },
|
||||||
|
orderBy: { price: "desc" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserCsSkinsByRarity(userId, rarity) {
|
||||||
|
return prisma.csSkin.findMany({
|
||||||
|
where: { userId, rarity },
|
||||||
|
orderBy: { price: "desc" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function insertCsSkin(data) {
|
||||||
|
return prisma.csSkin.create({ data });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateCsSkin(data) {
|
||||||
|
const { id, ...rest } = data;
|
||||||
|
return prisma.csSkin.update({ where: { id }, data: rest });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteCsSkin(id) {
|
||||||
|
return prisma.csSkin.delete({ where: { id } });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteManyCsSkins(ids) {
|
||||||
|
return prisma.csSkin.deleteMany({ where: { id: { in: ids } } });
|
||||||
|
}
|
||||||
@@ -14,16 +14,17 @@ export async function getMarketOfferById(id) {
|
|||||||
where: { id },
|
where: { id },
|
||||||
include: {
|
include: {
|
||||||
skin: { select: { displayName: true, displayIcon: true } },
|
skin: { select: { displayName: true, displayIcon: true } },
|
||||||
|
csSkin: { select: { displayName: true, imageUrl: true, rarity: true, wearState: true, float: true, isStattrak: true, isSouvenir: true } },
|
||||||
seller: { select: { username: true, globalName: true } },
|
seller: { select: { username: true, globalName: true } },
|
||||||
buyer: { select: { username: true, globalName: true } },
|
buyer: { select: { username: true, globalName: true } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!offer) return null;
|
if (!offer) return null;
|
||||||
// Flatten to match the old query shape
|
const skinData = offer.csSkin || offer.skin;
|
||||||
return toOffer({
|
return toOffer({
|
||||||
...offer,
|
...offer,
|
||||||
skinName: offer.skin?.displayName,
|
skinName: skinData?.displayName,
|
||||||
skinIcon: offer.skin?.displayIcon,
|
skinIcon: offer.skin?.displayIcon || offer.csSkin?.imageUrl,
|
||||||
sellerName: offer.seller?.username,
|
sellerName: offer.seller?.username,
|
||||||
sellerGlobalName: offer.seller?.globalName,
|
sellerGlobalName: offer.seller?.globalName,
|
||||||
buyerName: offer.buyer?.username ?? null,
|
buyerName: offer.buyer?.username ?? null,
|
||||||
@@ -53,12 +54,34 @@ export async function getMarketOffersBySkin(skinUuid) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getMarketOffersByCsSkin(csSkinId) {
|
||||||
|
const offers = await prisma.marketOffer.findMany({
|
||||||
|
where: { csSkinId },
|
||||||
|
include: {
|
||||||
|
csSkin: { select: { displayName: true, imageUrl: true } },
|
||||||
|
seller: { select: { username: true, globalName: true } },
|
||||||
|
buyer: { select: { username: true, globalName: true } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return offers.map((offer) =>
|
||||||
|
toOffer({
|
||||||
|
...offer,
|
||||||
|
skinName: offer.csSkin?.displayName,
|
||||||
|
skinIcon: offer.csSkin?.imageUrl,
|
||||||
|
sellerName: offer.seller?.username,
|
||||||
|
sellerGlobalName: offer.seller?.globalName,
|
||||||
|
buyerName: offer.buyer?.username ?? null,
|
||||||
|
buyerGlobalName: offer.buyer?.globalName ?? null,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function insertMarketOffer(data) {
|
export async function insertMarketOffer(data) {
|
||||||
return prisma.marketOffer.create({
|
return prisma.marketOffer.create({
|
||||||
data: {
|
data: {
|
||||||
...data,
|
...data,
|
||||||
openingAt: String(data.openingAt),
|
openingAt: new Date(data.openingAt),
|
||||||
closingAt: String(data.closingAt),
|
closingAt: new Date(data.closingAt),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,14 @@ const wearStateMultipliers = {
|
|||||||
[StateBattleScarred]: 0.5,
|
[StateBattleScarred]: 0.5,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const TRADE_UP_MAP = {
|
||||||
|
"Consumer Grade": "Industrial Grade",
|
||||||
|
"Industrial Grade": "Mil-Spec Grade",
|
||||||
|
"Mil-Spec Grade": "Restricted",
|
||||||
|
"Restricted": "Classified",
|
||||||
|
"Classified": "Covert",
|
||||||
|
};
|
||||||
|
|
||||||
export function randomSkinRarity() {
|
export function randomSkinRarity() {
|
||||||
const roll = Math.random();
|
const roll = Math.random();
|
||||||
|
|
||||||
@@ -42,58 +50,53 @@ export function randomSkinRarity() {
|
|||||||
const covertLimit = goldLimit + 0.014;
|
const covertLimit = goldLimit + 0.014;
|
||||||
const classifiedLimit = covertLimit + 0.04;
|
const classifiedLimit = covertLimit + 0.04;
|
||||||
const restrictedLimit = classifiedLimit + 0.2;
|
const restrictedLimit = classifiedLimit + 0.2;
|
||||||
const milSpecLimit = restrictedLimit + 0.5;
|
const milSpecLimit = restrictedLimit + 0.5;
|
||||||
const industrialLimit = milSpecLimit + 0.2;
|
const industrialLimit = milSpecLimit + 0.2;
|
||||||
|
|
||||||
if (roll < goldLimit) return "Extraordinary";
|
if (roll < goldLimit) return "Extraordinary";
|
||||||
if (roll < covertLimit) return "Covert";
|
if (roll < covertLimit) return "Covert";
|
||||||
if (roll < classifiedLimit) return "Classified";
|
if (roll < classifiedLimit) return "Classified";
|
||||||
if (roll < restrictedLimit) return "Restricted";
|
if (roll < restrictedLimit) return "Restricted";
|
||||||
if (roll < milSpecLimit) return "Mil-Spec Grade";
|
if (roll < milSpecLimit) return "Mil-Spec Grade";
|
||||||
if (roll < industrialLimit) return "Industrial Grade";
|
if (roll < industrialLimit) return "Industrial Grade";
|
||||||
return "Consumer Grade";
|
return "Consumer Grade";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generatePrice(rarity, float, isStattrak, isSouvenir, wearState) {
|
export function generatePrice(rarity, float, isStattrak, isSouvenir, wearState) {
|
||||||
const ranges = basePriceRanges[rarity] || basePriceRanges["Industrial Grade"];
|
const ranges = basePriceRanges[rarity] || basePriceRanges["Industrial Grade"];
|
||||||
console.log(ranges)
|
|
||||||
|
|
||||||
let basePrice = ranges.min + (Math.random()) * (ranges.max - ranges.min);
|
let basePrice = ranges.min + Math.random() * (ranges.max - ranges.min);
|
||||||
console.log(basePrice)
|
|
||||||
|
|
||||||
const stateMultiplier = wearStateMultipliers[wearState] ?? 1.0;
|
const stateMultiplier = wearStateMultipliers[wearState] ?? 1.0;
|
||||||
console.log(stateMultiplier)
|
|
||||||
|
|
||||||
let finalPrice = basePrice * stateMultiplier;
|
let finalPrice = basePrice * stateMultiplier;
|
||||||
console.log(finalPrice)
|
|
||||||
|
|
||||||
const isExtraordinary = rarity === "Extraordinary";
|
const isExtraordinary = rarity === "Extraordinary";
|
||||||
|
|
||||||
if (isSouvenir && !isExtraordinary) {
|
if (isSouvenir && !isExtraordinary) {
|
||||||
finalPrice *= 4 + (Math.random()) * (10.0 - 4);
|
finalPrice *= 4 + Math.random() * (10.0 - 4);
|
||||||
} else if (isStattrak && !isExtraordinary) {
|
} else if (isStattrak && !isExtraordinary) {
|
||||||
finalPrice *= 3 + (Math.random()) * (5.0 - 3);
|
finalPrice *= 3 + Math.random() * (5.0 - 3);
|
||||||
}
|
}
|
||||||
console.log(finalPrice)
|
finalPrice /= 1 + float;
|
||||||
finalPrice /= 1 + float; // Avoid division by zero and ensure float has a significant impact
|
|
||||||
|
|
||||||
if (finalPrice < 1) finalPrice = 1;
|
if (finalPrice < 1) finalPrice = 1;
|
||||||
|
|
||||||
return finalPrice.toFixed(0);
|
return finalPrice.toFixed(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isStattrak(canBeStattrak) {
|
export function rollStattrak(canBeStattrak) {
|
||||||
if (!canBeStattrak) return false;
|
if (!canBeStattrak) return false;
|
||||||
return Math.random() < 0.15;
|
return Math.random() < 0.15;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSouvenir(canBeSouvenir) {
|
export function rollSouvenir(canBeSouvenir) {
|
||||||
if (!canBeSouvenir) return false;
|
if (!canBeSouvenir) return false;
|
||||||
return Math.random() < 0.15;
|
return Math.random() < 0.15;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRandomFloatInRange(min, max) {
|
export function getRandomFloatInRange(min, max) {
|
||||||
return min + (Math.random()) * (max - min);
|
return min + Math.random() * (max - min);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getWearState(wear) {
|
export function getWearState(wear) {
|
||||||
@@ -106,23 +109,26 @@ export function getWearState(wear) {
|
|||||||
return StateBattleScarred;
|
return StateBattleScarred;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRandomSkinWithRandomSpecs(u_float=null) {
|
export function getRandomSkinWithRandomSpecs(u_float, forcedRarity) {
|
||||||
const skinNames = Object.keys(csSkinsData);
|
const skinNames = Object.keys(csSkinsData);
|
||||||
const randomRarity = randomSkinRarity();
|
const selectedRarity = forcedRarity || randomSkinRarity();
|
||||||
console.log(randomRarity)
|
const filteredSkinNames = skinNames.filter(name => csSkinsData[name].rarity.name === selectedRarity);
|
||||||
const filteredSkinNames = skinNames.filter(name => csSkinsData[name].rarity.name === randomRarity);
|
|
||||||
const randomIndex = Math.floor(Math.random() * filteredSkinNames.length);
|
const randomIndex = Math.floor(Math.random() * filteredSkinNames.length);
|
||||||
|
|
||||||
const skinName = filteredSkinNames[randomIndex];
|
const skinName = filteredSkinNames[randomIndex];
|
||||||
const skinData = csSkinsData[skinName];
|
const skinData = csSkinsData[skinName];
|
||||||
const float = u_float !== null ? u_float : getRandomFloatInRange(skinData.min_float, skinData.max_float);
|
const float = u_float !== null ? u_float : getRandomFloatInRange(skinData.min_float, skinData.max_float);
|
||||||
|
const wearState = getWearState(float);
|
||||||
|
const skinIsStattrak = rollStattrak(skinData.stattrak);
|
||||||
|
const skinIsSouvenir = rollSouvenir(skinData.souvenir);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: skinName,
|
name: skinName,
|
||||||
data: skinData,
|
data: skinData,
|
||||||
isStattrak: isStattrak(skinData.stattrak),
|
isStattrak: skinIsStattrak,
|
||||||
isSouvenir: isSouvenir(skinData.souvenir),
|
isSouvenir: skinIsSouvenir,
|
||||||
wearState: getWearState(float),
|
wearState,
|
||||||
float: float,
|
float,
|
||||||
price: generatePrice(skinData.rarity.name, float, isStattrak(skinData.stattrak), isSouvenir(skinData.souvenir), getWearState(float)),
|
price: generatePrice(skinData.rarity.name, float, skinIsStattrak, skinIsSouvenir, wearState),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { initTodaysSOTD } from "../game/points.js";
|
|||||||
import * as userService from "../services/user.service.js";
|
import * as userService from "../services/user.service.js";
|
||||||
import * as skinService from "../services/skin.service.js";
|
import * as skinService from "../services/skin.service.js";
|
||||||
import * as marketService from "../services/market.service.js";
|
import * as marketService from "../services/market.service.js";
|
||||||
|
import * as csSkinService from "../services/csSkin.service.js";
|
||||||
import { activeInventories, activePredis, activeSearchs, pokerRooms, skins } from "../game/state.js";
|
import { activeInventories, activePredis, activeSearchs, pokerRooms, skins } from "../game/state.js";
|
||||||
import { emitMarketUpdate } from "../server/socket.js";
|
import { emitMarketUpdate } from "../server/socket.js";
|
||||||
import { handleMarketOfferClosing, handleMarketOfferOpening } from "./marketNotifs.js";
|
import { handleMarketOfferClosing, handleMarketOfferOpening } from "./marketNotifs.js";
|
||||||
@@ -286,16 +287,22 @@ async function handleMarketOffersUpdate() {
|
|||||||
const buyer = await userService.getUser(lastBid.bidderId);
|
const buyer = await userService.getUser(lastBid.bidderId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Change skin ownership
|
// Change skin ownership (supports both Valorant and CS2 skins)
|
||||||
const skin = await skinService.getSkin(offer.skinUuid);
|
if (offer.csSkinId) {
|
||||||
if (!skin) throw new Error(`Skin not found for offer ID: ${offer.id}`);
|
const csSkin = await csSkinService.getCsSkin(offer.csSkinId);
|
||||||
await skinService.updateSkin({
|
if (!csSkin) throw new Error(`CS skin not found for offer ID: ${offer.id}`);
|
||||||
userId: buyer.id,
|
await csSkinService.updateCsSkin({ id: csSkin.id, userId: buyer.id });
|
||||||
currentLvl: skin.currentLvl,
|
} else if (offer.skinUuid) {
|
||||||
currentChroma: skin.currentChroma,
|
const skin = await skinService.getSkin(offer.skinUuid);
|
||||||
currentPrice: skin.currentPrice,
|
if (!skin) throw new Error(`Skin not found for offer ID: ${offer.id}`);
|
||||||
uuid: skin.uuid,
|
await skinService.updateSkin({
|
||||||
});
|
userId: buyer.id,
|
||||||
|
currentLvl: skin.currentLvl,
|
||||||
|
currentChroma: skin.currentChroma,
|
||||||
|
currentPrice: skin.currentPrice,
|
||||||
|
uuid: skin.uuid,
|
||||||
|
});
|
||||||
|
}
|
||||||
await marketService.updateMarketOffer({
|
await marketService.updateMarketOffer({
|
||||||
id: offer.id,
|
id: offer.id,
|
||||||
buyerId: buyer.id,
|
buyerId: buyer.id,
|
||||||
|
|||||||
@@ -1,12 +1,28 @@
|
|||||||
import * as userService from "../services/user.service.js";
|
import * as userService from "../services/user.service.js";
|
||||||
import * as skinService from "../services/skin.service.js";
|
import * as skinService from "../services/skin.service.js";
|
||||||
|
import * as csSkinService from "../services/csSkin.service.js";
|
||||||
import * as marketService from "../services/market.service.js";
|
import * as marketService from "../services/market.service.js";
|
||||||
import { EmbedBuilder } from "discord.js";
|
import { EmbedBuilder } from "discord.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the skin display name and icon from an offer, supporting both Valorant and CS2 skins.
|
||||||
|
*/
|
||||||
|
async function getOfferSkinInfo(offer) {
|
||||||
|
if (offer.csSkinId) {
|
||||||
|
const csSkin = await csSkinService.getCsSkin(offer.csSkinId);
|
||||||
|
return { name: csSkin?.displayName || offer.csSkinId, icon: csSkin?.imageUrl || null };
|
||||||
|
}
|
||||||
|
if (offer.skinUuid) {
|
||||||
|
const skin = await skinService.getSkin(offer.skinUuid);
|
||||||
|
return { name: skin?.displayName || offer.skinUuid, icon: skin?.displayIcon || null };
|
||||||
|
}
|
||||||
|
return { name: "Unknown", icon: null };
|
||||||
|
}
|
||||||
|
|
||||||
export async function handleNewMarketOffer(offerId, client) {
|
export async function handleNewMarketOffer(offerId, client) {
|
||||||
const offer = await marketService.getMarketOfferById(offerId);
|
const offer = await marketService.getMarketOfferById(offerId);
|
||||||
if (!offer) return;
|
if (!offer) return;
|
||||||
const skin = await skinService.getSkin(offer.skinUuid);
|
const { name: skinName, icon: skinIcon } = await getOfferSkinInfo(offer);
|
||||||
|
|
||||||
const discordUserSeller = await client.users.fetch(offer.sellerId);
|
const discordUserSeller = await client.users.fetch(offer.sellerId);
|
||||||
try {
|
try {
|
||||||
@@ -14,9 +30,8 @@ export async function handleNewMarketOffer(offerId, client) {
|
|||||||
if (discordUserSeller && userSeller?.isAkhy) {
|
if (discordUserSeller && userSeller?.isAkhy) {
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle("🔔 Offre créée")
|
.setTitle("🔔 Offre créée")
|
||||||
.setDescription(`Ton offre pour le skin **${skin ? skin.displayName : offer.skinUuid}** a bien été créée !`)
|
.setDescription(`Ton offre pour le skin **${skinName}** a bien été créée !`)
|
||||||
.setThumbnail(skin.displayIcon)
|
.setColor(0x5865f2)
|
||||||
.setColor(0x5865f2) // Discord blurple
|
|
||||||
.addFields(
|
.addFields(
|
||||||
{
|
{
|
||||||
name: "📌 Statut",
|
name: "📌 Statut",
|
||||||
@@ -37,27 +52,26 @@ export async function handleNewMarketOffer(offerId, client) {
|
|||||||
value: `<t:${Math.floor(offer.closingAt / 1000)}:F>`,
|
value: `<t:${Math.floor(offer.closingAt / 1000)}:F>`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "🆔 ID de l’offre",
|
name: "🆔 ID de l'offre",
|
||||||
value: `\`${offer.id}\``,
|
value: `\`${offer.id}\``,
|
||||||
inline: false,
|
inline: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
|
if (skinIcon) embed.setThumbnail(skinIcon);
|
||||||
|
|
||||||
discordUserSeller.send({ embeds: [embed] }).catch(console.error);
|
discordUserSeller.send({ embeds: [embed] }).catch(console.error);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
// Send notification in guild channel
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const guildChannel = await client.channels.fetch(process.env.BOT_CHANNEL_ID);
|
const guildChannel = await client.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle("🔔 Nouvelle offre")
|
.setTitle("🔔 Nouvelle offre")
|
||||||
.setDescription(`Une offre pour le skin **${skin ? skin.displayName : offer.skinUuid}** a été créée !`)
|
.setDescription(`Une offre pour le skin **${skinName}** a été créée !`)
|
||||||
.setThumbnail(skin.displayIcon)
|
.setColor(0x5865f2)
|
||||||
.setColor(0x5865f2) // Discord blurple
|
|
||||||
.addFields(
|
.addFields(
|
||||||
{
|
{
|
||||||
name: "💰 Prix de départ",
|
name: "💰 Prix de départ",
|
||||||
@@ -78,6 +92,7 @@ export async function handleNewMarketOffer(offerId, client) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
|
if (skinIcon) embed.setThumbnail(skinIcon);
|
||||||
guildChannel.send({ embeds: [embed] }).catch(console.error);
|
guildChannel.send({ embeds: [embed] }).catch(console.error);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -87,7 +102,7 @@ export async function handleNewMarketOffer(offerId, client) {
|
|||||||
export async function handleMarketOfferOpening(offerId, client) {
|
export async function handleMarketOfferOpening(offerId, client) {
|
||||||
const offer = await marketService.getMarketOfferById(offerId);
|
const offer = await marketService.getMarketOfferById(offerId);
|
||||||
if (!offer) return;
|
if (!offer) return;
|
||||||
const skin = await skinService.getSkin(offer.skinUuid);
|
const { name: skinName, icon: skinIcon } = await getOfferSkinInfo(offer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const discordUserSeller = await client.users.fetch(offer.sellerId);
|
const discordUserSeller = await client.users.fetch(offer.sellerId);
|
||||||
@@ -96,10 +111,9 @@ export async function handleMarketOfferOpening(offerId, client) {
|
|||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle("🔔 Début des enchères")
|
.setTitle("🔔 Début des enchères")
|
||||||
.setDescription(
|
.setDescription(
|
||||||
`Les enchères sur ton offre pour le skin **${skin ? skin.displayName : offer.skinUuid}** viennent de commencer !`,
|
`Les enchères sur ton offre pour le skin **${skinName}** viennent de commencer !`,
|
||||||
)
|
)
|
||||||
.setThumbnail(skin.displayIcon)
|
.setColor(0x5865f2)
|
||||||
.setColor(0x5865f2) // Discord blurple
|
|
||||||
.addFields(
|
.addFields(
|
||||||
{
|
{
|
||||||
name: "📌 Statut",
|
name: "📌 Statut",
|
||||||
@@ -116,29 +130,28 @@ export async function handleMarketOfferOpening(offerId, client) {
|
|||||||
value: `<t:${Math.floor(offer.closingAt / 1000)}:F>`,
|
value: `<t:${Math.floor(offer.closingAt / 1000)}:F>`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "🆔 ID de l’offre",
|
name: "🆔 ID de l'offre",
|
||||||
value: `\`${offer.id}\``,
|
value: `\`${offer.id}\``,
|
||||||
inline: false,
|
inline: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
|
if (skinIcon) embed.setThumbnail(skinIcon);
|
||||||
|
|
||||||
discordUserSeller.send({ embeds: [embed] }).catch(console.error);
|
discordUserSeller.send({ embeds: [embed] }).catch(console.error);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
// Send notification in guild channel
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const guildChannel = await client.channels.fetch(process.env.BOT_CHANNEL_ID);
|
const guildChannel = await client.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle("🔔 Début des enchères")
|
.setTitle("🔔 Début des enchères")
|
||||||
.setDescription(
|
.setDescription(
|
||||||
`Les enchères sur l'offre pour le skin **${skin ? skin.displayName : offer.skinUuid}** viennent de commencer !`,
|
`Les enchères sur l'offre pour le skin **${skinName}** viennent de commencer !`,
|
||||||
)
|
)
|
||||||
.setThumbnail(skin.displayIcon)
|
.setColor(0x5865f2)
|
||||||
.setColor(0x5865f2) // Discord blurple
|
|
||||||
.addFields(
|
.addFields(
|
||||||
{
|
{
|
||||||
name: "💰 Prix de départ",
|
name: "💰 Prix de départ",
|
||||||
@@ -151,6 +164,7 @@ export async function handleMarketOfferOpening(offerId, client) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
|
if (skinIcon) embed.setThumbnail(skinIcon);
|
||||||
guildChannel.send({ embeds: [embed] }).catch(console.error);
|
guildChannel.send({ embeds: [embed] }).catch(console.error);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -160,7 +174,7 @@ export async function handleMarketOfferOpening(offerId, client) {
|
|||||||
export async function handleMarketOfferClosing(offerId, client) {
|
export async function handleMarketOfferClosing(offerId, client) {
|
||||||
const offer = await marketService.getMarketOfferById(offerId);
|
const offer = await marketService.getMarketOfferById(offerId);
|
||||||
if (!offer) return;
|
if (!offer) return;
|
||||||
const skin = await skinService.getSkin(offer.skinUuid);
|
const { name: skinName, icon: skinIcon } = await getOfferSkinInfo(offer);
|
||||||
const bids = await marketService.getOfferBids(offer.id);
|
const bids = await marketService.getOfferBids(offer.id);
|
||||||
|
|
||||||
const discordUserSeller = await client.users.fetch(offer.sellerId);
|
const discordUserSeller = await client.users.fetch(offer.sellerId);
|
||||||
@@ -170,11 +184,11 @@ export async function handleMarketOfferClosing(offerId, client) {
|
|||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle("🔔 Fin des enchères")
|
.setTitle("🔔 Fin des enchères")
|
||||||
.setDescription(
|
.setDescription(
|
||||||
`Les enchères sur ton offre pour le skin **${skin ? skin.displayName : offer.skinUuid}** viennent de se terminer !`,
|
`Les enchères sur ton offre pour le skin **${skinName}** viennent de se terminer !`,
|
||||||
)
|
)
|
||||||
.setThumbnail(skin.displayIcon)
|
.setColor(0x5865f2)
|
||||||
.setColor(0x5865f2) // Discord blurple
|
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
|
if (skinIcon) embed.setThumbnail(skinIcon);
|
||||||
|
|
||||||
if (bids.length === 0) {
|
if (bids.length === 0) {
|
||||||
embed.addFields(
|
embed.addFields(
|
||||||
@@ -183,7 +197,7 @@ export async function handleMarketOfferClosing(offerId, client) {
|
|||||||
value: "Tu conserves ce skin dans ton inventaire.",
|
value: "Tu conserves ce skin dans ton inventaire.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "🆔 ID de l’offre",
|
name: "🆔 ID de l'offre",
|
||||||
value: `\`${offer.id}\``,
|
value: `\`${offer.id}\``,
|
||||||
inline: false,
|
inline: false,
|
||||||
},
|
},
|
||||||
@@ -197,7 +211,7 @@ export async function handleMarketOfferClosing(offerId, client) {
|
|||||||
value: `Ton skin a été vendu pour \`${highestBid.offerAmount} coins\` à <@${highestBid.bidderId}> ${highestBidderUser ? "(" + highestBidderUser.username + ")" : ""}.`,
|
value: `Ton skin a été vendu pour \`${highestBid.offerAmount} coins\` à <@${highestBid.bidderId}> ${highestBidderUser ? "(" + highestBidderUser.username + ")" : ""}.`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "🆔 ID de l’offre",
|
name: "🆔 ID de l'offre",
|
||||||
value: `\`${offer.id}\``,
|
value: `\`${offer.id}\``,
|
||||||
inline: false,
|
inline: false,
|
||||||
},
|
},
|
||||||
@@ -210,19 +224,17 @@ export async function handleMarketOfferClosing(offerId, client) {
|
|||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send notification in guild channel
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const guild = await client.guilds.fetch(process.env.BOT_GUILD_ID);
|
const guild = await client.guilds.fetch(process.env.BOT_GUILD_ID);
|
||||||
const guildChannel = await guild.channels.fetch(process.env.BOT_CHANNEL_ID);
|
const guildChannel = await guild.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle("🔔 Fin des enchères")
|
.setTitle("🔔 Fin des enchères")
|
||||||
.setDescription(
|
.setDescription(
|
||||||
`Les enchères sur l'offre pour le skin **${skin ? skin.displayName : offer.skinUuid}** viennent de se terminer !`,
|
`Les enchères sur l'offre pour le skin **${skinName}** viennent de se terminer !`,
|
||||||
)
|
)
|
||||||
.setThumbnail(skin.displayIcon)
|
.setColor(0x5865f2)
|
||||||
.setColor(0x5865f2) // Discord blurple
|
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
|
if (skinIcon) embed.setThumbnail(skinIcon);
|
||||||
|
|
||||||
if (bids.length === 0) {
|
if (bids.length === 0) {
|
||||||
embed.addFields({
|
embed.addFields({
|
||||||
@@ -239,21 +251,20 @@ export async function handleMarketOfferClosing(offerId, client) {
|
|||||||
const discordUserBidder = await client.users.fetch(highestBid.bidderId);
|
const discordUserBidder = await client.users.fetch(highestBid.bidderId);
|
||||||
const userBidder = await userService.getUser(highestBid.bidderId);
|
const userBidder = await userService.getUser(highestBid.bidderId);
|
||||||
if (discordUserBidder && userBidder?.isAkhy) {
|
if (discordUserBidder && userBidder?.isAkhy) {
|
||||||
const embed = new EmbedBuilder()
|
const bidderEmbed = new EmbedBuilder()
|
||||||
.setTitle("🔔 Fin des enchères")
|
.setTitle("🔔 Fin des enchères")
|
||||||
.setDescription(
|
.setDescription(
|
||||||
`Les enchères sur l'offre pour le skin **${skin ? skin.displayName : offer.skinUuid}** viennent de se terminer !`,
|
`Les enchères sur l'offre pour le skin **${skinName}** viennent de se terminer !`,
|
||||||
)
|
)
|
||||||
.setThumbnail(skin.displayIcon)
|
.setColor(0x5865f2)
|
||||||
.setColor(0x5865f2) // Discord blurple
|
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
const highestBid = bids[0];
|
if (skinIcon) bidderEmbed.setThumbnail(skinIcon);
|
||||||
embed.addFields({
|
bidderEmbed.addFields({
|
||||||
name: "✅ Enchères terminées avec succès !",
|
name: "✅ Enchères terminées avec succès !",
|
||||||
value: `Tu as acheté ce skin pour \`${highestBid.offerAmount} coins\` à <@${offer.sellerId}> ${discordUserSeller ? "(" + discordUserSeller.username + ")" : ""}. Il a été ajouté à ton inventaire.`,
|
value: `Tu as acheté ce skin pour \`${highestBid.offerAmount} coins\` à <@${offer.sellerId}> ${discordUserSeller ? "(" + discordUserSeller.username + ")" : ""}. Il a été ajouté à ton inventaire.`,
|
||||||
});
|
});
|
||||||
|
|
||||||
discordUserBidder.send({ embeds: [embed] }).catch(console.error);
|
discordUserBidder.send({ embeds: [bidderEmbed] }).catch(console.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
guildChannel.send({ embeds: [embed] }).catch(console.error);
|
guildChannel.send({ embeds: [embed] }).catch(console.error);
|
||||||
@@ -263,12 +274,11 @@ export async function handleMarketOfferClosing(offerId, client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function handleNewMarketOfferBid(offerId, bidId, client) {
|
export async function handleNewMarketOfferBid(offerId, bidId, client) {
|
||||||
// Notify Seller and Bidder
|
|
||||||
const offer = await marketService.getMarketOfferById(offerId);
|
const offer = await marketService.getMarketOfferById(offerId);
|
||||||
if (!offer) return;
|
if (!offer) return;
|
||||||
const bid = (await marketService.getOfferBids(offerId))[0];
|
const bid = (await marketService.getOfferBids(offerId))[0];
|
||||||
if (!bid) return;
|
if (!bid) return;
|
||||||
const skin = await skinService.getSkin(offer.skinUuid);
|
const { name: skinName, icon: skinIcon } = await getOfferSkinInfo(offer);
|
||||||
|
|
||||||
const bidderUser = client.users.fetch(bid.bidderId);
|
const bidderUser = client.users.fetch(bid.bidderId);
|
||||||
try {
|
try {
|
||||||
@@ -279,10 +289,9 @@ export async function handleNewMarketOfferBid(offerId, bidId, client) {
|
|||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle("🔔 Nouvelle enchère")
|
.setTitle("🔔 Nouvelle enchère")
|
||||||
.setDescription(
|
.setDescription(
|
||||||
`Il y a eu une nouvelle enchère sur ton offre pour le skin **${skin ? skin.displayName : offer.skinUuid}**.`,
|
`Il y a eu une nouvelle enchère sur ton offre pour le skin **${skinName}**.`,
|
||||||
)
|
)
|
||||||
.setThumbnail(skin.displayIcon)
|
.setColor(0x5865f2)
|
||||||
.setColor(0x5865f2) // Discord blurple
|
|
||||||
.addFields(
|
.addFields(
|
||||||
{
|
{
|
||||||
name: "👤 Enchérisseur",
|
name: "👤 Enchérisseur",
|
||||||
@@ -290,7 +299,7 @@ export async function handleNewMarketOfferBid(offerId, bidId, client) {
|
|||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "💰 Montant de l’enchère",
|
name: "💰 Montant de l'enchère",
|
||||||
value: `\`${bid.offerAmount} coins\``,
|
value: `\`${bid.offerAmount} coins\``,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
@@ -299,12 +308,13 @@ export async function handleNewMarketOfferBid(offerId, bidId, client) {
|
|||||||
value: `<t:${Math.floor(offer.closingAt / 1000)}:F>`,
|
value: `<t:${Math.floor(offer.closingAt / 1000)}:F>`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "🆔 ID de l’offre",
|
name: "🆔 ID de l'offre",
|
||||||
value: `\`${offer.id}\``,
|
value: `\`${offer.id}\``,
|
||||||
inline: false,
|
inline: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
|
if (skinIcon) embed.setThumbnail(skinIcon);
|
||||||
|
|
||||||
discordUserSeller.send({ embeds: [embed] }).catch(console.error);
|
discordUserSeller.send({ embeds: [embed] }).catch(console.error);
|
||||||
}
|
}
|
||||||
@@ -319,16 +329,16 @@ export async function handleNewMarketOfferBid(offerId, bidId, client) {
|
|||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle("🔔 Nouvelle enchère")
|
.setTitle("🔔 Nouvelle enchère")
|
||||||
.setDescription(
|
.setDescription(
|
||||||
`Ton enchère sur l'offre pour le skin **${skin ? skin.displayName : offer.skinUuid}** a bien été placée!`,
|
`Ton enchère sur l'offre pour le skin **${skinName}** a bien été placée!`,
|
||||||
)
|
)
|
||||||
.setThumbnail(skin.displayIcon)
|
.setColor(0x5865f2)
|
||||||
.setColor(0x5865f2) // Discord blurple
|
|
||||||
.addFields({
|
.addFields({
|
||||||
name: "💰 Montant de l’enchère",
|
name: "💰 Montant de l'enchère",
|
||||||
value: `\`${bid.offerAmount} coins\``,
|
value: `\`${bid.offerAmount} coins\``,
|
||||||
inline: true,
|
inline: true,
|
||||||
})
|
})
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
|
if (skinIcon) embed.setThumbnail(skinIcon);
|
||||||
|
|
||||||
discordUserNewBidder.send({ embeds: [embed] }).catch(console.error);
|
discordUserNewBidder.send({ embeds: [embed] }).catch(console.error);
|
||||||
}
|
}
|
||||||
@@ -338,7 +348,7 @@ export async function handleNewMarketOfferBid(offerId, bidId, client) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const offerBids = await marketService.getOfferBids(offer.id);
|
const offerBids = await marketService.getOfferBids(offer.id);
|
||||||
if (offerBids.length < 2) return; // No previous bidder to notify
|
if (offerBids.length < 2) return;
|
||||||
|
|
||||||
const discordUserPreviousBidder = await client.users.fetch(offerBids[1].bidderId);
|
const discordUserPreviousBidder = await client.users.fetch(offerBids[1].bidderId);
|
||||||
const userPreviousBidder = await userService.getUser(offerBids[1].bidderId);
|
const userPreviousBidder = await userService.getUser(offerBids[1].bidderId);
|
||||||
@@ -346,10 +356,9 @@ export async function handleNewMarketOfferBid(offerId, bidId, client) {
|
|||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle("🔔 Nouvelle enchère")
|
.setTitle("🔔 Nouvelle enchère")
|
||||||
.setDescription(
|
.setDescription(
|
||||||
`Quelqu'un a surenchéri sur l'offre pour le skin **${skin ? skin.displayName : offer.skinUuid}**, tu n'es plus le meilleur enchérisseur !`,
|
`Quelqu'un a surenchéri sur l'offre pour le skin **${skinName}**, tu n'es plus le meilleur enchérisseur !`,
|
||||||
)
|
)
|
||||||
.setThumbnail(skin.displayIcon)
|
.setColor(0x5865f2)
|
||||||
.setColor(0x5865f2) // Discord blurple
|
|
||||||
.addFields(
|
.addFields(
|
||||||
{
|
{
|
||||||
name: "👤 Enchérisseur",
|
name: "👤 Enchérisseur",
|
||||||
@@ -357,20 +366,19 @@ export async function handleNewMarketOfferBid(offerId, bidId, client) {
|
|||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "💰 Montant de l’enchère",
|
name: "💰 Montant de l'enchère",
|
||||||
value: `\`${bid.offerAmount} coins\``,
|
value: `\`${bid.offerAmount} coins\``,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
|
if (skinIcon) embed.setThumbnail(skinIcon);
|
||||||
|
|
||||||
discordUserPreviousBidder.send({ embeds: [embed] }).catch(console.error);
|
discordUserPreviousBidder.send({ embeds: [embed] }).catch(console.error);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify previous highest bidder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleCaseOpening(caseType, userId, skinUuid, client) {
|
export async function handleCaseOpening(caseType, userId, skinUuid, client) {
|
||||||
@@ -384,7 +392,7 @@ export async function handleCaseOpening(caseType, userId, skinUuid, client) {
|
|||||||
`${discordUser ? discordUser.username : "Un utilisateur"} vient d'ouvrir une caisse **${caseType}** et a obtenu le skin **${skin.displayName}** !`,
|
`${discordUser ? discordUser.username : "Un utilisateur"} vient d'ouvrir une caisse **${caseType}** et a obtenu le skin **${skin.displayName}** !`,
|
||||||
)
|
)
|
||||||
.setThumbnail(skin.displayIcon)
|
.setThumbnail(skin.displayIcon)
|
||||||
.setColor(skin.tierColor) // Discord blurple
|
.setColor(skin.tierColor)
|
||||||
.addFields(
|
.addFields(
|
||||||
{
|
{
|
||||||
name: "💰 Valeur estimée",
|
name: "💰 Valeur estimée",
|
||||||
|
|||||||
Reference in New Issue
Block a user