megabot/botmain.js
megaproxy a3ed25f8dd Initial commit: Enhanced IRC bot with duck hunt game
- 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>
2025-07-17 19:03:45 +00:00

423 lines
No EOL
14 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// bot.js - Main IRC Bot with Plugin System (Debug Version)
// ================================
// BOT CONFIGURATION - EDIT HERE
// ================================
const config = {
server: 'irc.libera.chat', // IRC server address
port: 6667, // IRC server port (6667 for plain, 6697 for SSL)
nick: 'cancerbot', // Bot's nickname
channels: ['#bakedbeans'], // Channels to join (add more like: ['#channel1', '#channel2'])
commandPrefix: '!', // Command prefix (e.g., !help, !ping)
pluginsDir: './plugins' // Directory containing plugin files
};
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;
console.log('🔧 Bot configuration:', this.config);
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(' ');
// Handle built-in admin commands first
if (commandName === 'reloadplugins' && msg.nick === 'megasconed') {
this.say(replyTo, '🔄 Reloading all plugins...');
const pluginCount = this.plugins.size;
this.reloadAllPlugins();
this.say(replyTo, `✅ Reloaded ${pluginCount} plugins. New commands: ${Array.from(this.commands.keys()).join(', ')}`);
return;
}
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}`);
}
} else {
console.log(`Command not found: ${commandName}`);
}
}
loadPlugins() {
console.log(`🔍 Looking for plugins in: ${this.config.pluginsDir}`);
console.log(`🔍 Full path: ${path.resolve(this.config.pluginsDir)}`);
if (!fs.existsSync(this.config.pluginsDir)) {
console.log(`📁 Creating plugins directory: ${this.config.pluginsDir}`);
fs.mkdirSync(this.config.pluginsDir, { recursive: true });
// Create a basic example plugin
this.createExamplePlugin();
return;
}
console.log(`📁 Plugins directory exists, scanning for .js files...`);
let pluginFiles;
try {
pluginFiles = fs.readdirSync(this.config.pluginsDir)
.filter(file => file.endsWith('.js'));
console.log(`📋 Found files:`, pluginFiles);
} catch (error) {
console.error(`❌ Error reading plugins directory:`, error);
return;
}
if (pluginFiles.length === 0) {
console.log(`⚠️ No .js files found in plugins directory`);
console.log(`💡 Creating example plugin...`);
this.createExamplePlugin();
return;
}
pluginFiles.forEach(file => {
console.log(`🔄 Loading plugin: ${file}`);
this.loadPlugin(file);
});
console.log(`✅ Loaded ${pluginFiles.length} plugins`);
console.log(`🎮 Available commands:`, Array.from(this.commands.keys()));
}
createExamplePlugin() {
const examplePlugin = `// plugins/basic.js - Basic commands plugin
module.exports = {
init(bot) {
console.log('Basic plugin initialized');
},
cleanup(bot) {
console.log('Basic plugin cleaned up');
},
commands: [
{
name: 'ping',
description: 'Responds with pong',
execute(context, bot) {
bot.say(context.replyTo, \`\${context.nick}: pong!\`);
}
},
{
name: 'help',
description: 'Shows available commands',
execute(context, bot) {
const commands = Array.from(bot.commands.keys());
bot.say(context.replyTo, \`Available commands: \${commands.join(', ')}\`);
}
},
{
name: 'time',
description: 'Shows current time',
execute(context, bot) {
const now = new Date().toLocaleString();
bot.say(context.replyTo, \`Current time: \${now}\`);
}
}
]
};`;
const pluginPath = path.join(this.config.pluginsDir, 'basic.js');
try {
fs.writeFileSync(pluginPath, examplePlugin);
console.log(`✅ Created example plugin: ${pluginPath}`);
// Give filesystem a moment to settle
setTimeout(() => {
console.log(`🔄 Loading newly created plugin...`);
this.loadPlugin('basic.js');
}, 100);
} catch (writeError) {
console.error(`❌ Error creating example plugin:`, writeError);
}
}
loadPlugin(filename) {
const filePath = path.join(this.config.pluginsDir, filename);
console.log(`🔄 Attempting to load: ${filePath}`);
try {
// Check if file exists
if (!fs.existsSync(filePath)) {
console.error(`❌ Plugin file not found: ${filePath}`);
return;
}
// Get absolute path for require.resolve
const absolutePath = path.resolve(filePath);
console.log(`📍 Absolute path: ${absolutePath}`);
// Clear require cache to allow reloading (only if already cached)
try {
const fullPath = require.resolve(absolutePath);
console.log(`🗑️ Clearing cache for: ${fullPath}`);
delete require.cache[fullPath];
} catch (resolveError) {
console.log(` File not in cache yet: ${absolutePath}`);
}
// Require the plugin
console.log(`📥 Requiring plugin: ${absolutePath}`);
const plugin = require(absolutePath);
console.log(`📋 Plugin object:`, Object.keys(plugin));
// Initialize plugin
if (typeof plugin.init === 'function') {
console.log(`🚀 Initializing plugin: ${filename}`);
plugin.init(this);
} else {
console.log(`⚠️ Plugin ${filename} has no init function`);
}
// Register commands
if (plugin.commands && Array.isArray(plugin.commands)) {
console.log(`📝 Registering ${plugin.commands.length} commands from ${filename}`);
plugin.commands.forEach(command => {
if (command.name && typeof command.execute === 'function') {
this.commands.set(command.name, command);
console.log(`✅ Registered command: ${command.name}`);
} else {
console.error(`❌ Invalid command in ${filename}:`, command);
}
});
} else {
console.log(`⚠️ Plugin ${filename} has no commands array`);
}
this.plugins.set(filename, plugin);
console.log(`✅ Successfully loaded plugin: ${filename}`);
} catch (error) {
console.error(`❌ Error loading plugin ${filename}:`, error.message);
console.error(`📍 Stack trace:`, error.stack);
}
}
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);
}
}
});
}
}
// 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;