128 lines
3.6 KiB
TypeScript
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=",
|
|
);
|