formatting head

This commit is contained in:
Acid
2025-12-27 15:33:48 -05:00
commit 6f3bdd61d4
63 changed files with 6935 additions and 0 deletions
View File
+1
View File
@@ -0,0 +1 @@
# Register your models here.
+6
View File
@@ -0,0 +1,6 @@
from django.apps import AppConfig
class SpotifyConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'spotify'
+24
View File
@@ -0,0 +1,24 @@
# Generated by Django 5.2.4 on 2025-08-18 22:20
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name='SpotifyToken',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('user', models.CharField(max_length=50, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('refresh_token', models.CharField(max_length=150)),
('access_token', models.CharField(max_length=150)),
('expires_in', models.DateTimeField()),
('token_type', models.CharField(max_length=50)),
],
),
]
View File
+10
View File
@@ -0,0 +1,10 @@
from django.db import models
class SpotifyToken(models.Model):
user = models.CharField(max_length=50, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
refresh_token = models.CharField(max_length=150)
access_token = models.CharField(max_length=150)
expires_in = models.DateTimeField()
token_type = models.CharField(max_length=50)
+15
View File
@@ -0,0 +1,15 @@
from rest_framework import serializers
from .models import SpotifyToken
class TokenSerializer(serializers.ModelSerializer):
class Meta: # pyright: ignore
model = SpotifyToken
fields = (
'id',
'user',
'access_token',
'token_type',
'expires_in',
'refresh_token',
)
+1
View File
@@ -0,0 +1 @@
# Create your tests here.
+12
View File
@@ -0,0 +1,12 @@
from django.urls import path
from .views import AuthURL, CurrentSong, PauseSong, PlaySong, SpotifyList, spotify_callback, IsAuthenticated
urlpatterns = [
path('get-auth-url', AuthURL.as_view()),
path('redirect', spotify_callback),
path('is-auth', IsAuthenticated.as_view()),
path('current-song', CurrentSong.as_view()),
path('tokens', SpotifyList.as_view()),
path('play', PlaySong.as_view()),
path('pause', PauseSong.as_view()),
]
+120
View File
@@ -0,0 +1,120 @@
from django.utils import timezone
from .models import SpotifyToken
import os
from dotenv import load_dotenv
from datetime import timedelta
from requests import post, put, get
load_dotenv()
CLIENT_ID = os.getenv('CLIENT_ID')
CLIENT_SECRET = os.getenv('CLIENT_SECRET')
BASE_URL = 'https://api.spotify.com/v1/me/'
def get_user_token(session_id):
user_tokens = SpotifyToken.objects.filter(user=session_id)
# DEBUG
print('## get_user_token()##')
print('user_tokens:', user_tokens.first())
print('session_id', session_id)
if user_tokens.exists():
return user_tokens[0]
else:
return None
def update_or_create_user_tokens(session_id, access_token, token_type, expires_in, refresh_token):
# default expires in = 3600 from spotify
tokens = get_user_token(session_id)
expires_in = timezone.now() + timedelta(seconds=expires_in)
# update if exist
if tokens:
tokens.access_token = access_token
tokens.expires_in = expires_in
tokens.refresh_token = refresh_token
tokens.token_type = token_type
tokens.save(update_fields=['access_token', 'expires_in', 'refresh_token', 'token_type'])
else: # create on db
tokens = SpotifyToken(
user=session_id,
access_token=access_token,
token_type=token_type,
expires_in=expires_in,
refresh_token=refresh_token,
)
tokens.save()
def is_spotify_authenticated(session_id):
tokens = get_user_token(session_id)
if tokens:
expiry = tokens.expires_in
return True
return False
def refresh_spotify_token(session_id):
refresh_token = get_user_token(session_id).refresh_token
_response = post(
'https://accounts.spotify.com/api/token',
data={
'grant_type': 'refresh_token',
'refresh_token': refresh_token,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
},
).json()
# dont need these just in case
access_token = _response.get('access_token')
token_type = _response.get('token_type')
expires_in = _response.get('expires_in')
update_or_create_user_tokens(session_id, access_token, token_type, expires_in, refresh_token)
def spotify_api_request(session_id, endpoint, post_=False, put_=False):
tokens = get_user_token(session_id)
# endpoint = 'player/currently-playing'
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + tokens.access_token,
}
if post_:
response = post(BASE_URL + endpoint, headers=headers)
try:
return response.json()
except:
return {'Error': 'Issue with POST request'}
if put_:
response = put(BASE_URL + endpoint, headers=headers)
try:
return response.json()
except:
return {'Error': 'Issue with PUT request'}
# GET request (default)
spotify_response = get(BASE_URL + endpoint, headers=headers)
try:
return spotify_response.json()
except:
return {'Error': 'Issue with GET request', 'status_code': spotify_response.status_code}
def play_song(session_id):
return spotify_api_request(session_id, 'player/play', put_=True)
def pause_song(session_id):
return spotify_api_request(session_id, 'player/pause', put_=True)
+200
View File
@@ -0,0 +1,200 @@
import os
import json
import base64
from dotenv import load_dotenv
from requests import Request, post
from rest_framework.views import APIView
from rest_framework import status, generics
from rest_framework.response import Response
from django.shortcuts import redirect
from .util import is_spotify_authenticated, update_or_create_user_tokens, spotify_api_request, play_song, pause_song
from api.models import Room
from .models import SpotifyToken
from .serializers import TokenSerializer
load_dotenv()
if os.getenv('Docker'):
REACT_PORT = os.getenv('REACT_PORT')
API_PORT = os.getenv('API_PORT')
WEBSITE = 'https://gxnet.cc'
REDIRECT_URI = 'https://gxnet.cc/spotify/redirect'
else:
REACT_PORT = 5173
API_PORT = 8000
REDIRECT_URI = f'http://127.0.0.1:{API_PORT}/spotify/redirect'
CLIENT_ID = os.getenv('CLIENT_ID')
CLIENT_SECRET = os.getenv('CLIENT_SECRET')
class AuthURL(APIView):
def get(self, request):
if not request.session.exists(request.session.session_key):
request.session.create()
orig_sid = request.session.session_key
room_code = request.GET.get('state')
state_obj = {"room": room_code, "sid": orig_sid}
state = base64.urlsafe_b64encode(json.dumps(state_obj).encode()).decode()
scopes = 'user-read-playback-state user-modify-playback-state user-read-currently-playing'
url = (
Request(
'GET',
'https://accounts.spotify.com/authorize',
params={
'scope': scopes,
'response_type': 'code',
'client_id': CLIENT_ID,
'redirect_uri': REDIRECT_URI,
'state': state,
},
)
.prepare()
.url
)
# sanity debug
print('🎧client_id:', CLIENT_ID)
print('🎧 orig_sid:', orig_sid)
print(' 🎧url:', url)
return Response({'url': url}, status=status.HTTP_200_OK)
# https://developer-assets.spotifycdn.com/images/documentation/web-api/auth-code-flow.png
def spotify_callback(request):
code = request.GET.get('code')
raw_state = request.GET.get("state") or ""
state = json.loads(base64.urlsafe_b64decode(raw_state).decode())
error = request.GET.get('error')
room_code = state.get("room") # <- plain string
orig_sid = state.get("sid")
response = post(
'https://accounts.spotify.com/api/token',
data={
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': REDIRECT_URI,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
},
).json()
access_token = response.get('access_token')
token_type = response.get('token_type')
refresh_token = response.get('refresh_token')
expires_in = response.get('expires_in')
error = response.get('error')
if not request.session.exists(request.session.session_key):
request.session.create()
cur_sid = request.session.session_key
# if session changed, rebind room host
room = Room.objects.filter(code=room_code, host=orig_sid).first()
if room and cur_sid != orig_sid:
room.host = cur_sid
room.save(update_fields=["host"])
update_or_create_user_tokens(request.session.session_key, access_token, token_type, expires_in, refresh_token)
if room_code:
request.session['room_code'] = room_code
print('🎧### On spotify Callback() ## 🎧')
print("Host:", request.get_host())
print("Cookies:", request.COOKIES)
print("Session key:", request.session.session_key)
print('room_code REDIRECT:', room_code)
if WEBSITE:
target = f'{WEBSITE}/room/{room_code}?code={room_code}&auth=done'
else:
target = f'http://127.0.0.1:{REACT_PORT}/room/{room_code}?code={room_code}&auth=done'
return redirect(target)
class IsAuthenticated(APIView):
def get(self, request):
is_auth = is_spotify_authenticated(self.request.session.session_key)
return Response({'status': is_auth, 'message': '🎧'})
class CurrentSong(APIView):
def get(self, request):
room_code = self.request.session.get('room_code')
print('DEBUG ; room_code:', room_code)
room = Room.objects.filter(code=room_code).first()
if room:
host = room.host
else:
return Response({'message': 'not a room'}, status=status.HTTP_404_NOT_FOUND)
endpoint = 'player/currently-playing'
response = spotify_api_request(host, endpoint)
if 'error' in response or 'item' not in response:
return Response({'error': 'error response from spotify'}, status=status.HTTP_400_BAD_REQUEST)
else:
item = response.get('item')
duration = item.get('duration_ms')
progress = response.get('progress_ms')
album_cover = item.get('album').get('images')[0].get('url')
is_playing = response.get('is_playing')
song_id = item.get('id')
artist_string = ""
for i, artist in enumerate(item.get('artists')):
if i > 0:
artist_string += ", "
name = artist.get('name')
artist_string += name
song = {
'title': item.get('name'),
'artist': artist_string,
'duration': duration,
'time': progress,
'image_url': album_cover,
'is_playing': is_playing,
'votes': 0,
'id': song_id,
}
return Response(song, status=status.HTTP_200_OK)
class SpotifyList(generics.ListAPIView):
queryset = SpotifyToken.objects.all()
serializer_class = TokenSerializer
class PauseSong(APIView):
def put(self, request):
room_code = self.request.session.get('room_code')
room = Room.objects.filter(code=room_code)[0]
if self.request.session.session_key == room.host or room.guest_can_pause:
upstream_response = pause_song(room.host)
return Response(upstream_response, status=status.HTTP_200_OK)
return Response({'Not allowed': 'you are not the host'}, status=status.HTTP_403_FORBIDDEN)
class PlaySong(APIView):
def put(self, request):
room_code = self.request.session.get('room_code')
room = Room.objects.filter(code=room_code)[0]
if self.request.session.session_key == room.host or room.guest_can_pause:
upstream_response = play_song(room.host)
return Response(upstream_response, status=status.HTTP_200_OK)
return Response({'Not allowed': 'you are not the host'}, status=status.HTTP_403_FORBIDDEN)