diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml index a52c18e..c0e01ca 100644 --- a/.idea/sqldialects.xml +++ b/.idea/sqldialects.xml @@ -1,7 +1,6 @@ - \ No newline at end of file diff --git a/flopobot.db-shm b/flopobot.db-shm deleted file mode 100644 index a776f1a..0000000 Binary files a/flopobot.db-shm and /dev/null differ diff --git a/flopobot.db-wal b/flopobot.db-wal deleted file mode 100644 index 4ced5aa..0000000 Binary files a/flopobot.db-wal and /dev/null differ diff --git a/game.js b/game.js deleted file mode 100644 index 0b70b06..0000000 --- a/game.js +++ /dev/null @@ -1,711 +0,0 @@ -import { capitalize } from './utils.js'; -import pkg from 'pokersolver'; -const { Hand } = pkg; - -import { - updateUserCoins, - getUser, - insertLog, - insertGame, - getUserElo, - insertElos, - updateElo, - getAllSkins, deleteSOTD, insertSOTD, clearSOTDStats, getAllSOTDStats -} from './init_database.js' -import {C4_COLS, C4_ROWS, skins} from "./index.js"; - -const messagesTimestamps = new Map(); - -const TimesChoices = [ - { - name: '1 minute', - value: 60, - }, - { - name: '5 minutes', - value: 300, - }, - { - name: '10 minutes', - value: 600, - }, - { - name: '15 minutes', - value: 900, - }, - { - name: '30 minutes', - value: 1800, - }, - { - name: '1 heure', - value: 3600, - }, - { - name: '2 heures', - value: 3600, - }, - { - name: '3 heures', - value: 10800, - }, - { - name: '6 heures', - value: 21600, - }, - { - name: '9 heures', - value: 32400, - }, - { - name: '12 heures', - value: 43200, - }, - { - name: '16 heures', - value: 57600, - }, - { - name: '1 jour', - value: 86400, - }, - /*{ - name: '2 journées', - value: 172800, - }, - { - name: '1 semaine', - value: 604800, - }, - { - name: '2 semaines', - value: 604800 * 2, - },*/ -]; - -export function getTimesChoices() { - return TimesChoices -} - -export function channelPointsHandler(msg) { - const author = msg.author - const authorDB = getUser.get(author.id) - - if (!authorDB) { - console.log("message from an unknown user") - return - } - - if (msg.content.length < 3 || msg.content.startsWith('.')) return - - const now = Date.now(); - const timestamps = messagesTimestamps.get(author.id) || []; - - // Remove all timestamps if first one is older than 15 minutes - const updatedTimestamps = now - timestamps[0] < 900000 ? timestamps : []; - - updatedTimestamps.push(now); - messagesTimestamps.set(author.id, updatedTimestamps); - - if (messagesTimestamps.get(author.id).length <= 10) { - // +10 or +50 coins - let coins = messagesTimestamps.get(author.id).length === 10 - ? 50 - : 10 - updateUserCoins.run({ - id: author.id, - coins: authorDB.coins + coins, - }) - insertLog.run({ - id: author.id + '-' + Date.now(), - user_id: author.id, - action: 'AUTOCOINS', - target_user_id: null, - coins_amount: coins, - user_new_amount: authorDB.coins + coins, - }) - } -} - -export async function slowmodesHandler(msg, activeSlowmodes) { - const author = msg.author - const authorDB = getUser.get(author.id) - const authorSlowmode = activeSlowmodes[author.id] - - if (!authorDB) return false - if (!authorSlowmode) return false - - console.log('Message from a slowmode user') - - const now = Date.now(); - if (now > authorSlowmode.endAt) { - console.log('Slow mode is over') - delete activeSlowmodes[author.id] - return true - } - - if (authorSlowmode.lastMessage && (authorSlowmode.lastMessage + 60 * 1000) > now) { - await msg.delete() - console.log('Message deleted') - } else { - authorSlowmode.lastMessage = Date.now() - } - return false -} - -export async function eloHandler(p1, p2, p1score, p2score, type) { - const p1DB = getUser.get(p1) - const p2DB = getUser.get(p2) - - if (!p1DB || !p2DB) return - - let p1elo = await getUserElo.get({ id: p1 }) - let p2elo = await getUserElo.get({ id: p2 }) - - if (!p1elo) { - await insertElos.run({ - id: p1?.toString(), - elo: 100, - }) - p1elo = await getUserElo.get({ id: p1 }) - } - if (!p2elo) { - await insertElos.run({ - id: p2?.toString(), - elo: 100, - }) - p2elo = await getUserElo.get({ id: p2 }) - } - - if (p1score === p2score) { - insertGame.run({ - id: p1?.toString() + '-' + p2?.toString() + '-' + Date.now()?.toString(), - p1: p1, - p2: p2, - p1_score: p1score, - p2_score: p2score, - p1_elo: p1elo.elo, - p2_elo: p2elo.elo, - p1_new_elo: p1elo.elo, - p2_new_elo: p2elo.elo, - type: type, - timestamp: Date.now(), - }) - return - } - - const prob1 = 1 / (1 + Math.pow(10, (p2elo.elo - p1elo.elo)/400)) - const prob2 = 1 / (1 + Math.pow(10, (p1elo.elo - p2elo.elo)/400)) - - const p1newElo = Math.max(Math.floor(p1elo.elo + 10 * (p1score - prob1)), 0) - const p2newElo = Math.max(Math.floor(p2elo.elo + 10 * (p2score - prob2)), 0) - - console.log(`${p1} elo update : ${p1elo.elo} -> ${p1newElo}`) - console.log(`${p2} elo update : ${p2elo.elo} -> ${p2newElo}`) - updateElo.run({ id: p1, elo: p1newElo }) - updateElo.run({ id: p2, elo: p2newElo }) - - insertGame.run({ - id: p1?.toString() + '-' + p2?.toString() + '-' + Date.now()?.toString(), - p1: p1, - p2: p2, - p1_score: p1score, - p2_score: p2score, - p1_elo: p1elo.elo, - p2_elo: p2elo.elo, - p1_new_elo: p1newElo, - p2_new_elo: p2newElo, - type: type, - timestamp: Date.now(), - }) -} - -export async function pokerEloHandler(room) { - if (room.fakeMoney) return - - let DBplayers = [] - Object.keys(room.players).forEach(playerId => { - const DBuser = getUser.get(playerId) - if (DBuser) { - DBplayers.push(DBuser) - } - }) - - const winnerIds = new Set(room.winners) - const playerCount = Object.keys(room.players).length - const baseK = 10 - - const avgOpponentElo = (player) => { - const opponents = DBplayers.filter(p => p.id !== player.id); - return opponents.reduce((sum, p) => sum + p.elo, 0) / opponents.length; - }; - - DBplayers.forEach(player => { - const avgElo = avgOpponentElo(player); - const expectedScore = 1 / (1 + 10 ** ((avgElo - player.elo) / 400)) - - let actualScore; - if (winnerIds.has(player.id)) { - actualScore = (winnerIds.size === playerCount) ? 0.5 : 1; - } else { - actualScore = 0; - } - - const K = winnerIds.has(player.id) ? (baseK * playerCount) : baseK - const delta = K * (actualScore - expectedScore) - - const newElo = Math.max(Math.floor(player.elo + delta), 0) - - - - if (!isNaN(newElo)) { - console.log(`${player.id} elo update: ${player.elo} -> ${newElo} (K: ${K.toFixed(2)}, Δ: ${delta.toFixed(2)})`); - updateElo.run({ id: player.id, elo: newElo }) - - insertGame.run({ - id: player.id + '-' + Date.now()?.toString(), - p1: player.id, - p2: null, - p1_score: actualScore, - p2_score: null, - p1_elo: player.elo, - p2_elo: avgElo, - p1_new_elo: newElo, - p2_new_elo: null, - type: 'POKER_ROUND', - timestamp: Date.now(), - }) - } else { - console.log(`# ELO UPDATE ERROR -> ${player.id} elo update: ${player.elo} -> ${newElo} (K: ${K.toFixed(2)}, Δ: ${delta.toFixed(2)})`); - } - }) -} - -export function randomSkinPrice(id=0) { - const dbSkins = getAllSkins.all(); - const randomIndex = Math.floor(Math.random() * dbSkins.length); - let randomSkin = skins.find((skin) => skin.uuid === dbSkins[randomIndex].uuid); - - // Generate random level and chroma - const randomLevel = Math.floor(Math.random() * randomSkin.levels.length + 1); - let randomChroma = randomLevel === randomSkin.levels.length - ? Math.floor(Math.random() * randomSkin.chromas.length + 1) - : 1; - if (randomChroma === randomSkin.chromas.length && randomSkin.chromas.length >= 2) randomChroma-- - const selectedLevel = randomSkin.levels[randomLevel - 1] - const selectedChroma = randomSkin.chromas[randomChroma - 1] - - - // Helper functions (unchanged from your original code) - const price = () => { - let result = dbSkins[randomIndex].basePrice; - - result *= (1 + (randomLevel / Math.max(randomSkin.levels.length, 2))) - result *= (1 + (randomChroma / 4)) - - return result.toFixed(2); - } - - const returnPrice = price() - console.log(`#${id} :`, returnPrice) - return returnPrice -} - -export function createConnect4Board() { - return Array(C4_ROWS).fill(null).map(() => Array(C4_COLS).fill(null)); -} - -export function checkConnect4Win(board, player) { - // Check horizontal - for (let r = 0; r < C4_ROWS; r++) { - for (let c = 0; c <= C4_COLS - 4; c++) { - if (board[r][c] === player && board[r][c+1] === player && board[r][c+2] === player && board[r][c+3] === player) { - return { win: true, pieces: [{row:r, col:c}, {row:r, col:c+1}, {row:r, col:c+2}, {row:r, col:c+3}] }; - } - } - } - - // Check vertical - for (let r = 0; r <= C4_ROWS - 4; r++) { - for (let c = 0; c < C4_COLS; c++) { - if (board[r][c] === player && board[r+1][c] === player && board[r+2][c] === player && board[r+3][c] === player) { - return { win: true, pieces: [{row:r, col:c}, {row:r+1, col:c}, {row:r+2, col:c}, {row:r+3, col:c}] }; - } - } - } - - // Check diagonal (down-right) - for (let r = 0; r <= C4_ROWS - 4; r++) { - for (let c = 0; c <= C4_COLS - 4; c++) { - if (board[r][c] === player && board[r+1][c+1] === player && board[r+2][c+2] === player && board[r+3][c+3] === player) { - return { win: true, pieces: [{row:r, col:c}, {row:r+1, col:c+1}, {row:r+2, col:c+2}, {row:r+3, col:c+3}] }; - } - } - } - - // Check diagonal (up-right) - for (let r = 3; r < C4_ROWS; r++) { - for (let c = 0; c <= C4_COLS - 4; c++) { - if (board[r][c] === player && board[r-1][c+1] === player && board[r-2][c+2] === player && board[r-3][c+3] === player) { - return { win: true, pieces: [{row:r, col:c}, {row:r-1, col:c+1}, {row:r-2, col:c+2}, {row:r-3, col:c+3}] }; - } - } - } - - return { win: false, pieces: [] }; -} - -export function checkConnect4Draw(board) { - return board[0].every(cell => cell !== null); -} - -export function formatConnect4BoardForDiscord(board) { - const symbols = { - 'R': '🔴', - 'Y': '🟡', - null: '⚪' - }; - return board.map(row => row.map(cell => symbols[cell]).join('')).join('\n'); -} - -/** - * Creates a seedable pseudorandom number generator (PRNG) using the Mulberry32 algorithm. - * @param {number} seed - An initial number to seed the generator. - * @returns {function} A function that, when called, returns a pseudorandom number between 0 and 1. - */ -export function createSeededRNG(seed) { - return function() { - let t = seed += 0x6D2B79F5; - t = Math.imul(t ^ t >>> 15, t | 1); - t ^= t + Math.imul(t ^ t >>> 7, t | 61); - return ((t ^ t >>> 14) >>> 0) / 4294967296; - } -} - -/** - * Shuffles an array in place using the Fisher-Yates algorithm. - * @param {Array} array - The array to shuffle. - * @returns {Array} The shuffled array. - */ -export function shuffle(array) { - let currentIndex = array.length, - randomIndex; - while (currentIndex !== 0) { - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex--; - [array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]]; - } - return array; -} - -/** - * Shuffles an array in place using a seedable PRNG via the Fisher-Yates algorithm. - * @param {Array} array - The array to shuffle. - * @param {function} rng - A seedable random number generator function. - * @returns {Array} The shuffled array. - */ -export function seededShuffle(array, rng) { - let currentIndex = array.length, - randomIndex; - - // While there remain elements to shuffle. - while (currentIndex !== 0) { - // Pick a remaining element using the seeded RNG. - randomIndex = Math.floor(rng() * currentIndex); - currentIndex--; - - // And swap it with the current element. - [array[currentIndex], array[randomIndex]] = [ - array[randomIndex], - array[currentIndex], - ]; - } - return array; -} - -/** - * Deals a shuffled deck into the initial Solitaire game state. - * @param {Array} deck - A shuffled deck of cards. - * @returns {Object} The initial gameState object. - */ -export function deal(deck) { - const gameState = { - tableauPiles: [[], [], [], [], [], [], []], - foundationPiles: [[], [], [], []], - stockPile: [], - wastePile: [], - isDone: false, - }; - - // Deal cards to the tableau piles - for (let i = 0; i < 7; i++) { - for (let j = i; j < 7; j++) { - gameState.tableauPiles[j].push(deck.shift()); - } - } - - // Flip the top card of each tableau pile - gameState.tableauPiles.forEach(pile => { - if (pile.length > 0) { - pile[pile.length - 1].faceUp = true; - } - }); - - // The rest of the deck becomes the stock - gameState.stockPile = deck; - - return gameState; -} - -/** - * Checks if a proposed move is valid according to the rules of Klondike Solitaire. - * @param {Object} gameState - The current state of the game. - * @param {Object} moveData - The details of the move. - * @returns {boolean} - */ -export function isValidMove(gameState, moveData) { - // Use more descriptive names to avoid confusion - const { sourcePileType, sourcePileIndex, sourceCardIndex, destPileType, destPileIndex } = moveData; - - let sourcePile; - // Get the actual source pile array based on its type and index - if (sourcePileType === 'tableauPiles') { - sourcePile = gameState.tableauPiles[sourcePileIndex]; - } else if (sourcePileType === 'wastePile') { - sourcePile = gameState.wastePile; - } else if (sourcePileType === 'foundationPiles') { - sourcePile = gameState.foundationPiles[sourcePileIndex]; - } else { - return false; // Cannot drag from foundation or stock - } - - // Get the actual card being dragged (the top of the stack) - const sourceCard = sourcePile[sourceCardIndex]; - - // A card must exist and be face-up to be moved - if (!sourceCard || !sourceCard.faceUp) { - return false; - } - - // --- Validate move TO a Tableau Pile --- - if (destPileType === 'tableauPiles') { - const destinationPile = gameState.tableauPiles[destPileIndex]; - const topCard = destinationPile.length > 0 ? destinationPile[destinationPile.length - 1] : null; - - if (!topCard) { - // If the destination tableau pile is empty, only a King can be moved there. - return sourceCard.rank === 'K'; - } - - // If the destination pile is not empty, check game rules - const sourceColor = getCardColor(sourceCard.suit); - const destColor = getCardColor(topCard.suit); - const sourceValue = getRankValue(sourceCard.rank); - const destValue = getRankValue(topCard.rank); - - // Card being moved must be opposite color and one rank lower than the destination top card. - return sourceColor !== destColor && destValue - sourceValue === 1; - } - - // --- Validate move TO a Foundation Pile --- - if (destPileType === 'foundationPiles' && sourcePileType !== 'foundationPiles') { - // You can only move one card at a time to a foundation pile. - const stackBeingMoved = sourcePile.slice(sourceCardIndex); - if (stackBeingMoved.length > 1) { - return false; - } - - const destinationPile = gameState.foundationPiles[destPileIndex]; - const topCard = destinationPile.length > 0 ? destinationPile[destinationPile.length - 1] : null; - - if (!topCard) { - // If the foundation is empty, only an Ace can be moved there. - return sourceCard.rank === 'A'; - } - - // If not empty, card must be same suit and one rank higher. - const sourceValue = getRankValue(sourceCard.rank); - const destValue = getRankValue(topCard.rank); - - return sourceCard.suit === topCard.suit && sourceValue - destValue === 1; - } - - return false; -} - -/** - * An array of suits and ranks to create a deck. - */ -const SUITS = ['h', 'd', 's', 'c']; // Hearts, Diamonds, Spades, Clubs -const RANKS = ['A', '2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K']; - -/** - * Gets the numerical value of a card's rank. - * @param {string} rank - e.g., 'A', 'K', '7' - * @returns {number} - */ -function getRankValue(rank) { - if (rank === 'A') return 1; - if (rank === 'T') return 10; - if (rank === 'J') return 11; - if (rank === 'Q') return 12; - if (rank === 'K') return 13; - return parseInt(rank, 10); -} - -/** - * Gets the color of a card's suit. - * @param {string} suit - e.g., 'h', 's' - * @returns {string} 'red' or 'black' - */ -function getCardColor(suit) { - return suit === 'h' || suit === 'd' ? 'red' : 'black'; -} - -/** - * Creates a standard 52-card deck. - * @returns {Array} - */ -export function createDeck() { - const deck = []; - for (const suit of SUITS) { - for (const rank of RANKS) { - deck.push({ suit, rank, faceUp: false }); - } - } - return deck; -} - -/** - * Mutates the game state by performing a valid move, correctly handling stacks. - * @param {Object} gameState - The current state of the game. - * @param {Object} moveData - The details of the move. - */ -export async function moveCard(gameState, moveData) { - const { sourcePileType, sourcePileIndex, sourceCardIndex, destPileType, destPileIndex } = moveData; - - // Identify the source pile array - let sourcePile; - if (sourcePileType === 'tableauPiles') { - sourcePile = gameState.tableauPiles[sourcePileIndex]; - } else if (sourcePileType === 'wastePile') { - sourcePile = gameState.wastePile; - } else if (sourcePileType === 'foundationPiles') { - sourcePile = gameState.foundationPiles[sourcePileIndex]; - } - - // Identify the destination pile array - let destPile; - if (destPileType === 'tableauPiles') { - destPile = gameState.tableauPiles[destPileIndex]; - } else { - destPile = gameState.foundationPiles[destPileIndex]; - } - - // Using splice(), cut the entire stack of cards to be moved from the source pile. - const cardsToMove = sourcePile.splice(sourceCardIndex); - - // Add the stack of cards to the destination pile. - // Using the spread operator (...) to add all items from the cardsToMove array. - destPile.push(...cardsToMove); - - if (sourcePileType === 'foundationPiles') { - sotdMoveUpdate(gameState, -15) - } else if (destPileType === 'foundationPiles') { - sotdMoveUpdate(gameState, 10) - } else { - sotdMoveUpdate(gameState, 0) - } - - // After moving, if the source was a tableau pile and it's not empty, - // flip the new top card to be face-up. - if (sourcePileType === 'tableauPiles' && sourcePile.length > 0) { - sourcePile[sourcePile.length - 1].faceUp = true; - } -} - -/** - * Moves a card from the stock to the waste pile. If stock is empty, resets it from the waste. - * @param {Object} gameState - The current state of the game. - */ -export async function drawCard(gameState) { - if (gameState.stockPile.length > 0) { - const card = gameState.stockPile.pop(); - card.faceUp = true; - gameState.wastePile.push(card); - } else if (gameState.wastePile.length > 0) { - // When stock is empty, move waste pile back to stock, face down - gameState.stockPile = gameState.wastePile.reverse(); - gameState.stockPile.forEach(card => (card.faceUp = false)); - gameState.wastePile = []; - } - sotdMoveUpdate(gameState, 0) -} - -/** - * Checks if the game has been won (all cards are in the foundation piles). - * @param {Object} gameState - The current state of the game. - * @returns {boolean} - */ -export function checkWinCondition(gameState) { - const foundationCardCount = gameState.foundationPiles.reduce( - (acc, pile) => acc + pile.length, - 0 - ); - return foundationCardCount === 52; -} - -export function initTodaysSOTD() { - const rankings = getAllSOTDStats.all() - const firstPlaceId = rankings.length > 0 ? rankings[0].user_id : null - - console.log(rankings) - console.log(firstPlaceId) - if (firstPlaceId) { - const firstPlaceUser = getUser.get(firstPlaceId) - console.log(firstPlaceUser) - if (firstPlaceUser) { - updateUserCoins.run({ id: firstPlaceId, coins: firstPlaceUser.coins + 1000 }); - insertLog.run({ - id: firstPlaceId + '-' + Date.now(), - user_id: firstPlaceId, - action: 'SOTD_FIRST_PLACE', - target_user_id: null, - coins_amount: 1000, - user_new_amount: firstPlaceUser.coins + 1000, - }) - } - console.log(`${firstPlaceId} wins ${new Date().toLocaleDateString()} SOTD`) - } - - const newRandomSeed = Date.now().toString(36) + Math.random().toString(36).substr(2); - let numericSeed = 0; - for (let i = 0; i < newRandomSeed.length; i++) { - numericSeed = (numericSeed + newRandomSeed.charCodeAt(i)) & 0xFFFFFFFF; - } - - const rng = createSeededRNG(numericSeed); - const deck = createDeck(); - const shuffledDeck = seededShuffle(deck, rng); - const todaysSOTD = deal(shuffledDeck); - todaysSOTD.seed = newRandomSeed; - - clearSOTDStats.run() - deleteSOTD.run() - insertSOTD.run({ - id: 0, - tableauPiles: JSON.stringify(todaysSOTD.tableauPiles), - foundationPiles: JSON.stringify(todaysSOTD.foundationPiles), - stockPile: JSON.stringify(todaysSOTD.stockPile), - wastePile: JSON.stringify(todaysSOTD.wastePile), - seed: todaysSOTD.seed, - }) - console.log('Today\'s SOTD is ready') -} - -export function sotdMoveUpdate(gameState, points) { - if (gameState.isSOTD) { - gameState.moves++ - gameState.score += points - } -} \ No newline at end of file diff --git a/init_database.js b/init_database.js deleted file mode 100644 index e104d36..0000000 --- a/init_database.js +++ /dev/null @@ -1,157 +0,0 @@ -import Database from "better-sqlite3"; - - -export const flopoDB = new Database('flopobot.db'); - -export const stmtUsers = flopoDB.prepare(` - CREATE TABLE IF NOT EXISTS users ( - id TEXT PRIMARY KEY, - username TEXT NOT NULL, - globalName TEXT, - warned BOOLEAN DEFAULT 0, - warns INTEGER DEFAULT 0, - allTimeWarns INTEGER DEFAULT 0, - totalRequests INTEGER DEFAULT 0, - coins INTEGER DEFAULT 0, - dailyQueried BOOLEAN DEFAULT 0 - ) -`); -stmtUsers.run(); -export const stmtSkins = flopoDB.prepare(` - CREATE TABLE IF NOT EXISTS skins ( - uuid TEXT PRIMARY KEY, - displayName TEXT, - contentTierUuid TEXT, - displayIcon TEXT, - user_id TEXT REFERENCES users, - tierRank TEXT, - tierColor TEXT, - tierText TEXT, - basePrice TEXT, - currentLvl INTEGER DEFAULT NULL, - currentChroma INTEGER DEFAULT NULL, - currentPrice INTEGER DEFAULT NULL, - maxPrice INTEGER DEFAULT NULL - ) -`); -stmtSkins.run() - -export const insertUser = flopoDB.prepare('INSERT INTO users (id, username, globalName, warned, warns, allTimeWarns, totalRequests) VALUES (@id, @username, @globalName, @warned, @warns, @allTimeWarns, @totalRequests)'); -export const updateUser = flopoDB.prepare('UPDATE users SET warned = @warned, warns = @warns, allTimeWarns = @allTimeWarns, totalRequests = @totalRequests WHERE id = @id'); -export const queryDailyReward = flopoDB.prepare(`UPDATE users SET dailyQueried = 1 WHERE id = ?`); -export const resetDailyReward = flopoDB.prepare(`UPDATE users SET dailyQueried = 0 WHERE id = ?`); -export const updateUserCoins = flopoDB.prepare('UPDATE users SET coins = @coins WHERE id = @id'); -export const getUser = flopoDB.prepare('SELECT users.*,elos.elo FROM users LEFT JOIN elos ON elos.id = users.id WHERE users.id = ?'); -export const getAllUsers = flopoDB.prepare('SELECT users.*,elos.elo FROM users LEFT JOIN elos ON elos.id = users.id ORDER BY coins DESC'); - -export const insertSkin = flopoDB.prepare('INSERT INTO skins (uuid, displayName, contentTierUuid, displayIcon, user_id, tierRank, tierColor, tierText, basePrice, currentLvl, currentChroma, currentPrice, maxPrice) VALUES (@uuid, @displayName, @contentTierUuid, @displayIcon, @user_id, @tierRank, @tierColor, @tierText, @basePrice, @currentLvl, @currentChroma, @currentPrice, @maxPrice)'); -export const updateSkin = flopoDB.prepare('UPDATE skins SET user_id = @user_id, currentLvl = @currentLvl, currentChroma = @currentChroma, currentPrice = @currentPrice WHERE uuid = @uuid'); -export const getSkin = flopoDB.prepare('SELECT * FROM skins WHERE uuid = ?'); -export const getAllSkins = flopoDB.prepare('SELECT * FROM skins ORDER BY maxPrice DESC'); -export const getAllAvailableSkins = flopoDB.prepare('SELECT * FROM skins WHERE user_id IS NULL'); -export const getUserInventory = flopoDB.prepare('SELECT * FROM skins WHERE user_id = @user_id ORDER BY currentPrice DESC'); -export const getTopSkins = flopoDB.prepare('SELECT * FROM skins ORDER BY maxPrice DESC LIMIT 10'); - -export const insertManyUsers = flopoDB.transaction(async (users) => { - for (const user of users) try { await insertUser.run(user) } catch (e) { /**/ } -}); -export const updateManyUsers = flopoDB.transaction(async (users) => { - for (const user of users) try { await updateUser.run(user) } catch (e) { console.log('user update failed') } -}); - -export const insertManySkins = flopoDB.transaction(async (skins) => { - for (const skin of skins) try { await insertSkin.run(skin) } catch (e) { console.log('skin insert failed') } -}); -export const updateManySkins = flopoDB.transaction(async (skins) => { - for (const skin of skins) try { await updateSkin.run(skin) } catch (e) { console.log('skin insert failed') } -}); - - -export const stmtLogs = flopoDB.prepare(` - CREATE TABLE IF NOT EXISTS logs ( - id PRIMARY KEY, - user_id TEXT REFERENCES users, - action TEXT, - target_user_id TEXT REFERENCES users, - coins_amount INTEGER, - user_new_amount INTEGER - ) -`); -stmtLogs.run() - -export const insertLog = flopoDB.prepare('INSERT INTO logs (id, user_id, action, target_user_id, coins_amount, user_new_amount) VALUES (@id, @user_id, @action, @target_user_id, @coins_amount, @user_new_amount)'); -export const getLogs = flopoDB.prepare('SELECT * FROM logs'); -export const getUserLogs = flopoDB.prepare('SELECT * FROM logs WHERE user_id = @user_id'); - - -export const stmtGames = flopoDB.prepare(` - CREATE TABLE IF NOT EXISTS games ( - id PRIMARY KEY, - p1 TEXT REFERENCES users, - p2 TEXT REFERENCES users, - p1_score INTEGER, - p2_score INTEGER, - p1_elo INTEGER, - p2_elo INTEGER, - p1_new_elo INTEGER, - p2_new_elo INTEGER, - type TEXT, - timestamp TIMESTAMP - ) -`); -stmtGames.run() - -export const insertGame = flopoDB.prepare('INSERT INTO games (id, p1, p2, p1_score, p2_score, p1_elo, p2_elo, p1_new_elo, p2_new_elo, type, timestamp) VALUES (@id, @p1, @p2, @p1_score, @p2_score, @p1_elo, @p2_elo, @p1_new_elo, @p2_new_elo, @type, @timestamp)'); -export const getGames = flopoDB.prepare('SELECT * FROM games'); -export const getUserGames = flopoDB.prepare('SELECT * FROM games WHERE p1 = @user_id OR p2 = @user_id'); - - -export const stmtElos = flopoDB.prepare(` - CREATE TABLE IF NOT EXISTS elos ( - id PRIMARY KEY REFERENCES users, - elo INTEGER - ) -`); -stmtElos.run() - -export const insertElos = flopoDB.prepare(`INSERT INTO elos (id, elo) VALUES (@id, @elo)`); -export const getElos = flopoDB.prepare(`SELECT * FROM elos`); -export const getUserElo = flopoDB.prepare(`SELECT * FROM elos WHERE id = @id`); -export const updateElo = flopoDB.prepare('UPDATE elos SET elo = @elo WHERE id = @id'); - - -export const getUsersByElo = flopoDB.prepare('SELECT * FROM users JOIN elos ON elos.id = users.id ORDER BY elos.elo DESC') - -export const stmtSOTD = flopoDB.prepare(` - CREATE TABLE IF NOT EXISTS sotd ( - id INT PRIMARY KEY, - tableauPiles TEXT, - foundationPiles TEXT, - stockPile TEXT, - wastePile TEXT, - isDone BOOLEAN DEFAULT false, - seed TEXT - ) -`); -stmtSOTD.run() - -export const getSOTD = flopoDB.prepare(`SELECT * FROM sotd WHERE id = '0'`) -export const insertSOTD = flopoDB.prepare(`INSERT INTO sotd (id, tableauPiles, foundationPiles, stockPile, wastePile, seed) VALUES (@id, @tableauPiles, @foundationPiles, @stockPile, @wastePile, @seed)`) -export const deleteSOTD = flopoDB.prepare(`DELETE FROM sotd WHERE id = '0'`) - -export const stmtSOTDStats = flopoDB.prepare(` - CREATE TABLE IF NOT EXISTS sotd_stats ( - id TEXT PRIMARY KEY, - user_id TEXT REFERENCES users, - time INTEGER, - moves INTEGER, - score INTEGER - ) -`); -stmtSOTDStats.run() - -export const getAllSOTDStats = flopoDB.prepare(`SELECT sotd_stats.*, users.globalName FROM sotd_stats JOIN users ON users.id = sotd_stats.user_id ORDER BY score DESC, moves ASC, time ASC`); -export const getUserSOTDStats = flopoDB.prepare(`SELECT * FROM sotd_stats WHERE user_id = ?`); -export const insertSOTDStats = flopoDB.prepare(`INSERT INTO sotd_stats (id, user_id, time, moves, score) VALUES (@id, @user_id, @time, @moves, @score)`); -export const clearSOTDStats = flopoDB.prepare(`DELETE FROM sotd_stats`); -export const deleteUserSOTDStats = flopoDB.prepare(`DELETE FROM sotd_stats WHERE user_id = ?`); \ No newline at end of file diff --git a/old_index.js b/old_index.js deleted file mode 100644 index 85ec644..0000000 --- a/old_index.js +++ /dev/null @@ -1,5167 +0,0 @@ -/* -import 'dotenv/config'; -import express from 'express'; -import { - ButtonStyleTypes, - InteractionResponseFlags, - InteractionResponseType, - InteractionType, - MessageComponentTypes, - verifyKeyMiddleware, -} from 'discord-interactions'; -import { - getRandomEmoji, - DiscordRequest, - //getOnlineUsersWithRole, - formatTime, - gork, - getRandomHydrateText, - getAPOUsers, - postAPOBuy, - initialShuffledCards, - getFirstActivePlayerAfterDealer, - getNextActivePlayer, checkEndOfBettingRound, initialCards, checkRoomWinners, pruneOldLogs -} from './utils.js'; -import { - channelPointsHandler, checkConnect4Draw, checkConnect4Win, createConnect4Board, - eloHandler, formatConnect4BoardForDiscord, - pokerEloHandler, - randomSkinPrice, - slowmodesHandler, - deal, isValidMove, moveCard, seededShuffle, drawCard, checkWinCondition, createDeck, initTodaysSOTD, createSeededRNG, -} from './game.js'; -import { Client, GatewayIntentBits, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; -import cron from 'node-cron'; -import Database from "better-sqlite3"; -import { - flopoDB, - insertUser, - insertManyUsers, - updateUser, - updateManyUsers, - getUser, - getAllUsers, - stmtUsers, - stmtSkins, - updateManySkins, - insertSkin, - updateSkin, - insertManySkins, - getAllSkins, - getSkin, - getAllAvailableSkins, - getUserInventory, - getTopSkins, - updateUserCoins, - insertLog, - stmtLogs, - getLogs, - getUserLogs, - getUserElo, - getUserGames, - getUsersByElo, - resetDailyReward, - queryDailyReward, - deleteSOTD, - insertSOTD, getSOTD, insertSOTDStats, deleteUserSOTDStats, getUserSOTDStats, getAllSOTDStats, -} from './init_database.js'; -import { getValorantSkins, getSkinTiers } from './valo.js'; -import {sleep} from "openai/core"; -import { v4 as uuidv4 } from 'uuid'; -import { uniqueNamesGenerator, adjectives, languages, animals } from 'unique-names-generator'; -import pkg from 'pokersolver'; -const { Hand } = pkg; -import axios from 'axios'; - -// Create an express app -const app = express(); -// Get port, or default to 25578 -const PORT = process.env.PORT || 25578; -const FLAPI_URL = process.env.DEV_SITE === 'true' ? process.env.FLAPI_URL_DEV : process.env.FLAPI_URL - -app.use((req, res, next) => { - res.header('Access-Control-Allow-Origin', FLAPI_URL); - res.header('Access-Control-Allow-Headers', 'Content-type, X-API-Key, ngrok-skip-browser-warning'); - next(); -}); -// To keep track of our active games -const activeGames = {}; -const activeSolitaireGames = {}; -const activePolls = {}; -const activeInventories = {}; -const activeSearchs = {}; -const activeSlowmodes = {}; -const activePredis = {}; -let todaysSOTD = {}; -const SPAM_INTERVAL = process.env.SPAM_INTERVAL - -const client = new Client({ - intents: [ - GatewayIntentBits.Guilds, // For guild events - GatewayIntentBits.GuildMessages, // For messages in guilds - GatewayIntentBits.MessageContent, // For reading message content (privileged intent) - GatewayIntentBits.GuildMembers, - GatewayIntentBits.GuildPresences, - ] -}); - -const requestTimestamps = new Map(); // userId => [timestamp1, timestamp2, ...] -const MAX_REQUESTS_PER_INTERVAL = parseInt(process.env.MAX_REQUESTS || "5"); - -const akhysData= new Map() -export const skins = [] - -async function getAkhys() { - try { - stmtUsers.run(); - const guild = await client.guilds.fetch(process.env.GUILD_ID); - const members = await guild.members.fetch(); // Fetch all members - - const akhys = members.filter(m => !m.user.bot && m.roles.cache.has(process.env.AKHY_ROLE_ID)); - - akhys.forEach(akhy => { - akhysData.set(akhy.user.id, { - id: akhy.user.id, - username: akhy.user.username, - globalName: akhy.user.globalName, - warned: false, - warns: 0, - allTimeWarns: 0, - totalRequests: 0, - }); - insertManyUsers([ - { - id: akhy.user.id, - username: akhy.user.username, - globalName: akhy.user.globalName, - warned: 0, - warns: 0, - allTimeWarns: 0, - totalRequests: 0 - }, - ]); - }); - } catch (err) { - console.error('Error while counting akhys:', err); - } - try { - stmtSkins.run(); - - const fetchedSkins = await getValorantSkins() - const fetchedTiers = await getSkinTiers() - - fetchedSkins.forEach((skin) => { - const chromas = [] - const levels = [] - skin.chromas.forEach((chroma) => { - chromas.push({ - uuid: chroma.uuid, - displayName: chroma.displayName, - displayIcon: chroma.displayIcon, - fullRender: chroma.fullRender, - swatch: chroma.swatch, - streamedVideo: chroma.streamedVideo, - }) - }) - skin.levels.forEach((level) => { - levels.push({ - uuid: level.uuid, - displayName: level.displayName, - displayIcon: level.displayIcon, - streamedVideo: level.streamedVideo, - }) - }) - skins.push({ - uuid: skin.uuid, - displayName: skin.displayName, - contentTierUuid: skin.contentTierUuid, - displayIcon: skin.displayIcon, - chromas: chromas, - levels: levels, - }) - }) - - let newSkinCount = 0; - let newSkinText = ''; - for (const skin of skins) { - try { - if (skin.contentTierUuid !== null) { - const tierRank = () => { - const tier = fetchedTiers.filter((tier) => { return tier.uuid === skin.contentTierUuid})[0] - const rank = tier ? tier['rank'] : null; - return rank ? rank + 1 : 0; - } - const tierColor = () => { - const tier = fetchedTiers.filter((tier) => { return tier.uuid === skin.contentTierUuid})[0] - return tier ? tier['highlightColor']?.slice(0, 6) : 'F2F3F3' - } - const tierText = () => { - const tier = fetchedTiers.filter((tier) => { return tier.uuid === skin.contentTierUuid})[0] - const rank = tier ? tier['rank'] : null; - let res; - if (rank === null) return 'Pas de tier'; - switch(rank) { - case 0: - res = '**<:select:1362964319498670222> Select**' - break - case 1: - res = '**<:deluxe:1362964308094488797> Deluxe**' - break - case 2: - res = '**<:premium:1362964330349330703> Premium**' - break - case 3: - res = '**<:exclusive:1362964427556651098> Exclusive**' - break - case 4: - res = '**<:ultra:1362964339685986314> Ultra**' - break - default: - return 'Pas de tier' - } - res += skin.displayName.includes('VCT') ? ' | Esports Edition' : '' - res += skin.displayName.toLowerCase().includes('champions') ? ' | Champions' : '' - res += skin.displayName.toLowerCase().includes('arcane') ? ' | Arcane' : '' - return res - } - const basePrice = () => { - let res; - if (skin.displayName.toLowerCase().includes('classic')){ - res = 150; - } else if (skin.displayName.toLowerCase().includes('shorty')) { - res = 300; - } else if (skin.displayName.toLowerCase().includes('frenzy')) { - res = 450; - } else if (skin.displayName.toLowerCase().includes('ghost')) { - res = 500; - } else if (skin.displayName.toLowerCase().includes('sheriff')) { - res = 800; - } else if (skin.displayName.toLowerCase().includes('stinger')) { - res = 1100; - } else if (skin.displayName.toLowerCase().includes('spectre')) { - res = 1600; - } else if (skin.displayName.toLowerCase().includes('bucky')) { - res = 850; - } else if (skin.displayName.toLowerCase().includes('judge')) { - res = 1850; - } else if (skin.displayName.toLowerCase().includes('bulldog')) { - res = 2050; - } else if (skin.displayName.toLowerCase().includes('guardian')) { - res = 2250; - } else if (skin.displayName.toLowerCase().includes('phantom')) { - res = 2900; - } else if (skin.displayName.toLowerCase().includes('vandal')) { - res = 2900; - } else if (skin.displayName.toLowerCase().includes('marshal')) { - res = 950; - } else if (skin.displayName.toLowerCase().includes('outlaw')) { - res = 2400; - } else if (skin.displayName.toLowerCase().includes('operator')) { - res = 4700; - } else if (skin.displayName.toLowerCase().includes('ares')) { - res = 1600; - } else if (skin.displayName.toLowerCase().includes('odin')) { - res = 3200; - } else { - res = 6000; - } - - res *= (1 + (tierRank())) - res *= skin.displayName.includes('VCT') ? 1.25 : 1; - res *= skin.displayName.toLowerCase().includes('champions') ? 2 : 1; - res *= skin.displayName.toLowerCase().includes('arcane') ? 1.5 : 1; - res *= 1+(Math.random()/100) // [1 to 1.01] - - return (res/1111).toFixed(2); - } - - const skinBasePrice = basePrice(); - - const maxPrice = (price) => { - let res = price - - res *= (1 + (skin.levels.length / Math.max(skin.levels.length, 2))) - res *= (1 + (skin.chromas.length / 4)) - - return res.toFixed(2); - } - - await insertSkin.run( - { - uuid: skin.uuid, - displayName: skin.displayName, - contentTierUuid: skin.contentTierUuid, - displayIcon: skin.displayIcon, - user_id: null, - tierRank: tierRank(), - tierColor: tierColor(), - tierText: tierText(), - basePrice: skinBasePrice, - currentLvl: null, - currentChroma: null, - currentPrice: null, - maxPrice: maxPrice(skinBasePrice), - }); - newSkinCount++; - newSkinText += skin.displayName + ' | '; - } - } catch (e) { - // - } - } - console.log(`New skins : ${newSkinCount}`); - if (newSkinCount <= 30 && newSkinCount > 0) console.log(newSkinText); - } catch (e) { - console.error('Error while fetching skins:', e); - } - try { - stmtLogs.run() - } catch (e) { - console.log('Logs table init error') - } -} - -async function getOnlineUsersWithRole(guild_id=process.env.GUILD_ID, role_id=process.env.VOTING_ROLE_ID) { - try { - const guild = await client.guilds.fetch(guild_id); - const members = await guild.members.fetch(); // Fetch all members - - const online = members.filter(m => !m.user.bot && m.presence?.status && m.roles.cache.has(role_id)); - return online - } catch (err) { - console.error('Error while counting online members:', err); - } -} - -// Login to Discord using bot token (optional) -client.login(process.env.BOT_TOKEN).then(r => console.log('')); - -// Listen for message events -client.on('messageCreate', async (message) => { - // Ignore messages from bots to avoid feedback loops - if (message.author.bot) return; - - // hihihiha - if (message.author.id === process.env.PATA_ID) { - if (message.content.startsWith('feur') - || message.content.startsWith('rati')) { - await sleep(1000) - await message.delete() - } - } - - // coins mechanic and slowmodes check - if (message.guildId === process.env.GUILD_ID) { - channelPointsHandler(message) - io.emit('data-updated', { table: 'users', action: 'update' }); - const deletedSlowmode = await slowmodesHandler(message, activeSlowmodes) - if (deletedSlowmode) io.emit('new-slowmode', { action: 'deleted slowmode' }); - } - - if (message.content.toLowerCase().startsWith(`<@${process.env.APP_ID}>`) || message.mentions.repliedUser?.id === process.env.APP_ID) { - let startTime = Date.now() - let akhyAuthor = await getUser.get(message.author.id) - - const now = Date.now(); - const timestamps = requestTimestamps.get(message.author.id) || []; - - // Remove timestamps older than SPAM_INTERVAL seconds - const updatedTimestamps = timestamps.filter(ts => now - ts < SPAM_INTERVAL); - - if (updatedTimestamps.length >= MAX_REQUESTS_PER_INTERVAL) { - console.log(akhyAuthor.warned ? `${message.author.username} is restricted : ${updatedTimestamps}` : `Rate limit exceeded for ${message.author.username}`); - if (!akhyAuthor.warned) { - await message.reply(`T'abuses fréro, attends un peu ⏳`) - } else if (akhyAuthor.warns === Math.max(1, process.env.MAX_WARNS - 3)) { - await message.author.send("Attention si tu continues de spam tu vas te faire timeout 🤯") - } - await updateManyUsers([ - { - id: akhyAuthor.id, - username: akhyAuthor.username, - globalName: akhyAuthor.globalName, - warned: 1, // true - warns: akhyAuthor.warns + 1, - allTimeWarns: akhyAuthor.allTimeWarns + 1, - totalRequests: akhyAuthor.totalRequests - }, - ]) - akhyAuthor = await getUser.get(akhyAuthor.id) - if (akhyAuthor.warns > process.env.MAX_WARNS ?? 10) { - const guild = await client.guilds.fetch(process.env.GUILD_ID); - const time = parseInt(process.env.SPAM_TIMEOUT_TIME) - try { - await guild.members.edit(akhyAuthor.id, { - communication_disabled_until: new Date(Date.now() + time).toISOString(), - reason: 'Dose le spam fdp', - }); - } catch (e) { - console.log('Tried timeout for AI spam : ', e) - message.channel.send(`<@${akhyAuthor.id}> tu me fais chier !! T'as de la chance que je puisse pas te timeout 🔪`) - .catch(console.error); - return - } - message.channel.send(`Ce bouffon de <@${akhyAuthor.id}> a été timeout pendant ${formatTime(time/1000)}, il me cassait les couilles 🤫`) - .catch(console.error); - return - } - return; - } - - - // Track this new usage - updatedTimestamps.push(now); - requestTimestamps.set(akhyAuthor.id, updatedTimestamps); - await updateManyUsers([ - { - id: akhyAuthor.id, - username: akhyAuthor.username, - globalName: akhyAuthor.globalName, - warned: 0, // false - warns: 0, // reset - allTimeWarns: akhyAuthor.allTimeWarns, - totalRequests: akhyAuthor.totalRequests + 1 - }, - ]) - akhyAuthor = await getUser.get(akhyAuthor.id) - - try { - // Fetch last messages from the channel - const fetched = await message.channel.messages.fetch({ limit: 100 }); - const messagesArray = Array.from(fetched.values()).reverse(); // oldest to newest - - const requestMessage = message.content.replace(`<@${process.env.APP_ID}>`, '') - - // Map to OpenAI/Gemini format - console.log('AI fetch', process.env.MODEL) - const allAkhys = await getAllUsers.all() - let allAkhysText = '' - allAkhys.forEach(akhy => { - allAkhysText += `<@${akhy.id}> alias ${akhy.globalName}, ` - }) - let convo = 'Voici les derniers messages de la conversation pour contexte (du plus vieux au plus récent) :\n' - messagesArray.forEach(msg => { - convo += `<@${msg.author.id}> a dit : ${msg.content}.\n` - }) - let formatted = []; - if (process.env.MODEL === 'OpenAI' || process.env.MODEL === 'Gemini') { - formatted.push({ - role: 'developer', - content: `${convo}`, - }); - formatted.push({ - role: 'developer', - content: `Voici la liste des différents utilisateurs présents : ${allAkhysText}`, - }) - formatted.push({ - role: 'developer', - content: `Voici une liste de quelques emojis que tu peux utiliser sur le serveur: <:CAUGHT:1323810730155446322> quand tu te fais prendre la main dans le sac ou que tu a un avis divergent ou risqué, <:hinhinhin:1072510144933531758> pour le rire ou quand tu es moqueur, <:o7:1290773422451986533> pour payer respect ou remercier ou dire au revoir, <:zhok:1115221772623683686> pour quand quelquechose manque de sens, <:nice:1154049521110765759> pour quelquechose de bien, <:nerd:1087658195603951666> pour une explication technique ou une attitude nerd, <:peepSelfie:1072508131839594597> pour à peu près n\'importe quelle situation quand tu es blazé` - }) - - formatted.push( - { - role: "developer", - content: "Adopte une attitude détendue et répond comme si tu participais à la conversation, pas trop long, pas de retour à la ligne, simple et utilise les emojis du serveur. N'hésites pas à utiliser des abréviations mais sans en abuser." - }, - { - role: 'developer', - content: message.mentions.repliedUser?.id ? `La phrase de l'utilisateur répond à un message de ${message.mentions.repliedUser?.id === process.env.APP_ID ? 'toi-même' : message.mentions.repliedUser?.id}` : '', - }, - { - role: "developer", - content: `Ton id est : <@${process.env.APP_ID}>, évite de l'utiliser. Ton username et global_name sont : ${process.env.APP_NAME}` - }, - { - role: "developer", - content: `L'utilisateur qui s'adresse a toi est : <@${akhyAuthor.id}>` - }, - { - role: "user", - content: requestMessage.length > 1 ? requestMessage : 'Salut', - }); - } - else if (process.env.MODEL === 'Mistral') { - // Map to Mistral format - formatted.push({ - role: 'system', - content: `${convo}`, - }); - - formatted.push({ - role: 'system', - content: `Voici la liste des différents utilisateurs présents : ${allAkhysText}`, - }); - - formatted.push( - { - role: "system", - content: "Adopte une attitude détendue et répond comme si tu participais à la conversation, pas trop long, pas de retour à la ligne, simple. N'hésites pas à utiliser des abréviations mais sans en abuser." - }, - { - role: 'system', - content: message.mentions.repliedUser?.id ? `La phrase de l'utilisateur répond à un message de ${message.mentions.repliedUser?.id === process.env.APP_ID ? 'toi-même' : message.mentions.repliedUser?.id}` : '', - }, - - { - role: "system", - content: `Ton id est : <@${process.env.APP_ID}>, évite de l'utiliser. Ton username et global_name sont : ${process.env.APP_NAME}` - }, - { - role: "system", - content: `L'utilisateur qui s'adresse a toi est : <@${akhyAuthor.id}>` - }, - { - role: "user", - content: requestMessage.length > 1 ? requestMessage : 'Salut', - }); - } - - // await gork(formatted); IA en marche - const reply = await gork(formatted); - - // Send response to the channel - await message.reply(reply); - } catch (err) { - console.error("Error fetching or sending messages:", err); - await message.reply("Oups, y'a eu un problème!"); - } - } - else if (message.content.toLowerCase().includes("quoi")) { - let prob = Math.random() - console.log(`feur ${prob}`) - if (prob < process.env.FEUR_PROB) { - // Send a message "feur" to the same channel - message.channel.send(`feur`) - .catch(console.error); - } - } - else if (message.guildId === process.env.DEV_GUILD_ID) { - // ADMIN COMMANDS - if (message.content.toLowerCase().startsWith('?u')) { - console.log(await getAPOUsers()) - } - else if (message.content.toLowerCase().startsWith('?b')) { - const amount = message.content.replace('?b ', '') - console.log(amount) - console.log(await postAPOBuy('650338922874011648', amount)) - } - else if (message.content.toLowerCase().startsWith('?v')) { - console.log('active polls :') - console.log(activePolls) - } - else if (message.content.toLowerCase().startsWith('?sv')) { - const amount = parseInt(message.content.replace('?sv ', '')) - let sum = 0 - let start_at = Date.now() - for (let i = 0; i < amount; i++) { - sum += parseFloat(randomSkinPrice(i+1)) - if (i%10 === 0 || i === amount-1) console.log(`Avg Skin Cost : ~${(sum/i+1).toFixed(2)}€ (~${sum.toFixed(2)}/${i+1}) - ${(Date.now() - start_at)}ms elapsed`) - } - console.log(`Result for ${amount} skins`) - } - else if (message.author.id === process.env.DEV_ID) { - const prefix = process.env.DEV_SITE === 'true' ? 'dev' : 'flopo' - if (message.content === prefix + ':add-coins-to-users') { - console.log(message.author.id) - try { - const stmtUpdateUsers = flopoDB.prepare(` - ALTER TABLE users - ADD coins INTEGER DEFAULT 0 - `); - stmtUpdateUsers.run() - } catch (e) { - console.log(e) - } - } - else if (message.content === prefix + ':sotd') { - initTodaysSOTD() - } - else if (message.content === prefix + ':users') { - const allAkhys = getAllUsers.all() - console.log(allAkhys) - } - else if (message.content === prefix + ':cancel') { - await message.delete() - } - else if (message.content.startsWith(prefix + ':reset-user-coins')) { - const userId = message.content.replace(prefix + ':reset-user-coins ', '') - const authorDB = getUser.get(userId) - if (authorDB) { - updateUserCoins.run({ - id: userId, - coins: 0, - }) - console.log(`${authorDB.username}'s coins were reset to 0`) - } else { - console.log('invalid user') - } - } - else if (message.content.startsWith(prefix + ':send-message')) { - const msg = message.content.replace(prefix + ':send-message ', '') - await fetch(process.env.BASE_URL + '/send-message', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - channelId: '1368908514545631262', - message: msg, - }) - }); - } - else if (message.content.startsWith(prefix + ':sql')) { - let sqlCommand = message.content.replace(prefix + ':sql ', '') - console.log(sqlCommand) - try { - if (sqlCommand.startsWith('SELECT')) { - const stmt = flopoDB.prepare(`${sqlCommand}`).all(); - console.log(stmt) - } else { - const stmt = flopoDB.prepare(`${sqlCommand}`).run(); - console.log(stmt) - } - } catch (e) { - console.log(e) - } - } - else if (message.content.startsWith(prefix + ':poker')) { - console.log('poker') - } - else if (message.content.startsWith(prefix + ':elo-test')) { - const numbers = message.content.match(/\d+/g); - - const score1 = parseInt(numbers[0]); - const score2 = parseInt(numbers[1]); - - const prob1 = 1 / (1 + Math.pow(10, (score2 - score1)/400)) - const prob2 = 1 / (1 + Math.pow(10, (score1 - score2)/400)) - - const res1 = Math.floor(score1 + 10 * (1 - prob1)) - const res2 = Math.floor(score2 + 10 * (0 - prob2)) - - console.log(res1, res2) - } - } - } -}); - -// Once bot is ready -client.once('ready', async () => { - console.log(`Logged in as ${client.user.tag}`); - console.log(`[Connected with ${FLAPI_URL}]`) - await getAkhys(); - console.log('FlopoBOT marked as ready') - - // every 10 minutes - cron.schedule('*!/10 * * * *', async () => { - const FIVE_MINUTES = 5 * 60 * 1000; - - // clean 5 minutes old inventories - for (const id in activeInventories) { - const inventory = activeInventories[id]; - if (Date.now() >= inventory.timestamp + FIVE_MINUTES) { - console.log(`Removing expired inventory : ${id}`); - delete activeInventories[id]; - } - } - for (const id in activeSearchs) { - const search = activeSearchs[id]; - if (Date.now() >= search.timestamp + FIVE_MINUTES) { - console.log(`Removing expired search : ${id}`); - delete activeSearchs[id]; - } - } - for (const id in activePredis) { - const predi = activePredis[id]; - if (predi.closed) { - if (predi.paidTime && Date.now() >= predi.paidTime + (24 * 60 * 60 * 1000)) { - console.log(`Removing expired paid predi : ${id}`); - delete activePredis[id]; - } else if (Date.now() >= predi.cancelledTime + (24 * 60 * 60 * 1000)) { - console.log(`Removing expired cancelled predi : ${id}`); - delete activePredis[id]; - } - } - } - for (const roomId in Object.keys(pokerRooms)) { - const room = pokerRooms[roomId]; - if (Object.keys(room.players)?.length === 0) { - delete pokerRooms[roomId]; - console.log(`Removing empty poker room : ${roomId}`); - io.emit('new-poker-room') - } - } - }); - - // at midnight - cron.schedule(process.env.CRON_EXPR, async () => { - try { - const akhys = getAllUsers.all() - akhys.forEach((akhy) => { - resetDailyReward.run(akhy.id); - }) - } catch (e) { - console.log(e) - } - - initTodaysSOTD() - }); - - // users/skins dayly fetch at 7am - cron.schedule('0 7 * * *', async() => { - // fetch eventual new users/skins - await getAkhys(); - console.log('Users and skins fetched') - }) -}); - -/!** - * Interactions endpoint URL where Discord will send HTTP requests - * Parse request body and verifies incoming requests using discord-interactions package - *!/ -app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), async function (req, res) { - // Interaction id, type and data - const { id, type, data } = req.body; - - /!** - * Handle verification requests - *!/ - if (type === InteractionType.PING) { - return res.send({ type: InteractionResponseType.PONG }); - } - - /!** - * Handle slash command requests - * See https://discord.com/developers/docs/interactions/application-commands#slash-commands - *!/ - if (type === InteractionType.APPLICATION_COMMAND) { - const { name } = data; - - // 'timeout' command - if (name === 'timeout') { - // Interaction context - const context = req.body.context; - // User ID is in user field for (G)DMs, and member for servers - const userId = context === 0 ? req.body.member.user.id : req.body.user.id; - // User's choices - const akhy = req.body.data.options[0].value; - const time = req.body.data.options[1].value; - - const guild = await client.guilds.fetch(req.body.guild_id); - const fromMember = await guild.members.fetch(userId); - const toMember = await guild.members.fetch(akhy); - - const already = Object.values(activePolls).find(poll => poll.toUsername === toMember.user); - - if (already) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Impossible de timeout **${toMember.user}** car un vote est déjà en cours`, - flags: InteractionResponseFlags.EPHEMERAL, - } - }); - } - - if (toMember.communicationDisabledUntilTimestamp > Date.now()) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `**${toMember.user}** est déjà timeout`, - flags: InteractionResponseFlags.EPHEMERAL, - } - }); - } - - // Save the poll information along with channel ID so we can notify later - activePolls[id] = { - id: userId, - username: fromMember.user, - toUserId: akhy, - toUsername: toMember.user, - time: time, - time_display: formatTime(time), - for: 0, - against: 0, - voters: [], - channelId: req.body.channel_id, // Capture channel for follow-up notification - endpoint: `webhooks/${process.env.APP_ID}/${req.body.token}/messages/@original`, - }; - - const guildId = req.body.guild_id; - const roleId = process.env.VOTING_ROLE_ID; // Set this in your .env file - const onlineEligibleUsers = await getOnlineUsersWithRole(guildId, roleId); - const requiredMajority = Math.max(parseInt(process.env.MIN_VOTES), Math.floor(onlineEligibleUsers.size / (time >= 21600 ? 2 : 3)) + 1); - const votesNeeded = Math.max(0, requiredMajority - activePolls[id].for); - - activePolls[id].endTime = Date.now() + process.env.POLL_TIME * 1000; - activePolls[id].requiredMajority = requiredMajority; - -// Set an interval to update the countdown every 10 seconds (or more often if you want) - const countdownInterval = setInterval(async () => { - const poll = activePolls[id]; - - if (!poll) { - clearInterval(countdownInterval); - io.emit('new-poll', { action: 'timeout cleared' }); - return; - } - - const remaining = Math.max(0, Math.floor((poll?.endTime - Date.now()) / 1000)); - const minutes = Math.floor(remaining / 60); - const seconds = remaining % 60; - const countdownText = `**${minutes}m ${seconds}s** restantes`; - const votesNeeded = Math.max(0, activePolls[id].requiredMajority - activePolls[id].for); - - if (!poll || remaining === 0) { - try { - let forText = '' - poll.voters.forEach((voter) => { - const user = getUser.get(voter); - forText += `- ${user.globalName}\n` - }) - await DiscordRequest( - poll.endpoint, - { - method: 'PATCH', - body: { - embeds: [ - { - title: `Le vote pour timeout ${poll.toUsername.username} pendant ${poll.time_display} a échoué 😔`, - description: `Il manquait **${votesNeeded}** vote(s)`, - fields: [ - { - name: 'Pour', - value: '✅ ' + poll.for + '\n' + forText, - inline: true, - }, - { - name: 'Temps restant', - value: '⏳ ' + countdownText, - inline: false, - }, - ], - color: 0xF2F3F3, // You can set the color of the embed - }, - ], - components: [], - }, - } - ); - } catch (err) { - console.error('Error sending message', err); - } - console.log('clear poll') - clearInterval(countdownInterval); - delete activePolls[id]; - io.emit('new-poll', { action: 'timeout cleared' }); - return; - } - - try { - let forText = '' - poll.voters.forEach((voter) => { - const user = getUser.get(voter); - forText += `- ${user.globalName}\n` - }) - await DiscordRequest( - poll.endpoint, - { - method: 'PATCH', - body: { - embeds: [ - { - title: `Timeout`, - description: `**${poll.username}** propose de timeout **${poll.toUsername}** pendant ${poll.time_display}\nIl manque **${votesNeeded}** vote(s)`, - fields: [ - { - name: 'Pour', - value: '✅ ' + poll.for + '\n' + forText, - inline: true, - }, - { - name: 'Temps restant', - value: '⏳ ' + countdownText, - inline: false, - }, - ], - color: 0xF2F3F3, // You can set the color of the embed - }, - ], - components: [ - { - type: MessageComponentTypes.ACTION_ROW, - components: [ - { - type: MessageComponentTypes.BUTTON, - custom_id: `vote_for_${req.body.id}`, - label: 'Oui ✅', - style: ButtonStyleTypes.SECONDARY, - }, - { - type: MessageComponentTypes.BUTTON, - custom_id: `vote_against_${req.body.id}`, - label: 'Non ❌', - style: ButtonStyleTypes.SECONDARY, - }, - ], - }, - ], - }, - } - ); - } catch (err) { - console.error('Error updating countdown:', err); - } - }, 1000); // every second - - const remaining = Math.max(0, Math.floor((activePolls[id].endTime - Date.now()) / 1000)); - const minutes = Math.floor(remaining / 60); - const seconds = remaining % 60; - const countdownText = `**${minutes}m ${seconds}s** restantes`; - - // web site update - io.emit('new-poll', { action: 'timeout command' }); - - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - embeds: [ - { - title: `Timeout`, - description: `**${activePolls[id].username}** propose de timeout **${activePolls[id].toUsername}** pendant ${activePolls[id].time_display}\nIl manque **${votesNeeded}** vote(s)`, - fields: [ - { - name: 'Pour', - value: '✅ ' + activePolls[id].for, - inline: true, - }, - { - name: 'Temps restant', - value: '⏳ ' + countdownText, - inline: false, - }, - ], - color: 0xF2F3F3, // You can set the color of the embed - }, - ], - components: [ - { - type: MessageComponentTypes.ACTION_ROW, - components: [ - { - type: MessageComponentTypes.BUTTON, - custom_id: `vote_for_${req.body.id}`, - label: 'Oui ✅', - style: ButtonStyleTypes.SECONDARY, - }, - { - type: MessageComponentTypes.BUTTON, - custom_id: `vote_against_${req.body.id}`, - label: 'Non ❌', - style: ButtonStyleTypes.SECONDARY, - }, - ], - }, - ], - }, - }); - } - - if (name === 'inventory') { - // Interaction context - const context = req.body.context; - // User ID is in user field for (G)DMs, and member for servers - const userId = context === 0 ? req.body.member.user.id : req.body.user.id; - // User's choices - const akhy = req.body.data.options ? req.body.data.options[0].value : userId; - - const guild = await client.guilds.fetch(req.body.guild_id); - const completeAkhy = await guild.members.fetch(akhy); - - const invSkins = getUserInventory.all({user_id: akhy}); - - const chromaText = (skin) => { - let result = "" - for (let i = 1; i <= skins.find((s) => s.uuid === skin.uuid).chromas.length; i++) { - result += skin.currentChroma === i ? '💠 ' : '◾ ' - } - return result - } - const chromaName = (skin) => { - if (skin.currentChroma >= 2) { - const name = skins.find((s) => s.uuid === skin.uuid).chromas[skin.currentChroma-1].displayName.replace(/[\r\n]+/g, '').replace(skin.displayName, '') - const match = name.match(/variante\s+[1-4]\s+([^)]+)/) - const result = match ? match[2] : null; - if (match) { - return match[1].trim() - } else { - return name - } - } - if (skin.currentChroma === 1) { - return 'Base' - } - return '' - }; - let content = ''; - let totalPrice = 0; - let fields = []; - invSkins.forEach(skin => { - content += `- ${skin.displayName} | ${skin.currentPrice.toFixed()}€ \n`; - totalPrice += skin.currentPrice; - fields.push({ - name: `${skin.displayName} | ${skin.currentPrice.toFixed(2)}€`, - value: `${skin.tierText}\nChroma : ${chromaText(skin)} | ${chromaName(skin)}\nLvl : **${skin.currentLvl}**!/${skins.find((s) => s.uuid === skin.uuid).levels.length}\n`, - inline: false, - }) - }) - - activeInventories[id] = { - akhyId: akhy, - userId: userId, - page: 0, - amount: invSkins.length, - reqBodyId: req.body.id, - endpoint: `webhooks/${process.env.APP_ID}/${req.body.token}/messages/@original`, - timestamp: Date.now(), - }; - - if (invSkins.length === 0) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - embeds: [ - { - title: `Inventaire de ${completeAkhy.user.username}`, - description: "Aucun skin dans l'inventaire", - color: 0xF2F3F3, - footer: {text: `Total : ${totalPrice.toFixed(2)}€`}, - }, - ], - }, - }); - } - const trueSkin = skins.find((s) => s.uuid === invSkins[0].uuid); - - const imageUrl = () => { - let result; - if (invSkins[0].currentLvl === trueSkin.levels.length) { - if (invSkins[0].currentChroma === 1) { - result = trueSkin.chromas[0].displayIcon - - } else { - result = trueSkin.chromas[invSkins[0].currentChroma-1].fullRender ?? trueSkin.chromas[invSkins[0].currentChroma-1].displayIcon - } - } else if (invSkins[0].currentLvl === 1) { - result = trueSkin.levels[0].displayIcon ?? trueSkin.chromas[0].fullRender - } else if (invSkins[0].currentLvl === 2 || invSkins[0].currentLvl === 3) { - result = trueSkin.displayIcon - } - if (result) return result; - return trueSkin.displayIcon - }; - - let components = [ - { - type: MessageComponentTypes.BUTTON, - custom_id: `prev_page_${req.body.id}`, - label: '⏮️ Préc.', - style: ButtonStyleTypes.SECONDARY, - }, - { - type: MessageComponentTypes.BUTTON, - custom_id: `next_page_${req.body.id}`, - label: 'Suiv. ⏭️', - style: ButtonStyleTypes.SECONDARY, - }, - ] - - if ((invSkins[0].currentLvl < trueSkin.levels.length || invSkins[0].currentChroma < trueSkin.chromas.length) && akhy === userId) { - components.push({ - type: MessageComponentTypes.BUTTON, - custom_id: `upgrade_${req.body.id}`, - label: `Upgrade ⏫`, - style: ButtonStyleTypes.PRIMARY, - }) - } - - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - embeds: [ - { - title: `Inventaire de ${completeAkhy.user.username}`, - description: `${invSkins?.length > 0 ? '' : "Aucun skin dans l'inventaire"}`, - color: 0xF2F3F3, - footer: {text: `${activeInventories[id].page+1}/${invSkins?.length} | Total : ${totalPrice.toFixed(2)}€`}, - fields: [fields[activeInventories[id].page]], - image: { - url: invSkins?.length > 0 ? imageUrl() : '', - } - }, - ], - components: [ - { - type: MessageComponentTypes.ACTION_ROW, - components: components, - }, - ], - }, - }); - } - - if (name === 'valorant') { - const buyResponse = await postAPOBuy(req.body.member.user.id, process.env.VALO_PRICE ?? 150) - - if (buyResponse.status === 500 || buyResponse.ok === false) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Tu n'as pas assez d'argent...`, - flags: InteractionResponseFlags.EPHEMERAL, - } - }); - } - - // First, send the initial response immediately - const initialEmbed = new EmbedBuilder() - .setTitle(`\t`) - .setImage('https://media.tenor.com/gIWab6ojBnYAAAAd/weapon-line-up-valorant.gif') - .setColor(`#F2F3F3`); - - // Send the initial response and store the reply object - await res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { embeds: [initialEmbed] } - }); - - // Get a random skin - const dbSkins = getAllAvailableSkins.all(); - const randomIndex = Math.floor(Math.random() * dbSkins.length); - let randomSkin; - - try { - randomSkin = skins.find((skin) => skin.uuid === dbSkins[randomIndex].uuid); - if (!randomSkin) throw new Error("Skin not found"); - } catch (e) { - // Edit the original message if there's an error - await DiscordRequest( - `webhooks/${process.env.APP_ID}/${req.body.token}/messages/@original`, - { - method: 'PATCH', - body: { - content: "Oups, ya eu un ptit problème", - embeds: [] - } - } - ); - return; - } - - // Generate random level and chroma - const randomLevel = Math.floor(Math.random() * randomSkin.levels.length + 1); - let randomChroma = randomLevel === randomSkin.levels.length - ? Math.floor(Math.random() * randomSkin.chromas.length + 1) - : 1; - if (randomChroma === randomSkin.chromas.length && randomSkin.chromas.length >= 2) randomChroma-- - const selectedLevel = randomSkin.levels[randomLevel - 1] - const selectedChroma = randomSkin.chromas[randomChroma - 1] - - // Set timeout for the reveal - setTimeout(async () => { - // Prepare the final embed - const selectedLevel = randomSkin.levels[randomLevel - 1]; - const selectedChroma = randomSkin.chromas[randomChroma - 1]; - - // Helper functions (unchanged from your original code) - const videoUrl = () => { - let result; - if (randomLevel === randomSkin.levels.length) { - if (randomChroma === 1) { - result = randomSkin.levels[randomSkin.levels.length - 1].streamedVideo ?? randomSkin.chromas[0].streamedVideo - } else { - result = randomSkin.chromas[randomChroma-1].streamedVideo - } - } else { - result = randomSkin.levels[randomLevel-1].streamedVideo - } - return result; - }; - const imageUrl = () => { - let result; - if (randomLevel === randomSkin.levels.length) { - if (randomChroma === 1) { - result = randomSkin.chromas[0].displayIcon - - } else { - result = randomSkin.chromas[randomChroma-1].fullRender ?? randomSkin.chromas[randomChroma-1].displayIcon - } - } else if (randomLevel === 1) { - result = randomSkin.levels[0].displayIcon ?? randomSkin.chromas[0].fullRender - } else if (randomLevel === 2 || randomLevel === 3) { - result = randomSkin.displayIcon - } - if (result) return result; - return randomSkin.displayIcon - }; - const chromaName = () => { - if (randomChroma >= 2) { - const name = selectedChroma.displayName.replace(/[\r\n]+/g, '').replace(randomSkin.displayName, '') - const match = name.match(/variante\s+[1-4]\s+([^)]+)/) - const result = match ? match[2] : null; - if (match) { - return match[1].trim() - } else { - return name - } - } - if (randomChroma === 1) { - return 'Base' - } - return '' - }; - const lvlText = () => { - let result = "" - if (randomLevel >= 1) { - result += '1️⃣ ' - } - if (randomLevel >= 2) { - result += '2️⃣ ' - } - if (randomLevel >= 3) { - result += '3️⃣ ' - } - if (randomLevel >= 4) { - result += '4️⃣ ' - } - if (randomLevel >= 5) { - result += '5️⃣ ' - } - for (let i = 0; i < randomSkin.levels.length - randomLevel; i++) { - result += '◾ ' - } - return result - } - const chromaText = () => { - let result = "" - for (let i = 1; i <= randomSkin.chromas.length; i++) { - result += randomChroma === i ? '💠 ' : '◾ ' - } - return result - } - const price = () => { - let result = dbSkins[randomIndex].basePrice; - - result *= (1 + (randomLevel / Math.max(randomSkin.levels.length, 2))) - result *= (1 + (randomChroma / 4)) - - return result.toFixed(2); - } - - // Update the database - try { - await updateSkin.run({ - uuid: randomSkin.uuid, - user_id: req.body.member.user.id, - currentLvl: randomLevel, - currentChroma: randomChroma, - currentPrice: price() - }); - } catch (e) { - console.log('Database error', e); - } - - // Build the final embed - const finalEmbed = new EmbedBuilder() - .setTitle(`${randomSkin.displayName} | ${chromaName()}`) - .setFields([ - { name: '', value: `**Lvl** | ${lvlText()}`, inline: true }, - { name: '', value: `**Chroma** | ${chromaText()}`, inline: true }, - { name: '', value: `**Prix** | ${price()} <:vp:1362964205808128122>`, inline: true }, - ]) - .setDescription(dbSkins[randomIndex].tierText) - .setImage(imageUrl()) - .setFooter({ text: 'Ajouté à ton inventaire' }) - .setColor(`#${dbSkins[randomIndex].tierColor}`); - - // Prepare components if video exists - const video = videoUrl(); - const components = []; - - if (video) { - components.push( - new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel('🎬 Aperçu vidéo') - .setStyle(ButtonStyle.Link) - .setURL(video) - ) - ); - } - - // Edit the original message - try { - await DiscordRequest( - `webhooks/${process.env.APP_ID}/${req.body.token}/messages/@original`, - { - method: 'PATCH', - body: { - embeds: [finalEmbed], - components: components - } - } - ); - } catch (err) { - console.error('Error editing message:', err); - } - }, 5000); - - return; - } - - if (name === 'info') { - const guild = await client.guilds.fetch(req.body.guild_id); - - await guild.members.fetch() - - const timedOutMembers = guild.members.cache.filter( - (member) => - member.communicationDisabledUntil && - member.communicationDisabledUntil > new Date() - ); - - if (timedOutMembers.size === 0) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - embeds: [ - { - title: `Membres timeout`, - description: "Aucun membre n'est actuellement timeout.", - color: 0xF2F3F3, - }, - ], - }, - }); - } - - const list = timedOutMembers.map( - (member) => - `**${member.user.tag}** (jusqu'à ${member.communicationDisabledUntil.toLocaleString()})` - ).join("\n"); - - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - embeds: [ - { - title: `Membres timeout`, - description: `${list}`, - color: 0xF2F3F3, - }, - ], - }, - }); - } - - if (name === 'skins') { - const topSkins = getTopSkins.all() - const guild = await client.guilds.fetch(req.body.guild_id) - - let fields = [] - - for (const skin of topSkins) { - const index = topSkins.indexOf(skin); - const owner = skin.user_id ? await guild.members.fetch(skin.user_id) : null; - fields.push({ - name: `#${index+1} - **${skin.displayName}**`, - value: `${skin.maxPrice}€ ${skin.user_id ? '| **@'+ owner.user.username+'** ✅' : ''}\n`, - inline: false - }); - } - - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - embeds: [ - { - fields: fields, - color: 0xF2F3F3, - }, - ], - }, - }); - } - - if (name === 'floposite') { - const originalComponents = [ - { - type: MessageComponentTypes.BUTTON, - label: 'Aller sur FlopoSite', - style: ButtonStyleTypes.LINK, - url: 'https://floposite.com', - }, - ]; - - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - embeds: [ - { - title: 'FlopoSite', - description: 'L\'officiel et très goatesque site de FlopoBot.', - color: 0x6571F3, - thumbnail: { - url: process.env.API_URL + '/public/images/flopo.png' - } - } - ], - components: [ - { - type: MessageComponentTypes.ACTION_ROW, - components: originalComponents, - }, - ], - } - }) - } - - if (name === 'search') { - const context = req.body.context; - // User ID is in user field for (G)DMs, and member for servers - const userId = context === 0 ? req.body.member.user.id : req.body.user.id; - const searchValue = req.body.data.options[0].value.toLowerCase(); - - const guild = await client.guilds.fetch(req.body.guild_id); - - let dbSkins = getAllSkins.all() - - let resultSkins = dbSkins.filter((skin) => { - return skin.displayName.toLowerCase().includes(searchValue) || skin.tierText.toLowerCase().includes(searchValue); - }) - - if (resultSkins.length === 0) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: 'Aucun résultat ne correspond à ta recherche', - flags: InteractionResponseFlags.EPHEMERAL, - } - }) - } - - const owner = await guild.members.fetch(resultSkins[0].user_id) - let fields = [ - { - name: `**${resultSkins[0].displayName}** | ${resultSkins[0].tierText}`, - value: `${resultSkins[0].maxPrice}€ ${resultSkins[0].user_id ? '| **@'+ owner.user.username +'** ✅' : ''}`, - inline: false, - } - ] - - activeSearchs[id] = { - userId: userId, - page: 0, - amount: resultSkins.length, - resultSkins: resultSkins, - endpoint: `webhooks/${process.env.APP_ID}/${req.body.token}/messages/@original`, - timestamp: Date.now(), - searchValue: searchValue, - }; - - const trueSkin = skins.find((s) => s.uuid === resultSkins[0].uuid); - const imageUrl = () => { - let res; - if (trueSkin.chromas[trueSkin.chromas.length-1].displayIcon) { - res = trueSkin.chromas[trueSkin.chromas.length-1].displayIcon - } else if (trueSkin.levels[trueSkin.levels.length-1].displayIcon) { - res = trueSkin.levels[trueSkin.levels.length-1].displayIcon - } else { - res = trueSkin.displayIcon - } - return res - }; - - const videoUrl = () => { - let res; - if (trueSkin.chromas[trueSkin.chromas.length-1].streamedVideo) { - res = trueSkin.chromas[trueSkin.chromas.length-1].streamedVideo - } else if (trueSkin.levels[trueSkin.levels.length-1].streamedVideo) { - res = trueSkin.levels[trueSkin.levels.length-1].streamedVideo - } else { - res = null - } - return res - }; - - const originalComponents = [ - { - type: MessageComponentTypes.BUTTON, - custom_id: `prev_search_page_${req.body.id}`, - label: '⏮️ Préc.', - style: ButtonStyleTypes.SECONDARY, - }, - { - type: MessageComponentTypes.BUTTON, - custom_id: `next_search_page_${req.body.id}`, - label: 'Suiv. ⏭️', - style: ButtonStyleTypes.SECONDARY, - }, - ]; - - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - embeds: [ - { - title: `Résultat de recherche`, - description: `🔎 ${searchValue}`, - fields: fields, - color: parseInt(resultSkins[0].tierColor, 16), - image: { url: imageUrl() }, - footer: { text: `1/${resultSkins.length} résultat(s)` }, - }, - ], - components: [ - { - type: MessageComponentTypes.ACTION_ROW, - components: originalComponents, - }, - ], - }, - }); - } - - console.error(`unknown command: ${name}`); - return res.status(400).json({ error: 'unknown command' }); - } - - if (type === InteractionType.MESSAGE_COMPONENT) { -// custom_id set in payload when sending message component - const componentId = data.custom_id; - - if (componentId.startsWith('accept_button_')) { - // get the associated game ID - const gameId = componentId.replace('accept_button_', ''); - // Delete message with token in request body - const endpoint = `webhooks/${process.env.APP_ID}/${req.body.token}/messages/${req.body.message.id}`; - try { - await res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: 'What is your object of choice?', - // Indicates it'll be an ephemeral message - flags: InteractionResponseFlags.EPHEMERAL, - components: [ - { - type: MessageComponentTypes.ACTION_ROW, - components: [ - { - type: MessageComponentTypes.STRING_SELECT, - // Append game ID - custom_id: `select_choice_${gameId}`, - options: getShuffledOptions(), - }, - ], - }, - ], - }, - }); - // Delete previous message - await DiscordRequest(endpoint, { method: 'DELETE' }); - } catch (err) { - console.error('Error sending message:', err); - } - } - else if (componentId.startsWith('vote_')) { - let gameId, isVotingFor; - - if (componentId.startsWith('vote_for_')) { - gameId = componentId.replace('vote_for_', ''); - isVotingFor = true; - } else { - gameId = componentId.replace('vote_against_', ''); - isVotingFor = false; - } - - if (activePolls[gameId]) { - const poll = activePolls[gameId]; - poll.voters = poll.voters || []; - const voterId = req.body.member.user.id; - - // Check if the voter has the required voting role - const voterRoles = req.body.member.roles || []; - if (!voterRoles.includes(process.env.VOTING_ROLE_ID)) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: "Tu n'as pas le rôle requis pour voter.", - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } - - // Enforce one vote per eligible user - if (poll.voters.find(u => u === voterId)) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: "Tu as déjà voté oui!", - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } - - // Record the vote - if (isVotingFor) { - poll.voters.push(voterId); - poll.for++; - } else { - poll.against++; - } - - io.emit('new-poll', { action: 'new vote' }); - - // Retrieve online eligible users (ensure your bot has the necessary intents) - const guildId = req.body.guild_id; - const roleId = process.env.VOTING_ROLE_ID; // Set this in your .env file - const onlineEligibleUsers = await getOnlineUsersWithRole(guildId, roleId); - const votesNeeded = Math.max(0, poll.requiredMajority - poll.for); - - // Check if the majority is reached - if (poll.for >= poll.requiredMajority) { - try { - // Build the updated poll message content - let forText = '' - poll.voters.forEach((voter) => { - const user = getUser.get(voter); - forText += `- ${user.globalName}\n` - }) - await DiscordRequest( - poll.endpoint, - { - method: 'PATCH', - body: { - embeds: [ - { - title: `Timeout`, - description: `Proposition de timeout **${poll.toUsername}** pendant ${poll.time_display}`, - fields: [ - { - name: 'Votes totaux', - value: '✅ ' + poll.for + '\n' + forText, - inline: true, - }, - ], - color: 0xF2F3F3, // You can set the color of the embed - }, - ], - components: [], // remove buttons - }, - } - ); - } catch (err) { - console.error('Error updating poll message:', err); - } - // Clear the poll so the setTimeout callback doesn't fire later - delete activePolls[gameId]; - - // **Actual Timeout Action** - try { - // Calculate the ISO8601 timestamp to disable communications until now + poll.time seconds - const timeoutUntil = new Date(Date.now() + poll.time * 1000).toISOString(); - const endpointTimeout = `guilds/${req.body.guild_id}/members/${poll.toUserId}`; - await DiscordRequest(endpointTimeout, { - method: 'PATCH', - body: { communication_disabled_until: timeoutUntil }, - }); - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `<@${poll.toUserId}> a été timeout pendant ${poll.time_display} par décision démocratique 👊`, - }, - }); - } catch (err) { - console.error('Error timing out user:', err); - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Impossible de timeout <@${poll.toUserId}>, désolé... 😔`, - }, - }); - } - } - - // If the vote is "for", update the original poll message to reflect the new vote count. - if (isVotingFor) { - const remaining = Math.max(0, Math.floor((poll.endTime - Date.now()) / 1000)); - const minutes = Math.floor(remaining / 60); - const seconds = remaining % 60; - const countdownText = `**${minutes}m ${seconds}s** restantes`; - try { - // Build the updated poll message content - let forText = '' - poll.voters.forEach((voter) => { - const user = getUser.get(voter); - forText += `- ${user.globalName}\n` - }) - await DiscordRequest( - poll.endpoint, - { - method: 'PATCH', - body: { - embeds: [ - { - title: `Timeout`, - description: `**${poll.username}** propose de timeout **${poll.toUsername}** pendant ${poll.time_display}\nIl manque **${votesNeeded}** vote(s)`, - fields: [ - { - name: 'Pour', - value: '✅ ' + poll.for + '\n' + forText, - inline: true, - }, - { - name: 'Temps restant', - value: '⏳ ' + countdownText, - inline: false, - }, - ], - color: 0xF2F3F3, // You can set the color of the embed - }, - ], - components: req.body.message.components, // preserve the buttons - }, - } - ); - } catch (err) { - console.error('Error updating poll message:', err); - } - } - - // Send an ephemeral acknowledgement to the voter - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Vote enregistré ! ✅`, - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } - } - else if (componentId.startsWith('prev_page')) { - let invId = componentId.replace('prev_page_', ''); - const context = req.body.context; - // User ID is in user field for (G)DMs, and member for servers - const userId = context === 0 ? req.body.member.user.id : req.body.user.id; - - const guild = await client.guilds.fetch(req.body.guild_id); - if (!activeInventories[invId]) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Oups, cet affichage n'est plus actif.\nRelance la commande pour avoir un nouvel élément intéractif`, - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } - - const completeAkhy = await guild.members.fetch(activeInventories[invId].akhyId); - - const invSkins = getUserInventory.all({user_id: activeInventories[invId].akhyId}); - - const chromaText = (skin) => { - let res = "" - for (let i = 1; i <= skins.find((s) => s.uuid === skin.uuid).chromas.length; i++) { - res += skin.currentChroma === i ? '💠 ' : '◾ ' - } - return res - } - const chromaName = (skin) => { - if (skin.currentChroma >= 2) { - const name = skins.find((s) => s.uuid === skin.uuid).chromas[skin.currentChroma-1].displayName.replace(/[\r\n]+/g, '').replace(skin.displayName, '') - const match = name.match(/variante\s+[1-4]\s+([^)]+)/) - const result = match ? match[2] : null; - if (match) { - return match[1].trim() - } else { - return name - } - } - if (skin.currentChroma === 1) { - return 'Base' - } - return '' - }; - let content = ''; - let totalPrice = 0; - let fields = []; - invSkins.forEach(skin => { - content += `- ${skin.displayName} | ${skin.currentPrice.toFixed()}€ \n`; - totalPrice += skin.currentPrice; - fields.push({ - name: `${skin.displayName} | ${skin.currentPrice.toFixed(2)}€`, - value: `${skin.tierText}\nChroma : ${chromaText(skin)} | ${chromaName(skin)}\nLvl : **${skin.currentLvl}**!/${skins.find((s) => s.uuid === skin.uuid).levels.length}\n`, - inline: false, - }) - }) - - if (activeInventories[invId] && activeInventories[invId].userId === req.body.member.user.id) { - if (activeInventories[invId].page === 0) { - activeInventories[invId].page = activeInventories[invId].amount-1 - } else { - activeInventories[invId].page-- - } - } else { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Tu n'est pas à l'origine de cette commande /inventory`, - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } - - const trueSkin = skins.find((s) => s.uuid === invSkins[activeInventories[invId].page].uuid); - const imageUrl = () => { - let res; - if (invSkins[activeInventories[invId].page].currentLvl === trueSkin.levels.length) { - if (invSkins[activeInventories[invId].page].currentChroma === 1) { - res = trueSkin.chromas[0].displayIcon - - } else { - res = trueSkin.chromas[invSkins[activeInventories[invId].page].currentChroma-1].fullRender ?? trueSkin.chromas[invSkins[activeInventories[invId].page].currentChroma-1].displayIcon - } - } else if (invSkins[activeInventories[invId].page].currentLvl === 1) { - res = trueSkin.levels[0].displayIcon ?? trueSkin.chromas[0].fullRender - } else if (invSkins[activeInventories[invId].page].currentLvl === 2 || invSkins[activeInventories[invId].page].currentLvl === 3) { - res = trueSkin.displayIcon - } - if (res) return res; - return trueSkin.displayIcon - }; - - let components = req.body.message.components; - - if ((invSkins[activeInventories[invId].page].currentLvl < trueSkin.levels.length || invSkins[activeInventories[invId].page].currentChroma < trueSkin.chromas.length) && activeInventories[invId].akhyId === activeInventories[invId].userId) { - if (components[0].components.length === 2) { - components[0].components.push({ - type: MessageComponentTypes.BUTTON, - custom_id: `upgrade_${activeInventories[invId].reqBodyId}`, - label: `Upgrade ⏫`, - style: ButtonStyleTypes.PRIMARY, - }) - } - } else { - if (components[0].components.length === 3) { - components[0].components.pop() - } - } - - try { - await DiscordRequest( - activeInventories[invId].endpoint, - { - method: 'PATCH', - body: { - embeds: [ - { - title: `Inventaire de ${completeAkhy.user.username}`, - description: `${invSkins?.length > 0 ? '' : "Aucun skin dans l'inventaire"}`, - color: 0xF2F3F3, - footer: {text: `${activeInventories[invId].page+1}/${invSkins?.length} | Total : ${totalPrice.toFixed(2)}€`}, - fields: [fields[activeInventories[invId].page]], - image: { - url: invSkins?.length > 0 ? imageUrl() : '', - } - }, - ], - components: components, - }, - } - ); - } catch (err) { - console.log('Pas trouvé : ', err) - } - return res.send({ - type: InteractionResponseType.DEFERRED_UPDATE_MESSAGE, - }); - } - else if (componentId.startsWith('next_page')) { - let invId = componentId.replace('next_page_', ''); - const context = req.body.context; - // User ID is in user field for (G)DMs, and member for servers - const userId = context === 0 ? req.body.member.user.id : req.body.user.id; - - const guild = await client.guilds.fetch(req.body.guild_id); - if (!activeInventories[invId]) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Oups, cet inventaire n'est plus actif.\nRelance la commande pour avoir un nouvel inventaire interactif`, - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } - const completeAkhy = await guild.members.fetch(activeInventories[invId].akhyId); - - const invSkins = getUserInventory.all({user_id: activeInventories[invId].akhyId}); - - const chromaText = (skin) => { - let res = "" - for (let i = 1; i <= skins.find((s) => s.uuid === skin.uuid).chromas.length; i++) { - res += skin.currentChroma === i ? '💠 ' : '◾ ' - } - return res - } - const chromaName = (skin) => { - if (skin.currentChroma >= 2) { - const name = skins.find((s) => s.uuid === skin.uuid).chromas[skin.currentChroma-1].displayName.replace(/[\r\n]+/g, '').replace(skin.displayName, '') - const match = name.match(/variante\s+[1-4]\s+([^)]+)/) - const result = match ? match[2] : null; - if (match) { - return match[1].trim() - } else { - return name - } - } - if (skin.currentChroma === 1) { - return 'Base' - } - return '' - }; - let content = ''; - let totalPrice = 0; - let fields = []; - invSkins.forEach(skin => { - content += `- ${skin.displayName} | ${skin.currentPrice.toFixed()}€ \n`; - totalPrice += skin.currentPrice; - fields.push({ - name: `${skin.displayName} | ${skin.currentPrice.toFixed(2)}€`, - value: `${skin.tierText}\nChroma : ${chromaText(skin)} | ${chromaName(skin)}\nLvl : **${skin.currentLvl}**!/${skins.find((s) => s.uuid === skin.uuid).levels.length}\n`, - inline: false, - }) - }) - - if (activeInventories[invId] && activeInventories[invId].userId === req.body.member.user.id) { - if (activeInventories[invId].page === activeInventories[invId].amount-1) { - activeInventories[invId].page = 0 - } else { - activeInventories[invId].page++ - } - } else { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Tu n'est pas à l'origine de cette commande /inventory`, - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } - - const trueSkin = skins.find((s) => s.uuid === invSkins[activeInventories[invId].page].uuid); - const imageUrl = () => { - let res; - if (invSkins[activeInventories[invId].page].currentLvl === trueSkin.levels.length) { - if (invSkins[activeInventories[invId].page].currentChroma === 1) { - res = trueSkin.chromas[0].displayIcon - - } else { - res = trueSkin.chromas[invSkins[activeInventories[invId].page].currentChroma-1].fullRender ?? trueSkin.chromas[invSkins[activeInventories[invId].page].currentChroma-1].displayIcon - } - } else if (invSkins[activeInventories[invId].page].currentLvl === 1) { - res = trueSkin.levels[0].displayIcon ?? trueSkin.chromas[0].fullRender - } else if (invSkins[activeInventories[invId].page].currentLvl === 2 || invSkins[activeInventories[invId].page].currentLvl === 3) { - res = trueSkin.displayIcon - } - if (res) return res; - return trueSkin.displayIcon - }; - - let components = req.body.message.components; - - if ((invSkins[activeInventories[invId].page].currentLvl < trueSkin.levels.length || invSkins[activeInventories[invId].page].currentChroma < trueSkin.chromas.length) && activeInventories[invId].akhyId === activeInventories[invId].userId) { - if (components[0].components.length === 2) { - components[0].components.push({ - type: MessageComponentTypes.BUTTON, - custom_id: `upgrade_${activeInventories[invId].reqBodyId}`, - label: `Upgrade ⏫`, - style: ButtonStyleTypes.PRIMARY, - }) - } - } else { - if (components[0].components.length === 3) { - components[0].components.pop() - } - } - - try { - await DiscordRequest( - activeInventories[invId].endpoint, - { - method: 'PATCH', - body: { - embeds: [ - { - title: `Inventaire de ${completeAkhy.user.username}`, - description: `${invSkins?.length > 0 ? '' : "Aucun skin dans l'inventaire"}`, - color: 0xF2F3F3, - footer: {text: `${activeInventories[invId].page+1}/${invSkins?.length} | Total : ${totalPrice.toFixed(2)}€`}, - fields: [fields[activeInventories[invId].page]], - image: { - url: invSkins?.length > 0 ? imageUrl() : '', - } - }, - ], - components: components, - }, - } - ); - } catch (err) { - console.log('Pas trouvé : ', err) - } - return res.send({ - type: InteractionResponseType.DEFERRED_UPDATE_MESSAGE, - }); - } - else if (componentId.startsWith('upgrade_')) { - let invId = componentId.replace('upgrade_', '') - const context = req.body.context - const userId = context === 0 ? req.body.member.user.id : req.body.user.id - - const guild = await client.guilds.fetch(req.body.guild.id) - if (!activeInventories[invId]) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Oups, cet inventaire n'est plus actif.\nRelance la commande pour avoir un nouvel inventaire interactif`, - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } - const completeAkhy = await guild.members.fetch(activeInventories[invId].akhyId) - - const invSkins = getUserInventory.all({user_id: activeInventories[invId].akhyId}) - - if (!activeInventories[invId] || activeInventories[invId].userId !== req.body.member.user.id) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Tu n'est pas à l'origine de cette commande /inventory`, - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } - - const upgradePrice = process.env.VALO_UPGRADE_PRICE ?? invSkins[activeInventories[invId].page].maxPrice/10 - const buyResponse = await postAPOBuy(req.body.member.user.id, upgradePrice) - - if (buyResponse.status === 500 || buyResponse.ok === false) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Tu n'as pas assez d'argent, cette amélioration coûte ${upgradePrice}€`, - flags: InteractionResponseFlags.EPHEMERAL, - } - }); - } - - const skin = invSkins[activeInventories[invId].page]; - const trueSkin = skins.find((s) => s.uuid === invSkins[activeInventories[invId].page].uuid); - - const lvlNb = trueSkin.levels.length - const chromaNb = trueSkin.chromas.length - const tierRank = trueSkin.tierRank - const currentLvl = skin.currentLvl - const currentChroma = skin.currentChroma - - let succeeded = false - - if (currentLvl < lvlNb) { - let prob = (currentLvl/lvlNb) - if (tierRank) prob *= ((tierRank+1)/4)+.1 - let rand = Math.random() - console.log(`lvl upgrade prob : ${prob} | ${rand}`) - succeeded = rand >= prob - //amélioration du lvl - if (succeeded) { - let newLvl = skin.currentLvl + 1 - const price = () => { - let res = skin.basePrice; - - res *= (1 + (newLvl / Math.max(trueSkin.levels.length, 2))) - res *= (1 + (skin.currentChroma / 4)) - - return res.toFixed(2); - } - try { - await updateSkin.run({ - uuid: skin.uuid, - user_id: skin.user_id, - currentLvl: newLvl, - currentChroma: skin.currentChroma, - currentPrice: price() - }); - } catch (e) { - console.log('Database error', e); - } - } - } - else if (currentChroma < chromaNb) { - let prob = (currentChroma/chromaNb) - if (tierRank) prob *= ((tierRank+1)/4)+.1 - let rand = Math.random() - console.log(`lvl upgrade prob : ${prob} | ${rand}`) - succeeded = rand >= prob - //amélioration du chroma - if (succeeded) { - let newChroma = skin.currentChroma + 1 - const price = () => { - let res = skin.basePrice; - - res *= (1 + (skin.currentLvl / Math.max(trueSkin.levels.length, 2))) - res *= (1 + (newChroma / 4)) - - return res.toFixed(2); - } - try { - await updateSkin.run({ - uuid: skin.uuid, - user_id: skin.user_id, - currentLvl: skin.currentLvl, - currentChroma: newChroma, - currentPrice: price() - }); - } catch (e) { - console.log('Database error', e); - } - } - } else { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Ce skin n'est pas améliorable`, - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } - - // gif - const initialEmbed = new EmbedBuilder() - .setTitle(`Amélioration en cours...`) - .setImage('https://media.tenor.com/HD8nVN2QP9MAAAAC/thoughts-think.gif') - .setColor(0xF2F3F3); - - // Send the initial response and store the reply object - await res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { embeds: [initialEmbed] } - }); - - // then result - setTimeout(async () => { - // Prepare the final embed - let updatedSkin = await getSkin.get(trueSkin.uuid) - const randomLevel = updatedSkin.currentLvl - const randomChroma = updatedSkin.currentChroma - const selectedChroma = trueSkin.chromas[randomChroma-1] - - // Helper functions (unchanged from your original code) - const videoUrl = () => { - let res; - if (randomLevel === trueSkin.levels.length) { - if (randomChroma === 1) { - res = trueSkin.levels[trueSkin.levels.length - 1].streamedVideo ?? trueSkin.chromas[0].streamedVideo - } else { - res = trueSkin.chromas[randomChroma-1].streamedVideo - } - } else { - res = trueSkin.levels[randomLevel-1].streamedVideo - } - return res; - }; - const imageUrl = () => { - let res; - if (randomLevel === trueSkin.levels.length) { - if (randomChroma === 1) { - res = trueSkin.chromas[0].displayIcon - - } else { - res = trueSkin.chromas[randomChroma-1].fullRender ?? trueSkin.chromas[randomChroma-1].displayIcon - } - } else if (randomLevel === 1) { - res = trueSkin.levels[0].displayIcon ?? trueSkin.chromas[0].fullRender - } else if (randomLevel === 2 || randomLevel === 3) { - res = trueSkin.displayIcon - } - if (res) return res; - return trueSkin.displayIcon - }; - const chromaName = () => { - if (randomChroma >= 2) { - const name = selectedChroma.displayName.replace(/[\r\n]+/g, '').replace(trueSkin.displayName, '') - const match = name.match(/variante\s+[1-4]\s+([^)]+)/) - const result = match ? match[2] : null; - if (match) { - return match[1].trim() - } else { - return name - } - } - if (randomChroma === 1) { - return 'Base' - } - return '' - }; - const lvlText = () => { - let res = "" - if (randomLevel >= 1) { - res += '1️⃣ ' - } - if (randomLevel >= 2) { - res += '2️⃣ ' - } - if (randomLevel >= 3) { - res += '3️⃣ ' - } - if (randomLevel >= 4) { - res += '4️⃣ ' - } - if (randomLevel >= 5) { - res += '5️⃣ ' - } - for (let i = 0; i < trueSkin.levels.length - randomLevel; i++) { - res += '◾ ' - } - return res - } - const chromaText = () => { - let res = "" - for (let i = 1; i <= trueSkin.chromas.length; i++) { - res += randomChroma === i ? '💠 ' : '◾ ' - } - return res - } - - // Build the final embed - let finalEmbed; - if (succeeded) { - finalEmbed = new EmbedBuilder() - .setTitle(`L'amélioration a réussi ! 🎉`) - .setFields([ - { name: '', value: `${updatedSkin.displayName} | ${chromaName()}`, inline: false }, - { name: '', value: `**Lvl** | ${lvlText()}`, inline: true }, - { name: '', value: `**Chroma** | ${chromaText()}`, inline: true }, - { name: '', value: `**Prix** | ${updatedSkin.currentPrice} <:vp:1362964205808128122>`, inline: true }, - ]) - .setDescription(updatedSkin.tierText) - .setImage(imageUrl()) - .setColor(0x00FF00); - } - else { - finalEmbed = new EmbedBuilder() - .setTitle(`L'amélioration a râté... ❌`) - .setFields([ - { name: '', value: `${updatedSkin.displayName} | ${chromaName()}`, inline: false }, - ]) - .setDescription(updatedSkin.tierText) - .setImage(imageUrl()) - .setColor(0xFF0000); - } - - - // Prepare components if video exists - const video = videoUrl(); - const components = []; - - if (!succeeded) { - components.push(new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel('Réessayer 🔄️') - .setStyle(ButtonStyle.Primary) - .setCustomId(`upgrade_${activeInventories[invId].reqBodyId}`) - )) - } else if (video) { - components.push( - new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel('🎬 Aperçu vidéo') - .setStyle(ButtonStyle.Link) - .setURL(video) - ) - ); - } - - // Edit the original message - try { - await DiscordRequest( - `webhooks/${process.env.APP_ID}/${req.body.token}/messages/@original`, - { - method: 'PATCH', - body: { - embeds: [finalEmbed], - components: components - } - } - ); - } catch (err) { - console.error('Error editing message:', err); - } - }, 500); - } - else if (componentId.startsWith('prev_search_page')) { - let searchId = componentId.replace('prev_search_page_', ''); - const context = req.body.context; - // User ID is in user field for (G)DMs, and member for servers - const userId = context === 0 ? req.body.member.user.id : req.body.user.id; - - const guild = await client.guilds.fetch(req.body.guild_id); - if (!activeSearchs[searchId]) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Oups, cet affichage n'est plus actif.\nRelance la commande pour avoir un nouvel élément intéractif`, - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } - - const chromaText = (skin) => { - let res = "" - for (let i = 1; i <= skins.find((s) => s.uuid === skin.uuid).chromas.length; i++) { - res += skin.currentChroma === i ? '💠 ' : '◾ ' - } - return res - } - const chromaName = (skin) => { - if (skin.currentChroma >= 2) { - const name = skins.find((s) => s.uuid === skin.uuid).chromas[skin.currentChroma-1].displayName.replace(/[\r\n]+/g, '').replace(skin.displayName, '') - const match = name.match(/variante\s+[1-4]\s+([^)]+)/) - const result = match ? match[2] : null; - if (match) { - return match[1].trim() - } else { - return name - } - } - if (skin.currentChroma === 1) { - return 'Base' - } - return '' - }; - - if (activeSearchs[searchId] && activeSearchs[searchId].userId === req.body.member.user.id) { - if (activeSearchs[searchId].page === 0) { - activeSearchs[searchId].page = activeSearchs[searchId].amount-1 - } else { - activeSearchs[searchId].page-- - } - } else { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Tu n'est pas à l'origine de cette commande /search`, - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } - - const trueSkin = skins.find((s) => s.uuid === activeSearchs[searchId].resultSkins[activeSearchs[searchId].page].uuid); - const imageUrl = () => { - let res; - if (trueSkin.chromas[trueSkin.chromas.length-1].displayIcon) { - res = trueSkin.chromas[trueSkin.chromas.length-1].displayIcon - } else if (trueSkin.levels[trueSkin.levels.length-1].displayIcon) { - res = trueSkin.levels[trueSkin.levels.length-1].displayIcon - } else { - res = trueSkin.displayIcon - } - return res - }; - - const videoUrl = () => { - let res; - if (trueSkin.chromas[trueSkin.chromas.length-1].streamedVideo) { - res = trueSkin.chromas[trueSkin.chromas.length-1].streamedVideo - } else if (trueSkin.levels[trueSkin.levels.length-1].streamedVideo) { - res = trueSkin.levels[trueSkin.levels.length-1].streamedVideo - } else { - res = null - } - return res - }; - - const owner = await guild.members.fetch(activeSearchs[searchId].resultSkins[activeSearchs[searchId].page].user_id) - let fields = [ - { - name: `**${activeSearchs[searchId].resultSkins[activeSearchs[searchId].page].displayName}** | ${activeSearchs[searchId].resultSkins[activeSearchs[searchId].page].tierText}`, - value: `${activeSearchs[searchId].resultSkins[activeSearchs[searchId].page].maxPrice}€ ${activeSearchs[searchId].resultSkins[activeSearchs[searchId].page].user_id ? '| **@'+ owner.user.username +'** ✅' : ''}`, - inline: false, - } - ] - - try { - const originalComponents = req.body.message.components || []; - - await DiscordRequest( - activeSearchs[searchId].endpoint, - { - method: 'PATCH', - body: { - embeds: [ - { - title: `Résultat de recherche`, - description: `🔎 ${activeSearchs[searchId].searchValue}`, - fields: fields, - color: parseInt(activeSearchs[searchId].resultSkins[activeSearchs[searchId].page].tierColor, 16), - image: { url: imageUrl() }, - footer: { text: `${activeSearchs[searchId].page+1}/${activeSearchs[searchId].resultSkins.length} résultat(s)` }, - }, - ], - components: originalComponents, - }, - } - ); - } catch (err) { - console.log('Pas trouvé : ', err) - } - return res.send({ - type: InteractionResponseType.DEFERRED_UPDATE_MESSAGE, - }); - } - else if (componentId.startsWith('next_search_page')) { - let searchId = componentId.replace('next_search_page_', ''); - const context = req.body.context; - // User ID is in user field for (G)DMs, and member for servers - const userId = context === 0 ? req.body.member.user.id : req.body.user.id; - - const guild = await client.guilds.fetch(req.body.guild_id); - if (!activeSearchs[searchId]) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Oups, cet affichage n'est plus actif.\nRelance la commande pour avoir un nouvel élément intéractif`, - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } - - const chromaText = (skin) => { - let res = "" - for (let i = 1; i <= skins.find((s) => s.uuid === skin.uuid).chromas.length; i++) { - res += skin.currentChroma === i ? '💠 ' : '◾ ' - } - return res - } - const chromaName = (skin) => { - if (skin.currentChroma >= 2) { - const name = skins.find((s) => s.uuid === skin.uuid).chromas[skin.currentChroma-1].displayName.replace(/[\r\n]+/g, '').replace(skin.displayName, '') - const match = name.match(/variante\s+[1-4]\s+([^)]+)/) - const result = match ? match[2] : null; - if (match) { - return match[1].trim() - } else { - return name - } - } - if (skin.currentChroma === 1) { - return 'Base' - } - return '' - }; - - if (activeSearchs[searchId] && activeSearchs[searchId].userId === req.body.member.user.id) { - if (activeSearchs[searchId].page === activeSearchs[searchId].amount-1) { - activeSearchs[searchId].page = 0 - } else { - activeSearchs[searchId].page++ - } - } else { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Tu n'est pas à l'origine de cette commande /search`, - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } - - const trueSkin = skins.find((s) => s.uuid === activeSearchs[searchId].resultSkins[activeSearchs[searchId].page].uuid); - const imageUrl = () => { - let res; - if (trueSkin.chromas[trueSkin.chromas.length-1].displayIcon) { - res = trueSkin.chromas[trueSkin.chromas.length-1].displayIcon - } else if (trueSkin.levels[trueSkin.levels.length-1].displayIcon) { - res = trueSkin.levels[trueSkin.levels.length-1].displayIcon - } else { - res = trueSkin.displayIcon - } - return res - }; - - const videoUrl = () => { - let res; - if (trueSkin.chromas[trueSkin.chromas.length-1].streamedVideo) { - res = trueSkin.chromas[trueSkin.chromas.length-1].streamedVideo - } else if (trueSkin.levels[trueSkin.levels.length-1].streamedVideo) { - res = trueSkin.levels[trueSkin.levels.length-1].streamedVideo - } else { - res = null - } - return res - }; - - const owner = await guild.members.fetch(activeSearchs[searchId].resultSkins[activeSearchs[searchId].page].user_id) - let fields = [ - { - name: `**${activeSearchs[searchId].resultSkins[activeSearchs[searchId].page].displayName}** | ${activeSearchs[searchId].resultSkins[activeSearchs[searchId].page].tierText}`, - value: `${activeSearchs[searchId].resultSkins[activeSearchs[searchId].page].maxPrice}€ ${activeSearchs[searchId].resultSkins[activeSearchs[searchId].page].user_id ? '| **@'+ owner.user.username +'** ✅' : ''}`, - inline: false, - } - ] - - try { - const originalComponents = req.body.message.components || []; - - await DiscordRequest( - activeSearchs[searchId].endpoint, - { - method: 'PATCH', - body: { - embeds: [ - { - title: `Résultat de recherche`, - description: `🔎 ${activeSearchs[searchId].searchValue}`, - fields: fields, - color: parseInt(activeSearchs[searchId].resultSkins[activeSearchs[searchId].page].tierColor, 16), - image: { url: imageUrl() }, - footer: { text: `${activeSearchs[searchId].page+1}/${activeSearchs[searchId].resultSkins.length} résultat(s)` }, - }, - ], - components: originalComponents, - }, - } - ); - } catch (err) { - console.log('Pas trouvé : ', err) - } - return res.send({ - type: InteractionResponseType.DEFERRED_UPDATE_MESSAGE, - }); - } - else if (componentId.startsWith('option_')) { - const optionId = parseInt(componentId.replace('option_', '')[0]); - const prediId = componentId.replace(`option_${optionId}_`, ''); - let intAmount = 10; - - const commandUserId = req.body.member.user.id - const commandUser = getUser.get(commandUserId); - if (!commandUser) return res.status(403).send({ message: 'Oups, je ne te connais pas'}) - if (commandUser.coins < intAmount) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: 'Tu n\'as pas assez de FlopoCoins', - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } - - const prediObject = activePredis[prediId] - if (!prediObject) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: 'Prédiction introuvable', - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } - - if (prediObject.endTime < Date.now()) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: 'Les votes de cette prédiction sont clos', - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } - - const otherOption = optionId === 0 ? 1 : 0; - if (prediObject.options[otherOption].votes.find(v => v.id === commandUserId) && commandUserId !== process.env.DEV_ID) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: 'Tu ne peux pas voter pour les 2 deux options', - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } - - let stopMaxCoins = false - if (prediObject.options[optionId].votes.find(v => v.id === commandUserId)) { - activePredis[prediId].options[optionId].votes.forEach(v => { - if (v.id === commandUserId) { - if (v.amount >= 250000) { - stopMaxCoins = true - return - } - if (v.amount + intAmount > 250000) { - intAmount = 250000-v.amount - } - v.amount += intAmount - } - }) - } else { - activePredis[prediId].options[optionId].votes.push({ - id: commandUserId, - amount: intAmount, - }) - } - - if (stopMaxCoins) { - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: 'Tu as déjà parié le max (250K)', - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } - - activePredis[prediId].options[optionId].total += intAmount - - activePredis[prediId].options[optionId].percent = (activePredis[prediId].options[optionId].total / (activePredis[prediId].options[otherOption].total + activePredis[prediId].options[optionId].total)) * 100 - activePredis[prediId].options[otherOption].percent = 100 - activePredis[prediId].options[optionId].percent - - io.emit('new-predi', { action: 'new vote' }); - - updateUserCoins.run({ - id: commandUserId, - coins: commandUser.coins - intAmount, - }) - insertLog.run({ - id: commandUserId + '-' + Date.now(), - user_id: commandUserId, - action: 'PREDI_VOTE', - target_user_id: null, - coins_amount: -intAmount, - user_new_amount: commandUser.coins - intAmount, - }) - io.emit('data-updated', { table: 'users', action: 'update' }); - - try { - const totalAmount = activePredis[prediId].options[optionId].votes.find(v => v.id === commandUserId)?.amount; - const optionLabel = activePredis[prediId].options[optionId].label; - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Vote enregistré, **${intAmount}** Flopocoins sur **"${optionLabel}"** (**${totalAmount}** au total)`, - flags: InteractionResponseFlags.EPHEMERAL, - }, - }); - } catch (err) { - console.log('Pas trouvé : ', err) - return res.send({ - type: InteractionResponseType.DEFERRED_UPDATE_MESSAGE, - }); - } - } - return; - } - - console.error('unknown interaction type', type); - return res.status(400).json({ error: 'unknown interaction type' }); -}); - -app.use(express.json()); -app.use('/public', express.static('public')); - -// Check flAPI -app.get('/check', (req, res) => { - res.status(200).json({ check: true, status: 'OK' }); -}); - -// Get all users ordered by coins -app.get('/users', (req, res) => { - const users = getAllUsers.all(); - res.json(users); -}); - -app.get('/users/by-elo', (req, res) => { - const users = getUsersByElo.all() - res.json(users); -}) - -app.get('/logs', async (req, res) => { - // purge old logs - await pruneOldLogs() - - return res.status(200).json(getLogs.all()) -}) - -app.post('/timedout', async (req, res) => { - const { userId } = req.body - const guild = await client.guilds.fetch(process.env.GUILD_ID); - - let member; - try { - member = await guild.members.fetch(userId); - } catch (e) { - return res.status(404).send({ message: 'Unknown member' }) - } - - return res.status(200).json({ isTimedOut: member?.communicationDisabledUntilTimestamp > Date.now()}) -}) - -// Get user's avatar -app.get('/user/:id/avatar', async (req, res) => { - try { - const userId = req.params.id; // Get the ID from route parameters - const user = await client.users.fetch(userId); - - if (!user) { - return res.status(404).json({ error: 'User not found' }); - } - - const avatarUrl = user.displayAvatarURL({ format: 'png', size: 256 }); - res.json({ avatarUrl }); - - } catch (error) { - console.error('Error fetching user avatar'); - res.status(500).json({ error: 'Failed to fetch avatar' }); - } -}) - -app.get('/user/:id/username', async (req, res) => { - try { - const userId = req.params.id; // Get the ID from route parameters - const user = await client.users.fetch(userId); - - if (!user) { - return res.status(404).json({ error: 'User not found' }); - } - - res.json({ user }); - } catch (error) { - console.error('Error fetching user'); - res.status(500).json({ error: 'Failed to fetch user' }); - } -}) - -app.get('/user/:id/sparkline', async (req, res) => { - try { - const userId = req.params.id - - const user = getUser.get(userId) - - if (!user) return res.status(404).send({ message: 'Utilisateur introuvable'}) - - return res.status(200).json({ sparkline: getUserLogs.all({user_id: userId}) }) - } catch (e) { - return res.status(500).send({ message: 'erreur'}) - } -}) - -app.get('/user/:id/elo', async (req, res) => { - try { - const userId = req.params.id - - const user = getUser.get(userId) - - if (!user) return res.status(404).send({ message: 'Utilisateur introuvable'}) - - const userElo = getUserElo.get({ id: userId }) - - if (!userElo) return res.status(200).json({ elo: null }) - - return res.status(200).json({ elo: userElo.elo }) - } catch (e) { - return res.status(500).send({ message: 'erreur'}) - } -}) - -app.get('/user/:id/elo-graph', async (req, res) => { - try { - const userId = req.params.id - - const user = getUser.get(userId) - - if (!user) return res.status(404).send({ message: 'Utilisateur introuvable'}) - - - const games = getUserGames.all({ user_id: userId }); - - if (!games) return res.status(404).send({ message: 'Aucune partie'}) - - let array = [] - games.forEach((game, index) => { - if (game.p1 === userId) { - array.push(game.p1_elo) - if (index === games.length - 1) array.push(game.p1_new_elo) - } else if (game.p2 === userId) { - array.push(game.p2_elo) - if (index === games.length - 1) array.push(game.p2_new_elo) - } - }) - - return res.status(200).json({ elo_graph: array }) - } catch (e) { - return res.status(500).send({ message: 'erreur'}) - } -}) - -// Get user's inventory -app.get('/user/:id/inventory', async (req, res) => { - try { - const userId = req.params.id; // Get the ID from route parameters - const user = await client.users.fetch(userId); - - if (!user) { - return res.status(404).json({ error: 'User not found' }); - } - - const inventory = getUserInventory.all({user_id: userId}); - res.json({ inventory }); - - } catch (error) { - console.error('Error fetching user avatar'); - res.status(500).json({ error: 'Failed to fetch inventory' }); - } -}) - -app.get('/user/:id/daily', async (req, res) => { - const userId = req.params.id - - const akhy = getUser.get(userId) - - if (!akhy) return res.status(404).send({ message: 'Utilisateur introuvable'}) - - if (akhy.dailyQueried) return res.status(403).send({ message: 'Récompense déjà récupérée'}) - - const amount = 200 - const coins = akhy.coins - - queryDailyReward.run(userId) - updateUserCoins.run({ - id: userId, - coins: coins + amount, - }) - insertLog.run({ - id: userId + '-' + Date.now(), - user_id: userId, - action: 'DAILY_REWARD', - target_user_id: null, - coins_amount: amount, - user_new_amount: coins + amount, - }) - io.emit('data-updated', { table: 'users', action: 'update' }); - - return res.status(200).send({ message: 'Récompense récupérée !' }) -}) - -// Get active polls -app.get('/polls', async (req, res) => { - try { - res.json({ activePolls }); - - } catch (error) { - console.error('Error fetching active polls'); - res.status(500).json({ error: 'Failed to fetch active polls' }); - } -}) - -// Send a custom message in the admin command channel -app.post('/send-message', (req, res) => { - const { userId, channelId, message } = req.body; - const channel = client.channels.cache.get(channelId); - - const user = getUser.get(userId); - - if (!user) return res.status(404).json({ error: 'User not found' }); - - if (!channel) return res.status(404).json({ error: 'Channel not found' }); - - if (user.coins < 10) return res.status(403).json({ error: 'Pas assez de coins' }); - - updateUserCoins.run({ - id: userId, - coins: user.coins - 10, - }) - insertLog.run({ - id: userId + '-' + Date.now(), - user_id: userId, - action: 'SEND_MESSAGE', - target_user_id: null, - coins_amount: -10, - user_new_amount: user.coins - 10, - }) - io.emit('data-updated', { table: 'users', action: 'update' }); - - channel.send(message) - .then(() => res.json({ success: true })) - .catch(err => res.status(500).json({ error: err.message })); -}); - -// Change user's server specific username -app.post('/change-nickname', async (req, res) => { - const { userId, nickname, commandUserId } = req.body; - - const commandUser = getUser.get(commandUserId); - - if (!commandUser) return res.status(404).json({ message: 'Oups petit soucis' }); - - if (commandUser.coins < 1000) return res.status(403).json({ message: 'Pas assez de coins' }); - - try { - const guild = await client.guilds.fetch(process.env.GUILD_ID); - const member = await guild.members.fetch(userId); - await member.setNickname(nickname); - let message = nickname ? `Le pseudo de '${member.user.tag}' a été changé en '${nickname}'` : `Le pseudo de '${member.user.tag}' a été remis par défaut` - res.status(200).json({ message : message }); - updateUserCoins.run({ - id: commandUserId, - coins: commandUser.coins - 1000, - }) - insertLog.run({ - id: commandUserId + '-' + Date.now(), - user_id: commandUserId, - action: 'CHANGE_NICKNAME', - target_user_id: userId, - coins_amount: -1000, - user_new_amount: commandUser.coins - 1000, - }) - io.emit('data-updated', { table: 'users', action: 'update' }); - } catch (error) { - res.status(500).json({ message : `J'ai pas réussi à changer le pseudo` }); - } -}) - -app.post('/spam-ping', async (req, res) => { - const { userId, commandUserId } = req.body; - - const user = getUser.get(userId); - const commandUser = getUser.get(commandUserId); - - if (!commandUser || !user) return res.status(404).json({ message: 'Oups petit soucis' }); - - if (commandUser.coins < 10000) return res.status(403).json({ message: 'Pas assez de coins' }); - - try { - const discordUser = await client.users.fetch(userId); - - await discordUser.send(`<@${userId}>`) - - res.status(200).json({ message : 'C\'est parti ehehe' }); - - updateUserCoins.run({ - id: commandUserId, - coins: commandUser.coins - 10000, - }) - insertLog.run({ - id: commandUserId + '-' + Date.now(), - user_id: commandUserId, - action: 'SPAM_PING', - target_user_id: userId, - coins_amount: -10000, - user_new_amount: commandUser.coins - 10000, - }) - io.emit('data-updated', { table: 'users', action: 'update' }); - - for (let i = 0; i < 29; i++) { - await discordUser.send(`<@${userId}>`) - await sleep(1000); - } - } catch (err) { - console.log(err) - res.status(500).json({ message : "Oups ça n'a pas marché" }); - } -}) - -app.post('/timeout/vote', async (req, res) => { - const { commandUserId, voteKey, voteFor } = req.body; - - const commandUser = getUser.get(commandUserId); - const poll = activePolls[voteKey]; - const isVotingFor = voteFor; - - if (!commandUser) return res.status(404).json({ message: 'Oups petit soucis' }); - if (!poll) return res.status(404).json({ message: 'Vote de timeout introuvable' }); - - if (activePolls[voteKey]) { - const poll = activePolls[voteKey]; - poll.voters = poll.voters || []; - const voterId = commandUserId; - - const guild = await client.guilds.fetch(process.env.GUILD_ID) - const commandMember = await guild.members.fetch(commandUserId); - // Check if the voter has the required voting role - const voterRoles = commandMember.roles.cache.map(role => role.id) || []; - if (!voterRoles.includes(process.env.VOTING_ROLE_ID)) { - return res.status(403).json({ message: 'Tu n\'as pas le rôle requis pour voter'}) - } - - // Enforce one vote per eligible user - if (poll.voters.find(u => u === voterId)) { - return res.status(403).json({ message: 'Tu as déjà voté'}) - } - - // Record the vote - poll.voters.push(voterId); - if (isVotingFor) { - poll.for++; - } else { - poll.against++; - } - - io.emit('new-poll', { action: 'new vote' }); - - // Retrieve online eligible users (ensure your bot has the necessary intents) - const guildId = process.env.GUILD_ID; - const roleId = process.env.VOTING_ROLE_ID; // Set this in your .env file - const onlineEligibleUsers = await getOnlineUsersWithRole(guildId, roleId); - const votesNeeded = Math.max(0, poll.requiredMajority - poll.for); - - // Check if the majority is reached - if (poll.for >= poll.requiredMajority) { - try { - // Build the updated poll message content - let forText = '' - poll.voters.forEach((voter) => { - const user = getUser.get(voter); - forText += `- ${user.globalName}\n` - }) - await DiscordRequest( - poll.endpoint, - { - method: 'PATCH', - body: { - embeds: [ - { - title: `Timeout`, - description: `Proposition de timeout **${poll.toUsername}** pendant ${poll.time_display}`, - fields: [ - { - name: 'Votes totaux', - value: '✅ ' + poll.for + '\n' + forText, - inline: true, - }, - ], - color: 0xF2F3F3, // You can set the color of the embed - }, - ], - components: [], // remove buttons - }, - } - ); - } catch (err) { - console.error('Error updating poll message:', err); - } - // Clear the poll so the setTimeout callback doesn't fire later - delete activePolls[voteKey]; - - // **Actual Timeout Action** - try { - // Calculate the ISO8601 timestamp to disable communications until now + poll.time seconds - const timeoutUntil = new Date(Date.now() + poll.time * 1000).toISOString(); - const endpointTimeout = `guilds/${process.env.GUILD_ID}/members/${poll.toUserId}`; - await DiscordRequest(endpointTimeout, { - method: 'PATCH', - body: { communication_disabled_until: timeoutUntil }, - }); - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `<@${poll.toUserId}> a été timeout pendant ${poll.time_display} par décision démocratique 👊`, - }, - }); - } catch (err) { - console.error('Error timing out user:', err); - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Impossible de timeout <@${poll.toUserId}>, désolé... 😔`, - }, - }); - } - } - - // If the vote is "for", update the original poll message to reflect the new vote count. - if (isVotingFor) { - const remaining = Math.max(0, Math.floor((poll.endTime - Date.now()) / 1000)); - const minutes = Math.floor(remaining / 60); - const seconds = remaining % 60; - const countdownText = `**${minutes}m ${seconds}s** restantes`; - try { - // Build the updated poll message content - let forText = '' - poll.voters.forEach((voter) => { - const user = getUser.get(voter); - forText += `- ${user.globalName}\n` - }) - await DiscordRequest( - poll.endpoint, - { - method: 'PATCH', - body: { - embeds: [ - { - title: `Timeout`, - description: `**${poll.username}** propose de timeout **${poll.toUsername}** pendant ${poll.time_display}\nIl manque **${votesNeeded}** vote(s)`, - fields: [ - { - name: 'Pour', - value: '✅ ' + poll.for + '\n' + forText, - inline: true, - }, - { - name: 'Temps restant', - value: '⏳ ' + countdownText, - inline: false, - }, - ], - color: 0xF2F3F3, // You can set the color of the embed - }, - ], - components: req.body.message.components, // preserve the buttons - }, - } - ); - } catch (err) { - console.error('Error updating poll message:', err); - } - } - - return res.status(200).json({ message: 'Vote enregistré !'}) - } -}) - -app.post('/slowmode', async (req, res) => { - let { userId, commandUserId} = req.body - - const user = getUser.get(userId) - const commandUser = getUser.get(commandUserId); - - if (!commandUser || !user) return res.status(404).json({ message: 'Oups petit soucis' }); - - if (commandUser.coins < 10000) return res.status(403).json({ message: 'Pas assez de coins' }); - - if (!user) return res.status(403).send({ message: 'Oups petit problème'}) - - if (activeSlowmodes[userId]) { - if (userId === commandUserId) { - delete activeSlowmodes[userId]; - return res.status(200).json({ message: 'Slowmode retiré'}) - } else { - let timeLeft = (activeSlowmodes[userId].endAt - Date.now())/1000 - timeLeft = timeLeft > 60 ? (timeLeft/60).toFixed()?.toString() + 'min' : timeLeft.toFixed()?.toString() + 'sec' - return res.status(403).json({ message: `${user.globalName} est déjà en slowmode (${timeLeft})`}) - } - } else if (userId === commandUserId) { - return res.status(403).json({ message: 'Impossible de te mettre toi-même en slowmode'}) - } - - activeSlowmodes[userId] = { - userId: userId, - endAt: Date.now() + 60 * 60 * 1000, // 1 heure - lastMessage: null, - }; - io.emit('new-slowmode', { action: 'new slowmode' }); - - updateUserCoins.run({ - id: commandUserId, - coins: commandUser.coins - 10000, - }) - insertLog.run({ - id: commandUserId + '-' + Date.now(), - user_id: commandUserId, - action: 'SLOWMODE', - target_user_id: userId, - coins_amount: -10000, - user_new_amount: commandUser.coins - 10000, - }) - io.emit('data-updated', { table: 'users', action: 'update' }); - - return res.status(200).json({ message: `${user.globalName} est maintenant en slowmode pour 1h`}) -}) - -app.get('/slowmodes', async (req, res) => { - res.status(200).json({ slowmodes: activeSlowmodes }); -}) - -app.post('/start-predi', async (req, res) => { - let { commandUserId, label, options, closingTime, payoutTime } = req.body - - const commandUser = getUser.get(commandUserId) - - if (!commandUser) return res.status(403).send({ message: 'Oups petit problème'}) - if (commandUser.coins < 100) return res.status(403).send({ message: 'Tu n\'as pas assez de FlopoCoins'}) - - if (Object.values(activePredis).find(p => p.creatorId === commandUserId && (p.endTime > Date.now() && !p.closed))) { - return res.status(403).json({ message: `Tu ne peux pas lancer plus d'une prédi à la fois !`}) - } - - const startTime = Date.now() - const newPrediId = commandUserId?.toString() + '-' + startTime?.toString() - - let msgId; - try { - const guild = await client.guilds.fetch(process.env.GUILD_ID); - const generalChannel = guild.channels.cache.find( - ch => ch.name === 'général' || ch.name === 'general' - ); - const embed = new EmbedBuilder() - .setTitle(`Prédiction de ${commandUser.username}`) - .setDescription(`**${label}**`) - .addFields( - { name: `${options[0]}`, value: ``, inline: true }, - { name: ``, value: `ou`, inline: true }, - { name: `${options[1]}`, value: ``, inline: true } - ) - .setFooter({ text: `${formatTime(closingTime).replaceAll('*', '')} pour voter` }) - .setColor('#5865f2') - .setTimestamp(new Date()); - - const row = new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setCustomId(`option_0_${newPrediId}`) - .setLabel(`+10 sur '${options[0]}'`) - .setStyle(ButtonStyle.Primary), - new ButtonBuilder() - .setCustomId(`option_1_${newPrediId}`) - .setLabel(`+10 sur '${options[1]}'`) - .setStyle(ButtonStyle.Primary) - ); - - const row2 = new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setLabel('Voter sur FlopoSite') - .setURL(`${process.env.DEV_SITE === 'true' ? process.env.FLAPI_URL_DEV : process.env.FLAPI_URL}/dashboard`) - .setStyle(ButtonStyle.Link) - ) - - const msg = await generalChannel.send({ embeds: [embed], components: [row, row2] }); - msgId = msg.id; - } catch (e) { - return res.status(500).send({ message: 'Erreur lors de l\'envoi du message'}) - } - - const formattedOptions = [ - { label: options[0], votes: [], total: 0, percent: 0, }, - { label: options[1], votes: [], total: 0, percent: 0, }, - ] - activePredis[newPrediId] = { - creatorId: commandUserId, - label: label, - options: formattedOptions, - startTime: startTime, - closingTime: startTime + (closingTime * 1000), - endTime: startTime + (closingTime * 1000) + (payoutTime * 1000), - closed: false, - winning: null, - cancelledTime: null, - paidTime: null, - msgId: msgId, - }; - io.emit('new-predi', { action: 'new predi' }); - - updateUserCoins.run({ - id: commandUserId, - coins: commandUser.coins - 100, - }) - insertLog.run({ - id: commandUserId + '-' + Date.now(), - user_id: commandUserId, - action: 'START_PREDI', - target_user_id: null, - coins_amount: -100, - user_new_amount: commandUser.coins - 100, - }) - io.emit('data-updated', { table: 'users', action: 'update' }); - - return res.status(200).json({ message: `Ta prédi '${label}' a commencée !`}) -}) - -app.get('/predis', async (req, res) => { - const reversedPredis = Object.entries(activePredis).reverse(); - - const openEntries = []; - const closedEntries = []; - - for (const [key, value] of reversedPredis) { - if (value.closed === true) { - closedEntries.push([key, value]); - } else { - openEntries.push([key, value]); - } - } - - const reorderedPredis = Object.fromEntries([...openEntries, ...closedEntries]); - - res.status(200).json({ predis: reorderedPredis }); -}); - -app.post('/vote-predi', async (req, res) => { - const { commandUserId, predi, amount, option } = req.body - - let warning = false; - - let intAmount = parseInt(amount) - if (intAmount < 10 || intAmount > 250000) return res.status(403).send({ message: 'Montant invalide'}) - - const commandUser = getUser.get(commandUserId) - if (!commandUser) return res.status(403).send({ message: 'Oups, je ne te connais pas'}) - if (commandUser.coins < intAmount) return res.status(403).send({ message: 'Tu n\'as pas assez de FlopoCoins'}) - - const prediObject = activePredis[predi] - if (!prediObject) return res.status(403).send({ message: 'Prédiction introuvable'}) - - if (prediObject.endTime < Date.now()) return res.status(403).send({ message: 'Les votes de cette prédiction sont clos'}) - - const otherOption = option === 0 ? 1 : 0; - if (prediObject.options[otherOption].votes.find(v => v.id === commandUserId) && commandUserId !== process.env.DEV_ID) return res.status(403).send({ message: 'Tu ne peux pas voter pour les 2 deux options'}) - - if (prediObject.options[option].votes.find(v => v.id === commandUserId)) { - activePredis[predi].options[option].votes.forEach(v => { - if (v.id === commandUserId) { - if (v.amount === 250000) { - return res.status(403).send({ message: 'Tu as déjà parié le max (250K)'}) - } - if (v.amount + intAmount > 250000) { - intAmount = 250000-v.amount - warning = true - } - v.amount += intAmount - } - }) - } else { - activePredis[predi].options[option].votes.push({ - id: commandUserId, - amount: intAmount, - }) - } - activePredis[predi].options[option].total += intAmount - - activePredis[predi].options[option].percent = (activePredis[predi].options[option].total / (activePredis[predi].options[otherOption].total + activePredis[predi].options[option].total)) * 100 - activePredis[predi].options[otherOption].percent = 100 - activePredis[predi].options[option].percent - - io.emit('new-predi', { action: 'new vote' }); - - updateUserCoins.run({ - id: commandUserId, - coins: commandUser.coins - intAmount, - }) - insertLog.run({ - id: commandUserId + '-' + Date.now(), - user_id: commandUserId, - action: 'PREDI_VOTE', - target_user_id: null, - coins_amount: -intAmount, - user_new_amount: commandUser.coins - intAmount, - }) - io.emit('data-updated', { table: 'users', action: 'update' }); - - return res.status(200).send({ message : `Vote enregistré!` }); -}) - -app.post('/end-predi', async (req, res) => { - const { commandUserId, predi, confirm, winningOption } = req.body - - const commandUser = getUser.get(commandUserId) - if (!commandUser) return res.status(403).send({ message: 'Oups, je ne te connais pas'}) - if (commandUserId !== process.env.DEV_ID) return res.status(403).send({ message: 'Tu n\'as pas les permissions requises' }) - - const prediObject = activePredis[predi] - if (!prediObject) return res.status(403).send({ message: 'Prédiction introuvable'}) - if (prediObject.closed) return res.status(403).send({ message: 'Prédiction déjà close'}) - - if (!confirm) { - activePredis[predi].cancelledTime = new Date(); - activePredis[predi].options[0].votes.forEach((v) => { - const tempUser = getUser.get(v.id) - try { - updateUserCoins.run({ - id: v.id, - coins: tempUser.coins + v.amount - }) - insertLog.run({ - id: v.id + '-' + Date.now(), - user_id: v.id, - action: 'PREDI_REFUND', - target_user_id: v.id, - coins_amount: v.amount, - user_new_amount: tempUser.coins + v.amount, - }) - } catch (e) { - console.log(`Impossible de rembourser ${v.id} (${v.amount} coins)`) - } - }) - activePredis[predi].options[1].votes.forEach((v) => { - const tempUser = getUser.get(v.id) - try { - updateUserCoins.run({ - id: v.id, - coins: tempUser.coins + v.amount - }) - insertLog.run({ - id: v.id + '-' + Date.now(), - user_id: v.id, - action: 'PREDI_REFUND', - target_user_id: v.id, - coins_amount: v.amount, - user_new_amount: tempUser.coins + v.amount, - }) - } catch (e) { - console.log(`Impossible de rembourser ${v.id} (${v.amount} coins)`) - } - }) - activePredis[predi].closed = true; - } - else { - const losingOption = winningOption === 0 ? 1 : 0; - activePredis[predi].options[winningOption].votes.forEach((v) => { - const tempUser = getUser.get(v.id) - const ratio = activePredis[predi].options[winningOption].total === 0 ? 0 : activePredis[predi].options[losingOption].total / activePredis[predi].options[winningOption].total - try { - updateUserCoins.run({ - id: v.id, - coins: tempUser.coins + (v.amount * (1 + ratio)) - }) - insertLog.run({ - id: v.id + '-' + Date.now(), - user_id: v.id, - action: 'PREDI_RESULT', - target_user_id: v.id, - coins_amount: v.amount * (1 + ratio), - user_new_amount: tempUser.coins + (v.amount * (1 + ratio)), - }) - } catch (e) { - console.log(`Impossible de créditer ${v.id} (${v.amount} coins pariés, *${1 + ratio})`) - } - }) - activePredis[predi].paidTime = new Date(); - activePredis[predi].closed = true; - activePredis[predi].winning = winningOption; - } - - try { - const guild = await client.guilds.fetch(process.env.GUILD_ID); - const generalChannel = guild.channels.cache.find( - ch => ch.name === 'général' || ch.name === 'general' - ); - const message = await generalChannel.messages.fetch(activePredis[predi].msgId) - const updatedEmbed = new EmbedBuilder() - .setTitle(`Prédiction de ${commandUser.username}`) - .setDescription(`**${activePredis[predi].label}**`) - .setFields({ name: `${activePredis[predi].options[0].label}`, value: ``, inline: true }, - { name: ``, value: `ou`, inline: true }, - { name: `${activePredis[predi].options[1].label}`, value: ``, inline: true }, - ) - .setFooter({ text: `${activePredis[predi].cancelledTime !== null ? 'Prédi annulée' : 'Prédi confirmée !' }` }) - .setTimestamp(new Date()); - const row = new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setLabel('Voir') - .setURL(`${process.env.DEV_SITE === 'true' ? process.env.FLAPI_URL_DEV : process.env.FLAPI_URL}/dashboard`) - .setStyle(ButtonStyle.Link) - ) - await message.edit({ embeds: [updatedEmbed], components: [row] }); - } catch (err) { - console.error('Error updating prédi message:', err); - } - - io.emit('new-predi', { action: 'closed predi' }); - io.emit('data-updated', { table: 'users', action: 'fin predi' }); - - return res.status(200).json({ message: 'Prédi close' }); -}) - -// ADMIN Add coins -app.post('/add-coins', (req, res) => { - const { commandUserId } = req.body; - - const commandUser = getUser.get(commandUserId); - - if (!commandUser) return res.status(404).json({ error: 'User not found' }); - if (commandUserId !== process.env.DEV_ID) return res.status(404).json({ error: 'Not admin' }); - - updateUserCoins.run({ - id: commandUserId, - coins: commandUser.coins + 1000, - }) - insertLog.run({ - id: commandUserId + '-' + Date.now(), - user_id: commandUserId, - action: 'ADD_COINS', - target_user_id: commandUserId, - coins_amount: 1000, - user_new_amount: commandUser.coins + 1000, - }) - io.emit('data-updated', { table: 'users', action: 'update' }); - - res.status(200).json({ message : `+1000` }); -}); - -app.post('/buy-coins', (req, res) => { - const { commandUserId, coins } = req.body; - - const commandUser = getUser.get(commandUserId); - - if (!commandUser) return res.status(404).json({ error: 'User not found' }); - - updateUserCoins.run({ - id: commandUserId, - coins: commandUser.coins + coins, - }) - insertLog.run({ - id: commandUserId + '-' + Date.now(), - user_id: commandUserId, - action: 'ADD_COINS', - target_user_id: commandUserId, - coins_amount: coins, - user_new_amount: commandUser.coins + coins, - }) - io.emit('data-updated', { table: 'users', action: 'update' }); - - res.status(200).json({ message : `+${coins}` }); -}); - -const pokerRooms = {} -app.post('/create-poker-room', async (req, res) => { - const { creatorId } = req.body - const id = uuidv4() - const t12names = [ - 'cassoule', - 'passoule', - 'kiwiko', - 'piwiko', - 'wata', - 'pata', - 'apologize', - 'apologay', - 'daspoon', - 'esteban', - 'edorima', - 'momozhok', - 'popozhok', - 'dodozhok', - 'flopozhok', - 'thomas', - 'poma' - ] - const name = uniqueNamesGenerator({ dictionaries: [adjectives, t12names], separator: ' ', style: 'capital' }); - - const creator = await client.users.fetch(creatorId) - - if (!creator) { - return res.status(404).send({message: 'Utilisateur introuvable'}) - } - if (Object.values(pokerRooms).find(room => room.host_id === creatorId)) { - return res.status(403).send({message: 'Tu ne peux créer qu\'une seule table à la fois'}) - } - - const alreadyInARoom = Object.values(pokerRooms).find((room) => { - return Object.keys(room.players).includes(creatorId) - }) - - if (alreadyInARoom) return res.status(403).send({ message: 'Tu es déjà assis à une table' }) - - pokerRooms[id] = { - id: id, - host_id: creatorId, - host_name: creator.globalName, - name: name, - created_at: Date.now(), - last_move_at: Date.now(), - players: {}, - queue: {}, - afk: {}, - pioche: initialShuffledCards(), - tapis: [], - dealer: null, - sb: null, - bb: null, - highest_bet: null, - current_player: null, - current_turn: null, - playing: false, - winners: [], - waiting_for_restart: false, - fakeMoney: false, - } - - res.status(200).send({ roomId: id }) - - try { - const url = (process.env.DEV_SITE === 'true' ? process.env.API_URL_DEV : process.env.API_URL) + '/poker-room/join' - const response = await axios.post(url, { userId: creatorId, roomId: id }) - } catch (e) { - console.log(e) - } - - io.emit('new-poker-room') -}); - -app.get('/poker-rooms', (req, res) => { - return res.status(200).send({ rooms: pokerRooms }) -}) - -app.get('/poker-rooms/:id', (req, res) => { - return res.status(200).send({ room: pokerRooms[req.params.id] }) -}) - -app.post('/poker-room/join', async (req, res) => { - const { userId, roomId } = req.body - - const user = await client.users.fetch(userId) - - const alreadyInARoom = Object.values(pokerRooms).find((room) => { - return Object.keys(room.players).includes(userId) - }) - - if (alreadyInARoom) return res.status(403).send({ message: 'Déjà assis à une table' }) - - let amount = getUser.get(userId)?.coins - let fakeMoney = false - - if (!amount || amount < 1000) { - amount = 1000 - fakeMoney = true - } - - const player = { - id: user.id, - globalName: user.globalName, - hand: [], - bank: amount, - bet: null, - solve: null, - folded: false, - allin: false, - last_played_turn: null, - is_last_raiser: false, - } - - try { - if (pokerRooms[roomId].playing) { - pokerRooms[roomId].queue[userId] = player - } else { - pokerRooms[roomId].players[userId] = player - } - if (pokerRooms[roomId].afk[userId]) { - delete pokerRooms[roomId].afk[userId] - } - if (fakeMoney) pokerRooms[roomId].fakeMoney = true - } catch (e) { - // - } - - io.emit('new-poker-room') - return res.status(200) -}); - -app.post('/poker-room/accept', async (req, res) => { - const { userId, roomId } = req.body - - const player = pokerRooms[roomId].queue[userId] - - if (!player) return res.status(404).send({ message: 'Joueur introuvable dans le file d\'attente'}); - - try { - pokerRooms[roomId].players[userId] = player - delete pokerRooms[roomId].queue[userId] - if (pokerRooms[roomId].afk[userId]) { - delete pokerRooms[roomId].afk[userId] - } - } catch (e) { - // - } - - io.emit('new-poker-room') - return res.status(200) -}) - -app.post('/poker-room/kick', async (req, res) => { - //TODO -}) - -app.post('/poker-room/leave', async (req, res) => { - const { userId, roomId } = req.body - - if (!pokerRooms[roomId]) return res.status(404).send({ message: 'Table introuvable' }) - if (!pokerRooms[roomId].players[userId]) return res.status(404).send({ message: 'Joueur introuvable' }) - - if (pokerRooms[roomId].playing && (pokerRooms[roomId].current_turn !== null && pokerRooms[roomId].current_turn !== 4)) { - pokerRooms[roomId].afk[userId] = pokerRooms[roomId].players[userId] - - try { - pokerRooms[roomId].players[userId].folded = true - pokerRooms[roomId].players[userId].last_played_turn = pokerRooms[roomId].current_turn - } catch(e) { - console.log(e) - } - - io.emit('new-poker-room') - - return res.status(200) - } - - try { - delete pokerRooms[roomId].players[userId] - - if (userId === pokerRooms[roomId].host_id) { - const newHostId = Object.keys(pokerRooms[roomId].players).find(id => id !== userId) - if (!newHostId) { - delete pokerRooms[roomId] - } else { - pokerRooms[roomId].host_id = newHostId - } - } - } catch (e) { - // - } - - io.emit('new-poker-room') - return res.status(200) -}); - -app.post('/poker-room/start', async (req, res) => { - const { roomId } = req.body - - if (!pokerRooms[roomId]) return res.status(404).send({ message: 'Table introuvable' }) - - // preflop - try { - for (const playerId in pokerRooms[roomId].players) { - const player = pokerRooms[roomId].players[playerId] - for (let i = 0; i < 2; i++) { - if (pokerRooms[roomId].pioche.length > 0) { - player.hand.push(pokerRooms[roomId].pioche[0]) - pokerRooms[roomId].pioche.shift() - } - } - } - for (const playerId in pokerRooms[roomId].players) { - try { - const player = pokerRooms[roomId].players[playerId] - let fullHand = pokerRooms[roomId].tapis - player.solve = Hand.solve(fullHand.concat(player.hand), 'standard', false)?.descr - } catch (e) { - console.log('erreur lors du hand solver') - } - } - } catch (e) { - console.log(e) - } - - pokerRooms[roomId].dealer = Object.keys(pokerRooms[roomId].players)[0] - pokerRooms[roomId].sb = Object.keys(pokerRooms[roomId].players)[1] - pokerRooms[roomId].bb = Object.keys(pokerRooms[roomId].players)[2 % Object.keys(pokerRooms[roomId].players).length] - pokerRooms[roomId].players[Object.keys(pokerRooms[roomId].players)[1]].bet = 10 //SB - pokerRooms[roomId].players[Object.keys(pokerRooms[roomId].players)[1]].bank -= 10 //SB - pokerRooms[roomId].players[Object.keys(pokerRooms[roomId].players)[2 % Object.keys(pokerRooms[roomId].players).length]].bet = 20 //BB - pokerRooms[roomId].players[Object.keys(pokerRooms[roomId].players)[2 % Object.keys(pokerRooms[roomId].players).length]].bank -= 20 //BB - pokerRooms[roomId].highest_bet = 20 - pokerRooms[roomId].current_player = Object.keys(pokerRooms[roomId].players)[3 % Object.keys(pokerRooms[roomId].players).length] - pokerRooms[roomId].current_turn = 0; - - pokerRooms[roomId].players[pokerRooms[roomId].bb].last_played_turn = pokerRooms[roomId].current_turn - - if (!pokerRooms[roomId].fakeMoney) { - const DB_SBplayer = await getUser.get(Object.keys(pokerRooms[roomId].players)[1]) - const DB_BBplayer = await getUser.get(Object.keys(pokerRooms[roomId].players)[2 % Object.keys(pokerRooms[roomId].players).length]) - if (DB_SBplayer) { - updateUserCoins.run({ - id: DB_SBplayer.id, - coins: pokerRooms[roomId].players[DB_SBplayer.id].bank, - }) - insertLog.run({ - id: DB_SBplayer.id + '-' + Date.now(), - user_id: DB_SBplayer.id, - action: 'POKER_SMALL_BLIND', - target_user_id: DB_SBplayer.id, - coins_amount: -10, - user_new_amount: DB_SBplayer.coins - 10, - }) - } - if (DB_BBplayer) { - updateUserCoins.run({ - id: DB_BBplayer.id, - coins: pokerRooms[roomId].players[DB_BBplayer.id].bank, - }) - insertLog.run({ - id: DB_BBplayer.id + '-' + Date.now(), - user_id: DB_BBplayer.id, - action: 'POKER_BIG_BLIND', - target_user_id: DB_BBplayer.id, - coins_amount: -20, - user_new_amount: DB_BBplayer.coins - 20, - }) - } - io.emit('data-updated', {table: 'users', action: 'update'}); - } - - pokerRooms[roomId].playing = true - pokerRooms[roomId].last_move_at = Date.now() - - io.emit('new-poker-room') - return res.status(200) -}) - -async function handleRoomStart(roomId, dealerId = 0) { - if (!pokerRooms[roomId]) return false - - // preflop - try { - for (const playerId in pokerRooms[roomId].players) { - const player = pokerRooms[roomId].players[playerId] - for (let i = 0; i < 2; i++) { - if (pokerRooms[roomId].pioche.length > 0) { - player.hand.push(pokerRooms[roomId].pioche[0]) - pokerRooms[roomId].pioche.shift() - } - } - } - for (const playerId in pokerRooms[roomId].players) { - try { - const player = pokerRooms[roomId].players[playerId] - let fullHand = pokerRooms[roomId].tapis - player.solve = Hand.solve(fullHand.concat(player.hand), 'standard', false)?.descr - } catch(e) { - console.log('erreur lors du hand solver') - } - } - } catch (e) { - console.log(e) - } - - pokerRooms[roomId].dealer = Object.keys(pokerRooms[roomId].players)[(dealerId + 1) % Object.keys(pokerRooms[roomId].players).length] - pokerRooms[roomId].sb = Object.keys(pokerRooms[roomId].players)[(dealerId + 2) % Object.keys(pokerRooms[roomId].players).length] - pokerRooms[roomId].bb = Object.keys(pokerRooms[roomId].players)[(dealerId + 3) % Object.keys(pokerRooms[roomId].players).length] - pokerRooms[roomId].players[Object.keys(pokerRooms[roomId].players)[(dealerId + 2) % Object.keys(pokerRooms[roomId].players).length]].bet = 10 //SB - pokerRooms[roomId].players[Object.keys(pokerRooms[roomId].players)[(dealerId + 2) % Object.keys(pokerRooms[roomId].players).length]].bank -= 10 //SB - pokerRooms[roomId].players[Object.keys(pokerRooms[roomId].players)[(dealerId + 3) % Object.keys(pokerRooms[roomId].players).length]].bet = 20 //BB - pokerRooms[roomId].players[Object.keys(pokerRooms[roomId].players)[(dealerId + 3) % Object.keys(pokerRooms[roomId].players).length]].bank -= 20 //BB - pokerRooms[roomId].highest_bet = 20 - pokerRooms[roomId].current_player = Object.keys(pokerRooms[roomId].players)[(dealerId + 4) % Object.keys(pokerRooms[roomId].players).length] - pokerRooms[roomId].current_turn = 0; - - pokerRooms[roomId].players[pokerRooms[roomId].bb].last_played_turn = pokerRooms[roomId].current_turn - - if (!pokerRooms[roomId].fakeMoney) { - const DB_SBplayer = await getUser.get(Object.keys(pokerRooms[roomId].players)[(dealerId + 2) % Object.keys(pokerRooms[roomId].players).length]) - const DB_BBplayer = await getUser.get(Object.keys(pokerRooms[roomId].players)[(dealerId + 3) % Object.keys(pokerRooms[roomId].players).length]) - if (DB_SBplayer) { - updateUserCoins.run({ - id: DB_SBplayer.id, - coins: pokerRooms[roomId].players[DB_SBplayer.id].bank, - }) - insertLog.run({ - id: DB_SBplayer.id + '-' + Date.now(), - user_id: DB_SBplayer.id, - action: 'POKER_SMALL_BLIND', - target_user_id: DB_SBplayer.id, - coins_amount: -10, - user_new_amount: DB_SBplayer.coins - 10, - }) - } - if (DB_BBplayer) { - updateUserCoins.run({ - id: DB_BBplayer.id, - coins: pokerRooms[roomId].players[DB_BBplayer.id].bank, - }) - insertLog.run({ - id: DB_BBplayer.id + '-' + Date.now(), - user_id: DB_BBplayer.id, - action: 'POKER_BIG_BLIND', - target_user_id: DB_BBplayer.id, - coins_amount: -20, - user_new_amount: DB_BBplayer.coins - 20, - }) - } - io.emit('data-updated', {table: 'users', action: 'update'}); - } - - pokerRooms[roomId].playing = true - pokerRooms[roomId].last_move_at = Date.now() - - io.emit('new-poker-room') - return true -} - -app.post('/poker-room/flop', async (req, res) => { - const { roomId } = req.body - - if (!pokerRooms[roomId]) return res.status(404).send({ message: 'Table introuvable' }) - - //flop - pokerRooms[roomId].current_turn = 1 - try { - for (let i = 0; i < 3; i++) { - if (pokerRooms[roomId].pioche.length > 0) { - pokerRooms[roomId].tapis.push(pokerRooms[roomId].pioche[0]) - pokerRooms[roomId].pioche.shift() - } - } - await updatePokerPlayersSolve(roomId) - } catch(e) { - console.log(e) - } - - pokerRooms[roomId].current_player = getFirstActivePlayerAfterDealer(pokerRooms[roomId]) - pokerRooms[roomId].last_move_at = Date.now() - - io.emit('new-poker-room') - return res.status(200) -}); - -async function handleFlop(roomId) { - if (!pokerRooms[roomId]) return false - - //flop - pokerRooms[roomId].current_turn = 1 - try { - for (let i = 0; i < 3; i++) { - if (pokerRooms[roomId].pioche.length > 0) { - pokerRooms[roomId].tapis.push(pokerRooms[roomId].pioche[0]) - pokerRooms[roomId].pioche.shift() - } - } - await updatePokerPlayersSolve(roomId) - } catch(e) { - console.log(e) - } - - pokerRooms[roomId].current_player = getFirstActivePlayerAfterDealer(pokerRooms[roomId]) - pokerRooms[roomId].last_move_at = Date.now() - - io.emit('new-poker-room') - return true -} - -app.post('/poker-room/turn', async (req, res) => { - const { roomId } = req.body - - if (!pokerRooms[roomId]) return res.status(404).send({ message: 'Table introuvable' }) - - //turn - pokerRooms[roomId].current_turn = 2 - try { - if (pokerRooms[roomId].pioche.length > 0) { - pokerRooms[roomId].tapis.push(pokerRooms[roomId].pioche[0]) - pokerRooms[roomId].pioche.shift() - } - - await updatePokerPlayersSolve(roomId) - } catch(e) { - console.log(e) - } - - pokerRooms[roomId].current_player = getFirstActivePlayerAfterDealer(pokerRooms[roomId]) - pokerRooms[roomId].last_move_at = Date.now() - - io.emit('new-poker-room') - return res.status(200) -}); - -async function handleTurn(roomId) { - if (!pokerRooms[roomId]) return false - - //turn - pokerRooms[roomId].current_turn = 2 - try { - if (pokerRooms[roomId].pioche.length > 0) { - pokerRooms[roomId].tapis.push(pokerRooms[roomId].pioche[0]) - pokerRooms[roomId].pioche.shift() - } - - await updatePokerPlayersSolve(roomId) - } catch(e) { - console.log(e) - } - - pokerRooms[roomId].current_player = getFirstActivePlayerAfterDealer(pokerRooms[roomId]) - pokerRooms[roomId].last_move_at = Date.now() - - io.emit('new-poker-room') - return true -} - -app.post('/poker-room/river', async (req, res) => { - const { roomId } = req.body - - if (!pokerRooms[roomId]) return res.status(404).send({ message: 'Table introuvable' }) - - //river - pokerRooms[roomId].current_turn = 3 - try { - if (pokerRooms[roomId].pioche.length > 0) { - pokerRooms[roomId].tapis.push(pokerRooms[roomId].pioche[0]) - pokerRooms[roomId].pioche.shift() - } - - await updatePokerPlayersSolve(roomId) - } catch(e) { - console.log(e) - } - - pokerRooms[roomId].current_player = getFirstActivePlayerAfterDealer(pokerRooms[roomId]) - pokerRooms[roomId].last_move_at = Date.now() - - io.emit('new-poker-room') - return res.status(200) -}); - -async function handleRiver(roomId) { - if (!pokerRooms[roomId]) return false - - //river - pokerRooms[roomId].current_turn = 3 - try { - if (pokerRooms[roomId].pioche.length > 0) { - pokerRooms[roomId].tapis.push(pokerRooms[roomId].pioche[0]) - pokerRooms[roomId].pioche.shift() - } - - await updatePokerPlayersSolve(roomId) - } catch(e) { - console.log(e) - } - - pokerRooms[roomId].current_player = getFirstActivePlayerAfterDealer(pokerRooms[roomId]) - pokerRooms[roomId].last_move_at = Date.now() - - io.emit('new-poker-room') - return true -} - -app.post('/poker-room/showdown', async (req, res) => { - const { roomId } = req.body - - if (!pokerRooms[roomId]) return res.status(404).send({ message: 'Table introuvable' }) - - //showdown - pokerRooms[roomId].current_turn = 4 - pokerRooms[roomId].current_player = null - - await updatePokerPlayersSolve(roomId) - - pokerRooms[roomId].winners = checkRoomWinners(pokerRooms[roomId]) - - try { - const url = (process.env.DEV_SITE === 'true' ? process.env.API_URL_DEV : process.env.API_URL) + '/poker-room/winner' - const response = await axios.post(url, { roomId: roomId, winnerIds: pokerRooms[roomId].winners }) - } catch (e) { - console.log(e) - } - - pokerRooms[roomId].last_move_at = Date.now() - - io.emit('new-poker-room') - return res.status(200) -}) - -async function handleShowdown(roomId) { - if (!pokerRooms[roomId]) return false - - //showdown - pokerRooms[roomId].current_turn = 4 - pokerRooms[roomId].current_player = null - - await updatePokerPlayersSolve(roomId) - - pokerRooms[roomId].winners = checkRoomWinners(pokerRooms[roomId]) - - try { - await handleWinner(roomId, pokerRooms[roomId].winners) - } catch (e) { - console.log(e) - } - - pokerRooms[roomId].last_move_at = Date.now() - - io.emit('new-poker-room') - return true -} - -app.post('/poker-room/progressive-showdown', async (req, res) => { - const { roomId } = req.body - - if (!pokerRooms[roomId]) return res.status(404).send({ message: 'Table introuvable' }) - - while(pokerRooms[roomId].current_turn < 4) { - let allGood = true - switch (pokerRooms[roomId].current_turn) { - case 0: - allGood = await handleFlop(roomId) - break; - case 1: - allGood = await handleTurn(roomId) - break; - case 2: - allGood = await handleRiver(roomId) - break; - case 3: - allGood = await handleShowdown(roomId) - break; - default: - allGood = false - break; - } - - if (!allGood) console.log('error in progressive showdown') - - await sleep(1000) - } - - return res.status(200) -}) - -app.post('/poker-room/winner', async (req, res) => { - const { roomId, winnerIds } = req.body - - if (!pokerRooms[roomId]) return res.status(404).send({ message: 'Table introuvable' }) - //if (!pokerRooms[roomId].players[winnerIds]) return res.status(404).send({ message: 'Joueur introuvable' }) - - pokerRooms[roomId].current_player = null - pokerRooms[roomId].current_turn = 4 - - let pool = 0; - for (const playerId in pokerRooms[roomId].players) { - const player = pokerRooms[roomId].players[playerId] - pool += player?.bet ?? 0 - player.bet = 0 - if (player.bank === 0 && !pokerRooms[roomId].winners.includes(player.id)) { - try { - delete pokerRooms[roomId].players[player.id] - - if (player.id === pokerRooms[roomId].host_id) { - const newHostId = Object.keys(pokerRooms[roomId].players).find(id => id !== player.id) - if (!newHostId) { - delete pokerRooms[roomId] - } else { - pokerRooms[roomId].host_id = newHostId - } - } - } catch (e) { - // - } - } - } - - pokerRooms[roomId].winners.forEach((winner) => { - pokerRooms[roomId].players[winner].bank += Math.floor(pool / winnerIds.length) - if (!pokerRooms[roomId].fakeMoney) { - const DBplayer = getUser.get(winner) - if (DBplayer) { - updateUserCoins.run({ - id: winner, - coins: pokerRooms[roomId].players[winner].bank, - }) - insertLog.run({ - id: winner + '-' + Date.now(), - user_id: winner, - action: 'POKER_WIN', - target_user_id: winner, - coins_amount: Math.floor(pool / winnerIds.length), - user_new_amount: pokerRooms[roomId].players[winner].bank, - }) - } - io.emit('data-updated', {table: 'users', action: 'update'}); - } - }); - - pokerRooms[roomId].waiting_for_restart = true - - io.emit('player-winner', { roomId: roomId, playerIds: winnerIds, amount: Math.floor(pool / winnerIds.length) }) - - await pokerEloHandler(pokerRooms[roomId]) - - io.emit('new-poker-room') - return res.status(200) -}) - -async function handleWinner(roomId, winnerIds) { - if (!pokerRooms[roomId]) return false - - pokerRooms[roomId].current_player = null - pokerRooms[roomId].current_turn = 4 - - let pool = 0; - for (const playerId in pokerRooms[roomId].players) { - const player = pokerRooms[roomId].players[playerId] - pool += player?.bet ?? 0 - player.bet = 0 - if (player.bank === 0 && !pokerRooms[roomId].winners.includes(player.id)) { - try { - delete pokerRooms[roomId].players[player.id] - } catch (e) { - // - } - } - } - - pokerRooms[roomId].winners = checkRoomWinners(pokerRooms[roomId]) - - pokerRooms[roomId]?.winners.forEach((winner) => { - pokerRooms[roomId].players[winner].bank += Math.floor(pool / winnerIds.length) - }); - - pokerRooms[roomId].waiting_for_restart = true - - io.emit('player-winner', { roomId: roomId, playerIds: pokerRooms[roomId].winners, amount: Math.floor(pool / winnerIds.length) }) - - await pokerEloHandler(pokerRooms[roomId]) - - io.emit('new-poker-room') - return true -} - -app.post('/poker-room/next-round', async (req, res) => { - const { roomId } = req.body - - if (!pokerRooms[roomId]) return res.status(404).send({ message: 'Table introuvable' }) - - const dealerId = Object.keys(pokerRooms[roomId].players).findIndex(p => p === pokerRooms[roomId].dealer) - console.log('dealer id', dealerId) - - pokerRooms[roomId].waiting_for_restart = false - pokerRooms[roomId].winners = [] - pokerRooms[roomId].pioche = initialShuffledCards() - pokerRooms[roomId].tapis = [] - pokerRooms[roomId].dealer = null - pokerRooms[roomId].sb = null - pokerRooms[roomId].bb = null - pokerRooms[roomId].highest_bet = null - pokerRooms[roomId].current_player = null - pokerRooms[roomId].current_turn = null - - for (const playerId in pokerRooms[roomId].afk) { - try { - delete pokerRooms[roomId].players[playerId] - } catch (e) { console.log(e) } - try { - delete pokerRooms[roomId].afk[playerId] - } catch (e) { console.log(e) } - } - - for (const playerId in pokerRooms[roomId].players) { - const player = pokerRooms[roomId].players[playerId] - player.hand = [] - player.bet = null - player.solve = null - player.folded = false - player.allin = false - player.last_played_turn = null - player.is_last_raiser = false - } - - try { - await handleRoomStart(roomId, dealerId) - } catch (e) { - console.log(e) - } - - io.emit('new-poker-room') - return res.status(200) -}) - -app.post('/poker-room/action/fold', async (req, res) => { - const { roomId, playerId } = req.body - - if (!pokerRooms[roomId]) return res.status(404).send({ message: 'Table introuvable' }) - if (!pokerRooms[roomId].players[playerId]) return res.status(404).send({ message: 'Joueur introuvable' }) - - if (pokerRooms[roomId].current_player !== playerId) return res.status(403).send({ message: 'Ce n\'est pas ton tour' }); - - try { - pokerRooms[roomId].players[playerId].folded = true - pokerRooms[roomId].players[playerId].last_played_turn = pokerRooms[roomId].current_turn - - io.emit('player-fold', { roomId: roomId, playerId: playerId, playerName: pokerRooms[roomId].players[playerId].globalName }) - - await checksAfterPokerAction(roomId) - - io.emit('new-poker-room') - } catch(e) { - console.log(e) - return res.status(500).send({ message: e}) - } - - return res.status(200) -}); - -app.post('/poker-room/action/check', async (req, res) => { - const { roomId, playerId } = req.body - - if (!pokerRooms[roomId]) return res.status(404).send({ message: 'Table introuvable' }) - if (!pokerRooms[roomId].players[playerId]) return res.status(404).send({ message: 'Joueur introuvable' }) - - if (pokerRooms[roomId].current_player !== playerId) return res.status(403).send({ message: 'Ce n\'est pas ton tour' }); - - try { - pokerRooms[roomId].players[playerId].last_played_turn = pokerRooms[roomId].current_turn - - io.emit('player-check', { roomId: roomId, playerId: playerId, playerName: pokerRooms[roomId].players[playerId].globalName }) - - await checksAfterPokerAction(roomId) - - io.emit('new-poker-room') - } catch(e) { - console.log(e) - return res.status(500).send({ message: e}) - } - - return res.status(200) -}); - -app.post('/poker-room/action/call', async (req, res) => { - const { roomId, playerId } = req.body - - if (!pokerRooms[roomId]) return res.status(404).send({ message: 'Table introuvable' }) - if (!pokerRooms[roomId].players[playerId]) return res.status(404).send({ message: 'Joueur introuvable' }) - - if (pokerRooms[roomId].current_player !== playerId) return res.status(403).send({ message: 'Ce n\'est pas ton tour' }); - - try { - let diff = pokerRooms[roomId].highest_bet - pokerRooms[roomId].players[playerId].bet - if (diff > pokerRooms[roomId].players[playerId].bank) { - diff = pokerRooms[roomId].players[playerId].bank - pokerRooms[roomId].players[playerId].allin = true - } - pokerRooms[roomId].players[playerId].bet += diff - pokerRooms[roomId].players[playerId].bank -= diff - pokerRooms[roomId].players[playerId].last_played_turn = pokerRooms[roomId].current_turn - - if (Object.values(pokerRooms[roomId].players).find(p => p.allin)) pokerRooms[roomId].players[playerId].allin = true - if (!pokerRooms[roomId].fakeMoney) { - const DBplayer = await getUser.get(playerId) - if (DBplayer) { - updateUserCoins.run({ - id: playerId, - coins: pokerRooms[roomId].players[playerId].bank, - }) - insertLog.run({ - id: playerId + '-' + Date.now(), - user_id: playerId, - action: 'POKER_CALL', - target_user_id: playerId, - coins_amount: -diff, - user_new_amount: pokerRooms[roomId].players[playerId].bank, - }) - } - io.emit('data-updated', { table: 'users', action: 'update' }); - } - - io.emit('player-call', { roomId: roomId, playerId: playerId, playerName: pokerRooms[roomId].players[playerId].globalName }) - - await checksAfterPokerAction(roomId) - - io.emit('new-poker-room') - } catch(e) { - console.log(e) - return res.status(500).send({ message: e}) - } - - return res.status(200) -}); - -app.post('/poker-room/action/raise', async (req, res) => { - const { roomId, playerId, amount } = req.body - - if (!pokerRooms[roomId]) return res.status(404).send({ message: 'Table introuvable' }) - if (!pokerRooms[roomId].players[playerId]) return res.status(404).send({ message: 'Joueur introuvable' }) - - if (pokerRooms[roomId].current_player !== playerId) return res.status(403).send({ message: 'Ce n\'est pas ton tour' }); - if (amount > pokerRooms[roomId].players[playerId].bank) return res.status(403).send({ message: 'Tu n\as pas assez'}); - - try { - if (amount === pokerRooms[roomId].players[playerId].bank) { - pokerRooms[roomId].players[playerId].allin = true - } - pokerRooms[roomId].players[playerId].bet += amount - pokerRooms[roomId].players[playerId].bank -= amount - pokerRooms[roomId].players[playerId].last_played_turn = pokerRooms[roomId].current_turn - for (let id in pokerRooms[roomId].players) { - pokerRooms[roomId].players[id].is_last_raiser = false - } - pokerRooms[roomId].players[playerId].is_last_raiser = true - pokerRooms[roomId].highest_bet = pokerRooms[roomId].players[playerId].bet - - if (!pokerRooms[roomId].fakeMoney) { - const DBplayer = await getUser.get(playerId) - if (DBplayer) { - updateUserCoins.run({ - id: playerId, - coins: DBplayer.coins - amount, - }) - insertLog.run({ - id: playerId + '-' + Date.now(), - user_id: playerId, - action: 'POKER_RAISE', - target_user_id: playerId, - coins_amount: -amount, - user_new_amount: DBplayer.coins - amount, - }) - } - io.emit('data-updated', { table: 'users', action: 'update' }); - } - - io.emit('player-raise', { roomId: roomId, playerId: playerId, amount: amount, playerName: pokerRooms[roomId].players[playerId].globalName }) - - await checksAfterPokerAction(roomId) - - io.emit('new-poker-room') - } catch(e) { - console.log(e) - return res.status(500).send({ message: e}) - } - - return res.status(200) -}); - -async function checksAfterPokerAction(roomId) { - const data = checkEndOfBettingRound(pokerRooms[roomId]) - - if (data.winner !== null) { - try { - pokerRooms[roomId].winners = [data.winner] - const url = (process.env.DEV_SITE === 'true' ? process.env.API_URL_DEV : process.env.API_URL) + '/poker-room/winner' - const response = await axios.post(url, { roomId: roomId, winnerIds: [data.winner] }) - } catch (e) { - console.log(e) - } - } else if (data.endRound) { - try { - const url = (process.env.DEV_SITE === 'true' ? process.env.API_URL_DEV : process.env.API_URL) + '/poker-room/' + data.nextPhase - const response = await axios.post(url, { roomId: roomId}) - } catch (e) { - console.log(e) - } - } else { - pokerRooms[roomId].current_player = getNextActivePlayer(pokerRooms[roomId]) - } - - pokerRooms[roomId].last_move_at = Date.now() - - io.emit('new-poker-room') -} - -async function updatePokerPlayersSolve(roomId) { - for (const playerId in pokerRooms[roomId].players) { - const player = pokerRooms[roomId].players[playerId] - let fullHand = pokerRooms[roomId].tapis - if (!fullHand && !player.hand) { - player.solve = Hand.solve([], 'standard', false)?.descr - } else if (!fullHand) { - player.solve = Hand.solve(player.hand, 'standard', false)?.descr - } else if (!player.hand) { - player.solve = Hand.solve(fullHand, 'standard', false)?.descr - } else { - player.solve = Hand.solve(fullHand.concat(player.hand), 'standard', false)?.descr - } - } -} - -app.get('/solitaire/sotd/rankings', async (req, res) => { - const rankings = getAllSOTDStats.all() - - return res.json({ rankings }) -}) - -app.post('/solitaire/start', async (req, res) => { - const userId = req.body.userId; - let userSeed = req.body.userSeed; - - if (activeSolitaireGames[userId] && !activeSolitaireGames[userId].isSOTD) { - return res.json({ succes: true, gameState: activeSolitaireGames[userId]}) - } - - if (userSeed) { - let numericSeed = 0 - for (let i = 0; i < userSeed.length; i++) { - numericSeed = (numericSeed + userSeed.charCodeAt(i)) & 0xFFFFFFFF; - } - - const rng = createSeededRNG(numericSeed); - const deck = createDeck() - const shuffledDeck = seededShuffle(deck, rng); - const gameState = deal(shuffledDeck); - gameState.seed = userSeed; - - activeSolitaireGames[userId] = gameState; - - return res.json({ success: true, gameState }); - } else { - const newRandomSeed = Date.now()?.toString(36) + Math.random()?.toString(36).substr(2); - let numericSeed = 0; - for (let i = 0; i < newRandomSeed.length; i++) { - numericSeed = (numericSeed + newRandomSeed.charCodeAt(i)) & 0xFFFFFFFF; - } - - const rng = createSeededRNG(numericSeed); - const deck = createDeck(); - const shuffledDeck = seededShuffle(deck, rng); - const gameState = deal(shuffledDeck); - gameState.seed = newRandomSeed; - - activeSolitaireGames[userId] = gameState; - - return res.json({ success: true, gameState }); - } -}); - -app.post('/solitaire/start/sotd', async (req, res) => { - const userId = req.body.userId - const sotd = getSOTD.get(); - - const user = getUser.get(userId); - if (!user) { - return res.status(404).json({ error: 'User not found' }); - } - - if (activeSolitaireGames[userId] && activeSolitaireGames[userId].isSOTD) { - return res.json({ success: true, gameState: activeSolitaireGames[userId]}) - } - - const gameState = { - tableauPiles: JSON.parse(sotd.tableauPiles), - foundationPiles: JSON.parse(sotd.foundationPiles), - stockPile: JSON.parse(sotd.stockPile), - wastePile: JSON.parse(sotd.wastePile), - isDone: false, - isSOTD: true, - hasFinToday: false, - startTime: Date.now(), - endTime: null, - moves: 0, - score: 0, - seed: sotd.seed, - } - - activeSolitaireGames[userId] = gameState - res.json({ success: true, gameState }); -}) - -app.post('/solitaire/reset', async (req, res) => { - const userId = req.body.userId; - delete activeSolitaireGames[userId] - res.json({ success: true }); -}); - -/!** - * GET /solitaire/state/:userId - * Gets the current game state for a user. If no game exists, creates a new one. - *!/ -app.get('/solitaire/state/:userId', (req, res) => { - const { userId } = req.params; - let gameState = activeSolitaireGames[userId]; - /!*if (!gameState) { - const deck = shuffle(createDeck()); - gameState = deal(deck); - activeSolitaireGames[userId] = gameState; - }*!/ - res.json({ success: true, gameState }); -}); - -/!** - * POST /solitaire/move - * Receives all necessary move data from the frontend. - *!/ -app.post('/solitaire/move', async (req, res) => { - // Destructure the complete move data from the request body - // Frontend must send all these properties. - const { - userId, - sourcePileType, - sourcePileIndex, - sourceCardIndex, - destPileType, - destPileIndex - } = req.body; - - const gameState = activeSolitaireGames[userId]; - - if (!gameState) { - return res.status(404).json({ error: 'Game not found for this user.' }); - } - - // Pass the entire data object to the validation function - if (isValidMove(gameState, req.body)) { - // If valid, mutate the state - await moveCard(gameState, req.body); - const win = checkWinCondition(gameState); - if (win) { - gameState.isDone = true - if (gameState.isSOTD) { - gameState.hasFinToday = true; - gameState.endTime = Date.now(); - const userStats = getUserSOTDStats.get(userId); - if (userStats) { - if ( - (gameState.score > userStats.score) || - (gameState.score === userStats.score && gameState.moves < userStats.moves) || - (gameState.score === userStats.score && gameState.moves === userStats.moves && gameState.time < userStats.time) - ) { - deleteUserSOTDStats.run(userId); - insertSOTDStats.run({ - id: userId, - user_id: userId, - time: gameState.endTime - gameState.startTime, - moves: gameState.moves, - score: gameState.score, - }) - } - } else { - insertSOTDStats.run({ - id: userId, - user_id: userId, - time: gameState.endTime - gameState.startTime, - moves: gameState.moves, - score: gameState.score, - }) - const user = getUser.get(userId) - if (user) { - updateUserCoins.run({ id: userId, coins: user.coins + 1000 }); - insertLog.run({ - id: userId + '-' + Date.now(), - user_id: userId, - action: 'SOTD_WIN', - target_user_id: null, - coins_amount: 1000, - user_new_amount: user.coins + 1000, - }) - } - } - } - } - res.json({ success: true, gameState, win, endTime: win ? Date.now() : null }); - } else { - // If the move is invalid, send a specific error message - res.status(400).json({ error: 'Invalid move' }); - } -}); - -/!** - * POST /solitaire/draw - * Draws a card from the stock pile to the waste pile. - *!/ -app.post('/solitaire/draw', async (req, res) => { - const { userId } = req.body; - const gameState = activeSolitaireGames[userId]; - if (!gameState) { - return res.status(404).json({ error: `Game not found for user ${userId}` }); - } - await drawCard(gameState); - res.json({ success: true, gameState }); -}); - -import http from 'http'; -import { Server } from 'socket.io'; -import * as test from "node:test"; -const server = http.createServer(app); - -const io = new Server(server, { - cors: { - Origin: FLAPI_URL, - methods: ['GET', 'POST', 'PUT', 'OPTIONS'], - } -}); - -let queue = [] -let playingArray = [] - -let connect4Queue = [] -let connect4PlayingArray = [] -export const C4_ROWS = 6 -export const C4_COLS = 7 - -io.on('connection', (socket) => { - - socket.on('user-connected', async (user) => { - const username = getUser.get(user) - - queue = queue.filter(obj => obj !== user) - let names = []; - for (const n of queue) { - let name = await client.users.fetch(n) - names.push(name?.globalName) - } - io.emit('tictactoequeue', { allPlayers: playingArray, queue: names }) - - connect4Queue = connect4Queue.filter(obj => obj !== user) - let C4names = [] - for (const n of connect4Queue) { - let name = await client.users.fetch(n) - C4names.push(name?.globalName) - } - - io.emit('connect4queue', { allPlayers: playingArray, queue: C4names }) - }) - - socket.on('tictactoeconnection', async (e) => { - queue = queue.filter(obj => obj !== e.id) - let names = []; - for (const n of queue) { - let name = await client.users.fetch(n) - names.push(name?.globalName) - } - io.emit('tictactoequeue', { allPlayers: playingArray, queue: names }) - }) - - socket.on('connect4connection', async (e) => { - connect4Queue = connect4Queue.filter(obj => obj !== e.id) - let names = []; - for (const n of connect4Queue) { - let name = await client.users.fetch(n) - names.push(name?.globalName) - } - io.emit('connect4queue', { allPlayers: playingArray, queue: names }) - }) - - socket.on('tictactoequeue', async (e) => { - console.log(`${e.playerId} in tic tac toe queue`); - - if (playingArray.find(obj => obj.p1.id === e.playerId || obj.p2.id === e.playerId)) { - let names = []; - for (const n of queue) { - let name = await client.users.fetch(n) - names.push(name?.globalName) - } - io.emit('tictactoequeue', { allPlayers: playingArray, queue: names }) - return - } - - let msgId; - - if (!queue.find(obj => obj === e.playerId)) { - queue.push(e.playerId) - - if (queue.length === 1) { - try { - const guild = await client.guilds.fetch(process.env.GUILD_ID); - const generalChannel = guild.channels.cache.find( - ch => ch.name === 'général' || ch.name === 'general' - ); - const user = await client.users.fetch(e.playerId) - - const embed = new EmbedBuilder() - .setTitle(`Tic Tac Toe`) - .setDescription(`**${user.username}** est dans la file d'attente`) - .setColor('#5865f2') - .setTimestamp(new Date()); - - const row = new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setLabel(`Jouer contre ${user.username}`) - .setURL(`${process.env.DEV_SITE === 'true' ? process.env.FLAPI_URL_DEV : process.env.FLAPI_URL}/tic-tac-toe`) - .setStyle(ButtonStyle.Link) - ) - - await generalChannel.send({ embeds: [embed], components: [row] }); - } catch (e) { - console.log(e) - } - } - } - - if (queue.length >= 2) { - let p1 = await client.users.fetch(queue[0]) - let p2 = await client.users.fetch(queue[1]) - - let msgId - try { - const guild = await client.guilds.fetch(process.env.GUILD_ID); - const generalChannel = guild.channels.cache.find( - ch => ch.name === 'général' || ch.name === 'general' - ); - - const embed = new EmbedBuilder() - .setTitle(`Tic Tac Toe`) - .setDescription(`### **❌ ${p1.globalName}** vs **${p2.globalName} ⭕**\n` + - `🟦🟦🟦\n` + - `🟦🟦🟦\n` + - `🟦🟦🟦\n`) - .setColor('#5865f2') - .setTimestamp(new Date()); - - const msg = await generalChannel.send({ embeds: [embed] }); - msgId = msg.id - } catch (e) { - console.log(e) - } - - let p1obj = { - id: queue[0], - name: p1.globalName, - val: 'X', - move: "", - } - let p2obj = { - id: queue[1], - name: p2.globalName, - val: 'O', - move: "", - } - - let lobby = { - p1: p1obj, - p2: p2obj, - sum: 1, - xs: [], - os: [], - lastmove: Date.now(), - msgId: msgId, - } - - playingArray.push(lobby) - - queue.splice(0, 2) - } - - let names = []; - for (const n of queue) { - let name = await client.users.fetch(n) - names.push(name?.globalName) - } - - io.emit('tictactoequeue', { allPlayers: playingArray, queue: names }) - }) - - socket.on('tictactoeplaying', async (e) => { - console.log('playing', e.value) - let lobbyToChange; - if (e.value === 'X') { - lobbyToChange = playingArray.find(obj => obj.p1.id === e.playerId) - - if (lobbyToChange.sum%2 === 1) { - console.log('yeah', e.value) - lobbyToChange.p2.move = '' - lobbyToChange.p1.move = e.boxId - lobbyToChange.sum++ - lobbyToChange.xs.push(e.boxId) - lobbyToChange.lastmove = Date.now() - } - } - else if (e.value === 'O') { - lobbyToChange = playingArray.find(obj => obj.p2.id === e.playerId) - - if (lobbyToChange.sum%2 === 0) { - console.log('yeah', e.value) - lobbyToChange.p1.move = '' - lobbyToChange.p2.move = e.boxId - lobbyToChange.sum++ - lobbyToChange.os.push(e.boxId) - lobbyToChange.lastmove = Date.now() - } - } - - let gridText = '' - for (let i = 1; i <= 9; i++) { - if (lobbyToChange.os.includes(i)) { - gridText += '⭕' - } else if (lobbyToChange.xs.includes(i)) { - gridText += '❌' - } else { - gridText += '🟦' - } - if (i%3 === 0) { - gridText += '\n' - } - } - - try { - const guild = await client.guilds.fetch(process.env.GUILD_ID); - const generalChannel = await guild.channels.cache.find( - ch => ch.name === 'général' || ch.name === 'general' - ); - - const message = await generalChannel.messages.fetch(lobbyToChange.msgId) - - const embed = new EmbedBuilder() - .setTitle(`Tic Tac Toe`) - .setDescription(`### **❌ ${lobbyToChange.p1.name}** vs **${lobbyToChange.p2.name} ⭕**\n` + gridText) - .setColor('#5865f2') - .setTimestamp(new Date()); - - await message.edit({ embeds: [embed] }); - } catch (e) { - console.log(e) - } - - io.emit('tictactoeplaying', { allPlayers: playingArray }) - }) - - socket.on('tictactoegameOver', async (e) => { - const winner = e.winner - const game = playingArray.find(obj => obj.p1.id === e.playerId) - - if (game && game.sum < 100) { - game.sum = 100 - let gridText = '' - for (let i = 1; i <= 9; i++) { - if (game.os.includes(i)) { - gridText += '⭕' - } else if (game.xs.includes(i)) { - gridText += '❌' - } else { - gridText += '🟦' - } - if (i%3 === 0) { - gridText += '\n' - } - } - - if (winner === null) { - await eloHandler(game.p1.id, game.p2.id, 0.5, 0.5, 'TICTACTOE') - - try { - const guild = await client.guilds.fetch(process.env.GUILD_ID); - const generalChannel = await guild.channels.cache.find( - ch => ch.name === 'général' || ch.name === 'general' - ); - - const message = await generalChannel.messages.fetch(game.msgId) - - const embed = new EmbedBuilder() - .setTitle(`Tic Tac Toe`) - .setDescription(`### **❌ ${game.p1.name}** vs **${game.p2.name} ⭕**\n` + gridText + `\n### Égalité`) - .setColor('#5865f2') - .setTimestamp(new Date()); - - await message.edit({ embeds: [embed] }); - } catch (e) { - console.log(e) - } - } else { - await eloHandler(game.p1.id, game.p2.id, game.p1.id === winner ? 1 : 0, game.p2.id === winner ? 1 : 0, 'TICTACTOE') - - try { - const guild = await client.guilds.fetch(process.env.GUILD_ID); - const generalChannel = await guild.channels.cache.find( - ch => ch.name === 'général' || ch.name === 'general' - ); - - const message = await generalChannel.messages.fetch(game.msgId) - - const embed = new EmbedBuilder() - .setTitle(`Tic Tac Toe`) - .setDescription(`### **❌ ${game.p1.name}** vs **${game.p2.name} ⭕**\n` + gridText + `\n### Victoire de ${game.p1.id === winner ? game.p1.name : game.p2.name}`) - .setColor('#5865f2') - .setTimestamp(new Date()); - - await message.edit({ embeds: [embed] }); - } catch (e) { - console.log(e) - } - - } - } - - playingArray = playingArray.filter(obj => obj.p1.id !== e.playerId) - }) - - socket.on('connect4queue', async (e) => { - console.log(`${e.playerId} in Connect 4 queue`); - - if (connect4PlayingArray.find(obj => obj.p1.id === e.playerId || obj.p2.id === e.playerId)) { - let names = []; - for (const n of connect4Queue) { - let name = await client.users.fetch(n); - names.push(name?.globalName); - } - io.emit('connect4queue', { allPlayers: connect4PlayingArray, queue: names }); - return - } - - if (!connect4Queue.find(obj => obj === e.playerId)) { - connect4Queue.push(e.playerId); - } - - if (connect4Queue.length === 1) { - try { - const guild = await client.guilds.fetch(process.env.GUILD_ID); - const generalChannel = guild.channels.cache.find( - ch => ch.name === 'général' || ch.name === 'general' - ); - const user = await client.users.fetch(e.playerId) - - const embed = new EmbedBuilder() - .setTitle(`Puissance 4`) - .setDescription(`**${user.username}** est dans la file d'attente`) - .setColor('#5865f2') - .setTimestamp(new Date()); - - const row = new ActionRowBuilder() - .addComponents( - new ButtonBuilder() - .setLabel(`Jouer contre ${user.username}`) - .setURL(`${process.env.DEV_SITE === 'true' ? process.env.FLAPI_URL_DEV : process.env.FLAPI_URL}/connect-4`) - .setStyle(ButtonStyle.Link) - ) - - await generalChannel.send({ embeds: [embed], components: [row] }); - } catch (e) { - console.log(e) - } - } - - if (connect4Queue.length >= 2) { - const p1Id = connect4Queue[0]; - const p2Id = connect4Queue[1]; - const p1 = await client.users.fetch(p1Id); - const p2 = await client.users.fetch(p2Id); - let msgId; - - const board = createConnect4Board(); - const boardText = formatConnect4BoardForDiscord(board); - - try { - const guild = await client.guilds.fetch(process.env.GUILD_ID); - const generalChannel = guild.channels.cache.find(ch => ch.name === 'général' || ch.name === 'general'); - const embed = new EmbedBuilder() - .setTitle('Puissance 4') - .setDescription(`**🔴 ${p1.globalName}** vs **${p2.globalName} 🟡**\n\n${boardText}`) - .setColor('#5865f2') - .setTimestamp(new Date()); - const msg = await generalChannel.send({ embeds: [embed] }); - msgId = msg.id; - } catch (err) { - console.error("Error sending Connect 4 start message:", err); - } - - const lobby = { - p1: { id: p1Id, name: p1.globalName, val: 'R' }, - p2: { id: p2Id, name: p2.globalName, val: 'Y' }, - turn: p1Id, - sum: 1, - board: board, - msgId: msgId, - gameOver: false, - lastmove: Date.now(), - winningPieces: [] - }; - - connect4PlayingArray.push(lobby); - connect4Queue.splice(0, 2); - } - - let names = []; - for (const n of connect4Queue) { - let name = await client.users.fetch(n); - names.push(name?.globalName); - } - io.emit('connect4queue', { allPlayers: connect4PlayingArray, queue: names }); - }); - - socket.on('connect4playing', async (e) => { - const lobby = connect4PlayingArray.find(l => (l.p1.id === e.playerId || l.p2.id === e.playerId) && !l.gameOver); - if (!lobby || lobby.turn !== e.playerId) return; - - const player = lobby.p1.id === e.playerId ? lobby.p1 : lobby.p2; - const col = e.col; - - // Drop the piece - lobby.lastmove = Date.now() - lobby.sum++ - let row; - for (row = C4_ROWS - 1; row >= 0; row--) { - if (lobby.board[row][col] === null) { - lobby.board[row][col] = player.val; - break; - } - } - - // Check for win - const winCheck = checkConnect4Win(lobby.board, player.val); - if (winCheck.win) { - lobby.gameOver = true; - lobby.winningPieces = winCheck.pieces; - await eloHandler(lobby.p1.id, lobby.p2.id, lobby.p1.id === player.id ? 1 : 0, lobby.p2.id === player.id ? 1 : 0, 'CONNECT4'); - io.emit('connect4gameOver', { game: lobby, winner: player.id }); - - connect4PlayingArray = connect4PlayingArray.filter(obj => obj.p1.id !== lobby.p1.id) - } - // Check for draw - else if (checkConnect4Draw(lobby.board)) { - lobby.gameOver = true; - await eloHandler(lobby.p1.id, lobby.p2.id, 0.5, 0.5, 'CONNECT4'); - io.emit('connect4gameOver', { game: lobby, winner: 'draw' }); - connect4PlayingArray = connect4PlayingArray.filter(obj => obj.p1.id !== lobby.p1.id) - } - // Switch turns - else { - lobby.turn = lobby.p1.id === player.id ? lobby.p2.id : lobby.p1.id; - } - - // Update Discord message - try { - const guild = await client.guilds.fetch(process.env.GUILD_ID); - const generalChannel = guild.channels.cache.find(ch => ch.name === 'général' || ch.name === 'general'); - const message = await generalChannel.messages.fetch(lobby.msgId); - let description = `**🔴 ${lobby.p1.name}** vs **${lobby.p2.name} 🟡**\n\n${formatConnect4BoardForDiscord(lobby.board)}`; - if (lobby.gameOver) { - if(winCheck.win) { - description += `\n\n### Victoire de ${player.name}!`; - } else { - description += `\n\n### Match Nul!`; - } - } - const embed = new EmbedBuilder() - .setTitle('Puissance 4') - .setDescription(description) - .setColor(lobby.gameOver ? '#2ade2a' : '#5865f2') - .setTimestamp(new Date()); - await message.edit({ embeds: [embed] }); - } catch (err) { - console.error("Error updating Connect 4 message:", err); - } - - if (!winCheck.win && !checkConnect4Draw(lobby.board)) { - io.emit('connect4playing', { allPlayers: connect4PlayingArray }); - } - }); - - socket.on('connect4NoTime', async (e) => { - const lobby = connect4PlayingArray.find(l => (l.p1.id === e.playerId || l.p2.id === e.playerId) && !l.gameOver); - const winner = e.winner - - if (lobby) { - lobby.gameOver = true; - await eloHandler(lobby.p1?.id, lobby.p2?.id, lobby.p1?.id === winner ? 1 : 0, lobby.p2?.id === winner ? 1 : 0, 'CONNECT4'); - - try { - const guild = await client.guilds.fetch(process.env.GUILD_ID); - const generalChannel = guild.channels.cache.find(ch => ch.name === 'général' || ch.name === 'general'); - const message = await generalChannel.messages.fetch(lobby.msgId); - let description = `**🔴 ${lobby.p1.name}** vs **${lobby.p2.name} 🟡**\n\n${formatConnect4BoardForDiscord(lobby.board)}`; - description += `\n\n### Victoire de ${lobby.p1.id === winner ? lobby.p1.name : lobby.p2.name}! (temps écoulé)`; - const embed = new EmbedBuilder() - .setTitle('Puissance 4') - .setDescription(description) - .setColor(lobby.gameOver ? '#2ade2a' : '#5865f2') - .setTimestamp(new Date()); - await message.edit({ embeds: [embed] }); - } catch (err) { - console.error("Error updating Connect 4 message:", err); - } - - try { - connect4PlayingArray = connect4PlayingArray.filter(obj => obj.p1.id !== lobby.p1.id) - } catch (e) { - console.log(e) - } - } - }); -}); - -server.listen(PORT, () => { - console.log(`Express+Socket.IO listening on port ${PORT}`); -}); - -*/ diff --git a/src/server/routes/api.js b/src/server/routes/api.js index e281b32..7aec515 100644 --- a/src/server/routes/api.js +++ b/src/server/routes/api.js @@ -18,7 +18,7 @@ import { DiscordRequest } from '../../api/discord.js'; // --- Discord.js Builder Imports --- import { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; import {emitDataUpdated, socketEmit} from "../socket.js"; -import {formatTime} from "../../../utils.js"; +import { formatTime } from "../../utils/index.js"; // Create a new router instance const router = express.Router(); diff --git a/src/server/routes/poker.js b/src/server/routes/poker.js index 21c7ca8..3acf2d0 100644 --- a/src/server/routes/poker.js +++ b/src/server/routes/poker.js @@ -38,7 +38,7 @@ export function pokerRoutes(client, io) { }); router.post('/create', async (req, res) => { - const { creatorId } = req.body; + const { creatorId, minBet, fakeMoney } = req.body; if (!creatorId) return res.status(400).json({ message: 'Creator ID is required.' }); if (Object.values(pokerRooms).some(room => room.host_id === creatorId || room.players[creatorId])) { @@ -51,14 +51,15 @@ export function pokerRoutes(client, io) { pokerRooms[id] = { id, host_id: creatorId, host_name: creator.globalName || creator.username, - name, created_at: Date.now(), last_move_at: Date.now(), + name, created_at: Date.now(), last_move_at: null, players: {}, queue: {}, afk: {}, pioche: initialShuffledCards(), tapis: [], dealer: null, sb: null, bb: null, highest_bet: 0, current_player: null, - current_turn: null, playing: false, winners: [], waiting_for_restart: false, fakeMoney: false, + current_turn: null, playing: false, winners: [], waiting_for_restart: false, fakeMoney: fakeMoney, + minBet: minBet, }; await joinRoom(id, creatorId, io); // Auto-join the creator - await emitPokerUpdate({ room: pokerRooms[id] }); + await emitPokerUpdate({ room: pokerRooms[id], type: 'room-created' }); res.status(201).json({ roomId: id }); }); @@ -69,6 +70,9 @@ export function pokerRoutes(client, io) { if (Object.values(pokerRooms).some(r => r.players[userId] || r.queue[userId])) { return res.status(403).json({ message: 'You are already in a room or queue.' }); } + if (!pokerRooms[roomId].fakeMoney && pokerRooms[roomId].minBet > (getUser.get(userId)?.coins ?? 0)) { + return res.status(403).json({ message: 'You do not have enough coins to join this room.' }); + } await joinRoom(roomId, userId, io); res.status(200).json({ message: 'Successfully joined.' }); @@ -82,16 +86,99 @@ export function pokerRoutes(client, io) { return res.status(403).json({ message: 'Unauthorized or player not in queue.' }); } + if (!room.fakeMoney) { + const userDB = getUser.get(playerId); + if (userDB) { + updateUserCoins.run({ id: playerId, coins: userDB.coins - room.minBet }); + insertLog.run({ + id: `${playerId}-poker-${Date.now()}`, + user_id: playerId, target_user_id: null, + action: 'POKER_JOIN', + coins_amount: -room.minBet, user_new_amount: userDB.coins - room.minBet, + }) + } + } + room.players[playerId] = room.queue[playerId]; delete room.queue[playerId]; - await emitPokerUpdate({ room: room }); + await emitPokerUpdate({ room: room, type: 'player-accepted' }); res.status(200).json({ message: 'Player accepted.' }); }); - router.post('/leave', (req, res) => { - // Implement leave logic... - res.status(501).json({ message: "Not Implemented" }); + router.post('/leave', async (req, res) => { + const { userId, roomId } = req.body + + if (!pokerRooms[roomId]) return res.status(404).send({ message: 'Table introuvable' }) + if (!pokerRooms[roomId].players[userId]) return res.status(404).send({ message: 'Joueur introuvable' }) + + if (pokerRooms[roomId].playing && (pokerRooms[roomId].current_turn !== null && pokerRooms[roomId].current_turn !== 4)) { + pokerRooms[roomId].afk[userId] = pokerRooms[roomId].players[userId] + + try { + pokerRooms[roomId].players[userId].folded = true + pokerRooms[roomId].players[userId].last_played_turn = pokerRooms[roomId].current_turn + if (pokerRooms[roomId].current_player === userId) { + await checkRoundCompletion(pokerRooms[roomId], io); + } + } catch(e) { + console.log(e) + } + + await emitPokerUpdate({ type: 'player-afk' }); + return res.status(200) + } + + try { + updatePlayerCoins(pokerRooms[roomId].players[userId], pokerRooms[roomId].players[userId].bank, pokerRooms[roomId].fakeMoney); + delete pokerRooms[roomId].players[userId] + + if (userId === pokerRooms[roomId].host_id) { + const newHostId = Object.keys(pokerRooms[roomId].players).find(id => id !== userId) + if (!newHostId) { + delete pokerRooms[roomId] + } else { + pokerRooms[roomId].host_id = newHostId + } + } + } catch (e) { + // + } + + await emitPokerUpdate({ type: 'player-left' }); + return res.status(200) + }); + + router.post('/kick', async (req, res) => { + const { commandUserId, userId, roomId } = req.body + + if (!pokerRooms[roomId]) return res.status(404).send({ message: 'Table introuvable' }) + if (!pokerRooms[roomId].players[commandUserId]) return res.status(404).send({ message: 'Joueur introuvable' }) + if (pokerRooms[roomId].host_id !== commandUserId) return res.status(403).send({ message: 'Seul l\'host peut kick' }) + if (!pokerRooms[roomId].players[userId]) return res.status(404).send({ message: 'Joueur introuvable' }) + + if (pokerRooms[roomId].playing && (pokerRooms[roomId].current_turn !== null && pokerRooms[roomId].current_turn !== 4)) { + return res.status(403).send({ message: 'Playing' }) + } + + try { + updatePlayerCoins(pokerRooms[roomId].players[userId], pokerRooms[roomId].players[userId].bank, pokerRooms[roomId].fakeMoney); + delete pokerRooms[roomId].players[userId] + + if (userId === pokerRooms[roomId].host_id) { + const newHostId = Object.keys(pokerRooms[roomId].players).find(id => id !== userId) + if (!newHostId) { + delete pokerRooms[roomId] + } else { + pokerRooms[roomId].host_id = newHostId + } + } + } catch (e) { + // + } + + await emitPokerUpdate({ type: 'player-kicked' }); + return res.status(200) }); // --- Game Action Endpoints --- @@ -152,7 +239,6 @@ export function pokerRoutes(client, io) { player.bank -= callAmount; player.bet += callAmount; if (player.bank === 0) player.allin = true; - updatePlayerCoins(player, -callAmount, room.fakeMoney); await emitPokerToast({ type: 'player-call', playerId: player.id, @@ -168,7 +254,6 @@ export function pokerRoutes(client, io) { player.bet += amount; if (player.bank === 0) player.allin = true; room.highest_bet = player.bet; - updatePlayerCoins(player, -amount, room.fakeMoney); await emitPokerToast({ type: 'player-raise', amount: amount, @@ -194,23 +279,29 @@ export function pokerRoutes(client, io) { async function joinRoom(roomId, userId, io) { const user = await client.users.fetch(userId); const userDB = getUser.get(userId); - const bank = (userDB?.coins ?? 0) >= 1000 ? userDB.coins : 1000; - const isFake = (userDB?.coins ?? 0) < 1000; + const room = pokerRooms[roomId]; const playerObject = { id: userId, globalName: user.globalName || user.username, - hand: [], bank: bank, bet: 0, folded: false, allin: false, + hand: [], bank: room.minBet, bet: 0, folded: false, allin: false, last_played_turn: null, solve: null }; - const room = pokerRooms[roomId]; if (room.playing) { room.queue[userId] = playerObject; } else { room.players[userId] = playerObject; + if (!room.fakeMoney) { + updateUserCoins.run({ id: userId, coins: userDB.coins - room.minBet }); + insertLog.run({ + id: `${userId}-poker-${Date.now()}`, + user_id: userId, target_user_id: null, + action: 'POKER_JOIN', + coins_amount: -room.minBet, user_new_amount: userDB.coins - room.minBet, + }) + } } - if (isFake) room.fakeMoney = true; await emitPokerUpdate({ room: room, type: 'player-joined' }); } @@ -218,7 +309,7 @@ async function startNewHand(room, io) { const playerIds = Object.keys(room.players); if (playerIds.length < 2) { room.playing = false; // Not enough players to continue - await emitPokerUpdate({ room: room }); + await emitPokerUpdate({ room: room, type: 'new-hand' }); return; } @@ -229,6 +320,7 @@ async function startNewHand(room, io) { room.winners = []; room.waiting_for_restart = false; room.highest_bet = 20; + room.last_move_at = Date.now(); // Rotate dealer const oldDealerIndex = playerIds.indexOf(room.dealer); @@ -248,9 +340,7 @@ async function startNewHand(room, io) { room.bb = bbPlayer.id; sbPlayer.bank -= 10; sbPlayer.bet = 10; - updatePlayerCoins(sbPlayer, -10, room.fakeMoney); bbPlayer.bank -= 20; bbPlayer.bet = 20; - updatePlayerCoins(bbPlayer, -20, room.fakeMoney); bbPlayer.last_played_turn = 0; room.current_player = playerIds[(dealerIndex + 3) % playerIds.length]; @@ -269,7 +359,7 @@ async function checkRoundCompletion(room, io) { } } else { room.current_player = getNextActivePlayer(room); - await emitPokerUpdate({ room: room }); + await emitPokerUpdate({ room: room, type: 'round-continue' }); } } @@ -293,19 +383,19 @@ async function advanceToNextPhase(room, io, phase) { await handleShowdown(room, io, checkRoomWinners(room)); return; case 'progressive-showdown': - await emitPokerUpdate({ room: room }); + await emitPokerUpdate({ room: room, type: 'progressive-showdown' }); while(room.tapis.length < 5) { await sleep(500); room.tapis.push(room.pioche.pop()); updatePlayerHandSolves(room); - await emitPokerUpdate({ room: room }); + await emitPokerUpdate({ room: room, type: 'progressive-showdown' }); } await handleShowdown(room, io, checkRoomWinners(room)); return; } updatePlayerHandSolves(room); // NEW: Update hand strength after new cards room.current_player = getFirstActivePlayerAfterDealer(room); - await emitPokerUpdate({ room: room }); + await emitPokerUpdate({ room: room, type: 'phase-advanced' }); } async function handleShowdown(room, io, winners) { @@ -313,6 +403,7 @@ async function handleShowdown(room, io, winners) { room.playing = false; room.waiting_for_restart = true; room.winners = winners; + room.current_player = null; let totalPot = 0; Object.values(room.players).forEach(p => { totalPot += p.bet; }); @@ -323,16 +414,20 @@ async function handleShowdown(room, io, winners) { const winnerPlayer = room.players[winnerId]; if(winnerPlayer) { winnerPlayer.bank += winAmount; - updatePlayerCoins(winnerPlayer, winAmount, room.fakeMoney); } }); + await clearAfkPlayers(room); + + console.log(room) + //await pokerEloHandler(room); - await emitPokerUpdate({ room: room }); + await emitPokerUpdate({ room: room, type: 'showdown' }); await emitPokerToast({ type: 'player-winner', playerIds: winners, roomId: room.id, + amount: winAmount, }) } @@ -352,12 +447,22 @@ function updatePlayerCoins(player, amount, isFake) { const user = getUser.get(player.id); if (!user) return; - const newCoins = player.bank; // The bank is the source of truth now - updateUserCoins.run({ id: player.id, coins: newCoins }); + const userDB = getUser.get(player.id); + updateUserCoins.run({ id: player.id, coins: userDB.coins + amount }); insertLog.run({ id: `${player.id}-poker-${Date.now()}`, user_id: player.id, target_user_id: null, action: `POKER_${amount > 0 ? 'WIN' : 'BET'}`, - coins_amount: amount, user_new_amount: newCoins, + coins_amount: amount, user_new_amount: userDB.coins + amount, }); +} + +async function clearAfkPlayers(room) { + Object.keys(room.afk).forEach(playerId => { + if (room.players[playerId]) { + updatePlayerCoins(room.players[playerId], room.players[playerId].bank, room.fakeMoney); + delete room.players[playerId]; + } + }); + room.afk = {}; } \ No newline at end of file diff --git a/src/server/routes/solitaire.js b/src/server/routes/solitaire.js index 02c850f..212a7e5 100644 --- a/src/server/routes/solitaire.js +++ b/src/server/routes/solitaire.js @@ -62,9 +62,9 @@ export function solitaireRoutes(client, io) { router.post('/start/sotd', (req, res) => { const { userId } = req.body; - if (!userId || !getUser.get(userId)) { + /*if (!userId || !getUser.get(userId)) { return res.status(404).json({ error: 'User not found.' }); - } + }*/ if (activeSolitaireGames[userId]?.isSOTD) { return res.json({ success: true, gameState: activeSolitaireGames[userId] }); @@ -177,7 +177,7 @@ function updateGameStats(gameState, actionType, moveData = {}) { } if(actionType === 'draw' && gameState.wastePile.length === 0) { // Penalty for cycling through an empty stock pile - gameState.score -= 100; + gameState.score -= 5; } } @@ -189,6 +189,7 @@ function handleWin(userId, gameState, io) { const timeTaken = gameState.endTime - gameState.startTime; const currentUser = getUser.get(userId); + if (!currentUser) return; const existingStats = getUserSOTDStats.get(userId); if (!existingStats) { diff --git a/utils.js b/utils.js deleted file mode 100644 index a38e6ab..0000000 --- a/utils.js +++ /dev/null @@ -1,314 +0,0 @@ -import 'dotenv/config'; -import OpenAI from "openai"; -import {GoogleGenAI} from "@google/genai"; -import {Mistral} from '@mistralai/mistralai'; -import pkg from 'pokersolver'; -import {flopoDB, getAllUsers} from "./init_database.js"; - -const { Hand } = pkg; - -export async function DiscordRequest(endpoint, options) { - // append endpoint to root API URL - const url = 'https://discord.com/api/v10/' + endpoint; - // Stringify payloads - if (options.body) options.body = JSON.stringify(options.body); - // Use fetch to make requests - const res = await fetch(url, { - headers: { - Authorization: `Bot ${process.env.DISCORD_TOKEN}`, - 'Content-Type': 'application/json; charset=UTF-8', - 'User-Agent': 'DiscordBot (https://github.com/discord/discord-example-app, 1.0.0)', - }, - ...options - }); - // throw API errors - if (!res.ok) { - const data = await res.json(); - console.log(res.status); - throw new Error(JSON.stringify(data)); - } - // return original response - return res; -} - -export async function InstallGlobalCommands(appId, commands) { - // API endpoint to overwrite global commands - const endpoint = `applications/${appId}/commands`; - - try { - // This is calling the bulk overwrite endpoint: https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands - await DiscordRequest(endpoint, { method: 'PUT', body: commands }); - } catch (err) { - console.error(err); - } -} - -// Simple method that returns a random emoji from list -export function getRandomEmoji(list=0) { - let emojiList - - switch (list) { - case 0: - emojiList = ['😭','😄','😌','🤓','😎','😤','🤖','😶‍🌫️','🌏','📸','💿','👋','🌊','✨'] - break - case 1: - emojiList = [ - '<:CAUGHT:1323810730155446322>', - '<:hinhinhin:1072510144933531758>', - '<:o7:1290773422451986533>', - '<:zhok:1115221772623683686>', - '<:nice:1154049521110765759>', - '<:nerd:1087658195603951666>', - '<:peepSelfie:1072508131839594597>', - ] - break - default: - emojiList = [''] - break - } - return emojiList[Math.floor(Math.random() * emojiList.length)]; -} -export function getRandomHydrateText() { - const texts = [ - `Hydratez-vous`, - `Pensez à vous hydratez`, - `Vous vous êtes hydratez aujourd'hui ?`, - `Buvez de l'eau la team`, - `#etsi vous vous hydratiez`, - `Oubliez pas de vous hydratez`, - `Hydratez vous la team`, - `Hydratez vous c'est important`, - `Hydratez-vous`, - `Pensez à vous hydratez`, - `Vous vous êtes hydratez aujourd'hui ?`, - `Buvez de l'eau la team`, - `#etsi vous vous hydratiez`, - `Oubliez pas de vous hydratez`, - `Hydratez vous la team`, - `Hydratez vous c'est important`, - `Hydratez-vous`, - `Pensez à vous hydratez`, - `Vous vous êtes hydratez aujourd'hui ?`, - `Buvez de l'eau la team`, - `#etsi vous vous hydratiez`, - `Oubliez pas de vous hydratez`, - `Hydratez vous la team`, - `Hydratez vous c'est important`, - `nsm ojd ça s'hydrate pas`, - ]; - return texts[Math.floor(Math.random() * texts.length)]; -} - -export const initialCards = [ - 'Ad', '2d', '3d', '4d', '5d', '6d', '7d', '8d', '9d', 'Td', 'Jd', 'Qd', 'Kd', - 'As', '2s', '3s', '4s', '5s', '6s', '7s', '8s', '9s', 'Ts', 'Js', 'Qs', 'Ks', - 'Ac', '2c', '3c', '4c', '5c', '6c', '7c', '8c', '9c', 'Tc', 'Jc', 'Qc', 'Kc', - 'Ah', '2h', '3h', '4h', '5h', '6h', '7h', '8h', '9h', 'Th', 'Jh', 'Qh', 'Kh', -] - -export function initialShuffledCards() { - return initialCards.sort((a, b) => 0.5 - Math.random()); -} -export function capitalize(str) { - return str.charAt(0).toUpperCase() + str.slice(1); -} - -export async function getOnlineUsersWithRole(guildId, roleId) { - const endpoint = `/guilds/${guildId}/members?limit=1000`; - const response = await DiscordRequest(endpoint, { method: 'GET' }); - - const members = await response.json(); - return members.filter( - (m) => - m.roles.includes(roleId) && - m.presence?.status !== 'offline' - ); -} - -export function formatTime(time) { - const days = Math.floor(time / (24 * 60 * 60)); - const hours = Math.floor((time % (24 * 60 * 60)) / (60 * 60)); - const minutes = Math.floor((time % (60 * 60)) / 60); - const seconds = time % 60; - - const parts = []; - - if (days > 0) parts.push(`**${days}** jour${days > 1 ? 's' : ''}`); - if (hours > 0) parts.push(`**${hours}** heure${hours > 1 ? 's' : ''}`); - if (minutes > 0) parts.push(`**${minutes}** minute${minutes > 1 ? 's' : ''}`); - if (seconds > 0 || parts.length === 0) parts.push(`**${seconds}** seconde${seconds > 1 ? 's' : ''}`); - - return parts.join(', ').replace(/,([^,]*)$/, ' et$1'); -} - -const openai = new OpenAI(); -const gemini = new GoogleGenAI({ apiKey: process.env.GEMINI_KEY}) -const leChat = new Mistral({apiKey: process.env.MISTRAL_KEY}); - -export async function gork(messageHistory) { - if (process.env.MODEL === 'OpenAI') { - // OPEN AI - const completion = await openai.chat.completions.create({ - model: "gpt-4.1-mini", - messages: messageHistory, - }); - - return completion.choices[0].message.content; - } - else if (process.env.MODEL === 'Gemini') { - //GEMINI - const formattedHistory = messageHistory.map(msg => { - return `${msg.role}: ${msg.content}`; - }).join('\n'); - const response = await gemini.models.generateContent({ - model: "gemini-2.0-flash-lite", - contents: formattedHistory, - }) - return response.text - } else if (process.env.MODEL === 'Mistral') { - // MISTRAL - const chatResponse = await leChat.chat.complete({ - model: 'mistral-large-latest', - messages: messageHistory, - }) - - return chatResponse.choices[0].message.content - } else { - return "Pas d'IA" - } -} - -export async function getAPOUsers() { - const fetchUrl = process.env.APO_BASE_URL + '/users?serverId=' + (process.env.T12_GUILD_ID ?? process.env.GUILD_ID) - console.log(fetchUrl) - return await fetch(fetchUrl) - .then(response => { - if (!response.ok) { - throw new Error('Error fetching APO users') - } - return response.json() - }) - .catch(error => { - console.error('There was a problem with the fetch operation:', error); - }); -} - -export async function postAPOBuy(userId, amount) { - const fetchUrl = process.env.APO_BASE_URL + '/buy?serverId=' + (process.env.T12_GUILD_ID ?? process.env.GUILD_ID) + '&userId=' + userId + '&amount=' + amount - console.log(fetchUrl) - return await fetch(fetchUrl, { - method: 'POST', - }) - .then(response => response) - .catch(error => console.log('Post error:', error)) -} - -export function getFirstActivePlayerAfterDealer(room) { - const players = Object.values(room.players); - const dealerPosition = players.findIndex((p) => p.id === room.dealer); - for (let i = 1; i < players.length; i++) { - const nextPos = (dealerPosition + i) % players.length; - if (!players[nextPos].folded && !players[nextPos].allin) { - return players[nextPos].id; - } - } - return null; -} - -export function getNextActivePlayer(room) { - const players = Object.values(room.players); - const currentPlayerPosition = players.findIndex((p) => p.id === room.current_player); - for (let i = 1; i < players.length; i++) { - const nextPos = (currentPlayerPosition + i) % players.length; - if (!players[nextPos].folded && !players[nextPos].allin) { - return players[nextPos].id; - } - } - return null; -} - -export function checkEndOfBettingRound(room) { - const players = Object.values(room.players); - const activePlayers = players.filter((p) => !p.folded); - - if (activePlayers.length === 1) { - return { endRound: true, winner: activePlayers[0].id, nextPhase: null }; - } - - const allInPlayers = activePlayers.filter(p => p.allin) - if (allInPlayers.length === activePlayers.length) { - return { endRound: true, winner: null, nextPhase: 'progressive-showdown' }; - } - - const allActed = activePlayers.every(p => (p.bet === room.highest_bet || p.allin) && p.last_played_turn === room.current_turn); - - if (allActed) { - let nextPhase; - switch (room.current_turn) { - case 0: - nextPhase = 'flop'; - break; - case 1: - nextPhase = 'turn'; - break; - case 2: - nextPhase = 'river'; - break; - case 3: - nextPhase = 'showdown'; - break; - default: - nextPhase = null; - break; - } - return { endRound: true, winner: null, nextPhase: nextPhase }; - } - - return { endRound: false, winner: null, nextPhase: null }; -} - -export function checkRoomWinners(room) { - const players = Object.values(room.players); - const activePlayers = players.filter(p => !p.folded); - - const playerSolutions = activePlayers.map(player => ({ - id: player.id, - solution: Hand.solve([...room.tapis, ...player.hand], 'standard', false) - })); - - const solutions = playerSolutions.map(ps => ps.solution); - - const winningIndices = Hand.winners(solutions).map(winningHand => - solutions.findIndex(sol => sol === winningHand) - ); - - return winningIndices.map(index => playerSolutions[index].id); -} - -export async function pruneOldLogs() { - const users = flopoDB.prepare(` - SELECT user_id - FROM logs - GROUP BY user_id - HAVING COUNT(*) > ${process.env.LOGS_BY_USER} - `).all(); - - const transaction = flopoDB.transaction(() => { - for (const { user_id } of users) { - flopoDB.prepare(` - DELETE FROM logs - WHERE id IN ( - SELECT id FROM ( - SELECT id, - ROW_NUMBER() OVER (ORDER BY id DESC) AS rn - FROM logs - WHERE user_id = ? - ) - WHERE rn > ${process.env.LOGS_BY_USER} - ) - `).run(user_id); - } - }); - - transaction() -} \ No newline at end of file diff --git a/valo.js b/valo.js deleted file mode 100644 index f88b875..0000000 --- a/valo.js +++ /dev/null @@ -1,11 +0,0 @@ -export async function getValorantSkins(locale='fr-FR') { - const response = await fetch(`https://valorant-api.com/v1/weapons/skins?language=${locale}`, { method: 'GET' }); - const data = await response.json(); - return data.data -} - -export async function getSkinTiers(locale='fr-FR') { - const response = await fetch(`https://valorant-api.com/v1/contenttiers?language=${locale}`, { method: 'GET'}); - const data = await response.json(); - return data.data -} \ No newline at end of file