Skip to content
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
26e22e9
feat(tvdb): get tv seasons/episodes with tvdb
TOomaAh Jul 26, 2024
3906430
fix: fix rate limiter index tvdb indexer
TOomaAh Jul 26, 2024
aa7de13
fix(usersettings): remove unused column tvdbtoken
TOomaAh Jul 26, 2024
b88606a
refactor(tvdb): replace tvdb api by skyhook
TOomaAh Oct 19, 2024
377c6a4
fix: error during get episodes
TOomaAh Oct 20, 2024
7107f1e
fix: error if tmdb poster is null
TOomaAh Oct 20, 2024
1fbe4d2
refactor: clean tvdb indexer code
TOomaAh Oct 22, 2024
4a19c81
fix: wrong language with tmdb indexer
TOomaAh Oct 23, 2024
f912783
style: replace avalaible to available
TOomaAh Oct 26, 2024
bedc8c4
style: tvdb.login to tvdb.test
TOomaAh Oct 26, 2024
3b908af
fix(test): fix discover test
TOomaAh Oct 26, 2024
c5987e2
fix(test): wrong url tv-details
TOomaAh Oct 26, 2024
f4997d0
test(tvdb): add tvdb tests
TOomaAh Oct 27, 2024
5b216ab
style(tvdb): rename pokemon to correct tv show
TOomaAh Oct 27, 2024
a54d3c5
refactor(indexer): remove unused getSeasonIdentifier method
TOomaAh Oct 28, 2024
1fb1dc9
refactor(settings): replace tvdb object to boolean type
TOomaAh Oct 29, 2024
b458986
refactor(tmdb): reduce still path condition
TOomaAh Oct 29, 2024
9b2c889
test(tvdb): change 'use' to 'tvdb' condition check
TOomaAh Oct 29, 2024
b1548b7
fix(tmdb): fix build
TOomaAh Dec 12, 2024
f755a5f
fix(build): revert package.json
TOomaAh Dec 12, 2024
a4ef53e
fix(tvdb): ensure that seasons contain data
TOomaAh Dec 12, 2024
5f49a97
refactor(swagger): fix /tvdb/test response
TOomaAh Dec 12, 2024
4dcb308
fix(scanner): add tvdb indexer for scanner
TOomaAh Jan 8, 2025
ac76be5
refactor(tvdb): remove skyhook api
TOomaAh Jan 19, 2025
3c9ed46
refactor(tvdb): use tvdb api
TOomaAh Jan 20, 2025
be91a3f
fix(tvdb): rename tvdb to medatada
TOomaAh Jan 20, 2025
4104f3d
refactor(medata): add tvdb settings
TOomaAh Jan 21, 2025
4b0652d
refactor(metadata): rewrite metadata settings
TOomaAh Mar 19, 2025
56f33fe
refactor(metadata): refactor metadata routes
TOomaAh Mar 19, 2025
07e8c46
refactor(metadata): remove french comments
TOomaAh Mar 19, 2025
25c2788
refactor(metadata): refactor tvdb api calls
TOomaAh Mar 20, 2025
5232950
style(prettier): run prettier
TOomaAh Mar 20, 2025
3952312
fix(scanner): fix jellyfin scanner with tvdb provider
TOomaAh Mar 20, 2025
3c310f2
fix(scanner): fix plex scanner tvdb provider
TOomaAh Mar 20, 2025
c24eeba
style(provider): change provider name in info section
TOomaAh Mar 20, 2025
6e27efc
style(provider): full provider name in select
TOomaAh Mar 20, 2025
1618eb9
style(provider): remove french comment
TOomaAh Mar 20, 2025
227533a
fix(tests): fix all cypress tests
Apr 16, 2025
e52cb4a
refactor(tvdb): fix apikey
Apr 16, 2025
df9921f
refactor(tmdb): apply prettier
Apr 16, 2025
503d8b1
refactor(tvdb): remove logger info
TOomaAh Apr 16, 2025
09d68e6
feat(metadata): replace fetch with axios for API calls
TOomaAh Apr 16, 2025
b26689b
feat(provider): replace indexer by provider
TOomaAh Apr 16, 2025
bade7f5
fix(tests): fix cypress test
TOomaAh Apr 16, 2025
da84b16
chore: add project-wide apikey for tvdb
fallenbagel May 5, 2025
d762123
chore: add correct application-wide key
fallenbagel May 6, 2025
ac24c37
fix(test): fix test with default provider tmdb anime
TOomaAh May 6, 2025
7814fff
style(cypress): fix anime name variable
TOomaAh May 6, 2025
61b764b
chore(i18n): remove french translation + apply i18n:extract
TOomaAh Aug 28, 2025
04c22ef
style(wording): standardize naming to "Metadata Provider" in UI text
TOomaAh Aug 28, 2025
04f0506
docs(comments): translate from French to English
TOomaAh Aug 28, 2025
be3454c
refactor(tvdb): remove unnecessary try/catch block
TOomaAh Aug 28, 2025
c0cc109
feat(i18n): add missing translations
TOomaAh Aug 28, 2025
ca52a3e
fix(scanner): correct metadata provider ID from Tmdb to Tvdb
TOomaAh Aug 28, 2025
9c072e4
style(settings): clarify navigation label from "Metadata" to "Metadat…
TOomaAh Aug 28, 2025
82d81fd
style(logs): update error log label from "Metadata" to "MetadataProvi…
TOomaAh Aug 28, 2025
d7655e5
refactor(tvdb): replace indexer by metadata providers
TOomaAh Aug 31, 2025
668a6ae
refactor(settings): remove metadata providers logo
TOomaAh Aug 31, 2025
d02b022
fix(config): restore missing config/db/.gitkeep file
TOomaAh Sep 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions cypress/e2e/providers/tvdb.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
describe('TVDB Integration', () => {
// Constants for routes and selectors
const ROUTES = {
home: '/',
metadataSettings: '/settings/metadata',
tomorrowIsOursTvShow: '/tv/72879',
monsterTvShow: '/tv/225634',
dragonnBallZKaiAnime: '/tv/61709',
};

const SELECTORS = {
sidebarToggle: '[data-testid=sidebar-toggle]',
sidebarSettingsMobile: '[data-testid=sidebar-menu-settings-mobile]',
settingsNavDesktop: 'nav[data-testid="settings-nav-desktop"]',
metadataTestButton: 'button[type="button"]:contains("Test")',
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"]',
seasonSelector: '[data-testid="season-selector"]',
season1: 'Season 1',
season2: 'Season 2',
season3: 'Season 3',
episodeList: '[data-testid="episode-list"]',
episode9: '9 - Hang Men',
};

// Reusable commands
const navigateToMetadataSettings = () => {
cy.visit(ROUTES.home);
cy.get(SELECTORS.sidebarToggle).click();
cy.get(SELECTORS.sidebarSettingsMobile).click();
cy.get(
`${SELECTORS.settingsNavDesktop} a[href="${ROUTES.metadataSettings}"]`
).click();
};

const testAndVerifyMetadataConnection = () => {
cy.intercept('POST', '/api/v1/settings/metadatas/test').as(
'testConnection'
);
cy.get(SELECTORS.metadataTestButton).click();
return cy.wait('@testConnection');
};

const saveMetadataSettings = (customBody = null) => {
// Si un corps personnalisé est fourni, utilisez-le pour modifier la requête
if (customBody) {
cy.intercept('PUT', '/api/v1/settings/metadatas', (req) => {
req.body = customBody;
}).as('saveMetadata');
} else {
// Sinon, juste intercepter sans modifier
cy.intercept('PUT', '/api/v1/settings/metadatas').as('saveMetadata');
}

cy.get(SELECTORS.metadataSaveButton).click();
return cy.wait('@saveMetadata');
};

beforeEach(() => {
// Perform login
cy.login(Cypress.env('ADMIN_EMAIL'), Cypress.env('ADMIN_PASSWORD'));

// Navigate to Metadata settings
navigateToMetadataSettings();

// Verify we're on the correct settings page
cy.contains('h3', 'Metadata').should('be.visible');

// Configure TVDB as TV provider and test connection
// Supposons que vous avez ajouté data-testid au div parent du Select
cy.get('[data-testid="tv-indexer-selector"]').click();

// get id react-select-4-option-1
cy.get('[id^="react-select-4-option-"]').contains('TheTVDB').click();

// Test the connection
testAndVerifyMetadataConnection().then(({ response }) => {
expect(response.statusCode).to.equal(200);
// Check TVDB connection status
cy.get(SELECTORS.tvdbStatus).should('contain', 'Operational');
});

// Save settings
saveMetadataSettings({
anime: 'tvdb',
tv: 'tvdb',
}).then(({ response }) => {
expect(response.statusCode).to.equal(200);
expect(response.body.tv).to.equal('tvdb');
});
});

it('should display "Tomorrow is Ours" show information with multiple seasons from TVDB', () => {
// Navigate to the TV show
cy.visit(ROUTES.tomorrowIsOursTvShow);

// Verify that multiple seasons are displayed (TMDB has only 1 season, TVDB has multiple)
//cy.get(SELECTORS.seasonSelector).should('exist');
cy.intercept('/api/v1/tv/225634/season/1').as('season1');
// Select Season 2 and verify it loads
cy.contains(SELECTORS.season2)
.should('be.visible')
.scrollIntoView()
.click();

// Verify that episodes are displayed for Season 2
cy.contains('260 - Episode 506').should('be.visible');
});

it('Should display "Monster" show information correctly when not existing on TVDB', () => {
// Navigate to the TV show
cy.visit(ROUTES.monsterTvShow);

// Intercept season 1 request
cy.intercept('/api/v1/tv/225634/season/1').as('season1');

// Select Season 1
cy.contains(SELECTORS.season1)
.should('be.visible')
.scrollIntoView()
.click();

// Wait for the season data to load
cy.wait('@season1');

// Verify specific episode exists
cy.contains(SELECTORS.episode9).should('be.visible');
});

it('should display "Dragon Ball Z Kai" show information with multiple only 2 seasons from TVDB', () => {
// Navigate to the TV show
cy.visit(ROUTES.dragonnBallZKaiAnime);

// Intercept season 1 request
cy.intercept('/api/v1/tv/61709/season/1').as('season1');

// Select Season 2 and verify it visible
cy.contains(SELECTORS.season2)
.should('be.visible')
.scrollIntoView()
.click();

// select season 3 and verify it not visible
cy.contains(SELECTORS.season3).should('not.exist');
});
});
81 changes: 78 additions & 3 deletions jellyseerr-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,20 @@ components:
serverID:
type: string
readOnly: true
MetadataSettings:
type: object
properties:
settings:
type: object
properties:
tv:
type: string
enum: [tvdb, tmdb]
example: 'tvdb'
anime:
type: string
enum: [tvdb, tmdb]
example: 'tvdb'
TautulliSettings:
type: object
properties:
Expand Down Expand Up @@ -2568,6 +2582,67 @@ paths:
type: string
thumb:
type: string
/settings/metadatas:
get:
summary: Get Metadata settings
description: Retrieves current Metadata settings.
tags:
- settings
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/MetadataSettings'
put:
summary: Update Metadata settings
description: Updates Metadata settings with the provided values.
tags:
- settings
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/MetadataSettings'
responses:
'200':
description: 'Values were successfully updated'
content:
application/json:
schema:
$ref: '#/components/schemas/MetadataSettings'
/settings/metadatas/test:
post:
summary: Test Provider configuration
description: Tests if the TVDB configuration is valid. Returns a list of available languages on success.
tags:
- settings
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
tmdb:
type: boolean
example: true
tvdb:
type: boolean
example: true
responses:
'200':
description: Succesfully connected to TVDB
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: 'Successfully connected to TVDB'
/settings/tautulli:
get:
summary: Get Tautulli settings
Expand Down Expand Up @@ -6472,7 +6547,7 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/TvDetails'
/tv/{tvId}/season/{seasonId}:
/tv/{tvId}/season/{seasonNumber}:
get:
summary: Get season details and episode list
description: Returns season details with a list of episodes in a JSON object.
Expand All @@ -6486,11 +6561,11 @@ paths:
type: number
example: 76479
- in: path
name: seasonId
name: seasonNumber
required: true
schema:
type: number
example: 1
example: 123456
- in: query
name: language
schema:
Expand Down
2 changes: 1 addition & 1 deletion server/api/externalapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const DEFAULT_TTL = 300;
// 10 seconds default rolling buffer (in ms)
const DEFAULT_ROLLING_BUFFER = 10000;

interface ExternalAPIOptions {
export interface ExternalAPIOptions {
nodeCache?: NodeCache;
headers?: Record<string, unknown>;
rateLimit?: {
Expand Down
36 changes: 36 additions & 0 deletions server/api/metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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 logger from '@server/logger';

export const getMetadataProvider = async (
mediaType: 'movie' | 'tv' | 'anime'
): Promise<TvShowProvider> => {
try {
const settings = await getSettings();

if (mediaType == 'movie') {
return new TheMovieDb();
}

if (mediaType == 'tv' && settings.metadataSettings.tv == IndexerType.TVDB) {
return await Tvdb.getInstance();
}

if (
mediaType == 'anime' &&
settings.metadataSettings.anime == IndexerType.TVDB
) {
return await Tvdb.getInstance();
}

return new TheMovieDb();
} catch (e) {
logger.error('Failed to get metadata provider', {
label: 'Metadata',
message: e.message,
});
return new TheMovieDb();
}
};
30 changes: 30 additions & 0 deletions server/api/provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type {
TmdbSeasonWithEpisodes,
TmdbTvDetails,
} from '@server/api/themoviedb/interfaces';

export interface TvShowProvider {
getTvShow({
tvId,
language,
}: {
tvId: number;
language?: string;
}): Promise<TmdbTvDetails>;
getTvSeason({
tvId,
seasonNumber,
language,
}: {
tvId: number;
seasonNumber: number;
language?: string;
}): Promise<TmdbSeasonWithEpisodes>;
getShowByTvdbId({
tvdbId,
language,
}: {
tvdbId: number;
language?: string;
}): Promise<TmdbTvDetails>;
}
10 changes: 9 additions & 1 deletion server/api/themoviedb/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ExternalAPI from '@server/api/externalapi';
import type { TvShowProvider } from '@server/api/provider';
import cacheManager from '@server/lib/cache';
import { getSettings } from '@server/lib/settings';
import { sortBy } from 'lodash';
Expand Down Expand Up @@ -120,7 +121,7 @@ interface DiscoverTvOptions {
certificationCountry?: string;
}

class TheMovieDb extends ExternalAPI {
class TheMovieDb extends ExternalAPI implements TvShowProvider {
private locale: string;
private discoverRegion?: string;
private originalLanguage?: string;
Expand Down Expand Up @@ -341,6 +342,13 @@ class TheMovieDb extends ExternalAPI {
}
);

data.episodes = data.episodes.map((episode) => {
if (episode.still_path) {
episode.still_path = `https://image.tmdb.org/t/p/original/${episode.still_path}`;
}
return episode;
});

return data;
} catch (e) {
throw new Error(`[TMDB] Failed to fetch TV show details: ${e.message}`);
Expand Down
2 changes: 1 addition & 1 deletion server/api/themoviedb/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ export interface TmdbTvEpisodeResult {
show_id: number;
still_path: string;
vote_average: number;
vote_cuont: number;
vote_count: number;
}

export interface TmdbTvSeasonResult {
Expand Down
Loading
Loading