Bilibili动态预览图片下载

在B站个人空间的投稿 - 图文界面,提供右键直接下载动态中的图片

当前为 2025-01-26 提交的版本,查看 最新版本

// ==UserScript==
// @name         Bilibili动态预览图片下载
// @namespace    http://tampermonkey.net/
// @version      0.8
// @description  在B站个人空间的投稿 - 图文界面,提供右键直接下载动态中的图片
// @author       You
// @match        https://space.bilibili.com/*/upload/opus
// @grant        none
// @require      https://update.gf.qytechs.cn/scripts/473358/1237031/JSZip.js
// ==/UserScript==

(function() {
    'use strict';

    const fetchJsonData = async (dynamicId) => {
        const apiUrl = `https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/get_dynamic_detail?dynamic_id=${dynamicId}`;
        try {
            const response = await fetch(apiUrl);
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            const jsonData = await response.json();
            const cardData = JSON.parse(jsonData.data.card.card);
            const pictures = cardData.item.pictures?.map(p => p.img_src.replace(/^http:/, 'https:')) || [];
            const uname = jsonData.data.card.desc.user_profile.info.uname;
            const uid = jsonData.data.card.desc.user_profile.info.uid;
            const fileName = `${uname} - ${uid} - ${dynamicId}`;

            console.log('提取的图片链接:', pictures);
            downloadFiles(pictures, fileName);
        } catch (error) {
            console.error('请求或解析失败:', error);
        }
    };

    const downloadFiles = async (urls, fileName) => {
        if (urls.length > 1) {
            await createZipAndDownload(urls, fileName);
        } else {
            const promises = urls.map((url, index) => downloadFile(url, index, fileName));
            await Promise.all(promises);
        }
    };

    const createZipAndDownload = async (urls, fileName) => {
        const zip = new JSZip();
        const promises = urls.map((url, index) => {
            return fetch(url)
                .then(response => {
                    if (!response.ok) {
                        throw new Error(`Failed to fetch ${url}`);
                    }
                    return response.blob();
                })
                .then(blob => {
                    // Get the file extension dynamically using the lambda
                    const extensionMatch = getFileExtensionFromUrl(url);
                    const extension = extensionMatch[1]; // No error handling, must have an extension
                    const fileNameWithIndex = `${fileName} - ${index + 1}.${extension}`;
                    zip.file(fileNameWithIndex, blob);
                })
                .catch(error => {
                    console.error('下载文件失败:', error);
                });
        });

        await Promise.all(promises);

        // Generate and download the ZIP file
        zip.generateAsync({ type: 'blob' })
            .then(content => {
                const link = document.createElement('a');
                link.href = URL.createObjectURL(content);
                link.download = `${fileName}.zip`;
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
            })
            .catch(error => {
                console.error('ZIP生成失败:', error);
            });
    };
    const getFileExtensionFromUrl = url => url.match(/\.([a-zA-Z0-9]+)$/);
    const downloadFile = async (url, index, fileName) => {
        try {
            const response = await fetch(url);
            if (!response.ok) {
                throw new Error(`Failed to fetch ${url}`);
            }
            const blob = await response.blob();
            const extensionMatch = getFileExtensionFromUrl(url);
            const extension = extensionMatch[1]; // No error handling, must have an extension
            const fileDownloadName = `${fileName} - ${index + 1}.${extension}`;
            const link = document.createElement('a');
            link.href = URL.createObjectURL(blob);
            link.download = fileDownloadName;
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        } catch (error) {
            console.error('下载文件失败:', error);
        }
    };

    // Lambda function to get file extension

    const handleEvent = (event, targetElement) => {
        event.preventDefault();
        event.stopPropagation(); // 阻止事件冒泡
        event.stopImmediatePropagation(); // 更强力阻止

        if (event.type === 'contextmenu') { // 右键事件
            const match = targetElement.href.match(/\/(\d+)\??/);
            if (match && match[1]) {
                const dynamicId = match[1];
                fetchJsonData(dynamicId);
            } else {
                console.warn('未匹配到动态ID:', targetElement.href);
            }
        }
    };

    const observer = new MutationObserver(() => {
        const targetElements = document.querySelectorAll('div.opus-body > div > div > div > div > div > div a');
        targetElements.forEach(targetElement => {
            if (!targetElement.hasAttribute('data-listener')) {
                targetElement.addEventListener('contextmenu', (event) => handleEvent(event, targetElement), true); // 捕获阶段绑定
                targetElement.setAttribute('data-listener', 'true'); // 防止重复绑定
            }
        });
    });

    // 配置 MutationObserver
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    // 初次匹配页面上已经存在的元素
    const initialTargetElements = document.querySelectorAll('div.opus-body > div > div > div > div > div > div a');
    initialTargetElements.forEach(targetElement => {
        targetElement.addEventListener('contextmenu', (event) => handleEvent(event, targetElement), true); // 捕获阶段绑定
    });
})();

QingJ © 2025

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