feat(tvdb): get tv seasons/episodes with tvdb
This commit is contained in:
@@ -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?: {
|
||||
|
||||
25
server/api/indexer/index.ts
Normal file
25
server/api/indexer/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type {
|
||||
TmdbSeasonWithEpisodes,
|
||||
TmdbTvDetails,
|
||||
} from '@server/api/indexer/themoviedb/interfaces';
|
||||
|
||||
export interface TvShowIndexer {
|
||||
getTvShow({
|
||||
tvId,
|
||||
language,
|
||||
}: {
|
||||
tvId: number;
|
||||
language?: string;
|
||||
}): Promise<TmdbTvDetails>;
|
||||
getTvSeason({
|
||||
tvId,
|
||||
seasonId,
|
||||
seasonNumber,
|
||||
language,
|
||||
}: {
|
||||
tvId: number;
|
||||
seasonId: number;
|
||||
seasonNumber: number;
|
||||
language?: string;
|
||||
}): Promise<TmdbSeasonWithEpisodes>;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import ExternalAPI from '@server/api/externalapi';
|
||||
import type { TvShowIndexer } from '@server/api/indexer';
|
||||
import cacheManager from '@server/lib/cache';
|
||||
import { getSettings } from '@server/lib/settings';
|
||||
import { sortBy } from 'lodash';
|
||||
@@ -120,14 +121,14 @@ interface DiscoverTvOptions {
|
||||
certificationCountry?: string;
|
||||
}
|
||||
|
||||
class TheMovieDb extends ExternalAPI {
|
||||
class TheMovieDb extends ExternalAPI implements TvShowIndexer {
|
||||
private locale: string;
|
||||
private discoverRegion?: string;
|
||||
private originalLanguage?: string;
|
||||
constructor({
|
||||
discoverRegion,
|
||||
originalLanguage,
|
||||
}: { discoverRegion?: string; originalLanguage?: string } = {}) {
|
||||
}: { discoverRegion?: string; originalLanguage?: string } = {}) {
|
||||
super(
|
||||
'https://api.themoviedb.org/3',
|
||||
{
|
||||
226
server/api/indexer/tvdb/index.ts
Normal file
226
server/api/indexer/tvdb/index.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
import ExternalAPI from '@server/api/externalapi';
|
||||
import type { TvShowIndexer } from '@server/api/indexer';
|
||||
import TheMovieDb from '@server/api/indexer/themoviedb';
|
||||
import type {
|
||||
TmdbSeasonWithEpisodes,
|
||||
TmdbTvDetails,
|
||||
} from '@server/api/indexer/themoviedb/interfaces';
|
||||
import type {
|
||||
TvdbEpisodeTranslation,
|
||||
TvdbLoginResponse,
|
||||
TvdbSeasonDetails,
|
||||
TvdbTvDetails,
|
||||
} from '@server/api/indexer/tvdb/interfaces';
|
||||
import cacheManager from '@server/lib/cache';
|
||||
import { getSettings } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
|
||||
class Tvdb extends ExternalAPI implements TvShowIndexer {
|
||||
static instance: Tvdb;
|
||||
private dateTokenExpires?: Date;
|
||||
private pin?: string;
|
||||
|
||||
private constructor(apiKey: string, pin?: string) {
|
||||
super(
|
||||
'https://api4.thetvdb.com/v4',
|
||||
{
|
||||
apiKey: apiKey,
|
||||
},
|
||||
{
|
||||
nodeCache: cacheManager.getCache('tvdb').data,
|
||||
rateLimit: {
|
||||
maxRPS: 50,
|
||||
id: 'tmdb',
|
||||
},
|
||||
}
|
||||
);
|
||||
this.pin = pin;
|
||||
}
|
||||
|
||||
public static async getInstance() {
|
||||
if (!this.instance) {
|
||||
const settings = getSettings();
|
||||
if (!settings.tvdb.apiKey) {
|
||||
throw new Error('TVDB API key is not set');
|
||||
}
|
||||
this.instance = new Tvdb(settings.tvdb.apiKey, settings.tvdb.pin);
|
||||
await this.instance.login();
|
||||
logger.info(
|
||||
'Tvdb instance created with token => ' +
|
||||
this.instance.defaultHeaders.Authorization
|
||||
);
|
||||
}
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
async login() {
|
||||
try {
|
||||
const res = await this.post<TvdbLoginResponse>('/login', {
|
||||
apiKey: this.params.apiKey,
|
||||
pin: this.pin,
|
||||
});
|
||||
this.defaultHeaders.Authorization = `Bearer ${res.data.token}`;
|
||||
this.dateTokenExpires = new Date();
|
||||
this.dateTokenExpires.setMonth(this.dateTokenExpires.getMonth() + 1);
|
||||
return res;
|
||||
} catch (error) {
|
||||
throw new Error(`[TVDB] Login failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
public getTvShow = async ({
|
||||
tvId,
|
||||
language = 'en',
|
||||
}: {
|
||||
tvId: number;
|
||||
language?: string;
|
||||
}): Promise<TmdbTvDetails> => {
|
||||
try {
|
||||
const tmdb = new TheMovieDb();
|
||||
const tmdbTvShow = await tmdb.getTvShow({ tvId: tvId });
|
||||
|
||||
const tvdbId = tmdbTvShow.external_ids.tvdb_id;
|
||||
|
||||
if (!tvdbId) {
|
||||
return tmdbTvShow;
|
||||
}
|
||||
|
||||
const data = await this.get<TvdbTvDetails>(
|
||||
`/series/${tvdbId}/extended`,
|
||||
{
|
||||
short: 'true',
|
||||
},
|
||||
43200
|
||||
);
|
||||
|
||||
const correctSeasons = data.data.seasons.filter(
|
||||
(season: TvdbSeasonDetails) =>
|
||||
season.id && season.number > 0 && season.type.name === 'Aired Order'
|
||||
);
|
||||
|
||||
tmdbTvShow.seasons = [];
|
||||
|
||||
for (const season of correctSeasons) {
|
||||
if (season.id) {
|
||||
logger.info(`Fetching TV season ${season.id}`);
|
||||
|
||||
try {
|
||||
const tvdbSeason = await this.getTvSeason({
|
||||
tvId: tvdbId,
|
||||
seasonNumber: season.id,
|
||||
language,
|
||||
});
|
||||
const seasonData = {
|
||||
id: season.id,
|
||||
episode_count: tvdbSeason.episodes.length,
|
||||
name: tvdbSeason.name,
|
||||
overview: tvdbSeason.overview,
|
||||
season_number: season.number,
|
||||
poster_path: '',
|
||||
air_date: '',
|
||||
image: tvdbSeason.poster_path,
|
||||
};
|
||||
|
||||
tmdbTvShow.seasons.push(seasonData);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Failed to get season ${season.id} for TV show ${tvdbId}: ${error.message}`,
|
||||
{
|
||||
label: 'Tvdb',
|
||||
message: `Failed to get season ${season.id} for TV show ${tvdbId}`,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tmdbTvShow;
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`[TVDB] Failed to fetch TV show details: ${error.message}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
getEpisode = async (
|
||||
episodeId: number,
|
||||
language: string
|
||||
): Promise<TvdbEpisodeTranslation> => {
|
||||
try {
|
||||
const tvdbEpisode = await this.get<TvdbEpisodeTranslation>(
|
||||
`/episodes/${episodeId}/translations/${language}`,
|
||||
{},
|
||||
43200
|
||||
);
|
||||
|
||||
return tvdbEpisode;
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`[TVDB] Failed to fetch TV episode details: ${error.message}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
public getTvSeason = async ({
|
||||
tvId,
|
||||
seasonNumber,
|
||||
language = 'en',
|
||||
}: {
|
||||
tvId: number;
|
||||
seasonNumber: number;
|
||||
language?: string;
|
||||
}): Promise<TmdbSeasonWithEpisodes> => {
|
||||
if (seasonNumber === 0) {
|
||||
return {
|
||||
episodes: [],
|
||||
external_ids: {
|
||||
tvdb_id: tvId,
|
||||
},
|
||||
name: '',
|
||||
overview: '',
|
||||
id: seasonNumber,
|
||||
air_date: '',
|
||||
season_number: 0,
|
||||
};
|
||||
}
|
||||
try {
|
||||
const tvdbSeason = await this.get<TvdbSeasonDetails>(
|
||||
`/seasons/${seasonNumber}/extended`,
|
||||
{ lang: language },
|
||||
43200
|
||||
);
|
||||
|
||||
const episodes = tvdbSeason.data.episodes.map((episode) => ({
|
||||
id: episode.id,
|
||||
air_date: episode.aired,
|
||||
episode_number: episode.number,
|
||||
name: episode.name,
|
||||
overview: episode.overview || '',
|
||||
season_number: episode.seasonNumber,
|
||||
production_code: '',
|
||||
show_id: tvId,
|
||||
still_path: episode.image,
|
||||
vote_average: 1,
|
||||
vote_cuont: 1,
|
||||
}));
|
||||
|
||||
return {
|
||||
episodes: episodes,
|
||||
external_ids: {
|
||||
tvdb_id: tvdbSeason.seriesId,
|
||||
},
|
||||
name: '',
|
||||
overview: '',
|
||||
id: tvdbSeason.id,
|
||||
air_date: tvdbSeason.year,
|
||||
season_number: tvdbSeason.number,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`[TVDB] Failed to fetch TV season details: ${error.message}`
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default Tvdb;
|
||||
143
server/api/indexer/tvdb/interfaces.ts
Normal file
143
server/api/indexer/tvdb/interfaces.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
export interface TvdbBaseResponse<T> {
|
||||
data: T;
|
||||
errors: any;
|
||||
}
|
||||
|
||||
export interface TvdbLoginResponse extends TvdbBaseResponse<{ token: string }> {
|
||||
data: { token: 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;
|
||||
defaultSeasonType: string;
|
||||
isOrderRandomized: boolean;
|
||||
lastUpdated: Date;
|
||||
averageRuntime: number;
|
||||
seasons: TvdbSeasonDetails[];
|
||||
}
|
||||
|
||||
interface TvdbCompanyType {
|
||||
companyTypeId: number;
|
||||
companyTypeName: string;
|
||||
}
|
||||
|
||||
interface TvdbParentCompany {
|
||||
id?: number;
|
||||
name?: string;
|
||||
relation?: {
|
||||
id?: number;
|
||||
typeName?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface TvdbCompany {
|
||||
id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
nameTranslations?: string[];
|
||||
overviewTranslations?: string[];
|
||||
aliases?: string[];
|
||||
country: string;
|
||||
primaryCompanyType: number;
|
||||
activeDate: string;
|
||||
inactiveDate?: string;
|
||||
companyType: TvdbCompanyType;
|
||||
parentCompany: TvdbParentCompany;
|
||||
tagOptions?: string[];
|
||||
}
|
||||
|
||||
interface TvdbType {
|
||||
id: number;
|
||||
name: string;
|
||||
type: string;
|
||||
alternateName?: string;
|
||||
}
|
||||
|
||||
interface TvdbArtwork {
|
||||
id: number;
|
||||
image: string;
|
||||
thumbnail: string;
|
||||
language: string;
|
||||
type: number;
|
||||
score: number;
|
||||
width: number;
|
||||
height: number;
|
||||
includesText: boolean;
|
||||
}
|
||||
|
||||
interface TvdbEpisode {
|
||||
id: number;
|
||||
seriesId: number;
|
||||
name: string;
|
||||
aired: string;
|
||||
runtime: number;
|
||||
nameTranslations: string[];
|
||||
overview?: 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[];
|
||||
}
|
||||
|
||||
export interface TvdbEpisodeTranslation
|
||||
extends TvdbBaseResponse<TvdbEpisodeTranslation> {
|
||||
name: string;
|
||||
overview: string;
|
||||
language: string;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants';
|
||||
import type { TmdbKeyword } from '@server/api/themoviedb/interfaces';
|
||||
import TheMovieDb from '@server/api/indexer/themoviedb';
|
||||
import { ANIME_KEYWORD_ID } from '@server/api/indexer/themoviedb/constants';
|
||||
import type { TmdbKeyword } from '@server/api/indexer/themoviedb/interfaces';
|
||||
import {
|
||||
MediaRequestStatus,
|
||||
MediaStatus,
|
||||
|
||||
@@ -39,6 +39,9 @@ export class UserSettings {
|
||||
@Column({ nullable: true })
|
||||
public originalLanguage?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
public tvdbToken?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
public pgpKey?: string;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import TheMovieDb from '@server/api/indexer/themoviedb';
|
||||
import { MediaType } from '@server/constants/media';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import Media from '@server/entity/Media';
|
||||
|
||||
@@ -9,7 +9,8 @@ export type AvailableCacheIds =
|
||||
| 'github'
|
||||
| 'plexguid'
|
||||
| 'plextv'
|
||||
| 'plexwatchlist';
|
||||
| 'plexwatchlist'
|
||||
| 'tvdb';
|
||||
|
||||
const DEFAULT_TTL = 300;
|
||||
const DEFAULT_CHECK_PERIOD = 120;
|
||||
@@ -70,6 +71,10 @@ class CacheManager {
|
||||
checkPeriod: 60,
|
||||
}),
|
||||
plexwatchlist: new Cache('plexwatchlist', 'Plex Watchlist'),
|
||||
tvdb: new Cache('tvdb', 'The TVDB API', {
|
||||
stdTtl: 21600,
|
||||
checkPeriod: 60 * 30,
|
||||
}),
|
||||
};
|
||||
|
||||
public getCache(id: AvailableCacheIds): Cache {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import TheMovieDb from '@server/api/indexer/themoviedb';
|
||||
import { MediaStatus, MediaType } from '@server/constants/media';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import Media from '@server/entity/Media';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import TheMovieDb from '@server/api/indexer/themoviedb';
|
||||
import type { TmdbTvDetails } from '@server/api/indexer/themoviedb/interfaces';
|
||||
import type { JellyfinLibraryItem } from '@server/api/jellyfin';
|
||||
import JellyfinAPI from '@server/api/jellyfin';
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import type { TmdbTvDetails } from '@server/api/themoviedb/interfaces';
|
||||
import { MediaStatus, MediaType } from '@server/constants/media';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import { getRepository } from '@server/datasource';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import animeList from '@server/api/animelist';
|
||||
import type { TmdbTvDetails } from '@server/api/indexer/themoviedb/interfaces';
|
||||
import type { PlexLibraryItem, PlexMetadata } from '@server/api/plexapi';
|
||||
import PlexAPI from '@server/api/plexapi';
|
||||
import type { TmdbTvDetails } from '@server/api/themoviedb/interfaces';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import { User } from '@server/entity/User';
|
||||
import cacheManager from '@server/lib/cache';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { TmdbTvDetails } from '@server/api/indexer/themoviedb/interfaces';
|
||||
import type { SonarrSeries } from '@server/api/servarr/sonarr';
|
||||
import SonarrAPI from '@server/api/servarr/sonarr';
|
||||
import type { TmdbTvDetails } from '@server/api/themoviedb/interfaces';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import Media from '@server/entity/Media';
|
||||
import type {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import TheMovieDb from '@server/api/indexer/themoviedb';
|
||||
import type {
|
||||
TmdbMovieDetails,
|
||||
TmdbMovieResult,
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
TmdbSearchTvResponse,
|
||||
TmdbTvDetails,
|
||||
TmdbTvResult,
|
||||
} from '@server/api/themoviedb/interfaces';
|
||||
} from '@server/api/indexer/themoviedb/interfaces';
|
||||
import {
|
||||
mapMovieDetailsToResult,
|
||||
mapPersonDetailsToResult,
|
||||
|
||||
@@ -79,6 +79,12 @@ export interface DVRSettings {
|
||||
overrideRule: number[];
|
||||
}
|
||||
|
||||
export interface TvdbSettings {
|
||||
apiKey?: string;
|
||||
pin?: string;
|
||||
use: boolean;
|
||||
}
|
||||
|
||||
export interface RadarrSettings extends DVRSettings {
|
||||
minimumAvailability: string;
|
||||
}
|
||||
@@ -333,6 +339,7 @@ export interface AllSettings {
|
||||
plex: PlexSettings;
|
||||
jellyfin: JellyfinSettings;
|
||||
tautulli: TautulliSettings;
|
||||
tvdb: TvdbSettings;
|
||||
radarr: RadarrSettings[];
|
||||
sonarr: SonarrSettings[];
|
||||
public: PublicSettings;
|
||||
@@ -399,6 +406,7 @@ class Settings {
|
||||
apiKey: '',
|
||||
},
|
||||
tautulli: {},
|
||||
tvdb: { use: false },
|
||||
radarr: [],
|
||||
sonarr: [],
|
||||
public: {
|
||||
@@ -593,6 +601,14 @@ class Settings {
|
||||
this.data.tautulli = data;
|
||||
}
|
||||
|
||||
get tvdb(): TvdbSettings {
|
||||
return this.data.tvdb;
|
||||
}
|
||||
|
||||
set tvdb(data: TvdbSettings) {
|
||||
this.data.tvdb = data;
|
||||
}
|
||||
|
||||
get radarr(): RadarrSettings[] {
|
||||
return this.data.radarr;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { TmdbCollection } from '@server/api/themoviedb/interfaces';
|
||||
import type { TmdbCollection } from '@server/api/indexer/themoviedb/interfaces';
|
||||
import { MediaType } from '@server/constants/media';
|
||||
import type Media from '@server/entity/Media';
|
||||
import { sortBy } from 'lodash';
|
||||
|
||||
@@ -2,7 +2,7 @@ import type {
|
||||
TmdbMovieDetails,
|
||||
TmdbMovieReleaseResult,
|
||||
TmdbProductionCompany,
|
||||
} from '@server/api/themoviedb/interfaces';
|
||||
} from '@server/api/indexer/themoviedb/interfaces';
|
||||
import type Media from '@server/entity/Media';
|
||||
import type {
|
||||
Cast,
|
||||
|
||||
@@ -2,7 +2,7 @@ import type {
|
||||
TmdbPersonCreditCast,
|
||||
TmdbPersonCreditCrew,
|
||||
TmdbPersonDetails,
|
||||
} from '@server/api/themoviedb/interfaces';
|
||||
} from '@server/api/indexer/themoviedb/interfaces';
|
||||
import type Media from '@server/entity/Media';
|
||||
|
||||
export interface PersonDetails {
|
||||
|
||||
@@ -6,7 +6,7 @@ import type {
|
||||
TmdbPersonResult,
|
||||
TmdbTvDetails,
|
||||
TmdbTvResult,
|
||||
} from '@server/api/themoviedb/interfaces';
|
||||
} from '@server/api/indexer/themoviedb/interfaces';
|
||||
import { MediaType as MainMediaType } from '@server/constants/media';
|
||||
import type Media from '@server/entity/Media';
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
TmdbTvEpisodeResult,
|
||||
TmdbTvRatingResult,
|
||||
TmdbTvSeasonResult,
|
||||
} from '@server/api/themoviedb/interfaces';
|
||||
} from '@server/api/indexer/themoviedb/interfaces';
|
||||
import type Media from '@server/entity/Media';
|
||||
import type {
|
||||
Cast,
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
TmdbVideoResult,
|
||||
TmdbWatchProviderDetails,
|
||||
TmdbWatchProviders,
|
||||
} from '@server/api/themoviedb/interfaces';
|
||||
} from '@server/api/indexer/themoviedb/interfaces';
|
||||
import type { Video } from '@server/models/Movie';
|
||||
|
||||
export interface ProductionCompany {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import TheMovieDb from '@server/api/indexer/themoviedb';
|
||||
import Media from '@server/entity/Media';
|
||||
import logger from '@server/logger';
|
||||
import { mapCollection } from '@server/models/Collection';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { SortOptions } from '@server/api/indexer/themoviedb';
|
||||
import TheMovieDb from '@server/api/indexer/themoviedb';
|
||||
import type { TmdbKeyword } from '@server/api/indexer/themoviedb/interfaces';
|
||||
import PlexTvAPI from '@server/api/plextv';
|
||||
import type { SortOptions } from '@server/api/themoviedb';
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
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';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import GithubAPI from '@server/api/github';
|
||||
import PushoverAPI from '@server/api/pushover';
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import TheMovieDb from '@server/api/indexer/themoviedb';
|
||||
import type {
|
||||
TmdbMovieResult,
|
||||
TmdbTvResult,
|
||||
} from '@server/api/themoviedb/interfaces';
|
||||
} from '@server/api/indexer/themoviedb/interfaces';
|
||||
import PushoverAPI from '@server/api/pushover';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import DiscoverSlider from '@server/entity/DiscoverSlider';
|
||||
import type { StatusResponse } from '@server/interfaces/api/settingsInterfaces';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import TheMovieDb from '@server/api/indexer/themoviedb';
|
||||
import RadarrAPI from '@server/api/servarr/radarr';
|
||||
import SonarrAPI from '@server/api/servarr/sonarr';
|
||||
import TautulliAPI from '@server/api/tautulli';
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import { MediaStatus, MediaType } from '@server/constants/media';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import Media from '@server/entity/Media';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import TheMovieDb from '@server/api/indexer/themoviedb';
|
||||
import IMDBRadarrProxy from '@server/api/rating/imdbRadarrProxy';
|
||||
import RottenTomatoes from '@server/api/rating/rottentomatoes';
|
||||
import { type RatingResponse } from '@server/api/ratings';
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import { MediaType } from '@server/constants/media';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import Media from '@server/entity/Media';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import TheMovieDb from '@server/api/indexer/themoviedb';
|
||||
import Media from '@server/entity/Media';
|
||||
import logger from '@server/logger';
|
||||
import {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import type { TmdbSearchMultiResponse } from '@server/api/themoviedb/interfaces';
|
||||
import TheMovieDb from '@server/api/indexer/themoviedb';
|
||||
import type { TmdbSearchMultiResponse } from '@server/api/indexer/themoviedb/interfaces';
|
||||
import Media from '@server/entity/Media';
|
||||
import { findSearchProvider } from '@server/lib/search';
|
||||
import logger from '@server/logger';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import TheMovieDb from '@server/api/indexer/themoviedb';
|
||||
import RadarrAPI from '@server/api/servarr/radarr';
|
||||
import SonarrAPI from '@server/api/servarr/sonarr';
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import type {
|
||||
ServiceCommonServer,
|
||||
ServiceCommonServerWithDetails,
|
||||
|
||||
@@ -42,6 +42,7 @@ import { URL } from 'url';
|
||||
import notificationRoutes from './notifications';
|
||||
import radarrRoutes from './radarr';
|
||||
import sonarrRoutes from './sonarr';
|
||||
import tvdbRoutes from './tvdb';
|
||||
|
||||
const settingsRoutes = Router();
|
||||
|
||||
@@ -49,6 +50,7 @@ settingsRoutes.use('/notifications', notificationRoutes);
|
||||
settingsRoutes.use('/radarr', radarrRoutes);
|
||||
settingsRoutes.use('/sonarr', sonarrRoutes);
|
||||
settingsRoutes.use('/discover', discoverSettingRoutes);
|
||||
settingsRoutes.use('/tvdb', tvdbRoutes);
|
||||
|
||||
const filteredMainSettings = (
|
||||
user: User,
|
||||
|
||||
46
server/routes/settings/tvdb.ts
Normal file
46
server/routes/settings/tvdb.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import Tvdb from '@server/api/indexer/tvdb';
|
||||
import type { TvdbSettings } from '@server/lib/settings';
|
||||
import { getSettings } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
import { Router } from 'express';
|
||||
|
||||
const tvdbRoutes = Router();
|
||||
|
||||
tvdbRoutes.get('/', (_req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
res.status(200).json(settings.tvdb);
|
||||
});
|
||||
|
||||
tvdbRoutes.put('/', (req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
const newTvdb = req.body as TvdbSettings;
|
||||
const tvdb = settings.tvdb;
|
||||
|
||||
tvdb.apiKey = newTvdb.apiKey;
|
||||
tvdb.pin = newTvdb.pin;
|
||||
tvdb.use = newTvdb.use;
|
||||
|
||||
settings.tvdb = tvdb;
|
||||
settings.save();
|
||||
|
||||
return res.status(200).json(newTvdb);
|
||||
});
|
||||
|
||||
tvdbRoutes.post('/test', async (req, res, next) => {
|
||||
try {
|
||||
const tvdb = await Tvdb.getInstance();
|
||||
await tvdb.login();
|
||||
return res.status(200).json({ message: 'Successfully connected to Tvdb' });
|
||||
} catch (e) {
|
||||
logger.error('Failed to test Tvdb', {
|
||||
label: 'Tvdb',
|
||||
message: e.message,
|
||||
});
|
||||
|
||||
return next({ status: 500, message: 'Failed to connect to Tvdb' });
|
||||
}
|
||||
});
|
||||
|
||||
export default tvdbRoutes;
|
||||
@@ -1,9 +1,11 @@
|
||||
import TheMovieDb from '@server/api/indexer/themoviedb';
|
||||
import Tvdb from '@server/api/indexer/tvdb';
|
||||
import RottenTomatoes from '@server/api/rating/rottentomatoes';
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import { MediaType } from '@server/constants/media';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import Media from '@server/entity/Media';
|
||||
import { Watchlist } from '@server/entity/Watchlist';
|
||||
import { getSettings } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
import { mapTvResult } from '@server/models/Search';
|
||||
import { mapSeasonWithEpisodes, mapTvDetails } from '@server/models/Tv';
|
||||
@@ -12,7 +14,13 @@ import { Router } from 'express';
|
||||
const tvRoutes = Router();
|
||||
|
||||
tvRoutes.get('/:id', async (req, res, next) => {
|
||||
const tmdb = new TheMovieDb();
|
||||
const settings = getSettings();
|
||||
let tmdb;
|
||||
if (settings.tvdb.use) {
|
||||
tmdb = await Tvdb.getInstance();
|
||||
} else {
|
||||
tmdb = new TheMovieDb();
|
||||
}
|
||||
try {
|
||||
const tv = await tmdb.getTvShow({
|
||||
tvId: Number(req.params.id),
|
||||
@@ -52,14 +60,22 @@ tvRoutes.get('/:id', async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
tvRoutes.get('/:id/season/:seasonNumber', async (req, res, next) => {
|
||||
const tmdb = new TheMovieDb();
|
||||
|
||||
tvRoutes.get('/:id/season/:seasonNumber/:seasonId', async (req, res, next) => {
|
||||
try {
|
||||
const settings = getSettings();
|
||||
let tmdb;
|
||||
let seasonIdentifier;
|
||||
if (settings.tvdb.use) {
|
||||
tmdb = await Tvdb.getInstance();
|
||||
seasonIdentifier = req.params.seasonId;
|
||||
} else {
|
||||
tmdb = new TheMovieDb();
|
||||
seasonIdentifier = req.params.seasonNumber;
|
||||
}
|
||||
|
||||
const season = await tmdb.getTvSeason({
|
||||
tvId: Number(req.params.id),
|
||||
seasonNumber: Number(req.params.seasonNumber),
|
||||
language: (req.query.language as string) ?? req.locale,
|
||||
seasonNumber: Number(seasonIdentifier),
|
||||
});
|
||||
|
||||
return res.status(200).json(mapSeasonWithEpisodes(season));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import TheMovieDb from '@server/api/indexer/themoviedb';
|
||||
import { IssueType, IssueTypeName } from '@server/constants/issue';
|
||||
import { MediaType } from '@server/constants/media';
|
||||
import { getRepository } from '@server/datasource';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import TheMovieDb from '@server/api/indexer/themoviedb';
|
||||
import { IssueStatus, IssueType, IssueTypeName } from '@server/constants/issue';
|
||||
import { MediaType } from '@server/constants/media';
|
||||
import Issue from '@server/entity/Issue';
|
||||
|
||||
@@ -6,7 +6,7 @@ import type {
|
||||
TmdbPersonResult,
|
||||
TmdbTvDetails,
|
||||
TmdbTvResult,
|
||||
} from '@server/api/themoviedb/interfaces';
|
||||
} from '@server/api/indexer/themoviedb/interfaces';
|
||||
|
||||
export const isMovie = (
|
||||
movie:
|
||||
|
||||
Reference in New Issue
Block a user