[scrobbling] Refactor webhook and simplify

This commit is contained in:
2024-08-12 00:03:30 -05:00
parent cc9a2a64df
commit 5e22cb3106
11 changed files with 249 additions and 301 deletions

View File

@ -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,180 +26,88 @@ 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
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"),
)
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 = 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"),
}
playback_seconds = post_data.get("playback_time_ticks", 1) / 1000
playback_status = post_data.get(MOPIDY_POST_KEYS.get("STATUS"), "")
return media_obj.scrobble_for_user(
user_id,
source="Mopidy",
playback_position_seconds=playback_seconds,
status=playback_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"]),
)
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"]),
playback_position_seconds = convert_to_seconds(
post_data.get(JELLYFIN_POST_KEYS.get("RUN_TIME"), 0)
)
run_time = convert_to_seconds(
data_dict.get(JELLYFIN_POST_KEYS["RUN_TIME"])
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
)
return media_obj.scrobble_for_user_id(
user_id,
playback_position_seconds=playback_position_seconds,
status=playback_status,
)
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)
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 Scrobble.create_or_update(video, user_id, scrobble_dict)
def manual_scrobble_video(imdb_id: str, user_id: int):