CHECKPOINT: Interactive soundboard and refactored SFX system

Major Features Implemented:
- Complete Discord.js v14 modernization from v12 with hybrid command system
- SQLite database for dynamic guild configuration management
- Interactive soundboard with categorized button interface (/soundboard)
- Three-tier SFX interface: prefix (!sfx), autocomplete (/sfx), and visual soundboard
- Auto-registration system for public bot distribution
- Soft delete guild management preserving configurations

Technical Improvements:
- Refactored SFX playing into reusable service methods (playSFXInteraction/playSFXMessage)
- Smart markdown chunking that respects code block boundaries
- High-performance caching for 275+ sound effects with autocomplete optimization
- Modern Discord.js v14 patterns (MessageFlags.Ephemeral, proper intents)
- Fixed security vulnerability in @discordjs/opus with pnpm overrides
- Docker deployment with Node 20 and npm for reliable SQLite compilation

Interactive Soundboard Features:
- Category-based navigation with buttons (GENERAL, NERDS, TWIN PEAKS, etc.)
- Pagination support for large categories (16 sounds per page, 4 per row)
- Real-time status updates (Playing → Finished playing)
- Navigation buttons (Previous/Next/Back to Categories)
- Ephemeral responses for clean chat experience

Database System:
- Auto-migration from config.json to SQLite on first run
- /config slash commands for live server configuration
- Scheduled events with timezone support (object and cron formats)
- Guild auto-registration with welcome messages for new servers

Current State: Fully functional modern Discord bot ready for public distribution
This commit is contained in:
Chris Ham
2025-08-16 16:20:02 -07:00
parent 0b167aaa35
commit aaf33d55db
6 changed files with 437 additions and 106 deletions

View File

@@ -26,64 +26,17 @@ module.exports = {
}
const sfxName = interaction.options.getString('sound');
// Log the slash command SFX request
console.log(
`/sfx '${sfxName}' requested in ${guildConfig.internalName || interaction.guild.name}#${interaction.channel.name} from @${interaction.user.username}`
);
// Check if SFX exists
if (!sfxManager.hasSFX(sfxName)) {
return interaction.reply({
content: 'This sound effect does not exist!',
flags: [MessageFlags.Ephemeral]
});
}
// Check if user is in a voice channel
const member = interaction.member;
if (!member.voice.channel) {
if (!interaction.member.voice.channel) {
return interaction.reply({
content: 'You need to be in a voice channel to use this command!',
flags: [MessageFlags.Ephemeral]
});
}
// Defer the reply as joining voice might take a moment
await interaction.deferReply();
try {
// Join the voice channel
await voiceService.join(member.voice.channel);
// Get the SFX file path
const sfxPath = sfxManager.getSFXPath(sfxName);
// Play the sound effect
await voiceService.play(
interaction.guild.id,
sfxPath,
{
volume: guildConfig.sfxVolume || 0.5
}
);
// Update the reply
await interaction.editReply(`Playing sound effect: **${sfxName}**`);
// Leave the voice channel after playing
setTimeout(() => {
voiceService.leave(interaction.guild.id);
}, 500);
console.log(`✅ Successfully played /sfx '${sfxName}'`);
} catch (error) {
console.error(`❌ Error playing /sfx '${sfxName}':`, error);
await interaction.editReply({
content: "I couldn't play that sound effect. Make sure I have permission to join your voice channel!"
});
}
// Use the reusable SFX playing method
await sfxManager.playSFXInteraction(interaction, sfxName, guildConfig, 'slash');
},
async autocomplete(interaction, guildConfig) {