File size: 16,877 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
// 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();
});