cpuopt/DLsite-Play-Downloader

卡在正在前往阅读器以获取url了

Closed this issue · 5 comments

这种在二级菜单是单张图片的,我没找到用来拼图的图片顺序,等找到了才能做适配。
2

研究了一个下午的API

把作者的内容全部替换成下面这段的话就能下载全部

const workNo = location.href.match(/\/work\/(\w+)\//)[1];

if (!workNo) {
  throw new Error("no workNo");
}

let scripts = [
  "https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.6.1/cropper.min.js",
  "https://cdn.jsdelivr.net/npm/streamsaver@2.0.3/StreamSaver.min.js",
  "https://jimmywarting.github.io/StreamSaver.js/examples/zip-stream.js",
  // "https://unpkg.com/mersenne-twister@1.1.0/src/mersenne-twister.js",
];

scripts.forEach((url) => {
  let script = document.createElement("script");
  script.setAttribute("type", "text/javascript");
  script.src = url;
  document.documentElement.appendChild(script);
});

async function getDownloadCredential() {
  const response = await fetch(
    `https://play.dl.dlsite.com/api/download/sign/cookie?workno=${workNo}`,
    {
      method: "GET",
      headers: {
        Accept: "*/*",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "zh-CN,zh;q=0.9",
      },
      referrer: "https://play.dlsite.com/",
      credentials: "include",
    }
  );
  return await response.json();
}

async function getDownloadUrls(prefix) {
  const response = await fetch(`${prefix}ziptree.json`, {
    referrer: "https://play.dlsite.com/",
    credentials: "include",
  });
  const zipTree = await response.json();

  const result = [];

  const travel = (fileObj, index, path) => {
    if (fileObj.type === "folder") {
      fileObj.children.forEach((child, index) =>
        travel(child, index, fileObj.path)
      );
    }
    if (fileObj.type === "file" && !fileObj.hashname.endsWith(".pdf")) {
      result.push({
        filename: `${path ? `${path}/` : ""}${fileObj.name}`,
        optimized: zipTree.playfile[fileObj.hashname].image.optimized,
      });
    }
  };
  zipTree.tree.forEach(travel);
  // console.log(result);
  return result;
}

function getDecryptedImageData(optimized) {
  const qv = (t, s) => {
      const MersenneTwister = unsafeWindow.module.exports;
      // const MersenneTwister = window.module.exports;
      const n = new MersenneTwister(t);
      for (let r = s.length - 1; r > 0; r--) {
        const o = Math.floor(n.random() * (r + 1));
        [s[r], s[o]] = [s[o], s[r]];
      }
      return s;
    },
    Ir = (t, s) => (t >= s ? t % s : t),
    Lr = (t, s) => (t >= s ? Math.floor(t / s) : 0);
  const n = {
      w: Math.ceil(optimized.width / 128),
      h: Math.ceil(optimized.height / 128),
    },
    r = parseInt(optimized.name.substring(5, 12), 16),
    i = qv(r, [...Array(n.w * n.h).keys()]).map((value, index) => ({
      sx: 128 * Ir(index, n.w),
      sy: 128 * Lr(index, n.w),
      dx: 128 * Ir(value, n.w),
      dy: 128 * Lr(value, n.w),
    }));
  return { sourceCropSize: 128, cropCount: n, coordinates: i };
}

async function imagePuzzle(downloadPrefix, { filename, optimized }) {
  let canvas = document.createElement("canvas");
  canvas.width = optimized.width;
  canvas.height = optimized.height;
  let ctx = canvas.getContext("2d");
  let binResponse = await fetch(
    `${downloadPrefix}optimized/${optimized.name}`,
    {
      method: "GET",
      headers: {
        Accept:
          "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "zh-CN,zh;q=0.9",
        "Sec-Fetch-Dest": "image",
      },
      referrer: "https://play.dlsite.com/",
      credentials: "include",
    }
  );
  let blob = await binResponse.blob();
  const img = new Image();
  img.src = URL.createObjectURL(blob);
  return new Promise((resolve) => {
    img.onload = function () {
      const {
          sourceCropSize: sourceCropSize,
          cropCount: cropCount,
          coordinates: coordinates,
        } = getDecryptedImageData(optimized),
        // g = isSpread && m === 1 ? t[0].width : 0,
        g = 0,
        // y = t[isSpread && m === 0 ? 1 : 0].height,
        // w = isSpread && optimized.height < y ? Math.round((y - optimized.height) / 2) : 0,
        w = 0,
        x = {
          w: img.width - optimized.width,
          h: img.height - optimized.height,
        };
      for (const coordinate of coordinates) {
        const k =
            coordinate.dx + sourceCropSize === sourceCropSize * cropCount.w
              ? sourceCropSize - x.w
              : sourceCropSize,
          O =
            coordinate.dy + sourceCropSize === sourceCropSize * cropCount.h
              ? sourceCropSize - x.h
              : sourceCropSize;
        ctx.drawImage(
          img,
          coordinate.sx,
          coordinate.sy,
          k,
          O,
          coordinate.dx + g,
          coordinate.dy + w,
          k,
          O
        );
      }

      canvas.toBlob(function (blob) {
        resolve({ filename, blob });
      });
    };
  });
}
function save(mangaName, blobs) {

  const fileStream = streamSaver.createWriteStream(`${mangaName}.zip`);

  const readableZipStream = new ZIP({
    start(ctrl) {
      blobs.forEach(({ blob, filename }, arrayIndex) => {
        let file = {
          // name: `${mangaName}/${(index + 1).toString().padStart(4, "0")}.jpg`,
          // name: `${(index + 1).toString().padStart(4, "0")}.png`,
          name: `${filename.split(".")[0]}.png`,
          stream: () => blob.stream(),
        };
        ctrl.enqueue(file);
      });
      ctrl.close();
    },
  });

  // more optimized
  if (window.WritableStream && readableZipStream.pipeTo) {
    return readableZipStream
      .pipeTo(fileStream)
      .then(() => console.debug("done writing"));
  }

  // less optimized
  const writer = fileStream.getWriter();
  const reader = readableZipStream.getReader();
  const pump = () =>
    reader
      .read()
      .then((res) =>
        res.done ? writer.close() : writer.write(res.value).then(pump)
      );

  pump();
}

async function getMangaName() {
  const response = await fetch(`https://play.dlsite.com/api/work/${workNo}`, {
    referrer: "https://play.dlsite.com/",
    credentials: "include",
  });
  const result = await response.json();
  return result.name["ja_JP"];
}

async function downloadFlow() {
  const { url: downloadPrefix, cookies } = await getDownloadCredential();
  const [mangaName, downloadUrls] = await Promise.all([
    getMangaName(),
    getDownloadUrls(downloadPrefix),
  ]);
  const downloadResults = await Promise.all(
    downloadUrls.map((value) => imagePuzzle(downloadPrefix, value))
  );

  save(mangaName, downloadResults);
}

class DownloadButton {
  button;
  constructor(className, father) {
    let button = document.createElement("button");
    button.className = className;
    button.innerText = "使用脚本下载";

    button.addEventListener("click", (e) => {
      e.preventDefault();
      e.stopPropagation();
      downloadFlow();
    });
    father.appendChild(button);
    this.button = button;
  }
}

GM_addStyle(`
  .button-down{
      border: none;
      background-color: #007aff;
      color: white;
      padding-inline: 0.6rem;
      position: absolute;
      right: 0;
      height: 100%;
      z-index: 2;
      font-weight: bolder;
      transition: background-color .5s;
  }
  .button-down:hover{
      background-color: #000000;
  }
  `);

setTimeout(() => {
  scripts = [
    "https://unpkg.com/mersenne-twister@1.1.0/src/mersenne-twister.js",
  ];
  // window.module = {
  //   exports: {}, // for mersenneTwister
  // };
  // tamperMonkey
  unsafeWindow.module = {
    exports: {},
  };

  scripts.forEach((url) => {
    let script = document.createElement("script");
    script.setAttribute("type", "text/javascript");
    script.src = url;
    document.documentElement.appendChild(script);
  });
  new DownloadButton(
    "button-down",
    document.querySelector("ol[class^='_tree_'] > li[class^='_item_']")
  );
}, 10 * 1000);

如果我回头还记得起这件事情的话再提交PR 🤣

        // g = isSpread && m === 1 ? t[0].width : 0,
        g = 0,
        // y = t[isSpread && m === 0 ? 1 : 0].height,
        // w = isSpread && optimized.height < y ? Math.round((y - optimized.height) / 2) : 0,
        w = 0,

关于g和w是0这个问题,我看了手头的几个作品,在页面中debug出来都是0,所以暂时置0

如果我回头还记得起这件事情的话再提交PR

老哥,用这个把文件下载到本地后压缩包里的文件名称全是[object Object]……重复,甚至导致文件名称太长了无法解压,请问有什么修复方法吗
image

如果我回头还记得起这件事情的话再提交PR

部分解决了,换了个7zip能改文件名了(