- // ==UserScript==
- // @name Dynasty Scans Batch Downloader
- // @namespace mccranky83.github.io
- // @version 2024-10-23
- // @description Download doujinshi from Dynasty Scans
- // @author Mccranky83
- // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js
- // @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.0/FileSaver.min.js
- // @match https://dynasty-scans.com/series/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=dynasty-scans.com
- // @grant none
- // @license MIT
- // ==/UserScript==
-
- class Semaphore {
- constructor() {
- this.counter = 200;
- this.queue = [];
- }
- async acquire() {
- this.counter > 0
- ? this.counter--
- : await new Promise((res) => {
- this.queue.push(res);
- });
- }
- release() {
- if (this.queue.length > 0) {
- this.queue.shift()();
- this.counter--;
- }
- this.counter++;
- }
- }
-
- (() => {
- "use strict";
-
- window.dl = dl;
- window.dlAll = dlAll;
- const s = new Semaphore();
- const h = {
- get(tar, key) {
- const val = Reflect.get(tar, key);
- if (typeof val === "object") return new Proxy(val, h);
- else return val;
- },
- set(tar, key, val) {
- Reflect.set(...arguments);
- const dl = $("#dl-all");
- if (key === "count") {
- Reflect.get(tar, key) ? dl.show() : dl.hide();
- }
- return true;
- },
- };
- const selected = new Proxy({ count: 0, index: [] }, h);
- $(".chapter-list dd").each((i, cur) => {
- $("<a>", {
- href: `javascript:;`,
- text: "Download",
- class: "label",
- }).appendTo(cur);
- $("<input>", { type: "checkbox", checked: false }).prependTo(cur);
- });
- $("dd")
- .slice(1)
- .find("a:last")
- .each((i, cur) => {
- $(cur).click(() => {
- dl(i + 1, s);
- });
- });
- $(".chapter-list").prepend(`
- <dd>
- <input type="checkbox">
- <span><b>Toggle All</b></span>
- </dd>
- `);
- const $checkbox = $("input[type='checkbox']");
- $checkbox.eq(0).on("change", function () {
- const checked = $(this).is(":checked");
- if (checked) {
- const length = $checkbox.length - 1;
- selected.count = length;
- selected.index = Array.from({ length }, (_, i) => i);
- } else {
- selected.count = 0;
- selected.index = [];
- }
- $checkbox.slice(1).each((_, cur) => {
- $(cur).prop("checked", checked);
- });
- console.log(JSON.stringify(selected));
- });
- $checkbox.slice(1).each(function (i) {
- $(this).on("change", () => {
- if ($(this).prop("checked")) {
- selected.count++;
- selected.index.push(i);
- selected.count === $checkbox.length - 1 &&
- $checkbox.eq(0).prop("checked", true);
- } else {
- selected.count--;
- selected.index.splice(selected.index.indexOf(i), 1);
- $checkbox.eq(0).prop("checked", false);
- }
- console.log(JSON.stringify(selected));
- });
- });
- $("<a>", {
- href: `javascript:;`,
- text: "Download all",
- class: "label",
- id: "dl-all",
- css: {
- display: "none",
- },
- }).appendTo("dd:first");
- $("a:contains('Download all')").click(() => {
- dlAll(selected, s);
- });
- })();
-
- async function dl(i, s) {
- const dl = $("dd")
- .eq(i + 1)
- .find("a:last");
- const text = dl.text();
- dl.text("Loading...");
- const zip = new JSZip();
- const name =
- $(".tag-title b").text() +
- "_" +
- $("dd")
- .eq(i + 1)
- .find("a:first")
- .text();
- const folder = zip.folder(name);
- const src =
- location.origin + $("dd").slice(1).eq(i).find("a:first").attr("href");
- const { pages, iframe } = await getPages(src);
- iframe.remove();
- await Promise.all(
- pages.map(async (page) => {
- const url = location.origin + page.image;
- const filename = page.image.split("/").slice(-1)[0];
- await s.acquire();
- await fetch(url, { signal: AbortSignal.timeout(30_000) })
- .then((res) => res.arrayBuffer())
- .then((res) => {
- folder.file(filename, res, { binary: true });
- })
- .catch(() => {})
- .finally(s.release.bind(s));
- }),
- );
- saveAs(
- await zip.generateAsync({
- type: "blob",
- compression: "DEFLATE",
- compressionOptions: {
- level: 6,
- },
- }),
- name,
- );
- dl.text(text);
- }
-
- async function dlAll(selected, s) {
- selected.index.forEach(async (i) => {
- await dl(i, s);
- selected.count = 0;
- selected.index = [];
- $("dd input").prop("checked", false);
- });
- }
-
- async function getPages(src) {
- return new Promise((res) => {
- const iframe = $("<iframe>", {
- src,
- css: {
- display: "none",
- },
- });
- iframe.appendTo("body");
- iframe.on("load", () =>
- res({
- pages: iframe[0].contentWindow.pages,
- iframe,
- }),
- );
- });
- }