From 952a05057cfbd9446f19ea4e7c71e26ae8acee89 Mon Sep 17 00:00:00 2001 From: Viren070 Date: Mon, 16 Jun 2025 18:59:50 +0100 Subject: [PATCH] feat: add external download streams option closes #191 --- packages/core/src/db/schemas.ts | 1 + packages/core/src/main.ts | 34 +++++++++++++++++-- .../src/components/menu/miscellaneous.tsx | 16 +++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/packages/core/src/db/schemas.ts b/packages/core/src/db/schemas.ts index 891ff8f1..6a892fbe 100644 --- a/packages/core/src/db/schemas.ts +++ b/packages/core/src/db/schemas.ts @@ -387,6 +387,7 @@ export const UserDataSchema = z.object({ services: ServiceList.optional(), presets: PresetList, catalogModifications: z.array(CatalogModification).optional(), + externalDownloads: z.boolean().optional(), }); export type UserData = z.infer; diff --git a/packages/core/src/main.ts b/packages/core/src/main.ts index b52848da..09bc6243 100644 --- a/packages/core/src/main.ts +++ b/packages/core/src/main.ts @@ -163,6 +163,8 @@ export class AIOStreams { await this.proxifyStreams(limitedStreams) ); + let finalStreams = proxifiedStreams; + // step 8 // if this.userData.precacheNextEpisode is true, start a new thread to request the next episode, check if // all provider streams are uncached, and only if so, then send a request to the first uncached stream in the list. @@ -178,18 +180,46 @@ export class AIOStreams { }); } + if (this.userData.externalDownloads) { + logger.info(`Adding external downloads to streams`); + let count = 0; + // for each stream object, insert a new stream object, replacing the url with undefined, and appending its value to externalUrl instead + // and place it right after the original stream object + const streamsWithExternalDownloads: ParsedStream[] = []; + for (const stream of proxifiedStreams) { + streamsWithExternalDownloads.push(stream); + if (stream.url) { + const downloadableStream: ParsedStream = + this.createExternalDownloadStream(stream); + streamsWithExternalDownloads.push(downloadableStream); + count++; + } + } + logger.info(`Added ${count} external downloads to streams`); + finalStreams = streamsWithExternalDownloads; + } // step 9 // return the final list of streams, followed by the error streams. logger.info( - `Returning ${proxifiedStreams.length} streams and ${errors.length} errors` + `Returning ${finalStreams.length} streams and ${errors.length} errors` ); return { success: true, - data: proxifiedStreams, + data: finalStreams, errors: errors, }; } + private createExternalDownloadStream(stream: ParsedStream): ParsedStream { + const copy = structuredClone(stream); + copy.url = undefined; + copy.externalUrl = stream.url; + copy.message = `Download this stream via your browser`; + copy.id = `${stream.id}-external-download`; + copy.type = 'external'; + return copy; + } + private async precacheNextEpisode(type: string, id: string) { const seasonEpisodeRegex = /:(\d+):(\d+)$/; const match = id.match(seasonEpisodeRegex); diff --git a/packages/frontend/src/components/menu/miscellaneous.tsx b/packages/frontend/src/components/menu/miscellaneous.tsx index 9fa93109..a05d371c 100644 --- a/packages/frontend/src/components/menu/miscellaneous.tsx +++ b/packages/frontend/src/components/menu/miscellaneous.tsx @@ -80,6 +80,22 @@ function Content() { }} /> + + { + setUserData((prev) => ({ + ...prev, + externalDownloads: value, + })); + }} + /> + );