哔哩哔哩动态图片下载

为方便下载bilibili图片而开发

// ==UserScript==
// @name         哔哩哔哩动态图片下载
// @namespace    https://space.bilibili.com/11768481
// @version      1.0
// @description  为方便下载bilibili图片而开发
// @author       伊墨墨
// @match        https://www.bilibili.com/opus/*
// @match        https://t.bilibili.com/*
// @match        https://space.bilibili.com/*/dynamic
// @match        https://www.bilibili.com/v/topic/*
// @grant        GM_download
// @grant        GM_addStyle
// @license      MIT
// @icon         https://www.bilibili.com/favicon.ico
// @supportURL   https://gf.qytechs.cn/zh-CN/scripts/531888/feedback
// @homepageURL  https://gf.qytechs.cn/zh-CN/scripts/531888
// ==/UserScript==

(function () {
    'use strict';

    // --- 样式定义 ---
    GM_addStyle(`
        #bili-download-images-button { /* 旧版悬浮按钮样式 */
            position: fixed;
            top: 50%;
            right: 20px;
            z-index: 1000;
            padding: 10px 20px;
            background-color: #00a1d6;
            color: white;
            border: none;
            border-radius: 5px;
            font-size: 16px;
            cursor: pointer;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
        }
        #bili-download-images-button:hover {
             background-color: #007ead;
        }
        .bili-toast-message { /* 提示框样式 */
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background-color: rgba(51, 51, 51, 0.9);
            color: white;
            padding: 10px 20px;
            border-radius: 5px;
            z-index: 9999;
            font-size: 14px;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
            pointer-events: none;
       }
       /* 动态菜单下载选项样式 */
       .download-images-option {
            cursor: pointer;
       }
       .download-images-option:hover .bili-cascader-options__item-label,
       .download-images-option.bili-dyn-more__menu__item:hover {
           color: #00a1d6;
       }
       /* 侧边栏下载按钮样式 */
       .side-toolbar__action.download {
            cursor: pointer;
            text-align: center;
            color: #61666D;
            transition: color 0.3s;
            margin-bottom: 16px;
        }
        .side-toolbar__action.download svg {
            width: 24px;
            height: 24px;
            margin-bottom: 2px;
        }
        .side-toolbar__action.download .side-toolbar__action__text {
            font-size: 12px;
            line-height: 14px;
            color: #9499A0;
            transform: scale(0.875);
            transform-origin: center top;
        }
        .side-toolbar__action.download:hover {
            color: #00A1D6;
        }
    `);

    // --- 常量与配置 ---
    const MAX_CONCURRENT_DOWNLOADS = 3; // 最大并发下载数
    const CONFIG = {
        DETAIL_PAGE_ID_REGEX: /(?:\/opus\/|\/dynamic\/|\/)(\d{10,})/, // 用于从URL提取ID
        IMAGE_CONTAINER_SELECTORS: [ // 详情页图片容器选择器 (优先级顺序)
            '.horizontal-scroll-album__indicator',
            '.bili-album__preview',
            '.bili-dyn-item__images',
            '.horizontal-scroll-album',
            '.opus-module-content',
            '.bili-dyn-gallery__track',
            '.bili-dyn-card-video__cover'
        ],
        CARD_CONFIG: { // 卡片模式下的基础配置
            CARD_SELECTOR: '.bili-dyn-list__item', // 动态卡片 (动态首页/空间)
            MORE_BUTTON_SELECTOR: '.bili-dyn-item__more .bili-dyn-more__btn', // "更多"按钮
            CASCADER_SELECTOR: '.bili-dyn-more__cascader, .bili-popover', // 弹出菜单
            OPTIONS_SELECTOR: '.bili-cascader-options, .bili-dyn-more__menu', // 菜单选项列表
            LIST_CONTAINER_SELECTOR: '.bili-dyn-list__items', // 卡片列表容器 (动态首页)
            IMAGE_SELECTORS: [ // 卡片内图片选择器
                '.bili-album__watch__track__list img[src]',
                '.bili-album__preview img[src]',
                '.bili-dyn-gallery__track img[src]',
                '.bili-dyn-card-video__cover img[src]'
            ],
        }
    };

    // --- 工具函数 ---
    const utils = {
        /** 等待指定选择器的元素出现在 DOM 中 */
        waitForElement: (selector, callback, timeout = 1000) => {
            let element = document.querySelector(selector);
            if (element) {
                callback(element);
                return;
            }
            let observer = null;
            let timeoutId = null;
            const cleanup = () => {
                if (observer) observer.disconnect();
                if (timeoutId) clearTimeout(timeoutId);
                observer = null;
                timeoutId = null;
            };
            observer = new MutationObserver((mutations, obs) => {
                element = document.querySelector(selector);
                if (element) {
                    cleanup();
                    callback(element);
                }
            });
            observer.observe(document.body, { childList: true, subtree: true });
            if (timeout > 0) {
                timeoutId = setTimeout(() => {
                    if (observer) {
                        console.warn(`[B站图片下载] 等待元素 "${selector}" 超时 (${timeout}ms).`);
                        cleanup();
                        // 超时后可以考虑调用 callback(null) 让调用者知道失败了
                    }
                }, timeout);
            }
        },

        /** 文件名消毒:移除或替换非法字符,限制长度 */
        sanitizeFilename: (name) => {
            if (!name) return 'unknown_file';
            return name
                .replace(/[/\\:*?"<>|]/g, '_')
                .replace(/\s+/g, ' ')
                .trim()
                .slice(0, 200);
        },

        /** 显示自动消失的提示消息 */
        showToast: (message, duration = 3000) => {
            const toast = document.createElement('div');
            toast.className = 'bili-toast-message';
            toast.textContent = message;
            document.body.appendChild(toast);
            setTimeout(() => toast.remove(), duration);
        },

        /** 处理图片 URL:确保是 https,移除 @ 参数 */
        processImageUrl: (rawUrl) => {
            if (!rawUrl || typeof rawUrl !== 'string') return null;
            let cleanUrl = rawUrl.startsWith('//') ? 'https:' + rawUrl : rawUrl;
            if (!cleanUrl.startsWith('http')) return null;
            cleanUrl = cleanUrl.split('@')[0];
            return cleanUrl;
        }
    };

    // --- 核心下载功能模块 ---
    const core = {
        /** 获取当前页面的用户名 (动态详情页/Opus页) */
        getUsernameFromPage: () => {
            const dynTitleElem = document.querySelector('.bili-dyn-title__text');
            if (dynTitleElem) return dynTitleElem.innerText.trim();
            const opusAuthorElem = document.querySelector('.opus-module-author__name');
            if (opusAuthorElem) return opusAuthorElem.innerText.trim();
            return '未知用户';
        },

        /** 获取当前页面的动态/Opus ID */
        getItemIdFromPage: () => {
            const urlMatch = window.location.href.match(CONFIG.DETAIL_PAGE_ID_REGEX);
            if (urlMatch?.[1]) return urlMatch[1];
            console.warn("[B站图片下载] 无法从URL获取ID,使用时间戳作为后备。");
            return Date.now().toString().slice(-10);
        },

        /** 查找页面中的主要图片容器 */
        findImageContainerOnPage: () => {
            for (const selector of CONFIG.IMAGE_CONTAINER_SELECTORS) {
                const container = document.querySelector(selector);
                if (container && container.querySelector('img[src]')) {
                    return container;
                }
            }
            return null;
        },

        /** 从指定的容器中提取所有有效图片信息 */
        extractImagesFromContainer: (container) => {
            if (!container) return [];
            return Array.from(container.querySelectorAll('img[src]'))
                .map(img => {
                    const cleanUrl = utils.processImageUrl(img.src);
                    if (!cleanUrl) return null;
                    const formatMatch = cleanUrl.match(/\.(jpg|jpeg|png|gif|webp|avif)(?:[?#]|$)/i);
                    const format = formatMatch ? formatMatch[1].toLowerCase() : 'jpg';
                    return { url: cleanUrl, format: format };
                })
                .filter(Boolean);
        },

        /** 监听 B站相册展开后的图片加载 */
        monitorAlbumExpansion: (username, itemId) => {
            const trackListSelector = '.bili-album__watch__track__list';
            utils.waitForElement(trackListSelector, (trackContainer) => {
                if (trackContainer.children.length > 0) {
                    const images = core.extractImagesFromContainer(trackContainer);
                    if (images.length) {
                        core.downloadImages(images, username, itemId);
                    } else {
                        utils.showToast('相册展开后未找到图片。');
                    }
                } else {
                    utils.showToast('相册轨道为空。'); // 可能展开了但内容加载失败或为空
                }
            }, 10000); // 10秒超时
        },

        /** 批量下载图片 */
        downloadImages: (images, username = '未知用户', itemId = '未知ID') => {
            if (!images || images.length === 0) {
                utils.showToast('未找到可下载的图片!');
                return;
            }
            const totalImages = images.length;
            let submittedCount = 0;
            utils.showToast(`开始处理 ${totalImages} 张图片...`);

            const downloadBatch = (startIndex) => {
                const endIndex = Math.min(startIndex + MAX_CONCURRENT_DOWNLOADS, totalImages);
                for (let i = startIndex; i < endIndex; i++) {
                    const img = images[i];
                    const filename = utils.sanitizeFilename(
                        `${username}_${itemId}_${String(i + 1).padStart(2, '0')}.${img.format}`
                    );
                    try {
                        GM_download({
                            url: img.url,
                            name: filename,
                            headers: { "Referer": location.href },
                            onerror: (error, details) => {
                                console.error(`[GM_download Error] ${filename}:`, error, details);
                                if (error.error === 'network' || details?.current === 'NETWORK_FAILED') {
                                    utils.showToast(`网络错误,尝试备用方法: ${filename}`, 4000);
                                    core.fetchAndDownload(img.url, filename);
                                } else if (error.error === 'not_enabled' || error.error === 'not_granted') {
                                    utils.showToast('GM_download权限不足或未启用', 5000);
                                    core.fetchAndDownload(img.url, filename);
                                } else {
                                    utils.showToast(`下载失败(${error.error || '未知'}): ${filename}`, 5000);
                                    core.fetchAndDownload(img.url, filename); // 尝试备用
                                }
                            },
                            ontimeout: () => {
                                console.warn(`[GM_download Timeout] ${filename}`);
                                utils.showToast(`下载超时,尝试备用方法: ${filename}`, 4000);
                                core.fetchAndDownload(img.url, filename);
                            }
                        });
                        submittedCount++;
                    } catch (e) {
                        console.error("[GM_download Exception] GM_download不可用:", e);
                        utils.showToast('GM_download不可用,使用备用下载...', 3000);
                        core.fetchAndDownload(img.url, filename);
                        submittedCount++;
                    }
                }

                if (endIndex < totalImages) {
                    setTimeout(() => downloadBatch(endIndex), 1000); // 下一批延迟
                } else {
                    utils.showToast(`已提交 ${submittedCount} / ${totalImages} 个下载任务。`);
                }
            };
            downloadBatch(0);
        },

        /** 使用 Fetch API 下载图片 (备用方案) */
        fetchAndDownload: async (url, filename) => {
            try {
                const response = await fetch(url, { headers: { "Referer": window.location.href } });
                if (!response.ok) throw new Error(`HTTP error ${response.status} (${response.statusText})`);
                const blob = await response.blob();
                if (!blob.size) throw new Error("下载的文件为空");

                const downloadUrl = window.URL.createObjectURL(blob);
                const anchor = document.createElement('a');
                anchor.href = downloadUrl;
                anchor.download = filename;
                anchor.style.display = 'none';
                document.body.appendChild(anchor);
                anchor.click();
                setTimeout(() => {
                    document.body.removeChild(anchor);
                    window.URL.revokeObjectURL(downloadUrl);
                }, 100);
            } catch (err) {
                console.error(`[Fetch Download Error] ${filename}:`, err);
                let errorMsg = `备用下载失败: ${filename}`;
                if (err.message.includes('HTTP error')) errorMsg += ` (${err.message})`;
                else if (err instanceof TypeError && err.message.includes('Failed to fetch')) errorMsg += ' (网络或跨域问题)';
                else errorMsg += ` (${err.message || '未知错误'})`;
                utils.showToast(errorMsg, 5000);
            }
        }
    };

    // --- 卡片处理模块 (动态流、话题页、用户空间) ---
    const cardHandler = {
        /** 初始化卡片下载功能 (事件委托) */
        initCardDownloads: function (pageConfig) {
            // 使用 waitForElement 等待列表容器加载
            utils.waitForElement(pageConfig.LIST_CONTAINER_SELECTOR, (listContainer) => {
                if (!listContainer) {
                    console.error(`[B站图片下载] 错误:未能找到列表容器: ${pageConfig.LIST_CONTAINER_SELECTOR}`);
                    return;
                }
                this.setupEventListeners(listContainer, pageConfig);
                this.setupMutationObserver(listContainer, pageConfig); // 监听容器本身的变化
            }, 20000); // 增加等待时间
        },

        /** 在指定容器上设置事件监听器 */
        setupEventListeners: function (container, pageConfig) {
            // 监听 "更多" 按钮的悬停
            container.addEventListener('mouseenter', (event) => {
                const moreButton = event.target.closest(pageConfig.MORE_BUTTON_SELECTOR);
                if (moreButton && !moreButton.dataset.downloadHandlerAttached) {
                    moreButton.dataset.downloadHandlerAttached = 'true';
                    this.addDownloadOptionWhenMenuReady(moreButton, pageConfig);
                }
            }, true);

            // 监听点击,应对点击才生成菜单的情况
            container.addEventListener('click', (event) => {
                const moreButton = event.target.closest(pageConfig.MORE_BUTTON_SELECTOR);
                if (moreButton && !moreButton.dataset.downloadOptionAdded) { // 检查是否已成功添加过
                    this.addDownloadOptionWhenMenuReady(moreButton, pageConfig);
                }
            }, true);
        },

        /** 设置 MutationObserver 监听卡片列表容器的变化 */
        setupMutationObserver: function (container, pageConfig) {
            const observer = new MutationObserver((mutations) => {
                // 主要应对动态加载新卡片,事件委托理论上能处理,此观察器可用于调试或未来扩展
                let hasAddedNodes = mutations.some(m => m.type === 'childList' && m.addedNodes.length > 0);
                // if (hasAddedNodes) { console.log('New cards potentially added.'); }
            });
            observer.observe(container, { childList: true, subtree: false });
        },

        /** 尝试在菜单准备好后添加下载选项 */
        addDownloadOptionWhenMenuReady: function (moreButton, pageConfig) {
            setTimeout(() => {
                const parentMoreWrapper = moreButton.closest('.bili-dyn-item__more') || moreButton.closest('div[class*="more"]');
                if (!parentMoreWrapper) return;
                const cascader = parentMoreWrapper.querySelector(pageConfig.CASCADER_SELECTOR);
                if (!cascader) return; // 菜单可能尚未生成

                const optionsContainer = cascader.querySelector(pageConfig.OPTIONS_SELECTOR);
                if (optionsContainer && !optionsContainer.querySelector('.download-images-option')) {
                    const downloadItem = document.createElement('div');
                    downloadItem.className = 'download-images-option';

                    // 应用 B 站菜单项样式
                    if (optionsContainer.classList.contains('bili-cascader-options')) {
                        downloadItem.classList.add('bili-cascader-options__item');
                        downloadItem.innerHTML = `<div class="bili-cascader-options__item-custom"><div><div class="bili-cascader-options__item-label">下载图片</div></div></div>`;
                    } else if (optionsContainer.classList.contains('bili-dyn-more__menu')) {
                        downloadItem.classList.add('bili-dyn-more__menu__item');
                        downloadItem.style.cssText = 'height: 25px; line-height: 25px; padding: 0px 12px; text-align: left;';
                        downloadItem.textContent = '下载图片';
                    } else {
                        downloadItem.textContent = '下载图片'; // 通用后备
                        downloadItem.style.padding = '5px 10px';
                        console.warn("[B站图片下载] 未知的菜单选项容器结构,使用基础样式");
                    }

                    optionsContainer.prepend(downloadItem); // 添加到顶部
                    moreButton.dataset.downloadOptionAdded = 'true'; // 标记已添加

                    // 添加点击事件
                    downloadItem.addEventListener('click', (e) => {
                        e.stopPropagation();
                        e.preventDefault();
                        const card = moreButton.closest(pageConfig.CARD_SELECTOR);
                        if (card) {
                            const { username, itemId, images } = pageConfig.getInfoFunction(card, pageConfig);
                            if (images && images.length > 0) {
                                core.downloadImages(images, username, itemId);
                            } else {
                                utils.showToast('在该动态中未找到可下载的图片');
                            }
                        } else {
                            console.error('[B站图片下载] 错误:无法从按钮追溯到卡片元素');
                            utils.showToast('无法定位动态卡片');
                        }
                    });
                } else if (optionsContainer && optionsContainer.querySelector('.download-images-option')) {
                    moreButton.dataset.downloadOptionAdded = 'true'; // 确保已存在的也被标记
                }
            }, 150); // 延迟等待菜单渲染
        },

        /** 从卡片元素中提取信息 */
        getInfoFromCard: function (card, pageConfig) {
            let username = '未知UP主';
            let itemId = `卡片_${Date.now()}`;
            let images = [];
            const seenImageKeys = new Set(); // 使用 Set 来存储已见过的图片标识符,提高去重效率

            // 提取用户名 (优先原作者)
            const origAuthorElement = card.querySelector('.dyn-orig-author__name');
            const currentAuthorElement = card.querySelector('.bili-dyn-title__text');
            username = origAuthorElement?.textContent.trim() || currentAuthorElement?.textContent.trim() || username;

            // 提取动态 ID (优先原动态)
            const origCardElement = card.querySelector('.bili-dyn-content__orig .dyn-card-opus[dyn-id], .bili-dyn-content__orig [data-origin-did]');
            itemId = origCardElement?.getAttribute('data-origin-did') || origCardElement?.getAttribute('dyn-id') || card.getAttribute('data-did') || card.getAttribute('dyn-id') || itemId;
            if (itemId.startsWith('卡片_')) { // 后备:尝试从链接获取
                const linkElement = card.querySelector('a[href*="/dynamic/"], a[href*="/opus/"]');
                if (linkElement) {
                    const idMatch = linkElement.href.match(CONFIG.DETAIL_PAGE_ID_REGEX);
                    if (idMatch?.[1]) itemId = idMatch[1];
                }
            }

            // 提取图片
            const imgElements = card.querySelectorAll(pageConfig.IMAGE_SELECTORS.join(', '));
            imgElements.forEach(img => {
                const rawUrl = img.src || img.dataset.src;
                const processedUrl = utils.processImageUrl(rawUrl);
                if (processedUrl) {
                    // --- 基于路径去重 ---
                    let imageKey = processedUrl; // 默认使用处理后的 URL 作为 key
                    const bfsIndex = processedUrl.indexOf('/bfs/');
                    if (bfsIndex !== -1) {
                        // 如果包含 /bfs/,则使用 /bfs/ 之后的部分作为 key
                        imageKey = processedUrl.substring(bfsIndex);
                    }
                    // else: 如果不包含 /bfs/ (可能是其他来源的图片),则继续使用完整 URL 作为 key

                    if (!seenImageKeys.has(imageKey)) { // 检查 key 是否已存在
                        seenImageKeys.add(imageKey); // 添加新的 key 到 Set

                        const formatMatch = processedUrl.match(/\.(jpg|jpeg|png|gif|webp|avif)(?:[?#]|$)/i);
                        const format = formatMatch ? formatMatch[1].toLowerCase() : 'jpg';
                        images.push({ url: processedUrl, format: format });
                    }
                }
            });

            return { username: utils.sanitizeFilename(username), itemId, images };
        }
    };

    // --- 初始化 ---
    (function init() {
        const hostname = location.hostname;
        const pathname = location.pathname;

        const isDynamicHome = hostname === 't.bilibili.com' && pathname === '/';
        const isUserDynamicPage = hostname === 'space.bilibili.com' && pathname.includes('/dynamic');
        const isTopicPage = hostname === 'www.bilibili.com' && pathname.includes('/v/topic');
        const isDynamicDetailPage = hostname === 't.bilibili.com' && pathname.match(CONFIG.DETAIL_PAGE_ID_REGEX);
        const isOpusPage = hostname === 'www.bilibili.com' && pathname.startsWith('/opus/');

        let pageConfig = null;

        // --- 卡片模式页面处理 ---
        if (isDynamicHome || isUserDynamicPage || isTopicPage) {
            pageConfig = { ...CONFIG.CARD_CONFIG }; // 基础配置
            pageConfig.getInfoFunction = cardHandler.getInfoFromCard; // 指定信息提取函数

            if (isDynamicHome) {
                pageConfig.pageName = '动态首页';
            } else if (isUserDynamicPage) {
                pageConfig.pageName = '用户空间动态';
            } else if (isTopicPage) {
                pageConfig.pageName = '话题页';
                pageConfig.CARD_SELECTOR = '.list__topic-card';
                pageConfig.LIST_CONTAINER_SELECTOR = '.list-view.topic-list__flow-list';
            }

            // 确保列表容器选择器有效后初始化
            if (pageConfig.LIST_CONTAINER_SELECTOR) {
                cardHandler.initCardDownloads(pageConfig);
            } else {
                console.error(`[B站图片下载] 未能确定 "${pageConfig.pageName}" 的列表容器选择器。`);
            }
        }
        // --- 详情页模式处理 (侧边栏按钮+旧版按钮) ---
        else if (isDynamicDetailPage || isOpusPage) {
            const toolbarSelector = '.side-toolbar__box';
            const toolbarCheck = '.sidebar-wrap';
            const floatingButtonId = 'bili-download-images-button'; // 旧版悬浮按钮 ID
            const sidebarButtonId = 'bili-custom-sidebar-download-button'; // 新版侧边栏按钮 ID

            // --- 提取详情页下载核心逻辑 ---
            function triggerDownloadForDetailPage() {
                const username = core.getUsernameFromPage();
                const itemId = core.getItemIdFromPage();
                const container = core.findImageContainerOnPage();
                if (!container) {
                    utils.showToast('未找到图片容器!'); return;
                }
                // 检查是否需要展开相册
                const requiresExpansionTrigger = container.querySelector('.bili-album__preview.more, .bili-album__preview--more, .total-mask, .bili-album-trigger--more');
                const expandButton = container.querySelector('.bili-album__preview--more button, .bili-album__preview__picture:last-child, .bili-album-trigger--more');
                if (requiresExpansionTrigger && expandButton) {
                    expandButton.click();
                    utils.showToast('正在展开相册...', 3000);
                    core.monitorAlbumExpansion(username, itemId);
                } else {
                    const images = core.extractImagesFromContainer(container);
                    core.downloadImages(images, username, itemId);
                }
            }
            // --- END: 提取详情页下载核心逻辑 ---

            // --- 判断使用哪种按钮 ---
            const toolbarBox1 = document.querySelector(toolbarCheck); // 检查侧边栏容器是否存在
            const toolbarBox2 = document.querySelector(toolbarSelector); // 检查侧边栏容器是否存在
            if (toolbarBox1 || toolbarBox2) {
                // **情况1: 存在侧边栏 -> 使用新版侧边栏按钮**
                utils.waitForElement(toolbarSelector, (actualToolbarBox) => {
                    if (actualToolbarBox.querySelector(`#${sidebarButtonId}`)) return; // 避免重复创建

                    const downloadAction = document.createElement('div');
                    downloadAction.id = sidebarButtonId;
                    downloadAction.className = 'side-toolbar__action download';
                    downloadAction.innerHTML = `
                        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                            <path d="M11 5C11 4.44772 11.4477 4 12 4C12.5523 4 13 4.44772 13 5V12.5858L14.2929 11.2929C14.6834 10.9024 15.3166 10.9024 15.7071 11.2929C16.0976 11.6834 16.0976 12.3166 15.7071 12.7071L12.7071 15.7071C12.3166 16.0976 11.6834 16.0976 11.2929 15.7071L8.29289 12.7071C7.90237 12.3166 7.90237 11.6834 8.29289 11.2929C8.68342 10.9024 9.31658 10.9024 9.70711 11.2929L11 12.5858V5Z" fill="currentColor"/>
                            <path d="M4 14C4 13.4477 4.44772 13 5 13H7C7.55228 13 8 13.4477 8 14V18C8 18.5523 7.55228 19 7 19H5C4.44772 19 4 18.5523 4 18V14Z" fill="currentColor"/>
                            <path d="M16 13C16.5523 13 17 13.4477 17 14V18C17 18.5523 16.5523 19 16 19H18C18.5523 19 19 18.5523 19 18V14C19 13.4477 18.5523 13 18 13H16Z" fill="currentColor" opacity="0.5"/>
                            <path d="M4 19C3.44772 19 3 19.4477 3 20C3 20.5523 3.44772 21 4 21H20C20.5523 21 21 20.5523 21 20C21 19.4477 20.5523 19 20 19H4Z" fill="currentColor"/>
                        </svg>
                        <div class="side-toolbar__action__text">下载</div>
                    `;
                    // 点击事件调用提取出的函数
                    downloadAction.addEventListener('click', triggerDownloadForDetailPage);
                    // 添加到侧边栏
                    if (actualToolbarBox.firstChild) actualToolbarBox.insertBefore(downloadAction, actualToolbarBox.firstChild);
                    else actualToolbarBox.appendChild(downloadAction);
                }, 1000); // 等待侧边栏容器

            } else {
                // **情况2: 不存在侧边栏 -> 使用旧版悬浮按钮**
                if (document.getElementById(floatingButtonId)) return; // 避免重复创建

                const downloadButton = document.createElement('button');
                downloadButton.id = floatingButtonId; // 应用 CSS 样式 ID
                downloadButton.textContent = '下载图片'; // 按钮文字
                // 点击事件调用提取出的函数
                downloadButton.addEventListener('click', triggerDownloadForDetailPage);
                document.body.appendChild(downloadButton); // 添加到页面
                // 确保 #bili-download-images-button 的 CSS 是启用的
                //console.log("[B站图片下载] 未检测到侧边栏,启用悬浮下载按钮。");
            }
            // --- END: 判断使用哪种按钮 ---
        }
        // else { console.log('[B站图片下载] 当前页面不适用脚本。'); }

    })(); // 立即执行初始化

})();

QingJ © 2025

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