From 1b95706f70d94a67b4ee8862f0ad83d23366e194 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Tue, 5 Nov 2024 23:32:03 -0500 Subject: [PATCH] [videos] Add youtube scrobbling --- vrobbler/apps/scrobbles/models.py | 2 +- vrobbler/apps/scrobbles/scrobblers.py | 44 +++++++++++++++++++ vrobbler/apps/scrobbles/urls.py | 5 +++ vrobbler/apps/scrobbles/views.py | 23 ++++++++++ ...ideo_youtube_url_alter_video_video_type.py | 33 ++++++++++++++ vrobbler/apps/videos/models.py | 2 + 6 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 vrobbler/apps/videos/migrations/0018_video_youtube_url_alter_video_video_type.py diff --git a/vrobbler/apps/scrobbles/models.py b/vrobbler/apps/scrobbles/models.py index 1a51a86..adbc76b 100644 --- a/vrobbler/apps/scrobbles/models.py +++ b/vrobbler/apps/scrobbles/models.py @@ -1186,7 +1186,7 @@ class Scrobble(TimeStampedModel): profile = UserProfile.objects.filter( user_id=scrobble_data["user_id"] ).first() - if profile.ntfy_enabled and profile.ntfy_url: + 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"): diff --git a/vrobbler/apps/scrobbles/scrobblers.py b/vrobbler/apps/scrobbles/scrobblers.py index b257fa9..26bdcfd 100644 --- a/vrobbler/apps/scrobbles/scrobblers.py +++ b/vrobbler/apps/scrobbles/scrobblers.py @@ -2,6 +2,7 @@ import logging import re from typing import Optional +from datetime import datetime import pendulum import pytz from beers.models import Beer @@ -133,6 +134,49 @@ def jellyfin_scrobble_media( ) +def web_scrobbler_scrobble_media( + post_data: dict, user_id: int +) -> Optional[Scrobble]: + media_type = Scrobble.MediaType.VIDEO + + event_name = post_data.get("eventName") + parsed = post_data.get("data").get("song").get("parsed") + processed = post_data.get("data").get("song").get("processed") + video, created = Video.objects.get_or_create( + video_type=Video.VideoType.YOUTUBE, + youtube_url=parsed.get("originUrl"), + ) + timestamp = datetime.utcfromtimestamp( + post_data.get("time", 0)/1000 + ).replace(tzinfo=pytz.utc) + + if created or event_name == "nowplaying": + video.run_time_seconds = 1800 + processed = post_data.get("data").get("song").get("processed") + # TODO maybe artist could be the series? + video.title = " - ".join([processed.get("artist"), processed.get("track")]) + video.save() + return video.scrobble_for_user( + user_id, + source="YouTube", + playback_position_seconds=0, + status=event_name, + ) + + scrobble = Scrobble.objects.filter(user_id=user_id, video=video, in_progress=True).first() + if not scrobble: + return video.scrobble_for_user( + user_id, + source="YouTube", + playback_position_seconds=0, + status=event_name, + ) + if event_name == "paused": + scrobble.pause() + if event_name == "resumedplaying": + scrobble.resume() + return scrobble + def manual_scrobble_video(imdb_id: str, user_id: int): if "tt" not in imdb_id: imdb_id = "tt" + imdb_id diff --git a/vrobbler/apps/scrobbles/urls.py b/vrobbler/apps/scrobbles/urls.py index 9a916ac..87956f5 100644 --- a/vrobbler/apps/scrobbles/urls.py +++ b/vrobbler/apps/scrobbles/urls.py @@ -31,6 +31,11 @@ urlpatterns = [ views.lastfm_import, name="lastfm-import", ), + path( + "webhook/web-scrobbler/", + views.web_scrobbler_webhook, + name="web-scrobbler-webhook", + ), path( "webhook/gps/", views.gps_webhook, diff --git a/vrobbler/apps/scrobbles/views.py b/vrobbler/apps/scrobbles/views.py index 1059c5e..9ced2a9 100644 --- a/vrobbler/apps/scrobbles/views.py +++ b/vrobbler/apps/scrobbles/views.py @@ -314,6 +314,29 @@ def lastfm_import(request): return HttpResponseRedirect(request.META.get("HTTP_REFERER")) +@csrf_exempt +@permission_classes([IsAuthenticated]) +@api_view(["POST"]) +def web_scrobbler_webhook(request): + post_data = request.data + logger.info( + "[web_scrobbler_webhook] called", + extra={ + "post_data": post_data, + "user_id": request.user.id, + }, + ) + + scrobble = web_scrobbler_scrobble_media(post_data, request.user.id) + + if not scrobble: + return Response({}, status=status.HTTP_400_BAD_REQUEST) + + logger.info( + "[jellyfin_webhook] finished", + extra={"scrobble_id": scrobble.id}, + ) + return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK) @csrf_exempt @permission_classes([IsAuthenticated]) diff --git a/vrobbler/apps/videos/migrations/0018_video_youtube_url_alter_video_video_type.py b/vrobbler/apps/videos/migrations/0018_video_youtube_url_alter_video_video_type.py new file mode 100644 index 0000000..149f6ea --- /dev/null +++ b/vrobbler/apps/videos/migrations/0018_video_youtube_url_alter_video_video_type.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.16 on 2024-11-06 03:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("videos", "0017_alter_video_video_type"), + ] + + operations = [ + migrations.AddField( + model_name="video", + name="youtube_url", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name="video", + name="video_type", + field=models.CharField( + choices=[ + ("U", "Unknown"), + ("E", "TV Episode"), + ("M", "Movie"), + ("S", "Skate Video"), + ("Y", "YouTube Video"), + ], + default="U", + max_length=1, + ), + ), + ] diff --git a/vrobbler/apps/videos/models.py b/vrobbler/apps/videos/models.py index d45500b..ef81b66 100644 --- a/vrobbler/apps/videos/models.py +++ b/vrobbler/apps/videos/models.py @@ -128,6 +128,7 @@ class Video(ScrobblableMixin): TV_EPISODE = "E", _("TV Episode") MOVIE = "M", _("Movie") SKATE_VIDEO = "S", _("Skate Video") + YOUTUBE = "Y", _("YouTube Video") video_type = models.CharField( max_length=1, @@ -161,6 +162,7 @@ class Video(ScrobblableMixin): tvrage_id = models.CharField(max_length=20, **BNULL) tvdb_id = models.CharField(max_length=20, **BNULL) tmdb_id = models.CharField(max_length=20, **BNULL) + youtube_url = models.CharField(max_length=255, **BNULL) plot = models.TextField(**BNULL) year = models.IntegerField(**BNULL)