const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); const { getServerMode, getEmbedColor, EMBED_COLORS } = require('../utils/modeHelper'); const { getStandaloneXp, calculateLevel } = require('../utils/standaloneXp'); module.exports = { data: new SlashCommandBuilder() .setName('rank') .setDescription('View your level and XP') .addUserOption(option => option.setName('user') .setDescription('User to check (defaults to yourself)') .setRequired(false) ), async execute(interaction, supabase, client) { if (!supabase) { return interaction.reply({ content: 'Database not configured.', ephemeral: true }); } const target = interaction.options.getUser('user') || interaction.user; await interaction.deferReply(); try { const mode = await getServerMode(supabase, interaction.guildId); if (mode === 'standalone') { return handleStandaloneRank(interaction, supabase, target); } else { return handleFederatedRank(interaction, supabase, target); } } catch (error) { console.error('Rank error:', error); await interaction.editReply({ content: 'Failed to fetch rank data.' }); } }, }; async function handleStandaloneRank(interaction, supabase, target) { const data = await getStandaloneXp(supabase, target.id, interaction.guildId); if (!data) { return interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(EMBED_COLORS.standalone) .setDescription(`${target.id === interaction.user.id ? 'You have' : `${target.tag} has`} no XP yet. Start chatting to earn XP!`) ] }); } const xp = data.xp || 0; const prestige = data.prestige_level || 0; const totalXpEarned = data.total_xp_earned || xp; const level = calculateLevel(xp, 'normal'); const currentLevelXp = level * level * 100; const nextLevelXp = (level + 1) * (level + 1) * 100; const progress = xp - currentLevelXp; const needed = nextLevelXp - currentLevelXp; const progressPercent = Math.floor((progress / needed) * 100); const progressBar = createProgressBar(progressPercent); const prestigeInfo = getPrestigeInfo(prestige); const { count: rankPosition } = await supabase .from('guild_user_xp') .select('*', { count: 'exact', head: true }) .eq('guild_id', interaction.guildId) .gt('xp', xp); const embed = new EmbedBuilder() .setColor(prestigeInfo.color) .setTitle(`${prestigeInfo.icon} ${target.tag}'s Rank`) .setThumbnail(target.displayAvatarURL()) .addFields( { name: 'Prestige', value: prestige > 0 ? `**${prestigeInfo.name}** (P${prestige})` : 'Not prestiged', inline: true }, { name: 'Level', value: `**${level}**`, inline: true }, { name: 'Server Rank', value: `#${(rankPosition || 0) + 1}`, inline: true }, { name: 'Current XP', value: `**${xp.toLocaleString()}**`, inline: true }, { name: 'XP Bonus', value: prestige > 0 ? `+${prestige * 5}%` : 'None', inline: true }, { name: 'Total XP Earned', value: totalXpEarned.toLocaleString(), inline: true }, { name: 'Progress to Next Level', value: `${progressBar}\n${progress.toLocaleString()} / ${needed.toLocaleString()} XP (${progressPercent}%)` } ) .setFooter({ text: `🏠 Standalone Mode • ${interaction.guild.name}` }) .setTimestamp(); await interaction.editReply({ embeds: [embed] }); } async function handleFederatedRank(interaction, supabase, target) { const { data: link } = await supabase .from('discord_links') .select('user_id, primary_arm') .eq('discord_id', target.id) .maybeSingle(); if (!link) { return interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(0xff6b6b) .setDescription(`${target.id === interaction.user.id ? 'You are' : `${target.tag} is`} not linked to AeThex. Use \`/verify\` to link your account.`) ] }); } const { data: profile } = await supabase .from('user_profiles') .select('username, avatar_url, xp, bio, prestige_level, total_xp_earned') .eq('id', link.user_id) .maybeSingle(); const xp = profile?.xp || 0; const prestige = profile?.prestige_level || 0; const totalXpEarned = profile?.total_xp_earned || xp; const level = Math.floor(Math.sqrt(xp / 100)); const currentLevelXp = level * level * 100; const nextLevelXp = (level + 1) * (level + 1) * 100; const progress = xp - currentLevelXp; const needed = nextLevelXp - currentLevelXp; const progressPercent = Math.floor((progress / needed) * 100); const progressBar = createProgressBar(progressPercent); const prestigeInfo = getPrestigeInfo(prestige); const { count: rankPosition } = await supabase .from('user_profiles') .select('*', { count: 'exact', head: true }) .gt('xp', xp); let avatarUrl = target.displayAvatarURL(); if (profile?.avatar_url && profile.avatar_url.startsWith('http')) { avatarUrl = profile.avatar_url; } const embed = new EmbedBuilder() .setColor(prestigeInfo.color) .setTitle(`${prestigeInfo.icon} ${profile?.username || target.tag}'s Rank`) .setThumbnail(avatarUrl) .addFields( { name: 'Prestige', value: prestige > 0 ? `**${prestigeInfo.name}** (P${prestige})` : 'Not prestiged', inline: true }, { name: 'Level', value: `**${level}**`, inline: true }, { name: 'Global Rank', value: `#${(rankPosition || 0) + 1}`, inline: true }, { name: 'Current XP', value: `**${xp.toLocaleString()}**`, inline: true }, { name: 'XP Bonus', value: prestige > 0 ? `+${prestige * 5}%` : 'None', inline: true }, { name: 'Total XP Earned', value: totalXpEarned.toLocaleString(), inline: true }, { name: 'Progress to Next Level', value: `${progressBar}\n${progress.toLocaleString()} / ${needed.toLocaleString()} XP (${progressPercent}%)` }, { name: 'Primary Realm', value: link.primary_arm || 'None set', inline: true } ) .setFooter({ text: prestige >= 1 ? `🌐 Federation • Prestige ${prestige} | XP earned across Discord & AeThex platforms` : '🌐 Federation • XP earned across Discord & AeThex platforms' }) .setTimestamp(); await interaction.editReply({ embeds: [embed] }); } function createProgressBar(percent) { const filled = Math.floor(percent / 10); const empty = 10 - filled; return '█'.repeat(filled) + '░'.repeat(empty); } function getPrestigeInfo(level) { const prestiges = [ { name: 'Unprestiged', icon: '⚪', color: 0x6b7280 }, { name: 'Bronze', icon: '🥉', color: 0xcd7f32 }, { name: 'Silver', icon: '🥈', color: 0xc0c0c0 }, { name: 'Gold', icon: '🥇', color: 0xffd700 }, { name: 'Platinum', icon: '💎', color: 0xe5e4e2 }, { name: 'Diamond', icon: '💠', color: 0xb9f2ff }, { name: 'Master', icon: '🔥', color: 0xff4500 }, { name: 'Grandmaster', icon: '⚔️', color: 0x9400d3 }, { name: 'Champion', icon: '👑', color: 0xffd700 }, { name: 'Legend', icon: '🌟', color: 0xff69b4 }, { name: 'Mythic', icon: '🌈', color: 0x7c3aed } ]; return prestiges[Math.min(level, 10)] || prestiges[0]; }