// 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 = 'kuber'; const password = 'cookTV12'; // Replace with the room you want to connect to const roomName = 'nononono'; // 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: 'Kuber', // Bot's name for mention detection respondToMentions: true, // Whether to respond when mentioned useAI: true, // Whether to use AI responses geminiAPIKey: 'AIzaSyAQAw_EZv1kX_l24ViMGnPYGC3ExbIZFCU' // Gemini API key }; 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.geminiAPIKey) { aiHandler = new AIHandler(config.geminiAPIKey, 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) { // Show typing indicator bot.sendChat(`/me is thinking...`); // 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 => { // Clear typing indicator by deleting last message (if possible) // This would require custom implementation as DubAPI doesn't support this directly // Format first message with user mention let firstMessage = `@${data.user.username}, `; // Smart message chunking for chat platforms const maxChatLength = 160; // DubAPI character limit (very conservative to be safe) // Check if we need to split the message if (aiResponse.length > maxChatLength - firstMessage.length) { console.log(`Response too long (${aiResponse.length} chars), splitting into multiple messages`); // Prepare chunks array let chunks = []; // Log the entire response for debugging console.log('Full AI response:', aiResponse); // Special handling for lists (numbered or bullet points) // Match numbered lists (1., 2., etc.) or bullet points (•, *) const listRegex = /((?:\d+\.\s+|[•\*]\s+)[^\d•\*\n.]+)/g; const hasList = listRegex.test(aiResponse); // If we have lists (bullet points or numbered lists), handle them specially if (hasList) { console.log('Detected list items in response, preserving list structure'); // Reset regex position listRegex.lastIndex = 0; // Extract list items using regex const listItems = []; let match; let lastIndex = 0; let preListText = ''; // First, capture any text before the first list item const firstListItem = aiResponse.search(/(\d+\.\s+|[•\*]\s+)/); if (firstListItem > 0) { preListText = aiResponse.substring(0, firstListItem); } // Now extract each list item while ((match = listRegex.exec(aiResponse)) !== null) { listItems.push(match[0]); lastIndex = match.index + match[0].length; } // Get any remaining text after the last list item let postListText = ''; if (lastIndex < aiResponse.length) { postListText = aiResponse.substring(lastIndex); } // Now create chunks from these elements // Start with pre-list text let chunks = []; if (preListText.trim()) { chunks.push(firstMessage + preListText.trim()); } else if (firstMessage) { // If no pre-list text, first list item needs the mention if (listItems.length > 0) { listItems[0] = firstMessage + listItems[0]; } } // Add each list item as a separate chunk // This ensures each bullet or numbered item appears as a separate chat message listItems.forEach(item => { if (item.length > maxChatLength) { // If a list item is very long, split it further let remaining = item; while (remaining.length > 0) { const splitPoint = Math.min(remaining.length, maxChatLength); const chunk = remaining.substring(0, splitPoint); chunks.push(chunk); remaining = remaining.substring(splitPoint); } } else { chunks.push(item); } }); // Add post-list text if any if (postListText.trim()) { chunks.push(postListText.trim()); } } else { // No lists found, just split by sentence or length // First try to split by sentences to keep coherence const sentences = aiResponse.split(/([.!?]+\s+)/).filter(part => part.trim().length > 0); let currentChunk = firstMessage; let isFirstChunk = true; // Try to keep sentences together when possible for (let i = 0; i < sentences.length; i++) { const sentence = sentences[i]; // Check if adding this sentence would make chunk too long if (currentChunk.length + sentence.length > maxChatLength) { // If sentence itself is too long, force-split it if (sentence.length > maxChatLength) { // Save current chunk first if it has content if (currentChunk.length > (isFirstChunk ? firstMessage.length : 0)) { chunks.push(currentChunk); isFirstChunk = false; } // Split long sentence into chunks of maxChatLength let remaining = sentence; while (remaining.length > 0) { // Try to split on a space when possible let splitPoint = maxChatLength; if (remaining.length > maxChatLength) { // Find last space within the limit const lastSpace = remaining.substring(0, maxChatLength).lastIndexOf(' '); if (lastSpace > 0) { splitPoint = lastSpace + 1; // +1 to include the space } } const chunk = remaining.substring(0, splitPoint); chunks.push(isFirstChunk ? (firstMessage + chunk) : chunk); isFirstChunk = false; remaining = remaining.substring(splitPoint); } // Reset current chunk currentChunk = ''; } else { // Add current chunk and start new one with this sentence chunks.push(currentChunk); currentChunk = sentence; isFirstChunk = false; } } else { // Add sentence to current chunk if (currentChunk.length > (isFirstChunk ? firstMessage.length : 0)) { currentChunk += sentence; } else { currentChunk += (isFirstChunk ? '' : '') + sentence; } } } // Add the last chunk if it has content if (currentChunk.length > 0) { chunks.push(currentChunk); } } // Send chunks with a delay between them let sentChunks = 0; function sendNextChunk() { if (sentChunks < chunks.length) { bot.sendChat(chunks[sentChunks]); sentChunks++; if (sentChunks < chunks.length) { // Schedule next chunk setTimeout(sendNextChunk, 800); } else { // All chunks sent, store in memory if (memoryHandler) { memoryHandler.addMessage(data.user.username, aiResponse, true); } } } } // Start sending chunks sendNextChunk(); } else { // Send as a single message bot.sendChat(firstMessage + aiResponse); // Store bot's response in memory if (memoryHandler) { memoryHandler.addMessage(data.user.username, aiResponse, true); } } }) .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`); // Greet users when they join if (config.greetUsers) { bot.sendChat(`Welcome to the room, @${data.user.username}!`); } }); // 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.updub(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(); });