Refactor plexApi to remove class dependency.

This commit is contained in:
mike.rode
2019-02-19 18:51:56 -06:00
parent d789d343af
commit 2a32fd445d
8 changed files with 211 additions and 82 deletions

56
package-lock.json generated
View File

@@ -1314,6 +1314,11 @@
"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
"dev": true
},
"btoa": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
"integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g=="
},
"buffer-shims": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz",
@@ -9984,6 +9989,13 @@
"request": "^2.87.0",
"uuid": "2.0.2",
"xml2js": "0.4.16"
},
"dependencies": {
"uuid": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.2.tgz",
"integrity": "sha1-SL1WmPBnfjx5AaHEbvFbFkN5RyY="
}
}
},
"plex-api-credentials": {
@@ -9995,6 +10007,18 @@
"plex-api-headers": "1.1.0",
"request-promise": "^3.0.0",
"xml2js": "^0.4.16"
},
"dependencies": {
"request-promise": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/request-promise/-/request-promise-3.0.0.tgz",
"integrity": "sha1-vh7bJvQcSc0dVlbGdT1oQqEkn0Y=",
"requires": {
"bluebird": "^3.3",
"lodash": "^4.6.1",
"request": "^2.34"
}
}
}
},
"plex-api-headers": {
@@ -10352,13 +10376,22 @@
}
},
"request-promise": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/request-promise/-/request-promise-3.0.0.tgz",
"integrity": "sha1-vh7bJvQcSc0dVlbGdT1oQqEkn0Y=",
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.4.tgz",
"integrity": "sha512-8wgMrvE546PzbR5WbYxUQogUnUDfM0S7QIFZMID+J73vdFARkFy+HElj4T+MWYhpXwlLp0EQ8Zoj8xUA0he4Vg==",
"requires": {
"bluebird": "^3.3",
"lodash": "^4.6.1",
"request": "^2.34"
"bluebird": "^3.5.0",
"request-promise-core": "1.1.2",
"stealthy-require": "^1.1.1",
"tough-cookie": "^2.3.3"
}
},
"request-promise-core": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz",
"integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==",
"requires": {
"lodash": "^4.17.11"
}
},
"require-main-filename": {
@@ -10890,6 +10923,11 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
},
"stealthy-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
},
"strict-uri-encode": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
@@ -11617,9 +11655,9 @@
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
"uuid": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.2.tgz",
"integrity": "sha1-SL1WmPBnfjx5AaHEbvFbFkN5RyY="
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
},
"v8flags": {
"version": "2.1.1",

View File

@@ -14,6 +14,7 @@
"dependencies": {
"axios": "^0.18.0",
"body-parser": "^1.18.3",
"btoa": "^1.2.1",
"build-url": "^1.3.2",
"cors": "^2.8.5",
"custom-env": "^1.0.0",
@@ -24,8 +25,10 @@
"onchange": "^5.2.0",
"pg": "^7.8.0",
"plex-api": "^5.2.1",
"request-promise": "^4.2.4",
"sequelize": "^4.42.0",
"tdaw": "^1.3.0",
"uuid": "^3.3.2",
"xml2json": "^0.11.2"
},
"devDependencies": {

View File

@@ -3,6 +3,8 @@ import plexService from '../../../services/plexApi';
const router = Router();
router.get('/auth', plexService.getAuthToken);
// users
router.get('/users', plexService.getUsers);

View File

@@ -17,11 +17,11 @@ const encryptUserCreds = (username, password) => {
return btoa(creds);
};
const fetchToken = (username, password) => {
request.post(options(username, password)).then(res => {
const token = res.match(rxAuthToken)[1];
return token;
});
const fetchToken = async (username, password) => {
const res = await request.post(options(username, password));
console.log(res);
const token = res.match(rxAuthToken)[1];
return token;
};
export default fetchToken;

View File

@@ -1,27 +1,35 @@
import plexApiClient from './plexApi';
import plexApi from './plexApi';
import importData from './importData';
import auth from './auth';
import helpers from './helpers';
const getAuthToken = async (req, res) => {
const {username} = req.query;
const {password} = req.query;
auth(username, password).then(data => {
console.log(data);
return res.json(data);
});
};
const getUsers = async (req, res) => {
const plexApi = plexApiClient();
const users = await plexApi.getUsers();
res.json(users);
};
const getMostWatched = async (req, res) => {
const plexApi = plexApiClient();
const mostWatched = await plexApi.getMostWatched(req);
res.json(mostWatched);
};
const getSections = async (req, res) => {
const plexApi = plexApiClient();
const sections = await plexApi.getSections();
res.json(sections);
};
const getLibraryDataBySection = async (req, res) => {
try {
const plexApi = plexApiClient();
const sections = await plexApi.getLibraryDataBySection(req);
res.json(sections);
} catch (error) {
@@ -53,4 +61,5 @@ export default {
importSections,
importLibraries,
importMostWatched,
getAuthToken,
};

View File

@@ -1,37 +1,29 @@
import axios from 'axios';
import buildUrl from 'build-url';
import buildUrlPack from 'build-url';
import config from '../../../config';
import formatResponse from './helpers';
function PlexApiClient(options) {
this.setOptions(options);
}
PlexApiClient.prototype.setOptions = function(options) {
this.options = options || {};
};
PlexApiClient.prototype.getUsersUrlParams = function() {
const getUsersUrlParams = function() {
return {
host: config.plex.plexApiUrl,
path: '/users',
queryParams: {
'X-Plex-Token': this.options.token || config.plex.token,
'X-Plex-Token': config.plex.token,
},
};
};
PlexApiClient.prototype.getSectionsUrlParams = function() {
const getSectionsUrlParams = function() {
return {
host: config.plex.plexServerUrl,
path: '/library/sections',
queryParams: {
'X-Plex-Token': this.options.token || config.plex.token,
'X-Plex-Token': config.plex.token,
},
};
};
PlexApiClient.prototype.mostWatchedUrlParams = function(req) {
const mostWatchedUrlParams = function(req) {
return {
host: config.plex.plexServerUrl,
path: '/library/all/top',
@@ -41,40 +33,40 @@ PlexApiClient.prototype.mostWatchedUrlParams = function(req) {
...((req && (req.query.limit && {limit: req.query.limit})) || {
limit: 10,
}),
'X-Plex-Token': this.options.token || config.plex.token,
'X-Plex-Token': config.plex.token,
},
};
};
PlexApiClient.prototype.getLibraryDataBySectionUrlParams = function(req) {
const getLibraryDataBySectionUrlParams = function(req) {
const sectionId = req.sectionId || req.params.id;
return {
host: config.plex.plexServerUrl,
path: `/library/sections/${sectionId}/all`,
queryParams: {
'X-Plex-Token': this.options.token || config.plex.token,
'X-Plex-Token': config.plex.token,
},
};
};
PlexApiClient.prototype.buildUrl = function(urlParams) {
const buildUrl = function(urlParams) {
try {
const params = urlParams;
const {host} = params;
delete params.host;
const urlHash = params;
return buildUrl(host, urlHash);
return buildUrlPack(host, urlHash);
} catch (error) {
console.log(error);
return error;
}
};
PlexApiClient.prototype.request = async function(url) {
const request = async function(url) {
console.log('Request URL', url);
return new Promise((resolve, reject) => {
const httpClient = this.options.httpClient || axios;
const httpClient = axios;
httpClient
.get(url)
.then(response => {
@@ -97,43 +89,47 @@ PlexApiClient.prototype.request = async function(url) {
});
};
PlexApiClient.prototype.getUsers = async function() {
const urlParams = this.getUsersUrlParams();
const getUsersUrl = this.buildUrl(urlParams);
const response = await this.request(getUsersUrl);
const getUsers = async function() {
const urlParams = getUsersUrlParams();
const getUsersUrl = buildUrl(urlParams);
const response = await request(getUsersUrl);
return response.MediaContainer.User;
};
PlexApiClient.prototype.getMostWatched = async function(req) {
const urlParams = this.mostWatchedUrlParams(req);
const mostWatchedUrl = this.buildUrl(urlParams);
const response = await this.request(mostWatchedUrl);
const getMostWatched = async function(req) {
const urlParams = mostWatchedUrlParams(req);
const mostWatchedUrl = buildUrl(urlParams);
const response = await request(mostWatchedUrl);
console.log(response.MediaContainer.Metadata);
return response.MediaContainer.Metadata;
};
PlexApiClient.prototype.getSections = async function() {
const urlParams = this.getSectionsUrlParams();
const getSectionsUrl = this.buildUrl(urlParams);
const response = await this.request(getSectionsUrl);
const getSections = async function() {
const urlParams = getSectionsUrlParams();
const getSectionsUrl = buildUrl(urlParams);
const response = await request(getSectionsUrl);
return response.MediaContainer.Directory;
};
PlexApiClient.prototype.getLibraryDataBySection = async function(req) {
const getLibraryDataBySection = async function(req) {
try {
console.log('Query', req.query);
const urlParams = this.getLibraryDataBySectionUrlParams(req);
const getLibraryDataBySectionUrl = this.buildUrl(urlParams);
const response = await this.request(getLibraryDataBySectionUrl);
const urlParams = getLibraryDataBySectionUrlParams(req);
const getLibraryDataBySectionUrl = buildUrl(urlParams);
const response = await request(getLibraryDataBySectionUrl);
return response.MediaContainer.Metadata;
} catch (error) {
console.log(error);
return error;
}
};
const plexApiClient = (options = []) => {
return new PlexApiClient(options);
export default {
getUsers,
getMostWatched,
getSections,
getLibraryDataBySection,
getUsersUrlParams,
getLibraryDataBySectionUrlParams,
getSectionsUrlParams,
buildUrl,
request,
};
export default plexApiClient;

View File

@@ -3794,4 +3794,89 @@ export default {
],
},
},
authResponse: `<?xml version="1.0" encoding="UTF-8"?>
<user email="michaelrode44@gmail.com" id="1785591" uuid="8aa3f610df561c94" mailing_list_status="active" thumb="https://plex.tv/users/8aa3f610df561c94/avatar?c=1550019870" username="mjrode" title="mjrode" cloudSyncDevice="" locale="" authenticationToken="testPlexApiToken" authToken="testPlexApiToken" scrobbleTypes="" restricted="0" home="0" guest="0" queueEmail="queue+99Jp8cx4zmqjSkE4YHZG@save.plex.tv" queueUid="e8500dd3b8ebf1e5" hasPassword="true" homeSize="1" maxHomeSize="15" rememberMe="false" secure="1" certificateVersion="2">
<subscription active="1" status="Active" plan="monthly">
<feature id="webhooks"/>
<feature id="camera_upload"/>
<feature id="home"/>
<feature id="pass"/>
<feature id="dvr"/>
<feature id="trailers"/>
<feature id="session_bandwidth_restrictions"/>
<feature id="music_videos"/>
<feature id="content_filter"/>
<feature id="adaptive_bitrate"/>
<feature id="sync"/>
<feature id="photo_autotags"/>
<feature id="lyrics"/>
<feature id="cloudsync"/>
<feature id="premium_music_metadata"/>
<feature id="hardware_transcoding"/>
<feature id="session_kick"/>
<feature id="photos-metadata-edition"/>
<feature id="collections"/>
<feature id="radio"/>
<feature id="tuner-sharing"/>
<feature id="photos-favorites"/>
<feature id="hwtranscode"/>
<feature id="photosV6-tv-albums"/>
<feature id="photosV6-edit"/>
<feature id="federated-auth"/>
<feature id="item_clusters"/>
<feature id="livetv"/>
<feature id="Android - PiP"/>
<feature id="news"/>
<feature id="photos-v5"/>
<feature id="publishing_platform"/>
<feature id="unsupportedtuners"/>
<feature id="kevin-bacon"/>
<feature id="client-radio-stations"/>
<feature id="imagga-v2"/>
<feature id="silence-removal"/>
<feature id="boost-voices"/>
<feature id="volume-leveling"/>
<feature id="sweet-fades"/>
<feature id="sleep-timer"/>
<feature id="TREBLE-show-features"/>
<feature id="web_server_dashboard"/>
<feature id="visualizers"/>
<feature id="premium-dashboard"/>
<feature id="conan_redirect_qa"/>
<feature id="conan_redirect_alpha"/>
<feature id="conan_redirect_beta"/>
<feature id="nominatim"/>
<feature id="transcoder_cache"/>
</subscription>
<roles>
<role id="plexpass"/>
</roles>
<entitlements all="1">
<entitlement id="roku"/>
<entitlement id="android"/>
<entitlement id="xbox_one"/>
<entitlement id="xbox_360"/>
<entitlement id="windows"/>
<entitlement id="windows_phone"/>
<entitlement id="ios"/>
</entitlements>
<profile_settings default_audio_language="en" default_subtitle_language="en" auto_select_subtitle="0" auto_select_audio="0" default_subtitle_accessibility="0" default_subtitle_forced="0"/>
<providers>
<provider id="google_federated" uid="michaelrode44@gmail.com" token="eyJhbGciOiJSUzI1NiIsImtpZCI6IjNlMTg3MTc3Zjk5ODdjMTkxMDg1MTA3ZjY4M2E2ZWEyNzdhZmJjOGQifQ.eyJhenAiOiI5NTQzOTYxMDczMTEtdnBkaTBpZTY5MDVqMXByOHVkYzJ2MWRybTlwMGtuMHAuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI5NTQzOTYxMDczMTEtdnBkaTBpZTY5MDVqMXByOHVkYzJ2MWRybTlwMGtuMHAuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMDM5MTMwOTczODY4MDc2ODAxNTEiLCJlbWFpbCI6Im1pY2hhZWxyb2RlNDRAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF0X2hhc2giOiI3MXNUaHk0Y2E2aHdpY2x1bjk3SVZRIiwiZXhwIjoxNTE5MjgzNjY1LCJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwianRpIjoiNzFjODljODFjYjg2NTJiOTM5NmJlZGZkOTVmNDM4NTM3OTcxMjAyMiIsImlhdCI6MTUxOTI4MDA2NSwibmFtZSI6Ik1pY2hhZWwgUm9kZSIsInBpY3R1cmUiOiJodHRwczovL2xoNi5nb29nbGV1c2VyY29udGVudC5jb20vLWozNzJBdEt6b1FnL0FBQUFBQUFBQUFJL0FBQUFBQUFBZHcwL0ZKSEpHcXZ6UWhNL3M5Ni1jL3Bob3RvLmpwZyIsImdpdmVuX25hbWUiOiJNaWNoYWVsIiwiZmFtaWx5X25hbWUiOiJSb2RlIiwibG9jYWxlIjoiZW4ifQ.wthNayjhoUWpFD0FzdUu0Sl3KLcRILIJzg0T5KaXrjECk0yAbTCkuY1DfeFGyGrX_j8CU-I0CisgZzlSvmmGX7aXWKHn6PQftZLFwE2KH0M1xubJPmS0DBOzBFShT115mCoMVfQwDIp4ibch5XA8asF6G5a9PA6ALVf-1IVC-Cw606g9zdzWogalPb2rN_KkYHzjONHnL8JQm0PLYK7jn7KbPAEK5tNrEXThWX4DlpRt4-nvcZZ3NLHAfxwyMzft_uUK0vXk5jlgqUlUCTJuQhfNgQ0fMUj71Cfw0w-XiugfYx9rP3aH4ZrCLdEoV_bWaeDrfVKNpER7k1ylgb7rhg"/>
<provider id="facebook_federated" uid="10211542468182915" token="EAATZBvQHPWEYBAOfor8c9ZAiZCsfdhMxBc5WVI39eOmSLjU58zV9FOSZCw5PbOMJ40FSaUAmVWzxDlGAtDVK7klpZBHcdlYkDZCbZBhPzppArqpAswW6maHu5YnRZAzCB7e6j6fUJpmjoqn1R7qhRFfvJLXOoEXNfzz8ZAtJk6i4YxeHl1U1C6sYBtPaI0pe6VJol4AdjBOX41vFfKI09DsNncmUd8Tbg5ymBw02DxrUY0AZDZD"/>
</providers>
<services>
<service identifier="eyeq" endpoint="https://c4412416.ipg.web.cddbp.net/webapi/xml/1.0/" token="6iUU7VrBzA/Hz5nsS9o65Yz77qvh63pYmUN+TaVHeonhsQWlzYGcsik1opEU3toj" status="online"/>
<service identifier="eyeq-channel-icons" endpoint="http://akamai-b.cdn.cddbp.net/cds/2.0/image" status="online"/>
<service identifier="imagga-v2" endpoint="https://api.imagga.com/v2" token="AunxEHP5CLc5vu/jsdobovGRXa8Rijd+b35tHpUavpY=" secret="XuI9cYEa+ljybVxAU5puuD8jxxA49EMey5xmomcmraKgv6AW2kaFv80dzQnpETm7" status="online"/>
<service identifier="nominatim" endpoint="https://locationiq.org/v1" token="MLJSTFcm5IurTy3c7juhvvFs0fX0YR6dRjKNAywidVk=" status="online"/>
<service identifier="metadata-provider" endpoint="https://mpm.plex.tv/" status="online"/>
<service identifier="tmsapi" endpoint="https://tmsapi.plex.tv/v1.1/" token="a0VC3zjGN7FmzvlZKIuRRUjLCRoQkgY2jwJsLgi8+OQ=" status="online"/>
<service identifier="subtitles-search" endpoint="https://metadata.provider.plex.tv/library/metadata/matches" status="online"/>
</services>
<username>mjrode</username>
<email>michaelrode44@gmail.com</email>
<joined-at type="datetime">2014-01-03 03:05:51 UTC</joined-at>
<authentication-token>testPlexApiToken</authentication-token>
</user>`,
};

View File

@@ -4,47 +4,44 @@ import chai from 'chai';
import chaiHttp from 'chai-http';
import nock from 'nock';
import plexResponses from './mocks/plexResponses';
import plexApiClient from '../../../../server/services/plexApi/plexApi';
import plexApi from '../../../../server/services/plexApi/plexApi';
const should = chai.should();
describe('plexApi', () => {
it('sets options when passed valid options object', () => {
const options = { token: 'plexToken' };
const result = plexApiClient(options).options;
result.should.deep.equal({
token: 'plexToken',
});
});
// it('sets options when passed valid options object', () => {
// const options = { token: 'testPlexApiToken' };
// const result = plexApi.options;
// result.should.deep.equal({
// token: 'testPlexApiToken',
// });
// });
it('return url params object', () => {
const options = { token: 'plexToken' };
const result = plexApiClient(options).getUsersUrlParams();
const result = plexApi.getUsersUrlParams();
result.should.deep.equal({
host: 'https://plex.tv/api',
path: '/users',
queryParams: {
'X-Plex-Token': 'plexToken',
'X-Plex-Token': 'testPlexApiToken',
},
});
});
it('returns url', () => {
const options = { token: 'plexToken' };
const PlexApi = plexApiClient(options);
const PlexApi = plexApi;
const urlParams = PlexApi.getUsersUrlParams();
const url = PlexApi.buildUrl(urlParams);
url.should.equal('https://plex.tv/api/users?X-Plex-Token=plexToken');
url.should.equal('https://plex.tv/api/users?X-Plex-Token=testPlexApiToken');
});
it('returns users', async () => {
const usersResponse = `${__dirname}/mocks/getUsersResponse.xml`;
nock('https://plex.tv')
.get('/api/users?X-Plex-Token=plexToken')
.get('/api/users?X-Plex-Token=testPlexApiToken')
.replyWithFile(200, usersResponse, { 'Content-Type': 'text/xml' });
const options = { token: 'plexToken' };
const PlexApi = plexApiClient(options);
const PlexApi = plexApi;
const urlParams = PlexApi.getUsersUrlParams();
const url = PlexApi.buildUrl(urlParams);
const result = await PlexApi.request(url);
@@ -54,11 +51,10 @@ describe('plexApi', () => {
it('returns users using getUsers', async () => {
const usersResponse = `${__dirname}/mocks/getUsersResponse.xml`;
nock('https://plex.tv')
.get('/api/users?X-Plex-Token=plexToken')
.get('/api/users?X-Plex-Token=testPlexApiToken')
.replyWithFile(200, usersResponse, { 'Content-Type': 'text/xml' });
const options = { token: 'plexToken' };
const PlexApi = plexApiClient(options);
const PlexApi = plexApi;
const result = await PlexApi.getUsers();
result.should.deep.equal(plexResponses.getUsersParsed);
});