From a88badabf8c9381a63ebb9015eff58a3a7ac424d Mon Sep 17 00:00:00 2001 From: Wifsimster Date: Wed, 7 Aug 2019 18:58:20 +0200 Subject: [PATCH] First commit --- .gitattributes | 2 + .gitignore | 3 + LICENSE | 21 ++++ README.md | 112 ++++++++++++++++++++- package-lock.json | 48 +++++++++ package.json | 33 ++++++ pavie.js | 252 ++++++++++++++++++++++++++++++++++++++++++++++ server.js | 13 +++ 8 files changed, 483 insertions(+), 1 deletion(-) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 pavie.js create mode 100644 server.js diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93548ab --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.vscode/ +.git diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..80a15bf --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 | BATTISTELLA Damien + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 24204d2..989d57a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,112 @@ # pavie -Node.js restfull API for Plex Server + +Node.js restfull API for Plex Media Server. + +Tested on Plex Media Server v1.16.3. + +## Install + +``` +$ npm install pavie +``` + +## Usage + +```js +const Pavie = require("./pavie") + +const pavie = new Pavie({ username: "USERNAME", password: "PASSWORD" }) + +pavie + .signin() + .then(user => { + console.log(user) + }) + .catch(err => { + console.error(err) + }) +``` + +## Documentation + +### Instance methods + +#### signin() + +Authenticate user to Plex server. This is the first method to call, this instantiate your server and your token for the others methods ! + +Return `user` `` if authentification is successfull. + +#### getResources() + +Get the list of resources with connection settings. +Conection : [name, protocol, address, port, uri, local, relay, IPv6]. + +#### getUser() + +Get user info. +Return: [id, uuid, username, title, email, thumb, authToken, certificateVersion, rememberExpiresAt]. + +#### getIdentity() + +Get basic info about the Plex server. +Return: [machineIdentifier, version] + +#### getActions() + +Get the list actions availables. +ie : platform, platformVersion, updatedAt, version, machineIdentifier, myPlexUsername. +Return : [activities, butler, channels, clients, devices, diagnostics, hubs, library, livetv, media, player, playlists, resources, search, server, ...]. + +## getLibraries() + +Get a list of libraries. +Return: [section, recentlyAdded, onDeck]. + +## getLibrary([library = 'sections']) + +Get a list of sections in the library. +Return: [Movies, Music, TV Shows]. + +## getDirectoriesFromSection([library = 'sections'], [sectionId = 2]) + +Get list of directory in a specified section. +Default : TV Shows section. +Return : [all, unwatched, newest, recentlyAdded, recenntlyViewed, recentlyViewedShows, onDeck, folder, ...] + +## getDirectory([library = 'sections'], [sectionId = 2], [directory = 'all']) + +Get a list of TV Shows by directory. +Default : TV Shows and all. +Return : [studio, type, title, contentRating, summary, index, rating, year, thumb, art, duration, originallyAvailableAt, ...]. + +## search([library = 'sections'], [sectionId = 2], [filters = { type: 2}]) + +Search Tv Shows, episodes, movies or musics. +Default : [sectionId : Tv Shows, type: Tv Shows] +For Tv Shows, type: [2: Tv Shows, 3: Seasonn, 4 : Episode] + +## refresh([library = 'sections'], [sectionId = 2]) + +Refresh a section. + +## getMedatadata([id]) + +Get metdata of a media. + +## getServers() + +Get a list of servers. +Return: [name, host, address, port, machineIdentifier, version]. + +## getSynchronize() + +Get synchronize info. + +## synchronize([accounntId]) + +Synchronize Plex and Trakt.tv. + +## getHubs([action = 'continueWatching']) + +Hubs actions [continueWatching, onDeck] diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0362760 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,48 @@ +{ + "name": "pavie", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "axios": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", + "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", + "requires": { + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + } + }, + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c055128 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "pavie", + "version": "1.0.0", + "description": "Node.js restfull API for Plex Server", + "main": "server.js", + "author": "BATTISTELLA Damien ", + "license": "MIT", + "scripts": { + "start": "node server.js", + "patch": "npm version patch && npm publish" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Wifsimster/pavie.git" + }, + "bugs": { + "url": "https://github.com/Wifsimster/pavie/issues" + }, + "keywords": [ + "javascript", + "rest", + "plex", + "movie", + "restfull", + "node.js", + "tv shows" + ], + "homepage": "https://github.com/Wifsimster/pavie#readme", + "dependencies": { + "axios": "^0.19.0", + "uuid": "^3.3.2" + } +} diff --git a/pavie.js b/pavie.js new file mode 100644 index 0000000..7d8d067 --- /dev/null +++ b/pavie.js @@ -0,0 +1,252 @@ +const uuid = require("uuid") +const axios = require("axios") +const querystring = require("querystring") + +module.exports = class { + constructor(settings) { + this.username = settings.username + this.password = settings.password + this.clientId = uuid.v4() + } + + getToken() { + if (this.user) { + return this.user.authToken + } + console.warn(`You are not yet signin !`) + return null + } + + /** + * Online API + * This medoth must be called first ! + */ + async signin() { + const response = await axios.post( + `https://plex.tv/api/v2/users/signin`, + { login: this.username, password: this.password }, + { + headers: { + "X-Plex-Client-Identifier": this.clientId + } + } + ) + + if (response.status < 400) { + this.user = response.data + + const resources = await this.getResources() + const resource = resources.filter(res => res.name === `Plex Server`)[0] + const connection = resource.connections.filter(con => con.local)[0] + + this.instance = axios.create({ + baseURL: `${connection.protocol}://${connection.address}:${connection.port}`, + headers: { + "X-Plex-Client-Identifier": this.clientId, + "X-Plex-Token": this.getToken() + } + }) + + return this.user + } else { + throw new Error("Signin failed !") + } + } + + /** + * Online API + * Get the list of resources with connection settings + * Conection : [name, protocol, address, port, uri, local, relay, IPv6] + */ + async getResources() { + const response = await axios.get(`https://plex.tv/api/v2/resources`, { + headers: { + "X-Plex-Client-Identifier": this.clientId, + "X-Plex-Token": this.getToken() + } + }) + + if (response.status < 400) { + return response.data + } + } + + /** + * Online API + * Get user info. + * Return: [id, uuid, username, title, email, thumb, authToken, certificateVersion, rememberExpiresAt] + */ + async getUser() { + const response = await axios.get( + `https://plex.tv/api/v2/user?includeSubscriptions=1&includeSettings=1&includeSharedSettings=1`, + { + headers: { + "X-Plex-Client-Identifier": this.clientId, + "X-Plex-Token": this.getToken() + } + } + ) + + if (response.status < 400) { + return response.data + } + } + + /** + * Get basic info about the Plex server. + * Return: [machineIdentifier, version] + */ + async getIdentity() { + const response = await this.instance.get(`/identity`) + + if (response.status < 400) { + return response.data.MediaContainer + } + } + + /** + * Get the list actions availables. + * ie : platform, platformVersion, updatedAt, version, machineIdentifier, myPlexUsername + * Return : [activities, butler, channels, clients, devices, diagnostics, hubs, library, livetv, media, player, playlists, resources, search, server, ...] + */ + async getActions() { + const response = await this.instance.get(`/`) + + if (response.status < 400) { + return response.data.MediaContainer + } + } + + /** + * Get a list of libraries + * Return: [section, recentlyAdded, onDeck] + */ + async getLibraries() { + const response = await this.instance.get("/library") + + if (response.status < 400) { + return response.data.MediaContainer + } + } + + /** + * Get a list of sections in the library + * Return: [Movies, Music, TV Shows] + */ + async getLibrary(library = "sections") { + const response = await this.instance.get(`/library/${library}`) + + if (response.status < 400) { + return response.data.MediaContainer + } + } + + /** + * Get list of directory in a specified section + * Default : TV Shows section + * Return : [all, unwatched, newest, recentlyAdded, recenntlyViewed, recentlyViewedShows, onDeck, folder, ...] * + */ + async getDirectoriesFromSection(library = "sections", sectionId = 2) { + const response = await this.instance.get(`/library/${library}/${sectionId}`) + + if (response.status < 400) { + return response.data.MediaContainer + } + } + + /** + * Get a list of TV Shows by directory + * Default : TV Shows and all + * Return : [studio, type, title, contentRating, summary, index, rating, year, thumb, art, duration, originallyAvailableAt, ...] + */ + async getDirectory(library = "sections", sectionId = 2, directory = "all") { + const response = await this.instance.get(`/library/${library}/${sectionId}/${directory}`) + + if (response.status < 400) { + return response.data.MediaContainer + } + } + + /** + * Search Tv Shows, episodes, movies or musics + * Default : [sectionId : Tv Shows, type: Tv Shows] + * For Tv Shows, type: [2: Tv Shows, 3: Seasonn, 4 : Episode] + */ + async search(library = "sections", sectionId = 2, filters = { type: 2 }) { + const response = await this.instance.get( + `/library/${library}/${sectionId}/search?${querystring.stringify(filters)}` + ) + + if (response.status < 400) { + return response.data.MediaContainer + } + } + + /** + * Refresh a section + */ + async refresh(library = "sections", sectionId = 2) { + const response = await this.instance.get(`/library/${library}/${sectionId}/refresh`) + + if (response.status < 400) { + return response.data.MediaContainer + } + } + + /** + * Get metdata of a media + */ + async getMedatadata(id) { + const response = await this.instance.get(`/library/metadata/${id}`) + + if (response.status < 400) { + return response.data.MediaContainer + } + } + + /** + * Get a list of servers + * Return: [name, host, address, port, machineIdentifier, version] + */ + async getServers() { + const response = await this.instance.get("/servers") + + if (response.status < 400) { + return response.data.MediaContainer + } + } + + /** + * Get synchronize info + */ + async getSynchronize() { + const response = await this.instance.get("/video/trakt/sync") + + if (response.status < 400) { + return response.data.MediaContainer + } + } + + /** + * Synchronize Plex and Trakt.tv + */ + async synchronize(accoundId = "1&t=1565171925.59") { + const response = await this.instance.get(`/video/trakt/sync/synchronize?account_id=${accoundId}`) + + if (response.status < 400) { + return response.data.MediaContainer + } + } + + /** + * Hubs actions + * [continueWatchig, onDeck] + */ + async getHubs(action = "continueWatching") { + const response = await this.instance.get(`/hubs/home/${action}`) + + if (response.status < 400) { + return response.data.MediaContainer + } + } +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..babf554 --- /dev/null +++ b/server.js @@ -0,0 +1,13 @@ +const Pavie = require("./pavie") + +const pavie = new Pavie({ username: "wifsimster", password: "192Lucie!!" }) + +pavie + .signin() + .then(async () => { + const response = await pavie.getLibraries() + console.log(response) + }) + .catch(err => { + console.error(err) + })