Introduces a new server mode configuration system (Federation/Standalone) with associated command changes, dynamic status rotation for the bot, and adds new commands and features. Replit-Commit-Author: Agent Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: b08e6ba5-7498-4b9f-b1c9-7dc11b362ddd Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/R9PkDi8 Replit-Helium-Checkpoint-Created: true
447 lines
14 KiB
JavaScript
447 lines
14 KiB
JavaScript
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)
|
|
.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("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] });
|
|
}
|