@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
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# @ctrl/plex
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@ctrl/plex)
|
|
4
|
+
[](https://codecov.io/gh/scttcper/plex)
|
|
5
|
+
|
|
3
6
|
> A TypeScript [Plex](https://www.plex.tv/) API client based on [pkkid/python-plexapi](https://github.com/pkkid/python-plexapi)
|
|
4
7
|
|
|
5
8
|
### Install
|
|
@@ -71,8 +74,6 @@ and accessing properties normally cannot make requests either.
|
|
|
71
74
|
|
|
72
75
|
Tests are run against a real instance of plex.
|
|
73
76
|
|
|
74
|
-
Start docker container [scttcper/plex-with-media](https://hub.docker.com/r/scttcper/plex-with-media)
|
|
75
|
-
|
|
76
77
|
Setup test environment variables, create a plex account just for testing. Using a real account will break everything
|
|
77
78
|
|
|
78
79
|
```sh
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import WebSocket from 'ws';
|
|
2
|
+
import { AlertTypes } from './alert.types.js';
|
|
3
|
+
import { PlexServer } from './server.js';
|
|
4
|
+
export declare class AlertListener {
|
|
5
|
+
private readonly server;
|
|
6
|
+
callback: (data: AlertTypes) => void;
|
|
7
|
+
key: string;
|
|
8
|
+
_ws?: WebSocket;
|
|
9
|
+
constructor(server: PlexServer, callback: (data: AlertTypes) => void);
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
stop(): void;
|
|
12
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import WebSocket from 'ws';
|
|
2
|
+
export class AlertListener {
|
|
3
|
+
constructor(server, callback) {
|
|
4
|
+
this.server = server;
|
|
5
|
+
this.callback = callback;
|
|
6
|
+
this.key = '/:/websockets/notifications';
|
|
7
|
+
}
|
|
8
|
+
async run() {
|
|
9
|
+
const url = this.server.url(this.key, true).toString().replace('http', 'ws');
|
|
10
|
+
this._ws = new WebSocket(url);
|
|
11
|
+
this._ws.on('message', (buffer) => {
|
|
12
|
+
try {
|
|
13
|
+
const data = JSON.parse(buffer.toString());
|
|
14
|
+
this.callback(data.NotificationContainer);
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
console.error(err);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return new Promise(resolve => {
|
|
21
|
+
this._ws.on('open', () => {
|
|
22
|
+
resolve();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
stop() {
|
|
27
|
+
this._ws?.close();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export interface NotificationContainer<T> {
|
|
2
|
+
NotificationContainer: T;
|
|
3
|
+
}
|
|
4
|
+
export interface ActivityNotification {
|
|
5
|
+
type: 'activity';
|
|
6
|
+
size: number;
|
|
7
|
+
ActivityNotification: Array<{
|
|
8
|
+
event: string;
|
|
9
|
+
uuid: string;
|
|
10
|
+
Activity: {
|
|
11
|
+
uuid: string;
|
|
12
|
+
type: 'library.update.section';
|
|
13
|
+
cancellable: false;
|
|
14
|
+
userID: 1;
|
|
15
|
+
title: string;
|
|
16
|
+
subtitle: string;
|
|
17
|
+
progress: 0;
|
|
18
|
+
};
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
export interface StatusNotification {
|
|
22
|
+
type: 'status';
|
|
23
|
+
size: number;
|
|
24
|
+
StatusNotification: Array<{
|
|
25
|
+
title: string;
|
|
26
|
+
description: string;
|
|
27
|
+
notificationName: string;
|
|
28
|
+
}>;
|
|
29
|
+
}
|
|
30
|
+
export interface TimelineNotification {
|
|
31
|
+
type: 'timeline';
|
|
32
|
+
size: number;
|
|
33
|
+
TimelineEntry: Array<{
|
|
34
|
+
/** eg com.plexapp.plugins.library */
|
|
35
|
+
identifier: string;
|
|
36
|
+
sectionID: string;
|
|
37
|
+
itemID: string;
|
|
38
|
+
type: number;
|
|
39
|
+
title: string;
|
|
40
|
+
state: number;
|
|
41
|
+
updatedAt: number;
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
44
|
+
export interface ReachabilityNotification {
|
|
45
|
+
type: 'reachability';
|
|
46
|
+
size: number;
|
|
47
|
+
TimelineEntry: Array<{
|
|
48
|
+
reachability: false;
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
51
|
+
export interface BackgroundProcessingQueueEventNotification {
|
|
52
|
+
type: 'backgroundProcessingQueue';
|
|
53
|
+
size: number;
|
|
54
|
+
TimelineEntry: Array<{
|
|
55
|
+
queueID: number;
|
|
56
|
+
event: 'queueRegenerated';
|
|
57
|
+
}>;
|
|
58
|
+
}
|
|
59
|
+
export type AlertTypes = ActivityNotification | StatusNotification | TimelineNotification | ReachabilityNotification | BackgroundProcessingQueueEventNotification;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { SearchResult } from '../search.js';
|
|
2
|
+
import { PlexObject } from './plexObject.js';
|
|
3
3
|
export declare abstract class PartialPlexObject extends PlexObject {
|
|
4
|
-
|
|
5
|
-
title?: string;
|
|
6
|
-
type?: string;
|
|
7
|
-
year?: number;
|
|
8
|
-
librarySectionID?: number;
|
|
9
|
-
protected _detailsKey: string;
|
|
10
|
-
protected _INCLUDES: {
|
|
4
|
+
_INCLUDES: {
|
|
11
5
|
checkFiles: number;
|
|
12
6
|
includeAllConcerts: number;
|
|
13
7
|
includeBandwidths: number;
|
|
@@ -28,6 +22,12 @@ export declare abstract class PartialPlexObject extends PlexObject {
|
|
|
28
22
|
includeReviews: number;
|
|
29
23
|
includeStations: number;
|
|
30
24
|
};
|
|
25
|
+
ratingKey?: string;
|
|
26
|
+
title?: string;
|
|
27
|
+
type?: string;
|
|
28
|
+
year?: number;
|
|
29
|
+
librarySectionID?: number;
|
|
30
|
+
protected _detailsKey: string;
|
|
31
31
|
/**
|
|
32
32
|
* Tell Plex Media Server to performs analysis on it this item to gather
|
|
33
33
|
* information. Analysis includes:
|
|
@@ -99,8 +99,8 @@ export declare abstract class PartialPlexObject extends PlexObject {
|
|
|
99
99
|
* @param maxresults Only return the specified number of results (optional).
|
|
100
100
|
* @param mindate Min datetime to return results from.
|
|
101
101
|
*/
|
|
102
|
-
history(maxresults?: number, mindate?: Date): Promise<import("../server.types").HistoryMetadatum[]>;
|
|
103
|
-
section(): Promise<import("
|
|
102
|
+
history(maxresults?: number, mindate?: Date): Promise<import("../server.types.js").HistoryMetadatum[]>;
|
|
103
|
+
section(): Promise<import("../library.js").Section>;
|
|
104
104
|
/**
|
|
105
105
|
* Delete a media element. This has to be enabled under settings > server > library in plex webui.
|
|
106
106
|
*/
|
|
@@ -117,6 +117,7 @@ export declare abstract class PartialPlexObject extends PlexObject {
|
|
|
117
117
|
addGenre(genres: string[]): Promise<void>;
|
|
118
118
|
/** Remove a genre(s). */
|
|
119
119
|
removeGenre(genres: string[]): Promise<void>;
|
|
120
|
+
getWebURL(base?: string): string;
|
|
120
121
|
/**
|
|
121
122
|
* Edit an object.
|
|
122
123
|
* @param changeObj Obj of settings to edit.
|
|
@@ -128,6 +129,12 @@ export declare abstract class PartialPlexObject extends PlexObject {
|
|
|
128
129
|
* 'collection.locked': 0}
|
|
129
130
|
*/
|
|
130
131
|
edit(changeObj: Record<string, string | number>): Promise<void>;
|
|
132
|
+
protected abstract _loadFullData(data: any): void;
|
|
133
|
+
/**
|
|
134
|
+
* Get the Plex Web URL with the correct parameters.
|
|
135
|
+
* Private method to allow overriding parameters from subclasses.
|
|
136
|
+
*/
|
|
137
|
+
private _getWebURL;
|
|
131
138
|
/**
|
|
132
139
|
* Helper to edit and refresh a tags.
|
|
133
140
|
* @param tag tag name
|
|
@@ -136,5 +143,4 @@ export declare abstract class PartialPlexObject extends PlexObject {
|
|
|
136
143
|
* @param remove If this is active remove the tags in items.
|
|
137
144
|
*/
|
|
138
145
|
private _editTags;
|
|
139
|
-
protected abstract _loadFullData(data: any): void;
|
|
140
146
|
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const search_1 = require("../search");
|
|
7
|
-
const util_1 = require("../util");
|
|
8
|
-
class PartialPlexObject extends plexObject_1.PlexObject {
|
|
1
|
+
import { URLSearchParams } from 'url';
|
|
2
|
+
import { SearchResult, searchType } from '../search.js';
|
|
3
|
+
import { getAgentIdentifier, ltrim, tagHelper } from '../util.js';
|
|
4
|
+
import { PlexObject } from './plexObject.js';
|
|
5
|
+
export class PartialPlexObject extends PlexObject {
|
|
9
6
|
constructor() {
|
|
10
7
|
super(...arguments);
|
|
11
|
-
this._detailsKey = this._buildDetailsKey();
|
|
12
8
|
this._INCLUDES = {
|
|
13
9
|
checkFiles: 1,
|
|
14
10
|
includeAllConcerts: 1,
|
|
@@ -30,6 +26,7 @@ class PartialPlexObject extends plexObject_1.PlexObject {
|
|
|
30
26
|
includeReviews: 1,
|
|
31
27
|
includeStations: 1,
|
|
32
28
|
};
|
|
29
|
+
this._detailsKey = this._buildDetailsKey();
|
|
33
30
|
}
|
|
34
31
|
/**
|
|
35
32
|
* Tell Plex Media Server to performs analysis on it this item to gather
|
|
@@ -50,22 +47,21 @@ class PartialPlexObject extends plexObject_1.PlexObject {
|
|
|
50
47
|
* 'Skip Intro' button in clients.
|
|
51
48
|
*/
|
|
52
49
|
async analyze() {
|
|
53
|
-
const key = `/${
|
|
50
|
+
const key = `/${ltrim(this.key, ['/'])}/analyze`;
|
|
54
51
|
await this.server.query(key, 'put');
|
|
55
52
|
}
|
|
56
53
|
/**
|
|
57
54
|
* load full data / reload the data for this object from this.key.
|
|
58
55
|
*/
|
|
59
56
|
async reload(ekey, args) {
|
|
60
|
-
var _a;
|
|
61
57
|
this._detailsKey = this._buildDetailsKey(args);
|
|
62
|
-
const key =
|
|
58
|
+
const key = ekey ?? this._detailsKey ?? this.key;
|
|
63
59
|
if (!key) {
|
|
64
60
|
throw new Error('Cannot reload an object not built from a URL');
|
|
65
61
|
}
|
|
66
62
|
this.initpath = key;
|
|
67
63
|
const data = await this.server.query(key);
|
|
68
|
-
const innerData = data.MediaContainer
|
|
64
|
+
const innerData = data.MediaContainer ?? data;
|
|
69
65
|
this._loadFullData(innerData);
|
|
70
66
|
}
|
|
71
67
|
/**
|
|
@@ -97,7 +93,7 @@ class PartialPlexObject extends plexObject_1.PlexObject {
|
|
|
97
93
|
else if (!searchResult) {
|
|
98
94
|
throw new Error('Must either provide a searchResult or set auto parameter to true');
|
|
99
95
|
}
|
|
100
|
-
const params = new
|
|
96
|
+
const params = new URLSearchParams({ guid: searchResult.guid, name: searchResult.name });
|
|
101
97
|
const url = `${key}?${params.toString()}`;
|
|
102
98
|
await this.server.query(url, 'put');
|
|
103
99
|
}
|
|
@@ -129,11 +125,11 @@ class PartialPlexObject extends plexObject_1.PlexObject {
|
|
|
129
125
|
*/
|
|
130
126
|
async matches(agent, title, year, language) {
|
|
131
127
|
const key = `/library/metadata/${this.ratingKey}/matches`;
|
|
132
|
-
const params = new
|
|
128
|
+
const params = new URLSearchParams({ manual: '1' });
|
|
133
129
|
if (agent && [title, year, language].some(x => x)) {
|
|
134
130
|
const section = await this.section();
|
|
135
131
|
params.append('language', section.language);
|
|
136
|
-
const ident = await
|
|
132
|
+
const ident = await getAgentIdentifier(section, agent);
|
|
137
133
|
params.append('agent', ident);
|
|
138
134
|
}
|
|
139
135
|
else if ([agent, title, year, language].some(x => x)) {
|
|
@@ -165,7 +161,7 @@ class PartialPlexObject extends plexObject_1.PlexObject {
|
|
|
165
161
|
}
|
|
166
162
|
}
|
|
167
163
|
const data = await this.server.query(`${key}?${params.toString()}`, 'get');
|
|
168
|
-
return data.MediaContainer.SearchResult.map(r => new
|
|
164
|
+
return data.MediaContainer.SearchResult.map(r => new SearchResult(this.server, r));
|
|
169
165
|
}
|
|
170
166
|
/** Unmatches metadata match from object. */
|
|
171
167
|
async unmatch() {
|
|
@@ -213,6 +209,9 @@ class PartialPlexObject extends plexObject_1.PlexObject {
|
|
|
213
209
|
async removeGenre(genres) {
|
|
214
210
|
await this._editTags('genre', genres, undefined, true);
|
|
215
211
|
}
|
|
212
|
+
getWebURL(base) {
|
|
213
|
+
return this._getWebURL(base);
|
|
214
|
+
}
|
|
216
215
|
/**
|
|
217
216
|
* Edit an object.
|
|
218
217
|
* @param changeObj Obj of settings to edit.
|
|
@@ -237,13 +236,22 @@ class PartialPlexObject extends plexObject_1.PlexObject {
|
|
|
237
236
|
changeObj.id = this.ratingKey;
|
|
238
237
|
}
|
|
239
238
|
if (changeObj.type === undefined) {
|
|
240
|
-
changeObj.type =
|
|
239
|
+
changeObj.type = searchType(this.type);
|
|
241
240
|
}
|
|
242
241
|
const strObj = Object.fromEntries(Object.entries(changeObj).map(([key, value]) => [key, value.toString()]));
|
|
243
|
-
const params = new
|
|
242
|
+
const params = new URLSearchParams(strObj);
|
|
244
243
|
const url = this.server.url(`/library/sections/${this.librarySectionID}/all`, true, params);
|
|
245
244
|
await this.server.query(url.toString(), 'put');
|
|
246
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Get the Plex Web URL with the correct parameters.
|
|
248
|
+
* Private method to allow overriding parameters from subclasses.
|
|
249
|
+
*/
|
|
250
|
+
_getWebURL(base) {
|
|
251
|
+
const params = new URLSearchParams();
|
|
252
|
+
params.append('key', this.key);
|
|
253
|
+
return this.server._buildWebURL(base, 'details', params);
|
|
254
|
+
}
|
|
247
255
|
/**
|
|
248
256
|
* Helper to edit and refresh a tags.
|
|
249
257
|
* @param tag tag name
|
|
@@ -252,12 +260,10 @@ class PartialPlexObject extends plexObject_1.PlexObject {
|
|
|
252
260
|
* @param remove If this is active remove the tags in items.
|
|
253
261
|
*/
|
|
254
262
|
async _editTags(tag, items, locked = true, remove = false) {
|
|
255
|
-
var _a;
|
|
256
263
|
const value = this[tag + 's'];
|
|
257
|
-
const existingCols =
|
|
258
|
-
const d =
|
|
264
|
+
const existingCols = value?.filter((x) => x && remove).map((x) => x.tag) ?? [];
|
|
265
|
+
const d = tagHelper(tag, [...existingCols, ...items], locked, remove);
|
|
259
266
|
await this.edit(d);
|
|
260
267
|
await this.reload();
|
|
261
268
|
}
|
|
262
269
|
}
|
|
263
|
-
exports.PartialPlexObject = PartialPlexObject;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PartialPlexObject } from './partialPlexObject';
|
|
1
|
+
import { PartialPlexObject } from './partialPlexObject.js';
|
|
2
2
|
/**
|
|
3
3
|
* This is a general place to store functions specific to media that is Playable.
|
|
4
4
|
* Things were getting mixed up a bit when dealing with Shows, Season, Artists,
|
|
@@ -18,5 +18,5 @@ export declare abstract class Playable extends PartialPlexObject {
|
|
|
18
18
|
/** (datetime): Datetime item was last viewed (history). */
|
|
19
19
|
viewedAt: any;
|
|
20
20
|
/** (int): Playlist item ID (only populated for :class:`~plexapi.playlist.Playlist` items). */
|
|
21
|
-
playlistItemID
|
|
21
|
+
playlistItemID?: number;
|
|
22
22
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { PartialPlexObject } from './partialPlexObject.js';
|
|
2
|
+
/**
|
|
3
|
+
* This is a general place to store functions specific to media that is Playable.
|
|
4
|
+
* Things were getting mixed up a bit when dealing with Shows, Season, Artists,
|
|
5
|
+
* Albums which are all not playable.
|
|
6
|
+
*/
|
|
7
|
+
export class Playable extends PartialPlexObject {
|
|
8
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PlexServer } from '../server';
|
|
1
|
+
import type { PlexServer } from '../server.js';
|
|
2
2
|
/**
|
|
3
3
|
* Base class for all? Plex objects
|
|
4
4
|
*/
|
|
@@ -22,6 +22,13 @@ export declare abstract class PlexObject {
|
|
|
22
22
|
* Reload the data for this object from this.key.
|
|
23
23
|
*/
|
|
24
24
|
reload(ekey?: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Refreshing a Library or individual item causes the metadata for the item to be
|
|
27
|
+
* refreshed, even if it already has metadata. You can think of refreshing as
|
|
28
|
+
* "update metadata for the requested item even if it already has some". You should
|
|
29
|
+
* refresh a Library or individual item if:
|
|
30
|
+
*/
|
|
31
|
+
refresh(): Promise<void>;
|
|
25
32
|
/**
|
|
26
33
|
* Returns True if this object is a child of the given class.
|
|
27
34
|
*/
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PlexObject = void 0;
|
|
4
|
-
const url_1 = require("url");
|
|
1
|
+
import { URLSearchParams } from 'url';
|
|
5
2
|
/**
|
|
6
3
|
* Base class for all? Plex objects
|
|
7
4
|
*/
|
|
8
|
-
class PlexObject {
|
|
5
|
+
export class PlexObject {
|
|
6
|
+
/** xml element tag */
|
|
7
|
+
static { this.TAG = null; }
|
|
8
|
+
/** xml element type */
|
|
9
|
+
static { this.TYPE = null; }
|
|
9
10
|
constructor(server, data, initpath, parent) {
|
|
10
11
|
this.server = server;
|
|
11
|
-
this.initpath = initpath
|
|
12
|
+
this.initpath = initpath ?? this.key;
|
|
12
13
|
this.parent = parent ? new WeakRef(parent) : undefined;
|
|
13
14
|
this._loadData(data);
|
|
14
15
|
this._detailsKey = this._buildDetailsKey();
|
|
@@ -17,8 +18,7 @@ class PlexObject {
|
|
|
17
18
|
* Reload the data for this object from this.key.
|
|
18
19
|
*/
|
|
19
20
|
async reload(ekey) {
|
|
20
|
-
|
|
21
|
-
const key = (_a = ekey !== null && ekey !== void 0 ? ekey : this._detailsKey) !== null && _a !== void 0 ? _a : this.key;
|
|
21
|
+
const key = ekey ?? this._detailsKey ?? this.key;
|
|
22
22
|
if (!key) {
|
|
23
23
|
throw new Error('Cannot reload an object not built from a URL');
|
|
24
24
|
}
|
|
@@ -26,21 +26,29 @@ class PlexObject {
|
|
|
26
26
|
const innerData = data.MediaContainer ? data.MediaContainer : data;
|
|
27
27
|
this._loadData(innerData);
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Refreshing a Library or individual item causes the metadata for the item to be
|
|
31
|
+
* refreshed, even if it already has metadata. You can think of refreshing as
|
|
32
|
+
* "update metadata for the requested item even if it already has some". You should
|
|
33
|
+
* refresh a Library or individual item if:
|
|
34
|
+
*/
|
|
35
|
+
async refresh() {
|
|
36
|
+
const key = `${this.key}/refresh`;
|
|
37
|
+
await this.server.query(key, 'put');
|
|
38
|
+
}
|
|
29
39
|
/**
|
|
30
40
|
* Returns True if this object is a child of the given class.
|
|
31
41
|
*/
|
|
32
42
|
isChildOf(cls) {
|
|
33
|
-
|
|
34
|
-
const parent = (_a = this.parent) === null || _a === void 0 ? void 0 : _a.deref();
|
|
43
|
+
const parent = this.parent?.deref();
|
|
35
44
|
return (parent && parent.constructor === cls.constructor) || false;
|
|
36
45
|
}
|
|
37
46
|
_buildDetailsKey(args = {}) {
|
|
38
|
-
var _a;
|
|
39
47
|
let detailsKey = this.key;
|
|
40
48
|
if (detailsKey && this._INCLUDES !== undefined) {
|
|
41
|
-
const params = new
|
|
49
|
+
const params = new URLSearchParams();
|
|
42
50
|
for (const [k, v] of Object.entries(this._INCLUDES)) {
|
|
43
|
-
|
|
51
|
+
const value = args[k] ?? v;
|
|
44
52
|
if (![false, 0, '0'].includes(value)) {
|
|
45
53
|
params.set(k, (value === true ? 1 : value).toString());
|
|
46
54
|
}
|
|
@@ -52,8 +60,3 @@ class PlexObject {
|
|
|
52
60
|
return detailsKey;
|
|
53
61
|
}
|
|
54
62
|
}
|
|
55
|
-
exports.PlexObject = PlexObject;
|
|
56
|
-
/** xml element tag */
|
|
57
|
-
PlexObject.TAG = null;
|
|
58
|
-
/** xml element type */
|
|
59
|
-
PlexObject.TYPE = null;
|
|
@@ -1,4 +1,20 @@
|
|
|
1
|
-
import type { PlexServer } from './server';
|
|
1
|
+
import type { PlexServer } from './server.js';
|
|
2
|
+
export declare const OPERATORS: {
|
|
3
|
+
readonly exact: (v: string | number, q: string | number) => boolean;
|
|
4
|
+
readonly iexact: (v: string, q: string) => boolean;
|
|
5
|
+
readonly contains: (v: string, q: string) => boolean;
|
|
6
|
+
readonly icontains: (v: string, q: string) => boolean;
|
|
7
|
+
readonly ne: (v: string, q: string) => boolean;
|
|
8
|
+
readonly in: (v: string, q: any) => boolean;
|
|
9
|
+
readonly gt: (v: number, q: number) => boolean;
|
|
10
|
+
readonly gte: (v: number, q: number) => boolean;
|
|
11
|
+
readonly lt: (v: number, q: number) => boolean;
|
|
12
|
+
readonly lte: (v: number, q: number) => boolean;
|
|
13
|
+
readonly startswith: (v: string, q: string) => boolean;
|
|
14
|
+
readonly istartswith: (v: string, q: string) => boolean;
|
|
15
|
+
readonly endswith: (v: string, q: string) => boolean;
|
|
16
|
+
readonly iendswith: (v: string, q: string) => boolean;
|
|
17
|
+
};
|
|
2
18
|
/**
|
|
3
19
|
* Load the specified key to find and build the first item with the
|
|
4
20
|
* specified tag and attrs. If no tag or attrs are specified then
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.findItems = exports.fetchItems = exports.fetchItem = void 0;
|
|
4
|
-
const OPERATORS = {
|
|
1
|
+
export const OPERATORS = {
|
|
5
2
|
exact: (v, q) => v === q,
|
|
6
3
|
iexact: (v, q) => v.toLowerCase() === q.toLowerCase(),
|
|
7
4
|
contains: (v, q) => q.includes(v),
|
|
@@ -29,12 +26,11 @@ const OPERATORS = {
|
|
|
29
26
|
* in, the key will be translated to /library/metadata/<key>. This allows
|
|
30
27
|
* fetching an item only knowing its key-id.
|
|
31
28
|
*/
|
|
32
|
-
async function fetchItem(server, ekey, options, cls) {
|
|
33
|
-
var _a, _b;
|
|
29
|
+
export async function fetchItem(server, ekey, options, cls) {
|
|
34
30
|
const key = typeof ekey === 'number' ? `/library/metadata/${ekey.toString()}` : ekey;
|
|
35
31
|
const response = await server.query(key);
|
|
36
|
-
const containerKey =
|
|
37
|
-
const elems =
|
|
32
|
+
const containerKey = cls?.TAG ?? 'Metadata';
|
|
33
|
+
const elems = response.MediaContainer[containerKey] ?? [];
|
|
38
34
|
for (const elem of elems) {
|
|
39
35
|
if (checkAttrs(elem, options)) {
|
|
40
36
|
return elem;
|
|
@@ -42,27 +38,24 @@ async function fetchItem(server, ekey, options, cls) {
|
|
|
42
38
|
}
|
|
43
39
|
throw new Error('Unable to find item');
|
|
44
40
|
}
|
|
45
|
-
exports.fetchItem = fetchItem;
|
|
46
41
|
/**
|
|
47
42
|
* Load the specified key to find and build all items with the specified tag
|
|
48
43
|
* and attrs. See :func:`~plexapi.base.PlexObject.fetchItem` for more details
|
|
49
44
|
* on how this is used.
|
|
50
45
|
*/
|
|
51
|
-
async function fetchItems(server, ekey, options, Cls, parent) {
|
|
52
|
-
var _a, _b;
|
|
46
|
+
export async function fetchItems(server, ekey, options, Cls, parent) {
|
|
53
47
|
const response = await server.query(ekey);
|
|
54
48
|
const { MediaContainer } = response;
|
|
55
|
-
const elems =
|
|
49
|
+
const elems = MediaContainer[Cls?.TAG] ?? MediaContainer.Metadata ?? [];
|
|
56
50
|
const items = findItems(elems, options, Cls, server, parent);
|
|
57
51
|
return items;
|
|
58
52
|
}
|
|
59
|
-
exports.fetchItems = fetchItems;
|
|
60
53
|
/**
|
|
61
54
|
* Load the specified data to find and build all items with the specified tag
|
|
62
55
|
* and attrs. See :func:`~plexapi.base.PlexObject.fetchItem` for more details
|
|
63
56
|
* on how this is used.
|
|
64
57
|
*/
|
|
65
|
-
function findItems(data, options = {}, Cls, server, parent) {
|
|
58
|
+
export function findItems(data, options = {}, Cls, server, parent) {
|
|
66
59
|
// if (Cls?.TAG && !('tag' in options)) {
|
|
67
60
|
// options.etag = Cls.TAG;
|
|
68
61
|
// }
|
|
@@ -77,7 +70,6 @@ function findItems(data, options = {}, Cls, server, parent) {
|
|
|
77
70
|
}
|
|
78
71
|
return items;
|
|
79
72
|
}
|
|
80
|
-
exports.findItems = findItems;
|
|
81
73
|
function checkAttrs(elem, obj = {}) {
|
|
82
74
|
const attrsFound = {};
|
|
83
75
|
for (const [attr, query] of Object.entries(obj)) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
2
|
import { URL } from 'url';
|
|
3
|
-
import { Player } from './client.types';
|
|
3
|
+
import { Player } from './client.types.js';
|
|
4
4
|
export interface PlexOptions {
|
|
5
5
|
/** (:class:`~plexapi.server.PlexServer`): PlexServer this client is connected to (optional). */
|
|
6
6
|
server?: any;
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.PlexClient = void 0;
|
|
7
|
-
const got_1 = __importDefault(require("got"));
|
|
8
|
-
const url_1 = require("url");
|
|
9
|
-
const config_1 = require("./config");
|
|
1
|
+
import { URL, URLSearchParams } from 'url';
|
|
2
|
+
import got from 'got';
|
|
3
|
+
import { BASE_HEADERS, TIMEOUT } from './config.js';
|
|
10
4
|
/**
|
|
11
5
|
* Main class for interacting with a Plex client. This class can connect
|
|
12
6
|
* directly to the client and control it or proxy commands through your
|
|
@@ -37,9 +31,8 @@ const config_1 = require("./config");
|
|
|
37
31
|
* _proxyThroughServer (bool): Set to True after calling
|
|
38
32
|
* :func:`~plexapi.client.PlexClient.proxyThroughServer()` (default False).
|
|
39
33
|
*/
|
|
40
|
-
class PlexClient {
|
|
34
|
+
export class PlexClient {
|
|
41
35
|
constructor(options = {}) {
|
|
42
|
-
var _a, _b;
|
|
43
36
|
/**
|
|
44
37
|
* HTTP address of the client
|
|
45
38
|
*/
|
|
@@ -58,8 +51,8 @@ class PlexClient {
|
|
|
58
51
|
this._baseurl = options.baseurl;
|
|
59
52
|
}
|
|
60
53
|
}
|
|
61
|
-
this._baseurl =
|
|
62
|
-
this._token =
|
|
54
|
+
this._baseurl = options.baseurl ?? 'http://localhost:32400';
|
|
55
|
+
this._token = options.token ?? null;
|
|
63
56
|
}
|
|
64
57
|
/**
|
|
65
58
|
* Alias of reload as any subsequent requests to this client will be
|
|
@@ -89,12 +82,12 @@ class PlexClient {
|
|
|
89
82
|
*/
|
|
90
83
|
async query(path, method = 'get', headers, timeout) {
|
|
91
84
|
const headersObj = this.headers(headers);
|
|
92
|
-
const response = await
|
|
85
|
+
const response = await got({
|
|
93
86
|
method,
|
|
94
87
|
url: this.url(path),
|
|
95
|
-
timeout:
|
|
88
|
+
timeout: { request: timeout ?? TIMEOUT },
|
|
96
89
|
headers: headersObj,
|
|
97
|
-
retry: 0,
|
|
90
|
+
retry: { limit: 0 },
|
|
98
91
|
}).json();
|
|
99
92
|
return response;
|
|
100
93
|
}
|
|
@@ -108,9 +101,9 @@ class PlexClient {
|
|
|
108
101
|
if (!this._baseurl) {
|
|
109
102
|
throw new Error('PlexClient object missing baseurl.');
|
|
110
103
|
}
|
|
111
|
-
const url = new
|
|
104
|
+
const url = new URL(key, this._baseurl);
|
|
112
105
|
if (this._token && includeToken) {
|
|
113
|
-
const searchParams = new
|
|
106
|
+
const searchParams = new URLSearchParams();
|
|
114
107
|
searchParams.append('X-Plex-Token', this._token);
|
|
115
108
|
url.search = searchParams.toString();
|
|
116
109
|
return url;
|
|
@@ -133,7 +126,7 @@ class PlexClient {
|
|
|
133
126
|
*/
|
|
134
127
|
headers(headers = {}) {
|
|
135
128
|
const headersObj = {
|
|
136
|
-
...
|
|
129
|
+
...BASE_HEADERS,
|
|
137
130
|
...headers,
|
|
138
131
|
};
|
|
139
132
|
if (this._token) {
|
|
@@ -142,4 +135,3 @@ class PlexClient {
|
|
|
142
135
|
return headersObj;
|
|
143
136
|
}
|
|
144
137
|
}
|
|
145
|
-
exports.PlexClient = PlexClient;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import { getMAC, parseMAC } from '@ctrl/mac-address';
|
|
3
|
+
// TODO: Load User Defined Config
|
|
4
|
+
// const DEFAULT_CONFIG_PATH = os.path.expanduser('~/.config/plexapi/config.ini');
|
|
5
|
+
// const CONFIG_PATH = os.environ.get('PLEXAPI_CONFIG_PATH', DEFAULT_CONFIG_PATH);
|
|
6
|
+
// const CONFIG = PlexConfig(CONFIG_PATH);
|
|
7
|
+
// PlexAPI Settings
|
|
8
|
+
export const PROJECT = 'PlexAPI';
|
|
9
|
+
export const VERSION = '3.3.0';
|
|
10
|
+
export const TIMEOUT = 30000;
|
|
11
|
+
export const X_PLEX_CONTAINER_SIZE = 100;
|
|
12
|
+
export const X_PLEX_ENABLE_FAST_CONNECT = false;
|
|
13
|
+
// Plex Header Configuation
|
|
14
|
+
export const X_PLEX_PROVIDES = 'controller';
|
|
15
|
+
export const X_PLEX_PLATFORM = os.type();
|
|
16
|
+
export const X_PLEX_PLATFORM_VERSION = os.release();
|
|
17
|
+
export const X_PLEX_PRODUCT = PROJECT;
|
|
18
|
+
export const X_PLEX_VERSION = VERSION;
|
|
19
|
+
export const X_PLEX_DEVICE = X_PLEX_PLATFORM;
|
|
20
|
+
export const X_PLEX_DEVICE_NAME = os.hostname();
|
|
21
|
+
const mac = getMAC();
|
|
22
|
+
const base16Mac = parseMAC(mac).toLong().toString(16);
|
|
23
|
+
export const X_PLEX_IDENTIFIER = `0x${base16Mac}`;
|
|
24
|
+
export const BASE_HEADERS = {
|
|
25
|
+
'X-Plex-Platform': X_PLEX_PLATFORM,
|
|
26
|
+
'X-Plex-Platform-Version': X_PLEX_PLATFORM_VERSION,
|
|
27
|
+
'X-Plex-Provides': X_PLEX_PROVIDES,
|
|
28
|
+
'X-Plex-Product': X_PLEX_PRODUCT,
|
|
29
|
+
'X-Plex-Version': X_PLEX_VERSION,
|
|
30
|
+
'X-Plex-Device': X_PLEX_DEVICE,
|
|
31
|
+
'X-Plex-Device-Name': X_PLEX_DEVICE_NAME,
|
|
32
|
+
'X-Plex-Client-Identifier': X_PLEX_IDENTIFIER,
|
|
33
|
+
'X-Plex-Sync-Version': '2',
|
|
34
|
+
'X-Plex-Language': 'en',
|
|
35
|
+
};
|