mirror of
https://github.com/cassoule/flopobot_v2.git
synced 2026-01-18 16:37:40 +01:00
3749 lines
129 KiB
JavaScript
3749 lines
129 KiB
JavaScript
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
|
||
} from './utils.js';
|
||
import {channelPointsHandler, eloHandler, pokerTest, slowmodesHandler} 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,
|
||
} 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';
|
||
|
||
// 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(express.json());
|
||
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 activePolls = {};
|
||
const activeInventories = {};
|
||
const activeSearchs = {};
|
||
const activeSlowmodes = {};
|
||
const activePredis = {};
|
||
let todaysHydrateCron = ''
|
||
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()
|
||
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.VOTING_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);
|
||
|
||
// 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.author.id === process.env.DEV_ID) {
|
||
const prefix = process.env.DEV_SITE === 'true' ? 'test' : '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 + ':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')) {
|
||
io.emit('message', message.content);
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
// Once bot is ready
|
||
client.once('ready', async () => {
|
||
console.log(`Logged in as ${client.user.tag}`);
|
||
console.log(`[Connected with ${FLAPI_URL}]`)
|
||
const randomMinute = Math.floor(Math.random() * 60);
|
||
const randomHour = Math.floor(Math.random() * (18 - 8 + 1)) + 8;
|
||
todaysHydrateCron = `${randomMinute} ${randomHour} * * *`
|
||
console.log(todaysHydrateCron)
|
||
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];
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
// ─── 💀 Midnight Chaos Timer ──────────────────────
|
||
cron.schedule(process.env.CRON_EXPR, async () => {
|
||
const randomMinute = Math.floor(Math.random() * 60);
|
||
const randomHour = Math.floor(Math.random() * (18 - 8 + 1)) + 8;
|
||
todaysHydrateCron = `${randomMinute} ${randomHour} * * *`
|
||
console.log(todaysHydrateCron)
|
||
|
||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||
const roleId = process.env.VOTING_ROLE_ID; // Set this in your .env file
|
||
const members = await getOnlineUsersWithRole(process.env.GUILD_ID, roleId);
|
||
|
||
const prob = Math.random();
|
||
if (members.size === 0 || prob > process.env.CHAOS_PROB) {
|
||
console.log(`No roulette tonight ${prob}`)
|
||
return
|
||
}
|
||
|
||
const randomMember = members[Math.floor(Math.random() * members.size)];
|
||
|
||
const timeoutUntil = new Date(Date.now() + 12 * 60 * 60 * 1000).toISOString();
|
||
|
||
try {
|
||
await guild.members.edit(randomMember.user.id, {
|
||
communication_disabled_until: timeoutUntil,
|
||
reason: 'Roulette Russe 🔔',
|
||
});
|
||
|
||
const generalChannel = guild.channels.cache.find(
|
||
ch => ch.name === 'général' || ch.name === 'general'
|
||
);
|
||
|
||
if (generalChannel && generalChannel.isTextBased()) {
|
||
generalChannel.send(
|
||
`🎯 <@${randomMember.user.id}> ça dégage, à mimir ! (jusqu'à 12h00)`
|
||
);
|
||
}
|
||
|
||
console.log(`${randomMember.user.username} has been timed out until ${timeoutUntil}`);
|
||
} catch (err) {
|
||
console.error('Failed to timeout random member:', err);
|
||
}
|
||
});
|
||
|
||
cron.schedule(todaysHydrateCron, async () => {
|
||
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||
|
||
try {
|
||
const generalChannel = guild.channels.cache.find(
|
||
ch => ch.name === 'général' || ch.name === 'general'
|
||
);
|
||
|
||
if (generalChannel && generalChannel.isTextBased()) {
|
||
generalChannel.send(
|
||
`${getRandomHydrateText()} ${getRandomEmoji(1)}`
|
||
);
|
||
}
|
||
|
||
console.log(`Message hydratation`);
|
||
} catch (err) {
|
||
console.error('Message hydratation:', err);
|
||
}
|
||
});
|
||
|
||
// 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 / 2) + 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 {
|
||
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,
|
||
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 {
|
||
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,
|
||
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 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,
|
||
})
|
||
})
|
||
|
||
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 res;
|
||
if (invSkins[0].currentLvl === trueSkin.levels.length) {
|
||
if (invSkins[0].currentChroma === 1) {
|
||
res = trueSkin.chromas[0].displayIcon
|
||
|
||
} else {
|
||
res = trueSkin.chromas[invSkins[0].currentChroma-1].fullRender ?? trueSkin.chromas[invSkins[0].currentChroma-1].displayIcon
|
||
}
|
||
} else if (invSkins[0].currentLvl === 1) {
|
||
res = trueSkin.levels[0].displayIcon ?? trueSkin.chromas[0].fullRender
|
||
} else if (invSkins[0].currentLvl === 2 || invSkins[0].currentLvl === 3) {
|
||
res = trueSkin.displayIcon
|
||
}
|
||
if (res) return res;
|
||
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 res;
|
||
if (randomLevel === randomSkin.levels.length) {
|
||
if (randomChroma === 1) {
|
||
res = randomSkin.levels[randomSkin.levels.length - 1].streamedVideo ?? randomSkin.chromas[0].streamedVideo
|
||
} else {
|
||
res = randomSkin.chromas[randomChroma-1].streamedVideo
|
||
}
|
||
} else {
|
||
res = randomSkin.levels[randomLevel-1].streamedVideo
|
||
}
|
||
return res;
|
||
};
|
||
const imageUrl = () => {
|
||
let res;
|
||
if (randomLevel === randomSkin.levels.length) {
|
||
if (randomChroma === 1) {
|
||
res = randomSkin.chromas[0].displayIcon
|
||
|
||
} else {
|
||
res = randomSkin.chromas[randomChroma-1].fullRender ?? randomSkin.chromas[randomChroma-1].displayIcon
|
||
}
|
||
} else if (randomLevel === 1) {
|
||
res = randomSkin.levels[0].displayIcon ?? randomSkin.chromas[0].fullRender
|
||
} else if (randomLevel === 2 || randomLevel === 3) {
|
||
res = randomSkin.displayIcon
|
||
}
|
||
if (res) return res;
|
||
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 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 < randomSkin.levels.length - randomLevel; i++) {
|
||
res += '◾ '
|
||
}
|
||
return res
|
||
}
|
||
const chromaText = () => {
|
||
let res = ""
|
||
for (let i = 1; i <= randomSkin.chromas.length; i++) {
|
||
res += randomChroma === i ? '💠 ' : '◾ '
|
||
}
|
||
return res
|
||
}
|
||
const price = () => {
|
||
let res = dbSkins[randomIndex].basePrice;
|
||
|
||
res *= (1 + (randomLevel / Math.max(randomSkin.levels.length, 2)))
|
||
res *= (1 + (randomChroma / 4))
|
||
|
||
return res.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 === '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é !",
|
||
flags: InteractionResponseFlags.EPHEMERAL,
|
||
},
|
||
});
|
||
}
|
||
|
||
// 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 = 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
|
||
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,
|
||
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
|
||
|
||
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,
|
||
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' });
|
||
});
|
||
|
||
// 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', (req, res) => {
|
||
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/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' });
|
||
}
|
||
})
|
||
|
||
// 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
|
||
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,
|
||
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
|
||
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,
|
||
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'})
|
||
}
|
||
|
||
pokerRooms[id] = {
|
||
id: id,
|
||
host_id: creatorId,
|
||
host_name: creator.globalName,
|
||
name: name,
|
||
created_at: Date.now(),
|
||
last_move_at: Date.now(),
|
||
players: {}
|
||
}
|
||
io.emit('new-poker-room')
|
||
return res.status(200).send({ roomId: id })
|
||
});
|
||
|
||
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)
|
||
|
||
try {
|
||
pokerRooms[roomId].players[userId] = user
|
||
} catch (e) {
|
||
//
|
||
}
|
||
|
||
io.emit('player-joined')
|
||
});
|
||
|
||
import http from 'http';
|
||
import { Server } from 'socket.io';
|
||
const server = http.createServer(app);
|
||
|
||
const io = new Server(server, {
|
||
cors: {
|
||
Origin: FLAPI_URL,
|
||
methods: ['GET', 'POST', 'PUT', 'OPTIONS'],
|
||
}
|
||
});
|
||
|
||
let queue = []
|
||
let playingArray = []
|
||
|
||
io.on('connection', (socket) => {
|
||
|
||
socket.on('user-connected', async (user) => {
|
||
const username = getUser.get(user)
|
||
console.log(`user connected: ${username?.username ?? '-'}`);
|
||
|
||
queue = queue.filter(obj => obj !== user)
|
||
let names = [];
|
||
for (const n of queue) {
|
||
let name = await client.users.fetch(n)
|
||
names.push(name?.username)
|
||
}
|
||
io.emit('tictactoequeue', { allPlayers: playingArray, queue: names })
|
||
})
|
||
|
||
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?.username)
|
||
}
|
||
io.emit('tictactoequeue', { allPlayers: playingArray, queue: names })
|
||
})
|
||
|
||
socket.on('tictactoequeue', async (e) => {
|
||
console.log(`${e.playerId} in tic tac toe queue`);
|
||
|
||
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) => {
|
||
let lobbyToChange;
|
||
if (e.value === 'X') {
|
||
lobbyToChange = playingArray.find(obj => obj.p1.id === e.playerId)
|
||
|
||
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)
|
||
|
||
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) {
|
||
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, 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### É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)
|
||
})
|
||
});
|
||
|
||
server.listen(PORT, () => {
|
||
console.log(`Express+Socket.IO listening on port ${PORT}`);
|
||
});
|
||
|