Sleazy Fork is available in English.

Kemono image download (Kemono 下载工具) self edited

Files 내 이미지 다운로드만 지원됩니다

// ==UserScript==
// @name         Kemono image download (Kemono 下载工具) self edited
// @namespace    http://tampermonkey.net/
// @version      1.1.1
// @description  Files 내 이미지 다운로드만 지원됩니다
// @author       modified by myself
// @match        *://*.kemono.su/*
// @match        *://*.kemono.party/*
// @match        *://kemono.su/*
// @match        *://kemono.party/*
// @connect      *
// @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 팬박스) (Patreon))
// 예시 제목: 예란 1 파트 3 (루프) (Patreon) 여기서 (Patreon)은 부제입니다.
// 1 -> 페이지 번호. 형식 예시: 1.jpg
// 2 -> 페이지_번호. 형식 예시: 1_20.jpg
// 3 -> 메인 제목 페이지_번호_오브_페이지. 형식 예: 옐란 1편 3부 (루프) 1_20.jpg
// 4 -> 메인 타이틀 부제 (소스) 페이지 번호_총_페이지. 형식 예시: 옐란 1편 3부 (루프) (Patreon) 1_20.jpg
// 5 -> [저자] 메인 제목 부제목(출처) [페이지_총계]. 형식 예시: [Abcde] 옐란 1편 3부 (루프) (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)
  })
  console.log(fileList);
  // 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", "downloadAll", {
        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 = "downloading"
        downloadingFiles(fileBtn, artist, titles, fileList)
      })
      let filesNode = document.getElementsByClassName('post__thumbnail')
      Array.from(filesNode).forEach((file, index) => {
        let href = file.querySelector('a').getAttribute('href')
        let fileName = createFileName(artist, titles, index + 1, fileList.length)

        // Create a download button for each image
        let downloadButton = createElement("button", "Download", {
          class: "download-button",
          type: "button",
        });

        downloadButton.addEventListener("click", function() {
            downloadButton.disabled = true;
            downloadButton.innerHTML = "downloading"
          downloadSingleFile(href, fileName, downloadButton);
        });

        let fileContainer = document.createElement("div");
        fileContainer.appendChild(downloadButton);
        fileContainer.appendChild(document.createTextNode(` - ${fileName}`));
        file.appendChild(fileContainer); // add button to bottom of image
      });
    }
  })
  let postDivNode = document.getElementsByClassName("post__files")
  //Array.from(postDivNode).forEach()
}
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 downloadSingleFile(url, fileName, btn) {
    //fileName = fileName.replace(/ /g,"_");
    fileName = fileName.replace(/\./g,"");
    GM_download({
      url: url,
      name: fileName,
      onload: function() {
        console.log(`다운로드 완료: ${fileName}`);
        alert(`다운로드 완료: ${fileName}`);
        btn.innerHTML = "Completed";
      },
      onerror: function(err) {
        console.error(`다운로드 실패: ${fileName}`, err);
        alert(`이미지 다운로드 실패: ${fileName}`);
      }
    });
  }
function downloadingFilesFinished(btn, totalQuest, total, success, faild) {
  if (totalQuest === 0) {
    btn.disabled = false
    btn.innerHTML = "downloadComplete"
    alert(`다운로드 완료, 총 ${total} 장, 성공 ${success} 장, 실패 ${faild} 장`)
  }
}