zeroed-some/loosecannon / a311f13

Browse files

chrome ext, enhancements

Authored by espadonne
SHA
a311f1334058fdfb635ccc310fb7347a4c6058ec
Parents
1e4e26d
Tree
2311a6e

8 changed files

StatusFile+-
A dashboard/dashboard.js 825 0
A dashboard/index.html 243 0
A dashboard/styles.css 673 0
A extension-chrome/background/pattern-learning.js 505 0
A extension-chrome/background/screenshot-manager.js 408 0
A extension-chrome/background/service-worker.js 393 0
A extension-chrome/background/unified-handler-v3.js 293 0
A extension-chrome/manifest.json 80 0
dashboard/dashboard.jsadded
@@ -0,0 +1,825 @@
1
+// LooseCannon Analytics Dashboard JavaScript
2
+// Real-time monitoring and control interface
3
+
4
+class LooseCannonDashboard {
5
+  constructor() {
6
+    this.serverUrl = 'http://localhost:8765';
7
+    this.ws = null;
8
+    this.charts = {};
9
+    this.updateInterval = null;
10
+    this.startTime = Date.now();
11
+    this.data = {
12
+      messages: [],
13
+      scammers: [],
14
+      conversations: new Map(),
15
+      patterns: [],
16
+      screenshots: []
17
+    };
18
+    this.init();
19
+  }
20
+
21
+  async init() {
22
+    await this.checkServerConnection();
23
+    this.initializeCharts();
24
+    this.setupEventListeners();
25
+    this.connectWebSocket();
26
+    this.startRealtimeUpdates();
27
+    this.loadInitialData();
28
+  }
29
+
30
+  async checkServerConnection() {
31
+    const statusElement = document.getElementById('serverStatus');
32
+
33
+    try {
34
+      const response = await fetch(`${this.serverUrl}/status`);
35
+      if (response.ok) {
36
+        statusElement.classList.add('connected');
37
+        console.log('Server connected');
38
+      } else {
39
+        throw new Error('Server not responding');
40
+      }
41
+    } catch (error) {
42
+      console.error('Server connection failed:', error);
43
+      statusElement.classList.remove('connected');
44
+      this.showNotification('Server offline - some features unavailable', 'error');
45
+    }
46
+  }
47
+
48
+  connectWebSocket() {
49
+    // WebSocket for real-time updates
50
+    this.ws = new WebSocket('ws://localhost:8766');
51
+
52
+    this.ws.onopen = () => {
53
+      console.log('WebSocket connected');
54
+      this.showNotification('Connected to real-time feed', 'success');
55
+    };
56
+
57
+    this.ws.onmessage = (event) => {
58
+      const data = JSON.parse(event.data);
59
+      this.handleRealtimeUpdate(data);
60
+    };
61
+
62
+    this.ws.onerror = (error) => {
63
+      console.error('WebSocket error:', error);
64
+    };
65
+
66
+    this.ws.onclose = () => {
67
+      console.log('WebSocket disconnected, reconnecting...');
68
+      setTimeout(() => this.connectWebSocket(), 5000);
69
+    };
70
+  }
71
+
72
+  handleRealtimeUpdate(data) {
73
+    switch (data.type) {
74
+      case 'MESSAGE':
75
+        this.addActivityItem(data);
76
+        this.updateMessageStats(data);
77
+        break;
78
+
79
+      case 'SCAMMER_DETECTED':
80
+        this.handleScammerDetection(data);
81
+        break;
82
+
83
+      case 'SESSION_UPDATE':
84
+        this.updateActiveSessions(data);
85
+        break;
86
+
87
+      case 'SCREENSHOT':
88
+        this.addScreenshot(data);
89
+        break;
90
+
91
+      case 'PATTERN_LEARNED':
92
+        this.addPattern(data);
93
+        break;
94
+    }
95
+
96
+    this.updateCharts();
97
+  }
98
+
99
+  initializeCharts() {
100
+    const chartOptions = {
101
+      responsive: true,
102
+      maintainAspectRatio: false,
103
+      plugins: {
104
+        legend: {
105
+          labels: {
106
+            color: '#e0e0e0'
107
+          }
108
+        }
109
+      },
110
+      scales: {
111
+        y: {
112
+          grid: {
113
+            color: '#333'
114
+          },
115
+          ticks: {
116
+            color: '#888'
117
+          }
118
+        },
119
+        x: {
120
+          grid: {
121
+            color: '#333'
122
+          },
123
+          ticks: {
124
+            color: '#888'
125
+          }
126
+        }
127
+      }
128
+    };
129
+
130
+    // Volume Chart
131
+    this.charts.volume = new Chart(document.getElementById('volumeChart'), {
132
+      type: 'line',
133
+      data: {
134
+        labels: this.generateTimeLabels(24),
135
+        datasets: [{
136
+          label: 'Messages',
137
+          data: new Array(24).fill(0),
138
+          borderColor: '#667eea',
139
+          backgroundColor: 'rgba(102, 126, 234, 0.1)',
140
+          tension: 0.4
141
+        }]
142
+      },
143
+      options: chartOptions
144
+    });
145
+
146
+    // Detection Chart
147
+    this.charts.detection = new Chart(document.getElementById('detectionChart'), {
148
+      type: 'bar',
149
+      data: {
150
+        labels: ['Low', 'Medium', 'High', 'Critical'],
151
+        datasets: [{
152
+          label: 'Detections',
153
+          data: [0, 0, 0, 0],
154
+          backgroundColor: ['#10b981', '#f59e0b', '#ef4444', '#7c3aed']
155
+        }]
156
+      },
157
+      options: chartOptions
158
+    });
159
+
160
+    // Platform Chart
161
+    this.charts.platform = new Chart(document.getElementById('platformChart'), {
162
+      type: 'doughnut',
163
+      data: {
164
+        labels: ['WhatsApp', 'Telegram', 'Messenger'],
165
+        datasets: [{
166
+          data: [0, 0, 0],
167
+          backgroundColor: ['#25d366', '#0088cc', '#0084ff']
168
+        }]
169
+      },
170
+      options: {
171
+        ...chartOptions,
172
+        plugins: {
173
+          legend: {
174
+            position: 'bottom',
175
+            labels: {
176
+              color: '#e0e0e0'
177
+            }
178
+          }
179
+        }
180
+      }
181
+    });
182
+
183
+    // Response Time Chart
184
+    this.charts.response = new Chart(document.getElementById('responseChart'), {
185
+      type: 'line',
186
+      data: {
187
+        labels: this.generateTimeLabels(60, 'minutes'),
188
+        datasets: [{
189
+          label: 'Response Time (ms)',
190
+          data: new Array(60).fill(0),
191
+          borderColor: '#3b82f6',
192
+          backgroundColor: 'rgba(59, 130, 246, 0.1)',
193
+          tension: 0.4
194
+        }]
195
+      },
196
+      options: chartOptions
197
+    });
198
+  }
199
+
200
+  generateTimeLabels(count, unit = 'hours') {
201
+    const labels = [];
202
+    for (let i = count - 1; i >= 0; i--) {
203
+      if (unit === 'hours') {
204
+        labels.push(`${i}h`);
205
+      } else {
206
+        labels.push(`${i}m`);
207
+      }
208
+    }
209
+    return labels;
210
+  }
211
+
212
+  updateCharts() {
213
+    // Update volume chart with message counts
214
+    const volumeData = this.calculateVolumeData();
215
+    this.charts.volume.data.datasets[0].data = volumeData;
216
+    this.charts.volume.update();
217
+
218
+    // Update detection chart
219
+    const detectionData = this.calculateDetectionData();
220
+    this.charts.detection.data.datasets[0].data = detectionData;
221
+    this.charts.detection.update();
222
+
223
+    // Update platform distribution
224
+    const platformData = this.calculatePlatformData();
225
+    this.charts.platform.data.datasets[0].data = platformData;
226
+    this.charts.platform.update();
227
+
228
+    // Update response times
229
+    const responseData = this.calculateResponseData();
230
+    this.charts.response.data.datasets[0].data = responseData;
231
+    this.charts.response.update();
232
+  }
233
+
234
+  calculateVolumeData() {
235
+    // Group messages by hour
236
+    const hourCounts = new Array(24).fill(0);
237
+    const now = Date.now();
238
+
239
+    this.data.messages.forEach(msg => {
240
+      const age = (now - msg.timestamp) / (1000 * 60 * 60);
241
+      if (age < 24) {
242
+        const hour = Math.floor(age);
243
+        hourCounts[23 - hour]++;
244
+      }
245
+    });
246
+
247
+    return hourCounts;
248
+  }
249
+
250
+  calculateDetectionData() {
251
+    const counts = [0, 0, 0, 0]; // Low, Medium, High, Critical
252
+
253
+    this.data.scammers.forEach(scammer => {
254
+      if (scammer.score < 0.3) counts[0]++;
255
+      else if (scammer.score < 0.6) counts[1]++;
256
+      else if (scammer.score < 0.9) counts[2]++;
257
+      else counts[3]++;
258
+    });
259
+
260
+    return counts;
261
+  }
262
+
263
+  calculatePlatformData() {
264
+    const counts = { whatsapp: 0, telegram: 0, messenger: 0 };
265
+
266
+    this.data.conversations.forEach(conv => {
267
+      counts[conv.platform] = (counts[conv.platform] || 0) + 1;
268
+    });
269
+
270
+    return [counts.whatsapp, counts.telegram, counts.messenger];
271
+  }
272
+
273
+  calculateResponseData() {
274
+    // Return last 60 response time measurements
275
+    return this.data.messages
276
+      .slice(-60)
277
+      .map(msg => msg.responseTime || 0);
278
+  }
279
+
280
+  addActivityItem(data) {
281
+    const feed = document.getElementById('activityFeed');
282
+    const item = document.createElement('div');
283
+    item.className = 'activity-item';
284
+
285
+    if (data.scammerScore > 0.7) {
286
+      item.classList.add('scammer');
287
+    }
288
+
289
+    const time = new Date(data.timestamp).toLocaleTimeString();
290
+
291
+    item.innerHTML = `
292
+      <span class="activity-time">${time}</span>
293
+      <span class="activity-message">${data.message || 'New activity'}</span>
294
+    `;
295
+
296
+    feed.insertBefore(item, feed.firstChild);
297
+
298
+    // Keep only last 50 items
299
+    while (feed.children.length > 50) {
300
+      feed.removeChild(feed.lastChild);
301
+    }
302
+  }
303
+
304
+  updateMessageStats(data) {
305
+    this.data.messages.push(data);
306
+
307
+    // Update counters
308
+    document.getElementById('totalMessages').textContent = this.data.messages.length;
309
+
310
+    // Calculate and show trend
311
+    const trend = this.calculateTrend('messages');
312
+    const trendElement = document.getElementById('messageTrend');
313
+    trendElement.textContent = `${trend > 0 ? '+' : ''}${trend}%`;
314
+    trendElement.className = trend >= 0 ? 'stat-trend' : 'stat-trend negative';
315
+  }
316
+
317
+  handleScammerDetection(data) {
318
+    this.data.scammers.push(data);
319
+
320
+    // Update counter
321
+    document.getElementById('scammersDetected').textContent = this.data.scammers.length;
322
+
323
+    // Add special notification
324
+    this.showNotification(`Scammer detected! Confidence: ${(data.score * 100).toFixed(0)}%`, 'warning');
325
+
326
+    // Flash the stat card
327
+    const card = document.querySelector('.stat-card.danger');
328
+    card.style.animation = 'pulse 0.5s';
329
+    setTimeout(() => {
330
+      card.style.animation = '';
331
+    }, 500);
332
+
333
+    // Add to activity feed
334
+    this.addActivityItem({
335
+      ...data,
336
+      message: `Scammer detected on ${data.platform} (${(data.score * 100).toFixed(0)}% confidence)`
337
+    });
338
+  }
339
+
340
+  updateActiveSessions(data) {
341
+    document.getElementById('activeSessions').textContent = data.count || 0;
342
+  }
343
+
344
+  addScreenshot(data) {
345
+    const grid = document.getElementById('evidenceGrid');
346
+
347
+    // Remove placeholder if exists
348
+    const placeholder = grid.querySelector('.evidence-placeholder');
349
+    if (placeholder) {
350
+      placeholder.remove();
351
+    }
352
+
353
+    const item = document.createElement('div');
354
+    item.className = 'evidence-item';
355
+    item.innerHTML = `
356
+      <img src="${data.thumbnail}" alt="Evidence">
357
+      <div class="evidence-meta">
358
+        ${data.platform} - ${new Date(data.timestamp).toLocaleString()}
359
+      </div>
360
+    `;
361
+
362
+    grid.insertBefore(item, grid.firstChild);
363
+
364
+    // Keep only last 12 screenshots
365
+    while (grid.children.length > 12) {
366
+      grid.removeChild(grid.lastChild);
367
+    }
368
+
369
+    this.data.screenshots.push(data);
370
+  }
371
+
372
+  addPattern(data) {
373
+    this.data.patterns.push(data);
374
+
375
+    // Update pattern stats
376
+    document.getElementById('totalPatterns').textContent = this.data.patterns.length;
377
+    document.getElementById('patternAccuracy').textContent =
378
+      `${(this.calculatePatternAccuracy() * 100).toFixed(0)}%`;
379
+
380
+    // Update patterns list
381
+    const list = document.getElementById('patternsList');
382
+    const item = document.createElement('div');
383
+    item.className = 'pattern-item';
384
+    item.innerHTML = `
385
+      <span class="pattern-type">${data.type}</span>
386
+      <span class="pattern-count">${data.occurrences} occurrences</span>
387
+    `;
388
+
389
+    list.insertBefore(item, list.firstChild);
390
+
391
+    // Keep only last 10 patterns
392
+    while (list.children.length > 10) {
393
+      list.removeChild(list.lastChild);
394
+    }
395
+  }
396
+
397
+  calculatePatternAccuracy() {
398
+    if (this.data.patterns.length === 0) return 0;
399
+
400
+    const correct = this.data.patterns.filter(p => p.verified).length;
401
+    return correct / this.data.patterns.length;
402
+  }
403
+
404
+  calculateTrend(metric) {
405
+    // Simple trend calculation (current hour vs previous hour)
406
+    const now = Date.now();
407
+    const hourAgo = now - (60 * 60 * 1000);
408
+
409
+    let current = 0;
410
+    let previous = 0;
411
+
412
+    if (metric === 'messages') {
413
+      this.data.messages.forEach(msg => {
414
+        if (msg.timestamp > hourAgo) current++;
415
+        else if (msg.timestamp > (hourAgo - 60 * 60 * 1000)) previous++;
416
+      });
417
+    }
418
+
419
+    if (previous === 0) return current > 0 ? 100 : 0;
420
+    return Math.round(((current - previous) / previous) * 100);
421
+  }
422
+
423
+  setupEventListeners() {
424
+    // Export button
425
+    document.getElementById('exportBtn').addEventListener('click', () => {
426
+      this.exportData();
427
+    });
428
+
429
+    // Emergency stop
430
+    document.getElementById('emergencyStop').addEventListener('click', () => {
431
+      this.emergencyStop();
432
+    });
433
+
434
+    // Clear data
435
+    document.getElementById('clearDataBtn').addEventListener('click', () => {
436
+      this.clearOldData();
437
+    });
438
+
439
+    // Sync patterns
440
+    document.getElementById('syncBtn').addEventListener('click', () => {
441
+      this.syncPatterns();
442
+    });
443
+
444
+    // Personality builder
445
+    document.getElementById('personalityBtn').addEventListener('click', () => {
446
+      this.openPersonalityBuilder();
447
+    });
448
+
449
+    // Conversation replay
450
+    document.getElementById('replayBtn').addEventListener('click', () => {
451
+      this.openConversationReplay();
452
+    });
453
+
454
+    // Modal controls
455
+    document.getElementById('modalClose').addEventListener('click', () => {
456
+      document.getElementById('personalityModal').classList.remove('active');
457
+    });
458
+
459
+    document.getElementById('replayClose').addEventListener('click', () => {
460
+      document.getElementById('replayModal').classList.remove('active');
461
+    });
462
+
463
+    // Personality form
464
+    document.getElementById('personalityForm').addEventListener('submit', (e) => {
465
+      e.preventDefault();
466
+      this.savePersonality();
467
+    });
468
+
469
+    // Test personality
470
+    document.getElementById('testPersonality').addEventListener('click', () => {
471
+      this.testPersonality();
472
+    });
473
+
474
+    // Temperature slider
475
+    document.getElementById('temperature').addEventListener('input', (e) => {
476
+      document.getElementById('tempValue').textContent = e.target.value;
477
+    });
478
+  }
479
+
480
+  async exportData() {
481
+    try {
482
+      const data = {
483
+        timestamp: new Date().toISOString(),
484
+        messages: this.data.messages,
485
+        scammers: this.data.scammers,
486
+        patterns: this.data.patterns,
487
+        screenshots: this.data.screenshots.map(s => s.id),
488
+        conversations: Array.from(this.data.conversations.values())
489
+      };
490
+
491
+      const blob = new Blob([JSON.stringify(data, null, 2)], {
492
+        type: 'application/json'
493
+      });
494
+
495
+      const url = URL.createObjectURL(blob);
496
+      const a = document.createElement('a');
497
+      a.href = url;
498
+      a.download = `loosecannon-export-${Date.now()}.json`;
499
+      a.click();
500
+
501
+      this.showNotification('Data exported successfully', 'success');
502
+    } catch (error) {
503
+      console.error('Export error:', error);
504
+      this.showNotification('Export failed', 'error');
505
+    }
506
+  }
507
+
508
+  async emergencyStop() {
509
+    if (!confirm('This will stop ALL active LooseCannon sessions. Continue?')) {
510
+      return;
511
+    }
512
+
513
+    try {
514
+      const response = await fetch(`${this.serverUrl}/emergency-stop`, {
515
+        method: 'POST'
516
+      });
517
+
518
+      if (response.ok) {
519
+        this.showNotification('Emergency stop activated', 'warning');
520
+
521
+        // Clear active sessions display
522
+        document.getElementById('activeSessions').textContent = '0';
523
+
524
+        // Send stop command via WebSocket
525
+        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
526
+          this.ws.send(JSON.stringify({ type: 'EMERGENCY_STOP' }));
527
+        }
528
+      }
529
+    } catch (error) {
530
+      console.error('Emergency stop error:', error);
531
+    }
532
+  }
533
+
534
+  async clearOldData() {
535
+    if (!confirm('Clear data older than 24 hours?')) {
536
+      return;
537
+    }
538
+
539
+    const cutoff = Date.now() - (24 * 60 * 60 * 1000);
540
+
541
+    // Filter data
542
+    this.data.messages = this.data.messages.filter(m => m.timestamp > cutoff);
543
+    this.data.scammers = this.data.scammers.filter(s => s.timestamp > cutoff);
544
+    this.data.screenshots = this.data.screenshots.filter(s => s.timestamp > cutoff);
545
+
546
+    // Update displays
547
+    this.updateCharts();
548
+    this.showNotification('Old data cleared', 'success');
549
+  }
550
+
551
+  async syncPatterns() {
552
+    try {
553
+      const response = await fetch(`${this.serverUrl}/patterns/sync`, {
554
+        method: 'GET'
555
+      });
556
+
557
+      if (response.ok) {
558
+        const patterns = await response.json();
559
+
560
+        // Merge with local patterns
561
+        patterns.forEach(pattern => {
562
+          if (!this.data.patterns.find(p => p.id === pattern.id)) {
563
+            this.addPattern(pattern);
564
+          }
565
+        });
566
+
567
+        this.showNotification(`Synced ${patterns.length} patterns`, 'success');
568
+      }
569
+    } catch (error) {
570
+      console.error('Pattern sync error:', error);
571
+      this.showNotification('Pattern sync failed', 'error');
572
+    }
573
+  }
574
+
575
+  openPersonalityBuilder() {
576
+    document.getElementById('personalityModal').classList.add('active');
577
+  }
578
+
579
+  async savePersonality() {
580
+    const personality = {
581
+      id: document.getElementById('personalityName').value.toLowerCase().replace(/\s+/g, '-'),
582
+      name: document.getElementById('personalityName').value,
583
+      systemPrompt: document.getElementById('systemPrompt').value,
584
+      temperature: parseFloat(document.getElementById('temperature').value),
585
+      examples: document.getElementById('examples').value.split('\n').filter(e => e.trim())
586
+    };
587
+
588
+    try {
589
+      const response = await fetch(`${this.serverUrl}/personalities`, {
590
+        method: 'POST',
591
+        headers: { 'Content-Type': 'application/json' },
592
+        body: JSON.stringify(personality)
593
+      });
594
+
595
+      if (response.ok) {
596
+        this.showNotification('Personality saved successfully', 'success');
597
+        document.getElementById('personalityModal').classList.remove('active');
598
+        document.getElementById('personalityForm').reset();
599
+      }
600
+    } catch (error) {
601
+      console.error('Save personality error:', error);
602
+      this.showNotification('Failed to save personality', 'error');
603
+    }
604
+  }
605
+
606
+  async testPersonality() {
607
+    const testPrompt = "Hello, I have an important message about your account.";
608
+    const systemPrompt = document.getElementById('systemPrompt').value;
609
+    const temperature = parseFloat(document.getElementById('temperature').value);
610
+
611
+    try {
612
+      const response = await fetch(`${this.serverUrl}/test-personality`, {
613
+        method: 'POST',
614
+        headers: { 'Content-Type': 'application/json' },
615
+        body: JSON.stringify({
616
+          systemPrompt,
617
+          temperature,
618
+          message: testPrompt
619
+        })
620
+      });
621
+
622
+      if (response.ok) {
623
+        const result = await response.json();
624
+        document.getElementById('testOutput').innerHTML = `
625
+          <strong>Test Input:</strong> "${testPrompt}"<br>
626
+          <strong>Response:</strong> "${result.response}"
627
+        `;
628
+      }
629
+    } catch (error) {
630
+      console.error('Test personality error:', error);
631
+      document.getElementById('testOutput').innerHTML = 'Test failed';
632
+    }
633
+  }
634
+
635
+  openConversationReplay() {
636
+    document.getElementById('replayModal').classList.add('active');
637
+    this.loadConversations();
638
+  }
639
+
640
+  async loadConversations() {
641
+    const select = document.getElementById('conversationSelect');
642
+    select.innerHTML = '<option>Select a conversation...</option>';
643
+
644
+    this.data.conversations.forEach((conv, id) => {
645
+      const option = document.createElement('option');
646
+      option.value = id;
647
+      option.textContent = `${conv.platform} - ${conv.chatId} (${conv.messages.length} messages)`;
648
+      select.appendChild(option);
649
+    });
650
+  }
651
+
652
+  startRealtimeUpdates() {
653
+    // Update uptime
654
+    setInterval(() => {
655
+      const uptime = Date.now() - this.startTime;
656
+      const hours = Math.floor(uptime / (1000 * 60 * 60));
657
+      const minutes = Math.floor((uptime % (1000 * 60 * 60)) / (1000 * 60));
658
+      document.getElementById('uptime').textContent = `${hours}h ${minutes}m`;
659
+    }, 60000);
660
+
661
+    // Update last updated
662
+    setInterval(() => {
663
+      document.getElementById('lastUpdated').textContent = new Date().toLocaleTimeString();
664
+    }, 1000);
665
+
666
+    // Update response time
667
+    setInterval(() => {
668
+      const avgResponse = this.calculateAverageResponseTime();
669
+      document.getElementById('responseTime').textContent = `${avgResponse}ms`;
670
+    }, 5000);
671
+  }
672
+
673
+  calculateAverageResponseTime() {
674
+    if (this.data.messages.length === 0) return 0;
675
+
676
+    const recent = this.data.messages.slice(-20);
677
+    const sum = recent.reduce((acc, msg) => acc + (msg.responseTime || 0), 0);
678
+    return Math.round(sum / recent.length);
679
+  }
680
+
681
+  async loadInitialData() {
682
+    try {
683
+      // Load analytics
684
+      const analyticsResponse = await fetch(`${this.serverUrl}/analytics`);
685
+      if (analyticsResponse.ok) {
686
+        const analytics = await analyticsResponse.json();
687
+        document.getElementById('totalMessages').textContent = analytics.totalMessages || 0;
688
+        document.getElementById('scammersDetected').textContent = analytics.scammersDetected || 0;
689
+      }
690
+
691
+      // Load active conversations
692
+      const conversationsResponse = await fetch(`${this.serverUrl}/conversations/active`);
693
+      if (conversationsResponse.ok) {
694
+        const conversations = await conversationsResponse.json();
695
+        this.updateConversationCards(conversations);
696
+      }
697
+
698
+      // Load patterns
699
+      const patternsResponse = await fetch(`${this.serverUrl}/patterns`);
700
+      if (patternsResponse.ok) {
701
+        const patterns = await patternsResponse.json();
702
+        patterns.forEach(pattern => this.addPattern(pattern));
703
+      }
704
+    } catch (error) {
705
+      console.error('Error loading initial data:', error);
706
+    }
707
+  }
708
+
709
+  updateConversationCards(conversations) {
710
+    const grid = document.getElementById('conversationsGrid');
711
+    grid.innerHTML = '';
712
+
713
+    if (conversations.length === 0) {
714
+      grid.innerHTML = `
715
+        <div class="conversation-card">
716
+          <div class="conversation-header">
717
+            <span class="conversation-id">No active conversations</span>
718
+          </div>
719
+        </div>
720
+      `;
721
+      return;
722
+    }
723
+
724
+    conversations.forEach(conv => {
725
+      this.data.conversations.set(conv.id, conv);
726
+
727
+      const card = document.createElement('div');
728
+      card.className = 'conversation-card';
729
+      card.innerHTML = `
730
+        <div class="conversation-header">
731
+          <span class="platform-badge ${conv.platform}">${conv.platform}</span>
732
+          <span class="conversation-id">${conv.chatId}</span>
733
+        </div>
734
+        <div class="conversation-stats">
735
+          <span>Messages: ${conv.messageCount}</span>
736
+          <span>Score: ${conv.scammerScore.toFixed(2)}</span>
737
+          <span>Duration: ${Math.round(conv.duration / 60000)}m</span>
738
+        </div>
739
+      `;
740
+
741
+      card.addEventListener('click', () => {
742
+        this.viewConversation(conv.id);
743
+      });
744
+
745
+      grid.appendChild(card);
746
+    });
747
+
748
+    document.getElementById('activeSessions').textContent = conversations.length;
749
+  }
750
+
751
+  viewConversation(conversationId) {
752
+    const conv = this.data.conversations.get(conversationId);
753
+    if (!conv) return;
754
+
755
+    // Open replay modal with this conversation
756
+    document.getElementById('replayModal').classList.add('active');
757
+    document.getElementById('conversationSelect').value = conversationId;
758
+    this.loadConversationMessages(conversationId);
759
+  }
760
+
761
+  loadConversationMessages(conversationId) {
762
+    const conv = this.data.conversations.get(conversationId);
763
+    if (!conv) return;
764
+
765
+    const container = document.getElementById('replayMessages');
766
+    container.innerHTML = '';
767
+
768
+    conv.messages.forEach(msg => {
769
+      const msgDiv = document.createElement('div');
770
+      msgDiv.className = `replay-message ${msg.sender}`;
771
+      msgDiv.innerHTML = `
772
+        <div class="message-time">${new Date(msg.timestamp).toLocaleTimeString()}</div>
773
+        <div class="message-content">${msg.content}</div>
774
+      `;
775
+      container.appendChild(msgDiv);
776
+    });
777
+
778
+    // Update stats
779
+    document.getElementById('replayStats').innerHTML = `
780
+      <span>Messages: ${conv.messages.length}</span>
781
+      <span>Duration: ${Math.round(conv.duration / 60000)}m</span>
782
+      <span>Scammer Score: ${conv.scammerScore.toFixed(2)}</span>
783
+    `;
784
+  }
785
+
786
+  showNotification(message, type = 'info') {
787
+    // Create notification element
788
+    const notification = document.createElement('div');
789
+    notification.className = `notification ${type}`;
790
+    notification.textContent = message;
791
+    notification.style.cssText = `
792
+      position: fixed;
793
+      top: 80px;
794
+      right: 20px;
795
+      padding: 1rem 1.5rem;
796
+      border-radius: 8px;
797
+      z-index: 2000;
798
+      animation: slideIn 0.3s ease;
799
+    `;
800
+
801
+    // Style based on type
802
+    const colors = {
803
+      info: '#3b82f6',
804
+      success: '#10b981',
805
+      warning: '#f59e0b',
806
+      error: '#ef4444'
807
+    };
808
+
809
+    notification.style.background = colors[type] || colors.info;
810
+    notification.style.color = 'white';
811
+
812
+    document.body.appendChild(notification);
813
+
814
+    // Auto remove after 5 seconds
815
+    setTimeout(() => {
816
+      notification.style.animation = 'slideOut 0.3s ease';
817
+      setTimeout(() => notification.remove(), 300);
818
+    }, 5000);
819
+  }
820
+}
821
+
822
+// Initialize dashboard when DOM is ready
823
+document.addEventListener('DOMContentLoaded', () => {
824
+  window.dashboard = new LooseCannonDashboard();
825
+});
dashboard/index.htmladded
@@ -0,0 +1,243 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+<head>
4
+  <meta charset="UTF-8">
5
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+  <title>LooseCannon Analytics Dashboard</title>
7
+  <link rel="stylesheet" href="styles.css">
8
+  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
9
+</head>
10
+<body>
11
+  <div class="dashboard">
12
+    <!-- Header -->
13
+    <header class="dashboard-header">
14
+      <div class="header-content">
15
+        <h1>LooseCannon Command Center</h1>
16
+        <div class="server-status">
17
+          <span class="status-indicator" id="serverStatus"></span>
18
+          <span>Server: <span id="serverUrl">localhost:8765</span></span>
19
+        </div>
20
+      </div>
21
+    </header>
22
+
23
+    <!-- Main Grid -->
24
+    <main class="dashboard-main">
25
+      <!-- Stats Cards -->
26
+      <section class="stats-grid">
27
+        <div class="stat-card">
28
+          <div class="stat-value" id="totalMessages">0</div>
29
+          <div class="stat-label">Messages Processed</div>
30
+          <div class="stat-trend" id="messageTrend">+0%</div>
31
+        </div>
32
+
33
+        <div class="stat-card danger">
34
+          <div class="stat-value" id="scammersDetected">0</div>
35
+          <div class="stat-label">Scammers Detected</div>
36
+          <div class="stat-trend" id="scammerTrend">+0%</div>
37
+        </div>
38
+
39
+        <div class="stat-card success">
40
+          <div class="stat-value" id="activeSessions">0</div>
41
+          <div class="stat-label">Active Sessions</div>
42
+          <div class="stat-indicator">
43
+            <span class="pulse"></span>
44
+            Live
45
+          </div>
46
+        </div>
47
+
48
+        <div class="stat-card info">
49
+          <div class="stat-value" id="responseTime">0ms</div>
50
+          <div class="stat-label">Avg Response Time</div>
51
+          <div class="stat-trend" id="responseTrend">↓ 0%</div>
52
+        </div>
53
+      </section>
54
+
55
+      <!-- Real-time Activity Feed -->
56
+      <section class="activity-section">
57
+        <h2>Live Activity</h2>
58
+        <div class="activity-feed" id="activityFeed">
59
+          <div class="activity-item">
60
+            <span class="activity-time">Waiting for activity...</span>
61
+            <span class="activity-message">System ready</span>
62
+          </div>
63
+        </div>
64
+      </section>
65
+
66
+      <!-- Charts Section -->
67
+      <section class="charts-section">
68
+        <div class="chart-container">
69
+          <h3>Message Volume (24h)</h3>
70
+          <canvas id="volumeChart"></canvas>
71
+        </div>
72
+
73
+        <div class="chart-container">
74
+          <h3>Scammer Detection Rate</h3>
75
+          <canvas id="detectionChart"></canvas>
76
+        </div>
77
+
78
+        <div class="chart-container">
79
+          <h3>Platform Distribution</h3>
80
+          <canvas id="platformChart"></canvas>
81
+        </div>
82
+
83
+        <div class="chart-container">
84
+          <h3>Response Times</h3>
85
+          <canvas id="responseChart"></canvas>
86
+        </div>
87
+      </section>
88
+
89
+      <!-- Active Conversations -->
90
+      <section class="conversations-section">
91
+        <h2>Active Conversations</h2>
92
+        <div class="conversations-grid" id="conversationsGrid">
93
+          <div class="conversation-card">
94
+            <div class="conversation-header">
95
+              <span class="platform-badge whatsapp">WhatsApp</span>
96
+              <span class="conversation-id">No active conversations</span>
97
+            </div>
98
+            <div class="conversation-stats">
99
+              <span>Messages: 0</span>
100
+              <span>Score: 0.0</span>
101
+              <span>Duration: 0m</span>
102
+            </div>
103
+          </div>
104
+        </div>
105
+      </section>
106
+
107
+      <!-- Screenshot Evidence -->
108
+      <section class="evidence-section">
109
+        <h2>Recent Evidence</h2>
110
+        <div class="evidence-grid" id="evidenceGrid">
111
+          <div class="evidence-placeholder">
112
+            No screenshots captured yet
113
+          </div>
114
+        </div>
115
+      </section>
116
+
117
+      <!-- Pattern Learning -->
118
+      <section class="patterns-section">
119
+        <h2>Learned Patterns</h2>
120
+        <div class="patterns-container">
121
+          <div class="pattern-stats">
122
+            <div class="pattern-stat">
123
+              <span class="pattern-value" id="totalPatterns">0</span>
124
+              <span class="pattern-label">Patterns Identified</span>
125
+            </div>
126
+            <div class="pattern-stat">
127
+              <span class="pattern-value" id="patternAccuracy">0%</span>
128
+              <span class="pattern-label">Detection Accuracy</span>
129
+            </div>
130
+          </div>
131
+          <div class="patterns-list" id="patternsList">
132
+            <div class="pattern-item">
133
+              <span class="pattern-type">No patterns learned yet</span>
134
+              <span class="pattern-count">0 occurrences</span>
135
+            </div>
136
+          </div>
137
+        </div>
138
+      </section>
139
+
140
+      <!-- Controls -->
141
+      <section class="controls-section">
142
+        <h2>System Controls</h2>
143
+        <div class="controls-grid">
144
+          <button class="control-btn primary" id="exportBtn">
145
+            Export Data
146
+          </button>
147
+          <button class="control-btn danger" id="emergencyStop">
148
+            Emergency Stop All
149
+          </button>
150
+          <button class="control-btn" id="clearDataBtn">
151
+            Clear Old Data
152
+          </button>
153
+          <button class="control-btn" id="syncBtn">
154
+            Sync Patterns
155
+          </button>
156
+          <button class="control-btn success" id="personalityBtn">
157
+            Personality Builder
158
+          </button>
159
+          <button class="control-btn info" id="replayBtn">
160
+            Conversation Replay
161
+          </button>
162
+        </div>
163
+      </section>
164
+    </main>
165
+
166
+    <!-- Footer -->
167
+    <footer class="dashboard-footer">
168
+      <div class="footer-content">
169
+        <span>LooseCannon v0.3.0</span>
170
+        <span>Uptime: <span id="uptime">0h 0m</span></span>
171
+        <span>Last Updated: <span id="lastUpdated">Never</span></span>
172
+      </div>
173
+    </footer>
174
+  </div>
175
+
176
+  <!-- Personality Builder Modal -->
177
+  <div class="modal" id="personalityModal">
178
+    <div class="modal-content">
179
+      <div class="modal-header">
180
+        <h2>Personality Builder</h2>
181
+        <button class="modal-close" id="modalClose">&times;</button>
182
+      </div>
183
+      <div class="modal-body">
184
+        <form id="personalityForm">
185
+          <div class="form-group">
186
+            <label>Personality Name</label>
187
+            <input type="text" id="personalityName" placeholder="e.g., Overly Helpful">
188
+          </div>
189
+          <div class="form-group">
190
+            <label>System Prompt</label>
191
+            <textarea id="systemPrompt" rows="6" placeholder="Describe the personality traits and behavior..."></textarea>
192
+          </div>
193
+          <div class="form-group">
194
+            <label>Temperature (0.1 - 1.0)</label>
195
+            <input type="range" id="temperature" min="0.1" max="1" step="0.1" value="0.8">
196
+            <span id="tempValue">0.8</span>
197
+          </div>
198
+          <div class="form-group">
199
+            <label>Example Responses</label>
200
+            <textarea id="examples" rows="4" placeholder="One example per line..."></textarea>
201
+          </div>
202
+          <div class="form-buttons">
203
+            <button type="button" class="btn secondary" id="testPersonality">Test</button>
204
+            <button type="submit" class="btn primary">Save Personality</button>
205
+          </div>
206
+        </form>
207
+        <div class="test-output" id="testOutput"></div>
208
+      </div>
209
+    </div>
210
+  </div>
211
+
212
+  <!-- Conversation Replay Modal -->
213
+  <div class="modal" id="replayModal">
214
+    <div class="modal-content">
215
+      <div class="modal-header">
216
+        <h2>Conversation Replay</h2>
217
+        <button class="modal-close" id="replayClose">&times;</button>
218
+      </div>
219
+      <div class="modal-body">
220
+        <select id="conversationSelect">
221
+          <option>Select a conversation...</option>
222
+        </select>
223
+        <div class="replay-container" id="replayContainer">
224
+          <div class="replay-controls">
225
+            <button id="playBtn">▶ Play</button>
226
+            <button id="pauseBtn">⏸ Pause</button>
227
+            <button id="speedBtn">Speed: 1x</button>
228
+            <input type="range" id="replayProgress" min="0" max="100" value="0">
229
+          </div>
230
+          <div class="replay-messages" id="replayMessages"></div>
231
+          <div class="replay-stats" id="replayStats">
232
+            <span>Messages: 0</span>
233
+            <span>Duration: 0m</span>
234
+            <span>Scammer Score: 0.0</span>
235
+          </div>
236
+        </div>
237
+      </div>
238
+    </div>
239
+  </div>
240
+
241
+  <script src="dashboard.js" type="module"></script>
242
+</body>
243
+</html>
dashboard/styles.cssadded
@@ -0,0 +1,673 @@
1
+/* LooseCannon Analytics Dashboard Styles */
2
+
3
+:root {
4
+  --bg-primary: #0f0f0f;
5
+  --bg-secondary: #1a1a1a;
6
+  --bg-card: #242424;
7
+  --text-primary: #e0e0e0;
8
+  --text-secondary: #888;
9
+  --accent: #667eea;
10
+  --success: #10b981;
11
+  --danger: #ef4444;
12
+  --warning: #f59e0b;
13
+  --info: #3b82f6;
14
+  --border: #333;
15
+}
16
+
17
+* {
18
+  margin: 0;
19
+  padding: 0;
20
+  box-sizing: border-box;
21
+}
22
+
23
+body {
24
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace;
25
+  background: var(--bg-primary);
26
+  color: var(--text-primary);
27
+  line-height: 1.6;
28
+}
29
+
30
+/* Dashboard Layout */
31
+.dashboard {
32
+  min-height: 100vh;
33
+  display: flex;
34
+  flex-direction: column;
35
+}
36
+
37
+/* Header */
38
+.dashboard-header {
39
+  background: var(--bg-secondary);
40
+  border-bottom: 1px solid var(--border);
41
+  padding: 1.5rem 2rem;
42
+  position: sticky;
43
+  top: 0;
44
+  z-index: 100;
45
+}
46
+
47
+.header-content {
48
+  display: flex;
49
+  justify-content: space-between;
50
+  align-items: center;
51
+  max-width: 1400px;
52
+  margin: 0 auto;
53
+}
54
+
55
+.dashboard-header h1 {
56
+  font-size: 1.8rem;
57
+  font-weight: 600;
58
+  background: linear-gradient(135deg, var(--accent), #764ba2);
59
+  -webkit-background-clip: text;
60
+  -webkit-text-fill-color: transparent;
61
+}
62
+
63
+.server-status {
64
+  display: flex;
65
+  align-items: center;
66
+  gap: 0.5rem;
67
+  font-size: 0.9rem;
68
+  color: var(--text-secondary);
69
+}
70
+
71
+.status-indicator {
72
+  width: 10px;
73
+  height: 10px;
74
+  border-radius: 50%;
75
+  background: var(--danger);
76
+  animation: pulse 2s infinite;
77
+}
78
+
79
+.status-indicator.connected {
80
+  background: var(--success);
81
+}
82
+
83
+/* Main Content */
84
+.dashboard-main {
85
+  flex: 1;
86
+  padding: 2rem;
87
+  max-width: 1400px;
88
+  margin: 0 auto;
89
+  width: 100%;
90
+}
91
+
92
+/* Stats Grid */
93
+.stats-grid {
94
+  display: grid;
95
+  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
96
+  gap: 1.5rem;
97
+  margin-bottom: 2rem;
98
+}
99
+
100
+.stat-card {
101
+  background: var(--bg-card);
102
+  border: 1px solid var(--border);
103
+  border-radius: 12px;
104
+  padding: 1.5rem;
105
+  transition: transform 0.2s, border-color 0.2s;
106
+}
107
+
108
+.stat-card:hover {
109
+  transform: translateY(-2px);
110
+  border-color: var(--accent);
111
+}
112
+
113
+.stat-card.danger {
114
+  border-color: var(--danger);
115
+}
116
+
117
+.stat-card.success {
118
+  border-color: var(--success);
119
+}
120
+
121
+.stat-card.info {
122
+  border-color: var(--info);
123
+}
124
+
125
+.stat-value {
126
+  font-size: 2.5rem;
127
+  font-weight: bold;
128
+  margin-bottom: 0.5rem;
129
+}
130
+
131
+.stat-label {
132
+  color: var(--text-secondary);
133
+  font-size: 0.9rem;
134
+  text-transform: uppercase;
135
+  letter-spacing: 1px;
136
+}
137
+
138
+.stat-trend {
139
+  margin-top: 0.5rem;
140
+  font-size: 0.85rem;
141
+  color: var(--success);
142
+}
143
+
144
+.stat-trend.negative {
145
+  color: var(--danger);
146
+}
147
+
148
+.stat-indicator {
149
+  display: flex;
150
+  align-items: center;
151
+  gap: 0.5rem;
152
+  margin-top: 0.5rem;
153
+  font-size: 0.85rem;
154
+}
155
+
156
+.pulse {
157
+  width: 8px;
158
+  height: 8px;
159
+  background: var(--success);
160
+  border-radius: 50%;
161
+  animation: pulse 1.5s infinite;
162
+}
163
+
164
+/* Activity Feed */
165
+.activity-section {
166
+  background: var(--bg-card);
167
+  border: 1px solid var(--border);
168
+  border-radius: 12px;
169
+  padding: 1.5rem;
170
+  margin-bottom: 2rem;
171
+}
172
+
173
+.activity-section h2 {
174
+  margin-bottom: 1rem;
175
+  font-size: 1.2rem;
176
+  color: var(--text-primary);
177
+}
178
+
179
+.activity-feed {
180
+  max-height: 300px;
181
+  overflow-y: auto;
182
+  space-y: 0.5rem;
183
+}
184
+
185
+.activity-item {
186
+  display: flex;
187
+  gap: 1rem;
188
+  padding: 0.75rem;
189
+  background: var(--bg-secondary);
190
+  border-radius: 8px;
191
+  margin-bottom: 0.5rem;
192
+  font-size: 0.9rem;
193
+}
194
+
195
+.activity-time {
196
+  color: var(--text-secondary);
197
+  min-width: 80px;
198
+}
199
+
200
+.activity-message {
201
+  flex: 1;
202
+}
203
+
204
+.activity-item.scammer {
205
+  border-left: 3px solid var(--danger);
206
+}
207
+
208
+.activity-item.success {
209
+  border-left: 3px solid var(--success);
210
+}
211
+
212
+/* Charts Section */
213
+.charts-section {
214
+  display: grid;
215
+  grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
216
+  gap: 1.5rem;
217
+  margin-bottom: 2rem;
218
+}
219
+
220
+.chart-container {
221
+  background: var(--bg-card);
222
+  border: 1px solid var(--border);
223
+  border-radius: 12px;
224
+  padding: 1.5rem;
225
+}
226
+
227
+.chart-container h3 {
228
+  margin-bottom: 1rem;
229
+  font-size: 1rem;
230
+  color: var(--text-primary);
231
+}
232
+
233
+.chart-container canvas {
234
+  max-height: 250px;
235
+}
236
+
237
+/* Conversations */
238
+.conversations-section {
239
+  background: var(--bg-card);
240
+  border: 1px solid var(--border);
241
+  border-radius: 12px;
242
+  padding: 1.5rem;
243
+  margin-bottom: 2rem;
244
+}
245
+
246
+.conversations-section h2 {
247
+  margin-bottom: 1rem;
248
+  font-size: 1.2rem;
249
+}
250
+
251
+.conversations-grid {
252
+  display: grid;
253
+  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
254
+  gap: 1rem;
255
+}
256
+
257
+.conversation-card {
258
+  background: var(--bg-secondary);
259
+  border: 1px solid var(--border);
260
+  border-radius: 8px;
261
+  padding: 1rem;
262
+  transition: border-color 0.2s;
263
+}
264
+
265
+.conversation-card:hover {
266
+  border-color: var(--accent);
267
+  cursor: pointer;
268
+}
269
+
270
+.conversation-header {
271
+  display: flex;
272
+  justify-content: space-between;
273
+  align-items: center;
274
+  margin-bottom: 0.75rem;
275
+}
276
+
277
+.platform-badge {
278
+  padding: 0.25rem 0.5rem;
279
+  border-radius: 4px;
280
+  font-size: 0.75rem;
281
+  text-transform: uppercase;
282
+  font-weight: bold;
283
+}
284
+
285
+.platform-badge.whatsapp {
286
+  background: #25d366;
287
+  color: white;
288
+}
289
+
290
+.platform-badge.telegram {
291
+  background: #0088cc;
292
+  color: white;
293
+}
294
+
295
+.platform-badge.messenger {
296
+  background: #0084ff;
297
+  color: white;
298
+}
299
+
300
+.conversation-id {
301
+  font-size: 0.85rem;
302
+  color: var(--text-secondary);
303
+}
304
+
305
+.conversation-stats {
306
+  display: flex;
307
+  gap: 1rem;
308
+  font-size: 0.85rem;
309
+  color: var(--text-secondary);
310
+}
311
+
312
+/* Evidence Section */
313
+.evidence-section {
314
+  background: var(--bg-card);
315
+  border: 1px solid var(--border);
316
+  border-radius: 12px;
317
+  padding: 1.5rem;
318
+  margin-bottom: 2rem;
319
+}
320
+
321
+.evidence-section h2 {
322
+  margin-bottom: 1rem;
323
+}
324
+
325
+.evidence-grid {
326
+  display: grid;
327
+  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
328
+  gap: 1rem;
329
+}
330
+
331
+.evidence-item {
332
+  position: relative;
333
+  border: 1px solid var(--border);
334
+  border-radius: 8px;
335
+  overflow: hidden;
336
+  cursor: pointer;
337
+  transition: transform 0.2s;
338
+}
339
+
340
+.evidence-item:hover {
341
+  transform: scale(1.05);
342
+}
343
+
344
+.evidence-item img {
345
+  width: 100%;
346
+  height: 150px;
347
+  object-fit: cover;
348
+}
349
+
350
+.evidence-meta {
351
+  position: absolute;
352
+  bottom: 0;
353
+  left: 0;
354
+  right: 0;
355
+  background: rgba(0, 0, 0, 0.8);
356
+  padding: 0.5rem;
357
+  font-size: 0.75rem;
358
+  color: white;
359
+}
360
+
361
+.evidence-placeholder {
362
+  grid-column: 1 / -1;
363
+  text-align: center;
364
+  color: var(--text-secondary);
365
+  padding: 2rem;
366
+}
367
+
368
+/* Patterns Section */
369
+.patterns-section {
370
+  background: var(--bg-card);
371
+  border: 1px solid var(--border);
372
+  border-radius: 12px;
373
+  padding: 1.5rem;
374
+  margin-bottom: 2rem;
375
+}
376
+
377
+.patterns-container {
378
+  display: grid;
379
+  grid-template-columns: 200px 1fr;
380
+  gap: 2rem;
381
+}
382
+
383
+.pattern-stats {
384
+  display: flex;
385
+  flex-direction: column;
386
+  gap: 1rem;
387
+}
388
+
389
+.pattern-stat {
390
+  text-align: center;
391
+  padding: 1rem;
392
+  background: var(--bg-secondary);
393
+  border-radius: 8px;
394
+}
395
+
396
+.pattern-value {
397
+  display: block;
398
+  font-size: 2rem;
399
+  font-weight: bold;
400
+  color: var(--accent);
401
+}
402
+
403
+.pattern-label {
404
+  display: block;
405
+  font-size: 0.8rem;
406
+  color: var(--text-secondary);
407
+  margin-top: 0.25rem;
408
+}
409
+
410
+.patterns-list {
411
+  max-height: 200px;
412
+  overflow-y: auto;
413
+}
414
+
415
+.pattern-item {
416
+  display: flex;
417
+  justify-content: space-between;
418
+  padding: 0.75rem;
419
+  background: var(--bg-secondary);
420
+  border-radius: 6px;
421
+  margin-bottom: 0.5rem;
422
+}
423
+
424
+.pattern-type {
425
+  font-weight: 500;
426
+}
427
+
428
+.pattern-count {
429
+  color: var(--text-secondary);
430
+  font-size: 0.9rem;
431
+}
432
+
433
+/* Controls */
434
+.controls-section {
435
+  background: var(--bg-card);
436
+  border: 1px solid var(--border);
437
+  border-radius: 12px;
438
+  padding: 1.5rem;
439
+  margin-bottom: 2rem;
440
+}
441
+
442
+.controls-grid {
443
+  display: grid;
444
+  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
445
+  gap: 1rem;
446
+}
447
+
448
+.control-btn {
449
+  padding: 0.75rem 1.5rem;
450
+  border: 1px solid var(--border);
451
+  border-radius: 8px;
452
+  background: var(--bg-secondary);
453
+  color: var(--text-primary);
454
+  font-size: 0.9rem;
455
+  cursor: pointer;
456
+  transition: all 0.2s;
457
+}
458
+
459
+.control-btn:hover {
460
+  transform: translateY(-2px);
461
+  border-color: var(--accent);
462
+}
463
+
464
+.control-btn.primary {
465
+  background: var(--accent);
466
+  color: white;
467
+  border-color: var(--accent);
468
+}
469
+
470
+.control-btn.danger {
471
+  background: var(--danger);
472
+  color: white;
473
+  border-color: var(--danger);
474
+}
475
+
476
+.control-btn.success {
477
+  background: var(--success);
478
+  color: white;
479
+  border-color: var(--success);
480
+}
481
+
482
+.control-btn.info {
483
+  background: var(--info);
484
+  color: white;
485
+  border-color: var(--info);
486
+}
487
+
488
+/* Modal */
489
+.modal {
490
+  display: none;
491
+  position: fixed;
492
+  top: 0;
493
+  left: 0;
494
+  right: 0;
495
+  bottom: 0;
496
+  background: rgba(0, 0, 0, 0.8);
497
+  z-index: 1000;
498
+  align-items: center;
499
+  justify-content: center;
500
+}
501
+
502
+.modal.active {
503
+  display: flex;
504
+}
505
+
506
+.modal-content {
507
+  background: var(--bg-card);
508
+  border: 1px solid var(--border);
509
+  border-radius: 12px;
510
+  width: 90%;
511
+  max-width: 600px;
512
+  max-height: 80vh;
513
+  overflow: auto;
514
+}
515
+
516
+.modal-header {
517
+  display: flex;
518
+  justify-content: space-between;
519
+  align-items: center;
520
+  padding: 1.5rem;
521
+  border-bottom: 1px solid var(--border);
522
+}
523
+
524
+.modal-close {
525
+  background: none;
526
+  border: none;
527
+  color: var(--text-secondary);
528
+  font-size: 1.5rem;
529
+  cursor: pointer;
530
+}
531
+
532
+.modal-body {
533
+  padding: 1.5rem;
534
+}
535
+
536
+/* Forms */
537
+.form-group {
538
+  margin-bottom: 1.5rem;
539
+}
540
+
541
+.form-group label {
542
+  display: block;
543
+  margin-bottom: 0.5rem;
544
+  color: var(--text-primary);
545
+  font-size: 0.9rem;
546
+}
547
+
548
+.form-group input,
549
+.form-group textarea,
550
+.form-group select {
551
+  width: 100%;
552
+  padding: 0.75rem;
553
+  background: var(--bg-secondary);
554
+  border: 1px solid var(--border);
555
+  border-radius: 6px;
556
+  color: var(--text-primary);
557
+  font-size: 0.9rem;
558
+}
559
+
560
+.form-group input:focus,
561
+.form-group textarea:focus {
562
+  outline: none;
563
+  border-color: var(--accent);
564
+}
565
+
566
+.form-buttons {
567
+  display: flex;
568
+  gap: 1rem;
569
+  justify-content: flex-end;
570
+}
571
+
572
+.btn {
573
+  padding: 0.75rem 1.5rem;
574
+  border: 1px solid var(--border);
575
+  border-radius: 6px;
576
+  background: var(--bg-secondary);
577
+  color: var(--text-primary);
578
+  cursor: pointer;
579
+  transition: all 0.2s;
580
+}
581
+
582
+.btn.primary {
583
+  background: var(--accent);
584
+  color: white;
585
+  border-color: var(--accent);
586
+}
587
+
588
+.btn.secondary {
589
+  background: var(--bg-secondary);
590
+  color: var(--text-primary);
591
+}
592
+
593
+/* Footer */
594
+.dashboard-footer {
595
+  background: var(--bg-secondary);
596
+  border-top: 1px solid var(--border);
597
+  padding: 1rem 2rem;
598
+  margin-top: auto;
599
+}
600
+
601
+.footer-content {
602
+  display: flex;
603
+  justify-content: space-between;
604
+  align-items: center;
605
+  max-width: 1400px;
606
+  margin: 0 auto;
607
+  font-size: 0.85rem;
608
+  color: var(--text-secondary);
609
+}
610
+
611
+/* Animations */
612
+@keyframes pulse {
613
+  0% {
614
+    box-shadow: 0 0 0 0 currentColor;
615
+    opacity: 1;
616
+  }
617
+  70% {
618
+    box-shadow: 0 0 0 10px currentColor;
619
+    opacity: 0;
620
+  }
621
+  100% {
622
+    box-shadow: 0 0 0 0 currentColor;
623
+    opacity: 0;
624
+  }
625
+}
626
+
627
+/* Scrollbar */
628
+::-webkit-scrollbar {
629
+  width: 8px;
630
+  height: 8px;
631
+}
632
+
633
+::-webkit-scrollbar-track {
634
+  background: var(--bg-secondary);
635
+}
636
+
637
+::-webkit-scrollbar-thumb {
638
+  background: var(--border);
639
+  border-radius: 4px;
640
+}
641
+
642
+::-webkit-scrollbar-thumb:hover {
643
+  background: var(--accent);
644
+}
645
+
646
+/* Responsive */
647
+@media (max-width: 768px) {
648
+  .dashboard-main {
649
+    padding: 1rem;
650
+  }
651
+
652
+  .stats-grid {
653
+    grid-template-columns: 1fr;
654
+  }
655
+
656
+  .charts-section {
657
+    grid-template-columns: 1fr;
658
+  }
659
+
660
+  .conversations-grid {
661
+    grid-template-columns: 1fr;
662
+  }
663
+
664
+  .patterns-container {
665
+    grid-template-columns: 1fr;
666
+  }
667
+
668
+  .footer-content {
669
+    flex-direction: column;
670
+    gap: 0.5rem;
671
+    text-align: center;
672
+  }
673
+}
extension-chrome/background/pattern-learning.jsadded
@@ -0,0 +1,505 @@
1
+// Pattern Learning System for Scammer Detection
2
+// Uses machine learning-inspired techniques to identify and learn scammer patterns
3
+
4
+export class PatternLearning {
5
+  constructor() {
6
+    this.patterns = new Map();
7
+    this.weights = new Map();
8
+    this.threshold = 0.7;
9
+    this.learningRate = 0.1;
10
+    this.decayRate = 0.95;
11
+    this.minConfidence = 0.6;
12
+    this.init();
13
+  }
14
+
15
+  async init() {
16
+    await this.loadPatterns();
17
+    await this.loadWeights();
18
+    this.startPeriodicTraining();
19
+  }
20
+
21
+  async loadPatterns() {
22
+    const stored = await chrome.storage.local.get('learned_patterns');
23
+    if (stored.learned_patterns) {
24
+      stored.learned_patterns.forEach(pattern => {
25
+        this.patterns.set(pattern.id, pattern);
26
+      });
27
+    }
28
+
29
+    // Load default patterns
30
+    this.loadDefaultPatterns();
31
+  }
32
+
33
+  loadDefaultPatterns() {
34
+    const defaults = [
35
+      {
36
+        id: 'urgent_action',
37
+        type: 'keyword_cluster',
38
+        features: ['urgent', 'immediate', 'act now', 'expire', 'limited time'],
39
+        weight: 0.8,
40
+        confidence: 0.9
41
+      },
42
+      {
43
+        id: 'money_request',
44
+        type: 'keyword_cluster',
45
+        features: ['send money', 'wire transfer', 'gift card', 'payment', 'fee'],
46
+        weight: 0.9,
47
+        confidence: 0.95
48
+      },
49
+      {
50
+        id: 'personal_info',
51
+        type: 'keyword_cluster',
52
+        features: ['social security', 'password', 'account number', 'pin', 'verification code'],
53
+        weight: 0.85,
54
+        confidence: 0.9
55
+      },
56
+      {
57
+        id: 'suspicious_link',
58
+        type: 'url_pattern',
59
+        features: ['bit.ly', 'tinyurl', 'short.link', 'click.here'],
60
+        weight: 0.75,
61
+        confidence: 0.8
62
+      },
63
+      {
64
+        id: 'impersonation',
65
+        type: 'keyword_cluster',
66
+        features: ['official', 'authorized', 'representative', 'department', 'agency'],
67
+        weight: 0.7,
68
+        confidence: 0.75
69
+      },
70
+      {
71
+        id: 'threat_language',
72
+        type: 'keyword_cluster',
73
+        features: ['suspended', 'terminated', 'legal action', 'arrest', 'prosecution'],
74
+        weight: 0.85,
75
+        confidence: 0.85
76
+      }
77
+    ];
78
+
79
+    defaults.forEach(pattern => {
80
+      if (!this.patterns.has(pattern.id)) {
81
+        this.patterns.set(pattern.id, pattern);
82
+        this.weights.set(pattern.id, pattern.weight);
83
+      }
84
+    });
85
+  }
86
+
87
+  async loadWeights() {
88
+    const stored = await chrome.storage.local.get('pattern_weights');
89
+    if (stored.pattern_weights) {
90
+      Object.entries(stored.pattern_weights).forEach(([id, weight]) => {
91
+        this.weights.set(id, weight);
92
+      });
93
+    }
94
+  }
95
+
96
+  async savePatterns() {
97
+    const patterns = Array.from(this.patterns.values());
98
+    await chrome.storage.local.set({ learned_patterns: patterns });
99
+  }
100
+
101
+  async saveWeights() {
102
+    const weights = Object.fromEntries(this.weights);
103
+    await chrome.storage.local.set({ pattern_weights: weights });
104
+  }
105
+
106
+  analyzeMessage(message, metadata = {}) {
107
+    const analysis = {
108
+      score: 0,
109
+      matchedPatterns: [],
110
+      features: [],
111
+      confidence: 0,
112
+      recommendation: 'monitor'
113
+    };
114
+
115
+    // Extract features from message
116
+    const features = this.extractFeatures(message, metadata);
117
+    analysis.features = features;
118
+
119
+    // Check against learned patterns
120
+    this.patterns.forEach((pattern, patternId) => {
121
+      const match = this.matchPattern(features, pattern);
122
+      if (match.score > this.minConfidence) {
123
+        analysis.matchedPatterns.push({
124
+          id: patternId,
125
+          type: pattern.type,
126
+          score: match.score,
127
+          weight: this.weights.get(patternId) || 0.5
128
+        });
129
+      }
130
+    });
131
+
132
+    // Calculate overall score
133
+    if (analysis.matchedPatterns.length > 0) {
134
+      const weightedSum = analysis.matchedPatterns.reduce((sum, pattern) =>
135
+        sum + (pattern.score * pattern.weight), 0
136
+      );
137
+      const totalWeight = analysis.matchedPatterns.reduce((sum, pattern) =>
138
+        sum + pattern.weight, 0
139
+      );
140
+
141
+      analysis.score = weightedSum / totalWeight;
142
+      analysis.confidence = this.calculateConfidence(analysis.matchedPatterns);
143
+    }
144
+
145
+    // Determine recommendation
146
+    if (analysis.score > 0.9) {
147
+      analysis.recommendation = 'block';
148
+    } else if (analysis.score > 0.7) {
149
+      analysis.recommendation = 'warn';
150
+    } else if (analysis.score > 0.5) {
151
+      analysis.recommendation = 'monitor_closely';
152
+    }
153
+
154
+    return analysis;
155
+  }
156
+
157
+  extractFeatures(message, metadata) {
158
+    const features = {
159
+      keywords: [],
160
+      urls: [],
161
+      patterns: [],
162
+      metrics: {},
163
+      behavioral: []
164
+    };
165
+
166
+    const text = message.content || message.text || '';
167
+    const lowerText = text.toLowerCase();
168
+
169
+    // Extract keywords
170
+    features.keywords = this.extractKeywords(lowerText);
171
+
172
+    // Extract URLs
173
+    features.urls = this.extractUrls(text);
174
+
175
+    // Extract patterns
176
+    features.patterns = this.extractPatterns(text);
177
+
178
+    // Calculate metrics
179
+    features.metrics = {
180
+      length: text.length,
181
+      wordCount: text.split(/\s+/).length,
182
+      uppercaseRatio: (text.match(/[A-Z]/g) || []).length / text.length,
183
+      punctuationCount: (text.match(/[!?]/g) || []).length,
184
+      numberCount: (text.match(/\d+/g) || []).length,
185
+      dollarSignCount: (text.match(/\$/g) || []).length
186
+    };
187
+
188
+    // Behavioral features
189
+    if (metadata.responseTime) {
190
+      features.behavioral.push({
191
+        type: 'response_speed',
192
+        value: metadata.responseTime < 1000 ? 'instant' : 'normal'
193
+      });
194
+    }
195
+
196
+    if (metadata.messageCount) {
197
+      features.behavioral.push({
198
+        type: 'message_frequency',
199
+        value: metadata.messageCount > 10 ? 'high' : 'normal'
200
+      });
201
+    }
202
+
203
+    return features;
204
+  }
205
+
206
+  extractKeywords(text) {
207
+    const keywords = [];
208
+    const commonScamWords = [
209
+      'urgent', 'verify', 'suspended', 'confirm', 'prize', 'winner',
210
+      'congratulations', 'claim', 'refund', 'irs', 'tax', 'arrest',
211
+      'legal', 'bitcoin', 'investment', 'guaranteed', 'risk free'
212
+    ];
213
+
214
+    commonScamWords.forEach(word => {
215
+      if (text.includes(word)) {
216
+        keywords.push(word);
217
+      }
218
+    });
219
+
220
+    return keywords;
221
+  }
222
+
223
+  extractUrls(text) {
224
+    const urlRegex = /(https?:\/\/[^\s]+)/g;
225
+    const urls = text.match(urlRegex) || [];
226
+
227
+    return urls.map(url => ({
228
+      url,
229
+      shortened: this.isShortened(url),
230
+      suspicious: this.isSuspiciousUrl(url)
231
+    }));
232
+  }
233
+
234
+  isShortened(url) {
235
+    const shorteners = ['bit.ly', 'tinyurl.com', 'short.link', 'ow.ly', 'goo.gl'];
236
+    return shorteners.some(shortener => url.includes(shortener));
237
+  }
238
+
239
+  isSuspiciousUrl(url) {
240
+    // Check for typosquatting and suspicious patterns
241
+    const suspicious = [
242
+      'amaz0n', 'payp4l', 'mircosoft', 'goggle',
243
+      'faceb00k', 'app1e', 'netf1ix'
244
+    ];
245
+
246
+    return suspicious.some(pattern => url.toLowerCase().includes(pattern));
247
+  }
248
+
249
+  extractPatterns(text) {
250
+    const patterns = [];
251
+
252
+    // Phone number pattern
253
+    if (/[\+]?[(]?[0-9]{1,4}[)]?[-\s\.]?[(]?[0-9]{1,4}[)]?[-\s\.]?[0-9]{1,5}[-\s\.]?[0-9]{1,5}/.test(text)) {
254
+      patterns.push('phone_number');
255
+    }
256
+
257
+    // Email pattern
258
+    if (/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/.test(text)) {
259
+      patterns.push('email_address');
260
+    }
261
+
262
+    // Money amount pattern
263
+    if (/\$[\d,]+(\.\d{2})?/.test(text)) {
264
+      patterns.push('money_amount');
265
+    }
266
+
267
+    // Verification code pattern
268
+    if (/\b\d{4,6}\b/.test(text) && text.includes('code')) {
269
+      patterns.push('verification_code');
270
+    }
271
+
272
+    return patterns;
273
+  }
274
+
275
+  matchPattern(features, pattern) {
276
+    let score = 0;
277
+    let matches = 0;
278
+
279
+    if (pattern.type === 'keyword_cluster') {
280
+      // Check how many keywords from the pattern are in the message
281
+      pattern.features.forEach(keyword => {
282
+        if (features.keywords.includes(keyword.toLowerCase())) {
283
+          matches++;
284
+        }
285
+      });
286
+
287
+      score = matches / pattern.features.length;
288
+    } else if (pattern.type === 'url_pattern') {
289
+      // Check URLs
290
+      features.urls.forEach(urlInfo => {
291
+        pattern.features.forEach(urlPattern => {
292
+          if (urlInfo.url.includes(urlPattern)) {
293
+            matches++;
294
+          }
295
+        });
296
+      });
297
+
298
+      score = matches > 0 ? 1 : 0;
299
+    } else if (pattern.type === 'behavioral') {
300
+      // Check behavioral patterns
301
+      features.behavioral.forEach(behavior => {
302
+        if (pattern.features.includes(behavior.type)) {
303
+          matches++;
304
+        }
305
+      });
306
+
307
+      score = matches / pattern.features.length;
308
+    }
309
+
310
+    return { score, matches };
311
+  }
312
+
313
+  calculateConfidence(matchedPatterns) {
314
+    if (matchedPatterns.length === 0) return 0;
315
+
316
+    // Confidence increases with more pattern matches
317
+    const baseConfidence = matchedPatterns.reduce((sum, p) => sum + p.score, 0) / matchedPatterns.length;
318
+    const diversityBonus = Math.min(matchedPatterns.length * 0.1, 0.3);
319
+
320
+    return Math.min(baseConfidence + diversityBonus, 1);
321
+  }
322
+
323
+  async addPattern(messageData, scammerScore) {
324
+    const features = this.extractFeatures(messageData);
325
+
326
+    // Only learn from high-confidence scammer messages
327
+    if (scammerScore < this.threshold) return;
328
+
329
+    // Update weights for matched patterns (reinforcement)
330
+    const analysis = this.analyzeMessage(messageData);
331
+    analysis.matchedPatterns.forEach(pattern => {
332
+      const currentWeight = this.weights.get(pattern.id) || 0.5;
333
+      const newWeight = currentWeight + (this.learningRate * (scammerScore - currentWeight));
334
+      this.weights.set(pattern.id, Math.min(newWeight, 1));
335
+    });
336
+
337
+    // Check if this represents a new pattern
338
+    const novelty = this.calculateNovelty(features);
339
+    if (novelty > 0.3) {
340
+      await this.createNewPattern(features, scammerScore);
341
+    }
342
+
343
+    // Save updated weights
344
+    await this.saveWeights();
345
+
346
+    // Notify dashboard
347
+    this.notifyLearning(features, scammerScore);
348
+  }
349
+
350
+  calculateNovelty(features) {
351
+    // Check how different these features are from existing patterns
352
+    let maxSimilarity = 0;
353
+
354
+    this.patterns.forEach(pattern => {
355
+      const match = this.matchPattern(features, pattern);
356
+      maxSimilarity = Math.max(maxSimilarity, match.score);
357
+    });
358
+
359
+    return 1 - maxSimilarity;
360
+  }
361
+
362
+  async createNewPattern(features, confidence) {
363
+    const id = `learned_${Date.now()}`;
364
+
365
+    const newPattern = {
366
+      id,
367
+      type: 'learned',
368
+      features: {
369
+        keywords: features.keywords.slice(0, 10),
370
+        patterns: features.patterns,
371
+        metrics: features.metrics
372
+      },
373
+      weight: confidence * 0.7, // Start with lower weight
374
+      confidence,
375
+      learnedAt: new Date().toISOString(),
376
+      occurrences: 1
377
+    };
378
+
379
+    this.patterns.set(id, newPattern);
380
+    this.weights.set(id, newPattern.weight);
381
+
382
+    await this.savePatterns();
383
+
384
+    console.log('[PatternLearning] New pattern learned:', id);
385
+
386
+    return newPattern;
387
+  }
388
+
389
+  async updateFromServer(serverPatterns) {
390
+    let updated = 0;
391
+
392
+    serverPatterns.forEach(serverPattern => {
393
+      const existing = this.patterns.get(serverPattern.id);
394
+
395
+      if (!existing || serverPattern.confidence > existing.confidence) {
396
+        this.patterns.set(serverPattern.id, serverPattern);
397
+        this.weights.set(serverPattern.id, serverPattern.weight);
398
+        updated++;
399
+      }
400
+    });
401
+
402
+    if (updated > 0) {
403
+      await this.savePatterns();
404
+      await this.saveWeights();
405
+      console.log(`[PatternLearning] Updated ${updated} patterns from server`);
406
+    }
407
+
408
+    return updated;
409
+  }
410
+
411
+  async getPatterns() {
412
+    return Array.from(this.patterns.values()).map(pattern => ({
413
+      ...pattern,
414
+      weight: this.weights.get(pattern.id) || 0.5,
415
+      effectiveness: this.calculateEffectiveness(pattern.id)
416
+    }));
417
+  }
418
+
419
+  calculateEffectiveness(patternId) {
420
+    // Track how effective each pattern is at detecting scammers
421
+    // This would be based on true/false positive rates in production
422
+    const weight = this.weights.get(patternId) || 0.5;
423
+    const pattern = this.patterns.get(patternId);
424
+
425
+    if (!pattern) return 0;
426
+
427
+    // Simple effectiveness score based on weight and confidence
428
+    return (weight * 0.7 + (pattern.confidence || 0.5) * 0.3);
429
+  }
430
+
431
+  startPeriodicTraining() {
432
+    // Decay weights periodically to adapt to changing patterns
433
+    setInterval(() => {
434
+      this.decayWeights();
435
+    }, 6 * 60 * 60 * 1000); // Every 6 hours
436
+  }
437
+
438
+  async decayWeights() {
439
+    let decayed = 0;
440
+
441
+    this.weights.forEach((weight, patternId) => {
442
+      // Don't decay core patterns below minimum
443
+      const pattern = this.patterns.get(patternId);
444
+      const minWeight = pattern && pattern.type !== 'learned' ? 0.5 : 0.1;
445
+
446
+      const newWeight = Math.max(weight * this.decayRate, minWeight);
447
+      if (newWeight !== weight) {
448
+        this.weights.set(patternId, newWeight);
449
+        decayed++;
450
+      }
451
+    });
452
+
453
+    if (decayed > 0) {
454
+      await this.saveWeights();
455
+      console.log(`[PatternLearning] Decayed ${decayed} pattern weights`);
456
+    }
457
+  }
458
+
459
+  async exportPatterns() {
460
+    const patterns = await this.getPatterns();
461
+
462
+    return {
463
+      version: '1.0',
464
+      timestamp: new Date().toISOString(),
465
+      patterns: patterns.sort((a, b) => b.effectiveness - a.effectiveness),
466
+      statistics: {
467
+        total: patterns.length,
468
+        learned: patterns.filter(p => p.type === 'learned').length,
469
+        averageConfidence: patterns.reduce((sum, p) => sum + (p.confidence || 0), 0) / patterns.length
470
+      }
471
+    };
472
+  }
473
+
474
+  notifyLearning(features, score) {
475
+    // Notify dashboard about learning event
476
+    chrome.runtime.sendMessage({
477
+      type: 'DASHBOARD_UPDATE',
478
+      data: {
479
+        event: 'pattern_learned',
480
+        features: features.keywords.slice(0, 5),
481
+        score,
482
+        timestamp: new Date().toISOString()
483
+      }
484
+    }).catch(() => {
485
+      // Dashboard might not be open
486
+    });
487
+  }
488
+
489
+  async resetLearning() {
490
+    // Reset to default patterns only
491
+    this.patterns.clear();
492
+    this.weights.clear();
493
+    this.loadDefaultPatterns();
494
+
495
+    await this.savePatterns();
496
+    await this.saveWeights();
497
+
498
+    console.log('[PatternLearning] Reset to default patterns');
499
+  }
500
+}
501
+
502
+// Export for use in service worker
503
+if (typeof module !== 'undefined' && module.exports) {
504
+  module.exports = PatternLearning;
505
+}
extension-chrome/background/screenshot-manager.jsadded
@@ -0,0 +1,408 @@
1
+// Screenshot Manager for Evidence Collection
2
+// Captures and stores screenshots when scammers are detected
3
+
4
+export class ScreenshotManager {
5
+  constructor() {
6
+    this.storage = 'screenshots';
7
+    this.maxScreenshots = 100;
8
+    this.compressionQuality = 0.8;
9
+    this.init();
10
+  }
11
+
12
+  async init() {
13
+    // Create storage structure if doesn't exist
14
+    const stored = await chrome.storage.local.get(this.storage);
15
+    if (!stored[this.storage]) {
16
+      await chrome.storage.local.set({ [this.storage]: [] });
17
+    }
18
+  }
19
+
20
+  async capture(tab, metadata = {}) {
21
+    try {
22
+      // Capture visible tab
23
+      const dataUrl = await chrome.tabs.captureVisibleTab(tab.windowId, {
24
+        format: 'jpeg',
25
+        quality: Math.round(this.compressionQuality * 100)
26
+      });
27
+
28
+      // Generate unique ID
29
+      const id = `screenshot_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
30
+
31
+      // Create screenshot object
32
+      const screenshot = {
33
+        id,
34
+        timestamp: new Date().toISOString(),
35
+        url: tab.url,
36
+        title: tab.title,
37
+        platform: this.detectPlatform(tab.url),
38
+        dataUrl: await this.compressImage(dataUrl),
39
+        thumbnail: await this.createThumbnail(dataUrl),
40
+        metadata: {
41
+          ...metadata,
42
+          tabId: tab.id,
43
+          windowId: tab.windowId,
44
+          incognito: tab.incognito
45
+        }
46
+      };
47
+
48
+      // Store screenshot
49
+      await this.store(screenshot);
50
+
51
+      // Notify dashboard
52
+      this.notifyDashboard(screenshot);
53
+
54
+      return {
55
+        success: true,
56
+        id: screenshot.id,
57
+        size: this.calculateSize(screenshot.dataUrl)
58
+      };
59
+    } catch (error) {
60
+      console.error('[ScreenshotManager] Capture error:', error);
61
+      return {
62
+        success: false,
63
+        error: error.message
64
+      };
65
+    }
66
+  }
67
+
68
+  detectPlatform(url) {
69
+    if (url.includes('whatsapp.com')) return 'whatsapp';
70
+    if (url.includes('telegram.org')) return 'telegram';
71
+    if (url.includes('messenger.com')) return 'messenger';
72
+    return 'unknown';
73
+  }
74
+
75
+  async compressImage(dataUrl) {
76
+    return new Promise((resolve) => {
77
+      const img = new Image();
78
+      img.onload = () => {
79
+        const canvas = document.createElement('canvas');
80
+        const ctx = canvas.getContext('2d');
81
+
82
+        // Set max dimensions
83
+        const maxWidth = 1920;
84
+        const maxHeight = 1080;
85
+
86
+        let width = img.width;
87
+        let height = img.height;
88
+
89
+        // Calculate new dimensions
90
+        if (width > maxWidth || height > maxHeight) {
91
+          const ratio = Math.min(maxWidth / width, maxHeight / height);
92
+          width *= ratio;
93
+          height *= ratio;
94
+        }
95
+
96
+        canvas.width = width;
97
+        canvas.height = height;
98
+
99
+        // Draw and compress
100
+        ctx.drawImage(img, 0, 0, width, height);
101
+        resolve(canvas.toDataURL('image/jpeg', this.compressionQuality));
102
+      };
103
+
104
+      img.src = dataUrl;
105
+    });
106
+  }
107
+
108
+  async createThumbnail(dataUrl) {
109
+    return new Promise((resolve) => {
110
+      const img = new Image();
111
+      img.onload = () => {
112
+        const canvas = document.createElement('canvas');
113
+        const ctx = canvas.getContext('2d');
114
+
115
+        // Thumbnail dimensions
116
+        const thumbWidth = 320;
117
+        const thumbHeight = 180;
118
+
119
+        canvas.width = thumbWidth;
120
+        canvas.height = thumbHeight;
121
+
122
+        // Draw thumbnail
123
+        ctx.drawImage(img, 0, 0, thumbWidth, thumbHeight);
124
+        resolve(canvas.toDataURL('image/jpeg', 0.6));
125
+      };
126
+
127
+      img.src = dataUrl;
128
+    });
129
+  }
130
+
131
+  calculateSize(dataUrl) {
132
+    // Estimate size in bytes
133
+    const base64Length = dataUrl.length - 'data:image/jpeg;base64,'.length;
134
+    const sizeInBytes = base64Length * 0.75;
135
+    return Math.round(sizeInBytes / 1024); // Return in KB
136
+  }
137
+
138
+  async store(screenshot) {
139
+    const stored = await chrome.storage.local.get(this.storage);
140
+    let screenshots = stored[this.storage] || [];
141
+
142
+    // Add new screenshot
143
+    screenshots.unshift(screenshot);
144
+
145
+    // Enforce max limit
146
+    if (screenshots.length > this.maxScreenshots) {
147
+      screenshots = screenshots.slice(0, this.maxScreenshots);
148
+    }
149
+
150
+    // Update storage
151
+    await chrome.storage.local.set({ [this.storage]: screenshots });
152
+
153
+    // Also store metadata separately for quick access
154
+    await this.storeMetadata(screenshot);
155
+
156
+    return screenshot;
157
+  }
158
+
159
+  async storeMetadata(screenshot) {
160
+    const metaKey = `${this.storage}_metadata`;
161
+    const stored = await chrome.storage.local.get(metaKey);
162
+    let metadata = stored[metaKey] || [];
163
+
164
+    metadata.unshift({
165
+      id: screenshot.id,
166
+      timestamp: screenshot.timestamp,
167
+      platform: screenshot.platform,
168
+      url: screenshot.url,
169
+      scammerScore: screenshot.metadata.scammerScore || 0
170
+    });
171
+
172
+    // Keep only last 500 metadata entries
173
+    if (metadata.length > 500) {
174
+      metadata = metadata.slice(0, 500);
175
+    }
176
+
177
+    await chrome.storage.local.set({ [metaKey]: metadata });
178
+  }
179
+
180
+  async get(id) {
181
+    const stored = await chrome.storage.local.get(this.storage);
182
+    const screenshots = stored[this.storage] || [];
183
+    return screenshots.find(s => s.id === id);
184
+  }
185
+
186
+  async getAll(options = {}) {
187
+    const stored = await chrome.storage.local.get(this.storage);
188
+    let screenshots = stored[this.storage] || [];
189
+
190
+    // Apply filters
191
+    if (options.platform) {
192
+      screenshots = screenshots.filter(s => s.platform === options.platform);
193
+    }
194
+
195
+    if (options.startDate) {
196
+      screenshots = screenshots.filter(s =>
197
+        new Date(s.timestamp) >= new Date(options.startDate)
198
+      );
199
+    }
200
+
201
+    if (options.endDate) {
202
+      screenshots = screenshots.filter(s =>
203
+        new Date(s.timestamp) <= new Date(options.endDate)
204
+      );
205
+    }
206
+
207
+    if (options.limit) {
208
+      screenshots = screenshots.slice(0, options.limit);
209
+    }
210
+
211
+    return screenshots;
212
+  }
213
+
214
+  async getMetadata() {
215
+    const metaKey = `${this.storage}_metadata`;
216
+    const stored = await chrome.storage.local.get(metaKey);
217
+    return stored[metaKey] || [];
218
+  }
219
+
220
+  async delete(id) {
221
+    const stored = await chrome.storage.local.get(this.storage);
222
+    let screenshots = stored[this.storage] || [];
223
+
224
+    screenshots = screenshots.filter(s => s.id !== id);
225
+    await chrome.storage.local.set({ [this.storage]: screenshots });
226
+
227
+    // Also remove from metadata
228
+    const metaKey = `${this.storage}_metadata`;
229
+    const metaStored = await chrome.storage.local.get(metaKey);
230
+    let metadata = metaStored[metaKey] || [];
231
+    metadata = metadata.filter(m => m.id !== id);
232
+    await chrome.storage.local.set({ [metaKey]: metadata });
233
+
234
+    return { success: true };
235
+  }
236
+
237
+  async cleanup(maxAge = 24 * 60 * 60 * 1000) {
238
+    const stored = await chrome.storage.local.get(this.storage);
239
+    let screenshots = stored[this.storage] || [];
240
+
241
+    const cutoff = Date.now() - maxAge;
242
+    const before = screenshots.length;
243
+
244
+    screenshots = screenshots.filter(s =>
245
+      new Date(s.timestamp).getTime() > cutoff
246
+    );
247
+
248
+    await chrome.storage.local.set({ [this.storage]: screenshots });
249
+
250
+    const deleted = before - screenshots.length;
251
+    console.log(`[ScreenshotManager] Cleaned up ${deleted} old screenshots`);
252
+
253
+    return { deleted };
254
+  }
255
+
256
+  async export(ids = null) {
257
+    const stored = await chrome.storage.local.get(this.storage);
258
+    let screenshots = stored[this.storage] || [];
259
+
260
+    if (ids) {
261
+      screenshots = screenshots.filter(s => ids.includes(s.id));
262
+    }
263
+
264
+    // Create export data
265
+    const exportData = {
266
+      version: '1.0',
267
+      timestamp: new Date().toISOString(),
268
+      count: screenshots.length,
269
+      screenshots: screenshots.map(s => ({
270
+        id: s.id,
271
+        timestamp: s.timestamp,
272
+        platform: s.platform,
273
+        url: s.url,
274
+        metadata: s.metadata,
275
+        thumbnail: s.thumbnail // Include thumbnail only
276
+      }))
277
+    };
278
+
279
+    return exportData;
280
+  }
281
+
282
+  async getStatistics() {
283
+    const stored = await chrome.storage.local.get(this.storage);
284
+    const screenshots = stored[this.storage] || [];
285
+
286
+    const stats = {
287
+      total: screenshots.length,
288
+      byPlatform: {},
289
+      byDay: {},
290
+      totalSize: 0,
291
+      oldestTimestamp: null,
292
+      newestTimestamp: null
293
+    };
294
+
295
+    screenshots.forEach(s => {
296
+      // By platform
297
+      stats.byPlatform[s.platform] = (stats.byPlatform[s.platform] || 0) + 1;
298
+
299
+      // By day
300
+      const day = new Date(s.timestamp).toDateString();
301
+      stats.byDay[day] = (stats.byDay[day] || 0) + 1;
302
+
303
+      // Size
304
+      stats.totalSize += this.calculateSize(s.dataUrl);
305
+
306
+      // Timestamps
307
+      const timestamp = new Date(s.timestamp).getTime();
308
+      if (!stats.oldestTimestamp || timestamp < stats.oldestTimestamp) {
309
+        stats.oldestTimestamp = s.timestamp;
310
+      }
311
+      if (!stats.newestTimestamp || timestamp > stats.newestTimestamp) {
312
+        stats.newestTimestamp = s.timestamp;
313
+      }
314
+    });
315
+
316
+    return stats;
317
+  }
318
+
319
+  notifyDashboard(screenshot) {
320
+    // Send to dashboard if open
321
+    chrome.runtime.sendMessage({
322
+      type: 'DASHBOARD_UPDATE',
323
+      data: {
324
+        event: 'screenshot_captured',
325
+        screenshot: {
326
+          id: screenshot.id,
327
+          timestamp: screenshot.timestamp,
328
+          platform: screenshot.platform,
329
+          thumbnail: screenshot.thumbnail
330
+        }
331
+      }
332
+    }).catch(() => {
333
+      // Dashboard might not be open
334
+    });
335
+  }
336
+
337
+  async annotate(id, annotations) {
338
+    const screenshot = await this.get(id);
339
+    if (!screenshot) {
340
+      throw new Error('Screenshot not found');
341
+    }
342
+
343
+    // Add annotations
344
+    screenshot.annotations = {
345
+      ...screenshot.annotations,
346
+      ...annotations,
347
+      updatedAt: new Date().toISOString()
348
+    };
349
+
350
+    // Update storage
351
+    const stored = await chrome.storage.local.get(this.storage);
352
+    let screenshots = stored[this.storage] || [];
353
+
354
+    const index = screenshots.findIndex(s => s.id === id);
355
+    if (index !== -1) {
356
+      screenshots[index] = screenshot;
357
+      await chrome.storage.local.set({ [this.storage]: screenshots });
358
+    }
359
+
360
+    return screenshot;
361
+  }
362
+
363
+  async createEvidence Report(screenshotIds, conversationId) {
364
+    const screenshots = await this.getAll();
365
+    const selected = screenshots.filter(s => screenshotIds.includes(s.id));
366
+
367
+    const report = {
368
+      id: `report_${Date.now()}`,
369
+      timestamp: new Date().toISOString(),
370
+      conversationId,
371
+      screenshots: selected.map(s => ({
372
+        id: s.id,
373
+        timestamp: s.timestamp,
374
+        platform: s.platform,
375
+        url: s.url,
376
+        annotations: s.annotations
377
+      })),
378
+      summary: {
379
+        totalScreenshots: selected.length,
380
+        platforms: [...new Set(selected.map(s => s.platform))],
381
+        timeRange: {
382
+          start: selected[selected.length - 1]?.timestamp,
383
+          end: selected[0]?.timestamp
384
+        }
385
+      }
386
+    };
387
+
388
+    // Store report
389
+    const reportKey = 'evidence_reports';
390
+    const stored = await chrome.storage.local.get(reportKey);
391
+    let reports = stored[reportKey] || [];
392
+    reports.unshift(report);
393
+
394
+    // Keep only last 50 reports
395
+    if (reports.length > 50) {
396
+      reports = reports.slice(0, 50);
397
+    }
398
+
399
+    await chrome.storage.local.set({ [reportKey]: reports });
400
+
401
+    return report;
402
+  }
403
+}
404
+
405
+// Export for use in service worker
406
+if (typeof module !== 'undefined' && module.exports) {
407
+  module.exports = ScreenshotManager;
408
+}
extension-chrome/background/service-worker.jsadded
@@ -0,0 +1,393 @@
1
+// Chrome Service Worker for LooseCannon (Manifest V3)
2
+console.log('[LooseCannon] Chrome Service Worker initialized');
3
+
4
+// Import modules
5
+import { UnifiedMessageHandler } from './unified-handler-v3.js';
6
+import { ScreenshotManager } from './screenshot-manager.js';
7
+import { PatternLearning } from './pattern-learning.js';
8
+
9
+class LooseCannonServiceWorker {
10
+  constructor() {
11
+    this.messageHandler = new UnifiedMessageHandler();
12
+    this.screenshotManager = new ScreenshotManager();
13
+    this.patternLearning = new PatternLearning();
14
+    this.serverUrl = 'http://localhost:8765';
15
+    this.analytics = {
16
+      sessionsStarted: 0,
17
+      messagesProcessed: 0,
18
+      scammersDetected: 0,
19
+      screenshotsCaptured: 0
20
+    };
21
+    this.init();
22
+  }
23
+
24
+  init() {
25
+    this.setupListeners();
26
+    this.setupAlarms();
27
+    this.connectToServer();
28
+    this.loadAnalytics();
29
+  }
30
+
31
+  setupListeners() {
32
+    // Message listener for content scripts and popup
33
+    chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
34
+      console.log('[ServiceWorker] Received message:', message.type);
35
+
36
+      // Handle async operations properly in Manifest V3
37
+      this.handleMessage(message, sender).then(sendResponse);
38
+      return true; // Keep channel open for async response
39
+    });
40
+
41
+    // Tab events for tracking
42
+    chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
43
+      if (changeInfo.status === 'complete') {
44
+        this.checkForSupportedSite(tab);
45
+      }
46
+    });
47
+
48
+    // Extension install/update events
49
+    chrome.runtime.onInstalled.addListener((details) => {
50
+      if (details.reason === 'install') {
51
+        this.onFirstInstall();
52
+      } else if (details.reason === 'update') {
53
+        this.onUpdate(details.previousVersion);
54
+      }
55
+    });
56
+
57
+    // Alarm listener for periodic tasks
58
+    chrome.alarms.onAlarm.addListener((alarm) => {
59
+      this.handleAlarm(alarm);
60
+    });
61
+  }
62
+
63
+  async handleMessage(message, sender) {
64
+    try {
65
+      switch (message.type) {
66
+        case 'NEW_MESSAGE':
67
+          return await this.handleNewMessage(message.data, sender);
68
+
69
+        case 'CAPTURE_SCREENSHOT':
70
+          return await this.captureScreenshot(sender.tab);
71
+
72
+        case 'GET_ANALYTICS':
73
+          return await this.getAnalytics();
74
+
75
+        case 'EXPORT_DATA':
76
+          return await this.exportData(message.data);
77
+
78
+        case 'LEARN_PATTERN':
79
+          return await this.learnPattern(message.data);
80
+
81
+        case 'GET_SERVER_STATUS':
82
+          return await this.checkServerStatus();
83
+
84
+        case 'TOGGLE_ACTIVE':
85
+          return await this.handleToggleActive(message.data, sender);
86
+
87
+        default:
88
+          return { error: 'Unknown message type' };
89
+      }
90
+    } catch (error) {
91
+      console.error('[ServiceWorker] Error handling message:', error);
92
+      return { error: error.message };
93
+    }
94
+  }
95
+
96
+  async handleNewMessage(data, sender) {
97
+    this.analytics.messagesProcessed++;
98
+
99
+    // Add Chrome-specific tab info
100
+    data.tabId = sender.tab.id;
101
+    data.url = sender.url;
102
+
103
+    // Process through unified handler
104
+    const response = await this.messageHandler.processMessage(data);
105
+
106
+    // If scammer detected, capture screenshot
107
+    if (response.scammerScore > 0.7) {
108
+      this.analytics.scammersDetected++;
109
+
110
+      // Capture evidence
111
+      const screenshot = await this.captureScreenshot(sender.tab);
112
+      if (screenshot.success) {
113
+        response.evidenceId = screenshot.id;
114
+      }
115
+
116
+      // Learn from pattern
117
+      await this.patternLearning.addPattern(data, response.scammerScore);
118
+    }
119
+
120
+    // Update analytics
121
+    await this.saveAnalytics();
122
+
123
+    return response;
124
+  }
125
+
126
+  async captureScreenshot(tab) {
127
+    if (!tab) return { success: false, error: 'No tab provided' };
128
+
129
+    try {
130
+      // Chrome-specific screenshot API
131
+      const dataUrl = await chrome.tabs.captureVisibleTab(tab.windowId, {
132
+        format: 'png',
133
+        quality: 90
134
+      });
135
+
136
+      // Store screenshot
137
+      const screenshot = await this.screenshotManager.store({
138
+        dataUrl,
139
+        tabId: tab.id,
140
+        url: tab.url,
141
+        timestamp: new Date().toISOString()
142
+      });
143
+
144
+      this.analytics.screenshotsCaptured++;
145
+
146
+      return {
147
+        success: true,
148
+        id: screenshot.id,
149
+        size: screenshot.size
150
+      };
151
+    } catch (error) {
152
+      console.error('[ServiceWorker] Screenshot error:', error);
153
+      return { success: false, error: error.message };
154
+    }
155
+  }
156
+
157
+  async checkForSupportedSite(tab) {
158
+    const supportedSites = [
159
+      'web.whatsapp.com',
160
+      'web.telegram.org',
161
+      'messenger.com'
162
+    ];
163
+
164
+    const isSupported = supportedSites.some(site => tab.url?.includes(site));
165
+
166
+    if (isSupported) {
167
+      // Show page action or badge
168
+      chrome.action.setBadgeText({
169
+        text: 'LC',
170
+        tabId: tab.id
171
+      });
172
+
173
+      chrome.action.setBadgeBackgroundColor({
174
+        color: '#4CAF50',
175
+        tabId: tab.id
176
+      });
177
+    }
178
+  }
179
+
180
+  setupAlarms() {
181
+    // Set up periodic tasks
182
+    chrome.alarms.create('analyticsSync', { periodInMinutes: 5 });
183
+    chrome.alarms.create('patternSync', { periodInMinutes: 30 });
184
+    chrome.alarms.create('cleanup', { periodInMinutes: 60 });
185
+  }
186
+
187
+  async handleAlarm(alarm) {
188
+    switch (alarm.name) {
189
+      case 'analyticsSync':
190
+        await this.syncAnalytics();
191
+        break;
192
+
193
+      case 'patternSync':
194
+        await this.syncPatterns();
195
+        break;
196
+
197
+      case 'cleanup':
198
+        await this.cleanup();
199
+        break;
200
+    }
201
+  }
202
+
203
+  async syncAnalytics() {
204
+    try {
205
+      // Send analytics to server
206
+      const response = await fetch(`${this.serverUrl}/analytics/sync`, {
207
+        method: 'POST',
208
+        headers: { 'Content-Type': 'application/json' },
209
+        body: JSON.stringify({
210
+          analytics: this.analytics,
211
+          timestamp: new Date().toISOString()
212
+        })
213
+      });
214
+
215
+      if (response.ok) {
216
+        console.log('[ServiceWorker] Analytics synced');
217
+      }
218
+    } catch (error) {
219
+      console.error('[ServiceWorker] Analytics sync failed:', error);
220
+    }
221
+  }
222
+
223
+  async syncPatterns() {
224
+    try {
225
+      const patterns = await this.patternLearning.getPatterns();
226
+
227
+      // Send learned patterns to server
228
+      const response = await fetch(`${this.serverUrl}/patterns/sync`, {
229
+        method: 'POST',
230
+        headers: { 'Content-Type': 'application/json' },
231
+        body: JSON.stringify({ patterns })
232
+      });
233
+
234
+      if (response.ok) {
235
+        const serverPatterns = await response.json();
236
+        await this.patternLearning.updateFromServer(serverPatterns);
237
+        console.log('[ServiceWorker] Patterns synced');
238
+      }
239
+    } catch (error) {
240
+      console.error('[ServiceWorker] Pattern sync failed:', error);
241
+    }
242
+  }
243
+
244
+  async cleanup() {
245
+    // Clean up old screenshots
246
+    await this.screenshotManager.cleanup(24 * 60 * 60 * 1000); // 24 hours
247
+
248
+    // Clean up old conversation data
249
+    const storage = await chrome.storage.local.get();
250
+    const now = Date.now();
251
+    const maxAge = 7 * 24 * 60 * 60 * 1000; // 7 days
252
+
253
+    for (const key in storage) {
254
+      if (key.startsWith('conversation_')) {
255
+        const data = storage[key];
256
+        if (data.timestamp && (now - new Date(data.timestamp).getTime() > maxAge)) {
257
+          await chrome.storage.local.remove(key);
258
+        }
259
+      }
260
+    }
261
+
262
+    console.log('[ServiceWorker] Cleanup completed');
263
+  }
264
+
265
+  async connectToServer() {
266
+    try {
267
+      const response = await fetch(`${this.serverUrl}/status`);
268
+      if (response.ok) {
269
+        console.log('[ServiceWorker] Server connected');
270
+        chrome.action.setTitle({ title: 'LooseCannon - Connected' });
271
+      } else {
272
+        throw new Error('Server not responding');
273
+      }
274
+    } catch (error) {
275
+      console.error('[ServiceWorker] Server connection failed:', error);
276
+      chrome.action.setTitle({ title: 'LooseCannon - Server Offline' });
277
+
278
+      // Retry in 30 seconds
279
+      setTimeout(() => this.connectToServer(), 30000);
280
+    }
281
+  }
282
+
283
+  async getAnalytics() {
284
+    const stored = await chrome.storage.local.get('analytics');
285
+    return {
286
+      ...this.analytics,
287
+      ...stored.analytics,
288
+      uptime: Date.now() - this.startTime
289
+    };
290
+  }
291
+
292
+  async saveAnalytics() {
293
+    await chrome.storage.local.set({ analytics: this.analytics });
294
+  }
295
+
296
+  async loadAnalytics() {
297
+    const stored = await chrome.storage.local.get('analytics');
298
+    if (stored.analytics) {
299
+      this.analytics = { ...this.analytics, ...stored.analytics };
300
+    }
301
+    this.startTime = Date.now();
302
+  }
303
+
304
+  async exportData(options = {}) {
305
+    const data = {
306
+      version: '0.3.0',
307
+      timestamp: new Date().toISOString(),
308
+      analytics: this.analytics,
309
+      patterns: await this.patternLearning.getPatterns(),
310
+      screenshots: await this.screenshotManager.getAll()
311
+    };
312
+
313
+    if (options.includeConversations) {
314
+      const storage = await chrome.storage.local.get();
315
+      data.conversations = Object.entries(storage)
316
+        .filter(([key]) => key.startsWith('conversation_'))
317
+        .map(([key, value]) => value);
318
+    }
319
+
320
+    // Create download
321
+    const blob = new Blob([JSON.stringify(data, null, 2)], {
322
+      type: 'application/json'
323
+    });
324
+
325
+    const url = URL.createObjectURL(blob);
326
+    const filename = `loosecannon-export-${Date.now()}.json`;
327
+
328
+    await chrome.downloads.download({
329
+      url,
330
+      filename,
331
+      saveAs: true
332
+    });
333
+
334
+    return { success: true, filename };
335
+  }
336
+
337
+  async handleToggleActive(data, sender) {
338
+    this.analytics.sessionsStarted++;
339
+    await this.saveAnalytics();
340
+
341
+    // Notify dashboard if open
342
+    chrome.runtime.sendMessage({
343
+      type: 'DASHBOARD_UPDATE',
344
+      data: {
345
+        event: 'session_toggle',
346
+        active: data.isActive,
347
+        platform: data.platform,
348
+        tabId: sender.tab?.id
349
+      }
350
+    }).catch(() => {
351
+      // Dashboard might not be open
352
+    });
353
+
354
+    return { success: true };
355
+  }
356
+
357
+  async checkServerStatus() {
358
+    try {
359
+      const response = await fetch(`${this.serverUrl}/status`);
360
+      const data = await response.json();
361
+      return { connected: true, ...data };
362
+    } catch (error) {
363
+      return { connected: false, error: error.message };
364
+    }
365
+  }
366
+
367
+  onFirstInstall() {
368
+    // Open welcome page
369
+    chrome.tabs.create({
370
+      url: chrome.runtime.getURL('dashboard/welcome.html')
371
+    });
372
+  }
373
+
374
+  onUpdate(previousVersion) {
375
+    console.log(`[ServiceWorker] Updated from ${previousVersion} to 0.3.0`);
376
+
377
+    // Show update notification
378
+    chrome.notifications.create({
379
+      type: 'basic',
380
+      iconUrl: 'icons/icon-128.png',
381
+      title: 'LooseCannon Updated',
382
+      message: 'New features: Chrome support, Analytics Dashboard, and more!'
383
+    });
384
+  }
385
+}
386
+
387
+// Initialize service worker
388
+const serviceWorker = new LooseCannonServiceWorker();
389
+
390
+// Export for testing
391
+if (typeof module !== 'undefined' && module.exports) {
392
+  module.exports = LooseCannonServiceWorker;
393
+}
extension-chrome/background/unified-handler-v3.jsadded
@@ -0,0 +1,293 @@
1
+// Unified Message Handler for Chrome Manifest V3
2
+// Handles messages from all supported platforms
3
+
4
+export class UnifiedMessageHandler {
5
+  constructor() {
6
+    this.serverUrl = 'http://localhost:8765';
7
+    this.activeConversations = new Map();
8
+    this.responseQueue = [];
9
+    this.isProcessing = false;
10
+    this.settings = {
11
+      autoActivateOnScammer: true,
12
+      scammerThreshold: 0.7,
13
+      maxResponseDelay: 15000,
14
+      minResponseDelay: 2000,
15
+      personalityRotation: false
16
+    };
17
+  }
18
+
19
+  async processMessage(data) {
20
+    try {
21
+      // Add to conversation on server
22
+      const contextResponse = await fetch(`${this.serverUrl}/conversation/add`, {
23
+        method: 'POST',
24
+        headers: { 'Content-Type': 'application/json' },
25
+        body: JSON.stringify({
26
+          chatId: data.chatId,
27
+          platform: data.platform || this.detectPlatform(data.url),
28
+          message: data
29
+        })
30
+      });
31
+
32
+      const context = await contextResponse.json();
33
+
34
+      // Check for auto-activation
35
+      if (this.settings.autoActivateOnScammer &&
36
+          context.scammerScore > this.settings.scammerThreshold &&
37
+          !this.isConversationActive(data.chatId, data.platform)) {
38
+
39
+        this.activateConversation(data.chatId, data.platform, data.tabId);
40
+
41
+        // Notify content script
42
+        chrome.tabs.sendMessage(data.tabId, {
43
+          type: 'SCAMMER_DETECTED',
44
+          data: {
45
+            score: context.scammerScore,
46
+            autoActivated: true
47
+          }
48
+        });
49
+      }
50
+
51
+      // Only generate response if active
52
+      if (!this.isConversationActive(data.chatId, data.platform)) {
53
+        return {
54
+          processed: false,
55
+          reason: 'Conversation not active',
56
+          scammerScore: context.scammerScore
57
+        };
58
+      }
59
+
60
+      // Get response suggestions
61
+      const suggestionsResponse = await fetch(`${this.serverUrl}/suggestions`, {
62
+        method: 'POST',
63
+        headers: { 'Content-Type': 'application/json' },
64
+        body: JSON.stringify({
65
+          chatId: data.chatId,
66
+          platform: data.platform,
67
+          context
68
+        })
69
+      });
70
+
71
+      const suggestions = await suggestionsResponse.json();
72
+
73
+      // Select personality
74
+      const personality = await this.selectPersonality(data.chatId, context);
75
+
76
+      // Generate response
77
+      const generateResponse = await fetch(`${this.serverUrl}/generate`, {
78
+        method: 'POST',
79
+        headers: { 'Content-Type': 'application/json' },
80
+        body: JSON.stringify({
81
+          message: data.content || data.text,
82
+          personality,
83
+          chatId: data.chatId,
84
+          platform: data.platform || this.detectPlatform(data.url),
85
+          context,
86
+          suggestions,
87
+          timestamp: data.timestamp
88
+        })
89
+      });
90
+
91
+      const result = await generateResponse.json();
92
+
93
+      // Calculate delay
94
+      const delay = this.calculateResponseDelay(result.reply, context);
95
+
96
+      // Queue response
97
+      await this.queueResponse({
98
+        tabId: data.tabId,
99
+        platform: data.platform || this.detectPlatform(data.url),
100
+        chatId: data.chatId,
101
+        reply: result.reply,
102
+        delay,
103
+        personality
104
+      });
105
+
106
+      return {
107
+        queued: true,
108
+        reply: result.reply,
109
+        delay,
110
+        personality,
111
+        scammerScore: context.scammerScore
112
+      };
113
+
114
+    } catch (error) {
115
+      console.error('[UnifiedHandler] Error:', error);
116
+
117
+      const fallback = this.getFallbackResponse(data.platform);
118
+      return {
119
+        reply: fallback,
120
+        delay: 3000,
121
+        error: error.message
122
+      };
123
+    }
124
+  }
125
+
126
+  detectPlatform(url) {
127
+    if (!url) return 'unknown';
128
+
129
+    if (url.includes('whatsapp.com')) return 'whatsapp';
130
+    if (url.includes('telegram.org')) return 'telegram';
131
+    if (url.includes('messenger.com')) return 'messenger';
132
+
133
+    return 'unknown';
134
+  }
135
+
136
+  async selectPersonality(chatId, context) {
137
+    if (!this.settings.personalityRotation) {
138
+      return context.personality || 'default';
139
+    }
140
+
141
+    // Load available personalities
142
+    const stored = await chrome.storage.local.get('personalities');
143
+    const personalities = stored.personalities || ['default'];
144
+
145
+    // Rotate based on message count
146
+    const messageCount = context.messageCount || 0;
147
+    const index = Math.floor(messageCount / 5) % personalities.length;
148
+
149
+    return personalities[index];
150
+  }
151
+
152
+  calculateResponseDelay(text, context) {
153
+    const wordCount = text.split(' ').length;
154
+    const baseDelay = this.settings.minResponseDelay;
155
+    const perWordDelay = 150;
156
+
157
+    let delay = baseDelay + (wordCount * perWordDelay);
158
+
159
+    // Adjust based on context
160
+    if (context.conversationTone === 'urgent') {
161
+      delay *= 1.5; // Be slower for urgent scammers
162
+    }
163
+
164
+    // Add random variation
165
+    const variation = 0.3;
166
+    const randomFactor = 1 + (Math.random() * 2 * variation - variation);
167
+    delay *= randomFactor;
168
+
169
+    return Math.min(delay, this.settings.maxResponseDelay);
170
+  }
171
+
172
+  async queueResponse(response) {
173
+    this.responseQueue.push({
174
+      ...response,
175
+      queuedAt: Date.now()
176
+    });
177
+
178
+    if (!this.isProcessing) {
179
+      this.processQueue();
180
+    }
181
+  }
182
+
183
+  async processQueue() {
184
+    if (this.responseQueue.length === 0) {
185
+      this.isProcessing = false;
186
+      return;
187
+    }
188
+
189
+    this.isProcessing = true;
190
+    const response = this.responseQueue.shift();
191
+
192
+    // Calculate remaining delay
193
+    const elapsed = Date.now() - response.queuedAt;
194
+    const remainingDelay = Math.max(0, response.delay - elapsed);
195
+
196
+    // Wait
197
+    await this.sleep(remainingDelay);
198
+
199
+    // Send response
200
+    try {
201
+      await chrome.tabs.sendMessage(response.tabId, {
202
+        type: 'SEND_MESSAGE',
203
+        data: {
204
+          text: response.reply,
205
+          delay: 0
206
+        }
207
+      });
208
+
209
+      console.log(`[UnifiedHandler] Sent response to ${response.platform}:${response.chatId}`);
210
+    } catch (error) {
211
+      console.error('[UnifiedHandler] Failed to send response:', error);
212
+    }
213
+
214
+    // Process next
215
+    this.processQueue();
216
+  }
217
+
218
+  activateConversation(chatId, platform, tabId) {
219
+    const key = `${platform}:${chatId}`;
220
+    this.activeConversations.set(key, {
221
+      chatId,
222
+      platform,
223
+      tabId,
224
+      activatedAt: Date.now(),
225
+      messageCount: 0
226
+    });
227
+
228
+    console.log(`[UnifiedHandler] Activated ${key}`);
229
+  }
230
+
231
+  deactivateConversation(chatId, platform) {
232
+    const key = `${platform}:${chatId}`;
233
+    this.activeConversations.delete(key);
234
+    console.log(`[UnifiedHandler] Deactivated ${key}`);
235
+  }
236
+
237
+  isConversationActive(chatId, platform) {
238
+    const key = `${platform}:${chatId}`;
239
+    return this.activeConversations.has(key);
240
+  }
241
+
242
+  getFallbackResponse(platform) {
243
+    const fallbacks = {
244
+      whatsapp: [
245
+        "Sorry, what was that? My WhatsApp is acting strange.",
246
+        "Can you repeat? The message came through garbled.",
247
+        "Hold on, my phone is being slow..."
248
+      ],
249
+      telegram: [
250
+        "Telegram is glitching for me, one second...",
251
+        "Strange, I'm getting errors. What did you say?",
252
+        "My Telegram is updating, please wait..."
253
+      ],
254
+      messenger: [
255
+        "Facebook is being weird, can you resend?",
256
+        "Messenger crashed, what were you saying?",
257
+        "Sorry, Facebook is slow today..."
258
+      ],
259
+      default: [
260
+        "I didn't catch that, can you repeat?",
261
+        "Sorry, technical difficulties...",
262
+        "One moment please..."
263
+      ]
264
+    };
265
+
266
+    const platformFallbacks = fallbacks[platform] || fallbacks.default;
267
+    return platformFallbacks[Math.floor(Math.random() * platformFallbacks.length)];
268
+  }
269
+
270
+  async getStatistics() {
271
+    const stats = {
272
+      activeConversations: this.activeConversations.size,
273
+      queuedResponses: this.responseQueue.length,
274
+      platformBreakdown: {}
275
+    };
276
+
277
+    this.activeConversations.forEach(conv => {
278
+      stats.platformBreakdown[conv.platform] =
279
+        (stats.platformBreakdown[conv.platform] || 0) + 1;
280
+    });
281
+
282
+    return stats;
283
+  }
284
+
285
+  sleep(ms) {
286
+    return new Promise(resolve => setTimeout(resolve, ms));
287
+  }
288
+}
289
+
290
+// Export for use in service worker
291
+if (typeof module !== 'undefined' && module.exports) {
292
+  module.exports = UnifiedMessageHandler;
293
+}
extension-chrome/manifest.jsonadded
@@ -0,0 +1,80 @@
1
+{
2
+  "manifest_version": 3,
3
+  "name": "LooseCannon",
4
+  "version": "0.3.0",
5
+  "description": "Automated scambaiting assistant - Now with Chrome support",
6
+
7
+  "permissions": [
8
+    "storage",
9
+    "tabs",
10
+    "scripting",
11
+    "webRequest",
12
+    "declarativeNetRequest"
13
+  ],
14
+
15
+  "host_permissions": [
16
+    "*://web.whatsapp.com/*",
17
+    "*://web.telegram.org/*",
18
+    "*://webk.telegram.org/*",
19
+    "*://webz.telegram.org/*",
20
+    "*://www.messenger.com/*",
21
+    "*://messenger.com/*",
22
+    "http://localhost:8765/*"
23
+  ],
24
+
25
+  "background": {
26
+    "service_worker": "background/service-worker.js",
27
+    "type": "module"
28
+  },
29
+
30
+  "content_scripts": [
31
+    {
32
+      "matches": ["*://web.whatsapp.com/*"],
33
+      "js": ["content-scripts/whatsapp-enhanced.js"],
34
+      "css": ["content-scripts/whatsapp.css"],
35
+      "run_at": "document_idle"
36
+    },
37
+    {
38
+      "matches": [
39
+        "*://web.telegram.org/*",
40
+        "*://webk.telegram.org/*",
41
+        "*://webz.telegram.org/*"
42
+      ],
43
+      "js": ["content-scripts/telegram.js"],
44
+      "run_at": "document_idle"
45
+    },
46
+    {
47
+      "matches": [
48
+        "*://www.messenger.com/*",
49
+        "*://messenger.com/*"
50
+      ],
51
+      "js": ["content-scripts/messenger.js"],
52
+      "run_at": "document_idle"
53
+    }
54
+  ],
55
+
56
+  "action": {
57
+    "default_popup": "popup/popup.html",
58
+    "default_icon": {
59
+      "16": "icons/icon-16.png",
60
+      "48": "icons/icon-48.png",
61
+      "128": "icons/icon-128.png"
62
+    }
63
+  },
64
+
65
+  "icons": {
66
+    "16": "icons/icon-16.png",
67
+    "48": "icons/icon-48.png",
68
+    "128": "icons/icon-128.png"
69
+  },
70
+
71
+  "web_accessible_resources": [
72
+    {
73
+      "resources": [
74
+        "icons/*",
75
+        "content-scripts/*.css"
76
+      ],
77
+      "matches": ["<all_urls>"]
78
+    }
79
+  ]
80
+}