// ==UserScript==
// @name Fanbox图片下载器
// @name:en Fanbox Downloader
// @namespace http://tampermonkey.net/
// @namespace https://github.com/709924470/pixiv_fanbox_downloader
// @version 1.0.0
// @description Download Pixiv Fanbox Images.
// @description:en Download Pixiv Fanbox Images.
// @author [email protected]
// @include /^https?:\/\/(.+?\.)?fanbox\.cc\/(@.+\/)?posts\/\d+/
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.2.0/jszip.min.js
// ==/UserScript==
(function() {
'use strict';
var dlList = [];
var guardObserver = new MutationObserver(guardingObserver);
var guardState = true;
var scriptState = false;
var observer = new MutationObserver(rootObserver);
function guardingObserver(mu){
mu.forEach((m) => {
try{
observer.observe(document.getElementById("root"), { childList: true });
}catch(err){}
});
};
guardObserver.observe(document.body, {childList:true,subtree:true});
setTimeout(() => mainFunc(null), 5000);
window.forceAddbutton = mainFunc;
var observeFlag = false;
var lastLoc = window.location.href;
var enableZip = true, enableSingle = true, nameformat = "$title-", auto = false;
var count = 0, downloaded = 0;
var zip;
var timeoutBackup;
var addFile = (name, content) => zip.file(name, content);
var generateName = (name, url) => name + ( "_" + count++ ) + "." + url.split(".")[url.split(".").length - 1];
function rootObserver(mutations) {
guardState = false;
mutations.forEach(function(mutation) {
for (var i = 0; i < mutation.addedNodes.length; i++){
if (window.location.href !== lastLoc){
console.log("[Fanbox Downloader.js] Page refresh detected.");
lastLoc = window.location.href;
if (lastLoc.match(/https?:\/\/(www\.)?fanbox\.cc\/\@.+?\/posts\/\d+/) === null){
console.log("[Fanbox Downloader.js] Not post page.");
return;
}
observeFlag = false;
timeoutBackup = setInterval(function(){
if(!observeFlag){
[...document.querySelectorAll("button")].forEach(
function(e){
if(e.innerHTML.includes("svg")){
observeFlag = mainFunc(e);
if(observeFlag){
console.log("[Fanbox Downloader.js] Backup function working...");
clearInterval(timeoutBackup);
}
}
}
);
}else{
clearInterval(timeoutBackup);
}
}, 1000);
}
if(!observeFlag){
observeFlag = mainFunc(null);
observer.observe(mutation.addedNodes[i],
{
childList: true,
characterData: true,
subtree: true
});
}else{
break;
}
}
});
}
function checkIsSub(){
return document.evaluate("//article//a[contains(@href, 'plans')]", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotLength == 0;
}
function mainFunc(btn){
initSettings();
if (scriptState){
return;
}
zip = new JSZip();
count = 0;
var button = null;
for (var c = 0; c < 10 && button === null; c++){
button = document.evaluate('//*[@id="root"]/div[5]/div[1]/div/div[3]/div/div/div[1]/div/div[' + c + ']/div/button', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
button = button.singleNodeValue;
}
if(!checkIsSub()){
console.error("[Fanbox Downloader.js] Cannot add download button! REASON=\"NOT_IN_FAN_PLAN\"");
return false;
}
if ((button === null && btn === null)){
console.error("[Fanbox Downloader.js] Cannot add download button! Attempting to use backup function.");
var svgs = document.getElementsByTagName("svg");
[...svgs].forEach((item) => {
var parentNode = item.parentNode;
while (parentNode.tagName.toLowerCase() != "button"){
if(parentNode.tagName.toLowerCase() == "body"){
return;
}
parentNode = parentNode.parentNode;
}
button = parentNode;
});
if(button === null){
console.error("[Fanbox Downloader.js] Cannot add download button!");
}
}else if(button !== null || btn !== null){
button = button ? button : btn;
}
if(getAllImageUrl().length == 0){
console.warn("[Fanbox Downloader.js] No image found, not adding buttons.");
return false;
}
scriptState = true;
if(auto){
if(enableZip){
downloadImages_ZIP(...getAllImageUrl());
}else{
downloadImages(...getAllImageUrl());
}
}
console.log("[Fanbox Downloader.js] Successfully added the button.");
var p = document.createElement("p");
var newButton = document.createElement("button");
button.classList.forEach(function(item){
newButton.classList.add(item);
});
newButton.id = "dl_images";
newButton.innerText = "下载图片\nDirect download";
newButton.onclick = function(){
downloadImages(...getAllImageUrl());
};
p.appendChild(newButton);
p.appendChild(document.createElement("br"));
var zipButton = document.createElement("button");
button.classList.forEach(function(item){
zipButton.classList.add(item);
});
zipButton.id = "dl_zip";
zipButton.innerText = "打包下载\nDownload as Zip";
zipButton.onclick = function(){
var content = document.evaluate('//*[@id="root"]/div[5]/div[1]/div/div[3]/div/div[1]/div/article', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
content = content?.singleNodeValue || document.createElement("article");
addFile("description.txt", content?.innerText || "");
downloadImages_ZIP(...getAllImageUrl());
};
p.appendChild(zipButton);
p.oncontextmenu = function(e){
createSettingsPopup();
e.preventDefault();
};
button.parentNode.appendChild(p);
return true;
}
function downloadImages(...urls){
if(!checkIsSub()){
alert("t5RIf eB1rsCBus Ot D3En UOy");
return "Why are you even thinking about download files for free???";
}
var name = formatName();
urls.forEach(function(url){
forceDownload(url,generateName(name, url),false);
});
return undefined;
}
function downloadImages_ZIP(...urls){
if(!checkIsSub()){
alert("t5RIf eB1rsCBus Ot D3En UOy");
return "Why are you even thinking about download these files free???";
}
var i = 0, name = formatName();
urls.forEach(function(url){
if(url === undefined){
console.warn("undefined url! > [" + i + "]" , urls);
i++;
return;
}
forceDownload(url,generateName(name, url),true);
i++;
});
return undefined;
}
function formatName(){
var scripts = document.getElementsByTagName("SCRIPT");
var data = undefined;
[...scripts].forEach((v, i) => {
if(v.type.indexOf("json") != -1){
data = eval(v.innerText)[0];
}
});
var dict = {
"$title": document.title.split("|")[0],
"$author": document.title.split("|")[1],
"$userid": location.href.split("/")[location.href.split("/").length - 3],
"$createdate": data === undefined ? new Date().getTime() : data["datePublished"],
"$editdata": data === undefined ? new Date().getTime() : data["dateModified"],
};
var result = nameformat;
for(var i in dict){
if(nameformat.indexOf(i) != -1){
result = result.replace(i, dict[i]);
}
}
return result.replace("/", "_");
}
function initSettings(){
enableZip = GM_getValue("ZIP", true);
enableSingle = GM_getValue("Single", true);
nameformat = GM_getValue("NameFormat", nameformat);
auto = GM_getValue("Auto", false);
GM_setValue("ZIP", enableZip);
GM_setValue("Single", enableSingle);
GM_setValue("NameFormat", nameformat);
GM_setValue("Auto", auto);
}
function createSettingsPopup(){
if (document.getElementById("settings-style") !== null){
var panel = document.getElementById("settings");
panel.style.display = "block";
return;
}
var style = document.createElement("style");
style.id = "settings-style";
style.innerHTML = `.settings {
display: none;
position: fixed;
z-index: 1;
padding-top: 100px;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.4);
}
.settings-content {
background-color: #fefefe;
margin: auto;
padding: 20px;
border: 1px solid #888;
border-radius: 5px;
width: 60%;
}
.close {
color: #aaaaaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: #000;
text-decoration: none;
cursor: pointer;
}`;
document.body.appendChild(style);
var panel = document.createElement("div");
panel.className = "settings";
panel.id = "settings";
panel.style.display = "block";
window.onclick = (e) => {
if (e.target.id == "settings"){
document.getElementById("settings").style.display = "none";
}
};
var content = document.createElement("div");
content.className = "settings-content";
panel.appendChild(content);
var close = document.createElement("span");
close.className = "close";
close.innerHTML = "×";
close.onclick = (e) => {document.getElementById("settings").style.display = "none";};
content.appendChild(close);
content.innerHTML += `<p><h2>Fanbox downloader settings 设置</h2></p>
<p><input type="checkbox" id="auto" unchecked>
<label for="auto">自动下载 / Auto download</label></p>
<p style="padding-left: 2em;"><input type="radio" id="auto-single" value="single" disabled>
<label for="auto-single">自动单张下载 / Automatically download as single Images</label></p>
<p style="padding-left: 2em;"><input type="radio" id="auto-zip" value="zip" disabled checked>
<label for="auto-zip">自动打包下载 / Automatically download as packed Zip file</label></p>
<p><br><label for="format">命名格式 / File name format</label>
<input type="text" id="format">
<br><br> "$title" = 标题 "$author" = 作者名 "$userid" = 用户ID
<br><br> "$createdate" = 创建日期 "$editdata" = 修改日期</p><p></p>`;
var save = document.createElement("button");
save.innerText = "Save 保存设置";
content.appendChild(save);
document.body.appendChild(panel);
document.getElementById("format").value = nameformat;
save.onclick = (e) => {
auto = document.getElementById("auto").checked;
enableZip = document.getElementById("auto-zip").checked;
enableSingle = document.getElementById("auto-single").checked;
nameformat = document.getElementById("format").value;
GM_setValue("ZIP", enableZip);
GM_setValue("Single", enableSingle);
GM_setValue("NameFormat", nameformat);
GM_setValue("Auto", auto);
alert("设置成功\nSaved.");
document.getElementById("settings").style.display = "none";
};
document.getElementById("auto").onchange = (e) => {
if(e.target.checked){
document.getElementById("auto-single").disabled = false;
document.getElementById("auto-zip").disabled = false;
}else{
document.getElementById("auto-single").disabled = true;
document.getElementById("auto-zip").disabled = true;
}
}
}
function getAllImageUrl(){
var elements = document.querySelectorAll("a[rel] > div > img");
var result = [];
for(var i = 0; i < elements.length; i++){
result.push(elements[i].parentNode.parentNode.getAttribute("href"));
}
return result;
}
function forceDownload(url, fileName,zipFlag){
if(dlList.includes(fileName)){
return;
}
dlList.push(fileName);
console.log("[Fanbox Downloader.js] Downloading " + fileName);
GM_xmlhttpRequest({
method: "GET",
url: url,
binary: true,
responseType: "blob",
onload: function(response) {
console.log("[Fanbox Downloader.js] Downloaded " + fileName);
var urlCreator = window.URL || window.webkitURL;
var imageUrl = urlCreator.createObjectURL(response.response);
if(!zipFlag){
var tag = document.createElement('a');
tag.href = imageUrl;
tag.download = fileName;
document.body.appendChild(tag);
tag.click();
document.body.removeChild(tag);
return;
}
addFile(fileName,response.response);
downloaded++;
if(dlList.length == downloaded){
zip.generateAsync({type:'blob'}).then(function(blob){
var imageUrl = urlCreator.createObjectURL(blob);
var tag = document.createElement('a');
tag.href = imageUrl;
tag.download = formatName() + ".zip";
document.body.appendChild(tag);
tag.click();
document.body.removeChild(tag);
});
}
},
onprogress: function (e) {
if(e.callengthComputable){
var ratio = Math.floor((e.loaded / e.total) * 100) + '%';
console.log("[Fanbox Downloader.js] " + fileName + " > " + ratio);
return;
}
console.log("[Fanbox Downloader.js] " + fileName + " downloaded " + (e.loaded / 1024).toFixed(3) + "kB (No total length found)");
},
onerror: function(e){
console.error("[Fanbox Downloader.js] Failed downloading file " + fileName);
},
});
}
})();