@redseat/api 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { Method, AxiosRequestConfig } from 'axios';
2
+ import { Observable } from 'rxjs';
2
3
  import { IToken } from './auth.js';
3
4
  import { IServer } from './interfaces.js';
5
+ import { SSEConnectionState, SSEConnectionOptions, SSEConnectionError, SSELibraryEvent, SSELibraryStatusEvent, SSEMediasEvent, SSEMediasProgressEvent, SSEConvertProgressEvent, SSEEpisodesEvent, SSESeriesEvent, SSEMoviesEvent, SSEPeopleEvent, SSETagsEvent, SSEBackupsEvent, SSEBackupFilesEvent } from './sse-types.js';
4
6
  export interface ClientOptions {
5
7
  server: IServer;
6
8
  getIdToken: () => Promise<string>;
@@ -18,6 +20,32 @@ export declare class RedseatClient {
18
20
  private localServerUrl?;
19
21
  private tokenData?;
20
22
  private tokenRefreshPromise?;
23
+ private sseAbortController?;
24
+ private sseReconnectTimeout?;
25
+ private sseReconnectAttempts;
26
+ private sseOptions?;
27
+ private readonly _sseConnectionState;
28
+ private readonly _sseError;
29
+ private readonly _sseEvents;
30
+ readonly sseConnectionState$: Observable<SSEConnectionState>;
31
+ readonly sseError$: Observable<SSEConnectionError>;
32
+ readonly sseConnected$: Observable<boolean>;
33
+ readonly library$: Observable<SSELibraryEvent>;
34
+ readonly libraryStatus$: Observable<SSELibraryStatusEvent>;
35
+ readonly medias$: Observable<SSEMediasEvent>;
36
+ readonly mediasProgress$: Observable<SSEMediasProgressEvent>;
37
+ readonly convertProgress$: Observable<SSEConvertProgressEvent>;
38
+ readonly episodes$: Observable<SSEEpisodesEvent>;
39
+ readonly series$: Observable<SSESeriesEvent>;
40
+ readonly movies$: Observable<SSEMoviesEvent>;
41
+ readonly people$: Observable<SSEPeopleEvent>;
42
+ readonly tags$: Observable<SSETagsEvent>;
43
+ readonly backups$: Observable<SSEBackupsEvent>;
44
+ readonly backupFiles$: Observable<SSEBackupFilesEvent>;
45
+ /**
46
+ * Creates a typed observable for a specific SSE event type
47
+ */
48
+ private createEventStream;
21
49
  constructor(options: ClientOptions);
22
50
  private getRegularServerUrl;
23
51
  private detectLocalUrl;
@@ -57,4 +85,47 @@ export declare class RedseatClient {
57
85
  * Returns public server info (IServer).
58
86
  */
59
87
  getServer(serverId: string): Promise<IServer>;
88
+ /**
89
+ * Connects to the server's SSE endpoint for real-time updates.
90
+ * Automatically manages authentication and reconnection.
91
+ * @param options - Connection options including library filters and reconnection settings
92
+ */
93
+ connectSSE(options?: SSEConnectionOptions): Promise<void>;
94
+ /**
95
+ * Disconnects from the SSE endpoint and cleans up resources.
96
+ */
97
+ disconnectSSE(): void;
98
+ /**
99
+ * Disposes of all SSE resources and completes observables.
100
+ * Call this when the client is no longer needed.
101
+ */
102
+ dispose(): void;
103
+ /**
104
+ * Gets the current SSE connection state
105
+ */
106
+ get sseConnectionState(): SSEConnectionState;
107
+ /**
108
+ * Internal method to establish SSE connection
109
+ */
110
+ private _connectSSE;
111
+ /**
112
+ * Builds the SSE endpoint URL with optional library filters
113
+ */
114
+ private buildSSEUrl;
115
+ /**
116
+ * Processes the SSE stream and emits events
117
+ */
118
+ private processSSEStream;
119
+ /**
120
+ * Parses the SSE buffer and extracts complete events
121
+ */
122
+ private parseSSEBuffer;
123
+ /**
124
+ * Handles SSE errors and triggers reconnection if appropriate
125
+ */
126
+ private handleSSEError;
127
+ /**
128
+ * Schedules a reconnection attempt with exponential backoff
129
+ */
130
+ private scheduleReconnect;
60
131
  }
package/dist/client.js CHANGED
@@ -1,7 +1,36 @@
1
1
  import axios from 'axios';
2
+ import { BehaviorSubject, Subject, filter, map } from 'rxjs';
2
3
  import { fetchServerToken } from './auth.js';
3
4
  export class RedseatClient {
5
+ /**
6
+ * Creates a typed observable for a specific SSE event type
7
+ */
8
+ createEventStream(eventName) {
9
+ return this._sseEvents.pipe(filter((event) => event.event === eventName), map(event => event.data));
10
+ }
4
11
  constructor(options) {
12
+ this.sseReconnectAttempts = 0;
13
+ // RxJS subjects for SSE
14
+ this._sseConnectionState = new BehaviorSubject('disconnected');
15
+ this._sseError = new Subject();
16
+ this._sseEvents = new Subject();
17
+ // Public observables for SSE connection state
18
+ this.sseConnectionState$ = this._sseConnectionState.asObservable();
19
+ this.sseError$ = this._sseError.asObservable();
20
+ this.sseConnected$ = this._sseConnectionState.pipe(map(state => state === 'connected'));
21
+ // Typed event streams
22
+ this.library$ = this.createEventStream('library');
23
+ this.libraryStatus$ = this.createEventStream('library-status');
24
+ this.medias$ = this.createEventStream('medias');
25
+ this.mediasProgress$ = this.createEventStream('medias_progress');
26
+ this.convertProgress$ = this.createEventStream('convert_progress');
27
+ this.episodes$ = this.createEventStream('episodes');
28
+ this.series$ = this.createEventStream('series');
29
+ this.movies$ = this.createEventStream('movies');
30
+ this.people$ = this.createEventStream('people');
31
+ this.tags$ = this.createEventStream('tags');
32
+ this.backups$ = this.createEventStream('backups');
33
+ this.backupFiles$ = this.createEventStream('backups-files');
5
34
  this.server = options.server;
6
35
  this.redseatUrl = options.redseatUrl;
7
36
  this.getIdToken = options.getIdToken;
@@ -213,4 +242,260 @@ export class RedseatClient {
213
242
  });
214
243
  return response.data;
215
244
  }
245
+ // ==================== SSE Methods ====================
246
+ /**
247
+ * Connects to the server's SSE endpoint for real-time updates.
248
+ * Automatically manages authentication and reconnection.
249
+ * @param options - Connection options including library filters and reconnection settings
250
+ */
251
+ async connectSSE(options) {
252
+ // Disconnect any existing connection
253
+ this.disconnectSSE();
254
+ this.sseOptions = {
255
+ autoReconnect: true,
256
+ initialReconnectDelay: 1000,
257
+ maxReconnectDelay: 30000,
258
+ ...options
259
+ };
260
+ this.sseReconnectAttempts = 0;
261
+ await this._connectSSE();
262
+ }
263
+ /**
264
+ * Disconnects from the SSE endpoint and cleans up resources.
265
+ */
266
+ disconnectSSE() {
267
+ if (this.sseReconnectTimeout) {
268
+ clearTimeout(this.sseReconnectTimeout);
269
+ this.sseReconnectTimeout = undefined;
270
+ }
271
+ if (this.sseAbortController) {
272
+ this.sseAbortController.abort();
273
+ this.sseAbortController = undefined;
274
+ }
275
+ this._sseConnectionState.next('disconnected');
276
+ }
277
+ /**
278
+ * Disposes of all SSE resources and completes observables.
279
+ * Call this when the client is no longer needed.
280
+ */
281
+ dispose() {
282
+ this.disconnectSSE();
283
+ this._sseConnectionState.complete();
284
+ this._sseError.complete();
285
+ this._sseEvents.complete();
286
+ }
287
+ /**
288
+ * Gets the current SSE connection state
289
+ */
290
+ get sseConnectionState() {
291
+ return this._sseConnectionState.value;
292
+ }
293
+ /**
294
+ * Internal method to establish SSE connection
295
+ */
296
+ async _connectSSE() {
297
+ this._sseConnectionState.next('connecting');
298
+ try {
299
+ // Ensure we have a valid token
300
+ await this.ensureValidToken();
301
+ if (!this.tokenData) {
302
+ throw new Error('No authentication token available');
303
+ }
304
+ const url = this.buildSSEUrl();
305
+ this.sseAbortController = new AbortController();
306
+ const response = await fetch(url, {
307
+ method: 'GET',
308
+ headers: {
309
+ 'Authorization': `Bearer ${this.tokenData.token}`,
310
+ 'Accept': 'text/event-stream',
311
+ 'Cache-Control': 'no-cache'
312
+ },
313
+ signal: this.sseAbortController.signal
314
+ });
315
+ if (!response.ok) {
316
+ if (response.status === 401) {
317
+ throw Object.assign(new Error('Authentication failed'), { type: 'auth' });
318
+ }
319
+ throw Object.assign(new Error(`Server returned ${response.status}`), { type: 'server' });
320
+ }
321
+ if (!response.body) {
322
+ throw Object.assign(new Error('No response body'), { type: 'server' });
323
+ }
324
+ this._sseConnectionState.next('connected');
325
+ this.sseReconnectAttempts = 0;
326
+ // Process the stream
327
+ await this.processSSEStream(response.body);
328
+ }
329
+ catch (error) {
330
+ if (error instanceof Error && error.name === 'AbortError') {
331
+ // Intentionally disconnected
332
+ return;
333
+ }
334
+ this.handleSSEError(error);
335
+ }
336
+ }
337
+ /**
338
+ * Builds the SSE endpoint URL with optional library filters
339
+ */
340
+ buildSSEUrl() {
341
+ let url = `${this.baseUrl}/sse`;
342
+ if (this.sseOptions?.libraries && this.sseOptions.libraries.length > 0) {
343
+ const params = new URLSearchParams();
344
+ this.sseOptions.libraries.forEach(lib => params.append('library', lib));
345
+ url = `${url}?${params.toString()}`;
346
+ }
347
+ return url;
348
+ }
349
+ /**
350
+ * Processes the SSE stream and emits events
351
+ */
352
+ async processSSEStream(body) {
353
+ const reader = body.getReader();
354
+ const decoder = new TextDecoder();
355
+ let buffer = '';
356
+ try {
357
+ while (true) {
358
+ const { done, value } = await reader.read();
359
+ if (done) {
360
+ // Stream ended - server closed connection
361
+ this._sseConnectionState.next('disconnected');
362
+ this.scheduleReconnect();
363
+ break;
364
+ }
365
+ buffer += decoder.decode(value, { stream: true });
366
+ const { events, remainingBuffer } = this.parseSSEBuffer(buffer);
367
+ buffer = remainingBuffer;
368
+ for (const event of events) {
369
+ this._sseEvents.next(event);
370
+ }
371
+ }
372
+ }
373
+ catch (error) {
374
+ if (error instanceof Error && error.name === 'AbortError') {
375
+ return;
376
+ }
377
+ throw error;
378
+ }
379
+ finally {
380
+ reader.releaseLock();
381
+ }
382
+ }
383
+ /**
384
+ * Parses the SSE buffer and extracts complete events
385
+ */
386
+ parseSSEBuffer(buffer) {
387
+ const events = [];
388
+ const lines = buffer.split('\n');
389
+ let currentEvent = {};
390
+ let processedUpTo = 0;
391
+ for (let i = 0; i < lines.length; i++) {
392
+ const line = lines[i];
393
+ // Check if this is an incomplete line (last line without newline)
394
+ if (i === lines.length - 1 && !buffer.endsWith('\n')) {
395
+ break;
396
+ }
397
+ processedUpTo += line.length + 1; // +1 for the newline
398
+ if (line === '') {
399
+ // Empty line marks end of event
400
+ if (currentEvent.event && currentEvent.data !== undefined) {
401
+ events.push(currentEvent);
402
+ }
403
+ currentEvent = {};
404
+ continue;
405
+ }
406
+ if (line.startsWith(':')) {
407
+ // Comment, ignore
408
+ continue;
409
+ }
410
+ const colonIndex = line.indexOf(':');
411
+ if (colonIndex === -1) {
412
+ continue;
413
+ }
414
+ const field = line.slice(0, colonIndex);
415
+ // Value starts after colon, skip optional space after colon
416
+ let value = line.slice(colonIndex + 1);
417
+ if (value.startsWith(' ')) {
418
+ value = value.slice(1);
419
+ }
420
+ switch (field) {
421
+ case 'event':
422
+ currentEvent.event = value;
423
+ break;
424
+ case 'data':
425
+ try {
426
+ currentEvent.data = JSON.parse(value);
427
+ }
428
+ catch {
429
+ this._sseError.next({
430
+ type: 'parse',
431
+ message: `Failed to parse SSE data: ${value}`,
432
+ timestamp: Date.now()
433
+ });
434
+ }
435
+ break;
436
+ case 'id':
437
+ currentEvent.id = value;
438
+ break;
439
+ case 'retry':
440
+ currentEvent.retry = parseInt(value, 10);
441
+ break;
442
+ }
443
+ }
444
+ return {
445
+ events,
446
+ remainingBuffer: buffer.slice(processedUpTo)
447
+ };
448
+ }
449
+ /**
450
+ * Handles SSE errors and triggers reconnection if appropriate
451
+ */
452
+ handleSSEError(error) {
453
+ const sseError = {
454
+ type: 'network',
455
+ message: error instanceof Error ? error.message : 'Unknown error',
456
+ originalError: error instanceof Error ? error : undefined,
457
+ timestamp: Date.now()
458
+ };
459
+ // Determine error type
460
+ if (error && typeof error === 'object' && 'type' in error) {
461
+ sseError.type = error.type;
462
+ }
463
+ this._sseConnectionState.next('error');
464
+ this._sseError.next(sseError);
465
+ // Don't reconnect on auth errors
466
+ if (sseError.type === 'auth') {
467
+ return;
468
+ }
469
+ this.scheduleReconnect();
470
+ }
471
+ /**
472
+ * Schedules a reconnection attempt with exponential backoff
473
+ */
474
+ scheduleReconnect() {
475
+ if (!this.sseOptions?.autoReconnect) {
476
+ return;
477
+ }
478
+ if (this.sseOptions.maxReconnectAttempts !== undefined &&
479
+ this.sseReconnectAttempts >= this.sseOptions.maxReconnectAttempts) {
480
+ return;
481
+ }
482
+ const initialDelay = this.sseOptions.initialReconnectDelay ?? 1000;
483
+ const maxDelay = this.sseOptions.maxReconnectDelay ?? 30000;
484
+ // Exponential backoff with jitter
485
+ const exponentialDelay = initialDelay * Math.pow(2, this.sseReconnectAttempts);
486
+ const jitter = Math.random() * 1000;
487
+ const delay = Math.min(exponentialDelay + jitter, maxDelay);
488
+ this._sseConnectionState.next('reconnecting');
489
+ this.sseReconnectAttempts++;
490
+ this.sseReconnectTimeout = setTimeout(async () => {
491
+ // Refresh token before reconnecting
492
+ try {
493
+ await this.refreshToken();
494
+ }
495
+ catch {
496
+ // Token refresh failed, will try again on next reconnect
497
+ }
498
+ this._connectSSE();
499
+ }, delay);
500
+ }
216
501
  }
package/dist/index.d.ts CHANGED
@@ -6,3 +6,4 @@ export * from './server.js';
6
6
  export * from './crypto.js';
7
7
  export * from './encryption.js';
8
8
  export * from './upload.js';
9
+ export * from './sse-types.js';
package/dist/index.js CHANGED
@@ -6,3 +6,4 @@ export * from './server.js';
6
6
  export * from './crypto.js';
7
7
  export * from './encryption.js';
8
8
  export * from './upload.js';
9
+ export * from './sse-types.js';
@@ -519,40 +519,18 @@ export interface RsRequest {
519
519
  audio?: string[];
520
520
  quality?: number;
521
521
  ignoreOriginDuplicate: boolean;
522
- }
523
- export interface MediaDownloadUrl {
524
- url: string;
525
- parse: boolean;
526
- uploadId?: string;
527
- ignoreOriginDuplicate?: boolean;
528
- kind?: FileTypes;
529
- filename?: string;
530
- mime?: string;
531
- description?: string;
532
- length?: number;
533
- thumbnailUrl?: string;
534
- peopleLookup?: string[];
535
- seriesLookup?: string[];
536
522
  tagsLookup?: string[];
537
- season?: number;
538
- episode?: number;
523
+ peopleLookup?: string[];
524
+ albumsLookup?: string[];
525
+ thumbnailUrl?: string;
526
+ originUrl?: string;
527
+ title?: string;
528
+ kind?: FileTypes;
539
529
  }
540
- export interface GroupMediaDownload<T> {
541
- group?: boolean;
530
+ export interface RsGroupDownload {
531
+ group: boolean;
542
532
  groupThumbnailUrl?: string;
543
533
  groupFilename?: string;
544
534
  groupMime?: string;
545
- files: T[];
546
- referer?: string;
547
- headers?: string[];
548
- cookies?: string[];
549
- originUrl?: string;
550
- title?: string;
551
- ignoreOriginDuplicate?: boolean;
552
- description?: string;
553
- peopleLookup?: string[];
554
- seriesLookup?: string[];
555
- tagsLookup?: string[];
556
- season?: number;
557
- episode?: number;
535
+ requests: RsRequest[];
558
536
  }
package/dist/library.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- import { IFile, ITag, IPerson, ISerie, IMovie, MediaRequest, IEpisode, ExternalImage, IBackupFile, ILibrary, SerieInMedia, DeletedQuery, RsDeleted, MovieSort, RsSort, SqlOrder, RsRequest, DetectedFaceResult, UnassignFaceResponse, MediaDownloadUrl, GroupMediaDownload } from './interfaces.js';
1
+ import { Observable } from 'rxjs';
2
+ import { IFile, ITag, IPerson, ISerie, IMovie, MediaRequest, IEpisode, ExternalImage, IBackupFile, ILibrary, SerieInMedia, DeletedQuery, RsDeleted, MovieSort, RsSort, SqlOrder, RsRequest, DetectedFaceResult, UnassignFaceResponse, RsGroupDownload } from './interfaces.js';
3
+ import { SSEMediasEvent, SSEMediasProgressEvent, SSEConvertProgressEvent, SSEEpisodesEvent, SSESeriesEvent, SSEMoviesEvent, SSEPeopleEvent, SSETagsEvent, SSELibraryStatusEvent } from './sse-types.js';
2
4
  import { EncryptFileOptions, EncryptedFile } from './encryption.js';
3
5
  export interface MediaForUpdate {
4
6
  name?: string;
@@ -74,6 +76,15 @@ export interface LibraryHttpClient {
74
76
  }>;
75
77
  getFullUrl(path: string, params?: Record<string, string>): string;
76
78
  getAuthToken(): string;
79
+ readonly medias$?: Observable<SSEMediasEvent>;
80
+ readonly mediasProgress$?: Observable<SSEMediasProgressEvent>;
81
+ readonly convertProgress$?: Observable<SSEConvertProgressEvent>;
82
+ readonly episodes$?: Observable<SSEEpisodesEvent>;
83
+ readonly series$?: Observable<SSESeriesEvent>;
84
+ readonly movies$?: Observable<SSEMoviesEvent>;
85
+ readonly people$?: Observable<SSEPeopleEvent>;
86
+ readonly tags$?: Observable<SSETagsEvent>;
87
+ readonly libraryStatus$?: Observable<SSELibraryStatusEvent>;
77
88
  }
78
89
  export declare class LibraryApi {
79
90
  private client;
@@ -81,7 +92,27 @@ export declare class LibraryApi {
81
92
  private library;
82
93
  private key?;
83
94
  private keyText?;
95
+ private disposed;
96
+ readonly medias$: Observable<SSEMediasEvent>;
97
+ readonly mediasProgress$: Observable<SSEMediasProgressEvent>;
98
+ readonly convertProgress$: Observable<SSEConvertProgressEvent>;
99
+ readonly episodes$: Observable<SSEEpisodesEvent>;
100
+ readonly series$: Observable<SSESeriesEvent>;
101
+ readonly movies$: Observable<SSEMoviesEvent>;
102
+ readonly people$: Observable<SSEPeopleEvent>;
103
+ readonly tags$: Observable<SSETagsEvent>;
104
+ readonly libraryStatus$: Observable<SSELibraryStatusEvent>;
84
105
  constructor(client: LibraryHttpClient, libraryId: string, library: ILibrary);
106
+ /**
107
+ * Creates a library-filtered stream from a client stream.
108
+ * Returns EMPTY if the client doesn't have SSE support.
109
+ */
110
+ private createLibraryFilteredStream;
111
+ /**
112
+ * Marks this LibraryApi as disposed.
113
+ * After calling dispose(), the filtered streams will stop emitting events.
114
+ */
115
+ dispose(): void;
85
116
  setKey(passPhrase: string): Promise<void>;
86
117
  private getUrl;
87
118
  getTags(query?: {
@@ -331,19 +362,13 @@ export declare class LibraryApi {
331
362
  thumbMime?: string;
332
363
  progressCallback?: (loaded: number, total: number) => void;
333
364
  }): Promise<IFile>;
334
- /**
335
- * Upload a media from a request (URL-based download)
336
- * @param request - The request containing URL and metadata for the media to download
337
- * @returns The created media file entry
338
- */
339
- uploadRequest(request: RsRequest): Promise<IFile>;
340
365
  /**
341
366
  * Upload a group of media files from URLs
342
- * @param download - The group download request containing multiple URLs and shared metadata
367
+ * @param download - The group download request containing requests array
343
368
  * @param options - Optional settings (spawn: true to run in background)
344
369
  * @returns Array of created media files when spawn=false, or { downloading: true } when spawn=true
345
370
  */
346
- uploadGroup(download: GroupMediaDownload<MediaDownloadUrl>, options?: {
371
+ uploadGroup(download: RsGroupDownload, options?: {
347
372
  spawn?: boolean;
348
373
  }): Promise<IFile[] | {
349
374
  downloading: boolean;
package/dist/library.js CHANGED
@@ -1,10 +1,39 @@
1
+ import { filter, EMPTY } from 'rxjs';
1
2
  import { deriveKey, encryptText as encryptTextUtil, decryptText as decryptTextUtil, encryptBuffer, decryptBuffer, encryptFile as encryptFileUtil, decryptFile as decryptFileUtil, decryptFileThumb, encryptFilename as encryptFilenameUtil, getRandomIV as getRandomIVUtil } from './encryption.js';
2
3
  import { uint8ArrayFromBase64 } from './crypto.js';
3
4
  export class LibraryApi {
4
5
  constructor(client, libraryId, library) {
5
6
  this.client = client;
6
7
  this.libraryId = libraryId;
8
+ this.disposed = false;
7
9
  this.library = library;
10
+ // Create library-filtered streams
11
+ this.medias$ = this.createLibraryFilteredStream(client.medias$);
12
+ this.mediasProgress$ = this.createLibraryFilteredStream(client.mediasProgress$);
13
+ this.convertProgress$ = this.createLibraryFilteredStream(client.convertProgress$);
14
+ this.episodes$ = this.createLibraryFilteredStream(client.episodes$);
15
+ this.series$ = this.createLibraryFilteredStream(client.series$);
16
+ this.movies$ = this.createLibraryFilteredStream(client.movies$);
17
+ this.people$ = this.createLibraryFilteredStream(client.people$);
18
+ this.tags$ = this.createLibraryFilteredStream(client.tags$);
19
+ this.libraryStatus$ = this.createLibraryFilteredStream(client.libraryStatus$);
20
+ }
21
+ /**
22
+ * Creates a library-filtered stream from a client stream.
23
+ * Returns EMPTY if the client doesn't have SSE support.
24
+ */
25
+ createLibraryFilteredStream(source$) {
26
+ if (!source$) {
27
+ return EMPTY;
28
+ }
29
+ return source$.pipe(filter(event => !this.disposed && event.library === this.libraryId));
30
+ }
31
+ /**
32
+ * Marks this LibraryApi as disposed.
33
+ * After calling dispose(), the filtered streams will stop emitting events.
34
+ */
35
+ dispose() {
36
+ this.disposed = true;
8
37
  }
9
38
  async setKey(passPhrase) {
10
39
  // Derive keys
@@ -824,18 +853,9 @@ export class LibraryApi {
824
853
  const res = await this.client.postForm(this.getUrl('/medias'), formData, config);
825
854
  return res.data;
826
855
  }
827
- /**
828
- * Upload a media from a request (URL-based download)
829
- * @param request - The request containing URL and metadata for the media to download
830
- * @returns The created media file entry
831
- */
832
- async uploadRequest(request) {
833
- const res = await this.client.post(this.getUrl('/medias/request'), request);
834
- return res.data;
835
- }
836
856
  /**
837
857
  * Upload a group of media files from URLs
838
- * @param download - The group download request containing multiple URLs and shared metadata
858
+ * @param download - The group download request containing requests array
839
859
  * @param options - Optional settings (spawn: true to run in background)
840
860
  * @returns Array of created media files when spawn=false, or { downloading: true } when spawn=true
841
861
  */