gadl/platforms/dlsite-maniax.ts
2024-07-01 21:15:18 +09:00

171 lines
5.1 KiB
TypeScript

import type {
Browser,
BrowserContext,
ImageFile,
PageOrFrame,
} from "../browser";
import type { Book } from "../library";
// リーダーのページ要素
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<null | string> {
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<Book> {
const totalCountEndpoint = "https://play.dlsite.com/api/product_count";
const endpoint = "https://play.dlsite.com/api/purchases";
const pager = {
page: 1,
perPage: 50,
totalCount: Infinity,
};
const res = await ctx.request.get(totalCountEndpoint);
const body: {
user: number;
} = await res.json();
pager.totalCount = body.user;
while ((pager.page - 1) * pager.perPage <= pager.totalCount) {
const res = await ctx.request.get(`${endpoint}?page=${pager.page}`);
if (!res.ok()) {
throw new Error(`${res.status()} ${res.statusText()}`);
}
const body: {
limit: number;
works: Array<{
workno: number;
name: {
ja_JP: string;
};
maker: {
name: {
ja_JP: string;
};
};
author_name: string | null;
}>;
} = await res.json();
for (const work of Object.values(body.works).flat()) {
yield {
id: NaN,
platform: "dlsite-maniax",
readerUrl: `https://play.dlsite.com/#/work/${work.workno}`,
title: work.name.ja_JP || "",
authors: [work.author_name || work.maker.name.ja_JP || ""],
};
process.stderr.write(".");
}
pager.page += 1;
pager.perPage = body.limit;
}
}
return {
async *pull(): AsyncGenerator<Book> {
const ctx = await browser.loadBrowserContext("dlsite-maniax");
yield* getAllBooks(ctx);
process.stderr.write(`\n`);
},
async getFiles(book: Book): Promise<Array<() => Promise<Blob>>> {
const ctx = await browser.loadBrowserContext("dlsite-maniax");
const page = await ctx.newPage();
const reader = Reader(page, book.readerUrl);
await reader.load();
const downloadUrl = await reader.downloadUrl();
if (downloadUrl) {
const imageFile: ImageFile = { url: downloadUrl };
return [
async () => {
const blob = await browser.drawImage(page, imageFile);
process.stderr.write(".");
return blob;
},
];
}
// ページ数 … 画面に表示されている要素を辿る
await page.waitForSelector(workTreeItemsSelector);
const workTreeItems = await page.locator(workTreeItemsSelector).count();
await page.click(workTreeItemsSelector);
// 見開き表示の無効化 … 初回: 右下見開きボタンをクリックして無効化
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");
// ページ数だけ画面送りを繰り返し行い、canvasをそのままキャプチャしていく
const files: Array<() => Promise<Blob>> = [];
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" }));
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",
logoutEndpoints: ["https://www.dlsite.com/home/logout"],
};
}
DlsiteManiax.siteUrl = (url: URL) =>
url.href.startsWith("https://play.dlsite.com/#/work/");