// ==UserScript==
// @name Bilibili专栏原图链接提取2024改版
// @namespace https://github.com/shangxueink
// @version 3.1
// @description PC端B站专栏图片默认是经压缩过的webp。此脚本帮助用户点击按钮后获取哔哩哔哩专栏中所有原图的直链,方便使用其他工具批量下载原图。
// @author shangxueink
// @license GPLv3
// @match https://www.bilibili.com/read/cv*
// @match https://www.bilibili.com/opus/*
// @match https://t.bilibili.com/*
// @match https://space.bilibili.com/*/dynamic
// @icon https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @grant none
// @acknowledgement 原始脚本由Hui-Shao开发,本脚本在其基础上进行了修改和增强。(https://greasyfork.org/zh-CN/scripts/456497-bilibili%E4%B8%93%E6%A0%8F%E5%8E%9F%E5%9B%BE%E9%93%BE%E6%8E%A5%E6%8F%90%E5%8F%96 -> https://greasyfork.org/zh-CN/scripts/521666-bilibili%E4%B8%93%E6%A0%8F%E5%8E%9F%E5%9B%BE%E9%93%BE%E6%8E%A5%E6%8F%90%E5%8F%962024%E6%94%B9%E7%89%88)
// ==/UserScript==
(function () {
'use strict';
const iconExtractLink = 'https://i0.hdslb.com/bfs/article/7a0cc21280e2ba013d2681cff4dee947312276085.png'; // 提取链接图标
const iconCopyLink = 'https://i0.hdslb.com/bfs/article/cecac694c99629afbe764eb2b2066a46312276085.png'; // 复制链接图标
const iconDownloadEach = 'https://i0.hdslb.com/bfs/article/0896498c861585719a122e0fc6ef5689312276085.png'; // 逐张下载图标
function createButton(targetContainer, showDownloadEach, isHorizontal) {
var buttonsContainer = document.createElement("div");
buttonsContainer.style.display = "flex";
buttonsContainer.style.flexDirection = isHorizontal ? "row" : "column";
buttonsContainer.style.alignItems = "center";
var existingItem = targetContainer.querySelector('.toolbar-item, .side-toolbar__action, .bili-dyn-item__action');
var height = existingItem ? existingItem.clientHeight : 40;
var width = existingItem ? existingItem.clientWidth : 40;
var buttonDownload = document.createElement("button");
buttonDownload.id = "btn001";
buttonDownload.className = "toolbar-item";
buttonDownload.style = setButtonStyle("#C7EDCC", width, height, iconExtractLink);
buttonDownload.onclick = function () {
buttonDownload.disabled = true;
buttonDownload.style.backgroundColor = "#FDE6E0";
buttonDownload.style.backgroundImage = "none"; // Remove icon
buttonDownload.innerHTML = "正在处理,请稍候...";
urlGetAllModes(buttonDownload, true);
};
buttonsContainer.appendChild(buttonDownload);
var buttonCopy = document.createElement("button");
buttonCopy.id = "btn002";
buttonCopy.className = "toolbar-item";
buttonCopy.style = setButtonStyle("#E3EDCD", width, height, iconCopyLink);
buttonCopy.onclick = function () {
buttonCopy.disabled = true;
buttonCopy.style.backgroundColor = "#FDE6E0";
buttonCopy.style.backgroundImage = "none"; // Remove icon
buttonCopy.innerHTML = "正在处理,请稍候...";
urlGetAllModes(buttonCopy, false);
};
buttonsContainer.appendChild(buttonCopy);
if (showDownloadEach) {
var buttonDownloadEach = document.createElement("button");
buttonDownloadEach.id = "btn003";
buttonDownloadEach.className = "toolbar-item";
buttonDownloadEach.style = setButtonStyle("#FFD700", width, height, iconDownloadEach);
buttonDownloadEach.onclick = function () {
buttonDownloadEach.disabled = true;
buttonDownloadEach.style.backgroundColor = "#FDE6E0";
buttonDownloadEach.style.backgroundImage = "none"; // Remove icon
buttonDownloadEach.innerHTML = "正在下载,请稍候...";
urlGetAllModes(buttonDownloadEach, 'each');
};
buttonsContainer.appendChild(buttonDownloadEach);
}
targetContainer.appendChild(buttonsContainer);
}
function setButtonStyle(backgroundColor, width, height, icon) {
return `
border-radius: 6px;
margin: 5px 0;
height: ${height}px;
width: ${width}px;
padding: 0px;
background-color: ${backgroundColor};
color: black;
font-weight: bold;
overflow: hidden;
text-align: center;
box-shadow: inset 0 0 20px rgba(255, 255, 255, 1);
background-image: url(${icon});
background-size: 30px 30px; /* 150% of 20px */
background-repeat: no-repeat;
background-position: center;
`;
}
function urlGetAllModes(button, mode) {
let modes = [1, 2, 3, 4];
let url_list = [];
let mode_found = false;
for (let m of modes) {
let { selector, attribute } = getSelectorAndAttributeByMode(m);
let img_list = document.querySelectorAll(selector);
if (img_list.length > 0) {
mode_found = true;
img_list.forEach(item => {
let text = item.getAttribute(attribute);
if (text && (text.includes('.jpg') || text.includes('.png') || text.includes('.webp') || text.includes('.jpeg') || text.includes('.gif') || text.includes('.bmp'))) {
if (text.startsWith('//')) {
text = 'https:' + text;
}
text = text.split('@')[0];
if (!text.includes('/face') && !text.includes('/garb')) {
url_list.push(text);
}
}
});
}
}
// 去重处理
url_list = Array.from(new Set(url_list));
if (!mode_found) {
alert("在正文中似乎并没有获取到图片……");
button.textContent = "无图片:点击无效,请刷新重试";
} else {
let url_str = url_list.join("\n");
if (mode === true) {
download_txt("bili_img_urls", url_str);
button.innerHTML = `已提取${url_list.length}张`;
} else if (mode === false) {
copyToClipboard(url_str);
button.innerHTML = `已复制${url_list.length}张`;
} else if (mode === 'each') {
downloadEachImage(url_list);
button.innerHTML = `正在下载`;
}
}
}
function getSelectorAndAttributeByMode(mode) {
switch (mode) {
case 1:
return { selector: "#article-content img[data-src].normal-img", attribute: "data-src" };
case 2:
return { selector: "#article-content p.normal-img img", attribute: "src" };
case 3:
return { selector: "div.opus-module-content img", attribute: "src" };
case 4:
return { selector: ".dyn-card-opus__pics img", attribute: "src" };
default:
alert("传入模式参数错误!");
return { selector: "", attribute: "" };
}
}
function download_txt(filename, text) {
let pom = document.createElement('a');
pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
pom.setAttribute('download', filename);
pom.click();
}
function downloadEachImage(urls) {
urls.forEach((url, index) => {
setTimeout(() => {
let link = document.createElement('a');
fetch(url)
.then(response => response.blob())
.then(blob => {
let blobUrl = URL.createObjectURL(blob);
link.href = blobUrl;
link.download = url.split('/').pop();
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(blobUrl);
})
.catch(e => console.error('Download failed:', e));
}, index * 100); // 逐张下载,每张间隔0.1秒
});
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
console.log('Text copied to clipboard');
}).catch(err => {
console.error('Failed to copy text: ', err);
});
}
function handleDynamicItem(item) {
const footer = item.querySelector('.bili-dyn-item__footer');
const action = footer ? footer.querySelector('.bili-dyn-item__action') : null;
if (footer && action && !footer.querySelector('button')) {
const buttonWidth = action.clientWidth;
const buttonHeight = action.clientHeight;
createButton(footer, false, true);
const buttonDownload = footer.querySelector("#btn001");
const buttonCopy = footer.querySelector("#btn002");
buttonDownload.onclick = () => {
buttonDownload.disabled = true;
buttonDownload.style.backgroundColor = "#FDE6E0";
buttonDownload.style.backgroundImage = "none";
extractDynamicImages(item, true, buttonDownload);
};
buttonCopy.onclick = () => {
buttonCopy.disabled = true;
buttonCopy.style.backgroundColor = "#FDE6E0";
buttonCopy.style.backgroundImage = "none";
extractDynamicImages(item, false, buttonCopy);
};
}
}
function extractDynamicImages(item, download, button) {
const imgList = item.querySelectorAll('.bili-album__preview__picture__img img, img');
const urlSet = new Set();
imgList.forEach(img => {
let src = img.getAttribute('src');
if (src.startsWith('//')) {
src = 'https:' + src;
}
const baseUrl = src.split('@')[0];
if (!baseUrl.includes('/face') && !baseUrl.includes('/garb')) {
urlSet.add(baseUrl);
}
});
const urlStr = Array.from(urlSet).join("\n");
if (download) {
download_txt("bili_dyn_img_urls", urlStr);
//button.innerHTML = `${urlSet.size}张`; // 会多一张
button.innerHTML = `已整理`;
} else {
copyToClipboard(urlStr);
//button.innerHTML = `${urlSet.size}张`; // 会多一张
button.innerHTML = `已复制`;
}
}
function observeDocument() {
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1 && node.matches('.bili-dyn-list__item')) {
handleDynamicItem(node);
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
}
if (window.location.href.includes("space.bilibili.com") && window.location.href.includes("dynamic")) {
observeDocument();
} else if (window.location.href.includes("read/cv")) {
var observer = new MutationObserver(function (mutations, me) {
var toolbar = document.querySelector(".side-toolbar");
if (toolbar) {
createButton(toolbar, false, false); // 不显示第三个按钮
me.disconnect();
return;
}
});
observer.observe(document.body, { childList: true, subtree: true });
} else if (window.location.href.includes("opus/")) {
var observerOpus = new MutationObserver(function (mutations, me) {
var toolbarOpus = document.querySelector(".side-toolbar__box");
if (toolbarOpus) {
var showDownloadEach = window.location.href.includes("opus/");
createButton(toolbarOpus, showDownloadEach, false); // 根据 URL 决定是否显示第三个按钮
me.disconnect();
return;
}
});
observerOpus.observe(document.body, { childList: true, subtree: true });
} else if (window.location.href.includes("t.bilibili.com")) {
var observerT = new MutationObserver(function (mutations, me) {
var toolbarT = document.querySelector(".side-toolbar__box");
if (toolbarT) {
createButton(toolbarT, true, false);
me.disconnect();
return;
}
});
observerT.observe(document.body, { childList: true, subtree: true });
}
})();