refactor(tvdb): use tvdb api
This commit is contained in:
39
server/api/metadata.ts
Normal file
39
server/api/metadata.ts
Normal file
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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 {
|
||||
@@ -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: {
|
||||
@@ -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', {});
|
||||
@@ -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,
|
||||
}: {
|
||||
@@ -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,
|
||||
@@ -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: '',
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
const episodes = this.processEpisodes(tvdbSeason, seasonNumber, tvId);
|
||||
if (!tvdbData) {
|
||||
return this.createEmptySeasonResponse(tvId);
|
||||
}
|
||||
|
||||
const seasons = await this.get<TvdbSeasonDetails>(
|
||||
`/series/${tvdbId}/episodes/official/${language}`,
|
||||
{}
|
||||
);
|
||||
|
||||
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[] {
|
||||
@@ -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: '',
|
||||
|
||||
@@ -3,73 +3,142 @@ export interface TvdbBaseResponse<T> {
|
||||
errors: string;
|
||||
}
|
||||
|
||||
export interface TvdbLoginResponse {
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface TvdbLoginResponse extends TvdbBaseResponse<{ token: string }> {
|
||||
data: { token: string };
|
||||
}
|
||||
|
||||
export interface TvdbTvShowDetail {
|
||||
tvdbId: number;
|
||||
title: string;
|
||||
overview: string;
|
||||
interface TvDetailsAliases {
|
||||
language: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface TvDetailsStatus {
|
||||
id: number;
|
||||
name: string;
|
||||
recordType: string;
|
||||
keepUpdated: boolean;
|
||||
}
|
||||
|
||||
export interface TvdbTvDetails extends TvdbBaseResponse<TvdbTvDetails> {
|
||||
id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
image: string;
|
||||
nameTranslations: string[];
|
||||
overwiewTranslations: string[];
|
||||
aliases: TvDetailsAliases[];
|
||||
firstAired: Date;
|
||||
lastAired: Date;
|
||||
nextAired: Date | string;
|
||||
score: number;
|
||||
status: TvDetailsStatus;
|
||||
originalCountry: string;
|
||||
originalLanguage: string;
|
||||
language: string;
|
||||
firstAired: string;
|
||||
lastAired: string;
|
||||
tvMazeId: number;
|
||||
tmdbId: number;
|
||||
imdbId: string;
|
||||
lastUpdated: string;
|
||||
status: string;
|
||||
runtime: number;
|
||||
timeOfDay: TvdbTimeOfDay;
|
||||
originalNetwork: string;
|
||||
network: string;
|
||||
genres: string[];
|
||||
alternativeTitles: TvdbAlternativeTitle[];
|
||||
actors: TvdbActor[];
|
||||
images: TvdbImage[];
|
||||
seasons: TvdbSeason[];
|
||||
defaultSeasonType: string;
|
||||
isOrderRandomized: boolean;
|
||||
lastUpdated: Date;
|
||||
averageRuntime: number;
|
||||
seasons: TvdbSeasonDetails[];
|
||||
episodes: TvdbEpisode[];
|
||||
}
|
||||
|
||||
export interface TvdbTimeOfDay {
|
||||
hours: number;
|
||||
minutes: number;
|
||||
interface TvdbCompanyType {
|
||||
companyTypeId: number;
|
||||
companyTypeName: string;
|
||||
}
|
||||
|
||||
export interface TvdbAlternativeTitle {
|
||||
title: string;
|
||||
interface TvdbParentCompany {
|
||||
id?: number;
|
||||
name?: string;
|
||||
relation?: {
|
||||
id?: number;
|
||||
typeName?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TvdbActor {
|
||||
interface TvdbCompany {
|
||||
id: number;
|
||||
name: string;
|
||||
character: string;
|
||||
image?: string;
|
||||
slug: string;
|
||||
nameTranslations?: string[];
|
||||
overviewTranslations?: string[];
|
||||
aliases?: string[];
|
||||
country: string;
|
||||
primaryCompanyType: number;
|
||||
activeDate: string;
|
||||
inactiveDate?: string;
|
||||
companyType: TvdbCompanyType;
|
||||
parentCompany: TvdbParentCompany;
|
||||
tagOptions?: string[];
|
||||
}
|
||||
|
||||
export interface TvdbImage {
|
||||
coverType: string;
|
||||
url: string;
|
||||
interface TvdbType {
|
||||
id: number;
|
||||
name: string;
|
||||
type: string;
|
||||
alternateName?: string;
|
||||
}
|
||||
|
||||
export interface TvdbSeason {
|
||||
seasonNumber: number;
|
||||
interface TvdbArtwork {
|
||||
id: number;
|
||||
image: string;
|
||||
thumbnail: string;
|
||||
language: string;
|
||||
type: number;
|
||||
score: number;
|
||||
width: number;
|
||||
height: number;
|
||||
includesText: boolean;
|
||||
}
|
||||
|
||||
export interface TvdbEpisode {
|
||||
tvdbShowId: number;
|
||||
tvdbId: number;
|
||||
seasonNumber: number;
|
||||
episodeNumber: number;
|
||||
absoluteEpisodeNumber: number;
|
||||
title?: string;
|
||||
airDate: string;
|
||||
airDateUtc: string;
|
||||
runtime?: number;
|
||||
id: number;
|
||||
seriesId: number;
|
||||
name: string;
|
||||
aired: string;
|
||||
runtime: number;
|
||||
nameTranslations: string[];
|
||||
overview?: string;
|
||||
image?: string;
|
||||
overviewTranslations: string[];
|
||||
image: string;
|
||||
imageType: number;
|
||||
isMovie: number;
|
||||
seasons?: string[];
|
||||
number: number;
|
||||
absoluteNumber: number;
|
||||
seasonNumber: number;
|
||||
lastUpdated: string;
|
||||
finaleType?: string;
|
||||
year: string;
|
||||
}
|
||||
|
||||
export interface TvdbSeasonDetails extends TvdbBaseResponse<TvdbSeasonDetails> {
|
||||
id: number;
|
||||
seriesId: number;
|
||||
type: TvdbType;
|
||||
number: number;
|
||||
nameTranslations: string[];
|
||||
overviewTranslations: string[];
|
||||
image: string;
|
||||
imageType: number;
|
||||
companies: {
|
||||
studio: TvdbCompany[];
|
||||
network: TvdbCompany[];
|
||||
production: TvdbCompany[];
|
||||
distributor: TvdbCompany[];
|
||||
special_effects: TvdbCompany[];
|
||||
};
|
||||
lastUpdated: string;
|
||||
year: string;
|
||||
episodes: TvdbEpisode[];
|
||||
trailers: string[];
|
||||
artwork: TvdbArtwork[];
|
||||
tagOptions?: string[];
|
||||
firstAired: string;
|
||||
}
|
||||
|
||||
export interface TvdbEpisodeTranslation
|
||||
|
||||
@@ -3,7 +3,7 @@ import { MediaStatus, MediaType } from '@server/constants/media';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import Media from '@server/entity/Media';
|
||||
import Season from '@server/entity/Season';
|
||||
import { getIndexer, getSettings } from '@server/lib/settings';
|
||||
import { getSettings } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
import AsyncLock from '@server/utils/asyncLock';
|
||||
import { randomUUID } from 'crypto';
|
||||
@@ -62,7 +62,6 @@ class BaseScanner<T> {
|
||||
protected sessionId: string;
|
||||
protected running = false;
|
||||
readonly asyncLock = new AsyncLock();
|
||||
readonly tvShowIndexer = getIndexer();
|
||||
readonly tmdb = new TheMovieDb();
|
||||
|
||||
protected constructor(
|
||||
|
||||
@@ -10,7 +10,7 @@ import Media from '@server/entity/Media';
|
||||
import Season from '@server/entity/Season';
|
||||
import { User } from '@server/entity/User';
|
||||
import type { Library } from '@server/lib/settings';
|
||||
import { getIndexer, getSettings } from '@server/lib/settings';
|
||||
import { getSettings } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
import AsyncLock from '@server/utils/asyncLock';
|
||||
import { getHostname } from '@server/utils/getHostname';
|
||||
@@ -45,7 +45,6 @@ class JellyfinScanner {
|
||||
|
||||
constructor({ isRecentOnly }: { isRecentOnly?: boolean } = {}) {
|
||||
this.tmdb = new TheMovieDb();
|
||||
this.tvShowIndexer = getIndexer();
|
||||
|
||||
this.isRecentOnly = isRecentOnly ?? false;
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ class PlexScanner
|
||||
await this.processHamaSpecials(metadata, mediaIds.tvdbId);
|
||||
}
|
||||
|
||||
const tvShow = await this.tvShowIndexer.getTvShow({
|
||||
const tvShow = await this.tmdb.getTvShow({
|
||||
tvId: mediaIds.tmdbId,
|
||||
});
|
||||
|
||||
@@ -431,7 +431,7 @@ class PlexScanner
|
||||
const matchedtvdb = plexitem.guid.match(hamaTvdbRegex);
|
||||
|
||||
if (matchedtvdb) {
|
||||
const show = await this.tvShowIndexer.getShowByTvdbId({
|
||||
const show = await this.tmdb.getShowByTvdbId({
|
||||
tvdbId: Number(matchedtvdb[1]),
|
||||
});
|
||||
|
||||
@@ -465,7 +465,7 @@ class PlexScanner
|
||||
type: 'tvdb',
|
||||
});
|
||||
if (extResponse.tv_results[0]) {
|
||||
tvShow = await this.tvShowIndexer.getTvShow({
|
||||
tvShow = await this.tmdb.getTvShow({
|
||||
tvId: extResponse.tv_results[0].id,
|
||||
});
|
||||
mediaIds.tvdbId = result.tvdbId;
|
||||
|
||||
@@ -94,11 +94,11 @@ class SonarrScanner
|
||||
});
|
||||
|
||||
if (!media || !media.tmdbId) {
|
||||
tvShow = await this.tvShowIndexer.getShowByTvdbId({
|
||||
tvShow = await this.tmdb.getShowByTvdbId({
|
||||
tvdbId: sonarrSeries.tvdbId,
|
||||
});
|
||||
} else {
|
||||
tvShow = await this.tvShowIndexer.getTvShow({ tvId: media.tmdbId });
|
||||
tvShow = await this.tmdb.getTvShow({ tvId: media.tmdbId });
|
||||
}
|
||||
|
||||
const tmdbId = tvShow.id;
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import type { TvShowIndexer } from '@server/api/indexer';
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import Tvdb from '@server/api/tvdb';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import { Permission } from '@server/lib/permissions';
|
||||
import { runMigrations } from '@server/lib/settings/migrator';
|
||||
@@ -103,6 +100,32 @@ interface Quota {
|
||||
quotaDays?: number;
|
||||
}
|
||||
|
||||
export enum IndexerType {
|
||||
TMDB,
|
||||
TVDB,
|
||||
}
|
||||
|
||||
export interface MetadataSettings {
|
||||
tvShow: IndexerType;
|
||||
anime: IndexerType;
|
||||
}
|
||||
|
||||
export interface TvdbSettings {
|
||||
apiKey?: string;
|
||||
pin?: string;
|
||||
}
|
||||
|
||||
export interface ProxySettings {
|
||||
enabled: boolean;
|
||||
hostname: string;
|
||||
port: number;
|
||||
useSsl: boolean;
|
||||
user: string;
|
||||
password: string;
|
||||
bypassFilter: string;
|
||||
bypassLocalAddresses: boolean;
|
||||
}
|
||||
|
||||
export interface MainSettings {
|
||||
apiKey: string;
|
||||
applicationTitle: string;
|
||||
@@ -342,7 +365,8 @@ export interface AllSettings {
|
||||
notifications: NotificationSettings;
|
||||
jobs: Record<JobId, JobSettings>;
|
||||
network: NetworkSettings;
|
||||
tvdb: boolean;
|
||||
tvdb: TvdbSettings;
|
||||
metadataSettings: MetadataSettings;
|
||||
}
|
||||
|
||||
const SETTINGS_PATH = process.env.CONFIG_DIRECTORY
|
||||
@@ -403,7 +427,14 @@ class Settings {
|
||||
apiKey: '',
|
||||
},
|
||||
tautulli: {},
|
||||
tvdb: false,
|
||||
metadataSettings: {
|
||||
tvShow: IndexerType.TMDB,
|
||||
anime: IndexerType.TVDB,
|
||||
},
|
||||
tvdb: {
|
||||
apiKey: '',
|
||||
pin: '',
|
||||
},
|
||||
radarr: [],
|
||||
sonarr: [],
|
||||
public: {
|
||||
@@ -598,11 +629,19 @@ class Settings {
|
||||
this.data.tautulli = data;
|
||||
}
|
||||
|
||||
get tvdb(): boolean {
|
||||
get metadataSettings(): MetadataSettings {
|
||||
return this.data.metadataSettings;
|
||||
}
|
||||
|
||||
set metadataSettings(data: MetadataSettings) {
|
||||
this.data.metadataSettings = data;
|
||||
}
|
||||
|
||||
get tvdb(): TvdbSettings {
|
||||
return this.data.tvdb;
|
||||
}
|
||||
|
||||
set tvdb(data: boolean) {
|
||||
set tvdb(data: TvdbSettings) {
|
||||
this.data.tvdb = data;
|
||||
}
|
||||
|
||||
@@ -786,13 +825,4 @@ export const getSettings = (initialSettings?: AllSettings): Settings => {
|
||||
return settings;
|
||||
};
|
||||
|
||||
export const getIndexer = (): TvShowIndexer => {
|
||||
const settings = getSettings();
|
||||
if (settings.tvdb) {
|
||||
return new Tvdb();
|
||||
} else {
|
||||
return new TheMovieDb();
|
||||
}
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
|
||||
@@ -39,10 +39,10 @@ import { rescheduleJob } from 'node-schedule';
|
||||
import path from 'path';
|
||||
import semver from 'semver';
|
||||
import { URL } from 'url';
|
||||
import tvdbRoutes from './metadata';
|
||||
import notificationRoutes from './notifications';
|
||||
import radarrRoutes from './radarr';
|
||||
import sonarrRoutes from './sonarr';
|
||||
import tvdbRoutes from './tvdb';
|
||||
|
||||
const settingsRoutes = Router();
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import Tvdb from '@server/api/tvdb';
|
||||
import { getSettings } from '@server/lib/settings';
|
||||
import { getSettings, type TvdbSettings } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
import { Router } from 'express';
|
||||
|
||||
const tvdbRoutes = Router();
|
||||
|
||||
export interface TvdbSettings {
|
||||
export interface MetadataSettings {
|
||||
tvdb: boolean;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,10 @@ tvdbRoutes.put('/', (req, res) => {
|
||||
|
||||
const body = req.body as TvdbSettings;
|
||||
|
||||
settings.tvdb = body.tvdb ?? settings.tvdb ?? false;
|
||||
settings.tvdb = {
|
||||
apiKey: body.apiKey,
|
||||
pin: body.pin,
|
||||
};
|
||||
settings.save();
|
||||
|
||||
return res.status(200).json({
|
||||
@@ -32,7 +35,7 @@ tvdbRoutes.put('/', (req, res) => {
|
||||
|
||||
tvdbRoutes.post('/test', async (req, res, next) => {
|
||||
try {
|
||||
const tvdb = new Tvdb();
|
||||
const tvdb = await Tvdb.getInstance();
|
||||
await tvdb.test();
|
||||
return res.status(200).json({ message: 'Successfully connected to Tvdb' });
|
||||
} catch (e) {
|
||||
@@ -1,10 +1,12 @@
|
||||
import { getMetadataProvider } from '@server/api/metadata';
|
||||
import RottenTomatoes from '@server/api/rating/rottentomatoes';
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants';
|
||||
import type { TmdbKeyword } from '@server/api/themoviedb/interfaces';
|
||||
import { MediaType } from '@server/constants/media';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import Media from '@server/entity/Media';
|
||||
import { Watchlist } from '@server/entity/Watchlist';
|
||||
import { getIndexer } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
import { mapTvResult } from '@server/models/Search';
|
||||
import { mapSeasonWithEpisodes, mapTvDetails } from '@server/models/Tv';
|
||||
@@ -13,14 +15,21 @@ import { Router } from 'express';
|
||||
const tvRoutes = Router();
|
||||
|
||||
tvRoutes.get('/:id', async (req, res, next) => {
|
||||
const indexer = getIndexer();
|
||||
const tmdb = new TheMovieDb();
|
||||
|
||||
try {
|
||||
const tmdbTv = await tmdb.getTvShow({
|
||||
tvId: Number(req.params.id),
|
||||
});
|
||||
const indexer = tmdbTv.keywords.results.some(
|
||||
(keyword: TmdbKeyword) => keyword.id === ANIME_KEYWORD_ID
|
||||
)
|
||||
? await getMetadataProvider('anime')
|
||||
: await getMetadataProvider('tv');
|
||||
const tv = await indexer.getTvShow({
|
||||
tvId: Number(req.params.id),
|
||||
language: (req.query.language as string) ?? req.locale,
|
||||
});
|
||||
|
||||
const media = await Media.getMedia(tv.id, MediaType.TV);
|
||||
|
||||
const onUserWatchlist = await getRepository(Watchlist).exist({
|
||||
@@ -58,7 +67,15 @@ tvRoutes.get('/:id', async (req, res, next) => {
|
||||
|
||||
tvRoutes.get('/:id/season/:seasonNumber', async (req, res, next) => {
|
||||
try {
|
||||
const indexer = getIndexer();
|
||||
const tmdb = new TheMovieDb();
|
||||
const tmdbTv = await tmdb.getTvShow({
|
||||
tvId: Number(req.params.id),
|
||||
});
|
||||
const indexer = tmdbTv.keywords.results.some(
|
||||
(keyword: TmdbKeyword) => keyword.id === ANIME_KEYWORD_ID
|
||||
)
|
||||
? await getMetadataProvider('anime')
|
||||
: await getMetadataProvider('tv');
|
||||
|
||||
const season = await indexer.getTvSeason({
|
||||
tvId: Number(req.params.id),
|
||||
|
||||
0
src/components/MetadataSelector/index.ts
Normal file
0
src/components/MetadataSelector/index.ts
Normal file
@@ -38,11 +38,6 @@ const SettingsLayout = ({ children }: SettingsLayoutProps) => {
|
||||
route: '/settings/users',
|
||||
regex: /^\/settings\/users/,
|
||||
},
|
||||
{
|
||||
text: 'Tvdb',
|
||||
route: '/settings/tvdb',
|
||||
regex: /^\/settings\/tvdb/,
|
||||
},
|
||||
settings.currentSettings.mediaServerType === MediaServerType.PLEX
|
||||
? {
|
||||
text: intl.formatMessage(messages.menuPlexSettings),
|
||||
@@ -64,6 +59,11 @@ const SettingsLayout = ({ children }: SettingsLayoutProps) => {
|
||||
route: '/settings/network',
|
||||
regex: /^\/settings\/network/,
|
||||
},
|
||||
{
|
||||
text: 'Metadata',
|
||||
route: '/settings/metadata',
|
||||
regex: /^\/settings\/metadata/,
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage(messages.menuNotifications),
|
||||
route: '/settings/notifications/email',
|
||||
|
||||
@@ -5,7 +5,7 @@ import SettingsBadge from '@app/components/Settings/SettingsBadge';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
|
||||
import type { TvdbSettings } from '@server/routes/settings/tvdb';
|
||||
import type { MetadataSettings } from '@server/routes/settings/metadata';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
@@ -15,15 +15,13 @@ import useSWR from 'swr';
|
||||
const messages = defineMessages('components.Settings', {
|
||||
general: 'General',
|
||||
settings: 'Settings',
|
||||
apikey: 'API Key',
|
||||
pin: 'PIN',
|
||||
enable: 'Enable',
|
||||
enableTip:
|
||||
'Enable Tvdb (only for season and episode).' +
|
||||
' Due to a limitation of the api used, only English is available.',
|
||||
});
|
||||
|
||||
const SettingsTvdb = () => {
|
||||
const SettingsMetadata = () => {
|
||||
const intl = useIntl();
|
||||
const [isTesting, setIsTesting] = useState(false);
|
||||
|
||||
@@ -42,7 +40,7 @@ const SettingsTvdb = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const saveSettings = async (value: TvdbSettings) => {
|
||||
const saveSettings = async (value: MetadataSettings) => {
|
||||
const response = await fetch('/api/v1/settings/tvdb', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
@@ -58,7 +56,7 @@ const SettingsTvdb = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const { data, error } = useSWR<TvdbSettings>('/api/v1/settings/tvdb');
|
||||
const { data, error } = useSWR<MetadataSettings>('/api/v1/settings/tvdb');
|
||||
|
||||
if (!data && !error) {
|
||||
return <LoadingSpinner />;
|
||||
@@ -73,8 +71,8 @@ const SettingsTvdb = () => {
|
||||
]}
|
||||
/>
|
||||
<div className="mb-6">
|
||||
<h3 className="heading">{'Tvdb'}</h3>
|
||||
<p className="description">{'Settings for Tvdb'}</p>
|
||||
<h3 className="heading">{'Metadata'}</h3>
|
||||
<p className="description">{'Settings for metadata indexer'}</p>
|
||||
</div>
|
||||
<div className="section">
|
||||
<Formik
|
||||
@@ -195,4 +193,4 @@ const SettingsTvdb = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsTvdb;
|
||||
export default SettingsMetadata;
|
||||
@@ -1,16 +1,16 @@
|
||||
import SettingsLayout from '@app/components/Settings/SettingsLayout';
|
||||
import SettingsTvdb from '@app/components/Settings/SettingsTvdb';
|
||||
import SettingsMetadata from '@app/components/Settings/SettingsMetadata';
|
||||
import useRouteGuard from '@app/hooks/useRouteGuard';
|
||||
import { Permission } from '@app/hooks/useUser';
|
||||
import type { NextPage } from 'next';
|
||||
|
||||
const TvdbSettingsPage: NextPage = () => {
|
||||
const MetadataSettingsPage: NextPage = () => {
|
||||
useRouteGuard(Permission.ADMIN);
|
||||
return (
|
||||
<SettingsLayout>
|
||||
<SettingsTvdb />
|
||||
<SettingsMetadata />
|
||||
</SettingsLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default TvdbSettingsPage;
|
||||
export default MetadataSettingsPage;
|
||||
Reference in New Issue
Block a user