· 7 months ago · Feb 23, 2025, 07:20 AM
1// http://2354213892/nitestryker <-------
2
3const irc = require('irc');
4const sqlite3 = require('sqlite3').verbose();
5const Sentiment = require('sentiment');
6const natural = require('natural');
7const axios = require('axios');
8const tokenizer = new natural.WordTokenizer();
9const classifier = new natural.BayesClassifier();
10const urlRegex = /(https?:\/\/[^\s]+)/g;
11
12async function fetchDefinition(term) {
13 try {
14 // Common name corrections
15 const nameCorrections = {
16 'george washington carter': 'George Washington Carver',
17 // Add more common misspellings here
18 };
19
20 // Check if we have a correction for this term
21 const correctedTerm = nameCorrections[term.toLowerCase()] || term;
22
23 // Log the term being searched
24 console.log(`Searching Wikipedia for term: "${correctedTerm}"`);
25
26 // Check if term contains programming-related keywords
27 const isProgramming = /^(javascript|python|java|html|css|api|function|class|database|framework)$/i.test(correctedTerm);
28
29 if (isProgramming) {
30 // Try MDN for technical terms
31 const mdnResponse = await axios.get(`https://developer.mozilla.org/api/rest_v1/search?q=${encodeURIComponent(term)}&limit=1`);
32 if (mdnResponse.data.documents && mdnResponse.data.documents.length > 0) {
33 return mdnResponse.data.documents[0].summary;
34 }
35 } else {
36 // Use Wikipedia for general knowledge
37 const searchTerm = term.replace(/^(who|what|where|when|why|how) (is|are|was|were) /i, '').trim();
38 try {
39 const wikiResponse = await axios.get(`https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(searchTerm)}`);
40
41 if (wikiResponse.data.extract && wikiResponse.data.extract.length > 10) {
42 // Process the response to be conversational and complete
43 let summary = wikiResponse.data.extract;
44
45 // Calculate a more reasonable typing delay (2-5 seconds)
46 const typingTimeMs = Math.random() * 3000 + 2000;
47 await new Promise(resolve => setTimeout(resolve, typingTimeMs));
48 if (summary.length > 250) {
49 summary = summary
50 .split(/[.!?]+/)
51 .filter(s => s.trim().length > 0)
52 .slice(0, 2)
53 .map(s => s.trim())
54 .join('. ');
55 }
56
57 if (!summary.endsWith('.')) {
58 summary += '.';
59 }
60
61 // Verify the response is meaningful
62 if (summary.includes('may refer to') || summary.length < 20) {
63 return null;
64 }
65
66 return summary.charAt(0).toUpperCase() + summary.slice(1);
67 }
68 return null;
69 } catch (error) {
70 console.error('Wikipedia API error:', error);
71 return null;
72 }
73 }
74 } catch (error) {
75 console.error('Definition lookup error:', error);
76 return null;
77 }
78}
79
80async function fetchUrlTitle(url) {
81 try {
82 const response = await axios.get(url);
83 const cheerio = require('cheerio');
84 const $ = cheerio.load(response.data);
85 return $('title').text().trim();
86 } catch (error) {
87 console.error('Error fetching URL title:', error);
88 return null;
89 }
90}
91
92async function searchStackOverflow(query) {
93 try {
94 const response = await axios.get('https://api.stackexchange.com/2.3/search/advanced', {
95 params: {
96 order: 'desc',
97 sort: 'votes',
98 q: query,
99 site: 'stackoverflow',
100 pagesize: 1,
101 answers: 1,
102 accepted: true,
103 filter: '!9Z(-wwYGT'
104 }
105 });
106
107 if (response.data.items && response.data.items.length > 0) {
108 const post = response.data.items[0];
109 const words = query.split(' ').length;
110 const typingTimeMs = (words / 30) * 60 * 1000; // 30 WPM typing speed
111 await new Promise(resolve => setTimeout(resolve, Math.min(Math.max(typingTimeMs, 2000), 10000)));
112
113 return {
114 link: post.link,
115 title: post.title,
116 answer_count: post.answer_count,
117 score: post.score
118 };
119 }
120 return null;
121 } catch (error) {
122 console.error('Stack Overflow API error:', error);
123 return null;
124 }
125}
126
127// Helper function for keyword identification
128function identifyKeywords(message) {
129 return message.toLowerCase()
130 .split(/\s+/)
131 .filter(word => word.length > 3)
132 .slice(0, 5);
133}
134
135// Train the classifier with more specific patterns
136classifier.addDocument('what is', 'question');
137classifier.addDocument('how do i', 'question');
138classifier.addDocument('can you help', 'question');
139classifier.addDocument('could you explain', 'question');
140classifier.addDocument('who knows', 'question');
141classifier.addDocument('why does', 'question');
142classifier.addDocument('where can i', 'question');
143classifier.addDocument('is there', 'question');
144classifier.addDocument('does anyone', 'question');
145classifier.addDocument('how can', 'question');
146classifier.addDocument('what are', 'question');
147classifier.addDocument('how do you', 'question');
148
149classifier.addDocument('lol', 'joke');
150classifier.addDocument('haha', 'joke');
151classifier.addDocument('rofl', 'joke');
152classifier.addDocument('😂', 'joke');
153classifier.addDocument('🤣', 'joke');
154
155classifier.addDocument('hello', 'greeting');
156classifier.addDocument('hi', 'greeting');
157classifier.addDocument('hey', 'greeting');
158classifier.addDocument('morning', 'greeting');
159
160classifier.addDocument('bye', 'farewell');
161classifier.addDocument('goodbye', 'farewell');
162classifier.addDocument('cya', 'farewell');
163classifier.addDocument('later', 'farewell');
164
165classifier.addDocument('i just', 'update');
166classifier.addDocument('i have', 'update');
167classifier.addDocument('check this', 'update');
168classifier.addDocument('look at', 'update');
169
170classifier.train();
171
172// Initialize SQLite database
173const db = new sqlite3.Database('irc_logs.db');
174const sentiment = new Sentiment();
175
176// Initialize maps and sets
177const learningData = new Map();
178const relatedQuestions = new Map();
179const activeUsers = new Set();
180const conversationMemory = new Map(); // Store recent messages for each user
181const MEMORY_WINDOW = 5; // Number of messages to remember per user
182const userCooldowns = new Map(); // Track user cooldowns
183const COOLDOWN_TIME = 30000; // 30 seconds cooldown
184
185function isUserInCooldown(username) {
186 const lastInteraction = userCooldowns.get(username);
187 if (!lastInteraction) return false;
188 return (Date.now() - lastInteraction) < COOLDOWN_TIME;
189}
190
191function setUserCooldown(username) {
192 userCooldowns.set(username, Date.now());
193}
194
195const Fuse = require('fuse.js');
196
197function handleLearning(message, from, to) {
198 if (message.startsWith('!q ')) {
199 const query = message.slice(3);
200 db.all('SELECT id, question, answer, related_to FROM learning', [], (err, rows) => {
201 if (err) return console.error(err);
202
203 const fuse = new Fuse(rows, {
204 keys: ['question', 'answer'],
205 threshold: 0.4,
206 includeScore: true
207 });
208
209 const results = fuse.search(query);
210 if (results.length > 0) {
211 const bestMatch = results[0].item;
212 client.say(to, `Found: ${bestMatch.question} -> ${bestMatch.answer}`);
213
214 // Find related questions using fuzzy search
215 const relatedResults = fuse.search(bestMatch.question)
216 .filter(r => r.item.id !== bestMatch.id)
217 .slice(0, 3);
218
219 if (relatedResults.length > 0) {
220 client.say(to, `Related questions:`);
221 relatedResults.forEach(r => {
222 client.say(to, `- ${r.item.question} -> ${r.item.answer}`);
223 });
224 }
225 } else {
226 client.say(to, `No matches found for "${query}"`);
227 }
228 });
229 } else if (message.startsWith('!a ')) {
230 const [, question, answer] = message.match(/!a (.+?) -> (.+)/) || [];
231 if (question && answer) {
232 learningData.set(question, answer);
233 db.get('SELECT id FROM learning ORDER BY created_at DESC LIMIT 1', [], (err, row) => {
234 if (err) return console.error(err);
235 const relatedTo = row ? row.id : null;
236 db.run('INSERT INTO learning (question, answer, related_to, user) VALUES (?, ?, ?, ?)',
237 [question, answer, relatedTo, from],
238 function(err) {
239 if (err) return console.error(err);
240 client.say(to, 'Answer stored! ' + (relatedTo ? '(Linked to previous question)' : ''));
241 }
242 );
243 });
244 }
245 } else if (message === '!resetlearning' && from.toLowerCase().includes('admin')) {
246 learningData.clear();
247 relatedQuestions.clear();
248 db.run('DELETE FROM learning');
249 client.say(to, 'Learning data reset!');
250 }
251}
252
253// Create necessary tables
254db.serialize(() => {
255 db.run(`CREATE TABLE IF NOT EXISTS messages (
256 id INTEGER PRIMARY KEY AUTOINCREMENT,
257 timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
258 user TEXT,
259 channel TEXT,
260 message TEXT,
261 sentiment_score INTEGER
262 )`);
263
264 db.run(`CREATE TABLE IF NOT EXISTS users (
265 username TEXT PRIMARY KEY,
266 channel TEXT,
267 last_seen DATETIME DEFAULT CURRENT_TIMESTAMP
268 )`);
269
270 db.run(`CREATE TABLE IF NOT EXISTS learning (
271 id INTEGER PRIMARY KEY AUTOINCREMENT,
272 question TEXT,
273 answer TEXT,
274 related_to INTEGER,
275 user TEXT,
276 created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
277 FOREIGN KEY(related_to) REFERENCES learning(id)
278 )`);
279
280 db.run(`CREATE TABLE IF NOT EXISTS user_knowledge (
281 id INTEGER PRIMARY KEY AUTOINCREMENT,
282 username TEXT,
283 question TEXT,
284 answer TEXT,
285 last_asked DATETIME DEFAULT CURRENT_TIMESTAMP,
286 times_asked INTEGER DEFAULT 1,
287 UNIQUE(username, question)
288 )`);
289});
290
291// Create IRC client
292const client = new irc.Client('irc.afternet.org', 'nodebot', {
293 channels: ['#Programming'],
294 port: 6667,
295 debug: true,
296 secure: false,
297 autoRejoin: true,
298 autoConnect: true,
299 retryDelay: 10000,
300 retryCount: 3
301});
302
303// Event Listeners
304client.addListener('connect', () => {
305 console.log('Connected to IRC server');
306});
307
308client.addListener('message', async (from, to, message) => {
309 // Update conversation memory
310 if (!conversationMemory.has(from)) {
311 conversationMemory.set(from, []);
312 }
313 const userMemory = conversationMemory.get(from);
314 userMemory.push({ message, timestamp: Date.now() });
315 if (userMemory.length > MEMORY_WINDOW) {
316 userMemory.shift();
317 }
318
319 const sentimentResult = sentiment.analyze(message);
320 const keywords = identifyKeywords(message);
321 const messageType = classifier.classify(message.toLowerCase());
322 const tokens = tokenizer.tokenize(message);
323
324 // Get conversation context
325 const recentContext = userMemory
326 .map(m => m.message)
327 .join(' ');
328
329 db.run(
330 'INSERT INTO messages (user, channel, message, sentiment_score) VALUES (?, ?, ?, ?)',
331 [from, to, message, sentimentResult.score]
332 );
333
334 db.run(
335 'INSERT OR REPLACE INTO users (username, channel, last_seen) VALUES (?, ?, CURRENT_TIMESTAMP)',
336 [from, to]
337 );
338
339
340
341 handleLearning(message, from, to);
342
343 // Handle URLs in messages
344 const urls = message.match(urlRegex);
345 if (urls) {
346 for (const url of urls) {
347 const title = await fetchUrlTitle(url);
348 if (title) {
349 client.say(to, `📎 Title: ${title}`);
350 }
351 }
352 }
353
354 // Detect question type and category
355 // Programming keywords must appear with technical context
356 const programmingKeywords = /\b(how|why|what|when)\b.{0,30}\b(javascript|python|java|function|api|sql|react|node|express|html|css|php)\b|\b(code|error|debug|compile|runtime|syntax)\b.{0,30}\b(javascript|python|java|function|loop|array|class)\b|\b(programming|coding|development)\b.{0,30}\b(problem|issue|error|bug)\b/i;
357
358 const generalKnowledgeKeywords = /(who|what|where|when) (is|are|was|were) (the|a|an)|history|president|country|capital|population|invented|discovered|born|died|leader|founded|king|jr|dr|martin|luther|historical|figure|civil|rights/i;
359
360 // A question is programming-related only if it contains programming keywords in proper context
361 const isProgrammingQuestion = programmingKeywords.test(message.toLowerCase()) &&
362 message.split(' ').length > 3 && // Ensure it's a substantial question
363 !generalKnowledgeKeywords.test(message); // Ensure it's not a general knowledge question
364
365 // Debug logging for question detection
366 console.log('\n=== Question Analysis ===');
367 console.log(`Message from ${from}: "${message}"`);
368 console.log(`Contains programming keywords: ${programmingKeywords.test(message.toLowerCase())}`);
369 console.log(`Contains general knowledge keywords: ${generalKnowledgeKeywords.test(message.toLowerCase())}`);
370 console.log(`Is programming question: ${isProgrammingQuestion}`);
371
372 // First check if message ends with question mark
373 const hasQuestionMark = message.trim().endsWith('?');
374
375 // Enhanced question detection patterns for additional context
376 const questionPatterns = [
377 /\b(who|what|where|when|why|which|whose|how)\b/i,
378 /\bcan\s+(you|i|we|they)\b/i,
379 /\bcould\s+(you|i|we|they)\b/i,
380 /\bwould\s+(you|i|we|they)\b/i,
381 /\bshould\s+(you|i|we|they)\b/i,
382 /^(is|are|do|does|did|was|were|will|would|should|has|have|had)\b/i
383 ];
384
385 const hasQuestionPattern = questionPatterns.some(pattern => pattern.test(message));
386 const isQuestion = hasQuestionMark || (hasQuestionPattern && message.length > 10);
387
388 // Handle programming questions
389 console.log(`Is question pattern match: ${isQuestion}`);
390
391 if (isProgrammingQuestion && isQuestion) {
392 console.log('\n=== Processing Programming Question ===');
393 if (isUserInCooldown(from)) {
394 console.log(`${from} is in cooldown period`);
395 return;
396 }
397 console.log('User not in cooldown, proceeding with Stack Overflow search');
398
399 try {
400 const searchQuery = message
401 .replace(/^(what|how|why|can|could|where|when|which|whose|is|are|do|does|did|was|were|will|would|should|has|have|had)\s+/i, '')
402 .replace(/[?.,!]/g, '')
403 .trim();
404
405 setUserCooldown(from);
406
407 console.log(`Searching Stack Overflow for programming question: ${searchQuery}`);
408 const result = await searchStackOverflow(searchQuery);
409 if (result && result.score > 5) {
410 const responses = [
411 `${from}, check this out: ${result.link}`,
412 `${from}, here you go: ${result.link}`,
413 `${from}, this might help: ${result.link}`,
414 `${from}, found something similar: ${result.link}`
415 ];
416 const response = responses[Math.floor(Math.random() * responses.length)];
417 client.say(to, response);
418 }
419 return;
420 } catch (error) {
421 console.error('Error searching Stack Overflow:', error);
422 return;
423 }
424 }
425
426 // Check user knowledge history
427 async function checkUserKnowledge(username, question) {
428 return new Promise((resolve, reject) => {
429 db.get(
430 'SELECT * FROM user_knowledge WHERE username = ? AND question LIKE ? ORDER BY last_asked DESC LIMIT 1',
431 [username, `%${question}%`],
432 (err, row) => {
433 if (err) reject(err);
434 else resolve(row);
435 }
436 );
437 });
438 }
439
440 async function updateUserKnowledge(username, question, answer) {
441 db.run(
442 `INSERT INTO user_knowledge (username, question, answer)
443 VALUES (?, ?, ?)
444 ON CONFLICT(username, question)
445 DO UPDATE SET times_asked = times_asked + 1, last_asked = CURRENT_TIMESTAMP`,
446 [username, question, answer]
447 );
448 }
449
450 // Handle general knowledge questions and "I don't know" statements
451 const definitionMatch = message.match(/\b(what|who) (is|are) ([^?]+)\??/i) ||
452 message.match(/\bwhere (is|are) ([^?]+)\??/i) ||
453 message.match(/\bwhen (is|was|will) ([^?]+)\??/i) ||
454 message.match(/\bwhy (is|are|does) ([^?]+)\??/i) ||
455 message.match(/\bwhich ([^?]+)\??/i) ||
456 message.match(/\bhow (does|do|can|could) ([^?]+)\??/i) ||
457 message.match(/I don['']?t know (what|who|where|when|why|which|how) ([^?.]+) (is|are|was|were|will|would)/i);
458 if (definitionMatch && !isUserInCooldown(from)) {
459 console.log('\n=== Processing General Knowledge Question ===');
460 console.log(`Definition match found: ${JSON.stringify(definitionMatch)}`);
461 setUserCooldown(from);
462 const term = definitionMatch[3]?.trim() || definitionMatch[2]?.trim();
463 console.log(`Searching term: "${term}"`);
464
465 // Check if user has asked this before
466 const previousKnowledge = await checkUserKnowledge(from, term);
467 if (previousKnowledge) {
468 const timeAgo = Math.floor((Date.now() - new Date(previousKnowledge.last_asked)) / (1000 * 60 * 60 * 24));
469 const responses = [
470 `${from}, you asked about this ${timeAgo} days ago! Need a refresher?`,
471 `${from}, we discussed this before! Want me to explain again?`,
472 `${from}, I remember you asking about this! Here's a reminder: ${previousKnowledge.answer}`
473 ];
474 client.say(to, responses[Math.floor(Math.random() * responses.length)]);
475 return;
476 }
477
478 console.log(`Fetching definition for term: "${term}"`);
479 const definition = await fetchDefinition(term);
480 if (definition && definition !== null) {
481 await updateUserKnowledge(from, term, definition);
482 client.say(to, `${from}, ${definition}`);
483 } else {
484 console.log(`No definition found for term: "${term}"`);
485 }
486 return;
487 }
488
489 // Prevent responding to our own messages or messages that look like they contain our previous messages
490 if (from === 'BGood' || message.includes('<BGood>')) {
491 return;
492 }
493
494 // Add realistic typing delay and only respond sometimes
495 if (Math.random() < 0.7) { // 70% chance to respond
496 // Calculate typing time based on message length (30 WPM)
497 const words = message.split(' ').length;
498 const typingTimeMs = (words / 30) * 60 * 1000; // Convert WPM to milliseconds
499
500 // Add typing delay (minimum 2 seconds, maximum 10 seconds)
501 await new Promise(resolve => setTimeout(resolve, Math.min(Math.max(typingTimeMs, 2000), 10000)));
502
503 const responses = {
504 greeting: [
505 `Hi ${from}! 👋`,
506 `Hello ${from}!`,
507 `Hey there ${from}!`
508 ],
509 farewell: [
510 `Goodbye ${from}!`,
511 `See you later ${from}!`,
512 `Take care ${from}!`
513 ],
514 joke: [
515 `😄 Good one ${from}!`,
516 `That's funny ${from}!`,
517 `You're hilarious ${from}! 😆`
518 ]
519 };
520
521 const getRandomResponse = (type) => {
522 const options = responses[type];
523 return options ? options[Math.floor(Math.random() * options.length)] : null;
524 };
525
526 switch(messageType) {
527 case 'greeting':
528 client.say(to, getRandomResponse('greeting'));
529 break;
530 case 'farewell':
531 client.say(to, getRandomResponse('farewell'));
532 break;
533 case 'question':
534 // Questions are now handled earlier in the code
535 break;
536 case 'joke':
537 if (sentimentResult.score >= 0) {
538 client.say(to, jokeResponses[Math.floor(Math.random() * jokeResponses.length)]);
539 }
540 break;
541 case 'update':
542 if (tokens.length > 5) {
543 client.say(to, `Thanks for sharing that update, ${from}!`);
544 }
545 break;
546 default:
547 if (sentimentResult.score < -3) {
548 client.say(to, `Hey ${from}, let's keep things positive! 😊`);
549 } else if (sentimentResult.score > 3) {
550 client.say(to, `Great energy, ${from}! 🎉`);
551 }
552 }
553 }
554});
555
556client.addListener('join', (channel, nick) => {
557 console.log(`${nick} joined ${channel}`);
558 activeUsers.add(nick);
559 db.run('INSERT OR REPLACE INTO users (username, channel) VALUES (?, ?)', [nick, channel]);
560});
561
562client.addListener('part', (channel, nick) => {
563 console.log(`${nick} left ${channel}`);
564 activeUsers.delete(nick);
565 db.run('UPDATE users SET last_seen = CURRENT_TIMESTAMP WHERE username = ?', [nick]);
566});
567
568client.addListener('quit', (nick, reason, channels) => {
569 console.log(`${nick} quit (${reason})`);
570 activeUsers.delete(nick);
571 db.run('UPDATE users SET last_seen = CURRENT_TIMESTAMP WHERE username = ?', [nick]);
572});
573
574client.addListener('error', (message) => {
575 console.error('IRC Error:', message);
576});
577
578process.on('SIGINT', () => {
579 db.close();
580 process.exit();
581});