From 9b07ba0d3ba23050ce1a7c92a55d055774b58b64 Mon Sep 17 00:00:00 2001 From: milo Date: Mon, 21 Apr 2025 17:53:05 +0200 Subject: [PATCH] valorant + inventory --- .idea/dataSources.xml | 12 + .idea/sqldialects.xml | 7 + commands.js | 28 +- index.js | 784 +++++++++++++++++++++++++++++++++++++++--- init_database.js | 40 ++- valo.js | 11 + 6 files changed, 820 insertions(+), 62 deletions(-) create mode 100644 .idea/dataSources.xml create mode 100644 .idea/sqldialects.xml create mode 100644 valo.js diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..2756b5d --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:C:\Users\milog\coding\bot_discord\flopobot_v2\flopobot.db + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..a52c18e --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/commands.js b/commands.js index 22465d7..01b1a6b 100644 --- a/commands.js +++ b/commands.js @@ -82,6 +82,32 @@ const TIMEOUT_COMMAND = { contexts: [0, 2], } -const ALL_COMMANDS = [/*TEST_COMMAND, CHALLENGE_COMMAND, */TIMEOUT_COMMAND]; +// Valorant +const VALORANT_COMMAND = { + name: 'valorant', + description: 'Ouvrir une caisse valorant', + type: 1, + integration_types: [0, 1], + contexts: [0, 2], +} + +// Own inventory command +const INVENTORY_COMMAND = { + name: 'inventory', + description: 'Voir inventaire', + options: [ + { + type: 6, + name: 'akhy', + description: 'Qui ?', + required: false, + }, + ], + type: 1, + integration_types: [0, 1], + contexts: [0, 2], +} + +const ALL_COMMANDS = [/*TEST_COMMAND, CHALLENGE_COMMAND, */TIMEOUT_COMMAND, INVENTORY_COMMAND, VALORANT_COMMAND]; InstallGlobalCommands(process.env.APP_ID, ALL_COMMANDS); diff --git a/index.js b/index.js index cf04b95..48a540c 100644 --- a/index.js +++ b/index.js @@ -17,9 +17,27 @@ import { getRandomHydrateText } from './utils.js'; import { getShuffledOptions, getResult } from './game.js'; -import { Client, GatewayIntentBits } from 'discord.js'; +import { Client, GatewayIntentBits, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; import cron from 'node-cron'; -import { flopoDB, insertUser, insertManyUsers, updateUser, updateManyUsers, getUser, getAllUsers, stmt } from './init_database.js'; +import { flopoDB, + insertUser, + insertManyUsers, + updateUser, + updateManyUsers, + getUser, + getAllUsers, + stmtUsers, + stmtSkins, + updateManySkins, + insertSkin, + updateSkin, + insertManySkins, + getAllSkins, + getSkin, + getAllAvailableSkins, + getUserInventory + } from './init_database.js'; +import { getValorantSkins, getSkinTiers } from './valo.js'; // Create an express app const app = express(); @@ -28,6 +46,7 @@ const PORT = process.env.PORT || 25578; // To keep track of our active games const activeGames = {}; const activePolls = {}; +const activeInventories = {}; let todaysHydrateCron = '' const SPAM_INTERVAL = process.env.SPAM_INTERVAL @@ -45,10 +64,11 @@ 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 { - stmt.run(); + stmtUsers.run(); const guild = await client.guilds.fetch(process.env.GUILD_ID); const members = await guild.members.fetch(); // Fetch all members @@ -79,6 +99,165 @@ async function getAkhys() { } 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; + 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' : '' + 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; + + return (res/111).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: basePrice(), + currentLvl: null, + currentChroma: null, + currentPrice: null, + }); + newSkinCount++; + } + } catch (e) { + // + } + } + console.log(`New skins : ${newSkinCount}`); + 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' + ); + if (generalChannel && generalChannel.isTextBased() && newSkinCount > 0) { + generalChannel.send( + `🔫 ${newSkinCount} nouveau(x) skin(s)` + ); + } + } catch (e) { + console.error('Error while fetching skins:', e); + } } async function getOnlineUsersWithRole(guild_id=process.env.GUILD_ID, role_id=process.env.VOTING_ROLE_ID) { @@ -100,9 +279,8 @@ client.login(process.env.BOT_TOKEN); client.on('messageCreate', async (message) => { // Ignore messages from bots to avoid feedback loops if (message.author.bot) return; + if (message.guildId !== process.env.GUILD_ID) return; - - if (message.content.toLowerCase().startsWith(`<@${process.env.APP_ID}>`) || message.mentions.repliedUser?.id === process.env.APP_ID) { let startTime = Date.now() console.log('-------------------------------') @@ -230,7 +408,8 @@ client.on('messageCreate', async (message) => { role: "developer", content: `Ton id est : ${process.env.APP_ID}, évite de l'utiliser et ne formatte pas tes messages avec ton propre id, si jamais tu utilises un id formatte le comme suit : <@ID>, en remplacant ID par l'id. Ton username et global_name sont : ${process.env.APP_NAME}` }); - } else if (process.env.MODEL === 'Mistral') { + } + else if (process.env.MODEL === 'Mistral') { // Map to Mistral format formatted.push({ role: 'system', @@ -316,7 +495,7 @@ client.once('ready', async () => { todaysHydrateCron = `${randomMinute} ${randomHour} * * *` console.log(todaysHydrateCron) await getAkhys(); - console.log('Akhys ready') + console.log('Ready') // ─── 💀 Midnight Chaos Timer ────────────────────── cron.schedule(process.env.CRON_EXPR, async () => { @@ -404,55 +583,6 @@ app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), async fun const { name } = data; console.log(name) - // "test" command - /*if (name === 'test') { - // Send a message into the channel where command was triggered from - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - // Fetches a random emoji to send from a helper function - content: `hello world ${getRandomEmoji()}`, - }, - }); - } - - // "challenge" command - if (name === 'challenge') { - // 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 object choice - const objectName = req.body.data.options[0].value; - - // Create active game using message ID as the game ID - activeGames[id] = { - id: userId, - objectName, - }; - - return res.send({ - type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `Rock papers scissors challenge from <@${userId}>`, - components: [ - { - type: MessageComponentTypes.ACTION_ROW, - components: [ - { - type: MessageComponentTypes.BUTTON, - // Append the game ID to use later on - custom_id: `accept_button_${req.body.id}`, - label: 'Accept', - style: ButtonStyleTypes.PRIMARY, - }, - ], - }, - ], - }, - }); - }*/ - // 'timeout' command if (name === 'timeout') { // Interaction context @@ -645,6 +775,342 @@ app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), async fun }); } + 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, + page: 0, + amount: invSkins.length, + endpoint: `webhooks/${process.env.APP_ID}/${req.body.token}/messages/@original`, + }; + + 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 + }; + + 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: [ + { + 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 (name === 'others_inventory') { + // TODO + } + + if (name === 'valorant') { + // 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); + const randomChroma = randomLevel === randomSkin.levels.length + ? Math.floor(Math.random() * randomSkin.chromas.length + 1) + : 1; + + const selectedLevel = randomSkin.levels[randomLevel - 1] + const selectedChroma = randomSkin.chromas[randomChroma - 1] + + //TODO : add currentLevel, currentChroma (null by default then set when opening) so I can calculate the real price in inventory + + // console.log(randomSkin.chromas) + // console.log(randomIndex) + + // 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; + console.log('default') + 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); + } + }, 5500); + + return; + } + console.error(`unknown command: ${name}`); return res.status(400).json({ error: 'unknown command' }); } @@ -894,6 +1360,212 @@ app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), async fun }); } } + 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); + 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].akhyId === req.body.member.user.id) { + if (activeInventories[invId].page === 0) { + activeInventories[invId].page = activeInventories[invId].amount-1 + } else { + activeInventories[invId].page-- + } + } else {return } + + 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 + }; + + 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: req.body.message.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); + 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].akhyId === req.body.member.user.id) { + if (activeInventories[invId].page === activeInventories[invId].amount-1) { + activeInventories[invId].page = 0 + } else { + activeInventories[invId].page++ + } + } else {return } + + 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 + }; + + 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: req.body.message.components, + }, + } + ); + } catch (err) { + console.log('Pas trouvé : ', err) + } + return res.send({ + type: InteractionResponseType.DEFERRED_UPDATE_MESSAGE, + }); + } return; } diff --git a/init_database.js b/init_database.js index d574746..d9fbb36 100644 --- a/init_database.js +++ b/init_database.js @@ -3,7 +3,7 @@ import Database from "better-sqlite3"; export const flopoDB = new Database('flopobot.db'); -export const stmt = flopoDB.prepare(` +export const stmtUsers = flopoDB.prepare(` CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, username TEXT NOT NULL, @@ -14,20 +14,50 @@ export const stmt = flopoDB.prepare(` totalRequests INTEGER DEFAULT 0 ) `); -stmt.run(); +stmtUsers.run(); +export const stmtSkins = flopoDB.prepare(` + CREATE TABLE IF NOT EXISTS skins ( + uuid TEXT PRIMARY KEY, + displayName TEXT, + contentTierUuid TEXT, + displayIcon TEXT, + user_id TEXT REFERENCES users, + tierRank TEXT, + tierColor TEXT, + tierText TEXT, + basePrice TEXT, + currentLvl INTEGER DEFAULT NULL, + currentChroma INTEGER DEFAULT NULL, + currentPrice INTEGER DEFAULT NULL + ) +`); +stmtSkins.run() export const insertUser = flopoDB.prepare('INSERT INTO users (id, username, globalName, warned, warns, allTimeWarns, totalRequests) VALUES (@id, @username, @globalName, @warned, @warns, @allTimeWarns, @totalRequests)'); export const updateUser = flopoDB.prepare('UPDATE users SET warned = @warned, warns = @warns, allTimeWarns = @allTimeWarns, totalRequests = @totalRequests WHERE id = @id'); export const getUser = flopoDB.prepare('SELECT * FROM users WHERE id = ?'); export const getAllUsers = flopoDB.prepare('SELECT * FROM users'); +export const insertSkin = flopoDB.prepare('INSERT INTO skins (uuid, displayName, contentTierUuid, displayIcon, user_id, tierRank, tierColor, tierText, basePrice, currentLvl, currentChroma, currentPrice) VALUES (@uuid, @displayName, @contentTierUuid, @displayIcon, @user_id, @tierRank, @tierColor, @tierText, @basePrice, @currentLvl, @currentChroma, @currentPrice)'); +export const updateSkin = flopoDB.prepare('UPDATE skins SET user_id = @user_id, currentLvl = @currentLvl, currentChroma = @currentChroma, currentPrice = @currentPrice WHERE uuid = @uuid'); +export const getSkin = flopoDB.prepare('SELECT * FROM skins WHERE uuid = ?'); +export const getAllSkins = flopoDB.prepare('SELECT * FROM skins'); +export const getAllAvailableSkins = flopoDB.prepare('SELECT * FROM skins WHERE user_id IS NULL'); +export const getUserInventory = flopoDB.prepare('SELECT * FROM skins WHERE user_id = @user_id'); + export const insertManyUsers = flopoDB.transaction(async (users) => { - for (const user of users) try { await insertUser.run(user) } catch (e) { console.log('users insert failed') }; + for (const user of users) try { await insertUser.run(user) } catch (e) { console.log('user insert failed') } }); export const updateManyUsers = flopoDB.transaction(async (users) => { - for (const user of users) try { await updateUser.run(user) } catch (e) { console.log('users update failed') }; + for (const user of users) try { await updateUser.run(user) } catch (e) { console.log('user update failed') } +}); + +export const insertManySkins = flopoDB.transaction(async (skins) => { + for (const skin of skins) try { await insertSkin.run(skin) } catch (e) { console.log('skin insert failed') } +}); +export const updateManySkins = flopoDB.transaction(async (skins) => { + for (const skin of skins) try { await updateSkin.run(skin) } catch (e) { console.log('skin insert failed') } }); -//const getManyUsers = flopoDB.transaction(()) // insertManyUsers([ diff --git a/valo.js b/valo.js new file mode 100644 index 0000000..f88b875 --- /dev/null +++ b/valo.js @@ -0,0 +1,11 @@ +export async function getValorantSkins(locale='fr-FR') { + const response = await fetch(`https://valorant-api.com/v1/weapons/skins?language=${locale}`, { method: 'GET' }); + const data = await response.json(); + return data.data +} + +export async function getSkinTiers(locale='fr-FR') { + const response = await fetch(`https://valorant-api.com/v1/contenttiers?language=${locale}`, { method: 'GET'}); + const data = await response.json(); + return data.data +} \ No newline at end of file