Conversation
Adds a "Usage Statistics" section to the Admin tab that, if an admin turns it on, periodically reports a small anonymous snapshot to a new community analytics project: which features are toggled on, a handful of usage counts (bookmarks, reviews, hidden content, spoiler blur items, Seerr requests), and byte sizes of a couple of data files. Off by default, three independently toggleable categories, and a live preview button that shows the exact payload before anything is ever sent. Each install gets a random ID minted by the backend along with a secret that has to be presented on every report, so nobody can overwrite or impersonate another install's data just by guessing or copying an ID that shows up in a shared preview. Opting back out flags the row instead of deleting it, and a couple of self-heal paths handle the backend rejecting stale credentials (e.g. after a manual cleanup) without the admin having to do anything. Results are visible on a new live dashboard page in the docs (Community Analytics), reading straight from a few public aggregate-only Supabase views. Feature-toggle grouping and display labels on that page are generated from configPage.html's own fieldset/legend/label structure (scripts/generate_config_flag_groups.py, run in CI before every docs build), so a new setting is grouped and labeled correctly the moment it's added to the right part of the config page, nothing to hand-maintain. Also swept in along the way: two settings that were missing from public-config (JellyseerrShowCollectionDiscovery silently never disabled, peopletags.js reading a config key that didn't exist) and a matching Postgres RPC overloading gotcha (fixed by keeping one function with a defaulted param instead of two overloads).
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Review + live-test pass over the Usage Statistics feature (#791, discussion capability removed; everything verified against a Jellyfin 10.11 container with the analytics backend blackholed. Privacy - Stop sending raw Jellyfin user GUIDs: MaintenanceModeAffectedUsers is no longer [AnalyticsInclude]; the payload and the anonymous public-config endpoint share one derived "all"/"selected" helper so the two shapes can never drift or regress to the raw value. - Normalize LanguageTagsPriority (a free-text box) to language-code tokens only (up to two BCP-47 subtags, so zh-Hans-CN survives). - Gate the server-side counter Increment on consent so no pre-opt-in activity is accumulated; clear counters synchronously at the opt-in boundary (in UpdateConfiguration, before anything can act on the newly enabled state) so no send can ever ship pre-consent data. Abuse / integrity - usage/track accepts only allowlisted keys, blocking unbounded distinct-key spam and forged "total.*" values; rejections of well-formed unknown keys log loudly (a new counter shipped without its KnownKeys entry would otherwise read as "unused" forever); persisted counters from pre-allowlist builds are filtered on load. - Preserve server-minted install credentials when an incoming save has EITHER half missing (covers secret-redacted backup restores), write them to the live config under a shared lock covering UpdateConfiguration's object swap, and log post-success SaveConfiguration failures honestly instead of as network errors — closing every path that orphaned or duplicated backend install rows. - Serialize sends behind a gate; run opt-in/opt-out transitions on a FIFO queue that re-checks live state; ForceSend re-checks enabled under the gate. - Require 3 x 403 before re-registering, with the count persisted in config (an in-memory count on a frequently restarted server would never trip and the install would 403 forever); reset it on success. - Subtract the exact sent snapshot on period reset (no lost increments), clamped at zero so a boundary reset racing an in-flight send can never persist or ship a negative count. - Validate the registration response shape; stop swallowing HTTP timeouts as silent "shutdown"; use InvariantCulture for all period dates (a Thai locale otherwise reports Buddhist-calendar years the dashboard would filter out as forged). Config page - First-opt-in cascade runs once and survives sessions: the configured flag now persists whenever the master was turned on this session, even if it is off again at save time — a deliberate category opt-out can no longer be silently re-cascaded over after a reload. - Preview requests carry a monotonic token (overlapping auto-triggered responses can't race last-write-wins into a stale payload); readable preview errors instead of "[object Response]". Public dashboard (docs) - Treat every report-derived field as hostile: strict period-shape validation plus a trailing 45-day aggregation window (a single forged "today" period could previously hide every genuine install's rows), numeric coercion + 0-100 clamp on all count/percentage fields, prototype- safe key lookups, String() guards, per-section error isolation, row limits with explicit order= (deterministic truncation), and display caps ranked by count — an alphabetical cut would have let forged names evict the genuine entries. - Fix isTargetMismatch for Jellyfin 13+; correct "three views" -> four and the garbled "Always included" note; document the derived/normalized fields. Tooling - generate_config_flag_groups.py: fix the label-dedup key comparison and remove dead handle_entityref (output byte-identical). Developed with AI assistance (Claude Code); all changes reviewed and live-tested on Jellyfin 10.11.11. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013sScjM7juAei2VwvrakmbS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This adds a new "Usage Statistics" section under the Admin tab. Off by default, and nothing is sent anywhere unless an admin explicitly turns it on and saves. #788
When enabled, the plugin periodically sends a small anonymous report to a new community analytics project. There are three independently toggleable categories:
There's a Preview button on the config page that shows the exact JSON that would be sent.
Along with this, there is a new live dashboard page in the docs, Community Analytics, that reads straight from the db. This will be visible live at - https://n00bcodr.github.io/Jellyfin-/advanced/community-analytics/ once the PR is merged.
Sample JSON that is sent
{ "InstallId": "<randomly_generated_unique_identifier>", "PluginVersion": "12.4.1", "JellyfinVersion": "12.0.0", "JellyfinTarget": "jf12", "Config": { "MaintenanceModeEnabled": false, "EnableCustomSplashScreen": false, "DevMode": false, "ElsewhereEnabled": true, "AutoPauseEnabled": true, "AutoResumeEnabled": false, "AutoPipEnabled": false, "AutoSkipIntro": false, "AutoSkipOutro": false, "LongPress2xEnabled": false, "RandomButtonEnabled": true, "RandomIncludeMovies": true, "RandomIncludeShows": true, "RandomUnwatchedOnly": false, "ShowWatchProgress": true, "ShowFileSizes": true, "RemoveContinueWatchingEnabled": true, "ShowAudioLanguages": true, "ShowReviews": true, "ShowUserReviews": true, "ReviewsExpandedByDefault": false, "HideReviewsFromHiddenUsers": true, "HideReviewsFromDisabledUsers": true, "ShowAwards": true, "MdblistRatingsEnabled": false, "MdblistRatingsShowOnItemDetails": true, "MdblistRatingsFetchEnabled": false, "MdblistRatingsAutoSyncEnabled": false, "MdblistRatingsOverwriteExisting": false, "MdblistRatingsShowPercentSymbol": false, "ShowReleaseDates": true, "ShowUserRatingOnPosters": false, "ShowUserRatingDash": true, "PauseScreenEnabled": true, "QualityTagsEnabled": true, "ShowResolutionTag": true, "ShowSourceTag": true, "ShowDynamicRangeTag": true, "ShowSpecialFormatTag": true, "ShowVideoCodecTag": true, "ShowAudioInfoTag": true, "LanguageTagsEnabled": true, "RatingTagsEnabled": true, "PeopleTagsEnabled": true, "DisableTagsOnSearchPage": false, "TagsHideOnHover": false, "TagCacheServerMode": true, "EnableTagsLocalStorageFallback": false, "DisableAllShortcuts": false, "DisableCustomSubtitleStyles": false, "LanguageTagsPriorityStrict": false, "ShowRatingInPlayer": true, "GenreTagsEnabled": true, "JellyseerrEnabled": true, "JellyseerrShowSearchResults": true, "JellyseerrShowReportButton": false, "JellyseerrShowIssueIndicator": false, "JellyseerrEnable4KRequests": false, "JellyseerrEnable4KTvRequests": false, "JellyseerrShowAdvanced": false, "JellyseerrShowQuotaInfo": true, "JellyseerrShowSimilar": true, "JellyseerrShowRecommended": true, "JellyseerrShowRequestMoreOnSeries": true, "JellyseerrShowNetworkDiscovery": true, "JellyseerrShowGenreDiscovery": true, "JellyseerrShowTagDiscovery": true, "JellyseerrShowPersonDiscovery": true, "JellyseerrShowCollectionDiscovery": true, "JellyseerrShowDetailPageLink": true, "JellyseerrShowDetailPageLinkAsText": false, "JellyseerrExcludeLibraryItems": true, "JellyseerrExcludeBlocklistedItems": false, "ShowElsewhereOnJellyseerr": false, "JellyseerrUseMoreInfoModal": true, "JellyseerrAvailablePosterLinksToJellyfin": false, "TriggerSeerrScanOnItemAdded": false, "ShowCollectionsInSearch": true, "JellyseerrDisableCache": false, "ArrLinksEnabled": false, "ShowArrLinksAsText": false, "ArrLinksShowStatusSingle": false, "ArrTagsSyncEnabled": false, "ArrTagsClearOldTags": true, "ArrTagsShowAsLinks": true, "LetterboxdEnabled": true, "ShowLetterboxdLinkAsText": false, "MetadataIconsEnabled": false, "AutoSeasonRequestEnabled": false, "AutoSeasonRequestRequireAllWatched": false, "AutoMovieRequestEnabled": false, "AutoMovieRequestCheckReleaseDate": true, "AutoMovieRequestFallbackOn4k": true, "AddRequestedMediaToWatchlist": false, "SyncJellyseerrWatchlist": false, "SyncJellyfinWatchlistToSeerr": false, "PreventWatchlistReAddition": true, "JellyseerrAutoImportUsers": false, "BookmarksEnabled": true, "BookmarksUsePluginPages": false, "BookmarksUseCustomTabs": false, "BookmarksUseNativeTab": true, "BookmarksAutoCreateCustomTab": false, "UseIcons": true, "ColoredRatingsEnabled": true, "ThemeSelectorEnabled": false, "ColoredActivityIconsEnabled": true, "PluginIconsEnabled": true, "EnableLoginImage": true, "ActiveStreamsEnabled": true, "ActiveStreamsAllUsers": false, "ActivityFeedEnabled": false, "ActivityFeedShowWatched": true, "ActivityFeedShowFavorited": true, "ActivityFeedShowReviewed": true, "ActivityFeedShowActiveStreams": true, "ActivityFeedUsePluginPages": false, "ActivityFeedUseCustomTabs": false, "ActivityFeedUseNativeTab": false, "ActivityFeedAutoCreateCustomTab": false, "DownloadsPageEnabled": true, "DownloadsUsePluginPages": false, "DownloadsUseCustomTabs": false, "DownloadsUseNativeTab": true, "DownloadsAutoCreateCustomTab": false, "DownloadsPagePollingEnabled": true, "DownloadsPageShowIssues": false, "ShowDownloadsInRequests": true, "DownloadsFilterByUserRequests": true, "DownloadsShowHistory": true, "DownloadsHistoryAdminOnly": false, "CalendarPageEnabled": true, "CalendarUseCustomTabs": false, "CalendarUsePluginPages": false, "CalendarUseNativeTab": true, "CalendarAutoCreateCustomTab": false, "CalendarHighlightFavorites": false, "CalendarHighlightWatchedSeries": false, "CalendarFilterByLibraryAccess": true, "CalendarShowOnlyRequested": false, "CalendarForceOnlyRequested": false, "RecommendationsPageEnabled": true, "RecommendationsUsePluginPages": false, "RecommendationsUseCustomTabs": false, "RecommendationsUseNativeTab": true, "RecommendationsAutoCreateCustomTab": false, "HiddenContentEnabled": true, "HiddenContentUsePluginPages": false, "HiddenContentUseCustomTabs": false, "HiddenContentUseNativeTab": true, "HiddenContentAutoCreateCustomTab": false, "HiddenContentAdmin": true, "HiddenContentDefaultEnabled": true, "HiddenContentDefaultShowHideButtons": true, "HiddenContentDefaultShowHideConfirmation": true, "HiddenContentDefaultShowButtonJellyseerr": true, "HiddenContentDefaultShowButtonLibrary": false, "HiddenContentDefaultShowButtonDetails": true, "HiddenContentDefaultShowButtonCast": false, "HiddenContentDefaultFilterLibrary": true, "HiddenContentDefaultFilterDiscovery": true, "HiddenContentDefaultFilterSearch": false, "HiddenContentDefaultFilterCalendar": true, "HiddenContentDefaultFilterUpcoming": true, "HiddenContentDefaultFilterRecommendations": true, "HiddenContentDefaultFilterRequests": true, "HiddenContentDefaultFilterNextUp": true, "HiddenContentDefaultFilterContinueWatching": true, "HiddenContentDefaultExperimentalHideCollections": false, "SpoilerBlurEnabled": false, "SpoilerStripOverview": true, "SpoilerStripTags": true, "SpoilerStripChapters": true, "SpoilerStripTaglines": true, "SpoilerStripRatings": true, "SpoilerStripPremiereDate": true, "SpoilerReplaceTitle": true, "SpoilerStripCast": true, "SpoilerStripReviews": true, "SpoilerBlurArtwork": false, "SpoilerAutoEnableOnFirstPlay": false, "SpoilerAutoEnableOnSeerrRequest": false, "SpoilerBlurStrictRefresh": false, "SpoilerKeepMoviePosters": true, "SpoilerAdvancedMode": false, "SpoilerNextEpisodeStripTitle": false, "SpoilerNextEpisodeStripOverview": true, "SpoilerNextEpisodeStripRatings": true, "SpoilerCurrentSeasonStripTitle": true, "SpoilerCurrentSeasonStripOverview": true, "SpoilerCurrentSeasonStripRatings": true, "TmdbEnabled": true, "MdblistEnabled": false }, "Settings": { "MaintenanceModeAction": "none", "MaintenanceModeAffectedUsers": "all", "QualityTagsPosition": "top-left", "GenreTagsPosition": "top-right", "LanguageTagsPosition": "bottom-left", "RatingTagsPosition": "bottom-right", "IconStyle": "emoji" }, "DataFileSizes": { "custom_branding": 36733 }, "Period": "2026-08-28", "Events": [ { "key": "seerr.request_submitted", "count": 1 }, { "key": "total.bookmarks", "count": 2 }, { "key": "total.hidden_content_items", "count": 1 }, { "key": "total.spoiler_blur_items", "count": 0 }, { "key": "total.reviews", "count": 2 } ] }