gadl/platforms/fanza-doujin.ts
2023-12-03 16:14:38 +09:00

128 lines
3.6 KiB
TypeScript

import type { Book } from "../library";
import type { Browser, BrowserContext, ImageFile } from "../browser";
import { getImageFiles } from "./dmm-books";
export function FanzaDoujin(browser: Browser) {
async function* getAllBooks(ctx: BrowserContext): AsyncGenerator<Book> {
const endpoint =
"https://www.dmm.co.jp/dc/doujin/api/mylibraries/?limit=20";
const pager = {
page: 1,
perPage: 20,
totalCount: Infinity,
};
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: {
data: {
items: Record<
string,
Array<{
productId: string;
title: string;
makerName: string;
}>
>;
total: number;
};
} = await res.json();
for (const item of Object.values(body.data.items).flat()) {
yield {
id: NaN,
platform: "fanza-doujin",
readerUrl: `https://www.dmm.co.jp/dc/-/mylibrary/detail/=/product_id=${item.productId}/`,
title: item.title || "",
authors: [item.makerName],
};
process.stderr.write(".");
}
pager.page += 1;
pager.totalCount = body.data.total;
}
}
return {
async *pull(): AsyncGenerator<Book> {
const ctx = await browser.loadBrowserContext("fanza-doujin");
yield* getAllBooks(ctx);
process.stderr.write(`\n`);
},
async getFiles(book: Book): Promise<Array<() => Promise<Blob>>> {
const ctx = await browser.loadBrowserContext("fanza-doujin");
const page = await ctx.newPage();
await page.goto(book.readerUrl);
const [, productId] = /product_id=([^/]*)/.exec(book.readerUrl) ?? [];
if (!productId) {
throw new Error(`product_id is not included: ${book.readerUrl}`);
}
const res = await ctx.request.get(
`https://www.dmm.co.jp/dc/doujin/api/mylibraries/details/${productId}/`,
);
if (!res.ok()) {
throw new Error(`${res.status()} ${res.statusText()}`);
}
const body: {
data: {
drm: {
dmmBooks: boolean;
softDenchi: boolean;
};
downloadLinks: Record<number, string>;
};
} = await res.json();
const imageFiles: Array<ImageFile> = [];
if (body.data.drm.dmmBooks) {
await page.waitForSelector(`li[class^="fileTreeItem"]`);
await page.click(`li[class^="fileTreeItem"]>a`);
await page.waitForURL((url) =>
url.href.startsWith(
"https://www.dmm.co.jp/dc/-/viewer/=/product_id=",
),
);
imageFiles.push(...(await page.evaluate(getImageFiles)));
} else {
for (const link of Object.values(body.data.downloadLinks)) {
const url = new URL(link, "https://www.dmm.co.jp/").href;
imageFiles.push({ url });
}
}
return imageFiles.map((imageFile) => async () => {
const blob = await browser.drawImage(page, imageFile);
process.stderr.write(".");
return blob;
});
},
loginEndpoints: ["https://accounts.dmm.co.jp/service/login/password"],
loginSuccessUrl: (url: URL) => url.href === "https://www.dmm.co.jp/top/",
logoutEndpoints: ["https://accounts.dmm.co.jp/service/logout"],
};
}
FanzaDoujin.siteUrl = (url: URL) =>
url.href.startsWith(
"https://www.dmm.co.jp/dc/-/mylibrary/detail/=/product_id=",
);