diff --git a/Config/config.json b/Config/config.json index 23d4af8..207f1d9 100644 --- a/Config/config.json +++ b/Config/config.json @@ -44,6 +44,11 @@ "bEnableBattlepass": false, "bBattlePassSeason": 2, + "//": "To set the item limit for the /locker command (not required)", + "bEnableLockerLimit": false, + "bLimitChapter": 1, + "bLimitSeason": 7, + "//": "If 'bUseAutoRotate' is enabled it will automatically rotate the item shop using the time you put in 'bRotateTime', you can select the season limit with 'bSeasonLimit' (please only do up to season 10, it might be a bit broken after!), 'bRotateTime' means what time the shop rotates (it is UTC and uses the 24 hour clock, not 12 hour), for 'bItemShopWebhook' you have to generate a webhook to your item shop channel and put the link here, you can select how many cosmetics are in daily: 'bDailyItemsAmount' and featured: 'bFeaturedItemsAmount'", "bUseAutoRotate": false, "bEnableAutoRotateDebugLogs": false, diff --git a/DiscordBot/commands/User/locker.js b/DiscordBot/commands/User/locker.js new file mode 100644 index 0000000..42c79a6 --- /dev/null +++ b/DiscordBot/commands/User/locker.js @@ -0,0 +1,247 @@ +const { MessageAttachment } = require("discord.js"); +const { createCanvas, loadImage } = require("canvas"); +const axios = require("axios"); +const User = require("../../../model/user.js"); +const Profiles = require("../../../model/profiles.js"); +const fs = require("fs"); +const config = require("../../../Config/config.json"); + +module.exports = { + commandInfo: { + name: "locker", + description: "Displays specific items from your locker as an image.", + options: [ + { + name: "category", + description: "Select the category of items to display.", + type: 3, + required: true, + choices: [ + { name: "Skins", value: "skins" }, + { name: "BackBlings", value: "backblings" }, + { name: "Emotes", value: "emotes" }, + { name: "Pickaxes", value: "pickaxes" }, + { name: "Gliders", value: "gliders" }, + { name: "Warps", value: "warp" }, + { name: "SkyDiveContrail", value: "skydivecontrail" }, + { name: "Music", value: "music" }, + { name: "Loading Screens", value: "loading_screens" }, + ], + }, + ], + }, + + execute: async (interaction) => { + await interaction.deferReply({ ephemeral: true }); + + const user = await User.findOne({ discordId: interaction.user.id }).lean(); + if (!user) { + return interaction.editReply({ content: "You do not have a registered account!", ephemeral: true }); + } + + const profile = await Profiles.findOne({ accountId: user.accountId }); + if (!profile || !profile.profiles || !profile.profiles.athena) { + return interaction.editReply({ content: "No locker data found for your account.", ephemeral: true }); + } + + const category = interaction.options.getString("category"); + const items = profile.profiles.athena.items; + let filteredItems = []; + switch (category) { + case "skins": + filteredItems = Object.keys(items).filter(id => id.startsWith("AthenaCharacter:")); + break; + case "backblings": + filteredItems = Object.keys(items).filter(id => id.startsWith("AthenaBackpack:")); + break; + case "emotes": + filteredItems = Object.keys(items).filter(id => id.startsWith("AthenaDance:")); + break; + case "emotes": + filteredItems = Object.keys(items).filter(id => id.startsWith("AthenaDance:")); + break; + case "pickaxes": + filteredItems = Object.keys(items).filter(id => id.startsWith("AthenaPickaxe:")); + break; + case "gliders": + filteredItems = Object.keys(items).filter(id => id.startsWith("AthenaGlider:")); + break; + case "warp": + filteredItems = Object.keys(items).filter(id => id.startsWith("AthenaItemWrap:")); + break; + case "skydivecontrail": + filteredItems = Object.keys(items).filter(id => id.startsWith("AthenaSkyDiveContrail:")); + break; + case "music": + filteredItems = Object.keys(items).filter(id => id.startsWith("AthenaMusicPack:")); + break; + case "loading_screens": + filteredItems = Object.keys(items).filter(id => id.startsWith("AthenaLoadingScreen:")); + break; + default: + return interaction.editReply({ content: "Invalid category selected.", ephemeral: true }); + } + + if (filteredItems.length === 0) { + return interaction.editReply({ content: `No items found in your locker for the ${category} category.`, ephemeral: true }); + } + let itemsData = []; + try { + const apiResponse = await axios.get("https://fortnite-api.com/v2/cosmetics/br"); + const allCosmetics = apiResponse.data.data; + + // bEnableLockerLimit false + if (!config.bEnableLockerLimit) { + itemsData = filteredItems.map(id => { + const itemId = id + .replace("AthenaCharacter:", "") + .replace("AthenaBackpack:", "") + .replace("AthenaDance:", "") + .replace("AthenaPickaxe:", "") + .replace("AthenaGlider:", "") + .replace("AthenaItemWrap:", "") + .replace("AthenaSkyDiveContrail:", "") + .replace("AthenaMusicPack:", "") + .replace("AthenaLoadingScreen:", ""); + const cosmetic = allCosmetics.find(item => item.id === itemId || item.id.includes(itemId)); + if (cosmetic) { + const season = cosmetic.introduction && cosmetic.introduction.season ? cosmetic.introduction.season : null; + const chapter = cosmetic.introduction && cosmetic.introduction.chapter ? cosmetic.introduction.chapter : null; + + return { + id: itemId, + name: cosmetic.name, + image: cosmetic.type.value === "emoji" && cosmetic.images.smallIcon ? cosmetic.images.smallIcon : cosmetic.images.icon, + season: season, + chapter: chapter + }; + } + return null; + }).filter(item => item !== null); + } else { + // bEnableLockerLimit true + itemsData = filteredItems.map(id => { + const itemId = id + .replace("AthenaCharacter:", "") + .replace("AthenaBackpack:", "") + .replace("AthenaDance:", "") + .replace("AthenaPickaxe:", "") + .replace("AthenaGlider:", "") + .replace("AthenaItemWrap:", "") + .replace("AthenaSkyDiveContrail:", "") + .replace("AthenaMusicPack:", "") + .replace("AthenaLoadingScreen:", ""); + const cosmetic = allCosmetics.find(item => item.id === itemId || item.id.includes(itemId)); + if (cosmetic) { + const season = cosmetic.introduction && cosmetic.introduction.season ? cosmetic.introduction.season : null; + const chapter = cosmetic.introduction && cosmetic.introduction.chapter ? cosmetic.introduction.chapter : null; + + if (season !== null && chapter !== null) { + if (chapter <= config.bLimitChapter && season <= config.bLimitSeason) { + return { + id: itemId, + name: cosmetic.name, + image: cosmetic.type.value === "emoji" && cosmetic.images.smallIcon ? cosmetic.images.smallIcon : cosmetic.images.icon, + season: season, + chapter: chapter + }; + } + } + } + return null; + }).filter(item => item !== null); + } + } catch (error) { + console.error("Error fetching cosmetics data from Fortnite API:", error); + return interaction.editReply({ content: "Failed to fetch item data from the Fortnite API.", ephemeral: true }); + } + if (itemsData.length === 0) { + return interaction.editReply({ content: `No matching items found for ${category} in the Fortnite API.`, ephemeral: true }); + } + + const itemSize = 150; + const padding = 20; + const itemsPerRow = 5; + const maxRowsPerPage = 6; + + const canvasWidth = itemsPerRow * (itemSize + padding) + padding; + const canvasHeight = maxRowsPerPage * (itemSize + padding) + 100; + + const totalPages = Math.ceil(itemsData.length / (itemsPerRow * maxRowsPerPage)); + const attachments = []; + + for (let page = 0; page < totalPages; page++) { + const canvas = createCanvas(canvasWidth, canvasHeight); + const ctx = canvas.getContext("2d"); + + const gradient = ctx.createLinearGradient(0, 0, canvasWidth, canvasHeight); + gradient.addColorStop(0, "#2C2F33"); + gradient.addColorStop(1, "#23272A"); + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, canvasWidth, canvasHeight); + + const logoImage = await loadImage("https://i.imgur.com/2RImwlb.png"); + const logoSize = 50; + const logoX = 20; + const logoY = 20; + + ctx.save(); + ctx.beginPath(); + ctx.arc(logoX + logoSize / 2, logoY + logoSize / 2, logoSize / 2, 0, Math.PI * 2, false); + ctx.closePath(); + ctx.clip(); + ctx.drawImage(logoImage, logoX, logoY, logoSize, logoSize); + ctx.restore(); + + ctx.fillStyle = "#FFFFFF"; + ctx.font = "40px Arial"; + ctx.fillText(`${user.username}'s Locker - Page ${page + 1}/${totalPages}`, 80, 50); + + let startIndex = page * itemsPerRow * maxRowsPerPage; + let endIndex = Math.min(startIndex + itemsPerRow * maxRowsPerPage, itemsData.length); + + let x = padding; + let y = 100; + for (let i = startIndex; i < endIndex; i++) { + const item = itemsData[i]; + + ctx.fillStyle = "#3A3F47"; + ctx.beginPath(); + ctx.moveTo(x + 10, y); + ctx.arcTo(x + itemSize, y, x + itemSize, y + itemSize, 10); + ctx.arcTo(x + itemSize, y + itemSize, x, y + itemSize, 10); + ctx.arcTo(x, y + itemSize, x, y, 10); + ctx.arcTo(x, y, x + itemSize, y, 10); + ctx.closePath(); + ctx.fill(); + + if (item.image) { + try { + const itemImage = await loadImage(item.image); + ctx.drawImage(itemImage, x + 10, y + 10, itemSize - 20, itemSize - 20); + } catch (err) { + console.error(`Error loading image for item ${item.name}:`, err); + } + } + + ctx.fillStyle = "#FFFFFF"; + ctx.font = "20px Arial"; + ctx.fillText(item.name, x + 10, y + itemSize + 20); + + x += itemSize + padding; + if (x + itemSize > canvasWidth) { + x = padding; + y += itemSize + padding + 40; + } + } + const attachment = new MessageAttachment(canvas.toBuffer(), `locker_page_${page + 1}.png`); + attachments.push(attachment); + } + + const chunkSize = 10; + for (let i = 0; i < attachments.length; i += chunkSize) { + const chunk = attachments.slice(i, i + chunkSize); + await interaction.followUp({ content: `Here are more pages of your ${category}:`, files: chunk, ephemeral: true }); + } + }, +}; diff --git a/install_packages.bat b/install_packages.bat index 31bb727..5dcb15f 100644 --- a/install_packages.bat +++ b/install_packages.bat @@ -4,4 +4,5 @@ title Reload Backend Package Installer npm i npm install express npm install ws +npm install canvas pause \ No newline at end of file diff --git a/routes/mcp.js b/routes/mcp.js index 0109ab7..f9d7e60 100644 --- a/routes/mcp.js +++ b/routes/mcp.js @@ -141,7 +141,7 @@ app.post("/fortnite/api/game/v2/profile/*/client/ClientQuestLogin", verifyToken, } for (var key in profile.items) { - if (profile.items[key].templateId.toLowerCase().startsWith("quest:athenadaily")) { + if (profile.items[key]?.templateId?.toLowerCase().startsWith("quest:athenadaily")) { QuestCount += 1; } } diff --git a/structs/autorotate.js b/structs/autorotate.js index 2efb0fe..81d1a16 100644 --- a/structs/autorotate.js +++ b/structs/autorotate.js @@ -329,6 +329,7 @@ function updatecfgomg(dailyItems, featuredItems) { dailyItems.forEach((item, index) => { catalogConfig[`daily${index + 1}`] = { itemGrants: formatitemgrantsyk(item), + name: item.name || "Unknown Name", price: notproperpricegen(item) }; }); @@ -336,12 +337,17 @@ function updatecfgomg(dailyItems, featuredItems) { featuredItems.forEach((item, index) => { catalogConfig[`featured${index + 1}`] = { itemGrants: formatitemgrantsyk(item), + name: item.name || "Unknown Name", price: notproperpricegen(item) }; }); - fs.writeFileSync(catalogcfg, JSON.stringify(catalogConfig, null, 2), 'utf-8'); - log.AutoRotation("The item shop has rotated!"); + try { + fs.writeFileSync(catalogcfg, JSON.stringify(catalogConfig, null, 2), 'utf-8'); + log.AutoRotation("The item shop has rotated!"); + } catch (error) { + log.error("Failed to save catalog config file:", error.message || error); + } } async function fetchItemIcon(itemName) {