diff --git a/.DS_Store b/.DS_Store index 6902ccc..61ef7d5 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/package-lock.json b/package-lock.json index f10f2da..f890fb7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,19 @@ { "name": "plex-api-oauth", - "version": "1.0.128", + "version": "1.1.10", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "plex-api-oauth", - "version": "1.0.128", + "version": "1.1.10", "license": "MIT", "dependencies": { "axios": "^0.27.2", "plex-oauth": "^2.0.2", "qs": "^6.11.0", + "react": "^18.2.0", + "react-infinite-scroller": "^1.2.6", "ts-mocha": "^10.0.0", "typescript": "^4.7.4", "uuid": "^8.3.2" @@ -173,9 +175,9 @@ } }, "node_modules/@sinclair/typebox": { - "version": "0.24.20", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.20.tgz", - "integrity": "sha512-kVaO5aEFZb33nPMTZBxiPEkY+slxiPtqC7QX8f9B3eGOMBvEfuMfxp9DSTTCsRJPumPKjrge4yagyssO4q6qzQ==", + "version": "0.24.22", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.22.tgz", + "integrity": "sha512-JsBe3cOFpNZ6yjBYnXKhcENWy5qZE3PQZwExQ5ksA/h8qp4bwwxFmy07A6bC2R6qv6+RF3SfrbQTskTwYNTXUQ==", "dev": true }, "node_modules/@types/expect": { @@ -225,9 +227,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.1.tgz", - "integrity": "sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==", + "version": "18.6.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.2.tgz", + "integrity": "sha512-KcfkBq9H4PI6Vpu5B/KoPeuVDAbmi+2mDBqGPGUgoL7yXQtcWGu2vJWmmRkneWK3Rh0nIAX192Aa87AqKHYChQ==", "dev": true }, "node_modules/@types/qs": { @@ -1137,8 +1139,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -1231,6 +1232,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -1382,6 +1394,14 @@ "node": "*" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", @@ -1502,6 +1522,21 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -1539,6 +1574,28 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-infinite-scroller": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/react-infinite-scroller/-/react-infinite-scroller-1.2.6.tgz", + "integrity": "sha512-mGdMyOD00YArJ1S1F3TVU9y4fGSfVVl6p5gh/Vt4u99CJOptfVu/q5V/Wlle72TMgYlBwIhbxK5wF0C/R33PXQ==", + "dependencies": { + "prop-types": "^15.5.8" + }, + "peerDependencies": { + "react": "^0.14.9 || ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -2159,9 +2216,9 @@ } }, "@sinclair/typebox": { - "version": "0.24.20", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.20.tgz", - "integrity": "sha512-kVaO5aEFZb33nPMTZBxiPEkY+slxiPtqC7QX8f9B3eGOMBvEfuMfxp9DSTTCsRJPumPKjrge4yagyssO4q6qzQ==", + "version": "0.24.22", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.22.tgz", + "integrity": "sha512-JsBe3cOFpNZ6yjBYnXKhcENWy5qZE3PQZwExQ5ksA/h8qp4bwwxFmy07A6bC2R6qv6+RF3SfrbQTskTwYNTXUQ==", "dev": true }, "@types/expect": { @@ -2210,9 +2267,9 @@ "dev": true }, "@types/node": { - "version": "18.6.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.1.tgz", - "integrity": "sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==", + "version": "18.6.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.2.tgz", + "integrity": "sha512-KcfkBq9H4PI6Vpu5B/KoPeuVDAbmi+2mDBqGPGUgoL7yXQtcWGu2vJWmmRkneWK3Rh0nIAX192Aa87AqKHYChQ==", "dev": true }, "@types/qs": { @@ -2897,8 +2954,7 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { "version": "4.1.0", @@ -2970,6 +3026,14 @@ "is-unicode-supported": "^0.1.0" } }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -3079,6 +3143,11 @@ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, "object-inspect": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", @@ -3167,6 +3236,23 @@ } } }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, "psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -3195,6 +3281,22 @@ "safe-buffer": "^5.1.0" } }, + "react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-infinite-scroller": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/react-infinite-scroller/-/react-infinite-scroller-1.2.6.tgz", + "integrity": "sha512-mGdMyOD00YArJ1S1F3TVU9y4fGSfVVl6p5gh/Vt4u99CJOptfVu/q5V/Wlle72TMgYlBwIhbxK5wF0C/R33PXQ==", + "requires": { + "prop-types": "^15.5.8" + } + }, "react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", diff --git a/package.json b/package.json index 01ad1ff..c126db5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plex-api-oauth", - "version": "1.1.9", + "version": "1.1.46", "description": "An NPM Module designed to make Plex Media Server and plex.tv API calls easier to implement in JavaScript and React projects", "main": "./src/index.js", "type": "module", @@ -13,6 +13,8 @@ "axios": "^0.27.2", "plex-oauth": "^2.0.2", "qs": "^6.11.0", + "react": "^18.2.0", + "react-infinite-scroller": "^1.2.6", "ts-mocha": "^10.0.0", "typescript": "^4.7.4", "uuid": "^8.3.2" diff --git a/src/Components/InfiniteScrollArtists.js b/src/Components/InfiniteScrollArtists.js new file mode 100644 index 0000000..e69de29 diff --git a/src/PlexAPIOAuth/PlexAPIOAuth.js b/src/PlexAPIOAuth/PlexAPIOAuth.js index 53aeaff..5fba485 100644 --- a/src/PlexAPIOAuth/PlexAPIOAuth.js +++ b/src/PlexAPIOAuth/PlexAPIOAuth.js @@ -2,6 +2,8 @@ import { PlexOauth } from "plex-oauth"; import { v4 } from "uuid"; import axios from "axios"; import qs from "qs"; +import InfiniteScroll from "react-infinite-scroller"; +import { useState, useEffect } from "react"; export class PlexAPIOAuth { plexClientInformation; @@ -16,6 +18,7 @@ export class PlexAPIOAuth { plexServers; plexLibraries; plexDevices; + cancelToken; constructor( clientId = "", product = "Plex-API-OAuth", @@ -27,7 +30,8 @@ export class PlexAPIOAuth { plexTVUserData = {}, plexServers = [], plexLibraries = [], - plexDevices = [] + plexDevices = [], + cancelToken = null ) { this.clientId = clientId; this.product = product; @@ -40,6 +44,7 @@ export class PlexAPIOAuth { this.plexServers = plexServers; this.plexDevices = plexDevices; this.plexLibraries = plexLibraries; + this.cancelToken = cancelToken; this.plexClientInformation = { clientIdentifier: this.clientId, // This is a unique identifier used to identify your app with Plex. - If none is provided a new one is generated and saved locally @@ -138,18 +143,51 @@ export class PlexAPIOAuth { ); } + fnBrowserDetect() { + let userAgent = navigator.userAgent; + let browserName; + + if (userAgent.match(/chrome|chromium|crios/i)) { + browserName = "chrome"; + } else if (userAgent.match(/firefox|fxios/i)) { + browserName = "firefox"; + } else if (userAgent.match(/safari/i)) { + browserName = "safari"; + } else if (userAgent.match(/opr\//i)) { + browserName = "opera"; + } else if (userAgent.match(/edg/i)) { + browserName = "edge"; + } else { + browserName = "No browser detection"; + } + return browserName; + } + GenerateClientId() { this.clientId = v4(); this.plexClientInformation.clientIdentifier = this.clientId; } - async PlexLogin() { - if ( - this.clientId == null || - this.plexClientInformation.clientIdentifier == null - ) { - this.GenerateClientId(); + GenerateClientInformation() { + if (typeof navigator !== "undefined") { + this.clientId = v4(); + this.product = this.fnBrowserDetect; + this.device = navigator.userAgentData.platform; + this.version = navigator.userAgentData.brands[0].version; + this.plexClientInformation = { + clientIdentifier: this.clientId, + product: this.product, + device: this.device, + version: this.version, + forwardUrl: this.forwardUrl, + platform: this.platform, + }; + console.log("Client Information generated successfully"); } + throw "Unable to detect Client"; + } + + async PlexLogin() { var plexOauth = new PlexOauth(this.plexClientInformation); let data = await plexOauth.requestHostedLoginURL().catch((err) => { throw err; @@ -205,45 +243,34 @@ export class PlexAPIOAuth { headers: { accept: "application/json" }, }).catch(function (error) { if (error.response) { - // The request was made and the server responded with a status code - // that falls out of the range of 2xx console.log(error.response.data); console.log(error.response.status); console.log(error.response.headers); } else if (error.request) { - // The request was made but no response was received - // `error.request` is an instance of XMLHttpRequest in the browser and an instance of - // http.ClientRequest in node.js console.log(error.request); } else { - // Something happened in setting up the request that triggered an Error console.log("Error", error.message); } console.log(error.config); }); - //console.log(response); - //console.log(response.status); if (response.status === 200) { - //console.log("Authentican Token Validated Successfully"); this.plexTVUserData = response.data; - //console.log("Populated User Data Successfully"); return this.plexTVUserData; } if (response.status === 401) { - //console.log("Authentican Token Failed Validation"); return null; } } - async GetPlexServers(searchParams = null) { + async GetPlexServers(searchParams = {}, filter = {}) { let serverArray = []; let response = await axios({ method: "GET", url: "https://plex.tv/api/v2/resources?" + - qs.stringify(searchParams) + qs.stringify({ + ...searchParams, includeHttps: 1, includeRelay: 1, includeIPv6: 1, @@ -257,9 +284,15 @@ export class PlexAPIOAuth { throw err; }); this.plexDevices = response.data; - for (const server of response.data.filter( - (Obj) => Obj.product === "Plex Media Server" - )) { + for (const server of response.data + .filter((Obj) => Obj.product === "Plex Media Server") + .filter((Obj) => { + for (var key in filter) { + if (Obj[key] === filter[key]) { + return Obj; + } + } + })) { let localConnection = null; let serverCapabilities = null; let preferredConnection = server.connections.filter( @@ -525,9 +558,9 @@ export class PlexAPIOAuth { } async GetPlexMovies( + searchParams = {}, servers = this.plexServers, - libraries = this.plexLibraries, - searchParams = null + libraries = this.plexLibraries ) { let movieLibraryContent = []; for (const server of servers) { @@ -542,19 +575,17 @@ export class PlexAPIOAuth { )) { let response = await axios({ method: "GET", - url: - connectionUri.uri + - "/library/sections/" + - library?.key + - "/all?" + - qs.stringify(searchParams) + - qs.stringify({ - type: 1, - "X-Plex-Token": server?.accessToken, - }), + url: connectionUri.uri + "/library/sections/" + library?.key + "/all", + params: { + type: 1, + ...searchParams, + "X-Plex-Token": server?.accessToken, + }, + cancelToken: axios.CancelToken((c) => (this.cancelToken = c)), headers: { accept: "application/json" }, - }).catch((err) => { - throw err; + }).catch((e) => { + if (axios.isCancel(e)) return; + throw e; }); for (const data of response.data.MediaContainer.Metadata) { movieLibraryContent.push({ @@ -595,9 +626,9 @@ export class PlexAPIOAuth { } async GetPlexArtists( + searchParams = {}, servers = this.plexServers, - libraries = this.plexLibraries, - searchParams = null + libraries = this.plexLibraries ) { let artistLibraryContent = []; for (const server of servers) { @@ -612,16 +643,14 @@ export class PlexAPIOAuth { )) { let response = await axios({ method: "GET", - url: - connectionUri.uri + - "/library/sections/" + - library?.key + - "/all?" + - qs.stringify(searchParams) + - qs.stringify({ - "X-Plex-Token": server.accessToken, - }), + url: connectionUri.uri + "/library/sections/" + library?.key + "/all", headers: { accept: "application/json" }, + params: { + type: 8, + ...searchParams, + "X-Plex-Token": server.accessToken, + }, + cancelToken: axios.CancelToken((c) => (this.cancelToken = c)), }).catch((err) => { throw err; }); @@ -651,9 +680,9 @@ export class PlexAPIOAuth { } async GetPlexAlbums( + searchParams = {}, servers = this.plexServers, - libraries = this.plexLibraries, - searchParams = null + libraries = this.plexLibraries ) { let albumLibraryContent = []; for (const server of servers) { @@ -668,17 +697,14 @@ export class PlexAPIOAuth { )) { let response = await axios({ method: "GET", - url: - connectionUri.uri + - "/library/sections/" + - library?.key + - "/all?" + - qs.stringify(searchParams) + - qs.stringify({ - type: 9, - "X-Plex-Token": server?.accessToken, - }), + url: connectionUri.uri + "/library/sections/" + library?.key + "/all", headers: { accept: "application/json" }, + params: { + type: 9, + ...searchParams, + "X-Plex-Token": server.accessToken, + }, + cancelToken: axios.CancelToken((c) => (this.cancelToken = c)), }).catch((err) => { throw err; }); @@ -713,9 +739,9 @@ export class PlexAPIOAuth { } async GetPlexSongs( + searchParams = {}, servers = this.plexServers, - libraries = this.plexLibraries, - searchParams = null + libraries = this.plexLibraries ) { let songLibraryContent = []; for (const server of servers) { @@ -730,60 +756,61 @@ export class PlexAPIOAuth { )) { let response = await axios({ method: "GET", - url: - connectionUri.uri + - "/library/sections/" + - library?.key + - "/all?" + - qs.stringify(searchParams) + - qs.stringify({ - type: 10, - "X-Plex-Token": server?.accessToken, - }), + url: connectionUri.uri + "/library/sections/" + library?.key + "/all", headers: { accept: "application/json" }, + params: { + type: 10, + ...searchParams, + "X-Plex-Token": server.accessToken, + }, + cancelToken: axios.CancelToken((c) => (this.cancelToken = c)), }).catch((err) => { + if (axios.isCancel(err)) return; throw err; }); - for (const data of response.data.MediaContainer.Metadata) { - songLibraryContent.push({ - server: server, - library: library, - ratingKey: data.ratingKey, - key: data.key, - parentRatingKey: data.parentRatingKey, - grandparentRatingKey: data.grandparentRatingKey, - guid: data.guid, - parentGuid: data.parentGuid, - grandparentGuid: data.grandparentGuid, - type: data.type, - title: data.title, - grandparentKey: data.grandparentKey, - parentKey: data.parentKey, - grandparentTitle: data.grandparentTitle, - parentTitle: data.parentTitle, - originalTitle: data.originalTitle, - summary: data.summary, - index: data.index, - parentIndex: data.parentIndex, - thumb: data.thumb, - parentThumb: data.parentThumb, - grandparentThumb: data.grandparentThumb, - duration: data.duration, - addedAt: data.addedAt, - updatedAt: data.updatedAt, - musicAnalysisVersion: data.musicAnalysisVersion, - Media: data.Media, - }); - } + console.log(response); + try { + for (const data of response.data.MediaContainer.Metadata) { + songLibraryContent.push({ + server: server, + library: library, + ratingKey: data.ratingKey, + key: data.key, + parentRatingKey: data.parentRatingKey, + grandparentRatingKey: data.grandparentRatingKey, + guid: data.guid, + parentGuid: data.parentGuid, + grandparentGuid: data.grandparentGuid, + type: data.type, + title: data.title, + grandparentKey: data.grandparentKey, + parentKey: data.parentKey, + grandparentTitle: data.grandparentTitle, + parentTitle: data.parentTitle, + originalTitle: data.originalTitle, + summary: data.summary, + index: data.index, + parentIndex: data.parentIndex, + thumb: data.thumb, + parentThumb: data.parentThumb, + grandparentThumb: data.grandparentThumb, + duration: data.duration, + addedAt: data.addedAt, + updatedAt: data.updatedAt, + musicAnalysisVersion: data.musicAnalysisVersion, + Media: data.Media, + }); + } + } catch {} } } return songLibraryContent; } async GetPlexShows( + searchParams = {}, servers = this.plexServers, - libraries = this.plexLibraries, - searchParams = null + libraries = this.plexLibraries ) { let tvShowLibraryContent = []; for (const server of servers) { @@ -802,13 +829,14 @@ export class PlexAPIOAuth { connectionUri.uri + "/library/sections/" + showLibrary?.key + - "/all?" + - qs.stringify(searchParams) + - qs.stringify({ - type: 2, - "X-Plex-Token": server?.accessToken, - }), + "/all", headers: { accept: "application/json" }, + params: { + type: 2, + ...searchParams, + "X-Plex-Token": server?.accessToken, + }, + cancelToken: axios.CancelToken((c) => (this.cancelToken = c)), }).catch((err) => { throw err; }); @@ -854,9 +882,9 @@ export class PlexAPIOAuth { } async GetPlexSeasons( + searchParams = {}, servers = this.plexServers, - libraries = this.plexLibraries, - searchParams = null + libraries = this.plexLibraries ) { let seasonArray = []; for (const server of servers) { @@ -875,13 +903,14 @@ export class PlexAPIOAuth { connectionUri.uri + "/library/sections/" + showLibrary?.key + - "/all?" + - qs.stringify(searchParams) + - qs.stringify({ - type: 3, - "X-Plex-Token": server?.accessToken, - }), + "/all", headers: { accept: "application/json" }, + params: { + type: 3, + ...searchParams, + "X-Plex-Token": server?.accessToken, + }, + cancelToken: axios.CancelToken((c) => (this.cancelToken = c)), }).catch((err) => { throw err; }); @@ -894,9 +923,9 @@ export class PlexAPIOAuth { } async GetPlexEpisodes( + searchParams = {}, servers = this.plexServers, - libraries = this.plexLibraries, - searchParams = null + libraries = this.plexLibraries ) { let episodeLibrary = []; for (const server of servers) { @@ -915,13 +944,14 @@ export class PlexAPIOAuth { connectionUri.uri + "/library/sections/" + showLibrary?.key + - "/all?" + - qs.stringify(searchParams) + - qs.stringify({ - type: 4, - "X-Plex-Token": server?.accessToken, - }), + "/all", headers: { accept: "application/json" }, + params: { + type: 4, + ...searchParams, + "X-Plex-Token": server?.accessToken, + }, + cancelToken: axios.CancelToken((c) => (this.cancelToken = c)), }).catch((err) => { throw err; }); @@ -932,4 +962,82 @@ export class PlexAPIOAuth { } return episodeLibrary; } + + GetLibraryPages( + libraryType = "", + query = null, + pageNumber = 0, + chunkSize = 50 + ) { + const [loading, setLoading] = useState(true); + const [error, setError] = useState(false); + const [items, setItems] = useState([]); + const [hasMore, setHasMore] = useState(false); + + let data = null; + + useEffect(() => { + setItems([]); + }, [query, libraryType]); + + useEffect(() => { + setLoading(true); + setError(false); + let searchParams = { + "X-Plex-Container-Start": pageNumber * chunkSize, + "X-Plex-Container-Size": chunkSize, + }; + if (query !== null) { + searchParams = { + title: query, + "X-Plex-Container-Start": pageNumber * chunkSize, + "X-Plex-Container-Size": chunkSize, + }; + } + + switch (libraryType) { + case "artists": + data = this.GetPlexArtists(searchParams); + break; + case "albums": + data = this.GetPlexAlbums(searchParams); + break; + case "songs": + data = this.GetPlexSongs(searchParams); + break; + case "shows": + data = this.GetPlexShows(searchParams); + break; + case "seasons": + data = this.GetPlexSeasons(searchParams); + break; + case "episodes": + data = this.GetPlexEpisodes(searchParams); + break; + case "movies": + data = this.GetPlexMovies(searchParams); + break; + } + try { + data + .then((finalData) => { + setItems((previousData) => { + return [...previousData, ...finalData]; + }); + setHasMore(finalData.length > 0); + setLoading(false); + console.log({ loading, error, items, hasMore }); + }) + .catch((e) => { + setError(e); + }); + } catch {} + return () => { + if (this.cancelToken) { + this.cancelToken(); + } + }; + }, [query, pageNumber]); + return { loading, error, items, hasMore }; + } } diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..8472c95 --- /dev/null +++ b/test/index.js @@ -0,0 +1,7 @@ +import { PlexAPIOAuth } from "../src/index.js"; + +const PlexSession = new PlexAPIOAuth(); + +PlexSession.GenerateClientId(); + +await PlexSession.PlexLogin(); diff --git a/test/index.test.js b/test/index.test.js index a9980ff..73ac42c 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -29,7 +29,7 @@ describe("Unit Tests", function () { }); it("Get Plex Servers", async function () { this.timeout(12000); - let response = await PlexSession.GetPlexServers({ owned: true }); + let response = await PlexSession.GetPlexServers({}, { owned: true }); assert.notEqual(PlexSession.plexServers, emptyArray); assert.notEqual(PlexSession.plexServers, null); assert.notEqual(PlexSession.plexServers, undefined); @@ -39,8 +39,8 @@ describe("Unit Tests", function () { assert.notEqual(response, emptyArray); assert.notEqual(response, null); assert.notEqual(response, undefined); - //console.log("Plex Servers"); - //console.log(PlexSession.plexServers); + // console.log("Plex Servers"); + // console.log(PlexSession.plexServers); }); it("Get Plex Libraries", async function () { this.timeout(10000); @@ -83,7 +83,10 @@ describe("Unit Tests", function () { }); it("Get Plex Movies", async function () { this.timeout(10000); - let response = await PlexSession.GetPlexMovies(); + let response = await PlexSession.GetPlexMovies({ + "X-Plex-Container-Start": 0, + "X-Plex-Container-Size": 2, + }); assert.notEqual(response, emptyArray); assert.notEqual(response, null); assert.notEqual(response, undefined); @@ -92,7 +95,10 @@ describe("Unit Tests", function () { }); it("Get Plex Shows", async function () { this.timeout(10000); - let response = await PlexSession.GetPlexShows(); + let response = await PlexSession.GetPlexShows({ + "X-Plex-Container-Start": 0, + "X-Plex-Container-Size": 2, + }); assert.notEqual(response, emptyArray); assert.notEqual(response, null); assert.notEqual(response, undefined); @@ -101,7 +107,10 @@ describe("Unit Tests", function () { }); it("Get Plex Seasons", async function () { this.timeout(10000); - let response = await PlexSession.GetPlexSeasons(); + let response = await PlexSession.GetPlexSeasons({ + "X-Plex-Container-Start": 0, + "X-Plex-Container-Size": 2, + }); assert.notEqual(response, emptyArray); assert.notEqual(response, null); assert.notEqual(response, undefined); @@ -110,7 +119,10 @@ describe("Unit Tests", function () { }); it("Get Plex Episodes", async function () { this.timeout(20000); - let response = await PlexSession.GetPlexEpisodes(); + let response = await PlexSession.GetPlexEpisodes({ + "X-Plex-Container-Start": 0, + "X-Plex-Container-Size": 2, + }); assert.notEqual(response, emptyArray); assert.notEqual(response, null); assert.notEqual(response, undefined); @@ -119,7 +131,10 @@ describe("Unit Tests", function () { }); it("Get Plex Artists", async function () { this.timeout(10000); - let response = await PlexSession.GetPlexArtists(); + let response = await PlexSession.GetPlexArtists({ + "X-Plex-Container-Start": 0, + "X-Plex-Container-Size": 2, + }); assert.notEqual(response, emptyArray); assert.notEqual(response, null); assert.notEqual(response, undefined); @@ -128,7 +143,10 @@ describe("Unit Tests", function () { }); it("Get Plex Albums", async function () { this.timeout(10000); - let response = await PlexSession.GetPlexAlbums(); + let response = await PlexSession.GetPlexAlbums({ + "X-Plex-Container-Start": 0, + "X-Plex-Container-Size": 2, + }); assert.notEqual(response, emptyArray); assert.notEqual(response, null); assert.notEqual(response, undefined); @@ -137,11 +155,23 @@ describe("Unit Tests", function () { }); it("Get Plex Songs", async function () { this.timeout(20000); - let response = await PlexSession.GetPlexSongs(); + let response = await PlexSession.GetPlexSongs({ + "X-Plex-Container-Start": 0, + "X-Plex-Container-Size": 2, + }); assert.notEqual(response, emptyArray); assert.notEqual(response, null); assert.notEqual(response, undefined); - //console.log("Plex Songs"); + // console.log("Plex Songs"); + // console.log(response); + }); + it("Get Plex Songs Paged", async function () { + this.timeout(20000); + let response = PlexSession.GetLibraryPages("songs"); + assert.notEqual(response, emptyArray); + assert.notEqual(response, null); + assert.notEqual(response, undefined); + //console.log("Plex Songs Paged"); //console.log(response); }); });