@redseat/api 0.3.0 → 0.3.5

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",
@@ -499,6 +545,7 @@ export interface RsRequest {
499
545
  filename?: string;
500
546
  status: RsRequestStatus;
501
547
  permanent: boolean;
548
+ instant?: boolean;
502
549
  jsonBody?: any;
503
550
  method: RsRequestMethod;
504
551
  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
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';
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.
@@ -235,6 +239,14 @@ export declare class LibraryApi {
235
239
  * @throws Error if the URL is not available or cannot be made permanent
236
240
  */
237
241
  checkRequestPermanent(request: RsRequest): Promise<RsRequest>;
242
+ /**
243
+ * Checks if a request can be processed instantly (immediate availability).
244
+ * @param request - The request to check for instant status
245
+ * @returns Object with instant boolean indicating if the request can be processed immediately
246
+ */
247
+ checkRequestInstant(request: RsRequest): Promise<{
248
+ instant: boolean;
249
+ }>;
238
250
  /**
239
251
  * Get a share token for a request URL.
240
252
  * 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.
@@ -492,6 +494,15 @@ export class LibraryApi {
492
494
  const res = await this.client.post(this.getUrl('/plugins/requests/permanent'), request);
493
495
  return res.data;
494
496
  }
497
+ /**
498
+ * Checks if a request can be processed instantly (immediate availability).
499
+ * @param request - The request to check for instant status
500
+ * @returns Object with instant boolean indicating if the request can be processed immediately
501
+ */
502
+ async checkRequestInstant(request) {
503
+ const res = await this.client.post(this.getUrl('/plugins/requests/check-instant'), request);
504
+ return res.data;
505
+ }
495
506
  /**
496
507
  * Get a share token for a request URL.
497
508
  * 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 } from './interfaces.js';
3
3
  export declare class ServerApi {
4
4
  private client;
5
5
  constructor(client: RedseatClient);
@@ -12,7 +12,11 @@ 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>;
18
22
  }
package/dist/server.js CHANGED
@@ -30,6 +30,20 @@ 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;
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redseat/api",
3
- "version": "0.3.0",
3
+ "version": "0.3.5",
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