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>
This commit is contained in:
commit
a3ed25f8dd
39 changed files with 12360 additions and 0 deletions
296
bot_template.js
Normal file
296
bot_template.js
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
// 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;
|
||||
Loading…
Add table
Add a link
Reference in a new issue