@redseat/api 0.3.0 → 0.3.6

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/client.md CHANGED
@@ -560,6 +560,60 @@ client.backupFiles$.subscribe(event => {
560
560
  });
561
561
  ```
562
562
 
563
+ #### `mediaRating$: Observable<SSEMediaRatingEvent>`
564
+
565
+ Emits when a user rates a media item.
566
+
567
+ ```typescript
568
+ client.mediaRating$.subscribe(event => {
569
+ console.log(`User ${event.rating.userRef} rated media ${event.rating.mediaRef}: ${event.rating.rating}`);
570
+ });
571
+ ```
572
+
573
+ **Event structure:**
574
+ - `library`: Library ID where the media is located
575
+ - `rating.userRef`: User who rated
576
+ - `rating.mediaRef`: Media that was rated
577
+ - `rating.rating`: Rating value (0-5)
578
+ - `rating.modified`: Timestamp of the rating
579
+
580
+ #### `mediaProgress$: Observable<SSEMediaProgressEvent>`
581
+
582
+ Emits when a user's playback progress is updated.
583
+
584
+ ```typescript
585
+ client.mediaProgress$.subscribe(event => {
586
+ console.log(`User ${event.progress.userRef} watched ${event.progress.mediaRef} to ${event.progress.progress}ms`);
587
+ });
588
+ ```
589
+
590
+ **Event structure:**
591
+ - `library`: Library ID where the media is located
592
+ - `progress.userRef`: User whose progress updated
593
+ - `progress.mediaRef`: Media being tracked
594
+ - `progress.progress`: Current playback position in milliseconds
595
+ - `progress.modified`: Timestamp of the update
596
+
597
+ #### `playersList$: Observable<SSEPlayersListEvent>`
598
+
599
+ Emits the full list of available media players for the user when it changes.
600
+
601
+ ```typescript
602
+ client.playersList$.subscribe(event => {
603
+ console.log(`Available players for ${event.userRef}:`);
604
+ for (const player of event.players) {
605
+ console.log(` - ${player.name} (${player.player})`);
606
+ }
607
+ });
608
+ ```
609
+
610
+ **Event structure:**
611
+ - `userRef`: User ID
612
+ - `players`: Array of available players
613
+ - `id`: Player socket ID (for casting)
614
+ - `name`: Player display name
615
+ - `player`: Player type identifier
616
+
563
617
  ### Complete SSE Example
564
618
 
565
619
  ```typescript
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 } from './sse-types.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';
6
6
  export interface ClientOptions {
7
7
  server: IServer;
8
8
  getIdToken: () => Promise<string>;
@@ -42,6 +42,9 @@ export declare class RedseatClient {
42
42
  readonly tags$: Observable<SSETagsEvent>;
43
43
  readonly backups$: Observable<SSEBackupsEvent>;
44
44
  readonly backupFiles$: Observable<SSEBackupFilesEvent>;
45
+ readonly mediaRating$: Observable<SSEMediaRatingEvent>;
46
+ readonly mediaProgress$: Observable<SSEMediaProgressEvent>;
47
+ readonly playersList$: Observable<SSEPlayersListEvent>;
45
48
  /**
46
49
  * Creates a typed observable for a specific SSE event type.
47
50
  * Unwraps the nested data structure from the server (e.g., {uploadProgress: {...}} -> {...})
package/dist/client.js CHANGED
@@ -9,14 +9,18 @@ export class RedseatClient {
9
9
  createEventStream(eventName) {
10
10
  // Map event names to their wrapper property names (snake_case -> camelCase)
11
11
  const wrapperMap = {
12
+ 'medias': 'medias',
12
13
  'upload_progress': 'uploadProgress',
13
14
  'convert_progress': 'convertProgress',
14
15
  'media_progress': 'mediaProgress',
16
+ 'media_rating': 'mediaRating',
15
17
  'library-status': 'libraryStatus',
16
18
  'backups-files': 'backupsFiles',
19
+ 'players-list': 'Players',
17
20
  };
18
21
  const wrapperKey = wrapperMap[eventName];
19
22
  return this._sseEvents.pipe(filter((event) => event.event === eventName), map(event => {
23
+ //console.log("EVENT", event)
20
24
  // If there's a wrapper, unwrap it; otherwise return data as-is
21
25
  const data = event.data;
22
26
  if (wrapperKey && data && typeof data === 'object' && wrapperKey in data) {
@@ -48,6 +52,9 @@ export class RedseatClient {
48
52
  this.tags$ = this.createEventStream('tags');
49
53
  this.backups$ = this.createEventStream('backups');
50
54
  this.backupFiles$ = this.createEventStream('backups-files');
55
+ this.mediaRating$ = this.createEventStream('media_rating');
56
+ this.mediaProgress$ = this.createEventStream('media_progress');
57
+ this.playersList$ = this.createEventStream('players-list');
51
58
  this.server = options.server;
52
59
  this.redseatUrl = options.redseatUrl;
53
60
  this.getIdToken = options.getIdToken;
@@ -341,8 +348,12 @@ export class RedseatClient {
341
348
  }
342
349
  this._sseConnectionState.next('connected');
343
350
  this.sseReconnectAttempts = 0;
344
- // Process the stream
345
- await this.processSSEStream(response.body);
351
+ // Process the stream in the background (don't await - it runs forever until disconnected)
352
+ this.processSSEStream(response.body).catch(err => {
353
+ if (err?.name !== 'AbortError') {
354
+ this.handleSSEError(err);
355
+ }
356
+ });
346
357
  }
347
358
  catch (error) {
348
359
  if (error instanceof Error && error.name === 'AbortError') {
@@ -384,6 +395,7 @@ export class RedseatClient {
384
395
  const { events, remainingBuffer } = this.parseSSEBuffer(buffer);
385
396
  buffer = remainingBuffer;
386
397
  for (const event of events) {
398
+ //console.log("event process", JSON.stringify(event))
387
399
  this._sseEvents.next(event);
388
400
  }
389
401
  }
@@ -414,6 +414,52 @@ export interface ClusterFacesResponse {
414
414
  export interface IChannelUpdate {
415
415
  [key: string]: any;
416
416
  }
417
+ export declare enum PluginAuthType {
418
+ OAUTH = "oauth",
419
+ URL = "url",
420
+ Token = "token",
421
+ LoginPassword = "password"
422
+ }
423
+ export interface IPluginParam {
424
+ name: string;
425
+ param: Record<string, any>;
426
+ description?: string;
427
+ required?: boolean;
428
+ }
429
+ export interface IPlugin {
430
+ id: string;
431
+ name: string;
432
+ libraries: string[];
433
+ description: string;
434
+ credential: string;
435
+ credentialType: {
436
+ type: PluginAuthType;
437
+ url?: string;
438
+ };
439
+ params?: IPluginParam[];
440
+ oauthUrl?: string;
441
+ repo?: string;
442
+ publisher?: string;
443
+ version?: number;
444
+ installed: boolean;
445
+ capabilities?: string[];
446
+ local?: boolean;
447
+ }
448
+ export interface ICredential {
449
+ id?: string;
450
+ name: string;
451
+ source: string;
452
+ type: {
453
+ type: PluginAuthType;
454
+ url?: string;
455
+ };
456
+ login?: string;
457
+ password?: string;
458
+ settings: Record<string, any>;
459
+ user_ref?: string;
460
+ refreshtoken?: string;
461
+ expires?: number;
462
+ }
417
463
  export declare enum ElementType {
418
464
  Tag = "tag",
419
465
  Person = "person",
@@ -437,6 +483,42 @@ export declare enum RsSort {
437
483
  Name = "name",
438
484
  Size = "size"
439
485
  }
486
+ export type MediaType = 'episode' | 'movie' | 'book' | 'song' | 'media';
487
+ export interface IWatched {
488
+ type: MediaType;
489
+ id: string;
490
+ userRef?: string;
491
+ date: number;
492
+ modified: number;
493
+ }
494
+ export interface IWatchedForAdd {
495
+ type: MediaType;
496
+ id: string;
497
+ date: number;
498
+ }
499
+ export interface IViewProgress {
500
+ type: MediaType;
501
+ id: string;
502
+ userRef: string;
503
+ progress: number;
504
+ parent?: string;
505
+ modified: number;
506
+ }
507
+ export interface IViewProgressForAdd {
508
+ type: MediaType;
509
+ id: string;
510
+ parent?: string;
511
+ progress: number;
512
+ }
513
+ export interface HistoryQuery {
514
+ sort?: RsSort;
515
+ order?: SqlOrder;
516
+ before?: number;
517
+ after?: number;
518
+ types?: MediaType[];
519
+ id?: string;
520
+ pageKey?: number;
521
+ }
440
522
  export type MovieSort = 'modified' | 'added' | 'created' | 'name' | 'digitalairdate';
441
523
  export interface DeletedQuery {
442
524
  after?: number;
@@ -499,6 +581,7 @@ export interface RsRequest {
499
581
  filename?: string;
500
582
  status: RsRequestStatus;
501
583
  permanent: boolean;
584
+ instant?: boolean;
502
585
  jsonBody?: any;
503
586
  method: RsRequestMethod;
504
587
  referer?: string;
@@ -36,6 +36,13 @@ export var LinkType;
36
36
  LinkType["post"] = "post";
37
37
  LinkType["other"] = "other";
38
38
  })(LinkType || (LinkType = {}));
39
+ export var PluginAuthType;
40
+ (function (PluginAuthType) {
41
+ PluginAuthType["OAUTH"] = "oauth";
42
+ PluginAuthType["URL"] = "url";
43
+ PluginAuthType["Token"] = "token";
44
+ PluginAuthType["LoginPassword"] = "password";
45
+ })(PluginAuthType || (PluginAuthType = {}));
39
46
  export var ElementType;
40
47
  (function (ElementType) {
41
48
  ElementType["Tag"] = "tag";
package/dist/library.d.ts CHANGED
@@ -1,6 +1,6 @@
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 } from './interfaces.js';
3
- import { SSEMediasEvent, SSEUploadProgressEvent, SSEConvertProgressEvent, SSEEpisodesEvent, SSESeriesEvent, SSEMoviesEvent, SSEPeopleEvent, SSETagsEvent, SSELibraryStatusEvent } from './sse-types.js';
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';
4
4
  import { EncryptFileOptions, EncryptedFile } from './encryption.js';
5
5
  export interface MediaForUpdate {
6
6
  name?: string;
@@ -85,6 +85,8 @@ export interface LibraryHttpClient {
85
85
  readonly people$?: Observable<SSEPeopleEvent>;
86
86
  readonly tags$?: Observable<SSETagsEvent>;
87
87
  readonly libraryStatus$?: Observable<SSELibraryStatusEvent>;
88
+ readonly mediaRating$?: Observable<SSEMediaRatingEvent>;
89
+ readonly mediaProgress$?: Observable<SSEMediaProgressEvent>;
88
90
  }
89
91
  export declare class LibraryApi {
90
92
  private client;
@@ -102,6 +104,8 @@ export declare class LibraryApi {
102
104
  readonly people$: Observable<SSEPeopleEvent>;
103
105
  readonly tags$: Observable<SSETagsEvent>;
104
106
  readonly libraryStatus$: Observable<SSELibraryStatusEvent>;
107
+ readonly mediaRating$: Observable<SSEMediaRatingEvent>;
108
+ readonly mediaProgress$: Observable<SSEMediaProgressEvent>;
105
109
  constructor(client: LibraryHttpClient, libraryId: string, library: ILibrary);
106
110
  /**
107
111
  * Creates a library-filtered stream from a client stream.
@@ -190,6 +194,9 @@ export declare class LibraryApi {
190
194
  updateMovie(movieId: string, updates: Partial<IMovie>): Promise<IMovie>;
191
195
  getMovieImages(movieId: string): Promise<ExternalImage[]>;
192
196
  setMovieWatched(movieId: string, date: number): Promise<void>;
197
+ getMovieWatched(movieId: string): Promise<IWatched>;
198
+ getMovieProgress(movieId: string): Promise<IViewProgress>;
199
+ setMovieProgress(movieId: string, progress: number): Promise<void>;
193
200
  searchMovies(name: string): Promise<IMovie[]>;
194
201
  movieRename(movieId: string, newName: string): Promise<IMovie>;
195
202
  updateMoviePoster(movieId: string, poster: FormData, type: string): Promise<void>;
@@ -210,6 +217,9 @@ export declare class LibraryApi {
210
217
  updatePersonPortrait(personId: string, portrait: FormData): Promise<void>;
211
218
  searchSeries(name: string): Promise<ISerie[]>;
212
219
  setEpisodeWatched(serieId: string, season: number, number: number, date: number): Promise<void>;
220
+ getEpisodeWatched(serieId: string, season: number, episode: number): Promise<IWatched>;
221
+ getEpisodeProgress(serieId: string, season: number, episode: number): Promise<IViewProgress>;
222
+ setEpisodeProgress(serieId: string, season: number, episode: number, progress: number): Promise<void>;
213
223
  /**
214
224
  * Searches for available media sources for a specific episode.
215
225
  * @param serieId - The series identifier
@@ -235,6 +245,14 @@ export declare class LibraryApi {
235
245
  * @throws Error if the URL is not available or cannot be made permanent
236
246
  */
237
247
  checkRequestPermanent(request: RsRequest): Promise<RsRequest>;
248
+ /**
249
+ * Checks if a request can be processed instantly (immediate availability).
250
+ * @param request - The request to check for instant status
251
+ * @returns Object with instant boolean indicating if the request can be processed immediately
252
+ */
253
+ checkRequestInstant(request: RsRequest): Promise<{
254
+ instant: boolean;
255
+ }>;
238
256
  /**
239
257
  * Get a share token for a request URL.
240
258
  * The token can be used to stream/download the resource without authentication.
package/dist/library.js CHANGED
@@ -17,6 +17,8 @@ export class LibraryApi {
17
17
  this.people$ = this.createLibraryFilteredStream(client.people$);
18
18
  this.tags$ = this.createLibraryFilteredStream(client.tags$);
19
19
  this.libraryStatus$ = this.createLibraryFilteredStream(client.libraryStatus$);
20
+ this.mediaRating$ = this.createLibraryFilteredStream(client.mediaRating$);
21
+ this.mediaProgress$ = this.createLibraryFilteredStream(client.mediaProgress$);
20
22
  }
21
23
  /**
22
24
  * Creates a library-filtered stream from a client stream.
@@ -366,6 +368,17 @@ export class LibraryApi {
366
368
  async setMovieWatched(movieId, date) {
367
369
  await this.client.post(this.getUrl(`/movies/${movieId}/watched`), { date });
368
370
  }
371
+ async getMovieWatched(movieId) {
372
+ const res = await this.client.get(this.getUrl(`/movies/${movieId}/watched`));
373
+ return res.data;
374
+ }
375
+ async getMovieProgress(movieId) {
376
+ const res = await this.client.get(this.getUrl(`/movies/${movieId}/progress`));
377
+ return res.data;
378
+ }
379
+ async setMovieProgress(movieId, progress) {
380
+ await this.client.post(this.getUrl(`/movies/${movieId}/progress`), { progress });
381
+ }
369
382
  async searchMovies(name) {
370
383
  const res = await this.client.get(this.getUrl(`/movies/search?name=${name}`));
371
384
  return res.data;
@@ -458,6 +471,17 @@ export class LibraryApi {
458
471
  async setEpisodeWatched(serieId, season, number, date) {
459
472
  await this.client.post(this.getUrl(`/series/${serieId}/seasons/${season}/episodes/${number}/watched`), { date });
460
473
  }
474
+ async getEpisodeWatched(serieId, season, episode) {
475
+ const res = await this.client.get(this.getUrl(`/series/${serieId}/seasons/${season}/episodes/${episode}/watched`));
476
+ return res.data;
477
+ }
478
+ async getEpisodeProgress(serieId, season, episode) {
479
+ const res = await this.client.get(this.getUrl(`/series/${serieId}/seasons/${season}/episodes/${episode}/progress`));
480
+ return res.data;
481
+ }
482
+ async setEpisodeProgress(serieId, season, episode, progress) {
483
+ await this.client.post(this.getUrl(`/series/${serieId}/seasons/${season}/episodes/${episode}/progress`), { progress });
484
+ }
461
485
  /**
462
486
  * Searches for available media sources for a specific episode.
463
487
  * @param serieId - The series identifier
@@ -492,6 +516,15 @@ export class LibraryApi {
492
516
  const res = await this.client.post(this.getUrl('/plugins/requests/permanent'), request);
493
517
  return res.data;
494
518
  }
519
+ /**
520
+ * Checks if a request can be processed instantly (immediate availability).
521
+ * @param request - The request to check for instant status
522
+ * @returns Object with instant boolean indicating if the request can be processed immediately
523
+ */
524
+ async checkRequestInstant(request) {
525
+ const res = await this.client.post(this.getUrl('/plugins/requests/check-instant'), request);
526
+ return res.data;
527
+ }
495
528
  /**
496
529
  * Get a share token for a request URL.
497
530
  * The token can be used to stream/download the resource without authentication.
package/dist/server.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { RedseatClient } from './client.js';
2
- import { ILibrary } from './interfaces.js';
2
+ import { ILibrary, IPlugin, ICredential, IWatched, IWatchedForAdd, IViewProgress, IViewProgressForAdd, HistoryQuery } from './interfaces.js';
3
3
  export declare class ServerApi {
4
4
  private client;
5
5
  constructor(client: RedseatClient);
@@ -12,7 +12,17 @@ export declare class ServerApi {
12
12
  }): Promise<any>;
13
13
  addLibrary(library: Partial<ILibrary>): Promise<ILibrary>;
14
14
  getSetting(key: string): Promise<string>;
15
- getPlugins(): Promise<any[]>;
16
- getCredentials(): Promise<any[]>;
15
+ getPlugins(): Promise<IPlugin[]>;
16
+ getCredentials(): Promise<ICredential[]>;
17
+ saveCredential(credential: ICredential): Promise<ICredential>;
18
+ updateCredential(credential: ICredential): Promise<ICredential>;
19
+ deleteCredential(id: string): Promise<void>;
20
+ saveOAuthCredentials(pluginId: string, params: Record<string, string>): Promise<void>;
17
21
  addLibraryCredential(credential: any): Promise<ILibrary>;
22
+ getHistory(query?: HistoryQuery): Promise<IWatched[]>;
23
+ addToHistory(watched: IWatchedForAdd): Promise<void>;
24
+ getProgressById(id: string): Promise<IViewProgress | null>;
25
+ addProgress(progress: IViewProgressForAdd): Promise<void>;
26
+ getAdminHistory(): Promise<IWatched[]>;
27
+ importHistory(watcheds: IWatchedForAdd[]): Promise<void>;
18
28
  }
package/dist/server.js CHANGED
@@ -30,8 +30,62 @@ export class ServerApi {
30
30
  const res = await this.client.get('/credentials');
31
31
  return res.data;
32
32
  }
33
+ async saveCredential(credential) {
34
+ const res = await this.client.post('/credentials', credential);
35
+ return res.data;
36
+ }
37
+ async updateCredential(credential) {
38
+ const res = await this.client.patch(`/credentials/${credential.id}`, credential);
39
+ return res.data;
40
+ }
41
+ async deleteCredential(id) {
42
+ await this.client.delete(`/credentials/${id}`);
43
+ }
44
+ async saveOAuthCredentials(pluginId, params) {
45
+ await this.client.post(`/plugins/${pluginId}/oauthtoken`, params);
46
+ }
33
47
  async addLibraryCredential(credential) {
34
48
  const res = await this.client.post('/libraries/credential', credential);
35
49
  return res.data;
36
50
  }
51
+ // History methods
52
+ async getHistory(query) {
53
+ const params = {};
54
+ if (query) {
55
+ if (query.sort !== undefined)
56
+ params.sort = query.sort;
57
+ if (query.order !== undefined)
58
+ params.order = query.order;
59
+ if (query.before !== undefined)
60
+ params.before = query.before;
61
+ if (query.after !== undefined)
62
+ params.after = query.after;
63
+ if (query.types !== undefined && query.types.length > 0)
64
+ params.types = query.types;
65
+ if (query.id !== undefined)
66
+ params.id = query.id;
67
+ if (query.pageKey !== undefined)
68
+ params.pageKey = query.pageKey;
69
+ }
70
+ const res = await this.client.get('/users/me/history', { params });
71
+ return res.data;
72
+ }
73
+ async addToHistory(watched) {
74
+ await this.client.post('/users/me/history', watched);
75
+ }
76
+ async getProgressById(id) {
77
+ const res = await this.client.get(`/users/me/history/progress/${id}`);
78
+ return res.data;
79
+ }
80
+ async addProgress(progress) {
81
+ await this.client.post('/users/me/history/progress', progress);
82
+ }
83
+ // Admin history methods
84
+ async getAdminHistory() {
85
+ const res = await this.client.get('/users/admin/history');
86
+ return res.data;
87
+ }
88
+ async importHistory(watcheds) {
89
+ await this.client.post('/users/admin/history/import', watcheds);
90
+ }
37
91
  }
@@ -107,6 +107,33 @@ export interface SSEBackupFilesEvent {
107
107
  status?: string;
108
108
  message?: string;
109
109
  }
110
+ export interface SSEMediaRatingEvent {
111
+ library: string;
112
+ rating: {
113
+ userRef: string;
114
+ mediaRef: string;
115
+ rating: number;
116
+ modified: number;
117
+ };
118
+ }
119
+ export interface SSEMediaProgressEvent {
120
+ library: string;
121
+ progress: {
122
+ userRef: string;
123
+ mediaRef: string;
124
+ progress: number;
125
+ modified: number;
126
+ };
127
+ }
128
+ export interface SSEPlayerEvent {
129
+ id: string;
130
+ name: string;
131
+ player: string;
132
+ }
133
+ export interface SSEPlayersListEvent {
134
+ userRef: string;
135
+ players: SSEPlayerEvent[];
136
+ }
110
137
  export interface SSEEventMap {
111
138
  'library': SSELibraryEvent;
112
139
  'library-status': SSELibraryStatusEvent;
@@ -120,6 +147,9 @@ export interface SSEEventMap {
120
147
  'tags': SSETagsEvent;
121
148
  'backups': SSEBackupsEvent;
122
149
  'backups-files': SSEBackupFilesEvent;
150
+ 'media_rating': SSEMediaRatingEvent;
151
+ 'media_progress': SSEMediaProgressEvent;
152
+ 'players-list': SSEPlayersListEvent;
123
153
  }
124
154
  export type SSEEventName = keyof SSEEventMap;
125
155
  export interface SSEEvent<T extends SSEEventName = SSEEventName> {
package/libraries.md CHANGED
@@ -190,6 +190,40 @@ libraryApi.libraryStatus$.subscribe(event => {
190
190
  });
191
191
  ```
192
192
 
193
+ #### `mediaRating$: Observable<SSEMediaRatingEvent>`
194
+
195
+ Emits when a user rates a media item in this library.
196
+
197
+ ```typescript
198
+ libraryApi.mediaRating$.subscribe(event => {
199
+ console.log(`Rating for ${event.rating.mediaRef}: ${event.rating.rating}`);
200
+ });
201
+ ```
202
+
203
+ **Event structure:**
204
+ - `library`: Library ID
205
+ - `rating.userRef`: User who rated
206
+ - `rating.mediaRef`: Media that was rated
207
+ - `rating.rating`: Rating value (0-5)
208
+ - `rating.modified`: Timestamp of the rating
209
+
210
+ #### `mediaProgress$: Observable<SSEMediaProgressEvent>`
211
+
212
+ Emits when a user's playback progress is updated for a media item in this library.
213
+
214
+ ```typescript
215
+ libraryApi.mediaProgress$.subscribe(event => {
216
+ console.log(`Progress for ${event.progress.mediaRef}: ${event.progress.progress}ms`);
217
+ });
218
+ ```
219
+
220
+ **Event structure:**
221
+ - `library`: Library ID
222
+ - `progress.userRef`: User whose progress updated
223
+ - `progress.mediaRef`: Media being tracked
224
+ - `progress.progress`: Current playback position in milliseconds
225
+ - `progress.modified`: Timestamp of the update
226
+
193
227
  ### Complete SSE Example with LibraryApi
194
228
 
195
229
  ```typescript
@@ -878,6 +912,62 @@ Marks an episode as watched.
878
912
  await libraryApi.setEpisodeWatched('serie-id', 1, 1, Date.now());
879
913
  ```
880
914
 
915
+ ### `getEpisodeWatched(serieId: string, season: number, episode: number): Promise<IWatched>`
916
+
917
+ Gets the watched status for an episode.
918
+
919
+ **Parameters:**
920
+
921
+ - `serieId`: The ID of the series
922
+ - `season`: Season number
923
+ - `episode`: Episode number
924
+
925
+ **Returns:** Promise resolving to an `IWatched` object containing watched date and metadata
926
+
927
+ **Example:**
928
+
929
+ ```typescript
930
+ const watched = await libraryApi.getEpisodeWatched('serie-id', 1, 1);
931
+ console.log(`Watched on: ${new Date(watched.date).toLocaleDateString()}`);
932
+ ```
933
+
934
+ ### `getEpisodeProgress(serieId: string, season: number, episode: number): Promise<IViewProgress>`
935
+
936
+ Gets the view progress for an episode.
937
+
938
+ **Parameters:**
939
+
940
+ - `serieId`: The ID of the series
941
+ - `season`: Season number
942
+ - `episode`: Episode number
943
+
944
+ **Returns:** Promise resolving to an `IViewProgress` object containing progress value
945
+
946
+ **Example:**
947
+
948
+ ```typescript
949
+ const progress = await libraryApi.getEpisodeProgress('serie-id', 1, 1);
950
+ console.log(`Progress: ${progress.progress}ms`);
951
+ ```
952
+
953
+ ### `setEpisodeProgress(serieId: string, season: number, episode: number, progress: number): Promise<void>`
954
+
955
+ Sets the view progress for an episode.
956
+
957
+ **Parameters:**
958
+
959
+ - `serieId`: The ID of the series
960
+ - `season`: Season number
961
+ - `episode`: Episode number
962
+ - `progress`: Progress value (typically in milliseconds for video playback position)
963
+
964
+ **Example:**
965
+
966
+ ```typescript
967
+ // Save progress at 30 minutes
968
+ await libraryApi.setEpisodeProgress('serie-id', 1, 1, 1800000);
969
+ ```
970
+
881
971
  ---
882
972
 
883
973
  ## Movies
@@ -1000,6 +1090,56 @@ Marks a movie as watched.
1000
1090
  await libraryApi.setMovieWatched('movie-id', Date.now());
1001
1091
  ```
1002
1092
 
1093
+ ### `getMovieWatched(movieId: string): Promise<IWatched>`
1094
+
1095
+ Gets the watched status for a movie.
1096
+
1097
+ **Parameters:**
1098
+
1099
+ - `movieId`: The ID of the movie
1100
+
1101
+ **Returns:** Promise resolving to an `IWatched` object containing watched date and metadata
1102
+
1103
+ **Example:**
1104
+
1105
+ ```typescript
1106
+ const watched = await libraryApi.getMovieWatched('movie-id');
1107
+ console.log(`Watched on: ${new Date(watched.date).toLocaleDateString()}`);
1108
+ ```
1109
+
1110
+ ### `getMovieProgress(movieId: string): Promise<IViewProgress>`
1111
+
1112
+ Gets the view progress for a movie.
1113
+
1114
+ **Parameters:**
1115
+
1116
+ - `movieId`: The ID of the movie
1117
+
1118
+ **Returns:** Promise resolving to an `IViewProgress` object containing progress value
1119
+
1120
+ **Example:**
1121
+
1122
+ ```typescript
1123
+ const progress = await libraryApi.getMovieProgress('movie-id');
1124
+ console.log(`Progress: ${progress.progress}ms`);
1125
+ ```
1126
+
1127
+ ### `setMovieProgress(movieId: string, progress: number): Promise<void>`
1128
+
1129
+ Sets the view progress for a movie.
1130
+
1131
+ **Parameters:**
1132
+
1133
+ - `movieId`: The ID of the movie
1134
+ - `progress`: Progress value (typically in milliseconds for video playback position)
1135
+
1136
+ **Example:**
1137
+
1138
+ ```typescript
1139
+ // Save progress at 45 minutes
1140
+ await libraryApi.setMovieProgress('movie-id', 2700000);
1141
+ ```
1142
+
1003
1143
  ### `searchMovies(name: string): Promise<IMovie[]>`
1004
1144
 
1005
1145
  Searches for movies by name.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redseat/api",
3
- "version": "0.3.0",
3
+ "version": "0.3.6",
4
4
  "description": "TypeScript API client library for interacting with Redseat servers",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/server.md CHANGED
@@ -109,17 +109,98 @@ plugins.forEach(plugin => {
109
109
  });
110
110
  ```
111
111
 
112
- ### `getCredentials(): Promise<any[]>`
112
+ ### `getCredentials(): Promise<ICredential[]>`
113
113
 
114
114
  Retrieves all available credentials configured on the server.
115
115
 
116
- **Returns:** Promise resolving to an array of credential objects
116
+ **Returns:** Promise resolving to an array of `ICredential` objects
117
117
 
118
118
  **Example:**
119
119
  ```typescript
120
120
  const credentials = await serverApi.getCredentials();
121
121
  credentials.forEach(cred => {
122
- console.log(`Credential: ${cred.name} (${cred.type})`);
122
+ console.log(`Credential: ${cred.name} (${cred.type.type})`);
123
+ });
124
+ ```
125
+
126
+ ### `saveCredential(credential: ICredential): Promise<ICredential>`
127
+
128
+ Creates a new credential on the server.
129
+
130
+ **Parameters:**
131
+ - `credential`: Credential object with required fields:
132
+ - `name`: Credential name (required)
133
+ - `source`: Plugin/source name (required)
134
+ - `type`: Authentication type object (required)
135
+ - `settings`: Settings record for plugin params (required)
136
+ - `login`: Optional login/username
137
+ - `password`: Optional password/token
138
+
139
+ **Returns:** Promise resolving to the created `ICredential` object with generated `id`
140
+
141
+ **Example:**
142
+ ```typescript
143
+ const newCredential = await serverApi.saveCredential({
144
+ name: 'My API Key',
145
+ source: 'jackett',
146
+ type: { type: PluginAuthType.Token },
147
+ settings: { url: 'http://localhost:9117' },
148
+ password: 'my-api-key'
149
+ });
150
+ console.log(`Created credential with ID: ${newCredential.id}`);
151
+ ```
152
+
153
+ ### `updateCredential(credential: ICredential): Promise<ICredential>`
154
+
155
+ Updates an existing credential.
156
+
157
+ **Parameters:**
158
+ - `credential`: Credential object with `id` and fields to update
159
+
160
+ **Returns:** Promise resolving to the updated `ICredential` object
161
+
162
+ **Example:**
163
+ ```typescript
164
+ const updated = await serverApi.updateCredential({
165
+ id: 'cred-123',
166
+ name: 'Updated Name',
167
+ source: 'jackett',
168
+ type: { type: PluginAuthType.Token },
169
+ settings: { url: 'http://localhost:9117' },
170
+ password: 'new-api-key'
171
+ });
172
+ ```
173
+
174
+ ### `deleteCredential(id: string): Promise<void>`
175
+
176
+ Deletes a credential by ID.
177
+
178
+ **Parameters:**
179
+ - `id`: The credential ID to delete
180
+
181
+ **Returns:** Promise resolving when deletion is complete
182
+
183
+ **Example:**
184
+ ```typescript
185
+ await serverApi.deleteCredential('cred-123');
186
+ ```
187
+
188
+ ### `saveOAuthCredentials(pluginId: string, params: Record<string, string>): Promise<void>`
189
+
190
+ Exchanges OAuth tokens and saves credentials for a plugin.
191
+
192
+ **Parameters:**
193
+ - `pluginId`: The plugin ID to save credentials for
194
+ - `params`: OAuth parameters including tokens and name
195
+
196
+ **Returns:** Promise resolving when credentials are saved
197
+
198
+ **Example:**
199
+ ```typescript
200
+ await serverApi.saveOAuthCredentials('trakt', {
201
+ name: 'My Trakt Account',
202
+ code: 'oauth-code',
203
+ access_token: 'token123'
123
204
  });
124
205
  ```
125
206
 
@@ -141,6 +222,127 @@ const library = await serverApi.addLibraryCredential({
141
222
  });
142
223
  ```
143
224
 
225
+ ## History Methods
226
+
227
+ These methods manage the user's watch history and progress tracking across all libraries.
228
+
229
+ ### `getHistory(query?: HistoryQuery): Promise<IWatched[]>`
230
+
231
+ Retrieves the current user's watch history.
232
+
233
+ **Parameters:**
234
+ - `query`: Optional query object with filtering parameters:
235
+ - `sort`: Sort key using `RsSort` enum
236
+ - `order`: Sort direction using `SqlOrder` enum (`ASC` or `DESC`)
237
+ - `before`: Filter entries before this timestamp
238
+ - `after`: Filter entries after this timestamp
239
+ - `types`: Array of `MediaType` to filter by (`'episode'`, `'movie'`, `'book'`, `'song'`, `'media'`)
240
+ - `id`: Filter by specific ID
241
+ - `pageKey`: Pagination key
242
+
243
+ **Returns:** Promise resolving to an array of `IWatched` objects
244
+
245
+ **Example:**
246
+ ```typescript
247
+ // Get all history
248
+ const history = await serverApi.getHistory();
249
+
250
+ // Get recent movie watch history
251
+ const movieHistory = await serverApi.getHistory({
252
+ types: ['movie'],
253
+ order: SqlOrder.DESC,
254
+ after: Date.now() - 86400000 * 30 // Last 30 days
255
+ });
256
+ ```
257
+
258
+ ### `addToHistory(watched: IWatchedForAdd): Promise<void>`
259
+
260
+ Adds an entry to the user's watch history.
261
+
262
+ **Parameters:**
263
+ - `watched`: Object containing:
264
+ - `type`: The `MediaType` (`'episode'`, `'movie'`, `'book'`, `'song'`, `'media'`)
265
+ - `id`: The ID of the watched item
266
+ - `date`: Timestamp when it was watched
267
+
268
+ **Returns:** Promise resolving when the entry is added
269
+
270
+ **Example:**
271
+ ```typescript
272
+ await serverApi.addToHistory({
273
+ type: 'movie',
274
+ id: 'movie-123',
275
+ date: Date.now()
276
+ });
277
+ ```
278
+
279
+ ### `getProgressById(id: string): Promise<IViewProgress | null>`
280
+
281
+ Gets the view progress for a specific item by ID.
282
+
283
+ **Parameters:**
284
+ - `id`: The ID of the item to get progress for
285
+
286
+ **Returns:** Promise resolving to `IViewProgress` object or `null` if not found
287
+
288
+ **Example:**
289
+ ```typescript
290
+ const progress = await serverApi.getProgressById('movie-123');
291
+ if (progress) {
292
+ console.log(`Progress: ${progress.progress}ms`);
293
+ }
294
+ ```
295
+
296
+ ### `addProgress(progress: IViewProgressForAdd): Promise<void>`
297
+
298
+ Adds or updates view progress for an item.
299
+
300
+ **Parameters:**
301
+ - `progress`: Object containing:
302
+ - `type`: The `MediaType`
303
+ - `id`: The ID of the item
304
+ - `progress`: Progress value (typically in milliseconds for video)
305
+ - `parent`: Optional parent ID (e.g., series ID for episodes)
306
+
307
+ **Returns:** Promise resolving when progress is saved
308
+
309
+ **Example:**
310
+ ```typescript
311
+ await serverApi.addProgress({
312
+ type: 'movie',
313
+ id: 'movie-123',
314
+ progress: 3600000 // 1 hour in milliseconds
315
+ });
316
+ ```
317
+
318
+ ### `getAdminHistory(): Promise<IWatched[]>`
319
+
320
+ **Admin only.** Retrieves watch history for all users.
321
+
322
+ **Returns:** Promise resolving to an array of `IWatched` objects
323
+
324
+ **Example:**
325
+ ```typescript
326
+ const allHistory = await serverApi.getAdminHistory();
327
+ ```
328
+
329
+ ### `importHistory(watcheds: IWatchedForAdd[]): Promise<void>`
330
+
331
+ **Admin only.** Imports watch history entries in bulk.
332
+
333
+ **Parameters:**
334
+ - `watcheds`: Array of `IWatchedForAdd` objects to import
335
+
336
+ **Returns:** Promise resolving when import is complete
337
+
338
+ **Example:**
339
+ ```typescript
340
+ await serverApi.importHistory([
341
+ { type: 'movie', id: 'movie-1', date: Date.now() - 86400000 },
342
+ { type: 'episode', id: 'episode-1', date: Date.now() }
343
+ ]);
344
+ ```
345
+
144
346
  ## Usage Examples
145
347
 
146
348
  ### Complete Workflow: Create Library and Get Info