Files
TG-Video-Downloader/inject.js
2025-12-21 15:19:03 +00:00

558 lines
22 KiB
JavaScript
Raw Permalink 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.
(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
};
})();