diff --git a/packages/core/src/debrid/utils.ts b/packages/core/src/debrid/utils.ts index 7c19e957..2aedd992 100644 --- a/packages/core/src/debrid/utils.ts +++ b/packages/core/src/debrid/utils.ts @@ -463,6 +463,19 @@ export const metadataStore = () => { return Cache.getInstance(prefix, 1_000_000_000, store); }; +export const fileInfoStore = () => { + const prefix = 'fis'; + let store: 'redis' | 'sql' | 'memory' | undefined; + if (Env.BUILTIN_DEBRID_FILEINFO_STORE === true) { + store = Env.REDIS_URI ? 'redis' : 'sql'; + } else if (!Env.BUILTIN_DEBRID_FILEINFO_STORE) { + return undefined; + } else { + store = Env.BUILTIN_DEBRID_FILEINFO_STORE; + } + return Cache.getInstance(prefix, 1_000_000_000, store); +}; + // export function generatePlaybackUrl( // storeAuth: ServiceAuth, // playbackInfo: MinimisedPlaybackInfo, @@ -484,5 +497,11 @@ export function generatePlaybackUrl( title?: string, filename?: string ): string { - return `${Env.BASE_URL}/api/v1/debrid/playback/${encryptedStoreAuth}/${toUrlSafeBase64(JSON.stringify(fileInfo))}/${metadataId}/${encodeURIComponent(filename ?? title ?? 'unknown')}`; + const fileInfoCache = fileInfoStore(); + let fileInfoId: string | undefined; + if (fileInfoCache) { + fileInfoId = getSimpleTextHash(JSON.stringify(fileInfo)); + fileInfoCache.set(fileInfoId, fileInfo, Env.BUILTIN_PLAYBACK_LINK_VALIDITY); + } + return `${Env.BASE_URL}/api/v1/debrid/playback/${encryptedStoreAuth}/${fileInfoId ?? toUrlSafeBase64(JSON.stringify(fileInfo))}/${metadataId}/${encodeURIComponent(filename ?? title ?? 'unknown')}`; } diff --git a/packages/core/src/utils/env.ts b/packages/core/src/utils/env.ts index 8cc2080f..2946f4cb 100644 --- a/packages/core/src/utils/env.ts +++ b/packages/core/src/utils/env.ts @@ -267,6 +267,20 @@ const urlMappings = makeValidator>((x) => { return mappings; }); +const boolOrChoice = (choices: T[]) => + makeValidator((input: string) => { + input = input.trim(); + if (['true', 'false', '1', '0'].includes(input.toLowerCase())) { + return input.toLowerCase() === 'true' || input === '1'; + } + if (choices.includes(input as T)) { + return input as T; + } + throw new EnvError( + `Invalid value: ${input}. Must be true, false or one of: ${choices.join(', ')}` + ); + }); + export const Env = cleanEnv(process.env, { VERSION: readonly({ default: metadata?.version || 'unknown', @@ -1641,6 +1655,10 @@ export const Env = cleanEnv(process.env, { default: undefined, desc: 'Builtin Debrid metadata store', }), + BUILTIN_DEBRID_FILEINFO_STORE: boolOrChoice(['redis', 'sql', 'memory'])({ + default: false, + desc: 'Builtin Debrid fileinfo store', + }), BUILTIN_PLAYBACK_LINK_VALIDITY: num({ default: 1 * 24 * 60 * 60, // 1 day desc: 'Builtin Debrid playback link validity', diff --git a/packages/server/src/routes/api/debrid.ts b/packages/server/src/routes/api/debrid.ts index 53932e61..fbb27d68 100644 --- a/packages/server/src/routes/api/debrid.ts +++ b/packages/server/src/routes/api/debrid.ts @@ -14,9 +14,11 @@ import { ServiceAuth, decryptString, metadataStore, + fileInfoStore, TitleMetadata, FileInfoSchema, getSimpleTextHash, + FileInfo, } from '@aiostreams/core'; import { ZodError } from 'zod'; import { StaticFiles } from '../../app.js'; @@ -53,9 +55,18 @@ router.get( ); } - const fileInfo = FileInfoSchema.parse( - JSON.parse(fromUrlSafeBase64(encodedFileInfo)) - ); + let fileInfo: FileInfo | undefined; + + try { + fileInfo = FileInfoSchema.parse( + JSON.parse(fromUrlSafeBase64(encodedFileInfo)) + ); + } catch (error: any) { + fileInfo = await fileInfoStore()?.get(encodedFileInfo); + if (!fileInfo) { + throw error; + } + } const decryptedStoreAuth = decryptString(encryptedStoreAuth); if (!decryptedStoreAuth.success) {