Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f7a59f54f | |||
| a89785a5c8 | |||
| 53dfbbddaa | |||
| 8e640efa37 |
193
background.js
193
background.js
@@ -1,99 +1,96 @@
|
|||||||
// background.js
|
const downloadsMap = new Map(); // downloadId -> { tabId, filename }
|
||||||
// Service worker / background script: startet chrome.downloads.download und sendet Fortschritte an die Content-Tab.
|
|
||||||
|
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||||
const downloadsMap = new Map(); // downloadId -> { tabId, filename }
|
if (!msg || !msg.action) {
|
||||||
|
sendResponse({ status: "unknown" });
|
||||||
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
return;
|
||||||
if (!msg || !msg.action) {
|
}
|
||||||
sendResponse({ status: "unknown" });
|
|
||||||
return;
|
if (msg.action === "videoProgressCompleted") {
|
||||||
}
|
console.log('Video completed:', msg.videoId, 'name:', msg.name);
|
||||||
|
sendResponse({ status: "success" });
|
||||||
if (msg.action === "videoProgressCompleted") {
|
return;
|
||||||
console.log('Video completed:', msg.videoId, 'name:', msg.name);
|
}
|
||||||
sendResponse({ status: "success" });
|
|
||||||
return;
|
if (msg.action === "startBackgroundDownload") {
|
||||||
}
|
const url = msg.url;
|
||||||
|
let filename = msg.filename || ('download_' + Date.now());
|
||||||
if (msg.action === "startBackgroundDownload") {
|
|
||||||
const url = msg.url;
|
if (!url) {
|
||||||
let filename = msg.filename || ('download_' + Date.now());
|
sendResponse({ status: "error", message: "No URL provided" });
|
||||||
|
return;
|
||||||
if (!url) {
|
}
|
||||||
sendResponse({ status: "error", message: "No URL provided" });
|
|
||||||
return;
|
try {
|
||||||
}
|
chrome.downloads.download(
|
||||||
|
{
|
||||||
try {
|
url: url,
|
||||||
chrome.downloads.download(
|
filename: filename,
|
||||||
{
|
conflictAction: "uniquify",
|
||||||
url: url,
|
saveAs: false
|
||||||
filename: filename,
|
},
|
||||||
conflictAction: "uniquify",
|
downloadId => {
|
||||||
saveAs: false
|
if (chrome.runtime.lastError) {
|
||||||
},
|
console.error('chrome.downloads.download error:', chrome.runtime.lastError.message);
|
||||||
downloadId => {
|
sendResponse({ status: "error", message: chrome.runtime.lastError.message });
|
||||||
if (chrome.runtime.lastError) {
|
} else {
|
||||||
console.error('chrome.downloads.download error:', chrome.runtime.lastError.message);
|
const tabId = (sender && sender.tab && sender.tab.id) ? sender.tab.id : null;
|
||||||
sendResponse({ status: "error", message: chrome.runtime.lastError.message });
|
downloadsMap.set(downloadId, { tabId, filename });
|
||||||
} else {
|
sendResponse({ status: "started", downloadId });
|
||||||
const tabId = (sender && sender.tab && sender.tab.id) ? sender.tab.id : null;
|
}
|
||||||
downloadsMap.set(downloadId, { tabId, filename });
|
}
|
||||||
sendResponse({ status: "started", downloadId });
|
);
|
||||||
}
|
return true;
|
||||||
}
|
} catch (e) {
|
||||||
);
|
console.error('Exception starting download', e);
|
||||||
return true;
|
sendResponse({ status: "error", message: String(e) });
|
||||||
} catch (e) {
|
return;
|
||||||
console.error('Exception starting download', e);
|
}
|
||||||
sendResponse({ status: "error", message: String(e) });
|
}
|
||||||
return;
|
|
||||||
}
|
sendResponse({ status: "ok" });
|
||||||
}
|
});
|
||||||
|
|
||||||
sendResponse({ status: "ok" });
|
// Listen for download changes and forward progress/state to the tab
|
||||||
});
|
chrome.downloads.onChanged.addListener(delta => {
|
||||||
|
const info = downloadsMap.get(delta.id);
|
||||||
// Listen for download changes and forward progress/state to the tab
|
if (!info || !info.tabId) return;
|
||||||
chrome.downloads.onChanged.addListener(delta => {
|
|
||||||
const info = downloadsMap.get(delta.id);
|
if (delta.bytesReceived) {
|
||||||
if (!info || !info.tabId) return;
|
chrome.downloads.search({ id: delta.id }, items => {
|
||||||
|
const item = items && items[0];
|
||||||
if (delta.bytesReceived) {
|
if (!item) return;
|
||||||
chrome.downloads.search({ id: delta.id }, items => {
|
|
||||||
const item = items && items[0];
|
const received = item.bytesReceived || 0;
|
||||||
if (!item) return;
|
const total = item.totalBytes || 0;
|
||||||
|
const percent = total > 0 ? Math.round(100 * received / total) : null;
|
||||||
const received = item.bytesReceived || 0;
|
|
||||||
const total = item.totalBytes || 0;
|
chrome.tabs.sendMessage(info.tabId, {
|
||||||
const percent = total > 0 ? Math.round(100 * received / total) : null;
|
action: 'downloadProgress',
|
||||||
|
downloadId: delta.id,
|
||||||
chrome.tabs.sendMessage(info.tabId, {
|
bytesReceived: received,
|
||||||
action: 'downloadProgress',
|
totalBytes: total,
|
||||||
downloadId: delta.id,
|
percent
|
||||||
bytesReceived: received,
|
}, () => { /* ignore response */ });
|
||||||
totalBytes: total,
|
});
|
||||||
percent
|
}
|
||||||
}, () => { /* ignore response */ });
|
|
||||||
});
|
if (delta.state && delta.state.current === 'complete') {
|
||||||
}
|
chrome.tabs.sendMessage(info.tabId, {
|
||||||
|
action: 'downloadComplete',
|
||||||
if (delta.state && delta.state.current === 'complete') {
|
downloadId: delta.id,
|
||||||
chrome.tabs.sendMessage(info.tabId, {
|
filename: info.filename
|
||||||
action: 'downloadComplete',
|
}, () => { /* ignore response */ });
|
||||||
downloadId: delta.id,
|
downloadsMap.delete(delta.id);
|
||||||
filename: info.filename
|
}
|
||||||
}, () => { /* ignore response */ });
|
|
||||||
downloadsMap.delete(delta.id);
|
if (delta.state && delta.state.current === 'interrupted') {
|
||||||
}
|
chrome.tabs.sendMessage(info.tabId, {
|
||||||
|
action: 'downloadFailed',
|
||||||
if (delta.state && delta.state.current === 'interrupted') {
|
downloadId: delta.id,
|
||||||
chrome.tabs.sendMessage(info.tabId, {
|
filename: info.filename,
|
||||||
action: 'downloadFailed',
|
reason: delta.state && delta.state.current
|
||||||
downloadId: delta.id,
|
}, () => { /* ignore response */ });
|
||||||
filename: info.filename,
|
downloadsMap.delete(delta.id);
|
||||||
reason: delta.state && delta.state.current
|
}
|
||||||
}, () => { /* ignore response */ });
|
|
||||||
downloadsMap.delete(delta.id);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
892
content_tg.js
892
content_tg.js
@@ -1,357 +1,537 @@
|
|||||||
// Topic-Übersicht erkennen (keine Buttons dort)
|
// Topic-Übersicht erkennen (keine Buttons dort)
|
||||||
function isInTopicList() {
|
function isInTopicList() {
|
||||||
return document.querySelector('.topics-list, .topic-list, .forums-list, .chat-list .topic') !== null ||
|
return document.querySelector('.topics-list, .topic-list, .forums-list, .chat-list .topic') !== null ||
|
||||||
document.body.classList.contains('topics-mode');
|
document.body.classList.contains('topics-mode');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kleine Helfer-Funktion: freundlich sanitisieren (erhalte Leerzeichen, Klammern, Punkte)
|
// Kleine Helfer-Funktion: freundlich sanitisieren (erhalte Leerzeichen, Klammern, Punkte)
|
||||||
function sanitizeFriendly(name) {
|
function sanitizeFriendly(name) {
|
||||||
if (!name) return 'Telegram';
|
if (!name) return 'Telegram';
|
||||||
return name.toString().replace(/[\/\\\?\%\*\:\|\"\<\>]/g, "_").trim();
|
return name.toString().replace(/[\/\\\?\%\*\:\|\"\<\>]/g, "_").trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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');
|
const textContent = messageContainer.querySelector('.text-content, .text-content.clearfix');
|
||||||
if (albumContainer) {
|
if (textContent) {
|
||||||
console.log('Album erkannt → verbessertes Serien-Naming');
|
const fullText = textContent.innerText || textContent.textContent;
|
||||||
|
const firstLine = fullText.split('\n')[0].trim();
|
||||||
const videoContainer = video.closest('.album-item-select-wrapper');
|
|
||||||
if (videoContainer) {
|
if (firstLine.length > 5 &&
|
||||||
const allVideoContainers = albumContainer.querySelectorAll('.album-item-select-wrapper');
|
!firstLine.startsWith('@') &&
|
||||||
let videoIndex = -1;
|
!firstLine.startsWith('#') &&
|
||||||
for (let i = 0; i < allVideoContainers.length; i++) {
|
!firstLine.includes('⬇') &&
|
||||||
if (allVideoContainers[i] === videoContainer) {
|
!firstLine.match(/^\d{2}:\d{2}:\d{2}$/)) {
|
||||||
videoIndex = i;
|
console.log('Found filename from .text-content:', firstLine);
|
||||||
break;
|
return sanitizeFriendly(firstLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Video index in album:', videoIndex);
|
// 3. Bei Alben/Serien
|
||||||
|
const albumContainer = messageContainer.querySelector('.album, .Album, [class*="album"]');
|
||||||
if (videoIndex >= 0) {
|
if (albumContainer) {
|
||||||
const textContent = messageContainer.querySelector('.text-content.clearfix.with-meta, .text-content');
|
console.log('Album/Series detected');
|
||||||
if (textContent) {
|
|
||||||
const lines = textContent.innerText.split('\n')
|
let elementContainer = element.closest('.album-item-select-wrapper, .album-item, .media-item');
|
||||||
.map(l => l.trim())
|
if (elementContainer) {
|
||||||
.filter(l => l.length > 0);
|
const allContainers = albumContainer.querySelectorAll('.album-item-select-wrapper, .album-item');
|
||||||
|
let elementIndex = -1;
|
||||||
console.log('Album text lines:', lines);
|
for (let i = 0; i < allContainers.length; i++) {
|
||||||
|
if (allContainers[i] === elementContainer || allContainers[i].contains(element)) {
|
||||||
const seriesTitle = lines[0] || 'Unknown Series';
|
elementIndex = i;
|
||||||
|
break;
|
||||||
// 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];
|
console.log('Element index in album:', elementIndex);
|
||||||
const match = line.match(/^(S\d{2}E\d{2})\s*-\s*(.+)$/);
|
|
||||||
if (match) {
|
const contentInner = messageContainer.querySelector('.content-inner, .text, .message-text');
|
||||||
const code = match[1];
|
if (contentInner && elementIndex >= 0) {
|
||||||
const title = match[2].trim();
|
const fullText = contentInner.innerText || contentInner.textContent;
|
||||||
const finalName = `${sanitizeFriendly(seriesTitle)} - ${code} - ${sanitizeFriendly(title)}`;
|
|
||||||
console.log('Perfekter Serien-Dateiname:', finalName);
|
const episodePattern = /S\d{2}E\d{2}\s*-\s*[^\n]+/gi;
|
||||||
return finalName;
|
const episodes = fullText.match(episodePattern);
|
||||||
}
|
|
||||||
}
|
if (episodes && episodes[elementIndex]) {
|
||||||
|
const episodeName = episodes[elementIndex].trim();
|
||||||
// Fallback: Zeile nach Serientitel als Episodentitel
|
console.log('Found episode name for index', elementIndex, ':', episodeName);
|
||||||
if (lines.length > videoIndex + 1) {
|
return sanitizeFriendly(episodeName);
|
||||||
const episodeTitle = lines[videoIndex + 1];
|
}
|
||||||
if (episodeTitle.length > 5) {
|
|
||||||
const finalName = `${sanitizeFriendly(seriesTitle)} - ${sanitizeFriendly(episodeTitle)}`;
|
const lines = fullText.split('\n').map(l => l.trim()).filter(l => l.length > 5);
|
||||||
console.log('Serien-Fallback-Dateiname:', finalName);
|
const episodeLines = lines.filter(line =>
|
||||||
return finalName;
|
/S\d{2}E\d{2}/.test(line) ||
|
||||||
}
|
(line.includes('-') && line.length > 10 && line.length < 150)
|
||||||
}
|
);
|
||||||
|
|
||||||
return sanitizeFriendly(seriesTitle + ' - Episode ' + (videoIndex + 1));
|
if (episodeLines[elementIndex]) {
|
||||||
}
|
console.log('Found episode from lines:', episodeLines[elementIndex]);
|
||||||
}
|
return sanitizeFriendly(episodeLines[elementIndex]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 2. Einzelvideo: Erste Zeile aus .text-content
|
}
|
||||||
const textContent = messageContainer.querySelector('.text-content, .text-content.clearfix');
|
|
||||||
if (textContent) {
|
// 4. Weitere Selektoren
|
||||||
const fullText = textContent.innerText || textContent.textContent;
|
const titleSelectors = [
|
||||||
const firstLine = fullText.split('\n')[0].trim();
|
'.media-caption-text',
|
||||||
|
'.media-caption',
|
||||||
if (firstLine.length > 5 &&
|
'.message-title',
|
||||||
!firstLine.startsWith('@') &&
|
'.video-title',
|
||||||
!firstLine.startsWith('#') &&
|
'.document-name',
|
||||||
!firstLine.includes('⬇') &&
|
'.file-name',
|
||||||
!firstLine.match(/^\d{2}:\d{2}:\d{2}$/)) {
|
'.name',
|
||||||
console.log('Found filename from .text-content:', firstLine);
|
'.title:not(.peer-title):not(.top-bar)'
|
||||||
return sanitizeFriendly(firstLine);
|
];
|
||||||
}
|
|
||||||
}
|
for (const selector of titleSelectors) {
|
||||||
|
const el = messageContainer.querySelector(selector);
|
||||||
// 3. Weitere Titel-Selektoren
|
if (el) {
|
||||||
const titleSelectors = [
|
const text = el.innerText?.trim() || el.textContent?.trim();
|
||||||
'.media-caption-text',
|
if (text && text.length > 0 && text.length < 300) {
|
||||||
'.media-caption',
|
if (!text.includes('⬇') &&
|
||||||
'.message-title',
|
!text.toLowerCase().includes('download') &&
|
||||||
'.video-title',
|
!text.match(/^\d{2}:\d{2}:\d{2}$/)) {
|
||||||
'.document-name',
|
console.log('Found filename from selector', selector, ':', text);
|
||||||
'.file-name',
|
return sanitizeFriendly(text);
|
||||||
'.name',
|
}
|
||||||
'.title:not(.peer-title):not(.top-bar)',
|
}
|
||||||
'[class*="caption"]',
|
}
|
||||||
'[class*="title"]:not([class*="peer"]):not([class*="top"])'
|
}
|
||||||
];
|
|
||||||
|
// 5. Fallbacks
|
||||||
for (const selector of titleSelectors) {
|
const match = elementUrl?.match(/document(\d+)/);
|
||||||
const element = messageContainer.querySelector(selector);
|
if (match) {
|
||||||
if (element) {
|
console.warn('Using document ID as fallback');
|
||||||
const text = element.innerText?.trim() || element.textContent?.trim();
|
return 'telegram_doc_' + match[1];
|
||||||
if (text && text.length > 0 && text.length < 300) {
|
}
|
||||||
if (!text.includes('⬇') &&
|
|
||||||
!text.toLowerCase().includes('download') &&
|
console.warn('Could not extract filename, using timestamp fallback');
|
||||||
!text.match(/^\d{2}:\d{2}:\d{2}$/)) {
|
return 'telegram_media_' + Date.now();
|
||||||
console.log('Found filename from selector', selector, ':', text);
|
|
||||||
return sanitizeFriendly(text);
|
} catch (err) {
|
||||||
}
|
console.error('Error extracting filename:', err);
|
||||||
}
|
return 'telegram_media_' + Date.now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Textblöcke, Text-Nodes, React Props usw. (wie in deinem Original)
|
// Video-Button
|
||||||
const allTextElements = messageContainer.querySelectorAll('div, span, p');
|
function addVideoDownloadButton(video) {
|
||||||
for (const el of allTextElements) {
|
if (isInTopicList()) return;
|
||||||
const text = Array.from(el.childNodes)
|
if (!video || video.tagName !== "VIDEO" ||
|
||||||
.filter(node => node.nodeType === Node.TEXT_NODE)
|
video.classList.contains("sticker-media") ||
|
||||||
.map(node => node.textContent.trim())
|
video.classList.contains("media-sticker")) return;
|
||||||
.join(' ')
|
if (!video.currentSrc && !video.src) return;
|
||||||
.trim();
|
|
||||||
|
let container = video.parentElement || video.closest('div') || video;
|
||||||
if (text.length > 5 && text.length < 200) {
|
if (!container || container.querySelector('.tg-video-btn')) return;
|
||||||
if (!text.startsWith('@') &&
|
|
||||||
!text.startsWith('#') &&
|
const computed = window.getComputedStyle(container);
|
||||||
!text.includes('⬇') &&
|
if (computed.position === 'static') container.style.position = 'relative';
|
||||||
!text.match(/^\d{2}:\d{2}:\d{2}$/) &&
|
|
||||||
/[A-Z]/.test(text)) {
|
const btn = document.createElement('button');
|
||||||
console.log('Found potential filename from text element:', text);
|
btn.className = 'tg-video-btn';
|
||||||
return sanitizeFriendly(text);
|
btn.innerText = '⬇ Download';
|
||||||
}
|
btn.title = 'Video herunterladen';
|
||||||
}
|
btn.style.cssText = `
|
||||||
}
|
position: absolute !important;
|
||||||
|
top: 10px !important;
|
||||||
const walker = document.createTreeWalker(
|
right: 10px !important;
|
||||||
messageContainer,
|
z-index: 100000 !important;
|
||||||
NodeFilter.SHOW_TEXT,
|
background: #e63946 !important;
|
||||||
{
|
color: white !important;
|
||||||
acceptNode: function(node) {
|
border: none !important;
|
||||||
const text = node.textContent.trim();
|
border-radius: 8px !important;
|
||||||
if (text.length < 5 ||
|
padding: 8px 12px !important;
|
||||||
text.match(/^\d{2}:\d{2}:\d{2}$/) ||
|
font-size: 14px !important;
|
||||||
text.startsWith('@') ||
|
font-weight: bold !important;
|
||||||
text.startsWith('#') ||
|
cursor: pointer !important;
|
||||||
text.includes('⬇')) {
|
box-shadow: 0 4px 12px rgba(0,0,0,0.6) !important;
|
||||||
return NodeFilter.FILTER_REJECT;
|
`;
|
||||||
}
|
|
||||||
return NodeFilter.FILTER_ACCEPT;
|
btn.addEventListener('click', (e) => {
|
||||||
}
|
e.stopPropagation();
|
||||||
},
|
e.preventDefault();
|
||||||
false
|
|
||||||
);
|
const videoUrl = video.currentSrc || video.src;
|
||||||
|
if (!videoUrl) {
|
||||||
let foundTexts = [];
|
alert('Video-URL nicht verfügbar.');
|
||||||
let node;
|
return;
|
||||||
while (node = walker.nextNode()) {
|
}
|
||||||
const text = node.textContent.trim();
|
|
||||||
if (text.length > 0) foundTexts.push(text);
|
const autoName = extractTelegramFilename(video);
|
||||||
}
|
console.log('=== VIDEO DOWNLOAD ===', autoName);
|
||||||
|
|
||||||
for (const text of foundTexts) {
|
const event = new CustomEvent('downloadRequested', {
|
||||||
if (text.length > 5 && text.length < 200 && /[A-Z]/.test(text)) {
|
detail: { url: videoUrl, name: autoName, version: location.pathname }
|
||||||
console.log('Using text node as filename:', text);
|
});
|
||||||
return sanitizeFriendly(text);
|
document.dispatchEvent(event);
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
container.appendChild(btn);
|
||||||
const reactKeys = Object.keys(messageContainer).filter(k => k.startsWith('__react'));
|
}
|
||||||
for (const key of reactKeys) {
|
|
||||||
try {
|
// Bild-Button
|
||||||
const fiber = messageContainer[key];
|
function addImageDownloadButton(img) {
|
||||||
const filename = searchReactTree(fiber, ['fileName', 'file_name', 'name', 'title', 'caption']);
|
if (isInTopicList()) return;
|
||||||
if (filename && typeof filename === 'string' && filename.length > 3) {
|
if (!img || img.tagName !== "IMG") return;
|
||||||
console.log('Found filename in React props:', filename);
|
if (img.classList.contains('emoji') || img.classList.contains('thumbnail') ||
|
||||||
return sanitizeFriendly(filename);
|
img.width < 100 || img.height < 100) return;
|
||||||
}
|
|
||||||
} catch (e) {}
|
let container = img.closest('.media-inner') || img.closest('.Message') || img.parentElement;
|
||||||
}
|
if (!container || container.querySelector('.tg-img-btn')) return;
|
||||||
|
|
||||||
const match = videoUrl.match(/document(\d+)/);
|
if (window.getComputedStyle(container).position === 'static') {
|
||||||
if (match) {
|
container.style.position = 'relative';
|
||||||
console.warn('Using document ID as fallback');
|
}
|
||||||
return 'telegram_doc_' + match[1];
|
|
||||||
}
|
const btn = document.createElement('button');
|
||||||
|
btn.className = 'tg-img-btn';
|
||||||
console.warn('Could not extract filename, using timestamp fallback');
|
btn.innerText = '⬇';
|
||||||
return 'telegram_video_' + Date.now();
|
btn.title = 'Bild herunterladen';
|
||||||
|
btn.style.cssText = `
|
||||||
} catch (err) {
|
position: absolute !important;
|
||||||
console.error('Error extracting filename:', err);
|
top: 5px !important;
|
||||||
return 'telegram_video_' + Date.now();
|
left: 5px !important;
|
||||||
}
|
z-index: 999999 !important;
|
||||||
}
|
background: rgba(42, 157, 143, 0.95) !important;
|
||||||
|
color: white !important;
|
||||||
// Durchsuche React Fiber Tree
|
border: 2px solid white !important;
|
||||||
function searchReactTree(node, keys, depth = 0, maxDepth = 15) {
|
border-radius: 50% !important;
|
||||||
if (depth > maxDepth || !node || typeof node !== 'object') return null;
|
width: 44px !important;
|
||||||
|
height: 44px !important;
|
||||||
try {
|
font-size: 22px !important;
|
||||||
for (const key of keys) {
|
cursor: pointer !important;
|
||||||
if (node[key]) {
|
box-shadow: 0 2px 8px rgba(0,0,0,0.8) !important;
|
||||||
const val = node[key];
|
display: flex !important;
|
||||||
if (typeof val === 'string' && val.length > 0) return val;
|
align-items: center !important;
|
||||||
if (typeof val === 'object' && val.name) return val.name;
|
justify-content: center !important;
|
||||||
}
|
pointer-events: auto !important;
|
||||||
}
|
`;
|
||||||
|
|
||||||
if (node.memoizedProps) {
|
btn.onmouseenter = () => btn.style.transform = 'scale(1.15)';
|
||||||
for (const key of keys) {
|
btn.onmouseleave = () => btn.style.transform = 'scale(1)';
|
||||||
if (node.memoizedProps[key]) {
|
|
||||||
const val = node.memoizedProps[key];
|
btn.addEventListener('click', (e) => {
|
||||||
if (typeof val === 'string' && val.length > 0) return val;
|
e.stopPropagation();
|
||||||
}
|
e.preventDefault();
|
||||||
}
|
|
||||||
}
|
const imgUrl = img.src || img.currentSrc;
|
||||||
|
if (!imgUrl || imgUrl.startsWith('data:')) {
|
||||||
if (node.child) {
|
alert('Bild-URL nicht verfügbar.');
|
||||||
const result = searchReactTree(node.child, keys, depth + 1, maxDepth);
|
return;
|
||||||
if (result) return result;
|
}
|
||||||
}
|
|
||||||
} catch (e) {}
|
const autoName = extractTelegramFilename(img) || 'telegram_image';
|
||||||
|
console.log('=== IMAGE DOWNLOAD ===', autoName);
|
||||||
return null;
|
|
||||||
}
|
const event = new CustomEvent('imageDownloadRequested', {
|
||||||
|
detail: { url: imgUrl, name: autoName }
|
||||||
// Download auslösen – OHNE .mp4 anhängen (für maximale Geschwindigkeit!)
|
});
|
||||||
function fallbackToInjectDownload(videoUrl, autoName) {
|
document.dispatchEvent(event);
|
||||||
const event = new CustomEvent('downloadRequested', {
|
});
|
||||||
detail: {
|
|
||||||
url: videoUrl,
|
container.appendChild(btn);
|
||||||
name: autoName, // Kein .mp4 hier → schneller Chunk-Download!
|
console.log('✅ Image button added');
|
||||||
version: location.pathname,
|
}
|
||||||
}
|
|
||||||
});
|
// Audio-Button
|
||||||
document.dispatchEvent(event);
|
function addAudioDownloadButton(audioContainer) {
|
||||||
}
|
if (isInTopicList()) return;
|
||||||
|
if (!audioContainer || audioContainer.querySelector('.tg-audio-btn')) return;
|
||||||
// Download-Button hinzufügen
|
|
||||||
function addVideoDownloadButton(video) {
|
const audio = audioContainer.querySelector('audio');
|
||||||
if (isInTopicList()) return;
|
if (!audio) return;
|
||||||
if (!video || video.tagName !== "VIDEO" ||
|
|
||||||
video.classList.contains("sticker-media") ||
|
if (window.getComputedStyle(audioContainer).position === 'static') {
|
||||||
video.classList.contains("media-sticker")) return;
|
audioContainer.style.position = 'relative';
|
||||||
if (!video.currentSrc && !video.src) return;
|
}
|
||||||
|
|
||||||
let container = video.closest('.media-inner') || video.parentElement || video.closest('div') || video;
|
const btn = document.createElement('button');
|
||||||
if (!container || container.querySelector('.tg-video-btn')) return;
|
btn.className = 'tg-audio-btn';
|
||||||
|
btn.innerText = '⬇ Download';
|
||||||
const computed = window.getComputedStyle(container);
|
btn.title = 'Audio herunterladen';
|
||||||
if (computed.position === 'static') container.style.position = 'relative';
|
btn.style.cssText = `
|
||||||
|
position: absolute !important;
|
||||||
const btn = document.createElement('button');
|
top: 10px !important;
|
||||||
btn.className = 'tg-video-btn';
|
right: 10px !important;
|
||||||
btn.innerText = '⬇ DOWNLOAD';
|
z-index: 999999 !important;
|
||||||
btn.title = 'Video herunterladen';
|
background: rgba(244, 162, 97, 0.95) !important;
|
||||||
btn.style.cssText = `
|
color: white !important;
|
||||||
position: absolute !important;
|
border: 2px solid white !important;
|
||||||
top: 10px !important;
|
border-radius: 50% !important;
|
||||||
right: 10px !important;
|
width: 40px !important;
|
||||||
z-index: 100000 !important;
|
height: 40px !important;
|
||||||
background: #e63946 !important;
|
font-size: 20px !important;
|
||||||
color: white !important;
|
cursor: pointer !important;
|
||||||
border: none !important;
|
box-shadow: 0 2px 8px rgba(0,0,0,0.8) !important;
|
||||||
border-radius: 8px !important;
|
display: flex !important;
|
||||||
padding: 8px 12px !important;
|
align-items: center !important;
|
||||||
font-size: 14px !important;
|
justify-content: center !important;
|
||||||
font-weight: bold !important;
|
pointer-events: auto !important;
|
||||||
cursor: pointer !important;
|
`;
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.6) !important;
|
|
||||||
`;
|
btn.onmouseenter = () => btn.style.transform = 'scale(1.15)';
|
||||||
|
btn.onmouseleave = () => btn.style.transform = 'scale(1)';
|
||||||
btn.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
btn.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
const videoUrl = video.currentSrc || video.src;
|
|
||||||
if (!videoUrl) {
|
const audioUrl = audio.currentSrc || audio.src;
|
||||||
alert('Video-URL nicht verfügbar. Bitte das Video einmal kurz abspielen.');
|
if (!audioUrl) {
|
||||||
return;
|
alert('Audio-URL nicht verfügbar.');
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
const autoName = extractTelegramFilename(video);
|
|
||||||
|
const autoName = extractTelegramFilename(audioContainer) || 'telegram_audio';
|
||||||
console.log('=== DOWNLOAD INFO ===');
|
console.log('=== AUDIO DOWNLOAD ===', autoName);
|
||||||
console.log('Final filename (ohne .mp4):', autoName);
|
|
||||||
|
const event = new CustomEvent('audioDownloadRequested', {
|
||||||
fallbackToInjectDownload(videoUrl, autoName);
|
detail: { url: audioUrl, name: autoName }
|
||||||
});
|
});
|
||||||
|
document.dispatchEvent(event);
|
||||||
container.appendChild(btn);
|
});
|
||||||
}
|
|
||||||
|
audioContainer.appendChild(btn);
|
||||||
// Alle Videos verarbeiten
|
console.log('✅ Audio button added');
|
||||||
function processVideos() {
|
}
|
||||||
if (isInTopicList()) return;
|
|
||||||
document.querySelectorAll('video:not([data-tg-video])').forEach(video => {
|
// Document-Button (für .mp3, .zip, .pdf, etc.)
|
||||||
try {
|
function addDocumentDownloadButton(docContainer) {
|
||||||
video.dataset.tgVideo = 'true';
|
if (isInTopicList()) return;
|
||||||
addVideoDownloadButton(video);
|
if (!docContainer || docContainer.querySelector('.tg-doc-btn')) return;
|
||||||
} catch (err) {
|
|
||||||
console.error('Error processing video element', err);
|
// 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 observer = new MutationObserver(processVideos);
|
const extEl = docContainer.querySelector('.file-ext');
|
||||||
observer.observe(document.body, { childList: true, subtree: true });
|
const fileExt = extEl?.innerText?.trim()?.toLowerCase() || '';
|
||||||
setInterval(processVideos, 1200);
|
|
||||||
processVideos();
|
// Wenn kein Name gefunden wurde, ignoriere
|
||||||
|
if (!docName && !fileExt) return;
|
||||||
// Completed-Event weiterleiten
|
|
||||||
document.addEventListener('telDownloaderCompleted', (e) => {
|
// Prüfe Dateityp basierend auf Extension oder Name
|
||||||
try {
|
const isAudio = /\.(mp3|ogg|wav|m4a|flac|aac|opus)$/i.test(docName) ||
|
||||||
const detail = e.detail || {};
|
['mp3', 'ogg', 'wav', 'm4a', 'flac', 'aac', 'opus'].includes(fileExt);
|
||||||
const extId = detail.extensionId || chrome.runtime.id;
|
const isArchive = /\.(zip|rar|7z|tar|gz)$/i.test(docName) ||
|
||||||
chrome.runtime.sendMessage(extId, {
|
['zip', 'rar', '7z', 'tar', 'gz'].includes(fileExt);
|
||||||
action: detail.action || 'videoProgressCompleted',
|
const isDocument = /\.(pdf|doc|docx|txt|xlsx|xls|ppt)$/i.test(docName) ||
|
||||||
videoId: detail.videoId,
|
['pdf', 'doc', 'docx', 'txt', 'xlsx', 'xls', 'ppt'].includes(fileExt);
|
||||||
clientId: detail.clientId,
|
|
||||||
name: detail.name,
|
if (window.getComputedStyle(docContainer).position === 'static') {
|
||||||
version: detail.version,
|
docContainer.style.position = 'relative';
|
||||||
locale: detail.locale
|
}
|
||||||
}, (res) => {
|
|
||||||
if (chrome.runtime.lastError) {
|
// Icon und Farbe basierend auf Dateityp
|
||||||
console.error('Error sending message to background:', chrome.runtime.lastError.message);
|
let icon = '⬇';
|
||||||
}
|
let bgColor = 'rgba(168, 85, 247, 0.95)';
|
||||||
});
|
let title = 'Datei herunterladen';
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to forward telDownloaderCompleted to extension:', err);
|
if (isAudio) {
|
||||||
}
|
icon = '⬇';
|
||||||
});
|
bgColor = 'rgba(244, 162, 97, 0.95)';
|
||||||
|
title = 'Audio herunterladen';
|
||||||
// inject.js laden
|
} else if (isArchive) {
|
||||||
const script = document.createElement('script');
|
icon = '⬇';
|
||||||
script.src = chrome.runtime.getURL('inject.js');
|
bgColor = 'rgba(59, 130, 246, 0.95)';
|
||||||
script.setAttribute('data-extension-id', chrome.runtime.id);
|
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 => {
|
||||||
|
try {
|
||||||
|
video.dataset.tgVideo = 'true';
|
||||||
|
addVideoDownloadButton(video);
|
||||||
|
} catch (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(processMedia);
|
||||||
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
|
setInterval(processMedia, 1200);
|
||||||
|
processMedia();
|
||||||
|
|
||||||
|
console.log('✅ TG Downloader loaded - Video, Image & Audio support enabled');
|
||||||
|
|
||||||
|
// Event-Weiterleitung
|
||||||
|
document.addEventListener('telDownloaderCompleted', (e) => {
|
||||||
|
try {
|
||||||
|
const detail = e.detail || {};
|
||||||
|
chrome.runtime.sendMessage(detail.extensionId || chrome.runtime.id, {
|
||||||
|
action: detail.action || 'videoProgressCompleted',
|
||||||
|
videoId: detail.videoId,
|
||||||
|
clientId: detail.clientId,
|
||||||
|
name: detail.name,
|
||||||
|
version: detail.version,
|
||||||
|
locale: detail.locale
|
||||||
|
}, () => {
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
console.error('Error sending message:', chrome.runtime.lastError.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to forward event:', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// inject.js laden
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = chrome.runtime.getURL('inject.js');
|
||||||
|
script.setAttribute('data-extension-id', chrome.runtime.id);
|
||||||
(document.head || document.documentElement).appendChild(script);
|
(document.head || document.documentElement).appendChild(script);
|
||||||
952
inject.js
952
inject.js
@@ -1,396 +1,558 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const scriptElement = document.currentScript;
|
const scriptElement = document.currentScript;
|
||||||
const extensionId = scriptElement ? scriptElement.getAttribute("data-extension-id") : null;
|
const extensionId = scriptElement ? scriptElement.getAttribute("data-extension-id") : null;
|
||||||
|
|
||||||
console.log('inject.js running, extensionId=', extensionId);
|
console.log('inject.js running, extensionId=', extensionId);
|
||||||
|
|
||||||
// Empfang von "downloadRequested" (kommt vom content script)
|
// Empfang von "downloadRequested" (kommt vom content script)
|
||||||
document.addEventListener("downloadRequested", function(e) {
|
document.addEventListener("downloadRequested", function(e) {
|
||||||
try {
|
try {
|
||||||
let t = e.detail.url;
|
let t = e.detail.url;
|
||||||
let l = e.detail.name;
|
let l = e.detail.name;
|
||||||
let r = e.detail.version;
|
let r = e.detail.version;
|
||||||
let o = e.detail.clientId;
|
let o = e.detail.clientId;
|
||||||
let n = e.detail.locale;
|
let n = e.detail.locale;
|
||||||
tel_download_video(t, l, r, o, n);
|
tel_download_video(t, l, r, o, n);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error in downloadRequested handler', err);
|
console.error('Error in downloadRequested handler', err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("imageDownloadRequested", function(e) {
|
document.addEventListener("imageDownloadRequested", function(e) {
|
||||||
try {
|
try {
|
||||||
let t = e.detail.url;
|
let t = e.detail.url;
|
||||||
let l = e.detail.name;
|
let l = e.detail.name;
|
||||||
tel_download_image(t, l);
|
tel_download_image(t, l);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error in imageDownloadRequested handler', err);
|
console.error('Error in imageDownloadRequested handler', err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hilfsfunktion: progress-bar-container
|
document.addEventListener("audioDownloadRequested", function(e) {
|
||||||
function ensureProgressContainer() {
|
try {
|
||||||
let container = document.getElementById("tel-downloader-progress-bar-container");
|
let t = e.detail.url;
|
||||||
if (!container) {
|
let l = e.detail.name;
|
||||||
container = document.createElement("div");
|
tel_download_audio(t, l);
|
||||||
container.id = "tel-downloader-progress-bar-container";
|
} catch (err) {
|
||||||
container.style.position = "fixed";
|
console.error('Error in audioDownloadRequested handler', err);
|
||||||
container.style.bottom = "0";
|
}
|
||||||
container.style.right = "0";
|
});
|
||||||
container.style.display = "flex";
|
|
||||||
container.style.flexDirection = "column";
|
document.addEventListener("documentDownloadRequested", function(e) {
|
||||||
container.style.gap = "0.4rem";
|
try {
|
||||||
container.style.padding = "0.4rem";
|
let t = e.detail.url;
|
||||||
container.style.zIndex = "1600";
|
let l = e.detail.name;
|
||||||
document.body.appendChild(container);
|
tel_download_document(t, l);
|
||||||
}
|
} catch (err) {
|
||||||
return container;
|
console.error('Error in documentDownloadRequested handler', err);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
const createProgressBar = (id, filename) => {
|
|
||||||
const dark = document.querySelector("html")?.classList?.contains("night") ||
|
// Hilfsfunktion: progress-bar-container
|
||||||
document.querySelector("html")?.classList?.contains("theme-dark");
|
function ensureProgressContainer() {
|
||||||
const container = ensureProgressContainer();
|
let container = document.getElementById("tel-downloader-progress-bar-container");
|
||||||
container.style.zIndex = location.pathname.startsWith("/k/") ? "4" : "1600";
|
if (!container) {
|
||||||
|
container = document.createElement("div");
|
||||||
const n = document.createElement("div");
|
container.id = "tel-downloader-progress-bar-container";
|
||||||
n.id = "tel-downloader-progress-" + id;
|
container.style.position = "fixed";
|
||||||
n.style.width = "20rem";
|
container.style.bottom = "0";
|
||||||
n.style.marginTop = "0.4rem";
|
container.style.right = "0";
|
||||||
n.style.padding = "0.6rem";
|
container.style.display = "flex";
|
||||||
n.style.backgroundColor = dark ? "rgba(0,0,0,0.6)" : "rgba(0,0,0,0.3)";
|
container.style.flexDirection = "column";
|
||||||
n.style.borderRadius = "8px";
|
container.style.gap = "0.4rem";
|
||||||
n.style.boxShadow = "0 4px 12px rgba(0,0,0,0.2)";
|
container.style.padding = "0.4rem";
|
||||||
n.style.color = "white";
|
container.style.zIndex = "1600";
|
||||||
n.style.fontFamily = "sans-serif";
|
document.body.appendChild(container);
|
||||||
|
}
|
||||||
const s = document.createElement("div");
|
return container;
|
||||||
s.style.display = "flex";
|
}
|
||||||
s.style.justifyContent = "space-between";
|
|
||||||
s.style.alignItems = "center";
|
const createProgressBar = (id, filename) => {
|
||||||
|
const dark = document.querySelector("html")?.classList?.contains("night") ||
|
||||||
const i = document.createElement("p");
|
document.querySelector("html")?.classList?.contains("theme-dark");
|
||||||
i.className = "filename";
|
const container = ensureProgressContainer();
|
||||||
i.style.margin = 0;
|
container.style.zIndex = location.pathname.startsWith("/k/") ? "4" : "1600";
|
||||||
i.style.color = "white";
|
|
||||||
i.style.fontSize = "0.9rem";
|
const n = document.createElement("div");
|
||||||
i.style.overflow = "hidden";
|
n.id = "tel-downloader-progress-" + id;
|
||||||
i.style.textOverflow = "ellipsis";
|
n.style.width = "20rem";
|
||||||
i.style.whiteSpace = "nowrap";
|
n.style.marginTop = "0.4rem";
|
||||||
i.innerText = filename || "video";
|
n.style.padding = "0.6rem";
|
||||||
|
n.style.backgroundColor = dark ? "rgba(0,0,0,0.6)" : "rgba(0,0,0,0.3)";
|
||||||
const d = document.createElement("div");
|
n.style.borderRadius = "8px";
|
||||||
d.style.cursor = "pointer";
|
n.style.boxShadow = "0 4px 12px rgba(0,0,0,0.2)";
|
||||||
d.style.fontSize = "1.2rem";
|
n.style.color = "white";
|
||||||
d.style.color = dark ? "#8a8a8a" : "white";
|
n.style.fontFamily = "sans-serif";
|
||||||
d.innerHTML = "×";
|
|
||||||
d.onclick = function() {
|
const s = document.createElement("div");
|
||||||
if (n.parentElement) n.parentElement.removeChild(n);
|
s.style.display = "flex";
|
||||||
};
|
s.style.justifyContent = "space-between";
|
||||||
|
s.style.alignItems = "center";
|
||||||
const a = document.createElement("div");
|
|
||||||
a.className = "progress";
|
const i = document.createElement("p");
|
||||||
a.style.backgroundColor = "#e2e2e2";
|
i.className = "filename";
|
||||||
a.style.position = "relative";
|
i.style.margin = 0;
|
||||||
a.style.width = "100%";
|
i.style.color = "white";
|
||||||
a.style.height = "1.6rem";
|
i.style.fontSize = "0.9rem";
|
||||||
a.style.borderRadius = "2rem";
|
i.style.overflow = "hidden";
|
||||||
a.style.overflow = "hidden";
|
i.style.textOverflow = "ellipsis";
|
||||||
a.style.marginTop = "0.6rem";
|
i.style.whiteSpace = "nowrap";
|
||||||
|
i.innerText = filename || "video";
|
||||||
const c = document.createElement("p");
|
|
||||||
c.style.position = "absolute";
|
const d = document.createElement("div");
|
||||||
c.style.zIndex = 5;
|
d.style.cursor = "pointer";
|
||||||
c.style.left = "50%";
|
d.style.fontSize = "1.2rem";
|
||||||
c.style.top = "50%";
|
d.style.color = dark ? "#8a8a8a" : "white";
|
||||||
c.style.transform = "translate(-50%, -50%)";
|
d.innerHTML = "×";
|
||||||
c.style.margin = 0;
|
d.onclick = function() {
|
||||||
c.style.color = "black";
|
if (n.parentElement) n.parentElement.removeChild(n);
|
||||||
c.style.fontSize = "0.85rem";
|
};
|
||||||
c.innerText = "0%";
|
|
||||||
|
const a = document.createElement("div");
|
||||||
const p = document.createElement("div");
|
a.className = "progress";
|
||||||
p.style.position = "absolute";
|
a.style.backgroundColor = "#e2e2e2";
|
||||||
p.style.height = "100%";
|
a.style.position = "relative";
|
||||||
p.style.width = "0%";
|
a.style.width = "100%";
|
||||||
p.style.backgroundColor = "#3390ec";
|
a.style.height = "1.6rem";
|
||||||
p.style.left = "0";
|
a.style.borderRadius = "2rem";
|
||||||
p.style.top = "0";
|
a.style.overflow = "hidden";
|
||||||
|
a.style.marginTop = "0.6rem";
|
||||||
a.appendChild(c);
|
|
||||||
a.appendChild(p);
|
const c = document.createElement("p");
|
||||||
s.appendChild(i);
|
c.style.position = "absolute";
|
||||||
s.appendChild(d);
|
c.style.zIndex = 5;
|
||||||
n.appendChild(s);
|
c.style.left = "50%";
|
||||||
n.appendChild(a);
|
c.style.top = "50%";
|
||||||
container.appendChild(n);
|
c.style.transform = "translate(-50%, -50%)";
|
||||||
};
|
c.style.margin = 0;
|
||||||
|
c.style.color = "black";
|
||||||
const updateProgress = (id, filename, percent) => {
|
c.style.fontSize = "0.85rem";
|
||||||
let r = document.getElementById("tel-downloader-progress-" + id);
|
c.innerText = "0%";
|
||||||
if (!r) return;
|
|
||||||
|
const p = document.createElement("div");
|
||||||
const fname = filename || r.querySelector("p.filename")?.innerText || "video";
|
p.style.position = "absolute";
|
||||||
r.querySelector("p.filename").innerText = fname;
|
p.style.height = "100%";
|
||||||
|
p.style.width = "0%";
|
||||||
const prog = r.querySelector("div.progress");
|
p.style.backgroundColor = "#3390ec";
|
||||||
if (!prog) return;
|
p.style.left = "0";
|
||||||
|
p.style.top = "0";
|
||||||
const txt = prog.querySelector("p");
|
|
||||||
const bar = prog.querySelector("div");
|
a.appendChild(c);
|
||||||
if (txt) txt.innerText = (percent || 0) + "%";
|
a.appendChild(p);
|
||||||
if (bar) bar.style.width = (percent || 0) + "%";
|
s.appendChild(i);
|
||||||
};
|
s.appendChild(d);
|
||||||
|
n.appendChild(s);
|
||||||
const completeProgress = (id, clientId, filename, version, locale) => {
|
n.appendChild(a);
|
||||||
let n = document.getElementById("tel-downloader-progress-" + id);
|
container.appendChild(n);
|
||||||
if (!n) return;
|
};
|
||||||
|
|
||||||
let prog = n.querySelector("div.progress");
|
const updateProgress = (id, filename, percent) => {
|
||||||
if (prog) {
|
let r = document.getElementById("tel-downloader-progress-" + id);
|
||||||
let txt = prog.querySelector("p");
|
if (!r) return;
|
||||||
if (txt) txt.innerText = "Completed";
|
|
||||||
let bar = prog.querySelector("div");
|
const fname = filename || r.querySelector("p.filename")?.innerText || "video";
|
||||||
if (bar) {
|
r.querySelector("p.filename").innerText = fname;
|
||||||
bar.style.backgroundColor = "#40DCA5";
|
|
||||||
bar.style.width = "100%";
|
const prog = r.querySelector("div.progress");
|
||||||
}
|
if (!prog) return;
|
||||||
}
|
|
||||||
|
const txt = prog.querySelector("p");
|
||||||
document.dispatchEvent(new CustomEvent('telDownloaderCompleted', {
|
const bar = prog.querySelector("div");
|
||||||
detail: {
|
if (txt) txt.innerText = (percent || 0) + "%";
|
||||||
extensionId: extensionId,
|
if (bar) bar.style.width = (percent || 0) + "%";
|
||||||
action: "videoProgressCompleted",
|
};
|
||||||
videoId: id,
|
|
||||||
clientId: clientId,
|
const completeProgress = (id, clientId, filename, version, locale) => {
|
||||||
name: filename,
|
let n = document.getElementById("tel-downloader-progress-" + id);
|
||||||
version: version,
|
if (!n) return;
|
||||||
locale: locale
|
|
||||||
}
|
let prog = n.querySelector("div.progress");
|
||||||
}));
|
if (prog) {
|
||||||
};
|
let txt = prog.querySelector("p");
|
||||||
|
if (txt) txt.innerText = "Completed";
|
||||||
const AbortProgress = id => {
|
let bar = prog.querySelector("div");
|
||||||
let t = document.getElementById("tel-downloader-progress-" + id);
|
if (bar) {
|
||||||
if (!t) return;
|
bar.style.backgroundColor = "#40DCA5";
|
||||||
|
bar.style.width = "100%";
|
||||||
let prog = t.querySelector("div.progress");
|
}
|
||||||
let txt = prog?.querySelector("p");
|
}
|
||||||
if (txt) {
|
|
||||||
txt.innerText = "Download failed. Try again";
|
document.dispatchEvent(new CustomEvent('telDownloaderCompleted', {
|
||||||
txt.style.fontSize = "12px";
|
detail: {
|
||||||
}
|
extensionId: extensionId,
|
||||||
let bar = prog?.querySelector("div");
|
action: "videoProgressCompleted",
|
||||||
if (bar) {
|
videoId: id,
|
||||||
bar.style.backgroundColor = "#D16666";
|
clientId: clientId,
|
||||||
bar.style.width = "100%";
|
name: filename,
|
||||||
}
|
version: version,
|
||||||
};
|
locale: locale
|
||||||
|
}
|
||||||
// Filename helper
|
}));
|
||||||
function sanitizeFilenamePreserveFriendly(name) {
|
};
|
||||||
if (!name) return 'video';
|
|
||||||
return name.toString().replace(/[\/\\\?\%\*\:\|\"\<\>]/g, "_").trim();
|
const AbortProgress = id => {
|
||||||
}
|
let t = document.getElementById("tel-downloader-progress-" + id);
|
||||||
|
if (!t) return;
|
||||||
// Download-Funktion mit automatischem Dateinamen - OPTIMIERT FÜR GESCHWINDIGKEIT
|
|
||||||
function tel_download_video(e, t, l, r, o) {
|
let prog = t.querySelector("div.progress");
|
||||||
if (!e) {
|
let txt = prog?.querySelector("p");
|
||||||
console.error('No url provided to tel_download_video');
|
if (txt) {
|
||||||
return;
|
txt.innerText = "Download failed. Try again";
|
||||||
}
|
txt.style.fontSize = "12px";
|
||||||
|
}
|
||||||
let baseName = sanitizeFilenamePreserveFriendly(t || 'video');
|
let bar = prog?.querySelector("div");
|
||||||
const hasExt = /\.\w{1,5}$/.test(baseName);
|
if (bar) {
|
||||||
const id = (Math.random() + 1).toString(36).substring(2, 10) + "_" + Date.now().toString();
|
bar.style.backgroundColor = "#D16666";
|
||||||
|
bar.style.width = "100%";
|
||||||
const finishDownload = (blob, finalName) => {
|
}
|
||||||
try {
|
};
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const link = document.createElement("a");
|
// Filename helper
|
||||||
document.body.appendChild(link);
|
function sanitizeFilenamePreserveFriendly(name) {
|
||||||
link.href = url;
|
if (!name) return 'video';
|
||||||
link.download = finalName;
|
return name.toString().replace(/[\/\\\?\%\*\:\|\"\<\>]/g, "_").trim();
|
||||||
link.click();
|
}
|
||||||
document.body.removeChild(link);
|
|
||||||
setTimeout(() => URL.revokeObjectURL(url), 2000);
|
// Download-Funktion mit automatischem Dateinamen - OPTIMIERT FÜR GESCHWINDIGKEIT
|
||||||
} catch (err) {
|
function tel_download_video(e, t, l, r, o) {
|
||||||
console.error('Error finishing download', err);
|
if (!e) {
|
||||||
AbortProgress(id);
|
console.error('No url provided to tel_download_video');
|
||||||
}
|
return;
|
||||||
};
|
}
|
||||||
|
|
||||||
createProgressBar(id, baseName + (hasExt ? '' : '.mp4'));
|
let baseName = sanitizeFilenamePreserveFriendly(t || 'video');
|
||||||
updateProgress(id, baseName + (hasExt ? '' : '.mp4'), 0);
|
const hasExt = /\.\w{1,5}$/.test(baseName);
|
||||||
|
const id = (Math.random() + 1).toString(36).substring(2, 10) + "_" + Date.now().toString();
|
||||||
// GESCHWINDIGKEITS-OPTIMIERUNG: Größere Chunks + parallele Downloads
|
|
||||||
const chunkSize = 10 * 1024 * 1024; // 10MB chunks (statt 1MB)
|
const finishDownload = (blob, finalName) => {
|
||||||
const maxParallel = 3; // 3 parallele Downloads
|
try {
|
||||||
let chunks = [];
|
const url = URL.createObjectURL(blob);
|
||||||
let totalSize = null;
|
const link = document.createElement("a");
|
||||||
let detectedExt = "mp4";
|
document.body.appendChild(link);
|
||||||
let activeDownloads = 0;
|
link.href = url;
|
||||||
let downloadComplete = false;
|
link.download = finalName;
|
||||||
|
link.click();
|
||||||
// Zuerst: Hole die Gesamtgröße
|
document.body.removeChild(link);
|
||||||
fetch(e, {
|
setTimeout(() => URL.revokeObjectURL(url), 2000);
|
||||||
method: "HEAD",
|
} catch (err) {
|
||||||
credentials: "include"
|
console.error('Error finishing download', err);
|
||||||
}).then(res => {
|
AbortProgress(id);
|
||||||
const contentLength = res.headers.get("Content-Length");
|
}
|
||||||
const contentType = res.headers.get("Content-Type") || "";
|
};
|
||||||
const mime = contentType.split(";")[0] || "";
|
|
||||||
if (mime.startsWith("video/")) {
|
createProgressBar(id, baseName + (hasExt ? '' : '.mp4'));
|
||||||
detectedExt = mime.split("/")[1] || detectedExt;
|
updateProgress(id, baseName + (hasExt ? '' : '.mp4'), 0);
|
||||||
}
|
|
||||||
|
// GESCHWINDIGKEITS-OPTIMIERUNG: Größere Chunks + parallele Downloads
|
||||||
if (contentLength) {
|
const chunkSize = 10 * 1024 * 1024; // 10MB chunks (statt 1MB)
|
||||||
totalSize = parseInt(contentLength, 10);
|
const maxParallel = 3; // 3 parallele Downloads
|
||||||
console.log('Total file size:', (totalSize / 1024 / 1024).toFixed(2), 'MB');
|
let chunks = [];
|
||||||
|
let totalSize = null;
|
||||||
// Berechne wie viele Chunks wir brauchen
|
let detectedExt = "mp4";
|
||||||
const numChunks = Math.ceil(totalSize / chunkSize);
|
let activeDownloads = 0;
|
||||||
chunks = new Array(numChunks).fill(null);
|
let downloadComplete = false;
|
||||||
|
|
||||||
// Starte parallele Downloads
|
// Zuerst: Hole die Gesamtgröße
|
||||||
for (let i = 0; i < Math.min(maxParallel, numChunks); i++) {
|
fetch(e, {
|
||||||
downloadChunk(i);
|
method: "HEAD",
|
||||||
}
|
credentials: "include"
|
||||||
} else {
|
}).then(res => {
|
||||||
// Fallback: sequentieller Download ohne Größeninfo
|
const contentLength = res.headers.get("Content-Length");
|
||||||
sequentialDownload();
|
const contentType = res.headers.get("Content-Type") || "";
|
||||||
}
|
const mime = contentType.split(";")[0] || "";
|
||||||
}).catch(err => {
|
if (mime.startsWith("video/")) {
|
||||||
console.warn('HEAD request failed, using sequential download');
|
detectedExt = mime.split("/")[1] || detectedExt;
|
||||||
sequentialDownload();
|
}
|
||||||
});
|
|
||||||
|
if (contentLength) {
|
||||||
function downloadChunk(chunkIndex) {
|
totalSize = parseInt(contentLength, 10);
|
||||||
if (downloadComplete || chunkIndex >= chunks.length) return;
|
console.log('Total file size:', (totalSize / 1024 / 1024).toFixed(2), 'MB');
|
||||||
|
|
||||||
activeDownloads++;
|
// Berechne wie viele Chunks wir brauchen
|
||||||
const start = chunkIndex * chunkSize;
|
const numChunks = Math.ceil(totalSize / chunkSize);
|
||||||
const end = Math.min(start + chunkSize - 1, totalSize - 1);
|
chunks = new Array(numChunks).fill(null);
|
||||||
|
|
||||||
fetch(e, {
|
// Starte parallele Downloads
|
||||||
method: "GET",
|
for (let i = 0; i < Math.min(maxParallel, numChunks); i++) {
|
||||||
headers: { Range: `bytes=${start}-${end}` },
|
downloadChunk(i);
|
||||||
credentials: "include"
|
}
|
||||||
}).then(res => {
|
} else {
|
||||||
if (![200, 206].includes(res.status)) {
|
// Fallback: sequentieller Download ohne Größeninfo
|
||||||
throw Error("Non 200/206 response: " + res.status);
|
sequentialDownload();
|
||||||
}
|
}
|
||||||
return res.blob();
|
}).catch(err => {
|
||||||
}).then(blob => {
|
console.warn('HEAD request failed, using sequential download');
|
||||||
chunks[chunkIndex] = blob;
|
sequentialDownload();
|
||||||
activeDownloads--;
|
});
|
||||||
|
|
||||||
// Update Progress
|
function downloadChunk(chunkIndex) {
|
||||||
const downloaded = chunks.filter(c => c !== null).length;
|
if (downloadComplete || chunkIndex >= chunks.length) return;
|
||||||
const percent = Math.round(100 * downloaded / chunks.length);
|
|
||||||
updateProgress(id, baseName + (hasExt ? "" : "." + detectedExt), percent);
|
activeDownloads++;
|
||||||
|
const start = chunkIndex * chunkSize;
|
||||||
// Check ob fertig
|
const end = Math.min(start + chunkSize - 1, totalSize - 1);
|
||||||
if (chunks.every(c => c !== null)) {
|
|
||||||
downloadComplete = true;
|
fetch(e, {
|
||||||
const finalName = hasExt ? baseName : (baseName + "." + detectedExt);
|
method: "GET",
|
||||||
const merged = new Blob(chunks, { type: "video/" + detectedExt });
|
headers: { Range: `bytes=${start}-${end}` },
|
||||||
finishDownload(merged, finalName);
|
credentials: "include"
|
||||||
completeProgress(id, r, finalName, l, o);
|
}).then(res => {
|
||||||
} else {
|
if (![200, 206].includes(res.status)) {
|
||||||
// Starte nächsten Chunk
|
throw Error("Non 200/206 response: " + res.status);
|
||||||
const nextChunk = chunks.findIndex(c => c === null);
|
}
|
||||||
if (nextChunk !== -1) {
|
return res.blob();
|
||||||
downloadChunk(nextChunk);
|
}).then(blob => {
|
||||||
}
|
chunks[chunkIndex] = blob;
|
||||||
}
|
activeDownloads--;
|
||||||
}).catch(err => {
|
|
||||||
console.error('Chunk download error:', err);
|
// Update Progress
|
||||||
activeDownloads--;
|
const downloaded = chunks.filter(c => c !== null).length;
|
||||||
// Retry nach kurzer Pause
|
const percent = Math.round(100 * downloaded / chunks.length);
|
||||||
setTimeout(() => downloadChunk(chunkIndex), 500);
|
updateProgress(id, baseName + (hasExt ? "" : "." + detectedExt), percent);
|
||||||
});
|
|
||||||
}
|
// Check ob fertig
|
||||||
|
if (chunks.every(c => c !== null)) {
|
||||||
function sequentialDownload() {
|
downloadComplete = true;
|
||||||
// Fallback: Sequentieller Download für Server ohne Range-Support
|
const finalName = hasExt ? baseName : (baseName + "." + detectedExt);
|
||||||
let blobs = [], s = 0;
|
const merged = new Blob(chunks, { type: "video/" + detectedExt });
|
||||||
|
finishDownload(merged, finalName);
|
||||||
const rangedFetch = () => {
|
completeProgress(id, r, finalName, l, o);
|
||||||
fetch(e, {
|
} else {
|
||||||
method: "GET",
|
// Starte nächsten Chunk
|
||||||
headers: { Range: `bytes=${s}-` },
|
const nextChunk = chunks.findIndex(c => c === null);
|
||||||
credentials: "include"
|
if (nextChunk !== -1) {
|
||||||
}).then(res2 => {
|
downloadChunk(nextChunk);
|
||||||
if (![200, 206].includes(res2.status)) {
|
}
|
||||||
throw Error("Non 200/206 response: " + res2.status);
|
}
|
||||||
}
|
}).catch(err => {
|
||||||
|
console.error('Chunk download error:', err);
|
||||||
const contentType2 = res2.headers.get("Content-Type") || "";
|
activeDownloads--;
|
||||||
const mime2 = contentType2.split(";")[0] || "";
|
// Retry nach kurzer Pause
|
||||||
if (mime2.startsWith("video/")) {
|
setTimeout(() => downloadChunk(chunkIndex), 500);
|
||||||
detectedExt = mime2.split("/")[1] || detectedExt;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const cr = res2.headers.get("Content-Range");
|
function sequentialDownload() {
|
||||||
if (cr) {
|
// Fallback: Sequentieller Download für Server ohne Range-Support
|
||||||
const match = cr.match(/^bytes (\d+)-(\d+)\/(\d+)$/);
|
let blobs = [], s = 0;
|
||||||
if (match) {
|
|
||||||
const start = parseInt(match[1], 10);
|
const rangedFetch = () => {
|
||||||
const end = parseInt(match[2], 10);
|
fetch(e, {
|
||||||
const total = parseInt(match[3], 10);
|
method: "GET",
|
||||||
|
headers: { Range: `bytes=${s}-` },
|
||||||
s = end + 1;
|
credentials: "include"
|
||||||
totalSize = total;
|
}).then(res2 => {
|
||||||
|
if (![200, 206].includes(res2.status)) {
|
||||||
const percent = Math.min(100, Math.round(100 * s / totalSize));
|
throw Error("Non 200/206 response: " + res2.status);
|
||||||
updateProgress(id, baseName + (hasExt ? "" : "." + detectedExt), percent);
|
}
|
||||||
}
|
|
||||||
} else if (res2.status === 200) {
|
const contentType2 = res2.headers.get("Content-Type") || "";
|
||||||
const contentLength = res2.headers.get("Content-Length");
|
const mime2 = contentType2.split(";")[0] || "";
|
||||||
if (contentLength) {
|
if (mime2.startsWith("video/")) {
|
||||||
totalSize = parseInt(contentLength, 10);
|
detectedExt = mime2.split("/")[1] || detectedExt;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
const cr = res2.headers.get("Content-Range");
|
||||||
return res2.blob();
|
if (cr) {
|
||||||
}).then(chunkBlob => {
|
const match = cr.match(/^bytes (\d+)-(\d+)\/(\d+)$/);
|
||||||
blobs.push(chunkBlob);
|
if (match) {
|
||||||
|
const start = parseInt(match[1], 10);
|
||||||
if (totalSize && s >= totalSize) {
|
const end = parseInt(match[2], 10);
|
||||||
const finalName = hasExt ? baseName : (baseName + "." + detectedExt);
|
const total = parseInt(match[3], 10);
|
||||||
const merged = new Blob(blobs, { type: "video/" + detectedExt });
|
|
||||||
finishDownload(merged, finalName);
|
s = end + 1;
|
||||||
completeProgress(id, r, finalName, l, o);
|
totalSize = total;
|
||||||
} else {
|
|
||||||
setTimeout(rangedFetch, 10);
|
const percent = Math.min(100, Math.round(100 * s / totalSize));
|
||||||
}
|
updateProgress(id, baseName + (hasExt ? "" : "." + detectedExt), percent);
|
||||||
}).catch(err2 => {
|
}
|
||||||
console.error('Sequential download error', err2);
|
} else if (res2.status === 200) {
|
||||||
AbortProgress(id);
|
const contentLength = res2.headers.get("Content-Length");
|
||||||
});
|
if (contentLength) {
|
||||||
};
|
totalSize = parseInt(contentLength, 10);
|
||||||
|
}
|
||||||
rangedFetch();
|
}
|
||||||
}
|
|
||||||
}
|
return res2.blob();
|
||||||
|
}).then(chunkBlob => {
|
||||||
const tel_download_image = (src, suggestedName) => {
|
blobs.push(chunkBlob);
|
||||||
try {
|
|
||||||
const safe = sanitizeFilenamePreserveFriendly(suggestedName || 'image') + "_" + Date.now() + ".jpg";
|
if (totalSize && s >= totalSize) {
|
||||||
const a = document.createElement("a");
|
const finalName = hasExt ? baseName : (baseName + "." + detectedExt);
|
||||||
document.body.appendChild(a);
|
const merged = new Blob(blobs, { type: "video/" + detectedExt });
|
||||||
a.href = src;
|
finishDownload(merged, finalName);
|
||||||
a.download = safe;
|
completeProgress(id, r, finalName, l, o);
|
||||||
a.click();
|
} else {
|
||||||
document.body.removeChild(a);
|
setTimeout(rangedFetch, 10);
|
||||||
} catch (err) {
|
}
|
||||||
console.error('tel_download_image error', err);
|
}).catch(err2 => {
|
||||||
}
|
console.error('Sequential download error', err2);
|
||||||
};
|
AbortProgress(id);
|
||||||
|
});
|
||||||
window.telDownloader = {
|
};
|
||||||
startVideo: tel_download_video,
|
|
||||||
startImage: tel_download_image
|
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
|
||||||
|
};
|
||||||
})();
|
})();
|
||||||
@@ -1,27 +1,27 @@
|
|||||||
{
|
{
|
||||||
"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/*"],
|
||||||
"icons": {
|
"icons": {
|
||||||
"128": "icon.png"
|
"128": "icon.png"
|
||||||
},
|
},
|
||||||
"background": {
|
"background": {
|
||||||
"service_worker": "background.js"
|
"service_worker": "background.js"
|
||||||
},
|
},
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"matches": ["https://web.telegram.org/*"],
|
"matches": ["https://web.telegram.org/*"],
|
||||||
"js": ["content_tg.js"],
|
"js": ["content_tg.js"],
|
||||||
"run_at": "document_end"
|
"run_at": "document_end"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"web_accessible_resources": [
|
"web_accessible_resources": [
|
||||||
{
|
{
|
||||||
"resources": ["inject.js"],
|
"resources": ["inject.js"],
|
||||||
"matches": ["https://web.telegram.org/*"]
|
"matches": ["https://web.telegram.org/*"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user