mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +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