const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); const { getServerMode, EMBED_COLORS } = require("../utils/modeHelper"); const { getStandaloneLeaderboard, calculateLevel } = require("../utils/standaloneXp"); module.exports = { data: new SlashCommandBuilder() .setName("leaderboard") .setDescription("View the top contributors") .addStringOption((option) => option .setName("category") .setDescription("Leaderboard category") .setRequired(false) .addChoices( { name: "XP Leaders (All-Time)", value: "xp" }, { name: "This Week", value: "weekly" }, { name: "This Month", value: "monthly" }, { name: "Most Active (Posts)", value: "posts" }, { name: "Most Liked", value: "likes" }, { name: "Top Creators", value: "creators" } ) ), async execute(interaction, supabase) { if (!supabase) { return interaction.reply({ content: "This feature requires Supabase to be configured.", ephemeral: true }); } await interaction.deferReply(); try { const mode = await getServerMode(supabase, interaction.guildId); const category = interaction.options.getString("category") || "xp"; if (mode === 'standalone') { return handleStandaloneLeaderboard(interaction, supabase, category); } else { return handleFederatedLeaderboard(interaction, supabase, category); } } catch (error) { console.error("Leaderboard command error:", error); const embed = new EmbedBuilder() .setColor(0xff0000) .setTitle("Error") .setDescription("Failed to fetch leaderboard. Please try again."); await interaction.editReply({ embeds: [embed] }); } }, }; async function handleStandaloneLeaderboard(interaction, supabase, category) { const guildId = interaction.guildId; if (category !== 'xp' && category !== 'weekly' && category !== 'monthly') { return interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(EMBED_COLORS.standalone) .setTitle("🏠 Standalone Mode") .setDescription("This leaderboard category is only available in Federation mode.\n\nIn Standalone mode, only XP, Weekly, and Monthly leaderboards are available.") ] }); } let leaderboardData = []; let title = ""; let color = EMBED_COLORS.standalone; if (category === 'xp') { title = "Server XP Leaderboard"; const data = await getStandaloneLeaderboard(supabase, guildId, 10); for (const entry of data) { const level = calculateLevel(entry.xp || 0, 'normal'); let displayName = entry.username || 'Unknown'; try { const member = await interaction.guild.members.fetch(entry.discord_id).catch(() => null); if (member) displayName = member.displayName; } catch (e) {} leaderboardData.push({ name: displayName, value: `Level ${level} • ${(entry.xp || 0).toLocaleString()} XP`, xp: entry.xp || 0 }); } } else if (category === 'weekly' || category === 'monthly') { const periodType = category === 'weekly' ? 'week' : 'month'; title = category === 'weekly' ? 'Weekly XP Leaderboard' : 'Monthly XP Leaderboard'; const now = new Date(); let periodStart; if (category === 'weekly') { const dayOfWeek = now.getDay(); const diffToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1; periodStart = new Date(now); periodStart.setDate(now.getDate() - diffToMonday); periodStart.setHours(0, 0, 0, 0); } else { periodStart = new Date(now.getFullYear(), now.getMonth(), 1); } const periodStartStr = periodStart.toISOString().split('T')[0]; const { data: periodicData } = await supabase .from("periodic_xp") .select("discord_id, weekly_xp, monthly_xp") .eq("guild_id", guildId) .eq("period_type", periodType) .eq("period_start", periodStartStr); const aggregated = {}; for (const entry of periodicData || []) { if (!aggregated[entry.discord_id]) { aggregated[entry.discord_id] = 0; } aggregated[entry.discord_id] += category === 'weekly' ? (entry.weekly_xp || 0) : (entry.monthly_xp || 0); } const sortedUsers = Object.entries(aggregated) .sort(([, a], [, b]) => b - a) .slice(0, 10); for (const [discordId, xp] of sortedUsers) { let displayName = 'Unknown User'; try { const member = await interaction.guild.members.fetch(discordId).catch(() => null); if (member) displayName = member.displayName; } catch (e) {} leaderboardData.push({ name: displayName, value: `${xp.toLocaleString()} XP`, xp: xp }); } } const medals = ['1.', '2.', '3.']; const description = leaderboardData.length > 0 ? leaderboardData .map((user, index) => { const medal = index < 3 ? medals[index] : `${index + 1}.`; return `**${medal}** ${user.name}\n ${user.value}`; }) .join("\n\n") : "No data available yet. Be the first to contribute!"; const embed = new EmbedBuilder() .setColor(color) .setTitle(`🏠 ${title}`) .setDescription(description) .setThumbnail(interaction.guild.iconURL({ size: 128 })) .setFooter({ text: `${interaction.guild.name} • Standalone Mode`, iconURL: interaction.guild.iconURL({ size: 32 }) }) .setTimestamp(); if (leaderboardData.length > 0) { embed.addFields({ name: 'Showing', value: `Top ${leaderboardData.length} contributors`, inline: true }); } await interaction.editReply({ embeds: [embed] }); } async function handleFederatedLeaderboard(interaction, supabase, category) { const guildId = interaction.guildId; let leaderboardData = []; let title = ""; let emoji = ""; let color = 0x7c3aed; let periodInfo = ""; if (category === "weekly") { title = "Weekly XP Leaderboard"; emoji = ""; color = 0x22c55e; const now = new Date(); const dayOfWeek = now.getDay(); const diffToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1; const weekStart = new Date(now); weekStart.setDate(now.getDate() - diffToMonday); weekStart.setHours(0, 0, 0, 0); const weekStartStr = weekStart.toISOString().split('T')[0]; const weekEnd = new Date(weekStart); weekEnd.setDate(weekStart.getDate() + 6); periodInfo = `${weekStart.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} - ${weekEnd.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}`; const { data: periodicData } = await supabase .from("periodic_xp") .select("discord_id, weekly_xp, weekly_messages") .eq("guild_id", guildId) .eq("period_type", "week") .eq("period_start", weekStartStr); const aggregated = {}; for (const entry of periodicData || []) { if (!aggregated[entry.discord_id]) { aggregated[entry.discord_id] = { xp: 0, messages: 0 }; } aggregated[entry.discord_id].xp += entry.weekly_xp || 0; aggregated[entry.discord_id].messages += entry.weekly_messages || 0; } const sortedUsers = Object.entries(aggregated) .sort(([, a], [, b]) => b.xp - a.xp) .slice(0, 10); for (const [discordId, data] of sortedUsers) { try { const member = await interaction.guild.members.fetch(discordId).catch(() => null); const displayName = member?.displayName || member?.user?.username || "Unknown User"; leaderboardData.push({ name: displayName, value: `${data.xp.toLocaleString()} XP • ${data.messages} msgs`, xp: data.xp }); } catch (e) { continue; } } } else if (category === "monthly") { title = "Monthly XP Leaderboard"; emoji = ""; color = 0x3b82f6; const now = new Date(); const monthStart = new Date(now.getFullYear(), now.getMonth(), 1); const monthStartStr = monthStart.toISOString().split('T')[0]; periodInfo = monthStart.toLocaleDateString('en-US', { month: 'long', year: 'numeric' }); const { data: periodicData } = await supabase .from("periodic_xp") .select("discord_id, monthly_xp, monthly_messages") .eq("guild_id", guildId) .eq("period_type", "month") .eq("period_start", monthStartStr); const aggregated = {}; for (const entry of periodicData || []) { if (!aggregated[entry.discord_id]) { aggregated[entry.discord_id] = { xp: 0, messages: 0 }; } aggregated[entry.discord_id].xp += entry.monthly_xp || 0; aggregated[entry.discord_id].messages += entry.monthly_messages || 0; } const sortedUsers = Object.entries(aggregated) .sort(([, a], [, b]) => b.xp - a.xp) .slice(0, 10); for (const [discordId, data] of sortedUsers) { try { const member = await interaction.guild.members.fetch(discordId).catch(() => null); const displayName = member?.displayName || member?.user?.username || "Unknown User"; leaderboardData.push({ name: displayName, value: `${data.xp.toLocaleString()} XP • ${data.messages} msgs`, xp: data.xp }); } catch (e) { continue; } } } else if (category === "xp") { title = "XP Leaderboard (All-Time)"; emoji = ""; color = 0xfbbf24; const { data: profiles } = await supabase .from("user_profiles") .select("id, username, full_name, avatar_url, xp") .not("xp", "is", null) .order("xp", { ascending: false }) .limit(10); for (const profile of profiles || []) { const level = Math.floor(Math.sqrt((profile.xp || 0) / 100)); leaderboardData.push({ name: profile.full_name || profile.username || "Anonymous", value: `Level ${level} • ${(profile.xp || 0).toLocaleString()} XP`, username: profile.username, xp: profile.xp || 0 }); } } else if (category === "posts") { title = "Most Active Posters"; emoji = ""; color = 0xef4444; const { data: posts } = await supabase .from("community_posts") .select("user_id") .not("user_id", "is", null); const postCounts = {}; posts?.forEach((post) => { postCounts[post.user_id] = (postCounts[post.user_id] || 0) + 1; }); const sortedUsers = Object.entries(postCounts) .sort(([, a], [, b]) => b - a) .slice(0, 10); for (const [userId, count] of sortedUsers) { const { data: profile } = await supabase .from("user_profiles") .select("username, full_name, avatar_url") .eq("id", userId) .maybeSingle(); if (profile) { leaderboardData.push({ name: profile.full_name || profile.username || "Anonymous", value: `${count} posts`, username: profile.username, }); } } } else if (category === "likes") { title = "Most Liked Users"; emoji = ""; color = 0xec4899; const { data: posts } = await supabase .from("community_posts") .select("user_id, likes_count") .not("user_id", "is", null) .order("likes_count", { ascending: false }); const likeCounts = {}; posts?.forEach((post) => { likeCounts[post.user_id] = (likeCounts[post.user_id] || 0) + (post.likes_count || 0); }); const sortedUsers = Object.entries(likeCounts) .sort(([, a], [, b]) => b - a) .slice(0, 10); for (const [userId, count] of sortedUsers) { const { data: profile } = await supabase .from("user_profiles") .select("username, full_name, avatar_url") .eq("id", userId) .maybeSingle(); if (profile) { leaderboardData.push({ name: profile.full_name || profile.username || "Anonymous", value: `${count.toLocaleString()} likes`, username: profile.username, }); } } } else if (category === "creators") { title = "Top Creators"; emoji = ""; color = 0x8b5cf6; const { data: creators } = await supabase .from("aethex_creators") .select("user_id, total_projects, verified, featured") .order("total_projects", { ascending: false }) .limit(10); for (const creator of creators || []) { const { data: profile } = await supabase .from("user_profiles") .select("username, full_name, avatar_url") .eq("id", creator.user_id) .maybeSingle(); if (profile) { const badges = []; if (creator.verified) badges.push("Verified"); if (creator.featured) badges.push("Featured"); leaderboardData.push({ name: profile.full_name || profile.username || "Anonymous", value: `${creator.total_projects || 0} projects ${badges.join(" ")}`, username: profile.username, }); } } } const medals = ['1.', '2.', '3.']; const description = leaderboardData.length > 0 ? leaderboardData .map((user, index) => { const medal = index < 3 ? medals[index] : `${index + 1}.`; return `**${medal}** ${user.name}\n ${user.value}`; }) .join("\n\n") : "No data available yet. Be the first to contribute!"; const embed = new EmbedBuilder() .setColor(color) .setTitle(`🌐 ${title}`) .setDescription(description) .setThumbnail(interaction.guild.iconURL({ size: 128 })) .setFooter({ text: `${interaction.guild.name} • Federation Mode`, iconURL: interaction.guild.iconURL({ size: 32 }) }) .setTimestamp(); if (periodInfo) { embed.addFields({ name: 'Period', value: periodInfo, inline: true }); } if (leaderboardData.length > 0) { embed.addFields({ name: 'Showing', value: `Top ${leaderboardData.length} contributors`, inline: true }); } if (category === "weekly" || category === "monthly") { embed.addFields({ name: 'Tip', value: 'Leaderboards reset automatically at the start of each period!', inline: false }); } await interaction.editReply({ embeds: [embed] }); }