import chalk from "chalk"; import { define } from "gunshi"; import { articleUrl } from "@/crawlers/fc2"; import { openDb } from "@/db"; import * as priceHistoryQ from "@/db/queries/price-history"; import * as sukebeiMagnetsQ from "@/db/queries/sukebei-magnets"; import * as worksQ from "@/db/queries/works"; import { formatDate, formatDateTime, formatIso } from "@/utils/dates"; import { ExitCodes, exitWith, wrap } from "../envelope"; import { enterJsonMode, isJsonMode, printHuman, printJson } from "../output"; export const infoCommand = define({ name: "info", description: "Show a work's full detail: metadata, price timeline, magnets", args: { id: { type: "string", required: true, description: "FC2 work id" }, db: { type: "string", description: "SQLite DB path (default: ~/.fc2cma/db.sqlite or $FC2CMA_DB)" }, json: { type: "boolean", description: "Emit machine-readable JSON envelope on stdout" } }, run(ctx) { const args = ctx.values; enterJsonMode(Boolean(args.json)); const db = openDb(args.db); const work = worksQ.getWork(db, args.id); if (!work) { db.close(); return exitWith(ExitCodes.USAGE, `info: unknown id ${args.id} (run 'sync' first)`); } const latest = worksQ.latestPrice(db, args.id); const priceTimeline = priceHistoryQ.getTimeline(db, args.id); const magnets = sukebeiMagnetsQ.listMagnetsForWork(db, args.id); const payload = { id: work.id, author: work.author, title: work.title, url: articleUrl(work.id), duration: work.duration, publishedAt: work.publishedAt, firstSeenAt: work.firstSeenAt, lastSeenAt: work.lastSeenAt, latestPrice: latest.priceYen != null && latest.observedAt != null ? { priceYen: latest.priceYen, onPromo: latest.onPromo, observedAt: latest.observedAt } : null, priceTimeline, magnets }; if (isJsonMode()) { printJson(wrap("info", payload)); } else { printHuman(chalk.bold(work.title)); printHuman(chalk.dim(` id: ${work.id} author: ${work.author}`)); printHuman(chalk.dim(` url: ${payload.url}`)); const when = work.publishedAt ?? work.firstSeenAt; printHuman( chalk.dim(` date: ${formatDate(when)}`) + (work.publishedAt == null ? chalk.yellow(" (first_seen fallback)") : "") ); if (payload.latestPrice) { const p = payload.latestPrice; printHuman( ` price: ¥${p.priceYen}${p.onPromo ? chalk.red(" ON SALE") : ""} @ ${formatIso(p.observedAt)}` ); } else { printHuman(chalk.dim(" price: —")); } if (priceTimeline.length > 0) { printHuman(chalk.bold(`\n price timeline (${priceTimeline.length} point(s)):`)); for (const ev of priceTimeline) { const evWhen = formatDateTime(ev.observedAt); const flag = ev.onPromo ? chalk.red(" SALE") : ""; printHuman(` ${chalk.dim(evWhen)} ¥${ev.priceYen}${flag} (${ev.source})`); } } if (magnets.length > 0) { printHuman(chalk.bold(`\n sukebei magnets (${magnets.length}):`)); for (const m of magnets) { const firstSeen = formatDate(m.firstSeenAt); printHuman( ` ${chalk.dim(firstSeen)} ${chalk.magenta(m.magnetId.slice(0, 10))} ${m.fileSize ?? ""} ${m.title ?? ""}` ); } } else { printHuman(chalk.dim("\n sukebei magnets: no results found on sukebei")); } } db.close(); } });