AeThex-Bot-Master/aethex-bot/commands/leaderboard.js
sirpiglr e5f6956392 Add separate weekly and monthly XP tracking for leaderboards
Refactor XP tracking to differentiate between weekly and monthly periods, updating `periodic_xp` table and leaderboard queries.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: 54606488-64d4-4a79-b52c-e95591c2e4fc
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/zRLxuQq
Replit-Helium-Checkpoint-Created: true
2025-12-08 21:58:43 +00:00

318 lines
11 KiB
JavaScript

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] });
}
},
};