// Topic-Übersicht erkennen (keine Buttons dort) function isInTopicList() { return document.querySelector('.topics-list, .topic-list, .forums-list, .chat-list .topic') !== null || document.body.classList.contains('topics-mode'); } // Kleine Helfer-Funktion: freundlich sanitisieren (erhalte Leerzeichen, Klammern, Punkte) function sanitizeFriendly(name) { if (!name) return 'Telegram'; return name.toString().replace(/[\/\\\?\%\*\:\|\"\<\>]/g, "_").trim(); } // Extrahiere den Original-Dateinamen aus Telegram-Metadaten function extractTelegramFilename(video) { try { const videoUrl = video.currentSrc || video.src; console.log('=== FILENAME EXTRACTION ==='); console.log('Video URL:', videoUrl); // 1. Suche die Message, die das Video enthält const messageContainer = video.closest('.message, .Message, .bubble, .media-container'); if (!messageContainer) { console.warn('No message container found'); return 'telegram_video_' + Date.now(); } console.log('Message container found'); // === PRIORITÄT 1: VERBESSERTES ALBUM/SERIEN-HANDLING === const albumContainer = messageContainer.querySelector('.Album'); if (albumContainer) { console.log('Album erkannt → verbessertes Serien-Naming'); const videoContainer = video.closest('.album-item-select-wrapper'); if (videoContainer) { const allVideoContainers = albumContainer.querySelectorAll('.album-item-select-wrapper'); let videoIndex = -1; for (let i = 0; i < allVideoContainers.length; i++) { if (allVideoContainers[i] === videoContainer) { videoIndex = i; break; } } console.log('Video index in album:', videoIndex); if (videoIndex >= 0) { const textContent = messageContainer.querySelector('.text-content.clearfix.with-meta, .text-content'); if (textContent) { const lines = textContent.innerText.split('\n') .map(l => l.trim()) .filter(l => l.length > 0); console.log('Album text lines:', lines); const seriesTitle = lines[0] || 'Unknown Series'; // Direkter Match: SxxExx - Titel const episodeLines = lines.filter(line => /^S\d{2}E\d{2}\s*-/.test(line)); if (episodeLines.length > videoIndex && episodeLines[videoIndex]) { const line = episodeLines[videoIndex]; const match = line.match(/^(S\d{2}E\d{2})\s*-\s*(.+)$/); if (match) { const code = match[1]; const title = match[2].trim(); const finalName = `${sanitizeFriendly(seriesTitle)} - ${code} - ${sanitizeFriendly(title)}`; console.log('Perfekter Serien-Dateiname:', finalName); return finalName; } } // Fallback: Zeile nach Serientitel als Episodentitel if (lines.length > videoIndex + 1) { const episodeTitle = lines[videoIndex + 1]; if (episodeTitle.length > 5) { const finalName = `${sanitizeFriendly(seriesTitle)} - ${sanitizeFriendly(episodeTitle)}`; console.log('Serien-Fallback-Dateiname:', finalName); return finalName; } } return sanitizeFriendly(seriesTitle + ' - Episode ' + (videoIndex + 1)); } } } } // 2. Einzelvideo: Erste Zeile aus .text-content const textContent = messageContainer.querySelector('.text-content, .text-content.clearfix'); if (textContent) { const fullText = textContent.innerText || textContent.textContent; const firstLine = fullText.split('\n')[0].trim(); if (firstLine.length > 5 && !firstLine.startsWith('@') && !firstLine.startsWith('#') && !firstLine.includes('⬇') && !firstLine.match(/^\d{2}:\d{2}:\d{2}$/)) { console.log('Found filename from .text-content:', firstLine); return sanitizeFriendly(firstLine); } } // 3. Weitere Titel-Selektoren const titleSelectors = [ '.media-caption-text', '.media-caption', '.message-title', '.video-title', '.document-name', '.file-name', '.name', '.title:not(.peer-title):not(.top-bar)', '[class*="caption"]', '[class*="title"]:not([class*="peer"]):not([class*="top"])' ]; for (const selector of titleSelectors) { const element = messageContainer.querySelector(selector); if (element) { const text = element.innerText?.trim() || element.textContent?.trim(); if (text && text.length > 0 && text.length < 300) { if (!text.includes('⬇') && !text.toLowerCase().includes('download') && !text.match(/^\d{2}:\d{2}:\d{2}$/)) { console.log('Found filename from selector', selector, ':', text); return sanitizeFriendly(text); } } } } // 4. Textblöcke, Text-Nodes, React Props usw. (wie in deinem Original) const allTextElements = messageContainer.querySelectorAll('div, span, p'); for (const el of allTextElements) { const text = Array.from(el.childNodes) .filter(node => node.nodeType === Node.TEXT_NODE) .map(node => node.textContent.trim()) .join(' ') .trim(); if (text.length > 5 && text.length < 200) { if (!text.startsWith('@') && !text.startsWith('#') && !text.includes('⬇') && !text.match(/^\d{2}:\d{2}:\d{2}$/) && /[A-Z]/.test(text)) { console.log('Found potential filename from text element:', text); return sanitizeFriendly(text); } } } const walker = document.createTreeWalker( messageContainer, NodeFilter.SHOW_TEXT, { acceptNode: function(node) { const text = node.textContent.trim(); if (text.length < 5 || text.match(/^\d{2}:\d{2}:\d{2}$/) || text.startsWith('@') || text.startsWith('#') || text.includes('⬇')) { return NodeFilter.FILTER_REJECT; } return NodeFilter.FILTER_ACCEPT; } }, false ); let foundTexts = []; let node; while (node = walker.nextNode()) { const text = node.textContent.trim(); if (text.length > 0) foundTexts.push(text); } for (const text of foundTexts) { if (text.length > 5 && text.length < 200 && /[A-Z]/.test(text)) { console.log('Using text node as filename:', text); return sanitizeFriendly(text); } } const reactKeys = Object.keys(messageContainer).filter(k => k.startsWith('__react')); for (const key of reactKeys) { try { const fiber = messageContainer[key]; const filename = searchReactTree(fiber, ['fileName', 'file_name', 'name', 'title', 'caption']); if (filename && typeof filename === 'string' && filename.length > 3) { console.log('Found filename in React props:', filename); return sanitizeFriendly(filename); } } catch (e) {} } const match = videoUrl.match(/document(\d+)/); if (match) { console.warn('Using document ID as fallback'); return 'telegram_doc_' + match[1]; } console.warn('Could not extract filename, using timestamp fallback'); return 'telegram_video_' + Date.now(); } catch (err) { console.error('Error extracting filename:', err); return 'telegram_video_' + Date.now(); } } // Durchsuche React Fiber Tree function searchReactTree(node, keys, depth = 0, maxDepth = 15) { if (depth > maxDepth || !node || typeof node !== 'object') return null; try { for (const key of keys) { if (node[key]) { const val = node[key]; if (typeof val === 'string' && val.length > 0) return val; if (typeof val === 'object' && val.name) return val.name; } } if (node.memoizedProps) { for (const key of keys) { if (node.memoizedProps[key]) { const val = node.memoizedProps[key]; if (typeof val === 'string' && val.length > 0) return val; } } } if (node.child) { const result = searchReactTree(node.child, keys, depth + 1, maxDepth); if (result) return result; } } catch (e) {} return null; } // Download auslösen – OHNE .mp4 anhängen (für maximale Geschwindigkeit!) function fallbackToInjectDownload(videoUrl, autoName) { const event = new CustomEvent('downloadRequested', { detail: { url: videoUrl, name: autoName, // Kein .mp4 hier → schneller Chunk-Download! version: location.pathname, } }); document.dispatchEvent(event); } // Download-Button hinzufügen function addVideoDownloadButton(video) { if (isInTopicList()) return; if (!video || video.tagName !== "VIDEO" || video.classList.contains("sticker-media") || video.classList.contains("media-sticker")) return; if (!video.currentSrc && !video.src) return; let container = video.closest('.media-inner') || video.parentElement || video.closest('div') || video; if (!container || container.querySelector('.tg-video-btn')) return; const computed = window.getComputedStyle(container); if (computed.position === 'static') container.style.position = 'relative'; const btn = document.createElement('button'); btn.className = 'tg-video-btn'; btn.innerText = '⬇ DOWNLOAD'; btn.title = 'Video herunterladen'; btn.style.cssText = ` position: absolute !important; top: 10px !important; right: 10px !important; z-index: 100000 !important; background: #e63946 !important; color: white !important; border: none !important; border-radius: 8px !important; padding: 8px 12px !important; font-size: 14px !important; font-weight: bold !important; cursor: pointer !important; box-shadow: 0 4px 12px rgba(0,0,0,0.6) !important; `; btn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); const videoUrl = video.currentSrc || video.src; if (!videoUrl) { alert('Video-URL nicht verfügbar. Bitte das Video einmal kurz abspielen.'); return; } const autoName = extractTelegramFilename(video); console.log('=== DOWNLOAD INFO ==='); console.log('Final filename (ohne .mp4):', autoName); fallbackToInjectDownload(videoUrl, autoName); }); container.appendChild(btn); } // Alle Videos verarbeiten function processVideos() { if (isInTopicList()) return; document.querySelectorAll('video:not([data-tg-video])').forEach(video => { try { video.dataset.tgVideo = 'true'; addVideoDownloadButton(video); } catch (err) { console.error('Error processing video element', err); } }); } const observer = new MutationObserver(processVideos); observer.observe(document.body, { childList: true, subtree: true }); setInterval(processVideos, 1200); processVideos(); // Completed-Event weiterleiten document.addEventListener('telDownloaderCompleted', (e) => { try { const detail = e.detail || {}; const extId = detail.extensionId || chrome.runtime.id; chrome.runtime.sendMessage(extId, { action: detail.action || 'videoProgressCompleted', videoId: detail.videoId, clientId: detail.clientId, name: detail.name, version: detail.version, locale: detail.locale }, (res) => { if (chrome.runtime.lastError) { console.error('Error sending message to background:', chrome.runtime.lastError.message); } }); } catch (err) { console.error('Failed to forward telDownloaderCompleted to extension:', err); } }); // inject.js laden const script = document.createElement('script'); script.src = chrome.runtime.getURL('inject.js'); script.setAttribute('data-extension-id', chrome.runtime.id); (document.head || document.documentElement).appendChild(script);