diff --git a/api/funkwhale_api/subsonic/serializers.py b/api/funkwhale_api/subsonic/serializers.py index f63ad5d2ee92885e5ec15713bfbf82d92ac2495a..97cdbcfc692b97bc763771a903888372768d47a7 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 87c9f72700ca8dcd7e5cbfeb3fadb37edb6c7e11..dbc31ec5f22efdee1b0444759aa1d454d9aa053e 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 081b669c571cceff6d41fd973bfd8b3113a35a60..6b9ec232da9e8789fd3bdbd60147c2d706b3e1ad 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 65c2ad95f351ca2228f4d05fc835f34c4edaf1e7..52e410e52b4c516b58ecc05a45df595316748553 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 0000000000000000000000000000000000000000..8d65034731ff9aca4b37fd2f20202c846d8f59af --- /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)