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
199 lines
6.6 KiB
JavaScript
199 lines
6.6 KiB
JavaScript
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)
|
|
.single();
|
|
|
|
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)
|
|
.single();
|
|
|
|
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: <t:${Math.floor(nextClaim.getTime() / 1000)}:R>`)
|
|
.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;
|
|
}
|