[scrobbles] Update webhook view permissions and small template changes
This commit is contained in:
@ -48,6 +48,7 @@ from rest_framework.decorators import (
|
||||
from rest_framework.parsers import MultiPartParser
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from scrobbles.api import serializers
|
||||
from scrobbles.constants import (
|
||||
LONG_PLAY_MEDIA,
|
||||
@ -80,6 +81,14 @@ User = get_user_model()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WebhookView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@property
|
||||
def logger(self):
|
||||
return logger
|
||||
|
||||
|
||||
class ScrobbleableListView(ListView):
|
||||
model = None
|
||||
paginate_by = 200
|
||||
@ -427,8 +436,8 @@ class KoReaderImportCreateView(LoginRequiredMixin, JsonableResponseMixin, Create
|
||||
return HttpResponseRedirect(self.request.META.get("HTTP_REFERER"))
|
||||
|
||||
|
||||
@permission_classes([IsAuthenticated])
|
||||
@api_view(["GET"])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def lastfm_import(request):
|
||||
lfm_import, created = LastFmImport.objects.get_or_create(
|
||||
user=request.user, processed_finished__isnull=True
|
||||
@ -439,155 +448,143 @@ def lastfm_import(request):
|
||||
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@api_view(["POST"])
|
||||
def web_scrobbler_webhook(request):
|
||||
"""Note, this does not work as implemented. For some reason the web
|
||||
scrobbler tool kicks back a bad response code and does not seem to POST data
|
||||
as expected.
|
||||
class WebScrobblerWebhookView(WebhookView):
|
||||
def post(self, request):
|
||||
"""Note, this does not work as implemented. For some reason the web
|
||||
scrobbler tool kicks back a bad response code and does not seem to POST data
|
||||
as expected.
|
||||
|
||||
"""
|
||||
user_id = request.user.id
|
||||
|
||||
logger.info(
|
||||
"[web_scrobbler_webhook] called",
|
||||
extra={
|
||||
"post_data": request.POST,
|
||||
"user_id": user_id,
|
||||
},
|
||||
)
|
||||
youtube_id = request.POST.get("uniqueID")
|
||||
if not youtube_id:
|
||||
logger.warning(
|
||||
"[web_scrobbler_webhook] failed",
|
||||
extra={
|
||||
"youtube_id": youtube_id,
|
||||
"user_id": user_id,
|
||||
},
|
||||
)
|
||||
return JsonResponse(
|
||||
{"errors": ["No youtube ID provided"]}, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
playing_state = "started"
|
||||
if request.POST.get("isPlaying") == "false":
|
||||
playing_state = "stopped"
|
||||
scrobble = web_scrobbler_scrobble_media(youtube_id, user_id, status=playing_state)
|
||||
if not scrobble:
|
||||
logger.warning(
|
||||
"[web_scrobbler_webhook] failed",
|
||||
extra={
|
||||
"youtube_id": youtube_id,
|
||||
"user_id": user_id,
|
||||
},
|
||||
)
|
||||
return JsonResponse(
|
||||
{"errors": ["No scrobble found for user or video"]},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"[web_scrobbler_webhook] finished",
|
||||
extra={"scrobble_id": scrobble.id},
|
||||
)
|
||||
return JsonResponse({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@permission_classes([IsAuthenticated])
|
||||
@api_view(["POST"])
|
||||
def jellyfin_webhook(request):
|
||||
post_data = request.data
|
||||
logger.info(
|
||||
"[jellyfin_webhook] called",
|
||||
extra={
|
||||
"post_data": post_data,
|
||||
"user_id": request.user.id,
|
||||
},
|
||||
)
|
||||
|
||||
in_progress = post_data.get("NotificationType", "") == "PlaybackProgress"
|
||||
is_music = post_data.get("ItemType", "") == "Audio"
|
||||
|
||||
# Disregard progress updates
|
||||
if in_progress and is_music:
|
||||
logger.info(
|
||||
"[jellyfin_webhook] ignoring update of music in progress",
|
||||
extra={"post_data": post_data},
|
||||
)
|
||||
return Response({}, status=status.HTTP_304_NOT_MODIFIED)
|
||||
|
||||
scrobble = jellyfin_scrobble_media(post_data, request.user.id)
|
||||
|
||||
if not scrobble:
|
||||
return Response({}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
logger.info(
|
||||
"[jellyfin_webhook] finished",
|
||||
extra={"scrobble_id": scrobble.id},
|
||||
)
|
||||
return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@permission_classes([IsAuthenticated])
|
||||
@api_view(["POST"])
|
||||
def mopidy_webhook(request):
|
||||
try:
|
||||
data_dict = json.loads(request.data)
|
||||
except TypeError:
|
||||
data_dict = request.data
|
||||
|
||||
logger.info(
|
||||
"[mopidy_webhook] called",
|
||||
extra={
|
||||
"post_data": data_dict,
|
||||
"user_id": request.user.id,
|
||||
},
|
||||
)
|
||||
|
||||
scrobble = mopidy_scrobble_media(data_dict, request.user.id)
|
||||
|
||||
if not scrobble:
|
||||
return Response({}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@permission_classes([IsAuthenticated])
|
||||
@api_view(["POST"])
|
||||
def gps_webhook(request):
|
||||
try:
|
||||
data_dict = json.loads(request.data)
|
||||
except TypeError:
|
||||
data_dict = request.data
|
||||
|
||||
logger.info(
|
||||
"[geolocation_webhook] called",
|
||||
extra={
|
||||
"post_data": data_dict,
|
||||
"user_id": 1,
|
||||
},
|
||||
)
|
||||
|
||||
# TODO Fix this so we have to authenticate!
|
||||
user_id = 1
|
||||
if request.user.id:
|
||||
"""
|
||||
user_id = request.user.id
|
||||
|
||||
scrobble = gpslogger_scrobble_location(data_dict, user_id)
|
||||
self.logger.info(
|
||||
"[web_scrobbler_webhook] called",
|
||||
extra={
|
||||
"post_data": request.POST,
|
||||
"user_id": user_id,
|
||||
},
|
||||
)
|
||||
youtube_id = request.POST.get("uniqueID")
|
||||
if not youtube_id:
|
||||
self.logger.warning(
|
||||
"[web_scrobbler_webhook] failed",
|
||||
extra={
|
||||
"youtube_id": youtube_id,
|
||||
"user_id": user_id,
|
||||
},
|
||||
)
|
||||
return JsonResponse(
|
||||
{"errors": ["No youtube ID provided"]}, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
if not scrobble:
|
||||
return Response({}, status=status.HTTP_200_OK)
|
||||
playing_state = "started"
|
||||
if request.POST.get("isPlaying") == "false":
|
||||
playing_state = "stopped"
|
||||
scrobble = web_scrobbler_scrobble_media(
|
||||
youtube_id, user_id, status=playing_state
|
||||
)
|
||||
if not scrobble:
|
||||
self.logger.warning(
|
||||
"[web_scrobbler_webhook] failed",
|
||||
extra={
|
||||
"youtube_id": youtube_id,
|
||||
"user_id": user_id,
|
||||
},
|
||||
)
|
||||
return JsonResponse(
|
||||
{"errors": ["No scrobble found for user or video"]},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)
|
||||
self.logger.info(
|
||||
"[web_scrobbler_webhook] finished",
|
||||
extra={"scrobble_id": scrobble.id},
|
||||
)
|
||||
return JsonResponse({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class JellyfinWebhookView(WebhookView):
|
||||
def post(self, request):
|
||||
post_data = request.data
|
||||
self.logger.info(
|
||||
"[jellyfin_webhook] called",
|
||||
extra={
|
||||
"post_data": post_data,
|
||||
"user_id": request.user.id,
|
||||
},
|
||||
)
|
||||
|
||||
in_progress = post_data.get("NotificationType", "") == "PlaybackProgress"
|
||||
is_music = post_data.get("ItemType", "") == "Audio"
|
||||
|
||||
if in_progress and is_music:
|
||||
self.logger.info(
|
||||
"[jellyfin_webhook] ignoring update of music in progress",
|
||||
extra={"post_data": post_data},
|
||||
)
|
||||
return Response({}, status=status.HTTP_304_NOT_MODIFIED)
|
||||
|
||||
scrobble = jellyfin_scrobble_media(post_data, request.user.id)
|
||||
|
||||
if not scrobble:
|
||||
return Response({}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
self.logger.info(
|
||||
"[jellyfin_webhook] finished",
|
||||
extra={"scrobble_id": scrobble.id},
|
||||
)
|
||||
return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class MopidyWebhookView(WebhookView):
|
||||
def post(self, request):
|
||||
try:
|
||||
data_dict = json.loads(request.data)
|
||||
except TypeError:
|
||||
data_dict = request.data
|
||||
|
||||
self.logger.info(
|
||||
"[mopidy_webhook] called",
|
||||
extra={
|
||||
"post_data": data_dict,
|
||||
"user_id": request.user.id,
|
||||
},
|
||||
)
|
||||
|
||||
scrobble = mopidy_scrobble_media(data_dict, request.user.id)
|
||||
|
||||
if not scrobble:
|
||||
return Response({}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class GPSWebhookView(WebhookView):
|
||||
def post(self, request):
|
||||
try:
|
||||
data_dict = json.loads(request.data)
|
||||
except TypeError:
|
||||
data_dict = request.data
|
||||
|
||||
self.logger.info(
|
||||
"[geolocation_webhook] called",
|
||||
extra={
|
||||
"post_data": data_dict,
|
||||
"user_id": request.user.id,
|
||||
},
|
||||
)
|
||||
|
||||
scrobble = gpslogger_scrobble_location(data_dict, request.user.id)
|
||||
|
||||
if not scrobble:
|
||||
return Response({}, status=status.HTTP_200_OK)
|
||||
|
||||
return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@permission_classes([IsAuthenticated])
|
||||
@api_view(["POST"])
|
||||
@parser_classes([MultiPartParser])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def import_audioscrobbler_file(request):
|
||||
"""Takes a TSV file in the Audioscrobbler format, saves it and processes the
|
||||
scrobbles.
|
||||
@ -603,8 +600,8 @@ def import_audioscrobbler_file(request):
|
||||
return Response(file_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
@permission_classes([IsAuthenticated])
|
||||
@api_view(["GET"])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def scrobble_start(request, uuid):
|
||||
logger.info(
|
||||
"[scrobble_start] called",
|
||||
@ -694,8 +691,8 @@ def scrobble_longplay_finish(request, uuid):
|
||||
return HttpResponseRedirect(success_url)
|
||||
|
||||
|
||||
@permission_classes([IsAuthenticated])
|
||||
@api_view(["GET"])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def scrobble_finish(request, uuid):
|
||||
user = request.user
|
||||
success_url = request.META.get("HTTP_REFERER")
|
||||
@ -718,8 +715,8 @@ def scrobble_finish(request, uuid):
|
||||
return HttpResponseRedirect(success_url)
|
||||
|
||||
|
||||
@permission_classes([IsAuthenticated])
|
||||
@api_view(["GET"])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def scrobble_cancel(request, uuid):
|
||||
user = request.user
|
||||
success_url = reverse_lazy("vrobbler-home")
|
||||
@ -740,8 +737,8 @@ def scrobble_cancel(request, uuid):
|
||||
return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
|
||||
|
||||
|
||||
@permission_classes([IsAuthenticated])
|
||||
@api_view(["GET"])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def export(request):
|
||||
format = request.GET.get("export_type", "csv")
|
||||
start = request.GET.get("start")
|
||||
@ -1020,23 +1017,23 @@ class ScrobbleSearchView(LoginRequiredMixin, TemplateView):
|
||||
template_name = "scrobbles/scrobble_search.html"
|
||||
|
||||
MEDIA_FIELDS = {
|
||||
"Video": ("video__title", "video__overview"),
|
||||
"Track": ("track__title", None),
|
||||
"PodcastEpisode": ("podcast_episode__title", None),
|
||||
"Book": ("book__title", "book__description"),
|
||||
"Paper": ("paper__title", None),
|
||||
"VideoGame": ("video_game__title", "video_game__description"),
|
||||
"BoardGame": ("board_game__title", "board_game__description"),
|
||||
"Beer": ("beer__name", "beer__style"),
|
||||
"Food": ("food__name", "food__description"),
|
||||
"Puzzle": ("puzzle__name", "puzzle__description"),
|
||||
"Trail": ("trail__name", "trail__location"),
|
||||
"GeoLocation": ("geo_location__name", "geo_location__description"),
|
||||
"Task": ("task__title", "task__description"),
|
||||
"WebPage": ("web_page__title", None),
|
||||
"LifeEvent": ("life_event__title", "life_event__description"),
|
||||
"Mood": ("mood__title", "mood__description"),
|
||||
"BrickSet": ("brick_set__title", None),
|
||||
"Video": ["video__title", "video__overview"],
|
||||
"Track": ["track__title", "track__artist__name", "track__album__name"],
|
||||
"PodcastEpisode": ["podcast_episode__title", None],
|
||||
"Book": ["book__title", "book__summary"],
|
||||
"Paper": ["paper__title", None],
|
||||
"VideoGame": ["video_game__title", "video_game__summary"],
|
||||
"BoardGame": ["board_game__title", "board_game__description"],
|
||||
"Beer": ["beer__name", "beer__style"],
|
||||
"Food": ["food__name", "food__description"],
|
||||
"Puzzle": ["puzzle__name", "puzzle__description"],
|
||||
"Trail": ["trail__name", "trail__location"],
|
||||
"GeoLocation": ["geo_location__title", None],
|
||||
"Task": ["task__title", "task__description"],
|
||||
"WebPage": ["web_page__title", None],
|
||||
"LifeEvent": ["life_event__title", "life_event__description"],
|
||||
"Mood": ["mood__title", "mood__description"],
|
||||
"BrickSet": ["brick_set__title", None],
|
||||
}
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
@ -1074,11 +1071,10 @@ class ScrobbleSearchView(LoginRequiredMixin, TemplateView):
|
||||
q |= Q(source__icontains=query)
|
||||
|
||||
if media_type and media_type in self.MEDIA_FIELDS:
|
||||
title_field, desc_field = self.MEDIA_FIELDS[media_type]
|
||||
if title_field:
|
||||
q |= Q(**{f"{title_field}__icontains": query})
|
||||
if desc_field:
|
||||
q |= Q(**{f"{desc_field}__icontains": query})
|
||||
fields = self.MEDIA_FIELDS[media_type]
|
||||
for field in fields:
|
||||
if field:
|
||||
q |= Q(**{f"{field}__icontains": query})
|
||||
|
||||
scrobbles = scrobbles.filter(q).distinct()
|
||||
else:
|
||||
@ -1087,12 +1083,11 @@ class ScrobbleSearchView(LoginRequiredMixin, TemplateView):
|
||||
base_matches = scrobbles.filter(q).values_list("id", flat=True)
|
||||
all_matching_ids.update(base_matches)
|
||||
|
||||
for mt, (title_field, desc_field) in self.MEDIA_FIELDS.items():
|
||||
for mt, fields in self.MEDIA_FIELDS.items():
|
||||
mt_queries = []
|
||||
if title_field:
|
||||
mt_queries.append(Q(**{f"{title_field}__icontains": query}))
|
||||
if desc_field:
|
||||
mt_queries.append(Q(**{f"{desc_field}__icontains": query}))
|
||||
for field in fields:
|
||||
if field:
|
||||
mt_queries.append(Q(**{f"{field}__icontains": query}))
|
||||
|
||||
if mt_queries:
|
||||
mt_q = mt_queries[0]
|
||||
|
||||
Reference in New Issue
Block a user