diff --git a/client/components/stats/YearInReview.vue b/client/components/stats/YearInReview.vue
new file mode 100644
index 00000000..fa57020a
--- /dev/null
+++ b/client/components/stats/YearInReview.vue
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/pages/config/stats.vue b/client/pages/config/stats.vue
index 9b8f7ea5..b527ea38 100644
--- a/client/pages/config/stats.vue
+++ b/client/pages/config/stats.vue
@@ -62,6 +62,13 @@
+
+ Year in Review
+
@@ -71,7 +78,9 @@ export default {
data() {
return {
listeningStats: null,
- windowWidth: 0
+ windowWidth: 0,
+ showYearInReview: false,
+ processingYearInReview: false
}
},
watch: {
@@ -114,6 +123,13 @@ export default {
}
},
methods: {
+ clickShowYearInReview() {
+ if (this.showYearInReview) {
+ this.$refs.yearInReview.refresh()
+ } else {
+ this.showYearInReview = true
+ }
+ },
async init() {
this.listeningStats = await this.$axios.$get(`/api/me/listening-stats`).catch((err) => {
console.error('Failed to load listening sesions', err)
diff --git a/server/utils/queries/userStats.js b/server/utils/queries/userStats.js
index b6895008..f9b9684e 100644
--- a/server/utils/queries/userStats.js
+++ b/server/utils/queries/userStats.js
@@ -2,7 +2,7 @@ const Sequelize = require('sequelize')
const Database = require('../../Database')
const PlaybackSession = require('../../models/PlaybackSession')
const MediaProgress = require('../../models/MediaProgress')
-const { elapsedPretty } = require('../index')
+const fsExtra = require('../../libs/fsExtra')
module.exports = {
/**
@@ -18,8 +18,21 @@ module.exports = {
createdAt: {
[Sequelize.Op.gte]: `${year}-01-01`,
[Sequelize.Op.lt]: `${year + 1}-01-01`
+ },
+ timeListening: {
+ [Sequelize.Op.gt]: 5
}
- }
+ },
+ include: {
+ model: Database.bookModel,
+ attributes: ['id', 'coverPath'],
+ include: {
+ model: Database.libraryItemModel,
+ attributes: ['id', 'mediaId', 'mediaType']
+ },
+ required: false
+ },
+ order: Database.sequelize.random()
})
return sessions
},
@@ -42,6 +55,10 @@ module.exports = {
},
include: {
model: Database.bookModel,
+ include: {
+ model: Database.libraryItemModel,
+ attributes: ['id', 'mediaId', 'mediaType']
+ },
required: true
}
})
@@ -63,8 +80,15 @@ module.exports = {
let genreListeningMap = {}
let narratorListeningMap = {}
let monthListeningMap = {}
+ let bookListeningMap = {}
+ const booksWithCovers = []
+
+ for (const ls of listeningSessions) {
+ // Grab first 16 that have a cover
+ if (ls.mediaItem?.coverPath && !booksWithCovers.includes(ls.mediaItem.libraryItem.id) && booksWithCovers.length < 16 && await fsExtra.pathExists(ls.mediaItem.coverPath)) {
+ booksWithCovers.push(ls.mediaItem.libraryItem.id)
+ }
- listeningSessions.forEach((ls) => {
const listeningSessionListeningTime = ls.timeListening || 0
const lsMonth = ls.createdAt.getMonth()
@@ -75,6 +99,12 @@ module.exports = {
if (ls.mediaItemType === 'book') {
totalBookListeningTime += listeningSessionListeningTime
+ if (ls.displayTitle && !bookListeningMap[ls.displayTitle]) {
+ bookListeningMap[ls.displayTitle] = listeningSessionListeningTime
+ } else if (ls.displayTitle) {
+ bookListeningMap[ls.displayTitle] += listeningSessionListeningTime
+ }
+
const authors = ls.mediaMetadata.authors || []
authors.forEach((au) => {
if (!authorListeningMap[au.name]) authorListeningMap[au.name] = 0
@@ -96,64 +126,54 @@ module.exports = {
} else {
totalPodcastListeningTime += listeningSessionListeningTime
}
- })
+ }
totalListeningTime = Math.round(totalListeningTime)
totalBookListeningTime = Math.round(totalBookListeningTime)
totalPodcastListeningTime = Math.round(totalPodcastListeningTime)
- let mostListenedAuthor = null
- for (const authorName in authorListeningMap) {
- if (!mostListenedAuthor?.time || authorListeningMap[authorName] > mostListenedAuthor.time) {
- mostListenedAuthor = {
- time: Math.round(authorListeningMap[authorName]),
- pretty: elapsedPretty(Math.round(authorListeningMap[authorName])),
- name: authorName
- }
- }
- }
+ let topAuthors = null
+ topAuthors = Object.keys(authorListeningMap).map(authorName => ({
+ name: authorName,
+ time: Math.round(authorListeningMap[authorName])
+ })).sort((a, b) => b.time - a.time).slice(0, 3)
+
let mostListenedNarrator = null
for (const narrator in narratorListeningMap) {
if (!mostListenedNarrator?.time || narratorListeningMap[narrator] > mostListenedNarrator.time) {
mostListenedNarrator = {
time: Math.round(narratorListeningMap[narrator]),
- pretty: elapsedPretty(Math.round(narratorListeningMap[narrator])),
name: narrator
}
}
}
- let mostListenedGenre = null
- for (const genre in genreListeningMap) {
- if (!mostListenedGenre?.time || genreListeningMap[genre] > mostListenedGenre.time) {
- mostListenedGenre = {
- time: Math.round(genreListeningMap[genre]),
- pretty: elapsedPretty(Math.round(genreListeningMap[genre])),
- name: genre
- }
- }
- }
+
+ let topGenres = null
+ topGenres = Object.keys(genreListeningMap).map(genre => ({
+ genre,
+ time: Math.round(genreListeningMap[genre])
+ })).sort((a, b) => b.time - a.time).slice(0, 3)
+
let mostListenedMonth = null
for (const month in monthListeningMap) {
if (!mostListenedMonth?.time || monthListeningMap[month] > mostListenedMonth.time) {
mostListenedMonth = {
month: Number(month),
- time: Math.round(monthListeningMap[month]),
- pretty: elapsedPretty(Math.round(monthListeningMap[month]))
+ time: Math.round(monthListeningMap[month])
}
}
}
- const bookProgresses = await this.getBookMediaProgressFinishedForYear(userId, year)
+ const bookProgressesFinished = await this.getBookMediaProgressFinishedForYear(userId, year)
- const numBooksFinished = bookProgresses.length
+ const numBooksFinished = bookProgressesFinished.length
let longestAudiobookFinished = null
- bookProgresses.forEach((mediaProgress) => {
+ bookProgressesFinished.forEach((mediaProgress) => {
if (mediaProgress.duration && (!longestAudiobookFinished?.duration || mediaProgress.duration > longestAudiobookFinished.duration)) {
longestAudiobookFinished = {
id: mediaProgress.mediaItem.id,
title: mediaProgress.mediaItem.title,
duration: Math.round(mediaProgress.duration),
- durationPretty: elapsedPretty(Math.round(mediaProgress.duration)),
finishedAt: mediaProgress.finishedAt
}
}
@@ -162,17 +182,16 @@ module.exports = {
return {
totalListeningSessions: listeningSessions.length,
totalListeningTime,
- totalListeningTimePretty: elapsedPretty(totalListeningTime),
totalBookListeningTime,
- totalBookListeningTimePretty: elapsedPretty(totalBookListeningTime),
totalPodcastListeningTime,
- totalPodcastListeningTimePretty: elapsedPretty(totalPodcastListeningTime),
- mostListenedAuthor,
+ topAuthors,
+ topGenres,
mostListenedNarrator,
- mostListenedGenre,
mostListenedMonth,
numBooksFinished,
- longestAudiobookFinished
+ numBooksListened: Object.keys(bookListeningMap).length,
+ longestAudiobookFinished,
+ booksWithCovers
}
}
}