Skip to content

builtbyproxy/Jellyscribe

Repository files navigation

CI Release codecov GitHub release License: MIT Downloads Fund contributors

Formerly LetterboxdSync. Renamed to Jellyscribe now that it syncs TV shows (via Serializd) alongside films (via Letterboxd), not just Letterboxd. Existing installs update in place automatically, no action needed.

Automatically sync your Jellyfin watch history to your Letterboxd diary (films) and Serializd diary (TV). Titles are logged in real-time when you finish watching, with a daily scheduled sync as a safety net.

Uses Letterboxd's current JSON API (/api/v0/production-log-entries) and Serializd's API.

Jellyscribe dashboard inside the Jellyfin admin UI, showing sync stats and recent activity

Features

Syncing

  • Real-time sync, titles logged to your diary the moment you finish watching
  • Daily catch-up, scheduled task picks up anything missed
  • Multi-user, multi-account, each Jellyfin user can link their own Letterboxd account, Serializd account, or both
  • TMDb matching, films and episodes matched by TMDb ID, so foreign titles and special characters work
  • Duplicate detection, won't log the same title twice on the same day
  • Rewatch detection, real-time playback automatically marks rewatches
  • Date filtering, limit catch-up syncs to recently watched titles

Ratings, reviews & diary

  • Rating sync, both ways, Jellyfin ratings (0-10) map to Letterboxd stars (0.5-5.0) or Serializd's 1-10 scale, and ratings you set on either service seed your Jellyfin user rating back
  • Favorites, sync Jellyfin favorites as Letterboxd likes or Serializd likes
  • Reviews, write and post reviews to Letterboxd (films) or Serializd (shows or individual episodes) from the plugin dashboard
  • Diary import, mark Jellyfin movies or episodes as played if they're already in your Letterboxd or Serializd diary

TV shows → Serializd

Full feature parity with the Letterboxd side: real-time sync, ratings, reviews, favorites, diary import, rewatch detection, watchlist sync, and Seerr auto-request/backfill/mirror all work the same way for Serializd as they do for Letterboxd, just scoped to TV episodes instead of films.

  • TV sync, finished TV episodes are logged to your Serializd watched list in real time, the TV counterpart to the Letterboxd film sync
  • Per-user accounts, each Jellyfin user links their own Serializd account (by email or username), with a Verify login button and passwords encrypted at rest
  • Daily catch-up, a "Sync watched TV to Serializd" scheduled task picks up anything real-time missed, plus a Sync TV Now button
  • TMDb matching, episodes matched by their series' TMDb id + season/episode number
  • Isolated from Letterboxd, films still sync to Letterboxd; a Serializd failure never blocks the Letterboxd path, or vice versa
  • No cookie fallback needed, Serializd's API never needs the Cloudflare cookie workaround Letterboxd sometimes does (see Cloudflare issues below), so TV sync has nothing to babysit

Watchlist & Seerr

  • Watchlist sync, import your Letterboxd or Serializd watchlist as a Jellyfin playlist (Serializd also gets a Jellyfin collection for the shows themselves)
  • Seerr integration, auto-request watchlisted films or shows missing from your library, attributed to the right user; optionally backfill requests for titles that arrived outside Seerr, and mirror your Letterboxd or Serializd watchlist into Seerr

Dashboard & diagnostics

  • Dashboard, sync stats, activity history, and one-click sync from the plugin page
  • Send logs to developer, one-click diagnostic bundle from the Logs tab, with a full preview of what's sent and a reference code to quote in a bug report
  • Cloudflare resilient, automatic retry with backoff on rate limits and transient Letterboxd errors, raw cookie fallback

Install

Plugin repository (recommended)

  1. In Jellyfin, go to Dashboard > Plugins > Repositories
  2. Add the File Transformation repository (required for the sidebar link):
    • Name: File Transformation
    • URL: https://www.iamparadox.dev/jellyfin/plugins/manifest.json
  3. Add the Jellyscribe repository:
    • Name: Jellyscribe
    • URL: https://lbsync-telemetry.lachlanbyoung.workers.dev/manifest.json
  4. Go to Catalog, install File Transformation, then install Jellyscribe
  5. Restart Jellyfin
  6. Hard-refresh the Jellyfin web UI (Ctrl/Cmd + Shift + R) so the new sidebar link loads

Manual install

  1. Install the File Transformation plugin first (see iamparadox27/Jellyfin.Plugin.FileTransformation), required for the sidebar link to appear
  2. Download the latest Jellyscribe ZIP from Releases
  3. Extract Jellyscribe.dll and HtmlAgilityPack.dll to your Jellyfin plugins directory
  4. Restart Jellyfin

Setup

  1. Go to Dashboard > Plugins > Jellyscribe
  2. Switch to the Settings tab
  3. Click + Add Account
  4. Select your Jellyfin user, enter your Letterboxd username and password
  5. Check Enabled
  6. Click Save

That's it. Watch a movie and check your Letterboxd diary.

Adding a Serializd account works the same way, just enter your Serializd email/username and password instead; a Jellyfin user can link a Letterboxd account, a Serializd account, or both.

Settings per account

These apply the same way whether the account is a Letterboxd (film) or Serializd (TV) account, just scoped to the matching media type.

Setting Description
Enabled Master switch for this account; nothing syncs while unchecked, saved settings are kept
Favorites as liked Marks the title as "liked" on Letterboxd or Serializd if favorited in Jellyfin
Recently played only Limits daily catch-up to titles played in the last N days
Primary account When one Jellyfin user links multiple accounts on the same service, the primary wins on rating-import conflicts and is preselected in the review modal
Watchlist to playlist Mirrors your Letterboxd or Serializd watchlist into a Jellyfin playlist daily; each account gets its own playlist (name configurable)
Auto-request via Seerr Watchlisted films or shows missing from your library are requested in Seerr, attributed to this user's Seerr account (set the Seerr URL and API key above the account list)
Backfill available requests Extends auto-request to titles already in the library that have no request record, so titles that arrived outside Seerr still show a requester; never triggers re-downloads
Mirror into Seerr watchlist Two-way mirror of your watchlist into your Seerr user's own watchlist (movies for Letterboxd accounts, TV for Serializd accounts)
Import diary as played Marks Jellyfin movies or episodes as played if they appear in your Letterboxd or Serializd diary
Skip previously synced Uses the plugin's local sync history to skip titles already logged without hitting Letterboxd/Serializd; recommended, especially on large libraries
Stop on failure Halts the run at the first failure to avoid inflaming rate limits; the rest are picked up next run
Raw Cookies For Cloudflare bypass, Letterboxd accounts only, see below

Dashboard

The Dashboard tab shows the same for both Letterboxd and Serializd accounts:

  • Sync statistics (total, synced, rewatches, skipped, failed, requested)
  • Recent activity with links to each title on Letterboxd or Serializd
  • Run Sync Now button to trigger a sync on demand
  • Review buttons to write and post reviews directly to Letterboxd or Serializd

Cloudflare issues

If login fails with a 403 error:

  1. Log into Letterboxd in your browser
  2. Open DevTools (F12) > Network tab
  3. Reload and click any request to letterboxd.com
  4. Copy the Cookie header value (everything after Cookie: , not the label itself)
  5. Paste it into the Raw Cookies field
  6. Copy the User-Agent request header value from the same request and paste it into the User-Agent field

Important: Cloudflare ties cf_clearance to the exact User-Agent that solved the challenge. If you copied cookies from Chrome but leave the User-Agent field blank, the plugin sends the default Firefox UA and Cloudflare will reject the cookie. Always paste the User-Agent from the same browser you copied the cookies from. Leave it blank only if you copied cookies from Firefox 134 on Windows.

Still 403ing after pasting Raw Cookies and a matching User-Agent

When a correctly-copied cookie still gets blocked, it's usually one of these:

  1. Different IP. Cloudflare pins cf_clearance to the IP address that solved the challenge, not just the User-Agent. If the box running Jellyfin reaches the internet via a different public IP than the browser did (different machine, VPN, mobile tether, a server in a datacenter), Cloudflare sees the token arrive from a new IP and rejects it. Fix: paste fresh cookies from a browser running on the same network as the Jellyfin server, and watch out for VPNs or split tunnels.

  2. It expired. cf_clearance from a managed challenge is short-lived, often around 30 minutes. If there's a gap between copying the cookies and the sync actually running, the token can already be dead. Fix: paste fresh cookies and immediately trigger a sync from the plugin dashboard rather than waiting for the scheduled run.

  3. Connection fingerprint. Cloudflare doesn't only check the cookie and UA, it also fingerprints the TLS handshake and HTTP/2 behaviour of the connection. A plugin's HTTP client doesn't look like a real browser at that layer, so on a site running bot-fight mode the right cookie isn't always enough on its own. There isn't much the plugin can do about this one.

If you've ruled all three out and a single film keeps getting stuck on the TMDb lookup, open an issue. A workaround that skips the Cloudflare-protected lookup for that one film (pointing a TMDb ID directly at a Letterboxd slug) is being considered.

Telemetry

The plugin can send anonymous, opt-in usage telemetry. It is off by default, nothing is ever sent unless you enable it (one-time dashboard banner or the Settings checkbox).

When enabled, one small ping is sent per week, plus one extra ping (capped at one per day) when sync errors start occurring so fleet-wide breakage gets caught early. The full payload is exactly this, you can see your own at any time via Settings → Anonymous Telemetry → Preview exact JSON:

{
  "schema_version": 1,
  "instance_id": "8a6f4f6e-1f2b-4c43-9a57-2f0e6f3b9d1c",
  "ping_type": "weekly",
  "plugin_version": "2.1.0.0",
  "jellyfin_version": "10.11.11",
  "features": { "watchlist_sync": true, "diary_import": false, "tv_watchlist_sync": false, "tv_diary_import": false,
                 "...": "booleans of which Letterboxd and Serializd settings are enabled" },
  "buckets": { "accounts": "1", "library": "2k-10k", "syncs_per_week": "1-10", "syncs_ever": "11-100",
               "tv_syncs_per_week": "0", "tv_syncs_ever": "0" },
  "errors": { "cloudflare_403": 0, "auth_failure": 0, "tmdb_lookup": 0, "jellyseerr_error": 0, "rate_limit": 0, "other": 0,
              "state": { "cloudflare_403": false, "...": "which error types are currently occurring" } }
}

The precise promise, worded carefully:

  • No IPs, usernames, film titles, library content, or exact counts ever enter the dataset. Counts are reported in buckets only. (Transport logs at the hosting platform retain caller IPs for the platform's own short retention window, like any HTTPS service; they are never stored in the telemetry dataset.)
  • The instance ID is random, generated when you opt in, never derived from your hardware, network, or Jellyfin install. Regenerate it any time in Settings: future pings get a fresh identity. Old rows remain (unlinked going forward); at small fleet sizes configuration similarity could in principle still allow correlation, so the honest claim is "unlinked", not "erased".
  • The "Preview exact JSON" modal doubles as a diagnostic bundle for bug reports. It contains your instance ID, pasting it into a public issue links that ID to your past pings, which is why the modal offers Copy + regenerate ID.
  • Disabling telemetry stops all pings immediately.

What it's for: deciding what gets built next based on what people actually use, and an automated canary that compares error rates across releases and files regression issues before bug reports arrive.

Install counting (separate from the opt-in telemetry)

The recommended plugin repository URL and the release downloads are served through an edge-cached mirror of the GitHub manifest. The mirror counts each request as an anonymous, weekly-rotating hash of the caller's IP so the project can estimate how many servers run the plugin. This is a plain traffic count, not the telemetry above: no instance ID, no settings, no versions beyond the release being downloaded, and the raw IP is never stored, the hash is salted and cannot be linked across weeks by design.

Since v1.19.0 the plugin also adds the mirror as a second catalog repository entry (named "... (mirror)") alongside your existing GitHub entry, once. Your GitHub entry is never removed, so updates keep working even if the mirror is unreachable. If you prefer not to be counted, delete the mirror entry, the plugin will not re-add it, and both entries serve the identical manifest.

Send logs to the developer

When something goes wrong, the Logs tab has a Send logs to developer button. It packages the recent Jellyscribe log lines shown on that tab (passwords, cookies, and auth tokens are never logged) plus an anonymous telemetry snapshot, uploads them privately, and gives you a short reference code (e.g. LBX-7Q2F9K) to quote if you open a bug report.

Unlike the anonymous telemetry above, logs are not anonymous, they can contain your Letterboxd username or film titles, and the bundle is linked to your telemetry instance ID. So it is strictly opt-in per use: a confirmation step spells this out, lets you add a note describing the problem, and offers a preview of exactly what is sent before anything leaves your server. Works whether or not telemetry is enabled. Uploaded bundles are stored privately and auto-deleted after 90 days.

Requirements

  • Jellyfin 10.11+ (current releases are also known to run on the Jellyfin 12.0 release candidates; official 12.0 support will be declared once 12.0 stable ships and passes verification, see openspec/changes/add-jellyfin-12-support/)
    • Migrating your server to Jellyfin 12? It is safe to follow Jellyfin's advice and remove the plugin first: your accounts, settings, and sync history all survive a reinstall from the catalog.
  • A Letterboxd and/or Serializd account
  • File Transformation plugin, required for the Jellyscribe link to appear in the Jellyfin sidebar (everything else works without it)
  • Optional: a Seerr instance for the auto-request and watchlist-mirror integrations

Building from source

git clone https://github.com/builtbyproxy/Jellyscribe.git
cd Jellyscribe
dotnet build -c Release

Output Jellyscribe.dll is in LetterboxdSync/bin/Release/net9.0/.

Contributing

PRs welcome. A few conventions:

  • PR body shape lives in .github/pull_request_template.md. Symptom first, plain English, six fixed sections (What's broken, Why it happens, What this PR does, How to test, Follow-ups).
  • Non-trivial changes are planned through openspec/ before implementation: proposal, design, specs, then tasks. See openspec/changes/ for active proposals and the archive/ folder for past ones.

License

MIT

About

Jellyfin plugin that syncs watch history to Letterboxd diary in real-time

Resources

License

Code of conduct

Security policy

Stars

71 stars

Watchers

0 watching

Forks

Sponsor this project

Packages

 
 
 

Contributors