From d7655e520d90f41c28a9e95666610b0ab0d6c58f Mon Sep 17 00:00:00 2001 From: TOomaAh Date: Mon, 1 Sep 2025 00:13:53 +0200 Subject: [PATCH] refactor(tvdb): replace indexer by metadata providers --- config/db/.gitkeep | 0 cypress/e2e/providers/tvdb.cy.ts | 11 ++-- server/api/metadata.ts | 9 ++-- server/lib/scanners/jellyfin/index.ts | 6 +-- server/lib/scanners/plex/index.ts | 6 +-- server/lib/settings/index.ts | 10 ++-- server/routes/settings/metadata.ts | 14 +++-- server/routes/tv.ts | 10 ++-- src/components/MetadataSelector/index.tsx | 32 ++++++------ src/components/Settings/SettingsMetadata.tsx | 55 ++++++++++++-------- src/i18n/locale/en.json | 8 +-- 11 files changed, 91 insertions(+), 70 deletions(-) delete mode 100644 config/db/.gitkeep diff --git a/config/db/.gitkeep b/config/db/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/cypress/e2e/providers/tvdb.cy.ts b/cypress/e2e/providers/tvdb.cy.ts index c5174443c..65e629b0e 100644 --- a/cypress/e2e/providers/tvdb.cy.ts +++ b/cypress/e2e/providers/tvdb.cy.ts @@ -16,8 +16,9 @@ describe('TVDB Integration', () => { metadataSaveButton: '[data-testid="metadata-save-button"]', tmdbStatus: '[data-testid="tmdb-status"]', tvdbStatus: '[data-testid="tvdb-status"]', - tvIndexerSelector: '[data-testid="tv-indexer-selector"]', - animeIndexerSelector: '[data-testid="anime-indexer-selector"]', + tvMetadataProviderSelector: '[data-testid="tv-metadata-provider-selector"]', + animeMetadataProviderSelector: + '[data-testid="anime-metadata-provider-selector"]', seasonSelector: '[data-testid="season-selector"]', season1: 'Season 1', season2: 'Season 2', @@ -50,7 +51,7 @@ describe('TVDB Integration', () => { req.body = customBody; }).as('saveMetadata'); } else { - // Sinon, juste intercepter sans modifier + // Else just intercept without modifying body cy.intercept('PUT', '/api/v1/settings/metadatas').as('saveMetadata'); } @@ -69,10 +70,10 @@ describe('TVDB Integration', () => { cy.contains('h3', 'Metadata Providers').should('be.visible'); // Configure TVDB as TV provider and test connection - cy.get('[data-testid="tv-indexer-selector"]').click(); + cy.get(SELECTORS.tvMetadataProviderSelector).click(); // get id react-select-4-option-1 - cy.get('[id^="react-select-4-option-"]').contains('TheTVDB').click(); + cy.get('[class*="react-select__option"]').contains('TheTVDB').click(); // Test the connection testAndVerifyMetadataConnection().then(({ response }) => { diff --git a/server/api/metadata.ts b/server/api/metadata.ts index 65b2f1d5a..cd1936cbf 100644 --- a/server/api/metadata.ts +++ b/server/api/metadata.ts @@ -1,7 +1,7 @@ import type { TvShowProvider } from '@server/api/provider'; import TheMovieDb from '@server/api/themoviedb'; import Tvdb from '@server/api/tvdb'; -import { getSettings, IndexerType } from '@server/lib/settings'; +import { getSettings, MetadataProviderType } from '@server/lib/settings'; import logger from '@server/logger'; export const getMetadataProvider = async ( @@ -14,13 +14,16 @@ export const getMetadataProvider = async ( return new TheMovieDb(); } - if (mediaType == 'tv' && settings.metadataSettings.tv == IndexerType.TVDB) { + if ( + mediaType == 'tv' && + settings.metadataSettings.tv == MetadataProviderType.TVDB + ) { return await Tvdb.getInstance(); } if ( mediaType == 'anime' && - settings.metadataSettings.anime == IndexerType.TVDB + settings.metadataSettings.anime == MetadataProviderType.TVDB ) { return await Tvdb.getInstance(); } diff --git a/server/lib/scanners/jellyfin/index.ts b/server/lib/scanners/jellyfin/index.ts index 6cb8117df..3283e3420 100644 --- a/server/lib/scanners/jellyfin/index.ts +++ b/server/lib/scanners/jellyfin/index.ts @@ -219,14 +219,14 @@ class JellyfinScanner { throw new Error('No ID provided'); } - const indexer = tvShow.keywords.results.some( + const metadataProvider = tvShow.keywords.results.some( (keyword: TmdbKeyword) => keyword.id === ANIME_KEYWORD_ID ) ? await getMetadataProvider('anime') : await getMetadataProvider('tv'); - if (!(indexer instanceof TheMovieDb)) { - tvShow = await indexer.getTvShow({ + if (!(metadataProvider instanceof TheMovieDb)) { + tvShow = await metadataProvider.getTvShow({ tvId: Number(tmdbId), }); } diff --git a/server/lib/scanners/plex/index.ts b/server/lib/scanners/plex/index.ts index c981a4e46..24862e558 100644 --- a/server/lib/scanners/plex/index.ts +++ b/server/lib/scanners/plex/index.ts @@ -276,14 +276,14 @@ class PlexScanner throw new Error('No ID provided'); } - const indexer = tvShow.keywords.results.some( + const metadataProvider = tvShow.keywords.results.some( (keyword: TmdbKeyword) => keyword.id === ANIME_KEYWORD_ID ) ? await getMetadataProvider('anime') : await getMetadataProvider('tv'); - if (!(indexer instanceof TheMovieDb)) { - tvShow = await indexer.getTvShow({ + if (!(metadataProvider instanceof TheMovieDb)) { + tvShow = await metadataProvider.getTvShow({ tvId: Number(tmdbId), }); } diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index 215f0e518..60e4769d8 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -100,14 +100,14 @@ interface Quota { quotaDays?: number; } -export enum IndexerType { +export enum MetadataProviderType { TMDB = 'tmdb', TVDB = 'tvdb', } export interface MetadataSettings { - tv: IndexerType; - anime: IndexerType; + tv: MetadataProviderType; + anime: MetadataProviderType; } export interface ProxySettings { @@ -422,8 +422,8 @@ class Settings { }, tautulli: {}, metadataSettings: { - tv: IndexerType.TMDB, - anime: IndexerType.TMDB, + tv: MetadataProviderType.TMDB, + anime: MetadataProviderType.TMDB, }, radarr: [], sonarr: [], diff --git a/server/routes/settings/metadata.ts b/server/routes/settings/metadata.ts index 2714e2107..8e007f0ed 100644 --- a/server/routes/settings/metadata.ts +++ b/server/routes/settings/metadata.ts @@ -2,7 +2,7 @@ import TheMovieDb from '@server/api/themoviedb'; import Tvdb from '@server/api/tvdb'; import { getSettings, - IndexerType, + MetadataProviderType, type MetadataSettings, } from '@server/lib/settings'; import logger from '@server/logger'; @@ -32,21 +32,27 @@ metadataRoutes.put('/', async (req, res) => { let tmdbTest = -1; try { - if (body.tv === IndexerType.TVDB || body.anime === IndexerType.TVDB) { + if ( + body.tv === MetadataProviderType.TVDB || + body.anime === MetadataProviderType.TVDB + ) { tvdbTest = 0; const tvdb = await Tvdb.getInstance(); await tvdb.test(); tvdbTest = 1; } } catch (e) { - logger.error('Failed to test indexers', { + logger.error('Failed to test metadata provider', { label: 'Metadata', message: e.message, }); } try { - if (body.tv === IndexerType.TMDB || body.anime === IndexerType.TMDB) { + if ( + body.tv === MetadataProviderType.TMDB || + body.anime === MetadataProviderType.TMDB + ) { tmdbTest = 0; const tmdb = new TheMovieDb(); await tmdb.getTvShow({ tvId: 1054 }); diff --git a/server/routes/tv.ts b/server/routes/tv.ts index d49a01e0f..5d3c3e097 100644 --- a/server/routes/tv.ts +++ b/server/routes/tv.ts @@ -21,12 +21,12 @@ tvRoutes.get('/:id', async (req, res, next) => { const tmdbTv = await tmdb.getTvShow({ tvId: Number(req.params.id), }); - const indexer = tmdbTv.keywords.results.some( + const metadataProvider = tmdbTv.keywords.results.some( (keyword: TmdbKeyword) => keyword.id === ANIME_KEYWORD_ID ) ? await getMetadataProvider('anime') : await getMetadataProvider('tv'); - const tv = await indexer.getTvShow({ + const tv = await metadataProvider.getTvShow({ tvId: Number(req.params.id), language: (req.query.language as string) ?? req.locale, }); @@ -45,7 +45,7 @@ tvRoutes.get('/:id', async (req, res, next) => { // TMDB issue where it doesnt fallback to English when no overview is available in requested locale. if (!data.overview) { - const tvEnglish = await indexer.getTvShow({ + const tvEnglish = await metadataProvider.getTvShow({ tvId: Number(req.params.id), }); data.overview = tvEnglish.overview; @@ -71,13 +71,13 @@ tvRoutes.get('/:id/season/:seasonNumber', async (req, res, next) => { const tmdbTv = await tmdb.getTvShow({ tvId: Number(req.params.id), }); - const indexer = tmdbTv.keywords.results.some( + const metadataProvider = tmdbTv.keywords.results.some( (keyword: TmdbKeyword) => keyword.id === ANIME_KEYWORD_ID ) ? await getMetadataProvider('anime') : await getMetadataProvider('tv'); - const season = await indexer.getTvSeason({ + const season = await metadataProvider.getTvSeason({ tvId: Number(req.params.id), seasonNumber: Number(req.params.seasonNumber), }); diff --git a/src/components/MetadataSelector/index.tsx b/src/components/MetadataSelector/index.tsx index 634a128f0..66835ab52 100644 --- a/src/components/MetadataSelector/index.tsx +++ b/src/components/MetadataSelector/index.tsx @@ -5,14 +5,14 @@ import React from 'react'; import { useIntl } from 'react-intl'; import Select, { type StylesConfig } from 'react-select'; -enum IndexerType { +enum MetadataProviderType { TMDB = 'tmdb', TVDB = 'tvdb', } -type IndexerOptionType = { +type MetadataProviderOptionType = { testId?: string; - value: IndexerType; + value: MetadataProviderType; label: string; icon: React.ReactNode; }; @@ -20,40 +20,40 @@ type IndexerOptionType = { const messages = defineMessages('components.MetadataSelector', { tmdbLabel: 'The Movie Database (TMDB)', tvdbLabel: 'TheTVDB', - selectIndexer: 'Select a metadata provider', + selectMetdataProvider: 'Select a metadata provider', }); interface MetadataSelectorProps { testId: string; - value: IndexerType; - onChange: (value: IndexerType) => void; + value: MetadataProviderType; + onChange: (value: MetadataProviderType) => void; isDisabled?: boolean; } const MetadataSelector = ({ - testId = 'indexer-selector', + testId = 'metadata-provider-selector', value, onChange, isDisabled = false, }: MetadataSelectorProps) => { const intl = useIntl(); - const indexerOptions: IndexerOptionType[] = [ + const metadataProviderOptions: MetadataProviderOptionType[] = [ { testId: 'tmdb-option', - value: IndexerType.TMDB, + value: MetadataProviderType.TMDB, label: intl.formatMessage(messages.tmdbLabel), icon: , }, { testId: 'tvdb-option', - value: IndexerType.TVDB, + value: MetadataProviderType.TVDB, label: intl.formatMessage(messages.tvdbLabel), icon: , }, ]; - const customStyles: StylesConfig = { + const customStyles: StylesConfig = { option: (base) => ({ ...base, display: 'flex', @@ -66,7 +66,7 @@ const MetadataSelector = ({ }), }; - const formatOptionLabel = (option: IndexerOptionType) => ( + const formatOptionLabel = (option: MetadataProviderOptionType) => (
{option.icon} {option.label} @@ -76,17 +76,17 @@ const MetadataSelector = ({ return (