(function () { const scriptElement = document.currentScript; const extensionId = scriptElement ? scriptElement.getAttribute("data-extension-id") : null; console.log('inject.js running, extensionId=', extensionId); // Empfang von "downloadRequested" (kommt vom content script) document.addEventListener("downloadRequested", function(e) { try { let t = e.detail.url; let l = e.detail.name; let r = e.detail.version; let o = e.detail.clientId; let n = e.detail.locale; tel_download_video(t, l, r, o, n); } catch (err) { console.error('Error in downloadRequested handler', err); } }); document.addEventListener("imageDownloadRequested", function(e) { try { let t = e.detail.url; let l = e.detail.name; tel_download_image(t, l); } catch (err) { console.error('Error in imageDownloadRequested handler', err); } }); 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 function ensureProgressContainer() { let container = document.getElementById("tel-downloader-progress-bar-container"); if (!container) { container = document.createElement("div"); container.id = "tel-downloader-progress-bar-container"; container.style.position = "fixed"; container.style.bottom = "0"; container.style.right = "0"; container.style.display = "flex"; container.style.flexDirection = "column"; container.style.gap = "0.4rem"; container.style.padding = "0.4rem"; container.style.zIndex = "1600"; document.body.appendChild(container); } return container; } const createProgressBar = (id, filename) => { const dark = document.querySelector("html")?.classList?.contains("night") || document.querySelector("html")?.classList?.contains("theme-dark"); const container = ensureProgressContainer(); container.style.zIndex = location.pathname.startsWith("/k/") ? "4" : "1600"; const n = document.createElement("div"); n.id = "tel-downloader-progress-" + id; n.style.width = "20rem"; n.style.marginTop = "0.4rem"; n.style.padding = "0.6rem"; n.style.backgroundColor = dark ? "rgba(0,0,0,0.6)" : "rgba(0,0,0,0.3)"; n.style.borderRadius = "8px"; n.style.boxShadow = "0 4px 12px rgba(0,0,0,0.2)"; n.style.color = "white"; n.style.fontFamily = "sans-serif"; const s = document.createElement("div"); s.style.display = "flex"; s.style.justifyContent = "space-between"; s.style.alignItems = "center"; const i = document.createElement("p"); i.className = "filename"; i.style.margin = 0; i.style.color = "white"; i.style.fontSize = "0.9rem"; i.style.overflow = "hidden"; i.style.textOverflow = "ellipsis"; i.style.whiteSpace = "nowrap"; i.innerText = filename || "video"; const d = document.createElement("div"); d.style.cursor = "pointer"; d.style.fontSize = "1.2rem"; d.style.color = dark ? "#8a8a8a" : "white"; d.innerHTML = "×"; d.onclick = function() { if (n.parentElement) n.parentElement.removeChild(n); }; const a = document.createElement("div"); a.className = "progress"; a.style.backgroundColor = "#e2e2e2"; a.style.position = "relative"; a.style.width = "100%"; a.style.height = "1.6rem"; a.style.borderRadius = "2rem"; a.style.overflow = "hidden"; a.style.marginTop = "0.6rem"; const c = document.createElement("p"); c.style.position = "absolute"; c.style.zIndex = 5; c.style.left = "50%"; c.style.top = "50%"; c.style.transform = "translate(-50%, -50%)"; c.style.margin = 0; c.style.color = "black"; c.style.fontSize = "0.85rem"; c.innerText = "0%"; const p = document.createElement("div"); p.style.position = "absolute"; p.style.height = "100%"; p.style.width = "0%"; p.style.backgroundColor = "#3390ec"; p.style.left = "0"; p.style.top = "0"; a.appendChild(c); a.appendChild(p); s.appendChild(i); s.appendChild(d); n.appendChild(s); n.appendChild(a); container.appendChild(n); }; const updateProgress = (id, filename, percent) => { let r = document.getElementById("tel-downloader-progress-" + id); if (!r) return; const fname = filename || r.querySelector("p.filename")?.innerText || "video"; r.querySelector("p.filename").innerText = fname; const prog = r.querySelector("div.progress"); if (!prog) return; const txt = prog.querySelector("p"); const bar = prog.querySelector("div"); if (txt) txt.innerText = (percent || 0) + "%"; if (bar) bar.style.width = (percent || 0) + "%"; }; const completeProgress = (id, clientId, filename, version, locale) => { let n = document.getElementById("tel-downloader-progress-" + id); if (!n) return; let prog = n.querySelector("div.progress"); if (prog) { let txt = prog.querySelector("p"); if (txt) txt.innerText = "Completed"; let bar = prog.querySelector("div"); if (bar) { bar.style.backgroundColor = "#40DCA5"; bar.style.width = "100%"; } } document.dispatchEvent(new CustomEvent('telDownloaderCompleted', { detail: { extensionId: extensionId, action: "videoProgressCompleted", videoId: id, clientId: clientId, name: filename, version: version, locale: locale } })); }; const AbortProgress = id => { let t = document.getElementById("tel-downloader-progress-" + id); if (!t) return; let prog = t.querySelector("div.progress"); let txt = prog?.querySelector("p"); if (txt) { txt.innerText = "Download failed. Try again"; txt.style.fontSize = "12px"; } let bar = prog?.querySelector("div"); if (bar) { bar.style.backgroundColor = "#D16666"; bar.style.width = "100%"; } }; // Filename helper function sanitizeFilenamePreserveFriendly(name) { if (!name) return 'video'; return name.toString().replace(/[\/\\\?\%\*\:\|\"\<\>]/g, "_").trim(); } // Download-Funktion mit automatischem Dateinamen - OPTIMIERT FÜR GESCHWINDIGKEIT function tel_download_video(e, t, l, r, o) { if (!e) { console.error('No url provided to tel_download_video'); return; } let baseName = sanitizeFilenamePreserveFriendly(t || 'video'); const hasExt = /\.\w{1,5}$/.test(baseName); const id = (Math.random() + 1).toString(36).substring(2, 10) + "_" + Date.now().toString(); const finishDownload = (blob, finalName) => { try { const url = URL.createObjectURL(blob); const link = document.createElement("a"); document.body.appendChild(link); link.href = url; link.download = finalName; link.click(); document.body.removeChild(link); setTimeout(() => URL.revokeObjectURL(url), 2000); } catch (err) { console.error('Error finishing download', err); AbortProgress(id); } }; createProgressBar(id, baseName + (hasExt ? '' : '.mp4')); updateProgress(id, baseName + (hasExt ? '' : '.mp4'), 0); // GESCHWINDIGKEITS-OPTIMIERUNG: Größere Chunks + parallele Downloads const chunkSize = 10 * 1024 * 1024; // 10MB chunks (statt 1MB) const maxParallel = 3; // 3 parallele Downloads let chunks = []; let totalSize = null; let detectedExt = "mp4"; let activeDownloads = 0; let downloadComplete = false; // Zuerst: Hole die Gesamtgröße fetch(e, { method: "HEAD", credentials: "include" }).then(res => { const contentLength = res.headers.get("Content-Length"); const contentType = res.headers.get("Content-Type") || ""; const mime = contentType.split(";")[0] || ""; if (mime.startsWith("video/")) { detectedExt = mime.split("/")[1] || detectedExt; } if (contentLength) { totalSize = parseInt(contentLength, 10); console.log('Total file size:', (totalSize / 1024 / 1024).toFixed(2), 'MB'); // Berechne wie viele Chunks wir brauchen const numChunks = Math.ceil(totalSize / chunkSize); chunks = new Array(numChunks).fill(null); // Starte parallele Downloads for (let i = 0; i < Math.min(maxParallel, numChunks); i++) { downloadChunk(i); } } else { // Fallback: sequentieller Download ohne Größeninfo sequentialDownload(); } }).catch(err => { console.warn('HEAD request failed, using sequential download'); sequentialDownload(); }); function downloadChunk(chunkIndex) { if (downloadComplete || chunkIndex >= chunks.length) return; activeDownloads++; const start = chunkIndex * chunkSize; const end = Math.min(start + chunkSize - 1, totalSize - 1); fetch(e, { method: "GET", headers: { Range: `bytes=${start}-${end}` }, credentials: "include" }).then(res => { if (![200, 206].includes(res.status)) { throw Error("Non 200/206 response: " + res.status); } return res.blob(); }).then(blob => { chunks[chunkIndex] = blob; activeDownloads--; // Update Progress const downloaded = chunks.filter(c => c !== null).length; const percent = Math.round(100 * downloaded / chunks.length); updateProgress(id, baseName + (hasExt ? "" : "." + detectedExt), percent); // Check ob fertig if (chunks.every(c => c !== null)) { downloadComplete = true; const finalName = hasExt ? baseName : (baseName + "." + detectedExt); const merged = new Blob(chunks, { type: "video/" + detectedExt }); finishDownload(merged, finalName); completeProgress(id, r, finalName, l, o); } else { // Starte nächsten Chunk const nextChunk = chunks.findIndex(c => c === null); if (nextChunk !== -1) { downloadChunk(nextChunk); } } }).catch(err => { console.error('Chunk download error:', err); activeDownloads--; // Retry nach kurzer Pause setTimeout(() => downloadChunk(chunkIndex), 500); }); } function sequentialDownload() { // Fallback: Sequentieller Download für Server ohne Range-Support let blobs = [], s = 0; const rangedFetch = () => { fetch(e, { method: "GET", headers: { Range: `bytes=${s}-` }, credentials: "include" }).then(res2 => { if (![200, 206].includes(res2.status)) { throw Error("Non 200/206 response: " + res2.status); } const contentType2 = res2.headers.get("Content-Type") || ""; const mime2 = contentType2.split(";")[0] || ""; if (mime2.startsWith("video/")) { detectedExt = mime2.split("/")[1] || detectedExt; } const cr = res2.headers.get("Content-Range"); if (cr) { const match = cr.match(/^bytes (\d+)-(\d+)\/(\d+)$/); if (match) { const start = parseInt(match[1], 10); const end = parseInt(match[2], 10); const total = parseInt(match[3], 10); s = end + 1; totalSize = total; const percent = Math.min(100, Math.round(100 * s / totalSize)); updateProgress(id, baseName + (hasExt ? "" : "." + detectedExt), percent); } } else if (res2.status === 200) { const contentLength = res2.headers.get("Content-Length"); if (contentLength) { totalSize = parseInt(contentLength, 10); } } return res2.blob(); }).then(chunkBlob => { blobs.push(chunkBlob); if (totalSize && s >= totalSize) { const finalName = hasExt ? baseName : (baseName + "." + detectedExt); const merged = new Blob(blobs, { type: "video/" + detectedExt }); finishDownload(merged, finalName); completeProgress(id, r, finalName, l, o); } else { setTimeout(rangedFetch, 10); } }).catch(err2 => { console.error('Sequential download error', err2); AbortProgress(id); }); }; rangedFetch(); } } const tel_download_image = (src, suggestedName) => { try { console.log('=== IMAGE DOWNLOAD START ==='); console.log('Image URL:', src); console.log('Suggested name:', suggestedName); const baseName = sanitizeFilenamePreserveFriendly(suggestedName || 'telegram_image'); const hasExt = /\.(jpg|jpeg|png|gif|webp)$/i.test(baseName); 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) { 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 = { startVideo: tel_download_video, startImage: tel_download_image, startAudio: tel_download_audio, startDocument: tel_download_document }; })();