// ==UserScript==
// @name Telegram Media Downloader (Batch Support)(by AFU IT)
// @name:en Telegram Media Downloader (Enhanced Batch + Restricted)
// @version 1.1
// @description Download images, GIFs, videos, and voice messages from private channels + batch download selected media
// @author AFU IT
// @license GNU GPLv3
// @website https://github.com/Neet-Nestor/Telegram-Media-Downloader
// @match https://web.telegram.org/*
// @match https://webk.telegram.org/*
// @match https://webz.telegram.org/*
// @icon https://img.icons8.com/color/452/telegram-app--v5.png
// @grant none
// @namespace https://github.com/Neet-Nestor/Telegram-Media-Downloader
// ==/UserScript==
(function() {
'use strict';
// Enhanced Logger
const logger = {
info: (message, fileName = null) => {
console.log(`[TG-Enhanced] ${fileName ? `${fileName}: ` : ""}${message}`);
},
error: (message, fileName = null) => {
console.error(`[TG-Enhanced] ${fileName ? `${fileName}: ` : ""}${message}`);
},
warn: (message, fileName = null) => {
console.warn(`[TG-Enhanced] ${fileName ? `${fileName}: ` : ""}${message}`);
}
};
// Unicode values for icons (used in /k/ app)
const DOWNLOAD_ICON = "\uE95A";
const FORWARD_ICON = "\uE976";
const contentRangeRegex = /^bytes (\d+)-(\d+)\/(\d+)$/;
const REFRESH_DELAY = 500;
const hashCode = (s) => {
var h = 0, l = s.length, i = 0;
if (l > 0) {
while (i < l) {
h = ((h << 5) - h + s.charCodeAt(i++)) | 0;
}
}
return h >>> 0;
};
// Progress bar functions
const createProgressBar = (videoId, fileName) => {
const isDarkMode = document.querySelector("html").classList.contains("night") ||
document.querySelector("html").classList.contains("theme-dark");
const container = document.getElementById("tel-downloader-progress-bar-container");
const innerContainer = document.createElement("div");
innerContainer.id = "tel-downloader-progress-" + videoId;
innerContainer.style.width = "20rem";
innerContainer.style.marginTop = "0.4rem";
innerContainer.style.padding = "0.6rem";
innerContainer.style.backgroundColor = isDarkMode ? "rgba(0,0,0,0.3)" : "rgba(0,0,0,0.6)";
const flexContainer = document.createElement("div");
flexContainer.style.display = "flex";
flexContainer.style.justifyContent = "space-between";
const title = document.createElement("p");
title.className = "filename";
title.style.margin = 0;
title.style.color = "white";
title.innerText = fileName;
const closeButton = document.createElement("div");
closeButton.style.cursor = "pointer";
closeButton.style.fontSize = "1.2rem";
closeButton.style.color = isDarkMode ? "#8a8a8a" : "white";
closeButton.innerHTML = "×";
closeButton.onclick = function() {
container.removeChild(innerContainer);
};
const progressBar = document.createElement("div");
progressBar.className = "progress";
progressBar.style.backgroundColor = "#e2e2e2";
progressBar.style.position = "relative";
progressBar.style.width = "100%";
progressBar.style.height = "1.6rem";
progressBar.style.borderRadius = "2rem";
progressBar.style.overflow = "hidden";
const counter = document.createElement("p");
counter.style.position = "absolute";
counter.style.zIndex = 5;
counter.style.left = "50%";
counter.style.top = "50%";
counter.style.transform = "translate(-50%, -50%)";
counter.style.margin = 0;
counter.style.color = "black";
const progress = document.createElement("div");
progress.style.position = "absolute";
progress.style.height = "100%";
progress.style.width = "0%";
progress.style.backgroundColor = "#6093B5";
progressBar.appendChild(counter);
progressBar.appendChild(progress);
flexContainer.appendChild(title);
flexContainer.appendChild(closeButton);
innerContainer.appendChild(flexContainer);
innerContainer.appendChild(progressBar);
container.appendChild(innerContainer);
};
const updateProgress = (videoId, fileName, progress) => {
const innerContainer = document.getElementById("tel-downloader-progress-" + videoId);
if (innerContainer) {
innerContainer.querySelector("p.filename").innerText = fileName;
const progressBar = innerContainer.querySelector("div.progress");
progressBar.querySelector("p").innerText = progress + "%";
progressBar.querySelector("div").style.width = progress + "%";
}
};
const completeProgress = (videoId) => {
const progressBar = document.getElementById("tel-downloader-progress-" + videoId)?.querySelector("div.progress");
if (progressBar) {
progressBar.querySelector("p").innerText = "Completed";
progressBar.querySelector("div").style.backgroundColor = "#B6C649";
progressBar.querySelector("div").style.width = "100%";
}
};
const AbortProgress = (videoId) => {
const progressBar = document.getElementById("tel-downloader-progress-" + videoId)?.querySelector("div.progress");
if (progressBar) {
progressBar.querySelector("p").innerText = "Aborted";
progressBar.querySelector("div").style.backgroundColor = "#D16666";
progressBar.querySelector("div").style.width = "100%";
}
};
// Enhanced download functions
const tel_download_video = (url) => {
let _blobs = [];
let _next_offset = 0;
let _total_size = null;
let _file_extension = "mp4";
const videoId = (Math.random() + 1).toString(36).substring(2, 10) + "_" + Date.now().toString();
let fileName = hashCode(url).toString(36) + "." + _file_extension;
try {
const metadata = JSON.parse(decodeURIComponent(url.split("/")[url.split("/").length - 1]));
if (metadata.fileName) {
fileName = metadata.fileName;
}
} catch (e) {
// Invalid JSON string, pass extracting fileName
}
logger.info(`URL: ${url}`, fileName);
const fetchNextPart = (_writable) => {
fetch(url, {
method: "GET",
headers: {
Range: `bytes=${_next_offset}-`,
},
"User-Agent": "User-Agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0",
})
.then((res) => {
if (![200, 206].includes(res.status)) {
throw new Error("Non 200/206 response was received: " + res.status);
}
const mime = res.headers.get("Content-Type").split(";")[0];
if (!mime.startsWith("video/")) {
throw new Error("Get non video response with MIME type " + mime);
}
_file_extension = mime.split("/")[1];
fileName = fileName.substring(0, fileName.indexOf(".") + 1) + _file_extension;
const match = res.headers.get("Content-Range").match(contentRangeRegex);
const startOffset = parseInt(match[1]);
const endOffset = parseInt(match[2]);
const totalSize = parseInt(match[3]);
if (startOffset !== _next_offset) {
logger.error("Gap detected between responses.", fileName);
throw "Gap detected between responses.";
}
if (_total_size && totalSize !== _total_size) {
logger.error("Total size differs", fileName);
throw "Total size differs";
}
_next_offset = endOffset + 1;
_total_size = totalSize;
logger.info(`Progress: ${((_next_offset * 100) / _total_size).toFixed(0)}%`, fileName);
updateProgress(videoId, fileName, ((_next_offset * 100) / _total_size).toFixed(0));
return res.blob();
})
.then((resBlob) => {
if (_writable !== null) {
_writable.write(resBlob).then(() => {});
} else {
_blobs.push(resBlob);
}
})
.then(() => {
if (!_total_size) {
throw new Error("_total_size is NULL");
}
if (_next_offset < _total_size) {
fetchNextPart(_writable);
} else {
if (_writable !== null) {
_writable.close().then(() => {
logger.info("Download finished", fileName);
});
} else {
save();
}
completeProgress(videoId);
}
})
.catch((reason) => {
logger.error(reason, fileName);
AbortProgress(videoId);
});
};
const save = () => {
logger.info("Finish downloading blobs", fileName);
const blob = new Blob(_blobs, { type: "video/mp4" });
const blobUrl = window.URL.createObjectURL(blob);
const a = document.createElement("a");
document.body.appendChild(a);
a.href = blobUrl;
a.download = fileName;
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(blobUrl);
logger.info("Download triggered", fileName);
};
fetchNextPart(null);
createProgressBar(videoId, fileName);
};
const tel_download_audio = (url) => {
let _blobs = [];
let _next_offset = 0;
let _total_size = null;
const fileName = hashCode(url).toString(36) + ".ogg";
const fetchNextPart = (_writable) => {
fetch(url, {
method: "GET",
headers: {
Range: `bytes=${_next_offset}-`,
},
})
.then((res) => {
if (res.status !== 206 && res.status !== 200) {
logger.error("Non 200/206 response was received: " + res.status, fileName);
return;
}
const mime = res.headers.get("Content-Type").split(";")[0];
if (!mime.startsWith("audio/")) {
logger.error("Get non audio response with MIME type " + mime, fileName);
throw "Get non audio response with MIME type " + mime;
}
try {
const match = res.headers.get("Content-Range").match(contentRangeRegex);
const startOffset = parseInt(match[1]);
const endOffset = parseInt(match[2]);
const totalSize = parseInt(match[3]);
if (startOffset !== _next_offset) {
logger.error("Gap detected between responses.");
throw "Gap detected between responses.";
}
if (_total_size && totalSize !== _total_size) {
logger.error("Total size differs");
throw "Total size differs";
}
_next_offset = endOffset + 1;
_total_size = totalSize;
} finally {
return res.blob();
}
})
.then((resBlob) => {
if (_writable !== null) {
_writable.write(resBlob).then(() => {});
} else {
_blobs.push(resBlob);
}
})
.then(() => {
if (_next_offset < _total_size) {
fetchNextPart(_writable);
} else {
if (_writable !== null) {
_writable.close().then(() => {
logger.info("Download finished", fileName);
});
} else {
save();
}
}
})
.catch((reason) => {
logger.error(reason, fileName);
});
};
const save = () => {
logger.info("Finish downloading blobs. Concatenating blobs and downloading...", fileName);
let blob = new Blob(_blobs, { type: "audio/ogg" });
const blobUrl = window.URL.createObjectURL(blob);
const a = document.createElement("a");
document.body.appendChild(a);
a.href = blobUrl;
a.download = fileName;
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(blobUrl);
logger.info("Download triggered", fileName);
};
fetchNextPart(null);
};
const tel_download_image = (imageUrl) => {
const fileName = (Math.random() + 1).toString(36).substring(2, 10) + ".jpeg";
const a = document.createElement("a");
document.body.appendChild(a);
a.href = imageUrl;
a.download = fileName;
a.click();
document.body.removeChild(a);
logger.info("Download triggered", fileName);
};
// BATCH DOWNLOAD FUNCTIONALITY
const getSelectedCount = () => {
const selectedBubbles = document.querySelectorAll('.bubble.is-selected');
return selectedBubbles.length;
};
const triggerNativeDownload = () => {
logger.info('Starting silent batch download process...');
const firstSelected = document.querySelector('.bubble.is-selected');
if (!firstSelected) {
logger.error('No selected bubbles found');
return;
}
const rightClickEvent = new MouseEvent('contextmenu', {
bubbles: true,
cancelable: true,
view: window,
button: 2,
buttons: 2,
clientX: 100,
clientY: 100
});
logger.info('Triggering context menu silently...');
firstSelected.dispatchEvent(rightClickEvent);
setTimeout(() => {
const contextMenu = document.querySelector('#bubble-contextmenu');
if (contextMenu) {
// Hide the context menu immediately
contextMenu.style.display = 'none';
contextMenu.style.visibility = 'hidden';
contextMenu.style.opacity = '0';
contextMenu.style.pointerEvents = 'none';
logger.info('Context menu hidden, looking for Download selected...');
const menuItems = contextMenu.querySelectorAll('.btn-menu-item');
let downloadFound = false;
menuItems.forEach(item => {
const textElement = item.querySelector('.btn-menu-item-text');
if (textElement && textElement.textContent.trim() === 'Download selected') {
logger.info('Found "Download selected" button, clicking silently...');
item.click();
downloadFound = true;
}
});
if (!downloadFound) {
logger.warn('Download selected option not found in context menu');
}
setTimeout(() => {
if (contextMenu) {
contextMenu.classList.remove('active', 'was-open');
contextMenu.style.display = 'none';
}
}, 50);
} else {
logger.error('Context menu not found');
}
}, 50);
};
// Create batch download button
const createBatchDownloadButton = () => {
const existingBtn = document.getElementById('tg-batch-download-btn');
if (existingBtn) {
const count = getSelectedCount();
const countSpan = existingBtn.querySelector('.media-count');
if (countSpan) {
countSpan.textContent = count > 0 ? count : '';
countSpan.style.display = count > 0 ? 'flex' : 'none';
}
return;
}
const downloadBtn = document.createElement('button');
downloadBtn.id = 'tg-batch-download-btn';
downloadBtn.title = 'Download Selected Files';
downloadBtn.innerHTML = `
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 20h14v-2H5v2zM12 4v12l-4-4h3V4h2v8h3l-4 4z" fill="white" stroke="white" stroke-width="0.5"/>
</svg>
<span class="media-count" style="
position: absolute;
top: -6px;
right: -6px;
background: #ff4757;
color: white;
border-radius: 11px;
width: 22px;
height: 22px;
font-size: 12px;
font-weight: bold;
display: none;
align-items: center;
justify-content: center;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
border: 2px solid white;
"></span>
`;
Object.assign(downloadBtn.style, {
position: 'fixed',
bottom: '20px',
right: '20px',
zIndex: '999999',
background: '#8774e1',
border: 'none',
borderRadius: '50%',
color: 'white',
cursor: 'pointer',
padding: '13px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '54px',
height: '54px',
boxShadow: '0 4px 16px rgba(135, 116, 225, 0.4)',
transition: 'all 0.2s ease',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
});
downloadBtn.addEventListener('mouseenter', () => {
downloadBtn.style.background = '#7c6ce0';
downloadBtn.style.transform = 'scale(1.05)';
});
downloadBtn.addEventListener('mouseleave', () => {
downloadBtn.style.background = '#8774e1';
downloadBtn.style.transform = 'scale(1)';
});
downloadBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const count = getSelectedCount();
if (count === 0) {
alert('Please select some messages first');
return;
}
logger.info(`Batch download button clicked! Starting silent download for ${count} selected items...`);
triggerNativeDownload();
});
document.body.appendChild(downloadBtn);
logger.info('Batch download button created and added to page');
};
// Monitor selection changes for batch download
const monitorSelection = () => {
const observer = new MutationObserver(() => {
setTimeout(createBatchDownloadButton, 100);
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class']
});
};
// ORIGINAL RESTRICTED CONTENT DOWNLOAD FUNCTIONALITY
logger.info("Initialized Enhanced Telegram Downloader");
// For webz /a/ webapp
setInterval(() => {
// Stories
const storiesContainer = document.getElementById("StoryViewer");
if (storiesContainer) {
const createDownloadButton = () => {
const downloadIcon = document.createElement("i");
downloadIcon.className = "icon icon-download";
const downloadButton = document.createElement("button");
downloadButton.className = "Button TkphaPyQ tiny translucent-white round tel-download";
downloadButton.appendChild(downloadIcon);
downloadButton.setAttribute("type", "button");
downloadButton.setAttribute("title", "Download");
downloadButton.setAttribute("aria-label", "Download");
downloadButton.onclick = () => {
const video = storiesContainer.querySelector("video");
const videoSrc = video?.src || video?.currentSrc || video?.querySelector("source")?.src;
if (videoSrc) {
tel_download_video(videoSrc);
} else {
const images = storiesContainer.querySelectorAll("img.PVZ8TOWS");
if (images.length > 0) {
const imageSrc = images[images.length - 1]?.src;
if (imageSrc) tel_download_image(imageSrc);
}
}
};
return downloadButton;
};
const storyHeader = storiesContainer.querySelector(".GrsJNw3y") ||
storiesContainer.querySelector(".DropdownMenu").parentNode;
if (storyHeader && !storyHeader.querySelector(".tel-download")) {
storyHeader.insertBefore(createDownloadButton(), storyHeader.querySelector("button"));
}
}
// Media viewer
const mediaContainer = document.querySelector("#MediaViewer .MediaViewerSlide--active");
const mediaViewerActions = document.querySelector("#MediaViewer .MediaViewerActions");
if (!mediaContainer || !mediaViewerActions) return;
const videoPlayer = mediaContainer.querySelector(".MediaViewerContent > .VideoPlayer");
const img = mediaContainer.querySelector(".MediaViewerContent > div > img");
const downloadIcon = document.createElement("i");
downloadIcon.className = "icon icon-download";
const downloadButton = document.createElement("button");
downloadButton.className = "Button smaller translucent-white round tel-download";
downloadButton.setAttribute("type", "button");
downloadButton.setAttribute("title", "Download");
downloadButton.setAttribute("aria-label", "Download");
if (videoPlayer) {
const videoUrl = videoPlayer.querySelector("video").currentSrc;
downloadButton.setAttribute("data-tel-download-url", videoUrl);
downloadButton.appendChild(downloadIcon);
downloadButton.onclick = () => {
tel_download_video(videoPlayer.querySelector("video").currentSrc);
};
const controls = videoPlayer.querySelector(".VideoPlayerControls");
if (controls) {
const buttons = controls.querySelector(".buttons");
if (!buttons.querySelector("button.tel-download")) {
const spacer = buttons.querySelector(".spacer");
spacer.after(downloadButton);
}
}
if (mediaViewerActions.querySelector("button.tel-download")) {
const telDownloadButton = mediaViewerActions.querySelector("button.tel-download");
if (mediaViewerActions.querySelectorAll('button[title="Download"]').length > 1) {
mediaViewerActions.querySelector("button.tel-download").remove();
} else if (telDownloadButton.getAttribute("data-tel-download-url") !== videoUrl) {
telDownloadButton.onclick = () => {
tel_download_video(videoPlayer.querySelector("video").currentSrc);
};
telDownloadButton.setAttribute("data-tel-download-url", videoUrl);
}
} else if (!mediaViewerActions.querySelector('button[title="Download"]')) {
mediaViewerActions.prepend(downloadButton);
}
} else if (img && img.src) {
downloadButton.setAttribute("data-tel-download-url", img.src);
downloadButton.appendChild(downloadIcon);
downloadButton.onclick = () => {
tel_download_image(img.src);
};
if (mediaViewerActions.querySelector("button.tel-download")) {
const telDownloadButton = mediaViewerActions.querySelector("button.tel-download");
if (mediaViewerActions.querySelectorAll('button[title="Download"]').length > 1) {
mediaViewerActions.querySelector("button.tel-download").remove();
} else if (telDownloadButton.getAttribute("data-tel-download-url") !== img.src) {
telDownloadButton.onclick = () => {
tel_download_image(img.src);
};
telDownloadButton.setAttribute("data-tel-download-url", img.src);
}
} else if (!mediaViewerActions.querySelector('button[title="Download"]')) {
mediaViewerActions.prepend(downloadButton);
}
}
}, REFRESH_DELAY);
// For webk /k/ webapp
setInterval(() => {
// Voice Message or Circle Video
const pinnedAudio = document.body.querySelector(".pinned-audio");
let dataMid;
let downloadButtonPinnedAudio = document.body.querySelector("._tel_download_button_pinned_container") ||
document.createElement("button");
if (pinnedAudio) {
dataMid = pinnedAudio.getAttribute("data-mid");
downloadButtonPinnedAudio.className = "btn-icon tgico-download _tel_download_button_pinned_container";
downloadButtonPinnedAudio.innerHTML = `<span class="tgico button-icon">${DOWNLOAD_ICON}</span>`;
}
const audioElements = document.body.querySelectorAll("audio-element");
audioElements.forEach((audioElement) => {
const bubble = audioElement.closest(".bubble");
if (!bubble || bubble.querySelector("._tel_download_button_pinned_container")) {
return;
}
if (dataMid && downloadButtonPinnedAudio.getAttribute("data-mid") !== dataMid &&
audioElement.getAttribute("data-mid") === dataMid) {
downloadButtonPinnedAudio.onclick = (e) => {
e.stopPropagation();
const link = audioElement.audio && audioElement.audio.getAttribute("src");
const isAudio = audioElement.audio && audioElement.audio instanceof HTMLAudioElement;
if (link) {
if (isAudio) {
tel_download_audio(link);
} else {
tel_download_video(link);
}
}
};
downloadButtonPinnedAudio.setAttribute("data-mid", dataMid);
const link = audioElement.audio && audioElement.audio.getAttribute("src");
if (link) {
pinnedAudio.querySelector(".pinned-container-wrapper-utils").appendChild(downloadButtonPinnedAudio);
}
}
});
// Stories
const storiesContainer = document.getElementById("stories-viewer");
if (storiesContainer) {
const createDownloadButton = () => {
const downloadButton = document.createElement("button");
downloadButton.className = "btn-icon rp tel-download";
downloadButton.innerHTML = `<span class="tgico">${DOWNLOAD_ICON}</span><div class="c-ripple"></div>`;
downloadButton.setAttribute("type", "button");
downloadButton.setAttribute("title", "Download");
downloadButton.setAttribute("aria-label", "Download");
downloadButton.onclick = () => {
const video = storiesContainer.querySelector("video.media-video");
const videoSrc = video?.src || video?.currentSrc || video?.querySelector("source")?.src;
if (videoSrc) {
tel_download_video(videoSrc);
} else {
const imageSrc = storiesContainer.querySelector("img.media-photo")?.src;
if (imageSrc) tel_download_image(imageSrc);
}
};
return downloadButton;
};
const storyHeader = storiesContainer.querySelector("[class^='_ViewerStoryHeaderRight']");
if (storyHeader && !storyHeader.querySelector(".tel-download")) {
storyHeader.prepend(createDownloadButton());
}
const storyFooter = storiesContainer.querySelector("[class^='_ViewerStoryFooterRight']");
if (storyFooter && !storyFooter.querySelector(".tel-download")) {
storyFooter.prepend(createDownloadButton());
}
}
// Media viewer
const mediaContainer = document.querySelector(".media-viewer-whole");
if (!mediaContainer) return;
const mediaAspecter = mediaContainer.querySelector(".media-viewer-movers .media-viewer-aspecter");
const mediaButtons = mediaContainer.querySelector(".media-viewer-topbar .media-viewer-buttons");
if (!mediaAspecter || !mediaButtons) return;
// Query hidden buttons and unhide them
const hiddenButtons = mediaButtons.querySelectorAll("button.btn-icon.hide");
let onDownload = null;
for (const btn of hiddenButtons) {
btn.classList.remove("hide");
if (btn.textContent === FORWARD_ICON) {
btn.classList.add("tgico-forward");
}
if (btn.textContent === DOWNLOAD_ICON) {
btn.classList.add("tgico-download");
onDownload = () => {
btn.click();
};
}
}
if (mediaAspecter.querySelector(".ckin__player")) {
const controls = mediaAspecter.querySelector(".default__controls.ckin__controls");
if (controls && !controls.querySelector(".tel-download")) {
const brControls = controls.querySelector(".bottom-controls .right-controls");
const downloadButton = document.createElement("button");
downloadButton.className = "btn-icon default__button tgico-download tel-download";
downloadButton.innerHTML = `<span class="tgico">${DOWNLOAD_ICON}</span>`;
downloadButton.setAttribute("type", "button");
downloadButton.setAttribute("title", "Download");
downloadButton.setAttribute("aria-label", "Download");
if (onDownload) {
downloadButton.onclick = onDownload;
} else {
downloadButton.onclick = () => {
tel_download_video(mediaAspecter.querySelector("video").src);
};
}
brControls.prepend(downloadButton);
}
} else if (mediaAspecter.querySelector("video") &&
!mediaButtons.querySelector("button.btn-icon.tgico-download")) {
const downloadButton = document.createElement("button");
downloadButton.className = "btn-icon tgico-download tel-download";
downloadButton.innerHTML = `<span class="tgico button-icon">${DOWNLOAD_ICON}</span>`;
downloadButton.setAttribute("type", "button");
downloadButton.setAttribute("title", "Download");
downloadButton.setAttribute("aria-label", "Download");
if (onDownload) {
downloadButton.onclick = onDownload;
} else {
downloadButton.onclick = () => {
tel_download_video(mediaAspecter.querySelector("video").src);
};
}
mediaButtons.prepend(downloadButton);
} else if (!mediaButtons.querySelector("button.btn-icon.tgico-download")) {
if (!mediaAspecter.querySelector("img.thumbnail") ||
!mediaAspecter.querySelector("img.thumbnail").src) {
return;
}
const downloadButton = document.createElement("button");
downloadButton.className = "btn-icon tgico-download tel-download";
downloadButton.innerHTML = `<span class="tgico button-icon">${DOWNLOAD_ICON}</span>`;
downloadButton.setAttribute("type", "button");
downloadButton.setAttribute("title", "Download");
downloadButton.setAttribute("aria-label", "Download");
if (onDownload) {
downloadButton.onclick = onDownload;
} else {
downloadButton.onclick = () => {
tel_download_image(mediaAspecter.querySelector("img.thumbnail").src);
};
}
mediaButtons.prepend(downloadButton);
}
}, REFRESH_DELAY);
// Progress bar container setup
(function setupProgressBar() {
const body = document.querySelector("body");
const container = document.createElement("div");
container.id = "tel-downloader-progress-bar-container";
container.style.position = "fixed";
container.style.bottom = 0;
container.style.right = 0;
if (location.pathname.startsWith("/k/")) {
container.style.zIndex = 4;
} else {
container.style.zIndex = 1600;
}
body.appendChild(container);
})();
// Initialize batch download functionality
const init = () => {
logger.info('Initializing enhanced Telegram downloader with batch support...');
createBatchDownloadButton();
monitorSelection();
setInterval(createBatchDownloadButton, 2000);
logger.info('Enhanced downloader ready!');
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
setTimeout(init, 1000);
}
logger.info("Enhanced Telegram Media Downloader setup completed.");
})();