mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2025-12-24 02:39:18 -05:00
* feat(tvdb): get tv seasons/episodes with tvdb * fix: fix rate limiter index tvdb indexer * fix(usersettings): remove unused column tvdbtoken * refactor(tvdb): replace tvdb api by skyhook * fix: error during get episodes * fix: error if tmdb poster is null * refactor: clean tvdb indexer code * fix: wrong language with tmdb indexer * style: replace avalaible to available * style: tvdb.login to tvdb.test * fix(test): fix discover test * fix(test): wrong url tv-details * test(tvdb): add tvdb tests * style(tvdb): rename pokemon to correct tv show * refactor(indexer): remove unused getSeasonIdentifier method * refactor(settings): replace tvdb object to boolean type * refactor(tmdb): reduce still path condition * test(tvdb): change 'use' to 'tvdb' condition check * fix(tmdb): fix build fix build after rebase * fix(build): revert package.json * fix(tvdb): ensure that seasons contain data * refactor(swagger): fix /tvdb/test response * fix(scanner): add tvdb indexer for scanner * refactor(tvdb): remove skyhook api * refactor(tvdb): use tvdb api * fix(tvdb): rename tvdb to medatada * refactor(medata): add tvdb settings * refactor(metadata): rewrite metadata settings * refactor(metadata): refactor metadata routes * refactor(metadata): remove french comments * refactor(metadata): refactor tvdb api calls * style(prettier): run prettier * fix(scanner): fix jellyfin scanner with tvdb provider * fix(scanner): fix plex scanner tvdb provider * style(provider): change provider name in info section * style(provider): full provider name in select * style(provider): remove french comment * fix(tests): fix all cypress tests * refactor(tvdb): fix apikey * refactor(tmdb): apply prettier * refactor(tvdb): remove logger info * feat(metadata): replace fetch with axios for API calls * feat(provider): replace indexer by provider * fix(tests): fix cypress test * chore: add project-wide apikey for tvdb * chore: add correct application-wide key * fix(test): fix test with default provider tmdb anime * style(cypress): fix anime name variable * chore(i18n): remove french translation + apply i18n:extract * style(wording): standardize naming to "Metadata Provider" in UI text * docs(comments): translate from French to English * refactor(tvdb): remove unnecessary try/catch block * feat(i18n): add missing translations * fix(scanner): correct metadata provider ID from Tmdb to Tvdb * style(settings): clarify navigation label from "Metadata" to "Metadata Providers" * style(logs): update error log label from "Metadata" to "MetadataProvider" * refactor(tvdb): replace indexer by metadata providers * refactor(settings): remove metadata providers logo * fix(config): restore missing config/db/.gitkeep file --------- Co-authored-by: TOomaAh <ubuntu@PC> Co-authored-by: fallenbagel <98979876+Fallenbagel@users.noreply.github.com>
157 lines
3.8 KiB
TypeScript
157 lines
3.8 KiB
TypeScript
import { requestInterceptorFunction } from '@server/utils/customProxyAgent';
|
|
import type { AxiosInstance, AxiosRequestConfig } from 'axios';
|
|
import axios from 'axios';
|
|
import rateLimit from 'axios-rate-limit';
|
|
import type NodeCache from 'node-cache';
|
|
|
|
// 5 minute default TTL (in seconds)
|
|
const DEFAULT_TTL = 300;
|
|
|
|
// 10 seconds default rolling buffer (in ms)
|
|
const DEFAULT_ROLLING_BUFFER = 10000;
|
|
|
|
export interface ExternalAPIOptions {
|
|
nodeCache?: NodeCache;
|
|
headers?: Record<string, unknown>;
|
|
rateLimit?: {
|
|
maxRPS: number;
|
|
maxRequests: number;
|
|
};
|
|
}
|
|
|
|
class ExternalAPI {
|
|
protected axios: AxiosInstance;
|
|
private baseUrl: string;
|
|
private cache?: NodeCache;
|
|
|
|
constructor(
|
|
baseUrl: string,
|
|
params: Record<string, unknown>,
|
|
options: ExternalAPIOptions = {}
|
|
) {
|
|
this.axios = axios.create({
|
|
baseURL: baseUrl,
|
|
params,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Accept: 'application/json',
|
|
...options.headers,
|
|
},
|
|
});
|
|
this.axios.interceptors.request.use(requestInterceptorFunction);
|
|
|
|
if (options.rateLimit) {
|
|
this.axios = rateLimit(this.axios, {
|
|
maxRequests: options.rateLimit.maxRequests,
|
|
maxRPS: options.rateLimit.maxRPS,
|
|
});
|
|
}
|
|
|
|
this.baseUrl = baseUrl;
|
|
this.cache = options.nodeCache;
|
|
}
|
|
|
|
protected async get<T>(
|
|
endpoint: string,
|
|
config?: AxiosRequestConfig,
|
|
ttl?: number
|
|
): Promise<T> {
|
|
const cacheKey = this.serializeCacheKey(endpoint, {
|
|
...config?.params,
|
|
headers: config?.headers,
|
|
});
|
|
const cachedItem = this.cache?.get<T>(cacheKey);
|
|
if (cachedItem) {
|
|
return cachedItem;
|
|
}
|
|
|
|
const response = await this.axios.get<T>(endpoint, config);
|
|
|
|
if (this.cache && ttl !== 0) {
|
|
this.cache.set(cacheKey, response.data, ttl ?? DEFAULT_TTL);
|
|
}
|
|
|
|
return response.data;
|
|
}
|
|
|
|
protected async post<T>(
|
|
endpoint: string,
|
|
data?: Record<string, unknown>,
|
|
config?: AxiosRequestConfig,
|
|
ttl?: number
|
|
): Promise<T> {
|
|
const cacheKey = this.serializeCacheKey(endpoint, {
|
|
config: config?.params,
|
|
...(data ? { data } : {}),
|
|
});
|
|
|
|
const cachedItem = this.cache?.get<T>(cacheKey);
|
|
if (cachedItem) {
|
|
return cachedItem;
|
|
}
|
|
|
|
const response = await this.axios.post<T>(endpoint, data, config);
|
|
|
|
if (this.cache && ttl !== 0) {
|
|
this.cache.set(cacheKey, response.data, ttl ?? DEFAULT_TTL);
|
|
}
|
|
|
|
return response.data;
|
|
}
|
|
|
|
protected async getRolling<T>(
|
|
endpoint: string,
|
|
config?: AxiosRequestConfig,
|
|
ttl?: number
|
|
): Promise<T> {
|
|
const cacheKey = this.serializeCacheKey(endpoint, {
|
|
...config?.params,
|
|
headers: config?.headers,
|
|
});
|
|
const cachedItem = this.cache?.get<T>(cacheKey);
|
|
|
|
if (cachedItem) {
|
|
const keyTtl = this.cache?.getTtl(cacheKey) ?? 0;
|
|
|
|
// If the item has passed our rolling check, fetch again in background
|
|
if (
|
|
keyTtl - (ttl ?? DEFAULT_TTL) * 1000 <
|
|
Date.now() - DEFAULT_ROLLING_BUFFER
|
|
) {
|
|
this.axios.get<T>(endpoint, config).then((response) => {
|
|
this.cache?.set(cacheKey, response.data, ttl ?? DEFAULT_TTL);
|
|
});
|
|
}
|
|
return cachedItem;
|
|
}
|
|
|
|
const response = await this.axios.get<T>(endpoint, config);
|
|
|
|
if (this.cache && ttl !== 0) {
|
|
this.cache.set(cacheKey, response.data, ttl ?? DEFAULT_TTL);
|
|
}
|
|
|
|
return response.data;
|
|
}
|
|
|
|
protected removeCache(endpoint: string, options?: Record<string, unknown>) {
|
|
const cacheKey = this.serializeCacheKey(endpoint, {
|
|
...options,
|
|
});
|
|
this.cache?.del(cacheKey);
|
|
}
|
|
|
|
private serializeCacheKey(
|
|
endpoint: string,
|
|
options?: Record<string, unknown>
|
|
) {
|
|
if (!options) {
|
|
return `${this.baseUrl}${endpoint}`;
|
|
}
|
|
|
|
return `${this.baseUrl}${endpoint}${JSON.stringify(options)}`;
|
|
}
|
|
}
|
|
|
|
export default ExternalAPI;
|