const Discord = require('discord.js'); const { QuickDB } = require("quick.db"); const db = new QuickDB(); const axios = require('axios'); let collector = null; module.exports = { name: "cosmetics", description: "Show a Minecraft Username's Lunar Client Cosmetics", integration_types: [0, 1], contexts: [0, 1, 2], cooldown: 10000, options: [ { name: 'username', description: 'Provide a Minecraft Username', type: 'STRING', required: false } ], run: async (client, interaction, options) => { db.add(`totalCommands`, 1); const { MessageEmbed, MessageActionRow, MessageButton } = require('discord.js'); let username = interaction.options.getString('username'); const guild = interaction.guild; const discordId = interaction.user.id let linkedUser = await db.get(`linkedUser_${discordId}`); if(!username) { username = linkedUser; } if(!linkedUser) { const noLink = new MessageEmbed() .setColor('ORANGE') .setDescription(`You have not linked an account to ${client.user}!\nPlease use: or manually enter a Username!`); interaction.reply({ embeds: [noLink], ephemeral: true }); return; } await interaction.deferReply(); try { if (collector) collector.stop(); const uuid = await getUsernameUUID(username); if (!uuid) { const noUUID = new MessageEmbed() .setColor('RED') .setDescription(`Failed to retrieve UUID for the given username.`); await interaction.editReply({ embeds: [noUUID] }); return; } const [frontSkinUrl, backSkinUrl, frontSkinName, backSkinName] = await getSkinUrls(uuid); if (!frontSkinUrl || !backSkinUrl) { const noSkinURL = new MessageEmbed() .setColor('RED') .setDescription(`Failed to retrieve Skin URLs.`); await interaction.editReply({ embeds: [noSkinURL] }); return; } const row = new MessageActionRow() .addComponents( new MessageButton() .setCustomId('front') .setLabel('Front Body') .setStyle('PRIMARY'), new MessageButton() .setCustomId('back') .setLabel('Back Body') .setStyle('PRIMARY') ); const user = await getUsernameExact(username); const cosmeticEmbed = new MessageEmbed() .setTitle(`IGN: \`${user}\` • ${frontSkinName} View`) .setColor('0x178719') .setImage(`${frontSkinUrl}`) .setFooter(`• Pets have been disabled •`); await interaction.editReply({ embeds: [cosmeticEmbed], components: [row] }); collector = interaction.channel.createMessageComponentCollector({ time: 120000 }); collector.on('collect', async buttonInteraction => { const { customId } = buttonInteraction; let skinUrl; let skinName; if (customId === 'front') { skinUrl = frontSkinUrl; skinName = frontSkinName; } else if (customId === 'back') { skinUrl = backSkinUrl; skinName = backSkinName; } const newCosmeticEmbed = new MessageEmbed() .setTitle(`IGN: \`${user}\` • ${skinName} View`) .setColor('0x178719') .setImage(skinUrl) .setFooter(`• Pets have been disabled •`); await buttonInteraction.update({ embeds: [newCosmeticEmbed] }) .catch(error => { console.error('Error updating interaction:', error); }); }); collector.on('end', async collected => { console.log('Collector ended. Collected:', collected.size); try { await interaction.editReply({ components: [] }); } catch (error) { console.error('Error removing buttons:', error); } }); } catch (error) { console.error('Error:', error); const errorEmbed = new MessageEmbed() .setColor('RED') .setDescription(`An error occurred: ${error.message}`); await interaction.editReply({ embeds: [errorEmbed] }); } } }; async function getUsernameExact(username) { try { const url = `https://api.mojang.com/users/profiles/minecraft/${username}`; const data = await fetchWithExponentialBackoff(url); return data.name; } catch (error) { console.error('Error while getting Exact Username for:', username); return null; } } async function getUsernameUUID(username) { try { const url = `https://api.mojang.com/users/profiles/minecraft/${username}`; const data = await fetchWithExponentialBackoff(url); return data.id; } catch (error) { console.error('Error while getting UUID for:', username); return null; } } async function getSkinUrls(uuid) { try { const frontSkinUrl = `https://skins.mcstats.com/body/front/${uuid}?overlay=true&shadow=true&download=false&fallbackTexture=astronaut&scale=1&cropLeft=0&cropRight=0&cropTop=0&cropBottom=0&cropMeasurement=pixels&disableCosmeticType=pet&blankColor=FFFFFF`; const backSkinUrl = `https://skins.mcstats.com/body/back/${uuid}?overlay=true&shadow=true&download=false&fallbackTexture=astronaut&scale=1&cropLeft=0&cropRight=0&cropTop=0&cropBottom=0&cropMeasurement=pixels&disableCosmeticType=pet&blankColor=FFFFFF`; const frontSkinName = `Front`; const backSkinName = `Back`; return [frontSkinUrl, backSkinUrl, frontSkinName, backSkinName]; } catch (error) { console.error('Error while getting Skin URLs for Player:', uuid); return [null, null]; } } async function fetchWithExponentialBackoff(url, retries = 5, delay = 1000) { for (let i = 0; i < retries; i++) { try { const response = await axios.get(url); return response.data; } catch (error) { if (error.response && error.response.status === 429) { console.log(`Rate limit hit, retrying in ${delay / 1000} seconds... --- Server Name: ${guild.name}`); await new Promise(resolve => setTimeout(resolve, delay)); delay *= 2; } else { throw error; } } } throw new Error('Failed to fetch data after multiple retries'); }