Bilibili CV Downloader

Easy to download images from BilibiliCV!!

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Bilibili CV Downloader
// @namespace    https://github.com/hv0905/bilibiliCv_downloader
// @version      1.4
// @description  Easy to download images from BilibiliCV!!
// @author       EdgeNeko(Github@hv0905)
// @match        *://www.bilibili.com/read/cv*
// @grant        GM_download
// @grant        GM_info
// @grant        GM_xmlhttpRequest
// @connect      i0.hdslb.com
// ==/UserScript==

//Bilibili CV Downloader
//build with love by EdgeNeko
//Github: @hv0905
//github project: https://github.com/hv0905/bilibiliCv_downloader/

'use strict';

const baseDownload = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNTQ1Mzk5NzI5NzM1IiBjbGFzcz0iaWNvbiIgc3R5bGU9IiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjI5MzciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PGRlZnM+PHN0eWxlIHR5cGU9InRleHQvY3NzIj48L3N0eWxlPjwvZGVmcz48cGF0aCBkPSJNMTYwIDMyYy0xMiAwLTI0LjggNC44LTMzLjYgMTQuNFMxMTIgNjggMTEyIDgwdjg2NGMwIDEyIDQuOCAyNC44IDE0LjQgMzMuNiA5LjYgOS42IDIxLjYgMTQuNCAzMy42IDE0LjRoNzA0YzEyIDAgMjQuOC00LjggMzMuNi0xNC40IDkuNi05LjYgMTQuNC0yMS42IDE0LjQtMzMuNlYzMDRMNjQwIDMySDE2MHoiIGZpbGw9IiM1QUNDOUIiIHAtaWQ9IjI5MzgiPjwvcGF0aD48cGF0aCBkPSJNOTEyIDMwNEg2ODhjLTEyIDAtMjQuOC00LjgtMzMuNi0xNC40LTkuNi04LjgtMTQuNC0yMS42LTE0LjQtMzMuNlYzMmwyNzIgMjcyeiIgZmlsbD0iI0JERUJENyIgcC1pZD0iMjkzOSI+PC9wYXRoPjxwYXRoIGQ9Ik01MDAuOCA2ODQuOGMzLjIgMi40IDYuNCA0LjggMTEuMiA0LjggNCAwIDgtMS42IDExLjItNC44bDE0Mi40LTEzNmMyLjQtMi40IDMuMi01LjYgMS42LTguOC0xLjYtMy4yLTQtNC44LTcuMi00LjhINTc2di0xMzZjMC00LTEuNi04LTQuOC0xMS4yLTMuMi0zLjItNy4yLTQuOC0xMS4yLTQuOEg0NjRjLTQgMC04IDEuNi0xMS4yIDQuOC0zLjIgMy4yLTQuOCA3LjItNC44IDExLjJ2MTM2SDM2NGMtMy4yIDAtNi40IDEuNi03LjIgNC44LTEuNiAzLjIgMCA2LjQgMS42IDguOGwxNDIuNCAxMzZ6TTcxMiA3NTEuMkgzMTJjLTguOCAwLTE2IDcuMi0xNiAxNnM3LjIgMTYgMTYgMTZoNDAwYzguOCAwIDE2LTcuMiAxNi0xNnMtNy4yLTE2LTE2LTE2eiIgZmlsbD0iI0ZGRkZGRiIgcC1pZD0iMjk0MCI+PC9wYXRoPjwvc3ZnPg==';
var lastDownloadTime = 0;
var missionCount = 0;
var completedMissionCount = 0;
var replaceTarget;
var downloaded = false;

function ondownload() {
    'use strict';
    setNotifyText('开始下载');
    let elements = document.getElementsByClassName('img-box');
    for (let i = 0; i < elements.length; i++) {
        let img = elements[i].querySelector('img').dataset.src;
        if (!img) {
            continue;
        }
        let txt = "";
        if (elements[i].querySelector('.caption')) {
            //caption
            txt = elements[i].querySelector('.caption').innerHTML.trim();
        }
        let imgOriginal = img.split('@')[0];
        let fileName;
        if(txt.length == 0){
            let urlSplit = imgOriginal.split('/');
            fileName = urlSplit[urlSplit.length - 1];
        }else{
            let extSplits = imgOriginal.split('.');
            fileName = txt.replace('\\','_').replace('/','_').replace('?','_').replace(':','_').replace('*','_').replace('|','_').replace('<','_').replace('>','_').replace('"','_') + '.' + extSplits[extSplits.length - 1];
        }
        console.log(`[${i}]准备下载:${imgOriginal}  文件名:${fileName}`);
        missionCount++;
        //瞬间完成
        downloadFileBlob('https:' + imgOriginal, fileName, i);
    }
}

function downloadFileBlob(url, fileName, i) {
    'use strict';
    GM_xmlhttpRequest({
        method: 'GET',
        url: url,
        responseType: 'blob',
        timeout: 180000,
        onload: function (xhr) {
            let blobURL = window.URL.createObjectURL(xhr.response); // 返回的blob对象是整个response,而非responseText
            saveBlob(blobURL, fileName);
        },
        onprogress: function (xhr) {
            let loaded = parseInt(xhr.loaded / 1000);
            let total = parseInt(xhr.total / 1000);
            console.log(`[${i}]正在下载:${fileName}  进度:${(loaded / total)}`);
        },
    });


}

function saveBlob(blob, fileName) {
    'use strict';
    if (new Date().getTime() - lastDownloadTime < 200) {
        setTimeout(() => {
            saveBlob(blob, fileName);
        }, 200 - (new Date().getTime() - lastDownloadTime));
        return;
    }
    lastDownloadTime = new Date().getTime();
    console.log("正在保存:" + fileName);
    let a = $('<a>')
        .attr('href', blob)
        .attr('download', fileName)
        .appendTo('body');

    a[0].click();

    a.remove();
    window.URL.revokeObjectURL(blob);
    completedMissionCount++;
    checkIfCompleted();
}

function checkIfCompleted() {
    'use strict';
    if (missionCount <= completedMissionCount) {
        alert('所有下载操作已完成!\n你可以关闭网页了');
        setNotifyText('下载完成!');
    } else {
        setNotifyText(`下载进度:${completedMissionCount}/${missionCount}`);
    }
}

function setNotifyText(text) {
    'use strict';
    replaceTarget.children[1].innerText = text;
}



(function () {
    'use strict';
    onload = function () {
        replaceTarget = this.document.getElementsByClassName('help')[0];
        let replaceA = replaceTarget.parentElement;
        replaceA.href = 'javascript:void(0)';
        replaceA.target = '';
        replaceA.onclick = function () {
            if (downloaded) return;
            if (confirm('确认下载全部图片?\n注意:在弹出下载成功提示前请不要关闭窗口,否则无法保证所有图片均下载完成\n要查看进度,请点击F12并转到Console')) {
                ondownload();
                downloaded = true;
            }
        }
        replaceTarget.children[0].style.backgroundImage = 'url(' + baseDownload + ')';
        replaceTarget.children[1].innerText = '下载所有图片';
        replaceTarget.children[2].innerText = 'by EdgeNeko'
    };
})();