Files
website/scripts/media-import.ts
T
stan 079399635d
Deploy production / deploy (push) Successful in 1m22s
trackers: media hub with webtoons, podcasts and videos; shows and anime filters
- /trackers/media: one card per child (reading, movies, shows, webtoons, music, podcasts,
  videos) with quick stats, a combined currently-consuming feed and a mixed watchlist
- webtoons, podcasts, videos: new owner-editable trackers on one generic media.items table,
  JSON import (owner form or scripts/media-run.sh)
- shows: /trackers/shows -> /trackers/movies?type=show; anime flag on movies.titles
  (backfilled, set from TMDB, owner toggle) and ?type=anime filter
- nav: media replaces the top-level reading, movies and music entries; their URLs are kept
2026-10-26 23:34:22 +00:00

68 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Import entries into the media trackers' generic children (webtoons, podcasts, videos;
* media.items, /trackers/webtoons|podcasts|videos). Idempotent: rows are keyed by kind +
* normalised title (+ creator for videos); re-running updates the given fields, never duplicates.
* The owner can paste the same JSON on each tracker page (import tile).
*
* On stan-ovh (DB_URL is loaded for you, nothing printed):
* scripts/media-run.sh ~/imports/webtoons.json --kind webtoons
* scripts/media-run.sh ~/imports/media.json # object keyed by kind
* Add --dry-run to parse and print a summary without touching the database.
*
* Format: an array of entries (for --kind, or each with its own "kind"), or an object
* { "webtoons": [...], "podcasts": [...], "videos": [...] }. Entry fields (only title required):
* title, creator | author | host | channel, platform, genre, status, progress | episode | chapter,
* total | total_episodes, series_status (ongoing|completed, webtoons), form (channel|video|playlist,
* videos), url, cover_url (https), rating (0.5–5), who (stan|juhee|both), started, finished
* (YYYY-MM-DD), notes.
* Statuses: webtoons reading|completed|on_hold|dropped|plan; podcasts listening|caught_up|paused|
* dropped|plan; videos watching|following|watched|dropped|plan ("plan to read" etc. map to plan).
*/
import { readFile } from 'node:fs/promises';
import postgres from 'postgres';
import { importRows, parseImport } from '../src/lib/server/media/store';
import { KINDS, MEDIA_KINDS, type MediaKind } from '../src/lib/media/kinds';
const args = process.argv.slice(2);
const flag = (name: string) => {
const i = args.indexOf(name);
return i >= 0 ? args.splice(i, 2)[1] : undefined;
};
const dry = args.includes('--dry-run');
const kindArg = flag('--kind');
const file = args.find((a) => !a.startsWith('--'));
if (!file)
throw new Error(
'usage: media-import.ts <file.json> [--kind webtoons|podcasts|videos] [--dry-run]'
);
const kind: MediaKind | undefined = kindArg
? (MEDIA_KINDS.find((k) => k === kindArg) ??
Object.values(KINDS).find((c) => c.name === kindArg)?.kind)
: undefined;
if (kindArg && !kind) throw new Error(`unknown --kind ${kindArg}`);
const { rows, errors } = parseImport(JSON.parse(await readFile(file, 'utf8')), kind);
const byKind = Object.groupBy(rows, (r) => r.kind);
console.log(
`parsed ${rows.length} entries: ${
Object.entries(byKind)
.map(([k, v]) => `${k} ${v?.length ?? 0}`)
.join(', ') || 'none'
}`
);
if (errors.length) {
for (const e of errors.slice(0, 50)) console.error(` ${e}`);
console.error(`${errors.length} invalid entries; nothing imported`);
process.exit(1);
}
if (dry || !rows.length) process.exit(0);
if (!process.env.DB_URL)
throw new Error('DB_URL is required (use scripts/media-run.sh on stan-ovh)');
const sql = postgres(process.env.DB_URL, { max: 1, onnotice: () => {} });
try {
const r = await importRows(sql, rows);
console.log(`imported ${r.total}: ${r.created} new, ${r.updated} updated`);
} finally {
await sql.end();
}