소스 검색

[notifications] Add a class for notifications

Colin Powell 1 개월 전
부모
커밋
676c40176c

+ 2 - 2
vrobbler/apps/boardgames/sources/lichess.py

@@ -3,7 +3,7 @@ from boardgames.models import BoardGame
 from django.conf import settings
 from django.contrib.auth import get_user_model
 from scrobbles.models import Scrobble
-from scrobbles.utils import send_start_notifications_for_scrobble
+from scrobbles.notifications import NtfyNotification
 
 User = get_user_model()
 
@@ -124,5 +124,5 @@ def import_chess_games_for_all_users():
     if scrobbles_to_create:
         created = Scrobble.objects.bulk_create(scrobbles_to_create)
         for scrobble in created:
-            send_start_notifications_for_scrobble(scrobble.id)
+            NtfyNotification(scrobble).send()
     return scrobbles_to_create

+ 2 - 2
vrobbler/apps/books/koreader.py

@@ -9,8 +9,8 @@ import requests
 from books.constants import BOOKS_TITLES_TO_IGNORE
 from django.apps import apps
 from django.contrib.auth import get_user_model
-from scrobbles.utils import send_start_notifications_for_scrobble
 from stream_sqlite import stream_sqlite
+from scrobbles.notifications import NtfyNotification
 from webdav.client import get_webdav_client
 
 logger = logging.getLogger(__name__)
@@ -443,7 +443,7 @@ def process_koreader_sqlite_file(file_path, user_id) -> list:
     if new_scrobbles:
         created = Scrobble.objects.bulk_create(new_scrobbles)
         if created:
-            send_start_notifications_for_scrobble(created[-1].id)
+            NtfyNotification(created[-1]).send()
         fix_long_play_stats_for_scrobbles(created)
         logger.info(
             f"Created {len(created)} scrobbles",

+ 18 - 0
vrobbler/apps/scrobbles/management/commands/dedup_tracks.py

@@ -0,0 +1,18 @@
+from django.core.management.base import BaseCommand
+from vrobbler.apps.scrobbles.utils import deduplicate_tracks
+
+
+class Command(BaseCommand):
+    def add_arguments(self, parser):
+        parser.add_argument(
+            "--commit",
+            action="store_true",
+            help="Commit changes",
+        )
+
+    def handle(self, *args, **options):
+        commit = False
+        if options["commit"]:
+            commit = True
+        dups = deduplicate_tracks(commit=commit)
+        print(f"Deduplicated {dups} music tracks")

+ 10 - 0
vrobbler/apps/scrobbles/management/commands/send_notification_for_in_progress.py

@@ -0,0 +1,10 @@
+from django.core.management.base import BaseCommand
+from vrobbler.apps.scrobbles.utils import (
+    send_stop_notifications_for_in_progress_scrobbles,
+)
+
+
+class Command(BaseCommand):
+    def handle(self, *args, **options):
+        sent_count = send_stop_notifications_for_in_progress_scrobbles()
+        print(f"Sent {sent_count} in progress notifications")

+ 2 - 2
vrobbler/apps/scrobbles/models.py

@@ -42,7 +42,6 @@ from scrobbles.stats import build_charts
 from scrobbles.utils import (
     get_file_md5_hash,
     media_class_to_foreign_key,
-    send_start_notifications_for_scrobble,
 )
 from sports.models import SportEvent
 from tasks.models import Task
@@ -50,6 +49,7 @@ from trails.models import Trail
 from videogames import retroarch
 from videogames.models import VideoGame
 from videos.models import Series, Video
+from scrobbles.notifications import NtfyNotification
 from webpages.models import WebPage
 
 logger = logging.getLogger(__name__)
@@ -1213,7 +1213,7 @@ class Scrobble(TimeStampedModel):
         scrobble_data: dict,
     ) -> "Scrobble":
         scrobble = cls.objects.create(**scrobble_data)
-        send_start_notifications_for_scrobble(scrobble.id)
+        NtfyNotification(scrobble).send()
         return scrobble
 
     def stop(self, timestamp=None, force_finish=False) -> None:

+ 55 - 0
vrobbler/apps/scrobbles/notifications.py

@@ -0,0 +1,55 @@
+from abc import ABC, abstractmethod
+import requests
+
+from django.conf import settings
+from django.contrib.sites.models import Site
+
+class Notification(ABC):
+    scrobble: "Scrobble"
+    ntfy_headers: dict = {}
+    ntfy_url: str = ""
+
+    def __init__(self, scrobble: "Scrobble"):
+        self.scrobble = scrobble
+        self.user = scrobble.user
+        self.media_obj = scrobble.media_obj
+        protocol = "http" if settings.DEBUG else "https"
+        domain = Site.objects.get_current().domain
+        self.url_tmpl = f'{protocol}://{domain}' + '{path}'
+
+
+
+    @abstractmethod
+    def send(self) -> None:
+        pass
+
+
+class NtfyNotification(Notification):
+    def __init__(self, scrobble, **kwargs):
+        super().__init__(scrobble)
+        self.ntfy_str: str = f"{self.scrobble.media_obj}"
+        self.click_url = self.url_tmpl.format(path=self.media_obj.get_absolute_url())
+        if kwargs.get("end", False):
+            self.click_url = self.url_tmpl.format(path=self.scrobble.finish_url)
+            self.title = "Finish " + self.media_obj.strings.verb.lower() + "?"
+
+        if self.scrobble.log and isinstance(self.scrobble.log, dict) and self.scrobble.log.get("description"):
+            self.ntfy_str += f" - {self.scrobble.log.get('description')}"
+
+    def send(self):
+        if (
+            self.user
+            and self.user.profile
+            and self.user.profile.ntfy_enabled
+            and self.user.profile.ntfy_url
+        ):
+            requests.post(
+                self.user.profile.ntfy_url,
+                data=self.ntfy_str.encode(encoding="utf-8"),
+                headers={
+                    "Title": self.title,
+                    "Priority": self.media_obj.strings.priority,
+                    "Tags": self.media_obj.strings.tags,
+                    "Click": self.click_url,
+                },
+            )

+ 3 - 3
vrobbler/apps/scrobbles/urls.py

@@ -88,7 +88,7 @@ urlpatterns = [
         views.ScrobbleLongPlaysView.as_view(),
         name="long-plays",
     ),
-    path("<slug:uuid>/start/", views.scrobble_start, name="start"),
-    path("<slug:uuid>/finish/", views.scrobble_finish, name="finish"),
-    path("<slug:uuid>/cancel/", views.scrobble_cancel, name="cancel"),
+    path("scrobble/<slug:uuid>/start/", views.scrobble_start, name="start"),
+    path("scrobble/<slug:uuid>/finish/", views.scrobble_finish, name="finish"),
+    path("scrobble/<slug:uuid>/cancel/", views.scrobble_cancel, name="cancel"),
 ]

+ 20 - 47
vrobbler/apps/scrobbles/utils.py

@@ -17,6 +17,7 @@ from scrobbles.tasks import (
     process_lastfm_import,
     process_retroarch_import,
 )
+from vrobbler.apps.scrobbles.notifications import NtfyNotification
 from webdav.client import get_webdav_client
 
 logger = logging.getLogger(__name__)
@@ -66,8 +67,7 @@ def get_scrobbles_for_media(media_obj, user: User) -> models.QuerySet:
     return Scrobble.objects.filter(media_query, user=user)
 
 
-def get_recently_played_board_games(user: User) -> dict:
-    ...
+def get_recently_played_board_games(user: User) -> dict: ...
 
 
 def get_long_plays_in_progress(user: User) -> dict:
@@ -272,7 +272,7 @@ def get_file_md5_hash(file_path: str) -> str:
     return file_hash.hexdigest()
 
 
-def deduplicate_tracks(commit=False):
+def deduplicate_tracks(commit=False) -> int:
     from music.models import Track
 
     # TODO This whole thing should iterate over users
@@ -291,54 +291,27 @@ def deduplicate_tracks(commit=False):
                 other.scrobble_set.update(track=first)
                 print("deleting ", other.id, " - ", other)
                 other.delete()
+    return len(dups)
 
 
-def send_start_notifications_for_scrobble(scrobble_id):
+def send_stop_notifications_for_in_progress_scrobbles() -> int:
+    """Get all inprogress scrobbles and check if they're passed their media obj length.
+
+    If so, send out a notification to offer to stop the scrobble."""
     from scrobbles.models import Scrobble
 
-    scrobble = Scrobble.objects.get(id=scrobble_id)
-    profile = scrobble.user.profile
-    if profile and profile.ntfy_enabled and profile.ntfy_url:
-        # TODO allow prority and tags to be configured in the profile
-        notify_str = f"{scrobble.media_obj}"
-        if scrobble.log and scrobble.log.get("description"):
-            notify_str += f" - {scrobble.log.get('description')}"
-        requests.post(
-            profile.ntfy_url,
-            data=notify_str.encode(encoding="utf-8"),
-            headers={
-                "Title": scrobble.media_obj.strings.verb,
-                "Priority": scrobble.media_obj.strings.priority,
-                "Tags": scrobble.media_obj.strings.tags,
-                "Click": scrobble.media_obj.get_absolute_url(),
-            },
-        )
+    scrobbles_in_progress_qs = Scrobble.objects.filter(
+        played_to_completion=False, in_progress=True
+    )
 
+    notifications_sent = 0
+    for scrobble in scrobbles_in_progress_qs:
+        elapsed_scrobble_seconds = (
+            timezone.now() - scrobble.timestamp
+        ).seconds
 
-def send_stop_notifications_for_scrobble(scrobble_id):
-    from scrobbles.models import Scrobble
+        if elapsed_scrobble_seconds > scrobble.media_obj.run_time_seconds:
+            NtfyNotification(scrobble, end=True).send()
+            notifications_sent += 1
 
-    scrobble = Scrobble.objects.get(id=scrobble_id)
-    scrobble_surpassed_media_runtime = (
-        timezone.now() - scrobble.timestamp
-    ).seconds > scrobble.media_obj.run_time_seconds
-    if not scrobble_surpassed_media_runtime:
-        logger.info("scrobble not surpassed media runtime")
-        return
-
-    profile = scrobble.user.profile
-    if profile and profile.ntfy_enabled and profile.ntfy_url:
-        # TODO allow prority and tags to be configured in the profile
-        notify_str = f"{scrobble.media_obj}"
-        if scrobble.log and scrobble.log.get("description"):
-            notify_str += f" - {scrobble.log.get('description')}"
-        requests.post(
-            profile.ntfy_url,
-            data=notify_str.encode(encoding="utf-8"),
-            headers={
-                "Title": "Stop " scrobble.media_obj.strings.verb.lower() + "?",
-                "Priority": scrobble.media_obj.strings.priority,
-                "Tags": scrobble.media_obj.strings.tags,
-                "Click": scrobble.finish_url,
-            },
-        )
+    return notifications_sent

+ 2 - 0
vrobbler/apps/scrobbles/views.py

@@ -546,6 +546,8 @@ def scrobble_longplay_finish(request, uuid):
 def scrobble_finish(request, uuid):
     user = request.user
     success_url = request.META.get("HTTP_REFERER")
+    if not success_url:
+        success_url = reverse_lazy("vrobbler-home")
 
     if not user.is_authenticated:
         return HttpResponseRedirect(success_url)

+ 2 - 0
vrobbler/urls.py

@@ -44,6 +44,7 @@ from vrobbler.apps.videogames import urls as videogame_urls
 from vrobbler.apps.videos import urls as video_urls
 from vrobbler.apps.videos.api.views import SeriesViewSet, VideoViewSet
 from vrobbler.apps.webpages import urls as webpages_urls
+from vrobbler.apps.modern_ui import urls as modern_ui_urls
 
 router = routers.DefaultRouter()
 router.register(r"scrobbles", ScrobbleViewSet)
@@ -72,6 +73,7 @@ urlpatterns = [
     path("admin/", admin.site.urls),
     path("accounts/", include("allauth.urls")),
     path("o/", include(oauth2_urls)),
+    path("modern_ui/", include(modern_ui_urls, namespace="modern_ui")),
     path("", include(music_urls, namespace="music")),
     path("", include(book_urls, namespace="books")),
     path("", include(video_urls, namespace="videos")),