zeroed-some/loosecannon / 3bb0bca

Browse files

enhancements, extensions

Authored by espadonne
SHA
3bb0bcaba449768e2851ada5305bde6ad6815440
Parents
cb9ab42
Tree
1e81d5f

6 changed files

StatusFile+-
A extension/background/unified-handler.js 474 0
A extension/content-scripts/messenger.js 539 0
A extension/content-scripts/telegram.js 480 0
A extension/content-scripts/whatsapp-enhanced.js 641 0
A server/src/conversation-manager.js 387 0
A server/src/index-enhanced.js 397 0
extension/background/unified-handler.jsadded
@@ -0,0 +1,474 @@
1
+// Unified Message Handler for Multiple Platforms
2
+// Handles messages from WhatsApp, Telegram, and Facebook Messenger
3
+
4
+class UnifiedMessageHandler {
5
+  constructor() {
6
+    this.serverUrl = 'http://localhost:8765';
7
+    this.platforms = new Map();
8
+    this.activeConversations = new Map();
9
+    this.responseQueue = [];
10
+    this.isProcessing = false;
11
+    this.settings = {
12
+      autoActivateOnScammer: true,
13
+      scammerThreshold: 0.7,
14
+      maxResponseDelay: 15000,
15
+      minResponseDelay: 2000,
16
+      personalityRotation: false
17
+    };
18
+    this.init();
19
+  }
20
+
21
+  init() {
22
+    this.loadSettings();
23
+    this.setupMessageListeners();
24
+    this.startResponseProcessor();
25
+    this.registerPlatforms();
26
+  }
27
+
28
+  registerPlatforms() {
29
+    // Register platform-specific configurations
30
+    this.platforms.set('whatsapp', {
31
+      name: 'WhatsApp Web',
32
+      domain: 'web.whatsapp.com',
33
+      contentScript: 'whatsapp-enhanced.js',
34
+      selectors: {
35
+        input: '[data-testid="conversation-compose-box-input"]',
36
+        sendButton: '[data-testid="compose-btn-send"]',
37
+        messageContainer: '[data-testid^="msg-"]'
38
+      }
39
+    });
40
+
41
+    this.platforms.set('telegram', {
42
+      name: 'Telegram Web',
43
+      domain: 'web.telegram.org',
44
+      contentScript: 'telegram.js',
45
+      selectors: {
46
+        input: '.composer-input-field',
47
+        sendButton: '.btn-send',
48
+        messageContainer: '.message'
49
+      }
50
+    });
51
+
52
+    this.platforms.set('messenger', {
53
+      name: 'Facebook Messenger',
54
+      domain: 'messenger.com',
55
+      contentScript: 'messenger.js',
56
+      selectors: {
57
+        input: '[role="textbox"]',
58
+        sendButton: '[aria-label="Send"]',
59
+        messageContainer: '[role="row"]'
60
+      }
61
+    });
62
+  }
63
+
64
+  setupMessageListeners() {
65
+    browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
66
+      // Identify platform from sender URL
67
+      const platform = this.identifyPlatform(sender.url);
68
+
69
+      console.log(`[UnifiedHandler] Message from ${platform}:`, message.type);
70
+
71
+      switch (message.type) {
72
+        case 'NEW_MESSAGE':
73
+          this.handleNewMessage(message.data, platform, sender.tab.id)
74
+            .then(sendResponse)
75
+            .catch(error => {
76
+              console.error('Error handling message:', error);
77
+              sendResponse({ error: error.message });
78
+            });
79
+          return true; // Keep channel open for async response
80
+
81
+        case 'TOGGLE_ACTIVE':
82
+          this.handleToggleActive(message.data, platform, sender.tab.id);
83
+          sendResponse({ success: true });
84
+          break;
85
+
86
+        case 'GET_PLATFORM_STATUS':
87
+          sendResponse({
88
+            platform,
89
+            registered: this.platforms.has(platform),
90
+            active: this.isConversationActive(message.data.chatId, platform)
91
+          });
92
+          break;
93
+
94
+        case 'EXPORT_CONVERSATION':
95
+          this.exportConversation(message.data.chatId, platform)
96
+            .then(sendResponse)
97
+            .catch(error => sendResponse({ error: error.message }));
98
+          return true;
99
+
100
+        case 'GET_STATISTICS':
101
+          this.getStatistics()
102
+            .then(sendResponse)
103
+            .catch(error => sendResponse({ error: error.message }));
104
+          return true;
105
+
106
+        default:
107
+          // Pass through to original background handler
108
+          return false;
109
+      }
110
+    });
111
+  }
112
+
113
+  identifyPlatform(url) {
114
+    if (!url) return 'unknown';
115
+
116
+    for (const [platformId, config] of this.platforms) {
117
+      if (url.includes(config.domain)) {
118
+        return platformId;
119
+      }
120
+    }
121
+
122
+    return 'unknown';
123
+  }
124
+
125
+  async handleNewMessage(data, platform, tabId) {
126
+    try {
127
+      // Add to conversation manager on server
128
+      const contextResponse = await fetch(`${this.serverUrl}/conversation/add`, {
129
+        method: 'POST',
130
+        headers: { 'Content-Type': 'application/json' },
131
+        body: JSON.stringify({
132
+          chatId: data.chatId,
133
+          platform,
134
+          message: data
135
+        })
136
+      });
137
+
138
+      const context = await contextResponse.json();
139
+
140
+      // Check if we should auto-activate
141
+      if (this.settings.autoActivateOnScammer &&
142
+          context.scammerScore > this.settings.scammerThreshold &&
143
+          !this.isConversationActive(data.chatId, platform)) {
144
+
145
+        console.log(`[UnifiedHandler] Auto-activating for suspected scammer (score: ${context.scammerScore})`);
146
+        this.activateConversation(data.chatId, platform, tabId);
147
+
148
+        // Notify content script
149
+        browser.tabs.sendMessage(tabId, {
150
+          type: 'SCAMMER_DETECTED',
151
+          data: {
152
+            score: context.scammerScore,
153
+            autoActivated: true
154
+          }
155
+        });
156
+      }
157
+
158
+      // Only generate response if conversation is active
159
+      if (!this.isConversationActive(data.chatId, platform)) {
160
+        return { processed: false, reason: 'Conversation not active' };
161
+      }
162
+
163
+      // Get response suggestions from server
164
+      const suggestionsResponse = await fetch(`${this.serverUrl}/suggestions`, {
165
+        method: 'POST',
166
+        headers: { 'Content-Type': 'application/json' },
167
+        body: JSON.stringify({
168
+          chatId: data.chatId,
169
+          platform,
170
+          context
171
+        })
172
+      });
173
+
174
+      const suggestions = await suggestionsResponse.json();
175
+
176
+      // Select personality (rotate if enabled)
177
+      const personality = this.selectPersonality(data.chatId, context);
178
+
179
+      // Generate response
180
+      const generateResponse = await fetch(`${this.serverUrl}/generate`, {
181
+        method: 'POST',
182
+        headers: { 'Content-Type': 'application/json' },
183
+        body: JSON.stringify({
184
+          message: data.content || data.text,
185
+          personality,
186
+          chatId: data.chatId,
187
+          platform,
188
+          context,
189
+          suggestions,
190
+          timestamp: data.timestamp
191
+        })
192
+      });
193
+
194
+      const result = await generateResponse.json();
195
+
196
+      // Calculate human-like delay
197
+      const delay = this.calculateResponseDelay(result.reply, context);
198
+
199
+      // Add to response queue
200
+      this.queueResponse({
201
+        tabId,
202
+        platform,
203
+        chatId: data.chatId,
204
+        reply: result.reply,
205
+        delay,
206
+        personality
207
+      });
208
+
209
+      return {
210
+        queued: true,
211
+        reply: result.reply,
212
+        delay,
213
+        personality
214
+      };
215
+
216
+    } catch (error) {
217
+      console.error('[UnifiedHandler] Error:', error);
218
+
219
+      // Use fallback response
220
+      const fallback = this.getFallbackResponse(platform);
221
+      return {
222
+        reply: fallback,
223
+        delay: 3000,
224
+        error: error.message
225
+      };
226
+    }
227
+  }
228
+
229
+  selectPersonality(chatId, context) {
230
+    if (!this.settings.personalityRotation) {
231
+      return context.personality || 'default';
232
+    }
233
+
234
+    // Rotate personalities based on conversation state
235
+    const personalities = ['confused-elder', 'tech-support-nightmare', 'conspiracy-theorist'];
236
+    const messageCount = context.messageCount || 0;
237
+
238
+    // Change personality every 5 messages
239
+    const index = Math.floor(messageCount / 5) % personalities.length;
240
+    return personalities[index];
241
+  }
242
+
243
+  calculateResponseDelay(text, context) {
244
+    // Base delay on text length (typing speed)
245
+    const wordCount = text.split(' ').length;
246
+    const baseDelay = this.settings.minResponseDelay;
247
+    const perWordDelay = 150; // 150ms per word (average typing speed)
248
+
249
+    let delay = baseDelay + (wordCount * perWordDelay);
250
+
251
+    // Add variation based on context
252
+    if (context.conversationTone === 'urgent') {
253
+      // Take longer to respond to urgent messages (frustrate scammers)
254
+      delay *= 1.5;
255
+    }
256
+
257
+    // Add random variation (±30%)
258
+    const variation = 0.3;
259
+    const randomFactor = 1 + (Math.random() * 2 * variation - variation);
260
+    delay *= randomFactor;
261
+
262
+    // Cap at max delay
263
+    return Math.min(delay, this.settings.maxResponseDelay);
264
+  }
265
+
266
+  queueResponse(response) {
267
+    this.responseQueue.push({
268
+      ...response,
269
+      queuedAt: Date.now()
270
+    });
271
+
272
+    // Start processor if not already running
273
+    if (!this.isProcessing) {
274
+      this.processResponseQueue();
275
+    }
276
+  }
277
+
278
+  async processResponseQueue() {
279
+    if (this.responseQueue.length === 0) {
280
+      this.isProcessing = false;
281
+      return;
282
+    }
283
+
284
+    this.isProcessing = true;
285
+    const response = this.responseQueue.shift();
286
+
287
+    // Wait for the calculated delay
288
+    const elapsed = Date.now() - response.queuedAt;
289
+    const remainingDelay = Math.max(0, response.delay - elapsed);
290
+
291
+    await this.sleep(remainingDelay);
292
+
293
+    // Send the response to the content script
294
+    try {
295
+      await browser.tabs.sendMessage(response.tabId, {
296
+        type: 'SEND_MESSAGE',
297
+        data: {
298
+          text: response.reply,
299
+          delay: 0 // No additional delay, we've already waited
300
+        }
301
+      });
302
+
303
+      console.log(`[UnifiedHandler] Sent response to ${response.platform}:${response.chatId}`);
304
+    } catch (error) {
305
+      console.error('[UnifiedHandler] Failed to send response:', error);
306
+    }
307
+
308
+    // Process next in queue
309
+    this.processResponseQueue();
310
+  }
311
+
312
+  startResponseProcessor() {
313
+    // Periodic check for stuck responses
314
+    setInterval(() => {
315
+      if (!this.isProcessing && this.responseQueue.length > 0) {
316
+        console.log('[UnifiedHandler] Restarting response processor');
317
+        this.processResponseQueue();
318
+      }
319
+    }, 5000);
320
+  }
321
+
322
+  handleToggleActive(data, platform, tabId) {
323
+    const key = `${platform}:${data.chatId}`;
324
+
325
+    if (data.isActive) {
326
+      this.activateConversation(data.chatId, platform, tabId);
327
+    } else {
328
+      this.deactivateConversation(data.chatId, platform);
329
+    }
330
+  }
331
+
332
+  activateConversation(chatId, platform, tabId) {
333
+    const key = `${platform}:${chatId}`;
334
+    this.activeConversations.set(key, {
335
+      chatId,
336
+      platform,
337
+      tabId,
338
+      activatedAt: Date.now(),
339
+      messageCount: 0
340
+    });
341
+
342
+    console.log(`[UnifiedHandler] Activated ${key}`);
343
+  }
344
+
345
+  deactivateConversation(chatId, platform) {
346
+    const key = `${platform}:${chatId}`;
347
+    this.activeConversations.delete(key);
348
+
349
+    console.log(`[UnifiedHandler] Deactivated ${key}`);
350
+  }
351
+
352
+  isConversationActive(chatId, platform) {
353
+    const key = `${platform}:${chatId}`;
354
+    return this.activeConversations.has(key);
355
+  }
356
+
357
+  getFallbackResponse(platform) {
358
+    const fallbacks = {
359
+      whatsapp: [
360
+        "Sorry, what was that? My WhatsApp is acting strange.",
361
+        "Can you repeat? The message came through garbled.",
362
+        "Hold on, my phone is being slow..."
363
+      ],
364
+      telegram: [
365
+        "Telegram is glitching for me, one second...",
366
+        "Strange, I'm getting errors. What did you say?",
367
+        "My Telegram is updating, please wait..."
368
+      ],
369
+      messenger: [
370
+        "Facebook is being weird, can you resend?",
371
+        "Messenger crashed, what were you saying?",
372
+        "Sorry, Facebook is slow today..."
373
+      ],
374
+      default: [
375
+        "I didn't catch that, can you repeat?",
376
+        "Sorry, technical difficulties...",
377
+        "One moment please..."
378
+      ]
379
+    };
380
+
381
+    const platformFallbacks = fallbacks[platform] || fallbacks.default;
382
+    return platformFallbacks[Math.floor(Math.random() * platformFallbacks.length)];
383
+  }
384
+
385
+  async exportConversation(chatId, platform) {
386
+    try {
387
+      const response = await fetch(`${this.serverUrl}/conversation/export`, {
388
+        method: 'POST',
389
+        headers: { 'Content-Type': 'application/json' },
390
+        body: JSON.stringify({ chatId, platform })
391
+      });
392
+
393
+      const data = await response.json();
394
+
395
+      // Save to browser storage for user access
396
+      const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
397
+      const filename = `loosecannon-${platform}-${chatId}-${timestamp}.json`;
398
+
399
+      await browser.storage.local.set({
400
+        [`export_${filename}`]: data
401
+      });
402
+
403
+      return {
404
+        success: true,
405
+        filename,
406
+        data
407
+      };
408
+    } catch (error) {
409
+      console.error('[UnifiedHandler] Export error:', error);
410
+      throw error;
411
+    }
412
+  }
413
+
414
+  async getStatistics() {
415
+    try {
416
+      const response = await fetch(`${this.serverUrl}/statistics`);
417
+      const serverStats = await response.json();
418
+
419
+      // Add local statistics
420
+      const localStats = {
421
+        activeConversations: this.activeConversations.size,
422
+        queuedResponses: this.responseQueue.length,
423
+        platformBreakdown: {}
424
+      };
425
+
426
+      // Count active conversations by platform
427
+      this.activeConversations.forEach(conv => {
428
+        localStats.platformBreakdown[conv.platform] =
429
+          (localStats.platformBreakdown[conv.platform] || 0) + 1;
430
+      });
431
+
432
+      return {
433
+        server: serverStats,
434
+        local: localStats,
435
+        combined: {
436
+          totalActive: localStats.activeConversations,
437
+          totalProcessed: serverStats.totalMessages || 0,
438
+          platforms: Object.keys({ ...serverStats.platformBreakdown, ...localStats.platformBreakdown })
439
+        }
440
+      };
441
+    } catch (error) {
442
+      console.error('[UnifiedHandler] Statistics error:', error);
443
+      throw error;
444
+    }
445
+  }
446
+
447
+  async loadSettings() {
448
+    try {
449
+      const stored = await browser.storage.local.get('unifiedSettings');
450
+      if (stored.unifiedSettings) {
451
+        this.settings = { ...this.settings, ...stored.unifiedSettings };
452
+      }
453
+    } catch (error) {
454
+      console.error('[UnifiedHandler] Error loading settings:', error);
455
+    }
456
+  }
457
+
458
+  async saveSettings() {
459
+    try {
460
+      await browser.storage.local.set({ unifiedSettings: this.settings });
461
+    } catch (error) {
462
+      console.error('[UnifiedHandler] Error saving settings:', error);
463
+    }
464
+  }
465
+
466
+  sleep(ms) {
467
+    return new Promise(resolve => setTimeout(resolve, ms));
468
+  }
469
+}
470
+
471
+// Export for use in background.js
472
+if (typeof module !== 'undefined' && module.exports) {
473
+  module.exports = UnifiedMessageHandler;
474
+}
extension/content-scripts/messenger.jsadded
@@ -0,0 +1,539 @@
1
+// Facebook Messenger Content Script for LooseCannon
2
+console.log('[LooseCannon] Messenger content script loaded');
3
+
4
+class MessengerIntegration {
5
+  constructor() {
6
+    this.isActive = false;
7
+    this.currentChat = null;
8
+    this.messageObserver = null;
9
+    this.platform = 'messenger';
10
+    this.init();
11
+  }
12
+
13
+  init() {
14
+    this.waitForMessenger().then(() => {
15
+      console.log('[LooseCannon] Messenger detected and ready');
16
+      this.setupMessageObserver();
17
+      this.injectControls();
18
+      this.listenForCommands();
19
+    });
20
+  }
21
+
22
+  waitForMessenger() {
23
+    return new Promise((resolve) => {
24
+      const checkForApp = setInterval(() => {
25
+        // Check for Messenger's main chat area
26
+        const chatArea = document.querySelector('[role="main"]');
27
+        const inputField = document.querySelector('[role="textbox"][aria-label*="Message"], [contenteditable="true"][data-lexical-editor]');
28
+
29
+        if (chatArea && inputField) {
30
+          clearInterval(checkForApp);
31
+          console.log('[LooseCannon] Messenger interface detected');
32
+          resolve();
33
+        }
34
+      }, 1000);
35
+    });
36
+  }
37
+
38
+  setupMessageObserver() {
39
+    // Find the messages container
40
+    const messageContainer = document.querySelector('[role="main"], [aria-label*="Messages"]');
41
+
42
+    if (!messageContainer) {
43
+      console.error('[LooseCannon] Could not find Messenger message container');
44
+      setTimeout(() => this.setupMessageObserver(), 2000);
45
+      return;
46
+    }
47
+
48
+    const config = {
49
+      childList: true,
50
+      subtree: true,
51
+      characterData: true,
52
+      attributes: true
53
+    };
54
+
55
+    this.messageObserver = new MutationObserver((mutations) => {
56
+      if (!this.isActive) return;
57
+
58
+      mutations.forEach((mutation) => {
59
+        if (mutation.type === 'childList') {
60
+          mutation.addedNodes.forEach((node) => {
61
+            if (this.isIncomingMessage(node)) {
62
+              // Small delay to let DOM settle
63
+              setTimeout(() => {
64
+                this.handleIncomingMessage(node);
65
+              }, 100);
66
+            }
67
+          });
68
+        }
69
+      });
70
+    });
71
+
72
+    this.messageObserver.observe(messageContainer, config);
73
+    console.log('[LooseCannon] Messenger message observer setup complete');
74
+  }
75
+
76
+  isIncomingMessage(node) {
77
+    if (!node || !node.querySelector) return false;
78
+
79
+    // Look for message containers
80
+    const messageSelectors = [
81
+      '[role="row"]',
82
+      '[data-scope="messages_table"]',
83
+      'div[class*="__fb-light-mode"]'
84
+    ];
85
+
86
+    let messageElement = null;
87
+    for (const selector of messageSelectors) {
88
+      messageElement = node.querySelector(selector);
89
+      if (messageElement) break;
90
+    }
91
+
92
+    if (!messageElement && node.matches) {
93
+      for (const selector of messageSelectors) {
94
+        if (node.matches(selector)) {
95
+          messageElement = node;
96
+          break;
97
+        }
98
+      }
99
+    }
100
+
101
+    if (!messageElement) return false;
102
+
103
+    // Check if it's an incoming message by looking for specific patterns
104
+    // Messenger uses different styling for sent vs received messages
105
+    const messageText = messageElement.textContent || '';
106
+
107
+    // Skip if it's empty or a status message
108
+    if (!messageText || messageText.length < 2) return false;
109
+
110
+    // Check if message is on the left side (incoming)
111
+    const computedStyle = window.getComputedStyle(messageElement);
112
+    const isLeftAligned = computedStyle.textAlign === 'left' ||
113
+                         computedStyle.justifyContent === 'flex-start';
114
+
115
+    // Additional check: sent messages usually have different background
116
+    const backgroundColor = computedStyle.backgroundColor;
117
+    const isSentMessage = backgroundColor && (
118
+      backgroundColor.includes('rgb(0, 132, 255)') || // Blue for sent
119
+      backgroundColor.includes('rgb(24, 119, 242)')    // Facebook blue
120
+    );
121
+
122
+    return isLeftAligned && !isSentMessage;
123
+  }
124
+
125
+  handleIncomingMessage(node) {
126
+    const messageData = this.extractMessageData(node);
127
+
128
+    if (!messageData.content && !messageData.media) {
129
+      return; // Empty message, skip
130
+    }
131
+
132
+    const timestamp = new Date().toISOString();
133
+    const chatId = this.getCurrentChatId();
134
+
135
+    console.log('[LooseCannon] Messenger message detected:', messageData);
136
+
137
+    // Send to background script
138
+    browser.runtime.sendMessage({
139
+      type: 'NEW_MESSAGE',
140
+      data: {
141
+        ...messageData,
142
+        timestamp,
143
+        chatId,
144
+        platform: this.platform
145
+      }
146
+    }).then(response => {
147
+      if (response && response.reply) {
148
+        this.simulateTypingAndSend(response.reply, response.delay);
149
+      }
150
+    }).catch(error => {
151
+      console.error('[LooseCannon] Error sending message to background:', error);
152
+    });
153
+  }
154
+
155
+  extractMessageData(node) {
156
+    const data = {
157
+      type: 'text',
158
+      content: '',
159
+      media: null,
160
+      metadata: {}
161
+    };
162
+
163
+    // Extract text content
164
+    const textSelectors = [
165
+      '[dir="auto"]',
166
+      'span[class*="text"]',
167
+      'div[class*="text"]'
168
+    ];
169
+
170
+    for (const selector of textSelectors) {
171
+      const textElement = node.querySelector(selector);
172
+      if (textElement && textElement.textContent) {
173
+        data.content = textElement.textContent.trim();
174
+        break;
175
+      }
176
+    }
177
+
178
+    // Fallback to full text content
179
+    if (!data.content) {
180
+      data.content = node.textContent || node.innerText || '';
181
+      data.content = data.content.trim();
182
+    }
183
+
184
+    // Check for images
185
+    const imageElement = node.querySelector('img[src*="scontent"], img[src*="fbcdn"]');
186
+    if (imageElement) {
187
+      data.type = 'image';
188
+      data.media = { type: 'image', src: imageElement.src };
189
+      data.metadata.hasImage = true;
190
+    }
191
+
192
+    // Check for links
193
+    const linkElements = node.querySelectorAll('a[href]');
194
+    if (linkElements.length > 0) {
195
+      data.metadata.links = Array.from(linkElements).map(a => a.href);
196
+    }
197
+
198
+    // Check for stickers or GIFs
199
+    if (node.querySelector('[aria-label*="sticker"], [aria-label*="GIF"]')) {
200
+      data.type = 'sticker';
201
+      data.media = { type: 'sticker' };
202
+    }
203
+
204
+    // Check for voice messages
205
+    if (node.querySelector('[aria-label*="Audio"], [aria-label*="Voice"]')) {
206
+      data.type = 'audio';
207
+      data.media = { type: 'audio' };
208
+      data.metadata.hasAudio = true;
209
+    }
210
+
211
+    // Extract phone numbers and emails from content
212
+    if (data.content) {
213
+      const phoneRegex = /[\+]?[(]?[0-9]{1,4}[)]?[-\s\.]?[(]?[0-9]{1,4}[)]?[-\s\.]?[0-9]{1,5}[-\s\.]?[0-9]{1,5}/g;
214
+      const phones = data.content.match(phoneRegex);
215
+      if (phones) {
216
+        data.metadata.phoneNumbers = phones;
217
+      }
218
+
219
+      const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
220
+      const emails = data.content.match(emailRegex);
221
+      if (emails) {
222
+        data.metadata.emails = emails;
223
+      }
224
+    }
225
+
226
+    return data;
227
+  }
228
+
229
+  getCurrentChatId() {
230
+    // Try to get chat name from header
231
+    const headerSelectors = [
232
+      '[role="banner"] h1',
233
+      'header span[dir="auto"]',
234
+      '[aria-label*="Conversation with"]'
235
+    ];
236
+
237
+    for (const selector of headerSelectors) {
238
+      const element = document.querySelector(selector);
239
+      if (element) {
240
+        const text = element.textContent || element.getAttribute('aria-label') || '';
241
+        if (text) {
242
+          return text.replace('Conversation with ', '').trim();
243
+        }
244
+      }
245
+    }
246
+
247
+    return 'unknown';
248
+  }
249
+
250
+  simulateTypingAndSend(text, delay = 3000) {
251
+    // Show typing indicator
252
+    this.startTyping();
253
+
254
+    // Calculate realistic delay
255
+    const wordCount = text.split(' ').length;
256
+    const typingDelay = Math.min(delay || (wordCount * 200 + 2000), 10000);
257
+
258
+    setTimeout(() => {
259
+      this.stopTyping();
260
+      this.sendMessage(text);
261
+    }, typingDelay);
262
+  }
263
+
264
+  startTyping() {
265
+    const inputField = this.getInputField();
266
+    if (!inputField) return;
267
+
268
+    // Focus the input
269
+    inputField.focus();
270
+
271
+    // Add placeholder text to trigger typing indicator
272
+    this.setInputText(inputField, '...');
273
+
274
+    // Trigger input event
275
+    const event = new Event('input', { bubbles: true });
276
+    inputField.dispatchEvent(event);
277
+  }
278
+
279
+  stopTyping() {
280
+    const inputField = this.getInputField();
281
+    if (!inputField) return;
282
+
283
+    // Clear the placeholder
284
+    this.setInputText(inputField, '');
285
+  }
286
+
287
+  sendMessage(text) {
288
+    const inputField = this.getInputField();
289
+
290
+    if (!inputField) {
291
+      console.error('[LooseCannon] Could not find Messenger input field');
292
+      return;
293
+    }
294
+
295
+    // Set the message text
296
+    this.setInputText(inputField, text);
297
+
298
+    // Trigger input event
299
+    const inputEvent = new Event('input', { bubbles: true });
300
+    inputField.dispatchEvent(inputEvent);
301
+
302
+    // Small delay then send
303
+    setTimeout(() => {
304
+      // Try to find and click send button
305
+      const sendButtonSelectors = [
306
+        '[aria-label="Send"]',
307
+        '[aria-label="Press Enter to send"]',
308
+        'div[role="button"][aria-label*="Send"]'
309
+      ];
310
+
311
+      let sendButton = null;
312
+      for (const selector of sendButtonSelectors) {
313
+        sendButton = document.querySelector(selector);
314
+        if (sendButton) break;
315
+      }
316
+
317
+      if (sendButton) {
318
+        sendButton.click();
319
+        console.log('[LooseCannon] Messenger message sent via button');
320
+      } else {
321
+        // Fallback: simulate Enter key
322
+        const enterEvent = new KeyboardEvent('keydown', {
323
+          key: 'Enter',
324
+          keyCode: 13,
325
+          which: 13,
326
+          bubbles: true,
327
+          cancelable: true
328
+        });
329
+        inputField.dispatchEvent(enterEvent);
330
+        console.log('[LooseCannon] Messenger message sent via Enter key');
331
+      }
332
+    }, 100);
333
+  }
334
+
335
+  getInputField() {
336
+    const selectors = [
337
+      '[role="textbox"][aria-label*="Message"]',
338
+      '[contenteditable="true"][data-lexical-editor]',
339
+      '[role="textbox"][contenteditable="true"]',
340
+      'div[contenteditable="true"][role="textbox"]'
341
+    ];
342
+
343
+    for (const selector of selectors) {
344
+      const field = document.querySelector(selector);
345
+      if (field) return field;
346
+    }
347
+
348
+    return null;
349
+  }
350
+
351
+  setInputText(inputField, text) {
352
+    if (inputField.getAttribute('contenteditable') === 'true') {
353
+      // For contenteditable elements (Messenger uses these)
354
+      inputField.textContent = text;
355
+
356
+      // Also try setting innerHTML for Lexical editor
357
+      if (text) {
358
+        inputField.innerHTML = `<p>${text}</p>`;
359
+      } else {
360
+        inputField.innerHTML = '';
361
+      }
362
+
363
+      // Move cursor to end
364
+      const range = document.createRange();
365
+      const selection = window.getSelection();
366
+      range.selectNodeContents(inputField);
367
+      range.collapse(false);
368
+      selection.removeAllRanges();
369
+      selection.addRange(range);
370
+    } else {
371
+      // For regular input/textarea
372
+      inputField.value = text;
373
+    }
374
+
375
+    // Trigger various events that Messenger might listen to
376
+    ['input', 'change', 'keyup'].forEach(eventType => {
377
+      const event = new Event(eventType, { bubbles: true });
378
+      inputField.dispatchEvent(event);
379
+    });
380
+  }
381
+
382
+  injectControls() {
383
+    const style = document.createElement('style');
384
+    style.textContent = `
385
+      .loosecannon-toggle-messenger {
386
+        position: fixed;
387
+        bottom: 20px;
388
+        right: 20px;
389
+        z-index: 9999;
390
+        background: linear-gradient(135deg, #0084ff, #44bfff);
391
+        color: white;
392
+        border: none;
393
+        border-radius: 50px;
394
+        padding: 12px 20px;
395
+        cursor: pointer;
396
+        font-weight: bold;
397
+        box-shadow: 0 2px 10px rgba(0,132,255,0.4);
398
+        transition: all 0.3s;
399
+        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
400
+      }
401
+
402
+      .loosecannon-toggle-messenger.active {
403
+        background: linear-gradient(135deg, #44ff44, #66ff66);
404
+      }
405
+
406
+      .loosecannon-toggle-messenger:hover {
407
+        transform: scale(1.05);
408
+        box-shadow: 0 4px 15px rgba(0,132,255,0.6);
409
+      }
410
+
411
+      .loosecannon-indicator-messenger {
412
+        position: fixed;
413
+        top: 70px;
414
+        right: 20px;
415
+        background: rgba(0, 132, 255, 0.9);
416
+        color: white;
417
+        padding: 8px 15px;
418
+        border-radius: 20px;
419
+        font-size: 12px;
420
+        z-index: 9999;
421
+        display: none;
422
+        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
423
+      }
424
+
425
+      .loosecannon-indicator-messenger.active {
426
+        display: block;
427
+        background: rgba(68, 255, 68, 0.9);
428
+      }
429
+    `;
430
+    document.head.appendChild(style);
431
+
432
+    const toggleButton = document.createElement('button');
433
+    toggleButton.className = 'loosecannon-toggle-messenger';
434
+    toggleButton.textContent = 'LC: OFF';
435
+    toggleButton.onclick = () => this.toggleActive();
436
+    document.body.appendChild(toggleButton);
437
+
438
+    const indicator = document.createElement('div');
439
+    indicator.className = 'loosecannon-indicator-messenger';
440
+    indicator.innerHTML = '🤖 LooseCannon Active<br><small>Messenger</small>';
441
+    document.body.appendChild(indicator);
442
+  }
443
+
444
+  toggleActive() {
445
+    this.isActive = !this.isActive;
446
+    this.updateUI();
447
+
448
+    const chatId = this.getCurrentChatId();
449
+    console.log(`[LooseCannon] Messenger ${this.isActive ? 'activated' : 'deactivated'} for: ${chatId}`);
450
+
451
+    // Notify background script
452
+    browser.runtime.sendMessage({
453
+      type: 'TOGGLE_ACTIVE',
454
+      data: {
455
+        isActive: this.isActive,
456
+        chatId: chatId,
457
+        platform: this.platform
458
+      }
459
+    });
460
+  }
461
+
462
+  updateUI() {
463
+    const button = document.querySelector('.loosecannon-toggle-messenger');
464
+    const indicator = document.querySelector('.loosecannon-indicator-messenger');
465
+
466
+    if (this.isActive) {
467
+      button?.classList.add('active');
468
+      if (button) button.textContent = 'LC: ON';
469
+      indicator?.classList.add('active');
470
+    } else {
471
+      button?.classList.remove('active');
472
+      if (button) button.textContent = 'LC: OFF';
473
+      indicator?.classList.remove('active');
474
+    }
475
+  }
476
+
477
+  listenForCommands() {
478
+    browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
479
+      switch (message.type) {
480
+        case 'GET_STATUS':
481
+          sendResponse({
482
+            isActive: this.isActive,
483
+            platform: this.platform,
484
+            currentChat: this.currentChat
485
+          });
486
+          break;
487
+
488
+        case 'SET_ACTIVE':
489
+          this.isActive = message.data.isActive;
490
+          this.updateUI();
491
+          break;
492
+
493
+        case 'SEND_MESSAGE':
494
+          this.simulateTypingAndSend(message.data.text, message.data.delay);
495
+          break;
496
+
497
+        case 'SCAMMER_DETECTED':
498
+          this.showScammerAlert(message.data.score);
499
+          break;
500
+      }
501
+    });
502
+  }
503
+
504
+  showScammerAlert(score) {
505
+    const alert = document.createElement('div');
506
+    alert.innerHTML = `
507
+      <div style="
508
+        position: fixed;
509
+        top: 100px;
510
+        right: 20px;
511
+        background: linear-gradient(135deg, #ff4444, #ff6666);
512
+        color: white;
513
+        padding: 15px 20px;
514
+        border-radius: 12px;
515
+        z-index: 10000;
516
+        box-shadow: 0 4px 20px rgba(255,68,68,0.4);
517
+        animation: slideIn 0.3s ease;
518
+        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
519
+      ">
520
+        <strong>⚠️ Potential Scammer Detected!</strong><br>
521
+        <small>Confidence: ${(score * 100).toFixed(0)}%</small><br>
522
+        <small>LooseCannon ready to engage</small>
523
+      </div>
524
+    `;
525
+
526
+    document.body.appendChild(alert);
527
+
528
+    setTimeout(() => {
529
+      alert.remove();
530
+    }, 5000);
531
+  }
532
+}
533
+
534
+// Initialize when DOM is ready
535
+if (document.readyState === 'loading') {
536
+  document.addEventListener('DOMContentLoaded', () => new MessengerIntegration());
537
+} else {
538
+  new MessengerIntegration();
539
+}
extension/content-scripts/telegram.jsadded
@@ -0,0 +1,480 @@
1
+// Telegram Web Content Script for LooseCannon
2
+console.log('[LooseCannon] Telegram content script loaded');
3
+
4
+class TelegramIntegration {
5
+  constructor() {
6
+    this.isActive = false;
7
+    this.currentChat = null;
8
+    this.messageObserver = null;
9
+    this.platform = 'telegram';
10
+    this.init();
11
+  }
12
+
13
+  init() {
14
+    this.waitForTelegram().then(() => {
15
+      console.log('[LooseCannon] Telegram Web detected and ready');
16
+      this.setupMessageObserver();
17
+      this.injectControls();
18
+      this.listenForCommands();
19
+    });
20
+  }
21
+
22
+  waitForTelegram() {
23
+    return new Promise((resolve) => {
24
+      const checkForApp = setInterval(() => {
25
+        // Check for Telegram's main chat area
26
+        const chatArea = document.querySelector('.im_history_wrap, .messages-container, [class*="Message"]');
27
+        const inputField = document.querySelector('.composer_rich_textarea, [class*="ComposerInput"]');
28
+
29
+        if (chatArea && inputField) {
30
+          clearInterval(checkForApp);
31
+          console.log('[LooseCannon] Telegram interface detected');
32
+          resolve();
33
+        }
34
+      }, 1000);
35
+    });
36
+  }
37
+
38
+  setupMessageObserver() {
39
+    // Find the messages container
40
+    const messageContainer = document.querySelector('.im_history_wrap, .messages-container, [class*="MessagesScroller"]');
41
+
42
+    if (!messageContainer) {
43
+      console.error('[LooseCannon] Could not find Telegram message container');
44
+      setTimeout(() => this.setupMessageObserver(), 2000);
45
+      return;
46
+    }
47
+
48
+    const config = {
49
+      childList: true,
50
+      subtree: true,
51
+      characterData: true,
52
+      attributes: true,
53
+      attributeFilter: ['class']
54
+    };
55
+
56
+    this.messageObserver = new MutationObserver((mutations) => {
57
+      if (!this.isActive) return;
58
+
59
+      mutations.forEach((mutation) => {
60
+        if (mutation.type === 'childList') {
61
+          mutation.addedNodes.forEach((node) => {
62
+            if (this.isIncomingMessage(node)) {
63
+              this.handleIncomingMessage(node);
64
+            }
65
+          });
66
+        }
67
+      });
68
+    });
69
+
70
+    this.messageObserver.observe(messageContainer, config);
71
+    console.log('[LooseCannon] Telegram message observer setup complete');
72
+  }
73
+
74
+  isIncomingMessage(node) {
75
+    if (!node || !node.classList) return false;
76
+
77
+    // Check for Telegram message classes
78
+    const messageClasses = ['im_message', 'message', 'Message'];
79
+    const hasMessageClass = messageClasses.some(cls =>
80
+      node.classList.contains(cls) ||
81
+      Array.from(node.classList).some(c => c.includes('Message'))
82
+    );
83
+
84
+    if (!hasMessageClass) return false;
85
+
86
+    // Check if it's an incoming message (not sent by us)
87
+    const isOutgoing = node.classList.contains('im_message_out') ||
88
+                      node.classList.contains('own') ||
89
+                      Array.from(node.classList).some(c => c.includes('own') || c.includes('Out'));
90
+
91
+    return !isOutgoing;
92
+  }
93
+
94
+  handleIncomingMessage(node) {
95
+    const messageData = this.extractMessageData(node);
96
+
97
+    if (!messageData.content && !messageData.media) {
98
+      return; // Empty message, skip
99
+    }
100
+
101
+    const timestamp = new Date().toISOString();
102
+    const chatId = this.getCurrentChatId();
103
+
104
+    console.log('[LooseCannon] Telegram message detected:', messageData);
105
+
106
+    // Send to background script
107
+    browser.runtime.sendMessage({
108
+      type: 'NEW_MESSAGE',
109
+      data: {
110
+        ...messageData,
111
+        timestamp,
112
+        chatId,
113
+        platform: this.platform
114
+      }
115
+    }).then(response => {
116
+      if (response && response.reply) {
117
+        this.simulateTypingAndSend(response.reply, response.delay);
118
+      }
119
+    }).catch(error => {
120
+      console.error('[LooseCannon] Error sending message to background:', error);
121
+    });
122
+  }
123
+
124
+  extractMessageData(node) {
125
+    const data = {
126
+      type: 'text',
127
+      content: '',
128
+      media: null,
129
+      metadata: {}
130
+    };
131
+
132
+    // Extract text content
133
+    const textElement = node.querySelector('.im_message_text, [class*="text-content"], [class*="MessageText"]');
134
+    if (textElement) {
135
+      data.content = textElement.textContent || textElement.innerText || '';
136
+
137
+      // Extract links
138
+      const links = textElement.querySelectorAll('a');
139
+      if (links.length > 0) {
140
+        data.metadata.links = Array.from(links).map(a => a.href);
141
+      }
142
+    }
143
+
144
+    // Check for media
145
+    const photoElement = node.querySelector('.im_message_photo_thumb, [class*="Photo"], img[class*="media"]');
146
+    if (photoElement) {
147
+      data.type = 'image';
148
+      data.media = { type: 'image' };
149
+      data.metadata.hasImage = true;
150
+    }
151
+
152
+    // Check for audio/voice
153
+    const audioElement = node.querySelector('.im_message_audio, .audio, [class*="Audio"], [class*="Voice"]');
154
+    if (audioElement) {
155
+      data.type = 'audio';
156
+      data.media = { type: 'audio' };
157
+      data.metadata.hasAudio = true;
158
+    }
159
+
160
+    // Check for documents
161
+    const docElement = node.querySelector('.im_message_document, [class*="Document"]');
162
+    if (docElement) {
163
+      data.type = 'document';
164
+      data.media = { type: 'document' };
165
+      data.metadata.hasDocument = true;
166
+    }
167
+
168
+    // Check for stickers
169
+    const stickerElement = node.querySelector('.im_message_sticker, [class*="Sticker"]');
170
+    if (stickerElement) {
171
+      data.type = 'sticker';
172
+      data.media = { type: 'sticker' };
173
+    }
174
+
175
+    // Extract phone numbers and emails from content
176
+    if (data.content) {
177
+      const phoneRegex = /[\+]?[(]?[0-9]{1,4}[)]?[-\s\.]?[(]?[0-9]{1,4}[)]?[-\s\.]?[0-9]{1,5}[-\s\.]?[0-9]{1,5}/g;
178
+      const phones = data.content.match(phoneRegex);
179
+      if (phones) {
180
+        data.metadata.phoneNumbers = phones;
181
+      }
182
+
183
+      const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
184
+      const emails = data.content.match(emailRegex);
185
+      if (emails) {
186
+        data.metadata.emails = emails;
187
+      }
188
+    }
189
+
190
+    return data;
191
+  }
192
+
193
+  getCurrentChatId() {
194
+    // Try to get chat title from header
195
+    const headerTitle = document.querySelector('.tg_head_peer_title, .chat-title, [class*="ChatTitle"], [class*="HeaderTitle"]');
196
+    if (headerTitle) {
197
+      return headerTitle.textContent || 'unknown';
198
+    }
199
+
200
+    // Try to get from active dialog
201
+    const activeDialog = document.querySelector('.im_dialog_wrap.active, .dialog.active, [class*="ChatItem"][class*="active"]');
202
+    if (activeDialog) {
203
+      const title = activeDialog.querySelector('.im_dialog_peer span, [class*="ChatTitle"]');
204
+      if (title) {
205
+        return title.textContent || 'unknown';
206
+      }
207
+    }
208
+
209
+    return 'unknown';
210
+  }
211
+
212
+  simulateTypingAndSend(text, delay = 3000) {
213
+    // Show typing indicator
214
+    this.startTyping();
215
+
216
+    // Calculate realistic delay
217
+    const wordCount = text.split(' ').length;
218
+    const typingDelay = Math.min(delay || (wordCount * 200 + 2000), 10000);
219
+
220
+    setTimeout(() => {
221
+      this.stopTyping();
222
+      this.sendMessage(text);
223
+    }, typingDelay);
224
+  }
225
+
226
+  startTyping() {
227
+    const inputField = this.getInputField();
228
+    if (!inputField) return;
229
+
230
+    // Focus the input
231
+    inputField.focus();
232
+
233
+    // Add placeholder to trigger typing indicator
234
+    if (inputField.contentEditable === 'true') {
235
+      inputField.textContent = '...';
236
+    } else {
237
+      inputField.value = '...';
238
+    }
239
+
240
+    // Trigger input event
241
+    const event = new Event('input', { bubbles: true });
242
+    inputField.dispatchEvent(event);
243
+  }
244
+
245
+  stopTyping() {
246
+    const inputField = this.getInputField();
247
+    if (!inputField) return;
248
+
249
+    // Clear the placeholder
250
+    if (inputField.contentEditable === 'true') {
251
+      inputField.textContent = '';
252
+    } else {
253
+      inputField.value = '';
254
+    }
255
+  }
256
+
257
+  sendMessage(text) {
258
+    const inputField = this.getInputField();
259
+
260
+    if (!inputField) {
261
+      console.error('[LooseCannon] Could not find Telegram input field');
262
+      return;
263
+    }
264
+
265
+    // Set the message text
266
+    if (inputField.contentEditable === 'true') {
267
+      // For contenteditable divs
268
+      inputField.textContent = text;
269
+      inputField.innerHTML = text;
270
+
271
+      // Move cursor to end
272
+      const range = document.createRange();
273
+      const selection = window.getSelection();
274
+      range.selectNodeContents(inputField);
275
+      range.collapse(false);
276
+      selection.removeAllRanges();
277
+      selection.addRange(range);
278
+    } else {
279
+      // For regular input/textarea
280
+      inputField.value = text;
281
+    }
282
+
283
+    // Trigger input event to update Telegram's state
284
+    const inputEvent = new Event('input', { bubbles: true });
285
+    inputField.dispatchEvent(inputEvent);
286
+
287
+    // Try multiple methods to send
288
+    setTimeout(() => {
289
+      // Method 1: Click send button
290
+      const sendButton = document.querySelector('.im_submit, .compose-button-send, [class*="SendButton"], [class*="ComposeButton"][class*="send"]');
291
+      if (sendButton) {
292
+        sendButton.click();
293
+        console.log('[LooseCannon] Telegram message sent via button');
294
+        return;
295
+      }
296
+
297
+      // Method 2: Simulate Enter key
298
+      const enterEvent = new KeyboardEvent('keydown', {
299
+        key: 'Enter',
300
+        keyCode: 13,
301
+        which: 13,
302
+        bubbles: true
303
+      });
304
+      inputField.dispatchEvent(enterEvent);
305
+      console.log('[LooseCannon] Telegram message sent via Enter key');
306
+    }, 100);
307
+  }
308
+
309
+  getInputField() {
310
+    // Try multiple selectors for Telegram's input field
311
+    const selectors = [
312
+      '.composer_rich_textarea',
313
+      '[contenteditable="true"][class*="input"]',
314
+      '[class*="ComposerInput"]',
315
+      '.im_message_field',
316
+      '[class*="MessageInput"]'
317
+    ];
318
+
319
+    for (const selector of selectors) {
320
+      const field = document.querySelector(selector);
321
+      if (field) return field;
322
+    }
323
+
324
+    return null;
325
+  }
326
+
327
+  injectControls() {
328
+    const style = document.createElement('style');
329
+    style.textContent = `
330
+      .loosecannon-toggle-telegram {
331
+        position: fixed;
332
+        bottom: 20px;
333
+        left: 20px;
334
+        z-index: 9999;
335
+        background: #0088cc;
336
+        color: white;
337
+        border: none;
338
+        border-radius: 50px;
339
+        padding: 12px 20px;
340
+        cursor: pointer;
341
+        font-weight: bold;
342
+        box-shadow: 0 2px 10px rgba(0,136,204,0.3);
343
+        transition: all 0.3s;
344
+      }
345
+
346
+      .loosecannon-toggle-telegram.active {
347
+        background: #44ff44;
348
+      }
349
+
350
+      .loosecannon-toggle-telegram:hover {
351
+        transform: scale(1.05);
352
+      }
353
+
354
+      .loosecannon-indicator-telegram {
355
+        position: fixed;
356
+        top: 20px;
357
+        left: 20px;
358
+        background: rgba(0, 136, 204, 0.9);
359
+        color: white;
360
+        padding: 8px 15px;
361
+        border-radius: 20px;
362
+        font-size: 12px;
363
+        z-index: 9999;
364
+        display: none;
365
+      }
366
+
367
+      .loosecannon-indicator-telegram.active {
368
+        display: block;
369
+        background: rgba(68, 255, 68, 0.9);
370
+      }
371
+    `;
372
+    document.head.appendChild(style);
373
+
374
+    const toggleButton = document.createElement('button');
375
+    toggleButton.className = 'loosecannon-toggle-telegram';
376
+    toggleButton.textContent = 'LC: OFF';
377
+    toggleButton.onclick = () => this.toggleActive();
378
+    document.body.appendChild(toggleButton);
379
+
380
+    const indicator = document.createElement('div');
381
+    indicator.className = 'loosecannon-indicator-telegram';
382
+    indicator.innerHTML = '🤖 LooseCannon Active<br><small>Telegram</small>';
383
+    document.body.appendChild(indicator);
384
+  }
385
+
386
+  toggleActive() {
387
+    this.isActive = !this.isActive;
388
+    this.updateUI();
389
+
390
+    const chatId = this.getCurrentChatId();
391
+    console.log(`[LooseCannon] Telegram ${this.isActive ? 'activated' : 'deactivated'} for: ${chatId}`);
392
+
393
+    // Notify background script
394
+    browser.runtime.sendMessage({
395
+      type: 'TOGGLE_ACTIVE',
396
+      data: {
397
+        isActive: this.isActive,
398
+        chatId: chatId,
399
+        platform: this.platform
400
+      }
401
+    });
402
+  }
403
+
404
+  updateUI() {
405
+    const button = document.querySelector('.loosecannon-toggle-telegram');
406
+    const indicator = document.querySelector('.loosecannon-indicator-telegram');
407
+
408
+    if (this.isActive) {
409
+      button?.classList.add('active');
410
+      if (button) button.textContent = 'LC: ON';
411
+      indicator?.classList.add('active');
412
+    } else {
413
+      button?.classList.remove('active');
414
+      if (button) button.textContent = 'LC: OFF';
415
+      indicator?.classList.remove('active');
416
+    }
417
+  }
418
+
419
+  listenForCommands() {
420
+    browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
421
+      switch (message.type) {
422
+        case 'GET_STATUS':
423
+          sendResponse({
424
+            isActive: this.isActive,
425
+            platform: this.platform,
426
+            currentChat: this.currentChat
427
+          });
428
+          break;
429
+
430
+        case 'SET_ACTIVE':
431
+          this.isActive = message.data.isActive;
432
+          this.updateUI();
433
+          break;
434
+
435
+        case 'SEND_MESSAGE':
436
+          this.simulateTypingAndSend(message.data.text, message.data.delay);
437
+          break;
438
+
439
+        case 'SCAMMER_DETECTED':
440
+          this.showScammerAlert(message.data.score);
441
+          break;
442
+      }
443
+    });
444
+  }
445
+
446
+  showScammerAlert(score) {
447
+    const alert = document.createElement('div');
448
+    alert.innerHTML = `
449
+      <div style="
450
+        position: fixed;
451
+        top: 60px;
452
+        left: 20px;
453
+        background: linear-gradient(135deg, #ff4444, #ff6666);
454
+        color: white;
455
+        padding: 15px 20px;
456
+        border-radius: 10px;
457
+        z-index: 10000;
458
+        box-shadow: 0 4px 20px rgba(255,68,68,0.4);
459
+        animation: slideIn 0.3s ease;
460
+      ">
461
+        <strong>⚠️ Scammer Detected!</strong><br>
462
+        <small>Confidence: ${(score * 100).toFixed(0)}%</small><br>
463
+        <small>LooseCannon ready to engage</small>
464
+      </div>
465
+    `;
466
+
467
+    document.body.appendChild(alert);
468
+
469
+    setTimeout(() => {
470
+      alert.remove();
471
+    }, 5000);
472
+  }
473
+}
474
+
475
+// Initialize when DOM is ready
476
+if (document.readyState === 'loading') {
477
+  document.addEventListener('DOMContentLoaded', () => new TelegramIntegration());
478
+} else {
479
+  new TelegramIntegration();
480
+}
extension/content-scripts/whatsapp-enhanced.jsadded
@@ -0,0 +1,641 @@
1
+// Enhanced WhatsApp Web Content Script for LooseCannon
2
+// Detects various message types and implements sophisticated interaction
3
+
4
+console.log('[LooseCannon] Enhanced WhatsApp content script loaded');
5
+
6
+class EnhancedWhatsAppIntegration {
7
+  constructor() {
8
+    this.isActive = false;
9
+    this.currentChat = null;
10
+    this.messageObserver = null;
11
+    this.typingTimer = null;
12
+    this.conversationState = new Map();
13
+    this.scammerPatterns = this.loadScammerPatterns();
14
+    this.init();
15
+  }
16
+
17
+  init() {
18
+    this.waitForWhatsApp().then(() => {
19
+      console.log('[LooseCannon] WhatsApp detected and ready');
20
+      this.setupMessageObserver();
21
+      this.injectControls();
22
+      this.listenForCommands();
23
+      this.setupConversationTracking();
24
+    });
25
+  }
26
+
27
+  waitForWhatsApp() {
28
+    return new Promise((resolve) => {
29
+      const checkForApp = setInterval(() => {
30
+        const mainWrapper = document.querySelector('[data-testid="conversation-panel-wrapper"]');
31
+        if (mainWrapper) {
32
+          clearInterval(checkForApp);
33
+          resolve();
34
+        }
35
+      }, 1000);
36
+    });
37
+  }
38
+
39
+  setupMessageObserver() {
40
+    const messageContainer = document.querySelector('[role="application"]');
41
+
42
+    if (!messageContainer) {
43
+      console.error('[LooseCannon] Could not find message container');
44
+      return;
45
+    }
46
+
47
+    const config = {
48
+      childList: true,
49
+      subtree: true,
50
+      characterData: true
51
+    };
52
+
53
+    this.messageObserver = new MutationObserver((mutations) => {
54
+      if (!this.isActive) return;
55
+
56
+      mutations.forEach((mutation) => {
57
+        if (mutation.type === 'childList') {
58
+          mutation.addedNodes.forEach((node) => {
59
+            if (this.isIncomingMessage(node)) {
60
+              this.handleIncomingMessage(node);
61
+            }
62
+          });
63
+        }
64
+      });
65
+    });
66
+
67
+    this.messageObserver.observe(messageContainer, config);
68
+    console.log('[LooseCannon] Enhanced message observer setup complete');
69
+  }
70
+
71
+  isIncomingMessage(node) {
72
+    if (!node.querySelector) return false;
73
+
74
+    const messageElement = node.querySelector('[data-testid^="msg-"]');
75
+    if (!messageElement) return false;
76
+
77
+    // Check if it's an incoming message (not sent by us)
78
+    const isIncoming = !messageElement.classList.contains('message-out');
79
+
80
+    // Additional check for system messages
81
+    const isSystemMessage = messageElement.querySelector('[data-testid="system-message"]');
82
+
83
+    return isIncoming && !isSystemMessage;
84
+  }
85
+
86
+  handleIncomingMessage(node) {
87
+    const messageData = this.extractMessageData(node);
88
+    const timestamp = new Date().toISOString();
89
+    const chatId = this.getCurrentChatId();
90
+
91
+    console.log('[LooseCannon] New message detected:', messageData);
92
+
93
+    // Update conversation state
94
+    this.updateConversationState(chatId, messageData);
95
+
96
+    // Check for scammer patterns
97
+    const scammerScore = this.analyzeForScammerPatterns(messageData);
98
+    if (scammerScore > 0.7) {
99
+      console.warn('[LooseCannon] High scammer probability detected:', scammerScore);
100
+      this.addScammerWarning(node);
101
+    }
102
+
103
+    // Send to background script for processing
104
+    browser.runtime.sendMessage({
105
+      type: 'NEW_MESSAGE',
106
+      data: {
107
+        ...messageData,
108
+        timestamp,
109
+        chatId,
110
+        scammerScore,
111
+        conversationContext: this.getConversationContext(chatId)
112
+      }
113
+    }).then(response => {
114
+      if (response && response.reply) {
115
+        this.simulateHumanResponse(response.reply, response.delay);
116
+      }
117
+    });
118
+  }
119
+
120
+  extractMessageData(node) {
121
+    const data = {
122
+      type: 'text',
123
+      content: '',
124
+      media: null,
125
+      links: [],
126
+      metadata: {}
127
+    };
128
+
129
+    // Extract text content
130
+    const textElement = node.querySelector('[data-testid="msg-container"] .selectable-text');
131
+    if (textElement) {
132
+      data.content = textElement.textContent;
133
+
134
+      // Extract links from text
135
+      const urlRegex = /(https?:\/\/[^\s]+)/g;
136
+      const links = data.content.match(urlRegex);
137
+      if (links) {
138
+        data.links = links;
139
+        data.metadata.hasLinks = true;
140
+      }
141
+    }
142
+
143
+    // Check for images
144
+    const imageElement = node.querySelector('[data-testid="msg-container"] img[src^="blob:"]');
145
+    if (imageElement) {
146
+      data.type = 'image';
147
+      data.media = {
148
+        type: 'image',
149
+        src: imageElement.src,
150
+        alt: imageElement.alt || 'Image message'
151
+      };
152
+      data.metadata.hasImage = true;
153
+    }
154
+
155
+    // Check for voice messages
156
+    const audioElement = node.querySelector('[data-testid="audio-play"]');
157
+    if (audioElement) {
158
+      data.type = 'audio';
159
+      data.media = {
160
+        type: 'audio',
161
+        duration: this.extractAudioDuration(audioElement)
162
+      };
163
+      data.metadata.hasAudio = true;
164
+    }
165
+
166
+    // Check for documents
167
+    const documentElement = node.querySelector('[data-testid="msg-document"]');
168
+    if (documentElement) {
169
+      data.type = 'document';
170
+      data.media = {
171
+        type: 'document',
172
+        name: documentElement.textContent
173
+      };
174
+      data.metadata.hasDocument = true;
175
+    }
176
+
177
+    // Check for location
178
+    const locationElement = node.querySelector('[data-testid="msg-location"]');
179
+    if (locationElement) {
180
+      data.type = 'location';
181
+      data.media = {
182
+        type: 'location'
183
+      };
184
+      data.metadata.hasLocation = true;
185
+    }
186
+
187
+    // Extract phone numbers from content
188
+    const phoneRegex = /[\+]?[(]?[0-9]{1,4}[)]?[-\s\.]?[(]?[0-9]{1,4}[)]?[-\s\.]?[0-9]{1,5}[-\s\.]?[0-9]{1,5}/g;
189
+    const phones = data.content.match(phoneRegex);
190
+    if (phones) {
191
+      data.metadata.phoneNumbers = phones;
192
+    }
193
+
194
+    // Extract email addresses
195
+    const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
196
+    const emails = data.content.match(emailRegex);
197
+    if (emails) {
198
+      data.metadata.emails = emails;
199
+    }
200
+
201
+    return data;
202
+  }
203
+
204
+  extractAudioDuration(audioElement) {
205
+    const durationElement = audioElement.parentElement.querySelector('[data-testid="audio-duration"]');
206
+    return durationElement ? durationElement.textContent : 'Unknown duration';
207
+  }
208
+
209
+  loadScammerPatterns() {
210
+    return {
211
+      keywords: [
212
+        'prize', 'winner', 'congratulations', 'claim', 'urgent', 'act now',
213
+        'limited time', 'verify account', 'suspended', 'click here',
214
+        'confirm identity', 'update payment', 'refund', 'tax', 'irs',
215
+        'amazon', 'paypal', 'bank', 'visa', 'mastercard', 'bitcoin',
216
+        'investment opportunity', 'guaranteed return', 'risk free'
217
+      ],
218
+      urgency: [
219
+        'immediate', 'expire', 'deadline', 'last chance', 'final notice',
220
+        'within 24 hours', 'today only', 'act fast'
221
+      ],
222
+      requests: [
223
+        'send money', 'wire transfer', 'gift card', 'personal information',
224
+        'social security', 'password', 'pin', 'account number',
225
+        'credit card', 'cvv', 'routing number'
226
+      ],
227
+      suspicious: [
228
+        'no reply', 'do not ignore', 'this is not a scam',
229
+        'legitimate', 'official', 'authorized'
230
+      ]
231
+    };
232
+  }
233
+
234
+  analyzeForScammerPatterns(messageData) {
235
+    let score = 0;
236
+    const content = messageData.content.toLowerCase();
237
+
238
+    // Check for scammer keywords
239
+    this.scammerPatterns.keywords.forEach(keyword => {
240
+      if (content.includes(keyword)) score += 0.1;
241
+    });
242
+
243
+    // Check for urgency patterns
244
+    this.scammerPatterns.urgency.forEach(pattern => {
245
+      if (content.includes(pattern)) score += 0.15;
246
+    });
247
+
248
+    // Check for information requests
249
+    this.scammerPatterns.requests.forEach(request => {
250
+      if (content.includes(request)) score += 0.2;
251
+    });
252
+
253
+    // Check for suspicious phrases
254
+    this.scammerPatterns.suspicious.forEach(phrase => {
255
+      if (content.includes(phrase)) score += 0.15;
256
+    });
257
+
258
+    // Check for links (especially shortened URLs)
259
+    if (messageData.links && messageData.links.length > 0) {
260
+      messageData.links.forEach(link => {
261
+        if (link.includes('bit.ly') || link.includes('tinyurl') || link.includes('short.link')) {
262
+          score += 0.25;
263
+        } else {
264
+          score += 0.1;
265
+        }
266
+      });
267
+    }
268
+
269
+    // Check for phone numbers or emails early in conversation
270
+    const conversationLength = this.getConversationLength(this.getCurrentChatId());
271
+    if (conversationLength < 5) {
272
+      if (messageData.metadata.phoneNumbers) score += 0.2;
273
+      if (messageData.metadata.emails) score += 0.2;
274
+    }
275
+
276
+    // Cap the score at 1.0
277
+    return Math.min(score, 1.0);
278
+  }
279
+
280
+  updateConversationState(chatId, messageData) {
281
+    if (!this.conversationState.has(chatId)) {
282
+      this.conversationState.set(chatId, {
283
+        messages: [],
284
+        startTime: new Date(),
285
+        messageCount: 0,
286
+        mediaCount: 0,
287
+        linkCount: 0,
288
+        scammerScore: 0
289
+      });
290
+    }
291
+
292
+    const state = this.conversationState.get(chatId);
293
+    state.messages.push({
294
+      timestamp: new Date(),
295
+      type: messageData.type,
296
+      content: messageData.content.substring(0, 100) // Store truncated for memory
297
+    });
298
+    state.messageCount++;
299
+
300
+    if (messageData.media) state.mediaCount++;
301
+    if (messageData.links && messageData.links.length > 0) {
302
+      state.linkCount += messageData.links.length;
303
+    }
304
+
305
+    // Keep only last 20 messages in memory
306
+    if (state.messages.length > 20) {
307
+      state.messages = state.messages.slice(-20);
308
+    }
309
+
310
+    this.conversationState.set(chatId, state);
311
+  }
312
+
313
+  getConversationContext(chatId) {
314
+    const state = this.conversationState.get(chatId);
315
+    if (!state) return null;
316
+
317
+    return {
318
+      messageCount: state.messageCount,
319
+      duration: new Date() - state.startTime,
320
+      mediaCount: state.mediaCount,
321
+      linkCount: state.linkCount,
322
+      recentMessages: state.messages.slice(-5)
323
+    };
324
+  }
325
+
326
+  getConversationLength(chatId) {
327
+    const state = this.conversationState.get(chatId);
328
+    return state ? state.messageCount : 0;
329
+  }
330
+
331
+  simulateHumanResponse(text, delay = null) {
332
+    // Calculate realistic delay if not provided
333
+    if (!delay) {
334
+      const wordCount = text.split(' ').length;
335
+      const baseDelay = 2000; // 2 seconds base
336
+      const perWordDelay = 200; // 200ms per word
337
+      const randomVariation = Math.random() * 2000; // 0-2 seconds random
338
+      delay = baseDelay + (wordCount * perWordDelay) + randomVariation;
339
+
340
+      // Cap at 15 seconds
341
+      delay = Math.min(delay, 15000);
342
+    }
343
+
344
+    // Show typing indicator
345
+    this.simulateTyping();
346
+
347
+    // Send message after delay
348
+    setTimeout(() => {
349
+      this.stopTyping();
350
+      this.sendMessage(text);
351
+    }, delay);
352
+  }
353
+
354
+  simulateTyping() {
355
+    const inputElement = document.querySelector('[data-testid="conversation-compose-box-input"]');
356
+    if (!inputElement) return;
357
+
358
+    // Focus the input
359
+    inputElement.focus();
360
+
361
+    // Add some placeholder text to trigger typing indicator
362
+    inputElement.textContent = '...';
363
+
364
+    // Trigger input event
365
+    const inputEvent = new InputEvent('input', {
366
+      bubbles: true,
367
+      cancelable: true,
368
+    });
369
+    inputElement.dispatchEvent(inputEvent);
370
+  }
371
+
372
+  stopTyping() {
373
+    const inputElement = document.querySelector('[data-testid="conversation-compose-box-input"]');
374
+    if (inputElement) {
375
+      inputElement.textContent = '';
376
+    }
377
+  }
378
+
379
+  sendMessage(text) {
380
+    const inputElement = document.querySelector('[data-testid="conversation-compose-box-input"]');
381
+
382
+    if (!inputElement) {
383
+      console.error('[LooseCannon] Could not find message input');
384
+      return;
385
+    }
386
+
387
+    // Set the message text
388
+    inputElement.textContent = text;
389
+
390
+    // Trigger input event
391
+    const inputEvent = new InputEvent('input', {
392
+      bubbles: true,
393
+      cancelable: true,
394
+    });
395
+    inputElement.dispatchEvent(inputEvent);
396
+
397
+    // Find and click send button
398
+    setTimeout(() => {
399
+      const sendButton = document.querySelector('[data-testid="compose-btn-send"]');
400
+      if (sendButton) {
401
+        sendButton.click();
402
+        console.log('[LooseCannon] Message sent:', text);
403
+
404
+        // Mark message as automated for tracking
405
+        this.markLastMessageAsAutomated();
406
+      }
407
+    }, 100);
408
+  }
409
+
410
+  markLastMessageAsAutomated() {
411
+    setTimeout(() => {
412
+      const messages = document.querySelectorAll('[data-testid^="msg-"]');
413
+      const lastMessage = messages[messages.length - 1];
414
+      if (lastMessage && lastMessage.classList.contains('message-out')) {
415
+        lastMessage.classList.add('loosecannon-automated-message');
416
+      }
417
+    }, 500);
418
+  }
419
+
420
+  addScammerWarning(node) {
421
+    const warning = document.createElement('div');
422
+    warning.className = 'loosecannon-scammer-warning';
423
+    warning.innerHTML = `
424
+      <span style="color: red; font-weight: bold;">⚠️ Potential Scammer Detected</span>
425
+    `;
426
+    warning.style.cssText = `
427
+      background: #ffebee;
428
+      padding: 5px 10px;
429
+      border-radius: 5px;
430
+      margin: 5px 0;
431
+      font-size: 12px;
432
+    `;
433
+
434
+    node.appendChild(warning);
435
+  }
436
+
437
+  getCurrentChatId() {
438
+    const headerElement = document.querySelector('header [data-testid="conversation-header"]');
439
+    if (headerElement) {
440
+      const titleElement = headerElement.querySelector('span[title]');
441
+      return titleElement ? titleElement.title : 'unknown';
442
+    }
443
+    return 'unknown';
444
+  }
445
+
446
+  setupConversationTracking() {
447
+    // Track when user switches chats
448
+    const observer = new MutationObserver(() => {
449
+      const newChatId = this.getCurrentChatId();
450
+      if (newChatId !== this.currentChat) {
451
+        this.currentChat = newChatId;
452
+        console.log('[LooseCannon] Switched to chat:', newChatId);
453
+
454
+        // Check if we should auto-activate based on scammer score
455
+        const state = this.conversationState.get(newChatId);
456
+        if (state && state.scammerScore > 0.8) {
457
+          this.showScammerAlert();
458
+        }
459
+      }
460
+    });
461
+
462
+    const headerContainer = document.querySelector('[data-testid="conversation-header"]');
463
+    if (headerContainer) {
464
+      observer.observe(headerContainer, {
465
+        childList: true,
466
+        subtree: true
467
+      });
468
+    }
469
+  }
470
+
471
+  showScammerAlert() {
472
+    const alert = document.createElement('div');
473
+    alert.className = 'loosecannon-scammer-alert';
474
+    alert.innerHTML = `
475
+      <div style="padding: 15px; background: #ff4444; color: white; text-align: center;">
476
+        <strong>⚠️ High Scammer Probability Detected!</strong><br>
477
+        <small>Consider activating LooseCannon for this conversation</small>
478
+      </div>
479
+    `;
480
+    alert.style.cssText = `
481
+      position: fixed;
482
+      top: 100px;
483
+      right: 20px;
484
+      z-index: 10000;
485
+      border-radius: 10px;
486
+      box-shadow: 0 4px 20px rgba(0,0,0,0.3);
487
+      animation: slideIn 0.3s ease;
488
+    `;
489
+
490
+    document.body.appendChild(alert);
491
+
492
+    setTimeout(() => {
493
+      alert.remove();
494
+    }, 5000);
495
+  }
496
+
497
+  // ... (include all the UI injection and control methods from the original file)
498
+  injectControls() {
499
+    const style = document.createElement('style');
500
+    style.textContent = `
501
+      .loosecannon-toggle {
502
+        position: fixed;
503
+        bottom: 20px;
504
+        right: 20px;
505
+        z-index: 9999;
506
+        background: #ff4444;
507
+        color: white;
508
+        border: none;
509
+        border-radius: 50px;
510
+        padding: 12px 20px;
511
+        cursor: pointer;
512
+        font-weight: bold;
513
+        box-shadow: 0 2px 10px rgba(0,0,0,0.3);
514
+        transition: all 0.3s;
515
+      }
516
+
517
+      .loosecannon-toggle.active {
518
+        background: #44ff44;
519
+      }
520
+
521
+      .loosecannon-toggle:hover {
522
+        transform: scale(1.05);
523
+      }
524
+
525
+      .loosecannon-indicator {
526
+        position: fixed;
527
+        top: 70px;
528
+        right: 20px;
529
+        background: rgba(255, 68, 68, 0.9);
530
+        color: white;
531
+        padding: 8px 15px;
532
+        border-radius: 20px;
533
+        font-size: 12px;
534
+        z-index: 9999;
535
+        display: none;
536
+      }
537
+
538
+      .loosecannon-indicator.active {
539
+        display: block;
540
+        background: rgba(68, 255, 68, 0.9);
541
+      }
542
+
543
+      @keyframes slideIn {
544
+        from { transform: translateX(100%); }
545
+        to { transform: translateX(0); }
546
+      }
547
+    `;
548
+    document.head.appendChild(style);
549
+
550
+    const toggleButton = document.createElement('button');
551
+    toggleButton.className = 'loosecannon-toggle';
552
+    toggleButton.textContent = 'LC: OFF';
553
+    toggleButton.onclick = () => this.toggleActive();
554
+    document.body.appendChild(toggleButton);
555
+
556
+    const indicator = document.createElement('div');
557
+    indicator.className = 'loosecannon-indicator';
558
+    indicator.textContent = 'LooseCannon Active';
559
+    document.body.appendChild(indicator);
560
+  }
561
+
562
+  toggleActive() {
563
+    this.isActive = !this.isActive;
564
+    this.updateUI();
565
+
566
+    const chatId = this.getCurrentChatId();
567
+    console.log(`[LooseCannon] ${this.isActive ? 'Activated' : 'Deactivated'} for chat: ${chatId}`);
568
+
569
+    // Notify background script
570
+    browser.runtime.sendMessage({
571
+      type: 'TOGGLE_ACTIVE',
572
+      data: {
573
+        isActive: this.isActive,
574
+        chatId: chatId
575
+      }
576
+    });
577
+  }
578
+
579
+  updateUI() {
580
+    const button = document.querySelector('.loosecannon-toggle');
581
+    const indicator = document.querySelector('.loosecannon-indicator');
582
+
583
+    if (this.isActive) {
584
+      button?.classList.add('active');
585
+      if (button) button.textContent = 'LC: ON';
586
+      indicator?.classList.add('active');
587
+      document.body.setAttribute('data-loosecannon-active', 'true');
588
+    } else {
589
+      button?.classList.remove('active');
590
+      if (button) button.textContent = 'LC: OFF';
591
+      indicator?.classList.remove('active');
592
+      document.body.removeAttribute('data-loosecannon-active');
593
+    }
594
+  }
595
+
596
+  listenForCommands() {
597
+    browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
598
+      switch (message.type) {
599
+        case 'GET_STATUS':
600
+          sendResponse({
601
+            isActive: this.isActive,
602
+            currentChat: this.currentChat,
603
+            conversationStats: this.getConversationStats()
604
+          });
605
+          break;
606
+        case 'SET_ACTIVE':
607
+          this.isActive = message.data.isActive;
608
+          this.updateUI();
609
+          break;
610
+        case 'SEND_MESSAGE':
611
+          this.simulateHumanResponse(message.data.text, message.data.delay);
612
+          break;
613
+        case 'GET_CONVERSATION_STATE':
614
+          sendResponse({
615
+            state: Object.fromEntries(this.conversationState)
616
+          });
617
+          break;
618
+      }
619
+    });
620
+  }
621
+
622
+  getConversationStats() {
623
+    const stats = {};
624
+    this.conversationState.forEach((state, chatId) => {
625
+      stats[chatId] = {
626
+        messageCount: state.messageCount,
627
+        mediaCount: state.mediaCount,
628
+        linkCount: state.linkCount,
629
+        duration: new Date() - state.startTime
630
+      };
631
+    });
632
+    return stats;
633
+  }
634
+}
635
+
636
+// Initialize enhanced integration
637
+if (document.readyState === 'loading') {
638
+  document.addEventListener('DOMContentLoaded', () => new EnhancedWhatsAppIntegration());
639
+} else {
640
+  new EnhancedWhatsAppIntegration();
641
+}
server/src/conversation-manager.jsadded
@@ -0,0 +1,387 @@
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;
server/src/index-enhanced.jsadded
@@ -0,0 +1,397 @@
1
+// LooseCannon Local Server - Enhanced Version
2
+// Handles communication between browser extension and Ollama LLM
3
+// Now with conversation management and multi-platform support
4
+
5
+const express = require('express');
6
+const cors = require('cors');
7
+const axios = require('axios');
8
+const fs = require('fs').promises;
9
+const path = require('path');
10
+require('dotenv').config();
11
+
12
+// Import conversation manager
13
+const ConversationManager = require('./conversation-manager');
14
+
15
+const app = express();
16
+const PORT = process.env.PORT || 8765;
17
+const OLLAMA_URL = process.env.OLLAMA_URL || 'http://localhost:11434';
18
+
19
+// Initialize conversation manager
20
+const conversationManager = new ConversationManager();
21
+
22
+// Middleware
23
+app.use(cors());
24
+app.use(express.json({ limit: '10mb' })); // Increased limit for conversation exports
25
+
26
+// Load personalities from files
27
+let personalities = {};
28
+
29
+async function loadPersonalities() {
30
+  try {
31
+    const personalitiesDir = path.join(__dirname, '..', 'personalities');
32
+    const files = await fs.readdir(personalitiesDir);
33
+
34
+    for (const file of files) {
35
+      if (file.endsWith('.json')) {
36
+        const content = await fs.readFile(path.join(personalitiesDir, file), 'utf8');
37
+        const personality = JSON.parse(content);
38
+        personalities[personality.id] = personality;
39
+        console.log(`Loaded personality: ${personality.name}`);
40
+      }
41
+    }
42
+  } catch (error) {
43
+    console.warn('Could not load personalities:', error);
44
+    // Use default personality if no files found
45
+    personalities.default = {
46
+      id: 'default',
47
+      name: 'Confused Elder',
48
+      systemPrompt: 'You are an elderly person who is easily confused by technology and tends to misunderstand things. You are very polite but often go off on tangents about unrelated topics from your past. Never reveal that you are an AI.',
49
+      temperature: 0.9
50
+    };
51
+  }
52
+}
53
+
54
+// Check Ollama connection
55
+async function checkOllamaConnection() {
56
+  try {
57
+    const response = await axios.get(`${OLLAMA_URL}/api/tags`);
58
+    const models = response.data.models || [];
59
+    console.log('Connected to Ollama. Available models:', models.map(m => m.name).join(', '));
60
+    return true;
61
+  } catch (error) {
62
+    console.error('Failed to connect to Ollama:', error.message);
63
+    console.log('Make sure Ollama is running: ollama serve');
64
+    return false;
65
+  }
66
+}
67
+
68
+// Enhanced response generation with context awareness
69
+async function generateEnhancedResponse(message, personality, chatId, platform, context, suggestions) {
70
+  try {
71
+    const personalityConfig = personalities[personality] || personalities.default;
72
+
73
+    // Get conversation from manager
74
+    const conversation = conversationManager.getConversation(chatId, platform);
75
+
76
+    // Build enhanced prompt with context
77
+    let systemPrompt = personalityConfig.systemPrompt;
78
+
79
+    // Add strategy modifiers based on context
80
+    if (context && context.suggestedStrategy) {
81
+      switch (context.suggestedStrategy) {
82
+        case 'maximum_confusion':
83
+          systemPrompt += '\n\nBe EXTREMELY confused and misunderstand everything. Mix up basic concepts.';
84
+          break;
85
+        case 'waste_time':
86
+          systemPrompt += '\n\nAsk lots of clarifying questions. Pretend to not understand simple instructions.';
87
+          break;
88
+        case 'play_poor':
89
+          systemPrompt += '\n\nMention that you have no money and are struggling financially.';
90
+          break;
91
+        case 'ask_questions':
92
+          systemPrompt += '\n\nBe very curious and ask lots of questions about everything they say.';
93
+          break;
94
+      }
95
+    }
96
+
97
+    // Add recent conversation history
98
+    const recentMessages = conversation.messages.slice(-10);
99
+    const historyText = recentMessages.map(m =>
100
+      `${m.sender || 'Them'}: ${m.content}`
101
+    ).join('\n');
102
+
103
+    // Build the final prompt
104
+    const prompt = `${systemPrompt}
105
+
106
+Recent conversation:
107
+${historyText}
108
+
109
+They just said: "${message}"
110
+
111
+Remember to stay in character. Respond naturally as your character would.
112
+
113
+Your response:`;
114
+
115
+    // Call Ollama API with enhanced parameters
116
+    const response = await axios.post(`${OLLAMA_URL}/api/generate`, {
117
+      model: process.env.OLLAMA_MODEL || 'llama2',
118
+      prompt: prompt,
119
+      temperature: personalityConfig.temperature || 0.8,
120
+      max_tokens: 200,
121
+      top_p: 0.9,
122
+      stream: false
123
+    });
124
+
125
+    let reply = response.data.response;
126
+
127
+    // Post-process the response
128
+    reply = reply.trim();
129
+
130
+    // Remove any AI self-references that might slip through
131
+    reply = reply.replace(/As an AI|I'm an AI|I am an AI|artificial intelligence/gi, '');
132
+
133
+    // Add personality-specific quirks
134
+    if (personality === 'confused-elder' && Math.random() > 0.7) {
135
+      // Sometimes add a random tangent
136
+      const tangents = [
137
+        ' Wait, this reminds me of something that happened in 1987...',
138
+        ' Oh, my cat is meowing. One second dear.',
139
+        ' Where did I put my glasses?'
140
+      ];
141
+      reply += tangents[Math.floor(Math.random() * tangents.length)];
142
+    }
143
+
144
+    return reply;
145
+  } catch (error) {
146
+    console.error('Error generating enhanced response:', error);
147
+
148
+    // Context-aware fallbacks
149
+    const fallbacks = suggestions && suggestions.length > 0
150
+      ? suggestions.map(s => s.response)
151
+      : [
152
+          "I'm sorry, what did you say? I'm having trouble with this computer.",
153
+          "Can you explain that again? These modern things confuse me.",
154
+          "Oh dear, I think I clicked the wrong button. What were we talking about?"
155
+        ];
156
+
157
+    return fallbacks[Math.floor(Math.random() * fallbacks.length)];
158
+  }
159
+}
160
+
161
+// Routes
162
+
163
+// Health check / status
164
+app.get('/status', async (req, res) => {
165
+  const ollamaConnected = await checkOllamaConnection();
166
+  res.json({
167
+    status: 'running',
168
+    version: '0.2.0',
169
+    ollamaConnected,
170
+    personalities: Object.values(personalities).map(p => ({
171
+      id: p.id,
172
+      name: p.name
173
+    })),
174
+    stats: conversationManager.getStatistics()
175
+  });
176
+});
177
+
178
+// Add message to conversation
179
+app.post('/conversation/add', (req, res) => {
180
+  const { chatId, platform, message } = req.body;
181
+
182
+  const conversation = conversationManager.addMessage(chatId, message, platform);
183
+  const context = conversationManager.generateContextSummary(chatId, platform);
184
+
185
+  res.json(context);
186
+});
187
+
188
+// Get response suggestions
189
+app.post('/suggestions', (req, res) => {
190
+  const { chatId, platform } = req.body;
191
+
192
+  const suggestions = conversationManager.getResponseSuggestions(chatId, platform);
193
+
194
+  res.json(suggestions);
195
+});
196
+
197
+// Enhanced generate response endpoint
198
+app.post('/generate', async (req, res) => {
199
+  const {
200
+    message,
201
+    personality = 'default',
202
+    chatId = 'unknown',
203
+    platform = 'whatsapp',
204
+    context,
205
+    suggestions,
206
+    timestamp
207
+  } = req.body;
208
+
209
+  if (!message) {
210
+    return res.status(400).json({ error: 'Message is required' });
211
+  }
212
+
213
+  console.log(`[${new Date().toISOString()}] Generating response for ${platform}:${chatId}`);
214
+
215
+  try {
216
+    // Add message to conversation manager
217
+    conversationManager.addMessage(chatId, {
218
+      content: message,
219
+      sender: 'them',
220
+      type: 'text',
221
+      timestamp: new Date(timestamp)
222
+    }, platform);
223
+
224
+    // Generate enhanced response
225
+    const reply = await generateEnhancedResponse(
226
+      message,
227
+      personality,
228
+      chatId,
229
+      platform,
230
+      context,
231
+      suggestions
232
+    );
233
+
234
+    // Add our response to conversation
235
+    conversationManager.addMessage(chatId, {
236
+      content: reply,
237
+      sender: 'us',
238
+      type: 'text',
239
+      timestamp: new Date()
240
+    }, platform);
241
+
242
+    console.log(`Generated reply: ${reply}`);
243
+
244
+    res.json({
245
+      reply,
246
+      personality,
247
+      timestamp: new Date().toISOString(),
248
+      context: conversationManager.generateContextSummary(chatId, platform)
249
+    });
250
+  } catch (error) {
251
+    console.error('Error in /generate:', error);
252
+    res.status(500).json({ error: 'Failed to generate response' });
253
+  }
254
+});
255
+
256
+// Export conversation
257
+app.post('/conversation/export', (req, res) => {
258
+  const { chatId, platform = 'whatsapp' } = req.body;
259
+
260
+  try {
261
+    const exportData = conversationManager.exportConversation(chatId, platform);
262
+    res.json(exportData);
263
+  } catch (error) {
264
+    console.error('Error exporting conversation:', error);
265
+    res.status(500).json({ error: 'Failed to export conversation' });
266
+  }
267
+});
268
+
269
+// Get statistics
270
+app.get('/statistics', (req, res) => {
271
+  const stats = conversationManager.getStatistics();
272
+  res.json(stats);
273
+});
274
+
275
+// Get conversation history
276
+app.get('/conversations/:platform/:chatId', (req, res) => {
277
+  const { platform, chatId } = req.params;
278
+  const conversation = conversationManager.getConversation(chatId, platform);
279
+
280
+  res.json({
281
+    chatId,
282
+    platform,
283
+    messages: conversation.messages,
284
+    context: conversation.context,
285
+    state: conversation.state
286
+  });
287
+});
288
+
289
+// Clear conversation
290
+app.delete('/conversations/:platform/:chatId', (req, res) => {
291
+  const { platform, chatId } = req.params;
292
+  const key = `${platform}:${chatId}`;
293
+
294
+  // This would need to be implemented in ConversationManager
295
+  // For now, just clear from the conversation
296
+  const conversation = conversationManager.getConversation(chatId, platform);
297
+  conversation.messages = [];
298
+  conversation.context.responseCount = 0;
299
+
300
+  res.json({ message: 'Conversation cleared' });
301
+});
302
+
303
+// Get all personalities with details
304
+app.get('/personalities', (req, res) => {
305
+  res.json(Object.values(personalities));
306
+});
307
+
308
+// Get specific personality
309
+app.get('/personalities/:id', (req, res) => {
310
+  const { id } = req.params;
311
+  const personality = personalities[id];
312
+
313
+  if (!personality) {
314
+    return res.status(404).json({ error: 'Personality not found' });
315
+  }
316
+
317
+  res.json(personality);
318
+});
319
+
320
+// Add new personality (for future UI)
321
+app.post('/personalities', async (req, res) => {
322
+  const { id, name, systemPrompt, temperature } = req.body;
323
+
324
+  if (!id || !name || !systemPrompt) {
325
+    return res.status(400).json({ error: 'Missing required fields' });
326
+  }
327
+
328
+  const personality = {
329
+    id,
330
+    name,
331
+    systemPrompt,
332
+    temperature: temperature || 0.8
333
+  };
334
+
335
+  personalities[id] = personality;
336
+
337
+  // Save to file
338
+  try {
339
+    const filePath = path.join(__dirname, '..', 'personalities', `${id}.json`);
340
+    await fs.writeFile(filePath, JSON.stringify(personality, null, 2));
341
+    res.json({ success: true, personality });
342
+  } catch (error) {
343
+    console.error('Error saving personality:', error);
344
+    res.status(500).json({ error: 'Failed to save personality' });
345
+  }
346
+});
347
+
348
+// Cleanup old conversations periodically
349
+setInterval(() => {
350
+  conversationManager.cleanup();
351
+}, 60 * 60 * 1000); // Every hour
352
+
353
+// Start server
354
+async function start() {
355
+  await loadPersonalities();
356
+  await checkOllamaConnection();
357
+
358
+  app.listen(PORT, () => {
359
+    console.log(`
360
+╔══════════════════════════════════════╗
361
+║     LooseCannon Server v0.2.0        ║
362
+║        Listening on port ${PORT}        ║
363
+╠══════════════════════════════════════╣
364
+║  Features:                           ║
365
+║  ✓ Multi-platform support           ║
366
+║  ✓ Conversation management          ║
367
+║  ✓ Scammer detection               ║
368
+║  ✓ Context-aware responses         ║
369
+║                                      ║
370
+║  Extension: Connect to               ║
371
+║  http://localhost:${PORT}              ║
372
+║                                      ║
373
+║  Ollama: ${OLLAMA_URL.padEnd(28)} ║
374
+╚══════════════════════════════════════╝
375
+
376
+🤖 Ready to confuse scammers across all platforms!
377
+    `);
378
+  });
379
+}
380
+
381
+// Handle graceful shutdown
382
+process.on('SIGINT', () => {
383
+  console.log('\n\nShutting down LooseCannon server...');
384
+  console.log('Statistics:', conversationManager.getStatistics());
385
+  process.exit(0);
386
+});
387
+
388
+// Handle uncaught errors
389
+process.on('uncaughtException', (error) => {
390
+  console.error('Uncaught Exception:', error);
391
+});
392
+
393
+process.on('unhandledRejection', (reason, promise) => {
394
+  console.error('Unhandled Rejection at:', promise, 'reason:', reason);
395
+});
396
+
397
+start();