@ctrl/plex 3.11.0 → 3.12.1

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/audio.js CHANGED
@@ -1,7 +1,8 @@
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
  */
@@ -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)) ?? [];
@@ -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
  }
@@ -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));
@@ -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,4 +1,4 @@
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';
@@ -80,7 +80,7 @@ export class PartialPlexObject extends PlexObject {
80
80
  const key = `/library/metadata/${this.ratingKey}/match`;
81
81
  if (auto) {
82
82
  const autoMatch = await this.matches();
83
- if (autoMatch.length) {
83
+ if (autoMatch.length > 0) {
84
84
  searchResult = autoMatch[0];
85
85
  }
86
86
  else {
@@ -170,7 +170,7 @@ export class PartialPlexObject extends PlexObject {
170
170
  * @param maxresults Only return the specified number of results (optional).
171
171
  * @param mindate Min datetime to return results from.
172
172
  */
173
- async history(maxresults = 9999999, mindate) {
173
+ async history(maxresults = 9_999_999, mindate) {
174
174
  return this.server.history(maxresults, mindate, this.ratingKey);
175
175
  }
176
176
  async section() {
@@ -257,7 +257,7 @@ export class PartialPlexObject extends PlexObject {
257
257
  * @param remove If this is active remove the tags in items.
258
258
  */
259
259
  async _editTags(tag, items, locked = true, remove = false) {
260
- const value = this[tag + 's'];
260
+ const value = this[`${tag}s`];
261
261
  const existingCols = value?.filter((x) => x && remove).map((x) => x.tag) ?? [];
262
262
  const d = tagHelper(tag, [...existingCols, ...items], locked, remove);
263
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,4 +1,4 @@
1
- import { URLSearchParams } from 'url';
1
+ import { URLSearchParams } from 'node:url';
2
2
  /**
3
3
  * Base class for all? Plex objects
4
4
  */
@@ -61,8 +61,9 @@ export class PlexObject {
61
61
  params.set(k, (value === true ? 1 : value).toString());
62
62
  }
63
63
  }
64
- if ([...params.keys()].length) {
65
- detailsKey += '?' + params.toString();
64
+ const searchParams = params.toString();
65
+ if (searchParams.length > 0) {
66
+ detailsKey += `?${searchParams}`;
66
67
  }
67
68
  }
68
69
  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). */
@@ -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
@@ -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');
@@ -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
  */
@@ -1,4 +1,4 @@
1
- import { URLSearchParams } from 'url';
1
+ import { URLSearchParams } from 'node:url';
2
2
  import { PartialPlexObject } from './base/partialPlexObject.js';
3
3
  import { PlexObject } from './base/plexObject.js';
4
4
  import { Album, Artist, Track } from './audio.js';
@@ -206,7 +206,7 @@ export class Library {
206
206
  language,
207
207
  ...extra,
208
208
  });
209
- const url = '/library/sections?' + search.toString();
209
+ const url = `/library/sections?${search.toString()}`;
210
210
  return this.server.query(url, 'post');
211
211
  }
212
212
  /**
package/dist/src/media.js CHANGED
@@ -50,7 +50,7 @@ export class MediaPart extends PlexObject {
50
50
  const params = new URLSearchParams({ allParts: '1' });
51
51
  const streamId = typeof stream === 'number' ? stream : stream.id;
52
52
  params.set('audioStreamID', streamId.toString());
53
- await this.server.query(key + '?' + params.toString(), 'put');
53
+ await this.server.query(`${key}?${params.toString()}`, 'put');
54
54
  return this;
55
55
  }
56
56
  /**
@@ -62,7 +62,7 @@ export class MediaPart extends PlexObject {
62
62
  const params = new URLSearchParams({ allParts: '1' });
63
63
  const streamId = typeof stream === 'number' ? stream : stream.id;
64
64
  params.set('subtitleStreamID', streamId.toString());
65
- await this.server.query(key + '?' + params.toString(), 'put');
65
+ await this.server.query(`${key}?${params.toString()}`, 'put');
66
66
  return this;
67
67
  }
68
68
  /**
@@ -130,16 +130,16 @@ export class SubtitleStream extends MediaPartStream {
130
130
  super._loadData(data);
131
131
  this.canAutoSync = Boolean(data.canAutoSync); // Use !! for boolean casting from potential string/number
132
132
  this.container = data.container;
133
- this.forced = Boolean(parseInt(data.forced ?? '0', 10));
133
+ this.forced = Boolean(Number.parseInt(data.forced ?? '0', 10));
134
134
  this.format = data.format;
135
135
  this.headerCompression = data.headerCompression;
136
- this.hearingImpaired = Boolean(parseInt(data.hearingImpaired ?? '0', 10));
136
+ this.hearingImpaired = Boolean(Number.parseInt(data.hearingImpaired ?? '0', 10));
137
137
  this.perfectMatch = Boolean(data.perfectMatch);
138
138
  this.providerTitle = data.providerTitle;
139
- this.score = data.score ? parseInt(data.score, 10) : undefined;
139
+ this.score = data.score ? Number.parseInt(data.score, 10) : undefined;
140
140
  this.sourceKey = data.sourceKey;
141
141
  this.transient = data.transient;
142
- this.userID = data.userID ? parseInt(data.userID, 10) : undefined;
142
+ this.userID = data.userID ? Number.parseInt(data.userID, 10) : undefined;
143
143
  }
144
144
  }
145
145
  /**
@@ -151,9 +151,9 @@ export class LyricStream extends MediaPartStream {
151
151
  _loadData(data) {
152
152
  super._loadData(data);
153
153
  this.format = data.format;
154
- this.minLines = data.minLines ? parseInt(data.minLines, 10) : undefined;
154
+ this.minLines = data.minLines ? Number.parseInt(data.minLines, 10) : undefined;
155
155
  this.provider = data.provider;
156
- this.timed = Boolean(parseInt(data.timed ?? '0', 10));
156
+ this.timed = Boolean(Number.parseInt(data.timed ?? '0', 10));
157
157
  }
158
158
  }
159
159
  /**
@@ -408,23 +408,25 @@ export class AudioStream extends MediaPartStream {
408
408
  _loadData(data) {
409
409
  super._loadData(data);
410
410
  this.audioChannelLayout = data.audioChannelLayout;
411
- this.bitDepth = data.bitDepth ? parseInt(data.bitDepth, 10) : undefined;
411
+ this.bitDepth = data.bitDepth ? Number.parseInt(data.bitDepth, 10) : undefined;
412
412
  this.bitrateMode = data.bitrateMode;
413
- this.channels = data.channels ? parseInt(data.channels, 10) : undefined;
414
- this.duration = data.duration ? parseInt(data.duration, 10) : undefined;
413
+ this.channels = data.channels ? Number.parseInt(data.channels, 10) : undefined;
414
+ this.duration = data.duration ? Number.parseInt(data.duration, 10) : undefined;
415
415
  this.profile = data.profile;
416
- this.samplingRate = data.samplingRate ? parseInt(data.samplingRate, 10) : undefined;
417
- this.streamIdentifier = data.streamIdentifier ? parseInt(data.streamIdentifier, 10) : undefined;
418
- this.visualImpaired = Boolean(parseInt(data.visualImpaired ?? '0', 10));
416
+ this.samplingRate = data.samplingRate ? Number.parseInt(data.samplingRate, 10) : undefined;
417
+ this.streamIdentifier = data.streamIdentifier
418
+ ? Number.parseInt(data.streamIdentifier, 10)
419
+ : undefined;
420
+ this.visualImpaired = Boolean(Number.parseInt(data.visualImpaired ?? '0', 10));
419
421
  // Track only attributes
420
- this.albumGain = data.albumGain ? parseFloat(data.albumGain) : undefined;
421
- this.albumPeak = data.albumPeak ? parseFloat(data.albumPeak) : undefined;
422
- this.albumRange = data.albumRange ? parseFloat(data.albumRange) : undefined;
422
+ this.albumGain = data.albumGain ? Number.parseFloat(data.albumGain) : undefined;
423
+ this.albumPeak = data.albumPeak ? Number.parseFloat(data.albumPeak) : undefined;
424
+ this.albumRange = data.albumRange ? Number.parseFloat(data.albumRange) : undefined;
423
425
  this.endRamp = data.endRamp;
424
- this.gain = data.gain ? parseFloat(data.gain) : undefined;
425
- this.loudness = data.loudness ? parseFloat(data.loudness) : undefined;
426
- this.lra = data.lra ? parseFloat(data.lra) : undefined;
427
- this.peak = data.peak ? parseFloat(data.peak) : undefined;
426
+ this.gain = data.gain ? Number.parseFloat(data.gain) : undefined;
427
+ this.loudness = data.loudness ? Number.parseFloat(data.loudness) : undefined;
428
+ this.lra = data.lra ? Number.parseFloat(data.lra) : undefined;
429
+ this.peak = data.peak ? Number.parseFloat(data.peak) : undefined;
428
430
  this.startRamp = data.startRamp;
429
431
  }
430
432
  }
@@ -43,7 +43,7 @@ export class MyPlexAccount {
43
43
  });
44
44
  return {
45
45
  ...pin,
46
- uri: `https://app.plex.tv/auth#?clientID=${encodeURIComponent(clientIdentifier)}&code=${encodeURIComponent(pin.code)}&context%5Bdevice%5D%5Bproduct%5D=${encodeURIComponent(appName)}${forwardUrl ? '&forwardUrl=' + encodeURIComponent(forwardUrl) : ''}`,
46
+ uri: `https://app.plex.tv/auth#?clientID=${encodeURIComponent(clientIdentifier)}&code=${encodeURIComponent(pin.code)}&context%5Bdevice%5D%5Bproduct%5D=${encodeURIComponent(appName)}${forwardUrl ? `&forwardUrl=${encodeURIComponent(forwardUrl)}` : ''}`,
47
47
  };
48
48
  }
49
49
  /**
@@ -220,7 +220,7 @@ export class MyPlexAccount {
220
220
  */
221
221
  async claimToken() {
222
222
  const url = 'https://plex.tv/api/claim/token.json';
223
- const response = await this.query(url, 'get', undefined);
223
+ const response = await this.query(url, 'get');
224
224
  return response.token;
225
225
  }
226
226
  /**
@@ -422,8 +422,8 @@ export class MyPlexDevice extends PlexObject {
422
422
  });
423
423
  this.screenResolution = data.$.screenResolution;
424
424
  this.screenDensity = data.$.screenDensity;
425
- this.createdAt = new Date(parseInt(data.$.createdAt, 10));
426
- this.lastSeenAt = new Date(parseInt(data.$.lastSeenAt, 10));
425
+ this.createdAt = new Date(Number.parseInt(data.$.createdAt, 10));
426
+ this.lastSeenAt = new Date(Number.parseInt(data.$.lastSeenAt, 10));
427
427
  this.connections = data.Connection?.map(connection => connection.$.uri);
428
428
  }
429
429
  }
@@ -1,4 +1,4 @@
1
- import { URLSearchParams } from 'url';
1
+ import { URLSearchParams } from 'node:url';
2
2
  import { Playable } from './base/playable.js';
3
3
  import { Album, Artist, Track } from './audio.js';
4
4
  import { fetchItems } from './baseFunctionality.js';
@@ -9,18 +9,24 @@ import { Episode, Movie } from './video.js';
9
9
  */
10
10
  function contentClass(data) {
11
11
  switch (data.type) {
12
- case 'episode':
12
+ case 'episode': {
13
13
  return Episode;
14
- case 'movie':
14
+ }
15
+ case 'movie': {
15
16
  return Movie;
16
- case 'track':
17
+ }
18
+ case 'track': {
17
19
  return Track;
18
- case 'album':
20
+ }
21
+ case 'album': {
19
22
  return Album;
20
- case 'artist':
23
+ }
24
+ case 'artist': {
21
25
  return Artist;
22
- default:
26
+ }
27
+ default: {
23
28
  throw new Error(`Media type '${data.type}' not implemented`);
29
+ }
24
30
  }
25
31
  }
26
32
  export class Playlist extends Playable {
@@ -255,7 +255,7 @@ export class PlayQueue extends PlexObject {
255
255
  this.playQueueSourceURI = data.playQueueSourceURI;
256
256
  this.playQueueTotalCount = data.playQueueTotalCount;
257
257
  this.playQueueVersion = data.playQueueVersion;
258
- this.size = data.size || this.playQueueTotalCount;
258
+ this.size = data.size > 0 ? data.size : this.playQueueTotalCount;
259
259
  // selectedItem will be set lazily when accessing items
260
260
  }
261
261
  _invalidateCacheAndLoadData(data) {
@@ -1,4 +1,4 @@
1
- import { URL, URLSearchParams } from 'url';
1
+ import { URL, URLSearchParams } from 'node:url';
2
2
  import { Playable } from './base/playable.js';
3
3
  import { PlexClient } from './client.js';
4
4
  import { Hub, Library } from './library.js';
@@ -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 { fetchItem, fetchItems } from './baseFunctionality.js';
4
4
  import { PlexClient } from './client.js';
5
5
  import { BASE_HEADERS, TIMEOUT, X_PLEX_CONTAINER_SIZE } from './config.js';
@@ -93,8 +93,9 @@ export class PlexServer {
93
93
  if (limit) {
94
94
  params.limit = limit.toString();
95
95
  }
96
- const key = '/hubs/search?' + new URLSearchParams(params).toString();
97
- const hubs = await fetchItems(this, key, undefined, Hub, this);
96
+ const url = new URL('/hubs/search', this.baseurl);
97
+ url.search = new URLSearchParams(params).toString();
98
+ const hubs = await fetchItems(this, url.pathname + url.search, undefined, Hub, this);
98
99
  return hubs;
99
100
  }
100
101
  /**
@@ -137,7 +138,7 @@ export class PlexServer {
137
138
  * @param accountId request history for a specific account ID.
138
139
  * @param librarySectionId request history for a specific library section ID.
139
140
  */
140
- async history(maxresults = 9999999, mindate, ratingKey, accountId, librarySectionId) {
141
+ async history(maxresults = 9_999_999, mindate, ratingKey, accountId, librarySectionId) {
141
142
  const args = { sort: 'viewedAt:desc' };
142
143
  if (ratingKey !== undefined) {
143
144
  args.metadataItemID = ratingKey.toString();
@@ -154,21 +155,22 @@ export class PlexServer {
154
155
  args['X-Plex-Container-Start'] = '0';
155
156
  args['X-Plex-Container-Size'] = Math.min(X_PLEX_CONTAINER_SIZE, maxresults).toString();
156
157
  let results = [];
157
- let key = '/status/sessions/history/all?' + new URLSearchParams(args).toString();
158
- let raw = await this.query(key);
158
+ const url = new URL('/status/sessions/history/all', this.baseurl);
159
+ url.search = new URLSearchParams(args).toString();
160
+ let raw = await this.query(url.pathname + url.search);
159
161
  const totalResults = raw.MediaContainer.totalSize;
160
162
  // Filter out null/undefined items from the metadata
161
163
  const validMetadata = raw.MediaContainer.Metadata?.filter(Boolean) ?? [];
162
- results = results.concat(validMetadata);
164
+ results.push(...validMetadata);
163
165
  while (results.length <= totalResults &&
164
166
  X_PLEX_CONTAINER_SIZE === raw.MediaContainer.size &&
165
167
  maxresults > results.length) {
166
168
  args['X-Plex-Container-Start'] = (Number(args['X-Plex-Container-Start']) + Number(args['X-Plex-Container-Size'])).toString();
167
- key = '/status/sessions/history/all?' + new URLSearchParams(args).toString();
168
- raw = await this.query(key);
169
+ url.search = new URLSearchParams(args).toString();
170
+ raw = await this.query(url.pathname + url.search);
169
171
  // Filter out null/undefined items from the metadata
170
172
  const validMetadata = raw.MediaContainer.Metadata?.filter(item => item != null) ?? [];
171
- results = results.concat(validMetadata);
173
+ results.push(...validMetadata);
172
174
  }
173
175
  return results;
174
176
  }
@@ -264,10 +266,15 @@ export class PlexServer {
264
266
  * None for server, 'playlist' for playlists, 'details' for all other media types.
265
267
  */
266
268
  _buildWebURL(base = 'https://app.plex.tv/desktop/', endpoint, params) {
269
+ const url = new URL(base);
270
+ const queryString = params?.toString() ? `?${params.toString()}` : '';
267
271
  if (endpoint) {
268
- return `${base}#!/server/${this.machineIdentifier}/${endpoint}?${params?.toString()}`;
272
+ url.hash = `!/server/${this.machineIdentifier}/${endpoint}${queryString}`;
269
273
  }
270
- return `${base}#!/media/${this.machineIdentifier}/com.plexapp.plugins.library?${params?.toString()}`;
274
+ else {
275
+ url.hash = `!/media/${this.machineIdentifier}/com.plexapp.plugins.library${queryString}`;
276
+ }
277
+ return url.toString();
271
278
  }
272
279
  _uriRoot() {
273
280
  return `server://${this.machineIdentifier}/com.plexapp.plugins.library`;
@@ -1,4 +1,4 @@
1
- import { URLSearchParams } from 'url';
1
+ import { URLSearchParams } from 'node:url';
2
2
  import { PlexObject } from './base/plexObject.js';
3
3
  import { NotFound } from './exceptions.js';
4
4
  import { lowerFirst } from './util.js';
@@ -74,7 +74,7 @@ export class Setting extends PlexObject {
74
74
  */
75
75
  set(value) {
76
76
  if (typeof value !== typeof this.value) {
77
- throw new Error('Invalid type');
77
+ throw new TypeError('Invalid type');
78
78
  }
79
79
  this._setValue = value;
80
80
  }
@@ -10,11 +10,11 @@ export interface MetadataContainer<T extends {
10
10
  export declare function rsplit(str: string, sep: string, maxsplit: number): string[];
11
11
  /**
12
12
  * Return the full agent identifier from a short identifier, name, or confirm full identifier.
13
- * @param section
14
- * @param agent
15
13
  */
16
14
  export declare function getAgentIdentifier(section: Section, agent: string): Promise<string>;
17
- /** Simple tag helper for editing a object. */
15
+ /**
16
+ * Simple tag helper for editing a object.
17
+ */
18
18
  export declare function tagHelper(tag: string, items: string[], locked?: boolean, remove?: boolean): Record<string, string | number>;
19
19
  export declare function ltrim(x: string, characters: string[]): string;
20
20
  export declare function lowerFirst(str: string): string;
package/dist/src/util.js CHANGED
@@ -1,11 +1,12 @@
1
1
  export function rsplit(str, sep, maxsplit) {
2
2
  const split = str.split(sep);
3
- return maxsplit ? [split.slice(0, -maxsplit).join(sep)].concat(split.slice(-maxsplit)) : split;
3
+ if (maxsplit) {
4
+ return [split.slice(0, -maxsplit).join(sep), ...split.slice(-maxsplit)];
5
+ }
6
+ return split;
4
7
  }
5
8
  /**
6
9
  * Return the full agent identifier from a short identifier, name, or confirm full identifier.
7
- * @param section
8
- * @param agent
9
10
  */
10
11
  export async function getAgentIdentifier(section, agent) {
11
12
  const agents = [];
@@ -14,11 +15,13 @@ export async function getAgentIdentifier(section, agent) {
14
15
  if (identifiers.includes(agent)) {
15
16
  return ag.identifier;
16
17
  }
17
- agents.concat(identifiers);
18
+ agents.push(...identifiers);
18
19
  }
19
20
  throw new Error(`Couldnt find "${agent}" in agents list (${agents.join(', ')})`);
20
21
  }
21
- /** Simple tag helper for editing a object. */
22
+ /**
23
+ * Simple tag helper for editing a object.
24
+ */
22
25
  export function tagHelper(tag, items, locked = true, remove = false) {
23
26
  const data = {};
24
27
  if (remove) {
@@ -40,9 +43,8 @@ export function ltrim(x, characters) {
40
43
  while (characters.includes(x[start])) {
41
44
  start += 1;
42
45
  }
43
- const end = x.length - 1;
44
- return x.substr(start, end);
46
+ return x.slice(start);
45
47
  }
46
48
  export function lowerFirst(str) {
47
- return str.charAt(0).toUpperCase() + str.slice(1);
49
+ return str.charAt(0).toLowerCase() + str.slice(1);
48
50
  }
@@ -1,4 +1,4 @@
1
- import type { URL } from 'url';
1
+ import type { URL } from 'node:url';
2
2
  import { Playable } from './base/playable.js';
3
3
  import type { ExtrasData, FullShowData, MovieData, ShowData } from './library.types.js';
4
4
  import { Chapter, Collection, Country, Director, Genre, Guid, Marker, Media, Poster, Producer, Rating, Role, Similar, Writer } from './media.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctrl/plex",
3
- "version": "3.11.0",
3
+ "version": "3.12.1",
4
4
  "description": "plex api client in typescript using ofetch",
5
5
  "author": "Scott Cooper <scttcper@gmail.com>",
6
6
  "publishConfig": {
@@ -36,31 +36,31 @@
36
36
  "@ctrl/mac-address": "^3.1.0",
37
37
  "ofetch": "^1.5.1",
38
38
  "p-any": "^4.0.0",
39
- "type-fest": "^5.2.0",
39
+ "type-fest": "^5.3.1",
40
40
  "ws": "^8.18.3",
41
41
  "xml2js": "^0.6.2"
42
42
  },
43
43
  "devDependencies": {
44
- "@ctrl/oxlint-config": "1.3.2",
44
+ "@ctrl/oxlint-config": "1.3.3",
45
45
  "@ctrl/video-filename-parser": "5.4.1",
46
46
  "@sindresorhus/tsconfig": "8.1.0",
47
47
  "@trivago/prettier-plugin-sort-imports": "6.0.0",
48
- "@types/node": "24.10.1",
48
+ "@types/node": "25.0.3",
49
49
  "@types/ws": "8.18.1",
50
50
  "@types/xml2js": "0.4.14",
51
- "@types/yargs": "17.0.34",
52
- "@vitest/coverage-v8": "4.0.8",
53
- "execa": "9.6.0",
54
- "globby": "15.0.0",
51
+ "@types/yargs": "17.0.35",
52
+ "@vitest/coverage-v8": "4.0.16",
53
+ "execa": "9.6.1",
54
+ "globby": "16.1.0",
55
55
  "make-dir": "5.1.0",
56
56
  "ora": "9.0.0",
57
- "oxlint": "1.28.0",
58
- "p-retry": "7.1.0",
59
- "prettier": "3.6.2",
60
- "tsx": "4.20.6",
61
- "typedoc": "0.28.14",
57
+ "oxlint": "1.35.0",
58
+ "p-retry": "7.1.1",
59
+ "prettier": "3.7.4",
60
+ "tsx": "4.21.0",
61
+ "typedoc": "0.28.15",
62
62
  "typescript": "5.9.3",
63
- "vitest": "4.0.8",
63
+ "vitest": "4.0.16",
64
64
  "yargs": "18.0.0"
65
65
  },
66
66
  "release": {
@@ -91,5 +91,5 @@
91
91
  "engines": {
92
92
  "node": ">=18"
93
93
  },
94
- "packageManager": "pnpm@10.22.0"
94
+ "packageManager": "pnpm@10.26.1"
95
95
  }