[scrobblers] Use MediaSourceId to avoid multiple Jellyfin scrobbles
This commit is contained in:
@ -90,6 +90,7 @@ JELLYFIN_POST_KEYS = {
|
||||
"TAGLINE": "TAGLINE",
|
||||
"PLAYBACK_POSITION_TICKS": "PlaybackPositionTicks",
|
||||
"PLAYBACK_POSITION": "PlaybackPosition",
|
||||
"MEDIA_SOURCE_ID": "MediaSourceId",
|
||||
"ARTIST_MB_ID": "Provider_musicbrainzartist",
|
||||
"ALBUM_MB_ID": "Provider_musicbrainzalbum",
|
||||
"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,
|
||||
user_id,
|
||||
source: str = "Vrobbler",
|
||||
source_id: Optional[str] = None,
|
||||
playback_position_seconds: int = 0,
|
||||
status: str = "started",
|
||||
log: Optional[dict] = None,
|
||||
@ -88,6 +89,7 @@ class ScrobblableMixin(TimeStampedModel):
|
||||
"user_id": user_id,
|
||||
"timestamp": timezone.now(),
|
||||
"source": source,
|
||||
"source_id": source_id,
|
||||
"status": status,
|
||||
"playback_position_seconds": playback_position_seconds,
|
||||
}
|
||||
|
||||
@ -582,6 +582,7 @@ class Scrobble(TimeStampedModel):
|
||||
|
||||
# Metadata
|
||||
source = models.CharField(max_length=255, **BNULL)
|
||||
source_id = models.CharField(max_length=255, **BNULL)
|
||||
log = models.JSONField(
|
||||
**BNULL,
|
||||
default=dict,
|
||||
@ -1136,6 +1137,23 @@ class Scrobble(TimeStampedModel):
|
||||
skip_in_progress_check = kwargs.get("skip_in_progress_check", False)
|
||||
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)
|
||||
scrobble = (
|
||||
|
||||
@ -153,6 +153,7 @@ def jellyfin_scrobble_media(
|
||||
return media_obj.scrobble_for_user(
|
||||
user_id,
|
||||
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,
|
||||
status=playback_status,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user