Full re-write. Refactored, cache and watchlink.

- Now the api calls to plex are cached.
- Search function has been refactored.
- Exists in plex has been re-written to use findPlexItemByTitleAndYear
if anything is found we return true. findPlexItemByTitleAndYear is then
also used for our new endpoint to get direct watch link for plex item.
Function parameters are title and year. Title is used when querying plex
and year is used with title to match correct plex search result. The
direct link includes the machine identifier so a function was added to
get system information from plex (read: 'PLEX_URL/' e.g. base path).
This commit is contained in:
2020-02-21 21:48:34 +01:00
parent 2672266908
commit 74340afd16

View File

@@ -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()
}
}