// ==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.6 // @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/jszip@2.6.0/dist/jszip.min.js // @require https://scriptcat.org/lib/513/2.0.0/ElementGetter.js#sha256=KbLWud5OMbbXZHRoU/GLVgvIgeosObRYkDEbE/YanRU= // @downloadURL none // ==/UserScript== (function() { 'use strict'; const apiEndpoint = "https://www.pixiv.net/ajax"; const translations = { en: { dlSeries:"Download this novel series", dlSeriesNotID:"The current page is not a novel series page!", dlNovel:"Download this novel", dlNovelNotID:"The current page is not a novel page!" }, zh: { dlSeries:"打包下载这个小说系列", dlSeriesNotID:"当前页面不是小说系列页面!", dlNovel:"下载此小说", dlNovelNotID:"当前页面不是小说页面!" } }; function translate(key) { return translations[navigator.language][key] || translations['en'][key] || 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(); }); 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(); }); 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(); }); 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(); }); 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(){ if(GetQueryValue("id") == null){ alert(translate('dlNovelNotID')); return; } let novelID = GetQueryValue("id"); const data = await GetNovel(novelID); if(data != null){ let Content = CreateHeader(data); DownloadFile(Content,`${data.title}_${data.id}.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(){ 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){ data.page.seriesContents.forEach(async(item) => { let novel = await GetNovel(item.id); if(novel != null){ let Content = CreateHeader(novel); 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()); GM_registerMenuCommand(translate('dlSeries'), () => DownloadSeries()); })();