@ctrl/plex 3.10.0 → 3.12.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/dist/src/alert.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import WebSocket from 'ws';
2
2
  export class AlertListener {
3
+ server;
4
+ callback;
5
+ key = '/:/websockets/notifications';
3
6
  constructor(server, callback) {
4
7
  this.server = server;
5
8
  this.callback = callback;
6
- this.key = '/:/websockets/notifications';
7
9
  }
8
10
  async run() {
9
11
  const url = this.server.url(this.key, true).toString().replace('http', 'ws');
package/dist/src/audio.js CHANGED
@@ -1,13 +1,14 @@
1
- import { URLSearchParams } from 'url';
1
+ import { URLSearchParams } from 'node:url';
2
2
  import { Playable } from './base/playable.js';
3
3
  import { fetchItem, fetchItems } from './baseFunctionality.js';
4
4
  import { Chapter, Collection, Country, Field, Format, Genre, Guid, Image, Label, Media, Mood, Similar, Style, Subformat, } from './media.js';
5
+ const hasSonicAdventure = (s) => typeof s.sonicAdventure === 'function';
5
6
  /**
6
7
  * Base class for all audio objects including Artist, Album, and Track.
7
8
  */
8
9
  export class Audio extends Playable {
9
10
  /** Default metadata type for audio sync items. */
10
- static { this.METADATA_TYPE = 'track'; }
11
+ static METADATA_TYPE = 'track';
11
12
  /** Hardcoded list type for filtering. */
12
13
  get listType() {
13
14
  return 'audio';
@@ -83,53 +84,61 @@ export class Audio extends Playable {
83
84
  */
84
85
  _loadData(data) {
85
86
  this._data = data;
86
- const addedAtTimestamp = data.addedAt ? parseInt(data.addedAt, 10) : NaN;
87
- this.addedAt = isNaN(addedAtTimestamp) ? undefined : new Date(addedAtTimestamp * 1000);
87
+ const addedAtTimestamp = data.addedAt ? Number.parseInt(data.addedAt, 10) : Number.NaN;
88
+ this.addedAt = Number.isNaN(addedAtTimestamp) ? undefined : new Date(addedAtTimestamp * 1000);
88
89
  this.art = data.art ? this.server.url(data.art, true)?.toString() : undefined;
89
90
  this.artBlurHash = data.artBlurHash;
90
- const distanceFloat = data.distance ? parseFloat(data.distance) : NaN;
91
- this.distance = isNaN(distanceFloat) ? undefined : distanceFloat;
91
+ const distanceFloat = data.distance ? Number.parseFloat(data.distance) : Number.NaN;
92
+ this.distance = Number.isNaN(distanceFloat) ? undefined : distanceFloat;
92
93
  this.guid = data.guid;
93
- const indexInt = data.index ? parseInt(data.index, 10) : NaN;
94
- this.index = isNaN(indexInt) ? undefined : indexInt;
94
+ const indexInt = data.index ? Number.parseInt(data.index, 10) : Number.NaN;
95
+ this.index = Number.isNaN(indexInt) ? undefined : indexInt;
95
96
  this.key = data.key ?? this.key ?? '';
96
- const lastRatedAtTimestamp = data.lastRatedAt ? parseInt(data.lastRatedAt, 10) : NaN;
97
- this.lastRatedAt = isNaN(lastRatedAtTimestamp)
97
+ const lastRatedAtTimestamp = data.lastRatedAt
98
+ ? Number.parseInt(data.lastRatedAt, 10)
99
+ : Number.NaN;
100
+ this.lastRatedAt = Number.isNaN(lastRatedAtTimestamp)
98
101
  ? undefined
99
102
  : new Date(lastRatedAtTimestamp * 1000);
100
- const lastViewedAtTimestamp = data.lastViewedAt ? parseInt(data.lastViewedAt, 10) : NaN;
101
- this.lastViewedAt = isNaN(lastViewedAtTimestamp)
103
+ const lastViewedAtTimestamp = data.lastViewedAt
104
+ ? Number.parseInt(data.lastViewedAt, 10)
105
+ : Number.NaN;
106
+ this.lastViewedAt = Number.isNaN(lastViewedAtTimestamp)
102
107
  ? undefined
103
108
  : new Date(lastViewedAtTimestamp * 1000);
104
- const librarySectionIDInt = data.librarySectionID ? parseInt(data.librarySectionID, 10) : NaN;
105
- this.librarySectionID = isNaN(librarySectionIDInt)
109
+ const librarySectionIDInt = data.librarySectionID
110
+ ? Number.parseInt(data.librarySectionID, 10)
111
+ : Number.NaN;
112
+ this.librarySectionID = Number.isNaN(librarySectionIDInt)
106
113
  ? this.librarySectionID
107
114
  : librarySectionIDInt;
108
115
  this.librarySectionKey = data.librarySectionKey;
109
116
  this.librarySectionTitle = data.librarySectionTitle;
110
117
  // listType is handled by the getter
111
118
  const musicAnalysisVersionInt = data.musicAnalysisVersion
112
- ? parseInt(data.musicAnalysisVersion, 10)
113
- : NaN;
114
- this.musicAnalysisVersion = isNaN(musicAnalysisVersionInt)
119
+ ? Number.parseInt(data.musicAnalysisVersion, 10)
120
+ : Number.NaN;
121
+ this.musicAnalysisVersion = Number.isNaN(musicAnalysisVersionInt)
115
122
  ? undefined
116
123
  : musicAnalysisVersionInt;
117
124
  this.playlistItemID = data.playlistItemID;
118
125
  this.ratingKey = data.ratingKey;
119
- const ratingKeyInt = data.ratingKey ? parseInt(data.ratingKey, 10) : NaN;
120
- this.ratingKey = isNaN(ratingKeyInt) ? this.ratingKey : ratingKeyInt.toString();
126
+ const ratingKeyInt = data.ratingKey ? Number.parseInt(data.ratingKey, 10) : Number.NaN;
127
+ this.ratingKey = Number.isNaN(ratingKeyInt) ? this.ratingKey : ratingKeyInt.toString();
121
128
  this.summary = data.summary;
122
129
  this.thumb = data.thumb ? this.server.url(data.thumb, true)?.toString() : undefined;
123
130
  this.thumbBlurHash = data.thumbBlurHash;
124
131
  this.title = data.title ?? this.title;
125
132
  this.titleSort = data.titleSort ?? this.title;
126
133
  this.type = data.type ?? this.type;
127
- const updatedAtTimestamp = data.updatedAt ? parseInt(data.updatedAt, 10) : NaN;
128
- this.updatedAt = isNaN(updatedAtTimestamp) ? undefined : new Date(updatedAtTimestamp * 1000);
129
- const userRatingFloat = data.userRating ? parseFloat(data.userRating) : NaN;
130
- this.userRating = isNaN(userRatingFloat) ? undefined : userRatingFloat;
131
- const viewCountInt = data.viewCount !== undefined ? parseInt(data.viewCount, 10) : NaN;
132
- this.viewCount = isNaN(viewCountInt) ? 0 : viewCountInt;
134
+ const updatedAtTimestamp = data.updatedAt ? Number.parseInt(data.updatedAt, 10) : Number.NaN;
135
+ this.updatedAt = Number.isNaN(updatedAtTimestamp)
136
+ ? undefined
137
+ : new Date(updatedAtTimestamp * 1000);
138
+ const userRatingFloat = data.userRating ? Number.parseFloat(data.userRating) : Number.NaN;
139
+ this.userRating = Number.isNaN(userRatingFloat) ? undefined : userRatingFloat;
140
+ const viewCountInt = data.viewCount !== undefined ? Number.parseInt(data.viewCount, 10) : Number.NaN;
141
+ this.viewCount = Number.isNaN(viewCountInt) ? 0 : viewCountInt;
133
142
  // Map tag arrays like video.ts does
134
143
  this.fields = data.Field?.map((d) => new Field(this.server, d, undefined, this)) ?? [];
135
144
  this.images = data.Image?.map((d) => new Image(this.server, d, undefined, this)) ?? [];
@@ -153,8 +162,8 @@ export class Audio extends Playable {
153
162
  * Represents a single Track.
154
163
  */
155
164
  export class Track extends Audio {
156
- static { this.TAG = 'Track'; }
157
- static { this.TYPE = 'track'; }
165
+ static TAG = 'Track';
166
+ static TYPE = 'track';
158
167
  // Properties from Mixins (assuming, some might overlap with Audio)
159
168
  // userRating inherited from Audio
160
169
  // art inherited from Audio
@@ -238,7 +247,6 @@ export class Track extends Audio {
238
247
  */
239
248
  async sonicAdventure(to) {
240
249
  const section = await this.section();
241
- const hasSonicAdventure = (s) => typeof s.sonicAdventure === 'function';
242
250
  if (!hasSonicAdventure(section)) {
243
251
  throw new Error('Section does not support sonicAdventure');
244
252
  }
@@ -309,8 +317,8 @@ export class Track extends Audio {
309
317
  */
310
318
  export class Artist extends Audio {
311
319
  /* implements AdvancedSettingsMixin, SplitMergeMixin, UnmatchMatchMixin, ExtrasMixin, HubsMixin, RatingMixin, ArtMixin, PosterMixin, ThemeMixin, ArtistEditMixins */
312
- static { this.TAG = 'Directory'; }
313
- static { this.TYPE = 'artist'; }
320
+ static TAG = 'Directory';
321
+ static TYPE = 'artist';
314
322
  get locations() {
315
323
  // Replicate listAttrs logic (assuming Location tag with path attribute)
316
324
  return this._data?.Location?.map((loc) => loc.path).filter(Boolean) ?? [];
@@ -459,13 +467,15 @@ export class Artist extends Audio {
459
467
  */
460
468
  _loadData(data) {
461
469
  super._loadData(data);
462
- const albumSortInt = data.albumSort ? parseInt(String(data.albumSort), 10) : NaN;
463
- this.albumSort = isNaN(albumSortInt) ? -1 : albumSortInt;
464
- const audienceRatingFloat = data.audienceRating !== undefined ? parseFloat(String(data.audienceRating)) : NaN;
465
- this.audienceRating = isNaN(audienceRatingFloat) ? undefined : audienceRatingFloat;
470
+ const albumSortInt = data.albumSort ? Number.parseInt(String(data.albumSort), 10) : Number.NaN;
471
+ this.albumSort = Number.isNaN(albumSortInt) ? -1 : albumSortInt;
472
+ const audienceRatingFloat = data.audienceRating !== undefined
473
+ ? Number.parseFloat(String(data.audienceRating))
474
+ : Number.NaN;
475
+ this.audienceRating = Number.isNaN(audienceRatingFloat) ? undefined : audienceRatingFloat;
466
476
  this.key = data.key?.replace('/children', '');
467
- const ratingFloat = data.rating !== undefined ? parseFloat(String(data.rating)) : NaN;
468
- this.rating = isNaN(ratingFloat) ? undefined : ratingFloat;
477
+ const ratingFloat = data.rating !== undefined ? Number.parseFloat(String(data.rating)) : Number.NaN;
478
+ this.rating = Number.isNaN(ratingFloat) ? undefined : ratingFloat;
469
479
  this.theme = data.theme;
470
480
  this.countries = data.Country?.map(d => new Country(this.server, d, undefined, this));
471
481
  this.genres = data.Genre?.map(d => new Genre(this.server, d, undefined, this));
@@ -480,8 +490,8 @@ export class Artist extends Audio {
480
490
  * Represents a single Album.
481
491
  */
482
492
  export class Album extends Audio {
483
- static { this.TAG = 'Directory'; }
484
- static { this.TYPE = 'album'; }
493
+ static TAG = 'Directory';
494
+ static TYPE = 'album';
485
495
  constructor(server, data, initpath, parent) {
486
496
  super(server, data, initpath, parent);
487
497
  this._loadData(data);
@@ -551,7 +561,7 @@ export class Album extends Audio {
551
561
  ? new Date(data.originallyAvailableAt)
552
562
  : undefined;
553
563
  // Check if the date is valid
554
- if (this.originallyAvailableAt && isNaN(this.originallyAvailableAt.getTime())) {
564
+ if (this.originallyAvailableAt && Number.isNaN(this.originallyAvailableAt.getTime())) {
555
565
  this.originallyAvailableAt = undefined;
556
566
  }
557
567
  }
@@ -66,12 +66,12 @@ export interface TrackData {
66
66
  skipCount?: number;
67
67
  source?: string;
68
68
  viewOffset?: number;
69
- Chapter?: import('./video.types.js').ChapterData[];
69
+ Chapter?: Array<import('./video.types.js').ChapterData>;
70
70
  Collection?: MediaTagData[];
71
71
  Genre?: MediaTagData[];
72
72
  Guid?: MediaTagData[];
73
73
  Label?: MediaTagData[];
74
- Media?: import('./video.types.js').MediaData[];
74
+ Media?: Array<import('./video.types.js').MediaData>;
75
75
  }
76
76
  export interface ArtistData {
77
77
  key: string;
@@ -1,33 +1,30 @@
1
- import { URLSearchParams } from 'url';
1
+ import { URLSearchParams } from 'node:url';
2
2
  import { SearchResult, searchType } from '../search.js';
3
3
  import { getAgentIdentifier, ltrim, tagHelper } from '../util.js';
4
4
  import { PlexObject } from './plexObject.js';
5
5
  export class PartialPlexObject extends PlexObject {
6
- constructor() {
7
- super(...arguments);
8
- this._INCLUDES = {
9
- checkFiles: 0,
10
- includeAllConcerts: 0,
11
- includeBandwidths: 1,
12
- includeChapters: 1,
13
- includeChildren: 0,
14
- includeConcerts: 0,
15
- includeExternalMedia: 0,
16
- includeExtras: 0,
17
- includeFields: 'thumbBlurHash,artBlurHash',
18
- includeGeolocation: 1,
19
- includeLoudnessRamps: 1,
20
- includeMarkers: 1,
21
- includeOnDeck: 0,
22
- includePopularLeaves: 0,
23
- includePreferences: 0,
24
- includeRelated: 0,
25
- includeRelatedCount: 0,
26
- includeReviews: 0,
27
- includeStations: 0,
28
- };
29
- this._detailsKey = this._buildDetailsKey();
30
- }
6
+ _INCLUDES = {
7
+ checkFiles: 0,
8
+ includeAllConcerts: 0,
9
+ includeBandwidths: 1,
10
+ includeChapters: 1,
11
+ includeChildren: 0,
12
+ includeConcerts: 0,
13
+ includeExternalMedia: 0,
14
+ includeExtras: 0,
15
+ includeFields: 'thumbBlurHash,artBlurHash',
16
+ includeGeolocation: 1,
17
+ includeLoudnessRamps: 1,
18
+ includeMarkers: 1,
19
+ includeOnDeck: 0,
20
+ includePopularLeaves: 0,
21
+ includePreferences: 0,
22
+ includeRelated: 0,
23
+ includeRelatedCount: 0,
24
+ includeReviews: 0,
25
+ includeStations: 0,
26
+ };
27
+ _detailsKey = this._buildDetailsKey();
31
28
  /**
32
29
  * Tell Plex Media Server to performs analysis on it this item to gather
33
30
  * information. Analysis includes:
@@ -83,7 +80,7 @@ export class PartialPlexObject extends PlexObject {
83
80
  const key = `/library/metadata/${this.ratingKey}/match`;
84
81
  if (auto) {
85
82
  const autoMatch = await this.matches();
86
- if (autoMatch.length) {
83
+ if (autoMatch.length > 0) {
87
84
  searchResult = autoMatch[0];
88
85
  }
89
86
  else {
@@ -173,7 +170,7 @@ export class PartialPlexObject extends PlexObject {
173
170
  * @param maxresults Only return the specified number of results (optional).
174
171
  * @param mindate Min datetime to return results from.
175
172
  */
176
- async history(maxresults = 9999999, mindate) {
173
+ async history(maxresults = 9_999_999, mindate) {
177
174
  return this.server.history(maxresults, mindate, this.ratingKey);
178
175
  }
179
176
  async section() {
@@ -260,7 +257,7 @@ export class PartialPlexObject extends PlexObject {
260
257
  * @param remove If this is active remove the tags in items.
261
258
  */
262
259
  async _editTags(tag, items, locked = true, remove = false) {
263
- const value = this[tag + 's'];
260
+ const value = this[`${tag}s`];
264
261
  const existingCols = value?.filter((x) => x && remove).map((x) => x.tag) ?? [];
265
262
  const d = tagHelper(tag, [...existingCols, ...items], locked, remove);
266
263
  await this.edit(d);
@@ -1,4 +1,4 @@
1
- import type { PlayQueue as PlayQueueType } from '../playqueue.js';
1
+ import { type PlayQueue as PlayQueueType } from '../playqueue.js';
2
2
  import type { CreatePlayQueueOptions } from '../playqueue.types.js';
3
3
  import { PartialPlexObject } from './partialPlexObject.js';
4
4
  /**
@@ -1,12 +1,20 @@
1
- import { URLSearchParams } from 'url';
1
+ import { URLSearchParams } from 'node:url';
2
2
  /**
3
3
  * Base class for all? Plex objects
4
4
  */
5
5
  export class PlexObject {
6
+ server;
6
7
  /** xml element tag */
7
- static { this.TAG = null; }
8
+ static TAG = null;
8
9
  /** xml element type */
9
- static { this.TYPE = null; }
10
+ static TYPE = null;
11
+ /**
12
+ * WeakRef to the parent object that this object is built from.
13
+ */
14
+ parent;
15
+ _detailsKey;
16
+ initpath;
17
+ _INCLUDES;
10
18
  constructor(server, data, initpath, parent) {
11
19
  this.server = server;
12
20
  this.initpath = initpath ?? this.key;
@@ -53,8 +61,8 @@ export class PlexObject {
53
61
  params.set(k, (value === true ? 1 : value).toString());
54
62
  }
55
63
  }
56
- if ([...params.keys()].length) {
57
- detailsKey += '?' + params.toString();
64
+ if ([...params.keys()].length > 0) {
65
+ detailsKey += `?${params.toString()}`;
58
66
  }
59
67
  }
60
68
  return detailsKey;
@@ -1,4 +1,4 @@
1
- import { URL } from 'url';
1
+ import { URL } from 'node:url';
2
2
  import type { Player } from './client.types.js';
3
3
  export interface PlexOptions {
4
4
  /** (:class:`~plexapi.server.PlexServer`): PlexServer this client is connected to (optional). */
@@ -57,6 +57,7 @@ export declare class PlexClient {
57
57
  _token: string | null;
58
58
  TAG: string;
59
59
  key: string;
60
+ timeout: number;
60
61
  deviceClass?: string;
61
62
  machineIdentifier?: string;
62
63
  product?: string;
@@ -66,7 +67,6 @@ export declare class PlexClient {
66
67
  platform?: string;
67
68
  platformVersion?: string;
68
69
  title?: string;
69
- timeout: number;
70
70
  constructor(options?: PlexOptions);
71
71
  /**
72
72
  * Alias of reload as any subsequent requests to this client will be
@@ -1,5 +1,5 @@
1
+ import { URL, URLSearchParams } from 'node:url';
1
2
  import { ofetch } from 'ofetch';
2
- import { URL, URLSearchParams } from 'url';
3
3
  import { BASE_HEADERS, TIMEOUT } from './config.js';
4
4
  /**
5
5
  * Main class for interacting with a Plex client. This class can connect
@@ -32,17 +32,18 @@ import { BASE_HEADERS, TIMEOUT } from './config.js';
32
32
  * :func:`~plexapi.client.PlexClient.proxyThroughServer()` (default False).
33
33
  */
34
34
  export class PlexClient {
35
+ /**
36
+ * HTTP address of the client
37
+ */
38
+ _baseurl = null;
39
+ /**
40
+ * Token used to access this client
41
+ */
42
+ _token = null;
43
+ TAG = 'Player';
44
+ key = '/resources';
45
+ timeout;
35
46
  constructor(options = {}) {
36
- /**
37
- * HTTP address of the client
38
- */
39
- this._baseurl = null;
40
- /**
41
- * Token used to access this client
42
- */
43
- this._token = null;
44
- this.TAG = 'Player';
45
- this.key = '/resources';
46
47
  if (options.baseurl) {
47
48
  if (options.baseurl.endsWith('/')) {
48
49
  this._baseurl = options.baseurl.slice(0, -1);
@@ -1,4 +1,4 @@
1
- import os from 'os';
1
+ import os from 'node:os';
2
2
  import { getMAC, parseMAC } from '@ctrl/mac-address';
3
3
  // TODO: Load User Defined Config
4
4
  // const DEFAULT_CONFIG_PATH = os.path.expanduser('~/.config/plexapi/config.ini');
@@ -1,39 +1,39 @@
1
1
  export class BadRequest extends Error {
2
+ message = 'An invalid request, generally a user error.';
2
3
  constructor(message) {
3
4
  super(message);
4
- this.message = 'An invalid request, generally a user error.';
5
5
  this.name = 'BadRequest';
6
6
  this.message = message;
7
7
  }
8
8
  }
9
9
  export class NotFound extends Error {
10
+ message = 'Request media item or device is not found.';
10
11
  constructor(message) {
11
12
  super(message);
12
- this.message = 'Request media item or device is not found.';
13
13
  this.name = 'NotFound';
14
14
  this.message = message;
15
15
  }
16
16
  }
17
17
  export class UnknownType extends Error {
18
+ message = 'Unknown library type.';
18
19
  constructor(message) {
19
20
  super(message);
20
- this.message = 'Unknown library type.';
21
21
  this.name = 'UnknownType';
22
22
  this.message = message;
23
23
  }
24
24
  }
25
25
  export class Unsupported extends Error {
26
+ message = 'Unsupported client request.';
26
27
  constructor(message) {
27
28
  super(message);
28
- this.message = 'Unsupported client request.';
29
29
  this.name = 'Unsupported';
30
30
  this.message = message;
31
31
  }
32
32
  }
33
33
  export class Unauthorized extends Error {
34
+ message = 'Invalid username or password.';
34
35
  constructor(message) {
35
36
  super(message);
36
- this.message = 'Invalid username or password.';
37
37
  this.name = 'Unauthorized';
38
38
  this.message = message;
39
39
  }
@@ -365,7 +365,7 @@ export declare abstract class LibrarySection<SType = SectionType> extends PlexOb
365
365
  * Returns a list of playlists from this library section.
366
366
  */
367
367
  playlists(): Promise<Playlist[]>;
368
- collections(args?: Record<string, number | string | boolean>): Promise<Collections<SType>[]>;
368
+ collections(args?: Record<string, number | string | boolean>): Promise<Array<Collections<SType>>>;
369
369
  /**
370
370
  * Returns a list of available Folders for this library section.
371
371
  */