Adds a comprehensive achievement system to the bot, including new triggers for various user actions, the ability to create, manage, and view achievements, and integration with existing XP and leveling systems. This also involves updating user statistics tracking to support achievement triggers. Replit-Commit-Author: Agent Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 28bc7e36-c36d-4b62-b518-bcc2c649398e Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/yTaZipL Replit-Helium-Checkpoint-Created: true
159 lines
5.9 KiB
JavaScript
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)
|
|
.single();
|
|
|
|
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)
|
|
.single();
|
|
|
|
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.' });
|
|
}
|
|
},
|
|
};
|