@ctrl/plex 1.5.3 → 2.0.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/README.md +3 -2
- package/dist/src/alert.d.ts +12 -0
- package/dist/src/alert.js +29 -0
- package/dist/src/alert.types.d.ts +59 -0
- package/dist/src/alert.types.js +1 -0
- package/dist/{base → src/base}/partialPlexObject.d.ts +18 -12
- package/dist/{base → src/base}/partialPlexObject.js +29 -23
- package/dist/{base → src/base}/playable.d.ts +2 -2
- package/dist/src/base/playable.js +8 -0
- package/dist/{base → src/base}/plexObject.d.ts +8 -1
- package/dist/{base → src/base}/plexObject.js +21 -18
- package/dist/{baseFunctionality.d.ts → src/baseFunctionality.d.ts} +17 -1
- package/dist/{baseFunctionality.js → src/baseFunctionality.js} +7 -15
- package/dist/{client.d.ts → src/client.d.ts} +2 -2
- package/dist/{client.js → src/client.js} +12 -20
- package/dist/src/client.types.js +1 -0
- package/dist/src/config.js +35 -0
- package/dist/src/exceptions.d.ts +20 -0
- package/dist/src/exceptions.js +40 -0
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.js +11 -0
- package/dist/{library.d.ts → src/library.d.ts} +207 -21
- package/dist/{library.js → src/library.js} +348 -132
- package/dist/{library.types.d.ts → src/library.types.d.ts} +59 -1
- package/dist/src/library.types.js +1 -0
- package/dist/{media.d.ts → src/media.d.ts} +16 -4
- package/dist/{media.js → src/media.js} +42 -49
- package/dist/src/media.types.d.ts +7 -0
- package/dist/src/media.types.js +1 -0
- package/dist/{myplex.d.ts → src/myplex.d.ts} +16 -6
- package/dist/{myplex.js → src/myplex.js} +71 -57
- package/dist/src/myplex.types.js +10 -0
- package/dist/src/playlist.d.ts +75 -0
- package/dist/src/playlist.js +142 -0
- package/dist/src/playlist.types.d.ts +17 -0
- package/dist/src/playlist.types.js +1 -0
- package/dist/{search.d.ts → src/search.d.ts} +4 -3
- package/dist/{search.js → src/search.js} +13 -19
- package/dist/src/search.types.js +1 -0
- package/dist/{server.d.ts → src/server.d.ts} +22 -10
- package/dist/{server.js → src/server.js} +65 -50
- package/dist/src/server.types.js +1 -0
- package/dist/src/settings.d.ts +79 -0
- package/dist/src/settings.js +160 -0
- package/dist/{util.d.ts → src/util.d.ts} +2 -1
- package/dist/{util.js → src/util.js} +8 -12
- package/dist/{video.d.ts → src/video.d.ts} +38 -60
- package/dist/{video.js → src/video.js} +109 -92
- package/dist/{video.types.d.ts → src/video.types.d.ts} +1 -1
- package/dist/src/video.types.js +6 -0
- package/package.json +46 -44
- package/dist/base/playable.js +0 -12
- package/dist/client.types.js +0 -2
- package/dist/config.js +0 -41
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -23
- package/dist/library.types.js +0 -2
- package/dist/myplex.types.js +0 -13
- package/dist/playlist.d.ts +0 -7
- package/dist/playlist.js +0 -19
- package/dist/search.types.js +0 -2
- package/dist/server.types.js +0 -2
- package/dist/video.types.js +0 -9
- /package/dist/{client.types.d.ts → src/client.types.d.ts} +0 -0
- /package/dist/{config.d.ts → src/config.d.ts} +0 -0
- /package/dist/{myplex.types.d.ts → src/myplex.types.d.ts} +0 -0
- /package/dist/{search.types.d.ts → src/search.types.d.ts} +0 -0
- /package/dist/{server.types.d.ts → src/server.types.d.ts} +0 -0
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Library {
|
|
1
|
+
import { URLSearchParams } from 'url';
|
|
2
|
+
import { PartialPlexObject } from './base/partialPlexObject.js';
|
|
3
|
+
import { PlexObject } from './base/plexObject.js';
|
|
4
|
+
import { fetchItem, fetchItems, findItems } from './baseFunctionality.js';
|
|
5
|
+
import { NotFound } from './exceptions.js';
|
|
6
|
+
import { Playlist } from './playlist.js';
|
|
7
|
+
import { searchType } from './search.js';
|
|
8
|
+
import { Movie, Show } from './video.js';
|
|
9
|
+
export class Library {
|
|
10
|
+
static { this.key = '/library'; }
|
|
12
11
|
constructor(server, data) {
|
|
13
12
|
this.server = server;
|
|
14
13
|
this._loadData(data);
|
|
@@ -41,8 +40,9 @@ class Library {
|
|
|
41
40
|
return section;
|
|
42
41
|
}
|
|
43
42
|
async sectionByID(sectionId) {
|
|
43
|
+
const sectionIdStr = sectionId.toString();
|
|
44
44
|
const sections = await this.sections();
|
|
45
|
-
const section = sections.find(s => s.key);
|
|
45
|
+
const section = sections.find(s => s.key === sectionIdStr);
|
|
46
46
|
if (!section) {
|
|
47
47
|
throw new Error(`Invalid library section id: ${sectionId}`);
|
|
48
48
|
}
|
|
@@ -193,7 +193,7 @@ class Library {
|
|
|
193
193
|
* 46:United Kingdom, 47:United States, 48:Uruguay, 49:Venezuela.
|
|
194
194
|
*/
|
|
195
195
|
async add(name, type, agent, scanner, location, language = 'en', extra = {}) {
|
|
196
|
-
const search = new
|
|
196
|
+
const search = new URLSearchParams({
|
|
197
197
|
name,
|
|
198
198
|
type,
|
|
199
199
|
agent,
|
|
@@ -239,6 +239,30 @@ class Library {
|
|
|
239
239
|
async optimize() {
|
|
240
240
|
await this.server.query('/library/optimize?async=1', 'put');
|
|
241
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Validates a filter field and values are available as a custom filter for the library.
|
|
244
|
+
* Returns the validated field and values as a URL encoded parameter string.
|
|
245
|
+
*/
|
|
246
|
+
// _validateFilterField(field: string): string {
|
|
247
|
+
// const match = /(?:([a-zA-Z]*)\.)?([a-zA-Z]+)([!<>=&]*)/.test(field);
|
|
248
|
+
// if (!match) {
|
|
249
|
+
// throw new Error('Invalid filter field: ' + field);
|
|
250
|
+
// }
|
|
251
|
+
// }
|
|
252
|
+
/**
|
|
253
|
+
* Returns the validated and formatted search query API key
|
|
254
|
+
* (``/library/sections/<sectionKey>/all?<params>``).
|
|
255
|
+
*/
|
|
256
|
+
// _buildSearchKey(kwargs: Record<string, string>) {
|
|
257
|
+
// const args: Record<string, string> = {};
|
|
258
|
+
// const filterArgs = [];
|
|
259
|
+
// for (const [field, values] of Object.entries(kwargs)) {
|
|
260
|
+
// if (!(field.split('__')[-1] in OPERATORS)) {
|
|
261
|
+
// filterArgs.push(this._validateFilterField(field, values, libtype));
|
|
262
|
+
// delete kwargs[field];
|
|
263
|
+
// }
|
|
264
|
+
// }
|
|
265
|
+
// }
|
|
242
266
|
_loadData(data) {
|
|
243
267
|
this.identifier = data.identifier;
|
|
244
268
|
this.mediaTagPrefix = data.mediaTagPrefix;
|
|
@@ -246,45 +270,95 @@ class Library {
|
|
|
246
270
|
this.title2 = data.title2;
|
|
247
271
|
}
|
|
248
272
|
}
|
|
249
|
-
exports.Library = Library;
|
|
250
|
-
Library.key = '/library';
|
|
251
273
|
/**
|
|
252
274
|
* Base class for a single library section.
|
|
253
275
|
*/
|
|
254
|
-
class LibrarySection extends
|
|
276
|
+
export class LibrarySection extends PlexObject {
|
|
277
|
+
static { this.ALLOWED_FILTERS = []; }
|
|
278
|
+
static { this.ALLOWED_SORT = []; }
|
|
279
|
+
static { this.BOOLEAN_FILTERS = ['unwatched', 'duplicate']; }
|
|
255
280
|
async all(sort = '') {
|
|
256
281
|
let sortStr = '';
|
|
257
282
|
if (sort) {
|
|
258
283
|
sortStr = `?sort=${sort}`;
|
|
259
284
|
}
|
|
260
285
|
const key = `/library/sections/${this.key}/all${sortStr}`;
|
|
261
|
-
const items = await
|
|
286
|
+
const items = await fetchItems(this.server, key);
|
|
262
287
|
return items;
|
|
263
288
|
}
|
|
264
289
|
async agents() {
|
|
265
|
-
return this.server.agents(
|
|
290
|
+
return this.server.agents(searchType(this.type));
|
|
266
291
|
}
|
|
267
292
|
/**
|
|
268
293
|
* @param title Title of the item to return.
|
|
269
294
|
* @returns the media item with the specified title.
|
|
270
295
|
*/
|
|
271
296
|
async get(title) {
|
|
272
|
-
const key = `/library/sections/${this.key}/all?title=${title}`;
|
|
273
|
-
const data = await
|
|
297
|
+
const key = `/library/sections/${this.key}/all?includeGuids=1&title=${title}`;
|
|
298
|
+
const data = await fetchItem(this.server, key, { title__iexact: title });
|
|
274
299
|
return new this.VIDEO_TYPE(this.server, data, key, this);
|
|
275
300
|
}
|
|
276
301
|
/**
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
302
|
+
* Returns the media item with the specified external IMDB, TMDB, or TVDB ID.
|
|
303
|
+
* Note: This search uses a PlexAPI operator so performance may be slow. All items from the
|
|
304
|
+
* entire Plex library need to be retrieved for each guid search. It is recommended to create
|
|
305
|
+
* your own lookup dictionary if you are searching for a lot of external guids.
|
|
306
|
+
*
|
|
307
|
+
* @param guid The external guid of the item to return.
|
|
308
|
+
* Examples: IMDB ``imdb://tt0944947``, TMDB ``tmdb://1399``, TVDB ``tvdb://121361``.
|
|
309
|
+
*
|
|
310
|
+
*
|
|
311
|
+
* Example:
|
|
312
|
+
*
|
|
313
|
+
* .. code-block:: python
|
|
314
|
+
*
|
|
315
|
+
* # This will retrieve all items in the entire library 3 times
|
|
316
|
+
* result1 = library.getGuid('imdb://tt0944947')
|
|
317
|
+
* result2 = library.getGuid('tmdb://1399')
|
|
318
|
+
* result3 = library.getGuid('tvdb://121361')
|
|
319
|
+
*
|
|
320
|
+
* # This will only retrieve all items in the library once to create a lookup dictionary
|
|
321
|
+
* guidLookup = {guid.id: item for item in library.all() for guid in item.guids}
|
|
322
|
+
* result1 = guidLookup['imdb://tt0944947']
|
|
323
|
+
* result2 = guidLookup['tmdb://1399']
|
|
324
|
+
* result3 = guidLookup['tvdb://121361']
|
|
325
|
+
*/
|
|
326
|
+
async getGuid(guid) {
|
|
327
|
+
const key = `/library/sections/${this.key}/all?includeGuids=1`;
|
|
328
|
+
return fetchItem(this.server, key, { Guid__id__iexact: guid });
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Returns the Plex Web URL for the library.
|
|
332
|
+
*
|
|
333
|
+
* @param base The base URL before the fragment (``#!``).
|
|
334
|
+
* Default is https://app.plex.tv/desktop.
|
|
335
|
+
* @param tab The library tab (recommended, library, collections, playlists, timeline).
|
|
336
|
+
* @param key A hub key.
|
|
337
|
+
*/
|
|
338
|
+
getWebURL(base, tab, key) {
|
|
339
|
+
const params = new URLSearchParams();
|
|
340
|
+
params.append('source', this.key);
|
|
341
|
+
if (tab) {
|
|
342
|
+
params.append('pivot', tab);
|
|
343
|
+
}
|
|
344
|
+
if (key) {
|
|
345
|
+
params.append('key', key);
|
|
346
|
+
params.append('pageType', 'list');
|
|
347
|
+
}
|
|
348
|
+
return this.server._buildWebURL(base, undefined, params);
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Search the library. The http requests will be batched in container_size. If you are only looking for the
|
|
352
|
+
* first <num> results, it would be wise to set the maxresults option to that amount so the search doesn't iterate
|
|
353
|
+
* over all results on the server.
|
|
280
354
|
*
|
|
281
355
|
* Example: "studio=Comedy%20Central" or "year=1999" "title=Kung Fu" all work. Other items
|
|
282
356
|
* such as actor=<id> seem to work, but require you already know the id of the actor.
|
|
283
357
|
* TLDR: This is untested but seems to work. Use library section search when you can.
|
|
284
358
|
* @param args Search using a number of different attributes
|
|
285
359
|
*/
|
|
286
|
-
async search(args = {},
|
|
287
|
-
const params = new
|
|
360
|
+
async search(args = {}, Cls = this.VIDEO_TYPE) {
|
|
361
|
+
const params = new URLSearchParams();
|
|
288
362
|
for (const [key, value] of Object.entries(args)) {
|
|
289
363
|
let strValue;
|
|
290
364
|
if (typeof value === 'string') {
|
|
@@ -298,11 +372,11 @@ class LibrarySection extends plexObject_1.PlexObject {
|
|
|
298
372
|
}
|
|
299
373
|
params.append(key, strValue);
|
|
300
374
|
}
|
|
301
|
-
if (libtype) {
|
|
302
|
-
params.append('type',
|
|
375
|
+
if (args.libtype) {
|
|
376
|
+
params.append('type', searchType(args.libtype).toString());
|
|
303
377
|
}
|
|
304
|
-
const key = `/library/all
|
|
305
|
-
const data = await
|
|
378
|
+
const key = `/library/sections/${this.key}/all?${params.toString()}`;
|
|
379
|
+
const data = await fetchItems(this.server, key, undefined, Cls, this);
|
|
306
380
|
return data;
|
|
307
381
|
}
|
|
308
382
|
/**
|
|
@@ -368,7 +442,7 @@ class LibrarySection extends plexObject_1.PlexObject {
|
|
|
368
442
|
* @param kwargs object of settings to edit
|
|
369
443
|
*/
|
|
370
444
|
async edit(kwargs) {
|
|
371
|
-
const params = new
|
|
445
|
+
const params = new URLSearchParams(kwargs);
|
|
372
446
|
const part = `/library/sections/${this.key}?${params.toString()}`;
|
|
373
447
|
await this.server.query(part, 'put');
|
|
374
448
|
const library = await this.server.library();
|
|
@@ -382,7 +456,7 @@ class LibrarySection extends plexObject_1.PlexObject {
|
|
|
382
456
|
}
|
|
383
457
|
async hubs() {
|
|
384
458
|
const key = `/hubs/sections/${this.key}`;
|
|
385
|
-
const hubs = await
|
|
459
|
+
const hubs = await fetchItems(this.server, key, undefined, Hub, this);
|
|
386
460
|
return hubs;
|
|
387
461
|
}
|
|
388
462
|
/**
|
|
@@ -390,10 +464,10 @@ class LibrarySection extends plexObject_1.PlexObject {
|
|
|
390
464
|
*/
|
|
391
465
|
async playlists() {
|
|
392
466
|
const key = `/playlists?type=15&playlistType=${this.CONTENT_TYPE}§ionID=${this.key}`;
|
|
393
|
-
return
|
|
467
|
+
return fetchItems(this.server, key, undefined, Playlist, this);
|
|
394
468
|
}
|
|
395
469
|
async collections(args = {}) {
|
|
396
|
-
const collections = await this.search(args, 'collection', Collections);
|
|
470
|
+
const collections = await this.search({ ...args, libtype: 'collection' }, Collections);
|
|
397
471
|
collections.forEach(collection => {
|
|
398
472
|
collection.VIDEO_TYPE = this.VIDEO_TYPE;
|
|
399
473
|
});
|
|
@@ -404,7 +478,71 @@ class LibrarySection extends plexObject_1.PlexObject {
|
|
|
404
478
|
*/
|
|
405
479
|
async folders() {
|
|
406
480
|
const key = `/library/sections/${this.key}/folder`;
|
|
407
|
-
return
|
|
481
|
+
return fetchItems(this.server, key, undefined, Folder);
|
|
482
|
+
}
|
|
483
|
+
async genres() {
|
|
484
|
+
const key = `/library/sections/${this.key}/genre`;
|
|
485
|
+
return fetchItems(this.server, key, undefined, FilterChoice);
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Returns a list of available {@link FilteringFields} for a specified libtype.
|
|
489
|
+
* This is the list of options in the custom filter dropdown menu
|
|
490
|
+
*/
|
|
491
|
+
async listFields(libtype = this.type) {
|
|
492
|
+
return (await this.getFilterType(libtype)).fields;
|
|
493
|
+
}
|
|
494
|
+
async getFilterType(libtype = this.type) {
|
|
495
|
+
const filterTypes = await this.filterTypes();
|
|
496
|
+
const filter = filterTypes.find(f => f.type === libtype);
|
|
497
|
+
if (!filter) {
|
|
498
|
+
throw new NotFound(`Unknown libtype "${libtype}" for this library.
|
|
499
|
+
Available libtypes: ${filterTypes.join(', ')}`);
|
|
500
|
+
}
|
|
501
|
+
return filter;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* @param fieldType The data type for the field (tag, integer, string, boolean, date,
|
|
505
|
+
subtitleLanguage, audioLanguage, resolution).
|
|
506
|
+
*/
|
|
507
|
+
async getFieldType(fieldType) {
|
|
508
|
+
const fieldTypes = await this.fieldTypes();
|
|
509
|
+
const fType = fieldTypes.find(f => f.type === fieldType);
|
|
510
|
+
if (!fType) {
|
|
511
|
+
const availableFieldTypes = fieldTypes.map(f => f.type);
|
|
512
|
+
throw new NotFound(`Unknown field type "${fieldType}" for this library. Available field types: ${availableFieldTypes.join(', ')}`);
|
|
513
|
+
}
|
|
514
|
+
return fType;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* @param libtype The library type to filter (movie, show, season, episode,
|
|
518
|
+
* artist, album, track, photoalbum, photo, collection).
|
|
519
|
+
*
|
|
520
|
+
* @example
|
|
521
|
+
* ```ts
|
|
522
|
+
* const availableFilters = (await library.listFilters()).map(f => f.filter)
|
|
523
|
+
* ```
|
|
524
|
+
*/
|
|
525
|
+
async listFilters(libtype) {
|
|
526
|
+
return (await this.getFilterType(libtype)).filters;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* @param fieldType The data type for the field (tag, integer, string, boolean, date,
|
|
530
|
+
* subtitleLanguage, audioLanguage, resolution).
|
|
531
|
+
*/
|
|
532
|
+
async listOperators(fieldType) {
|
|
533
|
+
return (await this.getFieldType(fieldType)).operators;
|
|
534
|
+
}
|
|
535
|
+
async filterTypes() {
|
|
536
|
+
if (this._filterTypes === null) {
|
|
537
|
+
await this._loadFilters();
|
|
538
|
+
}
|
|
539
|
+
return this._filterTypes;
|
|
540
|
+
}
|
|
541
|
+
async fieldTypes() {
|
|
542
|
+
if (this._fieldTypes === null) {
|
|
543
|
+
await this._loadFilters();
|
|
544
|
+
}
|
|
545
|
+
return this._fieldTypes;
|
|
408
546
|
}
|
|
409
547
|
_loadData(data) {
|
|
410
548
|
this.uuid = data.uuid;
|
|
@@ -425,61 +563,99 @@ class LibrarySection extends plexObject_1.PlexObject {
|
|
|
425
563
|
this.createdAt = new Date(data.createdAt * 1000);
|
|
426
564
|
this.scannedAt = new Date(data.scannedAt * 1000);
|
|
427
565
|
}
|
|
566
|
+
async _loadFilters() {
|
|
567
|
+
const key = `/library/sections/${this.key}/all?includeMeta=1&includeAdvanced=1&X-Plex-Container-Start=0&X-Plex-Container-Size=0`;
|
|
568
|
+
const data = await this.server.query(key);
|
|
569
|
+
// rtag='Meta'
|
|
570
|
+
this._filterTypes = findItems(data, undefined, FilteringType);
|
|
571
|
+
}
|
|
428
572
|
}
|
|
429
|
-
|
|
430
|
-
LibrarySection.ALLOWED_FILTERS = [];
|
|
431
|
-
LibrarySection.ALLOWED_SORT = [];
|
|
432
|
-
LibrarySection.BOOLEAN_FILTERS = ['unwatched', 'duplicate'];
|
|
433
|
-
class MovieSection extends LibrarySection {
|
|
573
|
+
export class MovieSection extends LibrarySection {
|
|
434
574
|
constructor() {
|
|
435
575
|
super(...arguments);
|
|
436
576
|
this.METADATA_TYPE = 'movie';
|
|
437
577
|
this.CONTENT_TYPE = 'video';
|
|
438
|
-
this.VIDEO_TYPE =
|
|
439
|
-
}
|
|
578
|
+
this.VIDEO_TYPE = Movie;
|
|
579
|
+
}
|
|
580
|
+
static { this.TYPE = 'movie'; }
|
|
581
|
+
static { this.ALLOWED_FILTERS = [
|
|
582
|
+
'unwatched',
|
|
583
|
+
'duplicate',
|
|
584
|
+
'year',
|
|
585
|
+
'decade',
|
|
586
|
+
'genre',
|
|
587
|
+
'contentRating',
|
|
588
|
+
'collection',
|
|
589
|
+
'director',
|
|
590
|
+
'actor',
|
|
591
|
+
'country',
|
|
592
|
+
'studio',
|
|
593
|
+
'resolution',
|
|
594
|
+
'guid',
|
|
595
|
+
'label',
|
|
596
|
+
'writer',
|
|
597
|
+
'producer',
|
|
598
|
+
'subtitleLanguage',
|
|
599
|
+
'audioLanguage',
|
|
600
|
+
'lastViewedAt',
|
|
601
|
+
'viewCount',
|
|
602
|
+
'addedAt',
|
|
603
|
+
]; }
|
|
604
|
+
static { this.ALLOWED_SORT = [
|
|
605
|
+
'addedAt',
|
|
606
|
+
'originallyAvailableAt',
|
|
607
|
+
'lastViewedAt',
|
|
608
|
+
'titleSort',
|
|
609
|
+
'rating',
|
|
610
|
+
'mediaHeight',
|
|
611
|
+
'duration',
|
|
612
|
+
]; }
|
|
613
|
+
static { this.TAG = 'Directory'; }
|
|
440
614
|
}
|
|
441
|
-
|
|
442
|
-
MovieSection.TYPE = 'movie';
|
|
443
|
-
MovieSection.ALLOWED_FILTERS = [
|
|
444
|
-
'unwatched',
|
|
445
|
-
'duplicate',
|
|
446
|
-
'year',
|
|
447
|
-
'decade',
|
|
448
|
-
'genre',
|
|
449
|
-
'contentRating',
|
|
450
|
-
'collection',
|
|
451
|
-
'director',
|
|
452
|
-
'actor',
|
|
453
|
-
'country',
|
|
454
|
-
'studio',
|
|
455
|
-
'resolution',
|
|
456
|
-
'guid',
|
|
457
|
-
'label',
|
|
458
|
-
'writer',
|
|
459
|
-
'producer',
|
|
460
|
-
'subtitleLanguage',
|
|
461
|
-
'audioLanguage',
|
|
462
|
-
'lastViewedAt',
|
|
463
|
-
'viewCount',
|
|
464
|
-
'addedAt',
|
|
465
|
-
];
|
|
466
|
-
MovieSection.ALLOWED_SORT = [
|
|
467
|
-
'addedAt',
|
|
468
|
-
'originallyAvailableAt',
|
|
469
|
-
'lastViewedAt',
|
|
470
|
-
'titleSort',
|
|
471
|
-
'rating',
|
|
472
|
-
'mediaHeight',
|
|
473
|
-
'duration',
|
|
474
|
-
];
|
|
475
|
-
MovieSection.TAG = 'Directory';
|
|
476
|
-
class ShowSection extends LibrarySection {
|
|
615
|
+
export class ShowSection extends LibrarySection {
|
|
477
616
|
constructor() {
|
|
478
617
|
super(...arguments);
|
|
479
618
|
this.METADATA_TYPE = 'episode';
|
|
480
619
|
this.CONTENT_TYPE = 'video';
|
|
481
|
-
this.VIDEO_TYPE =
|
|
482
|
-
}
|
|
620
|
+
this.VIDEO_TYPE = Show;
|
|
621
|
+
}
|
|
622
|
+
static { this.TYPE = 'show'; }
|
|
623
|
+
static { this.ALLOWED_FILTERS = [
|
|
624
|
+
'unwatched',
|
|
625
|
+
'year',
|
|
626
|
+
'genre',
|
|
627
|
+
'contentRating',
|
|
628
|
+
'network',
|
|
629
|
+
'collection',
|
|
630
|
+
'guid',
|
|
631
|
+
'duplicate',
|
|
632
|
+
'label',
|
|
633
|
+
'show.title',
|
|
634
|
+
'show.year',
|
|
635
|
+
'show.userRating',
|
|
636
|
+
'show.viewCount',
|
|
637
|
+
'show.lastViewedAt',
|
|
638
|
+
'show.actor',
|
|
639
|
+
'show.addedAt',
|
|
640
|
+
'episode.title',
|
|
641
|
+
'episode.originallyAvailableAt',
|
|
642
|
+
'episode.resolution',
|
|
643
|
+
'episode.subtitleLanguage',
|
|
644
|
+
'episode.unwatched',
|
|
645
|
+
'episode.addedAt',
|
|
646
|
+
'episode.userRating',
|
|
647
|
+
'episode.viewCount',
|
|
648
|
+
'episode.lastViewedAt',
|
|
649
|
+
]; }
|
|
650
|
+
static { this.ALLOWED_SORT = [
|
|
651
|
+
'addedAt',
|
|
652
|
+
'lastViewedAt',
|
|
653
|
+
'originallyAvailableAt',
|
|
654
|
+
'titleSort',
|
|
655
|
+
'rating',
|
|
656
|
+
'unwatched',
|
|
657
|
+
]; }
|
|
658
|
+
static { this.TAG = 'Directory'; }
|
|
483
659
|
// TODO: figure out how to return episode objects
|
|
484
660
|
// /**
|
|
485
661
|
// * Search for an episode. See :func:`~plexapi.library.LibrarySection.search` for usage.
|
|
@@ -496,46 +672,9 @@ class ShowSection extends LibrarySection {
|
|
|
496
672
|
return this.search({ libtype, maxresults, sort: 'episode.addedAt:desc', ...args });
|
|
497
673
|
}
|
|
498
674
|
}
|
|
499
|
-
exports.ShowSection = ShowSection;
|
|
500
|
-
ShowSection.TYPE = 'show';
|
|
501
|
-
ShowSection.ALLOWED_FILTERS = [
|
|
502
|
-
'unwatched',
|
|
503
|
-
'year',
|
|
504
|
-
'genre',
|
|
505
|
-
'contentRating',
|
|
506
|
-
'network',
|
|
507
|
-
'collection',
|
|
508
|
-
'guid',
|
|
509
|
-
'duplicate',
|
|
510
|
-
'label',
|
|
511
|
-
'show.title',
|
|
512
|
-
'show.year',
|
|
513
|
-
'show.userRating',
|
|
514
|
-
'show.viewCount',
|
|
515
|
-
'show.lastViewedAt',
|
|
516
|
-
'show.actor',
|
|
517
|
-
'show.addedAt',
|
|
518
|
-
'episode.title',
|
|
519
|
-
'episode.originallyAvailableAt',
|
|
520
|
-
'episode.resolution',
|
|
521
|
-
'episode.subtitleLanguage',
|
|
522
|
-
'episode.unwatched',
|
|
523
|
-
'episode.addedAt',
|
|
524
|
-
'episode.userRating',
|
|
525
|
-
'episode.viewCount',
|
|
526
|
-
'episode.lastViewedAt',
|
|
527
|
-
];
|
|
528
|
-
ShowSection.ALLOWED_SORT = [
|
|
529
|
-
'addedAt',
|
|
530
|
-
'lastViewedAt',
|
|
531
|
-
'originallyAvailableAt',
|
|
532
|
-
'titleSort',
|
|
533
|
-
'rating',
|
|
534
|
-
'unwatched',
|
|
535
|
-
];
|
|
536
|
-
ShowSection.TAG = 'Directory';
|
|
537
675
|
/** Represents a single Hub (or category) in the PlexServer search */
|
|
538
|
-
class Hub extends
|
|
676
|
+
export class Hub extends PlexObject {
|
|
677
|
+
static { this.TAG = 'Hub'; }
|
|
539
678
|
_loadData(data) {
|
|
540
679
|
this.hubIdentifier = data.hubIdentifier;
|
|
541
680
|
this.size = data.size;
|
|
@@ -545,33 +684,31 @@ class Hub extends plexObject_1.PlexObject {
|
|
|
545
684
|
this.Metadata = data.Metadata;
|
|
546
685
|
}
|
|
547
686
|
}
|
|
548
|
-
exports.Hub = Hub;
|
|
549
|
-
Hub.TAG = 'Hub';
|
|
550
687
|
/**
|
|
551
688
|
* Represents a Folder inside a library.
|
|
552
689
|
*/
|
|
553
|
-
class Folder extends
|
|
690
|
+
export class Folder extends PlexObject {
|
|
554
691
|
/**
|
|
555
692
|
* Returns a list of available Folders for this folder.
|
|
556
693
|
* Continue down subfolders until a mediaType is found.
|
|
557
694
|
*/
|
|
558
695
|
async subfolders() {
|
|
559
696
|
if (this.key.startsWith('/library/metadata')) {
|
|
560
|
-
return
|
|
697
|
+
return fetchItems(this.server, this.key);
|
|
561
698
|
}
|
|
562
|
-
return
|
|
699
|
+
return fetchItems(this.server, this.key, undefined, Folder);
|
|
563
700
|
}
|
|
564
701
|
_loadData(data) {
|
|
565
702
|
this.key = data.key;
|
|
566
703
|
this.title = data.title;
|
|
567
704
|
}
|
|
568
705
|
}
|
|
569
|
-
|
|
570
|
-
class Collections extends partialPlexObject_1.PartialPlexObject {
|
|
706
|
+
export class Collections extends PartialPlexObject {
|
|
571
707
|
constructor() {
|
|
572
708
|
super(...arguments);
|
|
573
709
|
this.TYPE = 'collection';
|
|
574
710
|
}
|
|
711
|
+
static { this.TAG = 'Directory'; }
|
|
575
712
|
// Alias for childCount
|
|
576
713
|
get size() {
|
|
577
714
|
return this.childCount;
|
|
@@ -582,10 +719,12 @@ class Collections extends partialPlexObject_1.PartialPlexObject {
|
|
|
582
719
|
async items() {
|
|
583
720
|
const key = `/library/metadata/${this.ratingKey}/children`;
|
|
584
721
|
const data = await this.server.query(key);
|
|
585
|
-
return data.MediaContainer.Metadata
|
|
722
|
+
return (data.MediaContainer.Metadata?.map(d => new this.VIDEO_TYPE(this.server, d, undefined, this)) ?? []);
|
|
723
|
+
}
|
|
724
|
+
_loadFullData(data) {
|
|
725
|
+
this._loadData(data);
|
|
586
726
|
}
|
|
587
727
|
_loadData(data) {
|
|
588
|
-
var _a, _b;
|
|
589
728
|
this.key = data.key;
|
|
590
729
|
this.title = data.title;
|
|
591
730
|
this.ratingKey = data.ratingKey;
|
|
@@ -601,14 +740,91 @@ class Collections extends partialPlexObject_1.PartialPlexObject {
|
|
|
601
740
|
this.thumb = data.thumb;
|
|
602
741
|
this.addedAt = data.addedAt;
|
|
603
742
|
this.updatedAt = data.updatedAt;
|
|
604
|
-
this.childCount =
|
|
743
|
+
this.childCount = Number(data.childCount ?? data.size ?? '0');
|
|
605
744
|
this.maxYear = data.maxYear;
|
|
606
745
|
this.minYear = data.minYear;
|
|
607
746
|
this.art = data.art;
|
|
608
747
|
}
|
|
609
|
-
|
|
610
|
-
|
|
748
|
+
}
|
|
749
|
+
export class FilterChoice extends PlexObject {
|
|
750
|
+
static { this.TAG = 'Directory'; }
|
|
751
|
+
_loadData(data) {
|
|
752
|
+
this.key = data.key;
|
|
753
|
+
this.title = data.title;
|
|
754
|
+
this.type = data.type;
|
|
755
|
+
this.thumb = data.thumb;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
export class FilteringType extends PlexObject {
|
|
759
|
+
static { this.TAG = 'Type'; }
|
|
760
|
+
_loadData(data) {
|
|
761
|
+
this.active = data.active;
|
|
762
|
+
this.fields = findItems(data, undefined, FilteringField);
|
|
763
|
+
this.filters = findItems(data, undefined, FilteringFilter);
|
|
764
|
+
this.key = data.key;
|
|
765
|
+
this.sorts = findItems(data, undefined, FilteringSort);
|
|
766
|
+
this.title = data.title;
|
|
767
|
+
this.type = data.type;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Represents a single Filter object for a {@link FilteringType}
|
|
772
|
+
*/
|
|
773
|
+
export class FilteringFilter extends PlexObject {
|
|
774
|
+
static { this.TAG = 'Filter'; }
|
|
775
|
+
_loadData(data) {
|
|
776
|
+
this.filter = data.filter;
|
|
777
|
+
this.filterType = data.filterType;
|
|
778
|
+
this.key = data.key;
|
|
779
|
+
this.title = data.title;
|
|
780
|
+
this.type = data.type;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Represents a single Sort object for a {@link FilteringType}
|
|
785
|
+
*/
|
|
786
|
+
export class FilteringSort extends PlexObject {
|
|
787
|
+
static { this.TAG = 'Sort'; }
|
|
788
|
+
_loadData(data) {
|
|
789
|
+
this.active = data.active;
|
|
790
|
+
this.activeDirection = data.activeDirection;
|
|
791
|
+
this.default = data.default;
|
|
792
|
+
this.defaultDirection = data.defaultDirection;
|
|
793
|
+
this.descKey = data.descKey;
|
|
794
|
+
this.firstCharacterKey = data.firstCharacterKey;
|
|
795
|
+
this.key = data.key;
|
|
796
|
+
this.title = data.title;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Represents a single Field object for a {@link FilteringType}
|
|
801
|
+
*/
|
|
802
|
+
export class FilteringField extends PlexObject {
|
|
803
|
+
static { this.TAG = 'Field'; }
|
|
804
|
+
_loadData(data) {
|
|
805
|
+
this.key = data.key;
|
|
806
|
+
this.title = data.title;
|
|
807
|
+
this.type = data.type;
|
|
808
|
+
this.subType = data.subType;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Represents a single FieldType for library filtering.
|
|
813
|
+
*/
|
|
814
|
+
export class FilteringFieldType extends PlexObject {
|
|
815
|
+
static { this.TAG = 'FieldType'; }
|
|
816
|
+
_loadData(data) {
|
|
817
|
+
this.type = data.type;
|
|
818
|
+
this.operators = findItems(data, undefined, FilteringOperator);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Represents a single FilterChoice for library filtering.
|
|
823
|
+
*/
|
|
824
|
+
export class FilteringOperator extends PlexObject {
|
|
825
|
+
static { this.TAG = 'Operator'; }
|
|
826
|
+
_loadData(data) {
|
|
827
|
+
this.key = data.key;
|
|
828
|
+
this.type = data.type;
|
|
611
829
|
}
|
|
612
830
|
}
|
|
613
|
-
exports.Collections = Collections;
|
|
614
|
-
Collections.TAG = 'Directory';
|