Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
// ==UserScript==
// @name Pixiv Fanbox Downloader
// @version 0.9
// @description Download Pixiv Fanbox images and files.
// @icon https://www.fanbox.cc/favicon.ico
// @author Mike Lei
// @match https://*.fanbox.cc/*
// @require https://code.jquery.com/jquery-3.5.1.slim.min.js#sha256=4+XzXVhsDmqanXGHaHvgh1gMQKX40OUvDEBTu8JcmNs=
// @grant GM_download
// ==/UserScript==
"use strict";
let single_filename = ``;
let multi_filename = ``;
let style = document.createElement("style");
style.type = "text/css";
style.innerText = `
#fanbox-dl-btn {
background-color: #eee;
border-radius: 20px;
border: none;
color: #333;
cursor: pointer;
float: right;
font-size: 14px;
font-weight: bold;
min-width: 120px;
overflow: hidden;
padding: 9px;
position: relative;
transition: background-color 0.2s ease 0s;
}
#fanbox-dl-btn.finished {
color: #fff;
}
#fanbox-dl-btn:hover {
background-color: #e0e0e0;
}
#fanbox-dl-btn > span {
position: relative;
z-index: 1;
}
#fanbox-dl-btn > div.btn-progress {
position: absolute;
background-color: #0096fa;
top: 0;
bottom: 0;
left: 0;
transition: width .3s linear;
}`;
document.head.appendChild(style);
const addDownloadButton = () => {
let downloadDir = ""; // Download to a specific directory in the default download directory. Only works in Chrome.
let btn = document.createElement("button");
let text = document.createElement("span");
let progress = document.createElement("div");
btn.id = "fanbox-dl-btn";
text.innerText = "下载文件";
progress.className = "btn-progress";
btn.appendChild(text);
btn.appendChild(progress);
let article = document.getElementsByTagName("article")[0];
article.querySelector("h1").appendChild(btn);
const download = (event) => {
progress.style.width = 0;
btn.removeAttribute("class");
let author_name = document.querySelectorAll("h1 a")[1].innerText;
let uid = document.querySelector("div[src]").getAttribute("src").match(/(?<=creator\/)\d+/)[0];
let dir = `${author_name} (${uid})/`; // Download files that have the same author to a single directory. Only works in Chrome.
let author = document.querySelector("link[rel='canonical']").href.split(/\/\/|\./)[1];
let basename = `fanbox ${author} ${location.href.split("/").pop()}`;
let files = Array.from(article.querySelectorAll("a img, a[download]")).map(node => {
if (node.nodeName == "IMG") {
return {
name: "",
url: node.parentNode.parentNode.href
};
} else if (node.nodeName == "A") {
return {
name: `_${node.download}`,
url: node.href
};
}
});
if (files.length == 0) {
if (article.querySelectorAll("a[href$='/plans']").length == 1) {
text.innerText = "需要赞助";
navigator.clipboard.writeText(basename);
} else {
text.innerText = "无文件";
}
} else {
text.innerText = "下载中";
if (files.length > 1) {
files.map((f, index) => {
f.name = `${downloadDir}${dir}${basename}_${String(index + 1).padStart(2, "0")}${f.name}.${f.url.split(".").pop()}`;
return f;
});
} else if (files.length == 1) {
files[0].name = `${downloadDir}${dir}${basename}${files[0].name}.${files[0].url.split(".").pop()}`;
}
for (let i = 0; i < files.length; i++) {
console.log(`${files[i].url} => ${files[i].name}`);
if ((i + 1) == files.length) {
files[i].onload = () => {
progress.style.width = "100%";
btn.className = "finished";
text.innerText = "完成";
}
} else {
files[i].onload = () => {
progress.style.width = Number.parseFloat(progress.style.width) + ((i + 1) / files.length * 100) + "%";
btn.className = "finished";
}
}
GM_download(files[i]);
}
}
}
btn.addEventListener('click', download);
document.onkeydown = (event) => {
// console.log(event.target);
if (event.target.tagName == "INPUT" || event.target.tagName == "TEXTAREA" || event.repeat) {
return;
} else if (document.querySelector("#root > div:last-child").style.overflow != "") {
if (event.key == "v") {
document.querySelector("#root > div > button").click();
}
return;
}
let posts = Array.from(article.parentElement.querySelector("article ~ div:last-of-type").querySelectorAll("div > div")).map(p => p.children[0]);
switch (event.key) {
case " ":
event.preventDefault();
article.scrollIntoView({behavior: "smooth", block: "nearest", inline: "nearest"});
break;
case "d":
event.preventDefault();
download();
break;
case "a":
event.preventDefault();
download();
case "f":
event.preventDefault();
article.parentElement.querySelector("article ~ div[class] button").click();
break;
case "v":
event.preventDefault();
article.querySelector("a img").click();
break;
case "ArrowLeft":
event.preventDefault();
if (posts[0]) {
posts[0].click();
}
break;
case "ArrowRight":
event.preventDefault();
if (posts[1]) {
posts[1].click();
}
break;
}
}
}
waitForKeyElements("article h1", addDownloadButton);
function waitForKeyElements(e,t,a,n){var o,r;(o=void 0===n?$(e):$(n).contents().find(e))&&o.length>0?(r=!0,o.each(function(){var e=$(this);e.data("alreadyFound")||!1||(t(e)?r=!1:e.data("alreadyFound",!0))})):r=!1;var l=waitForKeyElements.controlObj||{},i=e.replace(/[^\w]/g,"_"),c=l[i];r&&a&&c?(clearInterval(c),delete l[i]):c||(c=setInterval(function(){waitForKeyElements(e,t,a,n)},300),l[i]=c),waitForKeyElements.controlObj=l}
// function waitForKeyElements(
// selectorTxt, // Required: The jQuery selector string that specifies the desired element(s).
// actionFunction, // Required: The code to run when elements are found. It is passed a jNode to the matched element.
// bWaitOnce, // Optional: If false, will continue to scan for new elements even after the first match is found.
// iframeSelector // Optional: If set, identifies the iframe to search.
// )
#fanbox-dl-btn {
background-color: #eee;
color: #333;
border-radius: 18px;
border: none;
cursor: pointer;
float: right;
font-size: 14px;
font-weight: bold;
min-width: 112px;
height: 36px;
line-height: 36px;
padding: 0 18px;
overflow: hidden;
white-space: nowrap;
position: relative;
transition: background-color 0.2s ease 0s;
user-select: none;
}
#fanbox-dl-btn:hover {
background-color: #e0e0e0;
}
#fanbox-dl-btn > .btn-progress, #fanbox-dl-btn > .btn-progress::before {
position: absolute;
top: 0;
left: 0;
box-sizing: border-box;
height: 36px;
}
#fanbox-dl-btn > .btn-progress {
overflow: hidden;
white-space: nowrap;
transition: width .3s linear;
}
#fanbox-dl-btn > .btn-progress::before {
min-width: 112px;
padding: 0 18px;
background-color: #0096fa;
color: #fff;
content: attr(data-status);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment