AeThex-Bot-Master/aethex-bot/commands/level-roles.js
sirpiglr 89de99044d Improve role management for user milestones by adding new command and refactoring logic
Refactors `xpTracker.js` to export `checkMilestoneRoles` and updates `reactionXp.js` and `voiceXp.js` to utilize it. Introduces a new `/level-roles` slash command for managing milestone role rewards and implements role addition/removal logic within `checkMilestoneRoles`.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: 931d9dcd-12a8-492b-a86e-6812e31babb0
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/yTaZipL
Replit-Helium-Checkpoint-Created: true
2025-12-08 21:36:53 +00:00

256 lines
8.2 KiB
JavaScript

const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('level-roles')
.setDescription('Manage automatic role rewards for milestones')
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.addSubcommand(sub =>
sub.setName('add')
.setDescription('Add a role reward for a milestone')
.addRoleOption(opt =>
opt.setName('role')
.setDescription('The role to award')
.setRequired(true))
.addStringOption(opt =>
opt.setName('type')
.setDescription('Type of milestone')
.setRequired(true)
.addChoices(
{ name: 'Level - Awarded at specific level', value: 'level' },
{ name: 'Prestige - Awarded at prestige level', value: 'prestige' },
{ name: 'Total XP - Awarded at XP milestone', value: 'total_xp' }
))
.addIntegerOption(opt =>
opt.setName('value')
.setDescription('Milestone value (level #, prestige #, or XP amount)')
.setRequired(true)
.setMinValue(1))
.addBooleanOption(opt =>
opt.setName('stack')
.setDescription('Keep previous milestone roles (true) or replace them (false)')
.setRequired(false)))
.addSubcommand(sub =>
sub.setName('remove')
.setDescription('Remove a role reward')
.addRoleOption(opt =>
opt.setName('role')
.setDescription('The role to remove from rewards')
.setRequired(true)))
.addSubcommand(sub =>
sub.setName('list')
.setDescription('View all configured role rewards'))
.addSubcommand(sub =>
sub.setName('clear')
.setDescription('Clear all role rewards for a milestone type')
.addStringOption(opt =>
opt.setName('type')
.setDescription('Type of milestone to clear')
.setRequired(true)
.addChoices(
{ name: 'Level roles', value: 'level' },
{ name: 'Prestige roles', value: 'prestige' },
{ name: 'Total XP roles', value: 'total_xp' },
{ name: 'All roles', value: 'all' }
))),
async execute(interaction, client, supabase) {
if (!supabase) {
return interaction.reply({
content: '❌ Database not configured. Level roles require Supabase.',
ephemeral: true
});
}
const guildId = interaction.guildId;
const subcommand = interaction.options.getSubcommand();
switch (subcommand) {
case 'add':
return handleAdd(interaction, supabase, guildId);
case 'remove':
return handleRemove(interaction, supabase, guildId);
case 'list':
return handleList(interaction, supabase, guildId);
case 'clear':
return handleClear(interaction, supabase, guildId);
}
}
};
async function handleAdd(interaction, supabase, guildId) {
const role = interaction.options.getRole('role');
const type = interaction.options.getString('type');
const value = interaction.options.getInteger('value');
const stack = interaction.options.getBoolean('stack') ?? true;
if (role.managed) {
return interaction.reply({
content: '❌ Cannot use managed roles (bot roles, integration roles) as rewards.',
ephemeral: true
});
}
const botMember = interaction.guild.members.me;
if (role.position >= botMember.roles.highest.position) {
return interaction.reply({
content: '❌ I cannot assign this role. It is higher than or equal to my highest role.',
ephemeral: true
});
}
try {
const { error } = await supabase
.from('level_roles')
.upsert({
guild_id: guildId,
role_id: role.id,
milestone_type: type,
milestone_value: value,
stack_roles: stack
}, { onConflict: 'guild_id,role_id' });
if (error) throw error;
const typeNames = {
level: 'Level',
prestige: 'Prestige',
total_xp: 'Total XP'
};
const valueDisplay = type === 'total_xp' ? `${value.toLocaleString()} XP` : value;
return interaction.reply({
content: `${role} will be awarded at **${typeNames[type]} ${valueDisplay}**.\nRole stacking: ${stack ? 'Enabled (keep previous roles)' : 'Disabled (replace previous roles)'}`,
ephemeral: true
});
} catch (error) {
console.error('Failed to add level role:', error.message);
return interaction.reply({
content: '❌ Failed to add role reward. Please try again.',
ephemeral: true
});
}
}
async function handleRemove(interaction, supabase, guildId) {
const role = interaction.options.getRole('role');
try {
const { data, error } = await supabase
.from('level_roles')
.delete()
.eq('guild_id', guildId)
.eq('role_id', role.id)
.select();
if (error) throw error;
if (!data || data.length === 0) {
return interaction.reply({
content: `${role} is not configured as a role reward.`,
ephemeral: true
});
}
return interaction.reply({
content: `✅ Removed ${role} from role rewards.`,
ephemeral: true
});
} catch (error) {
console.error('Failed to remove level role:', error.message);
return interaction.reply({
content: '❌ Failed to remove role reward. Please try again.',
ephemeral: true
});
}
}
async function handleList(interaction, supabase, guildId) {
try {
const { data: roles, error } = await supabase
.from('level_roles')
.select('*')
.eq('guild_id', guildId)
.order('milestone_type')
.order('milestone_value', { ascending: true });
if (error) throw error;
if (!roles || roles.length === 0) {
return interaction.reply({
content: '📋 No role rewards configured. Use `/level-roles add` to create some!',
ephemeral: true
});
}
const levelRoles = roles.filter(r => r.milestone_type === 'level');
const prestigeRoles = roles.filter(r => r.milestone_type === 'prestige');
const xpRoles = roles.filter(r => r.milestone_type === 'total_xp');
const formatRoles = (roleList, prefix) => {
if (roleList.length === 0) return 'None configured';
return roleList.map(r => {
const stackIcon = r.stack_roles ? '📚' : '🔄';
const value = r.milestone_type === 'total_xp'
? `${r.milestone_value.toLocaleString()} XP`
: r.milestone_value;
return `${stackIcon} <@&${r.role_id}> → ${prefix}${value}`;
}).join('\n');
};
const embed = new EmbedBuilder()
.setTitle('🏆 Role Rewards Configuration')
.setColor(0x5865F2)
.addFields(
{ name: '📊 Level Roles', value: formatRoles(levelRoles, 'Level '), inline: false },
{ name: '⭐ Prestige Roles', value: formatRoles(prestigeRoles, 'Prestige '), inline: false },
{ name: '💎 Total XP Roles', value: formatRoles(xpRoles, ''), inline: false }
)
.setFooter({ text: '📚 = Stack roles | 🔄 = Replace previous' })
.setTimestamp();
return interaction.reply({ embeds: [embed] });
} catch (error) {
console.error('Failed to list level roles:', error.message);
return interaction.reply({
content: '❌ Failed to fetch role rewards. Please try again.',
ephemeral: true
});
}
}
async function handleClear(interaction, supabase, guildId) {
const type = interaction.options.getString('type');
try {
let query = supabase.from('level_roles').delete().eq('guild_id', guildId);
if (type !== 'all') {
query = query.eq('milestone_type', type);
}
const { data, error } = await query.select();
if (error) throw error;
const count = data?.length || 0;
const typeNames = {
level: 'level',
prestige: 'prestige',
total_xp: 'total XP',
all: ''
};
return interaction.reply({
content: `✅ Cleared **${count}** ${typeNames[type]} role reward(s).`,
ephemeral: true
});
} catch (error) {
console.error('Failed to clear level roles:', error.message);
return interaction.reply({
content: '❌ Failed to clear role rewards. Please try again.',
ephemeral: true
});
}
}