JavaScript · 11521 bytes Raw Blame History
1 // Conversation State Manager for LooseCannon
2 // Handles sophisticated conversation tracking and context management
3
4 class ConversationManager {
5 constructor() {
6 this.conversations = new Map();
7 this.scammerDatabase = new Map();
8 this.responseHistory = new Map();
9 }
10
11 // Initialize or get conversation
12 getConversation(chatId, platform = 'whatsapp') {
13 const key = `${platform}:${chatId}`;
14
15 if (!this.conversations.has(key)) {
16 this.conversations.set(key, {
17 id: key,
18 platform,
19 chatId,
20 startTime: new Date(),
21 messages: [],
22 context: {
23 scammerScore: 0,
24 personality: 'default',
25 responseCount: 0,
26 lastResponse: null,
27 detectedPatterns: [],
28 extractedInfo: {
29 phoneNumbers: [],
30 emails: [],
31 links: [],
32 bankMentions: 0,
33 moneyRequests: 0
34 }
35 },
36 state: 'active',
37 metadata: {}
38 });
39 }
40
41 return this.conversations.get(key);
42 }
43
44 // Add message to conversation
45 addMessage(chatId, message, platform = 'whatsapp') {
46 const conversation = this.getConversation(chatId, platform);
47
48 // Enrich message with analysis
49 const enrichedMessage = {
50 ...message,
51 timestamp: new Date(),
52 analysis: this.analyzeMessage(message),
53 platform
54 };
55
56 conversation.messages.push(enrichedMessage);
57
58 // Update context based on message
59 this.updateContext(conversation, enrichedMessage);
60
61 // Keep conversation size manageable (last 50 messages)
62 if (conversation.messages.length > 50) {
63 conversation.messages = conversation.messages.slice(-50);
64 }
65
66 return conversation;
67 }
68
69 // Analyze message for patterns and important information
70 analyzeMessage(message) {
71 const analysis = {
72 sentiment: 'neutral',
73 urgency: 0,
74 suspicion: 0,
75 topics: [],
76 entities: [],
77 intent: 'unknown'
78 };
79
80 const content = message.content.toLowerCase();
81
82 // Urgency detection
83 const urgencyWords = ['urgent', 'immediately', 'now', 'quick', 'fast', 'hurry', 'asap', 'expire'];
84 urgencyWords.forEach(word => {
85 if (content.includes(word)) analysis.urgency += 0.2;
86 });
87
88 // Suspicion detection
89 const suspiciousWords = [
90 'prize', 'winner', 'claim', 'verify', 'suspended', 'confirm',
91 'bitcoin', 'gift card', 'wire', 'transfer', 'payment'
92 ];
93 suspiciousWords.forEach(word => {
94 if (content.includes(word)) analysis.suspicion += 0.15;
95 });
96
97 // Topic extraction
98 if (content.includes('tech') || content.includes('computer')) {
99 analysis.topics.push('technology');
100 }
101 if (content.includes('money') || content.includes('payment') || content.includes('$')) {
102 analysis.topics.push('financial');
103 }
104 if (content.includes('account') || content.includes('password')) {
105 analysis.topics.push('account_security');
106 }
107
108 // Intent detection
109 if (content.includes('?')) {
110 analysis.intent = 'question';
111 } else if (content.includes('please') || content.includes('need')) {
112 analysis.intent = 'request';
113 } else if (content.includes('!')) {
114 analysis.intent = 'emphasis';
115 }
116
117 // Extract entities
118 if (message.metadata) {
119 if (message.metadata.phoneNumbers) {
120 analysis.entities.push(...message.metadata.phoneNumbers.map(p => ({ type: 'phone', value: p })));
121 }
122 if (message.metadata.emails) {
123 analysis.entities.push(...message.metadata.emails.map(e => ({ type: 'email', value: e })));
124 }
125 }
126
127 return analysis;
128 }
129
130 // Update conversation context based on new message
131 updateContext(conversation, message) {
132 const context = conversation.context;
133 const analysis = message.analysis;
134
135 // Update scammer score
136 context.scammerScore = Math.min(
137 1.0,
138 context.scammerScore + (analysis.suspicion * 0.3) + (analysis.urgency * 0.2)
139 );
140
141 // Track detected patterns
142 if (analysis.suspicion > 0.5) {
143 context.detectedPatterns.push({
144 type: 'suspicious',
145 timestamp: message.timestamp,
146 confidence: analysis.suspicion
147 });
148 }
149
150 // Update extracted information
151 if (analysis.entities.length > 0) {
152 analysis.entities.forEach(entity => {
153 if (entity.type === 'phone' && !context.extractedInfo.phoneNumbers.includes(entity.value)) {
154 context.extractedInfo.phoneNumbers.push(entity.value);
155 }
156 if (entity.type === 'email' && !context.extractedInfo.emails.includes(entity.value)) {
157 context.extractedInfo.emails.push(entity.value);
158 }
159 });
160 }
161
162 // Count financial mentions
163 if (analysis.topics.includes('financial')) {
164 context.extractedInfo.moneyRequests++;
165 }
166
167 // Update scammer database if high confidence
168 if (context.scammerScore > 0.8) {
169 this.addToScammerDatabase(conversation);
170 }
171 }
172
173 // Generate context summary for LLM
174 generateContextSummary(chatId, platform = 'whatsapp') {
175 const conversation = this.getConversation(chatId, platform);
176 const context = conversation.context;
177 const recentMessages = conversation.messages.slice(-10);
178
179 return {
180 conversationLength: conversation.messages.length,
181 scammerScore: context.scammerScore,
182 detectedPatterns: context.detectedPatterns.slice(-5),
183 recentTopics: this.extractRecentTopics(recentMessages),
184 extractedInfo: {
185 hasPhoneNumbers: context.extractedInfo.phoneNumbers.length > 0,
186 hasEmails: context.extractedInfo.emails.length > 0,
187 hasLinks: context.extractedInfo.links.length > 0,
188 financialMentions: context.extractedInfo.moneyRequests
189 },
190 conversationTone: this.determineConversationTone(recentMessages),
191 suggestedStrategy: this.suggestStrategy(context)
192 };
193 }
194
195 // Extract recent topics from messages
196 extractRecentTopics(messages) {
197 const topicCounts = {};
198
199 messages.forEach(msg => {
200 if (msg.analysis && msg.analysis.topics) {
201 msg.analysis.topics.forEach(topic => {
202 topicCounts[topic] = (topicCounts[topic] || 0) + 1;
203 });
204 }
205 });
206
207 return Object.entries(topicCounts)
208 .sort((a, b) => b[1] - a[1])
209 .slice(0, 3)
210 .map(([topic]) => topic);
211 }
212
213 // Determine overall conversation tone
214 determineConversationTone(messages) {
215 let urgencySum = 0;
216 let suspicionSum = 0;
217 let count = 0;
218
219 messages.forEach(msg => {
220 if (msg.analysis) {
221 urgencySum += msg.analysis.urgency || 0;
222 suspicionSum += msg.analysis.suspicion || 0;
223 count++;
224 }
225 });
226
227 if (count === 0) return 'neutral';
228
229 const avgUrgency = urgencySum / count;
230 const avgSuspicion = suspicionSum / count;
231
232 if (avgUrgency > 0.6) return 'urgent';
233 if (avgSuspicion > 0.6) return 'suspicious';
234 if (avgUrgency > 0.3 && avgSuspicion > 0.3) return 'aggressive';
235
236 return 'casual';
237 }
238
239 // Suggest response strategy based on context
240 suggestStrategy(context) {
241 if (context.scammerScore > 0.8) {
242 return 'maximum_confusion';
243 } else if (context.scammerScore > 0.6) {
244 return 'waste_time';
245 } else if (context.extractedInfo.moneyRequests > 0) {
246 return 'play_poor';
247 } else if (context.detectedPatterns.some(p => p.type === 'suspicious')) {
248 return 'ask_questions';
249 }
250
251 return 'be_confused';
252 }
253
254 // Add conversation to scammer database
255 addToScammerDatabase(conversation) {
256 const identifier = conversation.chatId;
257
258 if (!this.scammerDatabase.has(identifier)) {
259 this.scammerDatabase.set(identifier, {
260 firstSeen: new Date(),
261 lastSeen: new Date(),
262 platforms: new Set([conversation.platform]),
263 encounters: 1,
264 patterns: [],
265 extractedInfo: { ...conversation.context.extractedInfo }
266 });
267 } else {
268 const entry = this.scammerDatabase.get(identifier);
269 entry.lastSeen = new Date();
270 entry.encounters++;
271 entry.platforms.add(conversation.platform);
272
273 // Merge extracted info
274 conversation.context.extractedInfo.phoneNumbers.forEach(phone => {
275 if (!entry.extractedInfo.phoneNumbers.includes(phone)) {
276 entry.extractedInfo.phoneNumbers.push(phone);
277 }
278 });
279 }
280 }
281
282 // Get response suggestions based on conversation state
283 getResponseSuggestions(chatId, platform = 'whatsapp') {
284 const conversation = this.getConversation(chatId, platform);
285 const context = conversation.context;
286 const lastMessages = conversation.messages.slice(-3);
287
288 const suggestions = [];
289
290 // If they're asking for personal information
291 if (lastMessages.some(m => m.analysis && m.analysis.topics.includes('account_security'))) {
292 suggestions.push({
293 strategy: 'deflect',
294 response: "Oh dear, I always forget these things. Let me ask my grandson...",
295 delay: 5000
296 });
297 }
298
299 // If they're being urgent
300 if (context.conversationTone === 'urgent') {
301 suggestions.push({
302 strategy: 'slow_down',
303 response: "Hold on, I need to find my glasses first. Everything is so blurry...",
304 delay: 8000
305 });
306 }
307
308 // If they mention money
309 if (context.extractedInfo.moneyRequests > 0) {
310 suggestions.push({
311 strategy: 'confusion',
312 response: "Money? Is this about the church fundraiser? I already donated last week.",
313 delay: 4000
314 });
315 }
316
317 return suggestions;
318 }
319
320 // Export conversation for analysis
321 exportConversation(chatId, platform = 'whatsapp') {
322 const conversation = this.getConversation(chatId, platform);
323
324 return {
325 id: conversation.id,
326 platform: conversation.platform,
327 startTime: conversation.startTime,
328 endTime: new Date(),
329 messageCount: conversation.messages.length,
330 scammerScore: conversation.context.scammerScore,
331 detectedPatterns: conversation.context.detectedPatterns,
332 extractedInfo: conversation.context.extractedInfo,
333 messages: conversation.messages.map(m => ({
334 timestamp: m.timestamp,
335 sender: m.sender || 'unknown',
336 content: m.content,
337 type: m.type,
338 analysis: m.analysis
339 }))
340 };
341 }
342
343 // Get statistics
344 getStatistics() {
345 const stats = {
346 totalConversations: this.conversations.size,
347 activeConversations: 0,
348 suspiciousConversations: 0,
349 confirmedScammers: this.scammerDatabase.size,
350 platformBreakdown: {},
351 totalMessages: 0
352 };
353
354 this.conversations.forEach(conv => {
355 if (conv.state === 'active') stats.activeConversations++;
356 if (conv.context.scammerScore > 0.6) stats.suspiciousConversations++;
357
358 stats.platformBreakdown[conv.platform] =
359 (stats.platformBreakdown[conv.platform] || 0) + 1;
360
361 stats.totalMessages += conv.messages.length;
362 });
363
364 return stats;
365 }
366
367 // Clean up old conversations (memory management)
368 cleanup(maxAge = 24 * 60 * 60 * 1000) { // 24 hours default
369 const now = new Date();
370 const toDelete = [];
371
372 this.conversations.forEach((conv, key) => {
373 const age = now - conv.startTime;
374 if (age > maxAge && conv.state !== 'active') {
375 toDelete.push(key);
376 }
377 });
378
379 toDelete.forEach(key => {
380 this.conversations.delete(key);
381 });
382
383 console.log(`[ConversationManager] Cleaned up ${toDelete.length} old conversations`);
384 }
385 }
386
387 module.exports = ConversationManager;