JavaScript · 13794 bytes Raw Blame History
1 // Telegram Web Content Script for LooseCannon
2 console.log('[LooseCannon] Telegram content script loaded');
3
4 class TelegramIntegration {
5 constructor() {
6 this.isActive = false;
7 this.currentChat = null;
8 this.messageObserver = null;
9 this.platform = 'telegram';
10 this.init();
11 }
12
13 init() {
14 this.waitForTelegram().then(() => {
15 console.log('[LooseCannon] Telegram Web detected and ready');
16 this.setupMessageObserver();
17 this.injectControls();
18 this.listenForCommands();
19 });
20 }
21
22 waitForTelegram() {
23 return new Promise((resolve) => {
24 const checkForApp = setInterval(() => {
25 // Check for Telegram's main chat area
26 const chatArea = document.querySelector('.im_history_wrap, .messages-container, [class*="Message"]');
27 const inputField = document.querySelector('.composer_rich_textarea, [class*="ComposerInput"]');
28
29 if (chatArea && inputField) {
30 clearInterval(checkForApp);
31 console.log('[LooseCannon] Telegram interface detected');
32 resolve();
33 }
34 }, 1000);
35 });
36 }
37
38 setupMessageObserver() {
39 // Find the messages container
40 const messageContainer = document.querySelector('.im_history_wrap, .messages-container, [class*="MessagesScroller"]');
41
42 if (!messageContainer) {
43 console.error('[LooseCannon] Could not find Telegram message container');
44 setTimeout(() => this.setupMessageObserver(), 2000);
45 return;
46 }
47
48 const config = {
49 childList: true,
50 subtree: true,
51 characterData: true,
52 attributes: true,
53 attributeFilter: ['class']
54 };
55
56 this.messageObserver = new MutationObserver((mutations) => {
57 if (!this.isActive) return;
58
59 mutations.forEach((mutation) => {
60 if (mutation.type === 'childList') {
61 mutation.addedNodes.forEach((node) => {
62 if (this.isIncomingMessage(node)) {
63 this.handleIncomingMessage(node);
64 }
65 });
66 }
67 });
68 });
69
70 this.messageObserver.observe(messageContainer, config);
71 console.log('[LooseCannon] Telegram message observer setup complete');
72 }
73
74 isIncomingMessage(node) {
75 if (!node || !node.classList) return false;
76
77 // Check for Telegram message classes
78 const messageClasses = ['im_message', 'message', 'Message'];
79 const hasMessageClass = messageClasses.some(cls =>
80 node.classList.contains(cls) ||
81 Array.from(node.classList).some(c => c.includes('Message'))
82 );
83
84 if (!hasMessageClass) return false;
85
86 // Check if it's an incoming message (not sent by us)
87 const isOutgoing = node.classList.contains('im_message_out') ||
88 node.classList.contains('own') ||
89 Array.from(node.classList).some(c => c.includes('own') || c.includes('Out'));
90
91 return !isOutgoing;
92 }
93
94 handleIncomingMessage(node) {
95 const messageData = this.extractMessageData(node);
96
97 if (!messageData.content && !messageData.media) {
98 return; // Empty message, skip
99 }
100
101 const timestamp = new Date().toISOString();
102 const chatId = this.getCurrentChatId();
103
104 console.log('[LooseCannon] Telegram message detected:', messageData);
105
106 // Send to background script
107 browser.runtime.sendMessage({
108 type: 'NEW_MESSAGE',
109 data: {
110 ...messageData,
111 timestamp,
112 chatId,
113 platform: this.platform
114 }
115 }).then(response => {
116 if (response && response.reply) {
117 this.simulateTypingAndSend(response.reply, response.delay);
118 }
119 }).catch(error => {
120 console.error('[LooseCannon] Error sending message to background:', error);
121 });
122 }
123
124 extractMessageData(node) {
125 const data = {
126 type: 'text',
127 content: '',
128 media: null,
129 metadata: {}
130 };
131
132 // Extract text content
133 const textElement = node.querySelector('.im_message_text, [class*="text-content"], [class*="MessageText"]');
134 if (textElement) {
135 data.content = textElement.textContent || textElement.innerText || '';
136
137 // Extract links
138 const links = textElement.querySelectorAll('a');
139 if (links.length > 0) {
140 data.metadata.links = Array.from(links).map(a => a.href);
141 }
142 }
143
144 // Check for media
145 const photoElement = node.querySelector('.im_message_photo_thumb, [class*="Photo"], img[class*="media"]');
146 if (photoElement) {
147 data.type = 'image';
148 data.media = { type: 'image' };
149 data.metadata.hasImage = true;
150 }
151
152 // Check for audio/voice
153 const audioElement = node.querySelector('.im_message_audio, .audio, [class*="Audio"], [class*="Voice"]');
154 if (audioElement) {
155 data.type = 'audio';
156 data.media = { type: 'audio' };
157 data.metadata.hasAudio = true;
158 }
159
160 // Check for documents
161 const docElement = node.querySelector('.im_message_document, [class*="Document"]');
162 if (docElement) {
163 data.type = 'document';
164 data.media = { type: 'document' };
165 data.metadata.hasDocument = true;
166 }
167
168 // Check for stickers
169 const stickerElement = node.querySelector('.im_message_sticker, [class*="Sticker"]');
170 if (stickerElement) {
171 data.type = 'sticker';
172 data.media = { type: 'sticker' };
173 }
174
175 // Extract phone numbers and emails from content
176 if (data.content) {
177 const phoneRegex = /[\+]?[(]?[0-9]{1,4}[)]?[-\s\.]?[(]?[0-9]{1,4}[)]?[-\s\.]?[0-9]{1,5}[-\s\.]?[0-9]{1,5}/g;
178 const phones = data.content.match(phoneRegex);
179 if (phones) {
180 data.metadata.phoneNumbers = phones;
181 }
182
183 const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
184 const emails = data.content.match(emailRegex);
185 if (emails) {
186 data.metadata.emails = emails;
187 }
188 }
189
190 return data;
191 }
192
193 getCurrentChatId() {
194 // Try to get chat title from header
195 const headerTitle = document.querySelector('.tg_head_peer_title, .chat-title, [class*="ChatTitle"], [class*="HeaderTitle"]');
196 if (headerTitle) {
197 return headerTitle.textContent || 'unknown';
198 }
199
200 // Try to get from active dialog
201 const activeDialog = document.querySelector('.im_dialog_wrap.active, .dialog.active, [class*="ChatItem"][class*="active"]');
202 if (activeDialog) {
203 const title = activeDialog.querySelector('.im_dialog_peer span, [class*="ChatTitle"]');
204 if (title) {
205 return title.textContent || 'unknown';
206 }
207 }
208
209 return 'unknown';
210 }
211
212 simulateTypingAndSend(text, delay = 3000) {
213 // Show typing indicator
214 this.startTyping();
215
216 // Calculate realistic delay
217 const wordCount = text.split(' ').length;
218 const typingDelay = Math.min(delay || (wordCount * 200 + 2000), 10000);
219
220 setTimeout(() => {
221 this.stopTyping();
222 this.sendMessage(text);
223 }, typingDelay);
224 }
225
226 startTyping() {
227 const inputField = this.getInputField();
228 if (!inputField) return;
229
230 // Focus the input
231 inputField.focus();
232
233 // Add placeholder to trigger typing indicator
234 if (inputField.contentEditable === 'true') {
235 inputField.textContent = '...';
236 } else {
237 inputField.value = '...';
238 }
239
240 // Trigger input event
241 const event = new Event('input', { bubbles: true });
242 inputField.dispatchEvent(event);
243 }
244
245 stopTyping() {
246 const inputField = this.getInputField();
247 if (!inputField) return;
248
249 // Clear the placeholder
250 if (inputField.contentEditable === 'true') {
251 inputField.textContent = '';
252 } else {
253 inputField.value = '';
254 }
255 }
256
257 sendMessage(text) {
258 const inputField = this.getInputField();
259
260 if (!inputField) {
261 console.error('[LooseCannon] Could not find Telegram input field');
262 return;
263 }
264
265 // Set the message text
266 if (inputField.contentEditable === 'true') {
267 // For contenteditable divs
268 inputField.textContent = text;
269 inputField.innerHTML = text;
270
271 // Move cursor to end
272 const range = document.createRange();
273 const selection = window.getSelection();
274 range.selectNodeContents(inputField);
275 range.collapse(false);
276 selection.removeAllRanges();
277 selection.addRange(range);
278 } else {
279 // For regular input/textarea
280 inputField.value = text;
281 }
282
283 // Trigger input event to update Telegram's state
284 const inputEvent = new Event('input', { bubbles: true });
285 inputField.dispatchEvent(inputEvent);
286
287 // Try multiple methods to send
288 setTimeout(() => {
289 // Method 1: Click send button
290 const sendButton = document.querySelector('.im_submit, .compose-button-send, [class*="SendButton"], [class*="ComposeButton"][class*="send"]');
291 if (sendButton) {
292 sendButton.click();
293 console.log('[LooseCannon] Telegram message sent via button');
294 return;
295 }
296
297 // Method 2: Simulate Enter key
298 const enterEvent = new KeyboardEvent('keydown', {
299 key: 'Enter',
300 keyCode: 13,
301 which: 13,
302 bubbles: true
303 });
304 inputField.dispatchEvent(enterEvent);
305 console.log('[LooseCannon] Telegram message sent via Enter key');
306 }, 100);
307 }
308
309 getInputField() {
310 // Try multiple selectors for Telegram's input field
311 const selectors = [
312 '.composer_rich_textarea',
313 '[contenteditable="true"][class*="input"]',
314 '[class*="ComposerInput"]',
315 '.im_message_field',
316 '[class*="MessageInput"]'
317 ];
318
319 for (const selector of selectors) {
320 const field = document.querySelector(selector);
321 if (field) return field;
322 }
323
324 return null;
325 }
326
327 injectControls() {
328 const style = document.createElement('style');
329 style.textContent = `
330 .loosecannon-toggle-telegram {
331 position: fixed;
332 bottom: 20px;
333 left: 20px;
334 z-index: 9999;
335 background: #0088cc;
336 color: white;
337 border: none;
338 border-radius: 50px;
339 padding: 12px 20px;
340 cursor: pointer;
341 font-weight: bold;
342 box-shadow: 0 2px 10px rgba(0,136,204,0.3);
343 transition: all 0.3s;
344 }
345
346 .loosecannon-toggle-telegram.active {
347 background: #44ff44;
348 }
349
350 .loosecannon-toggle-telegram:hover {
351 transform: scale(1.05);
352 }
353
354 .loosecannon-indicator-telegram {
355 position: fixed;
356 top: 20px;
357 left: 20px;
358 background: rgba(0, 136, 204, 0.9);
359 color: white;
360 padding: 8px 15px;
361 border-radius: 20px;
362 font-size: 12px;
363 z-index: 9999;
364 display: none;
365 }
366
367 .loosecannon-indicator-telegram.active {
368 display: block;
369 background: rgba(68, 255, 68, 0.9);
370 }
371 `;
372 document.head.appendChild(style);
373
374 const toggleButton = document.createElement('button');
375 toggleButton.className = 'loosecannon-toggle-telegram';
376 toggleButton.textContent = 'LC: OFF';
377 toggleButton.onclick = () => this.toggleActive();
378 document.body.appendChild(toggleButton);
379
380 const indicator = document.createElement('div');
381 indicator.className = 'loosecannon-indicator-telegram';
382 indicator.innerHTML = '🤖 LooseCannon Active<br><small>Telegram</small>';
383 document.body.appendChild(indicator);
384 }
385
386 toggleActive() {
387 this.isActive = !this.isActive;
388 this.updateUI();
389
390 const chatId = this.getCurrentChatId();
391 console.log(`[LooseCannon] Telegram ${this.isActive ? 'activated' : 'deactivated'} for: ${chatId}`);
392
393 // Notify background script
394 browser.runtime.sendMessage({
395 type: 'TOGGLE_ACTIVE',
396 data: {
397 isActive: this.isActive,
398 chatId: chatId,
399 platform: this.platform
400 }
401 });
402 }
403
404 updateUI() {
405 const button = document.querySelector('.loosecannon-toggle-telegram');
406 const indicator = document.querySelector('.loosecannon-indicator-telegram');
407
408 if (this.isActive) {
409 button?.classList.add('active');
410 if (button) button.textContent = 'LC: ON';
411 indicator?.classList.add('active');
412 } else {
413 button?.classList.remove('active');
414 if (button) button.textContent = 'LC: OFF';
415 indicator?.classList.remove('active');
416 }
417 }
418
419 listenForCommands() {
420 browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
421 switch (message.type) {
422 case 'GET_STATUS':
423 sendResponse({
424 isActive: this.isActive,
425 platform: this.platform,
426 currentChat: this.currentChat
427 });
428 break;
429
430 case 'SET_ACTIVE':
431 this.isActive = message.data.isActive;
432 this.updateUI();
433 break;
434
435 case 'SEND_MESSAGE':
436 this.simulateTypingAndSend(message.data.text, message.data.delay);
437 break;
438
439 case 'SCAMMER_DETECTED':
440 this.showScammerAlert(message.data.score);
441 break;
442 }
443 });
444 }
445
446 showScammerAlert(score) {
447 const alert = document.createElement('div');
448 alert.innerHTML = `
449 <div style="
450 position: fixed;
451 top: 60px;
452 left: 20px;
453 background: linear-gradient(135deg, #ff4444, #ff6666);
454 color: white;
455 padding: 15px 20px;
456 border-radius: 10px;
457 z-index: 10000;
458 box-shadow: 0 4px 20px rgba(255,68,68,0.4);
459 animation: slideIn 0.3s ease;
460 ">
461 <strong>⚠️ Scammer Detected!</strong><br>
462 <small>Confidence: ${(score * 100).toFixed(0)}%</small><br>
463 <small>LooseCannon ready to engage</small>
464 </div>
465 `;
466
467 document.body.appendChild(alert);
468
469 setTimeout(() => {
470 alert.remove();
471 }, 5000);
472 }
473 }
474
475 // Initialize when DOM is ready
476 if (document.readyState === 'loading') {
477 document.addEventListener('DOMContentLoaded', () => new TelegramIntegration());
478 } else {
479 new TelegramIntegration();
480 }