Browse Source

[scrobbling] Refactor webhook and simplify

Colin Powell 9 months ago
parent
commit
5e22cb3106

+ 1 - 1
tests/scrobbles_tests/test_views.py

@@ -45,7 +45,7 @@ def test_scrobble_mopidy_track(
     assert scrobble.media_obj.title == "Same in the End"
 
 
-@pytest.mark.skip(reason="API is unstable")
+@pytest.mark.skip(reason="Allmusic API is unstable")
 @pytest.mark.django_db
 def test_scrobble_mopidy_same_track_different_album(
     client,

+ 0 - 6
vrobbler/apps/lifeevents/models.py

@@ -29,12 +29,6 @@ class LifeEvent(ScrobblableMixin):
     def find_or_create(cls, title: str) -> "LifeEvent":
         return cls.objects.filter(title=title).first()
 
-    def scrobble_for_user(self, user_id):
-        Scrobble = apps.get_model("scrobbles", "Scrobble")
-        return Scrobble.objects.create(
-            user_id=user_id, life_event=self, timestamp=pendulum.now()
-        )
-
     def scrobbles(self, user_id):
         Scrobble = apps.get_model("scrobbles", "Scrobble")
         return Scrobble.objects.filter(

+ 18 - 1
vrobbler/apps/music/constants.py

@@ -7,7 +7,7 @@ VARIOUS_ARTIST_DICT = {
 JELLYFIN_POST_KEYS = {
     "ITEM_TYPE": "ItemType",
     "RUN_TIME": "RunTime",
-    "TITLE": "Name",
+    "TRACK_TITLE": "Name",
     "TIMESTAMP": "UtcTimestamp",
     "YEAR": "Year",
     "PLAYBACK_POSITION_TICKS": "PlaybackPositionTicks",
@@ -18,4 +18,21 @@ JELLYFIN_POST_KEYS = {
     "TRACK_MB_ID": "Provider_musicbrainztrack",
     "ALBUM_NAME": "Album",
     "ARTIST_NAME": "Artist",
+    "STATUS": "Status",
+}
+
+MOPIDY_POST_KEYS = {
+    "ITEM_TYPE": None,
+    "RUN_TIME": "run_time",
+    "TRACK_TITLE": "name",
+    "TIMESTAMP": None,
+    "YEAR": None,
+    "PLAYBACK_POSITION_TICKS": "playback_time_ticks",
+    "ARTIST_MB_ID": "muscibrainz_artist_id",
+    "ALBUM_MB_ID": "musicbrainz_album_id",
+    "RELEASEGROUP_MB_ID": None,
+    "TRACK_MB_ID": "musicbrainz_track_id",
+    "ALBUM_NAME": "album",
+    "ARTIST_NAME": "artist",
+    "STATUS": "status",
 }

+ 25 - 10
vrobbler/apps/music/musicbrainz.py

@@ -104,22 +104,37 @@ def lookup_artist_from_mb(artist_name: str) -> dict:
 
 
 def lookup_track_from_mb(
-    track_name: str, artist_mbid: str, album_mbid: str
-) -> str:
+    track_name: str, artist_mb_id: str, album_mb_id: str
+) -> dict:
+    logger.info(
+        "[lookup_track_from_mb] called",
+        extra={
+            "track_name": track_name,
+            "artist_mb_id": artist_mb_id,
+            "album_mb_id": album_mb_id,
+        },
+    )
     musicbrainzngs.set_useragent("vrobbler", "0.3.0")
 
     try:
-        top_result = musicbrainzngs.search_recordings(
-            query=track_name, artist=artist_mbid, release=album_mbid
-        )["recording-list"][0]
+        results = musicbrainzngs.search_recordings(
+            query=track_name, artist=artist_mb_id, release=album_mb_id
+        )["recording-list"]
+        logger.info(
+            "[lookup_track_from_mb] musicbrainz recordings search results",
+            extra={"results": results},
+        )
+        top_result = results[0]
     except IndexError:
-        return ""
+        logger.error("[lookup_track_from_mb] no results found")
+        return {}
+
     score = int(top_result.get("ext:score"))
     if score < 85:
-        logger.debug(
-            "Track lookup score below 85 threshold",
-            extra={"result": top_result},
+        logger.info(
+            "[lookup_track_from_mb] no results above 85% certainty ",
+            extra={"results": results},
         )
-        return ""
+        return {}
 
     return top_result

+ 30 - 16
vrobbler/apps/music/utils.py

@@ -8,6 +8,7 @@ from music.musicbrainz import (
     lookup_track_from_mb,
 )
 from music.constants import VARIOUS_ARTIST_DICT
+from scrobbles.utils import convert_to_seconds
 
 logger = logging.getLogger(__name__)
 
@@ -89,36 +90,49 @@ def get_or_create_album(
     return album
 
 
-def get_or_create_track(
-    title: str,
-    artist: Artist,
-    album: Album = None,
-    mbid: str = None,
-    run_time_seconds=None,
-) -> Track:
+def get_or_create_track(post_data: dict, post_keys: dict):
+    artist_name = post_data.get(post_keys.get("ARTIST_NAME"), "")
+    artist_mb_id = post_data.get(post_keys.get("ARTIST_MB_ID"), "")
+    album_title = post_data.get(post_keys.get("ALBUM_NAME"), "")
+    album_mb_id = post_data.get(post_keys.get("ALBUM_MB_ID"), "")
+    track_run_time_seconds = convert_to_seconds(
+        post_data.get(post_keys.get("RUN_TIME"), 0)
+    )
+    track_title = post_data.get(post_keys.get("TRACK_TITLE"), "")
+    track_mb_id = post_data.get(post_keys.get("TRACK_MB_ID"), "")
+
+    artist = get_or_create_artist(
+        artist_name,
+        mbid=artist_mb_id,
+    )
+    album = get_or_create_album(
+        album_title,
+        artist=artist,
+        mbid=album_mb_id,
+    )
+
     track = None
-    if not mbid and album:
+    if not track_mb_id and album:
         try:
-            mbid = lookup_track_from_mb(
-                title,
+            track_mb_id = lookup_track_from_mb(
+                track_title,
                 artist.musicbrainz_id,
                 album.musicbrainz_id,
             ).get("id", 0)
         except TypeError:
             pass
 
-    if mbid:
-        track = Track.objects.filter(musicbrainz_id=mbid).first()
+    if track_mb_id:
+        track = Track.objects.filter(musicbrainz_id=track_mb_id).first()
 
     if not track:
         track = Track.objects.create(
-            title=title,
+            title=track_title,
             artist=artist,
             album=album,
-            musicbrainz_id=mbid,
-            run_time_seconds=run_time_seconds,
+            musicbrainz_id=track_mb_id,
+            run_time_seconds=track_run_time_seconds,
         )
-
     return track
 
 

+ 79 - 0
vrobbler/apps/podcasts/utils.py

@@ -0,0 +1,79 @@
+import logging
+import os
+from urllib.parse import unquote
+
+from dateutil.parser import ParserError, parse
+from podcasts.models import PodcastEpisode
+
+logger = logging.getLogger(__name__)
+
+# TODO This should be configurable in settings or per deploy
+PODCAST_DATE_FORMAT = "YYYY-MM-DD"
+
+
+def parse_mopidy_uri(uri: str) -> dict:
+    logger.debug(f"Parsing URI: {uri}")
+    parsed_uri = os.path.splitext(unquote(uri))[0].split("/")
+
+    episode_str = parsed_uri[-1]
+    podcast_name = parsed_uri[-2].strip()
+    episode_num = None
+    episode_num_pad = 0
+
+    try:
+        # Without episode numbers the date will lead
+        pub_date = parse(episode_str[0:10])
+    except ParserError:
+        episode_num = int(episode_str.split("-")[0])
+        episode_num_pad = len(str(episode_num)) + 1
+
+        try:
+            # Beacuse we have epsiode numbers on
+            pub_date = parse(
+                episode_str[
+                    episode_num_pad : len(PODCAST_DATE_FORMAT)
+                    + episode_num_pad
+                ]
+            )
+        except ParserError:
+            pub_date = ""
+
+    gap_to_strip = 0
+    if pub_date:
+        gap_to_strip += len(PODCAST_DATE_FORMAT)
+    if episode_num:
+        gap_to_strip += episode_num_pad
+
+    episode_name = episode_str[gap_to_strip:].replace("-", " ").strip()
+
+    return {
+        "episode_filename": episode_name,
+        "episode_num": episode_num,
+        "podcast_name": podcast_name,
+        "pub_date": pub_date,
+    }
+
+
+def get_or_create_podcast(post_data: dict):
+    mopidy_uri = post_data.get("mopidy_uri", "")
+    parsed_data = parse_mopidy_uri(mopidy_uri)
+
+    producer_dict = {"name": post_data.get("artist")}
+
+    podcast_name = post_data.get("album")
+    if not podcast_name:
+        podcast_name = parsed_data.get("podcast_name")
+    podcast_dict = {"name": podcast_name}
+
+    episode_name = parsed_data.get("episode_filename")
+    episode_dict = {
+        "title": episode_name,
+        "run_time_seconds": post_data.get("run_time"),
+        "number": parsed_data.get("episode_num"),
+        "pub_date": parsed_data.get("pub_date"),
+        "mopidy_uri": mopidy_uri,
+    }
+
+    return PodcastEpisode.find_or_create(
+        podcast_dict, producer_dict, episode_dict
+    )

+ 14 - 14
vrobbler/apps/scrobbles/mixins.py

@@ -47,21 +47,27 @@ class ScrobblableMixin(TimeStampedModel):
     class Meta:
         abstract = True
 
-    def basic_scrobble_data(self, user_id) -> dict:
-        return {
+    def scrobble_for_user(
+        self,
+        user_id,
+        source: str = "Vrobbler",
+        playback_position_seconds: int = 0,
+        status: str = "started",
+    ):
+        Scrobble = apps.get_model("scrobbles", "Scrobble")
+        scrobble_data = {
             "user_id": user_id,
             "timestamp": timezone.now(),
-            "playback_position_seconds": 0,
-            "source": "Vrobbler",
+            "source": source,
+            "playback_position_seconds": playback_position_seconds,
+            "status": status,
         }
 
-    def scrobble_for_user(self, user_id):
-        Scrobble = apps.get_model("scrobbles", "Scrobble")
-        scrobble_data = self.basic_scrobble_data(user_id)
         logger.info(
             "[scrobble_for_user] called",
             extra={
-                "webpage_id": self.id,
+                "id": self.id,
+                "media_type": self.__class__,
                 "user_id": user_id,
                 "scrobble_data": scrobble_data,
                 "media_type": Scrobble.MediaType.WEBPAGE,
@@ -90,12 +96,6 @@ class ScrobblableMixin(TimeStampedModel):
     def find_or_create(cls) -> None:
         logger.warn("find_or_create() not implemented yet")
 
-    def scrobble(self, user_id, **kwargs):
-        """Given a user ID and a dictionary of data, attempts to scrobble it"""
-        from scrobbles.models import Scrobble
-
-        Scrobble.create_or_update(self, user_id, **kwargs)
-
 
 class LongPlayScrobblableMixin(ScrobblableMixin):
     class Meta:

+ 8 - 21
vrobbler/apps/scrobbles/models.py

@@ -648,7 +648,10 @@ class Scrobble(TimeStampedModel):
 
     @property
     def is_stale(self) -> bool:
-        """Mark scrobble as stale if it's been more than an hour since it was updated"""
+        """Mark scrobble as stale if it's been more than an hour since it was updated
+
+        Effectively, this allows 'resuming' a video scrobble within an hour of starting it.
+        """
         is_stale = False
         now = timezone.now()
         seconds_since_last_update = (now - self.modified).seconds
@@ -911,7 +914,7 @@ class Scrobble(TimeStampedModel):
         # GeoLocations are a special case scrobble
         if mtype == cls.MediaType.GEO_LOCATION:
             logger.warn(
-                f"[scrobbling] use create_or_update_location for GeoLocations"
+                f"[create_or_update] geoloc requires create_or_update_location"
             )
             scrobble = cls.create_or_update_location(
                 media, scrobble_data, user_id
@@ -919,22 +922,16 @@ class Scrobble(TimeStampedModel):
             return scrobble
 
         logger.info(
-            f"[scrobbling] check for existing scrobble to update ",
+            f"[create_or_update] check for existing scrobble to update ",
             extra={
                 "scrobble_id": scrobble.id if scrobble else None,
                 "media_type": mtype,
                 "media_id": media.id,
                 "scrobble_data": scrobble_data,
-                "percent_played": scrobble.percent_played if scrobble else 0,
-                "can_be_updated": scrobble.can_be_updated
-                if scrobble
-                else False,
             },
         )
 
-        scrobble_data["playback_status"] = scrobble_data.pop(
-            "mopidy_status", scrobble_data.pop("jellyfin_status", None)
-        )
+        scrobble_data["playback_status"] = scrobble_data.pop("status", None)
         # If it's marked as stopped, send it through our update mechanism, which will complete it
         if scrobble and (
             scrobble.can_be_updated
@@ -1069,7 +1066,7 @@ class Scrobble(TimeStampedModel):
         playback_status = scrobble_data.pop("playback_status", None)
 
         logger.info(
-            "[scrobbling] update called",
+            "[update] called",
             extra={
                 "scrobble_id": self.id,
                 "scrobble_data": scrobble_data,
@@ -1101,16 +1098,6 @@ class Scrobble(TimeStampedModel):
             update_fields.append(key)
         self.save(update_fields=update_fields)
 
-        logger.info(
-            "[scrobbling] update finished",
-            extra={
-                "scrobble_id": self.id,
-                "scrobble_data": scrobble_data,
-                "playback_status": playback_status,
-                "media_type": self.media_type,
-            },
-        )
-
         return self
 
     @classmethod

+ 58 - 154
vrobbler/apps/scrobbles/scrobblers.py

@@ -1,25 +1,21 @@
+import json
 import logging
 from typing import Optional
 
 import pendulum
-from boardgames.bgg import lookup_boardgame_from_bgg
 from boardgames.models import BoardGame
 from books.models import Book
-from books.openlibrary import lookup_book_from_openlibrary
 from dateutil.parser import parse
 from django.utils import timezone
 from locations.constants import LOCATION_PROVIDERS
 from locations.models import GeoLocation
-from music.constants import JELLYFIN_POST_KEYS
+from music.constants import JELLYFIN_POST_KEYS, MOPIDY_POST_KEYS
 from music.models import Track
-from music.utils import (
-    get_or_create_album,
-    get_or_create_artist,
-    get_or_create_track,
-)
-from podcasts.models import PodcastEpisode
+from music.utils import get_or_create_track
+from podcasts.utils import get_or_create_podcast
+from scrobbles.constants import JELLYFIN_AUDIO_ITEM_TYPES
 from scrobbles.models import Scrobble
-from scrobbles.utils import convert_to_seconds, parse_mopidy_uri
+from scrobbles.utils import convert_to_seconds
 from sports.models import SportEvent
 from sports.thesportsdb import lookup_event_from_thesportsdb
 from videogames.howlongtobeat import lookup_game_from_hltb
@@ -30,181 +26,89 @@ from webpages.models import WebPage
 logger = logging.getLogger(__name__)
 
 
-def mopidy_scrobble_podcast(
-    data_dict: dict, user_id: Optional[int]
-) -> Scrobble:
-    mopidy_uri = data_dict.get("mopidy_uri", "")
-    parsed_data = parse_mopidy_uri(mopidy_uri)
-
-    producer_dict = {"name": data_dict.get("artist")}
-
-    podcast_name = data_dict.get("album")
-    if not podcast_name:
-        podcast_name = parsed_data.get("podcast_name")
-    podcast_dict = {"name": podcast_name}
-
-    episode_name = parsed_data.get("episode_filename")
-    episode_dict = {
-        "title": episode_name,
-        "run_time_seconds": data_dict.get("run_time"),
-        "number": parsed_data.get("episode_num"),
-        "pub_date": parsed_data.get("pub_date"),
-        "mopidy_uri": mopidy_uri,
-    }
-
-    episode = PodcastEpisode.find_or_create(
-        podcast_dict, producer_dict, episode_dict
-    )
-
-    # Now we run off a scrobble
-    mopidy_data = {
-        "user_id": user_id,
-        "timestamp": timezone.now(),
-        "playback_position_seconds": data_dict.get("playback_time_ticks"),
-        "source": "Mopidy",
-        "mopidy_status": data_dict.get("status"),
-    }
+def mopidy_scrobble_media(post_data: dict, user_id: int) -> Scrobble:
+    media_type = Scrobble.MediaType.TRACK
+    if "podcast" in post_data.get("mopidy_uri", ""):
+        media_type = Scrobble.MediaType.PODCAST_EPISODE
 
     logger.info(
         "[scrobblers] webhook mopidy scrobble request received",
         extra={
-            "episode_id": episode.id if episode else None,
             "user_id": user_id,
-            "scrobble_dict": mopidy_data,
-            "media_type": Scrobble.MediaType.PODCAST_EPISODE,
+            "post_data": post_data,
+            "media_type": media_type,
         },
     )
 
-    scrobble = None
-    if episode:
-        scrobble = Scrobble.create_or_update(episode, user_id, mopidy_data)
-    return scrobble
+    if media_type == Scrobble.MediaType.PODCAST_EPISODE:
+        media_obj = get_or_create_podcast(post_data)
+    else:
+        media_obj = get_or_create_track(post_data, MOPIDY_POST_KEYS)
 
+    # Now we run off a scrobble
+    playback_seconds = post_data.get("playback_time_ticks", 1) / 1000
+    playback_status = post_data.get(MOPIDY_POST_KEYS.get("STATUS"), "")
 
-def mopidy_scrobble_track(
-    data_dict: dict, user_id: Optional[int]
-) -> Optional[Scrobble]:
-    artist = get_or_create_artist(
-        data_dict.get("artist"),
-        mbid=data_dict.get("musicbrainz_artist_id", None),
-    )
-    album = get_or_create_album(
-        data_dict.get("album"),
-        artist=artist,
-        mbid=data_dict.get("musicbrainz_album_id"),
-    )
-    track = get_or_create_track(
-        title=data_dict.get("name"),
-        mbid=data_dict.get("musicbrainz_track_id"),
-        artist=artist,
-        album=album,
-        run_time_seconds=data_dict.get("run_time"),
+    return media_obj.scrobble_for_user(
+        user_id,
+        source="Mopidy",
+        playback_position_seconds=playback_seconds,
+        status=playback_status,
     )
 
-    # Now we run off a scrobble
-    playback_seconds = data_dict.get("playback_time_ticks") / 1000
-    mopidy_data = {
-        "user_id": user_id,
-        "timestamp": timezone.now(),
-        "playback_position_seconds": playback_seconds,
-        "source": "Mopidy",
-        "mopidy_status": data_dict.get("status"),
-    }
+
+def jellyfin_scrobble_media(
+    post_data: dict, user_id: int
+) -> Optional[Scrobble]:
+    media_type = Scrobble.MediaType.VIDEO
+    if post_data.pop("ItemType", "") in JELLYFIN_AUDIO_ITEM_TYPES:
+        media_type = Scrobble.MediaType.TRACK
 
     logger.info(
-        "[scrobblers] webhook mopidy scrobble request received",
+        "[jellyfin_scrobble_track] called",
         extra={
-            "track_id": track.id,
             "user_id": user_id,
-            "scrobble_dict": mopidy_data,
-            "media_type": Scrobble.MediaType.TRACK,
+            "post_data": post_data,
+            "media_type": media_type,
         },
     )
 
-    scrobble = Scrobble.create_or_update(track, user_id, mopidy_data)
-
-    return scrobble
-
-
-def build_scrobble_dict(data_dict: dict, user_id: int) -> dict:
-    jellyfin_status = "resumed"
-    if data_dict.get("IsPaused"):
-        jellyfin_status = "paused"
-    elif data_dict.get("NotificationType") == "PlaybackStop":
-        jellyfin_status = "stopped"
-
-    playback_seconds = convert_to_seconds(
-        data_dict.get("PlaybackPosition", "")
-    )
-    return {
-        "user_id": user_id,
-        "timestamp": parse(data_dict.get("UtcTimestamp")),
-        "playback_position_seconds": playback_seconds,
-        "source": data_dict.get("ClientName", "Vrobbler"),
-        "log": {"media_source_id": data_dict.get("MediaSourceId")},
-        "jellyfin_status": jellyfin_status,
-    }
-
-
-def jellyfin_scrobble_track(
-    data_dict: dict, user_id: Optional[int]
-) -> Optional[Scrobble]:
-
     null_position_on_progress = (
-        data_dict.get("PlaybackPosition") == "00:00:00"
-        and data_dict.get("NotificationType") == "PlaybackProgress"
+        post_data.get("PlaybackPosition") == "00:00:00"
+        and post_data.get("NotificationType") == "PlaybackProgress"
     )
 
+    playback_status = "resumed"
+    if post_data.get("IsPaused"):
+        playback_status = "paused"
+    elif post_data.get("NotificationType") == "PlaybackStop":
+        playback_status = "stopped"
+
     # Jellyfin has some race conditions with it's webhooks, these hacks fix some of them
     if null_position_on_progress:
-        logger.error("No playback position tick from Jellyfin, aborting")
+        logger.info(
+            "[jellyfin_scrobble_track] no playback position tick, aborting",
+            extra={"post_data": post_data},
+        )
         return
 
-    artist = get_or_create_artist(
-        data_dict.get(JELLYFIN_POST_KEYS["ARTIST_NAME"]),
-        mbid=data_dict.get(JELLYFIN_POST_KEYS["ARTIST_MB_ID"]),
+    playback_position_seconds = convert_to_seconds(
+        post_data.get(JELLYFIN_POST_KEYS.get("RUN_TIME"), 0)
     )
-    album = get_or_create_album(
-        data_dict.get(JELLYFIN_POST_KEYS["ALBUM_NAME"]),
-        artist=artist,
-        mbid=data_dict.get(JELLYFIN_POST_KEYS["ALBUM_MB_ID"]),
-    )
-
-    run_time = convert_to_seconds(
-        data_dict.get(JELLYFIN_POST_KEYS["RUN_TIME"])
-    )
-    track = get_or_create_track(
-        title=data_dict.get("Name"),
-        artist=artist,
-        album=album,
-        run_time_seconds=run_time,
-    )
-
-    scrobble_dict = build_scrobble_dict(data_dict, user_id)
-
-    # A hack to make Jellyfin work more like Mopidy for music tracks
-    scrobble_dict["playback_position_seconds"] = 0
 
-    return Scrobble.create_or_update(track, user_id, scrobble_dict)
+    if media_type == Scrobble.MediaType.VIDEO:
+        media_obj = Video.find_or_create(post_data)
+    else:
+        media_obj = get_or_create_track(
+            post_data, post_keys=JELLYFIN_POST_KEYS
+        )
 
-
-def jellyfin_scrobble_video(data_dict: dict, user_id: Optional[int]):
-    video = Video.find_or_create(data_dict)
-
-    scrobble_dict = build_scrobble_dict(data_dict, user_id)
-
-    logger.info(
-        "[scrobblers] webhook video scrobble request received",
-        extra={
-            "video_id": video.id,
-            "user_id": user_id,
-            "scrobble_dict": scrobble_dict,
-            "media_type": Scrobble.MediaType.VIDEO,
-        },
+    return media_obj.scrobble_for_user_id(
+        user_id,
+        playback_position_seconds=playback_position_seconds,
+        status=playback_status,
     )
 
-    return Scrobble.create_or_update(video, user_id, scrobble_dict)
-
 
 def manual_scrobble_video(imdb_id: str, user_id: int):
     video = Video.find_or_create({"imdb_id": imdb_id})

+ 0 - 49
vrobbler/apps/scrobbles/utils.py

@@ -1,11 +1,8 @@
 import logging
-import os
 import re
 from datetime import datetime, timedelta, tzinfo
-from urllib.parse import unquote
 
 import pytz
-from dateutil.parser import ParserError, parse
 from django.apps import apps
 from django.contrib.auth import get_user_model
 from django.db import models
@@ -19,9 +16,6 @@ logger = logging.getLogger(__name__)
 User = get_user_model()
 
 
-PODCAST_DATE_FORMAT = "YYYY-MM-DD"
-
-
 def timestamp_user_tz_to_utc(timestamp: int, user_tz: tzinfo) -> datetime:
     return user_tz.localize(datetime.utcfromtimestamp(timestamp)).astimezone(
         pytz.utc
@@ -45,49 +39,6 @@ def convert_to_seconds(run_time: str) -> int:
     return run_time_int
 
 
-def parse_mopidy_uri(uri: str) -> dict:
-    logger.debug(f"Parsing URI: {uri}")
-    parsed_uri = os.path.splitext(unquote(uri))[0].split("/")
-
-    episode_str = parsed_uri[-1]
-    podcast_name = parsed_uri[-2].strip()
-    episode_num = None
-    episode_num_pad = 0
-
-    try:
-        # Without episode numbers the date will lead
-        pub_date = parse(episode_str[0:10])
-    except ParserError:
-        episode_num = int(episode_str.split("-")[0])
-        episode_num_pad = len(str(episode_num)) + 1
-
-        try:
-            # Beacuse we have epsiode numbers on
-            pub_date = parse(
-                episode_str[
-                    episode_num_pad : len(PODCAST_DATE_FORMAT)
-                    + episode_num_pad
-                ]
-            )
-        except ParserError:
-            pub_date = ""
-
-    gap_to_strip = 0
-    if pub_date:
-        gap_to_strip += len(PODCAST_DATE_FORMAT)
-    if episode_num:
-        gap_to_strip += episode_num_pad
-
-    episode_name = episode_str[gap_to_strip:].replace("-", " ").strip()
-
-    return {
-        "episode_filename": episode_name,
-        "episode_num": episode_num,
-        "podcast_name": podcast_name,
-        "pub_date": pub_date,
-    }
-
-
 def get_scrobbles_for_media(media_obj, user: User) -> models.QuerySet:
     Scrobble = apps.get_model(app_label="scrobbles", model_name="Scrobble")
 

+ 13 - 26
vrobbler/apps/scrobbles/views.py

@@ -320,29 +320,24 @@ def lastfm_import(request):
 @permission_classes([IsAuthenticated])
 @api_view(["POST"])
 def jellyfin_webhook(request):
-    data_dict = request.data
-
-    # For making things easier to build new input processors
-    if getattr(settings, "DUMP_REQUEST_DATA", False):
-        json_data = json.dumps(data_dict, indent=4)
-        logger.info(f"{json_data}")
+    post_data = request.data
+    logger.info(
+        "[jellyfin_webhook] called",
+        extra={"post_data": post_data},
+    )
 
-    in_progress = data_dict.get("NotificationType", "") == "PlaybackProgress"
-    is_music = data_dict.get("ItemType", "") == "Audio"
+    in_progress = post_data.get("NotificationType", "") == "PlaybackProgress"
+    is_music = post_data.get("ItemType", "") == "Audio"
 
     # Disregard progress updates
     if in_progress and is_music:
-        logger.debug("Disregarding playback update from Jellyfin")
+        logger.info(
+            "[jellyfin_webhook] ignoring update of music in progress",
+            extra={"post_data": post_data},
+        )
         return Response({}, status=status.HTTP_304_NOT_MODIFIED)
 
-    scrobble = None
-    media_type = data_dict.get("ItemType", "")
-
-    if media_type in JELLYFIN_AUDIO_ITEM_TYPES:
-        scrobble = jellyfin_scrobble_track(data_dict, request.user.id)
-
-    if media_type in JELLYFIN_VIDEO_ITEM_TYPES:
-        scrobble = jellyfin_scrobble_video(data_dict, request.user.id)
+    scrobble = jellyfin_scrobble_media(post_data, request.user.id)
 
     if not scrobble:
         return Response({}, status=status.HTTP_400_BAD_REQUEST)
@@ -359,15 +354,7 @@ def mopidy_webhook(request):
     except TypeError:
         data_dict = request.data
 
-    # For making things easier to build new input processors
-    if getattr(settings, "DUMP_REQUEST_DATA", False):
-        json_data = json.dumps(data_dict, indent=4)
-        logger.debug(f"{json_data}")
-
-    if "podcast" in data_dict.get("mopidy_uri"):
-        scrobble = mopidy_scrobble_podcast(data_dict, request.user.id)
-    else:
-        scrobble = mopidy_scrobble_track(data_dict, request.user.id)
+    scrobble = mopidy_scrobble_media(data_dict, request.user.id)
 
     if not scrobble:
         return Response({}, status=status.HTTP_400_BAD_REQUEST)