@redseat/api 0.2.8 → 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 +60 -4
- package/dist/client.d.ts +7 -3
- package/dist/client.js +35 -5
- package/dist/interfaces.d.ts +47 -0
- package/dist/interfaces.js +7 -0
- package/dist/library.d.ts +15 -3
- package/dist/library.js +12 -1
- package/dist/server.d.ts +7 -3
- package/dist/server.js +14 -0
- package/dist/sse-types.d.ts +44 -5
- package/libraries.md +40 -4
- package/package.json +1 -1
- package/server.md +84 -3
package/client.md
CHANGED
|
@@ -458,13 +458,15 @@ client.medias$.subscribe(event => {
|
|
|
458
458
|
});
|
|
459
459
|
```
|
|
460
460
|
|
|
461
|
-
#### `
|
|
461
|
+
#### `uploadProgress$: Observable<SSEUploadProgressEvent>`
|
|
462
462
|
|
|
463
|
-
Emits
|
|
463
|
+
Emits upload progress updates including download, transfer, and analysis stages.
|
|
464
464
|
|
|
465
465
|
```typescript
|
|
466
|
-
client.
|
|
467
|
-
|
|
466
|
+
client.uploadProgress$.subscribe(event => {
|
|
467
|
+
const { progress } = event;
|
|
468
|
+
const percent = progress.total ? Math.round((progress.current ?? 0) / progress.total * 100) : 0;
|
|
469
|
+
console.log(`Upload ${progress.id} (${progress.type}): ${percent}% - ${progress.filename}`);
|
|
468
470
|
});
|
|
469
471
|
```
|
|
470
472
|
|
|
@@ -558,6 +560,60 @@ client.backupFiles$.subscribe(event => {
|
|
|
558
560
|
});
|
|
559
561
|
```
|
|
560
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
|
+
|
|
561
617
|
### Complete SSE Example
|
|
562
618
|
|
|
563
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,
|
|
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>;
|
|
@@ -33,7 +33,7 @@ export declare class RedseatClient {
|
|
|
33
33
|
readonly library$: Observable<SSELibraryEvent>;
|
|
34
34
|
readonly libraryStatus$: Observable<SSELibraryStatusEvent>;
|
|
35
35
|
readonly medias$: Observable<SSEMediasEvent>;
|
|
36
|
-
readonly
|
|
36
|
+
readonly uploadProgress$: Observable<SSEUploadProgressEvent>;
|
|
37
37
|
readonly convertProgress$: Observable<SSEConvertProgressEvent>;
|
|
38
38
|
readonly episodes$: Observable<SSEEpisodesEvent>;
|
|
39
39
|
readonly series$: Observable<SSESeriesEvent>;
|
|
@@ -42,8 +42,12 @@ 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
|
-
* Creates a typed observable for a specific SSE event type
|
|
49
|
+
* Creates a typed observable for a specific SSE event type.
|
|
50
|
+
* Unwraps the nested data structure from the server (e.g., {uploadProgress: {...}} -> {...})
|
|
47
51
|
*/
|
|
48
52
|
private createEventStream;
|
|
49
53
|
constructor(options: ClientOptions);
|
package/dist/client.js
CHANGED
|
@@ -3,10 +3,31 @@ import { BehaviorSubject, Subject, filter, map } from 'rxjs';
|
|
|
3
3
|
import { fetchServerToken } from './auth.js';
|
|
4
4
|
export class RedseatClient {
|
|
5
5
|
/**
|
|
6
|
-
* Creates a typed observable for a specific SSE event type
|
|
6
|
+
* Creates a typed observable for a specific SSE event type.
|
|
7
|
+
* Unwraps the nested data structure from the server (e.g., {uploadProgress: {...}} -> {...})
|
|
7
8
|
*/
|
|
8
9
|
createEventStream(eventName) {
|
|
9
|
-
|
|
10
|
+
// Map event names to their wrapper property names (snake_case -> camelCase)
|
|
11
|
+
const wrapperMap = {
|
|
12
|
+
'medias': 'medias',
|
|
13
|
+
'upload_progress': 'uploadProgress',
|
|
14
|
+
'convert_progress': 'convertProgress',
|
|
15
|
+
'media_progress': 'mediaProgress',
|
|
16
|
+
'media_rating': 'mediaRating',
|
|
17
|
+
'library-status': 'libraryStatus',
|
|
18
|
+
'backups-files': 'backupsFiles',
|
|
19
|
+
'players-list': 'Players',
|
|
20
|
+
};
|
|
21
|
+
const wrapperKey = wrapperMap[eventName];
|
|
22
|
+
return this._sseEvents.pipe(filter((event) => event.event === eventName), map(event => {
|
|
23
|
+
//console.log("EVENT", event)
|
|
24
|
+
// If there's a wrapper, unwrap it; otherwise return data as-is
|
|
25
|
+
const data = event.data;
|
|
26
|
+
if (wrapperKey && data && typeof data === 'object' && wrapperKey in data) {
|
|
27
|
+
return data[wrapperKey];
|
|
28
|
+
}
|
|
29
|
+
return event.data;
|
|
30
|
+
}));
|
|
10
31
|
}
|
|
11
32
|
constructor(options) {
|
|
12
33
|
this.sseReconnectAttempts = 0;
|
|
@@ -22,7 +43,7 @@ export class RedseatClient {
|
|
|
22
43
|
this.library$ = this.createEventStream('library');
|
|
23
44
|
this.libraryStatus$ = this.createEventStream('library-status');
|
|
24
45
|
this.medias$ = this.createEventStream('medias');
|
|
25
|
-
this.
|
|
46
|
+
this.uploadProgress$ = this.createEventStream('upload_progress');
|
|
26
47
|
this.convertProgress$ = this.createEventStream('convert_progress');
|
|
27
48
|
this.episodes$ = this.createEventStream('episodes');
|
|
28
49
|
this.series$ = this.createEventStream('series');
|
|
@@ -31,6 +52,9 @@ export class RedseatClient {
|
|
|
31
52
|
this.tags$ = this.createEventStream('tags');
|
|
32
53
|
this.backups$ = this.createEventStream('backups');
|
|
33
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');
|
|
34
58
|
this.server = options.server;
|
|
35
59
|
this.redseatUrl = options.redseatUrl;
|
|
36
60
|
this.getIdToken = options.getIdToken;
|
|
@@ -303,6 +327,7 @@ export class RedseatClient {
|
|
|
303
327
|
}
|
|
304
328
|
const url = this.buildSSEUrl();
|
|
305
329
|
this.sseAbortController = new AbortController();
|
|
330
|
+
console.log("SSSEEEE URL", url);
|
|
306
331
|
const response = await fetch(url, {
|
|
307
332
|
method: 'GET',
|
|
308
333
|
headers: {
|
|
@@ -323,8 +348,12 @@ export class RedseatClient {
|
|
|
323
348
|
}
|
|
324
349
|
this._sseConnectionState.next('connected');
|
|
325
350
|
this.sseReconnectAttempts = 0;
|
|
326
|
-
// Process the stream
|
|
327
|
-
|
|
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
|
+
});
|
|
328
357
|
}
|
|
329
358
|
catch (error) {
|
|
330
359
|
if (error instanceof Error && error.name === 'AbortError') {
|
|
@@ -366,6 +395,7 @@ export class RedseatClient {
|
|
|
366
395
|
const { events, remainingBuffer } = this.parseSSEBuffer(buffer);
|
|
367
396
|
buffer = remainingBuffer;
|
|
368
397
|
for (const event of events) {
|
|
398
|
+
//console.log("event process", JSON.stringify(event))
|
|
369
399
|
this._sseEvents.next(event);
|
|
370
400
|
}
|
|
371
401
|
}
|
package/dist/interfaces.d.ts
CHANGED
|
@@ -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;
|
package/dist/interfaces.js
CHANGED
|
@@ -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,
|
|
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;
|
|
@@ -77,7 +77,7 @@ export interface LibraryHttpClient {
|
|
|
77
77
|
getFullUrl(path: string, params?: Record<string, string>): string;
|
|
78
78
|
getAuthToken(): string;
|
|
79
79
|
readonly medias$?: Observable<SSEMediasEvent>;
|
|
80
|
-
readonly
|
|
80
|
+
readonly uploadProgress$?: Observable<SSEUploadProgressEvent>;
|
|
81
81
|
readonly convertProgress$?: Observable<SSEConvertProgressEvent>;
|
|
82
82
|
readonly episodes$?: Observable<SSEEpisodesEvent>;
|
|
83
83
|
readonly series$?: Observable<SSESeriesEvent>;
|
|
@@ -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;
|
|
@@ -94,7 +96,7 @@ export declare class LibraryApi {
|
|
|
94
96
|
private keyText?;
|
|
95
97
|
private disposed;
|
|
96
98
|
readonly medias$: Observable<SSEMediasEvent>;
|
|
97
|
-
readonly
|
|
99
|
+
readonly uploadProgress$: Observable<SSEUploadProgressEvent>;
|
|
98
100
|
readonly convertProgress$: Observable<SSEConvertProgressEvent>;
|
|
99
101
|
readonly episodes$: Observable<SSEEpisodesEvent>;
|
|
100
102
|
readonly series$: Observable<SSESeriesEvent>;
|
|
@@ -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
|
@@ -9,7 +9,7 @@ export class LibraryApi {
|
|
|
9
9
|
this.library = library;
|
|
10
10
|
// Create library-filtered streams
|
|
11
11
|
this.medias$ = this.createLibraryFilteredStream(client.medias$);
|
|
12
|
-
this.
|
|
12
|
+
this.uploadProgress$ = this.createLibraryFilteredStream(client.uploadProgress$);
|
|
13
13
|
this.convertProgress$ = this.createLibraryFilteredStream(client.convertProgress$);
|
|
14
14
|
this.episodes$ = this.createLibraryFilteredStream(client.episodes$);
|
|
15
15
|
this.series$ = this.createLibraryFilteredStream(client.series$);
|
|
@@ -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<
|
|
16
|
-
getCredentials(): Promise<
|
|
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;
|
package/dist/sse-types.d.ts
CHANGED
|
@@ -35,11 +35,20 @@ export interface SSEMediasEvent {
|
|
|
35
35
|
media: IFile;
|
|
36
36
|
}[];
|
|
37
37
|
}
|
|
38
|
-
export
|
|
38
|
+
export type RsProgressType = 'download' | 'transfert' | 'analysing' | 'finished' | {
|
|
39
|
+
duplicate: string;
|
|
40
|
+
};
|
|
41
|
+
export interface RsProgress {
|
|
42
|
+
id: string;
|
|
43
|
+
total?: number;
|
|
44
|
+
current?: number;
|
|
45
|
+
filename?: string;
|
|
46
|
+
type: RsProgressType;
|
|
47
|
+
}
|
|
48
|
+
export interface SSEUploadProgressEvent {
|
|
39
49
|
library: string;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
status?: string;
|
|
50
|
+
progress: RsProgress;
|
|
51
|
+
remainingSecondes?: number;
|
|
43
52
|
}
|
|
44
53
|
export interface SSEConvertProgressEvent {
|
|
45
54
|
library: string;
|
|
@@ -98,11 +107,38 @@ export interface SSEBackupFilesEvent {
|
|
|
98
107
|
status?: string;
|
|
99
108
|
message?: string;
|
|
100
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
|
+
}
|
|
101
137
|
export interface SSEEventMap {
|
|
102
138
|
'library': SSELibraryEvent;
|
|
103
139
|
'library-status': SSELibraryStatusEvent;
|
|
104
140
|
'medias': SSEMediasEvent;
|
|
105
|
-
'
|
|
141
|
+
'upload_progress': SSEUploadProgressEvent;
|
|
106
142
|
'convert_progress': SSEConvertProgressEvent;
|
|
107
143
|
'episodes': SSEEpisodesEvent;
|
|
108
144
|
'series': SSESeriesEvent;
|
|
@@ -111,6 +147,9 @@ export interface SSEEventMap {
|
|
|
111
147
|
'tags': SSETagsEvent;
|
|
112
148
|
'backups': SSEBackupsEvent;
|
|
113
149
|
'backups-files': SSEBackupFilesEvent;
|
|
150
|
+
'media_rating': SSEMediaRatingEvent;
|
|
151
|
+
'media_progress': SSEMediaProgressEvent;
|
|
152
|
+
'players-list': SSEPlayersListEvent;
|
|
114
153
|
}
|
|
115
154
|
export type SSEEventName = keyof SSEEventMap;
|
|
116
155
|
export interface SSEEvent<T extends SSEEventName = SSEEventName> {
|
package/libraries.md
CHANGED
|
@@ -98,13 +98,15 @@ libraryApi.medias$.subscribe(event => {
|
|
|
98
98
|
});
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
#### `
|
|
101
|
+
#### `uploadProgress$: Observable<SSEUploadProgressEvent>`
|
|
102
102
|
|
|
103
|
-
Emits
|
|
103
|
+
Emits upload progress updates for this library, including download, transfer, and analysis stages.
|
|
104
104
|
|
|
105
105
|
```typescript
|
|
106
|
-
libraryApi.
|
|
107
|
-
|
|
106
|
+
libraryApi.uploadProgress$.subscribe(event => {
|
|
107
|
+
const { progress } = event;
|
|
108
|
+
const percent = progress.total ? Math.round((progress.current ?? 0) / progress.total * 100) : 0;
|
|
109
|
+
console.log(`Upload ${progress.id} (${progress.type}): ${percent}% - ${progress.filename}`);
|
|
108
110
|
});
|
|
109
111
|
```
|
|
110
112
|
|
|
@@ -188,6 +190,40 @@ libraryApi.libraryStatus$.subscribe(event => {
|
|
|
188
190
|
});
|
|
189
191
|
```
|
|
190
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
|
+
|
|
191
227
|
### Complete SSE Example with LibraryApi
|
|
192
228
|
|
|
193
229
|
```typescript
|
package/package.json
CHANGED
package/server.md
CHANGED
|
@@ -109,17 +109,98 @@ plugins.forEach(plugin => {
|
|
|
109
109
|
});
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
### `getCredentials(): Promise<
|
|
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
|
|
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
|
|