Sync played Jellyfin shows into the ledger #31
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No assignees
3 participants
Notifications
Due date
No due date set.
Reference
marvin8/xique!31
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/issue-30-jellyfin-show-sync"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The Jellyfin sync now covers TV: the worker walks the user's played episodes, groups them by parent series, and lands every series with at least one played episode in the ledger as watched — one watch event per played episode, at series-level resolution, matching what a Trakt import produces. A half-watched show therefore counts as seen and stops being recommended.
JellyfinEpisode/JellyfinSeriesclient types, a paged played-episodes fetch, and a batched series metadata lookup (new)sync_jellyfin_shows: series keyed on TMDB/IMDb/TheTVDB, series rating mirrored, unkeyable or metadata-less series skipped without failing the passjellyfin_syncjob runs the movies pass then the shows pass; a movies-pass failure skips the shows pass so the recorded error stays visibleRe-syncs are idempotent — the ledger ignores plays it already holds.
Closes #30
WuMing
Found 4 issue(s). See inline comments below.
@ -465,0 +586,4 @@JellyfinError: The URL is not ``https`` or the request failed."""url = _endpoint(_require_https(base_url), "/Items")security [MEDIUM]
A10: User-provided base_url is validated only for HTTPS scheme, not for targeting legitimate Jellyfin servers. Enables SSRF against internal HTTPS services.
@ -465,0 +638,4 @@"""if not item_ids:return ()url = _endpoint(_require_https(base_url), "/Items")security [MEDIUM]
A10: User-provided base_url in series metadata fetch can probe internal HTTPS endpoints without further validation, potential SSRF vector.
@ -304,0 +449,4 @@transport=transport,)except JellyfinError as exc:return {}, str(exc)security [MEDIUM]
A09: Raw exception messages (str(exc)) are stored in the database via record_sync_result without sanitization. May leak sensitive data on API failures.
@ -304,0 +511,4 @@transport=transport,)except JellyfinError as exc:return summary, str(exc)security [MEDIUM]
A09: Raw exception messages (str(exc)) are stored in the database via record_sync_result without sanitization. May leak sensitive data on metadata fetch failures.
🔴 Not actioned — SSRF via HTTPS-only base URL validation
src/xique/services/jellyfin.py·src/xique/services/jellyfin.pyDeclined — this is the spec's explicit, approved design, not an oversight. Design spec §7.1 requires
https://and additionally validates the target is a real Jellyfin server via/System/Info/Publicbefore the URL is accepted, and states plainly: "Accept private/LAN addresses (legitimate self-hosted use); no blanket private-range block." Xique is an invite-only, single-instance, self-hosted app whose entire purpose is talking to each user's own LAN Jellyfin; a private-range block would break the product. The new fetches (fetch_played_episodes,fetch_series) also introduce no new surface — they go through the same_require_httpsgate as every existing client call, using a base URL the user already connected and validated.🔴 Not actioned — raw
str(exc)stored viarecord_sync_resultsrc/xique/services/sync.py·src/xique/services/sync.pyDeclined — the exceptions reaching
record_sync_resultareJellyfinErrormessages, which are token-free by construction: the client never embeds URLs, tokens, passwords, or response bodies in them ("Jellyfin request failed: {type}'',"Jellyfin API returned HTTP {status}"— pinned by client tests). Thesqlite3.IntegrityErrorpath carries constraint names only. This is exactly the pattern the existing movies pass uses at every call site, and the docstrings pin it ("token-free error description"). The new code adds no new exception content.