// ==UserScript== // @name Pixiv 小说下载/小说系列打包下载 // @namespace https://pixiv.net/ // @version 0.1 // @author huyaoi // @description 下载小说和小说系列 // @match https://www.pixiv.net/novel/show.php?id=* // @match https://www.pixiv.net/novel/series/* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant GM_registerMenuCommand // @license MIT // @require https://cdn.jsdelivr.net/npm/jszip@2.6.0/dist/jszip.min.js // @downloadURL none // ==/UserScript== (function() { 'use strict'; const apiEndpoint = "https://www.pixiv.net/ajax"; 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 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("当前页面不是小说页面!"); 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("当前页面不是小说系列页面!"); 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("下载小说", () => DownloadNovel()); GM_registerMenuCommand("下载小说系列", () => DownloadSeries()); })();