From 3dfe4839737c0add7d09e57989dcd211c0e5c8d8 Mon Sep 17 00:00:00 2001 From: Kohei Watanabe Date: Sun, 30 Jun 2024 23:40:04 +0900 Subject: [PATCH] fix dlsite-maniax --- README.md | 2 +- browser.ts | 4 +- package-lock.json | 4 +- package.json | 2 +- platforms/dlsite-maniax.ts | 103 ++++++++++++++++++++++++++++++------- 5 files changed, 92 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 92f7395..46a5240 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ $ npx https://git.fogtype.com/nebel/gadl/archive/main.tar.gz --help - Google Play ブックス (漫画) - DMM ブックス (漫画) -- DLsite 同人 +- DLsite 同人/がるまに/成年コミック - FANZA 同人 ## License diff --git a/browser.ts b/browser.ts index 3124529..18f0eef 100644 --- a/browser.ts +++ b/browser.ts @@ -1,8 +1,10 @@ -import * as Playwright from "playwright"; +import type * as Playwright from "playwright"; import { chromium, devices } from "playwright"; import type { Database } from "./database"; import type { TPlatform } from "./platform"; +export type PageOrFrame = Playwright.Page | Playwright.Frame; + export type ImageFile = { url: string; blocks?: Array>; diff --git a/package-lock.json b/package-lock.json index 0d8f132..3aa3455 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@fogtype/gadl", - "version": "1.5.0", + "version": "1.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@fogtype/gadl", - "version": "1.5.0", + "version": "1.5.1", "license": "AGPL-3.0", "dependencies": { "fflate": "^0.8.1", diff --git a/package.json b/package.json index f86f788..9ab2a7a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fogtype/gadl", - "version": "1.5.0", + "version": "1.5.1", "license": "AGPL-3.0", "type": "module", "bin": "bin/run.js", diff --git a/platforms/dlsite-maniax.ts b/platforms/dlsite-maniax.ts index 1e7bd9b..83b2c87 100644 --- a/platforms/dlsite-maniax.ts +++ b/platforms/dlsite-maniax.ts @@ -1,5 +1,41 @@ +import type { + Browser, + BrowserContext, + ImageFile, + PageOrFrame, +} from "../browser"; import type { Book } from "../library"; -import type { Browser, BrowserContext, ImageFile } from "../browser"; + +// リーダーのページ要素 +const workTreeItemsSelector = `[class^=_worktree_] li[class^=_item_]`; + +function Reader(page: PageOrFrame, readerUrl: string) { + const workId = /^https:[/][/]play[.]dlsite[.]com[/]#[/]work[/]([^/]+)/.exec( + readerUrl, + )?.[1]; + + if (!workId) { + throw new Error(`workId is not included: ${readerUrl}`); + } + + return { + async load() { + await page.goto(readerUrl); + }, + async downloadUrl(): Promise { + const isBook = workId.startsWith("B"); + + if (isBook) { + // PDFファイルでないことを確認 + const items = await page.waitForSelector(workTreeItemsSelector); + const text = await items.textContent(); + if (!text?.match(/PDFファイル/u)) return null; + } + + return `https://www.dlsite.com/home/download/=/product_id/${workId}.html`; + }, + }; +} export function DlsiteManiax(browser: Browser) { async function* getAllBooks(ctx: BrowserContext): AsyncGenerator { @@ -37,6 +73,7 @@ export function DlsiteManiax(browser: Browser) { ja_JP: string; }; }; + author_name: string | null; }>; } = await res.json(); @@ -46,7 +83,7 @@ export function DlsiteManiax(browser: Browser) { platform: "dlsite-maniax", readerUrl: `https://play.dlsite.com/#/work/${work.workno}`, title: work.name.ja_JP || "", - authors: [work.maker.name.ja_JP || ""], + authors: [work.author_name || work.maker.name.ja_JP || ""], }; process.stderr.write("."); @@ -69,30 +106,60 @@ export function DlsiteManiax(browser: Browser) { async getFiles(book: Book): Promise Promise>> { const ctx = await browser.loadBrowserContext("dlsite-maniax"); const page = await ctx.newPage(); + const reader = Reader(page, book.readerUrl); - await page.goto(book.readerUrl); + await reader.load(); + const downloadUrl = await reader.downloadUrl(); - const [, workId] = - /^https:[/][/]play[.]dlsite[.]com[/]#[/]work[/]([^/]+)/.exec( - book.readerUrl, - ) ?? []; + if (downloadUrl) { + const imageFile: ImageFile = { url: downloadUrl }; - if (!workId) { - throw new Error(`workId is not included: ${book.readerUrl}`); + return [ + async () => { + const blob = await browser.drawImage(page, imageFile); + + process.stderr.write("."); + + return blob; + }, + ]; } - const url = `https://www.dlsite.com/home/download/=/product_id/${workId}.html`; - const imageFile = { url }; + // ページ数 … 画面に表示されている要素を辿る + await page.waitForSelector(workTreeItemsSelector); + const workTreeItems = await page.locator(workTreeItemsSelector).count(); + await page.click(workTreeItemsSelector); - return [ - async () => { - const blob = await browser.drawImage(page, imageFile); + // 見開き表示の無効化 … 初回: 右下見開きボタンをクリックして無効化 + const spreadButton = page.getByRole("button", { name: "見開き" }); + await spreadButton.click(); + await Promise.all([ + spreadButton.waitFor({ state: "detached" }), + page.mouse.click(0, 720 / 2), + ]); + await page.keyboard.press("ArrowRight"); - process.stderr.write("."); + // ページ数だけ画面送りを繰り返し行い、canvasをそのままキャプチャしていく + const files: Array<() => Promise> = []; + while (files.length < workTreeItems) { + await page.waitForTimeout(1000); + const n = Math.min(2, Math.max(0, workTreeItems - 1 - files.length)); + const canvas = page.locator("canvas").nth(n); + await canvas.waitFor({ state: "visible" }); + const [width, height] = await Promise.all( + ["width", "height"].map((d) => canvas.getAttribute(d).then(Number)), + ); + await page.setViewportSize({ width, height }); + await page.waitForTimeout(1000); + const buff = await canvas.screenshot(); + files.push(async () => new Blob([buff], { type: "image/png" })); - return blob; - }, - ]; + process.stderr.write("."); + + await page.keyboard.press("ArrowLeft"); + } + + return files; }, loginEndpoints: ["https://www.dlsite.com/home/login"], loginSuccessUrl: (url: URL) => url.origin === "https://www.dlsite.com",