Feat: Redis cache #129
@@ -12,19 +12,35 @@ const redisCache = new RedisCache()
|
|||||||
// TODO? import class definitions to compare types ?
|
// TODO? import class definitions to compare types ?
|
||||||
// what would typescript do?
|
// 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 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 {
|
class Plex {
|
||||||
constructor(ip, port=32400, cache=null) {
|
constructor(ip, port=32400, cache=null) {
|
||||||
this.plexIP = ip
|
this.plexIP = ip
|
||||||
@@ -32,65 +48,110 @@ class Plex {
|
|||||||
|
|
||||||
this.cache = cache || redisCache;
|
this.cache = cache || redisCache;
|
||||||
this.cacheTags = {
|
this.cacheTags = {
|
||||||
|
machineInfo: 'mi',
|
||||||
search: 's'
|
search: 's'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
matchTmdbAndPlexMedia(plex, tmdb) {
|
fetchMachineIdentifier() {
|
||||||
if (plex === undefined || tmdb === undefined)
|
const cacheKey = `plex/${this.cacheTags.machineInfo}`
|
||||||
return false
|
const url = `http://${this.plexIP}:${this.plexPort}/`
|
||||||
|
|
||||||
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}`
|
|
||||||
const options = {
|
const options = {
|
||||||
timeout: 2000,
|
timeout: 20000,
|
||||||
headers: { 'Accept': 'application/json' }
|
headers: { 'Accept': 'application/json' }
|
||||||
}
|
}
|
||||||
|
|
||||||
return fetch(url, options)
|
return new Promise((resolve, reject) => this.cache.get(cacheKey)
|
||||||
.then(this.successfullResponse)
|
.then(machineInfo => resolve(machineInfo['machineIdentifier']))
|
||||||
.then(this.mapResults)
|
.catch(() => fetch(url, options))
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(machineInfo => this.cache.set(cacheKey, machineInfo['MediaContainer'], 2628000))
|
||||||
|
.then(machineInfo => resolve(machineInfo['machineIdentifier']))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error.type === 'request-timeout') {
|
if (error != undefined && error.type === 'request-timeout') {
|
||||||
throw { message: 'Plex did not respond', status: 408, success: false }
|
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) {
|
mapResults(response) {
|
||||||
@@ -103,10 +164,7 @@ class Plex {
|
|||||||
.filter(category => category.size > 0)
|
.filter(category => category.size > 0)
|
||||||
.map(category => {
|
.map(category => {
|
||||||
if (category.type === 'movie') {
|
if (category.type === 'movie') {
|
||||||
return category.Metadata.map(movie => {
|
return category.Metadata
|
||||||
const ovie = Movie.convertFromPlexResponse(movie)
|
|
||||||
return ovie.createJsonResponse()
|
|
||||||
})
|
|
||||||
} else if (category.type === 'show') {
|
} else if (category.type === 'show') {
|
||||||
return category.Metadata.map(convertPlexToShow)
|
return category.Metadata.map(convertPlexToShow)
|
||||||
} else if (category.type === 'episode') {
|
} else if (category.type === 'episode') {
|
||||||
@@ -114,7 +172,7 @@ class Plex {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(result => result !== undefined)
|
.filter(result => result !== undefined)
|
||||||
.flat()
|
// .flat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user