mirror of
https://github.com/cassoule/flopobot_v2.git
synced 2026-01-18 16:37:40 +01:00
almost there
This commit is contained in:
BIN
flopobot.db-shm
BIN
flopobot.db-shm
Binary file not shown.
BIN
flopobot.db-wal
BIN
flopobot.db-wal
Binary file not shown.
7
index.js
7
index.js
@@ -37,7 +37,12 @@ server.listen(PORT, async () => {
|
||||
console.log(`[Connected with ${FLAPI_URL}]`);
|
||||
|
||||
// Initial data fetch and setup
|
||||
await getAkhys(client);
|
||||
try {
|
||||
await getAkhys(client);
|
||||
} catch (error) {
|
||||
console.log('Initial Fetch Error');
|
||||
}
|
||||
|
||||
|
||||
// Setup scheduled tasks
|
||||
setupCronJobs(client, io);
|
||||
|
||||
@@ -14,10 +14,6 @@ export function initializeEvents(client, io) {
|
||||
// It's a good place for setup tasks that require the bot to be online.
|
||||
client.once('ready', async () => {
|
||||
console.log(`Bot is ready and logged in as ${client.user.tag}!`);
|
||||
// You can add any post-login setup tasks here if needed.
|
||||
// For example, setting the bot's activity:
|
||||
client.user.setActivity('FlopoSite.com', { type: 'WATCHING' });
|
||||
|
||||
console.log('[Startup] Bot is ready, performing initial data sync...');
|
||||
await getAkhys(client);
|
||||
console.log('[Startup] Setting up scheduled tasks...');
|
||||
|
||||
@@ -8,9 +8,11 @@ import { client } from '../bot/client.js';
|
||||
import { apiRoutes } from './routes/api.js';
|
||||
import { pokerRoutes } from './routes/poker.js';
|
||||
import { solitaireRoutes } from './routes/solitaire.js';
|
||||
import {getSocketIo} from "./socket.js";
|
||||
|
||||
// --- EXPRESS APP INITIALIZATION ---
|
||||
const app = express();
|
||||
const io = getSocketIo();
|
||||
const FLAPI_URL = process.env.DEV_SITE === 'true' ? process.env.FLAPI_URL_DEV : process.env.FLAPI_URL;
|
||||
|
||||
// --- GLOBAL MIDDLEWARE ---
|
||||
@@ -39,13 +41,13 @@ app.use('/public', express.static('public'));
|
||||
// --- API ROUTES ---
|
||||
|
||||
// General API routes (users, polls, etc.)
|
||||
app.use('/', apiRoutes(client));
|
||||
app.use('/api', apiRoutes(client, io));
|
||||
|
||||
// Poker-specific routes
|
||||
app.use('/', pokerRoutes(client));
|
||||
app.use('/api/poker', pokerRoutes(client, io));
|
||||
|
||||
// Solitaire-specific routes
|
||||
app.use('/solitaire', solitaireRoutes(client));
|
||||
app.use('/api/solitaire', solitaireRoutes(client, io));
|
||||
|
||||
|
||||
export { app };
|
||||
@@ -17,6 +17,8 @@ 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";
|
||||
|
||||
// Create a new router instance
|
||||
const router = express.Router();
|
||||
@@ -123,7 +125,7 @@ export function apiRoutes(client, io) {
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/user/:id/daily', (req, res) => {
|
||||
router.post('/user/:id/daily', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
try {
|
||||
const akhy = getUser.get(id);
|
||||
@@ -139,7 +141,7 @@ export function apiRoutes(client, io) {
|
||||
coins_amount: amount, user_new_amount: newCoins,
|
||||
});
|
||||
|
||||
io.emit('data-updated', { table: 'users' });
|
||||
await emitDataUpdated({ table: 'users' });
|
||||
res.status(200).json({ message: `+${amount} FlopoCoins! Récompense récupérée !` });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: "Failed to process daily reward." });
|
||||
@@ -179,11 +181,14 @@ export function apiRoutes(client, io) {
|
||||
const newCoins = commandUser.coins - 1000;
|
||||
updateUserCoins.run({ id: commandUserId, coins: newCoins });
|
||||
insertLog.run({
|
||||
id: `${commandUserId}-changenick-${Date.now()}`, user_id: commandUserId, action: 'CHANGE_NICKNAME',
|
||||
target_user_id: userId, coins_amount: -1000, user_new_amount: newCoins,
|
||||
id: `${commandUserId}-changenick-${Date.now()}`,
|
||||
user_id: commandUserId,
|
||||
action: 'CHANGE_NICKNAME',
|
||||
target_user_id: userId,
|
||||
coins_amount: -1000,
|
||||
user_new_amount: newCoins,
|
||||
});
|
||||
|
||||
io.emit('data-updated', { table: 'users' });
|
||||
res.status(200).json({ message: `Le pseudo de ${member.user.username} a été changé.` });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: `Erreur: Impossible de changer le pseudo.` });
|
||||
@@ -191,8 +196,44 @@ export function apiRoutes(client, io) {
|
||||
});
|
||||
|
||||
router.post('/spam-ping', async (req, res) => {
|
||||
// Implement spam-ping logic here...
|
||||
res.status(501).json({ message: "Not Implemented" });
|
||||
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,
|
||||
})
|
||||
await emitDataUpdated({ 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é" });
|
||||
}
|
||||
});
|
||||
|
||||
// --- Slowmode Routes ---
|
||||
@@ -201,9 +242,53 @@ export function apiRoutes(client, io) {
|
||||
res.status(200).json({ slowmodes: activeSlowmodes });
|
||||
});
|
||||
|
||||
router.post('/slowmode', (req, res) => {
|
||||
// Implement slowmode logic here...
|
||||
res.status(501).json({ message: "Not Implemented" });
|
||||
router.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,
|
||||
};
|
||||
await socketEmit('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,
|
||||
})
|
||||
await emitDataUpdated({ table: 'users', action: 'update' });
|
||||
|
||||
return res.status(200).json({ message: `${user.globalName} est maintenant en slowmode pour 1h`})
|
||||
});
|
||||
|
||||
// --- Prediction Routes ---
|
||||
@@ -214,18 +299,275 @@ export function apiRoutes(client, io) {
|
||||
});
|
||||
|
||||
router.post('/start-predi', async (req, res) => {
|
||||
// Implement prediction start logic here...
|
||||
res.status(501).json({ message: "Not Implemented" });
|
||||
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) {
|
||||
console.log(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,
|
||||
};
|
||||
await socketEmit('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,
|
||||
})
|
||||
await emitDataUpdated({ table: 'users', action: 'update' });
|
||||
|
||||
return res.status(200).json({ message: `Ta prédi '${label}' a commencée !`})
|
||||
});
|
||||
|
||||
router.post('/vote-predi', (req, res) => {
|
||||
// Implement prediction vote logic here...
|
||||
res.status(501).json({ message: "Not Implemented" });
|
||||
router.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
|
||||
|
||||
await socketEmit('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,
|
||||
})
|
||||
await emitDataUpdated({ table: 'users', action: 'update' });
|
||||
|
||||
return res.status(200).send({ message : `Vote enregistré!` });
|
||||
});
|
||||
|
||||
router.post('/end-predi', (req, res) => {
|
||||
// Implement prediction end logic here...
|
||||
res.status(501).json({ message: "Not Implemented" });
|
||||
router.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);
|
||||
}
|
||||
|
||||
await socketEmit('new-predi', { action: 'closed predi' });
|
||||
await emitDataUpdated({ table: 'users', action: 'fin predi' });
|
||||
|
||||
return res.status(200).json({ message: 'Prédi close' });
|
||||
});
|
||||
|
||||
// --- Admin Routes ---
|
||||
@@ -242,7 +584,6 @@ export function apiRoutes(client, io) {
|
||||
coins_amount: coins, user_new_amount: newCoins
|
||||
});
|
||||
|
||||
io.emit('data-updated', { table: 'users' });
|
||||
res.status(200).json({ message: `Added ${coins} coins.` });
|
||||
});
|
||||
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
import express from 'express';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { uniqueNamesGenerator, adjectives } from 'unique-names-generator';
|
||||
import pkg from 'pokersolver';
|
||||
const { Hand } = pkg;
|
||||
|
||||
import { pokerRooms } from '../../game/state.js';
|
||||
import { initialShuffledCards, getFirstActivePlayerAfterDealer, getNextActivePlayer, checkEndOfBettingRound, checkRoomWinners } from '../../game/poker.js';
|
||||
import { pokerEloHandler } from '../../game/elo.js';
|
||||
import { getUser, updateUserCoins, insertLog } from '../../database/index.js';
|
||||
import {sleep} from "openai/core";
|
||||
import { sleep } from "openai/core";
|
||||
import {client} from "../../bot/client.js";
|
||||
import { io } from '../../../index.js'
|
||||
import {emitPokerToast, emitPokerUpdate} from "../socket.js";
|
||||
|
||||
// Create a new router instance
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* Factory function to create and configure the poker API routes.
|
||||
* @param {object} client - The Discord.js client instance.
|
||||
* @param {object} io - The Socket.IO server instance. // FIX: Pass io in as a parameter
|
||||
* @returns {object} The configured Express router.
|
||||
*/
|
||||
export function pokerRoutes(client) {
|
||||
export function pokerRoutes(client, io) {
|
||||
|
||||
// --- Room Management Endpoints ---
|
||||
|
||||
router.get('/poker-rooms', (req, res) => {
|
||||
router.get('/', (req, res) => {
|
||||
res.status(200).json({ rooms: pokerRooms });
|
||||
});
|
||||
|
||||
router.get('/poker-rooms/:id', (req, res) => {
|
||||
router.get('/:id', (req, res) => {
|
||||
const room = pokerRooms[req.params.id];
|
||||
if (room) {
|
||||
res.status(200).json({ room });
|
||||
@@ -35,7 +37,7 @@ export function pokerRoutes(client) {
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/create-poker-room', async (req, res) => {
|
||||
router.post('/create', async (req, res) => {
|
||||
const { creatorId } = req.body;
|
||||
if (!creatorId) return res.status(400).json({ message: 'Creator ID is required.' });
|
||||
|
||||
@@ -48,57 +50,53 @@ export function pokerRoutes(client) {
|
||||
const name = uniqueNamesGenerator({ dictionaries: [adjectives, ['Poker']], separator: ' ', style: 'capital' });
|
||||
|
||||
pokerRooms[id] = {
|
||||
id,
|
||||
host_id: creatorId,
|
||||
host_name: creator.globalName || creator.username,
|
||||
name,
|
||||
created_at: Date.now(),
|
||||
last_move_at: Date.now(),
|
||||
players: {},
|
||||
queue: {},
|
||||
afk: {},
|
||||
pioche: initialShuffledCards(),
|
||||
tapis: [],
|
||||
dealer: null,
|
||||
sb: null,
|
||||
bb: null,
|
||||
highest_bet: 0,
|
||||
current_player: null,
|
||||
current_turn: null, // 0: pre-flop, 1: flop, 2: turn, 3: river, 4: showdown
|
||||
playing: false,
|
||||
winners: [],
|
||||
waiting_for_restart: false,
|
||||
fakeMoney: false,
|
||||
id, host_id: creatorId, host_name: creator.globalName || creator.username,
|
||||
name, created_at: Date.now(), last_move_at: Date.now(),
|
||||
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,
|
||||
};
|
||||
|
||||
// Auto-join the creator to their own room
|
||||
await joinRoom(id, creatorId, io);
|
||||
|
||||
io.emit('poker-update', { type: 'room-created', roomId: id });
|
||||
await joinRoom(id, creatorId, io); // Auto-join the creator
|
||||
await emitPokerUpdate({ room: pokerRooms[id] });
|
||||
res.status(201).json({ roomId: id });
|
||||
});
|
||||
|
||||
router.post('/poker-room/join', async (req, res) => {
|
||||
router.post('/join', async (req, res) => {
|
||||
const { userId, roomId } = req.body;
|
||||
if (!userId || !roomId) return res.status(400).json({ message: 'User ID and Room ID are required.' });
|
||||
if (!pokerRooms[roomId]) return res.status(404).json({ message: 'Room not found.' });
|
||||
|
||||
if (Object.values(pokerRooms).some(r => r.players[userId])) {
|
||||
return res.status(403).json({ message: 'You are already in a room.' });
|
||||
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.' });
|
||||
}
|
||||
|
||||
await joinRoom(roomId, userId, io);
|
||||
res.status(200).json({ message: 'Successfully joined room.' });
|
||||
res.status(200).json({ message: 'Successfully joined.' });
|
||||
});
|
||||
|
||||
router.post('poker-room/leave', (req, res) => {
|
||||
// NEW: Endpoint to accept a player from the queue
|
||||
router.post('/accept', async (req, res) => {
|
||||
const { hostId, playerId, roomId } = req.body;
|
||||
const room = pokerRooms[roomId];
|
||||
if (!room || room.host_id !== hostId || !room.queue[playerId]) {
|
||||
return res.status(403).json({ message: 'Unauthorized or player not in queue.' });
|
||||
}
|
||||
|
||||
room.players[playerId] = room.queue[playerId];
|
||||
delete room.queue[playerId];
|
||||
|
||||
await emitPokerUpdate({ room: room });
|
||||
res.status(200).json({ message: 'Player accepted.' });
|
||||
});
|
||||
|
||||
router.post('/leave', (req, res) => {
|
||||
// Implement leave logic...
|
||||
res.status(501).json({ message: "Not Implemented" });
|
||||
});
|
||||
|
||||
// --- Game Action Endpoints ---
|
||||
|
||||
router.post('/poker-room/start', async (req, res) => {
|
||||
router.post('/start', async (req, res) => {
|
||||
const { roomId } = req.body;
|
||||
const room = pokerRooms[roomId];
|
||||
if (!room) return res.status(404).json({ message: 'Room not found.' });
|
||||
@@ -108,7 +106,18 @@ export function pokerRoutes(client) {
|
||||
res.status(200).json({ message: 'Game started.' });
|
||||
});
|
||||
|
||||
router.post('/poker-room/action/:action', async (req, res) => {
|
||||
// NEW: Endpoint to start the next hand
|
||||
router.post('/next-hand', async (req, res) => {
|
||||
const { roomId } = req.body;
|
||||
const room = pokerRooms[roomId];
|
||||
if (!room || !room.waiting_for_restart) {
|
||||
return res.status(400).json({ message: 'Not ready for the next hand.' });
|
||||
}
|
||||
await startNewHand(room, io);
|
||||
res.status(200).json({ message: 'Next hand started.' });
|
||||
});
|
||||
|
||||
router.post('/action/:action', async (req, res) => {
|
||||
const { playerId, amount, roomId } = req.body;
|
||||
const { action } = req.params;
|
||||
const room = pokerRooms[roomId];
|
||||
@@ -122,38 +131,53 @@ export function pokerRoutes(client) {
|
||||
switch(action) {
|
||||
case 'fold':
|
||||
player.folded = true;
|
||||
io.emit('poker-update', { type: 'player-action', roomId, playerId, action, globalName: player.globalName });
|
||||
await emitPokerToast({
|
||||
type: 'player-fold',
|
||||
playerId: player.id,
|
||||
playerName: player.globalName,
|
||||
roomId: room.id,
|
||||
})
|
||||
break;
|
||||
case 'check':
|
||||
if (player.bet < room.highest_bet) return res.status(400).json({ message: 'Cannot check, you must call or raise.' });
|
||||
io.emit('poker-update', { type: 'player-action', roomId, playerId, action, globalName: player.globalName });
|
||||
if (player.bet < room.highest_bet) return res.status(400).json({ message: 'Cannot check.' });
|
||||
await emitPokerToast({
|
||||
type: 'player-check',
|
||||
playerId: player.id,
|
||||
playerName: player.globalName,
|
||||
roomId: room.id,
|
||||
})
|
||||
break;
|
||||
case 'call':
|
||||
const callAmount = room.highest_bet - player.bet;
|
||||
if (callAmount > player.bank) { // All-in call
|
||||
player.bet += player.bank;
|
||||
player.bank = 0;
|
||||
player.allin = true;
|
||||
} else {
|
||||
player.bet += callAmount;
|
||||
player.bank -= callAmount;
|
||||
}
|
||||
const callAmount = Math.min(room.highest_bet - player.bet, player.bank);
|
||||
player.bank -= callAmount;
|
||||
player.bet += callAmount;
|
||||
if (player.bank === 0) player.allin = true;
|
||||
updatePlayerCoins(player, -callAmount, room.fakeMoney);
|
||||
io.emit('poker-update', { type: 'player-action', roomId, playerId, action, globalName: player.globalName });
|
||||
await emitPokerToast({
|
||||
type: 'player-call',
|
||||
playerId: player.id,
|
||||
playerName: player.globalName,
|
||||
roomId: room.id,
|
||||
})
|
||||
break;
|
||||
case 'raise':
|
||||
const totalBet = player.bet + amount;
|
||||
if (amount > player.bank || totalBet <= room.highest_bet) return res.status(400).json({ message: 'Invalid raise amount.' });
|
||||
|
||||
player.bet = totalBet;
|
||||
if (amount <= 0 || amount > player.bank || (player.bet + amount) <= room.highest_bet) {
|
||||
return res.status(400).json({ message: 'Invalid raise amount.' });
|
||||
}
|
||||
player.bank -= amount;
|
||||
if(player.bank === 0) player.allin = true;
|
||||
room.highest_bet = totalBet;
|
||||
player.bet += amount;
|
||||
if (player.bank === 0) player.allin = true;
|
||||
room.highest_bet = player.bet;
|
||||
updatePlayerCoins(player, -amount, room.fakeMoney);
|
||||
io.emit('poker-update', { type: 'player-action', roomId, playerId, action, amount, globalName: player.globalName });
|
||||
await emitPokerToast({
|
||||
type: 'player-raise',
|
||||
amount: amount,
|
||||
playerId: player.id,
|
||||
playerName: player.globalName,
|
||||
roomId: room.id,
|
||||
})
|
||||
break;
|
||||
default:
|
||||
return res.status(400).json({ message: 'Invalid action.' });
|
||||
default: return res.status(400).json({ message: 'Invalid action.' });
|
||||
}
|
||||
|
||||
player.last_played_turn = room.current_turn;
|
||||
@@ -170,26 +194,34 @@ export function pokerRoutes(client) {
|
||||
async function joinRoom(roomId, userId, io) {
|
||||
const user = await client.users.fetch(userId);
|
||||
const userDB = getUser.get(userId);
|
||||
const bank = userDB?.coins >= 1000 ? userDB.coins : 1000;
|
||||
const isFake = userDB?.coins < 1000;
|
||||
const bank = (userDB?.coins ?? 0) >= 1000 ? userDB.coins : 1000;
|
||||
const isFake = (userDB?.coins ?? 0) < 1000;
|
||||
|
||||
pokerRooms[roomId].players[userId] = {
|
||||
id: userId,
|
||||
globalName: user.globalName || user.username,
|
||||
hand: [],
|
||||
bank: bank,
|
||||
bet: 0,
|
||||
folded: false,
|
||||
allin: false,
|
||||
last_played_turn: null,
|
||||
const playerObject = {
|
||||
id: userId, globalName: user.globalName || user.username,
|
||||
hand: [], bank: bank, bet: 0, folded: false, allin: false,
|
||||
last_played_turn: null, solve: null
|
||||
};
|
||||
|
||||
if(isFake) pokerRooms[roomId].fakeMoney = true;
|
||||
const room = pokerRooms[roomId];
|
||||
if (room.playing) {
|
||||
room.queue[userId] = playerObject;
|
||||
} else {
|
||||
room.players[userId] = playerObject;
|
||||
}
|
||||
|
||||
io.emit('poker-update', { type: 'player-join', roomId, player: pokerRooms[roomId].players[userId] });
|
||||
if (isFake) room.fakeMoney = true;
|
||||
await emitPokerUpdate({ room: room, type: 'player-joined' });
|
||||
}
|
||||
|
||||
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 });
|
||||
return;
|
||||
}
|
||||
|
||||
room.playing = true;
|
||||
room.current_turn = 0; // Pre-flop
|
||||
room.pioche = initialShuffledCards();
|
||||
@@ -198,32 +230,31 @@ async function startNewHand(room, io) {
|
||||
room.waiting_for_restart = false;
|
||||
room.highest_bet = 20;
|
||||
|
||||
// Reset players for the new hand
|
||||
// Rotate dealer
|
||||
const oldDealerIndex = playerIds.indexOf(room.dealer);
|
||||
room.dealer = playerIds[(oldDealerIndex + 1) % playerIds.length];
|
||||
|
||||
Object.values(room.players).forEach(p => {
|
||||
p.hand = [room.pioche.pop(), room.pioche.pop()];
|
||||
p.bet = 0;
|
||||
p.folded = false;
|
||||
p.allin = false;
|
||||
p.last_played_turn = null;
|
||||
p.bet = 0; p.folded = false; p.allin = false; p.last_played_turn = null;
|
||||
});
|
||||
updatePlayerHandSolves(room); // NEW: Calculate initial hand strength
|
||||
|
||||
// Handle blinds
|
||||
const playerIds = Object.keys(room.players);
|
||||
const sbPlayer = room.players[playerIds[0]];
|
||||
const bbPlayer = room.players[playerIds[1]];
|
||||
// Handle blinds based on new dealer
|
||||
const dealerIndex = playerIds.indexOf(room.dealer);
|
||||
const sbPlayer = room.players[playerIds[(dealerIndex + 1) % playerIds.length]];
|
||||
const bbPlayer = room.players[playerIds[(dealerIndex + 2) % playerIds.length]];
|
||||
room.sb = sbPlayer.id;
|
||||
room.bb = bbPlayer.id;
|
||||
|
||||
sbPlayer.bet = 10;
|
||||
sbPlayer.bank -= 10;
|
||||
sbPlayer.bank -= 10; sbPlayer.bet = 10;
|
||||
updatePlayerCoins(sbPlayer, -10, room.fakeMoney);
|
||||
|
||||
bbPlayer.bet = 20;
|
||||
bbPlayer.bank -= 20;
|
||||
bbPlayer.bank -= 20; bbPlayer.bet = 20;
|
||||
updatePlayerCoins(bbPlayer, -20, room.fakeMoney);
|
||||
|
||||
bbPlayer.last_played_turn = 0;
|
||||
room.current_player = playerIds[2 % playerIds.length];
|
||||
|
||||
io.emit('poker-update', { type: 'new-hand', room });
|
||||
room.current_player = playerIds[(dealerIndex + 3) % playerIds.length];
|
||||
await emitPokerUpdate({ room: room, type: 'room-started' });
|
||||
}
|
||||
|
||||
async function checkRoundCompletion(room, io) {
|
||||
@@ -232,27 +263,23 @@ async function checkRoundCompletion(room, io) {
|
||||
|
||||
if (roundResult.endRound) {
|
||||
if (roundResult.winner) {
|
||||
// Handle single winner case (everyone else folded)
|
||||
await handleShowdown(room, io, [roundResult.winner]);
|
||||
} else {
|
||||
// Proceed to the next phase
|
||||
await advanceToNextPhase(room, io, roundResult.nextPhase);
|
||||
}
|
||||
} else {
|
||||
// Continue the round
|
||||
room.current_player = getNextActivePlayer(room);
|
||||
io.emit('poker-update', { type: 'next-player', room });
|
||||
await emitPokerUpdate({ room: room });
|
||||
}
|
||||
}
|
||||
|
||||
async function advanceToNextPhase(room, io, phase) {
|
||||
// Reset player turn markers for the new betting round
|
||||
Object.values(room.players).forEach(p => p.last_played_turn = null);
|
||||
Object.values(room.players).forEach(p => { if (!p.folded) p.last_played_turn = null; });
|
||||
|
||||
switch(phase) {
|
||||
case 'flop':
|
||||
room.current_turn = 1;
|
||||
room.tapis = [room.pioche.pop(), room.pioche.pop(), room.pioche.pop()];
|
||||
room.tapis.push(room.pioche.pop(), room.pioche.pop(), room.pioche.pop());
|
||||
break;
|
||||
case 'turn':
|
||||
room.current_turn = 2;
|
||||
@@ -263,23 +290,22 @@ async function advanceToNextPhase(room, io, phase) {
|
||||
room.tapis.push(room.pioche.pop());
|
||||
break;
|
||||
case 'showdown':
|
||||
const winners = checkRoomWinners(room);
|
||||
await handleShowdown(room, io, winners);
|
||||
await handleShowdown(room, io, checkRoomWinners(room));
|
||||
return;
|
||||
case 'progressive-showdown':
|
||||
// Show cards and deal remaining community cards one by one
|
||||
io.emit('poker-update', { type: 'show-cards', room });
|
||||
await emitPokerUpdate({ room: room });
|
||||
while(room.tapis.length < 5) {
|
||||
await sleep(1500);
|
||||
await sleep(500);
|
||||
room.tapis.push(room.pioche.pop());
|
||||
io.emit('poker-update', { type: 'community-card-deal', room });
|
||||
updatePlayerHandSolves(room);
|
||||
await emitPokerUpdate({ room: room });
|
||||
}
|
||||
const finalWinners = checkRoomWinners(room);
|
||||
await handleShowdown(room, io, finalWinners);
|
||||
await handleShowdown(room, io, checkRoomWinners(room));
|
||||
return;
|
||||
}
|
||||
updatePlayerHandSolves(room); // NEW: Update hand strength after new cards
|
||||
room.current_player = getFirstActivePlayerAfterDealer(room);
|
||||
io.emit('poker-update', { type: 'phase-change', room });
|
||||
await emitPokerUpdate({ room: room });
|
||||
}
|
||||
|
||||
async function handleShowdown(room, io, winners) {
|
||||
@@ -288,17 +314,37 @@ async function handleShowdown(room, io, winners) {
|
||||
room.waiting_for_restart = true;
|
||||
room.winners = winners;
|
||||
|
||||
const totalPot = Object.values(room.players).reduce((sum, p) => sum + p.bet, 0);
|
||||
const winAmount = Math.floor(totalPot / winners.length);
|
||||
let totalPot = 0;
|
||||
Object.values(room.players).forEach(p => { totalPot += p.bet; });
|
||||
|
||||
const winAmount = winners.length > 0 ? Math.floor(totalPot / winners.length) : 0;
|
||||
|
||||
winners.forEach(winnerId => {
|
||||
const winnerPlayer = room.players[winnerId];
|
||||
winnerPlayer.bank += winAmount;
|
||||
updatePlayerCoins(winnerPlayer, winAmount, room.fakeMoney);
|
||||
if(winnerPlayer) {
|
||||
winnerPlayer.bank += winAmount;
|
||||
updatePlayerCoins(winnerPlayer, winAmount, room.fakeMoney);
|
||||
}
|
||||
});
|
||||
|
||||
//await pokerEloHandler(room);
|
||||
io.emit('poker-update', { type: 'showdown', room, winners, winAmount });
|
||||
await emitPokerUpdate({ room: room });
|
||||
await emitPokerToast({
|
||||
type: 'player-winner',
|
||||
playerIds: winners,
|
||||
roomId: room.id,
|
||||
})
|
||||
}
|
||||
|
||||
// NEW: Function to calculate and update hand strength for all players
|
||||
function updatePlayerHandSolves(room) {
|
||||
const communityCards = room.tapis;
|
||||
for (const player of Object.values(room.players)) {
|
||||
if (!player.folded) {
|
||||
const allCards = [...communityCards, ...player.hand];
|
||||
player.solve = Hand.solve(allCards).descr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updatePlayerCoins(player, amount, isFake) {
|
||||
@@ -306,14 +352,12 @@ function updatePlayerCoins(player, amount, isFake) {
|
||||
const user = getUser.get(player.id);
|
||||
if (!user) return;
|
||||
|
||||
const newCoins = user.coins + amount;
|
||||
const newCoins = player.bank; // The bank is the source of truth now
|
||||
updateUserCoins.run({ id: player.id, coins: newCoins });
|
||||
insertLog.run({
|
||||
id: `${player.id}-poker-${Date.now()}`,
|
||||
user_id: player.id,
|
||||
target_user_id: null,
|
||||
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: newCoins,
|
||||
});
|
||||
}
|
||||
@@ -39,18 +39,19 @@ export function solitaireRoutes(client, io) {
|
||||
if (userSeed) {
|
||||
// Use the provided seed to create a deterministic game
|
||||
seed = userSeed;
|
||||
let numericSeed = 0;
|
||||
for (let i = 0; i < seed.length; i++) {
|
||||
numericSeed = (numericSeed + seed.charCodeAt(i)) & 0xFFFFFFFF;
|
||||
}
|
||||
const rng = createSeededRNG(numericSeed);
|
||||
deck = seededShuffle(createDeck(), rng);
|
||||
} else {
|
||||
// Create a standard random game
|
||||
// Create a random seed if none is provided
|
||||
seed = Date.now().toString(36) + Math.random().toString(36).substr(2);
|
||||
deck = shuffle(createDeck());
|
||||
}
|
||||
|
||||
let numericSeed = 0;
|
||||
for (let i = 0; i < seed.length; i++) {
|
||||
numericSeed = (numericSeed + seed.charCodeAt(i)) & 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
const rng = createSeededRNG(numericSeed);
|
||||
deck = seededShuffle(createDeck(), rng);
|
||||
|
||||
const gameState = deal(deck);
|
||||
gameState.seed = seed;
|
||||
gameState.isSOTD = false;
|
||||
|
||||
@@ -13,8 +13,6 @@ export function initializeSocket(server, client) {
|
||||
io = server;
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
console.log(`[Socket.IO] User connected: ${socket.id}`);
|
||||
|
||||
socket.on('user-connected', async (userId) => {
|
||||
if (!userId) return;
|
||||
await refreshQueuesForUser(userId, client);
|
||||
@@ -24,7 +22,7 @@ export function initializeSocket(server, client) {
|
||||
registerConnect4Events(socket, client);
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log(`[Socket.IO] User disconnected: ${socket.id}`);
|
||||
//
|
||||
});
|
||||
});
|
||||
|
||||
@@ -288,4 +286,20 @@ function cleanupStaleGames() {
|
||||
};
|
||||
cleanup(activeTicTacToeGames, 'TicTacToe');
|
||||
cleanup(activeConnect4Games, 'Connect4');
|
||||
}
|
||||
|
||||
/* EMITS */
|
||||
export async function socketEmit(event, data) {
|
||||
io.emit(event, data);
|
||||
}
|
||||
export async function emitDataUpdated(data) {
|
||||
io.emit('data-updated', data);
|
||||
}
|
||||
|
||||
export async function emitPokerUpdate(data) {
|
||||
io.emit('poker-update', data);
|
||||
}
|
||||
|
||||
export async function emitPokerToast(data) {
|
||||
io.emit('poker-toast', data);
|
||||
}
|
||||
Reference in New Issue
Block a user