@ctrl/plex 3.3.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/alert.js +0 -1
- package/dist/src/client.d.ts +0 -1
- package/dist/src/library.js +0 -2
- package/dist/src/media.js +4 -0
- package/dist/src/myplex.d.ts +15 -2
- package/dist/src/myplex.js +66 -1
- package/dist/src/myplex.types.d.ts +5 -0
- package/dist/src/playlist.js +0 -2
- package/dist/src/server.d.ts +0 -1
- package/dist/src/server.js +0 -3
- package/dist/src/settings.js +0 -1
- package/dist/src/video.d.ts +0 -1
- package/package.json +21 -26
package/dist/src/alert.js
CHANGED
|
@@ -8,7 +8,6 @@ export class AlertListener {
|
|
|
8
8
|
async run() {
|
|
9
9
|
const url = this.server.url(this.key, true).toString().replace('http', 'ws');
|
|
10
10
|
this._ws = new WebSocket(url);
|
|
11
|
-
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
12
11
|
this._ws.on('message', (buffer) => {
|
|
13
12
|
try {
|
|
14
13
|
const data = JSON.parse(buffer.toString());
|
package/dist/src/client.d.ts
CHANGED
package/dist/src/library.js
CHANGED
|
@@ -213,7 +213,6 @@ export class Library {
|
|
|
213
213
|
const results = [];
|
|
214
214
|
const sections = await this.sections();
|
|
215
215
|
for (const section of sections) {
|
|
216
|
-
// eslint-disable-next-line no-await-in-loop
|
|
217
216
|
const items = await section.all();
|
|
218
217
|
for (const item of items) {
|
|
219
218
|
results.push(item);
|
|
@@ -227,7 +226,6 @@ export class Library {
|
|
|
227
226
|
async emptyTrash() {
|
|
228
227
|
const sections = await this.sections();
|
|
229
228
|
for (const section of sections) {
|
|
230
|
-
// eslint-disable-next-line no-await-in-loop
|
|
231
229
|
await section.emptyTrash();
|
|
232
230
|
}
|
|
233
231
|
}
|
package/dist/src/media.js
CHANGED
|
@@ -186,6 +186,7 @@ class GuidTag extends PlexObject {
|
|
|
186
186
|
this.id = data.id;
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
|
+
// biome-ignore lint/complexity/noStaticOnlyClass: <explanation>
|
|
189
190
|
export class Guid extends GuidTag {
|
|
190
191
|
static { this.TAG = 'Guid'; }
|
|
191
192
|
}
|
|
@@ -234,18 +235,21 @@ class BaseResource extends PlexObject {
|
|
|
234
235
|
/**
|
|
235
236
|
* Represents a single Art object.
|
|
236
237
|
*/
|
|
238
|
+
// biome-ignore lint/complexity/noStaticOnlyClass: <explanation>
|
|
237
239
|
export class Art extends BaseResource {
|
|
238
240
|
static { this.TAG = 'Art'; }
|
|
239
241
|
}
|
|
240
242
|
/**
|
|
241
243
|
* Represents a single Poster object.
|
|
242
244
|
*/
|
|
245
|
+
// biome-ignore lint/complexity/noStaticOnlyClass: <explanation>
|
|
243
246
|
export class Poster extends BaseResource {
|
|
244
247
|
static { this.TAG = 'Photo'; }
|
|
245
248
|
}
|
|
246
249
|
/**
|
|
247
250
|
* Represents a single Theme object.
|
|
248
251
|
*/
|
|
252
|
+
// biome-ignore lint/complexity/noStaticOnlyClass: <explanation>
|
|
249
253
|
export class Theme extends BaseResource {
|
|
250
254
|
static { this.TAG = 'Theme'; }
|
|
251
255
|
}
|
package/dist/src/myplex.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PlexObject } from './base/plexObject.js';
|
|
2
|
-
import { Connection, Device, ResourcesResponse } from './myplex.types.js';
|
|
2
|
+
import type { Connection, Device, ResourcesResponse, WebLogin } from './myplex.types.js';
|
|
3
3
|
import { PlexServer } from './server.js';
|
|
4
4
|
/**
|
|
5
5
|
* MyPlex account and profile information. This object represents the data found Account on
|
|
@@ -16,6 +16,19 @@ export declare class MyPlexAccount {
|
|
|
16
16
|
timeout: number;
|
|
17
17
|
server?: PlexServer;
|
|
18
18
|
static key: string;
|
|
19
|
+
/**
|
|
20
|
+
* This follows the outline described in https://forums.plex.tv/t/authenticating-with-plex/609370
|
|
21
|
+
* to fetch a token and potentially compromise username and password. To use first call `getWebLogin()`
|
|
22
|
+
* and present the returned uri to a user to go to, then await `webLoginCheck()`. If you pass in a
|
|
23
|
+
* `forwardUrl`, then send the user to the returned uri, and when a request comes in on the passed in
|
|
24
|
+
* url, then await `webLoginCheck()`.
|
|
25
|
+
*/
|
|
26
|
+
static getWebLogin(forwardUrl?: string | null): Promise<WebLogin>;
|
|
27
|
+
/**
|
|
28
|
+
* Pass in the `webLogin` object obtained from `getWebLogin()` and this will poll Plex to see if
|
|
29
|
+
* the user agreed. It returns a connected `MyPlexAccount` or throws an error.
|
|
30
|
+
*/
|
|
31
|
+
static webLoginCheck(webLogin: WebLogin, timeoutSeconds?: number): Promise<MyPlexAccount>;
|
|
19
32
|
FRIENDINVITE: string;
|
|
20
33
|
HOMEUSERCREATE: string;
|
|
21
34
|
EXISTINGUSER: string;
|
|
@@ -136,7 +149,7 @@ export declare class MyPlexAccount {
|
|
|
136
149
|
*/
|
|
137
150
|
export declare class MyPlexResource {
|
|
138
151
|
readonly account: MyPlexAccount;
|
|
139
|
-
private baseUrl;
|
|
152
|
+
private readonly baseUrl;
|
|
140
153
|
static key: string;
|
|
141
154
|
TAG: string;
|
|
142
155
|
/** Descriptive name of this resource */
|
package/dist/src/myplex.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
2
2
|
import { ofetch } from 'ofetch';
|
|
3
3
|
import pAny from 'p-any';
|
|
4
4
|
import { parseStringPromise } from 'xml2js';
|
|
@@ -14,6 +14,71 @@ import { PlexServer } from './server.js';
|
|
|
14
14
|
*/
|
|
15
15
|
export class MyPlexAccount {
|
|
16
16
|
static { this.key = 'https://plex.tv/api/v2/user'; }
|
|
17
|
+
/**
|
|
18
|
+
* This follows the outline described in https://forums.plex.tv/t/authenticating-with-plex/609370
|
|
19
|
+
* to fetch a token and potentially compromise username and password. To use first call `getWebLogin()`
|
|
20
|
+
* and present the returned uri to a user to go to, then await `webLoginCheck()`. If you pass in a
|
|
21
|
+
* `forwardUrl`, then send the user to the returned uri, and when a request comes in on the passed in
|
|
22
|
+
* url, then await `webLoginCheck()`.
|
|
23
|
+
*/
|
|
24
|
+
static async getWebLogin(forwardUrl = null) {
|
|
25
|
+
const appName = BASE_HEADERS['X-Plex-Product'];
|
|
26
|
+
const clientIdentifier = BASE_HEADERS['X-Plex-Client-Identifier'];
|
|
27
|
+
const pin = await ofetch('https://plex.tv/api/v2/pins', {
|
|
28
|
+
method: 'post',
|
|
29
|
+
headers: {
|
|
30
|
+
Accept: 'application/json',
|
|
31
|
+
},
|
|
32
|
+
query: {
|
|
33
|
+
strong: 'true',
|
|
34
|
+
'X-Plex-Product': appName,
|
|
35
|
+
'X-Plex-Client-Identifier': clientIdentifier,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
return {
|
|
39
|
+
...pin,
|
|
40
|
+
uri: `https://app.plex.tv/auth#?clientID=${encodeURIComponent(clientIdentifier)}&code=${encodeURIComponent(pin.code)}&context%5Bdevice%5D%5Bproduct%5D=${encodeURIComponent(appName)}${forwardUrl ? '&forwardUrl=' + encodeURIComponent(forwardUrl) : ''}`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Pass in the `webLogin` object obtained from `getWebLogin()` and this will poll Plex to see if
|
|
45
|
+
* the user agreed. It returns a connected `MyPlexAccount` or throws an error.
|
|
46
|
+
*/
|
|
47
|
+
static async webLoginCheck(webLogin, timeoutSeconds = 60) {
|
|
48
|
+
const recheckMs = 3000;
|
|
49
|
+
const clientIdentifier = BASE_HEADERS['X-Plex-Client-Identifier'];
|
|
50
|
+
const uri = `https://plex.tv/api/v2/pins/${webLogin.id}`;
|
|
51
|
+
const startTime = Date.now();
|
|
52
|
+
while (Date.now() < startTime + timeoutSeconds * 1000) {
|
|
53
|
+
try {
|
|
54
|
+
const tokenResponse = await ofetch(uri, {
|
|
55
|
+
method: 'GET',
|
|
56
|
+
headers: {
|
|
57
|
+
Accept: 'application/json',
|
|
58
|
+
},
|
|
59
|
+
query: {
|
|
60
|
+
code: webLogin.code,
|
|
61
|
+
'X-Plex-Client-Identifier': clientIdentifier,
|
|
62
|
+
},
|
|
63
|
+
timeout: recheckMs,
|
|
64
|
+
retry: 5,
|
|
65
|
+
retryDelay: recheckMs,
|
|
66
|
+
});
|
|
67
|
+
if (tokenResponse.authToken) {
|
|
68
|
+
const myPlexAccount = new MyPlexAccount(null, '', '', tokenResponse.authToken);
|
|
69
|
+
return await myPlexAccount.connect();
|
|
70
|
+
}
|
|
71
|
+
await sleep(recheckMs);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
if (err.message.includes('aborted')) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
throw new Error('Failed to authenticate before timeout');
|
|
81
|
+
}
|
|
17
82
|
/**
|
|
18
83
|
*
|
|
19
84
|
* @param username Your MyPlex username
|
package/dist/src/playlist.js
CHANGED
|
@@ -99,10 +99,8 @@ export class Playlist extends Playable {
|
|
|
99
99
|
throw new BadRequest('Cannot remove items to a smart playlist.');
|
|
100
100
|
}
|
|
101
101
|
for (const item of items) {
|
|
102
|
-
// eslint-disable-next-line no-await-in-loop
|
|
103
102
|
const playlistItemId = await this._getPlaylistItemID(item);
|
|
104
103
|
const key = `${this.key}/items/${playlistItemId}`;
|
|
105
|
-
// eslint-disable-next-line no-await-in-loop
|
|
106
104
|
await this.server.query(key, 'delete');
|
|
107
105
|
}
|
|
108
106
|
}
|
package/dist/src/server.d.ts
CHANGED
package/dist/src/server.js
CHANGED
|
@@ -156,7 +156,6 @@ export class PlexServer {
|
|
|
156
156
|
maxresults > results.length) {
|
|
157
157
|
args['X-Plex-Container-Start'] = (Number(args['X-Plex-Container-Start']) + Number(args['X-Plex-Container-Size'])).toString();
|
|
158
158
|
key = '/status/sessions/history/all?' + new URLSearchParams(args).toString();
|
|
159
|
-
// eslint-disable-next-line no-await-in-loop
|
|
160
159
|
raw = await this.query(key);
|
|
161
160
|
results = results.concat(raw.MediaContainer.Metadata);
|
|
162
161
|
}
|
|
@@ -185,7 +184,6 @@ export class PlexServer {
|
|
|
185
184
|
* you're likley to recieve an authentication error calling this.
|
|
186
185
|
*/
|
|
187
186
|
myPlexAccount() {
|
|
188
|
-
// eslint-disable-next-line logical-assignment-operators
|
|
189
187
|
if (!this._myPlexAccount) {
|
|
190
188
|
this._myPlexAccount = new MyPlexAccount(this.baseurl, undefined, undefined, this.token, this.timeout, this);
|
|
191
189
|
}
|
|
@@ -205,7 +203,6 @@ export class PlexServer {
|
|
|
205
203
|
}
|
|
206
204
|
for (const server of response.MediaContainer.Server) {
|
|
207
205
|
let { port } = server;
|
|
208
|
-
// eslint-disable-next-line logical-assignment-operators
|
|
209
206
|
if (!port) {
|
|
210
207
|
// TODO: print warning about doing weird port stuff
|
|
211
208
|
port = Number(ports?.[server.machineIdentifier]);
|
package/dist/src/settings.js
CHANGED
package/dist/src/video.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ctrl/plex",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"description": "plex api client in typescript",
|
|
5
5
|
"author": "Scott Cooper <scttcper@gmail.com>",
|
|
6
6
|
"publishConfig": {
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
"typescript"
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
|
-
"lint": "
|
|
23
|
+
"lint": "pnpm run '/^(lint:biome|lint:eslint)$/'",
|
|
24
24
|
"lint:biome": "biome check .",
|
|
25
|
-
"lint:eslint": "eslint
|
|
26
|
-
"lint:fix": "
|
|
27
|
-
"lint:eslint:fix": "eslint
|
|
28
|
-
"lint:biome:fix": "biome check . --
|
|
25
|
+
"lint:eslint": "eslint .",
|
|
26
|
+
"lint:fix": "pnpm run '/^(lint:biome|lint:eslint):fix$/'",
|
|
27
|
+
"lint:eslint:fix": "eslint . --fix",
|
|
28
|
+
"lint:biome:fix": "biome check . --write",
|
|
29
29
|
"prepare": "npm run build",
|
|
30
30
|
"build": "tsc",
|
|
31
31
|
"build:docs": "typedoc",
|
|
@@ -40,34 +40,29 @@
|
|
|
40
40
|
"@ctrl/mac-address": "^3.0.3",
|
|
41
41
|
"ofetch": "^1.3.4",
|
|
42
42
|
"p-any": "^4.0.0",
|
|
43
|
-
"type-fest": "^4.
|
|
44
|
-
"ws": "^8.
|
|
43
|
+
"type-fest": "^4.24.0",
|
|
44
|
+
"ws": "^8.18.0",
|
|
45
45
|
"xml2js": "^0.6.2"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@biomejs/biome": "1.
|
|
49
|
-
"@ctrl/eslint-config-biome": "
|
|
50
|
-
"@sindresorhus/tsconfig": "
|
|
51
|
-
"@types/
|
|
52
|
-
"@types/
|
|
53
|
-
"@types/node": "20.12.8",
|
|
54
|
-
"@types/ws": "8.5.10",
|
|
48
|
+
"@biomejs/biome": "1.8.3",
|
|
49
|
+
"@ctrl/eslint-config-biome": "4.1.3",
|
|
50
|
+
"@sindresorhus/tsconfig": "6.0.0",
|
|
51
|
+
"@types/node": "22.3.0",
|
|
52
|
+
"@types/ws": "8.5.12",
|
|
55
53
|
"@types/xml2js": "0.4.14",
|
|
56
|
-
"@types/yargs": "17.0.
|
|
57
|
-
"@vitest/coverage-v8": "
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"glob": "10.3.12",
|
|
62
|
-
"globby": "14.0.1",
|
|
63
|
-
"lodash": "4.17.21",
|
|
54
|
+
"@types/yargs": "17.0.33",
|
|
55
|
+
"@vitest/coverage-v8": "2.0.5",
|
|
56
|
+
"eslint": "9.9.0",
|
|
57
|
+
"execa": "9.3.1",
|
|
58
|
+
"globby": "14.0.2",
|
|
64
59
|
"make-dir": "5.0.0",
|
|
65
60
|
"ora": "8.0.1",
|
|
66
61
|
"p-retry": "6.2.0",
|
|
67
62
|
"ts-node": "10.9.2",
|
|
68
|
-
"typedoc": "0.
|
|
69
|
-
"typescript": "5.4
|
|
70
|
-
"vitest": "
|
|
63
|
+
"typedoc": "0.26.5",
|
|
64
|
+
"typescript": "5.5.4",
|
|
65
|
+
"vitest": "2.0.5",
|
|
71
66
|
"yargs": "17.7.2"
|
|
72
67
|
},
|
|
73
68
|
"release": {
|