mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Add:Recommended book home page shelf
This commit is contained in:
		
							parent
							
								
									030c20b12e
								
							
						
					
					
						commit
						49279430fc
					
				| @ -820,7 +820,6 @@ export default { | ||||
|         return null | ||||
|       }) | ||||
|       if (!libraryItem) return | ||||
|       console.log('Got library itemn', libraryItem) | ||||
|       this.store.commit('showEReader', libraryItem) | ||||
|     }, | ||||
|     selectBtnClick(evt) { | ||||
|  | ||||
| @ -308,6 +308,7 @@ | ||||
|   "LabelPublishYear": "Jahr", | ||||
|   "LabelRecentlyAdded": "Kürzlich hinzugefügt", | ||||
|   "LabelRecentSeries": "Aktuelle Serien", | ||||
|   "LabelRecommended": "Recommended", | ||||
|   "LabelRegion": "Region", | ||||
|   "LabelReleaseDate": "Veröffentlichungsdatum", | ||||
|   "LabelRemoveCover": "Lösche Titelbild", | ||||
|  | ||||
| @ -308,6 +308,7 @@ | ||||
|   "LabelPublishYear": "Publish Year", | ||||
|   "LabelRecentlyAdded": "Recently Added", | ||||
|   "LabelRecentSeries": "Recent Series", | ||||
|   "LabelRecommended": "Recommended", | ||||
|   "LabelRegion": "Region", | ||||
|   "LabelReleaseDate": "Release Date", | ||||
|   "LabelRemoveCover": "Remove cover", | ||||
|  | ||||
| @ -308,6 +308,7 @@ | ||||
|   "LabelPublishYear": "Publish Year", | ||||
|   "LabelRecentlyAdded": "Recently Added", | ||||
|   "LabelRecentSeries": "Recent Series", | ||||
|   "LabelRecommended": "Recommended", | ||||
|   "LabelRegion": "Region", | ||||
|   "LabelReleaseDate": "Release Date", | ||||
|   "LabelRemoveCover": "Remove cover", | ||||
|  | ||||
| @ -308,6 +308,7 @@ | ||||
|   "LabelPublishYear": "Année d'Edition", | ||||
|   "LabelRecentlyAdded": "Derniers Ajouts", | ||||
|   "LabelRecentSeries": "Séries Récentes", | ||||
|   "LabelRecommended": "Recommended", | ||||
|   "LabelRegion": "Région", | ||||
|   "LabelReleaseDate": "Date de Parution", | ||||
|   "LabelRemoveCover": "Supprimer la Couverture", | ||||
|  | ||||
| @ -308,6 +308,7 @@ | ||||
|   "LabelPublishYear": "Godina izdavanja", | ||||
|   "LabelRecentlyAdded": "Nedavno dodano", | ||||
|   "LabelRecentSeries": "Nedavne serije", | ||||
|   "LabelRecommended": "Recommended", | ||||
|   "LabelRegion": "Regija", | ||||
|   "LabelReleaseDate": "Datum izlaska", | ||||
|   "LabelRemoveCover": "Remove cover", | ||||
|  | ||||
| @ -308,6 +308,7 @@ | ||||
|   "LabelPublishYear": "Anno Pubblicazione", | ||||
|   "LabelRecentlyAdded": "Aggiunti Recentemente", | ||||
|   "LabelRecentSeries": "Serie Recenti", | ||||
|   "LabelRecommended": "Recommended", | ||||
|   "LabelRegion": "Regione", | ||||
|   "LabelReleaseDate": "Data Release", | ||||
|   "LabelRemoveCover": "Remove cover", | ||||
|  | ||||
| @ -308,6 +308,7 @@ | ||||
|   "LabelPublishYear": "Rok publikacji", | ||||
|   "LabelRecentlyAdded": "Niedawno dodany", | ||||
|   "LabelRecentSeries": "Ostatnie serie", | ||||
|   "LabelRecommended": "Recommended", | ||||
|   "LabelRegion": "Region", | ||||
|   "LabelReleaseDate": "Data wydania", | ||||
|   "LabelRemoveCover": "Remove cover", | ||||
|  | ||||
| @ -308,6 +308,7 @@ | ||||
|   "LabelPublishYear": "发布年份", | ||||
|   "LabelRecentlyAdded": "最近添加", | ||||
|   "LabelRecentSeries": "最近添加系列", | ||||
|   "LabelRecommended": "Recommended", | ||||
|   "LabelRegion": "区域", | ||||
|   "LabelReleaseDate": "发布日期", | ||||
|   "LabelRemoveCover": "移除封面", | ||||
|  | ||||
| @ -339,6 +339,14 @@ module.exports = { | ||||
|         entities: [], | ||||
|         category: 'continueSeries' | ||||
|       }, | ||||
|       { | ||||
|         id: 'episodes-recently-added', | ||||
|         label: 'Newest Episodes', | ||||
|         labelStringKey: 'LabelNewestEpisodes', | ||||
|         type: 'episode', | ||||
|         entities: [], | ||||
|         category: 'newestEpisodes' | ||||
|       }, | ||||
|       { | ||||
|         id: 'recently-added', | ||||
|         label: 'Recently Added', | ||||
| @ -347,14 +355,6 @@ module.exports = { | ||||
|         entities: [], | ||||
|         category: 'newestItems' | ||||
|       }, | ||||
|       { | ||||
|         id: 'listen-again', | ||||
|         label: 'Listen Again', | ||||
|         labelStringKey: 'LabelListenAgain', | ||||
|         type: isPodcastLibrary ? 'episode' : mediaType, | ||||
|         entities: [], | ||||
|         category: 'recentlyFinished' | ||||
|       }, | ||||
|       { | ||||
|         id: 'recent-series', | ||||
|         label: 'Recent Series', | ||||
| @ -363,6 +363,22 @@ module.exports = { | ||||
|         entities: [], | ||||
|         category: 'newestSeries' | ||||
|       }, | ||||
|       { | ||||
|         id: 'recommended', | ||||
|         label: 'Recommended', | ||||
|         labelStringKey: 'LabelRecommended', | ||||
|         type: mediaType, | ||||
|         entities: [], | ||||
|         category: 'recommended' | ||||
|       }, | ||||
|       { | ||||
|         id: 'listen-again', | ||||
|         label: 'Listen Again', | ||||
|         labelStringKey: 'LabelListenAgain', | ||||
|         type: isPodcastLibrary ? 'episode' : mediaType, | ||||
|         entities: [], | ||||
|         category: 'recentlyFinished' | ||||
|       }, | ||||
|       { | ||||
|         id: 'newest-authors', | ||||
|         label: 'Newest Authors', | ||||
| @ -370,22 +386,13 @@ module.exports = { | ||||
|         type: 'authors', | ||||
|         entities: [], | ||||
|         category: 'newestAuthors' | ||||
|       }, | ||||
|       { | ||||
|         id: 'episodes-recently-added', | ||||
|         label: 'Newest Episodes', | ||||
|         labelStringKey: 'LabelNewestEpisodes', | ||||
|         type: 'episode', | ||||
|         entities: [], | ||||
|         category: 'newestEpisodes' | ||||
|       } | ||||
|     ] | ||||
| 
 | ||||
|     const categories = ['recentlyListened', 'continueSeries', 'newestEpisodes', 'newestItems', 'newestSeries', 'recentlyFinished', 'newestAuthors'] | ||||
|     const categoryMap = {} | ||||
|     categories.forEach((cat) => { | ||||
|       categoryMap[cat] = { | ||||
|         category: cat, | ||||
|     shelves.forEach((shelf) => { | ||||
|       categoryMap[shelf.category] = { | ||||
|         category: shelf.category, | ||||
|         biggest: 0, | ||||
|         smallest: 0, | ||||
|         items: [] | ||||
| @ -395,6 +402,12 @@ module.exports = { | ||||
|     const seriesMap = {} | ||||
|     const authorMap = {} | ||||
| 
 | ||||
|     // For use with recommended
 | ||||
|     const topGenresListened = {} | ||||
|     const topAuthorsListened = {} | ||||
|     const topTagsListened = {} | ||||
|     const notStartedBooks = [] | ||||
| 
 | ||||
|     for (const libraryItem of libraryItems) { | ||||
|       if (libraryItem.addedAt > categoryMap.newestItems.smallest) { | ||||
| 
 | ||||
| @ -494,10 +507,28 @@ module.exports = { | ||||
|       } else if (libraryItem.isBook) { | ||||
|         // Book categories
 | ||||
| 
 | ||||
|         const mediaProgress = allItemProgress.length ? allItemProgress[0] : null | ||||
| 
 | ||||
|         // Used for recommended. Tally up most listened to authors/genres/tags
 | ||||
|         if (mediaProgress && (mediaProgress.inProgress || mediaProgress.isFinished)) { | ||||
|           libraryItem.media.metadata.authors.forEach((author) => { | ||||
|             topAuthorsListened[author.id] = (topAuthorsListened[author.id] || 0) + 1 | ||||
|           }) | ||||
|           libraryItem.media.metadata.genres.forEach((genre) => { | ||||
|             topGenresListened[genre] = (topGenresListened[genre] || 0) + 1 | ||||
|           }) | ||||
|           libraryItem.media.tags.forEach((tag) => { | ||||
|             topTagsListened[tag] = (topTagsListened[tag] || 0) + 1 | ||||
|           }) | ||||
|         } else { | ||||
|           // Insert in random position to add randomization to equal weighted items
 | ||||
|           notStartedBooks.splice(Math.floor(Math.random() * (notStartedBooks.length + 1)), 0, libraryItem) | ||||
|         } | ||||
| 
 | ||||
|         // Newest series
 | ||||
|         if (libraryItem.media.metadata.series.length) { | ||||
|           for (const librarySeries of libraryItem.media.metadata.series) { | ||||
|             const mediaProgress = allItemProgress.length ? allItemProgress[0] : null | ||||
| 
 | ||||
|             const bookInProgress = mediaProgress && (mediaProgress.inProgress || mediaProgress.isFinished) | ||||
|             const bookActive = mediaProgress && mediaProgress.inProgress && !mediaProgress.isFinished | ||||
|             const libraryItemJson = libraryItem.toJSONMinified() | ||||
| @ -602,7 +633,6 @@ module.exports = { | ||||
|         } | ||||
| 
 | ||||
|         // Book listening and finished
 | ||||
|         var mediaProgress = allItemProgress.length ? allItemProgress[0] : null | ||||
|         if (mediaProgress) { | ||||
|           // Handle most recently finished
 | ||||
|           if (mediaProgress.isFinished) { | ||||
| @ -612,7 +642,7 @@ module.exports = { | ||||
|                 finishedAt: mediaProgress.finishedAt | ||||
|               } | ||||
| 
 | ||||
|               var indexToPut = categoryMap.recentlyFinished.items.findIndex(i => mediaProgress.finishedAt > i.finishedAt) | ||||
|               const indexToPut = categoryMap.recentlyFinished.items.findIndex(i => mediaProgress.finishedAt > i.finishedAt) | ||||
|               if (indexToPut >= 0) { | ||||
|                 categoryMap.recentlyFinished.items.splice(indexToPut, 0, libraryItemObj) | ||||
|               } else { | ||||
| @ -632,7 +662,7 @@ module.exports = { | ||||
|                 progressLastUpdate: mediaProgress.lastUpdate | ||||
|               } | ||||
| 
 | ||||
|               var indexToPut = categoryMap.recentlyListened.items.findIndex(i => mediaProgress.lastUpdate > i.progressLastUpdate) | ||||
|               const indexToPut = categoryMap.recentlyListened.items.findIndex(i => mediaProgress.lastUpdate > i.progressLastUpdate) | ||||
|               if (indexToPut >= 0) { | ||||
|                 categoryMap.recentlyListened.items.splice(indexToPut, 0, libraryItemObj) | ||||
|               } else { // Should only happen when array is < max
 | ||||
| @ -652,9 +682,9 @@ module.exports = { | ||||
| 
 | ||||
|     // For Continue Series - Find next book in series for series that are in progress
 | ||||
|     for (const seriesId in seriesMap) { | ||||
|       if (seriesMap[seriesId].inProgress && !seriesMap[seriesId].hideFromContinueListening) { | ||||
|         seriesMap[seriesId].books = naturalSort(seriesMap[seriesId].books).asc(li => li.seriesSequence) | ||||
|       seriesMap[seriesId].books = naturalSort(seriesMap[seriesId].books).asc(li => li.seriesSequence) | ||||
| 
 | ||||
|       if (seriesMap[seriesId].inProgress && !seriesMap[seriesId].hideFromContinueListening) { | ||||
|         // take the first book unread with the smallest series sequence
 | ||||
|         // unless the user is already listening to a book from this series
 | ||||
|         const hasActiveBook = seriesMap[seriesId].hasActiveBook | ||||
| @ -683,6 +713,76 @@ module.exports = { | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     // For recommended
 | ||||
|     if (!isPodcastLibrary && notStartedBooks.length) { | ||||
|       const genresCount = Object.values(topGenresListened).reduce((a, b) => a + b, 0) | ||||
|       const authorsCount = Object.values(topAuthorsListened).reduce((a, b) => a + b, 0) | ||||
|       const tagsCount = Object.values(topTagsListened).reduce((a, b) => a + b, 0) | ||||
| 
 | ||||
|       for (const libraryItem of notStartedBooks) { | ||||
|         // dont include books in an unfinished series and books that are not first in an unstarted series
 | ||||
|         let shouldContinue = !libraryItem.media.metadata.series.length | ||||
|         libraryItem.media.metadata.series.forEach((se) => { | ||||
|           if (seriesMap[se.id]) { | ||||
|             if (seriesMap[se.id].inProgress) { | ||||
|               shouldContinue = false | ||||
|               return | ||||
|             } else if (seriesMap[se.id].books[0].id === libraryItem.id) { | ||||
|               shouldContinue = true | ||||
|             } | ||||
|           } | ||||
|         }) | ||||
|         if (!shouldContinue) { | ||||
|           continue; | ||||
|         } | ||||
| 
 | ||||
|         let totalWeight = 0 | ||||
| 
 | ||||
|         if (authorsCount > 0) { | ||||
|           libraryItem.media.metadata.authors.forEach((author) => { | ||||
|             if (topAuthorsListened[author.id]) { | ||||
|               totalWeight += topAuthorsListened[author.id] / authorsCount | ||||
|             } | ||||
|           }) | ||||
|         } | ||||
| 
 | ||||
|         if (genresCount > 0) { | ||||
|           libraryItem.media.metadata.genres.forEach((genre) => { | ||||
|             if (topGenresListened[genre]) { | ||||
|               totalWeight += topGenresListened[genre] / genresCount | ||||
|             } | ||||
|           }) | ||||
|         } | ||||
| 
 | ||||
|         if (tagsCount > 0) { | ||||
|           libraryItem.media.tags.forEach((tag) => { | ||||
|             if (topTagsListened[tag]) { | ||||
|               totalWeight += topTagsListened[tag] / tagsCount | ||||
|             } | ||||
|           }) | ||||
|         } | ||||
| 
 | ||||
|         if (!categoryMap.recommended.smallest || totalWeight > categoryMap.recommended.smallest) { | ||||
|           const libraryItemObj = { | ||||
|             ...libraryItem.toJSONMinified(), | ||||
|             weight: totalWeight | ||||
|           } | ||||
| 
 | ||||
|           const indexToPut = categoryMap.recommended.items.findIndex(i => totalWeight > i.weight) | ||||
|           if (indexToPut >= 0) { | ||||
|             categoryMap.recommended.items.splice(indexToPut, 0, libraryItemObj) | ||||
|           } else { | ||||
|             categoryMap.recommended.items.push(libraryItemObj) | ||||
|           } | ||||
| 
 | ||||
|           if (categoryMap.recommended.items.length > maxEntitiesPerShelf) { | ||||
|             categoryMap.recommended.items.pop() | ||||
|             categoryMap.recommended.smallest = categoryMap.recommended.items[categoryMap.recommended.items.length - 1].weight | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     // Sort series books by sequence
 | ||||
|     if (categoryMap.newestSeries.items.length) { | ||||
|       for (const seriesItem of categoryMap.newestSeries.items) { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user