mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2025-12-24 02:39:18 -05:00
refactor(tvdb): replace indexer by metadata providers
This commit is contained in:
@@ -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 }) => {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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: [],
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
|
||||
@@ -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: <TmdbLogo />,
|
||||
},
|
||||
{
|
||||
testId: 'tvdb-option',
|
||||
value: IndexerType.TVDB,
|
||||
value: MetadataProviderType.TVDB,
|
||||
label: intl.formatMessage(messages.tvdbLabel),
|
||||
icon: <TvdbLogo />,
|
||||
},
|
||||
];
|
||||
|
||||
const customStyles: StylesConfig<IndexerOptionType, false> = {
|
||||
const customStyles: StylesConfig<MetadataProviderOptionType, false> = {
|
||||
option: (base) => ({
|
||||
...base,
|
||||
display: 'flex',
|
||||
@@ -66,7 +66,7 @@ const MetadataSelector = ({
|
||||
}),
|
||||
};
|
||||
|
||||
const formatOptionLabel = (option: IndexerOptionType) => (
|
||||
const formatOptionLabel = (option: MetadataProviderOptionType) => (
|
||||
<div className="flex items-center">
|
||||
{option.icon}
|
||||
<span data-testid={option.testId}>{option.label}</span>
|
||||
@@ -76,17 +76,17 @@ const MetadataSelector = ({
|
||||
return (
|
||||
<div data-testid={testId}>
|
||||
<Select
|
||||
options={indexerOptions}
|
||||
options={metadataProviderOptions}
|
||||
isDisabled={isDisabled}
|
||||
className="react-select-container"
|
||||
classNamePrefix="react-select"
|
||||
value={indexerOptions.find((option) => option.value === value)}
|
||||
value={metadataProviderOptions.find((option) => option.value === value)}
|
||||
onChange={(selectedOption) => {
|
||||
if (selectedOption) {
|
||||
onChange(selectedOption.value);
|
||||
}
|
||||
}}
|
||||
placeholder={intl.formatMessage(messages.selectIndexer)}
|
||||
placeholder={intl.formatMessage(messages.selectMetdataProvider)}
|
||||
styles={customStyles}
|
||||
formatOptionLabel={formatOptionLabel}
|
||||
/>
|
||||
@@ -94,5 +94,5 @@ const MetadataSelector = ({
|
||||
);
|
||||
};
|
||||
|
||||
export { IndexerType };
|
||||
export { MetadataProviderType };
|
||||
export default MetadataSelector;
|
||||
|
||||
@@ -3,7 +3,7 @@ import Button from '@app/components/Common/Button';
|
||||
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
|
||||
import PageTitle from '@app/components/Common/PageTitle';
|
||||
import MetadataSelector, {
|
||||
IndexerType,
|
||||
MetadataProviderType,
|
||||
} from '@app/components/MetadataSelector';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
@@ -16,10 +16,11 @@ import { useToasts } from 'react-toast-notifications';
|
||||
import useSWR from 'swr';
|
||||
|
||||
const messages = defineMessages('components.Settings', {
|
||||
metadataProviderSettings: 'Metadata Providers',
|
||||
general: 'General',
|
||||
settings: 'Settings',
|
||||
seriesIndexer: 'Series metadata provider',
|
||||
animeIndexer: 'Anime metadata provider',
|
||||
seriesMetadataProvider: 'Series metadata provider',
|
||||
animeMetadataProvider: 'Anime metadata provider',
|
||||
metadataSettings: 'Settings for metadata provider',
|
||||
clickTest:
|
||||
'Click on the "Test" button to check connectivity with metadata providers',
|
||||
@@ -28,7 +29,7 @@ const messages = defineMessages('components.Settings', {
|
||||
operational: 'Operational',
|
||||
providerStatus: 'Metadata Provider Status',
|
||||
chooseProvider: 'Choose metadata providers for different content types',
|
||||
indexerSelection: 'Metadata Provider Selection',
|
||||
metadataProviderSelection: 'Metadata Provider Selection',
|
||||
tmdbProviderDoesnotWork:
|
||||
'TMDB provider does not work, please select another metadata provider',
|
||||
tvdbProviderDoesnotWork:
|
||||
@@ -48,8 +49,8 @@ interface ProviderResponse {
|
||||
}
|
||||
|
||||
interface MetadataValues {
|
||||
tv: IndexerType;
|
||||
anime: IndexerType;
|
||||
tv: MetadataProviderType;
|
||||
anime: MetadataProviderType;
|
||||
}
|
||||
|
||||
interface MetadataSettings {
|
||||
@@ -72,8 +73,8 @@ const SettingsMetadata = () => {
|
||||
'/api/v1/settings/metadatas',
|
||||
async (url: string) => {
|
||||
const response = await axios.get<{
|
||||
tv: IndexerType;
|
||||
anime: IndexerType;
|
||||
tv: MetadataProviderType;
|
||||
anime: MetadataProviderType;
|
||||
}>(url);
|
||||
|
||||
return {
|
||||
@@ -89,9 +90,11 @@ const SettingsMetadata = () => {
|
||||
values: MetadataValues
|
||||
): Promise<ProviderResponse> => {
|
||||
const useTmdb =
|
||||
values.tv === IndexerType.TMDB || values.anime === IndexerType.TMDB;
|
||||
values.tv === MetadataProviderType.TMDB ||
|
||||
values.anime === MetadataProviderType.TMDB;
|
||||
const useTvdb =
|
||||
values.tv === IndexerType.TVDB || values.anime === IndexerType.TVDB;
|
||||
values.tv === MetadataProviderType.TVDB ||
|
||||
values.anime === MetadataProviderType.TVDB;
|
||||
|
||||
const testData = {
|
||||
tmdb: useTmdb,
|
||||
@@ -141,8 +144,8 @@ const SettingsMetadata = () => {
|
||||
try {
|
||||
const response = await axios.put<{
|
||||
success: boolean;
|
||||
tv: IndexerType;
|
||||
anime: IndexerType;
|
||||
tv: MetadataProviderType;
|
||||
anime: MetadataProviderType;
|
||||
tests?: {
|
||||
tvdb: ProviderStatus;
|
||||
tmdb: ProviderStatus;
|
||||
@@ -252,8 +255,8 @@ const SettingsMetadata = () => {
|
||||
}
|
||||
|
||||
const initialValues: MetadataValues = data?.metadata || {
|
||||
tv: IndexerType.TMDB,
|
||||
anime: IndexerType.TMDB,
|
||||
tv: MetadataProviderType.TMDB,
|
||||
anime: MetadataProviderType.TMDB,
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -266,7 +269,9 @@ const SettingsMetadata = () => {
|
||||
/>
|
||||
|
||||
<div className="mb-6">
|
||||
<h3 className="heading">Metadata</h3>
|
||||
<h3 className="heading">
|
||||
{intl.formatMessage(messages.metadataProviderSettings)}
|
||||
</h3>
|
||||
<p className="description">
|
||||
{intl.formatMessage(messages.metadataSettings)}
|
||||
</p>
|
||||
@@ -331,7 +336,7 @@ const SettingsMetadata = () => {
|
||||
<Form className="section" data-testid="settings-main-form">
|
||||
<div className="mb-6">
|
||||
<h2 className="heading">
|
||||
{intl.formatMessage(messages.indexerSelection)}
|
||||
{intl.formatMessage(messages.metadataProviderSelection)}
|
||||
</h2>
|
||||
<p className="description">
|
||||
{intl.formatMessage(messages.chooseProvider)}
|
||||
@@ -339,14 +344,17 @@ const SettingsMetadata = () => {
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<label htmlFor="tvIndexer" className="checkbox-label">
|
||||
<label
|
||||
htmlFor="tv-metadata-provider"
|
||||
className="checkbox-label"
|
||||
>
|
||||
<span className="mr-2">
|
||||
{intl.formatMessage(messages.seriesIndexer)}
|
||||
{intl.formatMessage(messages.seriesMetadataProvider)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<MetadataSelector
|
||||
testId="tv-indexer-selector"
|
||||
testId="tv-metadata-provider-selector"
|
||||
value={values.metadata.tv}
|
||||
onChange={(value) => setFieldValue('metadata.tv', value)}
|
||||
isDisabled={isSubmitting}
|
||||
@@ -355,14 +363,17 @@ const SettingsMetadata = () => {
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<label htmlFor="animeIndexer" className="checkbox-label">
|
||||
<label
|
||||
htmlFor="anime-metadata-provider"
|
||||
className="checkbox-label"
|
||||
>
|
||||
<span className="mr-2">
|
||||
{intl.formatMessage(messages.animeIndexer)}
|
||||
{intl.formatMessage(messages.animeMetadataProvider)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<MetadataSelector
|
||||
testId="anime-indexer-selector"
|
||||
testId="anime-metadata-provider-selector"
|
||||
value={values.metadata.anime}
|
||||
onChange={(value) =>
|
||||
setFieldValue('metadata.anime', value)
|
||||
|
||||
@@ -307,7 +307,7 @@
|
||||
"components.ManageSlideOver.removearr4k": "Remove from 4K {arr}",
|
||||
"components.ManageSlideOver.tvshow": "series",
|
||||
"components.MediaSlider.ShowMoreCard.seemore": "See More",
|
||||
"components.MetadataSelector.selectIndexer": "Select a metadata provider",
|
||||
"components.MetadataSelector.selectMetdataProvider": "Select a metadata provider",
|
||||
"components.MetadataSelector.tmdbLabel": "The Movie Database (TMDB)",
|
||||
"components.MetadataSelector.tvdbLabel": "TheTVDB",
|
||||
"components.MovieDetails.MovieCast.fullcast": "Full Cast",
|
||||
@@ -1096,7 +1096,7 @@
|
||||
"components.Settings.addsonarr": "Add Sonarr Server",
|
||||
"components.Settings.advancedTooltip": "Incorrectly configuring this setting may result in broken functionality",
|
||||
"components.Settings.allChosenProvidersAreOperational": "All chosen metadata providers are operational",
|
||||
"components.Settings.animeIndexer": "Anime metadata provider",
|
||||
"components.Settings.animeMetadataProvider": "Anime metadata provider",
|
||||
"components.Settings.apiKey": "API key",
|
||||
"components.Settings.blacklistedTagImportInstructions": "Paste blacklist tag configuration below.",
|
||||
"components.Settings.blacklistedTagImportTitle": "Import Blacklisted Tag Configuration",
|
||||
@@ -1123,7 +1123,6 @@
|
||||
"components.Settings.general": "General",
|
||||
"components.Settings.hostname": "Hostname or IP Address",
|
||||
"components.Settings.importBlacklistedTagsTip": "Import blacklisted tag configuration",
|
||||
"components.Settings.indexerSelection": "Metadata Provider Selection",
|
||||
"components.Settings.invalidKeyword": "{keywordId} is not a TMDB keyword.",
|
||||
"components.Settings.invalidurlerror": "Unable to connect to {mediaServerName} server.",
|
||||
"components.Settings.is4k": "4K",
|
||||
@@ -1157,6 +1156,7 @@
|
||||
"components.Settings.menuPlexSettings": "Plex",
|
||||
"components.Settings.menuServices": "Services",
|
||||
"components.Settings.menuUsers": "Users",
|
||||
"components.Settings.metadataProviderSelection": "Metadata Provider Selection",
|
||||
"components.Settings.metadataSettings": "Settings for metadata provider",
|
||||
"components.Settings.metadataSettingsSaved": "Metadata provider settings saved",
|
||||
"components.Settings.no": "No",
|
||||
@@ -1188,7 +1188,7 @@
|
||||
"components.Settings.scanbackground": "Scanning will run in the background. You can continue the setup process in the meantime.",
|
||||
"components.Settings.scanning": "Syncing…",
|
||||
"components.Settings.searchKeywords": "Search keywords…",
|
||||
"components.Settings.seriesIndexer": "Series metadata provider",
|
||||
"components.Settings.seriesMetadataProvider": "Series metadata provider",
|
||||
"components.Settings.serverLocal": "local",
|
||||
"components.Settings.serverRemote": "remote",
|
||||
"components.Settings.serverSecure": "secure",
|
||||
|
||||
Reference in New Issue
Block a user