// Enhanced example of using the DubAPI library with AI integration const DubAPI = require('./index.js'); const AIHandler = require('./ai-handler.js'); const MemoryHandler = require('./memory-handler.js'); console.log('Starting DubAPI example...'); // Replace with your queup.net credentials const username = 'Jiwon'; const password = 'cookTV12'; // Replace with the room you want to connect to const roomName = 'classical-music_171599336015945'; // Bot configuration const config = { commandPrefix: '!', // Prefix for commands autoReconnect: true, // Automatically reconnect on disconnection reconnectInterval: 15000, // Time in ms to wait before reconnecting greetUsers: true, // Whether to greet users when they join autoUpvote: true, // Whether to automatically upvote songs botName: 'Jiwon', // Bot's name for mention detection respondToMentions: true, // Whether to respond when mentioned useAI: true, // Whether to use AI responses geminiAPIKeys: [ // Multiple Gemini API keys that will be used in rotation 'AIzaSyAQAw_EZv1kX_l24ViMGnPYGC3ExbIZFCU', 'AIzaSyCXBuz5GCChcPiff9MFtfb6tCspytAKukQ', 'AIzaSyDtntYvtVZ1huXt65sEE-Pj1UDxLFZom2w' // First API key // Add more API keys here, separated by commas ] }; console.log('Attempting to create DubAPI instance with username:', username); console.log('Connecting to room:', roomName); // Initialize memory handler let memoryHandler = new MemoryHandler({ maxMessagesPerUser: 10, // Remember last 10 messages per user maxMemoryAge: 30 * 60 * 1000 // Remember messages for 30 minutes }); // Initialize AI handler if enabled let aiHandler = null; if (config.useAI && config.geminiAPIKeys && config.geminiAPIKeys.length > 0) { aiHandler = new AIHandler(config.geminiAPIKeys, memoryHandler); console.log('AI handler initialized with Gemini API and conversation memory'); } new DubAPI({username: username, password: password}, function(err, bot) { if (err) return console.error('Error connecting:', err); console.log('Running DubAPI v' + bot.version); // ========== Utility Functions ========== // Format time in seconds to mm:ss function formatTime(seconds) { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs < 10 ? '0' + secs : secs}`; } // Check if user has staff permissions function isStaff(user) { return user && bot.isStaff(user); } // ========== Command Handler ========== // Handle chat commands function handleCommand(data) { // Ignore if not a command if (!data.message.startsWith(config.commandPrefix)) return; // Parse command and arguments const args = data.message.slice(config.commandPrefix.length).trim().split(/\s+/); const command = args.shift().toLowerCase(); // Get user who sent the command const user = bot.getUser(data.user.id); switch (command) { case 'ping': bot.sendChat('Pong! 🏓'); break; case 'memory': if (memoryHandler) { const stats = memoryHandler.getStats(); const now = Date.now(); const oldestTime = stats.oldestMessage ? new Date(stats.oldestMessage).toISOString().substring(11, 19) : 'none'; bot.sendChat(`Memory stats: ${stats.userCount} users, ${stats.totalMessages} messages, oldest from ${oldestTime}`); if (args[0] === 'clear' && isStaff(user)) { // Only staff can clear memory memoryHandler.memories.clear(); bot.sendChat('Memory cleared by ' + user.username); } } else { bot.sendChat('Memory system is not active.'); } break; case 'info': const currentMedia = bot.getMedia(); if (currentMedia) { bot.sendChat(`Current track: ${currentMedia.name} by ${currentMedia.artist || 'Unknown'}`); } else { bot.sendChat('No track is currently playing.'); } break; case 'time': const remaining = bot.getTimeRemaining(); const elapsed = bot.getTimeElapsed(); if (remaining >= 0) { bot.sendChat(`Time remaining: ${formatTime(remaining)} | Elapsed: ${formatTime(elapsed)}`); } else { bot.sendChat('No track is currently playing.'); } break; case 'skip': if (isStaff(user) && bot.hasPermission(user, 'skip')) { bot.moderateSkip(function(err) { if (err) return console.error('Error skipping track:', err); bot.sendChat('Track skipped by ' + user.username); }); } else { bot.sendChat('You need to be staff with skip permission to use this command.'); } break; case 'users': const users = bot.getUsers(); bot.sendChat(`There are ${users.length} users in the room.`); break; case 'staff': const staff = bot.getStaff(); bot.sendChat(`There are ${staff.length} staff members in the room.`); break; case 'help': bot.sendChat('Available commands: !ping, !info, !time, !users, !staff, !help'); break; default: // Unknown command break; } } // ========== Connection Functions ========== function connect() { console.log('Attempting to connect to room:', roomName); bot.connect(roomName); } // ========== Event Listeners ========== bot.on('connected', function(name) { console.log('Connected to ' + name); console.log('Bot is ready to respond to commands. Use ' + config.commandPrefix + 'help for available commands'); }); bot.on('disconnected', function(name) { console.log('Disconnected from ' + name); if (config.autoReconnect) { console.log(`Attempting to reconnect in ${config.reconnectInterval / 1000} seconds...`); setTimeout(connect, config.reconnectInterval); } }); bot.on('error', function(err) { console.error('Error:', err); }); // Chat message handler bot.on(bot.events.chatMessage, function(data) { console.log(data.user.username + ': ' + data.message); // Store all messages in memory for context (except bot's own messages) if (data.user.username !== config.botName && memoryHandler) { memoryHandler.addMessage(data.user.username, data.message, false); } // Handle commands handleCommand(data); // Handle mentions (when bot is called by name) if (config.respondToMentions && data.user.username !== config.botName) { // Use simple string methods instead of regex for more reliable detection const message = data.message; const lowerMessage = message.toLowerCase(); const botName = config.botName.toLowerCase(); // Check for mentions in several ways const isMentioned = lowerMessage.includes('@' + botName) || // @kuber lowerMessage.startsWith(botName) || // starts with kuber lowerMessage.includes(' ' + botName + ' ') || // kuber with spaces around lowerMessage.endsWith(' ' + botName); // ends with kuber console.log('Checking for mentions in message:', message, 'Mentioned:', isMentioned); // Check if bot is mentioned if (isMentioned) { console.log(`Bot was mentioned by ${data.user.username}`); // Get the user message without the bot's name for cleaner AI input let userMessage = data.message; const botNamePattern = new RegExp(`@?${config.botName}`, 'gi'); userMessage = userMessage.replace(botNamePattern, '').trim(); // Special case handling for specific commands if (userMessage.toLowerCase().includes('what') && (userMessage.toLowerCase().includes('song') || userMessage.toLowerCase().includes('track') || userMessage.toLowerCase().includes('playing'))) { // Get current song info directly const currentMedia = bot.getMedia(); if (currentMedia) { const response = `@${data.user.username}, we're listening to ${currentMedia.name} by ${currentMedia.artist || 'Unknown'}.`; return bot.sendChat(response); } } if (userMessage.toLowerCase() === 'help' || userMessage.toLowerCase() === '?') { const response = `@${data.user.username}, try using ${config.commandPrefix}help to see my commands.`; return bot.sendChat(response); } // Handle response based on whether AI is enabled if (config.useAI && aiHandler) { // Call AI for response with username for context console.log(`Sending to AI: ${userMessage} (from: ${data.user.username})`); aiHandler.getAIResponse(userMessage, data.user.username) .then(aiResponse => { // Log the full response for debugging console.log(`===== FULL AI RESPONSE (${aiResponse.length} chars) ====`); console.log(aiResponse); console.log(`===== END OF FULL AI RESPONSE ====`); // Split the message into larger chunks for output const maxLength = 250; // Increased from 160 to 250 characters per chunk const chunks = []; // Mention the user in the first chunk only let firstChunk = `@${data.user.username}, `; // Simplified chunking algorithm that focuses on reliability // Just split the text into chunks of maxLength characters, trying to split on word boundaries // First chunk gets the user mention let remaining = aiResponse; let isFirst = true; while (remaining.length > 0) { // Calculate how much we can fit in this chunk const limit = isFirst ? (maxLength - firstChunk.length) : maxLength; let splitAt = Math.min(remaining.length, limit); // If we need to split, try to do it at a word boundary if (remaining.length > limit) { // Look for the last space within our limit const lastSpace = remaining.substring(0, limit).lastIndexOf(' '); if (lastSpace > limit / 2) { // Only use space if it's reasonably positioned splitAt = lastSpace + 1; // Include the space } } // Extract this chunk of text const part = remaining.substring(0, splitAt).trim(); // Add to chunks array with user mention if it's the first chunk if (part.length > 0) { chunks.push(isFirst ? (firstChunk + part) : part); } // Move to next section of text remaining = remaining.substring(splitAt).trim(); isFirst = false; } // Send all chunks with a delay between them console.log(`Sending response in ${chunks.length} chunks`); let sentCount = 0; function sendNextChunk() { if (sentCount < chunks.length) { const chunk = chunks[sentCount]; console.log(`Sending chunk ${sentCount + 1}/${chunks.length}: ${chunk.length} chars`); bot.sendChat(chunk); sentCount++; if (sentCount < chunks.length) { // Schedule next chunk with a delay setTimeout(sendNextChunk, 800); } else { // All chunks sent, store in memory if (memoryHandler) { memoryHandler.addMessage(data.user.username, aiResponse, true); } console.log('All chunks sent successfully'); } } } // Start sending chunks sendNextChunk(); }) .catch(err => { console.error('AI Response Error:', err); bot.sendChat(`@${data.user.username}, sorry, my AI brain is having trouble right now. Try again later?`); }); } else { // Fallback to pre-defined responses when AI is disabled const responses = [ `Yes, @${data.user.username}? How can I help you?`, `I'm here, @${data.user.username}! Need something?`, `Hey @${data.user.username}, what's up?`, `@${data.user.username}, at your service! Try ${config.commandPrefix}help for commands.` ]; const response = responses[Math.floor(Math.random() * responses.length)]; // Send the response with a small delay to seem more natural setTimeout(() => { bot.sendChat(response); }, 1000 + Math.random() * 1000); // Random delay between 1-2 seconds } } } }); // User join handler bot.on(bot.events.userJoin, function(data) { console.log(`${data.user.username} joined the room`); }); // User leave handler bot.on(bot.events.userLeave, function(data) { console.log(`${data.user.username} left the room`); }); // Song change handler bot.on(bot.events.roomPlaylistUpdate, function(data) { if (!data.media) return; console.log(`Now playing: ${data.media.name} by ${data.media.artist || 'Unknown'}`); // Auto upvote songs if enabled if (config.autoUpvote) { setTimeout(() => { bot.downdub(function(err) { if (err) console.error('Error upvoting:', err); else console.log('Auto-upvoted current song'); }); }, 5000); // Wait 5 seconds before upvoting } }); // Connect to the room connect(); });