From 140fe14c348a551c62b779c22f0e50566dcff3e4 Mon Sep 17 00:00:00 2001 From: sirpiglr <49359077-sirpiglr@users.noreply.replit.com> Date: Mon, 8 Dec 2025 21:30:29 +0000 Subject: [PATCH] Add customizable level-up announcements for user progression Adds new subcommands to `xp-settings.js` for configuring level-up channel, message, DM notifications, and embed usage. Updates `xpTracker.js` to use these configurations, including placeholder support and embed customization, and introduces a `sendLevelUpAnnouncement` function. Replit-Commit-Author: Agent Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: df3297ab-2de4-4d8a-99e3-9f5a31ff4ac6 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 --- .replit | 4 - aethex-bot/commands/xp-settings.js | 218 ++++++++++++++++++++++++++++- aethex-bot/listeners/xpTracker.js | 66 +++++++-- 3 files changed, 265 insertions(+), 23 deletions(-) diff --git a/.replit b/.replit index 3aa8355..5b6f647 100644 --- a/.replit +++ b/.replit @@ -22,10 +22,6 @@ externalPort = 80 localPort = 8080 externalPort = 8080 -[[ports]] -localPort = 35953 -externalPort = 3000 - [workflows] runButton = "Project" diff --git a/aethex-bot/commands/xp-settings.js b/aethex-bot/commands/xp-settings.js index ce8fe90..8d58aae 100644 --- a/aethex-bot/commands/xp-settings.js +++ b/aethex-bot/commands/xp-settings.js @@ -126,7 +126,43 @@ module.exports = { .addBooleanOption(opt => opt.setName('enabled') .setDescription('Enable or disable voice XP') - .setRequired(true))), + .setRequired(true))) + .addSubcommand(sub => + sub.setName('levelup-channel') + .setDescription('Set a dedicated channel for level-up announcements') + .addChannelOption(opt => + opt.setName('channel') + .setDescription('Channel for level-ups (leave empty to use current channel)') + .setRequired(false))) + .addSubcommand(sub => + sub.setName('levelup-message') + .setDescription('Set custom level-up message (use {user}, {level}, {xp}, {server})') + .addStringOption(opt => + opt.setName('message') + .setDescription('Custom message with placeholders') + .setRequired(true) + .setMaxLength(500))) + .addSubcommand(sub => + sub.setName('levelup-dm') + .setDescription('Toggle DM notifications for level-ups instead of channel') + .addBooleanOption(opt => + opt.setName('enabled') + .setDescription('Send level-up via DM') + .setRequired(true))) + .addSubcommand(sub => + sub.setName('levelup-embed') + .setDescription('Toggle using embeds for level-up announcements') + .addBooleanOption(opt => + opt.setName('enabled') + .setDescription('Use embed for level-ups') + .setRequired(true)) + .addStringOption(opt => + opt.setName('color') + .setDescription('Embed color (hex, e.g., #5865F2)') + .setRequired(false))) + .addSubcommand(sub => + sub.setName('levelup-reset') + .setDescription('Reset level-up settings to defaults')), async execute(interaction, client, supabase) { if (!supabase) { @@ -169,6 +205,16 @@ module.exports = { return handleVoiceCooldown(interaction, supabase, guildId, config); case 'voice-toggle': return handleVoiceToggle(interaction, supabase, guildId, config); + case 'levelup-channel': + return handleLevelupChannel(interaction, supabase, guildId, config); + case 'levelup-message': + return handleLevelupMessage(interaction, supabase, guildId, config); + case 'levelup-dm': + return handleLevelupDm(interaction, supabase, guildId, config); + case 'levelup-embed': + return handleLevelupEmbed(interaction, supabase, guildId, config); + case 'levelup-reset': + return handleLevelupReset(interaction, supabase, guildId, config); } } }; @@ -199,7 +245,12 @@ async function getXpConfig(supabase, guildId) { reaction_cooldown: 30, voice_xp_enabled: true, voice_xp: 2, - voice_cooldown: 60 + voice_cooldown: 60, + levelup_channel_id: null, + levelup_message: '🎉 Congratulations {user}! You reached **Level {level}**!', + levelup_dm: false, + levelup_embed: false, + levelup_embed_color: '#5865F2' }; } @@ -220,7 +271,12 @@ async function getXpConfig(supabase, guildId) { reaction_cooldown: 30, voice_xp_enabled: true, voice_xp: 2, - voice_cooldown: 60 + voice_cooldown: 60, + levelup_channel_id: null, + levelup_message: '🎉 Congratulations {user}! You reached **Level {level}**!', + levelup_dm: false, + levelup_embed: false, + levelup_embed_color: '#5865F2' }; } } @@ -244,6 +300,11 @@ async function saveXpConfig(supabase, guildId, config) { voice_xp_enabled: config.voice_xp_enabled, voice_xp: config.voice_xp, voice_cooldown: config.voice_cooldown, + levelup_channel_id: config.levelup_channel_id, + levelup_message: config.levelup_message, + levelup_dm: config.levelup_dm, + levelup_embed: config.levelup_embed, + levelup_embed_color: config.levelup_embed_color, updated_at: new Date().toISOString() }); @@ -282,6 +343,12 @@ async function handleView(interaction, config) { const voiceXp = config.voice_xp ?? 2; const voiceCooldown = config.voice_cooldown ?? 60; + const levelupChannel = config.levelup_channel_id ? `<#${config.levelup_channel_id}>` : 'Current channel'; + const levelupMessage = config.levelup_message || '🎉 Congratulations {user}! You reached **Level {level}**!'; + const levelupDm = config.levelup_dm === true; + const levelupEmbed = config.levelup_embed === true; + const levelupColor = config.levelup_embed_color || '#5865F2'; + const embed = new EmbedBuilder() .setTitle('⚙️ XP Settings') .setColor(config.xp_enabled ? 0x00ff88 : 0xff4444) @@ -298,9 +365,14 @@ async function handleView(interaction, config) { { name: '⏳ Reaction Cooldown', value: `${reactionCooldown}s`, inline: true }, { name: '🎤 Voice XP', value: voiceEnabled ? '✅ Enabled' : '❌ Disabled', inline: true }, { name: '🔊 XP per Minute', value: `${voiceXp} XP`, inline: true }, - { name: '⏰ Voice Cooldown', value: `${voiceCooldown}s`, inline: true } + { name: '⏰ Voice Cooldown', value: `${voiceCooldown}s`, inline: true }, + { name: '\u200B', value: '**Level-Up Announcements**', inline: false }, + { name: '📣 Channel', value: levelupChannel, inline: true }, + { name: '💌 DM Mode', value: levelupDm ? '✅ Enabled' : '❌ Disabled', inline: true }, + { name: '🖼️ Use Embed', value: levelupEmbed ? `✅ (${levelupColor})` : '❌ Disabled', inline: true }, + { name: '💬 Message', value: `\`${levelupMessage.slice(0, 100)}${levelupMessage.length > 100 ? '...' : ''}\``, inline: false } ) - .setFooter({ text: 'Use /xp-settings subcommands to modify' }) + .setFooter({ text: 'Use /xp-settings subcommands to modify | Placeholders: {user}, {level}, {xp}, {server}' }) .setTimestamp(); return interaction.reply({ embeds: [embed] }); @@ -596,3 +668,139 @@ async function handleVoiceToggle(interaction, supabase, guildId, config) { ephemeral: true }); } + +async function handleLevelupChannel(interaction, supabase, guildId, config) { + const channel = interaction.options.getChannel('channel'); + + if (!channel) { + config.levelup_channel_id = null; + const saved = await saveXpConfig(supabase, guildId, config); + if (!saved) { + return interaction.reply({ + content: '❌ Failed to save settings. Please try again.', + ephemeral: true + }); + } + return interaction.reply({ + content: '✅ Level-up announcements will now appear in the channel where the user leveled up.', + ephemeral: true + }); + } + + config.levelup_channel_id = channel.id; + const saved = await saveXpConfig(supabase, guildId, config); + if (!saved) { + return interaction.reply({ + content: '❌ Failed to save settings. Please try again.', + ephemeral: true + }); + } + + return interaction.reply({ + content: `✅ Level-up announcements will now be sent to ${channel}.`, + ephemeral: true + }); +} + +async function handleLevelupMessage(interaction, supabase, guildId, config) { + const message = interaction.options.getString('message'); + config.levelup_message = message; + + const saved = await saveXpConfig(supabase, guildId, config); + if (!saved) { + return interaction.reply({ + content: '❌ Failed to save settings. Please try again.', + ephemeral: true + }); + } + + const preview = message + .replace(/{user}/g, interaction.user.toString()) + .replace(/{username}/g, interaction.user.username) + .replace(/{level}/g, '10') + .replace(/{xp}/g, '10,000') + .replace(/{server}/g, interaction.guild.name); + + return interaction.reply({ + content: `✅ Level-up message updated!\n\n**Preview:**\n${preview}`, + ephemeral: true + }); +} + +async function handleLevelupDm(interaction, supabase, guildId, config) { + const enabled = interaction.options.getBoolean('enabled'); + config.levelup_dm = enabled; + + const saved = await saveXpConfig(supabase, guildId, config); + if (!saved) { + return interaction.reply({ + content: '❌ Failed to save settings. Please try again.', + ephemeral: true + }); + } + + return interaction.reply({ + content: enabled + ? '✅ Level-up notifications will now be sent via **DM** to users.' + : '✅ Level-up notifications will now be sent in the **channel**.', + ephemeral: true + }); +} + +async function handleLevelupEmbed(interaction, supabase, guildId, config) { + const enabled = interaction.options.getBoolean('enabled'); + const color = interaction.options.getString('color'); + + config.levelup_embed = enabled; + + if (color) { + if (!/^#[0-9A-Fa-f]{6}$/.test(color)) { + return interaction.reply({ + content: '❌ Invalid color format. Please use hex format like `#5865F2`.', + ephemeral: true + }); + } + config.levelup_embed_color = color; + } + + const saved = await saveXpConfig(supabase, guildId, config); + if (!saved) { + return interaction.reply({ + content: '❌ Failed to save settings. Please try again.', + ephemeral: true + }); + } + + if (enabled) { + return interaction.reply({ + content: `✅ Level-up announcements will now use **embeds** with color \`${config.levelup_embed_color}\`.`, + ephemeral: true + }); + } else { + return interaction.reply({ + content: '✅ Level-up announcements will now use **plain text** messages.', + ephemeral: true + }); + } +} + +async function handleLevelupReset(interaction, supabase, guildId, config) { + config.levelup_channel_id = null; + config.levelup_message = '🎉 Congratulations {user}! You reached **Level {level}**!'; + config.levelup_dm = false; + config.levelup_embed = false; + config.levelup_embed_color = '#5865F2'; + + const saved = await saveXpConfig(supabase, guildId, config); + if (!saved) { + return interaction.reply({ + content: '❌ Failed to save settings. Please try again.', + ephemeral: true + }); + } + + return interaction.reply({ + content: '✅ Level-up announcement settings have been reset to defaults.', + ephemeral: true + }); +} diff --git a/aethex-bot/listeners/xpTracker.js b/aethex-bot/listeners/xpTracker.js index 9281ee1..5dec896 100644 --- a/aethex-bot/listeners/xpTracker.js +++ b/aethex-bot/listeners/xpTracker.js @@ -1,3 +1,5 @@ +const { EmbedBuilder } = require('discord.js'); + const xpCooldowns = new Map(); const xpConfigCache = new Map(); const CACHE_TTL = 60000; // 1 minute cache @@ -102,20 +104,7 @@ module.exports = { } if (newLevel > oldLevel) { - const serverConfig = client.serverConfigs?.get(guildId); - const levelUpChannelId = serverConfig?.level_up_channel; - - const levelUpMessage = `🎉 Congratulations ${message.author}! You reached **Level ${newLevel}**!`; - - if (levelUpChannelId) { - const channel = await client.channels.fetch(levelUpChannelId).catch(() => null); - if (channel) { - await channel.send(levelUpMessage); - } - } else { - await message.channel.send(levelUpMessage).catch(() => {}); - } - + await sendLevelUpAnnouncement(message, newLevel, newXp, config, client); await checkLevelRoles(message.member, newLevel, supabase, guildId); } @@ -125,6 +114,55 @@ module.exports = { } }; +async function sendLevelUpAnnouncement(message, newLevel, newXp, config, client) { + try { + const messageTemplate = config.levelup_message || '🎉 Congratulations {user}! You reached **Level {level}**!'; + const channelId = config.levelup_channel_id; + const sendDm = config.levelup_dm === true; + const useEmbed = config.levelup_embed === true; + const embedColor = config.levelup_embed_color || '#5865F2'; + + const formattedMessage = messageTemplate + .replace(/{user}/g, message.author.toString()) + .replace(/{username}/g, message.author.username) + .replace(/{level}/g, newLevel.toString()) + .replace(/{xp}/g, newXp.toLocaleString()) + .replace(/{server}/g, message.guild.name); + + let messageContent; + + if (useEmbed) { + const embed = new EmbedBuilder() + .setDescription(formattedMessage) + .setColor(parseInt(embedColor.replace('#', ''), 16)) + .setThumbnail(message.author.displayAvatarURL({ dynamic: true })) + .setTimestamp(); + + messageContent = { embeds: [embed] }; + } else { + messageContent = { content: formattedMessage }; + } + + if (sendDm) { + const dmSent = await message.author.send(messageContent).catch(() => null); + if (!dmSent) { + await message.channel.send(messageContent).catch(() => {}); + } + } else if (channelId) { + const channel = await client.channels.fetch(channelId).catch(() => null); + if (channel) { + await channel.send(messageContent).catch(() => {}); + } else { + await message.channel.send(messageContent).catch(() => {}); + } + } else { + await message.channel.send(messageContent).catch(() => {}); + } + } catch (error) { + console.error('Level-up announcement error:', error.message); + } +} + async function getXpConfig(supabase, guildId) { const now = Date.now(); const cached = xpConfigCache.get(guildId);