171 lines
5.1 KiB
TypeScript
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/");
|