diff --git a/library.ts b/library.ts index d58f16e..00d0ffd 100644 --- a/library.ts +++ b/library.ts @@ -8,35 +8,68 @@ export type Book = { id: number; platform: "dmm-books" | "google-play-books"; readerUrl: string; + title: string; + authors: Array; }; export function createLibrary(db: Database) { return { - async add(readerUrl: string) { + async add(readerUrlOrBook: string | Book) { const platform = "dmm-books"; + if (typeof readerUrlOrBook === "string") { + await db.run( + `insert into books(platform_id, reader_url) values((select id from platforms where name = ?), ?)`, + platform, + readerUrlOrBook, + ); + + return; + } + await db.run( - `insert into books(reader_url, platform_id) values(?, (select id from platforms where name = ?))`, - readerUrl, + `\ +insert into books( + platform_id, + reader_url, + title, + authors) +values((select id from platforms where name = ?), ?, ?, ?) +on conflict(reader_url) + do update set title = excluded.title, authors = excluded.authors +`, platform, + readerUrlOrBook.readerUrl, + readerUrlOrBook.title, + JSON.stringify(readerUrlOrBook.authors), ); }, async delete(id: number) { await db.run(`delete from books where id = ?`, id); }, async get(id: number): Promise { - const book: Book | undefined = await db.get( - `select books.id, platforms.name as platform, books.reader_url as readerUrl from books left join platforms on books.platform_id = platforms.id where books.id = ?`, + const row = await db.get( + `select books.id, platforms.name as platform, books.reader_url as readerUrl, books.title, books.authors from books left join platforms on books.platform_id = platforms.id where books.id = ?`, id, ); + const book: Book | undefined = row && { + ...row, + authors: JSON.parse(row.authors), + }; + return book; }, async getBooks(): Promise> { - const books: Array = await db.all( - `select books.id, platforms.name as platform, books.reader_url as readerUrl from books left join platforms on books.platform_id = platforms.id`, + const rows = await db.all( + `select books.id, platforms.name as platform, books.reader_url as readerUrl, books.title, books.authors from books left join platforms on books.platform_id = platforms.id`, ); + const books: Array = rows.map((row) => ({ + ...row, + authors: JSON.parse(row.authors), + })); + return books; }, async archive(dir: string) { diff --git a/main.ts b/main.ts index 090bac4..d9b2cf9 100644 --- a/main.ts +++ b/main.ts @@ -61,7 +61,12 @@ const options = { const db = await createDatabase(args.values.db!); const library = createLibrary(db); const books = await library.getBooks(); - console.dir(books, { depth: null }); + + console.dir(books, { + depth: null, + maxArrayLength: null, + maxStringLength: null, + }); }, }, view: { @@ -75,7 +80,24 @@ const options = { process.exit(1); } - console.dir(book, { depth: null }); + console.dir(book, { + depth: null, + maxArrayLength: null, + maxStringLength: null, + }); + }, + }, + pull: { + type: "boolean", + async run() { + const db = await createDatabase(args.values.db!); + const library = createLibrary(db); + const browser = await chromium.launch(); + const platform = createPlatform({ db, browser }); + + for await (const book of platform.pull()) { + await library.add(book); + } }, }, download: { diff --git a/migrations/2_add_tile_authors.sql b/migrations/2_add_tile_authors.sql new file mode 100644 index 0000000..1f9001b --- /dev/null +++ b/migrations/2_add_tile_authors.sql @@ -0,0 +1,2 @@ +alter table books add column title text not null default ''; +alter table books add column authors json not null default '[]'; diff --git a/platforms/dmm-books.ts b/platforms/dmm-books.ts index f3d7c08..24e27d7 100644 --- a/platforms/dmm-books.ts +++ b/platforms/dmm-books.ts @@ -132,6 +132,105 @@ export function DmmBooks({ db, browser }: { db: Database; browser: Browser }) { return ctx; } + async function* getSeriesBooks( + ctx: BrowserContext, + series: { + seriesId: string; + shopName: string; + title: string; + authors: Array; + }, + ): AsyncGenerator { + const endpoint = `https://book.dmm.com/ajax/bff/contents/?shop_name=${series.shopName}&series_id=${series.seriesId}`; + const pager = { + page: 1, + perPage: 0, + totalCount: Infinity, + }; + + while (pager.page * 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: { + volume_books: Array<{ + title: string; + purchased?: { + streaming_url: string; + }; + }>; + pager: { + page: number; + per_page: number; + total_count: number; + }; + } = await res.json(); + + for (const book of body.volume_books.filter((b) => b.purchased)) { + yield { + id: NaN, + platform: "dmm-books", + readerUrl: book.purchased?.streaming_url!, + title: book.title || series.title || "", + authors: series.authors, + }; + + process.stderr.write("."); + } + + pager.page += 1; + pager.perPage = body.pager.per_page; + pager.totalCount = body.pager.total_count; + } + } + + async function* getAllBooks(ctx: BrowserContext): AsyncGenerator { + const endpoint = "https://book.dmm.com/ajax/bff/library/?shop_name=all"; + const pager = { + page: 1, + perPage: 0, + totalCount: Infinity, + }; + + while (pager.page * 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: { + series_books: Array<{ + shop_name: string; + series_id: string; + title: string; + author: Array; + }>; + pager: { + page: number; + per_page: number; + total_count: number; + }; + } = await res.json(); + + for (const series of body.series_books) { + yield* getSeriesBooks(ctx, { + seriesId: series.series_id, + shopName: series.shop_name, + title: series.title, + authors: series.author, + }); + } + + pager.page += 1; + pager.perPage = body.pager.per_page; + pager.totalCount = body.pager.total_count; + } + } + return { async login() { const ctx = await browser.newContext(); @@ -145,12 +244,24 @@ export function DmmBooks({ db, browser }: { db: Database; browser: Browser }) { JSON.stringify(secrets), ); }, + async logout() { await browser.close(); await db.run( `update platforms set secrets = 'null' where name = 'dmm-books'`, ); }, + + async *pull(): AsyncGenerator { + const ctx = await loadBrowserContext(); + + yield* getAllBooks(ctx); + + process.stderr.write(`\n`); + + await browser.close(); + }, + async download(dir: string, book: Book) { const ctx = await loadBrowserContext(); const page = await ctx.newPage();