mirror of
https://github.com/Alexander-D-Karpov/akarpov
synced 2024-11-27 19:23:45 +03:00
Compare commits
No commits in common. "9c32235926d107edc7e3d3af2e2cf100f8edd39d" and "f6467bd12a2ab8b285773971fb0d9b4c201b2971" have entirely different histories.
9c32235926
...
f6467bd12a
|
@ -5,7 +5,6 @@
|
||||||
from akarpov.common.api.serializers import SetUserModelSerializer
|
from akarpov.common.api.serializers import SetUserModelSerializer
|
||||||
from akarpov.music.models import (
|
from akarpov.music.models import (
|
||||||
Album,
|
Album,
|
||||||
AnonMusicUser,
|
|
||||||
Author,
|
Author,
|
||||||
Playlist,
|
Playlist,
|
||||||
PlaylistSong,
|
PlaylistSong,
|
||||||
|
@ -199,26 +198,6 @@ def create(self, validated_data):
|
||||||
return playlist_song
|
return playlist_song
|
||||||
|
|
||||||
|
|
||||||
class ListenSongSerializer(serializers.Serializer):
|
|
||||||
song = serializers.SlugField()
|
|
||||||
user_id = serializers.CharField(required=False)
|
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
if not Song.objects.filter(slug=attrs["song"]).exists():
|
|
||||||
raise serializers.ValidationError("Song not found")
|
|
||||||
|
|
||||||
if "user_id" in attrs and attrs["user_id"]:
|
|
||||||
if not AnonMusicUser.objects.filter(id=attrs["user_id"]).exists():
|
|
||||||
raise serializers.ValidationError("User not found")
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
|
|
||||||
class AnonMusicUserSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = AnonMusicUser
|
|
||||||
fields = ["id"]
|
|
||||||
|
|
||||||
|
|
||||||
class LikeDislikeSongSerializer(serializers.ModelSerializer):
|
class LikeDislikeSongSerializer(serializers.ModelSerializer):
|
||||||
song = serializers.SlugField()
|
song = serializers.SlugField()
|
||||||
|
|
||||||
|
@ -271,10 +250,6 @@ def create(self, validated_data):
|
||||||
return song_user_rating
|
return song_user_rating
|
||||||
|
|
||||||
|
|
||||||
class ListSongSlugsSerializer(serializers.Serializer):
|
|
||||||
slugs = serializers.ListField(child=serializers.SlugField())
|
|
||||||
|
|
||||||
|
|
||||||
class ListPlaylistSerializer(serializers.ModelSerializer):
|
class ListPlaylistSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Playlist
|
model = Playlist
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from akarpov.music.api.views import (
|
from akarpov.music.api.views import (
|
||||||
AddSongToPlaylistAPIView,
|
AddSongToPlaylistAPIView,
|
||||||
CreateAnonMusicUserAPIView,
|
|
||||||
DislikeSongAPIView,
|
DislikeSongAPIView,
|
||||||
LikeSongAPIView,
|
LikeSongAPIView,
|
||||||
ListAlbumsAPIView,
|
ListAlbumsAPIView,
|
||||||
|
@ -14,7 +13,6 @@
|
||||||
ListLikedSongsAPIView,
|
ListLikedSongsAPIView,
|
||||||
ListPublicPlaylistAPIView,
|
ListPublicPlaylistAPIView,
|
||||||
ListSongPlaylistsAPIView,
|
ListSongPlaylistsAPIView,
|
||||||
ListSongSlugsAPIView,
|
|
||||||
ListUserListenedSongsAPIView,
|
ListUserListenedSongsAPIView,
|
||||||
RemoveSongFromPlaylistAPIView,
|
RemoveSongFromPlaylistAPIView,
|
||||||
RetrieveUpdateDestroyAlbumAPIView,
|
RetrieveUpdateDestroyAlbumAPIView,
|
||||||
|
@ -42,7 +40,6 @@
|
||||||
name="retrieve_update_delete_playlist",
|
name="retrieve_update_delete_playlist",
|
||||||
),
|
),
|
||||||
path("song/", ListCreateSongAPIView.as_view(), name="list_create_song"),
|
path("song/", ListCreateSongAPIView.as_view(), name="list_create_song"),
|
||||||
path("song/slugs/", ListSongSlugsAPIView.as_view(), name="list_songs_slugs"),
|
|
||||||
path(
|
path(
|
||||||
"song/<str:slug>",
|
"song/<str:slug>",
|
||||||
RetrieveUpdateDestroySongAPIView.as_view(),
|
RetrieveUpdateDestroySongAPIView.as_view(),
|
||||||
|
@ -79,5 +76,4 @@
|
||||||
RetrieveUpdateDestroyAuthorAPIView.as_view(),
|
RetrieveUpdateDestroyAuthorAPIView.as_view(),
|
||||||
name="retrieve_update_delete_author",
|
name="retrieve_update_delete_author",
|
||||||
),
|
),
|
||||||
path("anon/create/", CreateAnonMusicUserAPIView.as_view(), name="create-anon"),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,17 +6,14 @@
|
||||||
from akarpov.common.api.permissions import IsAdminOrReadOnly, IsCreatorOrReadOnly
|
from akarpov.common.api.permissions import IsAdminOrReadOnly, IsCreatorOrReadOnly
|
||||||
from akarpov.music.api.serializers import (
|
from akarpov.music.api.serializers import (
|
||||||
AddSongToPlaylistSerializer,
|
AddSongToPlaylistSerializer,
|
||||||
AnonMusicUserSerializer,
|
|
||||||
FullAlbumSerializer,
|
FullAlbumSerializer,
|
||||||
FullAuthorSerializer,
|
FullAuthorSerializer,
|
||||||
FullPlaylistSerializer,
|
FullPlaylistSerializer,
|
||||||
LikeDislikeSongSerializer,
|
LikeDislikeSongSerializer,
|
||||||
ListAlbumSerializer,
|
ListAlbumSerializer,
|
||||||
ListAuthorSerializer,
|
ListAuthorSerializer,
|
||||||
ListenSongSerializer,
|
|
||||||
ListPlaylistSerializer,
|
ListPlaylistSerializer,
|
||||||
ListSongSerializer,
|
ListSongSerializer,
|
||||||
ListSongSlugsSerializer,
|
|
||||||
PlaylistSerializer,
|
PlaylistSerializer,
|
||||||
SongSerializer,
|
SongSerializer,
|
||||||
)
|
)
|
||||||
|
@ -81,7 +78,11 @@ def get_queryset(self):
|
||||||
return qs.select_related("creator")
|
return qs.select_related("creator")
|
||||||
|
|
||||||
|
|
||||||
class ListBaseSongAPIView(generics.ListAPIView):
|
class ListCreateSongAPIView(LikedSongsContextMixin, generics.ListCreateAPIView):
|
||||||
|
serializer_class = ListSongSerializer
|
||||||
|
permission_classes = [IsAdminOrReadOnly]
|
||||||
|
pagination_class = StandardResultsSetPagination
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
search = self.request.query_params.get("search", None)
|
search = self.request.query_params.get("search", None)
|
||||||
if search:
|
if search:
|
||||||
|
@ -113,14 +114,6 @@ def get_queryset(self):
|
||||||
)
|
)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class ListCreateSongAPIView(
|
|
||||||
LikedSongsContextMixin, generics.ListCreateAPIView, ListBaseSongAPIView
|
|
||||||
):
|
|
||||||
serializer_class = ListSongSerializer
|
|
||||||
permission_classes = [IsAdminOrReadOnly]
|
|
||||||
pagination_class = StandardResultsSetPagination
|
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
parameters=[
|
parameters=[
|
||||||
OpenApiParameter(
|
OpenApiParameter(
|
||||||
|
@ -173,67 +166,6 @@ def get(self, request, *args, **kwargs):
|
||||||
return self.list(request, *args, **kwargs)
|
return self.list(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ListSongSlugsAPIView(ListBaseSongAPIView):
|
|
||||||
serializer_class = ListSongSlugsSerializer
|
|
||||||
permission_classes = [permissions.AllowAny]
|
|
||||||
|
|
||||||
@extend_schema(
|
|
||||||
parameters=[
|
|
||||||
OpenApiParameter(
|
|
||||||
name="search",
|
|
||||||
description="Search query",
|
|
||||||
required=False,
|
|
||||||
type=str,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="sort",
|
|
||||||
description="Sorting algorithm",
|
|
||||||
required=False,
|
|
||||||
type=str,
|
|
||||||
examples=[
|
|
||||||
OpenApiExample(
|
|
||||||
"Default",
|
|
||||||
description="by date added",
|
|
||||||
value=None,
|
|
||||||
),
|
|
||||||
OpenApiExample(
|
|
||||||
"played",
|
|
||||||
description="by total times played",
|
|
||||||
value="played",
|
|
||||||
),
|
|
||||||
OpenApiExample(
|
|
||||||
"likes",
|
|
||||||
description="by total likes",
|
|
||||||
value="likes",
|
|
||||||
),
|
|
||||||
OpenApiExample(
|
|
||||||
"likes reversed",
|
|
||||||
description="by total likes",
|
|
||||||
value="-likes",
|
|
||||||
),
|
|
||||||
OpenApiExample(
|
|
||||||
"length",
|
|
||||||
description="by track length",
|
|
||||||
value="length",
|
|
||||||
),
|
|
||||||
OpenApiExample(
|
|
||||||
"uploaded",
|
|
||||||
description="by date uploaded",
|
|
||||||
value="uploaded",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
songs = self.get_queryset()
|
|
||||||
return Response(
|
|
||||||
data={
|
|
||||||
"songs": songs.values_list("slug", flat=True),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RetrieveUpdateDestroySongAPIView(generics.RetrieveUpdateDestroyAPIView):
|
class RetrieveUpdateDestroySongAPIView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
lookup_field = "slug"
|
lookup_field = "slug"
|
||||||
lookup_url_kwarg = "slug"
|
lookup_url_kwarg = "slug"
|
||||||
|
@ -382,7 +314,7 @@ class RetrieveUpdateDestroyAuthorAPIView(
|
||||||
|
|
||||||
|
|
||||||
class ListenSongAPIView(generics.GenericAPIView):
|
class ListenSongAPIView(generics.GenericAPIView):
|
||||||
serializer_class = ListenSongSerializer
|
serializer_class = LikeDislikeSongSerializer
|
||||||
permission_classes = [permissions.AllowAny]
|
permission_classes = [permissions.AllowAny]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
@ -399,25 +331,12 @@ def post(self, request, *args, **kwargs):
|
||||||
return Response(status=404)
|
return Response(status=404)
|
||||||
if self.request.user.is_authenticated:
|
if self.request.user.is_authenticated:
|
||||||
listen_to_song.apply_async(
|
listen_to_song.apply_async(
|
||||||
kwargs={
|
kwargs={"song_id": song.id, "user_id": self.request.user.id},
|
||||||
"song_id": song.id,
|
|
||||||
"user_id": self.request.user.id,
|
|
||||||
"anon": False,
|
|
||||||
},
|
|
||||||
countdown=2,
|
|
||||||
)
|
|
||||||
elif "user_id" in data:
|
|
||||||
listen_to_song.apply_async(
|
|
||||||
kwargs={
|
|
||||||
"song_id": song.id,
|
|
||||||
"user_id": data["user_id"],
|
|
||||||
"anon": True,
|
|
||||||
},
|
|
||||||
countdown=2,
|
countdown=2,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
listen_to_song.apply_async(
|
listen_to_song.apply_async(
|
||||||
kwargs={"song_id": song.id, "user_id": None, "anon": True},
|
kwargs={"song_id": song.id},
|
||||||
countdown=2,
|
countdown=2,
|
||||||
)
|
)
|
||||||
return Response(status=201)
|
return Response(status=201)
|
||||||
|
@ -434,8 +353,3 @@ def get_queryset(self):
|
||||||
.filter(user=self.request.user)
|
.filter(user=self.request.user)
|
||||||
.values_list("song_id", flat=True)
|
.values_list("song_id", flat=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CreateAnonMusicUserAPIView(generics.CreateAPIView):
|
|
||||||
serializer_class = AnonMusicUserSerializer
|
|
||||||
permission_classes = [permissions.AllowAny]
|
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
# Generated by Django 4.2.8 on 2024-01-17 13:13
|
|
||||||
|
|
||||||
import django.contrib.postgres.fields
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.utils.timezone
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("music", "0015_usermusicprofile"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="AnonMusicUser",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.UUIDField(
|
|
||||||
default=uuid.uuid4,
|
|
||||||
editable=False,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="song",
|
|
||||||
name="created",
|
|
||||||
field=models.DateTimeField(
|
|
||||||
auto_now_add=True, default=django.utils.timezone.now
|
|
||||||
),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="song",
|
|
||||||
name="volume",
|
|
||||||
field=django.contrib.postgres.fields.ArrayField(
|
|
||||||
base_field=models.IntegerField(), null=True, size=None
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="AnonMusicUserHistory",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("created", models.DateTimeField(auto_now_add=True)),
|
|
||||||
(
|
|
||||||
"song",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name="anon_listeners",
|
|
||||||
to="music.song",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"user",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name="songs_listened",
|
|
||||||
to="music.anonmusicuser",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"ordering": ["-created"],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
20
akarpov/music/migrations/0016_song_volume.py
Normal file
20
akarpov/music/migrations/0016_song_volume.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 4.2.8 on 2024-01-15 17:28
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("music", "0015_usermusicprofile"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="song",
|
||||||
|
name="volume",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.IntegerField(), null=True, size=None
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,5 +1,3 @@
|
||||||
import uuid
|
|
||||||
|
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
@ -48,7 +46,6 @@ class Song(BaseImageModel, ShortLinkModel):
|
||||||
creator = models.ForeignKey(
|
creator = models.ForeignKey(
|
||||||
"users.User", related_name="songs", on_delete=models.SET_NULL, null=True
|
"users.User", related_name="songs", on_delete=models.SET_NULL, null=True
|
||||||
)
|
)
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
|
||||||
meta = models.JSONField(blank=True, null=True)
|
meta = models.JSONField(blank=True, null=True)
|
||||||
likes = models.IntegerField(default=0)
|
likes = models.IntegerField(default=0)
|
||||||
volume = ArrayField(models.IntegerField(), null=True)
|
volume = ArrayField(models.IntegerField(), null=True)
|
||||||
|
@ -157,29 +154,6 @@ class Meta:
|
||||||
ordering = ["-created"]
|
ordering = ["-created"]
|
||||||
|
|
||||||
|
|
||||||
class AnonMusicUser(models.Model):
|
|
||||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"AnonMusicUser {self.id}"
|
|
||||||
|
|
||||||
|
|
||||||
class AnonMusicUserHistory(models.Model):
|
|
||||||
user = models.ForeignKey(
|
|
||||||
"music.AnonMusicUser", related_name="songs_listened", on_delete=models.CASCADE
|
|
||||||
)
|
|
||||||
song = models.ForeignKey(
|
|
||||||
"Song", related_name="anon_listeners", on_delete=models.CASCADE
|
|
||||||
)
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ["-created"]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.user} - {self.song}"
|
|
||||||
|
|
||||||
|
|
||||||
class UserListenHistory(models.Model):
|
class UserListenHistory(models.Model):
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
"users.User", related_name="songs_listened", on_delete=models.CASCADE
|
"users.User", related_name="songs_listened", on_delete=models.CASCADE
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
import pylast
|
import pylast
|
||||||
import structlog
|
import structlog
|
||||||
from asgiref.sync import async_to_sync
|
from asgiref.sync import async_to_sync
|
||||||
|
@ -11,14 +9,7 @@
|
||||||
from pytube import Channel, Playlist
|
from pytube import Channel, Playlist
|
||||||
|
|
||||||
from akarpov.music.api.serializers import SongSerializer
|
from akarpov.music.api.serializers import SongSerializer
|
||||||
from akarpov.music.models import (
|
from akarpov.music.models import RadioSong, Song, UserListenHistory, UserMusicProfile
|
||||||
AnonMusicUser,
|
|
||||||
AnonMusicUserHistory,
|
|
||||||
RadioSong,
|
|
||||||
Song,
|
|
||||||
UserListenHistory,
|
|
||||||
UserMusicProfile,
|
|
||||||
)
|
|
||||||
from akarpov.music.services import yandex, youtube
|
from akarpov.music.services import yandex, youtube
|
||||||
from akarpov.music.services.file import load_dir, load_file
|
from akarpov.music.services.file import load_dir, load_file
|
||||||
from akarpov.utils.celery import get_scheduled_tasks_name
|
from akarpov.utils.celery import get_scheduled_tasks_name
|
||||||
|
@ -105,7 +96,7 @@ def start_next_song(previous_ids: list):
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def listen_to_song(song_id, user_id=None, anon=True):
|
def listen_to_song(song_id, user_id=None):
|
||||||
# protection from multiple listen,
|
# protection from multiple listen,
|
||||||
# check that last listen by user was more than the length of the song
|
# check that last listen by user was more than the length of the song
|
||||||
# and last listened song is not the same
|
# and last listened song is not the same
|
||||||
|
@ -113,38 +104,15 @@ def listen_to_song(song_id, user_id=None, anon=True):
|
||||||
s.played += 1
|
s.played += 1
|
||||||
s.save(update_fields=["played"])
|
s.save(update_fields=["played"])
|
||||||
if user_id:
|
if user_id:
|
||||||
if anon:
|
|
||||||
try:
|
try:
|
||||||
anon_user = AnonMusicUser.objects.get(id=user_id)
|
last_listen = UserListenHistory.objects.filter(user_id=user_id).latest("id")
|
||||||
except AnonMusicUser.DoesNotExist:
|
|
||||||
anon_user = AnonMusicUser.objects.create(id=user_id)
|
|
||||||
try:
|
|
||||||
last_listen = AnonMusicUserHistory.objects.filter(
|
|
||||||
user_id=user_id
|
|
||||||
).last()
|
|
||||||
except AnonMusicUserHistory.DoesNotExist:
|
|
||||||
last_listen = None
|
|
||||||
if (
|
|
||||||
last_listen
|
|
||||||
and last_listen.song_id == song_id
|
|
||||||
or last_listen
|
|
||||||
and last_listen.created + timedelta(seconds=s.length) > now()
|
|
||||||
):
|
|
||||||
return
|
|
||||||
AnonMusicUserHistory.objects.create(
|
|
||||||
user=anon_user,
|
|
||||||
song_id=song_id,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
last_listen = UserListenHistory.objects.filter(user_id=user_id).last()
|
|
||||||
except UserListenHistory.DoesNotExist:
|
except UserListenHistory.DoesNotExist:
|
||||||
last_listen = None
|
last_listen = None
|
||||||
if (
|
if (
|
||||||
last_listen
|
last_listen
|
||||||
and last_listen.song_id == song_id
|
and last_listen.song_id == song_id
|
||||||
or last_listen
|
or last_listen
|
||||||
and last_listen.created + timedelta(seconds=s.length) > now()
|
and last_listen.created + s.length > now()
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
UserListenHistory.objects.create(
|
UserListenHistory.objects.create(
|
||||||
|
@ -165,9 +133,7 @@ def listen_to_song(song_id, user_id=None, anon=True):
|
||||||
artist_name = song.artists_names
|
artist_name = song.artists_names
|
||||||
track_name = song.name
|
track_name = song.name
|
||||||
timestamp = int(timezone.now().timestamp())
|
timestamp = int(timezone.now().timestamp())
|
||||||
network.scrobble(
|
network.scrobble(artist=artist_name, title=track_name, timestamp=timestamp)
|
||||||
artist=artist_name, title=track_name, timestamp=timestamp
|
|
||||||
)
|
|
||||||
except UserMusicProfile.DoesNotExist:
|
except UserMusicProfile.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user