inject.js aktualisiert
This commit is contained in:
952
inject.js
952
inject.js
@@ -1,396 +1,558 @@
|
||||
(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);
|
||||
}
|
||||
});
|
||||
|
||||
// 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 {
|
||||
const safe = sanitizeFilenamePreserveFriendly(suggestedName || 'image') + "_" + Date.now() + ".jpg";
|
||||
const a = document.createElement("a");
|
||||
document.body.appendChild(a);
|
||||
a.href = src;
|
||||
a.download = safe;
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
} catch (err) {
|
||||
console.error('tel_download_image error', err);
|
||||
}
|
||||
};
|
||||
|
||||
window.telDownloader = {
|
||||
startVideo: tel_download_video,
|
||||
startImage: tel_download_image
|
||||
};
|
||||
(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
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user