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:
Chris Ham
2025-08-16 11:37:37 -07:00
parent 19c8f4fa85
commit 0ad4265bed
31 changed files with 2931 additions and 381 deletions

View File

@@ -0,0 +1,79 @@
const { EmbedBuilder } = require('discord.js');
const fs = require('fs');
const path = require('path');
class HamFactCommand {
constructor() {
this.hamFactsPath = path.join(__dirname, '..', '..', '..', 'conf', 'hamfacts');
this.hamFacts = [];
this.loadHamFacts();
this.watchFile();
}
loadHamFacts() {
try {
if (!fs.existsSync(this.hamFactsPath)) {
console.log('Ham facts file not found');
return;
}
const data = fs.readFileSync(this.hamFactsPath, 'utf-8');
this.hamFacts = data.split('\n').filter(line => line.trim().length > 0);
console.log(`Loaded ${this.hamFacts.length} ham facts`);
} catch (error) {
console.error('Error loading ham facts:', error);
}
}
watchFile() {
if (fs.existsSync(this.hamFactsPath)) {
fs.watchFile(this.hamFactsPath, (curr, prev) => {
if (curr.mtime !== prev.mtime) {
console.log('Ham facts file changed, reloading...');
this.loadHamFacts();
}
});
}
}
async execute(message, args, guildConfig) {
if (guildConfig.enableHamFacts === false) {
return;
}
if (this.hamFacts.length === 0) {
return message.channel.send('No ham facts found!');
}
// Check if a specific fact number was requested
let factIndex;
const requestedNum = parseInt(args[0]);
if (!isNaN(requestedNum) && requestedNum > 0 && requestedNum <= this.hamFacts.length) {
factIndex = requestedNum - 1;
} else {
factIndex = Math.floor(Math.random() * this.hamFacts.length);
}
const displayNum = factIndex + 1;
const hamFact = this.hamFacts[factIndex];
const embed = new EmbedBuilder()
.setTitle(`HamFact #${displayNum}`)
.setColor(0x21c629)
.setDescription(hamFact);
await message.channel.send({ embeds: [embed] });
}
}
const hamFactCommand = new HamFactCommand();
module.exports = {
name: 'hamfact',
description: 'Get a random ham fact',
async execute(message, args, guildConfig) {
await hamFactCommand.execute(message, args, guildConfig);
}
};