// ==UserScript==
// @name Pixiv 小说下载/小说系列打包下载
// @name:en Pixiv Novel download/Novel series batch download
// @name:zh-cn Pixiv 小说下载/小说系列打包下载
// @name:zh-tw Pixiv 小說下載/小說系列打包下載
// @namespace https://pixiv.net/
// @version 0.91
// @author huyaoi
// @description Pixiv 下载小说/小说系列打包下载
// @description:en Pixiv Novel download/Novel series download
// @description:zh-cn Pixiv 下载小说/小说系列
// @description:zh-tw Pixiv 下載小說/小說系列
// @match https://www.pixiv.net/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant GM_registerMenuCommand
// @run-at document-end
// @license MIT
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jszip.min.js
// @require https://scriptcat.org/lib/513/2.0.0/ElementGetter.js#sha256=KbLWud5OMbbXZHRoU/GLVgvIgeosObRYkDEbE/YanRU=
// ==/UserScript==
(function() {
'use strict';
const apiEndpoint = "https://www.pixiv.net/ajax";
const translations = {
en: {
dlSeries:"Download this novel series",
dlSeriesWithInfo:"Download this novel series (With information)",
dlSeriesNotID:"The current page is not a novel series page!",
dlNovel:"Download this novel",
dlNovelWithInfo:"Download this novel (With information)",
dlNovelNotID:"The current page is not a novel page!"
},
zh: {
dlSeries:"打包下载这个小说系列 (仅内容)",
dlSeriesWithInfo:"打包下载这个小说系列 (带信息)",
dlSeriesNotID:"当前页面不是小说系列页面!",
dlNovel:"下载此小说 (仅内容)",
dlNovelWithInfo:"下载此小说 (带信息)",
dlNovelNotID:"当前页面不是小说页面!"
}
};
function translate(key) {
if(translations[navigator.language] != null){
return translations[navigator.language][key] || translations['en'][key] || key;
}else{
return key;
}
}
const style = document.createElement('style');
style.innerHTML = `
.btn-style {
color: var(--charcoal-text5-hover);
background-color: var(--charcoal-brand-hover);
font-size: 14px;
line-height: 1;
font-weight: bold;
border-radius: 20px;
-moz-box-pack: center;
justify-content: center;
cursor: pointer;
user-select: none;
border-style: none;
margin-left: 8px;
padding: 0 24px;
}
.btn-style-novel {
color: var(--charcoal-text5-hover);
background-color: var(--charcoal-brand-hover);
font-size: 14px;
line-height: 30px;
font-weight: bold;
border-radius: 20px;
-moz-box-pack: center;
cursor: pointer;
user-select: none;
border-style: none;
margin-left: 8px;
padding: 0 24px;
display: flex;
}
`;
document.head.appendChild(style);
window.onhashchange=function(event){
if(GetURLQueryValue("id",event.newURL) == null && event.newURL.split('/series/').length != 2){
return;
}
elmGetter.each('section>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)', document, ele => {
let element = ele.lastChild;
let btn = document.createElement('button');
btn.setAttribute('class', 'btn-style');
btn.addEventListener('mouseup',function(){
DownloadSeries(false);
});
btn.innerText = translate('dlSeries');
element.appendChild(btn);
});
elmGetter.each('section>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)', document, ele => {
let element = ele.lastChild;
let btn = document.createElement('button');
btn.setAttribute('class', 'btn-style-novel');
btn.addEventListener('mouseup',function(){
DownloadNovel(false);
});
btn.innerText = translate('dlNovel');
element.appendChild(btn);
});
}
if(GetQueryValue("id") != null || window.location.href.split('/series/').length == 2){
elmGetter.each('section>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)', document, ele => {
let element = ele.lastChild;
let btn = document.createElement('button');
btn.setAttribute('class', 'btn-style');
btn.addEventListener('mouseup',function(){
DownloadSeries(false);
});
btn.innerText = translate('dlSeries');
element.appendChild(btn);
});
elmGetter.each('section>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)', document, ele => {
let element = ele.lastChild;
let btn = document.createElement('button');
btn.setAttribute('class', 'btn-style-novel');
btn.addEventListener('mouseup',function(){
DownloadNovel(false);
});
btn.innerText = translate('dlNovel');
element.appendChild(btn);
});
}
async function fetchJson(url) {
return await fetch(url).then(result => result.json());
}
function GetQueryValue(queryName) {
let query = decodeURI(window.location.search.substring(1));
let vars = query.split("&");
for (let i = 0; i < vars.length; i++) {
let pair = vars[i].split("=");
if (pair[0] === queryName) { return pair[1]; }
}
return null;
}
function GetURLQueryValue(queryName,url) {
if(url.lastIndexOf('?') == -1){
return;
}
let query = decodeURI(url.substring(url.lastIndexOf('?') + 1,url.length));
let vars = query.split("&");
for (let i = 0; i < vars.length; i++) {
let pair = vars[i].split("=");
if (pair[0] === queryName) { return pair[1]; }
}
return null;
}
function CreateHeader(data){
//替换掉","和"/"
let tags = data.tags.tags.map(tag => tag.tag).join(", ");
tags = tags.replaceAll(",",", ");
tags = tags.replaceAll("/",", ");
return `id: ${data.id}
user: ${data.userName} [${data.userId}]
title: ${data.title}
lang: ${data.language}
tags: ${tags}
count: ${data.characterCount}
description: ${data.description}
create: ${data.createDate}
update: ${data.uploadDate}
content:
${data.content}
`
}
function DownloadFile(content,filename){
const blob = new Blob([content]);
const t = document.createElement('a');
const href = URL.createObjectURL(blob);
t.setAttribute('href', href);
t.setAttribute('download', filename);
t.click();
window.URL.revokeObjectURL(href);
}
async function DownloadNovel(withInfo){
if(GetQueryValue("id") == null){
alert(translate('dlNovelNotID'));
return;
}
let novelID = GetQueryValue("id");
const data = await GetNovel(novelID);
if(data != null){
let Content = "";
if(withInfo){
Content = CreateHeader(data);
}else{
Content = data.content;
}
DownloadFile(Content,`${data.id}_${data.title}.txt`);
}
}
async function GetNovel(novelID){
let url = apiEndpoint + `/novel/${novelID}`;
return await fetchJson(url).then(data => {
return data.body;
})
.catch(err => {
console.log("获取失败");
console.log(err);
return null;
});
}
async function GetSeriesContent(id,last){
let contentUrl = apiEndpoint + `/novel/series_content/${id}?limit=30&last_order=${last}&order_by=asc`;
return await fetchJson(contentUrl).then(data => {
return data.body;
})
.catch(err => {
console.log("获取失败");
console.log(err);
return null;
});
}
async function DownloadSeries(withInfo){
let tmp = window.location.href.split('/series/');
if(tmp.length != 2){
alert(translate('dlSeriesNotID'));
return;
}
let seriesID = tmp[1];
let novelInfoUrl = apiEndpoint + `/novel/series/${seriesID}`;
let displaySeriesContentCount = 0;
let title = "";
await fetchJson(novelInfoUrl).then(data => {
displaySeriesContentCount = data.body.displaySeriesContentCount || 0;
title = data.body.title;
})
.catch(err => {
console.log("获取失败");
console.log(err);
return;
});
console.log(displaySeriesContentCount);
if(displaySeriesContentCount == 0){
return;
}
let zip = new JSZip();
let index = 0;
let maxPage = Math.ceil(displaySeriesContentCount/30);
for(let o = 0;o < maxPage;o++){
let data = await GetSeriesContent(seriesID,o * 30);
if(data!=null){
for(let i = 0;i <data.page.seriesContents.length;i ++){
let novel = await GetNovel(data.page.seriesContents[i].id);
if(novel != null){
let Content = "";
if(withInfo){
Content = CreateHeader(novel);
}else{
Content = novel.content;
}
await zip.file(`${seriesID}_${index}_${novel.id}_${novel.title}.txt`, Content);
index++;
if(index >= displaySeriesContentCount){
console.log("Start");
DownloadFile(zip.generate({type:"blob"}), `${seriesID}_${title}.zip`);
}
}
}
}
}
}
GM_registerMenuCommand(translate('dlNovel'), () => DownloadNovel(false));
GM_registerMenuCommand(translate('dlSeries'), () => DownloadSeries(false));
GM_registerMenuCommand(translate('dlNovelWithInfo'), () => DownloadNovel(true));
GM_registerMenuCommand(translate('dlSeriesWithInfo'), () => DownloadSeries(true));
})();