mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-22 00:07:52 +01:00
Update remove old sync local sessions endpoint & update MeController routes to use new user model
This commit is contained in:
parent
1923854202
commit
9facf77ff1
@ -1,5 +1,6 @@
|
||||
const axios = require('axios')
|
||||
const passport = require('passport')
|
||||
const { Request, Response, NextFunction } = require('express')
|
||||
const bcrypt = require('./libs/bcryptjs')
|
||||
const jwt = require('./libs/jsonwebtoken')
|
||||
const LocalStrategy = require('./libs/passportLocal')
|
||||
@ -355,8 +356,8 @@ class Auth {
|
||||
* - 'openid': OpenID authentication directly over web
|
||||
* - 'openid-mobile': OpenID authentication, but done via an mobile device
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {string} authMethod - The authentication method, default is 'local'.
|
||||
*/
|
||||
paramsToCookies(req, res, authMethod = 'local') {
|
||||
@ -385,8 +386,8 @@ class Auth {
|
||||
* Informs the client in the right mode about a successfull login and the token
|
||||
* (clients choise is restored from cookies).
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async handleLoginSuccessBasedOnCookie(req, res) {
|
||||
// get userLogin json (information about the user, server and the session)
|
||||
@ -740,9 +741,9 @@ class Auth {
|
||||
|
||||
/**
|
||||
* middleware to use in express to only allow authenticated users.
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {import('express').NextFunction} next
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
isAuthenticated(req, res, next) {
|
||||
// check if session cookie says that we are authenticated
|
||||
@ -914,13 +915,13 @@ class Auth {
|
||||
* User changes their password from request
|
||||
* TODO: Update responses to use error status codes
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {import('./controllers/MeController').RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async userChangePassword(req, res) {
|
||||
let { password, newPassword } = req.body
|
||||
newPassword = newPassword || ''
|
||||
const matchingUser = req.user
|
||||
const matchingUser = req.userNew
|
||||
|
||||
// Only root can have an empty password
|
||||
if (matchingUser.type !== 'root' && !newPassword) {
|
||||
|
@ -31,6 +31,8 @@ class MeController {
|
||||
/**
|
||||
* GET: /api/me/listening-sessions
|
||||
*
|
||||
* @this import('../routers/ApiRouter')
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
@ -94,6 +96,8 @@ class MeController {
|
||||
/**
|
||||
* GET: /api/me/listening-stats
|
||||
*
|
||||
* @this import('../routers/ApiRouter')
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
@ -261,113 +265,65 @@ class MeController {
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
// PATCH: api/me/password
|
||||
/**
|
||||
* PATCH: /api/me/password
|
||||
* User change password. Requires current password.
|
||||
* Guest users cannot change password.
|
||||
*
|
||||
* @this import('../routers/ApiRouter')
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
updatePassword(req, res) {
|
||||
if (req.user.isGuest) {
|
||||
Logger.error(`[MeController] Guest user attempted to change password`, req.user.username)
|
||||
if (req.userNew.isGuest) {
|
||||
Logger.error(`[MeController] Guest user "${req.userNew.username}" attempted to change password`)
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
this.auth.userChangePassword(req, res)
|
||||
}
|
||||
|
||||
// TODO: Deprecated. Removed from Android. Only used in iOS app now.
|
||||
// POST: api/me/sync-local-progress
|
||||
async syncLocalMediaProgress(req, res) {
|
||||
if (!req.body.localMediaProgress) {
|
||||
Logger.error(`[MeController] syncLocalMediaProgress invalid post body`)
|
||||
return res.sendStatus(500)
|
||||
}
|
||||
const updatedLocalMediaProgress = []
|
||||
let numServerProgressUpdates = 0
|
||||
const updatedServerMediaProgress = []
|
||||
const localMediaProgress = req.body.localMediaProgress || []
|
||||
|
||||
for (const localProgress of localMediaProgress) {
|
||||
if (!localProgress.libraryItemId) {
|
||||
Logger.error(`[MeController] syncLocalMediaProgress invalid local media progress object`, localProgress)
|
||||
continue
|
||||
}
|
||||
|
||||
const libraryItem = await Database.libraryItemModel.getOldById(localProgress.libraryItemId)
|
||||
if (!libraryItem) {
|
||||
Logger.error(`[MeController] syncLocalMediaProgress invalid local media progress object no library item with id "${localProgress.libraryItemId}"`, localProgress)
|
||||
continue
|
||||
}
|
||||
|
||||
let mediaProgress = req.user.getMediaProgress(localProgress.libraryItemId, localProgress.episodeId)
|
||||
if (!mediaProgress) {
|
||||
// New media progress from mobile
|
||||
Logger.debug(`[MeController] syncLocalMediaProgress local progress is new - creating ${localProgress.id}`)
|
||||
req.user.createUpdateMediaProgress(libraryItem, localProgress, localProgress.episodeId)
|
||||
mediaProgress = req.user.getMediaProgress(localProgress.libraryItemId, localProgress.episodeId)
|
||||
if (mediaProgress) await Database.upsertMediaProgress(mediaProgress)
|
||||
updatedServerMediaProgress.push(mediaProgress)
|
||||
numServerProgressUpdates++
|
||||
} else if (mediaProgress.lastUpdate < localProgress.lastUpdate) {
|
||||
Logger.debug(`[MeController] syncLocalMediaProgress local progress is more recent - updating ${mediaProgress.id}`)
|
||||
req.user.createUpdateMediaProgress(libraryItem, localProgress, localProgress.episodeId)
|
||||
mediaProgress = req.user.getMediaProgress(localProgress.libraryItemId, localProgress.episodeId)
|
||||
if (mediaProgress) await Database.upsertMediaProgress(mediaProgress)
|
||||
updatedServerMediaProgress.push(mediaProgress)
|
||||
numServerProgressUpdates++
|
||||
} else if (mediaProgress.lastUpdate > localProgress.lastUpdate) {
|
||||
const updateTimeDifference = mediaProgress.lastUpdate - localProgress.lastUpdate
|
||||
Logger.debug(`[MeController] syncLocalMediaProgress server progress is more recent by ${updateTimeDifference}ms - ${mediaProgress.id}`)
|
||||
|
||||
for (const key in localProgress) {
|
||||
// Local media progress ID uses the local library item id and server media progress uses the library item id
|
||||
if (key !== 'id' && mediaProgress[key] != undefined && localProgress[key] !== mediaProgress[key]) {
|
||||
// Logger.debug(`[MeController] syncLocalMediaProgress key ${key} changed from ${localProgress[key]} to ${mediaProgress[key]} - ${mediaProgress.id}`)
|
||||
localProgress[key] = mediaProgress[key]
|
||||
}
|
||||
}
|
||||
updatedLocalMediaProgress.push(localProgress)
|
||||
} else {
|
||||
Logger.debug(`[MeController] syncLocalMediaProgress server and local are in sync - ${mediaProgress.id}`)
|
||||
}
|
||||
}
|
||||
|
||||
Logger.debug(`[MeController] syncLocalMediaProgress server updates = ${numServerProgressUpdates}, local updates = ${updatedLocalMediaProgress.length}`)
|
||||
if (numServerProgressUpdates > 0) {
|
||||
SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
|
||||
}
|
||||
|
||||
res.json({
|
||||
numServerProgressUpdates,
|
||||
localProgressUpdates: updatedLocalMediaProgress, // Array of LocalMediaProgress that were updated from server (server more recent)
|
||||
serverProgressUpdates: updatedServerMediaProgress // Array of MediaProgress that made updates to server (local more recent)
|
||||
})
|
||||
}
|
||||
|
||||
// GET: api/me/items-in-progress
|
||||
/**
|
||||
* GET: /api/me/items-in-progress
|
||||
* Pull items in progress for all libraries
|
||||
* Used in Android Auto in progress list since there is no easy library selection
|
||||
* TODO: Update to use mediaItemId and mediaItemType. Use sort & limit in query
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getAllLibraryItemsInProgress(req, res) {
|
||||
const limit = !isNaN(req.query.limit) ? Number(req.query.limit) || 25 : 25
|
||||
|
||||
const mediaProgressesInProgress = req.userNew.mediaProgresses.filter((mp) => !mp.isFinished && (mp.currentTime > 0 || mp.ebookProgress > 0))
|
||||
|
||||
const libraryItemsIds = [...new Set(mediaProgressesInProgress.map((mp) => mp.extraData?.libraryItemId).filter((id) => id))]
|
||||
const libraryItems = await Database.libraryItemModel.getAllOldLibraryItems({ id: libraryItemsIds })
|
||||
|
||||
let itemsInProgress = []
|
||||
// TODO: More efficient to do this in a single query
|
||||
for (const mediaProgress of req.user.mediaProgress) {
|
||||
if (!mediaProgress.isFinished && (mediaProgress.progress > 0 || mediaProgress.ebookProgress > 0)) {
|
||||
const libraryItem = await Database.libraryItemModel.getOldById(mediaProgress.libraryItemId)
|
||||
|
||||
for (const mediaProgress of mediaProgressesInProgress) {
|
||||
const oldMediaProgress = mediaProgress.getOldMediaProgress()
|
||||
const libraryItem = libraryItems.find((li) => li.id === oldMediaProgress.libraryItemId)
|
||||
if (libraryItem) {
|
||||
if (mediaProgress.episodeId && libraryItem.mediaType === 'podcast') {
|
||||
const episode = libraryItem.media.episodes.find((ep) => ep.id === mediaProgress.episodeId)
|
||||
if (oldMediaProgress.episodeId && libraryItem.mediaType === 'podcast') {
|
||||
const episode = libraryItem.media.episodes.find((ep) => ep.id === oldMediaProgress.episodeId)
|
||||
if (episode) {
|
||||
const libraryItemWithEpisode = {
|
||||
...libraryItem.toJSONMinified(),
|
||||
recentEpisode: episode.toJSON(),
|
||||
progressLastUpdate: mediaProgress.lastUpdate
|
||||
progressLastUpdate: oldMediaProgress.lastUpdate
|
||||
}
|
||||
itemsInProgress.push(libraryItemWithEpisode)
|
||||
}
|
||||
} else if (!mediaProgress.episodeId) {
|
||||
} else if (!oldMediaProgress.episodeId) {
|
||||
itemsInProgress.push({
|
||||
...libraryItem.toJSONMinified(),
|
||||
progressLastUpdate: mediaProgress.lastUpdate
|
||||
progressLastUpdate: oldMediaProgress.lastUpdate
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
itemsInProgress = sort(itemsInProgress)
|
||||
.desc((li) => li.progressLastUpdate)
|
||||
@ -377,59 +333,67 @@ class MeController {
|
||||
})
|
||||
}
|
||||
|
||||
// GET: api/me/series/:id/remove-from-continue-listening
|
||||
/**
|
||||
* GET: /api/me/series/:id/remove-from-continue-listening
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async removeSeriesFromContinueListening(req, res) {
|
||||
const series = await Database.seriesModel.getOldById(req.params.id)
|
||||
if (!series) {
|
||||
if (!(await Database.seriesModel.checkExistsById(req.params.id))) {
|
||||
Logger.error(`[MeController] removeSeriesFromContinueListening: Series ${req.params.id} not found`)
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
const hasUpdated = req.user.addSeriesToHideFromContinueListening(req.params.id)
|
||||
const hasUpdated = await req.userNew.addSeriesToHideFromContinueListening(req.params.id)
|
||||
if (hasUpdated) {
|
||||
await Database.updateUser(req.user)
|
||||
SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
|
||||
SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', req.userNew.toOldJSONForBrowser())
|
||||
}
|
||||
res.json(req.user.toJSONForBrowser())
|
||||
res.json(req.userNew.toOldJSONForBrowser())
|
||||
}
|
||||
|
||||
// GET: api/me/series/:id/readd-to-continue-listening
|
||||
/**
|
||||
* GET: api/me/series/:id/readd-to-continue-listening
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async readdSeriesFromContinueListening(req, res) {
|
||||
const series = await Database.seriesModel.getOldById(req.params.id)
|
||||
if (!series) {
|
||||
if (!(await Database.seriesModel.checkExistsById(req.params.id))) {
|
||||
Logger.error(`[MeController] readdSeriesFromContinueListening: Series ${req.params.id} not found`)
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
const hasUpdated = req.user.removeSeriesFromHideFromContinueListening(req.params.id)
|
||||
const hasUpdated = await req.userNew.removeSeriesFromHideFromContinueListening(req.params.id)
|
||||
if (hasUpdated) {
|
||||
await Database.updateUser(req.user)
|
||||
SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
|
||||
SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', req.userNew.toOldJSONForBrowser())
|
||||
}
|
||||
res.json(req.user.toJSONForBrowser())
|
||||
res.json(req.userNew.toOldJSONForBrowser())
|
||||
}
|
||||
|
||||
// GET: api/me/progress/:id/remove-from-continue-listening
|
||||
/**
|
||||
* GET: api/me/progress/:id/remove-from-continue-listening
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async removeItemFromContinueListening(req, res) {
|
||||
const mediaProgress = req.user.mediaProgress.find((mp) => mp.id === req.params.id)
|
||||
const mediaProgress = req.userNew.mediaProgresses.find((mp) => mp.id === req.params.id)
|
||||
if (!mediaProgress) {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
const hasUpdated = req.user.removeProgressFromContinueListening(req.params.id)
|
||||
if (hasUpdated) {
|
||||
await Database.mediaProgressModel.update(
|
||||
{
|
||||
hideFromContinueListening: true
|
||||
},
|
||||
{
|
||||
where: {
|
||||
id: mediaProgress.id
|
||||
|
||||
// Already hidden
|
||||
if (mediaProgress.hideFromContinueListening) {
|
||||
return res.json(req.userNew.toOldJSONForBrowser())
|
||||
}
|
||||
}
|
||||
)
|
||||
SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
|
||||
}
|
||||
res.json(req.user.toJSONForBrowser())
|
||||
|
||||
mediaProgress.hideFromContinueListening = true
|
||||
await mediaProgress.save()
|
||||
|
||||
SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', req.userNew.toOldJSONForBrowser())
|
||||
|
||||
res.json(req.userNew.toOldJSONForBrowser())
|
||||
}
|
||||
|
||||
/**
|
||||
@ -444,7 +408,7 @@ class MeController {
|
||||
Logger.error(`[MeController] Invalid year "${year}"`)
|
||||
return res.status(400).send('Invalid year')
|
||||
}
|
||||
const data = await userStats.getStatsForYear(req.user, year)
|
||||
const data = await userStats.getStatsForYear(req.userNew.id, year)
|
||||
res.json(data)
|
||||
}
|
||||
}
|
||||
|
@ -425,6 +425,9 @@ class User extends Model {
|
||||
get isUser() {
|
||||
return this.type === 'user'
|
||||
}
|
||||
get isGuest() {
|
||||
return this.type === 'guest'
|
||||
}
|
||||
get canAccessExplicitContent() {
|
||||
return !!this.permissions?.accessExplicitContent && this.isActive
|
||||
}
|
||||
@ -780,6 +783,38 @@ class User extends Model {
|
||||
await this.save()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} seriesId
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async addSeriesToHideFromContinueListening(seriesId) {
|
||||
if (!this.extraData) this.extraData = {}
|
||||
const seriesHideFromContinueListening = this.extraData.seriesHideFromContinueListening || []
|
||||
if (seriesHideFromContinueListening.includes(seriesId)) return false
|
||||
seriesHideFromContinueListening.push(seriesId)
|
||||
this.extraData.seriesHideFromContinueListening = seriesHideFromContinueListening
|
||||
this.changed('extraData', true)
|
||||
await this.save()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} seriesId
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async removeSeriesFromHideFromContinueListening(seriesId) {
|
||||
if (!this.extraData) this.extraData = {}
|
||||
let seriesHideFromContinueListening = this.extraData.seriesHideFromContinueListening || []
|
||||
if (!seriesHideFromContinueListening.includes(seriesId)) return false
|
||||
seriesHideFromContinueListening = seriesHideFromContinueListening.filter((sid) => sid !== seriesId)
|
||||
this.extraData.seriesHideFromContinueListening = seriesHideFromContinueListening
|
||||
this.changed('extraData', true)
|
||||
await this.save()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = User
|
||||
|
@ -438,40 +438,6 @@ class User {
|
||||
return this.checkCanAccessLibraryItemWithTags(libraryItem.media.tags)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a user can access a library item
|
||||
* @param {string} libraryId
|
||||
* @param {boolean} explicit
|
||||
* @param {string[]} tags
|
||||
*/
|
||||
checkCanAccessLibraryItemWithData(libraryId, explicit, tags) {
|
||||
if (!this.checkCanAccessLibrary(libraryId)) return false
|
||||
if (explicit && !this.canAccessExplicitContent) return false
|
||||
return this.checkCanAccessLibraryItemWithTags(tags)
|
||||
}
|
||||
|
||||
checkShouldHideSeriesFromContinueListening(seriesId) {
|
||||
return this.seriesHideFromContinueListening.includes(seriesId)
|
||||
}
|
||||
|
||||
addSeriesToHideFromContinueListening(seriesId) {
|
||||
if (this.seriesHideFromContinueListening.includes(seriesId)) return false
|
||||
this.seriesHideFromContinueListening.push(seriesId)
|
||||
return true
|
||||
}
|
||||
|
||||
removeSeriesFromHideFromContinueListening(seriesId) {
|
||||
if (!this.seriesHideFromContinueListening.includes(seriesId)) return false
|
||||
this.seriesHideFromContinueListening = this.seriesHideFromContinueListening.filter((sid) => sid !== seriesId)
|
||||
return true
|
||||
}
|
||||
|
||||
removeProgressFromContinueListening(progressId) {
|
||||
const progress = this.mediaProgress.find((mp) => mp.id === progressId)
|
||||
if (!progress) return false
|
||||
return progress.removeFromContinueListening()
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of podcast episodes not finished for library item
|
||||
* Note: libraryItem passed in from libraryHelpers is not a LibraryItem class instance
|
||||
|
@ -182,7 +182,6 @@ class ApiRouter {
|
||||
this.router.patch('/me/item/:id/bookmark', MeController.updateBookmark.bind(this))
|
||||
this.router.delete('/me/item/:id/bookmark/:time', MeController.removeBookmark.bind(this))
|
||||
this.router.patch('/me/password', MeController.updatePassword.bind(this))
|
||||
this.router.post('/me/sync-local-progress', MeController.syncLocalMediaProgress.bind(this)) // TODO: Deprecated. Removed from Android. Only used in iOS app now.
|
||||
this.router.get('/me/items-in-progress', MeController.getAllLibraryItemsInProgress.bind(this))
|
||||
this.router.get('/me/series/:id/remove-from-continue-listening', MeController.removeSeriesFromContinueListening.bind(this))
|
||||
this.router.get('/me/series/:id/readd-to-continue-listening', MeController.readdSeriesFromContinueListening.bind(this))
|
||||
|
@ -65,11 +65,10 @@ module.exports = {
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {import('../../objects/user/User')} user
|
||||
* @param {string} userId
|
||||
* @param {number} year YYYY
|
||||
*/
|
||||
async getStatsForYear(user, year) {
|
||||
const userId = user.id
|
||||
async getStatsForYear(userId, year) {
|
||||
const listeningSessions = await this.getUserListeningSessionsForYear(userId, year)
|
||||
const bookProgressesFinished = await this.getBookMediaProgressFinishedForYear(userId, year)
|
||||
|
||||
@ -91,7 +90,7 @@ module.exports = {
|
||||
let longestAudiobookFinished = null
|
||||
for (const mediaProgress of bookProgressesFinished) {
|
||||
// Grab first 5 that have a cover
|
||||
if (mediaProgress.mediaItem?.coverPath && !finishedBooksWithCovers.includes(mediaProgress.mediaItem.libraryItem.id) && finishedBooksWithCovers.length < 5 && await fsExtra.pathExists(mediaProgress.mediaItem.coverPath)) {
|
||||
if (mediaProgress.mediaItem?.coverPath && !finishedBooksWithCovers.includes(mediaProgress.mediaItem.libraryItem.id) && finishedBooksWithCovers.length < 5 && (await fsExtra.pathExists(mediaProgress.mediaItem.coverPath))) {
|
||||
finishedBooksWithCovers.push(mediaProgress.mediaItem.libraryItem.id)
|
||||
}
|
||||
|
||||
@ -108,7 +107,7 @@ module.exports = {
|
||||
// Get listening session stats
|
||||
for (const ls of listeningSessions) {
|
||||
// Grab first 25 that have a cover
|
||||
if (ls.mediaItem?.coverPath && !booksWithCovers.includes(ls.mediaItem.libraryItem.id) && !finishedBooksWithCovers.includes(ls.mediaItem.libraryItem.id) && booksWithCovers.length < 25 && await fsExtra.pathExists(ls.mediaItem.coverPath)) {
|
||||
if (ls.mediaItem?.coverPath && !booksWithCovers.includes(ls.mediaItem.libraryItem.id) && !finishedBooksWithCovers.includes(ls.mediaItem.libraryItem.id) && booksWithCovers.length < 25 && (await fsExtra.pathExists(ls.mediaItem.coverPath))) {
|
||||
booksWithCovers.push(ls.mediaItem.libraryItem.id)
|
||||
}
|
||||
|
||||
@ -141,7 +140,7 @@ module.exports = {
|
||||
})
|
||||
|
||||
// Filter out bad genres like "audiobook" and "audio book"
|
||||
const genres = (ls.mediaMetadata.genres || []).filter(g => g && !g.toLowerCase().includes('audiobook') && !g.toLowerCase().includes('audio book'))
|
||||
const genres = (ls.mediaMetadata.genres || []).filter((g) => g && !g.toLowerCase().includes('audiobook') && !g.toLowerCase().includes('audio book'))
|
||||
genres.forEach((genre) => {
|
||||
if (!genreListeningMap[genre]) genreListeningMap[genre] = 0
|
||||
genreListeningMap[genre] += listeningSessionListeningTime
|
||||
@ -156,10 +155,13 @@ module.exports = {
|
||||
totalPodcastListeningTime = Math.round(totalPodcastListeningTime)
|
||||
|
||||
let topAuthors = null
|
||||
topAuthors = Object.keys(authorListeningMap).map(authorName => ({
|
||||
topAuthors = Object.keys(authorListeningMap)
|
||||
.map((authorName) => ({
|
||||
name: authorName,
|
||||
time: Math.round(authorListeningMap[authorName])
|
||||
})).sort((a, b) => b.time - a.time).slice(0, 3)
|
||||
}))
|
||||
.sort((a, b) => b.time - a.time)
|
||||
.slice(0, 3)
|
||||
|
||||
let mostListenedNarrator = null
|
||||
for (const narrator in narratorListeningMap) {
|
||||
@ -172,10 +174,13 @@ module.exports = {
|
||||
}
|
||||
|
||||
let topGenres = null
|
||||
topGenres = Object.keys(genreListeningMap).map(genre => ({
|
||||
topGenres = Object.keys(genreListeningMap)
|
||||
.map((genre) => ({
|
||||
genre,
|
||||
time: Math.round(genreListeningMap[genre])
|
||||
})).sort((a, b) => b.time - a.time).slice(0, 3)
|
||||
}))
|
||||
.sort((a, b) => b.time - a.time)
|
||||
.slice(0, 3)
|
||||
|
||||
let mostListenedMonth = null
|
||||
for (const month in monthListeningMap) {
|
||||
|
Loading…
Reference in New Issue
Block a user