Skip to content

Commit

Permalink
refactor(tvdb): use tvdb api
Browse files Browse the repository at this point in the history
  • Loading branch information
TOomaAh committed Jan 20, 2025
1 parent a54c360 commit c5d77a0
Show file tree
Hide file tree
Showing 15 changed files with 331 additions and 128 deletions.
39 changes: 39 additions & 0 deletions server/api/metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { TvShowIndexer } from '@server/api/indexer';
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<TvShowIndexer> => {
try {
const settings = await getSettings();

if (!settings.tvdb.apiKey || mediaType == 'movie') {
return new TheMovieDb();
}

if (
mediaType == 'tv' &&
settings.metadataSettings.tvShow == 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();
}
};
124 changes: 92 additions & 32 deletions server/api/tvdb/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import type {
import type {
TvdbEpisode,
TvdbLoginResponse,
TvdbSeason,
TvdbTvShowDetail,
TvdbSeasonDetails,
TvdbTvDetails,
} from '@server/api/tvdb/interfaces';
import cacheManager, { type AvailableCacheIds } from '@server/lib/cache';
import { getSettings } from '@server/lib/settings';
import logger from '@server/logger';

interface TvdbConfig {
Expand All @@ -36,16 +37,21 @@ type TvdbId = number;
type ValidTvdbId = Exclude<TvdbId, TvdbIdStatus.INVALID>;

class Tvdb extends ExternalAPI implements TvShowIndexer {
static instance: Tvdb;
private readonly tmdb: TheMovieDb;
private static readonly DEFAULT_CACHE_TTL = 43200;
private static readonly DEFAULT_LANGUAGE = 'en';

constructor(config: Partial<TvdbConfig> = {}) {
const finalConfig = { ...DEFAULT_CONFIG, ...config };
private static readonly DEFAULT_LANGUAGE = 'eng';
private token: string;
private apiKey?: string;
private pin?: string;

constructor(apiKey: string, pin?: string) {
const finalConfig = { ...DEFAULT_CONFIG };
super(
finalConfig.baseUrl,
{},
{
apiKey: apiKey,
},
{
nodeCache: cacheManager.getCache(finalConfig.cachePrefix).data,
rateLimit: {
Expand All @@ -54,9 +60,33 @@ class Tvdb extends ExternalAPI implements TvShowIndexer {
},
}
);
this.apiKey = apiKey;
this.pin = pin;
this.tmdb = new TheMovieDb();
}

public static async getInstance(): Promise<Tvdb> {
if (!this.instance) {
const settings = await getSettings();

if (!settings.tvdb.apiKey) {
throw new Error('TVDB API key is not set');
}

try {
this.instance = new Tvdb(settings.tvdb.apiKey, settings.tvdb.pin);
await this.instance.login();
} catch (error) {
logger.error(`Failed to login to TVDB: ${error.message}`);
throw new Error('TVDB API key is not set');
}

this.instance = new Tvdb(settings.tvdb.apiKey, settings.tvdb.pin);
}

return this.instance;
}

public async test(): Promise<TvdbLoginResponse> {
try {
return await this.get<TvdbLoginResponse>('/en/445009', {});
Expand All @@ -66,6 +96,21 @@ class Tvdb extends ExternalAPI implements TvShowIndexer {
}
}

async handleRenewToken(): Promise<TvdbLoginResponse> {
throw new Error('Method not implemented.');
}

async login(): Promise<TvdbLoginResponse> {
const response = await this.post<TvdbLoginResponse>('/login', {
apiKey: process.env.TVDB_API_KEY,
});

this.defaultHeaders.Authorization = `Bearer ${response.token}`;
this.token = response.token;

return response;
}

public async getShowByTvdbId({
tvdbId,
}: {
Expand Down Expand Up @@ -152,29 +197,34 @@ class Tvdb extends ExternalAPI implements TvShowIndexer {
}
}

private async fetchTvdbShowData(tvdbId: number): Promise<TvdbTvShowDetail> {
return await this.get<TvdbTvShowDetail>(
`/en/${tvdbId}`,
{},
private async fetchTvdbShowData(tvdbId: number): Promise<TvdbTvDetails> {
return await this.get<TvdbTvDetails>(
`/series/${tvdbId}/extended?meta=episodes`,
{
short: 'true',
},
Tvdb.DEFAULT_CACHE_TTL
);
}

private processSeasons(tvdbData: TvdbTvShowDetail): TmdbTvSeasonResult[] {
if (!tvdbData || !tvdbData.seasons) {
private processSeasons(tvdbData: TvdbTvDetails): TmdbTvSeasonResult[] {
if (!tvdbData || !tvdbData.seasons || !tvdbData.episodes) {
return [];
}

return tvdbData.seasons
.filter((season) => season.seasonNumber !== 0)
.filter(
(season) =>
season.number > 0 && season.type && season.type.type === 'official'
)
.map((season) => this.createSeasonData(season, tvdbData));
}

private createSeasonData(
season: TvdbSeason,
tvdbData: TvdbTvShowDetail
season: TvdbSeasonDetails,
tvdbData: TvdbTvDetails
): TmdbTvSeasonResult {
if (!season.seasonNumber) {
if (!season.number) {
return {
id: 0,
episode_count: 0,
Expand All @@ -187,15 +237,15 @@ class Tvdb extends ExternalAPI implements TvShowIndexer {
}

const episodeCount = tvdbData.episodes.filter(
(episode) => episode.seasonNumber === season.seasonNumber
(episode) => episode.seasonNumber === season.number
).length;

return {
id: tvdbData.tvdbId,
id: tvdbData.id,
episode_count: episodeCount,
name: `${season.seasonNumber}`,
name: `${season.number}`,
overview: '',
season_number: season.seasonNumber,
season_number: season.number,
poster_path: '',
air_date: '',
};
Expand All @@ -204,25 +254,35 @@ class Tvdb extends ExternalAPI implements TvShowIndexer {
private async getTvdbSeasonData(
tvdbId: number,
seasonNumber: number,
tvId: number
tvId: number,
language: string = Tvdb.DEFAULT_LANGUAGE
): Promise<TmdbSeasonWithEpisodes> {
const tvdbSeason = await this.fetchTvdbShowData(tvdbId);
const tvdbData = await this.fetchTvdbShowData(tvdbId);

if (!tvdbData) {
return this.createEmptySeasonResponse(tvId);
}

const seasons = await this.get<TvdbSeasonDetails>(
`/series/${tvdbId}/episodes/official/${language}`,
{}
);

const episodes = this.processEpisodes(tvdbSeason, seasonNumber, tvId);
const episodes = this.processEpisodes(seasons, seasonNumber, tvId);

return {
episodes,
external_ids: { tvdb_id: tvdbSeason.tvdbId },
external_ids: { tvdb_id: tvdbId },
name: '',
overview: '',
id: tvdbSeason.tvdbId,
air_date: tvdbSeason.firstAired,
id: seasons.id,
air_date: seasons.firstAired,
season_number: episodes.length,
};
}

private processEpisodes(
tvdbSeason: TvdbTvShowDetail,
tvdbSeason: TvdbSeasonDetails,
seasonNumber: number,
tvId: number
): TmdbTvEpisodeResult[] {
Expand All @@ -241,10 +301,10 @@ class Tvdb extends ExternalAPI implements TvShowIndexer {
tvId: number
): TmdbTvEpisodeResult {
return {
id: episode.tvdbId,
air_date: episode.airDate,
episode_number: episode.episodeNumber,
name: episode.title || `Episode ${index + 1}`,
id: episode.id,
air_date: episode.aired,
episode_number: episode.number,
name: episode.name || `Episode ${index + 1}`,
overview: episode.overview || '',
season_number: episode.seasonNumber,
production_code: '',
Expand Down
Loading

0 comments on commit c5d77a0

Please sign in to comment.