@redseat/api 0.3.6 → 0.3.11

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
@@ -2,7 +2,7 @@ import { Method, AxiosRequestConfig } from 'axios';
2
2
  import { Observable } from 'rxjs';
3
3
  import { IToken } from './auth.js';
4
4
  import { IServer } from './interfaces.js';
5
- import { SSEConnectionState, SSEConnectionOptions, SSEConnectionError, SSELibraryEvent, SSELibraryStatusEvent, SSEMediasEvent, SSEUploadProgressEvent, SSEConvertProgressEvent, SSEEpisodesEvent, SSESeriesEvent, SSEMoviesEvent, SSEPeopleEvent, SSETagsEvent, SSEBackupsEvent, SSEBackupFilesEvent, SSEMediaRatingEvent, SSEMediaProgressEvent, SSEPlayersListEvent } from './sse-types.js';
5
+ import { SSEConnectionState, SSEConnectionOptions, SSEConnectionError, SSELibraryEvent, SSELibraryStatusEvent, SSEMediasEvent, SSEUploadProgressEvent, SSEConvertProgressEvent, SSEEpisodesEvent, SSESeriesEvent, SSEMoviesEvent, SSEPeopleEvent, SSETagsEvent, SSEBackupsEvent, SSEBackupFilesEvent, SSEMediaRatingEvent, SSEMediaProgressEvent, SSEPlayersListEvent, SSEWatchedEvent, SSEUnwatchedEvent, SSERequestProcessingEvent } from './sse-types.js';
6
6
  export interface ClientOptions {
7
7
  server: IServer;
8
8
  getIdToken: () => Promise<string>;
@@ -45,6 +45,9 @@ export declare class RedseatClient {
45
45
  readonly mediaRating$: Observable<SSEMediaRatingEvent>;
46
46
  readonly mediaProgress$: Observable<SSEMediaProgressEvent>;
47
47
  readonly playersList$: Observable<SSEPlayersListEvent>;
48
+ readonly watched$: Observable<SSEWatchedEvent>;
49
+ readonly unwatched$: Observable<SSEUnwatchedEvent>;
50
+ readonly requestProcessing$: Observable<SSERequestProcessingEvent>;
48
51
  /**
49
52
  * Creates a typed observable for a specific SSE event type.
50
53
  * Unwraps the nested data structure from the server (e.g., {uploadProgress: {...}} -> {...})
package/dist/client.js CHANGED
@@ -17,6 +17,12 @@ export class RedseatClient {
17
17
  'library-status': 'libraryStatus',
18
18
  'backups-files': 'backupsFiles',
19
19
  'players-list': 'Players',
20
+ 'episodes': 'episodes',
21
+ 'series': 'series',
22
+ 'movies': 'movies',
23
+ 'people': 'people',
24
+ 'tags': 'tags',
25
+ 'request_processing': 'requestProcessing',
20
26
  };
21
27
  const wrapperKey = wrapperMap[eventName];
22
28
  return this._sseEvents.pipe(filter((event) => event.event === eventName), map(event => {
@@ -55,6 +61,9 @@ export class RedseatClient {
55
61
  this.mediaRating$ = this.createEventStream('media_rating');
56
62
  this.mediaProgress$ = this.createEventStream('media_progress');
57
63
  this.playersList$ = this.createEventStream('players-list');
64
+ this.watched$ = this.createEventStream('watched');
65
+ this.unwatched$ = this.createEventStream('unwatched');
66
+ this.requestProcessing$ = this.createEventStream('request_processing');
58
67
  this.server = options.server;
59
68
  this.redseatUrl = options.redseatUrl;
60
69
  this.getIdToken = options.getIdToken;
@@ -483,40 +483,191 @@ export declare enum RsSort {
483
483
  Name = "name",
484
484
  Size = "size"
485
485
  }
486
+ /**
487
+ * Media type for history/watched tracking.
488
+ * Used to categorize watched entries and view progress.
489
+ */
486
490
  export type MediaType = 'episode' | 'movie' | 'book' | 'song' | 'media';
491
+ /**
492
+ * Watch history entry - returned from GET /users/me/history.
493
+ *
494
+ * ## External ID Format
495
+ *
496
+ * Watch history uses **external IDs** (from providers like IMDb, Trakt, TMDb) rather than
497
+ * local database IDs. This design enables:
498
+ * - **Cross-server portability**: History syncs across different Redseat servers
499
+ * - **External service synchronization**: Seamless sync with Trakt, Plex, etc.
500
+ * - **Library independence**: History is global, not tied to a specific library
501
+ *
502
+ * ### ID Format: `provider:value`
503
+ *
504
+ * Examples:
505
+ * - `imdb:tt1234567` - IMDb ID for movies/episodes
506
+ * - `trakt:123456` - Trakt ID
507
+ * - `tmdb:550` - TMDb ID
508
+ * - `tvdb:12345` - TVDB ID for series/episodes
509
+ * - `redseat:abc123` - Local fallback when no external ID is available
510
+ *
511
+ * When marking content as watched via library endpoints (e.g., `setMovieWatched`),
512
+ * the server automatically resolves the local ID to an external ID.
513
+ */
487
514
  export interface IWatched {
515
+ /** Content type (episode, movie, etc.) */
488
516
  type: MediaType;
517
+ /**
518
+ * External ID in format `provider:value` (e.g., `imdb:tt1234567`).
519
+ * See interface documentation for details on the ID format.
520
+ */
489
521
  id: string;
522
+ /** User reference (for admin queries) */
490
523
  userRef?: string;
524
+ /** Timestamp when content was watched (unix milliseconds) */
491
525
  date: number;
526
+ /** Timestamp when entry was last modified (unix milliseconds) */
492
527
  modified: number;
493
528
  }
529
+ /**
530
+ * Unwatched event data - emitted via SSE when content is removed from watch history.
531
+ *
532
+ * Unlike IWatched which has a single `id`, this uses `ids[]` array containing
533
+ * all possible external IDs (imdb, trakt, tmdb, local) so clients can match
534
+ * against any provider they have cached.
535
+ *
536
+ * This is a user-scoped event - not library-scoped.
537
+ */
538
+ export interface IUnwatched {
539
+ /** Content type (episode, movie, etc.) */
540
+ type: MediaType;
541
+ /**
542
+ * All possible external IDs for this content.
543
+ * Format: `provider:value`
544
+ * Examples: ["imdb:tt0111161", "trakt:481", "tmdb:278"]
545
+ */
546
+ ids: string[];
547
+ /** User reference */
548
+ userRef?: string;
549
+ /** Timestamp when entry was removed (unix milliseconds) */
550
+ modified: number;
551
+ }
552
+ /**
553
+ * Request body for adding to watch history via POST /users/me/history.
554
+ *
555
+ * ## External ID Requirement
556
+ *
557
+ * When using the direct history endpoint, you must provide an external ID.
558
+ * The ID format is `provider:value`:
559
+ * - `imdb:tt1234567` - IMDb ID
560
+ * - `trakt:123456` - Trakt ID
561
+ * - `tmdb:550` - TMDb ID
562
+ * - `tvdb:12345` - TVDB ID
563
+ *
564
+ * **Tip**: For easier usage, prefer the library-specific endpoints
565
+ * (`setMovieWatched`, `setEpisodeWatched`) which automatically resolve IDs.
566
+ *
567
+ * @example
568
+ * ```typescript
569
+ * // Direct history endpoint - requires external ID
570
+ * await serverApi.addToHistory({
571
+ * type: 'movie',
572
+ * id: 'imdb:tt0111161', // The Shawshank Redemption
573
+ * date: Date.now()
574
+ * });
575
+ *
576
+ * // Library endpoint - uses local ID, server resolves to external
577
+ * await libraryApi.setMovieWatched('local-movie-id', Date.now());
578
+ * ```
579
+ */
494
580
  export interface IWatchedForAdd {
581
+ /** Content type (episode, movie, etc.) */
495
582
  type: MediaType;
583
+ /**
584
+ * External ID in format `provider:value` (e.g., `imdb:tt1234567`).
585
+ * See interface documentation for details on the ID format.
586
+ */
496
587
  id: string;
588
+ /** Timestamp when content was watched (unix milliseconds) */
497
589
  date: number;
498
590
  }
591
+ /**
592
+ * View progress entry - returned from GET /users/me/history/progress/:id.
593
+ *
594
+ * Tracks playback position for resumable viewing.
595
+ * Uses the same external ID format as watch history.
596
+ */
499
597
  export interface IViewProgress {
598
+ /** Content type (episode, movie, etc.) */
500
599
  type: MediaType;
600
+ /**
601
+ * External ID in format `provider:value` (e.g., `imdb:tt1234567`).
602
+ */
501
603
  id: string;
604
+ /** User reference */
502
605
  userRef: string;
606
+ /** Playback progress in milliseconds */
503
607
  progress: number;
608
+ /** Parent ID for episodes (series external ID) */
504
609
  parent?: string;
610
+ /** Timestamp when progress was last modified (unix milliseconds) */
505
611
  modified: number;
506
612
  }
613
+ /**
614
+ * Request body for updating view progress via POST /users/me/history/progress.
615
+ *
616
+ * Uses external IDs in format `provider:value` (e.g., `imdb:tt1234567`).
617
+ *
618
+ * @example
619
+ * ```typescript
620
+ * await serverApi.addProgress({
621
+ * type: 'movie',
622
+ * id: 'imdb:tt0111161',
623
+ * progress: 3600000 // 1 hour in milliseconds
624
+ * });
625
+ * ```
626
+ */
507
627
  export interface IViewProgressForAdd {
628
+ /** Content type (episode, movie, etc.) */
508
629
  type: MediaType;
630
+ /**
631
+ * External ID in format `provider:value` (e.g., `imdb:tt1234567`).
632
+ */
509
633
  id: string;
634
+ /** Parent ID for episodes (series external ID) */
510
635
  parent?: string;
636
+ /** Playback progress in milliseconds */
511
637
  progress: number;
512
638
  }
639
+ /**
640
+ * Query parameters for GET /users/me/history.
641
+ *
642
+ * @example
643
+ * ```typescript
644
+ * // Get recent movie history
645
+ * const history = await serverApi.getHistory({
646
+ * types: ['movie'],
647
+ * order: SqlOrder.DESC,
648
+ * after: Date.now() - 86400000 * 30 // Last 30 days
649
+ * });
650
+ *
651
+ * // Check if specific content was watched
652
+ * const watched = await serverApi.getHistory({
653
+ * id: 'imdb:tt0111161'
654
+ * });
655
+ * ```
656
+ */
513
657
  export interface HistoryQuery {
658
+ /** Sort field */
514
659
  sort?: RsSort;
660
+ /** Sort direction */
515
661
  order?: SqlOrder;
662
+ /** Filter entries watched before this timestamp (unix ms) */
516
663
  before?: number;
664
+ /** Filter entries watched after this timestamp (unix ms) */
517
665
  after?: number;
666
+ /** Filter by content types */
518
667
  types?: MediaType[];
668
+ /** Filter by specific external ID */
519
669
  id?: string;
670
+ /** Pagination key for next page */
520
671
  pageKey?: number;
521
672
  }
522
673
  export type MovieSort = 'modified' | 'added' | 'created' | 'name' | 'digitalairdate';
@@ -580,6 +731,8 @@ export interface RsRequest {
580
731
  size?: number;
581
732
  filename?: string;
582
733
  status: RsRequestStatus;
734
+ pluginName?: string;
735
+ pluginId?: string;
583
736
  permanent: boolean;
584
737
  instant?: boolean;
585
738
  jsonBody?: any;
@@ -617,3 +770,31 @@ export interface RsGroupDownload {
617
770
  groupMime?: string;
618
771
  requests: RsRequest[];
619
772
  }
773
+ /**
774
+ * Request processing status from plugin-based download/processing.
775
+ * Tracks progress of downloads, file processing, etc.
776
+ */
777
+ export interface IRsRequestProcessing {
778
+ /** Internal nanoid */
779
+ id: string;
780
+ /** Plugin's processing ID */
781
+ processingId: string;
782
+ /** Plugin handling the request */
783
+ pluginId: string;
784
+ /** Progress 0-100 */
785
+ progress: number;
786
+ /** Status: "pending" | "processing" | "paused" | "finished" | "error" */
787
+ status: string;
788
+ /** Error message if status is "error" */
789
+ error?: string;
790
+ /** UTC timestamp (ms) for estimated completion */
791
+ eta?: number;
792
+ /** Reference to media being processed */
793
+ mediaRef?: string;
794
+ /** The original request */
795
+ originalRequest?: RsRequest;
796
+ /** Last modified timestamp */
797
+ modified: number;
798
+ /** Creation timestamp */
799
+ added: number;
800
+ }
package/dist/library.d.ts CHANGED
@@ -1,6 +1,7 @@
1
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, IViewProgress, IWatched } from './interfaces.js';
3
- import { SSEMediasEvent, SSEUploadProgressEvent, SSEConvertProgressEvent, SSEEpisodesEvent, SSESeriesEvent, SSEMoviesEvent, SSEPeopleEvent, SSETagsEvent, SSELibraryStatusEvent, SSEMediaRatingEvent, SSEMediaProgressEvent } from './sse-types.js';
2
+ import type { AxiosResponse } from 'axios';
3
+ import { IFile, ITag, IPerson, ISerie, IMovie, MediaRequest, IEpisode, ExternalImage, IBackupFile, ILibrary, SerieInMedia, DeletedQuery, RsDeleted, MovieSort, RsSort, SqlOrder, RsRequest, DetectedFaceResult, UnassignFaceResponse, RsGroupDownload, IViewProgress, IWatched, IRsRequestProcessing } from './interfaces.js';
4
+ import { SSEMediasEvent, SSEUploadProgressEvent, SSEConvertProgressEvent, SSEEpisodesEvent, SSESeriesEvent, SSEMoviesEvent, SSEPeopleEvent, SSETagsEvent, SSELibraryStatusEvent, SSEMediaRatingEvent, SSEMediaProgressEvent, SSERequestProcessingEvent } from './sse-types.js';
4
5
  import { EncryptFileOptions, EncryptedFile } from './encryption.js';
5
6
  export interface MediaForUpdate {
6
7
  name?: string;
@@ -87,6 +88,7 @@ export interface LibraryHttpClient {
87
88
  readonly libraryStatus$?: Observable<SSELibraryStatusEvent>;
88
89
  readonly mediaRating$?: Observable<SSEMediaRatingEvent>;
89
90
  readonly mediaProgress$?: Observable<SSEMediaProgressEvent>;
91
+ readonly requestProcessing$?: Observable<SSERequestProcessingEvent>;
90
92
  }
91
93
  export declare class LibraryApi {
92
94
  private client;
@@ -106,6 +108,7 @@ export declare class LibraryApi {
106
108
  readonly libraryStatus$: Observable<SSELibraryStatusEvent>;
107
109
  readonly mediaRating$: Observable<SSEMediaRatingEvent>;
108
110
  readonly mediaProgress$: Observable<SSEMediaProgressEvent>;
111
+ readonly requestProcessing$: Observable<SSERequestProcessingEvent>;
109
112
  constructor(client: LibraryHttpClient, libraryId: string, library: ILibrary);
110
113
  /**
111
114
  * Creates a library-filtered stream from a client stream.
@@ -253,6 +256,43 @@ export declare class LibraryApi {
253
256
  checkRequestInstant(request: RsRequest): Promise<{
254
257
  instant: boolean;
255
258
  }>;
259
+ /**
260
+ * Process an unprocessed RsRequest and return the processed result.
261
+ * Takes a raw request and runs it through the server's plugin processing pipeline.
262
+ * @param request - The unprocessed request to process
263
+ * @returns The processed request with updated status and metadata
264
+ */
265
+ processRequest(request: RsRequest): Promise<RsRequest>;
266
+ /**
267
+ * Process a request and return the raw HTTP stream response.
268
+ * Use this to stream content directly without processing the full response.
269
+ * @param request - The request to process and stream
270
+ * @returns Raw axios response with stream data - use response.data for the stream
271
+ */
272
+ processRequestStream(request: RsRequest): Promise<AxiosResponse>;
273
+ /**
274
+ * Add a request to the processing queue.
275
+ * @param request - The request to add for processing
276
+ * @param mediaRef - Optional media reference to associate with the processing
277
+ * @returns The created request processing entry
278
+ */
279
+ addRequest(request: RsRequest, mediaRef?: string): Promise<IRsRequestProcessing>;
280
+ /**
281
+ * List all active request processings for this library.
282
+ * @returns Array of request processing entries
283
+ */
284
+ listRequestProcessing(): Promise<IRsRequestProcessing[]>;
285
+ /**
286
+ * Pause a request processing task.
287
+ * @param processingId - The ID of the processing task to pause
288
+ * @returns The updated request processing entry with status "paused"
289
+ */
290
+ pauseRequestProcessing(processingId: string): Promise<IRsRequestProcessing>;
291
+ /**
292
+ * Delete/remove a request processing task.
293
+ * @param processingId - The ID of the processing task to delete
294
+ */
295
+ deleteRequestProcessing(processingId: string): Promise<void>;
256
296
  /**
257
297
  * Get a share token for a request URL.
258
298
  * The token can be used to stream/download the resource without authentication.
package/dist/library.js CHANGED
@@ -19,6 +19,7 @@ export class LibraryApi {
19
19
  this.libraryStatus$ = this.createLibraryFilteredStream(client.libraryStatus$);
20
20
  this.mediaRating$ = this.createLibraryFilteredStream(client.mediaRating$);
21
21
  this.mediaProgress$ = this.createLibraryFilteredStream(client.mediaProgress$);
22
+ this.requestProcessing$ = this.createLibraryFilteredStream(client.requestProcessing$);
22
23
  }
23
24
  /**
24
25
  * Creates a library-filtered stream from a client stream.
@@ -525,6 +526,59 @@ export class LibraryApi {
525
526
  const res = await this.client.post(this.getUrl('/plugins/requests/check-instant'), request);
526
527
  return res.data;
527
528
  }
529
+ /**
530
+ * Process an unprocessed RsRequest and return the processed result.
531
+ * Takes a raw request and runs it through the server's plugin processing pipeline.
532
+ * @param request - The unprocessed request to process
533
+ * @returns The processed request with updated status and metadata
534
+ */
535
+ async processRequest(request) {
536
+ const res = await this.client.post(this.getUrl('/plugins/requests/process'), request);
537
+ return res.data;
538
+ }
539
+ /**
540
+ * Process a request and return the raw HTTP stream response.
541
+ * Use this to stream content directly without processing the full response.
542
+ * @param request - The request to process and stream
543
+ * @returns Raw axios response with stream data - use response.data for the stream
544
+ */
545
+ async processRequestStream(request) {
546
+ return this.client.post(this.getUrl('/plugins/requests/process/stream'), request, { responseType: 'stream' });
547
+ }
548
+ /**
549
+ * Add a request to the processing queue.
550
+ * @param request - The request to add for processing
551
+ * @param mediaRef - Optional media reference to associate with the processing
552
+ * @returns The created request processing entry
553
+ */
554
+ async addRequest(request, mediaRef) {
555
+ const res = await this.client.post(this.getUrl('/plugins/requests/add'), { request, mediaRef });
556
+ return res.data;
557
+ }
558
+ /**
559
+ * List all active request processings for this library.
560
+ * @returns Array of request processing entries
561
+ */
562
+ async listRequestProcessing() {
563
+ const res = await this.client.get(this.getUrl('/plugins/requests/processing'));
564
+ return res.data;
565
+ }
566
+ /**
567
+ * Pause a request processing task.
568
+ * @param processingId - The ID of the processing task to pause
569
+ * @returns The updated request processing entry with status "paused"
570
+ */
571
+ async pauseRequestProcessing(processingId) {
572
+ const res = await this.client.post(this.getUrl(`/plugins/requests/processing/${processingId}/pause`));
573
+ return res.data;
574
+ }
575
+ /**
576
+ * Delete/remove a request processing task.
577
+ * @param processingId - The ID of the processing task to delete
578
+ */
579
+ async deleteRequestProcessing(processingId) {
580
+ await this.client.delete(this.getUrl(`/plugins/requests/processing/${processingId}`));
581
+ }
528
582
  /**
529
583
  * Get a share token for a request URL.
530
584
  * The token can be used to stream/download the resource without authentication.
@@ -1,4 +1,4 @@
1
- import { ILibrary, IFile, IEpisode, ISerie, IMovie, IPerson, ITag, IBackup } from './interfaces.js';
1
+ import { ILibrary, IFile, IEpisode, ISerie, IMovie, IPerson, ITag, IBackup, IWatched, IUnwatched, IRsRequestProcessing } from './interfaces.js';
2
2
  export type ElementAction = 'Added' | 'Updated' | 'Deleted';
3
3
  export type SSEConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error';
4
4
  export interface SSEConnectionOptions {
@@ -134,6 +134,30 @@ export interface SSEPlayersListEvent {
134
134
  userRef: string;
135
135
  players: SSEPlayerEvent[];
136
136
  }
137
+ /**
138
+ * SSE event emitted when content is marked as watched.
139
+ * This is a user-scoped event - not library-scoped.
140
+ * Uses external IDs (e.g., "imdb:tt0111161").
141
+ */
142
+ export type SSEWatchedEvent = IWatched;
143
+ /**
144
+ * SSE event emitted when content is removed from watch history.
145
+ * Contains all possible external IDs so clients can match against any cached ID.
146
+ * This is a user-scoped event - not library-scoped.
147
+ */
148
+ export type SSEUnwatchedEvent = IUnwatched;
149
+ /**
150
+ * SSE event for request processing status updates.
151
+ * Tracks plugin-based request processing (downloads, file processing, etc.).
152
+ * This is a library-scoped event.
153
+ */
154
+ export interface SSERequestProcessingEvent {
155
+ library: string;
156
+ processings: {
157
+ action: ElementAction;
158
+ processing: IRsRequestProcessing;
159
+ }[];
160
+ }
137
161
  export interface SSEEventMap {
138
162
  'library': SSELibraryEvent;
139
163
  'library-status': SSELibraryStatusEvent;
@@ -150,6 +174,9 @@ export interface SSEEventMap {
150
174
  'media_rating': SSEMediaRatingEvent;
151
175
  'media_progress': SSEMediaProgressEvent;
152
176
  'players-list': SSEPlayersListEvent;
177
+ 'watched': SSEWatchedEvent;
178
+ 'unwatched': SSEUnwatchedEvent;
179
+ 'request_processing': SSERequestProcessingEvent;
153
180
  }
154
181
  export type SSEEventName = keyof SSEEventMap;
155
182
  export interface SSEEvent<T extends SSEEventName = SSEEventName> {