AeThex-Bot-Master/aethex-bot/commands/badges.js
sirpiglr 8c2dee5d9e Add pricing page and update navigation links
Replaced `.single()` with `.maybeSingle()` in multiple command files to handle cases where no record is found, and added a new /pricing route and navigation links to the UI.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: e91d020a-35a6-4add-9945-887dd3ecae9f
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/tdDjujk
Replit-Helium-Checkpoint-Created: true
2025-12-10 02:36:59 +00:00

159 lines
5.9 KiB
JavaScript

const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const BADGE_INFO = {
'verified': { emoji: '✅', name: 'Verified', description: 'Linked Discord to AeThex' },
'early_adopter': { emoji: '🌟', name: 'Early Adopter', description: 'Joined during early access' },
'contributor': { emoji: '💎', name: 'Contributor', description: 'Contributed to the community' },
'creator': { emoji: '🎨', name: 'Creator', description: 'Published projects on Studio' },
'supporter': { emoji: '❤️', name: 'Supporter', description: 'Donated to the Foundation' },
'level_10': { emoji: '🔟', name: 'Level 10', description: 'Reached level 10' },
'level_25': { emoji: '🏆', name: 'Level 25', description: 'Reached level 25' },
'level_50': { emoji: '👑', name: 'Level 50', description: 'Reached level 50' },
'streak_7': { emoji: '🔥', name: 'Week Streak', description: '7 day daily claim streak' },
'streak_30': { emoji: '💪', name: 'Month Streak', description: '30 day daily claim streak' },
'helpful': { emoji: '🤝', name: 'Helpful', description: 'Helped 10+ community members' },
'bug_hunter': { emoji: '🐛', name: 'Bug Hunter', description: 'Reported a valid bug' },
};
async function getServerAchievements(supabase, guildId, userId) {
try {
const [achievementsResult, earnedResult] = await Promise.all([
supabase.from('achievements').select('*').eq('guild_id', guildId),
supabase.from('user_achievements').select('achievement_id').eq('user_id', userId).eq('guild_id', guildId)
]);
const allAchievements = achievementsResult.data || [];
const earnedIds = new Set((earnedResult.data || []).map(e => e.achievement_id));
return {
earned: allAchievements.filter(a => earnedIds.has(a.id)),
available: allAchievements.filter(a => !earnedIds.has(a.id) && !a.hidden)
};
} catch (e) {
return { earned: [], available: [] };
}
}
module.exports = {
data: new SlashCommandBuilder()
.setName('badges')
.setDescription('View your earned badges across all platforms')
.addUserOption(option =>
option.setName('user')
.setDescription('User to view (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 { data: link } = await supabase
.from('discord_links')
.select('user_id')
.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.`)
]
});
}
const { data: profile } = await supabase
.from('user_profiles')
.select('username, avatar_url, badges, xp, daily_streak')
.eq('id', link.user_id)
.maybeSingle();
let earnedBadges = [];
if (profile?.badges) {
earnedBadges = typeof profile.badges === 'string'
? JSON.parse(profile.badges)
: profile.badges;
}
earnedBadges.push('verified');
const xp = profile?.xp || 0;
const level = Math.floor(Math.sqrt(xp / 100));
if (level >= 10) earnedBadges.push('level_10');
if (level >= 25) earnedBadges.push('level_25');
if (level >= 50) earnedBadges.push('level_50');
const streak = profile?.daily_streak || 0;
if (streak >= 7) earnedBadges.push('streak_7');
if (streak >= 30) earnedBadges.push('streak_30');
earnedBadges = [...new Set(earnedBadges)];
// Validate avatar URL - must be http/https, not base64
let avatarUrl = target.displayAvatarURL();
if (profile?.avatar_url && profile.avatar_url.startsWith('http')) {
avatarUrl = profile.avatar_url;
}
const embed = new EmbedBuilder()
.setColor(0x7c3aed)
.setTitle(`${profile?.username || target.tag}'s Badges`)
.setThumbnail(avatarUrl)
.setTimestamp();
if (earnedBadges.length > 0) {
const badgeDisplay = earnedBadges.map(key => {
const info = BADGE_INFO[key];
if (info) {
return `${info.emoji} **${info.name}**\n${info.description}`;
}
return `🏅 **${key}**`;
}).join('\n\n');
embed.setDescription(badgeDisplay);
embed.addFields({ name: 'Total Badges', value: `${earnedBadges.length}`, inline: true });
} else {
embed.setDescription('No badges earned yet. Keep engaging to earn badges!');
}
const allBadgeKeys = Object.keys(BADGE_INFO);
const lockedBadges = allBadgeKeys.filter(k => !earnedBadges.includes(k));
if (lockedBadges.length > 0 && lockedBadges.length <= 6) {
const lockedDisplay = lockedBadges.map(key => {
const info = BADGE_INFO[key];
return `🔒 ${info.name}`;
}).join(', ');
embed.addFields({ name: 'Locked Badges', value: lockedDisplay });
}
// Add server achievements
const serverAchievements = await getServerAchievements(supabase, interaction.guildId, link.user_id);
if (serverAchievements.earned.length > 0) {
const serverBadgeDisplay = serverAchievements.earned.map(ach =>
`${ach.icon} **${ach.name}**\n${ach.description}`
).join('\n\n').slice(0, 1024);
embed.addFields({
name: `Server Achievements (${serverAchievements.earned.length})`,
value: serverBadgeDisplay
});
}
await interaction.editReply({ embeds: [embed] });
} catch (error) {
console.error('Badges error:', error);
await interaction.editReply({ content: 'Failed to fetch badge data.' });
}
},
};