@redseat/api 0.3.12 → 0.4.0
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/CLAUDE.md +275 -2
- package/client.md +9 -2
- package/dist/client.d.ts +29 -2
- package/dist/client.js +257 -26
- package/dist/interfaces.d.ts +166 -8
- package/dist/interfaces.js +1 -0
- package/dist/library.d.ts +85 -63
- package/dist/library.js +205 -11
- package/dist/server.d.ts +5 -2
- package/dist/server.js +53 -3
- package/dist/sse-types.d.ts +24 -4
- package/libraries.md +176 -9
- package/package.json +1 -1
- package/server.md +60 -9
package/dist/interfaces.js
CHANGED
|
@@ -14,6 +14,7 @@ export var LibraryTypes;
|
|
|
14
14
|
LibraryTypes["Shows"] = "shows";
|
|
15
15
|
LibraryTypes["Movies"] = "movies";
|
|
16
16
|
LibraryTypes["IPTV"] = "iptv";
|
|
17
|
+
LibraryTypes["Books"] = "books";
|
|
17
18
|
})(LibraryTypes || (LibraryTypes = {}));
|
|
18
19
|
export var LibrarySources;
|
|
19
20
|
(function (LibrarySources) {
|
package/dist/library.d.ts
CHANGED
|
@@ -1,61 +1,8 @@
|
|
|
1
1
|
import { Observable } from 'rxjs';
|
|
2
2
|
import type { AxiosResponse } from 'axios';
|
|
3
|
-
import { IFile, ITag, IPerson, ISerie, IMovie, MediaRequest, IEpisode, ExternalImage, IBackupFile, ILibrary,
|
|
4
|
-
import { SSEMediasEvent, SSEUploadProgressEvent, SSEConvertProgressEvent, SSEEpisodesEvent, SSESeriesEvent, SSEMoviesEvent, SSEPeopleEvent, SSETagsEvent, SSELibraryStatusEvent, SSEMediaRatingEvent, SSEMediaProgressEvent, SSERequestProcessingEvent } from './sse-types.js';
|
|
3
|
+
import { IFile, ITag, IPerson, ISerie, IMovie, IBook, MediaRequest, IEpisode, ExternalImage, IBackupFile, ILibrary, DeletedQuery, RsDeleted, MovieSort, BookSort, RsSort, SqlOrder, RsRequest, DetectedFaceResult, UnassignFaceResponse, RsGroupDownload, IViewProgress, IWatched, IRsRequestProcessing, BookSearchStreamResult, MovieSearchStreamResult, SerieSearchStreamResult, BookSearchResult, MovieSearchResult, SerieSearchResult, ItemWithRelations, SearchStreamCallbacks, MediaForUpdate, IChannelUpdate } from './interfaces.js';
|
|
4
|
+
import { SSEMediasEvent, SSEUploadProgressEvent, SSEConvertProgressEvent, SSEEpisodesEvent, SSESeriesEvent, SSEMoviesEvent, SSEBooksEvent, SSEPeopleEvent, SSETagsEvent, SSELibraryStatusEvent, SSEMediaRatingEvent, SSEMediaProgressEvent, SSERequestProcessingEvent } from './sse-types.js';
|
|
5
5
|
import { EncryptFileOptions, EncryptedFile } from './encryption.js';
|
|
6
|
-
export interface MediaForUpdate {
|
|
7
|
-
name?: string;
|
|
8
|
-
description?: string;
|
|
9
|
-
mimetype?: string;
|
|
10
|
-
size?: number;
|
|
11
|
-
md5?: string;
|
|
12
|
-
modified?: number;
|
|
13
|
-
created?: number;
|
|
14
|
-
width?: number;
|
|
15
|
-
height?: number;
|
|
16
|
-
orientation?: number;
|
|
17
|
-
color_space?: string;
|
|
18
|
-
icc?: string;
|
|
19
|
-
mp?: number;
|
|
20
|
-
vcodecs?: string[];
|
|
21
|
-
acodecs?: string[];
|
|
22
|
-
fps?: number;
|
|
23
|
-
bitrate?: number;
|
|
24
|
-
focal?: number;
|
|
25
|
-
iso?: number;
|
|
26
|
-
model?: string;
|
|
27
|
-
sspeed?: string;
|
|
28
|
-
f_number?: number;
|
|
29
|
-
duration?: number;
|
|
30
|
-
progress?: number;
|
|
31
|
-
addTags?: {
|
|
32
|
-
id: string;
|
|
33
|
-
conf?: number;
|
|
34
|
-
}[];
|
|
35
|
-
removeTags?: string[];
|
|
36
|
-
tagsLookup?: string[];
|
|
37
|
-
addSeries?: SerieInMedia[];
|
|
38
|
-
removeSeries?: SerieInMedia[];
|
|
39
|
-
addPeople?: {
|
|
40
|
-
id: string;
|
|
41
|
-
conf?: number;
|
|
42
|
-
}[];
|
|
43
|
-
removePeople?: string[];
|
|
44
|
-
peopleLookup?: string[];
|
|
45
|
-
long?: number;
|
|
46
|
-
lat?: number;
|
|
47
|
-
gps?: string;
|
|
48
|
-
origin?: any;
|
|
49
|
-
originUrl?: string;
|
|
50
|
-
movie?: string;
|
|
51
|
-
lang?: string;
|
|
52
|
-
rating?: number;
|
|
53
|
-
thumbsize?: number;
|
|
54
|
-
iv?: string;
|
|
55
|
-
uploader?: string;
|
|
56
|
-
uploadkey?: string;
|
|
57
|
-
uploadId?: string;
|
|
58
|
-
}
|
|
59
6
|
export interface LibraryHttpClient {
|
|
60
7
|
get<T = unknown>(url: string, config?: any): Promise<{
|
|
61
8
|
data: T;
|
|
@@ -83,6 +30,7 @@ export interface LibraryHttpClient {
|
|
|
83
30
|
readonly episodes$?: Observable<SSEEpisodesEvent>;
|
|
84
31
|
readonly series$?: Observable<SSESeriesEvent>;
|
|
85
32
|
readonly movies$?: Observable<SSEMoviesEvent>;
|
|
33
|
+
readonly books$?: Observable<SSEBooksEvent>;
|
|
86
34
|
readonly people$?: Observable<SSEPeopleEvent>;
|
|
87
35
|
readonly tags$?: Observable<SSETagsEvent>;
|
|
88
36
|
readonly libraryStatus$?: Observable<SSELibraryStatusEvent>;
|
|
@@ -90,6 +38,12 @@ export interface LibraryHttpClient {
|
|
|
90
38
|
readonly mediaProgress$?: Observable<SSEMediaProgressEvent>;
|
|
91
39
|
readonly requestProcessing$?: Observable<SSERequestProcessingEvent>;
|
|
92
40
|
}
|
|
41
|
+
export interface UploadMediaMultipartOptions {
|
|
42
|
+
file: Blob;
|
|
43
|
+
info?: MediaForUpdate;
|
|
44
|
+
filename?: string;
|
|
45
|
+
progressCallback?: (loaded: number, total: number) => void;
|
|
46
|
+
}
|
|
93
47
|
export declare class LibraryApi {
|
|
94
48
|
private client;
|
|
95
49
|
private libraryId;
|
|
@@ -103,6 +57,7 @@ export declare class LibraryApi {
|
|
|
103
57
|
readonly episodes$: Observable<SSEEpisodesEvent>;
|
|
104
58
|
readonly series$: Observable<SSESeriesEvent>;
|
|
105
59
|
readonly movies$: Observable<SSEMoviesEvent>;
|
|
60
|
+
readonly books$: Observable<SSEBooksEvent>;
|
|
106
61
|
readonly people$: Observable<SSEPeopleEvent>;
|
|
107
62
|
readonly tags$: Observable<SSETagsEvent>;
|
|
108
63
|
readonly libraryStatus$: Observable<SSELibraryStatusEvent>;
|
|
@@ -122,6 +77,7 @@ export declare class LibraryApi {
|
|
|
122
77
|
dispose(): void;
|
|
123
78
|
setKey(passPhrase: string): Promise<void>;
|
|
124
79
|
private getUrl;
|
|
80
|
+
private openSearchStream;
|
|
125
81
|
getTags(query?: {
|
|
126
82
|
name?: string;
|
|
127
83
|
parent?: string;
|
|
@@ -152,6 +108,7 @@ export declare class LibraryApi {
|
|
|
152
108
|
}[];
|
|
153
109
|
limit?: number;
|
|
154
110
|
}): Promise<IEpisode[]>;
|
|
111
|
+
getSerieBooks(serieId: string): Promise<IBook[]>;
|
|
155
112
|
getMovies(query?: {
|
|
156
113
|
after?: number;
|
|
157
114
|
inDigital?: boolean;
|
|
@@ -200,10 +157,32 @@ export declare class LibraryApi {
|
|
|
200
157
|
getMovieWatched(movieId: string): Promise<IWatched>;
|
|
201
158
|
getMovieProgress(movieId: string): Promise<IViewProgress>;
|
|
202
159
|
setMovieProgress(movieId: string, progress: number): Promise<void>;
|
|
203
|
-
searchMovies(name: string): Promise<
|
|
160
|
+
searchMovies(name: string): Promise<MovieSearchResult[]>;
|
|
161
|
+
searchMoviesStream(name: string, callbacks: SearchStreamCallbacks<MovieSearchStreamResult>): () => void;
|
|
204
162
|
movieRename(movieId: string, newName: string): Promise<IMovie>;
|
|
205
163
|
updateMoviePoster(movieId: string, poster: FormData, type: string): Promise<void>;
|
|
206
164
|
updateMovieImageFetch(movieId: string, image: ExternalImage): Promise<void>;
|
|
165
|
+
getBooks(query?: {
|
|
166
|
+
after?: number;
|
|
167
|
+
sort?: BookSort;
|
|
168
|
+
}): Promise<ItemWithRelations<IBook>[]>;
|
|
169
|
+
getBook(bookId: string): Promise<ItemWithRelations<IBook>>;
|
|
170
|
+
getBookMedias(bookId: string): Promise<IFile[]>;
|
|
171
|
+
searchBookMedias(bookId: string, q?: string): Promise<RsGroupDownload[]>;
|
|
172
|
+
addSearchedBookMedia(bookId: string, request: RsRequest): Promise<IFile>;
|
|
173
|
+
createBook(book: Partial<IBook>, options?: {
|
|
174
|
+
upsertTags?: boolean;
|
|
175
|
+
upsertPeople?: boolean;
|
|
176
|
+
upsertSerie?: boolean;
|
|
177
|
+
}): Promise<IBook>;
|
|
178
|
+
removeBook(bookId: string): Promise<void>;
|
|
179
|
+
updateBook(bookId: string, updates: Partial<IBook>): Promise<IBook>;
|
|
180
|
+
searchBooks(name: string): Promise<BookSearchResult[]>;
|
|
181
|
+
searchBooksStream(name: string, callbacks: SearchStreamCallbacks<BookSearchStreamResult>): () => void;
|
|
182
|
+
bookRename(bookId: string, newName: string): Promise<IBook>;
|
|
183
|
+
getBookImages(bookId: string): Promise<ExternalImage[]>;
|
|
184
|
+
updateBookPoster(bookId: string, poster: FormData): Promise<void>;
|
|
185
|
+
updateBookImageFetch(bookId: string, image: ExternalImage): Promise<void>;
|
|
207
186
|
addTagToMedia(mediaId: string, tagId: string): Promise<void>;
|
|
208
187
|
removeTagFromMedia(mediaId: string, tagId: string): Promise<void>;
|
|
209
188
|
getCryptChallenge(): Promise<string>;
|
|
@@ -218,7 +197,8 @@ export declare class LibraryApi {
|
|
|
218
197
|
serieAddAlt(serieId: string, alt: string): Promise<ISerie>;
|
|
219
198
|
serieRemoveAlt(serieId: string, alt: string): Promise<ISerie>;
|
|
220
199
|
updatePersonPortrait(personId: string, portrait: FormData): Promise<void>;
|
|
221
|
-
searchSeries(name: string): Promise<
|
|
200
|
+
searchSeries(name: string): Promise<SerieSearchResult[]>;
|
|
201
|
+
searchSeriesStream(name: string, callbacks: SearchStreamCallbacks<SerieSearchStreamResult>): () => void;
|
|
222
202
|
setEpisodeWatched(serieId: string, season: number, number: number, date: number): Promise<void>;
|
|
223
203
|
getEpisodeWatched(serieId: string, season: number, episode: number): Promise<IWatched>;
|
|
224
204
|
getEpisodeProgress(serieId: string, season: number, episode: number): Promise<IViewProgress>;
|
|
@@ -228,9 +208,25 @@ export declare class LibraryApi {
|
|
|
228
208
|
* @param serieId - The series identifier
|
|
229
209
|
* @param season - The season number
|
|
230
210
|
* @param episode - The episode number
|
|
231
|
-
* @
|
|
211
|
+
* @param q - Optional search query to filter results
|
|
212
|
+
* @returns Array of grouped download requests. Use `.flatMap(g => g.requests)` to get a flat list of RsRequest items.
|
|
232
213
|
*/
|
|
233
|
-
searchEpisodeMedias(serieId: string, season: number, episode: number): Promise<
|
|
214
|
+
searchEpisodeMedias(serieId: string, season: number, episode: number, q?: string): Promise<RsGroupDownload[]>;
|
|
215
|
+
/**
|
|
216
|
+
* Searches for available media sources for an entire season.
|
|
217
|
+
* @param serieId - The series identifier
|
|
218
|
+
* @param season - The season number
|
|
219
|
+
* @param q - Optional search query to filter results
|
|
220
|
+
* @returns Array of grouped download requests. Use `.flatMap(g => g.requests)` to get a flat list of RsRequest items.
|
|
221
|
+
*/
|
|
222
|
+
searchSeasonMedias(serieId: string, season: number, q?: string): Promise<RsGroupDownload[]>;
|
|
223
|
+
/**
|
|
224
|
+
* Searches for available media sources for a movie.
|
|
225
|
+
* @param movieId - The movie identifier
|
|
226
|
+
* @param q - Optional search query to filter results
|
|
227
|
+
* @returns Array of grouped download requests. Use `.flatMap(g => g.requests)` to get a flat list of RsRequest items.
|
|
228
|
+
*/
|
|
229
|
+
searchMovieMedias(movieId: string, q?: string): Promise<RsGroupDownload[]>;
|
|
234
230
|
/**
|
|
235
231
|
* Adds a searched episode media to the database for download/storage.
|
|
236
232
|
* @param serieId - The series identifier
|
|
@@ -240,6 +236,27 @@ export declare class LibraryApi {
|
|
|
240
236
|
* @returns The created media file entry
|
|
241
237
|
*/
|
|
242
238
|
addSearchedEpisodeMedia(serieId: string, season: number, episode: number, request: RsRequest): Promise<IFile>;
|
|
239
|
+
/**
|
|
240
|
+
* Adds a searched movie media to the database for download/storage.
|
|
241
|
+
* @param movieId - The movie identifier
|
|
242
|
+
* @param request - The media request to add (typically from searchMovieMedias results)
|
|
243
|
+
* @returns The created media file entry
|
|
244
|
+
*/
|
|
245
|
+
addSearchedMovieMedia(movieId: string, request: RsRequest): Promise<IFile>;
|
|
246
|
+
/**
|
|
247
|
+
* Gets the media files attached to a specific episode.
|
|
248
|
+
* @param serieId - The series identifier
|
|
249
|
+
* @param season - The season number
|
|
250
|
+
* @param episode - The episode number
|
|
251
|
+
* @returns Array of media files for the episode
|
|
252
|
+
*/
|
|
253
|
+
getEpisodeMedias(serieId: string, season: number, episode: number): Promise<IFile[]>;
|
|
254
|
+
/**
|
|
255
|
+
* Gets the media files attached to a specific movie.
|
|
256
|
+
* @param movieId - The movie identifier
|
|
257
|
+
* @returns Array of media files for the movie
|
|
258
|
+
*/
|
|
259
|
+
getMovieMedias(movieId: string): Promise<IFile[]>;
|
|
243
260
|
/**
|
|
244
261
|
* Checks if a request can be made permanent (saved for later use).
|
|
245
262
|
* Tests the availability of the URL and returns a permanent version if valid.
|
|
@@ -341,14 +358,14 @@ export declare class LibraryApi {
|
|
|
341
358
|
mergePeople(request: any): Promise<any>;
|
|
342
359
|
clusterFaces(personId: string): Promise<any>;
|
|
343
360
|
mergeMedias(request: any): Promise<IFile>;
|
|
344
|
-
mediaUpdateMany(update:
|
|
361
|
+
mediaUpdateMany(update: MediaForUpdate, ids: string[]): Promise<IFile[]>;
|
|
345
362
|
mediaUpdateProgress(mediaId: string, progress: number): Promise<{
|
|
346
363
|
progress: number;
|
|
347
364
|
}>;
|
|
348
|
-
mediaUpdateChannel(mediaId: string, update:
|
|
365
|
+
mediaUpdateChannel(mediaId: string, update: IChannelUpdate): Promise<IFile[]>;
|
|
349
366
|
refreshMedia(mediaId: string): Promise<IFile[]>;
|
|
350
367
|
aiTagMedia(mediaId: string): Promise<IFile[]>;
|
|
351
|
-
mediaUpdate(mediaId: string, update:
|
|
368
|
+
mediaUpdate(mediaId: string, update: MediaForUpdate): Promise<IFile[]>;
|
|
352
369
|
splitZip(mediaId: string, from: number, to: number): Promise<IFile>;
|
|
353
370
|
deleteFromZip(mediaId: string, pages: number[]): Promise<IFile>;
|
|
354
371
|
updateMediaThumb(mediaId: string, thumb: FormData): Promise<void>;
|
|
@@ -415,11 +432,16 @@ export declare class LibraryApi {
|
|
|
415
432
|
*/
|
|
416
433
|
uploadMedia(data: ArrayBuffer | Uint8Array, options: {
|
|
417
434
|
thumb?: ArrayBuffer | Uint8Array;
|
|
418
|
-
metadata:
|
|
435
|
+
metadata: MediaForUpdate;
|
|
419
436
|
fileMime: string;
|
|
420
437
|
thumbMime?: string;
|
|
421
438
|
progressCallback?: (loaded: number, total: number) => void;
|
|
422
439
|
}): Promise<IFile>;
|
|
440
|
+
/**
|
|
441
|
+
* Upload media using raw multipart form data.
|
|
442
|
+
* `info` is optional and will be serialized as the `info` multipart field when provided.
|
|
443
|
+
*/
|
|
444
|
+
uploadMediaMultipart(options: UploadMediaMultipartOptions): Promise<unknown>;
|
|
423
445
|
/**
|
|
424
446
|
* Upload a group of media files from URLs
|
|
425
447
|
* @param download - The group download request containing requests array
|
package/dist/library.js
CHANGED
|
@@ -14,6 +14,7 @@ export class LibraryApi {
|
|
|
14
14
|
this.episodes$ = this.createLibraryFilteredStream(client.episodes$);
|
|
15
15
|
this.series$ = this.createLibraryFilteredStream(client.series$);
|
|
16
16
|
this.movies$ = this.createLibraryFilteredStream(client.movies$);
|
|
17
|
+
this.books$ = this.createLibraryFilteredStream(client.books$);
|
|
17
18
|
this.people$ = this.createLibraryFilteredStream(client.people$);
|
|
18
19
|
this.tags$ = this.createLibraryFilteredStream(client.tags$);
|
|
19
20
|
this.libraryStatus$ = this.createLibraryFilteredStream(client.libraryStatus$);
|
|
@@ -95,6 +96,50 @@ export class LibraryApi {
|
|
|
95
96
|
getUrl(path) {
|
|
96
97
|
return `/libraries/${this.libraryId}${path}`;
|
|
97
98
|
}
|
|
99
|
+
openSearchStream(path, params, callbacks) {
|
|
100
|
+
const EventSourceCtor = globalThis.EventSource;
|
|
101
|
+
if (!EventSourceCtor) {
|
|
102
|
+
callbacks.onError?.(new Error('EventSource is not available in this runtime.'));
|
|
103
|
+
return () => undefined;
|
|
104
|
+
}
|
|
105
|
+
const url = this.client.getFullUrl(path, {
|
|
106
|
+
...params,
|
|
107
|
+
token: this.client.getAuthToken()
|
|
108
|
+
});
|
|
109
|
+
const source = new EventSourceCtor(url);
|
|
110
|
+
let closed = false;
|
|
111
|
+
const close = () => {
|
|
112
|
+
if (closed) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
closed = true;
|
|
116
|
+
source.removeEventListener('results', onResults);
|
|
117
|
+
source.removeEventListener('finished', onFinished);
|
|
118
|
+
source.onerror = null;
|
|
119
|
+
source.close();
|
|
120
|
+
};
|
|
121
|
+
const onResults = (event) => {
|
|
122
|
+
try {
|
|
123
|
+
const payload = JSON.parse(event.data);
|
|
124
|
+
callbacks.onResults?.(payload);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
callbacks.onError?.(error);
|
|
128
|
+
close();
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const onFinished = () => {
|
|
132
|
+
callbacks.onFinished?.();
|
|
133
|
+
close();
|
|
134
|
+
};
|
|
135
|
+
source.addEventListener('results', onResults);
|
|
136
|
+
source.addEventListener('finished', onFinished);
|
|
137
|
+
source.onerror = (error) => {
|
|
138
|
+
callbacks.onError?.(error);
|
|
139
|
+
close();
|
|
140
|
+
};
|
|
141
|
+
return close;
|
|
142
|
+
}
|
|
98
143
|
async getTags(query) {
|
|
99
144
|
const params = {};
|
|
100
145
|
if (query) {
|
|
@@ -181,6 +226,10 @@ export class LibraryApi {
|
|
|
181
226
|
const res = await this.client.get(this.getUrl('/series/episodes'), { params });
|
|
182
227
|
return res.data;
|
|
183
228
|
}
|
|
229
|
+
async getSerieBooks(serieId) {
|
|
230
|
+
const res = await this.client.get(this.getUrl(`/series/${serieId}/books`));
|
|
231
|
+
return res.data;
|
|
232
|
+
}
|
|
184
233
|
async getMovies(query) {
|
|
185
234
|
const params = {};
|
|
186
235
|
if (query) {
|
|
@@ -335,7 +384,7 @@ export class LibraryApi {
|
|
|
335
384
|
return res.data;
|
|
336
385
|
}
|
|
337
386
|
async getSerieImages(serieId) {
|
|
338
|
-
const res = await this.client.get(this.getUrl(`/series/${serieId}/
|
|
387
|
+
const res = await this.client.get(this.getUrl(`/series/${serieId}/image/search`));
|
|
339
388
|
return res.data;
|
|
340
389
|
}
|
|
341
390
|
async updateSeriePoster(serieId, poster, type) {
|
|
@@ -363,7 +412,7 @@ export class LibraryApi {
|
|
|
363
412
|
return res.data;
|
|
364
413
|
}
|
|
365
414
|
async getMovieImages(movieId) {
|
|
366
|
-
const res = await this.client.get(this.getUrl(`/movies/${movieId}/
|
|
415
|
+
const res = await this.client.get(this.getUrl(`/movies/${movieId}/image/search`));
|
|
367
416
|
return res.data;
|
|
368
417
|
}
|
|
369
418
|
async setMovieWatched(movieId, date) {
|
|
@@ -384,6 +433,9 @@ export class LibraryApi {
|
|
|
384
433
|
const res = await this.client.get(this.getUrl(`/movies/search?name=${name}`));
|
|
385
434
|
return res.data;
|
|
386
435
|
}
|
|
436
|
+
searchMoviesStream(name, callbacks) {
|
|
437
|
+
return this.openSearchStream(this.getUrl('/movies/searchstream'), { name }, callbacks);
|
|
438
|
+
}
|
|
387
439
|
async movieRename(movieId, newName) {
|
|
388
440
|
const res = await this.client.patch(this.getUrl(`/movies/${movieId}`), {
|
|
389
441
|
name: newName
|
|
@@ -396,6 +448,78 @@ export class LibraryApi {
|
|
|
396
448
|
async updateMovieImageFetch(movieId, image) {
|
|
397
449
|
await this.client.post(this.getUrl(`/movies/${movieId}/image/fetch`), image);
|
|
398
450
|
}
|
|
451
|
+
// ==================== Books ====================
|
|
452
|
+
async getBooks(query) {
|
|
453
|
+
const params = {};
|
|
454
|
+
if (query) {
|
|
455
|
+
if (query.after !== undefined) {
|
|
456
|
+
params.after = query.after;
|
|
457
|
+
}
|
|
458
|
+
if (query.sort !== undefined) {
|
|
459
|
+
params.sort = query.sort;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const res = await this.client.get(this.getUrl('/books'), { params });
|
|
463
|
+
return res.data;
|
|
464
|
+
}
|
|
465
|
+
async getBook(bookId) {
|
|
466
|
+
const res = await this.client.get(this.getUrl(`/books/${bookId}`));
|
|
467
|
+
return res.data;
|
|
468
|
+
}
|
|
469
|
+
async getBookMedias(bookId) {
|
|
470
|
+
const res = await this.client.get(this.getUrl(`/books/${bookId}/medias`));
|
|
471
|
+
return res.data;
|
|
472
|
+
}
|
|
473
|
+
async searchBookMedias(bookId, q) {
|
|
474
|
+
const res = await this.client.get(this.getUrl(`/books/${bookId}/search`), q ? { params: { q } } : undefined);
|
|
475
|
+
return res.data;
|
|
476
|
+
}
|
|
477
|
+
async addSearchedBookMedia(bookId, request) {
|
|
478
|
+
const res = await this.client.post(this.getUrl(`/books/${bookId}/search`), request);
|
|
479
|
+
return res.data;
|
|
480
|
+
}
|
|
481
|
+
async createBook(book, options) {
|
|
482
|
+
const params = new URLSearchParams();
|
|
483
|
+
if (options?.upsertTags)
|
|
484
|
+
params.set('upsertTags', 'true');
|
|
485
|
+
if (options?.upsertPeople)
|
|
486
|
+
params.set('upsertPeople', 'true');
|
|
487
|
+
if (options?.upsertSerie)
|
|
488
|
+
params.set('upsertSerie', 'true');
|
|
489
|
+
const query = params.toString();
|
|
490
|
+
const res = await this.client.post(this.getUrl(`/books${query ? `?${query}` : ''}`), book);
|
|
491
|
+
return res.data;
|
|
492
|
+
}
|
|
493
|
+
async removeBook(bookId) {
|
|
494
|
+
await this.client.delete(this.getUrl(`/books/${bookId}`));
|
|
495
|
+
}
|
|
496
|
+
async updateBook(bookId, updates) {
|
|
497
|
+
const res = await this.client.patch(this.getUrl(`/books/${bookId}`), updates);
|
|
498
|
+
return res.data;
|
|
499
|
+
}
|
|
500
|
+
async searchBooks(name) {
|
|
501
|
+
const res = await this.client.get(this.getUrl(`/books/search?name=${name}`));
|
|
502
|
+
return res.data;
|
|
503
|
+
}
|
|
504
|
+
searchBooksStream(name, callbacks) {
|
|
505
|
+
return this.openSearchStream(this.getUrl('/books/searchstream'), { name }, callbacks);
|
|
506
|
+
}
|
|
507
|
+
async bookRename(bookId, newName) {
|
|
508
|
+
const res = await this.client.patch(this.getUrl(`/books/${bookId}`), {
|
|
509
|
+
name: newName
|
|
510
|
+
});
|
|
511
|
+
return res.data;
|
|
512
|
+
}
|
|
513
|
+
async getBookImages(bookId) {
|
|
514
|
+
const res = await this.client.get(this.getUrl(`/books/${bookId}/image/search`));
|
|
515
|
+
return res.data;
|
|
516
|
+
}
|
|
517
|
+
async updateBookPoster(bookId, poster) {
|
|
518
|
+
await this.client.post(this.getUrl(`/books/${bookId}/image`), poster);
|
|
519
|
+
}
|
|
520
|
+
async updateBookImageFetch(bookId, image) {
|
|
521
|
+
await this.client.post(this.getUrl(`/books/${bookId}/image/fetch`), image);
|
|
522
|
+
}
|
|
399
523
|
async addTagToMedia(mediaId, tagId) {
|
|
400
524
|
await this.client.post(this.getUrl(`/medias/${mediaId}/tags/${tagId}`), {});
|
|
401
525
|
}
|
|
@@ -469,6 +593,9 @@ export class LibraryApi {
|
|
|
469
593
|
const res = await this.client.get(this.getUrl(`/series/search?name=${name}`));
|
|
470
594
|
return res.data;
|
|
471
595
|
}
|
|
596
|
+
searchSeriesStream(name, callbacks) {
|
|
597
|
+
return this.openSearchStream(this.getUrl('/series/searchstream'), { name }, callbacks);
|
|
598
|
+
}
|
|
472
599
|
async setEpisodeWatched(serieId, season, number, date) {
|
|
473
600
|
await this.client.post(this.getUrl(`/series/${serieId}/seasons/${season}/episodes/${number}/watched`), { date });
|
|
474
601
|
}
|
|
@@ -488,10 +615,32 @@ export class LibraryApi {
|
|
|
488
615
|
* @param serieId - The series identifier
|
|
489
616
|
* @param season - The season number
|
|
490
617
|
* @param episode - The episode number
|
|
491
|
-
* @
|
|
618
|
+
* @param q - Optional search query to filter results
|
|
619
|
+
* @returns Array of grouped download requests. Use `.flatMap(g => g.requests)` to get a flat list of RsRequest items.
|
|
620
|
+
*/
|
|
621
|
+
async searchEpisodeMedias(serieId, season, episode, q) {
|
|
622
|
+
const res = await this.client.get(this.getUrl(`/series/${serieId}/seasons/${season}/episodes/${episode}/search`), q ? { params: { q } } : undefined);
|
|
623
|
+
return res.data;
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Searches for available media sources for an entire season.
|
|
627
|
+
* @param serieId - The series identifier
|
|
628
|
+
* @param season - The season number
|
|
629
|
+
* @param q - Optional search query to filter results
|
|
630
|
+
* @returns Array of grouped download requests. Use `.flatMap(g => g.requests)` to get a flat list of RsRequest items.
|
|
631
|
+
*/
|
|
632
|
+
async searchSeasonMedias(serieId, season, q) {
|
|
633
|
+
const res = await this.client.get(this.getUrl(`/series/${serieId}/seasons/${season}/search`), q ? { params: { q } } : undefined);
|
|
634
|
+
return res.data;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Searches for available media sources for a movie.
|
|
638
|
+
* @param movieId - The movie identifier
|
|
639
|
+
* @param q - Optional search query to filter results
|
|
640
|
+
* @returns Array of grouped download requests. Use `.flatMap(g => g.requests)` to get a flat list of RsRequest items.
|
|
492
641
|
*/
|
|
493
|
-
async
|
|
494
|
-
const res = await this.client.get(this.getUrl(`/
|
|
642
|
+
async searchMovieMedias(movieId, q) {
|
|
643
|
+
const res = await this.client.get(this.getUrl(`/movies/${movieId}/search`), q ? { params: { q } } : undefined);
|
|
495
644
|
return res.data;
|
|
496
645
|
}
|
|
497
646
|
/**
|
|
@@ -506,6 +655,36 @@ export class LibraryApi {
|
|
|
506
655
|
const res = await this.client.post(this.getUrl(`/series/${serieId}/seasons/${season}/episodes/${episode}/search`), request);
|
|
507
656
|
return res.data;
|
|
508
657
|
}
|
|
658
|
+
/**
|
|
659
|
+
* Adds a searched movie media to the database for download/storage.
|
|
660
|
+
* @param movieId - The movie identifier
|
|
661
|
+
* @param request - The media request to add (typically from searchMovieMedias results)
|
|
662
|
+
* @returns The created media file entry
|
|
663
|
+
*/
|
|
664
|
+
async addSearchedMovieMedia(movieId, request) {
|
|
665
|
+
const res = await this.client.post(this.getUrl(`/movies/${movieId}/search`), request);
|
|
666
|
+
return res.data;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Gets the media files attached to a specific episode.
|
|
670
|
+
* @param serieId - The series identifier
|
|
671
|
+
* @param season - The season number
|
|
672
|
+
* @param episode - The episode number
|
|
673
|
+
* @returns Array of media files for the episode
|
|
674
|
+
*/
|
|
675
|
+
async getEpisodeMedias(serieId, season, episode) {
|
|
676
|
+
const res = await this.client.get(this.getUrl(`/series/${serieId}/seasons/${season}/episodes/${episode}/medias`));
|
|
677
|
+
return res.data;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Gets the media files attached to a specific movie.
|
|
681
|
+
* @param movieId - The movie identifier
|
|
682
|
+
* @returns Array of media files for the movie
|
|
683
|
+
*/
|
|
684
|
+
async getMovieMedias(movieId) {
|
|
685
|
+
const res = await this.client.get(this.getUrl(`/movies/${movieId}/medias`));
|
|
686
|
+
return res.data;
|
|
687
|
+
}
|
|
509
688
|
/**
|
|
510
689
|
* Checks if a request can be made permanent (saved for later use).
|
|
511
690
|
* Tests the availability of the URL and returns a permanent version if valid.
|
|
@@ -922,13 +1101,28 @@ export class LibraryApi {
|
|
|
922
1101
|
filename = options.metadata.name || 'file';
|
|
923
1102
|
fileBlob = new Blob([dataBuffer], { type: options.fileMime });
|
|
924
1103
|
}
|
|
925
|
-
|
|
1104
|
+
return (await this.uploadMediaMultipart({
|
|
1105
|
+
file: fileBlob,
|
|
1106
|
+
filename,
|
|
1107
|
+
info: { ...metadata, size: fileBlob.size },
|
|
1108
|
+
progressCallback: options.progressCallback
|
|
1109
|
+
}));
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Upload media using raw multipart form data.
|
|
1113
|
+
* `info` is optional and will be serialized as the `info` multipart field when provided.
|
|
1114
|
+
*/
|
|
1115
|
+
async uploadMediaMultipart(options) {
|
|
926
1116
|
const formData = new FormData();
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1117
|
+
if (options.info) {
|
|
1118
|
+
const infoPayload = { ...options.info };
|
|
1119
|
+
if (infoPayload.size === undefined) {
|
|
1120
|
+
infoPayload.size = options.file.size;
|
|
1121
|
+
}
|
|
1122
|
+
formData.append('info', JSON.stringify(infoPayload));
|
|
1123
|
+
}
|
|
1124
|
+
const filename = options.filename ?? (options.file.name ?? 'file');
|
|
1125
|
+
formData.append('file', options.file, filename);
|
|
932
1126
|
const config = {};
|
|
933
1127
|
if (options.progressCallback) {
|
|
934
1128
|
config.onUploadProgress = (progressEvent) => {
|
package/dist/server.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { RedseatClient } from './client.js';
|
|
2
|
-
import { ILibrary, IPlugin, ICredential, IWatched, IWatchedForAdd, IViewProgress, IViewProgressForAdd, HistoryQuery } from './interfaces.js';
|
|
2
|
+
import { ILibrary, IPlugin, ICredential, IWatched, IWatchedForAdd, IViewProgress, IViewProgressForAdd, HistoryQuery, ServerLibraryForUpdate, LookupSearchStreamResult, SearchStreamCallbacks } from './interfaces.js';
|
|
3
3
|
export declare class ServerApi {
|
|
4
4
|
private client;
|
|
5
5
|
constructor(client: RedseatClient);
|
|
6
|
+
private openSearchStream;
|
|
6
7
|
getLibraries(): Promise<ILibrary[]>;
|
|
7
8
|
ping(options?: {
|
|
8
9
|
timeout?: number;
|
|
@@ -11,8 +12,10 @@ export declare class ServerApi {
|
|
|
11
12
|
timeout?: number;
|
|
12
13
|
}): Promise<any>;
|
|
13
14
|
addLibrary(library: Partial<ILibrary>): Promise<ILibrary>;
|
|
14
|
-
|
|
15
|
+
updateLibrary(libraryId: string, library: ServerLibraryForUpdate): Promise<ILibrary>;
|
|
16
|
+
deleteLibrary(libraryId: string, deleteMediaContent?: boolean): Promise<void>;
|
|
15
17
|
getPlugins(): Promise<IPlugin[]>;
|
|
18
|
+
searchLookupStream(query: string, type: string, callbacks: SearchStreamCallbacks<LookupSearchStreamResult>): () => void;
|
|
16
19
|
getCredentials(): Promise<ICredential[]>;
|
|
17
20
|
saveCredential(credential: ICredential): Promise<ICredential>;
|
|
18
21
|
updateCredential(credential: ICredential): Promise<ICredential>;
|
package/dist/server.js
CHANGED
|
@@ -2,6 +2,50 @@ export class ServerApi {
|
|
|
2
2
|
constructor(client) {
|
|
3
3
|
this.client = client;
|
|
4
4
|
}
|
|
5
|
+
openSearchStream(path, params, callbacks) {
|
|
6
|
+
const EventSourceCtor = globalThis.EventSource;
|
|
7
|
+
if (!EventSourceCtor) {
|
|
8
|
+
callbacks.onError?.(new Error('EventSource is not available in this runtime.'));
|
|
9
|
+
return () => undefined;
|
|
10
|
+
}
|
|
11
|
+
const url = this.client.getFullUrl(path, {
|
|
12
|
+
...params,
|
|
13
|
+
token: this.client.getAuthToken()
|
|
14
|
+
});
|
|
15
|
+
const source = new EventSourceCtor(url);
|
|
16
|
+
let closed = false;
|
|
17
|
+
const close = () => {
|
|
18
|
+
if (closed) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
closed = true;
|
|
22
|
+
source.removeEventListener('results', onResults);
|
|
23
|
+
source.removeEventListener('finished', onFinished);
|
|
24
|
+
source.onerror = null;
|
|
25
|
+
source.close();
|
|
26
|
+
};
|
|
27
|
+
const onResults = (event) => {
|
|
28
|
+
try {
|
|
29
|
+
const payload = JSON.parse(event.data);
|
|
30
|
+
callbacks.onResults?.(payload);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
callbacks.onError?.(error);
|
|
34
|
+
close();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const onFinished = () => {
|
|
38
|
+
callbacks.onFinished?.();
|
|
39
|
+
close();
|
|
40
|
+
};
|
|
41
|
+
source.addEventListener('results', onResults);
|
|
42
|
+
source.addEventListener('finished', onFinished);
|
|
43
|
+
source.onerror = (error) => {
|
|
44
|
+
callbacks.onError?.(error);
|
|
45
|
+
close();
|
|
46
|
+
};
|
|
47
|
+
return close;
|
|
48
|
+
}
|
|
5
49
|
async getLibraries() {
|
|
6
50
|
const res = await this.client.get('/libraries');
|
|
7
51
|
return res.data;
|
|
@@ -18,14 +62,20 @@ export class ServerApi {
|
|
|
18
62
|
const res = await this.client.post('/libraries', library);
|
|
19
63
|
return res.data;
|
|
20
64
|
}
|
|
21
|
-
async
|
|
22
|
-
const res = await this.client.
|
|
23
|
-
return res.data
|
|
65
|
+
async updateLibrary(libraryId, library) {
|
|
66
|
+
const res = await this.client.patch(`/libraries/${libraryId}`, library);
|
|
67
|
+
return res.data;
|
|
68
|
+
}
|
|
69
|
+
async deleteLibrary(libraryId, deleteMediaContent = false) {
|
|
70
|
+
await this.client.delete(`/libraries/${libraryId}?delete_media_content=${deleteMediaContent}`);
|
|
24
71
|
}
|
|
25
72
|
async getPlugins() {
|
|
26
73
|
const res = await this.client.get('/plugins');
|
|
27
74
|
return res.data;
|
|
28
75
|
}
|
|
76
|
+
searchLookupStream(query, type, callbacks) {
|
|
77
|
+
return this.openSearchStream('/plugins/lookup/searchstream', { q: query, type }, callbacks);
|
|
78
|
+
}
|
|
29
79
|
async getCredentials() {
|
|
30
80
|
const res = await this.client.get('/credentials');
|
|
31
81
|
return res.data;
|