Greasy Fork镜像 还支持 简体中文。

Telegram Media Downloader (Batch Support)(by AFU IT)

Download images, GIFs, videos, and voice messages from private channels + batch download selected media

目前為 2025-06-05 提交的版本,檢視 最新版本

// ==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 = "&times;";
        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.");

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址