@ctrl/plex 1.5.2 → 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.
Files changed (68) hide show
  1. package/README.md +3 -2
  2. package/dist/src/alert.d.ts +12 -0
  3. package/dist/src/alert.js +29 -0
  4. package/dist/src/alert.types.d.ts +59 -0
  5. package/dist/src/alert.types.js +1 -0
  6. package/dist/{base → src/base}/partialPlexObject.d.ts +18 -12
  7. package/dist/{base → src/base}/partialPlexObject.js +32 -23
  8. package/dist/{base → src/base}/playable.d.ts +2 -2
  9. package/dist/src/base/playable.js +8 -0
  10. package/dist/{base → src/base}/plexObject.d.ts +8 -1
  11. package/dist/{base → src/base}/plexObject.js +21 -18
  12. package/dist/{baseFunctionality.d.ts → src/baseFunctionality.d.ts} +17 -1
  13. package/dist/{baseFunctionality.js → src/baseFunctionality.js} +7 -15
  14. package/dist/{client.d.ts → src/client.d.ts} +2 -2
  15. package/dist/{client.js → src/client.js} +12 -20
  16. package/dist/src/client.types.js +1 -0
  17. package/dist/src/config.js +35 -0
  18. package/dist/src/exceptions.d.ts +20 -0
  19. package/dist/src/exceptions.js +40 -0
  20. package/dist/src/index.d.ts +12 -0
  21. package/dist/src/index.js +11 -0
  22. package/dist/{library.d.ts → src/library.d.ts} +207 -21
  23. package/dist/{library.js → src/library.js} +349 -133
  24. package/dist/{library.types.d.ts → src/library.types.d.ts} +71 -1
  25. package/dist/src/library.types.js +1 -0
  26. package/dist/{media.d.ts → src/media.d.ts} +16 -4
  27. package/dist/{media.js → src/media.js} +42 -49
  28. package/dist/src/media.types.d.ts +7 -0
  29. package/dist/src/media.types.js +1 -0
  30. package/dist/{myplex.d.ts → src/myplex.d.ts} +16 -6
  31. package/dist/{myplex.js → src/myplex.js} +71 -57
  32. package/dist/src/myplex.types.js +10 -0
  33. package/dist/src/playlist.d.ts +75 -0
  34. package/dist/src/playlist.js +142 -0
  35. package/dist/src/playlist.types.d.ts +17 -0
  36. package/dist/src/playlist.types.js +1 -0
  37. package/dist/{search.d.ts → src/search.d.ts} +5 -4
  38. package/dist/{search.js → src/search.js} +16 -20
  39. package/dist/src/search.types.js +1 -0
  40. package/dist/{server.d.ts → src/server.d.ts} +22 -10
  41. package/dist/{server.js → src/server.js} +65 -50
  42. package/dist/src/server.types.js +1 -0
  43. package/dist/src/settings.d.ts +79 -0
  44. package/dist/src/settings.js +160 -0
  45. package/dist/{util.d.ts → src/util.d.ts} +2 -1
  46. package/dist/{util.js → src/util.js} +8 -12
  47. package/dist/{video.d.ts → src/video.d.ts} +39 -61
  48. package/dist/{video.js → src/video.js} +110 -93
  49. package/dist/{video.types.d.ts → src/video.types.d.ts} +1 -1
  50. package/dist/src/video.types.js +6 -0
  51. package/package.json +46 -44
  52. package/dist/base/playable.js +0 -12
  53. package/dist/client.types.js +0 -2
  54. package/dist/config.js +0 -41
  55. package/dist/index.d.ts +0 -8
  56. package/dist/index.js +0 -23
  57. package/dist/library.types.js +0 -2
  58. package/dist/myplex.types.js +0 -13
  59. package/dist/playlist.d.ts +0 -7
  60. package/dist/playlist.js +0 -19
  61. package/dist/search.types.js +0 -2
  62. package/dist/server.types.js +0 -2
  63. package/dist/video.types.js +0 -9
  64. /package/dist/{client.types.d.ts → src/client.types.d.ts} +0 -0
  65. /package/dist/{config.d.ts → src/config.d.ts} +0 -0
  66. /package/dist/{myplex.types.d.ts → src/myplex.types.d.ts} +0 -0
  67. /package/dist/{search.types.d.ts → src/search.types.d.ts} +0 -0
  68. /package/dist/{server.types.d.ts → src/server.types.d.ts} +0 -0
@@ -1,16 +1,10 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.MyPlexDevice = exports.ResourceConnection = exports.MyPlexResource = exports.MyPlexAccount = void 0;
7
- const got_1 = __importDefault(require("got"));
8
- const url_1 = require("url");
9
- const p_any_1 = __importDefault(require("p-any"));
10
- const config_1 = require("./config");
11
- const server_1 = require("./server");
12
- const plexObject_1 = require("./base/plexObject");
13
- const xml2js_1 = require("xml2js");
1
+ import { URL, URLSearchParams } from 'url';
2
+ import got from 'got';
3
+ import pAny from 'p-any';
4
+ import { parseStringPromise } from 'xml2js';
5
+ import { PlexObject } from './base/plexObject.js';
6
+ import { BASE_HEADERS, TIMEOUT } from './config.js';
7
+ import { PlexServer } from './server.js';
14
8
  /**
15
9
  * MyPlex account and profile information. This object represents the data found Account on
16
10
  * the myplex.tv servers at the url https://plex.tv/users/account. You may create this object
@@ -18,7 +12,8 @@ const xml2js_1 = require("xml2js");
18
12
  * method provided at :class:`~plexapi.server.PlexServer.myPlexAccount()` which will create
19
13
  * and return this object.
20
14
  */
21
- class MyPlexAccount {
15
+ export class MyPlexAccount {
16
+ static { this.key = 'https://plex.tv/api/v2/user'; }
22
17
  /**
23
18
  *
24
19
  * @param username Your MyPlex username
@@ -28,7 +23,7 @@ class MyPlexAccount {
28
23
  * @param timeout timeout in seconds on initial connect to myplex
29
24
  * @param server not often available
30
25
  */
31
- constructor(baseUrl = null, username, password, token, timeout = config_1.TIMEOUT, server) {
26
+ constructor(baseUrl = null, username, password, token, timeout = TIMEOUT, server) {
32
27
  this.baseUrl = baseUrl;
33
28
  this.username = username;
34
29
  this.password = password;
@@ -94,6 +89,18 @@ class MyPlexAccount {
94
89
  const data = await this.query(MyPlexResource.key);
95
90
  return data.map(device => new MyPlexResource(this, device, this.baseUrl));
96
91
  }
92
+ /**
93
+ * @param name Name to match against.
94
+ * @param clientId clientIdentifier to match against.
95
+ */
96
+ async device(name = '', clientId) {
97
+ const devices = await this.devices();
98
+ const device = devices.find(device => device.name?.toLowerCase() === name.toLowerCase() || device.clientIdentifier === clientId);
99
+ if (!device) {
100
+ throw new Error('Unable to find device');
101
+ }
102
+ return device;
103
+ }
97
104
  /**
98
105
  * Returns a list of all :class:`~plexapi.myplex.MyPlexDevice` objects connected to the server.
99
106
  */
@@ -117,18 +124,18 @@ class MyPlexAccount {
117
124
  const credentials = Buffer.from(`${username}:${password}`).toString('base64');
118
125
  requestHeaders.Authorization = `Basic ${credentials}`;
119
126
  }
120
- const promise = got_1.default({
127
+ const promise = got({
121
128
  method,
122
- url: new url_1.URL(url),
129
+ url: new URL(url),
123
130
  headers: requestHeaders,
124
- timeout: timeout !== null && timeout !== void 0 ? timeout : config_1.TIMEOUT,
125
- username,
126
- password,
127
- retry: 0,
131
+ timeout: { request: timeout ?? TIMEOUT },
132
+ ...(username ? { username } : {}),
133
+ ...(password ? { password } : {}),
134
+ retry: { limit: 0 },
128
135
  });
129
136
  if (url.includes('xml')) {
130
137
  const res = await promise;
131
- const xml = await xml2js_1.parseStringPromise(res.body);
138
+ const xml = await parseStringPromise(res.body);
132
139
  return xml;
133
140
  }
134
141
  const res = await promise.json();
@@ -141,29 +148,29 @@ class MyPlexAccount {
141
148
  */
142
149
  async claimToken() {
143
150
  const url = 'https://plex.tv/api/claim/token.json';
144
- const response = await this.query(url, 'get', undefined, config_1.TIMEOUT);
151
+ const response = await this.query(url, 'get', undefined, TIMEOUT);
145
152
  return response.token;
146
153
  }
147
154
  /**
148
155
  * @param token pass token from claimToken
149
156
  */
150
157
  async claimServer(token) {
151
- const params = new url_1.URLSearchParams({
158
+ const params = new URLSearchParams({
152
159
  token,
153
- ...config_1.BASE_HEADERS,
160
+ ...BASE_HEADERS,
154
161
  });
155
162
  const url = `${this.baseUrl}/myplex/claim?${params.toString()}`;
156
- return got_1.default({
163
+ return got({
157
164
  method: 'POST',
158
165
  url,
159
- timeout: config_1.TIMEOUT,
166
+ timeout: { request: TIMEOUT },
160
167
  headers: this._headers(),
161
- retry: 0,
168
+ retry: { limit: 0 },
162
169
  });
163
170
  }
164
171
  _headers() {
165
172
  const headers = {
166
- ...config_1.BASE_HEADERS,
173
+ ...BASE_HEADERS,
167
174
  'Content-type': 'application/json',
168
175
  };
169
176
  if (this.token) {
@@ -176,7 +183,6 @@ class MyPlexAccount {
176
183
  return data.user;
177
184
  }
178
185
  _loadData(user) {
179
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
180
186
  // Attempt to prevent token from being logged accidentally
181
187
  Object.defineProperty(this, 'token', {
182
188
  enumerable: false,
@@ -194,21 +200,19 @@ class MyPlexAccount {
194
200
  this.username = user.username;
195
201
  this.title = user.title;
196
202
  this.locale = user.locale;
197
- this.mailingListStatus = (_a = user.mailingListStatus) !== null && _a !== void 0 ? _a : 'inactive';
203
+ this.mailingListStatus = user.mailingListStatus ?? 'inactive';
198
204
  this.mailingListActive = user.mailingListActive;
199
205
  this.queueEmail = user.queueEmail;
200
206
  this.thumb = user.thumb;
201
207
  this.scrobbleTypes = user.scrobbleTypes;
202
208
  this.restricted = user.restricted;
203
- this.subscriptionActive = (_c = (_b = user.subscription) === null || _b === void 0 ? void 0 : _b.active) !== null && _c !== void 0 ? _c : null;
204
- this.subscriptionStatus = (_f = (_e = (_d = user.subscription) === null || _d === void 0 ? void 0 : _d.status) === null || _e === void 0 ? void 0 : _e.toLowerCase()) !== null && _f !== void 0 ? _f : 'inactive';
205
- this.subscriptionPlan = (_h = (_g = user.subscription) === null || _g === void 0 ? void 0 : _g.plan) !== null && _h !== void 0 ? _h : null;
206
- this.subscriptionFeatures = (_k = (_j = user.subscription) === null || _j === void 0 ? void 0 : _j.features) !== null && _k !== void 0 ? _k : [];
209
+ this.subscriptionActive = user.subscription?.active ?? null;
210
+ this.subscriptionStatus = user.subscription?.status?.toLowerCase() ?? 'inactive';
211
+ this.subscriptionPlan = user.subscription?.plan ?? null;
212
+ this.subscriptionFeatures = user.subscription?.features ?? [];
207
213
  this.entitlements = user.entitlements;
208
214
  }
209
215
  }
210
- exports.MyPlexAccount = MyPlexAccount;
211
- MyPlexAccount.key = 'https://plex.tv/api/v2/user';
212
216
  /**
213
217
  * Connects to the specified cls with url and token
214
218
  */
@@ -221,7 +225,8 @@ async function connect(cls, url, token, timeout) {
221
225
  * This object represents resources connected to your Plex server that can provide
222
226
  * content such as Plex Media Servers, iPhone or Android clients, etc.
223
227
  */
224
- class MyPlexResource {
228
+ export class MyPlexResource {
229
+ static { this.key = 'https://plex.tv/api/v2/resources?includeHttps=1&includeRelay=1'; }
225
230
  constructor(account, data, baseUrl = null) {
226
231
  this.account = account;
227
232
  this.baseUrl = baseUrl;
@@ -255,19 +260,18 @@ class MyPlexResource {
255
260
  // TODO: switch between PlexServer and PlexClient
256
261
  // Try connecting to all known resource connections in parellel, but
257
262
  // only return the first server (in order) that provides a response.
258
- const promises = attemptUrls.map(async (url) => connect((...args) => new server_1.PlexServer(...args), url, this.accessToken, timeout));
259
- const result = await p_any_1.default(promises);
263
+ const promises = attemptUrls.map(async (url) => connect((...args) => new PlexServer(...args), url, this.accessToken, timeout));
264
+ const result = await pAny(promises);
260
265
  return result;
261
266
  }
262
267
  /** Remove this device from your account */
263
268
  async delete() {
264
- const key = `https://plex.tv/api/servers/${this.clientIdentifier}?X-Plex-Client-Identifier=${config_1.BASE_HEADERS['X-Plex-Client-Identifier']}&X-Plex-Token=${this.accessToken}`;
265
- await got_1.default.delete(key, { retry: 0 });
269
+ 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 } });
266
271
  }
267
272
  _loadData(data) {
268
- var _a, _b, _c;
269
273
  this.name = data.name;
270
- this.accessToken = (_a = data.accessToken) !== null && _a !== void 0 ? _a : '';
274
+ this.accessToken = data.accessToken ?? '';
271
275
  this.owned = data.owned;
272
276
  this.clientIdentifier = data.clientIdentifier;
273
277
  this.createdAt = new Date(data.createdAt);
@@ -282,12 +286,10 @@ class MyPlexResource {
282
286
  this.product = data.product;
283
287
  this.productVersion = data.productVersion;
284
288
  this.connections =
285
- (_c = (_b = data.connections) === null || _b === void 0 ? void 0 : _b.map(connection => new ResourceConnection(connection))) !== null && _c !== void 0 ? _c : [];
289
+ data.connections?.map(connection => new ResourceConnection(connection)) ?? [];
286
290
  }
287
291
  }
288
- exports.MyPlexResource = MyPlexResource;
289
- MyPlexResource.key = 'https://plex.tv/api/v2/resources?includeHttps=1&includeRelay=1';
290
- class ResourceConnection {
292
+ export class ResourceConnection {
291
293
  constructor(data) {
292
294
  this.TAG = 'Connection';
293
295
  this._loadData(data);
@@ -301,16 +303,31 @@ class ResourceConnection {
301
303
  this.httpuri = `http://${data.address}:${data.port}`;
302
304
  }
303
305
  }
304
- exports.ResourceConnection = ResourceConnection;
305
306
  /**
306
307
  * This object represents resources connected to your Plex server that provide
307
308
  * playback ability from your Plex Server, iPhone or Android clients, Plex Web,
308
309
  * this API, etc. The raw xml for the data presented here can be found at:
309
310
  * https://plex.tv/devices.xml
310
311
  */
311
- class MyPlexDevice extends plexObject_1.PlexObject {
312
+ export class MyPlexDevice extends PlexObject {
313
+ static { this.TAG = 'Device'; }
314
+ static { this.key = 'https://plex.tv/devices.xml'; }
315
+ async connect() {
316
+ // TODO: switch between PlexServer and PlexClient
317
+ // Try connecting to all known resource connections in parellel, but
318
+ // only return the first server (in order) that provides a response.
319
+ const promises = (this.connections ?? []).map(async (url) => connect((...args) => new PlexServer(...args), url, this.token));
320
+ const result = await pAny(promises);
321
+ return result;
322
+ }
323
+ /**
324
+ * Remove this device from your account
325
+ */
326
+ async delete() {
327
+ const key = `https://plex.tv/devices/${this.id}.xml`;
328
+ await this.server.query(key, 'delete');
329
+ }
312
330
  _loadData(data) {
313
- var _a;
314
331
  this.name = data.$.name;
315
332
  this.publicAddress = data.$.publicAddress;
316
333
  this.product = data.$.product;
@@ -332,11 +349,8 @@ class MyPlexDevice extends plexObject_1.PlexObject {
332
349
  });
333
350
  this.screenResolution = data.$.screenResolution;
334
351
  this.screenDensity = data.$.screenDensity;
335
- this.createdAt = new Date(data.$.createdAt);
336
- this.lastSeenAt = new Date(data.$.lastSeenAt);
337
- this.connections = (_a = data.Connection) === null || _a === void 0 ? void 0 : _a.map(connection => connection.$.uri);
352
+ this.createdAt = new Date(parseInt(data.$.createdAt, 10));
353
+ this.lastSeenAt = new Date(parseInt(data.$.lastSeenAt, 10));
354
+ this.connections = data.Connection?.map(connection => connection.$.uri);
338
355
  }
339
356
  }
340
- exports.MyPlexDevice = MyPlexDevice;
341
- MyPlexDevice.TAG = 'Device';
342
- MyPlexDevice.key = 'https://plex.tv/devices.xml';
@@ -0,0 +1,10 @@
1
+ export var Status;
2
+ (function (Status) {
3
+ Status["Online"] = "online";
4
+ Status["Offline"] = "offline";
5
+ })(Status || (Status = {}));
6
+ export var Protocol;
7
+ (function (Protocol) {
8
+ Protocol["HTTP"] = "http";
9
+ Protocol["HTTPS"] = "https";
10
+ })(Protocol || (Protocol = {}));
@@ -0,0 +1,75 @@
1
+ import { Playable } from './base/playable.js';
2
+ import type { Section } from './library.js';
3
+ import { PlaylistResponse } from './playlist.types.js';
4
+ import type { PlexServer } from './server.js';
5
+ import { Episode, Movie, VideoType } from './video.js';
6
+ interface CreateRegularPlaylistOptions {
7
+ /** True to create a smart playlist */
8
+ smart?: false;
9
+ /** Regular playlists only */
10
+ items?: VideoType[];
11
+ }
12
+ interface CreateSmartPlaylistOptions {
13
+ /** True to create a smart playlist */
14
+ smart: true;
15
+ /** Smart playlists only, the library section to create the playlist in. */
16
+ section?: Section;
17
+ /** Smart playlists only, limit the number of items in the playlist. */
18
+ limit?: number;
19
+ /**
20
+ * Smart playlists only, a string of comma separated sort fields
21
+ * or a list of sort fields in the format ``column:dir``.
22
+ * See {@link Section.search} for more info.
23
+ */
24
+ sort?: string;
25
+ /**
26
+ * Smart playlists only, a dictionary of advanced filters.
27
+ * See {@link Section.search} for more info.
28
+ */
29
+ filters?: Record<string, any>;
30
+ }
31
+ type CreatePlaylistOptions = CreateRegularPlaylistOptions | CreateSmartPlaylistOptions;
32
+ type PlaylistContent = Episode | Movie;
33
+ export declare class Playlist extends Playable {
34
+ static TAG: string;
35
+ static create(server: PlexServer, title: string, options: CreatePlaylistOptions): Promise<Playlist>;
36
+ /** Create a smart playlist. */
37
+ private static _create;
38
+ TYPE: string;
39
+ addedAt: Date;
40
+ updatedAt: Date;
41
+ composite: string;
42
+ guid: string;
43
+ leafCount: number;
44
+ playlistType: string;
45
+ smart: boolean;
46
+ summary: string;
47
+ allowSync?: boolean;
48
+ duration?: number;
49
+ durationInSeconds?: number;
50
+ /** Cache of playlist items */
51
+ private _items;
52
+ _edit(args: {
53
+ title?: string;
54
+ summary?: string;
55
+ }): Promise<void>;
56
+ edit(changeObj: {
57
+ title?: string;
58
+ summary?: string;
59
+ }): Promise<void>;
60
+ /**
61
+ * @returns the item in the playlist that matches the specified title.
62
+ */
63
+ item(title: string): Promise<PlaylistContent | null>;
64
+ items(): Promise<PlaylistContent[]>;
65
+ /** Add items to a playlist. */
66
+ addItems(items: PlaylistContent[]): Promise<void>;
67
+ /** Remove an item from a playlist. */
68
+ removeItems(items: PlaylistContent[]): Promise<void>;
69
+ /** Delete the playlist. */
70
+ delete(): Promise<void>;
71
+ protected _loadData(data: PlaylistResponse): void;
72
+ protected _loadFullData(data: any): void;
73
+ private _getPlaylistItemID;
74
+ }
75
+ export {};
@@ -0,0 +1,142 @@
1
+ import { URLSearchParams } from 'url';
2
+ import { Playable } from './base/playable.js';
3
+ import { fetchItems } from './baseFunctionality.js';
4
+ import { BadRequest, NotFound } from './exceptions.js';
5
+ import { Episode, Movie } from './video.js';
6
+ /**
7
+ * Map media types to their respective class
8
+ */
9
+ function contentClass(data) {
10
+ switch (data.type) {
11
+ case 'episode':
12
+ return Episode;
13
+ case 'movie':
14
+ return Movie;
15
+ default:
16
+ throw new Error('Media type not implemented');
17
+ }
18
+ }
19
+ export class Playlist extends Playable {
20
+ constructor() {
21
+ super(...arguments);
22
+ this.TYPE = 'playlist';
23
+ /** Cache of playlist items */
24
+ this._items = null;
25
+ }
26
+ static { this.TAG = 'Playlist'; }
27
+ static async create(server, title, options) {
28
+ if (options.smart) {
29
+ throw new Error('not yet supported');
30
+ // return this._createSmart(server, title, options);
31
+ }
32
+ return this._create(server, title, options.items);
33
+ }
34
+ /** Create a smart playlist. */
35
+ // private static _createSmart(server: PlexServer, title: string, options: CreatePlaylistOptions) {}
36
+ static async _create(server, title, items) {
37
+ if (!items || items.length === 0) {
38
+ throw new BadRequest('Must include items to add when creating new playlist.');
39
+ }
40
+ const { listType } = items[0];
41
+ const ratingKeys = items ? items.map(x => x.ratingKey) : [];
42
+ const uri = `${server._uriRoot()}/library/metadata/${ratingKeys.join(',')}`;
43
+ const params = new URLSearchParams({
44
+ uri,
45
+ type: listType,
46
+ title,
47
+ smart: '0',
48
+ });
49
+ const key = `/playlists?${params.toString()}`;
50
+ const data = await server.query(key, 'post');
51
+ return new Playlist(server, data.MediaContainer.Metadata[0], key);
52
+ }
53
+ async _edit(args) {
54
+ const searchparams = new URLSearchParams(args);
55
+ const key = `${this.key}?${searchparams.toString()}`;
56
+ await this.server.query(key, 'put');
57
+ }
58
+ async edit(changeObj) {
59
+ await this._edit(changeObj);
60
+ }
61
+ /**
62
+ * @returns the item in the playlist that matches the specified title.
63
+ */
64
+ async item(title) {
65
+ const items = await this.items();
66
+ const matched = items.find(item => item.title.toLowerCase() === title.toLowerCase());
67
+ return matched ?? null;
68
+ }
69
+ async items() {
70
+ if (this._items === null) {
71
+ const key = `/playlists/${this.ratingKey}/items`;
72
+ const items = await fetchItems(this.server, key);
73
+ this._items = items.map(data => {
74
+ const Cls = contentClass(data);
75
+ return new Cls(this.server, data, key, this);
76
+ });
77
+ }
78
+ return this._items;
79
+ }
80
+ /** Add items to a playlist. */
81
+ async addItems(items) {
82
+ if (this.smart) {
83
+ throw new BadRequest('Cannot add items to a smart playlist.');
84
+ }
85
+ const isInvalidType = items.some(x => x.listType !== this.playlistType);
86
+ if (isInvalidType) {
87
+ throw new BadRequest('Can not mix media types when building a playlist');
88
+ }
89
+ const ratingKeys = items.map(x => x.ratingKey);
90
+ const params = new URLSearchParams({
91
+ uri: `${this.server._uriRoot()}/library/metadata/${ratingKeys.join(',')}`,
92
+ });
93
+ const key = `${this.key}/items?${params.toString()}`;
94
+ await this.server.query(key, 'put');
95
+ }
96
+ /** Remove an item from a playlist. */
97
+ async removeItems(items) {
98
+ if (this.smart) {
99
+ throw new BadRequest('Cannot remove items to a smart playlist.');
100
+ }
101
+ for (const item of items) {
102
+ // eslint-disable-next-line no-await-in-loop
103
+ const playlistItemId = await this._getPlaylistItemID(item);
104
+ const key = `${this.key}/items/${playlistItemId}`;
105
+ // eslint-disable-next-line no-await-in-loop
106
+ await this.server.query(key, 'delete');
107
+ }
108
+ }
109
+ /** Delete the playlist. */
110
+ async delete() {
111
+ await this.server.query(this.key, 'delete');
112
+ }
113
+ _loadData(data) {
114
+ this.key = data.key.replace('/items', '');
115
+ this.ratingKey = data.ratingKey;
116
+ this.title = data.title;
117
+ this.type = data.type;
118
+ this.addedAt = new Date(data.addedAt);
119
+ this.updatedAt = new Date(data.updatedAt);
120
+ this.composite = data.composite;
121
+ this.guid = data.guid;
122
+ this.playlistType = data.playlistType;
123
+ this.summary = data.summary;
124
+ this.smart = data.smart;
125
+ this.leafCount = data.leafCount;
126
+ // TODO: verify these. Possibly audio playlist related
127
+ this.allowSync = data.allowSync;
128
+ this.duration = data.duration;
129
+ this.durationInSeconds = data.durationInSeconds;
130
+ }
131
+ _loadFullData(data) {
132
+ this._loadData(data.Metadata[0]);
133
+ }
134
+ async _getPlaylistItemID(item) {
135
+ const items = await this.items();
136
+ const playlistItem = items.find(i => i.ratingKey === item.ratingKey);
137
+ if (!playlistItem) {
138
+ throw new NotFound(`Item with title "${item.title}" not found in the playlist`);
139
+ }
140
+ return playlistItem.playlistItemID;
141
+ }
142
+ }
@@ -0,0 +1,17 @@
1
+ export interface PlaylistResponse {
2
+ ratingKey: string;
3
+ key: string;
4
+ guid: string;
5
+ type: string;
6
+ title: string;
7
+ summary: string;
8
+ smart: boolean;
9
+ playlistType: string;
10
+ composite: string;
11
+ leafCount: number;
12
+ addedAt: number;
13
+ updatedAt: number;
14
+ allowSync?: boolean;
15
+ duration?: number;
16
+ durationInSeconds?: number;
17
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
1
  import { ValueOf } from 'type-fest';
2
- import { PlexObject } from './base/plexObject';
3
- import { MatchSearchResult } from './search.types';
2
+ import { PlexObject } from './base/plexObject.js';
3
+ import { MatchSearchResult } from './search.types.js';
4
4
  export declare class SearchResult extends PlexObject {
5
5
  static TAG: string;
6
6
  guid: string;
@@ -41,12 +41,13 @@ export declare const SEARCHTYPES: {
41
41
  readonly playlist: 15;
42
42
  readonly playlistFolder: 16;
43
43
  readonly collection: 18;
44
+ readonly optimizedVersion: 42;
44
45
  readonly userPlaylistItem: 1001;
45
46
  };
46
- declare type SearchTypesValues = ValueOf<typeof SEARCHTYPES>;
47
+ type SearchTypesValues = ValueOf<typeof SEARCHTYPES>;
47
48
  /**
48
49
  * Returns the integer value of the library string type.
49
50
  * @param libtype to lookup (movie, show, season, episode, artist, album, track, collection)
50
51
  */
51
- export declare function searchType(libtype: string | number | keyof typeof SEARCHTYPES | SearchTypesValues): SearchTypesValues;
52
+ export declare function searchType(libtype?: string | number | keyof typeof SEARCHTYPES | SearchTypesValues): SearchTypesValues;
52
53
  export {};
@@ -1,9 +1,7 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.searchType = exports.SEARCHTYPES = exports.Agent = exports.SearchResult = void 0;
4
- const plexObject_1 = require("./base/plexObject");
5
- const util_1 = require("./util");
6
- class SearchResult extends plexObject_1.PlexObject {
1
+ import { PlexObject } from './base/plexObject.js';
2
+ import { rsplit } from './util.js';
3
+ export class SearchResult extends PlexObject {
4
+ static { this.TAG = 'SearchResult'; }
7
5
  _loadData(data) {
8
6
  this.guid = data.guid;
9
7
  this.lifespanEnded = data.lifespanEnded;
@@ -12,19 +10,18 @@ class SearchResult extends plexObject_1.PlexObject {
12
10
  this.year = data.year;
13
11
  }
14
12
  }
15
- exports.SearchResult = SearchResult;
16
- SearchResult.TAG = 'SearchResult';
17
13
  /**
18
14
  * Represents a single Agent
19
15
  */
20
- class Agent extends plexObject_1.PlexObject {
16
+ export class Agent extends PlexObject {
17
+ static { this.TAG = 'Agent'; }
21
18
  // languageCode: any[] = [];
22
19
  _loadData(data) {
23
20
  this.hasAttribution = data.hasAttribution;
24
21
  this.hasPrefs = data.hasPrefs;
25
22
  this.identifier = data.identifier;
26
23
  this.primary = data.primary;
27
- this.shortIdentifier = util_1.rsplit(this.identifier, '.', 1)[1];
24
+ this.shortIdentifier = rsplit(this.identifier, '.', 1)[1];
28
25
  if (this.initpath.includes('mediaType')) {
29
26
  this.name = data.name;
30
27
  // this.languageCode = [];
@@ -32,9 +29,7 @@ class Agent extends plexObject_1.PlexObject {
32
29
  }
33
30
  }
34
31
  }
35
- exports.Agent = Agent;
36
- Agent.TAG = 'Agent';
37
- exports.SEARCHTYPES = {
32
+ export const SEARCHTYPES = {
38
33
  movie: 1,
39
34
  show: 2,
40
35
  season: 3,
@@ -52,21 +47,22 @@ exports.SEARCHTYPES = {
52
47
  playlist: 15,
53
48
  playlistFolder: 16,
54
49
  collection: 18,
50
+ optimizedVersion: 42,
55
51
  userPlaylistItem: 1001,
56
52
  };
57
53
  /**
58
54
  * Returns the integer value of the library string type.
59
55
  * @param libtype to lookup (movie, show, season, episode, artist, album, track, collection)
60
56
  */
61
- function searchType(libtype) {
62
- if (Object.values(exports.SEARCHTYPES)
63
- .map(x => x.toString())
64
- .includes(libtype.toString())) {
57
+ export function searchType(libtype) {
58
+ if (libtype &&
59
+ Object.values(SEARCHTYPES)
60
+ .map(num => num.toString())
61
+ .includes(`${libtype}`)) {
65
62
  return Number(libtype);
66
63
  }
67
- if (exports.SEARCHTYPES[libtype] !== undefined) {
68
- return exports.SEARCHTYPES[libtype];
64
+ if (libtype && SEARCHTYPES[libtype] !== undefined) {
65
+ return SEARCHTYPES[libtype];
69
66
  }
70
67
  throw new Error(`Unknown libtype: ${libtype}`);
71
68
  }
72
- exports.searchType = searchType;
@@ -0,0 +1 @@
1
+ export {};