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
This commit is contained in:
parent
feb54ff272
commit
d7b41b5b4f
8 changed files with 470 additions and 364 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
324
aethex-bot/commands/prestige.js
Normal file
324
aethex-bot/commands/prestige.js
Normal file
|
|
@ -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] || [];
|
||||
}
|
||||
|
|
@ -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];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
377
replit.md
377
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.
|
||||
Loading…
Reference in a new issue