[scrobblers] Use MediaSourceId to avoid multiple Jellyfin scrobbles
This commit is contained in:
@ -90,6 +90,7 @@ JELLYFIN_POST_KEYS = {
|
|||||||
"TAGLINE": "TAGLINE",
|
"TAGLINE": "TAGLINE",
|
||||||
"PLAYBACK_POSITION_TICKS": "PlaybackPositionTicks",
|
"PLAYBACK_POSITION_TICKS": "PlaybackPositionTicks",
|
||||||
"PLAYBACK_POSITION": "PlaybackPosition",
|
"PLAYBACK_POSITION": "PlaybackPosition",
|
||||||
|
"MEDIA_SOURCE_ID": "MediaSourceId",
|
||||||
"ARTIST_MB_ID": "Provider_musicbrainzartist",
|
"ARTIST_MB_ID": "Provider_musicbrainzartist",
|
||||||
"ALBUM_MB_ID": "Provider_musicbrainzalbum",
|
"ALBUM_MB_ID": "Provider_musicbrainzalbum",
|
||||||
"RELEASEGROUP_MB_ID": "Provider_musicbrainzreleasegroup",
|
"RELEASEGROUP_MB_ID": "Provider_musicbrainzreleasegroup",
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
# Generated manually for deduplication fix
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("scrobbles", "0070_rename_brickset_scrobble_brick_set"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="scrobble",
|
||||||
|
name="source_id",
|
||||||
|
field=models.CharField(max_length=255, null=True, blank=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -79,6 +79,7 @@ class ScrobblableMixin(TimeStampedModel):
|
|||||||
self,
|
self,
|
||||||
user_id,
|
user_id,
|
||||||
source: str = "Vrobbler",
|
source: str = "Vrobbler",
|
||||||
|
source_id: Optional[str] = None,
|
||||||
playback_position_seconds: int = 0,
|
playback_position_seconds: int = 0,
|
||||||
status: str = "started",
|
status: str = "started",
|
||||||
log: Optional[dict] = None,
|
log: Optional[dict] = None,
|
||||||
@ -88,6 +89,7 @@ class ScrobblableMixin(TimeStampedModel):
|
|||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"timestamp": timezone.now(),
|
"timestamp": timezone.now(),
|
||||||
"source": source,
|
"source": source,
|
||||||
|
"source_id": source_id,
|
||||||
"status": status,
|
"status": status,
|
||||||
"playback_position_seconds": playback_position_seconds,
|
"playback_position_seconds": playback_position_seconds,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -582,6 +582,7 @@ class Scrobble(TimeStampedModel):
|
|||||||
|
|
||||||
# Metadata
|
# Metadata
|
||||||
source = models.CharField(max_length=255, **BNULL)
|
source = models.CharField(max_length=255, **BNULL)
|
||||||
|
source_id = models.CharField(max_length=255, **BNULL)
|
||||||
log = models.JSONField(
|
log = models.JSONField(
|
||||||
**BNULL,
|
**BNULL,
|
||||||
default=dict,
|
default=dict,
|
||||||
@ -1136,6 +1137,23 @@ class Scrobble(TimeStampedModel):
|
|||||||
skip_in_progress_check = kwargs.get("skip_in_progress_check", False)
|
skip_in_progress_check = kwargs.get("skip_in_progress_check", False)
|
||||||
read_log_page = kwargs.get("read_log_page", None)
|
read_log_page = kwargs.get("read_log_page", None)
|
||||||
|
|
||||||
|
source_id = scrobble_data.get("source_id")
|
||||||
|
|
||||||
|
# If source_id is provided (e.g., MediaSourceId from Jellyfin), check for existing scrobble first
|
||||||
|
# This prevents duplicates when webhooks arrive faster than scrobble creation
|
||||||
|
if source_id:
|
||||||
|
existing_by_source_id = cls.objects.filter(
|
||||||
|
media_query,
|
||||||
|
user_id=user_id,
|
||||||
|
source_id=source_id,
|
||||||
|
).first()
|
||||||
|
if existing_by_source_id:
|
||||||
|
logger.info(
|
||||||
|
"[create_or_update] found existing scrobble by source_id, updating",
|
||||||
|
extra={"scrobble_id": existing_by_source_id.id, "source_id": source_id},
|
||||||
|
)
|
||||||
|
scrobble_data["playback_status"] = scrobble_data.pop("status", None)
|
||||||
|
return existing_by_source_id.update(scrobble_data)
|
||||||
|
|
||||||
# Find our last scrobble of this media item (track, video, etc)
|
# Find our last scrobble of this media item (track, video, etc)
|
||||||
scrobble = (
|
scrobble = (
|
||||||
|
|||||||
@ -153,6 +153,7 @@ def jellyfin_scrobble_media(
|
|||||||
return media_obj.scrobble_for_user(
|
return media_obj.scrobble_for_user(
|
||||||
user_id,
|
user_id,
|
||||||
source=post_data.get(JELLYFIN_POST_KEYS.get("SOURCE")),
|
source=post_data.get(JELLYFIN_POST_KEYS.get("SOURCE")),
|
||||||
|
source_id=post_data.get(JELLYFIN_POST_KEYS.get("MEDIA_SOURCE_ID")),
|
||||||
playback_position_seconds=playback_position_seconds,
|
playback_position_seconds=playback_position_seconds,
|
||||||
status=playback_status,
|
status=playback_status,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user