import { parsePartialDate } from "@sttracker/plugin-sdk/partial-date"; import type { EntryFields, SourceRecord, Status, WorkHint } from "@sttracker/plugin-sdk/types"; export interface HcBook { id: number; title: string; release_year?: number | null; contributions?: { author: { name: string } | null }[]; book_series?: { position: number | null; series: { id: number; name: string } }[]; } export interface HcRead { id: number; started_at: string | null; finished_at: string | null; progress_pages: number | null } export interface HcUserBook { id: number; status_id: number | null; rating: number | null; review_raw: string | null; private_notes: string | null; starred: boolean | null; updated_at: string; merged_at: string | null; original_book_id: number | null; book: HcBook; edition: { id: number; isbn_13: string | null; isbn_10: string | null; asin: string | null } | null; user_book_reads: HcRead[]; } export interface HcList { id: number; name: string; description: string | null; list_books: { book: HcBook }[] } export const STATUS: Record = { 1: "planned", 2: "in_progress", 3: "completed", 4: "paused", 5: "dropped", 6: "none" }; export function bookHint(b: HcBook): WorkHint { const creators = (b.contributions ?? []).flatMap((c) => (c.author ? [{ name: c.author.name }] : [])); const h: WorkHint = { title: b.title, mediaType: "book", externalIds: { hardcover_book: String(b.id) } }; if (b.release_year != null) h.year = b.release_year; if (creators.length) h.creators = creators; return h; } function workHint(ub: HcUserBook): WorkHint { const h = bookHint(ub.book); const e = ub.edition; if (e) { h.externalIds.hardcover_edition = String(e.id); if (e.isbn_13) h.externalIds.isbn13 = e.isbn_13; if (e.isbn_10) h.externalIds.isbn10 = e.isbn_10; if (e.asin) h.externalIds.asin = e.asin; } const s = ub.book.book_series?.[0]; if (s) { h.parent = { title: s.series.name, mediaType: "book", externalIds: { hardcover_series: String(s.series.id) } }; if (s.position != null) h.position = { index: s.position }; } return h; } export function* mapUserBook(ub: HcUserBook): Generator { const work = workHint(ub); const entry: EntryFields = { status: STATUS[ub.status_id ?? 6] ?? "none", rating: ub.rating == null ? null : Math.round(ub.rating * 20), review: ub.review_raw, notes: ub.private_notes, favorite: ub.starred === true, }; const key = `ub:${ub.id}`; yield { kind: "entry", key, raw: ub, work, entry }; for (const r of ub.user_book_reads) { yield { kind: "engagement", key: `read:${r.id}`, raw: r, work, engagement: { startedAt: parsePartialDate(r.started_at), finishedAt: parsePartialDate(r.finished_at), ...(r.progress_pages != null ? { progress: { pages: r.progress_pages } } : {}), }, }; } if (ub.merged_at && ub.original_book_id != null && ub.original_book_id !== ub.book.id) { yield { kind: "entry", key: `${key}@book:${ub.original_book_id}`, raw: ub, mergedIntoKey: key, work: { title: ub.book.title, mediaType: "book", externalIds: { hardcover_book: String(ub.original_book_id) } }, entry, }; } } export function mapList(l: HcList): SourceRecord { return { kind: "list", key: `list:${l.id}`, raw: l, list: { name: l.name, ...(l.description ? { description: l.description } : {}), items: l.list_books.map((lb) => ({ work: bookHint(lb.book) })), }, }; }