diff --git a/vrobbler/apps/scrobbles/admin.py b/vrobbler/apps/scrobbles/admin.py index 2960a3c..599c046 100644 --- a/vrobbler/apps/scrobbles/admin.py +++ b/vrobbler/apps/scrobbles/admin.py @@ -92,6 +92,7 @@ class ScrobbleAdmin(admin.ModelAdmin): list_filter = ( "is_paused", "in_progress", + "long_play_complete", "source", ) ordering = ("-timestamp",) diff --git a/vrobbler/apps/scrobbles/constants.py b/vrobbler/apps/scrobbles/constants.py index 182c04a..1b12ea7 100644 --- a/vrobbler/apps/scrobbles/constants.py +++ b/vrobbler/apps/scrobbles/constants.py @@ -1,4 +1,7 @@ JELLYFIN_VIDEO_ITEM_TYPES = ["Episode", "Movie"] JELLYFIN_AUDIO_ITEM_TYPES = ["Audio"] -LONG_PLAY_MEDIA = ["VideoGame", "Book"] +LONG_PLAY_MEDIA = { + "videogames": "VideoGame", + "books": "Book", +} diff --git a/vrobbler/apps/scrobbles/models.py b/vrobbler/apps/scrobbles/models.py index 14fb055..ee494f7 100644 --- a/vrobbler/apps/scrobbles/models.py +++ b/vrobbler/apps/scrobbles/models.py @@ -1,6 +1,7 @@ import calendar import datetime import logging +from typing import Optional from uuid import uuid4 from books.models import Book @@ -447,6 +448,25 @@ class Scrobble(TimeStampedModel): is_stale = True return is_stale + @property + def previous(self): + return ( + self.media_obj.scrobble_set.filter(timestamp__lt=self.timestamp) + .order_by("-timestamp") + .last() + ) + + @property + def long_play_session_seconds(self) -> Optional[int]: + """Look one scrobble back, if it isn't complete,""" + if self.long_play_complete is not None: + if self.previous: + return int(self.playback_position) - int( + self.previous.playback_position + ) + else: + return self.playback_position + @property def percent_played(self) -> int: if not self.media_obj: @@ -470,7 +490,7 @@ class Scrobble(TimeStampedModel): @property def can_be_updated(self) -> bool: updatable = True - if self.media_obj.__class__.__name__ in LONG_PLAY_MEDIA: + if self.media_obj.__class__.__name__ in LONG_PLAY_MEDIA.values(): logger.info(f"No - Long play media") updatable = False if self.percent_played > 100: diff --git a/vrobbler/apps/scrobbles/urls.py b/vrobbler/apps/scrobbles/urls.py index 2b0eeb9..ae98791 100644 --- a/vrobbler/apps/scrobbles/urls.py +++ b/vrobbler/apps/scrobbles/urls.py @@ -67,4 +67,9 @@ urlpatterns = [ views.ChartRecordView.as_view(), name="charts-home", ), + path( + "long-plays/", + views.ScrobbleLongPlaysView.as_view(), + name="long-plays", + ), ] diff --git a/vrobbler/apps/scrobbles/utils.py b/vrobbler/apps/scrobbles/utils.py index b08af70..3142c8e 100644 --- a/vrobbler/apps/scrobbles/utils.py +++ b/vrobbler/apps/scrobbles/utils.py @@ -7,6 +7,8 @@ from django.conf import settings from django.contrib.auth import get_user_model from django.db import models +from vrobbler.apps.scrobbles.constants import LONG_PLAY_MEDIA + logger = logging.getLogger(__name__) User = get_user_model() @@ -123,3 +125,37 @@ def get_scrobbles_for_media(media_obj, user: User) -> models.QuerySet: logger.warn("Do not know about media {media_class} 🙍") return [] return Scrobble.objects.filter(media_query, user=user) + + +def get_long_plays_in_progress(user: User) -> list: + """Find all books where the last scrobble is not marked complete""" + media_list = [] + for app, model in LONG_PLAY_MEDIA.items(): + media_obj = apps.get_model(app_label=app, model_name=model) + for media in media_obj.objects.all(): + if ( + media.scrobble_set.all() + and media.scrobble_set.filter(user=user) + .last() + .long_play_complete + == False + ): + media_list.append(media) + return media_list + + +def get_long_plays_completed(user: User) -> list: + """Find all books where the last scrobble is not marked complete""" + media_list = [] + for app, model in LONG_PLAY_MEDIA.items(): + media_obj = apps.get_model(app_label=app, model_name=model) + for media in media_obj.objects.all(): + if ( + media.scrobble_set.all() + and media.scrobble_set.filter(user=user) + .last() + .long_play_complete + == True + ): + media_list.append(media) + return media_list diff --git a/vrobbler/apps/scrobbles/views.py b/vrobbler/apps/scrobbles/views.py index 84790b2..19dca3f 100644 --- a/vrobbler/apps/scrobbles/views.py +++ b/vrobbler/apps/scrobbles/views.py @@ -57,9 +57,14 @@ from scrobbles.tasks import ( process_tsv_import, ) from sports.thesportsdb import lookup_event_from_thesportsdb -from videos.imdb import lookup_video_from_imdb from videogames.howlongtobeat import lookup_game_from_hltb +from videos.imdb import lookup_video_from_imdb + from vrobbler.apps.books.openlibrary import lookup_book_from_openlibrary +from vrobbler.apps.scrobbles.utils import ( + get_long_plays_completed, + get_long_plays_in_progress, +) logger = logging.getLogger(__name__) @@ -74,6 +79,7 @@ class RecentScrobbleList(ListView): completed_for_user = Scrobble.objects.filter( played_to_completion=True, user=user ) + data["long_play_in_progress"] = get_long_plays_in_progress(user) data["video_scrobble_list"] = completed_for_user.filter( video__isnull=False ).order_by("-timestamp")[:15] @@ -124,6 +130,18 @@ class RecentScrobbleList(ListView): ).order_by("-timestamp")[:15] +class ScrobbleLongPlaysView(TemplateView): + template_name = "scrobbles/long_plays_in_progress.html" + + def get_context_data(self, **kwargs): + context_data = super().get_context_data(**kwargs) + context_data["in_progress"] = get_long_plays_in_progress( + self.request.user + ) + context_data["completed"] = get_long_plays_completed(self.request.user) + return context_data + + class ScrobbleImportListView(TemplateView): template_name = "scrobbles/import_list.html" @@ -181,28 +199,28 @@ class ManualScrobbleView(FormView): def form_valid(self, form): - item_id = form.cleaned_data.get("item_id") + key, item_id = form.cleaned_data.get("item_id").split(" ") data_dict = None - if "-v" in item_id or not data_dict: + if key == "-v": logger.debug(f"Looking for video game with ID {item_id}") data_dict = lookup_game_from_hltb(item_id.replace("-v", "")) if data_dict: manual_scrobble_video_game(data_dict, self.request.user.id) - if "-b" in item_id and not data_dict: + if key == "-b": logger.debug(f"Looking for book with ID {item_id}") data_dict = lookup_book_from_openlibrary(item_id.replace("-b", "")) if data_dict: manual_scrobble_book(data_dict, self.request.user.id) - if "-s" in item_id and not data_dict: + if key == "-s": logger.debug(f"Looking for sport event with ID {item_id}") data_dict = lookup_event_from_thesportsdb(item_id) if data_dict: manual_scrobble_event(data_dict, self.request.user.id) - if "tt" in item_id: + if key == "-i": data_dict = lookup_video_from_imdb(item_id) if data_dict: manual_scrobble_video(data_dict, self.request.user.id) diff --git a/vrobbler/apps/videogames/templatetags/naturalduration.py b/vrobbler/apps/videogames/templatetags/naturalduration.py index 76e2ab3..806faa5 100644 --- a/vrobbler/apps/videogames/templatetags/naturalduration.py +++ b/vrobbler/apps/videogames/templatetags/naturalduration.py @@ -5,6 +5,8 @@ register = template.Library() @register.filter def natural_duration(value): + if not value: + return value = int(value) total_minutes = int(value / 60) hours = int(total_minutes / 60) diff --git a/vrobbler/templates/base.html b/vrobbler/templates/base.html index 1fa8620..a6e2728 100644 --- a/vrobbler/templates/base.html +++ b/vrobbler/templates/base.html @@ -32,6 +32,8 @@ min-height: 3em; border-right: 1px solid #777; } + .now-playing p { margin:0; } + .now-playing .right { float:right; margin-right:10px; } .latest-scrobble { width: 50%; } @@ -220,33 +222,9 @@
+ + {% if view == 'grid' %} + View as List + {% else %} + View as Grid + {% endif %} + +
+| Scrobbles | +Title | +
|---|---|
| {{media.scrobble_set.count}} | +{{media}} | +
| Scrobbles | +Title | +
|---|---|
| {{media.scrobble_set.count}} | +{{media}} | +
{{object.scrobble_set.count}} scrobbles
- +{{object.scrobble_set.last.playback_position|natural_duration}}{% if object.scrobble_set.last.long_play_complete %} and completed{% else %} spent playing{% endif %}
+