Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

letterboxd-seerr-plex-sync

Letterboxd → Seerr local watchlist + Marathon Man Plex watchlist on download.

Automation in bin/ is ~2k lines of Python/shell (standalone; not the Seerr application).

Install: clone this repo and symlink or copy bin/ into your Seerr install (e.g. ~/.config/seerr/bin/) and config/*.env.example into ~/.config/seerr/config/.

Overview

Phase Destination Trigger
Browse / request benmo Seerr local watchlist (SEERR_USER_ID=3) Letterboxd sync cron (30s)
Playback queue Plex Home user Marathon Man watchlist Seerr Media Available webhook

Letterboxd sync never touches Plex. The webhook is add-only on Marathon Man (never removeFromWatchlist).

Setup

cd ~/.config/seerr
python3 -m venv venv
./venv/bin/pip install -r bin/requirements-letterboxd-plex.txt
chmod 600 config/letterboxd-sync.env config/plex-watchlist-hook.env
chmod +x bin/*.sh bin/*.py

Copy examples if env files are missing:

cp config/letterboxd-sync.env.example config/letterboxd-sync.env
cp config/plex-watchlist-hook.env.example config/plex-watchlist-hook.env
# Edit both; API key from config/settings.json → main.apiKey

Letterboxd backfill (once)

Preferred: export watchlist.csv from Letterboxd → save as config/letterboxd-watchlist.csv:

~/.config/seerr/venv/bin/python3 bin/letterboxd-seerr-watchlist-backfill.py --csv config/letterboxd-watchlist.csv

Alternatives:

  • --json config/letterboxd-watchlist-scrape.json (off-box scrape)
  • --scrape ben__hyman (often 403 on Whatbox / Cloudflare)

Incremental sync (cron)

bin/letterboxd-seerr-watchlist-sync.sh sources config/letterboxd-sync.env, uses flock, runs the Python sync.

On Whatbox, direct Letterboxd RSS and public RSSHub instances usually return 403/503 (Cloudflare). No laptop required: refresh the snapshot on-box with HTML scrape (browser user-agent).

Script Where Purpose
bin/letterboxd-fetch-rss-snapshot-whatbox.sh Whatbox Refresh letterboxd-rss-snapshot.json (proxy RSS → direct RSS → scrape)
bin/letterboxd-fetch-rss-snapshot.py Any host Same fetch logic; -o path from env
bin/letterboxd-seerr-watchlist-sync.sh Whatbox Every 30s: read snapshot, sync to Seerr (no RSS if LETTERBOXD_SNAPSHOT_JSON is set)
bin/letterboxd-push-snapshot.sh Optional laptop Legacy off-box fetch + rsync (zero-laptop setup ignores this)
bin/letterboxd-seerr-diary-remove.sh Whatbox Every 5m: diary RSS → remove benmo watchlist for new watches

Logs: config/logs/letterboxd-sync.log, config/logs/letterboxd-fetch.log

Whatbox snapshot (zero laptop)

Env (config/letterboxd-sync.env):

LETTERBOXD_SNAPSHOT_JSON=/mnt/mpathe/ahhhhhhh/.config/seerr/config/letterboxd-rss-snapshot.json
LETTERBOXD_RSS_URL=https://letterboxd.com/ben__hyman/rss/watchlist/
# Optional if a proxy works from your network:
# LETTERBOXD_RSS_PROXY_URL=https://rsshub.app/letterboxd/watchlist/{username}

While LETTERBOXD_SNAPSHOT_JSON is set, sync never calls Letterboxd RSS. Fetch order: proxy URL (if set) → direct RSS → HTML scrape.

Optional freshness for sync only:

  • LETTERBOXD_SNAPSHOT_MAX_AGE_MINUTES=180 — skip sync if file older than 3h
  • LETTERBOXD_SNAPSHOT_ALWAYS=1 — use snapshot regardless of age

Manual refresh

/mnt/mpathe/ahhhhhhh/.config/seerr/bin/letterboxd-fetch-rss-snapshot-whatbox.sh

Fetch only

./venv/bin/python3 bin/letterboxd-fetch-rss-snapshot.py -o config/letterboxd-rss-snapshot.json

JSON format: array of objects with slug, title, year, link (or {"items":[...]} with --wrap).

Watchlist removal — Letterboxd diary RSS → Seerr (every 5m)

Letterboxd has no webhooks. “Constant listening” means polling; on Whatbox use cron (not true push). Diary RSS is small (latest ~50 watches), so every 5 minutes is reasonable. The 30s watchlist sync is separate (adds from snapshot); diary removal is cheap and does not need 30s unless you want it — match sync with flock if you run both often.

bin/letterboxd-seerr-diary-remove.sh polls LETTERBOXD_DIARY_RSS_URL (default https://letterboxd.com/ben__hyman/rss/ — main activity/diary feed, browser UA like watchlist fetch). New RSS guids since last poll → TMDB → DELETE /api/v1/watchlist/{tmdbId}?mediaType=movie (X-Api-Key, X-API-User: 3).

Control Meaning
LETTERBOXD_DIARY_RSS_URL Diary/activity RSS, e.g. https://letterboxd.com/ben__hyman/rss/
LETTERBOXD_DIARY_STATE_FILE Optional; default config/letterboxd-diary-state.json (seen_guids)

Logs: config/logs/letterboxd-diary-remove.log

Safety

  • Feed parse failure: no deletes
  • Empty feed on first run (no state): no deletes, no state write
  • First successful run: records all current guids as baseline — no mass delete
  • Later runs: only guids not in seen_guids trigger removal

Dry run

./venv/bin/python3 bin/letterboxd-seerr-diary-remove.py --dry-run

Cron (Whatbox)

@reboot /mnt/mpathe/ahhhhhhh/.config/seerr/bin/letterboxd-seerr-diary-remove.sh >>/mnt/mpathe/ahhhhhhh/.config/seerr/config/logs/letterboxd-diary-remove.log 2>&1
*/5 * * * * /mnt/mpathe/ahhhhhhh/.config/seerr/bin/letterboxd-seerr-diary-remove.sh >>/mnt/mpathe/ahhhhhhh/.config/seerr/config/logs/letterboxd-diary-remove.log 2>&1

Watchlist removal — snapshot sync (left watchlist)

When a film leaves the Letterboxd watchlist, incremental sync can DELETE it from benmo’s Seerr watchlist (SEERR_USER_ID=3) via DELETE /api/v1/watchlist/{tmdbId}?mediaType=movie.

Control Default Meaning
LETTERBOXD_SYNC_REMOVE on (1) Set 0 to disable removal pass
LETTERBOXD_SNAPSHOT_TRUST_FULL off Set 1 when the snapshot is a full watchlist (Whatbox scrape, CSV backfill, or trusted full fetch). Required for safe removals if the file might be RSS-truncated

State (config/letterboxd-sync-state.json) is the source of truth for Letterboxd-managed items only:

  • synced: { "slug": tmdbId, ... } (plus legacy synced_slugs / synced_tmdb_ids)
  • Removals run only for slugs in synced that are not in the current snapshot slug set
  • Manual Seerr watchlist adds are not removed unless they were recorded in synced by this sync/backfill

Safety

  • Empty snapshot or failed fetch: no removal pass (avoids mass-delete)
  • If snapshot has fewer slugs than synced and LETTERBOXD_SNAPSHOT_TRUST_FULL is unset, removals are skipped (RSS/proxy feeds are often truncated)
  • Prefer letterboxd-fetch-rss-snapshot-whatbox.sh (scrape fallback) + LETTERBOXD_SNAPSHOT_TRUST_FULL=1 on Whatbox

Dry run

./venv/bin/python3 bin/letterboxd-seerr-watchlist-sync.py --dry-run

Legacy: laptop push (optional)

Only if on-box scrape stops working. Standard Whatbox setup uses letterboxd-fetch-rss-snapshot-whatbox.sh only. bin/letterboxd-push-snapshot.sh needs WHATBOX_HOST from a laptop that can reach Letterboxd RSS.

Plex webhook service

bin/plex-watchlist-webhook-control.sh start
bin/plex-watchlist-webhook-control.sh is-running

Ensure script (cron @reboot + every minute):

bin/plex-watchlist-webhook-ensure.sh

Logs: config/logs/plex-watchlist-webhook.log

Seerr UI

  1. Settings → Notifications → Webhook → enable
  2. URL: http://127.0.0.1:27861/
  3. Enable Media Available only (disable other types)
  4. Test with curl (see below)

Curl test

curl -sS -X POST http://127.0.0.1:27861/ \
  -H 'Content-Type: application/json' \
  -d '{
    "notification_type": "MEDIA_AVAILABLE",
    "media_tmdbid": 550,
    "media_type": "movie",
    "requestedBy_email": "benmo.hyman@gmail.com",
    "requestedBy_username": "benmo"
  }'

Expect 200 JSON; check plex-watchlist-webhook.log for add/skip/no_match.

Crontab (Whatbox)

Merge with existing entries (do not remove Seerr ensure lines):

@reboot /mnt/mpathe/ahhhhhhh/.config/seerr/bin/letterboxd-fetch-rss-snapshot-whatbox.sh >>/mnt/mpathe/ahhhhhhh/.config/seerr/config/logs/letterboxd-fetch.log 2>&1
@reboot /mnt/mpathe/ahhhhhhh/.config/seerr/bin/letterboxd-seerr-watchlist-sync.sh >>/mnt/mpathe/ahhhhhhh/.config/seerr/config/logs/letterboxd-sync.log 2>&1
@reboot /mnt/mpathe/ahhhhhhh/.config/seerr/bin/letterboxd-seerr-diary-remove.sh >>/mnt/mpathe/ahhhhhhh/.config/seerr/config/logs/letterboxd-diary-remove.log 2>&1
*/10 * * * * /mnt/mpathe/ahhhhhhh/.config/seerr/bin/letterboxd-fetch-rss-snapshot-whatbox.sh >>/mnt/mpathe/ahhhhhhh/.config/seerr/config/logs/letterboxd-fetch.log 2>&1
*/5 * * * * /mnt/mpathe/ahhhhhhh/.config/seerr/bin/letterboxd-seerr-diary-remove.sh >>/mnt/mpathe/ahhhhhhh/.config/seerr/config/logs/letterboxd-diary-remove.log 2>&1
* * * * * /mnt/mpathe/ahhhhhhh/.config/seerr/bin/letterboxd-seerr-watchlist-sync.sh >>/mnt/mpathe/ahhhhhhh/.config/seerr/config/logs/letterboxd-sync.log 2>&1
* * * * * sleep 30; /mnt/mpathe/ahhhhhhh/.config/seerr/bin/letterboxd-seerr-watchlist-sync.sh >>/mnt/mpathe/ahhhhhhh/.config/seerr/config/logs/letterboxd-sync.log 2>&1
@reboot /mnt/mpathe/ahhhhhhh/.config/seerr/bin/plex-watchlist-webhook-ensure.sh >>/mnt/mpathe/ahhhhhhh/.config/seerr/config/logs/plex-watchlist-webhook-ensure.log 2>&1
* * * * * /mnt/mpathe/ahhhhhhh/.config/seerr/bin/plex-watchlist-webhook-ensure.sh >>/mnt/mpathe/ahhhhhhh/.config/seerr/config/logs/plex-watchlist-webhook-ensure.log 2>&1

Security

  • Rotate benmo Seerr password if it was shared in chat; do not store passwords in env files.
  • Keep letterboxd-sync.env and plex-watchlist-hook.env at mode 600.

Blockers

  • CSV upload: Full watchlist needs Letterboxd CSV export or off-box JSON; RSS alone is incomplete.
  • Cloudflare: Direct RSS and public RSSHub often fail on Whatbox; use letterboxd-fetch-rss-snapshot-whatbox.sh (HTML scrape fallback) + LETTERBOXD_SNAPSHOT_JSON.
  • Seerr webhook: Must enable Media Available → http://127.0.0.1:27861/ in UI.
  • Plex match: Title must exist in Plex library for TMDB guid search before watchlist add.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages