fix(server): Use shared watch state for isWatched and Plex viewCount#2453
fix(server): Use shared watch state for isWatched and Plex viewCount#2453wortmanb wants to merge 10 commits into
Conversation
b3179b5 to
498bf67
Compare
|
Hmm, wonder if the same can be done for Jellyfin... |
|
@wortmanb Nice direction. It may also be worth checking Jellyfin for parity: Jellyfin viewCount is currently derived from aggregated watch history, not a native item counter, so Plex and Jellyfin may still diverge semantically. Might be worth aligning them by intent if possible. |
|
More concretely, if we want Plex and Jellyfin to behave closer:
|
|
How would this effect an item that had a previous view count, was removed and then added back at a later date. Does it start back at 0 or do the view counts start from where they were before? I don't think the current watch history method addresses this either, but it has come up in previous conversations elsewhere and made me curious. |
|
The data that is being used in this change is cached for 5 minutes. Would not be/could not be a problem unless you are trying to run rules or test media shortly after marking something as watched. Lowering that cache is not the fix there. The fix is to use the PlexMetadata pulled at the top of the getter, which is already called and has per item cache updates. It currently does not have viewCount as a returned item. Adding it isn't a solution either as that call, and its results, are per the user token who pulled the metadata (whoever is authenticated in Maintainerr and usually admin). So viewCount for the admin user might be 1 and another user looking at viewCount would see 2 (only there two watches). Maintainerr only sees 1 instead of 3. So that isn't a viable option either when you have multiple users and want viewcount for the entire server, across all users. Unfortunately, I cannot merge this the way it is. 😞 |
|
I'll look into addressing these issues today. I appreciate the detailed comments. |
viewCount: restore watch history API as primary source (server-wide across all users) with fallback to libItem.viewCount when history is empty or API fails. The fallback is per-user (admin token) but is better than returning 0 when Plex has purged watch history. isWatched: new boolean property (Plex - Is Watched) that returns true if the item has been watched by anyone. Checks watch history first, falls back to libItem.viewCount > 0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
498bf67 to
db4697d
Compare
|
Updated the PR based on the review feedback. Here's what changed: @ydkmlt84 — You were right that the original approach (replacing watch history with
On the remove/re-add question: both watch history and native viewCount reset when an item is removed and re-added to Plex. That's a Plex-level constraint neither approach can work around. @enoch85 — Agreed on Jellyfin parity. The Jellyfin viewCount derives from aggregated watch history rather than a native counter, so the semantics already differ. Aligning them by intent would be a good follow-up but felt out of scope for this fix. New addition: Added a All 510 tests pass, 10 new tests covering both viewCount and isWatched. |
|
The boolean moves this update to a new rule type, so as not to interferre with any existing rules users might have. I left a fallback there, to check libItem.viewCount if the history returns 0 as a double-check, but otherwise, everything has been moved to "Plex - Is Watched". |
…, #2442, #2406, #2386, #2370 PR #2466 - fix: honor Jellyfin played threshold - Respect configured played percentage threshold for Jellyfin watch status PR #2461 - feat(rules): add ARR disk target path selection for disk space rules - Allow selecting specific disk target paths for Radarr/Sonarr disk space rules PR #2458 - feat: clean up empty ended shows in Sonarr after season actions - Automatically remove ended shows from Sonarr when all seasons are processed PR #2453 - fix: improve Plex viewCount reliability and add isWatched boolean - Use native Plex viewCount field with watch history fallback - Add new isWatched boolean rule property PR #2452 - build(deps): bump actions/download-artifact from 7 to 8 PR #2451 - build(deps): bump actions/upload-artifact from 6 to 7 PR #2442 - fix(server): reject null/undefined in numeric rule comparisons - Add getComparisonResult wrapper that fails closed on null/undefined operands - Strict type checking for BIGGER/SMALLER comparisons PR #2406 - Metadata provider abstraction layer with TVDB support - Add MetadataService as central metadata resolution layer - TVDB support as alternative metadata provider - Dynamic provider preference with fallback - Replace TmdbIdService with unified MetadataService PR #2386 - feat: missing_episode rules - Add missing episode count as a rule property for Sonarr PR #2370 - build(deps-dev): bump the eslint group with 2 updates
|
This is now included in the You can check the latest commits here: https://github.com/Maintainerr/Maintainerr/commits/jellyfin-dev
Thank you very much! 🚀 |
|
Added a small follow-up on top of this PR to keep the new watched rule aligned with the media-server abstraction. Why this change:
Why this is better:
I also ran yarn format and the full yarn test suite after the change set. |
|
@ydkmlt84 This is now in a state were I can accept the changes. Please have a look. |
PR #2453 - fix: improve Plex viewCount reliability and add isWatched boolean - Use native Plex viewCount field with watch history fallback - Add new isWatched boolean rule property - Share watched rule behavior across media servers
|
@wortmanb This is now included in |
|
Note to self: #2466 (comment) |
PR #2453 - fix: improve Plex viewCount reliability and add isWatched boolean - Use native Plex viewCount field with watch history fallback - Add new isWatched boolean rule property - Share watched rule behavior across media servers - Use uncached Plex history for watched rules - Share Plex test fixtures
The last two commits finish the Plex-side fix from that comment. The functional change removes the fallback to metadata viewCount and uses the item-scoped Plex history endpoint directly, with cache bypassed for watched-rule evaluation. That means the rule no longer depends on the admin-scoped viewCount field and is not stale for up to 5 minutes. The newest commit is test-only. It extracts shared Plex fixtures so the new history-based behavior is covered more clearly and with less duplicated setup. |
|
@wortmanb Sorry for the iterations here, I just forgot the discussion I had, and the comment, from @ydkmlt84 earlier. Thank s a lot for your work! I'm now done with this. Have been over it several times and are pleased with the results. The latest code is in the |
|
No worries! I was out of communication for a few days but will take a look this weekend. I guess I need to figure out how to get Jellyfin running alongside Plex now. ;-) Thanks for your work on this! |
|
I just deployed the |
Hmm, it works for me. 🤔 |
|
@wortmanb How did you deploy the jellyfin-dev branch, with docker tag? |
|
I checked it out locally and deployed it to my k8s cluster via: |
OK, seems correct. I will have another look when time allows. 👍 |
|
Actually, I also noticed that a number of movies that should be collected are included too. My rules are this, but I went from 26 in the collection under my code change to 112 now. Plex - Date Added > 365 AND I've also done this changing the second rule to: Plex - Times Viewed = 0 and got the same result. |
Can you export the YAML rule here and tell me the exact expectations? |
This should find all movies added over a year ago that haven't been watched and aren't in any collections. |
Thanks! I don't see any "watched" rule in there? Also, what does the test say for two known items? |
|
This is breaking my brain a little, but I think it's good. The strict AND means all 3 conditions need to be met, so it's normal and good that I'm seeing watched, but uncollected, movies over 365 days old in my collection. Wait, that's not right, is it? The only movies in there should be unwatched, uncollected, and older than 365 days. Still not quite what I'm looking for. Could be because some of these are marked watched but never viewed, since I had to re-create my library a while back. |
So all good, no need to change anything here? |
|
No, this isn't a fix for me yet. I think the issue might be "watched" versus "viewed". And maybe I'm in the extreme minority who needs to know whether the movie was watched, not whether it was viewed. |
|
Hey @wortmanb, I think I see what's going on here. You re-created your Plex library, so the watch history is gone. On top of that, some movies are "marked as watched" but were never actually played — so there's no history entry for them either. The thing is, Plex has two separate concepts: watch history (actual play events from the history API) and watched state (the checkmark in the UI, stored as item metadata). These are not the same thing. Right now both viewCount and isWatched only use the history API, so items without history always return 0/false regardless of what Plex shows in the UI. Your original fallback to libItem.viewCount covered this, but it got removed in d04c208 because it's per-user (admin token). Fair point for the numeric count, but it broke detection of "marked watched" items entirely. I've pushed a fix (9b3ddfe) that re-adds the native viewCount as a fallback for the isWatched boolean only — the numeric viewCount still uses history to keep server-wide accuracy. I will push this to |
PR #2453 - fix(server): fall back to native Plex viewCount for isWatched - Use native Plex viewCount as fallback for isWatched boolean when watch history is empty - Covers items marked watched without play events and purged history - Numeric viewCount still uses history only to preserve server-wide accuracy
When watch history is empty (purged or item marked watched without a play event), use the native Plex viewCount from item metadata as a fallback signal for the isWatched boolean. The numeric viewCount still uses history only since the native value is per-user (admin token) and would misrepresent server-wide counts.
|
@wortmanb Did you have any chance to test the latest changes? |
Summary
Addresses reviewer feedback from the original PR. The previous approach replaced the watch history API entirely with
libItem.viewCount, which is per-user (admin token only) and not suitable for server-wide view counts.viewCount (revised approach)
/status/sessions/history/all) — server-wide, counts all userslibItem.viewCountwhen history returns 0 or the API fails — per-user (admin), but better than returning 0 when Plex has purged watch historyisWatched (new property)
New boolean rule option: Plex - Is Watched (id: 43, movie type).
Returns
trueif the item has been watched by anyone:libItem.viewCount > 0if history is empty/failsThis provides a simple "has this been watched?" check that is resilient to Plex history purging.
Re: Jellyfin parity (enoch85)
Noted that Jellyfin viewCount is derived from aggregated watch history, not a native counter. Aligning Plex and Jellyfin semantics is worthwhile but orthogonal to this fix — suggest addressing in a follow-up.
Re: remove/re-add behavior (ydkmlt84)
When an item is removed from Plex and re-added, the watch history is lost and
viewCountresets to 0. Neither the history API nor the native counter survives this. TheisWatchedboolean has the same limitation. This is a Plex-level constraint.Test plan