const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); const { checkAchievements } = require('./achievements'); const { getUserStats, calculateLevel, updateQuestProgress } = require('../listeners/xpTracker'); const { getServerMode, EMBED_COLORS } = require('../utils/modeHelper'); const { claimStandaloneDaily, getStandaloneXp, calculateLevel: calcStandaloneLevel } = require('../utils/standaloneXp'); const DAILY_XP = 50; const STREAK_BONUS = 10; const MAX_STREAK_BONUS = 100; module.exports = { data: new SlashCommandBuilder() .setName('daily') .setDescription('Claim your daily XP bonus'), async execute(interaction, supabase, client) { if (!supabase) { return interaction.reply({ content: 'Database not configured.', ephemeral: true }); } await interaction.deferReply(); try { const mode = await getServerMode(supabase, interaction.guildId); if (mode === 'standalone') { return handleStandaloneDaily(interaction, supabase); } else { return handleFederatedDaily(interaction, supabase, client); } } catch (error) { console.error('Daily error:', error); await interaction.editReply({ content: 'Failed to claim daily reward.' }); } }, }; async function handleStandaloneDaily(interaction, supabase) { const result = await claimStandaloneDaily( supabase, interaction.user.id, interaction.guildId, interaction.user.username ); if (!result.success) { return interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(EMBED_COLORS.warning) .setTitle('Already Claimed!') .setDescription(result.message) ] }); } const level = calcStandaloneLevel(result.totalXp, 'normal'); const embed = new EmbedBuilder() .setColor(EMBED_COLORS.success) .setTitle('Daily Reward Claimed!') .setDescription(`You received **+${result.xpGained} XP**!`) .addFields( { name: 'Base XP', value: `+50`, inline: true }, { name: 'Streak Bonus', value: `+${Math.min((result.streak - 1) * 5, 100)}`, inline: true }, { name: 'Current Streak', value: `${result.streak} days`, inline: true }, { name: 'Total XP', value: result.totalXp.toLocaleString(), inline: true }, { name: 'Level', value: `${level}`, inline: true } ) .setFooter({ text: `🏠 Standalone Mode • Come back tomorrow!` }) .setTimestamp(); await interaction.editReply({ embeds: [embed] }); } async function handleFederatedDaily(interaction, supabase, client) { const { data: link } = await supabase .from('discord_links') .select('user_id') .eq('discord_id', interaction.user.id) .maybeSingle(); if (!link) { return interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(0xff6b6b) .setDescription('You need to link your account first! Use `/verify` to get started.') ] }); } const { data: profile } = await supabase .from('user_profiles') .select('xp, daily_streak, last_daily, prestige_level, total_xp_earned') .eq('id', link.user_id) .maybeSingle(); const now = new Date(); const lastDaily = profile?.last_daily ? new Date(profile.last_daily) : null; const currentXp = profile?.xp || 0; const prestige = profile?.prestige_level || 0; let streak = profile?.daily_streak || 0; if (lastDaily) { const hoursSince = (now - lastDaily) / (1000 * 60 * 60); if (hoursSince < 20) { const nextClaim = new Date(lastDaily.getTime() + 20 * 60 * 60 * 1000); return interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(0xfbbf24) .setTitle('Already Claimed!') .setDescription(`You've already claimed your daily XP.\nNext claim: `) .addFields({ name: 'Current Streak', value: `${streak} days` }) ] }); } if (hoursSince > 48) { streak = 0; } } streak += 1; const streakBonus = Math.min(streak * STREAK_BONUS, MAX_STREAK_BONUS); const prestigeDailyBonus = prestige >= 4 ? 25 : 0; let totalXp = DAILY_XP + streakBonus + prestigeDailyBonus; if (prestige > 0) { const prestigeMultiplier = 1 + (prestige * 0.05); totalXp = Math.floor(totalXp * prestigeMultiplier); } const newXp = currentXp + totalXp; const totalEarned = (profile?.total_xp_earned || currentXp) + totalXp; await supabase .from('user_profiles') .update({ xp: newXp, daily_streak: streak, last_daily: now.toISOString(), total_xp_earned: totalEarned }) .eq('id', link.user_id); const newLevel = Math.floor(Math.sqrt(newXp / 100)); const oldLevel = Math.floor(Math.sqrt(currentXp / 100)); const embed = new EmbedBuilder() .setColor(prestige > 0 ? getPrestigeColor(prestige) : 0x00ff00) .setTitle('Daily Reward Claimed!') .setDescription(`You received **+${totalXp} XP**!${prestige > 0 ? ` *(includes P${prestige} bonus)*` : ''}`) .addFields( { name: 'Base XP', value: `+${DAILY_XP}`, inline: true }, { name: 'Streak Bonus', value: `+${streakBonus}`, inline: true }, { name: 'Current Streak', value: `${streak} days`, inline: true }, { name: 'Total XP', value: newXp.toLocaleString(), inline: true }, { name: 'Level', value: `${newLevel}`, inline: true } ); if (prestige > 0) { embed.addFields({ name: 'Prestige Bonus', value: `+${prestige * 5}% XP${prestigeDailyBonus > 0 ? ` + ${prestigeDailyBonus} daily bonus` : ''}`, inline: true }); } embed.setFooter({ text: '🌐 Federation • Come back tomorrow to keep your streak!' }) .setTimestamp(); if (newLevel > oldLevel) { embed.addFields({ name: 'Level Up!', value: `You reached level ${newLevel}!` }); } await interaction.editReply({ embeds: [embed] }); const guildId = interaction.guildId; const stats = await getUserStats(supabase, link.user_id, guildId); stats.level = newLevel; stats.prestige = prestige; stats.totalXp = totalEarned; stats.dailyStreak = streak; await checkAchievements(link.user_id, interaction.member, stats, supabase, guildId, client); await updateQuestProgress(supabase, link.user_id, guildId, 'daily_claims', 1); await updateQuestProgress(supabase, link.user_id, guildId, 'xp_earned', totalXp); if (newLevel > oldLevel) { await updateQuestProgress(supabase, link.user_id, guildId, 'level_ups', 1); } } function getPrestigeColor(level) { const colors = [0x6b7280, 0xcd7f32, 0xc0c0c0, 0xffd700, 0xe5e4e2, 0xb9f2ff, 0xff4500, 0x9400d3, 0xffd700, 0xff69b4, 0x7c3aed]; return colors[Math.min(level, 10)] || 0x00ff00; }