diff --git a/seasoned_api/src/plex/plex.js b/seasoned_api/src/plex/plex.js index 01814bd..403d15a 100644 --- a/seasoned_api/src/plex/plex.js +++ b/seasoned_api/src/plex/plex.js @@ -12,19 +12,35 @@ const redisCache = new RedisCache() // TODO? import class definitions to compare types ? // what would typescript do? -const matchingTitleOrName = (plex, tmdb) => { - if (plex['title'] !== undefined && tmdb['title'] !== undefined) - return sanitize(plex.title) === sanitize(tmdb.title) - - return false -} - -const matchingYear = (plex, tmdb) => { - return plex.year === tmdb.year -} - const sanitize = (string) => string.toLowerCase() +const matchingTitleAndYear = (plex, tmdb) => { + let matchingTitle, matchingYear; + + if (plex['title'] != null && tmdb['title'] != null) + matchingTitle = sanitize(plex.title) == sanitize(tmdb.title); + else + matchingTitle = false; + + if (plex['year'] != null && tmdb['year'] != null) + matchingYear = plex.year == tmdb.year; + else + matchingYear = false; + + return matchingTitle && matchingYear +} + +const successfullResponse = (response) => { + const { status, statusText } = response + + if (status === 200) { + return response.json() + } else { + throw { message: statusText, status: status } + } +} + + class Plex { constructor(ip, port=32400, cache=null) { this.plexIP = ip @@ -32,65 +48,110 @@ class Plex { this.cache = cache || redisCache; this.cacheTags = { + machineInfo: 'mi', search: 's' } } - matchTmdbAndPlexMedia(plex, tmdb) { - if (plex === undefined || tmdb === undefined) - return false - - let titleMatches; - let yearMatches; - - if (plex instanceof Array) { - const plexList = plex - - titleMatches = plexList.map(plexItem => matchingTitleOrName(plexItem, tmdb)) - yearMatches = plexList.map(plexItem => matchingYear(plexItem, tmdb)) - titleMatches = titleMatches.includes(true) - yearMatches = yearMatches.includes(true) - } else { - titleMatches = matchingTitleOrName(plex, tmdb) - yearMatches = matchingYear(plex, tmdb) - } - - return titleMatches && yearMatches - } - - existsInPlex(tmdbMovie) { - return this.search(tmdbMovie.title) - .then(plexMovies => plexMovies.some(plex => this.matchTmdbAndPlexMedia(plex, tmdbMovie))) - } - - successfullResponse(response) { - const { status, statusText } = response - - if (status === 200) { - return response.json() - } else { - throw { message: statusText, status: status } - } - } - - search(query) { - query = encodeURIComponent(query) - const url = `http://${this.plexIP}:${this.plexPort}/hubs/search?query=${query}` + fetchMachineIdentifier() { + const cacheKey = `plex/${this.cacheTags.machineInfo}` + const url = `http://${this.plexIP}:${this.plexPort}/` const options = { - timeout: 2000, + timeout: 20000, headers: { 'Accept': 'application/json' } } - return fetch(url, options) - .then(this.successfullResponse) - .then(this.mapResults) + return new Promise((resolve, reject) => this.cache.get(cacheKey) + .then(machineInfo => resolve(machineInfo['machineIdentifier'])) + .catch(() => fetch(url, options)) + .then(response => response.json()) + .then(machineInfo => this.cache.set(cacheKey, machineInfo['MediaContainer'], 2628000)) + .then(machineInfo => resolve(machineInfo['machineIdentifier'])) .catch(error => { - if (error.type === 'request-timeout') { - throw { message: 'Plex did not respond', status: 408, success: false } + if (error != undefined && error.type === 'request-timeout') { + reject({ message: 'Plex did not respond', status: 408, success: false }) } - throw error + reject(error) }) + ) + } + + matchTmdbAndPlexMedia(plex, tmdb) { + let match; + + if (plex == null || tmdb == null) + return false + + if (plex instanceof Array) { + let possibleMatches = plex.map(plexItem => matchingTitleAndYear(plexItem, tmdb)) + match = possibleMatches.includes(true) + } else { + match = matchingTitleAndYear(plex, tmdb) + } + + return match + } + + existsInPlex(tmdb) { + const plexMatch = this.findPlexItemByTitleAndYear(tmdb.title, tmdb.year) + return plexMatch ? true : false + } + + + findPlexItemByTitleAndYear(title, year) { + const query = { title, year } + + return this.search(query.title) + .then(plexSearchResults => { + const matchesInPlex = plexSearchResults.map(plex => this.matchTmdbAndPlexMedia(plex, query)) + + if (matchesInPlex.includes(true) === false) + return false; + + const firstMatchIndex = matchesInPlex.indexOf(true) + return plexSearchResults[firstMatchIndex][0] + }) + } + + getDirectLinkByTitleAndYear(title, year) { + const machineIdentifierPromise = this.fetchMachineIdentifier() + const matchingObjectInPlexPromise = this.findPlexItemByTitleAndYear(title, year) + + return Promise.all([machineIdentifierPromise, matchingObjectInPlexPromise]) + .then(([machineIdentifier, matchingObjectInPlex]) => { + if (matchingObjectInPlex == false || matchingObjectInPlex == null || + matchingObjectInPlex['key'] == null || machineIdentifier == null) + return false + + const keyUriComponent = encodeURIComponent(matchingObjectInPlex.key) + return `https://app.plex.tv/desktop#!/server/${machineIdentifier}/details?key=${keyUriComponent}` + }) + } + + search(query) { + const cacheKey = `plex/${this.cacheTags.search}:${query}` + const url = `http://${this.plexIP}:${this.plexPort}/hubs/search?query=${query}` + const options = { + timeout: 20000, + headers: { 'Accept': 'application/json' } + } + + return new Promise((resolve, reject) => this.cache.get(cacheKey) + .then(resolve) // if found in cache resolve + .catch(() => fetch(url, options)) // else fetch fresh data + .then(successfullResponse) + .then(this.mapResults) + .then(results => this.cache.set(cacheKey, results, 600)) + .then(resolve) + .catch(error => { + if (error != undefined && error.type === 'request-timeout') { + reject({ message: 'Plex did not respond', status: 408, success: false }) + } + + reject(error) + }) + ) } mapResults(response) { @@ -103,10 +164,7 @@ class Plex { .filter(category => category.size > 0) .map(category => { if (category.type === 'movie') { - return category.Metadata.map(movie => { - const ovie = Movie.convertFromPlexResponse(movie) - return ovie.createJsonResponse() - }) + return category.Metadata } else if (category.type === 'show') { return category.Metadata.map(convertPlexToShow) } else if (category.type === 'episode') { @@ -114,7 +172,7 @@ class Plex { } }) .filter(result => result !== undefined) - .flat() +// .flat() } }