Pixiv 小說下載/小說系列打包下載

Pixiv 下載小說/小說系列

// ==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));
})();