1+ import { isDeepStrictEqual } from "node:util"
2+
13import type { CobaltDownloadParams , SuccessfulCobaltMediaResponse } from "@/core/data/cobalt/download"
24import { getDownloadLink } from "@/core/data/cobalt/download"
35import type { ApiServer } from "@/core/data/cobalt/server"
@@ -6,34 +8,15 @@ import { retrieveTunneledMedia } from "@/core/data/cobalt/tunnel"
68
79import type { Result } from "@/core/utils/result"
810import { error , ok } from "@/core/utils/result"
9- import type { CompoundText , Text } from "@/core/utils/text"
11+ import type { Text } from "@/core/utils/text"
1012import { compound , literal , translatable } from "@/core/utils/text"
1113import { safeUrlSchema } from "@/core/utils/url"
1214
1315export { type CobaltDownloadParams } from "@/core/data/cobalt/download"
1416export { type ApiServer , apiServerSchema } from "@/core/data/cobalt/server"
1517
1618export async function download ( params : CobaltDownloadParams , apiPool : ApiServer [ ] ) : Promise < Result < DownloadedMedia [ ] , Text > > {
17- const link = await tryGetDownloadLink ( params , apiPool )
18- if ( ! link . success )
19- return link
20- const resolvedMedia = resolveMedia ( link . result , params . downloadMode === "audio" )
21- const downloadedMedia = await Promise . all (
22- resolvedMedia . map ( m => downloadResolvedMedia ( m , link . result . api ) ) ,
23- )
24-
25- const successfulDownloads = downloadedMedia
26- . filter ( m => m . success )
27- . map ( m => m . result )
28- const failedDownloads = downloadedMedia
29- . filter ( m => ! m . success )
30- . map ( m => m . error )
31- if ( successfulDownloads . length === 0 ) {
32- if ( failedDownloads . length === 1 )
33- return error ( compound ( literal ( `${ link . result . api . name } : ` ) , failedDownloads [ 0 ] ) )
34- return error ( compound ( ...failedDownloads . map ( m => compound ( literal ( `\n${ link . result . api . name } : ` ) , m ) ) ) )
35- }
36- return ok ( successfulDownloads )
19+ return await tryDownload ( params , apiPool )
3720}
3821
3922export type DownloadedMedia = { file : DownloadedMediaContent , filename ?: string }
@@ -50,7 +33,7 @@ async function downloadResolvedMedia(link: ResolvedMedia, api: ApiServer): Promi
5033}
5134
5235type ResolvedMedia = { url : string , filename ?: string }
53- function resolveMedia ( link : DownloadLink , audio ?: boolean ) : ResolvedMedia [ ] {
36+ function resolveMedia ( link : SuccessfulCobaltMediaResponse , audio ?: boolean ) : ResolvedMedia [ ] {
5437 if ( link . status === "picker" ) {
5538 if ( audio && link . audio ) {
5639 const source = new URL ( link . audio )
@@ -61,17 +44,35 @@ function resolveMedia(link: DownloadLink, audio?: boolean): ResolvedMedia[] {
6144 return [ { url : link . url , filename : link . filename } ]
6245}
6346
64- type DownloadLink = SuccessfulCobaltMediaResponse & { api : ApiServer }
65- async function tryGetDownloadLink ( params : CobaltDownloadParams , apiPool : ApiServer [ ] , fails : Text [ ] = [ ] ) : Promise < Result < DownloadLink , CompoundText > > {
47+ type DownloadFailure = { reason : Text , api : ApiServer }
48+ function formatDownloadFailures ( fails : DownloadFailure [ ] ) : Text {
49+ const mergedFails = fails . reduce ( ( acc , fail ) => {
50+ const existing = acc . find ( f => isDeepStrictEqual ( f . reason , fail . reason ) )
51+ if ( existing ) {
52+ existing . apis . push ( fail . api )
53+ } else {
54+ acc . push ( { reason : fail . reason , apis : [ fail . api ] } )
55+ }
56+ return acc
57+ } , [ ] as { reason : Text , apis : ApiServer [ ] } [ ] )
58+ return compound ( ...mergedFails . map ( f => compound (
59+ literal ( "\n" ) ,
60+ ...f . apis . map ( ( api , i ) => literal ( ( i === 0 ? "" : ", " ) + api . name ) ) ,
61+ literal ( ": " ) ,
62+ f . reason ,
63+ ) ) )
64+ }
65+
66+ async function tryDownload ( params : CobaltDownloadParams , apiPool : ApiServer [ ] , fails : DownloadFailure [ ] = [ ] ) : Promise < Result < DownloadedMedia [ ] , Text > > {
6667 const currentApi = apiPool . at ( 0 )
6768 if ( ! currentApi )
68- return error ( compound ( ... fails ) )
69+ return error ( formatDownloadFailures ( fails ) )
6970
7071 const next = ( reason : Text ) =>
71- tryGetDownloadLink (
72+ tryDownload (
7273 params ,
7374 apiPool . slice ( 1 ) ,
74- [ ...fails , compound ( literal ( `\n ${ currentApi . name } : ` ) , reason ) ] ,
75+ [ ...fails , { reason , api : currentApi } ] ,
7576 )
7677
7778 if ( currentApi . unsafe && ! ( await safeUrlSchema . safeParseAsync ( currentApi . url ) ) . success )
@@ -82,5 +83,21 @@ async function tryGetDownloadLink(params: CobaltDownloadParams, apiPool: ApiServ
8283 if ( ! res . success )
8384 return next ( res . error )
8485
85- return { success : res . success , result : { ...res . result , api : currentApi } }
86+ const resolvedMedia = resolveMedia ( res . result , params . downloadMode === "audio" )
87+ const downloadedMedia = await Promise . all (
88+ resolvedMedia . map ( m => downloadResolvedMedia ( m , currentApi ) ) ,
89+ )
90+
91+ const successfulDownloads = downloadedMedia
92+ . filter ( m => m . success )
93+ . map ( m => m . result )
94+ const failedDownloads = downloadedMedia
95+ . filter ( m => ! m . success )
96+ . map ( m => m . error )
97+ if ( successfulDownloads . length === 0 ) {
98+ if ( failedDownloads . length === 1 )
99+ return next ( failedDownloads [ 0 ] )
100+ return next ( compound ( ...failedDownloads ) )
101+ }
102+ return ok ( successfulDownloads )
86103}
0 commit comments