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
This commit is contained in:
parent
140fe14c34
commit
89de99044d
4 changed files with 354 additions and 56 deletions
256
aethex-bot/commands/level-roles.js
Normal file
256
aethex-bot/commands/level-roles.js
Normal file
|
|
@ -0,0 +1,256 @@
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
const { checkMilestoneRoles, calculateLevel } = require('./xpTracker');
|
||||||
|
|
||||||
const reactionCooldowns = new Map();
|
const reactionCooldowns = new Map();
|
||||||
const xpConfigCache = new Map();
|
const xpConfigCache = new Map();
|
||||||
const CACHE_TTL = 60000;
|
const CACHE_TTL = 60000;
|
||||||
|
|
@ -51,12 +53,12 @@ module.exports = {
|
||||||
const receiverMember = await reaction.message.guild.members.fetch(messageAuthor.id).catch(() => null);
|
const receiverMember = await reaction.message.guild.members.fetch(messageAuthor.id).catch(() => null);
|
||||||
|
|
||||||
if (!giverOnCooldown && giverXp > 0) {
|
if (!giverOnCooldown && giverXp > 0) {
|
||||||
await grantXp(supabase, user.id, giverXp, client, giverMember, config);
|
await grantXp(supabase, user.id, giverXp, client, giverMember, config, guildId);
|
||||||
reactionCooldowns.set(cooldownKeyGiver, now);
|
reactionCooldowns.set(cooldownKeyGiver, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!receiverOnCooldown && receiverXp > 0) {
|
if (!receiverOnCooldown && receiverXp > 0) {
|
||||||
await grantXp(supabase, messageAuthor.id, receiverXp, client, receiverMember, config);
|
await grantXp(supabase, messageAuthor.id, receiverXp, client, receiverMember, config, guildId);
|
||||||
reactionCooldowns.set(cooldownKeyReceiver, now);
|
reactionCooldowns.set(cooldownKeyReceiver, now);
|
||||||
reactionCooldowns.set(cooldownKeyMessage, now);
|
reactionCooldowns.set(cooldownKeyMessage, now);
|
||||||
}
|
}
|
||||||
|
|
@ -66,7 +68,7 @@ module.exports = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function grantXp(supabase, discordUserId, xpAmount, client, member, config) {
|
async function grantXp(supabase, discordUserId, xpAmount, client, member, config, guildId) {
|
||||||
const { data: link, error: linkError } = await supabase
|
const { data: link, error: linkError } = await supabase
|
||||||
.from('discord_links')
|
.from('discord_links')
|
||||||
.select('user_id')
|
.select('user_id')
|
||||||
|
|
@ -103,7 +105,6 @@ async function grantXp(supabase, discordUserId, xpAmount, client, member, config
|
||||||
finalXp = Math.floor(xpAmount * highestMultiplier);
|
finalXp = Math.floor(xpAmount * highestMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply prestige bonus (+5% per prestige level)
|
|
||||||
if (prestige > 0) {
|
if (prestige > 0) {
|
||||||
const prestigeBonus = 1 + (prestige * 0.05);
|
const prestigeBonus = 1 + (prestige * 0.05);
|
||||||
finalXp = Math.floor(finalXp * prestigeBonus);
|
finalXp = Math.floor(finalXp * prestigeBonus);
|
||||||
|
|
@ -113,6 +114,9 @@ async function grantXp(supabase, discordUserId, xpAmount, client, member, config
|
||||||
const newXp = currentXp + finalXp;
|
const newXp = currentXp + finalXp;
|
||||||
const totalEarned = (profile.total_xp_earned || currentXp) + finalXp;
|
const totalEarned = (profile.total_xp_earned || currentXp) + finalXp;
|
||||||
|
|
||||||
|
const oldLevel = calculateLevel(currentXp, config?.level_curve);
|
||||||
|
const newLevel = calculateLevel(newXp, config?.level_curve);
|
||||||
|
|
||||||
const { error: updateError } = await supabase
|
const { error: updateError } = await supabase
|
||||||
.from('user_profiles')
|
.from('user_profiles')
|
||||||
.update({ xp: newXp, total_xp_earned: totalEarned })
|
.update({ xp: newXp, total_xp_earned: totalEarned })
|
||||||
|
|
@ -123,6 +127,14 @@ async function grantXp(supabase, discordUserId, xpAmount, client, member, config
|
||||||
if (client.trackXP) {
|
if (client.trackXP) {
|
||||||
client.trackXP(finalXp);
|
client.trackXP(finalXp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (member && guildId) {
|
||||||
|
await checkMilestoneRoles(member, {
|
||||||
|
level: newLevel,
|
||||||
|
prestige: prestige,
|
||||||
|
totalXp: totalEarned
|
||||||
|
}, supabase, guildId, newLevel <= oldLevel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getXpConfig(supabase, guildId) {
|
async function getXpConfig(supabase, guildId) {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
const { checkMilestoneRoles, calculateLevel } = require('./xpTracker');
|
||||||
|
|
||||||
const voiceSessions = new Map();
|
const voiceSessions = new Map();
|
||||||
const voiceConfigCache = new Map();
|
const voiceConfigCache = new Map();
|
||||||
const CACHE_TTL = 60000;
|
const CACHE_TTL = 60000;
|
||||||
|
|
@ -197,20 +199,26 @@ async function grantVoiceXp(supabase, client, guildId, userId, config, member, m
|
||||||
client.trackXP(xpGain);
|
client.trackXP(xpGain);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newLevel > oldLevel && member) {
|
if (member) {
|
||||||
const serverConfig = client.serverConfigs?.get(guildId);
|
if (newLevel > oldLevel) {
|
||||||
const levelUpChannelId = serverConfig?.level_up_channel;
|
const serverConfig = client.serverConfigs?.get(guildId);
|
||||||
|
const levelUpChannelId = serverConfig?.level_up_channel;
|
||||||
|
|
||||||
const levelUpMessage = `🎉 Congratulations ${member}! You reached **Level ${newLevel}** from voice chat activity!`;
|
const levelUpMessage = `🎉 Congratulations ${member}! You reached **Level ${newLevel}** from voice chat activity!`;
|
||||||
|
|
||||||
if (levelUpChannelId) {
|
if (levelUpChannelId) {
|
||||||
const channel = await client.channels.fetch(levelUpChannelId).catch(() => null);
|
const channel = await client.channels.fetch(levelUpChannelId).catch(() => null);
|
||||||
if (channel) {
|
if (channel) {
|
||||||
await channel.send(levelUpMessage);
|
await channel.send(levelUpMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await checkLevelRoles(member, newLevel, supabase, guildId);
|
await checkMilestoneRoles(member, {
|
||||||
|
level: newLevel,
|
||||||
|
prestige: prestige,
|
||||||
|
totalXp: totalEarned
|
||||||
|
}, supabase, guildId, newLevel <= oldLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -261,34 +269,3 @@ function getDefaultConfig() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateLevel(xp, curve = 'normal') {
|
|
||||||
const bases = {
|
|
||||||
easy: 50,
|
|
||||||
normal: 100,
|
|
||||||
hard: 200
|
|
||||||
};
|
|
||||||
const base = bases[curve] || 100;
|
|
||||||
return Math.floor(Math.sqrt(xp / base));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkLevelRoles(member, level, supabase, guildId) {
|
|
||||||
if (!member || !supabase) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { data: levelRoles, error } = await supabase
|
|
||||||
.from('level_roles')
|
|
||||||
.select('role_id, level_required')
|
|
||||||
.eq('guild_id', guildId)
|
|
||||||
.lte('level_required', level)
|
|
||||||
.order('level_required', { ascending: true });
|
|
||||||
|
|
||||||
if (error || !levelRoles || levelRoles.length === 0) return;
|
|
||||||
|
|
||||||
for (const lr of levelRoles) {
|
|
||||||
if (!member.roles.cache.has(lr.role_id)) {
|
|
||||||
await member.roles.add(lr.role_id).catch(() => {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,17 @@ module.exports = {
|
||||||
|
|
||||||
if (newLevel > oldLevel) {
|
if (newLevel > oldLevel) {
|
||||||
await sendLevelUpAnnouncement(message, newLevel, newXp, config, client);
|
await sendLevelUpAnnouncement(message, newLevel, newXp, config, client);
|
||||||
await checkLevelRoles(message.member, newLevel, supabase, guildId);
|
await checkMilestoneRoles(message.member, {
|
||||||
|
level: newLevel,
|
||||||
|
prestige: prestige,
|
||||||
|
totalXp: totalEarned
|
||||||
|
}, supabase, guildId);
|
||||||
|
} else {
|
||||||
|
await checkMilestoneRoles(message.member, {
|
||||||
|
level: newLevel,
|
||||||
|
prestige: prestige,
|
||||||
|
totalXp: totalEarned
|
||||||
|
}, supabase, guildId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -216,28 +226,71 @@ function calculateLevel(xp, curve = 'normal') {
|
||||||
return Math.floor(Math.sqrt(xp / base));
|
return Math.floor(Math.sqrt(xp / base));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkLevelRoles(member, level, supabase, guildId) {
|
async function checkMilestoneRoles(member, milestones, supabase, guildId, xpOnly = false) {
|
||||||
if (!member || !supabase) return;
|
if (!member || !supabase) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data: levelRoles, error } = await supabase
|
const { data: allRoles, error } = await supabase
|
||||||
.from('level_roles')
|
.from('level_roles')
|
||||||
.select('role_id, level_required')
|
.select('*')
|
||||||
.eq('guild_id', guildId)
|
.eq('guild_id', guildId);
|
||||||
.lte('level_required', level)
|
|
||||||
.order('level_required', { ascending: true });
|
|
||||||
|
|
||||||
if (error || !levelRoles || levelRoles.length === 0) return;
|
if (error || !allRoles || allRoles.length === 0) return;
|
||||||
|
|
||||||
for (const lr of levelRoles) {
|
const rolesToAdd = [];
|
||||||
if (!member.roles.cache.has(lr.role_id)) {
|
const rolesToRemove = [];
|
||||||
await member.roles.add(lr.role_id).catch(() => {});
|
|
||||||
|
for (const roleConfig of allRoles) {
|
||||||
|
const { role_id, milestone_type, milestone_value, stack_roles } = roleConfig;
|
||||||
|
let qualifies = false;
|
||||||
|
|
||||||
|
switch (milestone_type) {
|
||||||
|
case 'level':
|
||||||
|
if (!xpOnly) qualifies = milestones.level >= milestone_value;
|
||||||
|
break;
|
||||||
|
case 'prestige':
|
||||||
|
if (!xpOnly) qualifies = milestones.prestige >= milestone_value;
|
||||||
|
break;
|
||||||
|
case 'total_xp':
|
||||||
|
qualifies = milestones.totalXp >= milestone_value;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (qualifies && !member.roles.cache.has(role_id)) {
|
||||||
|
rolesToAdd.push({ role_id, milestone_type, milestone_value, stack_roles });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const roleToAdd of rolesToAdd) {
|
||||||
|
try {
|
||||||
|
await member.roles.add(roleToAdd.role_id);
|
||||||
|
|
||||||
|
if (!roleToAdd.stack_roles) {
|
||||||
|
const sameTypeRoles = allRoles.filter(r =>
|
||||||
|
r.milestone_type === roleToAdd.milestone_type &&
|
||||||
|
r.milestone_value < roleToAdd.milestone_value &&
|
||||||
|
member.roles.cache.has(r.role_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const oldRole of sameTypeRoles) {
|
||||||
|
if (!rolesToRemove.includes(oldRole.role_id)) {
|
||||||
|
rolesToRemove.push(oldRole.role_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Role addition failed, don't remove old roles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const roleId of rolesToRemove) {
|
||||||
|
await member.roles.remove(roleId).catch(() => {});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Table may not exist yet - silently ignore
|
// Table may not exist yet - silently ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export calculateLevel for use in other commands
|
// Export functions for use in other commands
|
||||||
module.exports.calculateLevel = calculateLevel;
|
module.exports.calculateLevel = calculateLevel;
|
||||||
|
module.exports.checkMilestoneRoles = checkMilestoneRoles;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue