zeroed-some/loosecannon / e8b5f80

Browse files

init. firefox ext. w. content script, worker, popup, dom integ, indicators. local server for local llm on 8765. three personalities

Authored by espadonne
SHA
e8b5f80867c06e8aa5cbcf8b108b3787646610e9
Tree
8b56e99

19 changed files

StatusFile+-
A .env.example 15 0
A .gitignore 37 0
A README.md 124 0
A extension/background/background.js 184 0
A extension/content-scripts/whatsapp.css 26 0
A extension/content-scripts/whatsapp.js 273 0
A extension/icons/icon-128.png 0 0
A extension/icons/icon-16.png 0 0
A extension/icons/icon-48.png 0 0
A extension/icons/icon.svg 11 0
A extension/manifest.json 42 0
A extension/popup/popup.html 278 0
A extension/popup/popup.js 245 0
A package.json 33 0
A server/personalities/confused-elder.json 21 0
A server/personalities/conspiracy-theorist.json 21 0
A server/personalities/tech-support-nightmare.json 21 0
A server/src/index.js 205 0
A setup.sh 96 0
.env.exampleadded
@@ -0,0 +1,15 @@
1
+# LooseCannon Configuration
2
+
3
+# Server port (default: 8765)
4
+PORT=8765
5
+
6
+# Ollama API URL (default: http://localhost:11434)
7
+OLLAMA_URL=http://localhost:11434
8
+
9
+# Ollama model to use (default: llama2)
10
+# You can use any model available in Ollama: llama2, mistral, codellama, etc.
11
+# Run 'ollama list' to see available models
12
+OLLAMA_MODEL=llama2
13
+
14
+# Debug mode (shows extra logging)
15
+DEBUG=false
.gitignoreadded
@@ -0,0 +1,37 @@
1
+# Dependencies
2
+node_modules/
3
+package-lock.json
4
+
5
+# Environment files
6
+.env
7
+.env.local
8
+.env.production
9
+
10
+# Build outputs
11
+web-ext-artifacts/
12
+dist/
13
+build/
14
+
15
+# IDE
16
+.vscode/
17
+.idea/
18
+*.swp
19
+*.swo
20
+.DS_Store
21
+
22
+# Logs
23
+*.log
24
+npm-debug.log*
25
+
26
+# Firefox profiles
27
+firefox-profile/
28
+loosecannon-profile/
29
+
30
+# Temporary files
31
+tmp/
32
+temp/
33
+*.tmp
34
+
35
+# Conversation logs (privacy)
36
+conversations/
37
+logs/
README.mdadded
@@ -0,0 +1,124 @@
1
+# LooseCannon 🤖
2
+
3
+Automated scambaiting assistant that integrates with messaging platforms to waste scammers' time using local LLMs.
4
+
5
+## ⚠️ Legal Disclaimer
6
+
7
+This tool is for educational and defensive security purposes only. Use responsibly and be aware that automation may violate platform Terms of Service. Your account could be banned.
8
+
9
+## Features
10
+
11
+- 🦊 Firefox browser extension
12
+- 💬 WhatsApp Web integration (more platforms coming)
13
+- 🤖 Local LLM support via Ollama
14
+- 🎭 Multiple scambaiter personalities
15
+- 🛑 Emergency stop functionality
16
+- 📊 Conversation logging
17
+
18
+## Project Structure
19
+
20
+```
21
+LooseCannon/
22
+├── extension/          # Firefox browser extension
23
+│   ├── manifest.json
24
+│   ├── content-scripts/
25
+│   ├── background/
26
+│   └── popup/
27
+├── server/            # Local server for LLM integration
28
+│   ├── src/
29
+│   └── personalities/
30
+└── package.json
31
+```
32
+
33
+## Quick Start
34
+
35
+### Prerequisites
36
+
37
+1. **Ollama** - Install from https://ollama.ai
38
+2. **Node.js** - Version 18+ recommended
39
+3. **Firefox Developer Edition** (recommended for extension development)
40
+
41
+### Installation
42
+
43
+1. Clone the repository:
44
+```bash
45
+cd ~/Documents/GithubOrgs/zeroed-some/LooseCannon
46
+```
47
+
48
+2. Install dependencies:
49
+```bash
50
+npm install
51
+```
52
+
53
+3. Start Ollama with a model:
54
+```bash
55
+ollama pull llama2
56
+ollama serve
57
+```
58
+
59
+4. Start the local server:
60
+```bash
61
+npm run dev:server
62
+```
63
+
64
+5. Load the extension in Firefox:
65
+```bash
66
+npm run dev:extension
67
+```
68
+
69
+Or manually:
70
+- Open Firefox and navigate to `about:debugging`
71
+- Click "This Firefox"
72
+- Click "Load Temporary Add-on"
73
+- Select `extension/manifest.json`
74
+
75
+## Usage
76
+
77
+1. Navigate to WhatsApp Web (https://web.whatsapp.com)
78
+2. Click the LooseCannon button in the bottom right
79
+3. Select a personality from the popup
80
+4. Toggle "LC: ON" to activate
81
+5. The bot will automatically respond to incoming messages in the current chat
82
+
83
+## Development Roadmap
84
+
85
+### Phase 1: Foundation ✅
86
+- Firefox extension skeleton
87
+- Basic WhatsApp Web integration
88
+
89
+### Phase 2: Message Interception (Current)
90
+- DOM manipulation for reading messages
91
+- Message injection system
92
+
93
+### Phase 3: LLM Integration
94
+- Ollama server connection
95
+- Response generation
96
+
97
+### Phase 4: Scambaiter Logic
98
+- Personality system
99
+- Conversation strategies
100
+
101
+### Phase 5: Polish
102
+- Chrome support
103
+- Additional platforms
104
+- Advanced features
105
+
106
+## Safety Features
107
+
108
+- Manual activation per chat (no automatic activation)
109
+- Visual indicators when active
110
+- Emergency stop button
111
+- Conversation logging for review
112
+- Rate limiting to avoid detection
113
+
114
+## Contributing
115
+
116
+This project is in active development. Issues and PRs welcome!
117
+
118
+## License
119
+
120
+MIT - See LICENSE file for details
121
+
122
+## Acknowledgments
123
+
124
+Inspired by the scambaiting community and projects like Kitboga's work.
extension/background/background.jsadded
@@ -0,0 +1,184 @@
1
+// Background script for LooseCannon
2
+console.log('[LooseCannon Background] Initialized');
3
+
4
+class LooseCannonBackground {
5
+  constructor() {
6
+    this.serverUrl = 'http://localhost:8765';  // Local server URL
7
+    this.isConnected = false;
8
+    this.activeTabs = new Map();
9
+    this.personalities = [];
10
+    this.currentPersonality = 'default';
11
+    this.init();
12
+  }
13
+
14
+  init() {
15
+    this.setupMessageListeners();
16
+    this.checkServerConnection();
17
+    this.loadSettings();
18
+  }
19
+
20
+  setupMessageListeners() {
21
+    browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
22
+      console.log('[LooseCannon Background] Received message:', message.type);
23
+
24
+      switch (message.type) {
25
+        case 'NEW_MESSAGE':
26
+          this.handleNewMessage(message.data, sender.tab.id)
27
+            .then(sendResponse)
28
+            .catch(error => {
29
+              console.error('Error handling message:', error);
30
+              sendResponse({ error: error.message });
31
+            });
32
+          return true; // Keep channel open for async response
33
+
34
+        case 'TOGGLE_ACTIVE':
35
+          this.handleToggleActive(sender.tab.id, message.data.isActive);
36
+          sendResponse({ success: true });
37
+          break;
38
+
39
+        case 'GET_SERVER_STATUS':
40
+          sendResponse({ connected: this.isConnected });
41
+          break;
42
+
43
+        case 'GET_PERSONALITIES':
44
+          sendResponse({ personalities: this.personalities });
45
+          break;
46
+
47
+        case 'SET_PERSONALITY':
48
+          this.currentPersonality = message.data.personality;
49
+          this.saveSettings();
50
+          sendResponse({ success: true });
51
+          break;
52
+
53
+        default:
54
+          console.warn('Unknown message type:', message.type);
55
+      }
56
+    });
57
+  }
58
+
59
+  async handleNewMessage(data, tabId) {
60
+    if (!this.isConnected) {
61
+      console.warn('[LooseCannon] Server not connected, cannot process message');
62
+      return { error: 'Server not connected' };
63
+    }
64
+
65
+    try {
66
+      // Send message to local server for LLM processing
67
+      const response = await fetch(`${this.serverUrl}/generate`, {
68
+        method: 'POST',
69
+        headers: {
70
+          'Content-Type': 'application/json',
71
+        },
72
+        body: JSON.stringify({
73
+          message: data.text,
74
+          personality: this.currentPersonality,
75
+          chatId: data.chatId,
76
+          timestamp: data.timestamp
77
+        })
78
+      });
79
+
80
+      if (!response.ok) {
81
+        throw new Error(`Server error: ${response.status}`);
82
+      }
83
+
84
+      const result = await response.json();
85
+      console.log('[LooseCannon] Generated response:', result.reply);
86
+
87
+      // Log conversation for debugging
88
+      this.logConversation(data.chatId, data.text, result.reply);
89
+
90
+      return { reply: result.reply };
91
+    } catch (error) {
92
+      console.error('[LooseCannon] Error generating response:', error);
93
+      return { error: error.message };
94
+    }
95
+  }
96
+
97
+  handleToggleActive(tabId, isActive) {
98
+    if (isActive) {
99
+      this.activeTabs.set(tabId, {
100
+        activated: new Date().toISOString(),
101
+        messageCount: 0
102
+      });
103
+      console.log(`[LooseCannon] Activated for tab ${tabId}`);
104
+    } else {
105
+      this.activeTabs.delete(tabId);
106
+      console.log(`[LooseCannon] Deactivated for tab ${tabId}`);
107
+    }
108
+  }
109
+
110
+  async checkServerConnection() {
111
+    try {
112
+      const response = await fetch(`${this.serverUrl}/status`);
113
+      if (response.ok) {
114
+        const data = await response.json();
115
+        this.isConnected = true;
116
+        this.personalities = data.personalities || [];
117
+        console.log('[LooseCannon] Server connected, personalities:', this.personalities);
118
+      } else {
119
+        this.isConnected = false;
120
+        console.warn('[LooseCannon] Server responded with error:', response.status);
121
+      }
122
+    } catch (error) {
123
+      this.isConnected = false;
124
+      console.error('[LooseCannon] Could not connect to server:', error);
125
+    }
126
+
127
+    // Retry connection every 5 seconds if not connected
128
+    if (!this.isConnected) {
129
+      setTimeout(() => this.checkServerConnection(), 5000);
130
+    }
131
+  }
132
+
133
+  async loadSettings() {
134
+    try {
135
+      const settings = await browser.storage.local.get(['personality', 'serverUrl']);
136
+      if (settings.personality) {
137
+        this.currentPersonality = settings.personality;
138
+      }
139
+      if (settings.serverUrl) {
140
+        this.serverUrl = settings.serverUrl;
141
+      }
142
+      console.log('[LooseCannon] Settings loaded:', settings);
143
+    } catch (error) {
144
+      console.error('[LooseCannon] Error loading settings:', error);
145
+    }
146
+  }
147
+
148
+  async saveSettings() {
149
+    try {
150
+      await browser.storage.local.set({
151
+        personality: this.currentPersonality,
152
+        serverUrl: this.serverUrl
153
+      });
154
+      console.log('[LooseCannon] Settings saved');
155
+    } catch (error) {
156
+      console.error('[LooseCannon] Error saving settings:', error);
157
+    }
158
+  }
159
+
160
+  logConversation(chatId, userMessage, botReply) {
161
+    const log = {
162
+      timestamp: new Date().toISOString(),
163
+      chatId,
164
+      userMessage,
165
+      botReply
166
+    };
167
+
168
+    // Store conversation logs (with limit to prevent storage overflow)
169
+    browser.storage.local.get('conversationLogs').then(result => {
170
+      let logs = result.conversationLogs || [];
171
+      logs.push(log);
172
+
173
+      // Keep only last 100 messages
174
+      if (logs.length > 100) {
175
+        logs = logs.slice(-100);
176
+      }
177
+
178
+      browser.storage.local.set({ conversationLogs: logs });
179
+    });
180
+  }
181
+}
182
+
183
+// Initialize background script
184
+const looseCannonBg = new LooseCannonBackground();
extension/content-scripts/whatsapp.cssadded
@@ -0,0 +1,26 @@
1
+/* Custom styles for LooseCannon WhatsApp integration */
2
+
3
+/* Ensure our controls stay visible */
4
+.loosecannon-toggle,
5
+.loosecannon-indicator {
6
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
7
+}
8
+
9
+/* Highlight active conversations when LooseCannon is active */
10
+body[data-loosecannon-active="true"] [data-testid="conversation-panel-wrapper"] {
11
+  border-left: 3px solid #44ff44;
12
+}
13
+
14
+/* Visual feedback for automated messages */
15
+.loosecannon-automated-message {
16
+  position: relative;
17
+}
18
+
19
+.loosecannon-automated-message::after {
20
+  content: "🤖";
21
+  position: absolute;
22
+  top: -5px;
23
+  right: -5px;
24
+  font-size: 12px;
25
+  opacity: 0.5;
26
+}
extension/content-scripts/whatsapp.jsadded
@@ -0,0 +1,273 @@
1
+// WhatsApp Web Content Script for LooseCannon
2
+console.log('[LooseCannon] WhatsApp content script loaded');
3
+
4
+class WhatsAppIntegration {
5
+  constructor() {
6
+    this.isActive = false;
7
+    this.currentChat = null;
8
+    this.messageObserver = null;
9
+    this.init();
10
+  }
11
+
12
+  init() {
13
+    // Wait for WhatsApp to fully load
14
+    this.waitForWhatsApp().then(() => {
15
+      console.log('[LooseCannon] WhatsApp detected and ready');
16
+      this.setupMessageObserver();
17
+      this.injectControls();
18
+      this.listenForCommands();
19
+    });
20
+  }
21
+
22
+  waitForWhatsApp() {
23
+    return new Promise((resolve) => {
24
+      const checkForApp = setInterval(() => {
25
+        // Look for WhatsApp's main app wrapper
26
+        const mainWrapper = document.querySelector('[data-testid="conversation-panel-wrapper"]');
27
+        if (mainWrapper) {
28
+          clearInterval(checkForApp);
29
+          resolve();
30
+        }
31
+      }, 1000);
32
+    });
33
+  }
34
+
35
+  setupMessageObserver() {
36
+    // Observe the message list for new messages
37
+    const messageContainer = document.querySelector('[role="application"]');
38
+
39
+    if (!messageContainer) {
40
+      console.error('[LooseCannon] Could not find message container');
41
+      return;
42
+    }
43
+
44
+    const config = {
45
+      childList: true,
46
+      subtree: true,
47
+      characterData: true
48
+    };
49
+
50
+    this.messageObserver = new MutationObserver((mutations) => {
51
+      if (!this.isActive) return;
52
+
53
+      mutations.forEach((mutation) => {
54
+        // Check if new message nodes were added
55
+        if (mutation.type === 'childList') {
56
+          mutation.addedNodes.forEach((node) => {
57
+            if (this.isIncomingMessage(node)) {
58
+              this.handleIncomingMessage(node);
59
+            }
60
+          });
61
+        }
62
+      });
63
+    });
64
+
65
+    this.messageObserver.observe(messageContainer, config);
66
+    console.log('[LooseCannon] Message observer setup complete');
67
+  }
68
+
69
+  isIncomingMessage(node) {
70
+    // Check if this is an incoming message element
71
+    // WhatsApp uses specific classes for incoming vs outgoing messages
72
+    if (!node.querySelector) return false;
73
+
74
+    const messageElement = node.querySelector('[data-testid^="msg-"]');
75
+    if (!messageElement) return false;
76
+
77
+    // Incoming messages don't have the "message-out" class
78
+    return !messageElement.classList.contains('message-out');
79
+  }
80
+
81
+  handleIncomingMessage(node) {
82
+    const messageText = this.extractMessageText(node);
83
+    const timestamp = new Date().toISOString();
84
+
85
+    console.log('[LooseCannon] New message detected:', messageText);
86
+
87
+    // Send to background script for processing
88
+    browser.runtime.sendMessage({
89
+      type: 'NEW_MESSAGE',
90
+      data: {
91
+        text: messageText,
92
+        timestamp: timestamp,
93
+        chatId: this.getCurrentChatId()
94
+      }
95
+    }).then(response => {
96
+      if (response && response.reply) {
97
+        this.sendMessage(response.reply);
98
+      }
99
+    });
100
+  }
101
+
102
+  extractMessageText(node) {
103
+    // Extract text from WhatsApp message element
104
+    const textElement = node.querySelector('[data-testid="msg-container"] .selectable-text');
105
+    return textElement ? textElement.textContent : '';
106
+  }
107
+
108
+  getCurrentChatId() {
109
+    // Get unique identifier for current chat
110
+    const headerElement = document.querySelector('header [data-testid="conversation-header"]');
111
+    if (headerElement) {
112
+      const titleElement = headerElement.querySelector('span[title]');
113
+      return titleElement ? titleElement.title : 'unknown';
114
+    }
115
+    return 'unknown';
116
+  }
117
+
118
+  sendMessage(text) {
119
+    // Find the message input field
120
+    const inputElement = document.querySelector('[data-testid="conversation-compose-box-input"]');
121
+
122
+    if (!inputElement) {
123
+      console.error('[LooseCannon] Could not find message input');
124
+      return;
125
+    }
126
+
127
+    // Focus the input
128
+    inputElement.focus();
129
+
130
+    // Set the message text
131
+    inputElement.textContent = text;
132
+
133
+    // Trigger input event to update WhatsApp's state
134
+    const inputEvent = new InputEvent('input', {
135
+      bubbles: true,
136
+      cancelable: true,
137
+    });
138
+    inputElement.dispatchEvent(inputEvent);
139
+
140
+    // Find and click send button
141
+    setTimeout(() => {
142
+      const sendButton = document.querySelector('[data-testid="compose-btn-send"]');
143
+      if (sendButton) {
144
+        sendButton.click();
145
+        console.log('[LooseCannon] Message sent:', text);
146
+      }
147
+    }, 100);
148
+  }
149
+
150
+  injectControls() {
151
+    // Add toggle button to WhatsApp interface
152
+    const style = document.createElement('style');
153
+    style.textContent = `
154
+      .loosecannon-toggle {
155
+        position: fixed;
156
+        bottom: 20px;
157
+        right: 20px;
158
+        z-index: 9999;
159
+        background: #ff4444;
160
+        color: white;
161
+        border: none;
162
+        border-radius: 50px;
163
+        padding: 12px 20px;
164
+        cursor: pointer;
165
+        font-weight: bold;
166
+        box-shadow: 0 2px 10px rgba(0,0,0,0.3);
167
+        transition: all 0.3s;
168
+      }
169
+
170
+      .loosecannon-toggle.active {
171
+        background: #44ff44;
172
+      }
173
+
174
+      .loosecannon-toggle:hover {
175
+        transform: scale(1.05);
176
+      }
177
+
178
+      .loosecannon-indicator {
179
+        position: fixed;
180
+        top: 70px;
181
+        right: 20px;
182
+        background: rgba(255, 68, 68, 0.9);
183
+        color: white;
184
+        padding: 8px 15px;
185
+        border-radius: 20px;
186
+        font-size: 12px;
187
+        z-index: 9999;
188
+        display: none;
189
+      }
190
+
191
+      .loosecannon-indicator.active {
192
+        display: block;
193
+        background: rgba(68, 255, 68, 0.9);
194
+      }
195
+    `;
196
+    document.head.appendChild(style);
197
+
198
+    const toggleButton = document.createElement('button');
199
+    toggleButton.className = 'loosecannon-toggle';
200
+    toggleButton.textContent = 'LC: OFF';
201
+    toggleButton.onclick = () => this.toggleActive();
202
+    document.body.appendChild(toggleButton);
203
+
204
+    const indicator = document.createElement('div');
205
+    indicator.className = 'loosecannon-indicator';
206
+    indicator.textContent = 'LooseCannon Active';
207
+    document.body.appendChild(indicator);
208
+  }
209
+
210
+  toggleActive() {
211
+    this.isActive = !this.isActive;
212
+    const button = document.querySelector('.loosecannon-toggle');
213
+    const indicator = document.querySelector('.loosecannon-indicator');
214
+
215
+    if (this.isActive) {
216
+      button.classList.add('active');
217
+      button.textContent = 'LC: ON';
218
+      indicator.classList.add('active');
219
+      console.log('[LooseCannon] Activated for current chat');
220
+    } else {
221
+      button.classList.remove('active');
222
+      button.textContent = 'LC: OFF';
223
+      indicator.classList.remove('active');
224
+      console.log('[LooseCannon] Deactivated');
225
+    }
226
+
227
+    // Notify background script
228
+    browser.runtime.sendMessage({
229
+      type: 'TOGGLE_ACTIVE',
230
+      data: { isActive: this.isActive }
231
+    });
232
+  }
233
+
234
+  listenForCommands() {
235
+    // Listen for commands from popup or background script
236
+    browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
237
+      switch (message.type) {
238
+        case 'GET_STATUS':
239
+          sendResponse({ isActive: this.isActive });
240
+          break;
241
+        case 'SET_ACTIVE':
242
+          this.isActive = message.data.isActive;
243
+          this.updateUI();
244
+          break;
245
+        case 'SEND_MESSAGE':
246
+          this.sendMessage(message.data.text);
247
+          break;
248
+      }
249
+    });
250
+  }
251
+
252
+  updateUI() {
253
+    const button = document.querySelector('.loosecannon-toggle');
254
+    const indicator = document.querySelector('.loosecannon-indicator');
255
+
256
+    if (this.isActive) {
257
+      button?.classList.add('active');
258
+      if (button) button.textContent = 'LC: ON';
259
+      indicator?.classList.add('active');
260
+    } else {
261
+      button?.classList.remove('active');
262
+      if (button) button.textContent = 'LC: OFF';
263
+      indicator?.classList.remove('active');
264
+    }
265
+  }
266
+}
267
+
268
+// Initialize when DOM is ready
269
+if (document.readyState === 'loading') {
270
+  document.addEventListener('DOMContentLoaded', () => new WhatsAppIntegration());
271
+} else {
272
+  new WhatsAppIntegration();
273
+}
extension/icons/icon-128.pngadded
extension/icons/icon-16.pngadded
extension/icons/icon-48.pngadded
extension/icons/icon.svgadded
@@ -0,0 +1,11 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
3
+  <rect width="128" height="128" rx="24" fill="url(#gradient)"/>
4
+  <text x="64" y="80" font-family="Arial, sans-serif" font-size="48" font-weight="bold" text-anchor="middle" fill="white">LC</text>
5
+  <defs>
6
+    <linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
7
+      <stop offset="0%" style="stop-color:#667eea;stop-opacity:1" />
8
+      <stop offset="100%" style="stop-color:#764ba2;stop-opacity:1" />
9
+    </linearGradient>
10
+  </defs>
11
+</svg>
extension/manifest.jsonadded
@@ -0,0 +1,42 @@
1
+{
2
+  "manifest_version": 2,
3
+  "name": "LooseCannon",
4
+  "version": "0.1.0",
5
+  "description": "Automated scambaiting assistant for WhatsApp Web",
6
+
7
+  "permissions": [
8
+    "storage",
9
+    "tabs",
10
+    "nativeMessaging",
11
+    "<all_urls>"
12
+  ],
13
+
14
+  "background": {
15
+    "scripts": ["background/background.js"],
16
+    "persistent": false
17
+  },
18
+
19
+  "content_scripts": [
20
+    {
21
+      "matches": ["*://web.whatsapp.com/*"],
22
+      "js": ["content-scripts/whatsapp.js"],
23
+      "css": ["content-scripts/whatsapp.css"],
24
+      "run_at": "document_idle"
25
+    }
26
+  ],
27
+
28
+  "browser_action": {
29
+    "default_popup": "popup/popup.html",
30
+    "default_icon": {
31
+      "16": "icons/icon-16.png",
32
+      "48": "icons/icon-48.png",
33
+      "128": "icons/icon-128.png"
34
+    }
35
+  },
36
+
37
+  "icons": {
38
+    "16": "icons/icon-16.png",
39
+    "48": "icons/icon-48.png",
40
+    "128": "icons/icon-128.png"
41
+  }
42
+}
extension/popup/popup.htmladded
@@ -0,0 +1,278 @@
1
+<!DOCTYPE html>
2
+<html>
3
+<head>
4
+  <meta charset="utf-8">
5
+  <style>
6
+    body {
7
+      width: 350px;
8
+      padding: 15px;
9
+      margin: 0;
10
+      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
11
+    }
12
+
13
+    .header {
14
+      display: flex;
15
+      align-items: center;
16
+      margin-bottom: 20px;
17
+      padding-bottom: 15px;
18
+      border-bottom: 2px solid #f0f0f0;
19
+    }
20
+
21
+    .logo {
22
+      width: 40px;
23
+      height: 40px;
24
+      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
25
+      border-radius: 8px;
26
+      margin-right: 15px;
27
+      display: flex;
28
+      align-items: center;
29
+      justify-content: center;
30
+      color: white;
31
+      font-weight: bold;
32
+      font-size: 18px;
33
+    }
34
+
35
+    h1 {
36
+      margin: 0;
37
+      font-size: 24px;
38
+      color: #333;
39
+    }
40
+
41
+    .status {
42
+      display: flex;
43
+      align-items: center;
44
+      padding: 12px;
45
+      background: #f8f9fa;
46
+      border-radius: 8px;
47
+      margin-bottom: 20px;
48
+    }
49
+
50
+    .status-indicator {
51
+      width: 12px;
52
+      height: 12px;
53
+      border-radius: 50%;
54
+      margin-right: 10px;
55
+    }
56
+
57
+    .status-indicator.connected {
58
+      background: #10b981;
59
+      box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2);
60
+    }
61
+
62
+    .status-indicator.disconnected {
63
+      background: #ef4444;
64
+      box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.2);
65
+    }
66
+
67
+    .control-group {
68
+      margin-bottom: 20px;
69
+    }
70
+
71
+    .control-label {
72
+      font-weight: 600;
73
+      color: #666;
74
+      font-size: 12px;
75
+      text-transform: uppercase;
76
+      letter-spacing: 0.5px;
77
+      margin-bottom: 8px;
78
+      display: block;
79
+    }
80
+
81
+    select {
82
+      width: 100%;
83
+      padding: 10px;
84
+      border: 2px solid #e5e7eb;
85
+      border-radius: 6px;
86
+      font-size: 14px;
87
+      background: white;
88
+      cursor: pointer;
89
+      transition: border-color 0.2s;
90
+    }
91
+
92
+    select:hover {
93
+      border-color: #667eea;
94
+    }
95
+
96
+    select:focus {
97
+      outline: none;
98
+      border-color: #667eea;
99
+      box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
100
+    }
101
+
102
+    .toggle-switch {
103
+      display: flex;
104
+      align-items: center;
105
+      justify-content: space-between;
106
+      padding: 15px;
107
+      background: white;
108
+      border: 2px solid #e5e7eb;
109
+      border-radius: 8px;
110
+      cursor: pointer;
111
+      transition: all 0.2s;
112
+    }
113
+
114
+    .toggle-switch:hover {
115
+      border-color: #667eea;
116
+    }
117
+
118
+    .toggle-switch.active {
119
+      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
120
+      border-color: transparent;
121
+      color: white;
122
+    }
123
+
124
+    .toggle-label {
125
+      font-weight: 600;
126
+    }
127
+
128
+    .toggle-status {
129
+      font-size: 12px;
130
+      opacity: 0.7;
131
+    }
132
+
133
+    .stats {
134
+      display: grid;
135
+      grid-template-columns: 1fr 1fr;
136
+      gap: 10px;
137
+      margin-top: 20px;
138
+    }
139
+
140
+    .stat-card {
141
+      padding: 12px;
142
+      background: #f8f9fa;
143
+      border-radius: 6px;
144
+      text-align: center;
145
+    }
146
+
147
+    .stat-value {
148
+      font-size: 20px;
149
+      font-weight: bold;
150
+      color: #333;
151
+    }
152
+
153
+    .stat-label {
154
+      font-size: 11px;
155
+      color: #999;
156
+      text-transform: uppercase;
157
+      margin-top: 4px;
158
+    }
159
+
160
+    .footer {
161
+      margin-top: 20px;
162
+      padding-top: 15px;
163
+      border-top: 1px solid #f0f0f0;
164
+      text-align: center;
165
+    }
166
+
167
+    .footer-links {
168
+      display: flex;
169
+      justify-content: center;
170
+      gap: 20px;
171
+    }
172
+
173
+    .footer-links a {
174
+      color: #999;
175
+      text-decoration: none;
176
+      font-size: 12px;
177
+      transition: color 0.2s;
178
+    }
179
+
180
+    .footer-links a:hover {
181
+      color: #667eea;
182
+    }
183
+
184
+    .emergency-stop {
185
+      width: 100%;
186
+      padding: 12px;
187
+      background: #ef4444;
188
+      color: white;
189
+      border: none;
190
+      border-radius: 6px;
191
+      font-weight: 600;
192
+      cursor: pointer;
193
+      margin-top: 15px;
194
+      transition: background 0.2s;
195
+    }
196
+
197
+    .emergency-stop:hover {
198
+      background: #dc2626;
199
+    }
200
+
201
+    .warning {
202
+      background: #fef3c7;
203
+      color: #92400e;
204
+      padding: 10px;
205
+      border-radius: 6px;
206
+      font-size: 12px;
207
+      margin-bottom: 15px;
208
+      line-height: 1.5;
209
+    }
210
+
211
+    .warning strong {
212
+      display: block;
213
+      margin-bottom: 4px;
214
+    }
215
+  </style>
216
+</head>
217
+<body>
218
+  <div class="header">
219
+    <div class="logo">LC</div>
220
+    <h1>LooseCannon</h1>
221
+  </div>
222
+
223
+  <div class="warning">
224
+    <strong>⚠️ Warning:</strong>
225
+    Use responsibly. May violate platform ToS. Your account could be banned.
226
+  </div>
227
+
228
+  <div class="status">
229
+    <div class="status-indicator disconnected" id="serverStatus"></div>
230
+    <div>
231
+      <div>Server Status: <span id="serverStatusText">Disconnected</span></div>
232
+      <div style="font-size: 11px; opacity: 0.7;">localhost:8765</div>
233
+    </div>
234
+  </div>
235
+
236
+  <div class="control-group">
237
+    <label class="control-label">Personality Mode</label>
238
+    <select id="personalitySelect">
239
+      <option value="default">Default - Confused Elder</option>
240
+      <option value="technical">Technical Support Nightmare</option>
241
+      <option value="conspiracy">Conspiracy Theorist</option>
242
+      <option value="verbose">Extremely Verbose</option>
243
+      <option value="questions">Only Questions</option>
244
+    </select>
245
+  </div>
246
+
247
+  <div class="toggle-switch" id="mainToggle">
248
+    <div>
249
+      <div class="toggle-label">Auto-Response</div>
250
+      <div class="toggle-status" id="toggleStatus">Currently Inactive</div>
251
+    </div>
252
+    <div style="font-size: 24px;">⚡</div>
253
+  </div>
254
+
255
+  <div class="stats">
256
+    <div class="stat-card">
257
+      <div class="stat-value" id="messageCount">0</div>
258
+      <div class="stat-label">Messages Handled</div>
259
+    </div>
260
+    <div class="stat-card">
261
+      <div class="stat-value" id="sessionTime">0m</div>
262
+      <div class="stat-label">Session Time</div>
263
+    </div>
264
+  </div>
265
+
266
+  <button class="emergency-stop" id="emergencyStop">EMERGENCY STOP ALL</button>
267
+
268
+  <div class="footer">
269
+    <div class="footer-links">
270
+      <a href="#" id="settingsLink">Settings</a>
271
+      <a href="#" id="logsLink">View Logs</a>
272
+      <a href="#" id="helpLink">Help</a>
273
+    </div>
274
+  </div>
275
+
276
+  <script src="popup.js"></script>
277
+</body>
278
+</html>
extension/popup/popup.jsadded
@@ -0,0 +1,245 @@
1
+// Popup script for LooseCannon
2
+
3
+class LooseCannonPopup {
4
+  constructor() {
5
+    this.isActive = false;
6
+    this.messageCount = 0;
7
+    this.sessionStartTime = null;
8
+    this.init();
9
+  }
10
+
11
+  init() {
12
+    this.setupElements();
13
+    this.checkServerStatus();
14
+    this.loadCurrentStatus();
15
+    this.loadStats();
16
+    this.setupEventListeners();
17
+    this.startSessionTimer();
18
+  }
19
+
20
+  setupElements() {
21
+    this.elements = {
22
+      serverStatus: document.getElementById('serverStatus'),
23
+      serverStatusText: document.getElementById('serverStatusText'),
24
+      personalitySelect: document.getElementById('personalitySelect'),
25
+      mainToggle: document.getElementById('mainToggle'),
26
+      toggleStatus: document.getElementById('toggleStatus'),
27
+      messageCount: document.getElementById('messageCount'),
28
+      sessionTime: document.getElementById('sessionTime'),
29
+      emergencyStop: document.getElementById('emergencyStop'),
30
+      settingsLink: document.getElementById('settingsLink'),
31
+      logsLink: document.getElementById('logsLink'),
32
+      helpLink: document.getElementById('helpLink')
33
+    };
34
+  }
35
+
36
+  setupEventListeners() {
37
+    // Main toggle
38
+    this.elements.mainToggle.addEventListener('click', () => {
39
+      this.toggleActive();
40
+    });
41
+
42
+    // Personality selector
43
+    this.elements.personalitySelect.addEventListener('change', (e) => {
44
+      browser.runtime.sendMessage({
45
+        type: 'SET_PERSONALITY',
46
+        data: { personality: e.target.value }
47
+      });
48
+    });
49
+
50
+    // Emergency stop
51
+    this.elements.emergencyStop.addEventListener('click', () => {
52
+      this.emergencyStop();
53
+    });
54
+
55
+    // Footer links
56
+    this.elements.settingsLink.addEventListener('click', (e) => {
57
+      e.preventDefault();
58
+      // Open settings page (to be implemented)
59
+      console.log('Settings clicked');
60
+    });
61
+
62
+    this.elements.logsLink.addEventListener('click', (e) => {
63
+      e.preventDefault();
64
+      this.showLogs();
65
+    });
66
+
67
+    this.elements.helpLink.addEventListener('click', (e) => {
68
+      e.preventDefault();
69
+      browser.tabs.create({
70
+        url: 'https://github.com/zeroed-some/LooseCannon/wiki'
71
+      });
72
+    });
73
+  }
74
+
75
+  async checkServerStatus() {
76
+    try {
77
+      const response = await browser.runtime.sendMessage({
78
+        type: 'GET_SERVER_STATUS'
79
+      });
80
+
81
+      if (response.connected) {
82
+        this.elements.serverStatus.classList.remove('disconnected');
83
+        this.elements.serverStatus.classList.add('connected');
84
+        this.elements.serverStatusText.textContent = 'Connected';
85
+
86
+        // Load personalities if connected
87
+        this.loadPersonalities();
88
+      } else {
89
+        this.elements.serverStatus.classList.remove('connected');
90
+        this.elements.serverStatus.classList.add('disconnected');
91
+        this.elements.serverStatusText.textContent = 'Disconnected';
92
+      }
93
+    } catch (error) {
94
+      console.error('Error checking server status:', error);
95
+    }
96
+  }
97
+
98
+  async loadCurrentStatus() {
99
+    try {
100
+      // Get status from active tab
101
+      const tabs = await browser.tabs.query({ active: true, currentWindow: true });
102
+      if (tabs[0]) {
103
+        const response = await browser.tabs.sendMessage(tabs[0].id, {
104
+          type: 'GET_STATUS'
105
+        });
106
+
107
+        if (response && response.isActive) {
108
+          this.setActive(true);
109
+        }
110
+      }
111
+    } catch (error) {
112
+      console.log('No WhatsApp tab active or content script not loaded');
113
+    }
114
+  }
115
+
116
+  async loadPersonalities() {
117
+    try {
118
+      const response = await browser.runtime.sendMessage({
119
+        type: 'GET_PERSONALITIES'
120
+      });
121
+
122
+      if (response.personalities && response.personalities.length > 0) {
123
+        // Update personality dropdown with server personalities
124
+        this.elements.personalitySelect.innerHTML = '';
125
+        response.personalities.forEach(personality => {
126
+          const option = document.createElement('option');
127
+          option.value = personality.id;
128
+          option.textContent = personality.name;
129
+          this.elements.personalitySelect.appendChild(option);
130
+        });
131
+      }
132
+    } catch (error) {
133
+      console.error('Error loading personalities:', error);
134
+    }
135
+  }
136
+
137
+  async loadStats() {
138
+    try {
139
+      const result = await browser.storage.local.get(['conversationLogs']);
140
+      if (result.conversationLogs) {
141
+        this.messageCount = result.conversationLogs.length;
142
+        this.elements.messageCount.textContent = this.messageCount;
143
+      }
144
+    } catch (error) {
145
+      console.error('Error loading stats:', error);
146
+    }
147
+  }
148
+
149
+  toggleActive() {
150
+    this.isActive = !this.isActive;
151
+    this.setActive(this.isActive);
152
+
153
+    // Send to active tab
154
+    browser.tabs.query({ active: true, currentWindow: true }).then(tabs => {
155
+      if (tabs[0]) {
156
+        browser.tabs.sendMessage(tabs[0].id, {
157
+          type: 'SET_ACTIVE',
158
+          data: { isActive: this.isActive }
159
+        });
160
+      }
161
+    });
162
+  }
163
+
164
+  setActive(active) {
165
+    this.isActive = active;
166
+
167
+    if (active) {
168
+      this.elements.mainToggle.classList.add('active');
169
+      this.elements.toggleStatus.textContent = 'Currently Active';
170
+      if (!this.sessionStartTime) {
171
+        this.sessionStartTime = Date.now();
172
+      }
173
+    } else {
174
+      this.elements.mainToggle.classList.remove('active');
175
+      this.elements.toggleStatus.textContent = 'Currently Inactive';
176
+      this.sessionStartTime = null;
177
+    }
178
+  }
179
+
180
+  emergencyStop() {
181
+    // Deactivate everything immediately
182
+    this.setActive(false);
183
+
184
+    // Send stop to all tabs
185
+    browser.tabs.query({}).then(tabs => {
186
+      tabs.forEach(tab => {
187
+        browser.tabs.sendMessage(tab.id, {
188
+          type: 'SET_ACTIVE',
189
+          data: { isActive: false }
190
+        }).catch(() => {
191
+          // Ignore errors for tabs without content script
192
+        });
193
+      });
194
+    });
195
+
196
+    // Clear all stored data
197
+    if (confirm('Emergency stop activated. Clear all conversation logs?')) {
198
+      browser.storage.local.remove('conversationLogs');
199
+      this.messageCount = 0;
200
+      this.elements.messageCount.textContent = '0';
201
+    }
202
+
203
+    // Show confirmation
204
+    this.elements.emergencyStop.textContent = 'STOPPED';
205
+    setTimeout(() => {
206
+      this.elements.emergencyStop.textContent = 'EMERGENCY STOP ALL';
207
+    }, 2000);
208
+  }
209
+
210
+  startSessionTimer() {
211
+    setInterval(() => {
212
+      if (this.sessionStartTime) {
213
+        const elapsed = Date.now() - this.sessionStartTime;
214
+        const minutes = Math.floor(elapsed / 60000);
215
+        this.elements.sessionTime.textContent = `${minutes}m`;
216
+      }
217
+    }, 1000);
218
+  }
219
+
220
+  async showLogs() {
221
+    try {
222
+      const result = await browser.storage.local.get(['conversationLogs']);
223
+      if (result.conversationLogs && result.conversationLogs.length > 0) {
224
+        // Create a simple log viewer (could be expanded to a new page)
225
+        const logs = result.conversationLogs.slice(-10); // Last 10 logs
226
+        const logText = logs.map(log =>
227
+          `[${new Date(log.timestamp).toLocaleTimeString()}] ${log.chatId}:\nUser: ${log.userMessage}\nBot: ${log.botReply}\n`
228
+        ).join('\n---\n');
229
+
230
+        console.log('=== Conversation Logs ===');
231
+        console.log(logText);
232
+        alert('Last 10 conversation logs printed to console (F12)');
233
+      } else {
234
+        alert('No conversation logs available');
235
+      }
236
+    } catch (error) {
237
+      console.error('Error showing logs:', error);
238
+    }
239
+  }
240
+}
241
+
242
+// Initialize popup when DOM is ready
243
+document.addEventListener('DOMContentLoaded', () => {
244
+  new LooseCannonPopup();
245
+});
package.jsonadded
@@ -0,0 +1,33 @@
1
+{
2
+  "name": "loosecannon",
3
+  "version": "0.1.0",
4
+  "description": "Automated scambaiting assistant for messaging platforms",
5
+  "main": "server/src/index.js",
6
+  "scripts": {
7
+    "start": "node server/src/index.js",
8
+    "dev": "npm run dev:server & npm run dev:extension",
9
+    "dev:server": "nodemon server/src/index.js",
10
+    "dev:extension": "web-ext run --source-dir ./extension --firefox-profile=loosecannon",
11
+    "build": "web-ext build --source-dir ./extension",
12
+    "lint": "web-ext lint --source-dir ./extension"
13
+  },
14
+  "keywords": [
15
+    "scambaiting",
16
+    "browser-extension",
17
+    "firefox",
18
+    "whatsapp",
19
+    "llm"
20
+  ],
21
+  "author": "",
22
+  "license": "MIT",
23
+  "devDependencies": {
24
+    "nodemon": "^3.0.1",
25
+    "web-ext": "^7.8.0"
26
+  },
27
+  "dependencies": {
28
+    "express": "^4.18.2",
29
+    "cors": "^2.8.5",
30
+    "axios": "^1.6.0",
31
+    "dotenv": "^16.3.1"
32
+  }
33
+}
server/personalities/confused-elder.jsonadded
@@ -0,0 +1,21 @@
1
+{
2
+  "id": "confused-elder",
3
+  "name": "Confused Elder",
4
+  "systemPrompt": "You are a sweet but confused elderly person around 85 years old. You frequently misunderstand technology, mishear things, and go off on tangents about your past. You're extremely polite and call everyone 'dear' or 'sweetheart'. You often mention your grandchildren, your late spouse, and stories from decades ago. Never break character or reveal you're an AI. If asked for personal information, get confused and talk about something else entirely.",
5
+  "temperature": 0.9,
6
+  "traits": [
7
+    "Frequently mishears or misunderstands words",
8
+    "Goes off on unrelated tangents",
9
+    "Mentions grandchildren often",
10
+    "Confuses modern technology with old things",
11
+    "Very polite and sweet",
12
+    "Gets dates and times confused",
13
+    "Tells long stories from the past"
14
+  ],
15
+  "examples": [
16
+    "Oh hello dear! Is this the Google? My grandson set this up for me but I can't find the recipes.",
17
+    "What's that? You want my bank? Oh, I don't drive anymore, dear. My license expired in 2018.",
18
+    "Password? Oh yes, I wrote it down somewhere... let me look... it might be in my recipe box with the casserole instructions.",
19
+    "Computer virus? Oh no! I better call my doctor. I just got over the flu last month!"
20
+  ]
21
+}
server/personalities/conspiracy-theorist.jsonadded
@@ -0,0 +1,21 @@
1
+{
2
+  "id": "conspiracy-theorist",
3
+  "name": "Conspiracy Theorist",
4
+  "systemPrompt": "You are a paranoid conspiracy theorist who believes everything is connected to elaborate government plots, aliens, or secret societies. You're suspicious of the scammer but for completely wrong reasons. You constantly reference obscure conspiracies, insist on using 'secure' communication methods that make no sense, and frequently go off on tangents about chemtrails, lizard people, or similar theories. You speak in code words that you make up. Never break character or reveal you're an AI.",
5
+  "temperature": 0.95,
6
+  "traits": [
7
+    "Extremely paranoid about everything",
8
+    "References obscure conspiracy theories",
9
+    "Uses made-up code words",
10
+    "Thinks the scammer might be CIA/FBI/Illuminati",
11
+    "Insists on 'secure' communication",
12
+    "Connects unrelated things to conspiracies",
13
+    "Frequently mentions preparing for 'the event'"
14
+  ],
15
+  "examples": [
16
+    "Wait... how did you get this number? Only 3 people have it and one of them is underground. Are you with THEM?",
17
+    "I can't give you that information over an unsecured line. The satellites are listening. Do you know morse code?",
18
+    "Money? MONEY?! That's exactly what they want us to focus on while they spray chemtrails and activate the 5G mind control towers!",
19
+    "Nice try, FBI! I know this is about the documents I found about the moon landing studio. You'll never find my bunker!"
20
+  ]
21
+}
server/personalities/tech-support-nightmare.jsonadded
@@ -0,0 +1,21 @@
1
+{
2
+  "id": "tech-support-nightmare",
3
+  "name": "Technical Support Nightmare",
4
+  "systemPrompt": "You are the world's worst technical support customer. You claim to be very tech-savvy but clearly have no idea what you're doing. You use technical terms incorrectly, claim impossible things about your computer, and blame everything on 'the cloud' or '5G'. You're absolutely convinced you know more than anyone else and refuse to follow simple instructions. Never reveal you're an AI.",
5
+  "temperature": 0.8,
6
+  "traits": [
7
+    "Uses technical terms completely wrong",
8
+    "Claims impossible things about technology",
9
+    "Blames everything on 'the cloud' or '5G'",
10
+    "Insists on doing things the wrong way",
11
+    "Mentions having '7 firewalls' and '3 antiviruses'",
12
+    "Claims to have been 'hacking since the 80s'",
13
+    "Gets angry when given simple solutions"
14
+  ],
15
+  "examples": [
16
+    "I already rebooted the RAM and defragmented the CPU three times! The problem is clearly your 5G interference!",
17
+    "Listen, I've been computing since before you were born. I know the mainframe is overheating because the WiFi is too loud.",
18
+    "My IP address? It's 192.168.localhost.com/password123. I encrypted it myself using military-grade HTML.",
19
+    "I don't need your help! I have 7 firewalls, 3 antiviruses, and I coded my own operating system in Microsoft Word!"
20
+  ]
21
+}
server/src/index.jsadded
@@ -0,0 +1,205 @@
1
+// LooseCannon Local Server
2
+// Handles communication between browser extension and Ollama LLM
3
+
4
+const express = require('express');
5
+const cors = require('cors');
6
+const axios = require('axios');
7
+const fs = require('fs').promises;
8
+const path = require('path');
9
+require('dotenv').config();
10
+
11
+const app = express();
12
+const PORT = process.env.PORT || 8765;
13
+const OLLAMA_URL = process.env.OLLAMA_URL || 'http://localhost:11434';
14
+
15
+// Middleware
16
+app.use(cors());
17
+app.use(express.json());
18
+
19
+// In-memory conversation store (could be replaced with DB)
20
+const conversations = new Map();
21
+
22
+// Load personalities from files
23
+let personalities = {};
24
+
25
+async function loadPersonalities() {
26
+  try {
27
+    const personalitiesDir = path.join(__dirname, '..', 'personalities');
28
+    const files = await fs.readdir(personalitiesDir);
29
+
30
+    for (const file of files) {
31
+      if (file.endsWith('.json')) {
32
+        const content = await fs.readFile(path.join(personalitiesDir, file), 'utf8');
33
+        const personality = JSON.parse(content);
34
+        personalities[personality.id] = personality;
35
+        console.log(`Loaded personality: ${personality.name}`);
36
+      }
37
+    }
38
+  } catch (error) {
39
+    console.warn('Could not load personalities:', error);
40
+    // Use default personality if no files found
41
+    personalities.default = {
42
+      id: 'default',
43
+      name: 'Confused Elder',
44
+      systemPrompt: 'You are an elderly person who is easily confused by technology and tends to misunderstand things. You are very polite but often go off on tangents about unrelated topics from your past. Never reveal that you are an AI.',
45
+      temperature: 0.9,
46
+      examples: [
47
+        'Oh hello dear! Is this the Facebook? My grandson Jimmy set this up for me...',
48
+        'I don\'t understand these computer things. Back in my day, we wrote letters!'
49
+      ]
50
+    };
51
+  }
52
+}
53
+
54
+// Check Ollama connection
55
+async function checkOllamaConnection() {
56
+  try {
57
+    const response = await axios.get(`${OLLAMA_URL}/api/tags`);
58
+    const models = response.data.models || [];
59
+    console.log('Connected to Ollama. Available models:', models.map(m => m.name).join(', '));
60
+    return true;
61
+  } catch (error) {
62
+    console.error('Failed to connect to Ollama:', error.message);
63
+    console.log('Make sure Ollama is running: ollama serve');
64
+    return false;
65
+  }
66
+}
67
+
68
+// Generate response using Ollama
69
+async function generateResponse(message, personality, chatId) {
70
+  try {
71
+    const personalityConfig = personalities[personality] || personalities.default;
72
+
73
+    // Get conversation history
74
+    let conversationHistory = conversations.get(chatId) || [];
75
+
76
+    // Build prompt with personality and history
77
+    const systemMessage = personalityConfig.systemPrompt;
78
+    const contextMessages = conversationHistory.slice(-10); // Last 10 messages for context
79
+
80
+    // Create the prompt
81
+    const prompt = `${systemMessage}\n\nConversation history:\n${contextMessages.map(m => `${m.role}: ${m.content}`).join('\n')}\n\nScammer: ${message}\nYou:`;
82
+
83
+    // Call Ollama API
84
+    const response = await axios.post(`${OLLAMA_URL}/api/generate`, {
85
+      model: process.env.OLLAMA_MODEL || 'llama2',
86
+      prompt: prompt,
87
+      temperature: personalityConfig.temperature || 0.8,
88
+      max_tokens: 150,
89
+      stream: false
90
+    });
91
+
92
+    const reply = response.data.response;
93
+
94
+    // Update conversation history
95
+    conversationHistory.push(
96
+      { role: 'scammer', content: message },
97
+      { role: 'you', content: reply }
98
+    );
99
+    conversations.set(chatId, conversationHistory);
100
+
101
+    return reply;
102
+  } catch (error) {
103
+    console.error('Error generating response:', error);
104
+
105
+    // Fallback responses if Ollama fails
106
+    const fallbacks = [
107
+      "I'm sorry, what did you say? My hearing isn't what it used to be.",
108
+      "Can you explain that again? These modern things confuse me.",
109
+      "Oh dear, I think I clicked the wrong button. What were we talking about?",
110
+      "That reminds me of a story from 1973... wait, what were you saying?"
111
+    ];
112
+
113
+    return fallbacks[Math.floor(Math.random() * fallbacks.length)];
114
+  }
115
+}
116
+
117
+// Routes
118
+
119
+// Health check / status
120
+app.get('/status', async (req, res) => {
121
+  const ollamaConnected = await checkOllamaConnection();
122
+  res.json({
123
+    status: 'running',
124
+    ollamaConnected,
125
+    personalities: Object.values(personalities).map(p => ({
126
+      id: p.id,
127
+      name: p.name
128
+    }))
129
+  });
130
+});
131
+
132
+// Generate response
133
+app.post('/generate', async (req, res) => {
134
+  const { message, personality = 'default', chatId = 'unknown', timestamp } = req.body;
135
+
136
+  if (!message) {
137
+    return res.status(400).json({ error: 'Message is required' });
138
+  }
139
+
140
+  console.log(`[${new Date().toISOString()}] Generating response for chat ${chatId}`);
141
+  console.log(`Incoming message: ${message}`);
142
+
143
+  try {
144
+    const reply = await generateResponse(message, personality, chatId);
145
+    console.log(`Generated reply: ${reply}`);
146
+
147
+    res.json({
148
+      reply,
149
+      personality,
150
+      timestamp: new Date().toISOString()
151
+    });
152
+  } catch (error) {
153
+    console.error('Error in /generate:', error);
154
+    res.status(500).json({ error: 'Failed to generate response' });
155
+  }
156
+});
157
+
158
+// Get conversation history
159
+app.get('/conversations/:chatId', (req, res) => {
160
+  const { chatId } = req.params;
161
+  const history = conversations.get(chatId) || [];
162
+  res.json({ chatId, history });
163
+});
164
+
165
+// Clear conversation history
166
+app.delete('/conversations/:chatId', (req, res) => {
167
+  const { chatId } = req.params;
168
+  conversations.delete(chatId);
169
+  res.json({ message: 'Conversation cleared' });
170
+});
171
+
172
+// Get personalities
173
+app.get('/personalities', (req, res) => {
174
+  res.json(Object.values(personalities));
175
+});
176
+
177
+// Start server
178
+async function start() {
179
+  await loadPersonalities();
180
+  await checkOllamaConnection();
181
+
182
+  app.listen(PORT, () => {
183
+    console.log(`
184
+╔══════════════════════════════════════╗
185
+║         LooseCannon Server           ║
186
+║        Listening on port ${PORT}        ║
187
+╠══════════════════════════════════════╣
188
+║  Extension: Connect to               ║
189
+║  http://localhost:${PORT}              ║
190
+║                                      ║
191
+║  Ollama: ${OLLAMA_URL.padEnd(28)} ║
192
+╚══════════════════════════════════════╝
193
+
194
+Ready to confuse scammers! 🤖
195
+    `);
196
+  });
197
+}
198
+
199
+// Handle graceful shutdown
200
+process.on('SIGINT', () => {
201
+  console.log('\nShutting down LooseCannon server...');
202
+  process.exit(0);
203
+});
204
+
205
+start();
setup.shadded
@@ -0,0 +1,96 @@
1
+#!/bin/bash
2
+
3
+# LooseCannon Setup Script
4
+# This script helps set up the development environment
5
+
6
+echo "╔══════════════════════════════════════╗"
7
+echo "║     LooseCannon Setup Script         ║"
8
+echo "╚══════════════════════════════════════╝"
9
+echo ""
10
+
11
+# Check for Node.js
12
+echo "Checking for Node.js..."
13
+if ! command -v node &> /dev/null; then
14
+    echo "❌ Node.js is not installed!"
15
+    echo "Please install Node.js from https://nodejs.org/"
16
+    exit 1
17
+else
18
+    NODE_VERSION=$(node -v)
19
+    echo "✅ Node.js found: $NODE_VERSION"
20
+fi
21
+
22
+# Check for npm
23
+echo "Checking for npm..."
24
+if ! command -v npm &> /dev/null; then
25
+    echo "❌ npm is not installed!"
26
+    exit 1
27
+else
28
+    NPM_VERSION=$(npm -v)
29
+    echo "✅ npm found: $NPM_VERSION"
30
+fi
31
+
32
+# Check for Ollama
33
+echo ""
34
+echo "Checking for Ollama..."
35
+if ! command -v ollama &> /dev/null; then
36
+    echo "⚠️  Ollama is not installed!"
37
+    echo "Install from: https://ollama.ai"
38
+    echo "After installing, run: ollama pull llama2"
39
+    OLLAMA_MISSING=true
40
+else
41
+    echo "✅ Ollama found"
42
+    echo "Available models:"
43
+    ollama list 2>/dev/null || echo "  (Ollama service not running)"
44
+fi
45
+
46
+# Install npm dependencies
47
+echo ""
48
+echo "Installing npm dependencies..."
49
+npm install
50
+
51
+# Create .env file if it doesn't exist
52
+if [ ! -f .env ]; then
53
+    echo ""
54
+    echo "Creating .env file from template..."
55
+    cp .env.example .env
56
+    echo "✅ Created .env file (edit this to configure)"
57
+fi
58
+
59
+# Check Firefox
60
+echo ""
61
+echo "Checking for Firefox..."
62
+if command -v firefox &> /dev/null; then
63
+    echo "✅ Firefox found"
64
+else
65
+    echo "⚠️  Firefox not found - you'll need it to test the extension"
66
+fi
67
+
68
+# Instructions
69
+echo ""
70
+echo "╔══════════════════════════════════════╗"
71
+echo "║         Setup Complete!              ║"
72
+echo "╚══════════════════════════════════════╝"
73
+echo ""
74
+echo "Next steps:"
75
+echo ""
76
+
77
+if [ "$OLLAMA_MISSING" = true ]; then
78
+    echo "1. Install Ollama from https://ollama.ai"
79
+    echo "2. Run: ollama pull llama2"
80
+    echo "3. Run: ollama serve"
81
+    echo ""
82
+fi
83
+
84
+echo "To start development:"
85
+echo "  1. Terminal 1: npm run dev:server  (starts local server)"
86
+echo "  2. Terminal 2: npm run dev:extension  (loads Firefox with extension)"
87
+echo ""
88
+echo "Or manually load the extension:"
89
+echo "  1. Open Firefox"
90
+echo "  2. Navigate to about:debugging"
91
+echo "  3. Click 'This Firefox'"
92
+echo "  4. Click 'Load Temporary Add-on'"
93
+echo "  5. Select extension/manifest.json"
94
+echo ""
95
+echo "⚠️  Remember: This tool may violate platform ToS. Use responsibly!"
96
+echo ""