Pixiv Download Helper

Add download button or link for the Pixiv original picture in view page

当前为 2018-01-10 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name           Pixiv Download Helper
// @name:zh        Pixiv 下载助手
// @name:ja        ダウンロードヘルパー
// @description    Add download button or link for the Pixiv original picture in view page
// @description:zh 为 Pixiv 图片阅览页增加下载原图按钮或链接
// @description:ja Pixiv作品ダウンロードヘルパー
// @namespace      https://github.com/Sg4Dylan/PixivDownloadHelper
// @icon           https://www.pixiv.net/favicon.ico
// @include        http://www.pixiv.net/*
// @include        https://www.pixiv.net/*
// @grant          GM_xmlhttpRequest
// @grant          GM_setValue
// @grant          GM_getValue
// @connect        i.pximg.net
// @connect        i1.pixiv.net
// @connect        i2.pixiv.net
// @connect        i3.pixiv.net
// @connect        i4.pixiv.net
// @connect        i5.pixiv.net
// @version        2018.01.10.0
// ==/UserScript==

//Turn thumbnail titles into direct links (single images) or mode=manga links.  Some kinds of thumbnails aren't covered, and an isolated few (like #17099702) don't work.
var directTitles = false;

//Append direct links below images on mode=manga pages
var directManga = true;

//Force pixiv's 'book view' style for manga sequences to something like the normal view.  Clicking a page won't scroll the window to the next page.
var breakBookView = false;

//Replace the medium thumbnail on mode=medium pages with the full size.  The image will be relinked to the full size regardless of this setting.
var fullSizeMedium = true;

//Disable lazy loading images.  These appear on mode=manga pages, rankings, and the "Recommended" section of the bookmarks page.
var dontSayLazy = true;

//Text for Button & link
var mangaModeLang = [["Right click \"Save As\" to download, file name: "], ["名前をつけて保存、ファイル名:"], ["下载请使用右键“链接另存为”保存,文件名:"]];
var normalModeLangZero = [["Direct download", "Right click \"Save As\" to download"], ["直接ダウンロード", "名前をつけて保存"], ["直接下载", "使用右键链接另存为"]];
var saveFileNameFormat = [["<tr> <th> Pixiv Download Helper<br>setting<br> <a name=\"saveSetting\" class=\"btn_type01\">Save setting</a> </th> <td> <dl> <dt>format of saving file name</dt> <dd> <label><input type=\"radio\" name=\"pdh0\" value=\"fmt0\" checked>[author name][illustrate name]</label><br> <label><input type=\"radio\" name=\"pdh0\" value=\"fmt1\">[author name][illustrate name][pixiv id]</label><br> <label><input type=\"radio\" name=\"pdh0\" value=\"fmt2\">[author name][pixiv id]</label><br> <label><input type=\"radio\" name=\"pdh0\" value=\"fmt3\">[illustrate name][pixiv id]</label> </dd> <dt>split mode</dt> <dd> <label><input type=\"radio\" name=\"pdh1\" value=\"spl0\" checked>[Name][Name]</label><br> <label><input type=\"radio\" name=\"pdh1\" value=\"spl1\">【Name】【...】</label><br> <label><input type=\"radio\" name=\"pdh1\" value=\"spl2\">Name - Name</label><br> <label><input type=\"radio\" name=\"pdh1\" value=\"spl3\">Name ~ Name</label> </dd> </dl> </td> </tr>","script setting saved"], ["<tr> <th> Pixivダウンロードヘルパー設定<br> <a name=\"saveSetting\" class=\"btn_type01\">設定を保存する</a> </th> <td> <dl> <dt>ファイル名を保存する形式</dt> <dd> <label><input type=\"radio\" name=\"pdh0\" value=\"fmt0\" checked>[author name][illustrate name]</label><br> <label><input type=\"radio\" name=\"pdh0\" value=\"fmt1\">[author name][illustrate name][pixiv id]</label><br> <label><input type=\"radio\" name=\"pdh0\" value=\"fmt2\">[author name][pixiv id]</label><br> <label><input type=\"radio\" name=\"pdh0\" value=\"fmt3\">[illustrate name][pixiv id]</label> </dd> <dt>スプリットモード</dt> <dd> <label><input type=\"radio\" name=\"pdh1\" value=\"spl0\" checked>[Name][Name]</label><br> <label><input type=\"radio\" name=\"pdh1\" value=\"spl1\">【Name】【...】</label><br> <label><input type=\"radio\" name=\"pdh1\" value=\"spl2\">Name - Name</label><br> <label><input type=\"radio\" name=\"pdh1\" value=\"spl3\">Name ~ Name</label> </dd> </dl> </td> </tr>","スクリプト設定が保存されました"], ["<tr> <th> Pixiv 下载助手设置<br> <a name=\"saveSetting\" class=\"btn_type01\">保存插件设置</a> </th> <td> <dl> <dt>设置保存格式</dt> <dd> <label><input type=\"radio\" name=\"pdh0\" value=\"fmt0\" checked>[author name][illustrate name]</label><br> <label><input type=\"radio\" name=\"pdh0\" value=\"fmt1\">[author name][illustrate name][pixiv id]</label><br> <label><input type=\"radio\" name=\"pdh0\" value=\"fmt2\">[author name][pixiv id]</label><br> <label><input type=\"radio\" name=\"pdh0\" value=\"fmt3\">[illustrate name][pixiv id]</label> </dd> <dt>设置分割方式</dt> <dd> <label><input type=\"radio\" name=\"pdh1\" value=\"spl0\" checked>[Name][Name]</label><br> <label><input type=\"radio\" name=\"pdh1\" value=\"spl1\">【Name】【...】</label><br> <label><input type=\"radio\" name=\"pdh1\" value=\"spl2\">Name - Name</label><br> <label><input type=\"radio\" name=\"pdh1\" value=\"spl3\">Name ~ Name</label> </dd> </dl> </td> </tr>","插件设置保存成功"]];

//----------------------------------------------------------------//

var fullSizeWidth = "740px";

if( typeof(custom) != "undefined" )
    custom();

if (!String.prototype.format) {
  String.prototype.format = function() {
    var args = arguments;
    return this.replace(/{(\d+)}/g, function(match, number) {
      return typeof args[number] != 'undefined' ? args[number] : match;
    });
  };
}

if( location.search.indexOf("mode=manga_big") > 0 || location.search.indexOf("mode=big") > 0 )
{
    //Make the 'big'/'manga_big' image link to itself instead of closing the window
    console.log("Mode=manga_big");
    var image = document.getElementsByTagName("img")[0];
    if( image )
    {
        var link = document.createElement("a");
        link.href = image.src;
        link.appendChild( document.createElement("img") ).src = image.src;
        document.body.innerHTML = "";
        document.body.appendChild( link );
    }
}
else if( location.search.indexOf("mode=manga") > 0 )
{
    console.log("Mode=manga");
    var container = document.getElementsByClassName("full-size-container");
    if( directManga && container.length )
    {
        //Check the mode=manga_big page for the first page, since the sample extension is always "jpg".
        var req = new XMLHttpRequest();
        req.open( "GET", location.href.replace(/page=\d+&?/,'').replace('mode=manga','mode=manga_big&page=0'), true );
        req.onload = function()
        {
            console.log("Pixiv Download Helper Patch ver 0.1 by SgDylan.");
            console.log("Parsing download link...");
            var firstImage = req.responseXML.querySelector("img[src*='_p0.']").src;
            for( var i = 0; i < container.length; i++ )
            {
                console.log("Getting link...");
                var sourcePictureLink = firstImage.replace( "_p0.", "_p"+i+"." );
                var extName = "." + sourcePictureLink.split(".").pop(-1);
                authorName = document.getElementsByClassName("breadcrumbs")[0].children[1].children[0].innerHTML.split(">")[1];
                illustName = document.getElementsByClassName("breadcrumbs")[0].children[2].children[0].children[0].innerHTML;
                FileName = filenameConcater(authorName, illustName, i) + extName;
                console.log("File name: "+FileName);
                console.log("File Link: "+sourcePictureLink);
                console.log("Put download link");
                var link = document.createElement("a");
                link.textContent = multiLang(0, 0)+FileName;
                link.style.display = "block";
                link.href = sourcePictureLink;
                link.download = FileName;
                container[i].parentNode.appendChild( link );
            }
            console.log("All Ready !");
        };
        req.responseType = "document";
        req.send(null);
    }
    else if( breakBookView && document.head.innerHTML.indexOf("pixiv.context.images") > 0 )
    {
        //Book view (e.g. #54139174, #57045668)

        console.log("Mode=bookview");
        var mangaSection = document.createElement("section");
        mangaSection.className = "manga";

        var scripts = document.head.getElementsByTagName("script");
        var hits = 0;
        for( var i = 0; i < scripts.length; i++ )
        {
            var urls = scripts[i].innerHTML.match( /pixiv.context.images[^"]+"([^"]+)".*pixiv.context.originalImages[^"]+"([^"]+)"/ );
            if( urls )
            {
                var full = urls[2].replace( /\\\//g, "/");
                mangaSection.innerHTML += '<div class="item-container"><a href="'+full+'" class="full-size-container"><i class="_icon-20 _icon-full-size"></i></a><img style="width:auto;height:auto;max-width:1200px;max-height:1200px" src="'+full+'" class="image">'+( directManga ? '<a href="'+full+'" style="display:block">direct link</a>' : '' )+'</div>';
                hits++;
            }
        }

        if( hits > 0 )
        {
            var sheet = document.createElement("link");
            sheet.setAttribute("rel","stylesheet");
            sheet.setAttribute("href","http://source.pixiv.net/www/css/member_illust_manga.css");
            document.head.appendChild( sheet );
            document.getElementsByTagName("html")[0].className = "verticaltext no-textcombine no-ie";
            document.body.innerHTML = "";
            document.body.appendChild( mangaSection );
        }
    }
}
else if( window == window.top )//not inside iframe
{
    if( directTitles )
    {
        //Link dem titles.
        linkThumbTitles([document]);
        new MutationObserver( function(mutationSet)
        {
            mutationSet.forEach( function(mutation){ linkThumbTitles( mutation.addedNodes ); } );
        }).observe( document.body, { childList:true, subtree:true } );
    }

    var worksDisplay = document.getElementsByClassName("works_display")[0];
    if( worksDisplay )
    {
        var mainImage, fullsizeSrc = 0, mainLink = worksDisplay.querySelector("a[href*='mode=']");
        if( mainLink )
            mainLink.removeAttribute('target');//Make link open in same window

        var oClass = document.getElementsByClassName("original-image");
        var downloadButton = document.getElementsByClassName("bookmark-container")[0];
        if( oClass.length == 1 )//47235071
        {
            var worksDiv = worksDisplay.getElementsByTagName("div")[0];
            worksDisplay.removeChild( worksDiv );//Need to remove instead of hide to prevent double source search links in other script
            var link = worksDisplay.insertBefore( document.createElement("a"), worksDisplay.firstChild );
            mainImage = link.appendChild( fullSizeMedium ? document.createElement("img") : worksDiv.getElementsByTagName("img")[0] );
            fullsizeSrc = link.href = oClass[0].getAttribute("data-src");
            //Add button to page
            if( fullSizeMedium )
            {
                console.log("Pixiv Download Helper Patch ver 0.1 by SgDylan.");
                console.log("Parsing download link...");
                var dButton0 = downloadButton.insertBefore( document.createElement("a"), downloadButton.firstChild );
                var dButton1 = downloadButton.insertBefore( document.createElement("a"), downloadButton.firstChild );
                console.log("Getting link...");
                var sourcePictureLink = oClass[0].getAttribute("data-src");
                var extName = "." + sourcePictureLink.split(".").pop(-1);
                authorTagList = document.getElementsByClassName("user-name");
                authorName = "";
                for (var index=0; index<authorTagList.length; index++) {
                    if (authorTagList[index].tagName=="A" && authorTagList[index].classList.value=="user-name") {
                        authorName = authorTagList[index].innerHTML;
                    }
                }
                var FileName = filenameConcater(authorName, document.getElementsByClassName("title")[2].innerHTML) + extName;
                console.log("File name: "+FileName);
                console.log("File Link: "+sourcePictureLink);
                console.log("Prepare right click button");
                dButton0.className = "_bookmark-toggle-button add-bookmark";
                dButton0.innerHTML = "<span class=\"description\">"+multiLang(1, 1)+"</span>";
                dButton0.download = FileName;
                dButton0.href = sourcePictureLink;
                console.log("Prepare right click button - Done !");
                // Prepare direct click button
                var retry_count = 0;
                get_blob_obj();
                function get_blob_obj() {
                    console.log("Prepare direct click button");
                    console.log("Preparing download file...");
                    GM_xmlhttpRequest({
                        method: "GET",
                        url: sourcePictureLink,
                        responseType: "blob",
                        timeout: 6000,
                        ontimeout: function() {
                            console.log("Timeout");
                            retry_count++;
                            if(retry_count<6) get_blob_obj();
                        },
                        onerror: function() {
                            console.log("Error");
                            retry_count++;
                            if(retry_count<6) get_blob_obj();
                        },
                        onload: function(response){
                            dButton1.className = "_bookmark-toggle-button add-bookmark";
                            dButton1.innerHTML = "<span class=\"description\">"+multiLang(1, 0)+"</span>";
                            dButton1.download = FileName;
                            dButton1.href = URL.createObjectURL(response.response);
                            console.log("Prepare direct click button - Done !");
                            console.log("All Ready !");
                        }
                    });
                }
            }
        }
        else if( mainLink && mainLink.href.indexOf("mode=big") > 0 && (mainImage = mainLink.getElementsByTagName("img")[0]) !== null )//17099702
        {
            //New thumbnails are always jpg, need to query mode=big page to get the right file extension.
            console.log("Mode=big");
            var req = new XMLHttpRequest();
            req.open( "GET", mainLink.href, true );
            req.onload = function()
            {
                mainLink.href = req.responseXML.getElementsByTagName("img")[0].src;
                if( fullSizeMedium ) {
                    mainImage.src = mainLink.href;
                }
            };
            req.responseType = "document";
            req.send(null);
        }

        if( mainImage && fullSizeMedium )
        {
            if( fullsizeSrc )
                mainImage.src = fullsizeSrc;
            mainImage.setAttribute("style", "max-width: "+fullSizeWidth+"; height: auto; width: auto;");
            worksDisplay.style.width = fullSizeWidth;
        }
    }
}

if( dontSayLazy && unlazyImage() && window == window.top )
{
    //Initial page has lazy images; listen for more images added later
    new MutationObserver( function(mutationSet)
    {
        mutationSet.forEach( function(mutation)
        {
            for( var i = 0; i < mutation.addedNodes; i++ )
                unlazyImage( mutation.addedNodes[i] );
        } );
    }).observe( document.body, { childList:true, subtree:true } );
}

//----------------------------------------------------------------//

if ( location.href.indexOf('setting_user.php') > 0 ) {
    // add some options
    appendSwitchChildren = document.getElementsByTagName('tr');
    appendSwitchParent = appendSwitchChildren[appendSwitchChildren.length-1].parentNode;
    switchOption = document.createElement('tr');
    switchOption.innerHTML = multiLang(2,0);
    appendSwitchParent.insertBefore(switchOption, appendSwitchParent.childNodes[appendSwitchChildren.length-1]);
    // bind button
    document.getElementsByName("saveSetting")[0].addEventListener('click', saveScriptSetting, false);
}

function saveScriptSetting() {
    for(let index=0; index<4; index++) {
        if(document.getElementsByName("pdh0")[index].checked) {
            GM_setValue("concatFmt", index);
            break;
        }
    }
    for(let index=0; index<4; index++) {
        if(document.getElementsByName("pdh1")[index].checked) {
            GM_setValue("splitFmt", index);
            break;
        }
    }
    alert(multiLang(2,1));
}

function filenameConcater(author_name, ill_name, index_num) {
    name_template = "";
    ill_id = "ID:" + getQueryString("illust_id");
    template_two = ["[{0}][{1}]","【{0}】【{1}】","{0} - {1}","{0} ~ {1}"];
    template_three = ["[{0}][{1}][{2}]","【{0}】【{1}】【{2}】","{0} - {1} - {2}","{0} ~ {1} ~ {2}"];
    template_index = ["[{0}]","【{0}】","- {0}","~ {0}"];
    if (GM_getValue("concatFmt", 0)==1) {
        name_template = template_three[GM_getValue("splitFmt", 0)].format(author_name, ill_name, ill_id);
        if (index_num) {
            name_template += template_index[GM_getValue("splitFmt", 0)].format(index_num);
        }
        return name_template;
    }
    switch(GM_getValue("splitFmt", 0)) {
        case 2:
            name_template = template_two[2].format(author_name, ill_id);
            break;
        case 3:
            name_template = template_two[3].format(ill_name, ill_id);
            break;
        default:
            name_template = template_two[0].format(author_name, ill_name);
    }
    if (index_num) {
        name_template += template_index[GM_getValue("splitFmt", 0)].format(index_num);
    }
    return name_template;
}

function getQueryString(name) {
    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
    var r = window.location.search.substr(1).match(reg);
    if (r !== null) return unescape(r[2]); return null;
}

//----------------------------------------------------------------//

function multiLang(mode, position) {
    lang_mode = 0;
    localUsingLang = navigator.language;
    if(!localUsingLang) {
        localUsingLang = navigator.languages[0];
    }
    if(localUsingLang.indexOf("zh") !== -1) {
        lang_mode = 2;
    } else if(localUsingLang.indexOf("ja") !== -1) {
        lang_mode = 1;
    } else {
        lang_mode = 0;
    }
    switch(mode) {
        case 0:
            return mangaModeLang[lang_mode][position];
        case 1:
            return normalModeLangZero[lang_mode][position];
        case 2:
            return saveFileNameFormat[lang_mode][position];
        default:
            break;
    }
}

function unlazyImage(target)
{
    var images = ( target || document ).querySelectorAll("img[data-src]");
    for( var i = 0; i < images.length; i++ )
        images[i].src = images[i].getAttribute("data-src");
    return images.length;
}

function pushTitleLink(list, link)
{
    var matcher;
    if( link && link.href && (matcher = link.href.match(/illust_id=(\d+)/)) && matcher[1] > 0 )
        list.push({ "id": matcher[1], "link": link });
}

function linkThumbTitles(targets)
{
    var titleList = [];

    for( var i = 0; i < targets.length; i++ )
    {
        //search.php
		var foundTitle = targets[i].querySelectorAll("a[href*='mode=medium'][href*='illust_id='][title]");
		for( var j = 0; j < foundTitle.length; j++ )
			pushTitleLink( titleList, foundTitle[j] );
		
		//bookmark.php, member_illust.php, new_illust.php, member.php (uploads), mypage.php (new works)
        var foundTitle = targets[i].querySelectorAll("a[href*='mode=medium'][href*='illust_id='] > .title");
        for( var j = 0; j < foundTitle.length; j++ )
            pushTitleLink( titleList, foundTitle[j].parentNode );

        //ranking.php
        foundTitle = targets[i].querySelectorAll(".ranking-item a.title[href*='mode=medium'][href*='illust_id=']");
        for( var j = 0; j < foundTitle.length; j++ )
            pushTitleLink( titleList, foundTitle[j] );

        //member_illust.php (what image was responding to)
        foundTitle = targets[i].querySelector(".worksImageresponseInfo a.response-out-work[href*='mode=medium'][href*='illust_id=']");
        if( foundTitle )
            pushTitleLink( titleList, foundTitle );

        //response.php, member_illust.php (before/after thumbnails), ?member.php (bookmarks)?
        var image = targets[i].querySelectorAll("li a[href*='mode=medium'][href*='illust_id='] img");
        for( var j = 0; j < image.length; j++ )
        {
            var page, title;
            for( page = image[j].parentNode; page.tagName != "A"; page = page.parentNode );

            //The prev/next thumbnails on mode=medium pages have text before/after the image.  Text also follows the image on image responses listings.
            if( !(title = page.getElementsByClassName("title")[0]) && (title = page.lastChild).nodeName != '#text' && (title = page.firstChild).nodeName != '#text' )
                continue;//Can't find title element

            //Start title link at mode=medium and change later.
            var titleLink = document.createElement("a");
            titleLink.href = page.href;
            titleLink.style.color = "#333333";//Style used on some pages

            //Move the title out of the thumbnail link
            page.removeChild(title);
            titleLink.appendChild(title);
            page.parentNode.insertBefore( titleLink, page.nextSibling );

            pushTitleLink( titleList, titleLink );
        }
    }

    for( var i = 0; i < titleList.length; i++ )
        directLinkSingle( titleList[i] );
}

//Query an image's mode=medium page.
function directLinkSingle(title)
{
    var req = new XMLHttpRequest();
    req.open( "GET", location.protocol+"//www.pixiv.net/member_illust.php?mode=medium&illust_id="+title.id, true );
    req.onload = function()
    {
        var select = req.responseXML.getElementsByClassName("original-image");
        if( select.length == 1 )
            title.link.href = select[0].getAttribute("data-src");
        else if( (select = req.responseXML.querySelector(".works_display a[href*='mode=manga']")) !== null )
        {
            title.link.href = select.href;
            var page = req.responseXML.querySelectorAll("ul.meta li")[1].textContent.match(/(\d+)P$/);
            if( page )
                ( title.link.firstChild.nodeName == '#text' ? title.link : title.link.firstChild ).title += " ("+page[1]+" pages)";
        }
    };
    req.responseType = "document";
    req.send(null);
}