File size: 24,780 Bytes
e7c953d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
// 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();
});