diff --git a/app/api/scrobble/__init__.py b/app/api/scrobble/__init__.py index 06b071f6..8928a873 100644 --- a/app/api/scrobble/__init__.py +++ b/app/api/scrobble/__init__.py @@ -12,7 +12,7 @@ from datetime import datetime, timedelta from collections import defaultdict import locale -from app.db.userdata import ScrobbleTable +from app.db.userdata import FavoritesTable, ScrobbleTable from app.lib.extras import get_extra_info from app.models.album import Album from app.models.stats import StatItem @@ -295,27 +295,39 @@ def get_stats(): """ Get the stats for the user. """ - start_time, end_time = get_date_range("week") + period = "week" + start_time, end_time = get_date_range(period) + said_period = period + match period: + case "week": + said_period = "this week" + case "month": + said_period = "this month" + case "year": + said_period = "this year" + case "alltime": + said_period = "all time" + + count = len(TrackStore.get_flat_list()) total_tracks = StatItem( "trackcount", - "Total tracks", - len(TrackStore.get_flat_list()), - ) - tracks, playcount, playduration = ( - get_tracks_in_period(start_time, end_time) + "in your library", + f"{count} track{'' if count == 1 else 's'}", ) + tracks, playcount, playduration = get_tracks_in_period(start_time, end_time) + playcount = StatItem( "streams", - "Track plays last week", - playcount, + said_period, + f"{playcount} track{'' if playcount == 1 else 's'} played", ) playduration = StatItem( "playtime", - "Playtime last week", - seconds_to_time_string(playduration), + said_period, + f"{seconds_to_time_string(playduration)} listened", ) tracks = sorted(tracks, key=lambda t: t.playduration, reverse=True) @@ -323,8 +335,20 @@ def get_stats(): # Find the top track from the last 7 days top_track = StatItem( "toptrack", - "Top track last week", - tracks[0].title if len(tracks) > 0 else "-", + f"Top track {said_period}", + ( + tracks[0].title + " - " + tracks[0].artists[0]["name"] + if len(tracks) > 0 + else "—" + ), + tracks[0].image if len(tracks) > 0 else None, + ) + + fav_count = FavoritesTable.count_favs_in_period(start_time, end_time) + favorites = StatItem( + "favorites", + said_period, + f"{fav_count} {'new' if period != 'alltime' else ''} favorite{'' if fav_count == 1 else 's'}", ) return { @@ -332,6 +356,8 @@ def get_stats(): top_track, playcount, playduration, + favorites, total_tracks, - ] - } + ], + "dates": format_date(start_time, end_time), + }, 200 diff --git a/app/db/userdata.py b/app/db/userdata.py index 209a6e40..d0e7de98 100644 --- a/app/db/userdata.py +++ b/app/db/userdata.py @@ -8,6 +8,7 @@ from sqlalchemy import ( String, and_, delete, + func, insert, select, update, @@ -248,6 +249,21 @@ class FavoritesTable(Base): result, total = cls.get_all_of_type("artist", start, limit) return favorites_to_dataclass(result), total + @classmethod + def count_favs_in_period(cls, start_time: int, end_time: int): + result = cls.execute( + select(func.count(cls.id)).where( + and_(cls.timestamp >= start_time, cls.timestamp <= end_time) + ) + ) + + result = result.fetchone() + + if result: + return result[0] + + return 0 + class ScrobbleTable(Base): __tablename__ = "scrobble" diff --git a/app/models/stats.py b/app/models/stats.py index 3c1bc262..57ee55df 100644 --- a/app/models/stats.py +++ b/app/models/stats.py @@ -6,3 +6,4 @@ class StatItem: cssclass: str text: str value: str | int + image: str | None = None diff --git a/app/utils/dates.py b/app/utils/dates.py index eaaa344e..2310391e 100644 --- a/app/utils/dates.py +++ b/app/utils/dates.py @@ -70,29 +70,33 @@ def get_date_range(duration: str): """ Returns a tuple of dates representing the start and end of a given duration. """ + date_range = None + match duration: case "week": - return ( + date_range = ( pendulum.now().subtract().start_of("week").timestamp(), pendulum.now().end_of("week").timestamp(), ) case "month": - return ( + date_range = ( pendulum.now().subtract().start_of("month").timestamp(), pendulum.now().end_of("month").timestamp(), ) case "year": - return ( + date_range = ( pendulum.now().subtract().start_of("year").timestamp(), pendulum.now().end_of("year").timestamp(), ) case "alltime": - return (float(0), pendulum.now().timestamp()) + date_range = (float(0), pendulum.now().timestamp()) case _: raise ValueError(f"Invalid duration: {duration}") + return (int(date_range[0]), int(date_range[1])) -def get_duration_in_seconds(duration: str) -> float: + +def get_duration_in_seconds(duration: str) -> int: """ Returns the number of seconds in a given duration. """ @@ -104,6 +108,6 @@ def get_duration_in_seconds(duration: str) -> float: case "year": return 31556926 case "alltime": - return pendulum.now().timestamp() + return int(pendulum.now().timestamp()) raise ValueError(f"Invalid duration: {duration}")