Merge branch 'advplyr:master' into social

This commit is contained in:
Ben 2022-09-03 14:24:18 -04:00 committed by GitHub
commit 2a5ac8f094
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 3170 additions and 374 deletions

View file

@ -440,26 +440,7 @@ class Server {
return
}
// Check if user has session open
var session = this.playbackSessionManager.getUserSession(user.id)
if (session) {
Logger.debug(`[Server] User Online "${client.user.username}" with session open "${session.id}"`)
var sessionLibraryItem = this.db.libraryItems.find(li => li.id === session.libraryItemId)
if (!sessionLibraryItem) {
Logger.error(`[Server] Library Item for session "${session.id}" does not exist "${session.libraryItemId}"`)
this.playbackSessionManager.removeSession(session.id)
session = null
} else if (session.mediaType === 'podcast' && !sessionLibraryItem.media.checkHasEpisode(session.episodeId)) {
Logger.error(`[Server] Library Item for session "${session.id}" episode ${session.episodeId} does not exist "${session.libraryItemId}"`)
this.playbackSessionManager.removeSession(session.id)
session = null
}
if (session) {
session = session.toJSONForClient(sessionLibraryItem)
}
} else {
Logger.debug(`[Server] User Online ${client.user.username}`)
}
Logger.debug(`[Server] User Online ${client.user.username}`)
this.io.emit('user_online', client.user.toJSONForPublic(this.playbackSessionManager.sessions, this.db.libraryItems))
@ -470,7 +451,6 @@ class Server {
metadataPath: global.MetadataPath,
configPath: global.ConfigPath,
user: client.user.toJSONForBrowser(),
session,
librariesScanning: this.scanner.librariesScanning,
backups: (this.backupManager.backups || []).map(b => b.toJSON())
}

View file

@ -24,18 +24,11 @@ class CollectionController {
}
findOne(req, res) {
var collection = this.db.collections.find(c => c.id === req.params.id)
if (!collection) {
return res.status(404).send('Collection not found')
}
res.json(collection.toJSONExpanded(this.db.libraryItems))
res.json(req.collection.toJSONExpanded(this.db.libraryItems))
}
async update(req, res) {
var collection = this.db.collections.find(c => c.id === req.params.id)
if (!collection) {
return res.status(404).send('Collection not found')
}
const collection = req.collection
var wasUpdated = collection.update(req.body)
var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems)
if (wasUpdated) {
@ -46,10 +39,7 @@ class CollectionController {
}
async delete(req, res) {
var collection = this.db.collections.find(c => c.id === req.params.id)
if (!collection) {
return res.status(404).send('Collection not found')
}
const collection = req.collection
var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems)
await this.db.removeEntity('collection', collection.id)
this.emitter('collection_removed', jsonExpanded)
@ -57,10 +47,7 @@ class CollectionController {
}
async addBook(req, res) {
var collection = this.db.collections.find(c => c.id === req.params.id)
if (!collection) {
return res.status(404).send('Collection not found')
}
const collection = req.collection
var libraryItem = this.db.libraryItems.find(li => li.id === req.body.id)
if (!libraryItem) {
return res.status(500).send('Book not found')
@ -80,11 +67,7 @@ class CollectionController {
// DELETE: api/collections/:id/book/:bookId
async removeBook(req, res) {
var collection = this.db.collections.find(c => c.id === req.params.id)
if (!collection) {
return res.status(404).send('Collection not found')
}
const collection = req.collection
if (collection.books.includes(req.params.bookId)) {
collection.removeBook(req.params.bookId)
var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems)
@ -96,10 +79,7 @@ class CollectionController {
// POST: api/collections/:id/batch/add
async addBatch(req, res) {
var collection = this.db.collections.find(c => c.id === req.params.id)
if (!collection) {
return res.status(404).send('Collection not found')
}
const collection = req.collection
if (!req.body.books || !req.body.books.length) {
return res.status(500).send('Invalid request body')
}
@ -120,10 +100,7 @@ class CollectionController {
// POST: api/collections/:id/batch/remove
async removeBatch(req, res) {
var collection = this.db.collections.find(c => c.id === req.params.id)
if (!collection) {
return res.status(404).send('Collection not found')
}
const collection = req.collection
if (!req.body.books || !req.body.books.length) {
return res.status(500).send('Invalid request body')
}
@ -141,5 +118,25 @@ class CollectionController {
}
res.json(collection.toJSONExpanded(this.db.libraryItems))
}
middleware(req, res, next) {
if (req.params.id) {
var collection = this.db.collections.find(c => c.id === req.params.id)
if (!collection) {
return res.status(404).send('Collection not found')
}
req.collection = collection
}
if (req.method == 'DELETE' && !req.user.canDelete) {
Logger.warn(`[CollectionController] User attempted to delete without permission`, req.user.username)
return res.sendStatus(403)
} else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) {
Logger.warn('[CollectionController] User attempted to update without permission', req.user.username)
return res.sendStatus(403)
}
next()
}
}
module.exports = new CollectionController()

View file

@ -165,6 +165,7 @@ class LibraryController {
if (payload.filterBy) {
// If filtering by series, will include seriesName and seriesSequence on media metadata
filterSeries = (payload.mediaType == 'book' && payload.filterBy.startsWith('series.')) ? libraryHelpers.decode(payload.filterBy.replace('series.', '')) : null
if (filterSeries === 'No Series') filterSeries = null
libraryItems = libraryHelpers.getFilteredLibraryItems(libraryItems, payload.filterBy, req.user, this.rssFeedManager.feedsArray)
payload.total = libraryItems.length

View file

@ -51,11 +51,6 @@ class LibraryItemController {
var hasUpdates = libraryItem.update(req.body)
if (hasUpdates) {
// Turn on podcast auto download cron if not already on
if (libraryItem.mediaType == 'podcast' && req.body.media.autoDownloadEpisodes && !this.podcastManager.episodeScheduleTask) {
this.podcastManager.schedulePodcastEpisodeCron()
}
Logger.debug(`[LibraryItemController] Updated now saving`)
await this.db.updateLibraryItem(libraryItem)
this.emitter('item_updated', libraryItem.toJSONExpanded())

View file

@ -96,9 +96,9 @@ class MeController {
var shouldUpdate = false
itemProgressPayloads.forEach((itemProgress) => {
var libraryItem = this.db.libraryItems.find(li => li.id === itemProgress.id) // Make sure this library item exists
var libraryItem = this.db.libraryItems.find(li => li.id === itemProgress.libraryItemId) // Make sure this library item exists
if (libraryItem) {
var wasUpdated = req.user.createUpdateMediaProgress(libraryItem, itemProgress)
var wasUpdated = req.user.createUpdateMediaProgress(libraryItem, itemProgress, itemProgress.episodeId)
if (wasUpdated) shouldUpdate = true
} else {
Logger.error(`[MeController] batchUpdateMediaProgress: Library Item does not exist ${itemProgress.id}`)

View file

@ -86,8 +86,8 @@ class PodcastController {
}
// Turn on podcast auto download cron if not already on
if (libraryItem.media.autoDownloadEpisodes && !this.podcastManager.episodeScheduleTask) {
this.podcastManager.schedulePodcastEpisodeCron()
if (libraryItem.media.autoDownloadEpisodes) {
this.cronManager.checkUpdatePodcastCron(libraryItem)
}
}
@ -140,7 +140,9 @@ class PodcastController {
return res.status(500).send('Podcast has no rss feed url')
}
var newEpisodes = await this.podcastManager.checkAndDownloadNewEpisodes(libraryItem)
const maxEpisodesToDownload = !isNaN(req.query.limit) ? Number(req.query.limit) : 3
var newEpisodes = await this.podcastManager.checkAndDownloadNewEpisodes(libraryItem, maxEpisodesToDownload)
res.json({
episodes: newEpisodes || []
})

View file

@ -16,6 +16,7 @@ class BackupManager {
constructor(db, emitter) {
this.BackupPath = Path.join(global.MetadataPath, 'backups')
this.ItemsMetadataPath = Path.join(global.MetadataPath, 'items')
this.AuthorsMetadataPath = Path.join(global.MetadataPath, 'authors')
this.db = db
this.emitter = emitter
@ -119,6 +120,7 @@ class BackupManager {
await zip.extract('config/', global.ConfigPath)
if (backup.backupMetadataCovers) {
await zip.extract('metadata-items/', this.ItemsMetadataPath)
await zip.extract('metadata-authors/', this.AuthorsMetadataPath)
}
await this.db.reinit()
this.emitter('backup_applied')
@ -178,8 +180,6 @@ class BackupManager {
async runBackup() {
// Check if Metadata Path is inside Config Path (otherwise there will be an infinite loop as the archiver tries to zip itself)
Logger.info(`[BackupManager] Running Backup`)
var metadataItemsPath = this.serverSettings.backupMetadataCovers ? this.ItemsMetadataPath : null
var newBackup = new Backup()
const newBackData = {
@ -188,7 +188,10 @@ class BackupManager {
}
newBackup.setData(newBackData)
var zipResult = await this.zipBackup(metadataItemsPath, newBackup).then(() => true).catch((error) => {
var metadataAuthorsPath = this.AuthorsMetadataPath
if (!await fs.pathExists(metadataAuthorsPath)) metadataAuthorsPath = null
var zipResult = await this.zipBackup(metadataAuthorsPath, newBackup).then(() => true).catch((error) => {
Logger.error(`[BackupManager] Backup Failed ${error}`)
return false
})
@ -228,7 +231,7 @@ class BackupManager {
}
}
zipBackup(metadataItemsPath, backup) {
zipBackup(metadataAuthorsPath, backup) {
return new Promise((resolve, reject) => {
// create a file to stream archive data to
const output = fs.createWriteStream(backup.fullPath)
@ -299,10 +302,16 @@ class BackupManager {
archive.directory(this.db.AuthorsPath, 'config/authors')
archive.directory(this.db.SeriesPath, 'config/series')
if (metadataItemsPath) {
Logger.debug(`[BackupManager] Backing up Metadata Items "${metadataItemsPath}"`)
archive.directory(metadataItemsPath, 'metadata-items')
if (this.serverSettings.backupMetadataCovers) {
Logger.debug(`[BackupManager] Backing up Metadata Items "${this.ItemsMetadataPath}"`)
archive.directory(this.ItemsMetadataPath, 'metadata-items')
if (metadataAuthorsPath) {
Logger.debug(`[BackupManager] Backing up Metadata Authors "${metadataAuthorsPath}"`)
archive.directory(metadataAuthorsPath, 'metadata-authors')
}
}
archive.append(backup.detailsString, { name: 'details' })
archive.finalize()

View file

@ -19,6 +19,7 @@ class PlaybackSessionManager {
this.clientEmitter = clientEmitter
this.sessions = []
this.localSessionLock = {}
}
getSession(sessionId) {
@ -58,18 +59,26 @@ class PlaybackSessionManager {
}
async syncLocalSessionRequest(user, sessionJson, res) {
if (this.localSessionLock[sessionJson.id]) {
Logger.debug(`[PlaybackSessionManager] syncLocalSessionRequest: Local session is locked and already syncing`)
return res.sendStatus(200)
}
var libraryItem = this.db.getLibraryItem(sessionJson.libraryItemId)
if (!libraryItem) {
Logger.error(`[PlaybackSessionManager] syncLocalSessionRequest: Library item not found for session "${sessionJson.libraryItemId}"`)
return res.sendStatus(200)
}
this.localSessionLock[sessionJson.id] = true // Lock local session
var session = await this.db.getPlaybackSession(sessionJson.id)
if (!session) {
// New session from local
session = new PlaybackSession(sessionJson)
await this.db.insertEntity('session', session)
} else {
session.currentTime = sessionJson.currentTime
session.timeListening = sessionJson.timeListening
session.updatedAt = sessionJson.updatedAt
session.date = date.format(new Date(), 'YYYY-MM-DD')
@ -94,6 +103,9 @@ class PlaybackSessionManager {
data: itemProgress.toJSON()
})
}
delete this.localSessionLock[sessionJson.id] // Unlock local session
res.sendStatus(200)
}
@ -256,4 +268,4 @@ class PlaybackSessionManager {
}
}
}
module.exports = PlaybackSessionManager
module.exports = PlaybackSessionManager

View file

@ -221,7 +221,7 @@ class PodcastManager {
return libraryItem.media.autoDownloadEpisodes
}
async checkPodcastForNewEpisodes(podcastLibraryItem, dateToCheckForEpisodesAfter) {
async checkPodcastForNewEpisodes(podcastLibraryItem, dateToCheckForEpisodesAfter, maxNewEpisodes = 3) {
if (!podcastLibraryItem.media.metadata.feedUrl) {
Logger.error(`[PodcastManager] checkPodcastForNewEpisodes no feed url for ${podcastLibraryItem.media.metadata.title} (ID: ${podcastLibraryItem.id})`)
return false
@ -234,15 +234,18 @@ class PodcastManager {
// Filter new and not already has
var newEpisodes = feed.episodes.filter(ep => ep.publishedAt > dateToCheckForEpisodesAfter && !podcastLibraryItem.media.checkHasEpisodeByFeedUrl(ep.enclosure.url))
// Max new episodes for safety = 3
newEpisodes = newEpisodes.slice(0, 3)
if (maxNewEpisodes > 0) {
newEpisodes = newEpisodes.slice(0, maxNewEpisodes)
}
return newEpisodes
}
async checkAndDownloadNewEpisodes(libraryItem) {
async checkAndDownloadNewEpisodes(libraryItem, maxEpisodesToDownload) {
const lastEpisodeCheckDate = new Date(libraryItem.media.lastEpisodeCheck || 0)
Logger.info(`[PodcastManager] checkAndDownloadNewEpisodes for "${libraryItem.media.metadata.title}" - Last episode check: ${lastEpisodeCheckDate}`)
var newEpisodes = await this.checkPodcastForNewEpisodes(libraryItem, libraryItem.media.lastEpisodeCheck)
var newEpisodes = await this.checkPodcastForNewEpisodes(libraryItem, libraryItem.media.lastEpisodeCheck, maxEpisodesToDownload)
if (newEpisodes.length) {
Logger.info(`[PodcastManager] Found ${newEpisodes.length} new episodes for podcast "${libraryItem.media.metadata.title}" - starting download`)
this.downloadPodcastEpisodes(libraryItem, newEpisodes, false)

View file

@ -30,7 +30,7 @@ class RssFeedManager {
return Object.values(this.feeds).find(feed => feed.entityId === libraryItemId)
}
getFeed(req, res) {
async getFeed(req, res) {
var feed = this.feeds[req.params.id]
if (!feed) {
Logger.error(`[RssFeedManager] Feed not found ${req.params.id}`)
@ -38,6 +38,15 @@ class RssFeedManager {
return
}
if (feed.entityType === 'item') {
const libraryItem = this.db.getLibraryItem(feed.entityId)
if (libraryItem && (!feed.entityUpdatedAt || libraryItem.updatedAt > feed.entityUpdatedAt)) {
Logger.debug(`[RssFeedManager] Updating RSS feed for item ${libraryItem.id} "${libraryItem.media.metadata.title}"`)
feed.updateFromItem(libraryItem)
await this.db.updateEntity('feed', feed)
}
}
var xml = feed.buildXml()
res.set('Content-Type', 'text/xml')
res.send(xml)

View file

@ -9,6 +9,7 @@ class Feed {
this.userId = null
this.entityType = null
this.entityId = null
this.entityUpdatedAt = null
this.coverPath = null
this.serverAddress = null
@ -79,6 +80,7 @@ class Feed {
this.userId = userId
this.entityType = 'item'
this.entityId = libraryItem.id
this.entityUpdatedAt = libraryItem.updatedAt
this.coverPath = media.coverPath || null
this.serverAddress = serverAddress
this.feedUrl = feedUrl
@ -111,6 +113,39 @@ class Feed {
this.updatedAt = Date.now()
}
updateFromItem(libraryItem) {
const media = libraryItem.media
const mediaMetadata = media.metadata
const isPodcast = libraryItem.mediaType === 'podcast'
const author = isPodcast ? mediaMetadata.author : mediaMetadata.authorName
this.entityUpdatedAt = libraryItem.updatedAt
this.meta.title = mediaMetadata.title
this.meta.description = mediaMetadata.description
this.meta.author = author
this.meta.imageUrl = media.coverPath ? `${this.serverAddress}/feed/${this.slug}/cover` : `${this.serverAddress}/Logo.png`
this.meta.explicit = !!mediaMetadata.explicit
this.episodes = []
if (isPodcast) { // PODCAST EPISODES
media.episodes.forEach((episode) => {
var feedEpisode = new FeedEpisode()
feedEpisode.setFromPodcastEpisode(libraryItem, this.serverAddress, this.slug, episode, this.meta)
this.episodes.push(feedEpisode)
})
} else { // AUDIOBOOK EPISODES
media.tracks.forEach((audioTrack) => {
var feedEpisode = new FeedEpisode()
feedEpisode.setFromAudiobookTrack(libraryItem, this.serverAddress, this.slug, audioTrack, this.meta)
this.episodes.push(feedEpisode)
})
}
this.updatedAt = Date.now()
this.xml = null
}
buildXml() {
if (this.xml) return this.xml

View file

@ -86,7 +86,7 @@ class FeedEpisode {
setFromAudiobookTrack(libraryItem, serverAddress, slug, audioTrack, meta) {
// Example: <pubDate>Fri, 04 Feb 2015 00:00:00 GMT</pubDate>
const timeOffset = isNaN(audioTrack.index) ? 0 : (Number(audioTrack.index) * 1000) // Offset pubdate to ensure correct order
const audiobookPubDate = date.format(new Date(libraryItem.addedAt + timeOffset), 'ddd, DD MMM YYYY HH:mm:ss [GMT]')
const audiobookPubDate = date.format(new Date(libraryItem.addedAt - timeOffset), 'ddd, DD MMM YYYY HH:mm:ss [GMT]')
const contentUrl = `/feed/${slug}/item/${audioTrack.index}/${audioTrack.metadata.filename}`
const media = libraryItem.media

View file

@ -116,16 +116,16 @@ class ApiRouter {
//
// Collection Routes
//
this.router.post('/collections', CollectionController.create.bind(this))
this.router.post('/collections', CollectionController.middleware.bind(this), CollectionController.create.bind(this))
this.router.get('/collections', CollectionController.findAll.bind(this))
this.router.get('/collections/:id', CollectionController.findOne.bind(this))
this.router.patch('/collections/:id', CollectionController.update.bind(this))
this.router.delete('/collections/:id', CollectionController.delete.bind(this))
this.router.get('/collections/:id', CollectionController.middleware.bind(this), CollectionController.findOne.bind(this))
this.router.patch('/collections/:id', CollectionController.middleware.bind(this), CollectionController.update.bind(this))
this.router.delete('/collections/:id', CollectionController.middleware.bind(this), CollectionController.delete.bind(this))
this.router.post('/collections/:id/book', CollectionController.addBook.bind(this))
this.router.delete('/collections/:id/book/:bookId', CollectionController.removeBook.bind(this))
this.router.post('/collections/:id/batch/add', CollectionController.addBatch.bind(this))
this.router.post('/collections/:id/batch/remove', CollectionController.removeBatch.bind(this))
this.router.post('/collections/:id/book', CollectionController.middleware.bind(this), CollectionController.addBook.bind(this))
this.router.delete('/collections/:id/book/:bookId', CollectionController.middleware.bind(this), CollectionController.removeBook.bind(this))
this.router.post('/collections/:id/batch/add', CollectionController.middleware.bind(this), CollectionController.addBatch.bind(this))
this.router.post('/collections/:id/batch/remove', CollectionController.middleware.bind(this), CollectionController.removeBatch.bind(this))
//
// Current User Routes (Me)

View file

@ -10,6 +10,7 @@ const { ScanResult, LogLevel } = require('../utils/constants')
const MediaFileScanner = require('./MediaFileScanner')
const BookFinder = require('../finders/BookFinder')
const PodcastFinder = require('../finders/PodcastFinder')
const LibraryItem = require('../objects/LibraryItem')
const LibraryScan = require('./LibraryScan')
const ScanOptions = require('./ScanOptions')
@ -28,7 +29,12 @@ class Scanner {
this.cancelLibraryScan = {}
this.librariesScanning = []
// Watcher file update scan vars
this.pendingFileUpdatesToScan = []
this.scanningFilesChanged = false
this.bookFinder = new BookFinder()
this.podcastFinder = new PodcastFinder()
}
isLibraryScanning(libraryId) {
@ -494,7 +500,16 @@ class Scanner {
}
async scanFilesChanged(fileUpdates) {
if (!fileUpdates.length) return
if (!fileUpdates || !fileUpdates.length) return
// If already scanning files from watcher then add these updates to queue
if (this.scanningFilesChanged) {
this.pendingFileUpdatesToScan.push(fileUpdates)
Logger.debug(`[Scanner] Already scanning files from watcher - file updates pushed to queue (size ${this.pendingFileUpdatesToScan.length})`)
return
}
this.scanningFilesChanged = true
// files grouped by folder
var folderGroups = this.getFileUpdatesGrouped(fileUpdates)
@ -520,6 +535,13 @@ class Scanner {
var folderScanResults = await this.scanFolderUpdates(library, folder, fileUpdateGroup)
Logger.debug(`[Scanner] Folder scan results`, folderScanResults)
}
this.scanningFilesChanged = false
if (this.pendingFileUpdatesToScan.length) {
Logger.debug(`[Scanner] File updates finished scanning with more updates in queue (${this.pendingFileUpdatesToScan.length})`)
this.scanFilesChanged(this.pendingFileUpdatesToScan.shift())
}
}
async scanFolderUpdates(library, folder, fileUpdateGroup) {
@ -652,16 +674,6 @@ class Scanner {
var provider = options.provider || 'google'
var searchTitle = options.title || libraryItem.media.metadata.title
var searchAuthor = options.author || libraryItem.media.metadata.authorName
var searchISBN = options.isbn || libraryItem.media.metadata.isbn
var searchASIN = options.asin || libraryItem.media.metadata.asin
var results = await this.bookFinder.search(provider, searchTitle, searchAuthor, searchISBN, searchASIN)
if (!results.length) {
return {
warning: `No ${provider} match found`
}
}
var matchData = results[0]
// Set to override existing metadata if scannerPreferMatchedMetadata setting is true
if (this.db.serverSettings.scannerPreferMatchedMetadata) {
@ -669,18 +681,110 @@ class Scanner {
options.overrideDetails = true
}
// Update cover if not set OR overrideCover flag
var updatePayload = {}
var hasUpdated = false
if (matchData.cover && (!libraryItem.media.coverPath || options.overrideCover)) {
Logger.debug(`[Scanner] Updating cover "${matchData.cover}"`)
var coverResult = await this.coverManager.downloadCoverFromUrl(libraryItem, matchData.cover)
if (!coverResult || coverResult.error || !coverResult.cover) {
Logger.warn(`[Scanner] Match cover "${matchData.cover}" failed to use: ${coverResult ? coverResult.error : 'Unknown Error'}`)
} else {
if (libraryItem.mediaType === 'book') {
var searchISBN = options.isbn || libraryItem.media.metadata.isbn
var searchASIN = options.asin || libraryItem.media.metadata.asin
var results = await this.bookFinder.search(provider, searchTitle, searchAuthor, searchISBN, searchASIN)
if (!results.length) {
return {
warning: `No ${provider} match found`
}
}
var matchData = results[0]
// Update cover if not set OR overrideCover flag
if (matchData.cover && (!libraryItem.media.coverPath || options.overrideCover)) {
Logger.debug(`[Scanner] Updating cover "${matchData.cover}"`)
var coverResult = await this.coverManager.downloadCoverFromUrl(libraryItem, matchData.cover)
if (!coverResult || coverResult.error || !coverResult.cover) {
Logger.warn(`[Scanner] Match cover "${matchData.cover}" failed to use: ${coverResult ? coverResult.error : 'Unknown Error'}`)
} else {
hasUpdated = true
}
}
updatePayload = await this.quickMatchBookBuildUpdatePayload(libraryItem, matchData, options)
} else { // Podcast quick match
var results = await this.podcastFinder.search(searchTitle)
if (!results.length) {
return {
warning: `No ${provider} match found`
}
}
var matchData = results[0]
// Update cover if not set OR overrideCover flag
if (matchData.cover && (!libraryItem.media.coverPath || options.overrideCover)) {
Logger.debug(`[Scanner] Updating cover "${matchData.cover}"`)
var coverResult = await this.coverManager.downloadCoverFromUrl(libraryItem, matchData.cover)
if (!coverResult || coverResult.error || !coverResult.cover) {
Logger.warn(`[Scanner] Match cover "${matchData.cover}" failed to use: ${coverResult ? coverResult.error : 'Unknown Error'}`)
} else {
hasUpdated = true
}
}
updatePayload = this.quickMatchPodcastBuildUpdatePayload(libraryItem, matchData, options)
}
if (Object.keys(updatePayload).length) {
Logger.debug('[Scanner] Updating details', updatePayload)
if (libraryItem.media.update(updatePayload)) {
hasUpdated = true
}
}
if (hasUpdated) {
await this.db.updateLibraryItem(libraryItem)
this.emitter('item_updated', libraryItem.toJSONExpanded())
}
return {
updated: hasUpdated,
libraryItem: libraryItem.toJSONExpanded()
}
}
quickMatchPodcastBuildUpdatePayload(libraryItem, matchData, options) {
const updatePayload = {}
updatePayload.metadata = {}
const matchDataTransformed = {
title: matchData.title || null,
author: matchData.artistName || null,
genres: matchData.genres || [],
itunesId: matchData.id || null,
itunesPageUrl: matchData.pageUrl || null,
itunesArtistId: matchData.artistId || null,
releaseDate: matchData.releaseDate || null,
imageUrl: matchData.cover || null,
description: matchData.descriptionPlain || null
}
for (const key in matchDataTransformed) {
if (matchDataTransformed[key]) {
if (key === 'genres') {
if ((!libraryItem.media.metadata.genres || options.overrideDetails)) {
updatePayload.metadata[key] = matchDataTransformed[key].split(',').map(v => v.trim()).filter(v => !!v)
}
} else if (!libraryItem.media.metadata[key] || options.overrideDetails) {
updatePayload.metadata[key] = matchDataTransformed[key]
}
}
}
if (!Object.keys(updatePayload.metadata).length) {
delete updatePayload.metadata
}
return updatePayload
}
async quickMatchBookBuildUpdatePayload(libraryItem, matchData, options) {
// Update media metadata if not set OR overrideDetails flag
const detailKeysToUpdate = ['title', 'subtitle', 'description', 'narrator', 'publisher', 'publishedYear', 'genres', 'tags', 'language', 'explicit', 'asin', 'isbn']
const updatePayload = {}
@ -743,22 +847,11 @@ class Scanner {
updatePayload.metadata.series = seriesPayload
}
if (Object.keys(updatePayload).length) {
Logger.debug('[Scanner] Updating details', updatePayload)
if (libraryItem.media.update(updatePayload)) {
hasUpdated = true
}
if (!Object.keys(updatePayload.metadata).length) {
delete updatePayload.metadata
}
if (hasUpdated) {
await this.db.updateLibraryItem(libraryItem)
this.emitter('item_updated', libraryItem.toJSONExpanded())
}
return {
updated: hasUpdated,
libraryItem: libraryItem.toJSONExpanded()
}
return updatePayload
}
async matchLibraryItems(library) {

2235
server/utils/htmlEntities.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,5 @@
const sanitizeHtml = require('../libs/sanitizeHtml')
const {entities} = require("./htmlEntities");
function sanitize(html) {
const sanitizerOptions = {
@ -17,12 +18,22 @@ function sanitize(html) {
}
module.exports.sanitize = sanitize
function stripAllTags(html) {
function stripAllTags(html, shouldDecodeEntities = true) {
const sanitizerOptions = {
allowedTags: [],
disallowedTagsMode: 'discard'
}
return sanitizeHtml(html, sanitizerOptions)
let sanitized = sanitizeHtml(html, sanitizerOptions)
return shouldDecodeEntities ? decodeHTMLEntities(sanitized) : sanitized
}
module.exports.stripAllTags = stripAllTags
function decodeHTMLEntities(strToDecode) {
return strToDecode.replace(/\&([^;]+);?/g, function (entity) {
if (entity in entities) {
return entities[entity]
}
return entity;
})
}
module.exports.stripAllTags = stripAllTags