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