Add Jellyfin support#2330
Merged
Merged
Conversation
* feat: Add Jellyfin support with abstraction layer - Add MediaServerService abstraction for Plex/Jellyfin - Implement JellyfinService with full API integration - Update rules engine to support multiple media servers - Add Jellyfin settings UI and configuration - Refactor collections to use MediaServerService - Add comprehensive type safety improvements - Update database schema for media server support * snc against this branch instead * feat(api): add deprecated /api/plex legacy wrapper for backward compatibility - Add PlexApiLegacyController with all 19 original endpoints - All endpoints delegate to MediaServerFactory abstraction - Add deprecation headers (X-Deprecated, Deprecation, Link) - Uses MediaServerSetupGuard (works with both Plex and Jellyfin) - Maps old route patterns to new abstraction interface - Single file design for easy removal when legacy support ends To remove legacy support: delete plex-api-legacy.controller.ts and remove PlexApiLegacyController from plex-api.module.ts * chore: merge main branch changes - Use leftJoinAndSelect for rules (allows groups without rules) - Delete unused CollectionDetail/index.tsx - Use useRuleGroupForCollection hook instead of manual fetch - Conditionally show Test Media button only when useRules=true - Simplify addCollectionToDB and RemoveCollectionFromDB methods * fix: update SchemaSync migration to use mediaServerId instead of plexId The SchemaSync migration from main referenced plexId (integer) but our JellyfinSupport migration already renamed this to mediaServerId (varchar). Updated both UP and DOWN methods to use the correct column name and type. * regenerated with typeorm * fix: standardize quotation marks and formatting in SchemaSync migration * generate clean typeorm * fix(ui): filter out empty/invalid rules to prevent crash When useRules=false, the backend creates a rule with empty ruleJson. These rules have no firstVal, causing TypeError when shouldFilterApp tries to access rule.firstVal[0]. Refactored to remove all useEffect usage: - Use useSyncExternalStore for scroll detection (React 19 pattern) - Move rule filtering to event handlers (updateLibraryId, handleUpdateArrAction) - Extract helper functions outside component (parseValidRules, shouldFilterApp, filterRulesForArrSettings) This is cleaner and more aligned with modern React patterns. * fix: complete merge of main branch rule editor fixes This commit completes the merge from main that was started in 0923d01. The original merge included the leftJoinAndSelect changes but missed removing the empty rule creation code from rules.service.ts. Backend (rules.service.ts): - Remove empty rule creation in createRuleGroup and updateRuleGroup - When useRules=false, the backend was creating rules with empty ruleJson - This aligns with main's fix from commit 730adb5 (#2270) - The RemoveEmptyRules migration (already present) cleans up existing data Frontend (RuleFormPage.tsx): - Add key={id} prop to AddModal component - This ensures the form fully remounts when navigating between rule groups - Replaces the need for useEffect-based form reset logic Frontend (AddModal/index.tsx): - Remove parseValidRules helper (no longer needed since empty rules won't exist) - Remove defensive firstVal check in filterRulesForArrSettings - Simplify rules state initialization to direct parsing The root cause was that commit 0923d01 merged the query changes (leftJoinAndSelect for rules) but not the corresponding code removal that prevents empty rules from being created in the first place. * replace more useEffect usage * prettier * refactor(ui): replace JS touch detection with CSS media queries - Remove unused useInteraction, useIsTouch hooks and InteractionContext - Add CSS @media (hover: hover/none) for touch-friendly MediaCard - Simplify MediaCard: click always opens modal, CSS handles hover states - Reduces bundle size by ~150 lines of JS with 15 lines of CSS * fix click behaviour on mobile * fix bug on about page showing 0 items in collections * add support for retrieving seasons and episode view counts * retrieve admin user ID for UserData fields in getSeasons and getItems * fix: make watchedAt optional in WatchRecord interface * add temporary debug for viewdate * more temp debug * possible fix for viewdate season/episodes * prettier * feat: add getPlaylistItems method to JellyfinAdapterService and update JellyfinGetterService to utilize it * fix cd/ci tests
Added a warning about the development branch and Jellyfin support.
When upgrading from pre-Jellyfin versions, media_server_type is null. Instead of defaulting to Plex everywhere (which caused noisy errors if Plex was down), auto-detect the server type from existing credentials during settings init and persist it. - settings.service.ts: auto-detect Jellyfin/Plex from credentials on init - media-server.factory.ts: throw instead of defaulting to Plex when no server type is configured; distinguish "not configured" from "unreachable" in startup logging - collections.service.ts: update return type to match factory change
Collaborator
Author
…ching Handle partial failures gracefully in Jellyfin adapter. If any single user query fails when fetching watch history or play counts, the other successful queries are still processed instead of losing all data.
Query parameters are received as strings. Without ParseIntPipe, numeric comparisons and math operations work on strings, producing incorrect results. Added ParseIntPipe with optional: true to: - media-server.controller.ts: page, limit parameters - plex-api-legacy.controller.ts: amount parameter - rules.controller.ts: rulegroupId, typeId parameters Also refactored rules.controller.ts to use individual @query parameters instead of inline object types for better type safety.
The Plex adapter's resetMetadataCache() was a no-op despite PlexApiService having this capability. Now properly delegates to plexApi.resetMetadataCache() when an itemId is provided.
Instead of hardcoding progress to 100, use the actual PlayedPercentage from Jellyfin's UserData when available. Falls back to 100 if not set. Note: Plex API doesn't expose progress percentage in watch history, so it remains hardcoded to 100 (which is correct for completed watches).
The testResult state was never cleared when URL or API key inputs changed. Users could modify credentials and save with a stale success indicator. Now clears test result and testedSettings when credentials are modified.
The factory already handled errors properly, but the warning message didn't include the actual error details for debugging. Now includes the error message in the log output.
Remove leftover debug console.log statement from addImportExclusion.
Add comprehensive unit tests for PlexAdapterService covering: - Lifecycle management (isSetup, initialize, uninitialize) - Server type identification (returns PLEX) - Feature detection (LABELS, PLAYLISTS, COLLECTION_VISIBILITY, etc.) - Cache management with resetMetadataCache delegation - Server status mapping to MediaServerStatus - User fetching and mapping to MediaUser - Library fetching and mapping to MediaLibrary - Library content queries with Jellyfin ID detection - Watch history retrieval and mapping to WatchRecord - Collection operations (create, delete) - Search content delegation This brings the test count from 392 to 419 tests.
Jellyfin provides ratings through CommunityRating (typically TMDB, 0-10 scale) and CriticRating (typically Rotten Tomatoes, 0-100 scale normalized to 0-10). Map these to the existing rating rule properties: - rating_imdb/rating_tmdb: Use CommunityRating (TMDB-sourced) - rating_rottenTomatoesCritic: Use CriticRating - rating_rottenTomatoesAudience: Use CommunityRating as approximation - All *Show variants: Traverse to parent/grandparent for show metadata This enables Jellyfin users to create rules based on ratings, which previously returned null for all external rating properties.
Jellyfin genres don't have IDs, so we were using array index which was fragile and could change between requests or items. Now using djb2 hash algorithm to generate stable IDs from genre names. Fixes review item #11.
During initial setup, the Plex and Jellyfin tabs were both visible before the user selected a media server type. Now only the General tab is shown until a selection is made. Fixes review item #14.
…switch Added a switchInProgress flag that: - Prevents concurrent switch operations from starting - Blocks factory.getService() calls during the switch - Is always released in a finally block This prevents API requests from routing to the wrong adapter during the window between settings update and server initialization. Fixes review item #8.
…apters - Document error handling contract in IMediaServerService interface: - Read operations return empty/undefined on failure - Write operations throw with descriptive message - Add try/catch with logging to Plex adapter write operations - Improve error messages to include context (collection ID, item ID, etc.) Fixes review item #10.
The lock added to MediaServerFactory caused a circular dependency between MediaServerModule and SettingsModule. Removed the lock entirely as it was over-engineering - the switch operation is rare and short-lived.
- Hide Plex and Tautulli rules when on Jellyfin media server - Hide Jellyfin rules when on Plex media server - Use MediaServerType enum from contracts instead of string literals
- Replace string literals 'plex' and 'jellyfin' with MediaServerType enum - Update Settings/index.tsx to use enum for tab visibility - Update MediaServerSelector to use enum for all comparisons
Collaborator
Author
|
@ydkmlt84 OK, please consider this ready for testing now by the masses. I have nothing more to add here afik right now. |
… in jellyfin adapter
Merge origin/main to bring in: - Database download/backup functionality - Apple touch icon for webapp - 3.0 update banner removal - Dependency bumps Conflicts resolved by keeping both branches' changes: - settings.controller.ts: both MediaServerSwitchService and DatabaseDownloadService - settings.module.ts: combined providers/exports from both branches - settings.ts (api): kept jellyfin hooks + downloadDatabase function - Settings/Main/index.tsx: both MediaServerSelector and DatabaseBackupModal imports - search-context.tsx: adopted main's stricter type annotation
Fixes from security review (H2, H4, M1, M7, L4): H2 - Race condition on media server switch: Add mutex flag to executeSwitch() with 409 Conflict on concurrent calls. H4 - API keys/tokens exposed in GET /settings: Mask secrets (first4...last4) in getPublicSettings(). Add dedicated GET /settings/jellyfin endpoint for real values. Move Plex token verification server-side for existing (masked) tokens. M1 - No validation on SwitchMediaServerRequest: Add Zod schema (switchMediaServerSchema) with ZodValidationPipe. Also add ParseEnumPipe on switch preview path param. M7 - State divergence during media server switch: MediaServerFactory.getService() checks isSwitching() and returns 503 Service Unavailable during the switch window. L4 - deleteFromDisk without safety checks: Validate itemId is non-empty (throw on empty instead of silent return). Add audit-level logging for destructive operations in both Jellyfin and Plex adapters. Throw on uninitialized Jellyfin API. Additional cleanup: Remove dead AbortController code in Plex verifyToken. Export MediaServerSwitchService from SettingsModule.
Collaborator
Author
OK, this is now RC1 |
CriticRating was divided by 10 in both the mapper (0-100 → 0-10) and the getter, resulting in a 0-1 scale instead of the documented 1-10 scale.
fix(jellyfin): remove double normalization of CriticRating (#2375)
Collaborator
Author
RC2 |
ydkmlt84
added a commit
to Maintainerr/maintainerr_site
that referenced
this pull request
Feb 22, 2026
* feat: add Jellyfin support and update dependencies - Update site to mention Jellyfin alongside Plex - Change 'Plex Overview' to 'Media Server Overview' - Change 'Plex Collections' to 'Media Server Collections' - Add Jellyfin mention in Solution section - Update copyright year to 2026 - Update dependencies: astro 5.9.2→5.17.1, tailwindcss 3.4.3→3.4.19, and others - Fix all security vulnerabilities Related to Maintainerr/Maintainerr#2330 * fix: fix mouse/touch scrolling behavior and update footer link --------- Co-authored-by: ydkmlt84 <justin@tacohouse.us>
improve loggning
Removed warning about development branch and Jellyfin support.
enoch85
added a commit
to enoch85/Maintainerr
that referenced
this pull request
Feb 27, 2026
…nerr#2330 merge Keep favorites feature additions (getItemFavoritedBy, favoritedBy/sw_favoritedBy rule properties and getter cases) while accepting upstream's improved log message.
Contributor
|
🎉 This PR is included in version 3.0.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
|
does plex authentication still needed ? 🤔 |
Collaborator
Author
Please open a new issue if you found any bugs, or join Discord to ask questions 👍🏼 |
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.


Summary
This PR introduces Jellyfin support via a new
MediaServerServiceabstraction layer and refactors rules/collections to operate against that abstraction instead of being Plex-only.A deprecated
/api/plexwrapper is included to preserve backward compatibility with existing clients.Highlights
MediaServerServiceabstraction + factory (Plex + Jellyfin)/api/plexlegacy controller with deprecation headersBreaking Changes
plexId→mediaServerId)Migration & Upgrade Notes
Additional Improvements