Modernize Discord bot to v14 and Node.js 22
Major upgrades and architectural improvements: - Upgrade Discord.js from v12 to v14.21.0 - Upgrade Node.js from 14 to 22 LTS - Switch to pnpm package manager - Complete rewrite with modern Discord API patterns New Features: - Hybrid command system: prefix commands + slash commands - /sfx slash command with autocomplete for sound discovery - Modern @discordjs/voice integration for audio - Improved voice connection management - Enhanced logging for SFX commands - Multi-stage Docker build for optimized images Technical Improvements: - Modular architecture with services and command handlers - Proper intent management for Discord gateway - Better error handling and logging - Hot-reload capability maintained - Environment variable support - Optimized Docker container with Alpine Linux Breaking Changes: - Moved main entry from index.js to src/index.js - Updated configuration structure for v14 compatibility - Replaced deprecated voice APIs with @discordjs/voice - Updated audio dependencies (opus, ffmpeg) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
108
src/commands/slash/sfx.js
Normal file
108
src/commands/slash/sfx.js
Normal file
@@ -0,0 +1,108 @@
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
const sfxManager = require('../../services/sfxManager');
|
||||
const voiceService = require('../../services/voiceService');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('sfx')
|
||||
.setDescription('Play a sound effect')
|
||||
.addStringOption(option =>
|
||||
option.setName('sound')
|
||||
.setDescription('The sound effect to play')
|
||||
.setRequired(true)
|
||||
.setAutocomplete(true)
|
||||
),
|
||||
|
||||
async execute(interaction, guildConfig) {
|
||||
// Check if SFX is allowed in this channel
|
||||
if (guildConfig.allowedSfxChannels) {
|
||||
const allowedChannels = new RegExp(guildConfig.allowedSfxChannels);
|
||||
if (!allowedChannels.test(interaction.channel.name)) {
|
||||
return interaction.reply({
|
||||
content: 'Sound effects are not allowed in this channel!',
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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!',
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user is in a voice channel
|
||||
const member = interaction.member;
|
||||
if (!member.voice.channel) {
|
||||
return interaction.reply({
|
||||
content: 'You need to be in a voice channel to use this command!',
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
|
||||
// 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!"
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async autocomplete(interaction, guildConfig) {
|
||||
const focusedValue = interaction.options.getFocused().toLowerCase();
|
||||
|
||||
// Get all SFX names
|
||||
const choices = sfxManager.getSFXNames();
|
||||
|
||||
// Filter based on what the user has typed
|
||||
const filtered = choices
|
||||
.filter(choice => choice.toLowerCase().includes(focusedValue))
|
||||
.slice(0, 25); // Discord limits autocomplete to 25 choices
|
||||
|
||||
// Respond with the filtered choices
|
||||
await interaction.respond(
|
||||
filtered.map(choice => ({
|
||||
name: choice,
|
||||
value: choice
|
||||
}))
|
||||
);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user