4 Commits
1.4 ... main

Author SHA1 Message Date
7f7a59f54f inject.js aktualisiert 2025-12-21 15:19:03 +00:00
a89785a5c8 content_tg.js aktualisiert 2025-12-21 15:18:49 +00:00
53dfbbddaa manifest.json aktualisiert 2025-12-21 15:18:27 +00:00
8e640efa37 background.js aktualisiert 2025-12-21 15:18:10 +00:00
4 changed files with 1214 additions and 875 deletions

View File

@@ -1,6 +1,3 @@
// background.js
// Service worker / background script: startet chrome.downloads.download und sendet Fortschritte an die Content-Tab.
const downloadsMap = new Map(); // downloadId -> { tabId, filename } const downloadsMap = new Map(); // downloadId -> { tabId, filename }
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {

View File

@@ -11,82 +11,23 @@ function sanitizeFriendly(name) {
} }
// Extrahiere den Original-Dateinamen aus Telegram-Metadaten // Extrahiere den Original-Dateinamen aus Telegram-Metadaten
function extractTelegramFilename(video) { function extractTelegramFilename(element) {
try { try {
const videoUrl = video.currentSrc || video.src; const elementUrl = element.currentSrc || element.src;
console.log('=== FILENAME EXTRACTION ==='); console.log('=== FILENAME EXTRACTION ===');
console.log('Video URL:', videoUrl); console.log('Element URL:', elementUrl);
// 1. Suche die Message, die das Video enthält // 1. Suche die Message, die das Element enthält
const messageContainer = video.closest('.message, .Message, .bubble, .media-container'); const messageContainer = element.closest('.message, .Message, .bubble, .media-container');
if (!messageContainer) { if (!messageContainer) {
console.warn('No message container found'); console.warn('No message container found');
return 'telegram_video_' + Date.now(); return 'telegram_media_' + Date.now();
} }
console.log('Message container found'); console.log('Message container found');
// === PRIORITÄT 1: VERBESSERTES ALBUM/SERIEN-HANDLING === // 2. Zuerst: Suche nach .text-content (funktioniert für einzelne Medien)
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'); const textContent = messageContainer.querySelector('.text-content, .text-content.clearfix');
if (textContent) { if (textContent) {
const fullText = textContent.innerText || textContent.textContent; const fullText = textContent.innerText || textContent.textContent;
@@ -102,7 +43,52 @@ function extractTelegramFilename(video) {
} }
} }
// 3. Weitere Titel-Selektoren // 3. Bei Alben/Serien
const albumContainer = messageContainer.querySelector('.album, .Album, [class*="album"]');
if (albumContainer) {
console.log('Album/Series detected');
let elementContainer = element.closest('.album-item-select-wrapper, .album-item, .media-item');
if (elementContainer) {
const allContainers = albumContainer.querySelectorAll('.album-item-select-wrapper, .album-item');
let elementIndex = -1;
for (let i = 0; i < allContainers.length; i++) {
if (allContainers[i] === elementContainer || allContainers[i].contains(element)) {
elementIndex = i;
break;
}
}
console.log('Element index in album:', elementIndex);
const contentInner = messageContainer.querySelector('.content-inner, .text, .message-text');
if (contentInner && elementIndex >= 0) {
const fullText = contentInner.innerText || contentInner.textContent;
const episodePattern = /S\d{2}E\d{2}\s*-\s*[^\n]+/gi;
const episodes = fullText.match(episodePattern);
if (episodes && episodes[elementIndex]) {
const episodeName = episodes[elementIndex].trim();
console.log('Found episode name for index', elementIndex, ':', episodeName);
return sanitizeFriendly(episodeName);
}
const lines = fullText.split('\n').map(l => l.trim()).filter(l => l.length > 5);
const episodeLines = lines.filter(line =>
/S\d{2}E\d{2}/.test(line) ||
(line.includes('-') && line.length > 10 && line.length < 150)
);
if (episodeLines[elementIndex]) {
console.log('Found episode from lines:', episodeLines[elementIndex]);
return sanitizeFriendly(episodeLines[elementIndex]);
}
}
}
}
// 4. Weitere Selektoren
const titleSelectors = [ const titleSelectors = [
'.media-caption-text', '.media-caption-text',
'.media-caption', '.media-caption',
@@ -111,15 +97,13 @@ function extractTelegramFilename(video) {
'.document-name', '.document-name',
'.file-name', '.file-name',
'.name', '.name',
'.title:not(.peer-title):not(.top-bar)', '.title:not(.peer-title):not(.top-bar)'
'[class*="caption"]',
'[class*="title"]:not([class*="peer"]):not([class*="top"])'
]; ];
for (const selector of titleSelectors) { for (const selector of titleSelectors) {
const element = messageContainer.querySelector(selector); const el = messageContainer.querySelector(selector);
if (element) { if (el) {
const text = element.innerText?.trim() || element.textContent?.trim(); const text = el.innerText?.trim() || el.textContent?.trim();
if (text && text.length > 0 && text.length < 300) { if (text && text.length > 0 && text.length < 300) {
if (!text.includes('⬇') && if (!text.includes('⬇') &&
!text.toLowerCase().includes('download') && !text.toLowerCase().includes('download') &&
@@ -131,131 +115,23 @@ function extractTelegramFilename(video) {
} }
} }
// 4. Textblöcke, Text-Nodes, React Props usw. (wie in deinem Original) // 5. Fallbacks
const allTextElements = messageContainer.querySelectorAll('div, span, p'); const match = elementUrl?.match(/document(\d+)/);
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) { if (match) {
console.warn('Using document ID as fallback'); console.warn('Using document ID as fallback');
return 'telegram_doc_' + match[1]; return 'telegram_doc_' + match[1];
} }
console.warn('Could not extract filename, using timestamp fallback'); console.warn('Could not extract filename, using timestamp fallback');
return 'telegram_video_' + Date.now(); return 'telegram_media_' + Date.now();
} catch (err) { } catch (err) {
console.error('Error extracting filename:', err); console.error('Error extracting filename:', err);
return 'telegram_video_' + Date.now(); return 'telegram_media_' + Date.now();
} }
} }
// Durchsuche React Fiber Tree // Video-Button
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) { function addVideoDownloadButton(video) {
if (isInTopicList()) return; if (isInTopicList()) return;
if (!video || video.tagName !== "VIDEO" || if (!video || video.tagName !== "VIDEO" ||
@@ -263,7 +139,7 @@ function addVideoDownloadButton(video) {
video.classList.contains("media-sticker")) return; video.classList.contains("media-sticker")) return;
if (!video.currentSrc && !video.src) return; if (!video.currentSrc && !video.src) return;
let container = video.closest('.media-inner') || video.parentElement || video.closest('div') || video; let container = video.parentElement || video.closest('div') || video;
if (!container || container.querySelector('.tg-video-btn')) return; if (!container || container.querySelector('.tg-video-btn')) return;
const computed = window.getComputedStyle(container); const computed = window.getComputedStyle(container);
@@ -271,7 +147,7 @@ function addVideoDownloadButton(video) {
const btn = document.createElement('button'); const btn = document.createElement('button');
btn.className = 'tg-video-btn'; btn.className = 'tg-video-btn';
btn.innerText = '⬇ DOWNLOAD'; btn.innerText = '⬇ Download';
btn.title = 'Video herunterladen'; btn.title = 'Video herunterladen';
btn.style.cssText = ` btn.style.cssText = `
position: absolute !important; position: absolute !important;
@@ -295,58 +171,362 @@ function addVideoDownloadButton(video) {
const videoUrl = video.currentSrc || video.src; const videoUrl = video.currentSrc || video.src;
if (!videoUrl) { if (!videoUrl) {
alert('Video-URL nicht verfügbar. Bitte das Video einmal kurz abspielen.'); alert('Video-URL nicht verfügbar.');
return; return;
} }
const autoName = extractTelegramFilename(video); const autoName = extractTelegramFilename(video);
console.log('=== VIDEO DOWNLOAD ===', autoName);
console.log('=== DOWNLOAD INFO ==='); const event = new CustomEvent('downloadRequested', {
console.log('Final filename (ohne .mp4):', autoName); detail: { url: videoUrl, name: autoName, version: location.pathname }
});
fallbackToInjectDownload(videoUrl, autoName); document.dispatchEvent(event);
}); });
container.appendChild(btn); container.appendChild(btn);
} }
// Alle Videos verarbeiten // Bild-Button
function processVideos() { function addImageDownloadButton(img) {
if (isInTopicList()) return; if (isInTopicList()) return;
if (!img || img.tagName !== "IMG") return;
if (img.classList.contains('emoji') || img.classList.contains('thumbnail') ||
img.width < 100 || img.height < 100) return;
let container = img.closest('.media-inner') || img.closest('.Message') || img.parentElement;
if (!container || container.querySelector('.tg-img-btn')) return;
if (window.getComputedStyle(container).position === 'static') {
container.style.position = 'relative';
}
const btn = document.createElement('button');
btn.className = 'tg-img-btn';
btn.innerText = '⬇';
btn.title = 'Bild herunterladen';
btn.style.cssText = `
position: absolute !important;
top: 5px !important;
left: 5px !important;
z-index: 999999 !important;
background: rgba(42, 157, 143, 0.95) !important;
color: white !important;
border: 2px solid white !important;
border-radius: 50% !important;
width: 44px !important;
height: 44px !important;
font-size: 22px !important;
cursor: pointer !important;
box-shadow: 0 2px 8px rgba(0,0,0,0.8) !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
pointer-events: auto !important;
`;
btn.onmouseenter = () => btn.style.transform = 'scale(1.15)';
btn.onmouseleave = () => btn.style.transform = 'scale(1)';
btn.addEventListener('click', (e) => {
e.stopPropagation();
e.preventDefault();
const imgUrl = img.src || img.currentSrc;
if (!imgUrl || imgUrl.startsWith('data:')) {
alert('Bild-URL nicht verfügbar.');
return;
}
const autoName = extractTelegramFilename(img) || 'telegram_image';
console.log('=== IMAGE DOWNLOAD ===', autoName);
const event = new CustomEvent('imageDownloadRequested', {
detail: { url: imgUrl, name: autoName }
});
document.dispatchEvent(event);
});
container.appendChild(btn);
console.log('✅ Image button added');
}
// Audio-Button
function addAudioDownloadButton(audioContainer) {
if (isInTopicList()) return;
if (!audioContainer || audioContainer.querySelector('.tg-audio-btn')) return;
const audio = audioContainer.querySelector('audio');
if (!audio) return;
if (window.getComputedStyle(audioContainer).position === 'static') {
audioContainer.style.position = 'relative';
}
const btn = document.createElement('button');
btn.className = 'tg-audio-btn';
btn.innerText = '⬇ Download';
btn.title = 'Audio herunterladen';
btn.style.cssText = `
position: absolute !important;
top: 10px !important;
right: 10px !important;
z-index: 999999 !important;
background: rgba(244, 162, 97, 0.95) !important;
color: white !important;
border: 2px solid white !important;
border-radius: 50% !important;
width: 40px !important;
height: 40px !important;
font-size: 20px !important;
cursor: pointer !important;
box-shadow: 0 2px 8px rgba(0,0,0,0.8) !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
pointer-events: auto !important;
`;
btn.onmouseenter = () => btn.style.transform = 'scale(1.15)';
btn.onmouseleave = () => btn.style.transform = 'scale(1)';
btn.addEventListener('click', (e) => {
e.stopPropagation();
e.preventDefault();
const audioUrl = audio.currentSrc || audio.src;
if (!audioUrl) {
alert('Audio-URL nicht verfügbar.');
return;
}
const autoName = extractTelegramFilename(audioContainer) || 'telegram_audio';
console.log('=== AUDIO DOWNLOAD ===', autoName);
const event = new CustomEvent('audioDownloadRequested', {
detail: { url: audioUrl, name: autoName }
});
document.dispatchEvent(event);
});
audioContainer.appendChild(btn);
console.log('✅ Audio button added');
}
// Document-Button (für .mp3, .zip, .pdf, etc.)
function addDocumentDownloadButton(docContainer) {
if (isInTopicList()) return;
if (!docContainer || docContainer.querySelector('.tg-doc-btn')) return;
// Suche nach dem Dateinamen - verschiedene mögliche Selektoren
const docNameEl = docContainer.querySelector('.file-title, .document-name, .file-name');
const docName = docNameEl?.innerText?.trim() || docNameEl?.title || '';
// Suche nach File-Extension
const extEl = docContainer.querySelector('.file-ext');
const fileExt = extEl?.innerText?.trim()?.toLowerCase() || '';
// Wenn kein Name gefunden wurde, ignoriere
if (!docName && !fileExt) return;
// Prüfe Dateityp basierend auf Extension oder Name
const isAudio = /\.(mp3|ogg|wav|m4a|flac|aac|opus)$/i.test(docName) ||
['mp3', 'ogg', 'wav', 'm4a', 'flac', 'aac', 'opus'].includes(fileExt);
const isArchive = /\.(zip|rar|7z|tar|gz)$/i.test(docName) ||
['zip', 'rar', '7z', 'tar', 'gz'].includes(fileExt);
const isDocument = /\.(pdf|doc|docx|txt|xlsx|xls|ppt)$/i.test(docName) ||
['pdf', 'doc', 'docx', 'txt', 'xlsx', 'xls', 'ppt'].includes(fileExt);
if (window.getComputedStyle(docContainer).position === 'static') {
docContainer.style.position = 'relative';
}
// Icon und Farbe basierend auf Dateityp
let icon = '⬇';
let bgColor = 'rgba(168, 85, 247, 0.95)';
let title = 'Datei herunterladen';
if (isAudio) {
icon = '⬇';
bgColor = 'rgba(244, 162, 97, 0.95)';
title = 'Audio herunterladen';
} else if (isArchive) {
icon = '⬇';
bgColor = 'rgba(59, 130, 246, 0.95)';
title = 'Archiv herunterladen';
} else if (isDocument) {
icon = '⬇';
bgColor = 'rgba(168, 85, 247, 0.95)';
title = 'Dokument herunterladen';
}
const btn = document.createElement('button');
btn.className = 'tg-doc-btn';
btn.innerText = icon;
btn.title = title + (docName ? ': ' + docName : '');
btn.style.cssText = `
position: absolute !important;
top: 10px !important;
right: 10px !important;
z-index: 999999 !important;
background: ${bgColor} !important;
color: white !important;
border: 2px solid white !important;
border-radius: 50% !important;
width: 40px !important;
height: 40px !important;
font-size: 20px !important;
cursor: pointer !important;
box-shadow: 0 2px 8px rgba(0,0,0,0.8) !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
pointer-events: auto !important;
`;
btn.onmouseenter = () => btn.style.transform = 'scale(1.15)';
btn.onmouseleave = () => btn.style.transform = 'scale(1)';
btn.addEventListener('click', (e) => {
e.stopPropagation();
e.preventDefault();
// Versuche Download-URL zu finden
let downloadUrl = null;
// Methode 1: Suche nach data-doc-id im File-Element oder Container
const fileEl = docContainer.querySelector('.File');
const docId = fileEl?.dataset?.docId ||
docContainer.dataset?.docId ||
docContainer.closest('[data-doc-id]')?.dataset?.docId;
if (docId) {
// Konstruiere die Telegram progressive URL
downloadUrl = `${location.origin}/a/progressive/document${docId}`;
}
// Methode 2: Suche nach existierendem Download-Link
if (!downloadUrl) {
const downloadLink = docContainer.querySelector('a[href*="document"], a[download]');
if (downloadLink) {
downloadUrl = downloadLink.href;
}
}
// Methode 3: Suche im parent Message nach document-ID
if (!downloadUrl) {
const message = docContainer.closest('.Message');
const messageId = message?.id?.replace('message-', '');
if (messageId) {
// Versuche document-ID aus verschiedenen Quellen
const allDataIds = message.querySelectorAll('[data-doc-id]');
if (allDataIds.length > 0) {
const firstDocId = allDataIds[0].dataset.docId;
downloadUrl = `${location.origin}/a/progressive/document${firstDocId}`;
}
}
}
if (!downloadUrl) {
alert('Download-URL nicht gefunden. Bitte klicke zuerst auf die Datei um sie zu laden.');
return;
}
const finalName = docName || 'telegram_file';
console.log('=== DOCUMENT DOWNLOAD ===');
console.log('Name:', finalName);
console.log('URL:', downloadUrl);
const eventName = isAudio ? 'audioDownloadRequested' : 'documentDownloadRequested';
const event = new CustomEvent(eventName, {
detail: { url: downloadUrl, name: finalName }
});
document.dispatchEvent(event);
});
docContainer.appendChild(btn);
console.log('✅ Document button added:', docName || fileExt);
}
// Alle Medien verarbeiten
function processMedia() {
if (isInTopicList()) return;
// Videos
document.querySelectorAll('video:not([data-tg-video])').forEach(video => { document.querySelectorAll('video:not([data-tg-video])').forEach(video => {
try { try {
video.dataset.tgVideo = 'true'; video.dataset.tgVideo = 'true';
addVideoDownloadButton(video); addVideoDownloadButton(video);
} catch (err) { } catch (err) {
console.error('Error processing video element', err); console.error('Error processing video', err);
}
});
// Bilder
document.querySelectorAll('img.full-media').forEach(img => {
try {
const container = img.closest('.media-inner, .Message') || img.parentElement;
if (container && !container.querySelector('.tg-img-btn')) {
addImageDownloadButton(img);
}
} catch (err) {
console.error('Error processing image', err);
}
});
// Audio mit <audio> Element
document.querySelectorAll('.Audio, .audio').forEach(container => {
try {
if (!container.querySelector('.tg-audio-btn')) {
const audio = container.querySelector('audio');
if (audio) addAudioDownloadButton(container);
}
} catch (err) {
console.error('Error processing audio', err);
}
});
// Documents (.mp3, .zip, .pdf, etc.) - erkennbar an .document Container MIT .File Element
document.querySelectorAll('.document, .Document').forEach(container => {
try {
// Prüfe ob es ein .File Element gibt (das ist der Indikator für Documents)
const fileEl = container.querySelector('.File');
const fileTitle = container.querySelector('.file-title');
if ((fileEl || fileTitle) && !container.querySelector('.tg-doc-btn')) {
addDocumentDownloadButton(container);
}
} catch (err) {
console.error('Error processing document', err);
} }
}); });
} }
const observer = new MutationObserver(processVideos); const observer = new MutationObserver(processMedia);
observer.observe(document.body, { childList: true, subtree: true }); observer.observe(document.body, { childList: true, subtree: true });
setInterval(processVideos, 1200); setInterval(processMedia, 1200);
processVideos(); processMedia();
// Completed-Event weiterleiten console.log('✅ TG Downloader loaded - Video, Image & Audio support enabled');
// Event-Weiterleitung
document.addEventListener('telDownloaderCompleted', (e) => { document.addEventListener('telDownloaderCompleted', (e) => {
try { try {
const detail = e.detail || {}; const detail = e.detail || {};
const extId = detail.extensionId || chrome.runtime.id; chrome.runtime.sendMessage(detail.extensionId || chrome.runtime.id, {
chrome.runtime.sendMessage(extId, {
action: detail.action || 'videoProgressCompleted', action: detail.action || 'videoProgressCompleted',
videoId: detail.videoId, videoId: detail.videoId,
clientId: detail.clientId, clientId: detail.clientId,
name: detail.name, name: detail.name,
version: detail.version, version: detail.version,
locale: detail.locale locale: detail.locale
}, (res) => { }, () => {
if (chrome.runtime.lastError) { if (chrome.runtime.lastError) {
console.error('Error sending message to background:', chrome.runtime.lastError.message); console.error('Error sending message:', chrome.runtime.lastError.message);
} }
}); });
} catch (err) { } catch (err) {
console.error('Failed to forward telDownloaderCompleted to extension:', err); console.error('Failed to forward event:', err);
} }
}); });

178
inject.js
View File

@@ -28,6 +28,26 @@
} }
}); });
document.addEventListener("audioDownloadRequested", function(e) {
try {
let t = e.detail.url;
let l = e.detail.name;
tel_download_audio(t, l);
} catch (err) {
console.error('Error in audioDownloadRequested handler', err);
}
});
document.addEventListener("documentDownloadRequested", function(e) {
try {
let t = e.detail.url;
let l = e.detail.name;
tel_download_document(t, l);
} catch (err) {
console.error('Error in documentDownloadRequested handler', err);
}
});
// Hilfsfunktion: progress-bar-container // Hilfsfunktion: progress-bar-container
function ensureProgressContainer() { function ensureProgressContainer() {
let container = document.getElementById("tel-downloader-progress-bar-container"); let container = document.getElementById("tel-downloader-progress-bar-container");
@@ -377,20 +397,162 @@
const tel_download_image = (src, suggestedName) => { const tel_download_image = (src, suggestedName) => {
try { try {
const safe = sanitizeFilenamePreserveFriendly(suggestedName || 'image') + "_" + Date.now() + ".jpg"; console.log('=== IMAGE DOWNLOAD START ===');
const a = document.createElement("a"); console.log('Image URL:', src);
document.body.appendChild(a); console.log('Suggested name:', suggestedName);
a.href = src;
a.download = safe; const baseName = sanitizeFilenamePreserveFriendly(suggestedName || 'telegram_image');
a.click(); const hasExt = /\.(jpg|jpeg|png|gif|webp)$/i.test(baseName);
document.body.removeChild(a); const finalName = hasExt ? baseName : (baseName + '.jpg');
fetch(src, {
method: "GET",
credentials: "include"
}).then(res => {
if (!res.ok) throw new Error("Fetch failed: " + res.status);
// Detect image type
const contentType = res.headers.get("Content-Type") || "";
let ext = "jpg";
if (contentType.includes("png")) ext = "png";
else if (contentType.includes("gif")) ext = "gif";
else if (contentType.includes("webp")) ext = "webp";
const finalNameWithExt = hasExt ? baseName : (baseName + '.' + ext);
return res.blob().then(blob => {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
document.body.appendChild(a);
a.href = url;
a.download = finalNameWithExt;
a.click();
document.body.removeChild(a);
setTimeout(() => URL.revokeObjectURL(url), 2000);
console.log('Image downloaded:', finalNameWithExt);
});
}).catch(err => {
console.error('Image download error:', err);
alert('Bild-Download fehlgeschlagen. Versuche es erneut.');
});
} catch (err) { } catch (err) {
console.error('tel_download_image error', err); console.error('tel_download_image error', err);
} }
}; };
const tel_download_audio = (src, suggestedName) => {
try {
console.log('=== AUDIO DOWNLOAD START ===');
console.log('Audio URL:', src);
console.log('Suggested name:', suggestedName);
const baseName = sanitizeFilenamePreserveFriendly(suggestedName || 'telegram_audio');
const hasExt = /\.(mp3|ogg|wav|m4a|opus)$/i.test(baseName);
const id = (Math.random() + 1).toString(36).substring(2, 10) + "_" + Date.now().toString();
createProgressBar(id, baseName + (hasExt ? '' : '.mp3'));
updateProgress(id, baseName + (hasExt ? '' : '.mp3'), 0);
fetch(src, {
method: "GET",
credentials: "include"
}).then(res => {
if (!res.ok) throw new Error("Fetch failed: " + res.status);
const contentType = res.headers.get("Content-Type") || "";
let ext = "mp3";
if (contentType.includes("ogg")) ext = "ogg";
else if (contentType.includes("wav")) ext = "wav";
else if (contentType.includes("m4a")) ext = "m4a";
else if (contentType.includes("opus")) ext = "opus";
const finalName = hasExt ? baseName : (baseName + '.' + ext);
return res.blob().then(blob => {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
document.body.appendChild(a);
a.href = url;
a.download = finalName;
a.click();
document.body.removeChild(a);
setTimeout(() => URL.revokeObjectURL(url), 2000);
updateProgress(id, finalName, 100);
completeProgress(id, null, finalName, null, null);
console.log('Audio downloaded:', finalName);
});
}).catch(err => {
console.error('Audio download error:', err);
AbortProgress(id);
});
} catch (err) {
console.error('tel_download_audio error', err);
}
};
const tel_download_document = (src, suggestedName) => {
try {
console.log('=== DOCUMENT DOWNLOAD START ===');
console.log('Document URL:', src);
console.log('Suggested name:', suggestedName);
const baseName = sanitizeFilenamePreserveFriendly(suggestedName || 'telegram_file');
const hasExt = /\.\w{2,5}$/i.test(baseName);
const id = (Math.random() + 1).toString(36).substring(2, 10) + "_" + Date.now().toString();
createProgressBar(id, baseName);
updateProgress(id, baseName, 0);
fetch(src, {
method: "GET",
credentials: "include"
}).then(res => {
if (!res.ok) throw new Error("Fetch failed: " + res.status);
const contentType = res.headers.get("Content-Type") || "";
let ext = "";
// Erkenne Extension aus Content-Type
if (contentType.includes("zip")) ext = "zip";
else if (contentType.includes("rar")) ext = "rar";
else if (contentType.includes("pdf")) ext = "pdf";
else if (contentType.includes("msword")) ext = "doc";
else if (contentType.includes("spreadsheet")) ext = "xlsx";
else if (contentType.includes("audio/mpeg")) ext = "mp3";
else if (contentType.includes("audio/")) ext = "mp3";
const finalName = hasExt ? baseName : (baseName + (ext ? '.' + ext : ''));
return res.blob().then(blob => {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
document.body.appendChild(a);
a.href = url;
a.download = finalName;
a.click();
document.body.removeChild(a);
setTimeout(() => URL.revokeObjectURL(url), 2000);
updateProgress(id, finalName, 100);
completeProgress(id, null, finalName, null, null);
console.log('Document downloaded:', finalName);
});
}).catch(err => {
console.error('Document download error:', err);
AbortProgress(id);
});
} catch (err) {
console.error('tel_download_document error', err);
}
};
window.telDownloader = { window.telDownloader = {
startVideo: tel_download_video, startVideo: tel_download_video,
startImage: tel_download_image startImage: tel_download_image,
startAudio: tel_download_audio,
startDocument: tel_download_document
}; };
})(); })();

View File

@@ -1,7 +1,7 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "TG Downloader - Free & Unlimited", "name": "TG Downloader - Free & Unlimited",
"version": "1.4", "version": "1.5",
"description": "Download Videos & Bilder aus Telegram Web.", "description": "Download Videos & Bilder aus Telegram Web.",
"permissions": ["storage", "downloads"], "permissions": ["storage", "downloads"],
"host_permissions": ["https://web.telegram.org/*"], "host_permissions": ["https://web.telegram.org/*"],