Files
TG-Video-Downloader/content_tg.js
2025-12-20 18:03:26 +00:00

357 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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);