docs(readme): split user and build tracks; fix release for BRAT

README interleaved plugin usage with build steps, so a user wanting only
the plugin had to read past clone/bun/build. Now two tracks: install,
config, commands, settings, note schema, issues first; build and dev
after a divider.

Also adds the standalone note (fork carries the whole upstream plugin,
stock one not needed; keys and settings are not shared) and splits the
bun requirement into tests (hard, bun:test + mock.module) vs build
(tsc + vite, node-capable).

release.yml: no tag was ever pushed, so BRAT had nothing to install.
Two defects fixed:
- prerelease flag only tripped on 'canary', so a beta tag would have
  published as stable. Now any semver prerelease tag (contains '-').
- release shipped manifest.json 0.1.0 while BRAT resolves the tag from
  manifest-beta.json 0.1.0-beta.1. Version mismatch makes BRAT
  re-download every check. Prerelease tags now ship manifest-beta.json
  as manifest.json.
This commit is contained in:
afiqzudinhadi 2026-08-06 01:30:31 +08:00
parent 18d90e3c1a
commit 7045a2404c
2 changed files with 262 additions and 190 deletions

View file

@ -25,7 +25,8 @@ jobs:
- name: Determine prerelease status - name: Determine prerelease status
id: status id: status
run: | run: |
if [[ "${{ github.ref }}" == *"canary"* ]]; then # Any semver prerelease tag (0.1.0-beta.1, 0.8.0-canary.x, 1.0.0-rc.1) ships as a prerelease.
if [[ "${{ github.ref_name }}" == *"-"* ]]; then
echo "prerelease=true" >> $GITHUB_OUTPUT echo "prerelease=true" >> $GITHUB_OUTPUT
else else
echo "prerelease=false" >> $GITHUB_OUTPUT echo "prerelease=false" >> $GITHUB_OUTPUT
@ -38,9 +39,17 @@ jobs:
- name: Build - name: Build
id: build id: build
env:
TAG: ${{ github.ref_name }}
run: | run: |
bun install --frozen-lockfile bun install --frozen-lockfile
bun run build bun run build
# BRAT resolves the tag from manifest-beta.json, then installs the manifest.json
# shipped in that release. On a prerelease tag the two must agree, or BRAT reads
# back a different version than it installed and re-downloads forever.
if [[ "$TAG" == *"-"* ]]; then
cp manifest-beta.json dist/manifest.json
fi
mkdir ${{ env.PLUGIN_NAME }} mkdir ${{ env.PLUGIN_NAME }}
cp dist/main.js dist/manifest.json dist/styles.css ${{ env.PLUGIN_NAME }} cp dist/main.js dist/manifest.json dist/styles.css ${{ env.PLUGIN_NAME }}
zip -r ${{ env.PLUGIN_NAME }}-${{ github.ref_name }}.zip ${{ env.PLUGIN_NAME }} zip -r ${{ env.PLUGIN_NAME }}-${{ github.ref_name }}.zip ${{ env.PLUGIN_NAME }}

441
README.md
View file

@ -4,12 +4,22 @@ An Obsidian plugin that fills in media metadata, re-syncs it on a schedule, and
The stock plugin writes a note once, at import time, and never touches it again. This fork adds the part that runs afterwards. The stock plugin writes a note once, at import time, and never touches it again. This fork adds the part that runs afterwards.
This is a complete plugin, not an add-on. The stock Media DB plugin does not need to be installed. See [Relationship to the Stock Plugin](#relationship-to-the-stock-plugin).
# Table of Contents # Table of Contents
- [Media DB Sync](#media-db-sync) **Using the plugin**
- [Table of Contents](#table-of-contents)
- [What This Fork Adds](#what-this-fork-adds) - [What This Fork Adds](#what-this-fork-adds)
- [Plugin Info](#plugin-info) - [Relationship to the Stock Plugin](#relationship-to-the-stock-plugin)
- [Install](#install)
- [Requirements](#requirements)
- [Option A: BRAT](#option-a-brat)
- [Option B: Copy the files in by hand](#option-b-copy-the-files-in-by-hand)
- [Configure](#configure)
- [First sync](#first-sync)
- [Commands](#commands)
- [Settings](#settings)
- [Watchlist Notes](#watchlist-notes) - [Watchlist Notes](#watchlist-notes)
- [Frontmatter written on every sync](#frontmatter-written-on-every-sync) - [Frontmatter written on every sync](#frontmatter-written-on-every-sync)
- [Fields the sync never overwrites](#fields-the-sync-never-overwrites) - [Fields the sync never overwrites](#fields-the-sync-never-overwrites)
@ -17,56 +27,151 @@ The stock plugin writes a note once, at import time, and never touches it again.
- [The watch-status rule](#the-watch-status-rule) - [The watch-status rule](#the-watch-status-rule)
- [Anime detection](#anime-detection) - [Anime detection](#anime-detection)
- [Library Types](#library-types) - [Library Types](#library-types)
- [Commands](#commands)
- [Settings](#settings)
- [Related Articles](#related-articles)
- [Pre-requisites](#pre-requisites)
- [Clone Repository](#clone-repository)
- [Set Up Build Environment](#set-up-build-environment)
- [1. Install Bun](#1-install-bun)
- [2. Install Dependencies](#2-install-dependencies)
- [3. Build](#3-build)
- [4. Run the Checks](#4-run-the-checks)
- [Install Into Obsidian](#install-into-obsidian)
- [Option A: BRAT](#option-a-brat)
- [Option B: Copy the Build](#option-b-copy-the-build)
- [Configure](#configure)
- [Development](#development)
- [Live Build Into a Test Vault](#live-build-into-a-test-vault)
- [Tests](#tests)
- [Issues](#issues) - [Issues](#issues)
- [TMDB API key not configured](#tmdb-api-key-not-configured) - [TMDB API key not configured](#tmdb-api-key-not-configured)
- [Watchlist sync already running](#watchlist-sync-already-running) - [Watchlist sync already running](#watchlist-sync-already-running)
- [Notes skipped for having no id](#notes-skipped-for-having-no-id) - [Notes skipped for having no id](#notes-skipped-for-having-no-id)
- [TMDB works in Obsidian but not from a script](#tmdb-works-in-obsidian-but-not-from-a-script) - [TMDB works in Obsidian but not from a script](#tmdb-works-in-obsidian-but-not-from-a-script)
**Building it yourself**
- [Build From Source](#build-from-source)
- [Requirements](#requirements-1)
- [1. Clone](#1-clone)
- [2. Install Bun](#2-install-bun)
- [3. Install dependencies](#3-install-dependencies)
- [4. Build](#4-build)
- [Development](#development)
- [Live build into a test vault](#live-build-into-a-test-vault)
- [Tests and checks](#tests-and-checks)
- [Why Bun](#why-bun)
- [Pulling from upstream](#pulling-from-upstream)
- [Plugin Info](#plugin-info)
- [Related Articles](#related-articles)
- [License and Credit](#license-and-credit) - [License and Credit](#license-and-credit)
# What This Fork Adds # What This Fork Adds
| Capability | Stock Media DB | This fork | | Capability | Stock Media DB | This fork |
| ----------------------------------------------------------------------------- | ------------------------------- | ------------------------------------------------ | | ------------------------------------------------------------------------- | -------------------------- | --------------------------------------------------- |
| Import a title from an API | Yes | Yes, unchanged | | Import a title from an API | Yes | Yes, unchanged |
| Re-sync an existing note later | Manual, one note at a time | Scheduled, across the whole folder | | Re-sync an existing note later | Manual, one note at a time | Scheduled, across the whole folder |
| `language`, `country`, `imdb_id`, `content_rating`, `trailer`, `homepage` | Left blank on TMDB imports | Filled | | `language`, `country`, `imdb_id`, `content_rating`, `trailer`, `homepage` | Left blank on TMDB imports | Filled |
| `producer` | Writes `studio` instead | Individual producers | | `producer` | Writes `studio` instead | Individual producers |
| Episode tracking | No | `last_episode`, `upcoming_episode`, `next_air_date` | | Episode tracking | No | `last_episode`, `upcoming_episode`, `next_air_date` |
| Watch-status automation | No | `Watched` flips to `Unwatched` on a new episode | | Watch-status automation | No | `Watched` flips to `Unwatched` on a new episode |
| Backfilling ids onto notes that predate the plugin | No | `Watchlist: resolve missing TMDB ids` | | Backfilling ids onto notes that predate the plugin | No | `Watchlist: resolve missing TMDB ids` |
| Manga, book, game, comic upkeep | Import only | Scheduled sync per type | | Manga, book, game, comic upkeep | Import only | Scheduled sync per type |
# Plugin Info # Relationship to the Stock Plugin
| Property | Value | This fork carries the entire upstream plugin: all 16 API adapters, the search and ID modals, the ribbon icon, templates, and property mapping. Nothing in `src/` references the stock plugin, so it runs on its own.
| ------------------------- | ----------------------------------------------------------------------------------------- |
| Plugin ID | `media-db-sync` | It uses the plugin id `media-db-sync` rather than `obsidian-media-db`, which means it can also sit alongside the stock plugin without either one touching the other's folder or `data.json`. Two consequences if you are switching over:
| Display name | Media DB Sync |
| Minimum Obsidian version | 1.12.0 | | Thing | What happens |
| Desktop only | No, runs on mobile | | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| License | GPL-3.0 | | API keys | Not shared. Each plugin keeps its own keychain reference. Paste the key again here. |
| Upstream | [mProjectsCode/obsidian-media-db-plugin](https://github.com/mProjectsCode/obsidian-media-db-plugin) | | Settings: folders, templates, property mappings | Not inherited. Set them again. |
| Toolchain | Bun + Vite | | Notes the stock plugin already wrote | Read fine. Anything with a `tmdb_id` syncs straight away; the rest go through `Watchlist: resolve missing TMDB ids` or local canonical conversion. |
| Build output | `dist/main.js`, `dist/manifest.json`, `dist/styles.css` |
| Network calls | Obsidian `requestUrl()` only, never node `fetch` | # Install
### Requirements
- Obsidian 1.12.0 or newer, desktop or mobile
- A TMDB API key or read access token, from [your TMDB API settings](https://www.themoviedb.org/settings/api)
RAWG and Comic Vine keys are needed only for the game and comic library types.
### Option A: BRAT
The plugin is not in Obsidian's community plugin browser, so [BRAT](https://github.com/TfTHacker/obsidian42-brat) is the way to install and keep it updated.
1. Install BRAT from the community plugin browser and enable it.
2. Run `BRAT: Add a beta plugin for testing`.
3. Enter `afiqzudinhadi/obsidian-media-db-sync`.
4. Leave "Enable after installing" checked.
Releases are cut as prereleases, and BRAT tracks the version in `manifest-beta.json`, so `BRAT: Check for updates to all beta plugins` picks up new builds.
### Option B: Copy the files in by hand
Download `main.js`, `manifest.json`, and `styles.css` from the [latest release](https://github.com/afiqzudinhadi/obsidian-media-db-sync/releases), or produce them yourself with [Build From Source](#build-from-source). Then:
```bash
mkdir -p /path/to/vault/.obsidian/plugins/media-db-sync
cp main.js manifest.json styles.css /path/to/vault/.obsidian/plugins/media-db-sync/
```
The folder structure ends up as:
```
[vault]
|_ .obsidian
|_ plugins
|_ media-db-sync
|_ main.js
|_ manifest.json
|_ styles.css
```
Reload Obsidian, then enable **Media DB Sync** under Settings, Community plugins.
### Configure
1. Paste the TMDB key into Settings, Media DB Sync.
2. Turn on **Enable watchlist sync** and point it at your folder.
3. Enable any library types you want, and set their folders.
### First sync
Run `Watchlist: dry-run full sync` and read the console output. It reports what it would change and writes nothing.
If the diff looks right, run `Watchlist: full sync`. If the vault is under git, commit it first.
# Commands
Inherited from upstream, unchanged:
| Command |
| ---------------------------------------------- |
| Create entry |
| Create entry (per media type) |
| Create entry (advanced search) |
| Create entry by id |
| Update open note (this will recreate the note) |
| Update metadata |
| Insert link |
Added here:
| Command | What it does |
| ---------------------------------------------------- | ----------------------------------------------------- |
| `Watchlist: sync now (airing/active only)` | Syncs active notes only |
| `Watchlist: full sync (all entries)` | Syncs every note in the folder |
| `Watchlist: dry-run full sync (log only, no writes)` | Walks the full sync and logs the diff without writing |
| `Watchlist: resolve missing TMDB ids` | Searches TMDB for notes that have no `tmdb_id` |
| `Library: sync <type> now` | One type |
| `Library: resolve <type> ids` | Backfills ids for one type |
| `Library: dry-run <type> full sync` | Log only |
| `Library: sync all` | Every enabled type |
# Settings
Under **Watchlist sync**:
| Setting | Default | Notes |
| --------------------- | ----------- | ---------------- |
| Enable watchlist sync | Off | |
| Watchlist folder | `Watchlist` | |
| Sync interval (hours) | `24` | Accepts 1 to 168 |
Under **Library sync**, per type: an enable toggle and a folder path. All four types run on the watchlist's interval.
The scheduler is catch-up based rather than a wall-clock timer. On load it checks whether `lastSync + interval` is already in the past and runs if so, so a laptop that was asleep at the scheduled hour still syncs when it wakes.
API keys are held in the Obsidian keychain, not in `data.json`. TMDB takes either credential: a token starting with `eyJ` is sent as `Authorization: Bearer`, anything else is sent as `?api_key=`.
# Watchlist Notes # Watchlist Notes
@ -136,7 +241,7 @@ This is why the blank `language` field on stock TMDB imports matters. Without it
# Library Types # Library Types
Each type is a separate folder with its own toggle. All four run on the watchlist's sync interval. Each type is a separate folder with its own toggle.
| Type | Default folder | Data sources | API key needed | | Type | Default folder | Data sources | API key needed |
| ----- | -------------- | ------------------------------------------------------------- | -------------- | | ----- | -------------- | ------------------------------------------------------------- | -------------- |
@ -149,152 +254,6 @@ When a resolve pass turns up several plausible matches for one note, it opens a
Notes that resolve to nothing still get converted into the canonical shape locally, with no API calls, so they appear in Bases views rather than staying invisible. Notes that resolve to nothing still get converted into the canonical shape locally, with no API calls, so they appear in Bases views rather than staying invisible.
# Commands
Inherited from upstream, unchanged:
| Command |
| ------------------------------------------------ |
| Create entry |
| Create entry (per media type) |
| Create entry (advanced search) |
| Create entry by id |
| Update open note (this will recreate the note) |
| Update metadata |
| Insert link |
Added here:
| Command | What it does |
| ------------------------------------------------------ | --------------------------------------------------------------- |
| `Watchlist: sync now (airing/active only)` | Syncs active notes only |
| `Watchlist: full sync (all entries)` | Syncs every note in the folder |
| `Watchlist: dry-run full sync (log only, no writes)` | Walks the full sync and logs the diff without writing |
| `Watchlist: resolve missing TMDB ids` | Searches TMDB for notes that have no `tmdb_id` |
| `Library: sync <type> now` | One type |
| `Library: resolve <type> ids` | Backfills ids for one type |
| `Library: dry-run <type> full sync` | Log only |
| `Library: sync all` | Every enabled type |
Run the dry-run against a real folder before the first live sync. If the vault is under git, commit it first.
# Settings
Under **Watchlist sync**:
| Setting | Default | Notes |
| ---------------------- | ----------- | ---------------- |
| Enable watchlist sync | Off | |
| Watchlist folder | `Watchlist` | |
| Sync interval (hours) | `24` | Accepts 1 to 168 |
Under **Library sync**, per type: an enable toggle and a folder path.
The scheduler is catch-up based rather than a wall-clock timer. On load it checks whether `lastSync + interval` is already in the past and runs if so, so a laptop that was asleep at the scheduled hour still syncs when it wakes.
API keys are held in the Obsidian keychain, not in `data.json`. TMDB takes either credential: a token starting with `eyJ` is sent as `Authorization: Bearer`, anything else is sent as `?api_key=`.
# Related Articles
- [Upstream Media DB README](https://github.com/mProjectsCode/obsidian-media-db-plugin/blob/master/README.md), for the import features, templates, property mappings, and the per-API "search by ID" reference
- [TMDB API docs](https://developer.themoviedb.org/reference/intro/getting-started)
- [BRAT](https://github.com/TfTHacker/obsidian42-brat), for installing a plugin straight from a repo
- [Obsidian Bases](https://help.obsidian.md/bases), which is what the `watchlist_item` shape is written for
# Pre-requisites
- Obsidian 1.12.0 or newer
- A TMDB API key or read access token, from [your TMDB API settings](https://www.themoviedb.org/settings/api)
- [Bun](https://bun.sh/) 1.x, if you are building from source
- Git
# Clone Repository
```bash
git clone git@github.com:afiqzudinhadi/obsidian-media-db-sync.git
cd obsidian-media-db-sync
```
The upstream remote is kept, so changes from Moritz Jung's plugin can be pulled in:
```bash
git fetch upstream
```
# Set Up Build Environment
### 1. Install Bun
```bash
curl -fsSL https://bun.sh/install | bash
```
This project uses Bun and Vite. It does not use npm or esbuild; older notes that say otherwise are stale.
### 2. Install Dependencies
```bash
bun install
```
### 3. Build
```bash
bun run build
```
Type-checks first, then writes `main.js`, `manifest.json`, and `styles.css` into `dist/`.
### 4. Run the Checks
```bash
bun run check
```
Formatter check, both type-check passes, ESLint at zero warnings, then the test suite.
# Install Into Obsidian
### Option A: BRAT
Install BRAT, then add `afiqzudinhadi/obsidian-media-db-sync` as a beta plugin. BRAT reads `manifest-beta.json` and updates from releases.
### Option B: Copy the Build
```bash
mkdir -p /path/to/vault/.obsidian/plugins/media-db-sync
cp dist/main.js dist/manifest.json dist/styles.css /path/to/vault/.obsidian/plugins/media-db-sync/
```
Reload Obsidian, then enable **Media DB Sync** under Settings, Community plugins.
The plugin id is `media-db-sync`, not `obsidian-media-db`, so this installs alongside the stock plugin and neither one overwrites the other's folder or `data.json`.
### Configure
1. Paste the TMDB key into Settings, Media DB Sync.
2. Turn on **Enable watchlist sync** and set the folder.
3. Run `Watchlist: dry-run full sync` and read the console output.
4. If the diff looks right, run `Watchlist: full sync`.
# Development
### Live Build Into a Test Vault
```bash
bun run dev
```
In development mode the output goes to `exampleVault/.obsidian/plugins/media-db-sync/` instead of `dist/`, and rebuilds on save.
### Tests
```bash
bun test --preload ./tests/setup.ts
```
Use `bun run test:log` to see the sync engine's log lines. The suite covers the serializer, YAML quoting, the diff-on-write path, the candidate picker loop, and the watch-status rule.
# Issues # Issues
### TMDB API key not configured ### TMDB API key not configured
@ -325,6 +284,110 @@ nodename nor servname provided, or not known
Some ISP DNS resolvers return nothing for TMDB's API host, which breaks `curl` and any Python or Node script on that connection. Obsidian's `requestUrl()` goes through Chromium's network stack and resolves it fine. That is why every network call here goes through `requestUrl()`, and why an external cron script is not a working substitute on such a connection. Some ISP DNS resolvers return nothing for TMDB's API host, which breaks `curl` and any Python or Node script on that connection. Obsidian's `requestUrl()` goes through Chromium's network stack and resolves it fine. That is why every network call here goes through `requestUrl()`, and why an external cron script is not a working substitute on such a connection.
---
Everything below is for building and modifying the plugin. Skip it if you only want to use it.
# Build From Source
### Requirements
- [Bun](https://bun.sh/) 1.x
- Git
### 1. Clone
```bash
git clone git@github.com:afiqzudinhadi/obsidian-media-db-sync.git
cd obsidian-media-db-sync
```
### 2. Install Bun
```bash
curl -fsSL https://bun.sh/install | bash
```
### 3. Install dependencies
```bash
bun install
```
### 4. Build
```bash
bun run build
```
Type-checks, then writes `main.js`, `manifest.json`, and `styles.css` into `dist/`. Those are the three files the [manual install](#option-b-copy-the-files-in-by-hand) step copies into a vault.
### Cutting a release
Pushing a tag triggers `.github/workflows/release.yml`, which builds and attaches those three files plus a zip to a GitHub release. A tag containing a hyphen is treated as a semver prerelease: it is published with the prerelease flag, and `manifest-beta.json` is shipped in place of `manifest.json` so the installed version matches the tag BRAT resolved.
```bash
git tag 0.1.0-beta.2
git push origin 0.1.0-beta.2
```
Bump the version in `manifest-beta.json` first, and keep it equal to the tag.
# Development
### Live build into a test vault
```bash
bun run dev
```
In development mode the output goes to `exampleVault/.obsidian/plugins/media-db-sync/` instead of `dist/`, and rebuilds on save.
### Tests and checks
```bash
bun test --preload ./tests/setup.ts # or: bun run test
bun run check # format check, typecheck, lint at zero warnings, tests
```
`bun run test:log` adds the sync engine's log lines. The suite covers the serializer, YAML quoting, the diff-on-write path, the candidate picker loop, and the watch-status rule.
### Why Bun
The toolchain comes from upstream; the `scripts` block is unchanged from Moritz Jung's plugin. It matters in two different degrees:
- **Tests need Bun.** All 25 test files import from `bun:test`, and `tests/setup.ts` calls `mock.module('obsidian', ...)` to stub the Obsidian API, which has no runtime outside the app. Another runner means rewriting the imports and that mock layer.
- **The build does not, strictly.** It is `tsc` plus `vite`, both plain node tools; only the script string hardcodes `bun run tsc`. Note that `bun.lock` is the only committed lockfile, so `npm install` resolves fresh rather than reproducing the locked tree.
### Pulling from upstream
The upstream remote is kept:
```bash
git fetch upstream
```
# Plugin Info
| Property | Value |
| ------------------------ | ----------------------------------------------------------------------------------------------------- |
| Plugin ID | `media-db-sync` |
| Display name | Media DB Sync |
| Minimum Obsidian version | 1.12.0 |
| Desktop only | No, runs on mobile |
| License | GPL-3.0 |
| Upstream | [mProjectsCode/obsidian-media-db-plugin](https://github.com/mProjectsCode/obsidian-media-db-plugin) |
| Toolchain | Bun + Vite |
| Build output | `dist/main.js`, `dist/manifest.json`, `dist/styles.css` |
| Network calls | Obsidian `requestUrl()` only, never node `fetch` |
# Related Articles
- [Upstream Media DB README](https://github.com/mProjectsCode/obsidian-media-db-plugin/blob/master/README.md), for the import features, templates, property mappings, and the per-API "search by ID" reference
- [TMDB API docs](https://developer.themoviedb.org/reference/intro/getting-started)
- [BRAT](https://github.com/TfTHacker/obsidian42-brat), for installing a plugin straight from a repo
- [Obsidian Bases](https://help.obsidian.md/bases), which is what the `watchlist_item` shape is written for
# License and Credit # License and Credit
GPL-3.0, inherited from upstream. The import pipeline, API adapters, modals, and property mapping are Moritz Jung's work: see [mProjectsCode/obsidian-media-db-plugin](https://github.com/mProjectsCode/obsidian-media-db-plugin). The sync engine, watchlist schema, library specs, and resolve flow are added in this fork. GPL-3.0, inherited from upstream. The import pipeline, API adapters, modals, and property mapping are Moritz Jung's work: see [mProjectsCode/obsidian-media-db-plugin](https://github.com/mProjectsCode/obsidian-media-db-plugin). The sync engine, watchlist schema, library specs, and resolve flow are added in this fork.