- Added hunt/feed duck mechanics (80% hunt, 20% feed) - Implemented persistent scoring system - Added channel control commands (\!stopducks/\!startducks) - Enhanced duck hunt with wrong action penalties - Organized bot structure with botmain.js as main file - Added comprehensive documentation (README.md) - Included 17 plugins with various games and utilities 🦆 Duck Hunt Features: - Hunt ducks with \!shoot/\!bang (80% of spawns) - Feed ducks with \!feed (20% of spawns) - Persistent scores saved to JSON - Channel-specific controls for #bakedbeans - Reaction time tracking and special achievements 🎮 Other Games: - Casino games (slots, coinflip, hi-lo, scratch cards) - Multiplayer games (pigs, zombie dice, quiplash) - Text generation (babble, conspiracy, drunk historian) - Interactive features (story writing, emojify, combos) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
296 lines
No EOL
8.7 KiB
JavaScript
296 lines
No EOL
8.7 KiB
JavaScript
// bot.js - Main IRC Bot with Plugin System
|
|
const net = require('net');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
class IRCBot {
|
|
constructor(config) {
|
|
this.config = {
|
|
server: config.server || 'irc.libera.chat',
|
|
port: config.port || 6667,
|
|
nick: config.nick || 'MyBot',
|
|
channels: config.channels || ['#test'],
|
|
commandPrefix: config.commandPrefix || '!',
|
|
pluginsDir: config.pluginsDir || './plugins'
|
|
};
|
|
|
|
this.socket = null;
|
|
this.plugins = new Map();
|
|
this.commands = new Map();
|
|
this.connected = false;
|
|
|
|
this.loadPlugins();
|
|
}
|
|
|
|
connect() {
|
|
console.log(`Connecting to ${this.config.server}:${this.config.port}`);
|
|
|
|
this.socket = net.createConnection(this.config.port, this.config.server);
|
|
|
|
this.socket.on('connect', () => {
|
|
console.log('Connected to IRC server');
|
|
this.send(`NICK ${this.config.nick}`);
|
|
this.send(`USER ${this.config.nick} 0 * :${this.config.nick}`);
|
|
});
|
|
|
|
this.socket.on('data', (data) => {
|
|
const lines = data.toString().split('\r\n');
|
|
lines.forEach(line => {
|
|
if (line.trim()) {
|
|
this.handleMessage(line);
|
|
}
|
|
});
|
|
});
|
|
|
|
this.socket.on('error', (err) => {
|
|
console.error('Socket error:', err);
|
|
});
|
|
|
|
this.socket.on('close', () => {
|
|
console.log('Connection closed');
|
|
this.connected = false;
|
|
// Reconnect after 5 seconds
|
|
setTimeout(() => this.connect(), 5000);
|
|
});
|
|
}
|
|
|
|
send(message) {
|
|
if (this.socket && this.socket.writable) {
|
|
console.log('→', message);
|
|
this.socket.write(message + '\r\n');
|
|
}
|
|
}
|
|
|
|
handleMessage(line) {
|
|
console.log('←', line);
|
|
|
|
// Handle PING
|
|
if (line.startsWith('PING')) {
|
|
this.send('PONG ' + line.substring(5));
|
|
return;
|
|
}
|
|
|
|
// Handle successful connection
|
|
if (line.includes('001')) {
|
|
this.connected = true;
|
|
console.log('Successfully connected and registered');
|
|
// Join channels
|
|
this.config.channels.forEach(channel => {
|
|
this.send(`JOIN ${channel}`);
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Parse IRC message
|
|
const parsed = this.parseMessage(line);
|
|
if (parsed) {
|
|
this.handleParsedMessage(parsed);
|
|
}
|
|
}
|
|
|
|
parseMessage(line) {
|
|
// Basic IRC message parsing
|
|
const match = line.match(/^:([^!]+)!([^@]+)@([^\s]+)\s+(\w+)\s+([^:]+):(.*)$/);
|
|
if (match) {
|
|
return {
|
|
nick: match[1],
|
|
user: match[2],
|
|
host: match[3],
|
|
command: match[4],
|
|
target: match[5].trim(),
|
|
message: match[6]
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
handleParsedMessage(msg) {
|
|
// Handle PRIVMSG (channel messages and private messages)
|
|
if (msg.command === 'PRIVMSG') {
|
|
const isChannel = msg.target.startsWith('#');
|
|
const replyTo = isChannel ? msg.target : msg.nick;
|
|
|
|
// Check if it's a command
|
|
if (msg.message.startsWith(this.config.commandPrefix)) {
|
|
const commandLine = msg.message.substring(this.config.commandPrefix.length);
|
|
const [commandName, ...args] = commandLine.split(' ');
|
|
|
|
this.executeCommand(commandName, {
|
|
nick: msg.nick,
|
|
user: msg.user,
|
|
host: msg.host,
|
|
channel: isChannel ? msg.target : null,
|
|
replyTo: replyTo,
|
|
args: args,
|
|
fullMessage: msg.message
|
|
});
|
|
}
|
|
|
|
// Emit message event for plugins
|
|
this.emit('message', {
|
|
nick: msg.nick,
|
|
user: msg.user,
|
|
host: msg.host,
|
|
target: msg.target,
|
|
message: msg.message,
|
|
isChannel: isChannel,
|
|
replyTo: replyTo
|
|
});
|
|
}
|
|
}
|
|
|
|
executeCommand(commandName, context) {
|
|
const command = this.commands.get(commandName);
|
|
if (command) {
|
|
try {
|
|
command.execute(context, this);
|
|
} catch (error) {
|
|
console.error(`Error executing command ${commandName}:`, error);
|
|
this.say(context.replyTo, `Error executing command: ${error.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
loadPlugins() {
|
|
if (!fs.existsSync(this.config.pluginsDir)) {
|
|
fs.mkdirSync(this.config.pluginsDir, { recursive: true });
|
|
console.log(`Created plugins directory: ${this.config.pluginsDir}`);
|
|
return;
|
|
}
|
|
|
|
const pluginFiles = fs.readdirSync(this.config.pluginsDir)
|
|
.filter(file => file.endsWith('.js'));
|
|
|
|
pluginFiles.forEach(file => {
|
|
this.loadPlugin(file);
|
|
});
|
|
|
|
console.log(`Loaded ${pluginFiles.length} plugins`);
|
|
}
|
|
|
|
loadPlugin(filename) {
|
|
const filePath = path.join(this.config.pluginsDir, filename);
|
|
|
|
try {
|
|
// Clear require cache to allow reloading
|
|
delete require.cache[require.resolve(filePath)];
|
|
|
|
const plugin = require(filePath);
|
|
|
|
if (typeof plugin.init === 'function') {
|
|
plugin.init(this);
|
|
}
|
|
|
|
// Register commands
|
|
if (plugin.commands) {
|
|
plugin.commands.forEach(command => {
|
|
this.commands.set(command.name, command);
|
|
console.log(`Registered command: ${command.name}`);
|
|
});
|
|
}
|
|
|
|
this.plugins.set(filename, plugin);
|
|
console.log(`Loaded plugin: ${filename}`);
|
|
|
|
} catch (error) {
|
|
console.error(`Error loading plugin ${filename}:`, error);
|
|
}
|
|
}
|
|
|
|
reloadPlugin(filename) {
|
|
const plugin = this.plugins.get(filename);
|
|
if (plugin) {
|
|
// Unregister old commands
|
|
if (plugin.commands) {
|
|
plugin.commands.forEach(command => {
|
|
this.commands.delete(command.name);
|
|
});
|
|
}
|
|
|
|
// Call cleanup if available
|
|
if (typeof plugin.cleanup === 'function') {
|
|
plugin.cleanup(this);
|
|
}
|
|
|
|
this.plugins.delete(filename);
|
|
}
|
|
|
|
// Load the plugin again
|
|
this.loadPlugin(filename);
|
|
}
|
|
|
|
reloadAllPlugins() {
|
|
// Clear all plugins and commands
|
|
this.plugins.forEach((plugin, filename) => {
|
|
if (typeof plugin.cleanup === 'function') {
|
|
plugin.cleanup(this);
|
|
}
|
|
});
|
|
|
|
this.plugins.clear();
|
|
this.commands.clear();
|
|
|
|
// Reload all plugins
|
|
this.loadPlugins();
|
|
}
|
|
|
|
// Helper methods for plugins
|
|
say(target, message) {
|
|
this.send(`PRIVMSG ${target} :${message}`);
|
|
}
|
|
|
|
action(target, message) {
|
|
this.send(`PRIVMSG ${target} :\x01ACTION ${message}\x01`);
|
|
}
|
|
|
|
notice(target, message) {
|
|
this.send(`NOTICE ${target} :${message}`);
|
|
}
|
|
|
|
join(channel) {
|
|
this.send(`JOIN ${channel}`);
|
|
}
|
|
|
|
part(channel, reason = '') {
|
|
this.send(`PART ${channel} :${reason}`);
|
|
}
|
|
|
|
// Simple event system for plugins
|
|
emit(event, data) {
|
|
this.plugins.forEach(plugin => {
|
|
if (typeof plugin[`on${event.charAt(0).toUpperCase() + event.slice(1)}`] === 'function') {
|
|
try {
|
|
plugin[`on${event.charAt(0).toUpperCase() + event.slice(1)}`](data, this);
|
|
} catch (error) {
|
|
console.error(`Error in plugin event handler:`, error);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Configuration
|
|
const config = {
|
|
server: 'irc.libera.chat',
|
|
port: 6667,
|
|
nick: 'MyBot',
|
|
channels: ['#test'],
|
|
commandPrefix: '!',
|
|
pluginsDir: './plugins'
|
|
};
|
|
|
|
// Create and start bot
|
|
const bot = new IRCBot(config);
|
|
bot.connect();
|
|
|
|
// Handle graceful shutdown
|
|
process.on('SIGINT', () => {
|
|
console.log('\nShutting down bot...');
|
|
if (bot.socket) {
|
|
bot.send('QUIT :Bot shutting down');
|
|
bot.socket.end();
|
|
}
|
|
process.exit(0);
|
|
});
|
|
|
|
module.exports = IRCBot; |