Kemono 下载工具

仅支持Files内图片下载

// ==UserScript==
// @name         Kemono 下载工具
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  仅支持Files内图片下载
// @author       ljw2487
// @match        *://*.kemono.su/*
// @match        *://*.kemono.party/*
// @match        *://kemono.su/*
// @match        *://kemono.party/*
// @license      GNU General Public License v3.0 or later
// @grant        GM_addStyle
// @grant        GM_addElement
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// ==/UserScript==

GM_addStyle(".ad-container {display: none;}");
GM_addStyle(".root--ujvuu {display: none;}");

// 下载文件命名规则:
// 包含:作者 | 主标题 | 副标题(来源) | 页码 | 共多少页 (副标题一般是(Pixiv Fanbox) (Patreon))
// 标题示例:Yelan 1 Part 3 (Loop) (Patreon) 其中(Patreon)为副标题
// 1 -> 页码.格式                                        示例:1.jpg
// 2 -> 页码_共多少页.格式                               示例:1_20.jpg
// 3 -> 主标题 页码_共多少页.格式                        示例:Yelan 1 Part 3 (Loop) 1_20.jpg
// 4 -> 主标题 副标题(来源) 页码_共多少页.格式           示例:Yelan 1 Part 3 (Loop) (Patreon) 1_20.jpg
// 5 -> [作者] 主标题 副标题(来源) [页码_共多少页].格式  示例:[Abcde] Yelan 1 Part 3 (Loop) (Patreon) [1_20].jpg
const nameRule = 5

function script() {
  const host = location.host
  const path = location.pathname
  const regular = {
    "/": homePage, // 首页
    "/artists": artistsPage, // 画师列表
    "/(\\w+)/user/(\\d+)": userPage, // 画师详情
    "/(\\w+)/user/(\\d+)/post/(\\d+)": postPage, // 作品页
  };
  Object.keys(regular).some((pattern) => {
    if (new RegExp(`^${pattern}$`).test(path)) {
      regular[pattern]();
      return true;
    }
  })
}
script()

function homePage () {
  // console.log('homepage');
}
function artistsPage () {
  // console.log('artistsPage');
}
function userPage () {
  // console.log('userPage');
}
function postPage () {
  GM_addStyle(".download-button {color: white; font-size: 16px; background-color: #3e3e44; padding: 3px 15px; border-radius: 5px; appearance: none; border: none; outline: none; transition: color 0.2s ease-in-out;}");
  GM_addStyle(".download-button:hover {color: #f8b696; transition: color 0.2s ease-in-out;}");
  GM_addStyle(".modify-h2 {display:flex; align-items: center; justify-content: space-between;}");

  // GET ARTIST {String} "name"
  let artist = ""
  artist = document.getElementsByClassName('post__user-name')[0].innerText
  // GET TITLES {Array} [mainTitle, subTitle]
  let titles = []
  let titleNode = document.getElementsByClassName('post__title')[0]
  titleNode = titleNode.getElementsByTagName('span')
  Array.from(titleNode).forEach(item => { titles.push(item.innerText) })
  // GET FILE URL LIST {Array} [Url, Url, ...]
  let fileList = []
  let filesNode = document.getElementsByClassName('post__thumbnail')
  Array.from(filesNode).forEach(file => {
    let href = file.querySelector('a').getAttribute('href')
    fileList.push(href)
  })
  // Modify-DOM
  let h2Node = document.getElementsByTagName("h2")
  Array.from(h2Node).forEach(element => {
    // Files
    if(element.innerHTML == "Files") {
      let fileTitle = createElement("h2", "Files")
      let fileBtn = createElement("button", "下载全部", {
        id: "fileButton",
        class: "download-button",
        type: "button"
      })
      element.innerHTML = ""
      element.classList.add("modify-h2")
      element.appendChild(fileTitle)
      element.appendChild(fileBtn)

      fileBtn.addEventListener("click", function() {
        fileBtn.disabled = true
        fileBtn.innerHTML = "下载中"
        downloadingFiles(fileBtn, artist, titles, fileList)
      })
    }
  })
}
function createElement(tag, text, attributes = undefined) {
  let element = document.createElement(tag);
  element.innerHTML = text
  if (attributes) {
    for (let k in attributes) {
      element.setAttribute(k, attributes[k])
    }
  }
  return element
}
function createFileName(artist, titles, index, pages) {
  // console.log(artist, titles, index, pages);
  if (nameRule === 1) {
    return `${index}` // 1.jpg
  } else if (nameRule === 2) {
    return `${index}_${pages}` // 1_20.jpg
  } else if (nameRule === 3) {
    return `${titles[0]} ${index}_${pages}` // title 1_20.jpg
  } else if (nameRule === 4) {
    return `${titles[0]} ${titles[1]} ${index}_${pages}` // title (fanbox) 1_20.jpg
  } else if (nameRule === 5) {
    return `[${artist}] ${titles[0]} ${titles[1]} [${index}_${pages}]` // [artist] title (fanbox) [1_20].jpg
  }
}
function downloadingFiles(btn, artist, titles, fileList) {
  let totalQuest = fileList.length, success = 0, faild = 0
  fileList.forEach((url, index) => {
    let fileName = createFileName(artist, titles, index + 1, fileList.length)
    GM_download({
      url: url,
      name: fileName,
      onload: function() {
        console.log(`下载 ${index + 1}/${fileList.length} 完成`)
        totalQuest -= 1
        success += 1
        downloadingFilesFinished(btn, totalQuest, fileList.length, success, faild)
      },
      onerror: function(err) {
        console.error(`下载 ${index + 1}/${fileList.length} 失败 准备二次尝试`, err)
        GM_download({
          url: url,
          name: fileName,
          onload: function() {
            console.log(`下载 ${index + 1}/${fileList.length} 完成 (二次尝试)`)
            totalQuest -= 1
            success += 1
            downloadingFilesFinished(btn, totalQuest, fileList.length, success, faild)
          },
          onerror: function(err) {
            console.log(`下载 ${index + 1}/${fileList.length} 失败 (二次尝试) 请尝试手动下载`, err, {
              name: fileName,
              url: url
            })
            totalQuest -= 1
            faild += 1
            downloadingFilesFinished(btn, totalQuest, fileList.length, success, faild)
          }
        })
      }
    })
  })
}
function downloadingFilesFinished(btn, totalQuest, total, success, faild) {
  if (totalQuest === 0) {
    btn.disabled = false
    btn.innerHTML = "已下载"
    alert(`下载完成, 共${total}张, 成功${success}张, 失败${faild}张`)
  }
}