const { SlashCommandBuilder, EmbedBuilder } = require("discord.js"); module.exports = { data: new SlashCommandBuilder() .setName("leaderboard") .setDescription("View the top AeThex 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 category = interaction.options.getString("category") || "xp"; 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' })}`; // Fetch weekly records using period_type 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); // Aggregate per user (handles multiple records if they exist) 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; } // Sort and limit after aggregation 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' }); // Fetch monthly records using period_type 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); // Aggregate all entries per user first 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; } // Sort and limit AFTER aggregation 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) .single(); 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) .single(); 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) .single(); if (profile) { const badges = []; if (creator.verified) badges.push("✅"); if (creator.featured) badges.push("⭐"); leaderboardData.push({ name: profile.full_name || profile.username || "Anonymous", value: `${creator.total_projects || 0} projects ${badges.join(" ")}`, username: profile.username, }); } } } const medals = ['🥇', '🥈', '🥉']; 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(`${emoji} ${title}`) .setDescription(description) .setThumbnail(interaction.guild.iconURL({ size: 128 })) .setFooter({ text: `${interaction.guild.name} • Updated in real-time`, 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] }); } 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] }); } }, };