mirror of
https://github.com/cassoule/flopobot_v2.git
synced 2026-03-18 21:40:27 +01:00
Compare commits
19 Commits
milo-26022
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19f9a11e4b | ||
|
|
78ecf9698c | ||
|
|
82c9c8ddad | ||
|
|
622522afa7 | ||
|
|
bd4375b2cc | ||
|
|
e4beb7f5be | ||
|
|
ede1489016 | ||
|
|
ef6022ae35 | ||
|
|
30233e5239 | ||
|
|
ee231b517e | ||
|
|
1523d0f696 | ||
|
|
4cc1b17984 | ||
|
|
e18aabadb6 | ||
|
|
4d1de5d48c | ||
|
|
885f7d9b44 | ||
|
|
7f043a7c93 | ||
|
|
72e67be565 | ||
|
|
2d2e2d71a8 | ||
|
|
d7eb194db6 |
18
.github/workflows/deploy.yml
vendored
Normal file
18
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: Deploy to Hetzner
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Deploy via SSH
|
||||
uses: appleboy/ssh-action@v1
|
||||
with:
|
||||
host: ${{ secrets.SERVER_IP }}
|
||||
username: root
|
||||
key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
script: ~/deploy.sh
|
||||
6
index.js
6
index.js
@@ -26,9 +26,15 @@ initializeSocket(io, client);
|
||||
|
||||
// --- BOT INITIALIZATION ---
|
||||
initializeEvents(client, io);
|
||||
client.rest.on("rateLimited", (info) => {
|
||||
console.log("Rate limited:", info);
|
||||
});
|
||||
console.log("Logging in...");
|
||||
client.login(process.env.BOT_TOKEN).then(() => {
|
||||
console.log(`Logged in as ${client.user.tag}`);
|
||||
console.log("[Discord Bot Events Initialized]");
|
||||
}).catch((error) => {
|
||||
console.error("Error logging in to Discord:", error);
|
||||
});
|
||||
|
||||
// --- APP STARTUP ---
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 296 KiB |
@@ -12,7 +12,7 @@ export async function handleInfoCommand(req, res, client) {
|
||||
|
||||
try {
|
||||
// Fetch the guild object from the client
|
||||
const guild = await client.guilds.fetch(guild_id);
|
||||
const guild = client.guilds.cache.get(guild_id);
|
||||
|
||||
// Fetch all members to ensure the cache is up to date
|
||||
await guild.members.fetch();
|
||||
|
||||
@@ -8,6 +8,7 @@ import { activeInventories, skins } from "../../game/state.js";
|
||||
import * as skinService from "../../services/skin.service.js";
|
||||
import * as csSkinService from "../../services/csSkin.service.js";
|
||||
import { RarityToColor } from "../../utils/cs.utils.js";
|
||||
import { resolveMember } from "../../utils/index.js";
|
||||
|
||||
/**
|
||||
* Handles the /inventory slash command.
|
||||
@@ -31,8 +32,8 @@ export async function handleInventoryCommand(req, res, client, interactionId) {
|
||||
const targetUserId = data.options && data.options.length > 0 ? data.options[0].value : commandUserId;
|
||||
|
||||
try {
|
||||
const guild = await client.guilds.fetch(guild_id);
|
||||
const targetMember = await guild.members.fetch(targetUserId);
|
||||
const guild = client.guilds.cache.get(guild_id);
|
||||
const targetMember = await resolveMember(guild, targetUserId);
|
||||
|
||||
// Fetch both Valorant and CS2 inventories
|
||||
const valoSkins = await skinService.getUserInventory(targetUserId);
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from "discord-interactions";
|
||||
import { activeSearchs, skins } from "../../game/state.js";
|
||||
import * as skinService from "../../services/skin.service.js";
|
||||
import { resolveMember } from "../../utils/index.js";
|
||||
|
||||
/**
|
||||
* Handles the /search slash command.
|
||||
@@ -52,7 +53,7 @@ export async function handleSearchCommand(req, res, client, interactionId) {
|
||||
};
|
||||
|
||||
// --- 4. Prepare Initial Embed Content ---
|
||||
const guild = await client.guilds.fetch(guild_id);
|
||||
const guild = client.guilds.cache.get(guild_id);
|
||||
const currentSkin = resultSkins[0];
|
||||
const skinData = skins.find((s) => s.uuid === currentSkin.uuid);
|
||||
if (!skinData) {
|
||||
@@ -63,7 +64,7 @@ export async function handleSearchCommand(req, res, client, interactionId) {
|
||||
let ownerText = "";
|
||||
if (currentSkin.userId) {
|
||||
try {
|
||||
const owner = await guild.members.fetch(currentSkin.userId);
|
||||
const owner = await resolveMember(guild, currentSkin.userId);
|
||||
ownerText = `| **@${owner.user.globalName || owner.user.username}** ✅`;
|
||||
} catch (e) {
|
||||
console.warn(`Could not fetch owner for user ID: ${currentSkin.userId}`);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { InteractionResponseType } from "discord-interactions";
|
||||
import * as skinService from "../../services/skin.service.js";
|
||||
import { resolveMember } from "../../utils/index.js";
|
||||
|
||||
/**
|
||||
* Handles the /skins slash command.
|
||||
@@ -14,7 +15,7 @@ export async function handleSkinsCommand(req, res, client) {
|
||||
try {
|
||||
// --- 1. Fetch Data ---
|
||||
const topSkins = await skinService.getTopSkins();
|
||||
const guild = await client.guilds.fetch(guild_id);
|
||||
const guild = client.guilds.cache.get(guild_id);
|
||||
const fields = [];
|
||||
|
||||
// --- 2. Build Embed Fields Asynchronously ---
|
||||
@@ -25,7 +26,7 @@ export async function handleSkinsCommand(req, res, client) {
|
||||
// If the skin has an owner, fetch their details
|
||||
if (skin.userId) {
|
||||
try {
|
||||
const owner = await guild.members.fetch(skin.userId);
|
||||
const owner = await resolveMember(guild, skin.userId);
|
||||
// Use globalName if available, otherwise fallback to username
|
||||
ownerText = `**@${owner.user.globalName || owner.user.username}** ✅`;
|
||||
} catch (e) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
ButtonStyleTypes,
|
||||
} from "discord-interactions";
|
||||
|
||||
import { formatTime, getOnlineUsersWithRole } from "../../utils/index.js";
|
||||
import { formatTime, getOnlineUsersWithRole, resolveMember } from "../../utils/index.js";
|
||||
import { DiscordRequest } from "../../api/discord.js";
|
||||
import { activePolls } from "../../game/state.js";
|
||||
import { getSocketIo } from "../../server/socket.js";
|
||||
@@ -28,9 +28,9 @@ export async function handleTimeoutCommand(req, res, client) {
|
||||
const time = options[1].value;
|
||||
|
||||
// Fetch member objects from Discord
|
||||
const guild = await client.guilds.fetch(guild_id);
|
||||
const fromMember = await guild.members.fetch(userId);
|
||||
const toMember = await guild.members.fetch(targetUserId);
|
||||
const guild = client.guilds.cache.get(guild_id);
|
||||
const fromMember = await resolveMember(guild, userId);
|
||||
const toMember = await resolveMember(guild, targetUserId);
|
||||
|
||||
// --- Validation Checks ---
|
||||
// 1. Check if a poll is already running for the target user
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { DiscordRequest } from "../../api/discord.js";
|
||||
import { activeInventories } from "../../game/state.js";
|
||||
import { buildSkinEmbed } from "../commands/inventory.js";
|
||||
import { resolveMember } from "../../utils/index.js";
|
||||
|
||||
/**
|
||||
* Handles navigation button clicks (Previous/Next) for the inventory embed.
|
||||
@@ -55,8 +56,8 @@ export async function handleInventoryNav(req, res, client) {
|
||||
const currentPage = inventorySession.page;
|
||||
const currentSkin = inventorySkins[currentPage];
|
||||
|
||||
const guild = await client.guilds.fetch(guild_id);
|
||||
const targetMember = await guild.members.fetch(inventorySession.akhyId);
|
||||
const guild = client.guilds.cache.get(guild_id);
|
||||
const targetMember = await resolveMember(guild, inventorySession.akhyId);
|
||||
const totalPrice = inventorySkins.reduce((sum, skin) => {
|
||||
return sum + (skin._type === "cs" ? skin.price || 0 : skin.currentPrice || 0);
|
||||
}, 0);
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
|
||||
import { DiscordRequest } from "../../api/discord.js";
|
||||
import { activeSearchs, skins } from "../../game/state.js";
|
||||
import { resolveUser } from "../../utils/index.js";
|
||||
|
||||
/**
|
||||
* Handles navigation button clicks (Previous/Next) for the search results embed.
|
||||
@@ -67,7 +68,7 @@ export async function handleSearchNav(req, res, client) {
|
||||
let ownerText = "";
|
||||
if (currentSkin.userId) {
|
||||
try {
|
||||
const owner = await client.users.fetch(currentSkin.userId);
|
||||
const owner = await resolveUser(client, currentSkin.userId);
|
||||
ownerText = `| **@${owner.globalName || owner.username}** ✅`;
|
||||
} catch (e) {
|
||||
console.warn(`Could not fetch owner for user ID: ${currentSkin.userId}`);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { handleMessageCreate } from "./handlers/messageCreate.js";
|
||||
import { getAkhys } from "../utils/index.js";
|
||||
import { fetchSuggestedPrices, fetchSkinsData } from "../api/cs.js";
|
||||
import { buildPriceIndex, buildWeaponRarityPriceMap } from "../utils/cs.state.js";
|
||||
|
||||
/**
|
||||
* Initializes and attaches all necessary event listeners to the Discord client.
|
||||
@@ -21,6 +22,8 @@ export function initializeEvents(client, io) {
|
||||
//setupCronJobs(client, io);
|
||||
await fetchSuggestedPrices();
|
||||
await fetchSkinsData();
|
||||
buildPriceIndex();
|
||||
buildWeaponRarityPriceMap();
|
||||
console.log("--- FlopoBOT is fully operational ---");
|
||||
});
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
MAX_ATTS_PER_MESSAGE,
|
||||
stripMentionsOfBot,
|
||||
} from "../../utils/ai.js";
|
||||
import { calculateBasePrice, calculateMaxPrice, formatTime, getAkhys } from "../../utils/index.js";
|
||||
import { calculateBasePrice, calculateMaxPrice, formatTime, getAkhys, resolveMember } from "../../utils/index.js";
|
||||
import { channelPointsHandler, initTodaysSOTD, randomSkinPrice, slowmodesHandler } from "../../game/points.js";
|
||||
import { activePolls, activeSlowmodes, requestTimestamps, skins } from "../../game/state.js";
|
||||
import prisma from "../../prisma/client.js";
|
||||
@@ -106,7 +106,7 @@ async function handleAiMention(message, client, io) {
|
||||
// Apply timeout if warn count is too high
|
||||
if (authorDB.warns > (parseInt(process.env.MAX_WARNS) || 10)) {
|
||||
try {
|
||||
const member = await message.guild.members.fetch(authorId);
|
||||
const member = await resolveMember(message.guild, authorId);
|
||||
const time = parseInt(process.env.SPAM_TIMEOUT_TIME);
|
||||
await member.timeout(time, "Spam excessif du bot AI.");
|
||||
message.channel
|
||||
@@ -255,7 +255,7 @@ async function handleAdminCommands(message) {
|
||||
await getAkhys(client);
|
||||
break;
|
||||
case `${prefix}:avatars`:
|
||||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
const members = await guild.members.fetch();
|
||||
const akhys = members.filter((m) => !m.user.bot && m.roles.cache.has(process.env.AKHY_ROLE_ID));
|
||||
|
||||
@@ -355,34 +355,33 @@ async function handleAdminCommands(message) {
|
||||
break;
|
||||
case `${prefix}:refund-skins`:
|
||||
try {
|
||||
const DBskins = await skinService.getAllSkins();
|
||||
for (const skin of DBskins) {
|
||||
const allCsSkins = await csSkinService.getAllOwnedCsSkins();
|
||||
let refundedCount = 0;
|
||||
let totalRefunded = 0;
|
||||
for (const skin of allCsSkins) {
|
||||
const price = skin.price || 0;
|
||||
let owner = null;
|
||||
try {
|
||||
owner = await userService.getUser(skin.userId)
|
||||
owner = await userService.getUser(skin.userId);
|
||||
} catch {
|
||||
//
|
||||
};
|
||||
}
|
||||
if (owner) {
|
||||
await userService.updateUserCoins(owner.id, owner.coins + skin.currentPrice);
|
||||
await userService.updateUserCoins(owner.id, owner.coins + price);
|
||||
await logService.insertLog({
|
||||
id: `${skin.uuid}-skin-refund-${Date.now()}`,
|
||||
id: `${skin.id}-cs-skin-refund-${Date.now()}`,
|
||||
userId: owner.id,
|
||||
targetUserId: null,
|
||||
action: "SKIN_REFUND",
|
||||
coinsAmount: skin.currentPrice,
|
||||
userNewAmount: owner.coins + skin.currentPrice,
|
||||
action: "CS_SKIN_REFUND",
|
||||
coinsAmount: price,
|
||||
userNewAmount: owner.coins + price,
|
||||
});
|
||||
totalRefunded += price;
|
||||
refundedCount++;
|
||||
}
|
||||
await skinService.updateSkin({
|
||||
uuid: skin.uuid,
|
||||
userId: null,
|
||||
currentPrice: null,
|
||||
currentLvl: null,
|
||||
currentChroma: null,
|
||||
});
|
||||
await csSkinService.deleteCsSkin(skin.id);
|
||||
}
|
||||
message.reply("All skins refunded.");
|
||||
message.reply(`Refunded ${refundedCount} CS skins (${totalRefunded} FlopoCoins total).`);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
message.reply(`Error during refund skins ${e.message}`);
|
||||
|
||||
@@ -323,8 +323,8 @@ export async function settleAll(room) {
|
||||
hand.result = res.result;
|
||||
hand.delta = res.delta;
|
||||
try {
|
||||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||
const generalChannel = await guild.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
const generalChannel = guild.channels.cache.get(process.env.BOT_CHANNEL_ID);
|
||||
const msg = await generalChannel.messages.fetch(p.msgId);
|
||||
const updatedEmbed = new EmbedBuilder()
|
||||
.setDescription(`<@${p.id}> joue au Blackjack.`)
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as userService from "../services/user.service.js";
|
||||
import * as gameService from "../services/game.service.js";
|
||||
import { ButtonStyle, EmbedBuilder } from "discord.js";
|
||||
import { client } from "../bot/client.js";
|
||||
import { resolveUser } from "../utils/index.js";
|
||||
|
||||
/**
|
||||
* Handles Elo calculation for a standard 1v1 game.
|
||||
@@ -72,9 +73,9 @@ export async function eloHandler(p1Id, p2Id, p1Score, p2Score, type, scores = nu
|
||||
console.log(`Elo Update (${type}) for ${p1DB.globalName}: ${p1CurrentElo} -> ${finalP1Elo}`);
|
||||
console.log(`Elo Update (${type}) for ${p2DB.globalName}: ${p2CurrentElo} -> ${finalP2Elo}`);
|
||||
try {
|
||||
const generalChannel = await client.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||
const user1 = await client.users.fetch(p1Id);
|
||||
const user2 = await client.users.fetch(p2Id);
|
||||
const generalChannel = client.channels.cache.get(process.env.BOT_CHANNEL_ID);
|
||||
const user1 = await resolveUser(client, p1Id);
|
||||
const user2 = await resolveUser(client, p2Id);
|
||||
const diff1 = finalP1Elo - p1CurrentElo;
|
||||
const diff2 = finalP2Elo - p2CurrentElo;
|
||||
const embed = new EmbedBuilder()
|
||||
|
||||
@@ -15,7 +15,7 @@ import * as csSkinService from "../../services/csSkin.service.js";
|
||||
import { activePolls, activePredis, activeSlowmodes, skins, activeSnakeGames } from "../../game/state.js";
|
||||
|
||||
// --- Utility and API Imports ---
|
||||
import { formatTime, isMeleeSkin, isVCTSkin, isChampionsSkin, getVCTRegion } from "../../utils/index.js";
|
||||
import { formatTime, isMeleeSkin, isVCTSkin, isChampionsSkin, getVCTRegion, resolveUser, resolveMember } from "../../utils/index.js";
|
||||
import { DiscordRequest } from "../../api/discord.js";
|
||||
|
||||
// --- Discord.js Builder Imports ---
|
||||
@@ -38,6 +38,10 @@ const router = express.Router();
|
||||
export function apiRoutes(client, io) {
|
||||
// --- Server Health & Basic Data ---
|
||||
|
||||
router.get("/download-db", (req, res) => {
|
||||
res.download("/db/flopobot.db");
|
||||
});
|
||||
|
||||
router.get("/check", (req, res) => {
|
||||
res.status(200).json({ status: "OK", message: "FlopoBot API is running." });
|
||||
});
|
||||
@@ -64,7 +68,7 @@ export function apiRoutes(client, io) {
|
||||
|
||||
router.post("/register-user", requireAuth, async (req, res) => {
|
||||
const discordUserId = req.userId;
|
||||
const discordUser = await client.users.fetch(discordUserId);
|
||||
const discordUser = await resolveUser(client, discordUserId);
|
||||
|
||||
try {
|
||||
await userService.insertUser({
|
||||
@@ -594,7 +598,7 @@ export function apiRoutes(client, io) {
|
||||
|
||||
router.get("/user/:id/avatar", async (req, res) => {
|
||||
try {
|
||||
const user = await client.users.fetch(req.params.id);
|
||||
const user = await resolveUser(client, req.params.id);
|
||||
const avatarUrl = user.displayAvatarURL({ format: "png", size: 256 });
|
||||
res.json({ avatarUrl });
|
||||
} catch (error) {
|
||||
@@ -604,7 +608,7 @@ export function apiRoutes(client, io) {
|
||||
|
||||
router.get("/user/:id/username", async (req, res) => {
|
||||
try {
|
||||
const user = await client.users.fetch(req.params.id);
|
||||
const user = await resolveUser(client, req.params.id);
|
||||
res.json({ user });
|
||||
} catch (error) {
|
||||
res.status(404).json({ error: "User not found." });
|
||||
@@ -729,8 +733,8 @@ export function apiRoutes(client, io) {
|
||||
router.post("/timedout", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||
const member = await guild.members.fetch(userId);
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
const member = await resolveMember(guild, userId);
|
||||
res.status(200).json({ isTimedOut: member?.isCommunicationDisabled() || false });
|
||||
} catch (e) {
|
||||
res.status(404).send({ message: "Member not found or guild unavailable." });
|
||||
@@ -747,8 +751,8 @@ export function apiRoutes(client, io) {
|
||||
if (commandUser.coins < 1000) return res.status(403).json({ message: "Pas assez de FlopoCoins (1000 requis)." });
|
||||
|
||||
try {
|
||||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||
const member = await guild.members.fetch(userId);
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
const member = await resolveMember(guild, userId);
|
||||
const old_nickname = member.nickname;
|
||||
await member.setNickname(nickname);
|
||||
|
||||
@@ -766,7 +770,7 @@ export function apiRoutes(client, io) {
|
||||
console.log(`${commandUserId} change nickname of ${userId}: ${old_nickname} -> ${nickname}`);
|
||||
|
||||
try {
|
||||
const generalChannel = await guild.channels.fetch(process.env.GENERAL_CHANNEL_ID);
|
||||
const generalChannel = guild.channels.cache.get(process.env.GENERAL_CHANNEL_ID);
|
||||
const embed = new EmbedBuilder()
|
||||
.setDescription(`<@${commandUserId}> a modifié le pseudo de <@${userId}>`)
|
||||
.addFields(
|
||||
@@ -802,7 +806,7 @@ export function apiRoutes(client, io) {
|
||||
if (commandUser.coins < 5000) return res.status(403).json({ message: "Pas assez de coins" });
|
||||
|
||||
try {
|
||||
const discordUser = await client.users.fetch(userId);
|
||||
const discordUser = await resolveUser(client, userId);
|
||||
|
||||
await discordUser.send(`<@${userId}>`);
|
||||
|
||||
@@ -820,8 +824,8 @@ export function apiRoutes(client, io) {
|
||||
await emitDataUpdated({ table: "users", action: "update" });
|
||||
|
||||
try {
|
||||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||
const generalChannel = await guild.channels.fetch(process.env.GENERAL_CHANNEL_ID);
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
const generalChannel = guild.channels.cache.get(process.env.GENERAL_CHANNEL_ID);
|
||||
const embed = new EmbedBuilder()
|
||||
.setDescription(`<@${commandUserId}> a envoyé un spam ping à <@${userId}>`)
|
||||
.setColor("#5865f2")
|
||||
@@ -877,8 +881,8 @@ export function apiRoutes(client, io) {
|
||||
});
|
||||
|
||||
try {
|
||||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||
const generalChannel = await guild.channels.fetch(process.env.GENERAL_CHANNEL_ID);
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
const generalChannel = guild.channels.cache.get(process.env.GENERAL_CHANNEL_ID);
|
||||
const embed = new EmbedBuilder()
|
||||
.setDescription(`<@${commandUserId}> a retiré son slowmode`)
|
||||
.setColor("#5865f2")
|
||||
@@ -920,8 +924,8 @@ export function apiRoutes(client, io) {
|
||||
await emitDataUpdated({ table: "users", action: "update" });
|
||||
|
||||
try {
|
||||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||
const generalChannel = await guild.channels.fetch(process.env.GENERAL_CHANNEL_ID);
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
const generalChannel = guild.channels.cache.get(process.env.GENERAL_CHANNEL_ID);
|
||||
const embed = new EmbedBuilder()
|
||||
.setDescription(`<@${commandUserId}> a mis <@${userId}> en slowmode pendant 1h`)
|
||||
.setColor("#5865f2")
|
||||
@@ -952,8 +956,8 @@ export function apiRoutes(client, io) {
|
||||
|
||||
if (!user) return res.status(403).send({ message: "Oups petit problème" });
|
||||
|
||||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||
const member = await guild.members.fetch(userId);
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
const member = await resolveMember(guild, userId);
|
||||
|
||||
if (userId === commandUserId) {
|
||||
if (
|
||||
@@ -988,7 +992,7 @@ export function apiRoutes(client, io) {
|
||||
});
|
||||
|
||||
try {
|
||||
const generalChannel = await guild.channels.fetch(process.env.GENERAL_CHANNEL_ID);
|
||||
const generalChannel = guild.channels.cache.get(process.env.GENERAL_CHANNEL_ID);
|
||||
const embed = new EmbedBuilder()
|
||||
.setDescription(`<@${commandUserId}> a retiré son time-out`)
|
||||
.setColor("#5865f2")
|
||||
@@ -1035,7 +1039,7 @@ export function apiRoutes(client, io) {
|
||||
await emitDataUpdated({ table: "users", action: "update" });
|
||||
|
||||
try {
|
||||
const generalChannel = await guild.channels.fetch(process.env.GENERAL_CHANNEL_ID);
|
||||
const generalChannel = guild.channels.cache.get(process.env.GENERAL_CHANNEL_ID);
|
||||
const embed = new EmbedBuilder()
|
||||
.setDescription(`<@${commandUserId}> a time-out <@${userId}> pour 12h`)
|
||||
.setColor("#5865f2")
|
||||
@@ -1076,8 +1080,8 @@ export function apiRoutes(client, io) {
|
||||
|
||||
let msgId;
|
||||
try {
|
||||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||
const generalChannel = await guild.channels.fetch(process.env.GENERAL_CHANNEL_ID);
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
const generalChannel = guild.channels.cache.get(process.env.GENERAL_CHANNEL_ID);
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(`Prédiction de ${commandUser.username}`)
|
||||
.setDescription(`**${label}**`)
|
||||
@@ -1298,8 +1302,8 @@ export function apiRoutes(client, io) {
|
||||
}
|
||||
|
||||
try {
|
||||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||
const generalChannel = await guild.channels.fetch(process.env.GENERAL_CHANNEL_ID);
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
const generalChannel = guild.channels.cache.get(process.env.GENERAL_CHANNEL_ID);
|
||||
const message = await generalChannel.messages.fetch(activePredis[predi].msgId);
|
||||
const updatedEmbed = new EmbedBuilder()
|
||||
.setTitle(`Prédiction de ${commandUser.username}`)
|
||||
@@ -1570,7 +1574,7 @@ export function apiRoutes(client, io) {
|
||||
|
||||
// Notify user via Discord if possible
|
||||
try {
|
||||
const discordUser = await client.users.fetch(commandUserId);
|
||||
const discordUser = await resolveUser(client, commandUserId);
|
||||
await discordUser.send(
|
||||
`✅ Votre achat de ${expectedCoins} FlopoCoins a été confirmé ! Merci pour votre soutien !`,
|
||||
);
|
||||
|
||||
@@ -81,6 +81,8 @@ router.get("/discord/callback", async (req, res) => {
|
||||
res.redirect(`${FLAPI_URL}/auth/callback?token=${token}&discordId=${discordUser.id}`);
|
||||
} catch (error) {
|
||||
console.error("Discord OAuth2 error:", error.response?.data || error.message);
|
||||
console.log("Status:", error.response?.status);
|
||||
console.log("Headers:", JSON.stringify(error.response?.headers));
|
||||
res.redirect(`${FLAPI_URL}/auth/callback?error=auth_failed`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ import { client } from "../../bot/client.js";
|
||||
import { emitToast, emitUpdate, emitPlayerUpdate } from "../socket.js";
|
||||
import { EmbedBuilder, time } from "discord.js";
|
||||
import { requireAuth } from "../middleware/auth.js";
|
||||
import { resolveUser } from "../../utils/index.js";
|
||||
|
||||
export function blackjackRoutes(io) {
|
||||
const router = express.Router();
|
||||
@@ -126,7 +127,7 @@ export function blackjackRoutes(io) {
|
||||
const userId = req.userId;
|
||||
if (room.players[userId]) return res.status(200).json({ message: "Already here" });
|
||||
|
||||
const user = await client.users.fetch(userId);
|
||||
const user = await resolveUser(client, userId);
|
||||
const bank = (await userService.getUser(userId))?.coins ?? 0;
|
||||
|
||||
room.players[userId] = {
|
||||
@@ -155,8 +156,8 @@ export function blackjackRoutes(io) {
|
||||
};
|
||||
|
||||
try {
|
||||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||
const generalChannel = await guild.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
const generalChannel = guild.channels.cache.get(process.env.BOT_CHANNEL_ID);
|
||||
const embed = new EmbedBuilder()
|
||||
.setDescription(`<@${userId}> joue au Blackjack`)
|
||||
.addFields(
|
||||
@@ -194,8 +195,8 @@ export function blackjackRoutes(io) {
|
||||
if (!room.players[userId]) return res.status(403).json({ message: "not in room" });
|
||||
|
||||
try {
|
||||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||
const generalChannel = await guild.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
const generalChannel = guild.channels.cache.get(process.env.BOT_CHANNEL_ID);
|
||||
const msg = await generalChannel.messages.fetch(room.players[userId].msgId);
|
||||
const updatedEmbed = new EmbedBuilder()
|
||||
.setDescription(`<@${userId}> a quitté la table de Blackjack.`)
|
||||
@@ -226,7 +227,7 @@ export function blackjackRoutes(io) {
|
||||
} else {
|
||||
delete room.players[userId];
|
||||
emitUpdate("player-left", snapshot(room));
|
||||
const user = await client.users.fetch(userId);
|
||||
const user = await resolveUser(client, userId);
|
||||
emitPlayerUpdate({
|
||||
id: userId,
|
||||
msg: `${user?.globalName || user?.username} a quitté la table de Blackjack.`,
|
||||
@@ -367,7 +368,7 @@ export function blackjackRoutes(io) {
|
||||
// Remove leavers
|
||||
for (const userId of Object.keys(room.leavingAfterRound)) {
|
||||
delete room.players[userId];
|
||||
const user = await client.users.fetch(userId);
|
||||
const user = await resolveUser(client, userId);
|
||||
emitPlayerUpdate({
|
||||
id: userId,
|
||||
msg: `${user?.globalName || user?.username} a quitté la table de Blackjack.`,
|
||||
|
||||
@@ -2,6 +2,7 @@ import express from "express";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { erinyesRooms } from "../../game/state.js";
|
||||
import { socketEmit } from "../socket.js";
|
||||
import { resolveUser } from "../../utils/index.js";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -35,7 +36,7 @@ export function erinyesRoutes(client, io) {
|
||||
res.status(404).json({ message: "You are already in a room." });
|
||||
}
|
||||
|
||||
const creator = await client.users.fetch(creatorId);
|
||||
const creator = await resolveUser(client, creatorId);
|
||||
const id = uuidv4();
|
||||
|
||||
createRoom({
|
||||
|
||||
@@ -16,7 +16,7 @@ import { sleep } from "openai/core";
|
||||
import { client } from "../../bot/client.js";
|
||||
import { emitPokerToast, emitPokerUpdate } from "../socket.js";
|
||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
||||
import { formatAmount } from "../../utils/index.js";
|
||||
import { formatAmount, resolveUser } from "../../utils/index.js";
|
||||
import { requireAuth } from "../middleware/auth.js";
|
||||
|
||||
const { Hand } = pkg;
|
||||
@@ -53,8 +53,8 @@ export function pokerRoutes(client, io) {
|
||||
return res.status(403).json({ message: "You are already in a poker room." });
|
||||
}
|
||||
|
||||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||
const creator = await client.users.fetch(creatorId);
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
const creator = await resolveUser(client, creatorId);
|
||||
const id = uuidv4();
|
||||
const name = uniqueNamesGenerator({
|
||||
dictionaries: [adjectives, ["Poker"]],
|
||||
@@ -91,7 +91,7 @@ export function pokerRoutes(client, io) {
|
||||
await emitPokerUpdate({ room: pokerRooms[id], type: "room-created" });
|
||||
|
||||
try {
|
||||
const generalChannel = await guild.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||
const generalChannel = guild.channels.cache.get(process.env.BOT_CHANNEL_ID);
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("Flopoker 🃏")
|
||||
.setDescription(`<@${creatorId}> a créé une table de poker`)
|
||||
@@ -365,7 +365,7 @@ export function pokerRoutes(client, io) {
|
||||
// --- Helper Functions ---
|
||||
|
||||
async function joinRoom(roomId, userId, io) {
|
||||
const user = await client.users.fetch(userId);
|
||||
const user = await resolveUser(client, userId);
|
||||
const userDB = await userService.getUser(userId);
|
||||
const room = pokerRooms[roomId];
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from "../game/various.js";
|
||||
import { eloHandler } from "../game/elo.js";
|
||||
import { verifyToken } from "./middleware/auth.js";
|
||||
import { resolveUser } from "../utils/index.js";
|
||||
|
||||
// --- Module-level State ---
|
||||
let io;
|
||||
@@ -319,7 +320,7 @@ async function createGame(client, gameType) {
|
||||
const { queue, activeGames, title } = getGameAssets(gameType);
|
||||
const p1Id = queue.shift();
|
||||
const p2Id = queue.shift();
|
||||
const [p1, p2] = await Promise.all([client.users.fetch(p1Id), client.users.fetch(p2Id)]);
|
||||
const [p1, p2] = await Promise.all([resolveUser(client, p1Id), resolveUser(client, p2Id)]);
|
||||
|
||||
let lobby;
|
||||
if (gameType === "tictactoe") {
|
||||
@@ -426,9 +427,9 @@ async function refreshQueuesForUser(userId, client) {
|
||||
if (index > -1) {
|
||||
tictactoeQueue.splice(index, 1);
|
||||
try {
|
||||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||
const generalChannel = await guild.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||
const user = await client.users.fetch(userId);
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
const generalChannel = guild.channels.cache.get(process.env.BOT_CHANNEL_ID);
|
||||
const user = await resolveUser(client, userId);
|
||||
const queueMsg = await generalChannel.messages.fetch(queueMessagesEndpoints[userId]);
|
||||
const updatedEmbed = new EmbedBuilder()
|
||||
.setTitle("Tic Tac Toe")
|
||||
@@ -446,9 +447,9 @@ async function refreshQueuesForUser(userId, client) {
|
||||
if (index > -1) {
|
||||
connect4Queue.splice(index, 1);
|
||||
try {
|
||||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||
const generalChannel = await guild.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||
const user = await client.users.fetch(userId);
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
const generalChannel = guild.channels.cache.get(process.env.BOT_CHANNEL_ID);
|
||||
const user = await resolveUser(client, userId);
|
||||
const queueMsg = await generalChannel.messages.fetch(queueMessagesEndpoints[userId]);
|
||||
const updatedEmbed = new EmbedBuilder()
|
||||
.setTitle("Puissance 4")
|
||||
@@ -466,9 +467,9 @@ async function refreshQueuesForUser(userId, client) {
|
||||
if (index > -1) {
|
||||
snakeQueue.splice(index, 1);
|
||||
try {
|
||||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||
const generalChannel = await guild.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||
const user = await client.users.fetch(userId);
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
const generalChannel = guild.channels.cache.get(process.env.BOT_CHANNEL_ID);
|
||||
const user = await resolveUser(client, userId);
|
||||
const queueMsg = await generalChannel.messages.fetch(queueMessagesEndpoints[userId]);
|
||||
const updatedEmbed = new EmbedBuilder()
|
||||
.setTitle("Snake 1v1")
|
||||
@@ -491,7 +492,7 @@ async function emitQueueUpdate(client, gameType) {
|
||||
const { queue, activeGames } = getGameAssets(gameType);
|
||||
const names = await Promise.all(
|
||||
queue.map(async (id) => {
|
||||
const user = await client.users.fetch(id).catch(() => null);
|
||||
const user = await resolveUser(client, id).catch(() => null);
|
||||
return user?.globalName || user?.username;
|
||||
}),
|
||||
);
|
||||
@@ -528,8 +529,8 @@ function getGameAssets(gameType) {
|
||||
|
||||
async function postQueueToDiscord(client, playerId, title, url) {
|
||||
try {
|
||||
const generalChannel = await client.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||
const user = await client.users.fetch(playerId);
|
||||
const generalChannel = client.channels.cache.get(process.env.BOT_CHANNEL_ID);
|
||||
const user = await resolveUser(client, playerId);
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(title)
|
||||
.setDescription(`**${user.globalName || user.username}** est dans la file d'attente.`)
|
||||
@@ -552,7 +553,7 @@ async function postQueueToDiscord(client, playerId, title, url) {
|
||||
}
|
||||
|
||||
async function updateDiscordMessage(client, game, title, resultText = "") {
|
||||
const channel = await client.channels.fetch(process.env.BOT_CHANNEL_ID).catch(() => null);
|
||||
const channel = client.channels.cache.get(process.env.BOT_CHANNEL_ID);
|
||||
if (!channel) return null;
|
||||
|
||||
let description;
|
||||
|
||||
@@ -18,6 +18,12 @@ export async function getUserCsSkinsByRarity(userId, rarity) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAllOwnedCsSkins() {
|
||||
return prisma.csSkin.findMany({
|
||||
where: { userId: { not: null } },
|
||||
});
|
||||
}
|
||||
|
||||
export async function insertCsSkin(data) {
|
||||
return prisma.csSkin.create({ data });
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export async function deleteSOTD() {
|
||||
|
||||
export async function getAllSOTDStats() {
|
||||
const stats = await prisma.sotdStat.findMany({
|
||||
include: { user: { select: { globalName: true, avatarUrl: true } } },
|
||||
include: { user: { select: { username: true, globalName: true, avatarUrl: true } } },
|
||||
orderBy: [{ score: "desc" }, { moves: "asc" }, { time: "asc" }],
|
||||
});
|
||||
return stats.map((s) => ({
|
||||
|
||||
@@ -1,3 +1,80 @@
|
||||
export let csSkinsData = {};
|
||||
|
||||
export let csSkinsPrices = {};
|
||||
|
||||
// Structured index: baseSkinName -> { base, stattrak, souvenir } -> wearState -> priceData
|
||||
export let csSkinsPriceIndex = {};
|
||||
|
||||
// weaponType -> rarity -> [baseSkinName, ...] (only skins that have Skinport prices)
|
||||
export let weaponRarityPriceMap = {};
|
||||
|
||||
const wearRegex = /\s*\((Factory New|Minimal Wear|Field-Tested|Well-Worn|Battle-Scarred)\)\s*$/;
|
||||
|
||||
function parseSkinportKey(key) {
|
||||
const wearMatch = key.match(wearRegex);
|
||||
if (!wearMatch) return null;
|
||||
|
||||
const wearState = wearMatch[1];
|
||||
let baseName = key.slice(0, wearMatch.index);
|
||||
let variant = "base";
|
||||
|
||||
if (baseName.startsWith("★ StatTrak™ ")) {
|
||||
variant = "stattrak";
|
||||
baseName = "★ " + baseName.slice("★ StatTrak™ ".length);
|
||||
} else if (baseName.startsWith("StatTrak™ ")) {
|
||||
variant = "stattrak";
|
||||
baseName = baseName.slice("StatTrak™ ".length);
|
||||
} else if (baseName.startsWith("Souvenir ")) {
|
||||
variant = "souvenir";
|
||||
baseName = baseName.slice("Souvenir ".length);
|
||||
}
|
||||
|
||||
return { baseName, variant, wearState };
|
||||
}
|
||||
|
||||
export function buildPriceIndex() {
|
||||
csSkinsPriceIndex = {};
|
||||
|
||||
for (const [key, priceData] of Object.entries(csSkinsPrices)) {
|
||||
const parsed = parseSkinportKey(key);
|
||||
if (!parsed) continue;
|
||||
|
||||
const { baseName, variant, wearState } = parsed;
|
||||
|
||||
if (!csSkinsPriceIndex[baseName]) {
|
||||
csSkinsPriceIndex[baseName] = {};
|
||||
}
|
||||
if (!csSkinsPriceIndex[baseName][variant]) {
|
||||
csSkinsPriceIndex[baseName][variant] = {};
|
||||
}
|
||||
csSkinsPriceIndex[baseName][variant][wearState] = priceData;
|
||||
}
|
||||
|
||||
const indexedCount = Object.keys(csSkinsPriceIndex).length;
|
||||
const totalSkins = Object.keys(csSkinsData).length;
|
||||
const coverage = totalSkins > 0 ? ((indexedCount / totalSkins) * 100).toFixed(1) : 0;
|
||||
console.log(`[Skinport] Price index built: ${indexedCount} skins indexed, ${totalSkins} total skins (${coverage}% coverage)`);
|
||||
}
|
||||
|
||||
export function buildWeaponRarityPriceMap() {
|
||||
weaponRarityPriceMap = {};
|
||||
|
||||
for (const [skinName, skinData] of Object.entries(csSkinsData)) {
|
||||
// Only include skins that have at least one Skinport price entry
|
||||
if (!csSkinsPriceIndex[skinName]) continue;
|
||||
|
||||
const weapon = skinData.weapon?.name;
|
||||
const rarity = skinData.rarity?.name;
|
||||
if (!weapon || !rarity) continue;
|
||||
|
||||
if (!weaponRarityPriceMap[weapon]) {
|
||||
weaponRarityPriceMap[weapon] = {};
|
||||
}
|
||||
if (!weaponRarityPriceMap[weapon][rarity]) {
|
||||
weaponRarityPriceMap[weapon][rarity] = [];
|
||||
}
|
||||
weaponRarityPriceMap[weapon][rarity].push(skinName);
|
||||
}
|
||||
|
||||
console.log(`[Skinport] Weapon/rarity price map built: ${Object.keys(weaponRarityPriceMap).length} weapon types`);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { csSkinsData, csSkinsPrices } from "./cs.state.js";
|
||||
import { findReferenceSkin } from "../services/csSkin.service.js";
|
||||
import { csSkinsData, csSkinsPriceIndex, weaponRarityPriceMap } from "./cs.state.js";
|
||||
|
||||
const StateFactoryNew = "Factory New";
|
||||
const StateMinimalWear = "Minimal Wear";
|
||||
@@ -7,6 +6,20 @@ const StateFieldTested = "Field-Tested";
|
||||
const StateWellWorn = "Well-Worn";
|
||||
const StateBattleScarred = "Battle-Scarred";
|
||||
|
||||
const EUR_TO_FLOPOS = parseInt(process.env.EUR_TO_FLOPOS) || 6;
|
||||
const FLOAT_MODIFIER_MAX = 0.05;
|
||||
const STATTRAK_FALLBACK_MULTIPLIER = 3.5;
|
||||
const SOUVENIR_FALLBACK_MULTIPLIER = 6;
|
||||
|
||||
const WEAR_STATE_ORDER = [StateFactoryNew, StateMinimalWear, StateFieldTested, StateWellWorn, StateBattleScarred];
|
||||
const WEAR_STATE_RANGES = {
|
||||
[StateFactoryNew]: { min: 0.00, max: 0.07 },
|
||||
[StateMinimalWear]: { min: 0.07, max: 0.15 },
|
||||
[StateFieldTested]: { min: 0.15, max: 0.38 },
|
||||
[StateWellWorn]: { min: 0.38, max: 0.45 },
|
||||
[StateBattleScarred]: { min: 0.45, max: 1.00 },
|
||||
};
|
||||
|
||||
export const RarityToColor = {
|
||||
Gold: 0xffd700, // Standard Gold
|
||||
Extraordinary: 0xffae00, // Orange
|
||||
@@ -18,14 +31,15 @@ export const RarityToColor = {
|
||||
"Consumer Grade": 0xb0c3d9, // Light Grey/White
|
||||
};
|
||||
|
||||
// Last-resort fallback price ranges in EUR (used only when Skinport has no data)
|
||||
const basePriceRanges = {
|
||||
"Consumer Grade": { min: 1, max: 10 },
|
||||
"Industrial Grade": { min: 5, max: 50 },
|
||||
"Mil-Spec Grade": { min: 20, max: 150 },
|
||||
"Restricted": { min: 100, max: 1000 },
|
||||
"Classified": { min: 500, max: 4000 },
|
||||
"Covert": { min: 2500, max: 10000 },
|
||||
"Extraordinary": { min: 1500, max: 3000 },
|
||||
"Consumer Grade": { min: 0.03, max: 0.10 },
|
||||
"Industrial Grade": { min: 0.05, max: 0.30 },
|
||||
"Mil-Spec Grade": { min: 0.10, max: 1.50 },
|
||||
"Restricted": { min: 1.00, max: 10.00 },
|
||||
"Classified": { min: 5.00, max: 40.00 },
|
||||
"Covert": { min: 25.00, max: 150.00 },
|
||||
"Extraordinary": { min: 100.00, max: 800.00 },
|
||||
};
|
||||
|
||||
export const TRADE_UP_MAP = {
|
||||
@@ -55,71 +69,116 @@ export function randomSkinRarity() {
|
||||
return "Consumer Grade";
|
||||
}
|
||||
|
||||
export async function generatePrice(skinName, rarity, float, isStattrak, isSouvenir) {
|
||||
const ranges = basePriceRanges[rarity] || basePriceRanges["Industrial Grade"];
|
||||
function getSkinportPrice(priceData) {
|
||||
if (!priceData) return null;
|
||||
return priceData.suggested_price ?? priceData.median_price ?? priceData.mean_price ?? priceData.min_price ?? null;
|
||||
}
|
||||
|
||||
let finalPrice;
|
||||
const ref = await findReferenceSkin(skinName, isStattrak, isSouvenir);
|
||||
function applyFloatModifier(basePrice, float, wearState) {
|
||||
const range = WEAR_STATE_RANGES[wearState];
|
||||
if (!range) return basePrice;
|
||||
const span = range.max - range.min;
|
||||
if (span <= 0) return basePrice;
|
||||
// 0 = best float in range, 1 = worst
|
||||
const positionInRange = (float - range.min) / span;
|
||||
const modifier = 1 + FLOAT_MODIFIER_MAX * (1 - 2 * positionInRange);
|
||||
return basePrice * modifier;
|
||||
}
|
||||
|
||||
if (ref && ref.float !== null) {
|
||||
// Derive base price from reference: refPrice = basePrice * (1 - refFloat) → basePrice = refPrice / (1 - refFloat)
|
||||
const refBasePrice = ref.price / Math.max(1 - ref.float, 0.01);
|
||||
finalPrice = refBasePrice * (1 - float);
|
||||
} else {
|
||||
// No reference: random base price, scaled by float
|
||||
const basePrice = ranges.min + Math.random() * (ranges.max - ranges.min);
|
||||
finalPrice = basePrice * (1 - float) + ranges.min * float;
|
||||
function getAdjacentWearStates(wearState) {
|
||||
const idx = WEAR_STATE_ORDER.indexOf(wearState);
|
||||
if (idx === -1) return [];
|
||||
// Return wear states ordered by proximity
|
||||
const adjacent = [];
|
||||
for (let dist = 1; dist < WEAR_STATE_ORDER.length; dist++) {
|
||||
if (idx - dist >= 0) adjacent.push(WEAR_STATE_ORDER[idx - dist]);
|
||||
if (idx + dist < WEAR_STATE_ORDER.length) adjacent.push(WEAR_STATE_ORDER[idx + dist]);
|
||||
}
|
||||
return adjacent;
|
||||
}
|
||||
|
||||
function lookupSkinportEurPrice(skinName, wearState, isStattrak, isSouvenir) {
|
||||
const skinEntry = csSkinsPriceIndex[skinName];
|
||||
if (!skinEntry) return null;
|
||||
|
||||
const variant = isSouvenir ? "souvenir" : isStattrak ? "stattrak" : "base";
|
||||
|
||||
// 1. Exact match: correct variant + wear state
|
||||
let price = getSkinportPrice(skinEntry[variant]?.[wearState]);
|
||||
if (price !== null) return price;
|
||||
|
||||
// 2. Drop variant: use base price × multiplier
|
||||
if (variant !== "base") {
|
||||
const basePrice = getSkinportPrice(skinEntry["base"]?.[wearState]);
|
||||
if (basePrice !== null) {
|
||||
const multiplier = isSouvenir ? SOUVENIR_FALLBACK_MULTIPLIER : STATTRAK_FALLBACK_MULTIPLIER;
|
||||
return basePrice * multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
const isGold = rarity === "Covert";
|
||||
if (isSouvenir && !isGold) {
|
||||
finalPrice *= 7;
|
||||
} else if (isStattrak && !isGold) {
|
||||
finalPrice *= 4;
|
||||
// 3. Adjacent wear state (same variant, then base with multiplier)
|
||||
for (const adjWear of getAdjacentWearStates(wearState)) {
|
||||
const adjPrice = getSkinportPrice(skinEntry[variant]?.[adjWear]);
|
||||
if (adjPrice !== null) return adjPrice;
|
||||
|
||||
if (variant !== "base") {
|
||||
const adjBase = getSkinportPrice(skinEntry["base"]?.[adjWear]);
|
||||
if (adjBase !== null) {
|
||||
const multiplier = isSouvenir ? SOUVENIR_FALLBACK_MULTIPLIER : STATTRAK_FALLBACK_MULTIPLIER;
|
||||
return adjBase * multiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (finalPrice < 1) finalPrice = 1;
|
||||
return null;
|
||||
}
|
||||
|
||||
const name = skinName.toLowerCase();
|
||||
function findSimilarSkinPrice(skinName, rarity, wearState) {
|
||||
const skinData = csSkinsData[skinName];
|
||||
const weapon = skinData?.weapon?.name;
|
||||
if (!weapon) return null;
|
||||
|
||||
// Special pattern multipliers (more specific patterns first)
|
||||
if (name.includes("marble fade")) {
|
||||
finalPrice *= 1.35;
|
||||
} else if (name.includes("gamma doppler")) {
|
||||
finalPrice *= 1.4;
|
||||
} else if (name.includes("doppler")) {
|
||||
finalPrice *= 1.5;
|
||||
} else if (name.includes("fade")) {
|
||||
finalPrice *= 1.4;
|
||||
} else if (name.includes("crimson web")) {
|
||||
finalPrice *= 1.3;
|
||||
} else if (name.includes("case hardened")) {
|
||||
finalPrice *= 1.25;
|
||||
} else if (name.includes("lore")) {
|
||||
finalPrice *= 1.25;
|
||||
} else if (name.includes("tiger tooth")) {
|
||||
finalPrice *= 1.2;
|
||||
} else if (name.includes("slaughter")) {
|
||||
finalPrice *= 1.2;
|
||||
const candidates = weaponRarityPriceMap[weapon]?.[rarity];
|
||||
if (!candidates || candidates.length === 0) return null;
|
||||
|
||||
// Pick a random candidate that has a price for this wear state
|
||||
const shuffled = [...candidates].sort(() => Math.random() - 0.5);
|
||||
for (const candidate of shuffled) {
|
||||
if (candidate === skinName) continue;
|
||||
const entry = csSkinsPriceIndex[candidate];
|
||||
if (!entry) continue;
|
||||
// Try base variant first
|
||||
const price = getSkinportPrice(entry["base"]?.[wearState]);
|
||||
if (price !== null) return price;
|
||||
// Try any wear state
|
||||
for (const ws of WEAR_STATE_ORDER) {
|
||||
const wsPrice = getSkinportPrice(entry["base"]?.[ws]);
|
||||
if (wsPrice !== null) return wsPrice;
|
||||
}
|
||||
}
|
||||
|
||||
// Knife type boosts (more specific first)
|
||||
if (name.includes("butterfly")) {
|
||||
finalPrice *= 2;
|
||||
} else if (name.includes("karambit")) {
|
||||
finalPrice *= 1.8;
|
||||
} else if (name.includes("m9 bayonet")) {
|
||||
finalPrice *= 1.4;
|
||||
} else if (name.includes("talon")) {
|
||||
finalPrice *= 1.3;
|
||||
} else if (name.includes("skeleton")) {
|
||||
finalPrice *= 1.2;
|
||||
} else if (name.includes("bayonet")) {
|
||||
finalPrice *= 1.1;
|
||||
} else if (name.includes("gut") || name.includes("navaja") || name.includes("falchion")) {
|
||||
finalPrice *= 0.8;
|
||||
return null;
|
||||
}
|
||||
|
||||
export function generatePrice(skinName, rarity, float, isStattrak, isSouvenir) {
|
||||
const wearState = getWearState(float);
|
||||
let eurPrice = lookupSkinportEurPrice(skinName, wearState, isStattrak, isSouvenir);
|
||||
|
||||
if (eurPrice === null) {
|
||||
// 4. Similar skin: same weapon + same rarity
|
||||
eurPrice = findSimilarSkinPrice(skinName, rarity, wearState);
|
||||
}
|
||||
|
||||
if (eurPrice === null) {
|
||||
// 5. Last resort: rarity-based random range (already in EUR-ish scale)
|
||||
const ranges = basePriceRanges[rarity] || basePriceRanges["Industrial Grade"];
|
||||
eurPrice = ranges.min + Math.random() * (ranges.max - ranges.min);
|
||||
}
|
||||
|
||||
let finalPrice = Math.round(eurPrice * EUR_TO_FLOPOS);
|
||||
finalPrice = applyFloatModifier(finalPrice, float, wearState);
|
||||
finalPrice = Math.max(Math.round(finalPrice), 1);
|
||||
|
||||
return finalPrice.toFixed(0);
|
||||
}
|
||||
|
||||
@@ -167,6 +226,6 @@ export async function getRandomSkinWithRandomSpecs(u_float, forcedRarity) {
|
||||
isSouvenir: skinIsSouvenir,
|
||||
wearState,
|
||||
float,
|
||||
price: await generatePrice(skinName, skinData.rarity.name, float, skinIsStattrak, skinIsSouvenir),
|
||||
price: generatePrice(skinName, skinData.rarity.name, float, skinIsStattrak, skinIsSouvenir),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export async function getAkhys(client) {
|
||||
try {
|
||||
// 1. Fetch Discord Members
|
||||
const initial_akhys = (await userService.getAllUsers()).length;
|
||||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
const members = await guild.members.fetch();
|
||||
const akhys = members.filter((m) => !m.user.bot && m.roles.cache.has(process.env.AKHY_ROLE_ID));
|
||||
|
||||
@@ -547,3 +547,11 @@ export function isChampionsSkin(skinName) {
|
||||
const name = skinName.toLowerCase();
|
||||
return name.includes("champions");
|
||||
}
|
||||
|
||||
export async function resolveUser(client, userId) {
|
||||
return client.users.cache.get(userId) || await client.users.fetch(userId);
|
||||
}
|
||||
|
||||
export async function resolveMember(guild, userId) {
|
||||
return guild.members.cache.get(userId) || await guild.members.fetch(userId);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ 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 { EmbedBuilder } from "discord.js";
|
||||
import { resolveUser } from "./index.js";
|
||||
|
||||
/**
|
||||
* Gets the skin display name and icon from an offer, supporting both Valorant and CS2 skins.
|
||||
@@ -24,7 +25,7 @@ export async function handleNewMarketOffer(offerId, client) {
|
||||
if (!offer) return;
|
||||
const { name: skinName, icon: skinIcon } = await getOfferSkinInfo(offer);
|
||||
|
||||
const discordUserSeller = await client.users.fetch(offer.sellerId);
|
||||
const discordUserSeller = await resolveUser(client, offer.sellerId);
|
||||
try {
|
||||
const userSeller = await userService.getUser(offer.sellerId);
|
||||
if (discordUserSeller && userSeller?.isAkhy) {
|
||||
@@ -67,7 +68,7 @@ export async function handleNewMarketOffer(offerId, client) {
|
||||
}
|
||||
|
||||
try {
|
||||
const guildChannel = await client.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||
const guildChannel = client.channels.cache.get(process.env.BOT_CHANNEL_ID);
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("🔔 Nouvelle offre")
|
||||
.setDescription(`Une offre pour le skin **${skinName}** a été créée !`)
|
||||
@@ -105,7 +106,7 @@ export async function handleMarketOfferOpening(offerId, client) {
|
||||
const { name: skinName, icon: skinIcon } = await getOfferSkinInfo(offer);
|
||||
|
||||
try {
|
||||
const discordUserSeller = await client.users.fetch(offer.sellerId);
|
||||
const discordUserSeller = await resolveUser(client, offer.sellerId);
|
||||
const userSeller = await userService.getUser(offer.sellerId);
|
||||
if (discordUserSeller && userSeller?.isAkhy) {
|
||||
const embed = new EmbedBuilder()
|
||||
@@ -145,7 +146,7 @@ export async function handleMarketOfferOpening(offerId, client) {
|
||||
}
|
||||
|
||||
try {
|
||||
const guildChannel = await client.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||
const guildChannel = client.channels.cache.get(process.env.BOT_CHANNEL_ID);
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("🔔 Début des enchères")
|
||||
.setDescription(
|
||||
@@ -177,7 +178,7 @@ export async function handleMarketOfferClosing(offerId, client) {
|
||||
const { name: skinName, icon: skinIcon } = await getOfferSkinInfo(offer);
|
||||
const bids = await marketService.getOfferBids(offer.id);
|
||||
|
||||
const discordUserSeller = await client.users.fetch(offer.sellerId);
|
||||
const discordUserSeller = await resolveUser(client, offer.sellerId);
|
||||
try {
|
||||
const userSeller = await userService.getUser(offer.sellerId);
|
||||
if (discordUserSeller && userSeller?.isAkhy) {
|
||||
@@ -204,7 +205,7 @@ export async function handleMarketOfferClosing(offerId, client) {
|
||||
);
|
||||
} else {
|
||||
const highestBid = bids[0];
|
||||
const highestBidderUser = await client.users.fetch(highestBid.bidderId);
|
||||
const highestBidderUser = await resolveUser(client, highestBid.bidderId);
|
||||
embed.addFields(
|
||||
{
|
||||
name: "✅ Enchères terminées avec succès !",
|
||||
@@ -225,8 +226,8 @@ export async function handleMarketOfferClosing(offerId, client) {
|
||||
}
|
||||
|
||||
try {
|
||||
const guild = await client.guilds.fetch(process.env.BOT_GUILD_ID);
|
||||
const guildChannel = await guild.channels?.fetch(process.env.BOT_CHANNEL_ID);
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
const guildChannel = guild.channels.cache.get(process.env.BOT_CHANNEL_ID);
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("🔔 Fin des enchères")
|
||||
.setDescription(
|
||||
@@ -243,12 +244,12 @@ export async function handleMarketOfferClosing(offerId, client) {
|
||||
});
|
||||
} else {
|
||||
const highestBid = bids[0];
|
||||
const highestBidderUser = await client.users.fetch(highestBid.bidderId);
|
||||
const highestBidderUser = await resolveUser(client, highestBid.bidderId);
|
||||
embed.addFields({
|
||||
name: "✅ Enchères terminées avec succès !",
|
||||
value: `Le skin de <@${offer.sellerId}> ${discordUserSeller ? "(" + discordUserSeller.username + ")" : ""} a été vendu pour \`${highestBid.offerAmount} coins\` à <@${highestBid.bidderId}> ${highestBidderUser ? "(" + highestBidderUser.username + ")" : ""}.`,
|
||||
});
|
||||
const discordUserBidder = await client.users.fetch(highestBid.bidderId);
|
||||
const discordUserBidder = await resolveUser(client, highestBid.bidderId);
|
||||
const userBidder = await userService.getUser(highestBid.bidderId);
|
||||
if (discordUserBidder && userBidder?.isAkhy) {
|
||||
const bidderEmbed = new EmbedBuilder()
|
||||
@@ -280,9 +281,9 @@ export async function handleNewMarketOfferBid(offerId, bidId, client) {
|
||||
if (!bid) return;
|
||||
const { name: skinName, icon: skinIcon } = await getOfferSkinInfo(offer);
|
||||
|
||||
const bidderUser = client.users.fetch(bid.bidderId);
|
||||
const bidderUser = await resolveUser(client, bid.bidderId);
|
||||
try {
|
||||
const discordUserSeller = await client.users.fetch(offer.sellerId);
|
||||
const discordUserSeller = await resolveUser(client, offer.sellerId);
|
||||
const userSeller = await userService.getUser(offer.sellerId);
|
||||
|
||||
if (discordUserSeller && userSeller?.isAkhy) {
|
||||
@@ -323,7 +324,7 @@ export async function handleNewMarketOfferBid(offerId, bidId, client) {
|
||||
}
|
||||
|
||||
try {
|
||||
const discordUserNewBidder = await client.users.fetch(bid.bidderId);
|
||||
const discordUserNewBidder = await resolveUser(client, bid.bidderId);
|
||||
const userNewBidder = await userService.getUser(bid.bidderId);
|
||||
if (discordUserNewBidder && userNewBidder?.isAkhy) {
|
||||
const embed = new EmbedBuilder()
|
||||
@@ -350,7 +351,7 @@ export async function handleNewMarketOfferBid(offerId, bidId, client) {
|
||||
const offerBids = await marketService.getOfferBids(offer.id);
|
||||
if (offerBids.length < 2) return;
|
||||
|
||||
const discordUserPreviousBidder = await client.users.fetch(offerBids[1].bidderId);
|
||||
const discordUserPreviousBidder = await resolveUser(client, offerBids[1].bidderId);
|
||||
const userPreviousBidder = await userService.getUser(offerBids[1].bidderId);
|
||||
if (discordUserPreviousBidder && userPreviousBidder?.isAkhy) {
|
||||
const embed = new EmbedBuilder()
|
||||
@@ -382,10 +383,10 @@ export async function handleNewMarketOfferBid(offerId, bidId, client) {
|
||||
}
|
||||
|
||||
export async function handleCaseOpening(caseType, userId, skinUuid, client) {
|
||||
const discordUser = await client.users.fetch(userId);
|
||||
const discordUser = await resolveUser(client, userId);
|
||||
const skin = await skinService.getSkin(skinUuid);
|
||||
try {
|
||||
const guildChannel = await client.channels.fetch(process.env.BOT_CHANNEL_ID);
|
||||
const guildChannel = client.channels.cache.get(process.env.BOT_CHANNEL_ID);
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("🔔 Ouverture de caisse")
|
||||
.setDescription(
|
||||
|
||||
Reference in New Issue
Block a user