@ctrl/plex 2.0.0 → 3.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/dist/src/alert.js CHANGED
@@ -8,6 +8,7 @@ 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
11
12
  this._ws.on('message', (buffer) => {
12
13
  try {
13
14
  const data = JSON.parse(buffer.toString());
@@ -67,6 +67,7 @@ export declare class PlexClient {
67
67
  platform?: string;
68
68
  platformVersion?: string;
69
69
  title?: string;
70
+ timeout: number;
70
71
  constructor(options?: PlexOptions);
71
72
  /**
72
73
  * Alias of reload as any subsequent requests to this client will be
@@ -74,7 +75,7 @@ export declare class PlexClient {
74
75
  * populated from a PlexServer.
75
76
  * @param timeout
76
77
  */
77
- connect(timeout?: number): Promise<void>;
78
+ connect(): Promise<void>;
78
79
  /**
79
80
  * @alias PlexClient.connect
80
81
  */
@@ -89,7 +90,7 @@ export declare class PlexClient {
89
90
  * @param headers
90
91
  * @param timeout
91
92
  */
92
- query<T>(path: string, method?: 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete', headers?: Record<string, string>, timeout?: number): Promise<T>;
93
+ query<T>(path: string, method?: 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete', headers?: Record<string, string>): Promise<T>;
93
94
  /**
94
95
  * Build a URL string with proper token argument. Token will be appended to the URL
95
96
  * if either includeToken is True or TODO: CONFIG.log.show_secrets is 'true'.
@@ -1,5 +1,5 @@
1
1
  import { URL, URLSearchParams } from 'url';
2
- import got from 'got';
2
+ import { ofetch } from 'ofetch';
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
@@ -53,6 +53,7 @@ export class PlexClient {
53
53
  }
54
54
  this._baseurl = options.baseurl ?? 'http://localhost:32400';
55
55
  this._token = options.token ?? null;
56
+ this.timeout = options.timeout ?? TIMEOUT;
56
57
  }
57
58
  /**
58
59
  * Alias of reload as any subsequent requests to this client will be
@@ -60,8 +61,8 @@ export class PlexClient {
60
61
  * populated from a PlexServer.
61
62
  * @param timeout
62
63
  */
63
- async connect(timeout) {
64
- const data = await this.query(this.key, undefined, undefined, timeout);
64
+ async connect() {
65
+ const data = await this.query(this.key);
65
66
  this._loadData(data.MediaContainer.Player);
66
67
  }
67
68
  /**
@@ -80,15 +81,15 @@ export class PlexClient {
80
81
  * @param headers
81
82
  * @param timeout
82
83
  */
83
- async query(path, method = 'get', headers, timeout) {
84
+ async query(path, method = 'get', headers) {
84
85
  const headersObj = this.headers(headers);
85
- const response = await got({
86
+ const response = await ofetch(this.url(path).toString(), {
86
87
  method,
87
- url: this.url(path),
88
- timeout: { request: timeout ?? TIMEOUT },
88
+ timeout: this.timeout ?? TIMEOUT,
89
89
  headers: headersObj,
90
- retry: { limit: 0 },
91
- }).json();
90
+ responseType: 'json',
91
+ retry: 0,
92
+ });
92
93
  return response;
93
94
  }
94
95
  /**
@@ -128,6 +129,7 @@ export class PlexClient {
128
129
  const headersObj = {
129
130
  ...BASE_HEADERS,
130
131
  ...headers,
132
+ accept: 'application/json',
131
133
  };
132
134
  if (this._token) {
133
135
  headersObj['X-Plex-Token'] = this._token;
@@ -1,7 +1,10 @@
1
1
  export declare const PROJECT = "PlexAPI";
2
2
  export declare const VERSION = "3.3.0";
3
+ /**
4
+ * Default timeout for requests in milliseconds.
5
+ */
3
6
  export declare const TIMEOUT = 30000;
4
- export declare const X_PLEX_CONTAINER_SIZE: 100;
7
+ export declare const X_PLEX_CONTAINER_SIZE = 100;
5
8
  export declare const X_PLEX_ENABLE_FAST_CONNECT: false;
6
9
  export declare const X_PLEX_PROVIDES = "controller";
7
10
  export declare const X_PLEX_PLATFORM: string;
@@ -7,7 +7,10 @@ import { getMAC, parseMAC } from '@ctrl/mac-address';
7
7
  // PlexAPI Settings
8
8
  export const PROJECT = 'PlexAPI';
9
9
  export const VERSION = '3.3.0';
10
- export const TIMEOUT = 30000;
10
+ /**
11
+ * Default timeout for requests in milliseconds.
12
+ */
13
+ export const TIMEOUT = 30_000;
11
14
  export const X_PLEX_CONTAINER_SIZE = 100;
12
15
  export const X_PLEX_ENABLE_FAST_CONNECT = false;
13
16
  // Plex Header Configuation
@@ -357,7 +357,7 @@ export declare abstract class LibrarySection<SectionVideoType = VideoType> exten
357
357
  * Returns a list of playlists from this library section.
358
358
  */
359
359
  playlists(): Promise<Playlist[]>;
360
- collections(args?: Record<string, number | string | boolean>): Promise<Array<Collections<SectionVideoType>>>;
360
+ collections(args?: Record<string, number | string | boolean>): Promise<Collections<SectionVideoType>[]>;
361
361
  /**
362
362
  * Returns a list of available Folders for this library section.
363
363
  */
@@ -9,12 +9,12 @@ import { PlexServer } from './server.js';
9
9
  * and return this object.
10
10
  */
11
11
  export declare class MyPlexAccount {
12
- private readonly baseUrl;
12
+ baseUrl: string | null;
13
13
  username?: string;
14
- private readonly password?;
14
+ password?: string;
15
15
  token?: string;
16
- private readonly timeout;
17
- private readonly server?;
16
+ timeout: number;
17
+ server?: PlexServer;
18
18
  static key: string;
19
19
  FRIENDINVITE: string;
20
20
  HOMEUSERCREATE: string;
@@ -115,7 +115,7 @@ export declare class MyPlexAccount {
115
115
  * @param headers
116
116
  * @param timeout
117
117
  */
118
- query<T = any>(url: string, method?: 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete', headers?: any, timeout?: number, username?: string, password?: string): Promise<T>;
118
+ query<T = any>(url: string, method?: 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete', headers?: any, username?: string, password?: string): Promise<T>;
119
119
  /**
120
120
  * Returns a str, a new "claim-token", which you can use to register your new Plex Server instance to your account.
121
121
  * @link https://hub.docker.com/r/plexinc/pms-docker/
@@ -136,7 +136,7 @@ export declare class MyPlexAccount {
136
136
  */
137
137
  export declare class MyPlexResource {
138
138
  readonly account: MyPlexAccount;
139
- private readonly baseUrl;
139
+ private baseUrl;
140
140
  static key: string;
141
141
  TAG: string;
142
142
  /** Descriptive name of this resource */
@@ -1,5 +1,5 @@
1
- import { URL, URLSearchParams } from 'url';
2
- import got from 'got';
1
+ import { URLSearchParams } from 'url';
2
+ import { ofetch } from 'ofetch';
3
3
  import pAny from 'p-any';
4
4
  import { parseStringPromise } from 'xml2js';
5
5
  import { PlexObject } from './base/plexObject.js';
@@ -64,8 +64,7 @@ export class MyPlexAccount {
64
64
  */
65
65
  async connect() {
66
66
  if (!this.token) {
67
- // log('Logging in with username', { username: this.username });
68
- const data = await this._signin(this.username, this.password, this.timeout);
67
+ const data = await this._signin(this.username, this.password);
69
68
  this._loadData(data);
70
69
  return this;
71
70
  }
@@ -118,27 +117,28 @@ export class MyPlexAccount {
118
117
  * @param headers
119
118
  * @param timeout
120
119
  */
121
- async query(url, method = 'get', headers, timeout, username, password) {
120
+ async query(url, method = 'get', headers, username, password) {
122
121
  const requestHeaders = this._headers();
123
122
  if (username && password) {
124
123
  const credentials = Buffer.from(`${username}:${password}`).toString('base64');
125
124
  requestHeaders.Authorization = `Basic ${credentials}`;
126
125
  }
127
- const promise = got({
126
+ if (!url.includes('xml')) {
127
+ requestHeaders.accept = 'application/json';
128
+ }
129
+ const body = await ofetch(url, {
128
130
  method,
129
- url: new URL(url),
130
131
  headers: requestHeaders,
131
- timeout: { request: timeout ?? TIMEOUT },
132
- ...(username ? { username } : {}),
133
- ...(password ? { password } : {}),
134
- retry: { limit: 0 },
132
+ timeout: this.timeout ?? TIMEOUT,
133
+ retry: 0,
134
+ parseResponse: res => res,
135
+ // Can't seem to pass responseType
135
136
  });
136
137
  if (url.includes('xml')) {
137
- const res = await promise;
138
- const xml = await parseStringPromise(res.body);
138
+ const xml = await parseStringPromise(body);
139
139
  return xml;
140
140
  }
141
- const res = await promise.json();
141
+ const res = JSON.parse(body);
142
142
  return res;
143
143
  }
144
144
  /**
@@ -148,7 +148,7 @@ export class MyPlexAccount {
148
148
  */
149
149
  async claimToken() {
150
150
  const url = 'https://plex.tv/api/claim/token.json';
151
- const response = await this.query(url, 'get', undefined, TIMEOUT);
151
+ const response = await this.query(url, 'get', undefined);
152
152
  return response.token;
153
153
  }
154
154
  /**
@@ -160,12 +160,11 @@ export class MyPlexAccount {
160
160
  ...BASE_HEADERS,
161
161
  });
162
162
  const url = `${this.baseUrl}/myplex/claim?${params.toString()}`;
163
- return got({
164
- method: 'POST',
165
- url,
166
- timeout: { request: TIMEOUT },
163
+ return ofetch(url, {
164
+ method: 'post',
165
+ timeout: TIMEOUT,
167
166
  headers: this._headers(),
168
- retry: { limit: 0 },
167
+ retry: 0,
169
168
  });
170
169
  }
171
170
  _headers() {
@@ -178,8 +177,8 @@ export class MyPlexAccount {
178
177
  }
179
178
  return headers;
180
179
  }
181
- async _signin(username, password, timeout) {
182
- const data = await this.query(this.SIGNIN, 'post', undefined, timeout, username, password);
180
+ async _signin(username, password) {
181
+ const data = await this.query(this.SIGNIN, 'post', undefined, username, password);
183
182
  return data.user;
184
183
  }
185
184
  _loadData(user) {
@@ -234,9 +233,7 @@ export class MyPlexResource {
234
233
  this._loadData(data);
235
234
  }
236
235
  async connect(ssl = null, timeout) {
237
- const connections = [...this.connections].sort((a, b) => {
238
- return Number(b.local) - Number(a.local);
239
- });
236
+ const connections = [...this.connections].sort((a, b) => Number(b.local) - Number(a.local));
240
237
  const ownedOrUnownedNonLocal = (connection) => {
241
238
  if (this.owned || (!this.owned && !connection.local)) {
242
239
  return true;
@@ -267,7 +264,7 @@ export class MyPlexResource {
267
264
  /** Remove this device from your account */
268
265
  async delete() {
269
266
  const key = `https://plex.tv/api/servers/${this.clientIdentifier}?X-Plex-Client-Identifier=${BASE_HEADERS['X-Plex-Client-Identifier']}&X-Plex-Token=${this.accessToken}`;
270
- await got.delete(key, { retry: { limit: 0 } });
267
+ await ofetch(key, { method: 'delete', retry: 0 });
271
268
  }
272
269
  _loadData(data) {
273
270
  this.name = data.name;
@@ -15,9 +15,13 @@ import { Settings } from './settings.js';
15
15
  * can also create an PlexServer instance from :class:`~plexapi.myplex.MyPlexAccount`.
16
16
  */
17
17
  export declare class PlexServer {
18
- readonly baseurl: string;
19
- readonly token: string;
20
- readonly timeout?: number;
18
+ baseurl: string;
19
+ token: string;
20
+ /**
21
+ * Default request timeout in milliseconds.
22
+ * @default 30000
23
+ */
24
+ timeout: number;
21
25
  key: string;
22
26
  /** True if server allows camera upload */
23
27
  allowCameraUpload: boolean;
@@ -131,7 +135,12 @@ export declare class PlexServer {
131
135
  _library?: Library;
132
136
  _settings?: Settings;
133
137
  private _myPlexAccount?;
134
- constructor(baseurl: string, token: string, timeout?: number);
138
+ constructor(baseurl: string, token: string,
139
+ /**
140
+ * Default request timeout in milliseconds.
141
+ * @default 30000
142
+ */
143
+ timeout?: number);
135
144
  agents(mediaType?: number | string): Promise<Agent[]>;
136
145
  connect(): Promise<void>;
137
146
  /**
@@ -162,9 +171,8 @@ export declare class PlexServer {
162
171
  * @param path
163
172
  * @param method
164
173
  * @param headers
165
- * @param timeout
166
174
  */
167
- query<T = any>(path: string, method?: 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete', headers?: any, timeout?: number, username?: string, password?: string): Promise<T>;
175
+ query<T = any>(path: string, method?: 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete', headers?: any, username?: string, password?: string): Promise<T>;
168
176
  /**
169
177
  * Returns a list of media items from watched history. If there are many results, they will
170
178
  * be fetched from the server in batches of X_PLEX_CONTAINER_SIZE amounts. If you're only
@@ -1,5 +1,5 @@
1
1
  import { URL, URLSearchParams } from 'url';
2
- import got from 'got';
2
+ import { ofetch } from 'ofetch';
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';
@@ -16,7 +16,12 @@ import { Settings } from './settings.js';
16
16
  * can also create an PlexServer instance from :class:`~plexapi.myplex.MyPlexAccount`.
17
17
  */
18
18
  export class PlexServer {
19
- constructor(baseurl, token, timeout) {
19
+ constructor(baseurl, token,
20
+ /**
21
+ * Default request timeout in milliseconds.
22
+ * @default 30000
23
+ */
24
+ timeout = TIMEOUT) {
20
25
  this.baseurl = baseurl;
21
26
  this.token = token;
22
27
  this.timeout = timeout;
@@ -30,7 +35,7 @@ export class PlexServer {
30
35
  return fetchItems(this, key, undefined, Agent, this);
31
36
  }
32
37
  async connect() {
33
- const data = await this.query(this.key, undefined, undefined, this.timeout);
38
+ const data = await this.query(this.key);
34
39
  this._loadData(data.MediaContainer);
35
40
  // Attempt to prevent token from being logged accidentally
36
41
  if (this.token) {
@@ -93,24 +98,24 @@ export class PlexServer {
93
98
  * @param path
94
99
  * @param method
95
100
  * @param headers
96
- * @param timeout
97
101
  */
98
- async query(path, method = 'get', headers, timeout, username, password) {
102
+ async query(path, method = 'get', headers, username, password) {
99
103
  const requestHeaders = this._headers();
100
104
  if (username && password) {
101
105
  const credentials = Buffer.from(`${username}:${password}`).toString('base64');
102
106
  requestHeaders.Authorization = `Basic ${credentials}`;
103
107
  }
104
108
  const url = this.url(path);
105
- const response = await got({
109
+ if (!url.toString().includes('xml')) {
110
+ requestHeaders.accept = 'application/json';
111
+ }
112
+ const response = await ofetch(url.toString(), {
106
113
  method,
107
- url,
108
114
  headers: requestHeaders,
109
- timeout: { request: timeout ?? TIMEOUT },
110
- ...(username ? { username } : {}),
111
- ...(password ? { password } : {}),
112
- retry: { limit: 0 },
113
- }).json();
115
+ timeout: this.timeout ?? TIMEOUT,
116
+ retry: 0,
117
+ responseType: 'json',
118
+ });
114
119
  return response;
115
120
  }
116
121
  /**
@@ -179,6 +184,7 @@ export class PlexServer {
179
184
  * you're likley to recieve an authentication error calling this.
180
185
  */
181
186
  myPlexAccount() {
187
+ // eslint-disable-next-line logical-assignment-operators
182
188
  if (!this._myPlexAccount) {
183
189
  this._myPlexAccount = new MyPlexAccount(this.baseurl, undefined, undefined, this.token, this.timeout, this);
184
190
  }
@@ -198,6 +204,7 @@ export class PlexServer {
198
204
  }
199
205
  for (const server of response.MediaContainer.Server) {
200
206
  let { port } = server;
207
+ // eslint-disable-next-line logical-assignment-operators
201
208
  if (!port) {
202
209
  // TODO: print warning about doing weird port stuff
203
210
  port = Number(ports?.[server.machineIdentifier]);
@@ -55,6 +55,7 @@ export class Settings extends PlexObject {
55
55
  }
56
56
  _loadData(data) {
57
57
  this._data = data;
58
+ // eslint-disable-next-line logical-assignment-operators
58
59
  this._settings = this._settings ?? {};
59
60
  for (const elem of data) {
60
61
  const id = lowerFirst(elem.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctrl/plex",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "plex api client in typescript",
5
5
  "author": "Scott Cooper <scttcper@gmail.com>",
6
6
  "publishConfig": {
@@ -20,8 +20,12 @@
20
20
  "typescript"
21
21
  ],
22
22
  "scripts": {
23
- "lint": "eslint --ext .ts .",
24
- "lint:fix": "eslint --fix --ext .ts .",
23
+ "lint": "pnpm run '/^(lint:biome|lint:eslint)$/'",
24
+ "lint:biome": "biome check .",
25
+ "lint:eslint": "eslint --ext .ts,.tsx .",
26
+ "lint:fix": "pnpm run '/^(lint:biome|lint:eslint):fix$/'",
27
+ "lint:eslint:fix": "eslint --ext .ts,.tsx . --fix",
28
+ "lint:biome:fix": "biome check . --apply",
25
29
  "prepare": "npm run build",
26
30
  "build": "tsc",
27
31
  "build:docs": "typedoc",
@@ -35,14 +39,15 @@
35
39
  },
36
40
  "dependencies": {
37
41
  "@ctrl/mac-address": "^3.0.3",
38
- "got": "^13.0.0",
42
+ "ofetch": "^1.3.4",
39
43
  "p-any": "^4.0.0",
40
- "type-fest": "^4.15.0",
41
- "ws": "^8.16.0",
44
+ "type-fest": "^4.17.0",
45
+ "ws": "^8.17.0",
42
46
  "xml2js": "^0.6.2"
43
47
  },
44
48
  "devDependencies": {
45
- "@ctrl/eslint-config": "4.0.14",
49
+ "@biomejs/biome": "1.7.1",
50
+ "@ctrl/eslint-config-biome": "2.6.7",
46
51
  "@sindresorhus/tsconfig": "5.0.0",
47
52
  "@types/lodash": "4.17.0",
48
53
  "@types/micromatch": "4.0.7",
@@ -50,8 +55,9 @@
50
55
  "@types/ws": "8.5.10",
51
56
  "@types/xml2js": "0.4.14",
52
57
  "@types/yargs": "17.0.32",
53
- "@vitest/coverage-v8": "^1.5.0",
58
+ "@vitest/coverage-v8": "1.5.2",
54
59
  "delay": "6.0.0",
60
+ "eslint": "8.57.0",
55
61
  "execa": "8.0.1",
56
62
  "glob": "10.3.12",
57
63
  "globby": "14.0.1",
@@ -62,7 +68,7 @@
62
68
  "ts-node": "10.9.2",
63
69
  "typedoc": "0.25.13",
64
70
  "typescript": "5.4.5",
65
- "vitest": "1.5.0",
71
+ "vitest": "1.5.2",
66
72
  "yargs": "17.7.2"
67
73
  },
68
74
  "release": {
@@ -71,6 +77,6 @@
71
77
  ]
72
78
  },
73
79
  "engines": {
74
- "node": ">=16.10"
80
+ "node": ">=18"
75
81
  }
76
82
  }