views.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import json
  2. import logging
  3. import pytz
  4. from django.conf import settings
  5. from django.db.models.fields import timezone
  6. from django.http import HttpResponseRedirect
  7. from django.urls import reverse
  8. from django.utils import timezone
  9. from django.views.decorators.csrf import csrf_exempt
  10. from django.views.generic import FormView
  11. from django.views.generic.list import ListView
  12. from rest_framework import status
  13. from rest_framework.decorators import api_view, permission_classes
  14. from rest_framework.permissions import IsAuthenticated
  15. from rest_framework.response import Response
  16. from scrobbles.constants import (
  17. JELLYFIN_AUDIO_ITEM_TYPES,
  18. JELLYFIN_VIDEO_ITEM_TYPES,
  19. )
  20. from scrobbles.forms import ScrobbleForm
  21. from scrobbles.imdb import lookup_video_from_imdb
  22. from scrobbles.models import Scrobble
  23. from scrobbles.scrobblers import (
  24. jellyfin_scrobble_track,
  25. jellyfin_scrobble_video,
  26. manual_scrobble_event,
  27. manual_scrobble_video,
  28. mopidy_scrobble_podcast,
  29. mopidy_scrobble_track,
  30. )
  31. from scrobbles.serializers import ScrobbleSerializer
  32. from scrobbles.thesportsdb import lookup_event_from_thesportsdb
  33. from vrobbler.apps.music.aggregators import (
  34. scrobble_counts,
  35. top_artists,
  36. top_tracks,
  37. week_of_scrobbles,
  38. )
  39. logger = logging.getLogger(__name__)
  40. class RecentScrobbleList(ListView):
  41. model = Scrobble
  42. def get_context_data(self, **kwargs):
  43. data = super().get_context_data(**kwargs)
  44. user = self.request.user
  45. now = timezone.now()
  46. if user.is_authenticated:
  47. if user.profile:
  48. timezone.activate(pytz.timezone(user.profile.timezone))
  49. now = timezone.localtime(timezone.now())
  50. data['now_playing_list'] = Scrobble.objects.filter(
  51. in_progress=True,
  52. is_paused=False,
  53. timestamp__lte=now,
  54. user=user,
  55. )
  56. completed_for_user = Scrobble.objects.filter(
  57. played_to_completion=True, user=user
  58. )
  59. data['video_scrobble_list'] = completed_for_user.filter(
  60. video__isnull=False
  61. ).order_by('-timestamp')[:15]
  62. data['podcast_scrobble_list'] = completed_for_user.filter(
  63. podcast_episode__isnull=False
  64. ).order_by('-timestamp')[:15]
  65. data['sport_scrobble_list'] = completed_for_user.filter(
  66. sport_event__isnull=False
  67. ).order_by('-timestamp')[:15]
  68. # data['top_daily_tracks'] = top_tracks()
  69. data['top_weekly_tracks'] = top_tracks(user, filter='week')
  70. data['top_monthly_tracks'] = top_tracks(user, filter='month')
  71. # data['top_daily_artists'] = top_artists()
  72. data['top_weekly_artists'] = top_artists(user, filter='week')
  73. data['top_monthly_artists'] = top_artists(user, filter='month')
  74. data["weekly_data"] = week_of_scrobbles(user=user)
  75. data['counts'] = scrobble_counts(user)
  76. data['imdb_form'] = ScrobbleForm
  77. return data
  78. def get_queryset(self):
  79. return Scrobble.objects.filter(
  80. track__isnull=False, in_progress=False
  81. ).order_by('-timestamp')[:15]
  82. class ManualScrobbleView(FormView):
  83. form_class = ScrobbleForm
  84. template_name = 'scrobbles/manual_form.html'
  85. def form_valid(self, form):
  86. item_id = form.cleaned_data.get('item_id')
  87. data_dict = None
  88. if 'tt' in item_id:
  89. data_dict = lookup_video_from_imdb(
  90. form.cleaned_data.get('item_id')
  91. )
  92. if data_dict:
  93. manual_scrobble_video(data_dict, self.request.user.id)
  94. if not data_dict:
  95. logger.debug(f"Looking for sport event with ID {item_id}")
  96. data_dict = lookup_event_from_thesportsdb(
  97. form.cleaned_data.get('item_id')
  98. )
  99. if data_dict:
  100. manual_scrobble_event(data_dict, self.request.user.id)
  101. return HttpResponseRedirect(reverse("home"))
  102. @csrf_exempt
  103. @api_view(['GET'])
  104. def scrobble_endpoint(request):
  105. """List all Scrobbles, or create a new Scrobble"""
  106. scrobble = Scrobble.objects.all()
  107. serializer = ScrobbleSerializer(scrobble, many=True)
  108. return Response(serializer.data)
  109. @csrf_exempt
  110. @api_view(['POST'])
  111. @permission_classes([IsAuthenticated])
  112. def jellyfin_websocket(request):
  113. data_dict = request.data
  114. # For making things easier to build new input processors
  115. if getattr(settings, "DUMP_REQUEST_DATA", False):
  116. json_data = json.dumps(data_dict, indent=4)
  117. logger.debug(f"{json_data}")
  118. scrobble = None
  119. media_type = data_dict.get("ItemType", "")
  120. if media_type in JELLYFIN_AUDIO_ITEM_TYPES:
  121. scrobble = jellyfin_scrobble_track(data_dict, request.user.id)
  122. if media_type in JELLYFIN_VIDEO_ITEM_TYPES:
  123. scrobble = jellyfin_scrobble_video(data_dict, request.user.id)
  124. if not scrobble:
  125. return Response({}, status=status.HTTP_400_BAD_REQUEST)
  126. return Response({'scrobble_id': scrobble.id}, status=status.HTTP_200_OK)
  127. @csrf_exempt
  128. @api_view(['POST'])
  129. @permission_classes([IsAuthenticated])
  130. def mopidy_websocket(request):
  131. try:
  132. data_dict = json.loads(request.data)
  133. except TypeError:
  134. logger.warning('Received Mopidy data as dict, rather than a string')
  135. data_dict = request.data
  136. # For making things easier to build new input processors
  137. if getattr(settings, "DUMP_REQUEST_DATA", False):
  138. json_data = json.dumps(data_dict, indent=4)
  139. logger.debug(f"{json_data}")
  140. if 'podcast' in data_dict.get('mopidy_uri'):
  141. scrobble = mopidy_scrobble_podcast(data_dict, request.user.id)
  142. else:
  143. scrobble = mopidy_scrobble_track(data_dict, request.user.id)
  144. if not scrobble:
  145. return Response({}, status=status.HTTP_400_BAD_REQUEST)
  146. return Response({'scrobble_id': scrobble.id}, status=status.HTTP_200_OK)
  147. @csrf_exempt
  148. @api_view(['GET'])
  149. @permission_classes([IsAuthenticated])
  150. def scrobble_finish(request, uuid):
  151. user = request.user
  152. if not user.is_authenticated:
  153. return Response({}, status=status.HTTP_403_FORBIDDEN)
  154. scrobble = Scrobble.objects.filter(user=user, uuid=uuid).first()
  155. if not scrobble:
  156. return Response({}, status=status.HTTP_404_NOT_FOUND)
  157. scrobble.stop(force_finish=True)
  158. return Response(
  159. {'id': scrobble.id, 'status': scrobble.status},
  160. status=status.HTTP_200_OK,
  161. )
  162. @csrf_exempt
  163. @api_view(['GET'])
  164. @permission_classes([IsAuthenticated])
  165. def scrobble_cancel(request, uuid):
  166. user = request.user
  167. if not user.is_authenticated:
  168. return Response({}, status=status.HTTP_403_FORBIDDEN)
  169. scrobble = Scrobble.objects.filter(user=user, uuid=uuid).first()
  170. if not scrobble:
  171. return Response({}, status=status.HTTP_404_NOT_FOUND)
  172. scrobble.cancel()
  173. return Response(
  174. {'id': scrobble.id, 'status': 'cancelled'}, status=status.HTTP_200_OK
  175. )