Pixiv Download Assistant

Provides some functions to download from pixiv

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        Pixiv Download Assistant
// @name:ja     Pixiv Download Assistant
// @description Provides some functions to download from pixiv
// @description:ja pixiv に作品の保存を容易にするための機能を追加します
// @namespace   https://greasyfork.org/ja/users/24052-granony
// @author      granony
// @version     0.2.0
// @grant       GM_xmlhttpRequest
// @include     http://www.pixiv.net/member_illust.php?*
// @include     http://www.pixiv.net/novel/show.php?*
// @connect     pixiv.net
// @require     https://cdnjs.cloudflare.com/ajax/libs/jszip/3.0.0/jszip.min.js
// @license     MIT License
// ==/UserScript==

(function(){
"use strict;";
/*************************************************************************
 * 設定
 *************************************************************************/
 // 現在はなし

/*************************************************************************
 * 共通の処理
 *************************************************************************/
var pixiv = unsafeWindow.pixiv;
if(typeof(pixiv)=="undefined"){
  return;
}

console.log("pda: start");

var directLinkIcon = getDirectLinkIcon();
var isCreatingZip = false;
var metaInfo = getMetaInfo();
console.log(metaInfo);

// ページの種類によって作業を分岐
if(metaInfo.workType=="illust-single"){
  processSingleIllust();
}else if(metaInfo.workType=="manga-single"){
  processSingleManga();
}else if(metaInfo.workType=="illust-multi"){
  if(metaInfo.pageMode=="cover"){
    if(metaInfo.multiIllustDirection=="horizontal"){
      processMultiMangaCover();
    }else if(metaInfo.multiIllustDirection=="vertical"){
      processMultiIllustCover();
    }
  }else if(metaInfo.pageMode=="index"){
    if(metaInfo.multiIllustDirection=="horizontal"){
      processMultiMangaIndex();
    }else if(metaInfo.multiIllustDirection=="vertical"){
      processMultiIllustIndex();
    }
  }
}else if(metaInfo.workType=="ugoira"){
  processUgoira();
}else if(metaInfo.workType=="novel"){
  processNovel();
}

/*************************************************************************
 * 各ページの処理
 *************************************************************************/
/* 1枚で構成されたmanga ページの処理 */
function processSingleManga(){
  var origURL;
  var bigPageURL = "http://www.pixiv.net/member_illust.php?mode=big&illust_id="+metaInfo.workId;
  var zipButton;

  // 原寸画像の URL を取得,その後Zipボタンと直リンクボタンを生成
  GM_xmlhttpRequest({
    method: "GET",
    url: bigPageURL,
    headers: {
      referer: window.location.href,
    },
    onload: function(xhr){
      var parser = new DOMParser();
      var dom = parser.parseFromString(xhr.responseText, "text/html");
      origURL = dom.getElementsByTagName("img")[0].src;
      addDirectLink();
      zipButton = addZipButtonInCoverPage();
      setZipButtonHandlerForSingleIllustManga(zipButton, origURL);
    }
  });
  /* 直リンクボタンを生成する */
  function addDirectLink(){
    addDirectLinkButtonStyle();
    var button = createDirectLinkButton(origURL);
    var worksDisplay = document.getElementsByClassName("works_display")[0];
    var thumbnail = worksDisplay.getElementsByClassName("_work")[0];
    worksDisplay.insertBefore(button, thumbnail);
  }
}

/* 複数枚で構成された漫画について,cover ページの処理 */
function processMultiMangaCover(){
  var zipButton = addZipButtonInCoverPage();
  setZipButtonHandlerForMultiIllustManga(zipButton);
}

/* 複数枚で構成された漫画について,indexページの処理 */
function processMultiMangaIndex(){
  var zipButton = addZipButton();
  setZipButtonHandlerForMultiIllustManga(zipButton);
  
  function addZipButton(){
    var container = document.getElementsByClassName("panel-container")[0];
    var button = document.createElement("li");
    button.innerHTML = 'ZIP';
    button.style = 'display:inline-block; margin-left: 20px; cursor:pointer; background-color: #999; ' +
    'font-weight:bold; color: #FFF; padding:5px; border-radius:5px;';
    container.appendChild(button);
    return button;
  }
}

/* 1枚で構成されたイラストの処理 */
function processSingleIllust(){
  var origURL = document.getElementsByClassName("_illust_modal")[0]
                        .getElementsByTagName("img")[0]
                        .getAttribute("data-src");
  addDirectLink();
  var zipButton = addZipButtonInCoverPage();
  setZipButtonHandlerForSingleIllustManga(zipButton, origURL);
  
  function addDirectLink(){
    addDirectLinkButtonStyle();
    var button = createDirectLinkButton(origURL);
    var worksDisplay = document.getElementsByClassName("works_display")[0];
    var thumbnail = worksDisplay.getElementsByClassName("_layout-thumbnail")[0];
    worksDisplay.insertBefore(button, thumbnail);
  }
}

/* 複数枚で構成されたイラストについて,cover ページの処理 */
function processMultiIllustCover(){
  var zipButton = addZipButtonInCoverPage();
  setZipButtonHandlerForMultiIllustManga(zipButton);
}

/* 複数枚で構成されたイラストについて,index ページの処理 */
function processMultiIllustIndex(){
  addDirectLinks();
  var zipButton = addZipButton();
  setZipButtonHandlerForMultiIllustManga(zipButton);
  
  function addDirectLinks(){
    addDirectLinkButtonStyle();
    var containers = document.getElementsByClassName('item-container');
    for (var i = 0; i < containers.length; i++) {
      (function () {
        var container = containers[i];
        var image = container.getElementsByClassName('image') [0];
        var fsc = container.getElementsByClassName('full-size-container') [0];
        var mangaBigURL = fsc.href;
        GM_xmlhttpRequest({
          method: "GET",
          url: mangaBigURL,
          onload: function(xhr){
            var parser = new DOMParser();
            var dom = parser.parseFromString(xhr.responseText, "text/html");
            var origURL = dom.getElementsByTagName("img")[0].src;
            var uiContainer = document.createElement('div');
            uiContainer.style = 'display:inline-block;position:relative; width:37px;';
            
            var button = createDirectLinkButton(origURL);
            //button.style = "position: absolute; display:block; top:40px;margin: 5px 5px 0 0;";
            button.style.margin = "5px 0 0 0";
            fsc = container.removeChild(fsc);
            uiContainer.appendChild(fsc);
            uiContainer.appendChild(button);
            container.insertBefore(uiContainer, image);
          }
        });
      })();
    }
  }
  function addZipButton() {
    var pageMenu = document.getElementsByClassName('page-menu') [0];
    var button = document.createElement('div');
    button.innerHTML = 'ZIP';
    button.style = 'display:inline-block; margin-left: 5px; cursor:pointer; background-color: #FFF; ' +
    'font-weight:bold; color: #999; padding:5px; border-radius:5px;';
    pageMenu.appendChild(button);
    return button;
  }
}

/* うごイラの処理 */
function processUgoira(){
  var zipButton = addZipButtonInCoverPage();
  zipButton.addEventListener("click",function(){
    if(isCreatingZip){return;}
    isCreatingZip = true;
    zipButton.setAttribute("class", "button-on");
    createZipWrapper();
  });
  
  function createZipWrapper(){
    var framesText="";
    for(var i=0; i<pixiv.context.ugokuIllustFullscreenData.frames.length; i++){
      var frame = pixiv.context.ugokuIllustFullscreenData.frames[i];
      framesText+='{file:"'+frame.file+'", delay:'+frame.delay+'},';
    }
    var player =  document.getElementsByClassName("player")[0];
    var canvas = player.getElementsByTagName("canvas")[0];
    var cstyle = getComputedStyle(canvas,"null");
    var defaultSizeText='width:'+canvas.width+',height:'+canvas.height;
    var ugoiraHTML = getUgoiraHTML(framesText, defaultSizeText);
    var url = pixiv.context.ugokuIllustFullscreenData.src; 
    console.log(url);
    GM_xmlhttpRequest({
    method: 'GET',
    url: url,
    overrideMimeType: 'text/plain; charset=x-user-defined',
    headers: {referer:  window.location.href,},
    onload: function (xhr) {
      var zip = new JSZip();
      zip.file("ugoira.html",ugoiraHTML);
      zip.folder("src").loadAsync(xhr.responseText).then(function(){
        zip.generateAsync({
          type:"blob",
        }).then(function(content){
          isCreatingZip = false;
          zipButton.setAttribute("class", "_button");
          var zipURL = window.URL.createObjectURL(content);
          var zipFileName = getZipFileName();
          makeUserDownloadFile(zipURL, zipFileName);
        });
      });
    },
  });
  }
}
/* 小説の処理 */
function processNovel(){
  var isExpanded = false;
  var zipButton = addZipButtonInCoverPage();
  var expandButton = addExpandButton();
  
  zipButton.addEventListener("click", function(){
    if(isCreatingZip){return;}
    isCreatingZip = true;
    zipButton.setAttribute("class", "button-on");
    createZipWrapper();
  });
  
  expandButton.addEventListener("click", function(){
    if(isExpanded){return;}
    expandButton.setAttribute("class", "button-on");
    expandAllPages(document);
    hideScrollFollower(document);
    hidePageController(document);
    isExpanded = true; // ボタン連打を気にするなら,expandButton 内で設定したほうが良い
  });
  
  function addExpandButton(){
    var buttonContainer = document.createElement("div");
    buttonContainer.style = "display:inline-block; line-height:20px; margin-right:10px; padding-left:0; padding-right:0";
    var button = document.createElement("a");
    button.setAttribute("class", "_button");
    button.innerHTML = "Expand All";
    buttonContainer.appendChild(button);
    var bookmarkContainer = document.getElementsByClassName("bookmark-container")[0];
    bookmarkContainer.insertBefore(buttonContainer, bookmarkContainer.firstChild);
    return button;
  }
  function createZipWrapper(){
    var html = getHTML();
    var links = document.getElementsByTagName("link");
    var imgs = document.getElementsByTagName("img");
    var resourceURLs = [];
    var linkNum = 0;
    var imgNum = 0;
    var resourceNum = 0;
    var downloadedURLs = {};
    var zip = new JSZip();
    zip.file("novel.html", html);
    downloadLinkHref();
    
    /* link 要素が外部のリソースを参照している場合,ダウンロードする */
    function downloadLinkHref(){
      if(linkNum>=links.length){
        downloadResource();
        return;
      }
      var link = links[linkNum];
      linkNum++;
      if(link.rel=="stylesheet" && link.href.match(/^http/) && !downloadedURLs[link.href] ){
        downloadedURLs[link.href]=true;
        console.log(link.href);
        GM_xmlhttpRequest({
          method: "GET",
          url: link.href,
          onload:function(xhr){
            var fileName = getFileName(link.href);
            var file = xhr.responseText;
            storeResourceURLs(file, link.href);
            file = modifyCSS(file);
            zip.folder("src").file(fileName, file);
            downloadLinkHref();
          }
        });
      }else{
        downloadLinkHref();
      }
    }
    
    function downloadBackgroundImage(){
      var bStyle = getComputedStyle(document.body, null);
      if(bStyle.backgroundImage && bStyle.backgroundImage!="none"){
        var url = bStyle.backgroundImage.match(/"([^"]+)/)[1];
        downloadedURLs[url] = true;
        console.log(url);
        GM_xmlhttpRequest({
          method: "GET",
          url: url,
          overrideMimeType: 'text/plain; charset=x-user-defined',
          headers: {referer:  window.location },
          onload:function(xhr){
            var file = getFileName(url);
            zip.folder("src").file(file, xhr.responseText,{binary:true});
            downloadImgSrc();
          }
        });
      }else{
        downloadImgSrc();
      }
    }
    
    // スタイルシートを解析して,外部リソースのURLを保存
    function storeResourceURLs(text, stylesheetURL){
      var url0s = text.match(/url\(['"][^data]([^"']+)["']\)/g);
      if(!url0s){return;}
      for (var i=0; i<url0s.length; i++){
        var url0 = url0s[i];
        var url1 = url0.match(/["'](.+)["']/)[1];
        if( url1.match(/(png|jpg|gif)$/) ){
          var url;
          if(url1.match(/^http/)){
            url = url1;
          }else if(url1.match(/^\//)){
            url = getOrigin(stylesheetURL)+url1;
          }else{
            url = getDir(stylesheetURL)+url1;
            console.log(url);
            url = url.replace(/[^/]*?\/\.\.\//,"");
            url = url.replace(/[^/]*?\/\.\//,"");
          }
          resourceURLs.push(url);
          console.log(url);
        }
      }
    }
    
    // スタイルシートのパスを変更
    function modifyCSS(text){
      text = text.replace(/(url\(["'][^data])[^"']+\//g, "$1/");
      return text;
    }
    
    function downloadResource(){
      if(resourceNum>=resourceURLs.length){
        downloadBackgroundImage();
        return;
      }
      var url = resourceURLs[resourceNum];
      resourceNum++;
      if(!downloadedURLs[url] && !url.match(/\.\./)){
        downloadedURLs[url] = true;
        console.log(url);
        GM_xmlhttpRequest({
          method: "GET",
          url: url,
          overrideMimeType: 'text/plain; charset=x-user-defined',
          headers: {referer:  window.location },
          onload: function(xhr){
            var file = getFileName(url);
            zip.folder("src").file(file, xhr.responseText,{binary:true});
            downloadResource();
          }
        });
      }else{
        downloadResource();
      }
    }
    
    function downloadImgSrc(){
      if(imgNum>=imgs.length){
        makeUserDownloadZip();
        return;
      }
      var img = imgs[imgNum];
      imgNum++;
      if(!downloadedURLs[img.src]){
        downloadedURLs[img.src] = true;
        console.log(img.src);
        GM_xmlhttpRequest({
          method: "GET",
          url: img.src,
          overrideMimeType: 'text/plain; charset=x-user-defined',
          headers: {referer:  window.location.href },
          onload:function(xhr){
            var file = getFileName(img.src);
            zip.folder("src").file(file, xhr.responseText,{binary:true});
            downloadImgSrc();
          }
        });
      }else{
        downloadImgSrc();
      }
    }
    
    function makeUserDownloadZip(){
      isCreatingZip = false;
      zipButton.setAttribute("class","_button");
      zip.generateAsync({
        type: 'blob'
      }).then(function (content) {
        var zipURL = window.URL.createObjectURL(content);
        var zipFileName = getZipFileName();
        makeUserDownloadFile(zipURL, zipFileName);
      });
    }
    
    // document のコピーをとり,
    // オフラインの閲覧に適した形式に変更した html をテキストとして返す
    function getHTML(){
      // DOM を変更
      var parser = new DOMParser();
      var serializer = new XMLSerializer();
      var newdoc = parser.parseFromString(serializer.serializeToString(document), "text/html");
      expandAllPages(newdoc);
      removeScript(newdoc);
      hideScrollFollower(newdoc);
      hidePageController(newdoc);
      hideToolMenu(newdoc);
      hideToolBarItems(newdoc);
      // HTML をテキストにし,パスを変更
      var html = new XMLSerializer().serializeToString(newdoc);
      return modifyHTML(html);
    }
  }
  // 全ページを展開する
  // expandButton 経由と zipButton 経由とで呼びだされる
  // isExpanded のフラグは expandButton クリック時にのみセットされている
  function expandAllPages(dom){
    if(isExpanded){return;}
    var novelPages = dom.getElementsByClassName("novel-page");
    for(var i=0; i<novelPages.length; i++){
      var novelPage = novelPages[i];
      novelPage.style.display = "block";
      novelPage.style.borderWidth = "1px";
      novelPage.style.borderColor = "#999999";
      novelPage.style.borderStyle = "none none solid none";
      novelPage.innerHTML = '<p style="text-align:left;color:#999999;margin-bottom:2em;" id="'+(i+1)+'">page: '+(i+1)+'</p>'+novelPage.innerHTML;
    }
   
    var script = document.createElement('script');
    // ページ内遷移を強制するためのアドホックな対処
    script.text = `
      $("a.novel-jump").click(function(event){
        var url = event.currentTarget.href;
        var targetId = url.match(/#(.+)/)[1];
        var target = document.getElementById(targetId);
        var rect = target.getBoundingClientRect();
        var positionY = rect.top + window.pageYOffset;
        window.scrollTo( 0, positionY );
      });
      $("a.novel-jump").click(function(event){event.preventDefault()});
      $("a.novel-jump").mouseup(function(event){event.preventDefault()});
      $(".novel-outline a").click(function(event){
        var url = event.currentTarget.href;
        var targetId = url.match(/#(.+)/)[1];
        var target = document.getElementById(targetId);
        var rect = target.getBoundingClientRect();
        var positionY = rect.top + window.pageYOffset;
        window.scrollTo( 0, positionY );
      });
      $(".novel-outline a").click(function(event){event.preventDefault()});
      $(".novel-outline a").mouseup(function(event){event.preventDefault()});
    `;
    document.head.appendChild(script).remove();
    return dom;
  }
  
  function modifyHTML(html){
    // link href=* のパスを変更
    html = html.replace(/(<link[^>]*?href=")[^"]*\//g, '$1src/');
    // パスの? 以降を削除
    html = html.replace(/(<link[^>]*?href="[^"?]*)\?[^"]*/g,'$1');
    // img src=* のパスを変更
    html = html.replace(/(<img[^>]*?src=")[^"]*\//g, '$1src/');
    // a href=*のパスを変更
    html = html.replace(/(<a[^>]*?href=")\//g, '$1'+getOrigin(window.location.href));
    // 背景画像のパスを変更
    html = html.replace(/(background.*url\(')http[^']*\//g,"$1src/");
    return html;
  }
  
  function hideScrollFollower(dom){
    var scrollFollower = dom.getElementsByClassName("scroll-follower")[0];
    scrollFollower.innerHTML = "";
    //scrollFollower.parentNode.removeChild(scrollFollower);
  }
  
  function hidePageController(dom){    
    var pagers = dom.getElementsByClassName("pager");
    for(var i=0; i<pagers.length; i++){
      pagers[i].innerHTML = "";
    }
  }
  
  function removeScript(dom){
    // なぜか二重ループを回さないと消えない
    for (var j=0; j<10; j++){
      var scripts = dom.getElementsByTagName("script");
      for(var i=0; i<scripts.length; i++){
        var script = scripts[i];
        script.parentNode.removeChild(script);
      }
    }
  }
  
  // 右下の「pixiv sketch」等を非表示にする
  function hideToolMenu(dom){
    var toolMenu = dom.getElementsByClassName("_toolmenu")[0];
    toolMenu.style.display = "none";
    //toolMenu.parentNode.removeChild(toolmenu);
  }
  
  // 右下の「サムネイルフィルター設定」を非表示にする
  function hideToolBarItems(dom){
    var toolbarItems = dom.getElementById("toolbar-items");
    toolbarItems.style.display = "none";
    //toolbarItems.parentNode.removeChild(toolbarItems);
  }
}

/* メタ情報を取得 */
function getMetaInfo(){
  var info = {
    workType: "", // illust-single, illust-multi, manga-single, ugoira, novel
    multiIllustDirection: "", // horizontal, vertical
    pageMode: "", // cover, index
    illustSize: "", // [width, height]
    workId: "",
    workTitle: "",
    authorName: "",
    authorId: "",
  };
  // ユーザーの画像一覧を閲覧時に,ユーザーのIDが取得されてしまうが,害はない
  var url = location.href;
  info.workId = url.match(/id=(\d+)/)[1];

  if(url.match(/novel/)){
    info.workType = "novel";
    info.workTitle = document.getElementsByClassName("work-info")[0]
                             .getElementsByClassName("title")[0].innerHTML;
  }else if(url.match(/mode=medium/)){
    info.pageMode = "cover";
    info.workTitle = document.getElementsByClassName("work-info")[0]
                             .getElementsByClassName("title")[0].innerHTML;
    var worksDisplay = document.getElementsByClassName("works_display")[0];
    if(worksDisplay.getElementsByClassName("_ugoku-illust-player-container")[0]){
      info.workType = "ugoira";  
    }
    else if(worksDisplay.getElementsByClassName("multiple")[0]){
      info.workType = "illust-multi";
      if(worksDisplay.getElementsByClassName("ltr")[0] || worksDisplay.getElementsByClassName("rtl")[0]){
        info.multiIllustDirection = "horizontal";
      }else{
        info.multiIllustDirection = "vertical";
      }
    }
    else{
      if(document.getElementsByClassName("_illust_modal")[0]){
        info.workType = "illust-single";
      }else{
        info.workType = "manga-single";
      }
    }
  }else if(url.match(/mode=manga/)){
    info.pageMode = "index";
    info.workType = "illust-multi";
    
    if(document.getElementsByClassName("_book-viewer")[0]){
      info.multiIllustDirection = "horizontal";
      info.workTitle = document.getElementsByTagName("title")[0].innerHTML;
    }else{
      info.multiIllustDirection = "vertical";
      info.workTitle = document.getElementsByClassName("thumbnail-container")[0]
      .getElementsByTagName("h1")[0].getElementsByTagName("a")[0].innerHTML;
    }
  }
  
  return info;
}

/**************************************************************************
 * 下請けの関数
 **************************************************************************/

function getMimeType(url){
  var suffix = url.match(/.*\.(.+)$/) [1];
  return suffix == 'jpg' ? 'image/jpg' 
       : suffix == 'png' ? 'image/png' 
       : suffix == 'gif' ? 'image/gif' 
       : undefined;
}

function getFileName(url){
  return url.match(/.+\/([^\?#]*)/)[1];
}

function getFileName0(url){
  return url.match(/.+\/(.*)/)[1];
}

function getDir(url){
  return url.match(/^.+\//)[0];
}

function getOrigin(url){
  return url.match(/^.*:\/\/[^/]+/);
}


function modifyImageFileName(name){
  return name.replace(/_p(\d)\./,"_p0$1.");
}

function makeUserDownloadFile(url, fileName){
  var dummy = document.createElement('a');
  dummy.href = url;
  dummy.download = fileName;
  document.body.appendChild(dummy);
  dummy.click();
  document.body.removeChild(dummy);
}

function getZipFileName(){
  var title = metaInfo.workTitle;
  title = title.replace(/[\\\/:\;\*\?\"<>\|]/g,"_");
  return "pixiv_"+metaInfo.workId+"_"+metaInfo.workTitle+".zip";
}

function addZipButtonInCoverPage(){
  var buttonContainer = document.createElement("div");
  buttonContainer.style = "display:inline-block; line-height:20px; margin-right:10px;";
  var button = document.createElement("a");
  button.setAttribute("class", "_button");
  button.innerHTML = "Download as Zip";
  buttonContainer.appendChild(button);
  var bookmarkContainer = document.getElementsByClassName("bookmark-container")[0];
  bookmarkContainer.insertBefore(buttonContainer, bookmarkContainer.firstChild);
  return button;
}

function addDirectLinkButtonStyle(){
  var style = document.createElement("style");
  document.head.appendChild(style);
  style.sheet.insertRule(".pda_direct_link{"+
    "display:inline-block;width:20px;height:20px;padding:5px;margin:5px 0 0 0;"+
    "border-radius: 5px; border-style:none; border-width:1px; border-color:#d6dee5;"+
    "background-image:url("+directLinkIcon+");}",0);
}

function createDirectLinkButton(url){
  var buttonContainer = document.createElement("div");
  buttonContainer.style = "text-align:left;";
  var button = document.createElement("a");
  button.href = url;
  button.setAttribute("class", "pda_direct_link");
  buttonContainer.appendChild(button);
  button.addEventListener('mouseenter', function () {
    button.style.borderStyle = 'solid';
  });
  button.addEventListener('mouseleave', function () {
    button.style.borderStyle = 'none';
  });
  return buttonContainer;
}

function setZipButtonHandlerForSingleIllustManga(zipButton, origURL){
  zipButton.addEventListener("click",function(){
    if(isCreatingZip){return;}
    isCreatingZip = true;
    zipButton.setAttribute("class", "button-on");
    var zip = new JSZip();
    GM_xmlhttpRequest({
      method: "GET",
      url: origURL,
      overrideMimeType: 'text/plain; charset=x-user-defined',
      headers: {referer:  window.location.href,},
      onload:function(xhr){
        var imgFileName = modifyImageFileName(getFileName(origURL));
        zip.file(imgFileName, xhr.responseText,{binary:true});
        zip.generateAsync({
          type: 'blob'
        }).then(function (content) {
          isCreatingZip = false;
          zipButton.setAttribute("class", "_button");
          var zipURL = window.URL.createObjectURL(content);
          var zipFileName = getZipFileName();
          makeUserDownloadFile(zipURL, zipFileName);
        });
      },
    });
  });
}

function setZipButtonHandlerForMultiIllustManga(zipButton){
  zipButton.addEventListener("click", function(){
    if(isCreatingZip){return;}
    isCreatingZip = true;
    if(metaInfo.pageMode=="cover"){
      zipButton.setAttribute("class", "button-on");
      GM_xmlhttpRequest({
      method: "GET",
      url: "http://www.pixiv.net/member_illust.php?mode=manga&illust_id="+metaInfo.workId,
      onload: function(xhr){
        var parser = new DOMParser();
        var dom = parser.parseFromString(xhr.responseText, "text/html");
        createZipWrapper(dom);
      },
    });
    }else{
      createZipWrapper(document);
    }
  });
  function createZipWrapper(dom) {
    var html = new XMLSerializer().serializeToString(dom);
    // manga と illust で処理を分ける必要があるかも(ないかも)
    var totalPage = html.match(/pixiv\.context\.images/g).length-1;
    var pageURLList = [];
    for (var i=0; i<totalPage; i++){
      pageURLList.push("http://www.pixiv.net/member_illust.php?mode=manga_big&illust_id="+metaInfo.workId+"&page="+i);
    }
    var zip = new JSZip();
    var pageNum = 0;
    createZip();
    
    function createZip(){
      zipButton.innerHTML = pageNum+"/"+totalPage;
      var pageURL = pageURLList[pageNum];
      downloadBigPage();
      
      function downloadBigPage(){
        GM_xmlhttpRequest({
          method: "GET",
          url: pageURL,
          onload: function(xhr){
            var parser = new DOMParser();
            var dom = parser.parseFromString(xhr.responseText, "text/html");
            var imageURL = dom.getElementsByTagName("img")[0].src;
            var type = getMimeType(imageURL);
            if (!type) {
              alert('undefined file type for: ' + imageURL);
              return;
            }
            downloadOriginalImage(imageURL);
          }
        });
      }
      function downloadOriginalImage(imageURL){
        console.log(imageURL);
        GM_xmlhttpRequest({
          method: 'GET',
          url: imageURL,
          overrideMimeType: 'text/plain; charset=x-user-defined',
          headers: {
              referer:  window.location.href,
              //origin: getOrigin(window.location.href),
          },
          onload: function (xhr) {
            var file = modifyImageFileName(getFileName(imageURL));
            zip.file(file, xhr.responseText, {
              binary: true
            });
            pageNum++;
            if (pageNum < pageURLList.length) {
              setTimeout(createZip, 1000);
            } else {
              makeUserDownloadZip();
            }
          },
        });
      }
      function makeUserDownloadZip(){
        isCreatingZip = false;
        if(metaInfo.pageMode=="cover"){
          zipButton.setAttribute("class", "_button");
          zipButton.innerHTML = "Downloas as Zip";
        }else{
          zipButton.innerHTML = "ZIP";
        }
        zip.generateAsync({
          type: 'blob'
        }).then(function (content) {
          var zipURL = window.URL.createObjectURL(content);
          var zipFileName = getZipFileName();
          makeUserDownloadFile(zipURL, zipFileName);
        });
      }
    }
  }
}
/***************************************************************************
 * Resources
 ***************************************************************************/
// 直リンク用アイコンの SVG データ
function getDirectLinkIcon(){
  return "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+Cjxzdmcgd2lkdGg9IjMwIiBoZWlnaHQ9IjMwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogPCEtLSBDcmVhdGVkIHdpdGggU1ZHLWVkaXQgLSBodHRwOi8vc3ZnLWVkaXQuZ29vZ2xlY29kZS5jb20vIC0tPgoKIDxnPgogIDx0aXRsZT5MYXllciAxPC90aXRsZT4KICA8cmVjdCByeT0iMSIgcng9IjEiIGZpbGw9IiNjY2NjY2MiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSI1IiBzdHJva2UtZGFzaGFycmF5PSJudWxsIiBzdHJva2UtbGluZWpvaW49Im51bGwiIHN0cm9rZS1saW5lY2FwPSJudWxsIiBzdHJva2Utb3BhY2l0eT0iMCIgeD0iMiIgeT0iMjIiIHdpZHRoPSIyNiIgaGVpZ2h0PSI3IiBpZD0ic3ZnXzEiLz4KICA8cGF0aCB0cmFuc2Zvcm09InJvdGF0ZSgxODAsIDE1LjAwMiwgMTEuODU1NSkiIGlkPSJzdmdfMiIgZD0ibTQuNTAwMTcsMTEuODMxMjJsMTAuNTAxODMsLTEwLjMzMTM2bDEwLjUwMTgxMSwxMC4zMzEzNmwtNS4yNTA0NDEsMGwwLDEwLjM3OTg0bC0xMC41MDI2NywwbDAsLTEwLjM3OTg0bC01LjI1MDUzLDBsMCwweiIgc3Ryb2tlLW9wYWNpdHk9IjAiIHN0cm9rZS1saW5lY2FwPSJudWxsIiBzdHJva2UtbGluZWpvaW49Im51bGwiIHN0cm9rZS1kYXNoYXJyYXk9Im51bGwiIHN0cm9rZS13aWR0aD0iNSIgc3Ryb2tlPSIjMDAwMDAwIiBmaWxsPSIjY2NjY2NjIi8+CiA8L2c+Cjwvc3ZnPg==";
}

// うごイラビューワー
function getUgoiraHTML(frames, defaultSize){
  var template = `<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>ugougo</title>
  <script>
    "use strict";
    var frames = [$frames]; // [{file:"000000.jpg",delay:250},{file:"000001.jpg",delay:500},]
    var canvasDefaultSize = {$defaultSize}; // {width: "600", height: "400"}
    var aspectRatio = undefined;
    var frameNum = 0;
    var canvasMode = "default";
    
    // 起動
    window.onload = function(){
      loadImages();
    }
    
    // 全フレームの画像を読み込む
    function loadImages(){
      //console.log("loadImages:",frameNum);
      var frame = frames[frameNum];
      frame.image = document.createElement("img");
      frame.image.addEventListener("load", function(){
        frame.width  = frame.image.width;
        frame.height = frame.image.height;
        if(!aspectRatio){
          aspectRatio = frame.width/frame.height;
        }
        frameNum++;
        if(frameNum>=frames.length){
          frameNum = 0;
          setCanvas();
          drawCanvas();
        }else{
          loadImages();
        }
      });
      frame.image.src = "src/"+frame.file;
    };
   // canvas の設定
   function setCanvas(){
      var canvas = document.getElementById("canvas");
      var changeCanvasPosition = function(){
        var bWidth = document.documentElement.clientWidth;
        var bHeight = document.documentElement.clientHeight;
        canvas.style.left = (bWidth-canvas.width)/2+"px";
        canvas.style.top = (bHeight-canvas.height)/2+"px";
      }
      var changeCanvasSize = function(){
        var bWidth = document.documentElement.clientWidth;
        var bHeight = document.documentElement.clientHeight;
        var fWidth = frames[0].width;
        var fHeight = frames[0].height;
        if(canvasMode=="default"){
          canvas.width = canvasDefaultSize.width;
          canvas.height = canvasDefaultSize.height;
        }else{
          if(bWidth>fWidth && bHeight>fHeight){
            canvas.width  = fWidth;
            canvas.height = fHeight;
          }else{
            if(bHeight*aspectRatio<bWidth){
              canvas.width  = bHeight*aspectRatio;
              canvas.height = bHeight;
            }else{
              canvas.width  = bWidth;
              canvas.height = bWidth/aspectRatio;
            }
          }
        }
      }
      // canvas の click 時, canvasMode を反転させ,canvas のサイズと位置を変更
      canvas.addEventListener("click", function(){
        if(canvasMode=="default"){
          canvasMode = "zoom";
          canvas.className = "zoom";
        }else{
          canvasMode = "default";
          canvas.className = "default";
        }
        changeCanvasSize();
        changeCanvasPosition();
      });
      // Window のサイズ変更時, canvas のサイズと位置を変更
      (function(){
        var resizeTimer = false;
        window.addEventListener('resize', function () {
          if (resizeTimer !== false) {
            clearTimeout(resizeTimer);
          }
          resizeTimer = setTimeout(function () {
            changeCanvasSize();
            changeCanvasPosition();
          }, 50);
        });
      })();
      changeCanvasSize();
      changeCanvasPosition();
    }
    
    // 一定時間毎に canvas に描画
    function drawCanvas(){
      var frame = frames[frameNum];
      var canvas = document.getElementById("canvas");
      var ctx = canvas.getContext("2d");
      ctx.clearRect(0,0,canvas.width,canvas.height);
      ctx.drawImage(frame.image, 0,0,frame.width,frame.height,0,0,canvas.width,canvas.height);
      frameNum++;
      if(frameNum>=frames.length){frameNum = 0;}
      setTimeout(drawCanvas, frame.delay);
    }
    
  </script>
  <style>
    *{margin:0; padding:0;}
    body{
      background-color: #FFFFFF;
      position: relative;
    }
    #canvas{
      position:absolute;
      margin: auto;
    }
    #canvas.default{
      cursor: zoom-in;
    }
    #canvas.zoom{
      cursor: zoom-out;
    }
  </style>
</head>
<body>
  <canvas id ="canvas" class="default" width=600 height=400>
</body>
</html>`;
  template = template.replace("$frames", frames);
  template = template.replace("$defaultSize", defaultSize);
  return template;
}

})();