From 0f792bf75cfa9ea0be1b73f2863706c38e66cd35 Mon Sep 17 00:00:00 2001 From: Eliot Berriot <contact@eliotberriot.com> Date: Fri, 1 Jun 2018 23:59:08 +0200 Subject: [PATCH] Fix #260: Implemented scrobble endpoint of subsonic API --- api/funkwhale_api/subsonic/serializers.py | 16 ++++++++++++++++ api/funkwhale_api/subsonic/views.py | 23 +++++++++++++++++++++-- api/tests/subsonic/test_serializers.py | 19 +++++++++++++++++++ api/tests/subsonic/test_views.py | 14 ++++++++++++++ changes/changelog.d/260.enhancement | 2 ++ 5 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 changes/changelog.d/260.enhancement diff --git a/api/funkwhale_api/subsonic/serializers.py b/api/funkwhale_api/subsonic/serializers.py index f63ad5d2..97cdbcfc 100644 --- a/api/funkwhale_api/subsonic/serializers.py +++ b/api/funkwhale_api/subsonic/serializers.py @@ -4,6 +4,7 @@ from django.db.models import functions, Count from rest_framework import serializers +from funkwhale_api.history import models as history_models from funkwhale_api.music import models as music_models @@ -228,3 +229,18 @@ def get_music_directory_data(artist): td['size'] = tf.size data['child'].append(td) return data + + +class ScrobbleSerializer(serializers.Serializer): + submission = serializers.BooleanField(default=True, required=False) + id = serializers.PrimaryKeyRelatedField( + queryset=music_models.Track.objects.annotate( + files_count=Count('files') + ).filter(files_count__gt=0) + ) + + def create(self, data): + return history_models.Listening.objects.create( + user=self.context['user'], + track=data['id'], + ) diff --git a/api/funkwhale_api/subsonic/views.py b/api/funkwhale_api/subsonic/views.py index 87c9f727..dbc31ec5 100644 --- a/api/funkwhale_api/subsonic/views.py +++ b/api/funkwhale_api/subsonic/views.py @@ -519,7 +519,7 @@ class SubsonicViewSet(viewsets.GenericViewSet): 'message': 'cover art ID must be specified.' } }) - + if id.startswith('al-'): try: album_id = int(id.replace('al-', '')) @@ -551,4 +551,23 @@ class SubsonicViewSet(viewsets.GenericViewSet): # let the proxy set the content-type r = response.Response({}, content_type='') r[file_header] = path - return r \ No newline at end of file + return r + + @list_route( + methods=['get', 'post'], + url_name='scrobble', + url_path='scrobble') + def scrobble(self, request, *args, **kwargs): + data = request.GET or request.POST + serializer = serializers.ScrobbleSerializer( + data=data, context={'user': request.user}) + if not serializer.is_valid(): + return response.Response({ + 'error': { + 'code': 0, + 'message': 'Invalid payload' + } + }) + if serializer.validated_data['submission']: + serializer.save() + return response.Response({}) diff --git a/api/tests/subsonic/test_serializers.py b/api/tests/subsonic/test_serializers.py index 081b669c..6b9ec232 100644 --- a/api/tests/subsonic/test_serializers.py +++ b/api/tests/subsonic/test_serializers.py @@ -214,3 +214,22 @@ def test_directory_serializer_artist(factories): } data = serializers.get_music_directory_data(artist) assert data == expected + + +def test_scrobble_serializer(factories): + tf = factories['music.TrackFile']() + track = tf.track + user = factories['users.User']() + payload = { + 'id': track.pk, + 'submission': True, + } + serializer = serializers.ScrobbleSerializer( + data=payload, context={'user': user}) + + assert serializer.is_valid(raise_exception=True) + + listening = serializer.save() + + assert listening.user == user + assert listening.track == track diff --git a/api/tests/subsonic/test_views.py b/api/tests/subsonic/test_views.py index 65c2ad95..52e410e5 100644 --- a/api/tests/subsonic/test_views.py +++ b/api/tests/subsonic/test_views.py @@ -404,3 +404,17 @@ def test_get_cover_art_album(factories, logged_in_api_client): assert response['X-Accel-Redirect'] == music_views.get_file_path( album.cover ).decode('utf-8') + + +def test_scrobble(factories, logged_in_api_client): + tf = factories['music.TrackFile']() + track = tf.track + url = reverse('api:subsonic-scrobble') + assert url.endswith('scrobble') is True + response = logged_in_api_client.get( + url, {'id': track.pk, 'submission': True}) + + assert response.status_code == 200 + + l = logged_in_api_client.user.listenings.latest('id') + assert l.track == track diff --git a/changes/changelog.d/260.enhancement b/changes/changelog.d/260.enhancement new file mode 100644 index 00000000..8d650347 --- /dev/null +++ b/changes/changelog.d/260.enhancement @@ -0,0 +1,2 @@ +Implemented scrobble endpoint of subsonic API, listenings are now tracked +correctly from third party apps that use this endpoint (#260) -- GitLab