megabot/plugins/babble_plugin_creative(3).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

773 lines
No EOL
30 KiB
JavaScript

// plugins/babble.js - Fresh Markov Chain Text Generator plugin with user-specific generation
const path = require('path');
const fs = require('fs');
module.exports = {
init(bot) {
console.log('🎯 Babble plugin starting...');
this.bot = bot;
// Try to load sqlite3
try {
this.sqlite3 = require('sqlite3').verbose();
console.log('✅ SQLite3 loaded successfully');
} catch (error) {
console.error('❌ SQLite3 failed to load:', error.message);
this.sqlite3 = null;
return;
}
// Database setup
this.dbPath = path.join(__dirname, 'babble.db');
console.log(`📁 Database will be created at: ${this.dbPath}`);
// Initialize database
this.initDB();
// Simple settings
this.settings = {
minLength: 5,
excludeBots: ['cancerbot', 'services'],
excludeCommands: true
};
console.log('✅ Babble plugin initialized');
},
cleanup(bot) {
console.log('🎯 Babble plugin cleaning up');
if (this.db) {
this.db.close();
}
},
initDB() {
try {
this.db = new this.sqlite3.Database(this.dbPath, (err) => {
if (err) {
console.error('❌ Database creation failed:', err.message);
this.db = null;
return;
}
console.log('✅ Database created/opened successfully');
this.createTables();
});
} catch (error) {
console.error('❌ Database init error:', error.message);
this.db = null;
}
},
createTables() {
if (!this.db) return;
// Simple messages table
this.db.run(`
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nick TEXT,
channel TEXT,
message TEXT,
created DATETIME DEFAULT CURRENT_TIMESTAMP
)
`, (err) => {
if (err) {
console.error('❌ Messages table creation failed:', err.message);
} else {
console.log('✅ Messages table ready');
}
});
// Check if word_pairs table exists and if it has nick column
this.db.get("PRAGMA table_info(word_pairs)", (err, result) => {
if (err) {
console.log('🔧 Creating new word_pairs table...');
this.createWordPairsTable();
} else {
// Table exists, check if it has nick column
this.db.all("PRAGMA table_info(word_pairs)", (err, columns) => {
if (err) {
console.error('❌ Error checking table structure:', err.message);
return;
}
const hasNickColumn = columns.some(col => col.name === 'nick');
if (!hasNickColumn) {
console.log('🔧 Migrating word_pairs table to add nick column...');
this.migrateWordPairsTable();
} else {
console.log('✅ Word pairs table ready with nick column');
console.log('🎯 Database fully initialized');
}
});
}
});
},
createWordPairsTable() {
this.db.run(`
CREATE TABLE word_pairs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
word1 TEXT,
word2 TEXT,
count INTEGER DEFAULT 1,
nick TEXT,
UNIQUE(word1, word2, nick)
)
`, (err) => {
if (err) {
console.error('❌ Word pairs table creation failed:', err.message);
} else {
console.log('✅ Word pairs table created with nick column');
console.log('🎯 Database fully initialized');
}
});
},
migrateWordPairsTable() {
// Rename old table
this.db.run("ALTER TABLE word_pairs RENAME TO word_pairs_old", (err) => {
if (err) {
console.error('❌ Failed to rename old table:', err.message);
return;
}
// Create new table with nick column
this.db.run(`
CREATE TABLE word_pairs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
word1 TEXT,
word2 TEXT,
count INTEGER DEFAULT 1,
nick TEXT,
UNIQUE(word1, word2, nick)
)
`, (err) => {
if (err) {
console.error('❌ Failed to create new table:', err.message);
return;
}
// Copy old data (nick will be NULL for old entries)
this.db.run(`
INSERT INTO word_pairs (word1, word2, count, nick)
SELECT word1, word2, count, NULL FROM word_pairs_old
`, (err) => {
if (err) {
console.error('❌ Failed to migrate data:', err.message);
return;
}
// Drop old table
this.db.run("DROP TABLE word_pairs_old", (err) => {
if (err) {
console.error('❌ Failed to drop old table:', err.message);
} else {
console.log('✅ Database migration completed successfully');
console.log('🎯 Database fully initialized');
}
});
});
});
});
},
shouldLog(nick, message) {
// Skip bots
if (this.settings.excludeBots.includes(nick.toLowerCase())) {
return false;
}
// Skip commands
if (this.settings.excludeCommands && message.startsWith('!')) {
return false;
}
// Skip short messages
if (message.length < this.settings.minLength) {
return false;
}
return true;
},
cleanMessage(message) {
// Remove IRC colors and formatting
message = message.replace(/[\x02\x1D\x1F\x16\x0F]|\x03\d{0,2}(,\d{0,2})?/g, '');
// Basic cleanup
message = message.toLowerCase().trim();
message = message.replace(/[^\w\s]/g, ' '); // Remove punctuation
message = message.replace(/\s+/g, ' '); // Multiple spaces to single
return message;
},
storeMessage(nick, channel, message) {
if (!this.db) {
console.log('❌ No database available for storing message');
return;
}
if (!this.shouldLog(nick, message)) {
console.log(`⏭️ Skipping message from ${nick}: ${message.substring(0, 20)}...`);
return;
}
const cleaned = this.cleanMessage(message);
if (cleaned.length < this.settings.minLength) {
console.log('⏭️ Message too short after cleaning');
return;
}
console.log(`📝 Storing message from ${nick}: "${cleaned}"`);
// Store the message
this.db.run(
'INSERT INTO messages (nick, channel, message) VALUES (?, ?, ?)',
[nick, channel, cleaned],
(err) => {
if (err) {
console.error('❌ Failed to store message:', err.message);
} else {
console.log('✅ Message stored successfully');
this.buildPairs(cleaned, nick);
}
}
);
},
buildPairs(message, nick) {
if (!this.db) return;
const words = message.split(' ').filter(w => w.length > 0);
if (words.length < 2) return;
// Add START and END markers
const allWords = ['<START>', ...words, '<END>'];
// Create word pairs
for (let i = 0; i < allWords.length - 1; i++) {
const word1 = allWords[i];
const word2 = allWords[i + 1];
this.db.run(`
INSERT INTO word_pairs (word1, word2, count, nick) VALUES (?, ?, 1, ?)
ON CONFLICT(word1, word2, nick) DO UPDATE SET count = count + 1
`, [word1, word2, nick], (err) => {
if (err) {
console.error('❌ Failed to store word pair:', err.message);
} else {
console.log(`🔗 Stored pair: "${word1}" → "${word2}" (${nick})`);
}
});
}
},
generateText(callback, targetNick = null, creative = false) {
if (!this.db) {
callback('No database available');
return;
}
const mode = creative ? 'creative' : 'normal';
console.log(`🤖 Starting ${mode} text generation${targetNick ? ` for ${targetNick}` : ''}...`);
// First check if target user exists and has enough data
if (targetNick) {
this.db.get(
'SELECT COUNT(*) as count FROM word_pairs WHERE nick = ?',
[targetNick],
(err, row) => {
if (err || !row || row.count < 10) {
callback(`Not enough data for user "${targetNick}" (need at least 10 word pairs)`);
return;
}
this.performGeneration(callback, targetNick, creative);
}
);
} else {
this.performGeneration(callback, null, creative);
}
},
performGeneration(callback, targetNick, creative = false) {
const self = this; // Capture 'this' reference
// Strategy: Start from a random word
this.getRandomStartWord((err, startWord) => {
if (err) {
callback(err);
return;
}
let currentWord = startWord;
let result = [];
let attempts = 0;
const maxAttempts = creative ? 80 : 50; // More attempts for creative mode
const maxLength = creative ?
Math.floor(Math.random() * 15) + 8 : // Creative: 8-22 words
Math.floor(Math.random() * 10) + 5; // Normal: 5-14 words
console.log(`🎲 Starting with word: "${currentWord}" (${creative ? 'CREATIVE' : 'normal'} mode)`);
// Add the starting word if it's not a marker
if (currentWord !== '<START>' && currentWord !== '<END>') {
result.push(currentWord);
}
const getNext = () => {
if (attempts++ > maxAttempts || result.length >= maxLength) {
if (result.length >= 3) {
const prefix = targetNick ? `[${targetNick}] ` : '';
const emoji = creative ? '🎨 ' : '🤖 ';
callback(null, prefix + emoji + result.join(' '));
} else {
callback('Generation failed - too short');
}
return;
}
// Build query based on whether we're targeting a specific user
let query = 'SELECT word2, count FROM word_pairs WHERE word1 = ?';
let params = [currentWord];
if (targetNick) {
query += ' AND nick = ?';
params.push(targetNick);
}
query += ' ORDER BY RANDOM() LIMIT 15'; // More options for creative mode
this.db.all(query, params, (err, rows) => {
if (err) {
console.error('❌ Database error during generation:', err.message);
callback('Database error: ' + err.message);
return;
}
if (rows.length === 0) {
// If stuck, try jumping to a random word
console.log(`🔄 No next words for "${currentWord}", trying random jump`);
// In creative mode, try mixing users more often
const mixUsers = creative && Math.random() < 0.4;
const jumpTargetNick = mixUsers ? null : targetNick;
this.getRandomWord((err, randomWord) => {
if (err || !randomWord || randomWord === '<START>' || randomWord === '<END>') {
if (result.length >= 3) {
const prefix = targetNick ? `[${targetNick}] ` : '';
const emoji = creative ? '🎨 ' : '🤖 ';
callback(null, prefix + emoji + result.join(' '));
} else {
callback('Generation incomplete');
}
return;
}
result.push(randomWord);
currentWord = randomWord;
console.log(`🎲 Random jump to: "${randomWord}"${mixUsers ? ' (mixed user)' : ''}`);
getNext();
}, jumpTargetNick);
return;
}
// Different selection strategies for creative vs normal mode
let nextWord;
if (creative) {
// Creative mode: Much more chaos and randomness
const randomChoice = Math.random();
if (randomChoice < 0.6) {
// 60% chance: Pick rare/uncommon words for weirdness
const uncommon = rows.filter(r => r.count <= 2);
if (uncommon.length > 0) {
nextWord = uncommon[Math.floor(Math.random() * uncommon.length)].word2;
console.log(`🎨 Weird choice: "${nextWord}"`);
} else {
// If no uncommon words, pick any random one
nextWord = rows[Math.floor(Math.random() * rows.length)].word2;
console.log(`🎨 Random fallback: "${nextWord}"`);
}
} else if (randomChoice < 0.8) {
// 20% chance: Pick completely random word (ignore context)
self.getRandomWord((err, randomWord) => {
if (!err && randomWord && randomWord !== '<START>' && randomWord !== '<END>') {
result.push(randomWord);
currentWord = randomWord;
console.log(`🎨 Context break: "${randomWord}"`);
getNext();
return;
}
// Fallback to normal selection
nextWord = rows[Math.floor(Math.random() * rows.length)].word2;
console.log(`🎨 Fallback random: "${nextWord}"`);
processNextWord();
}, null); // null = any user for maximum chaos
return;
} else {
// 20% chance: Normal weighted selection for some coherence
const totalWeight = rows.reduce((sum, row) => sum + row.count, 0);
let random = Math.random() * totalWeight;
nextWord = rows[0].word2;
for (const row of rows) {
random -= row.count;
if (random <= 0) {
nextWord = row.word2;
break;
}
}
console.log(`🎨 Weighted choice: "${nextWord}"`);
}
} else {
// Normal mode: Original logic
if (Math.random() < 0.3) {
// 30% chance: Pick a less common word for creativity
const lessCommon = rows.filter(r => r.count <= 2);
if (lessCommon.length > 0) {
nextWord = lessCommon[Math.floor(Math.random() * lessCommon.length)].word2;
console.log(`🎨 Creative choice: "${nextWord}"`);
} else {
nextWord = rows[Math.floor(Math.random() * Math.min(5, rows.length))].word2;
}
} else {
// 70% chance: Normal weighted selection
const totalWeight = rows.reduce((sum, row) => sum + row.count, 0);
let random = Math.random() * totalWeight;
nextWord = rows[0].word2;
for (const row of rows) {
random -= row.count;
if (random <= 0) {
nextWord = row.word2;
break;
}
}
}
}
processNextWord();
function processNextWord() {
console.log(`🔗 Selected next word: "${nextWord}"`);
// Check for end
if (nextWord === '<END>') {
if (result.length >= 3) {
const prefix = targetNick ? `[${targetNick}] ` : '';
const emoji = creative ? '🎨 ' : '🤖 ';
callback(null, prefix + emoji + result.join(' '));
} else {
// Too short, try to continue with random word
self.getRandomWord((err, randomWord) => {
if (!err && randomWord && randomWord !== '<START>' && randomWord !== '<END>') {
result.push(randomWord);
currentWord = randomWord;
getNext();
} else {
const prefix = targetNick ? `[${targetNick}] ` : '';
const emoji = creative ? '🎨 ' : '🤖 ';
callback(null, prefix + emoji + result.join(' '));
}
}, targetNick);
}
return;
}
// Add word and continue
if (nextWord !== '<START>') {
result.push(nextWord);
}
currentWord = nextWord;
getNext();
}
});
};
getNext();
}, targetNick);
},
getRandomStartWord(callback, targetNick = null) {
if (!this.db) {
callback('No database available');
return;
}
// 50% chance to start with <START>, 50% chance to start with random word
if (Math.random() < 0.5) {
callback(null, '<START>');
return;
}
// Build query for random word that appears as word1
let query = 'SELECT word1 FROM word_pairs WHERE word1 != "<START>" AND word1 != "<END>"';
let params = [];
if (targetNick) {
query += ' AND nick = ?';
params.push(targetNick);
}
query += ' ORDER BY RANDOM() LIMIT 1';
this.db.get(query, params, (err, row) => {
if (err || !row) {
callback(null, '<START>'); // Fallback
} else {
callback(null, row.word1);
}
});
},
getRandomWord(callback, targetNick = null) {
if (!this.db) {
callback('No database available');
return;
}
let query = 'SELECT word2 FROM word_pairs WHERE word2 != "<START>" AND word2 != "<END>"';
let params = [];
if (targetNick) {
query += ' AND nick = ?';
params.push(targetNick);
}
query += ' ORDER BY RANDOM() LIMIT 1';
this.db.get(query, params, (err, row) => {
if (err || !row) {
callback('No random word found');
} else {
callback(null, row.word2);
}
});
},
getStats(callback) {
if (!this.db) {
callback('No database available');
return;
}
this.db.get('SELECT COUNT(*) as messages FROM messages', (err, msgRow) => {
if (err) {
callback('Database error: ' + err.message);
return;
}
this.db.get('SELECT COUNT(*) as pairs FROM word_pairs', (err, pairRow) => {
if (err) {
callback('Database error: ' + err.message);
return;
}
this.db.get('SELECT COUNT(DISTINCT nick) as users FROM messages', (err, userRow) => {
if (err) {
callback('Database error: ' + err.message);
return;
}
callback(null, {
messages: msgRow.messages,
pairs: pairRow.pairs,
users: userRow.users
});
});
});
});
},
// This is the key - message event handler
onMessage(data, bot) {
console.log(`📨 Babble received message from ${data.nick}: "${data.message}"`);
if (data.isChannel) {
this.storeMessage(data.nick, data.target, data.message);
}
},
commands: [
{
name: 'babble',
description: 'Generate random text from learned messages, optionally like a specific user',
execute: function(context, bot) {
const plugin = module.exports;
const target = context.replyTo;
const from = context.nick;
const args = context.args;
// Check if user specified a target nick
const targetNick = args.length > 0 ? args[0] : null;
plugin.generateText((err, text) => {
if (err) {
bot.say(target, `${from}: ${err}`);
} else {
bot.say(target, text);
}
}, targetNick, false);
}
},
{
name: 'babblecreative',
description: 'Generate weird, funny, and strange text with maximum chaos',
execute: function(context, bot) {
const plugin = module.exports;
const target = context.replyTo;
const from = context.nick;
const args = context.args;
// Check if user specified a target nick
const targetNick = args.length > 0 ? args[0] : null;
plugin.generateText((err, text) => {
if (err) {
bot.say(target, `${from}: ${err}`);
} else {
bot.say(target, text);
}
}, targetNick, true); // true = creative mode
}
},
{
name: 'babblestats',
description: 'Show babble learning statistics',
execute: function(context, bot) {
const plugin = module.exports;
const target = context.replyTo;
plugin.getStats((err, stats) => {
if (err) {
bot.say(target, `Error: ${err}`);
} else {
bot.say(target, `🧠 Learned: ${stats.messages} messages, ${stats.pairs} word pairs, ${stats.users} users`);
}
});
}
},
{
name: 'babbleusers',
description: 'Show users available for targeted babbling',
execute: function(context, bot) {
const plugin = module.exports;
const target = context.replyTo;
if (!plugin.db) {
bot.say(target, 'No database available');
return;
}
plugin.db.all(
'SELECT nick, COUNT(*) as word_count FROM word_pairs WHERE nick IS NOT NULL GROUP BY nick ORDER BY word_count DESC LIMIT 10',
(err, rows) => {
if (err) {
bot.say(target, 'Database error: ' + err.message);
return;
}
if (rows.length === 0) {
bot.say(target, '🤖 No users found in babble database yet.');
return;
}
const userList = rows.map(row => `${row.nick}(${row.word_count})`).join(', ');
bot.say(target, `🎭 Available users: ${userList}`);
bot.say(target, `💡 Usage: !babble <nick> or !babblecreative <nick>`);
}
);
}
},
{
name: 'babbleadd',
description: 'Manually add a message to learning database',
execute: function(context, bot) {
const plugin = module.exports;
const target = context.replyTo;
const from = context.nick;
const args = context.args;
if (args.length === 0) {
bot.say(target, `Usage: !babbleadd <message to learn>`);
return;
}
const message = args.join(' ');
plugin.storeMessage(from, target, message);
bot.say(target, `${from}: Added message to learning database`);
}
},
{
name: 'babbletest',
description: 'Add test data to verify babble is working',
execute: function(context, bot) {
const plugin = module.exports;
const target = context.replyTo;
const from = context.nick;
const testMessages = [
'hello world how are you doing today',
'i love programming with javascript and node',
'the weather is really nice outside today',
'coding is fun but sometimes challenging',
'artificial intelligence is fascinating technology'
];
testMessages.forEach(msg => {
plugin.storeMessage('testuser', target, msg);
});
bot.say(target, `${from}: Added ${testMessages.length} test messages. Try !babble in a few seconds.`);
}
},
{
name: 'babblereset',
description: 'Reset all babble data (admin only)',
execute: function(context, bot) {
const plugin = module.exports;
const target = context.replyTo;
const from = context.nick;
const adminNicks = ['admin', 'owner', 'cancerbot', 'megasconed'];
if (!adminNicks.includes(from)) {
bot.say(target, `${from}: Access denied - admin only command`);
return;
}
if (!plugin.db) {
bot.say(target, `${from}: No database available`);
return;
}
plugin.db.run('DELETE FROM messages', (err) => {
if (err) {
bot.say(target, `${from}: Error clearing messages: ${err.message}`);
return;
}
plugin.db.run('DELETE FROM word_pairs', (err) => {
if (err) {
bot.say(target, `${from}: Error clearing word pairs: ${err.message}`);
} else {
bot.say(target, `${from}: All babble data cleared`);
}
});
});
}
}
]
};