// bot.js - Main IRC Bot with Plugin System // ================================ // 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; 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); } } }); } } // 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;