From d7b41b5b4feaeb3a393a0c9082db601af93288e5 Mon Sep 17 00:00:00 2001 From: sirpiglr <49359077-sirpiglr@users.noreply.replit.com> Date: Mon, 8 Dec 2025 21:21:59 +0000 Subject: [PATCH] Introduce prestige system for permanent XP bonuses and rewards Integrates a new prestige system across multiple commands, including daily rewards, profile, rank, reaction XP, voice XP, and message XP. This involves fetching and utilizing `prestige_level` and `total_xp_earned` from `user_profiles`, applying prestige-based XP multipliers, updating embed messages with prestige information, and adding new helper functions like `getPrestigeInfo` and `getPrestigeColor`. A new `/prestige` command is also introduced with subcommands to view prestige status, perform the prestige action, and view rewards. Replit-Commit-Author: Agent Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 0d5b8d92-a33c-43bb-abda-1d75baaf3b97 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/ZjyNKqu Replit-Helium-Checkpoint-Created: true --- aethex-bot/commands/daily.js | 39 ++- aethex-bot/commands/prestige.js | 324 +++++++++++++++++++++++++ aethex-bot/commands/profile.js | 24 ++ aethex-bot/commands/rank.js | 33 ++- aethex-bot/listeners/reactionXp.js | 12 +- aethex-bot/listeners/voiceXp.js | 12 +- aethex-bot/listeners/xpTracker.js | 13 +- replit.md | 377 +++-------------------------- 8 files changed, 470 insertions(+), 364 deletions(-) create mode 100644 aethex-bot/commands/prestige.js diff --git a/aethex-bot/commands/daily.js b/aethex-bot/commands/daily.js index 928b2d5..a275a55 100644 --- a/aethex-bot/commands/daily.js +++ b/aethex-bot/commands/daily.js @@ -35,13 +35,14 @@ module.exports = { const { data: profile } = await supabase .from('user_profiles') - .select('xp, daily_streak, last_daily') + .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) { @@ -67,15 +68,29 @@ module.exports = { streak += 1; const streakBonus = Math.min(streak * STREAK_BONUS, MAX_STREAK_BONUS); - const totalXp = DAILY_XP + streakBonus; + + // Prestige level 4+ gets bonus daily XP (+25) + const prestigeDailyBonus = prestige >= 4 ? 25 : 0; + + // Base total before prestige multiplier + let totalXp = DAILY_XP + streakBonus + prestigeDailyBonus; + + // Apply prestige XP bonus (+5% per prestige level) + 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() + last_daily: now.toISOString(), + total_xp_earned: totalEarned }) .eq('id', link.user_id); @@ -83,17 +98,22 @@ module.exports = { const oldLevel = Math.floor(Math.sqrt(currentXp / 100)); const embed = new EmbedBuilder() - .setColor(0x00ff00) + .setColor(prestige > 0 ? getPrestigeColor(prestige) : 0x00ff00) .setTitle('Daily Reward Claimed!') - .setDescription(`You received **+${totalXp} XP**!`) + .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 } - ) - .setFooter({ text: 'Come back tomorrow to keep your streak!' }) + ); + + if (prestige > 0) { + embed.addFields({ name: 'Prestige Bonus', value: `+${prestige * 5}% XP${prestigeDailyBonus > 0 ? ` + ${prestigeDailyBonus} daily bonus` : ''}`, inline: true }); + } + + embed.setFooter({ text: 'Come back tomorrow to keep your streak!' }) .setTimestamp(); if (newLevel > oldLevel) { @@ -108,3 +128,8 @@ module.exports = { } }, }; + +function getPrestigeColor(level) { + const colors = [0x6b7280, 0xcd7f32, 0xc0c0c0, 0xffd700, 0xe5e4e2, 0xb9f2ff, 0xff4500, 0x9400d3, 0xffd700, 0xff69b4, 0x7c3aed]; + return colors[Math.min(level, 10)] || 0x00ff00; +} diff --git a/aethex-bot/commands/prestige.js b/aethex-bot/commands/prestige.js new file mode 100644 index 0000000..f0e72d0 --- /dev/null +++ b/aethex-bot/commands/prestige.js @@ -0,0 +1,324 @@ +const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('prestige') + .setDescription('Prestige system - reset your level for permanent rewards') + .addSubcommand(sub => + sub.setName('view') + .setDescription('View your prestige status and rewards') + .addUserOption(opt => + opt.setName('user') + .setDescription('User to check') + .setRequired(false) + ) + ) + .addSubcommand(sub => + sub.setName('up') + .setDescription('Prestige up! Reset your XP for permanent rewards') + ) + .addSubcommand(sub => + sub.setName('rewards') + .setDescription('View all prestige rewards') + ), + + async execute(interaction, supabase, client) { + if (!supabase) { + return interaction.reply({ content: 'Database not configured.', ephemeral: true }); + } + + const sub = interaction.options.getSubcommand(); + + if (sub === 'view') { + return viewPrestige(interaction, supabase); + } else if (sub === 'up') { + return prestigeUp(interaction, supabase, client); + } else if (sub === 'rewards') { + return viewRewards(interaction); + } + }, +}; + +async function viewPrestige(interaction, supabase) { + 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, xp, prestige_level, total_xp_earned') + .eq('id', link.user_id) + .single(); + + const prestige = profile?.prestige_level || 0; + const totalXpEarned = profile?.total_xp_earned || profile?.xp || 0; + const currentXp = profile?.xp || 0; + const level = Math.floor(Math.sqrt(currentXp / 100)); + + const prestigeInfo = getPrestigeInfo(prestige); + const nextPrestigeReq = getPrestigeRequirement(prestige + 1); + const canPrestige = level >= 50; + + const embed = new EmbedBuilder() + .setColor(prestigeInfo.color) + .setTitle(`${prestigeInfo.icon} ${profile?.username || target.tag}'s Prestige`) + .setThumbnail(target.displayAvatarURL({ size: 256 })) + .addFields( + { name: 'Prestige Level', value: `**${prestigeInfo.name}** (${prestige})`, inline: true }, + { name: 'XP Bonus', value: `+${prestige * 5}%`, inline: true }, + { name: 'Current Level', value: `${level}`, inline: true }, + { name: 'Total XP Earned', value: totalXpEarned.toLocaleString(), inline: true }, + { name: 'Current XP', value: currentXp.toLocaleString(), inline: true }, + { name: 'Can Prestige?', value: canPrestige ? 'āœ… Yes (Level 50+)' : `āŒ Need Level 50 (${level}/50)`, inline: true } + ) + .setFooter({ text: `Next prestige requirement: Level 50` }) + .setTimestamp(); + + if (prestige > 0) { + embed.addFields({ + name: 'šŸ† Prestige Rewards Unlocked', + value: getUnlockedRewards(prestige).join('\n') || 'None yet', + inline: false + }); + } + + await interaction.editReply({ embeds: [embed] }); + } catch (error) { + console.error('Prestige view error:', error); + await interaction.editReply({ content: 'Failed to fetch prestige data.' }); + } +} + +async function prestigeUp(interaction, supabase, client) { + await interaction.deferReply(); + + try { + 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 must link your Discord to AeThex first. Use `/verify` to link.') + ] + }); + } + + const { data: profile } = await supabase + .from('user_profiles') + .select('username, xp, prestige_level, total_xp_earned') + .eq('id', link.user_id) + .single(); + + const currentXp = profile?.xp || 0; + const level = Math.floor(Math.sqrt(currentXp / 100)); + const prestige = profile?.prestige_level || 0; + const totalEarned = profile?.total_xp_earned || currentXp; + + if (level < 50) { + return interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setColor(0xff6b6b) + .setTitle('āŒ Cannot Prestige') + .setDescription(`You need to reach **Level 50** to prestige.\nCurrent level: **${level}**/50`) + ] + }); + } + + if (prestige >= 10) { + return interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setColor(0xffd700) + .setTitle('šŸ‘‘ Maximum Prestige!') + .setDescription('You have reached the maximum prestige level! You are a true legend.') + ] + }); + } + + const newPrestige = prestige + 1; + const newPrestigeInfo = getPrestigeInfo(newPrestige); + const xpBonus = newPrestige * 5; + + const confirmEmbed = new EmbedBuilder() + .setColor(0xf59e0b) + .setTitle('āš ļø Confirm Prestige') + .setDescription(`Are you sure you want to prestige?\n\n**What will happen:**\n• Your XP will reset to **0**\n• Your level will reset to **0**\n• You gain **Prestige ${newPrestige}** (${newPrestigeInfo.name})\n• You get a permanent **+${xpBonus}%** XP bonus\n• You unlock new **prestige rewards**\n\n**Current Stats:**\n• Level: ${level}\n• XP: ${currentXp.toLocaleString()}`) + .setFooter({ text: 'This action cannot be undone!' }); + + const row = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId('prestige_confirm') + .setLabel('✨ Prestige Up!') + .setStyle(ButtonStyle.Success), + new ButtonBuilder() + .setCustomId('prestige_cancel') + .setLabel('Cancel') + .setStyle(ButtonStyle.Secondary) + ); + + const response = await interaction.editReply({ embeds: [confirmEmbed], components: [row] }); + + try { + const confirmation = await response.awaitMessageComponent({ + filter: i => i.user.id === interaction.user.id, + time: 60000 + }); + + if (confirmation.customId === 'prestige_confirm') { + const { error } = await supabase + .from('user_profiles') + .update({ + xp: 0, + prestige_level: newPrestige, + total_xp_earned: totalEarned + currentXp + }) + .eq('id', link.user_id); + + if (error) { + console.error('Prestige update error:', error); + return confirmation.update({ + embeds: [ + new EmbedBuilder() + .setColor(0xff0000) + .setDescription('Failed to prestige. Please try again.') + ], + components: [] + }); + } + + const successEmbed = new EmbedBuilder() + .setColor(newPrestigeInfo.color) + .setTitle(`${newPrestigeInfo.icon} Prestige ${newPrestige} Achieved!`) + .setDescription(`Congratulations! You are now **${newPrestigeInfo.name}**!\n\n**Rewards Unlocked:**\n${getNewRewards(newPrestige).join('\n')}`) + .addFields( + { name: 'XP Bonus', value: `+${xpBonus}%`, inline: true }, + { name: 'Total XP Earned (All Time)', value: (totalEarned + currentXp).toLocaleString(), inline: true } + ) + .setThumbnail(interaction.user.displayAvatarURL({ size: 256 })) + .setTimestamp(); + + await confirmation.update({ embeds: [successEmbed], components: [] }); + } else { + await confirmation.update({ + embeds: [ + new EmbedBuilder() + .setColor(0x6b7280) + .setDescription('Prestige cancelled.') + ], + components: [] + }); + } + } catch (e) { + await interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setColor(0x6b7280) + .setDescription('Prestige request timed out.') + ], + components: [] + }); + } + } catch (error) { + console.error('Prestige up error:', error); + await interaction.editReply({ content: 'Failed to process prestige.' }); + } +} + +async function viewRewards(interaction) { + const embed = new EmbedBuilder() + .setColor(0x7c3aed) + .setTitle('✨ Prestige Rewards') + .setDescription('Each prestige level grants permanent rewards!\n\n**Requirements:** Level 50 to prestige') + .addFields( + { name: '⭐ Prestige 1 - Bronze', value: '+5% XP bonus\nšŸ·ļø Bronze Prestige badge', inline: true }, + { name: '⭐ Prestige 2 - Silver', value: '+10% XP bonus\nšŸ·ļø Silver Prestige badge', inline: true }, + { name: '⭐ Prestige 3 - Gold', value: '+15% XP bonus\nšŸ·ļø Gold Prestige badge', inline: true }, + { name: 'šŸ’Ž Prestige 4 - Platinum', value: '+20% XP bonus\nšŸ·ļø Platinum badge\nšŸŽ Bonus daily XP', inline: true }, + { name: 'šŸ’Ž Prestige 5 - Diamond', value: '+25% XP bonus\nšŸ·ļø Diamond badge\nā±ļø Reduced cooldowns', inline: true }, + { name: 'šŸ”„ Prestige 6 - Master', value: '+30% XP bonus\nšŸ·ļø Master badge\nšŸŽÆ XP milestone rewards', inline: true }, + { name: 'šŸ”„ Prestige 7 - Grandmaster', value: '+35% XP bonus\nšŸ·ļø Grandmaster badge\nšŸ’« Special profile effects', inline: true }, + { name: 'šŸ‘‘ Prestige 8 - Champion', value: '+40% XP bonus\nšŸ·ļø Champion badge\nšŸ† Leaderboard priority', inline: true }, + { name: 'šŸ‘‘ Prestige 9 - Legend', value: '+45% XP bonus\nšŸ·ļø Legend badge\n✨ Legendary profile aura', inline: true }, + { name: '🌟 Prestige 10 - Mythic', value: '+50% XP bonus\nšŸ·ļø Mythic badge\n🌈 All prestige perks!', inline: true } + ) + .setFooter({ text: 'Use /prestige up when you reach Level 50!' }) + .setTimestamp(); + + await interaction.reply({ embeds: [embed] }); +} + +function getPrestigeInfo(level) { + const prestiges = [ + { name: 'Unprestiged', icon: '⚪', color: 0x6b7280 }, + { name: 'Bronze', icon: 'šŸ„‰', color: 0xcd7f32 }, + { name: 'Silver', icon: '🄈', color: 0xc0c0c0 }, + { name: 'Gold', icon: 'šŸ„‡', color: 0xffd700 }, + { name: 'Platinum', icon: 'šŸ’Ž', color: 0xe5e4e2 }, + { name: 'Diamond', icon: 'šŸ’ ', color: 0xb9f2ff }, + { name: 'Master', icon: 'šŸ”„', color: 0xff4500 }, + { name: 'Grandmaster', icon: 'āš”ļø', color: 0x9400d3 }, + { name: 'Champion', icon: 'šŸ‘‘', color: 0xffd700 }, + { name: 'Legend', icon: '🌟', color: 0xff69b4 }, + { name: 'Mythic', icon: '🌈', color: 0x7c3aed } + ]; + return prestiges[Math.min(level, 10)] || prestiges[0]; +} + +function getPrestigeRequirement(level) { + return 50; +} + +function getUnlockedRewards(prestige) { + const rewards = []; + if (prestige >= 1) rewards.push('šŸ„‰ Bronze Prestige badge'); + if (prestige >= 2) rewards.push('🄈 Silver Prestige badge'); + if (prestige >= 3) rewards.push('šŸ„‡ Gold Prestige badge'); + if (prestige >= 4) rewards.push('šŸ’Ž Platinum badge + Bonus daily XP'); + if (prestige >= 5) rewards.push('šŸ’  Diamond badge + Reduced cooldowns'); + if (prestige >= 6) rewards.push('šŸ”„ Master badge + XP milestones'); + if (prestige >= 7) rewards.push('āš”ļø Grandmaster badge + Profile effects'); + if (prestige >= 8) rewards.push('šŸ‘‘ Champion badge + Leaderboard priority'); + if (prestige >= 9) rewards.push('🌟 Legend badge + Legendary aura'); + if (prestige >= 10) rewards.push('🌈 Mythic badge + All perks unlocked'); + return rewards; +} + +function getNewRewards(prestige) { + const rewardMap = { + 1: ['šŸ„‰ Bronze Prestige badge', '+5% XP bonus on all XP gains'], + 2: ['🄈 Silver Prestige badge', '+10% XP bonus on all XP gains'], + 3: ['šŸ„‡ Gold Prestige badge', '+15% XP bonus on all XP gains'], + 4: ['šŸ’Ž Platinum badge', '+20% XP bonus', 'šŸŽ +25 bonus daily XP'], + 5: ['šŸ’  Diamond badge', '+25% XP bonus', 'ā±ļø 10% reduced XP cooldowns'], + 6: ['šŸ”„ Master badge', '+30% XP bonus', 'šŸŽÆ XP milestone rewards'], + 7: ['āš”ļø Grandmaster badge', '+35% XP bonus', 'šŸ’« Special profile effects'], + 8: ['šŸ‘‘ Champion badge', '+40% XP bonus', 'šŸ† Leaderboard priority display'], + 9: ['🌟 Legend badge', '+45% XP bonus', '✨ Legendary profile aura'], + 10: ['🌈 Mythic badge', '+50% XP bonus', '🌟 All prestige perks unlocked!'] + }; + return rewardMap[prestige] || []; +} diff --git a/aethex-bot/commands/profile.js b/aethex-bot/commands/profile.js index 474deb3..08925c2 100644 --- a/aethex-bot/commands/profile.js +++ b/aethex-bot/commands/profile.js @@ -71,6 +71,7 @@ module.exports = { }; const xp = profile.xp || 0; + const prestige = profile.prestige_level || 0; const level = Math.floor(Math.sqrt(xp / 100)); const currentLevelXp = level * level * 100; const nextLevelXp = (level + 1) * (level + 1) * 100; @@ -79,6 +80,7 @@ module.exports = { const progressPercent = Math.min(100, Math.floor((progressXp / neededXp) * 100)); const progressBar = createProgressBar(progressPercent); + const prestigeInfo = getPrestigeInfo(prestige); const badges = profile.badges || []; const badgeDisplay = badges.length > 0 @@ -115,6 +117,11 @@ module.exports = { value: formatRole(profile.user_type), inline: true, }, + { + name: `${prestigeInfo.icon} Prestige`, + value: prestige > 0 ? `**${prestigeInfo.name}** (P${prestige}) +${prestige * 5}% XP` : 'Not prestiged', + inline: true, + }, { name: `šŸ“ˆ Level ${level}`, value: `${progressBar}\n\`${xp.toLocaleString()}\` / \`${nextLevelXp.toLocaleString()}\` XP`, @@ -187,3 +194,20 @@ function getBadgeEmoji(badge) { }; return badgeMap[badge] || `[${badge}]`; } + +function getPrestigeInfo(level) { + const prestiges = [ + { name: 'Unprestiged', icon: '⚪', color: 0x6b7280 }, + { name: 'Bronze', icon: 'šŸ„‰', color: 0xcd7f32 }, + { name: 'Silver', icon: '🄈', color: 0xc0c0c0 }, + { name: 'Gold', icon: 'šŸ„‡', color: 0xffd700 }, + { name: 'Platinum', icon: 'šŸ’Ž', color: 0xe5e4e2 }, + { name: 'Diamond', icon: 'šŸ’ ', color: 0xb9f2ff }, + { name: 'Master', icon: 'šŸ”„', color: 0xff4500 }, + { name: 'Grandmaster', icon: 'āš”ļø', color: 0x9400d3 }, + { name: 'Champion', icon: 'šŸ‘‘', color: 0xffd700 }, + { name: 'Legend', icon: '🌟', color: 0xff69b4 }, + { name: 'Mythic', icon: '🌈', color: 0x7c3aed } + ]; + return prestiges[Math.min(level, 10)] || prestiges[0]; +} diff --git a/aethex-bot/commands/rank.js b/aethex-bot/commands/rank.js index cc8e424..3b546dc 100644 --- a/aethex-bot/commands/rank.js +++ b/aethex-bot/commands/rank.js @@ -37,11 +37,13 @@ module.exports = { const { data: profile } = await supabase .from('user_profiles') - .select('username, avatar_url, xp, bio') + .select('username, avatar_url, xp, bio, prestige_level, total_xp_earned') .eq('id', link.user_id) .single(); const xp = profile?.xp || 0; + const prestige = profile?.prestige_level || 0; + const totalXpEarned = profile?.total_xp_earned || xp; const level = Math.floor(Math.sqrt(xp / 100)); const currentLevelXp = level * level * 100; const nextLevelXp = (level + 1) * (level + 1) * 100; @@ -50,6 +52,7 @@ module.exports = { const progressPercent = Math.floor((progress / needed) * 100); const progressBar = createProgressBar(progressPercent); + const prestigeInfo = getPrestigeInfo(prestige); const { count: rankPosition } = await supabase .from('user_profiles') @@ -63,17 +66,20 @@ module.exports = { } const embed = new EmbedBuilder() - .setColor(0x7c3aed) - .setTitle(`${profile?.username || target.tag}'s Rank`) + .setColor(prestigeInfo.color) + .setTitle(`${prestigeInfo.icon} ${profile?.username || target.tag}'s Rank`) .setThumbnail(avatarUrl) .addFields( + { name: 'Prestige', value: prestige > 0 ? `**${prestigeInfo.name}** (P${prestige})` : 'Not prestiged', inline: true }, { name: 'Level', value: `**${level}**`, inline: true }, - { name: 'Total XP', value: `**${xp.toLocaleString()}**`, inline: true }, { name: 'Rank', value: `#${(rankPosition || 0) + 1}`, inline: true }, + { name: 'Current XP', value: `**${xp.toLocaleString()}**`, inline: true }, + { name: 'XP Bonus', value: prestige > 0 ? `+${prestige * 5}%` : 'None', inline: true }, + { name: 'Total XP Earned', value: totalXpEarned.toLocaleString(), inline: true }, { name: 'Progress to Next Level', value: `${progressBar}\n${progress.toLocaleString()} / ${needed.toLocaleString()} XP (${progressPercent}%)` }, { name: 'Primary Realm', value: link.primary_arm || 'None set', inline: true } ) - .setFooter({ text: 'XP earned across Discord & AeThex platforms' }) + .setFooter({ text: prestige >= 1 ? `Prestige ${prestige} | XP earned across Discord & AeThex platforms` : 'XP earned across Discord & AeThex platforms' }) .setTimestamp(); await interaction.editReply({ embeds: [embed] }); @@ -90,3 +96,20 @@ function createProgressBar(percent) { const empty = 10 - filled; return 'ā–ˆ'.repeat(filled) + 'ā–‘'.repeat(empty); } + +function getPrestigeInfo(level) { + const prestiges = [ + { name: 'Unprestiged', icon: '⚪', color: 0x6b7280 }, + { name: 'Bronze', icon: 'šŸ„‰', color: 0xcd7f32 }, + { name: 'Silver', icon: '🄈', color: 0xc0c0c0 }, + { name: 'Gold', icon: 'šŸ„‡', color: 0xffd700 }, + { name: 'Platinum', icon: 'šŸ’Ž', color: 0xe5e4e2 }, + { name: 'Diamond', icon: 'šŸ’ ', color: 0xb9f2ff }, + { name: 'Master', icon: 'šŸ”„', color: 0xff4500 }, + { name: 'Grandmaster', icon: 'āš”ļø', color: 0x9400d3 }, + { name: 'Champion', icon: 'šŸ‘‘', color: 0xffd700 }, + { name: 'Legend', icon: '🌟', color: 0xff69b4 }, + { name: 'Mythic', icon: '🌈', color: 0x7c3aed } + ]; + return prestiges[Math.min(level, 10)] || prestiges[0]; +} diff --git a/aethex-bot/listeners/reactionXp.js b/aethex-bot/listeners/reactionXp.js index 3171073..54456c1 100644 --- a/aethex-bot/listeners/reactionXp.js +++ b/aethex-bot/listeners/reactionXp.js @@ -77,13 +77,14 @@ async function grantXp(supabase, discordUserId, xpAmount, client, member, config const { data: profile, error: profileError } = await supabase .from('user_profiles') - .select('xp') + .select('xp, prestige_level, total_xp_earned') .eq('id', link.user_id) .maybeSingle(); if (profileError || !profile) return; let finalXp = xpAmount; + const prestige = profile.prestige_level || 0; if (member && config) { const multiplierRoles = config.multiplier_roles || []; @@ -102,12 +103,19 @@ async function grantXp(supabase, discordUserId, xpAmount, client, member, config finalXp = Math.floor(xpAmount * highestMultiplier); } + // Apply prestige bonus (+5% per prestige level) + if (prestige > 0) { + const prestigeBonus = 1 + (prestige * 0.05); + finalXp = Math.floor(finalXp * prestigeBonus); + } + const currentXp = profile.xp || 0; const newXp = currentXp + finalXp; + const totalEarned = (profile.total_xp_earned || currentXp) + finalXp; const { error: updateError } = await supabase .from('user_profiles') - .update({ xp: newXp }) + .update({ xp: newXp, total_xp_earned: totalEarned }) .eq('id', link.user_id); if (updateError) return; diff --git a/aethex-bot/listeners/voiceXp.js b/aethex-bot/listeners/voiceXp.js index a72c05b..923b745 100644 --- a/aethex-bot/listeners/voiceXp.js +++ b/aethex-bot/listeners/voiceXp.js @@ -149,13 +149,14 @@ async function grantVoiceXp(supabase, client, guildId, userId, config, member, m const { data: profile, error: profileError } = await supabase .from('user_profiles') - .select('xp') + .select('xp, prestige_level, total_xp_earned') .eq('id', link.user_id) .maybeSingle(); if (profileError || !profile) return; let xpGain = (config.voice_xp || 2) * minutes; + const prestige = profile.prestige_level || 0; const multiplierRoles = config.multiplier_roles || []; let highestMultiplier = 1; @@ -172,15 +173,22 @@ async function grantVoiceXp(supabase, client, guildId, userId, config, member, m xpGain = Math.floor(xpGain * highestMultiplier); + // Apply prestige bonus (+5% per prestige level) + if (prestige > 0) { + const prestigeBonus = 1 + (prestige * 0.05); + xpGain = Math.floor(xpGain * prestigeBonus); + } + const currentXp = profile.xp || 0; const newXp = currentXp + xpGain; + const totalEarned = (profile.total_xp_earned || currentXp) + xpGain; const oldLevel = calculateLevel(currentXp, config.level_curve); const newLevel = calculateLevel(newXp, config.level_curve); const { error: updateError } = await supabase .from('user_profiles') - .update({ xp: newXp }) + .update({ xp: newXp, total_xp_earned: totalEarned }) .eq('id', link.user_id); if (updateError) return; diff --git a/aethex-bot/listeners/xpTracker.js b/aethex-bot/listeners/xpTracker.js index f81fc16..9281ee1 100644 --- a/aethex-bot/listeners/xpTracker.js +++ b/aethex-bot/listeners/xpTracker.js @@ -39,7 +39,7 @@ module.exports = { const { data: profile, error: profileError } = await supabase .from('user_profiles') - .select('xp') + .select('xp, prestige_level, total_xp_earned') .eq('id', link.user_id) .maybeSingle(); @@ -47,6 +47,7 @@ module.exports = { // Calculate base XP let xpGain = config.message_xp || 5; + const prestige = profile.prestige_level || 0; // Apply channel bonus const bonusChannels = config.bonus_channels || []; @@ -71,6 +72,12 @@ module.exports = { xpGain = Math.floor(xpGain * highestMultiplier); + // Apply prestige bonus (+5% per prestige level) + if (prestige > 0) { + const prestigeBonus = 1 + (prestige * 0.05); + xpGain = Math.floor(xpGain * prestigeBonus); + } + const currentXp = profile.xp || 0; const newXp = currentXp + xpGain; @@ -78,9 +85,11 @@ module.exports = { const oldLevel = calculateLevel(currentXp, config.level_curve); const newLevel = calculateLevel(newXp, config.level_curve); + // Update XP and track total earned + const totalEarned = (profile.total_xp_earned || currentXp) + xpGain; const { error: updateError } = await supabase .from('user_profiles') - .update({ xp: newXp }) + .update({ xp: newXp, total_xp_earned: totalEarned }) .eq('id', link.user_id); if (updateError) return; diff --git a/replit.md b/replit.md index a1c1c1a..905046d 100644 --- a/replit.md +++ b/replit.md @@ -1,359 +1,44 @@ # AeThex Unified Bot -A complete Discord bot combining AeThex community features, Sentinel enterprise security, and multi-purpose server management in one instance. - ## Overview -AeThex Unified Bot handles community features, security, AND general server management: +The AeThex Unified Bot is a comprehensive Discord bot designed to integrate community features, enterprise-level security, and multi-purpose server management. It aims to unify various functionalities into a single, powerful instance for the AeThex ecosystem. -- **Community Features**: User verification, profile linking, realm selection, leaderboards, community posts -- **Sentinel Security**: Anti-nuke protection with RAM-based heat tracking -- **Federation Sync**: Cross-server role synchronization across 5 realms -- **Ticket System**: Support tickets with automatic channel creation -- **Moderation**: Full moderation suite (warn, kick, ban, timeout) -- **Leveling System**: Unified XP across Discord and AeThex platform -- **Cross-Platform**: Integration with AeThex.studio and AeThex.foundation -- **Role Panels**: Button-based self-assignable roles -- **Giveaways**: Automated giveaway system with entries -- **Auto-Moderation**: Link/spam/badwords/invite filtering -- **Scheduled Messages**: Timed announcements +Key capabilities include: +- **Community Engagement**: User verification, profile linking, realm selection, leaderboards, community posts, leveling system, role panels, and giveaways. +- **Sentinel Security**: Anti-nuke protection with RAM-based heat tracking to safeguard servers. +- **Federation Sync**: Cross-server role synchronization across multiple realms. +- **Support & Moderation**: A ticket system for support, a full suite of moderation tools (warn, kick, ban, timeout), and auto-moderation for content filtering. +- **Cross-Platform Integration**: Seamless connection with AeThex.studio and AeThex.foundation platforms. -## Tech Stack +## User Preferences -- **Runtime**: Node.js 20 -- **Framework**: discord.js v14 -- **Database**: Supabase (for verification, XP, moderation logs) -- **Health Endpoint**: HTTP server on port 8080 +I prefer iterative development and receiving explanations that focus on the "why" behind changes, not just the "what". Please provide clear, step-by-step instructions for any complex tasks. I also appreciate it when you anticipate potential issues and suggest solutions proactively. -## Project Structure +## System Architecture -``` -aethex-bot/ -ā”œā”€ā”€ bot.js # Main entry point -ā”œā”€ā”€ package.json -ā”œā”€ā”€ public/ -│ └── dashboard.html # Web dashboard -ā”œā”€ā”€ commands/ -│ ā”œā”€ā”€ admin.js # /admin status|heat|servers|threats|federation -│ ā”œā”€ā”€ announce.js # /announce - cross-server announcements (6 templates) -│ ā”œā”€ā”€ auditlog.js # /auditlog - admin action history -│ ā”œā”€ā”€ automod.js # /automod - auto-moderation settings -│ ā”œā”€ā”€ avatar.js # /avatar - get user avatar -│ ā”œā”€ā”€ badges.js # /badges - view earned badges -│ ā”œā”€ā”€ ban.js # /ban - ban users -│ ā”œā”€ā”€ config.js # /config - server settings -│ ā”œā”€ā”€ daily.js # /daily - claim daily XP -│ ā”œā”€ā”€ embed.js # /embed - custom embed builder with modal -│ ā”œā”€ā”€ federation.js # /federation link|unlink|list -│ ā”œā”€ā”€ foundation.js # /foundation - foundation stats -│ ā”œā”€ā”€ giveaway.js # /giveaway - giveaway system -│ ā”œā”€ā”€ help.js # /help - categorized command list with dropdown -│ ā”œā”€ā”€ kick.js # /kick - kick users -│ ā”œā”€ā”€ leaderboard.js # /leaderboard - top contributors with medals -│ ā”œā”€ā”€ modlog.js # /modlog - user mod history -│ ā”œā”€ā”€ poll.js # /poll - community polls -│ ā”œā”€ā”€ post.js # /post - community feed posts -│ ā”œā”€ā”€ profile.js # /profile - card-style profile with XP bar -│ ā”œā”€ā”€ rank.js # /rank - view level and XP -│ ā”œā”€ā”€ refresh-roles.js # /refresh-roles - sync roles -│ ā”œā”€ā”€ rolepanel.js # /rolepanel - button role panels -│ ā”œā”€ā”€ schedule.js # /schedule - timed announcements -│ ā”œā”€ā”€ serverinfo.js # /serverinfo - rich server stats -│ ā”œā”€ā”€ set-realm.js # /set-realm - choose primary realm -│ ā”œā”€ā”€ stats.js # /stats - user statistics -│ ā”œā”€ā”€ status.js # /status - network overview -│ ā”œā”€ā”€ studio.js # /studio - studio profile -│ ā”œā”€ā”€ ticket.js # /ticket create|close -│ ā”œā”€ā”€ timeout.js # /timeout - timeout users -│ ā”œā”€ā”€ unlink.js # /unlink - disconnect account -│ ā”œā”€ā”€ userinfo.js # /userinfo - user details -│ ā”œā”€ā”€ verify-role.js # /verify-role - check roles -│ ā”œā”€ā”€ verify.js # /verify - link account -│ ā”œā”€ā”€ warn.js # /warn - warn users -│ └── xp-settings.js # /xp-settings - configure XP per server -ā”œā”€ā”€ events/ -│ └── messageCreate.js # Message event handler -ā”œā”€ā”€ listeners/ -│ ā”œā”€ā”€ automod.js # Auto-moderation listener -│ ā”œā”€ā”€ feedSync.js # Community feed sync -│ ā”œā”€ā”€ welcome.js # Rich welcome messages + auto-role -│ ā”œā”€ā”€ goodbye.js # Rich goodbye messages -│ ā”œā”€ā”€ xpTracker.js # XP tracking on messages -│ ā”œā”€ā”€ reactionXp.js # XP tracking for reactions -│ ā”œā”€ā”€ voiceXp.js # XP tracking for voice channels -│ └── sentinel/ -│ ā”œā”€ā”€ antiNuke.js # Channel delete monitor -│ ā”œā”€ā”€ roleDelete.js # Role delete monitor -│ ā”œā”€ā”€ memberBan.js # Mass ban detection -│ └── memberKick.js # Mass kick detection -└── scripts/ - └── register-commands.js # Slash command registration -``` +The bot is built on **Node.js 20** using the **discord.js v14** framework. It follows a modular design with commands and event listeners separated into distinct directories. -## Commands (37 Total) +**Key Architectural Decisions:** +- **Modular Command and Event Handling**: Commands are organized by function, and events are handled by dedicated listeners, promoting maintainability and scalability. +- **Unified XP System**: A cross-platform leveling system tracks XP from Discord messages, reactions, voice chat, daily claims, and AeThex platform activity. XP is unified across all platforms and stored in a single user profile. + - Leveling formula: `level = floor(sqrt(xp / base))`, with configurable base difficulty. + - **Prestige System**: Users can reset XP at Level 50 for permanent bonuses and rewards, including XP multipliers and unique badges. +- **Sentinel Security Module**: Implements RAM-based heat tracking for anti-nuke protection, monitoring for mass bans, kicks, role deletions, and channel deletions. +- **Federation Sync**: Manages cross-server role synchronization to ensure consistent access and permissions across linked guilds. +- **Interactive UI**: Utilizes Discord's button interactions for features like role panels and modals for custom embed creation. +- **Web Dashboard**: A `dashboard.html` file in the `public/` directory is available for potential web-based interactions or monitoring. -### Community Commands -| Command | Description | -|---------|-------------| -| `/verify` | Link your Discord account to AeThex | -| `/unlink` | Disconnect your Discord from AeThex | -| `/profile` | View your linked AeThex profile (card-style with XP bar) | -| `/set-realm` | Choose your primary realm | -| `/verify-role` | Check your assigned Discord roles | -| `/refresh-roles` | Sync roles based on AeThex profile | -| `/stats` | View your AeThex statistics | -| `/leaderboard` | View top contributors (with medal rankings) | -| `/post` | Create a community feed post | -| `/help` | View categorized commands with dropdown menu | +**Feature Specifications:** +- **38 Commands**: Covering community, leveling, moderation, utility, admin, cross-platform, and security functions. +- **Rich Embeds**: Used extensively for welcome/goodbye messages, user profiles, server info, and announcements. +- **Configurable Auto-moderation**: Settings for link, spam, badword, invite, and mention filtering with adjustable actions. +- **Scheduled Messages**: Allows scheduling timed announcements with support for embeds. +- **Giveaway System**: Automated creation, management, and rerolling of giveaways. -### Leveling & Engagement -| Command | Description | -|---------|-------------| -| `/rank` | View your level and unified XP | -| `/daily` | Claim daily XP bonus | -| `/badges` | View earned badges across platforms | +## External Dependencies -### Moderation -| Command | Description | -|---------|-------------| -| `/warn @user [reason]` | Warn a user | -| `/kick @user [reason]` | Kick a user | -| `/ban @user [reason]` | Ban a user | -| `/timeout @user [minutes] [reason]` | Timeout a user | -| `/modlog @user` | View moderation history | -| `/automod` | Configure auto-moderation (links, spam, badwords, invites, mentions) | - -### Utility -| Command | Description | -|---------|-------------| -| `/userinfo [@user]` | View user information | -| `/serverinfo` | View rich server statistics (boost level, features) | -| `/avatar [@user]` | Get user's avatar | -| `/embed` | Create custom embeds with modal builder | - -### Admin & Config -| Command | Description | -|---------|-------------| -| `/config view` | View server configuration | -| `/config welcome #channel` | Set welcome channel | -| `/config goodbye #channel` | Set goodbye channel | -| `/config modlog #channel` | Set mod log channel | -| `/config levelup #channel` | Set level-up announcement channel | -| `/config autorole @role` | Set auto-role for new members | -| `/config levelrole @role [level]` | Add level-based role reward | -| `/announce [title] [message]` | Send announcements (6 template types) | -| `/poll [question] [options]` | Create community poll | -| `/auditlog` | View admin action history | -| `/rolepanel` | Create/manage button role panels | -| `/giveaway` | Create/end/reroll giveaways | -| `/schedule` | Schedule timed messages | - -### Cross-Platform -| Command | Description | -|---------|-------------| -| `/studio [@user]` | View AeThex Studio profile | -| `/foundation [@user]` | View Foundation contributions | - -### Sentinel Security -| Command | Description | -|---------|-------------| -| `/admin status` | View bot status and statistics | -| `/admin heat @user` | Check heat level of a user | -| `/admin servers` | View all connected servers | -| `/admin threats` | View active threat monitor | -| `/federation link @role` | Link a role for cross-server sync | -| `/federation unlink @role` | Remove a role from sync | -| `/federation list` | List all linked roles | -| `/ticket create [reason]` | Create a support ticket | -| `/ticket close` | Close the current ticket | -| `/status` | View network status | - -## Unified XP System - -XP is earned across all platforms and stored in a single profile: - -- **Discord Messages**: +5 XP per message (60s cooldown, configurable) -- **Reaction XP**: +3 XP for receiving reactions, +1 XP for giving (30s cooldown, configurable) -- **Voice Chat XP**: +2 XP per minute in voice channels (60s cooldown, configurable) -- **Daily Claims**: +50 XP base + streak bonus (up to +100) -- **Platform Activity**: Posts, likes, comments on AeThex sites - -Level formula: `level = floor(sqrt(xp / base))` where base is 50 (easy), 100 (normal), or 200 (hard) - -### XP Configuration (/xp-settings) -- `message-xp` - Set XP per message (1-50) -- `cooldown` - Set message XP cooldown (10-300s) -- `reaction-xp` - Set XP for giving/receiving reactions -- `reaction-cooldown` - Set reaction XP cooldown (5-120s) -- `reaction-toggle` - Enable/disable reaction XP -- `voice-xp` - Set XP per minute in voice channels (1-20) -- `voice-cooldown` - Set voice XP cooldown (30-300s) -- `voice-toggle` - Enable/disable voice XP -- `multiplier-role` - Add role-based XP multipliers -- `bonus-channel` - Add channel-based XP bonuses -- `level-curve` - Set leveling difficulty (easy/normal/hard) -- `toggle` - Enable/disable XP system - -## Supabase Tables Required - -```sql --- Server configuration -CREATE TABLE server_config ( - guild_id TEXT PRIMARY KEY, - welcome_channel TEXT, - goodbye_channel TEXT, - modlog_channel TEXT, - level_up_channel TEXT, - auto_role TEXT, - updated_at TIMESTAMPTZ DEFAULT NOW() -); - --- Warnings -CREATE TABLE warnings ( - id SERIAL PRIMARY KEY, - guild_id TEXT NOT NULL, - user_id TEXT NOT NULL, - user_tag TEXT, - moderator_id TEXT NOT NULL, - moderator_tag TEXT, - reason TEXT, - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Moderation actions -CREATE TABLE mod_actions ( - id SERIAL PRIMARY KEY, - guild_id TEXT NOT NULL, - action TEXT NOT NULL, - user_id TEXT NOT NULL, - user_tag TEXT, - moderator_id TEXT NOT NULL, - moderator_tag TEXT, - reason TEXT, - duration_minutes INTEGER, - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Level roles -CREATE TABLE level_roles ( - guild_id TEXT NOT NULL, - role_id TEXT NOT NULL, - level_required INTEGER NOT NULL, - PRIMARY KEY (guild_id, role_id) -); - --- Role panels (NEW) -CREATE TABLE role_panels ( - message_id TEXT PRIMARY KEY, - channel_id TEXT, - guild_id TEXT, - title TEXT, - description TEXT, - color TEXT, - roles JSONB DEFAULT '[]', - created_by TEXT, - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Giveaways (NEW) -CREATE TABLE giveaways ( - message_id TEXT PRIMARY KEY, - channel_id TEXT, - guild_id TEXT, - prize TEXT, - winners_count INTEGER DEFAULT 1, - required_role TEXT, - host_id TEXT, - end_time TIMESTAMPTZ, - entries JSONB DEFAULT '[]', - status TEXT DEFAULT 'active', - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Scheduled messages (NEW) -CREATE TABLE scheduled_messages ( - id TEXT PRIMARY KEY, - guild_id TEXT, - channel_id TEXT, - type TEXT, - content TEXT, - embed_data JSONB, - send_time TIMESTAMPTZ, - created_by TEXT, - status TEXT DEFAULT 'pending', - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Auto-mod config (NEW) -CREATE TABLE automod_config ( - guild_id TEXT PRIMARY KEY, - links_enabled BOOLEAN DEFAULT FALSE, - links_action TEXT DEFAULT 'delete', - spam_enabled BOOLEAN DEFAULT FALSE, - spam_threshold INTEGER DEFAULT 5, - badwords JSONB DEFAULT '[]', - invites_enabled BOOLEAN DEFAULT FALSE, - mentions_enabled BOOLEAN DEFAULT FALSE, - mentions_limit INTEGER DEFAULT 5, - exempt_roles JSONB DEFAULT '[]', - updated_at TIMESTAMPTZ DEFAULT NOW() -); - --- Add to user_profiles (if not exists) -ALTER TABLE user_profiles ADD COLUMN IF NOT EXISTS xp INTEGER DEFAULT 0; -ALTER TABLE user_profiles ADD COLUMN IF NOT EXISTS daily_streak INTEGER DEFAULT 0; -ALTER TABLE user_profiles ADD COLUMN IF NOT EXISTS last_daily TIMESTAMPTZ; -ALTER TABLE user_profiles ADD COLUMN IF NOT EXISTS badges JSONB DEFAULT '[]'; -``` - -## Environment Variables - -### Required -- `DISCORD_BOT_TOKEN` - Bot token from Discord Developer Portal -- `DISCORD_CLIENT_ID` - Application ID - -### Optional - Supabase -- `SUPABASE_URL` - Supabase project URL -- `SUPABASE_SERVICE_ROLE` - Supabase service role key - -### Optional - Federation -- `HUB_GUILD_ID`, `LABS_GUILD_ID`, `GAMEFORGE_GUILD_ID`, `CORP_GUILD_ID`, `FOUNDATION_GUILD_ID` - -### Optional - Security -- `WHITELISTED_USERS` - Comma-separated user IDs to skip heat tracking -- `ALERT_CHANNEL_ID` - Channel for security alerts -- `EXTRA_WHITELISTED_GUILDS` - Additional whitelisted server IDs - -## Health Endpoints - -**GET /health** - Bot health status -**GET /stats** - Server statistics -**GET /dashboard** - Web dashboard - -## Running the Bot - -```bash -cd aethex-bot -npm install -npm start -``` - -## Current Status - -- Bot running as AeThex#9389 -- 36 commands loaded -- Unified XP system active -- Welcome/goodbye system active (rich embeds) -- Moderation suite active -- Auto-moderation system ready -- Role panels with button interactions -- Giveaway system active -- Scheduled messages active -- Cross-platform integration ready - -## Workflow - -- **Name**: AeThex Unified Bot -- **Command**: `cd aethex-bot && npm start` -- **Runtime**: Node.js 20 -- **Status**: Running +- **Database**: Supabase (used for `server_config`, `warnings`, `mod_actions`, `level_roles`, `role_panels`, `giveaways`, `scheduled_messages`, `automod_config`, and `user_profiles` tables). +- **Discord API**: `discord.js v14` for interacting with the Discord platform. +- **AeThex.studio**: Integration for viewing user profiles. +- **AeThex.foundation**: Integration for viewing user contributions. \ No newline at end of file