@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
|
@@ -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,9 +41,10 @@ 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
|
-
|
|
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)
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
36
|
-
Agent.TAG = 'Agent';
|
|
37
|
-
exports.SEARCHTYPES = {
|
|
32
|
+
export const SEARCHTYPES = {
|
|
38
33
|
movie: 1,
|
|
39
34
|
show: 2,
|
|
40
35
|
season: 3,
|
|
@@ -52,23 +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) {
|
|
57
|
+
export function searchType(libtype) {
|
|
62
58
|
if (libtype &&
|
|
63
|
-
Object.values(
|
|
59
|
+
Object.values(SEARCHTYPES)
|
|
64
60
|
.map(num => num.toString())
|
|
65
61
|
.includes(`${libtype}`)) {
|
|
66
62
|
return Number(libtype);
|
|
67
63
|
}
|
|
68
|
-
if (libtype &&
|
|
69
|
-
return
|
|
64
|
+
if (libtype && SEARCHTYPES[libtype] !== undefined) {
|
|
65
|
+
return SEARCHTYPES[libtype];
|
|
70
66
|
}
|
|
71
|
-
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
72
67
|
throw new Error(`Unknown libtype: ${libtype}`);
|
|
73
68
|
}
|
|
74
|
-
exports.searchType = searchType;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
2
|
import { URL, URLSearchParams } from 'url';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { Optimized } from './media';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
3
|
+
import { PlexClient } from './client.js';
|
|
4
|
+
import { Hub, Library } from './library.js';
|
|
5
|
+
import { Optimized } from './media.js';
|
|
6
|
+
import { MyPlexAccount } from './myplex.js';
|
|
7
|
+
import { Agent, SEARCHTYPES } from './search.js';
|
|
8
|
+
import { HistoryMetadatum } from './server.types.js';
|
|
9
|
+
import { Settings } from './settings.js';
|
|
9
10
|
/**
|
|
10
11
|
* This is the main entry point to interacting with a Plex server. It allows you to
|
|
11
12
|
* list connected clients, browse your library sections and perform actions such as
|
|
@@ -16,7 +17,7 @@ import { MyPlexAccount } from './myplex';
|
|
|
16
17
|
export declare class PlexServer {
|
|
17
18
|
readonly baseurl: string;
|
|
18
19
|
readonly token: string;
|
|
19
|
-
readonly timeout?: number
|
|
20
|
+
readonly timeout?: number;
|
|
20
21
|
key: string;
|
|
21
22
|
/** True if server allows camera upload */
|
|
22
23
|
allowCameraUpload: boolean;
|
|
@@ -51,7 +52,7 @@ export declare class PlexServer {
|
|
|
51
52
|
*/
|
|
52
53
|
hubSearch: boolean;
|
|
53
54
|
/** Unique ID for this server (looks like an md5) */
|
|
54
|
-
machineIdentifier
|
|
55
|
+
machineIdentifier?: string;
|
|
55
56
|
/**
|
|
56
57
|
* True if `multiusers <https!://support.plex.tv/hc/en-us/articles/200250367-Multi-User-Support>`_ are enabled.
|
|
57
58
|
*/
|
|
@@ -128,8 +129,9 @@ export declare class PlexServer {
|
|
|
128
129
|
/** Unknown */
|
|
129
130
|
pushNotifications: boolean;
|
|
130
131
|
_library?: Library;
|
|
132
|
+
_settings?: Settings;
|
|
131
133
|
private _myPlexAccount?;
|
|
132
|
-
constructor(baseurl: string, token: string, timeout?: number
|
|
134
|
+
constructor(baseurl: string, token: string, timeout?: number);
|
|
133
135
|
agents(mediaType?: number | string): Promise<Agent[]>;
|
|
134
136
|
connect(): Promise<void>;
|
|
135
137
|
/**
|
|
@@ -175,6 +177,7 @@ export declare class PlexServer {
|
|
|
175
177
|
* @param librarySectionId request history for a specific library section ID.
|
|
176
178
|
*/
|
|
177
179
|
history(maxresults?: number, mindate?: Date, ratingKey?: number | string, accountId?: number | string, librarySectionId?: number | string): Promise<HistoryMetadatum[]>;
|
|
180
|
+
settings(): Promise<Settings>;
|
|
178
181
|
/**
|
|
179
182
|
* Returns a :class:`~plexapi.myplex.MyPlexAccount` object using the same
|
|
180
183
|
* token to access this server. If you are not the owner of this PlexServer
|
|
@@ -189,6 +192,15 @@ export declare class PlexServer {
|
|
|
189
192
|
* if either includeToken is True or TODO: CONFIG.log.show_secrets is 'true'.
|
|
190
193
|
*/
|
|
191
194
|
url(key: string, includeToken?: boolean, params?: URLSearchParams): URL;
|
|
195
|
+
/**
|
|
196
|
+
* Build the Plex Web URL for the object.
|
|
197
|
+
* @param base The base URL before the fragment (``#!``).
|
|
198
|
+
* Default is https://app.plex.tv/desktop.
|
|
199
|
+
* @param endpoint The Plex Web URL endpoint.
|
|
200
|
+
* None for server, 'playlist' for playlists, 'details' for all other media types.
|
|
201
|
+
*/
|
|
202
|
+
_buildWebURL(base?: string, endpoint?: string, params?: URLSearchParams): string;
|
|
203
|
+
_uriRoot(): string;
|
|
192
204
|
private _headers;
|
|
193
205
|
private _loadData;
|
|
194
206
|
/**
|
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const baseFunctionality_1 = require("./baseFunctionality");
|
|
12
|
-
const media_1 = require("./media");
|
|
13
|
-
const search_1 = require("./search");
|
|
14
|
-
const client_1 = require("./client");
|
|
15
|
-
const myplex_1 = require("./myplex");
|
|
1
|
+
import { URL, URLSearchParams } from 'url';
|
|
2
|
+
import got from 'got';
|
|
3
|
+
import { fetchItem, fetchItems } from './baseFunctionality.js';
|
|
4
|
+
import { PlexClient } from './client.js';
|
|
5
|
+
import { BASE_HEADERS, TIMEOUT, X_PLEX_CONTAINER_SIZE } from './config.js';
|
|
6
|
+
import { Hub, Library } from './library.js';
|
|
7
|
+
import { Optimized } from './media.js';
|
|
8
|
+
import { MyPlexAccount } from './myplex.js';
|
|
9
|
+
import { Agent, SEARCHTYPES } from './search.js';
|
|
10
|
+
import { Settings } from './settings.js';
|
|
16
11
|
/**
|
|
17
12
|
* This is the main entry point to interacting with a Plex server. It allows you to
|
|
18
13
|
* list connected clients, browse your library sections and perform actions such as
|
|
@@ -20,7 +15,7 @@ const myplex_1 = require("./myplex");
|
|
|
20
15
|
* server, or simply want to access your server with your username and password, you
|
|
21
16
|
* can also create an PlexServer instance from :class:`~plexapi.myplex.MyPlexAccount`.
|
|
22
17
|
*/
|
|
23
|
-
class PlexServer {
|
|
18
|
+
export class PlexServer {
|
|
24
19
|
constructor(baseurl, token, timeout) {
|
|
25
20
|
this.baseurl = baseurl;
|
|
26
21
|
this.token = token;
|
|
@@ -32,7 +27,7 @@ class PlexServer {
|
|
|
32
27
|
if (mediaType) {
|
|
33
28
|
key += `?mediaType=${mediaType}`;
|
|
34
29
|
}
|
|
35
|
-
return
|
|
30
|
+
return fetchItems(this, key, undefined, Agent, this);
|
|
36
31
|
}
|
|
37
32
|
async connect() {
|
|
38
33
|
const data = await this.query(this.key, undefined, undefined, this.timeout);
|
|
@@ -53,13 +48,13 @@ class PlexServer {
|
|
|
53
48
|
return this._library;
|
|
54
49
|
}
|
|
55
50
|
try {
|
|
56
|
-
const data = await this.query(
|
|
57
|
-
this._library = new
|
|
51
|
+
const data = await this.query(Library.key);
|
|
52
|
+
this._library = new Library(this, data.MediaContainer);
|
|
58
53
|
}
|
|
59
|
-
catch
|
|
54
|
+
catch {
|
|
60
55
|
// TODO: validate error type, also TODO figure out how this is used
|
|
61
56
|
const data = await this.query('/library/sections/');
|
|
62
|
-
this._library = new
|
|
57
|
+
this._library = new Library(this, data.MediaContainer);
|
|
63
58
|
}
|
|
64
59
|
return this._library;
|
|
65
60
|
}
|
|
@@ -81,13 +76,13 @@ class PlexServer {
|
|
|
81
76
|
async search(query, mediatype, limit) {
|
|
82
77
|
const params = { query };
|
|
83
78
|
if (mediatype) {
|
|
84
|
-
params.section =
|
|
79
|
+
params.section = SEARCHTYPES[mediatype].toString();
|
|
85
80
|
}
|
|
86
81
|
if (limit) {
|
|
87
82
|
params.limit = limit.toString();
|
|
88
83
|
}
|
|
89
|
-
const key = '/hubs/search?' + new
|
|
90
|
-
const hubs = await
|
|
84
|
+
const key = '/hubs/search?' + new URLSearchParams(params).toString();
|
|
85
|
+
const hubs = await fetchItems(this, key, undefined, Hub, this);
|
|
91
86
|
return hubs;
|
|
92
87
|
}
|
|
93
88
|
/**
|
|
@@ -107,14 +102,14 @@ class PlexServer {
|
|
|
107
102
|
requestHeaders.Authorization = `Basic ${credentials}`;
|
|
108
103
|
}
|
|
109
104
|
const url = this.url(path);
|
|
110
|
-
const response = await
|
|
105
|
+
const response = await got({
|
|
111
106
|
method,
|
|
112
107
|
url,
|
|
113
108
|
headers: requestHeaders,
|
|
114
|
-
timeout:
|
|
115
|
-
username,
|
|
116
|
-
password,
|
|
117
|
-
retry: 0,
|
|
109
|
+
timeout: { request: timeout ?? TIMEOUT },
|
|
110
|
+
...(username ? { username } : {}),
|
|
111
|
+
...(password ? { password } : {}),
|
|
112
|
+
retry: { limit: 0 },
|
|
118
113
|
}).json();
|
|
119
114
|
return response;
|
|
120
115
|
}
|
|
@@ -144,23 +139,30 @@ class PlexServer {
|
|
|
144
139
|
args['viewedAt>'] = mindate.getTime().toString();
|
|
145
140
|
}
|
|
146
141
|
args['X-Plex-Container-Start'] = '0';
|
|
147
|
-
args['X-Plex-Container-Size'] = Math.min(
|
|
142
|
+
args['X-Plex-Container-Size'] = Math.min(X_PLEX_CONTAINER_SIZE, maxresults).toString();
|
|
148
143
|
let results = [];
|
|
149
|
-
let key = '/status/sessions/history/all?' + new
|
|
144
|
+
let key = '/status/sessions/history/all?' + new URLSearchParams(args).toString();
|
|
150
145
|
let raw = await this.query(key);
|
|
151
146
|
const totalResults = raw.MediaContainer.totalSize;
|
|
152
147
|
results = results.concat(raw.MediaContainer.Metadata);
|
|
153
148
|
while (results.length <= totalResults &&
|
|
154
|
-
|
|
149
|
+
X_PLEX_CONTAINER_SIZE === raw.MediaContainer.size &&
|
|
155
150
|
maxresults > results.length) {
|
|
156
151
|
args['X-Plex-Container-Start'] = (Number(args['X-Plex-Container-Start']) + Number(args['X-Plex-Container-Size'])).toString();
|
|
157
|
-
key = '/status/sessions/history/all?' + new
|
|
152
|
+
key = '/status/sessions/history/all?' + new URLSearchParams(args).toString();
|
|
158
153
|
// eslint-disable-next-line no-await-in-loop
|
|
159
154
|
raw = await this.query(key);
|
|
160
155
|
results = results.concat(raw.MediaContainer.Metadata);
|
|
161
156
|
}
|
|
162
157
|
return results;
|
|
163
158
|
}
|
|
159
|
+
async settings() {
|
|
160
|
+
if (!this._settings) {
|
|
161
|
+
const data = await this.query(Settings.key);
|
|
162
|
+
this._settings = new Settings(this, data.MediaContainer.Setting);
|
|
163
|
+
}
|
|
164
|
+
return this._settings;
|
|
165
|
+
}
|
|
164
166
|
// TODO: not sure if this works
|
|
165
167
|
// /**
|
|
166
168
|
// * Returns a list of all playlist objects saved on the server.
|
|
@@ -178,38 +180,37 @@ class PlexServer {
|
|
|
178
180
|
*/
|
|
179
181
|
myPlexAccount() {
|
|
180
182
|
if (!this._myPlexAccount) {
|
|
181
|
-
this._myPlexAccount = new
|
|
183
|
+
this._myPlexAccount = new MyPlexAccount(this.baseurl, undefined, undefined, this.token, this.timeout, this);
|
|
182
184
|
}
|
|
183
185
|
return this._myPlexAccount;
|
|
184
186
|
}
|
|
185
187
|
// Returns list of all :class:`~plexapi.client.PlexClient` objects connected to server.
|
|
186
188
|
async clients() {
|
|
187
|
-
var _a, _b;
|
|
188
189
|
const items = [];
|
|
189
190
|
const response = await this.query('/clients');
|
|
190
|
-
if (
|
|
191
|
+
if (response.MediaContainer?.Server === undefined) {
|
|
191
192
|
return [];
|
|
192
193
|
}
|
|
193
194
|
const shouldFetchPorts = response.MediaContainer.Server.some(server => server.port === null || server.port === undefined);
|
|
194
|
-
let ports;
|
|
195
|
+
let ports = {};
|
|
195
196
|
if (shouldFetchPorts) {
|
|
196
197
|
ports = await this._myPlexClientPorts();
|
|
197
198
|
}
|
|
198
199
|
for (const server of response.MediaContainer.Server) {
|
|
199
|
-
let port = server
|
|
200
|
+
let { port } = server;
|
|
200
201
|
if (!port) {
|
|
201
202
|
// TODO: print warning about doing weird port stuff
|
|
202
|
-
port = (
|
|
203
|
+
port = Number(ports?.[server.machineIdentifier]);
|
|
203
204
|
}
|
|
204
205
|
const baseurl = `http://${server.host}:${port}`;
|
|
205
|
-
items.push(new
|
|
206
|
+
items.push(new PlexClient({ baseurl, token: this.token, server: this, data: server }));
|
|
206
207
|
}
|
|
207
208
|
return items;
|
|
208
209
|
}
|
|
209
210
|
/** Returns list of all :class:`~plexapi.media.Optimized` objects connected to server. */
|
|
210
211
|
async optimizedItems() {
|
|
211
|
-
const backgroundProcessing = await
|
|
212
|
-
const items = await
|
|
212
|
+
const backgroundProcessing = await fetchItem(this, '/playlists?type=42');
|
|
213
|
+
const items = await fetchItems(this, backgroundProcessing.key, undefined, Optimized, this);
|
|
213
214
|
return items;
|
|
214
215
|
}
|
|
215
216
|
/**
|
|
@@ -220,18 +221,34 @@ class PlexServer {
|
|
|
220
221
|
if (!this.baseurl) {
|
|
221
222
|
throw new Error('PlexClient object missing baseurl.');
|
|
222
223
|
}
|
|
223
|
-
const url = new
|
|
224
|
+
const url = new URL(key, this.baseurl);
|
|
224
225
|
if (this.token && includeToken) {
|
|
225
|
-
const searchParams = new
|
|
226
|
+
const searchParams = new URLSearchParams(params);
|
|
226
227
|
searchParams.append('X-Plex-Token', this.token);
|
|
227
228
|
url.search = searchParams.toString();
|
|
228
229
|
return url;
|
|
229
230
|
}
|
|
230
231
|
return url;
|
|
231
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Build the Plex Web URL for the object.
|
|
235
|
+
* @param base The base URL before the fragment (``#!``).
|
|
236
|
+
* Default is https://app.plex.tv/desktop.
|
|
237
|
+
* @param endpoint The Plex Web URL endpoint.
|
|
238
|
+
* None for server, 'playlist' for playlists, 'details' for all other media types.
|
|
239
|
+
*/
|
|
240
|
+
_buildWebURL(base = 'https://app.plex.tv/desktop/', endpoint, params) {
|
|
241
|
+
if (endpoint) {
|
|
242
|
+
return `${base}#!/server/${this.machineIdentifier}/${endpoint}?${params?.toString()}`;
|
|
243
|
+
}
|
|
244
|
+
return `${base}#!/media/${this.machineIdentifier}/com.plexapp.plugins.library?${params?.toString()}`;
|
|
245
|
+
}
|
|
246
|
+
_uriRoot() {
|
|
247
|
+
return `server://${this.machineIdentifier}/com.plexapp.plugins.library`;
|
|
248
|
+
}
|
|
232
249
|
_headers() {
|
|
233
250
|
const headers = {
|
|
234
|
-
...
|
|
251
|
+
...BASE_HEADERS,
|
|
235
252
|
'Content-type': 'application/json',
|
|
236
253
|
};
|
|
237
254
|
if (this.token) {
|
|
@@ -292,16 +309,14 @@ class PlexServer {
|
|
|
292
309
|
* See python plex issue #126: Make PlexServer.clients() more user friendly.
|
|
293
310
|
*/
|
|
294
311
|
async _myPlexClientPorts() {
|
|
295
|
-
|
|
296
|
-
let ports = {};
|
|
312
|
+
const ports = {};
|
|
297
313
|
const account = this.myPlexAccount();
|
|
298
314
|
const devices = await account.devices();
|
|
299
315
|
for (const device of devices) {
|
|
300
|
-
if (
|
|
301
|
-
ports[device.clientIdentifier] = new
|
|
316
|
+
if (device.connections?.length) {
|
|
317
|
+
ports[device.clientIdentifier] = new URL('http://172.17.0.2:32400').port;
|
|
302
318
|
}
|
|
303
319
|
}
|
|
304
320
|
return ports;
|
|
305
321
|
}
|
|
306
322
|
}
|
|
307
|
-
exports.PlexServer = PlexServer;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|