@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 +42 -32
- package/dist/src/audio.types.d.ts +2 -2
- package/dist/src/base/partialPlexObject.js +4 -4
- package/dist/src/base/playable.d.ts +1 -1
- package/dist/src/base/plexObject.js +4 -3
- package/dist/src/client.d.ts +1 -1
- package/dist/src/client.js +1 -1
- package/dist/src/config.js +1 -1
- package/dist/src/library.d.ts +1 -1
- package/dist/src/library.js +2 -2
- package/dist/src/media.js +23 -21
- package/dist/src/myplex.js +4 -4
- package/dist/src/playlist.js +13 -7
- package/dist/src/playqueue.js +1 -1
- package/dist/src/server.d.ts +1 -1
- package/dist/src/server.js +19 -12
- package/dist/src/settings.js +2 -2
- package/dist/src/util.d.ts +3 -3
- package/dist/src/util.js +10 -8
- package/dist/src/video.d.ts +1 -1
- package/package.json +15 -15
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
|
|
97
|
-
|
|
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
|
|
101
|
-
|
|
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
|
|
105
|
-
|
|
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)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
this.
|
|
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
|
|
465
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
65
|
-
|
|
64
|
+
const searchParams = params.toString();
|
|
65
|
+
if (searchParams.length > 0) {
|
|
66
|
+
detailsKey += `?${searchParams}`;
|
|
66
67
|
}
|
|
67
68
|
}
|
|
68
69
|
return detailsKey;
|
package/dist/src/client.d.ts
CHANGED
package/dist/src/client.js
CHANGED
package/dist/src/config.js
CHANGED
package/dist/src/library.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/src/library.js
CHANGED
|
@@ -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 =
|
|
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
|
|
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
|
|
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
|
|
418
|
-
|
|
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
|
}
|
package/dist/src/myplex.js
CHANGED
|
@@ -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 ?
|
|
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'
|
|
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
|
}
|
package/dist/src/playlist.js
CHANGED
|
@@ -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
|
-
|
|
14
|
+
}
|
|
15
|
+
case 'movie': {
|
|
15
16
|
return Movie;
|
|
16
|
-
|
|
17
|
+
}
|
|
18
|
+
case 'track': {
|
|
17
19
|
return Track;
|
|
18
|
-
|
|
20
|
+
}
|
|
21
|
+
case 'album': {
|
|
19
22
|
return Album;
|
|
20
|
-
|
|
23
|
+
}
|
|
24
|
+
case 'artist': {
|
|
21
25
|
return Artist;
|
|
22
|
-
|
|
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 {
|
package/dist/src/playqueue.js
CHANGED
|
@@ -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
|
|
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) {
|
package/dist/src/server.d.ts
CHANGED
package/dist/src/server.js
CHANGED
|
@@ -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
|
|
97
|
-
|
|
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 =
|
|
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
|
-
|
|
158
|
-
|
|
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
|
|
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
|
-
|
|
168
|
-
raw = await this.query(
|
|
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
|
|
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
|
-
|
|
272
|
+
url.hash = `!/server/${this.machineIdentifier}/${endpoint}${queryString}`;
|
|
269
273
|
}
|
|
270
|
-
|
|
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`;
|
package/dist/src/settings.js
CHANGED
|
@@ -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
|
|
77
|
+
throw new TypeError('Invalid type');
|
|
78
78
|
}
|
|
79
79
|
this._setValue = value;
|
|
80
80
|
}
|
package/dist/src/util.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
|
|
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.
|
|
18
|
+
agents.push(...identifiers);
|
|
18
19
|
}
|
|
19
20
|
throw new Error(`Couldnt find "${agent}" in agents list (${agents.join(', ')})`);
|
|
20
21
|
}
|
|
21
|
-
/**
|
|
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
|
-
|
|
44
|
-
return x.substr(start, end);
|
|
46
|
+
return x.slice(start);
|
|
45
47
|
}
|
|
46
48
|
export function lowerFirst(str) {
|
|
47
|
-
return str.charAt(0).
|
|
49
|
+
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
48
50
|
}
|
package/dist/src/video.d.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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": "
|
|
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.
|
|
52
|
-
"@vitest/coverage-v8": "4.0.
|
|
53
|
-
"execa": "9.6.
|
|
54
|
-
"globby": "
|
|
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.
|
|
58
|
-
"p-retry": "7.1.
|
|
59
|
-
"prettier": "3.
|
|
60
|
-
"tsx": "4.
|
|
61
|
-
"typedoc": "0.28.
|
|
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.
|
|
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.
|
|
94
|
+
"packageManager": "pnpm@10.26.1"
|
|
95
95
|
}
|