spotify/src/utils/spotify.ts
2025-10-16 15:52:07 +02:00

228 lines
7.1 KiB
TypeScript

import SpotifyWebApi from 'spotify-web-api-js';
import { SpotifyTrack, SpotifyUser, RecentlyPlayedItem, SpotifyPlaylist } from '../types';
const SPOTIFY_CLIENT_ID = (import.meta as any).env.VITE_SPOTIFY_CLIENT_ID;
// Use same-origin callback for prod proxy server; env can override
const REDIRECT_URI = (import.meta as any).env.VITE_REDIRECT_URI || (typeof window !== 'undefined' ? `${window.location.origin}/callback.html` : 'http://localhost:3000/callback.html');
// Debug: Check what redirect URI is being used
console.log('🔍 Debug - Current redirect URI:', REDIRECT_URI);
console.log('🔍 Debug - Environment VITE_REDIRECT_URI:', (import.meta as any).env.VITE_REDIRECT_URI);
export const spotifyApi = new SpotifyWebApi();
export const getSpotifyAuthUrl = (): string => {
const scopes = [
'user-read-private',
'user-read-email',
'user-read-recently-played',
'user-top-read',
'playlist-read-private',
'playlist-read-collaborative',
'playlist-modify-public',
'playlist-modify-private',
'user-read-playback-state',
'user-modify-playback-state',
'user-read-currently-playing',
'ugc-image-upload', // Required for uploading playlist cover images
].join(' ');
const params = new URLSearchParams({
client_id: SPOTIFY_CLIENT_ID,
response_type: 'code',
redirect_uri: REDIRECT_URI,
scope: scopes,
show_dialog: 'true',
});
const authUrl = `https://accounts.spotify.com/authorize?${params.toString()}`;
console.log('🔍 Debug - Generated Spotify Auth URL:', authUrl);
return authUrl;
};
export const exchangeCodeForToken = async (code: string): Promise<{
access_token: string;
refresh_token: string;
expires_in: number;
}> => {
const body = new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: REDIRECT_URI,
});
console.log('🔍 Spotify - Token exchange request:', {
code: code.substring(0, 20) + '...',
redirect_uri: REDIRECT_URI,
client_id: SPOTIFY_CLIENT_ID
});
const response = await fetch('https://accounts.spotify.com/api/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Basic ${btoa(`${SPOTIFY_CLIENT_ID}:${(import.meta as any).env.VITE_SPOTIFY_CLIENT_SECRET}`)}`,
},
body: body,
});
console.log('🔍 Spotify - Token exchange response:', {
status: response.status,
statusText: response.statusText,
ok: response.ok
});
if (!response.ok) {
const errorText = await response.text();
console.error('🔍 Spotify - Token exchange error:', errorText);
throw new Error(`Failed to exchange code for token: ${response.status} ${response.statusText}`);
}
const tokenData = await response.json();
console.log('🔍 Spotify - Token exchange success:', !!tokenData.access_token);
return tokenData;
};
export const refreshAccessToken = async (refreshToken: string): Promise<{
access_token: string;
expires_in: number;
}> => {
const response = await fetch('https://accounts.spotify.com/api/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Basic ${btoa(`${SPOTIFY_CLIENT_ID}:${(import.meta as any).env.VITE_SPOTIFY_CLIENT_SECRET}`)}`,
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
}),
});
if (!response.ok) {
throw new Error('Failed to refresh access token');
}
return response.json();
};
export const initializeSpotifyApi = (accessToken: string): void => {
spotifyApi.setAccessToken(accessToken);
};
export const fetchUserProfile = async (accessToken: string): Promise<SpotifyUser> => {
spotifyApi.setAccessToken(accessToken);
return spotifyApi.getMe();
};
export const fetchRecentlyPlayed = async (accessToken: string, limit = 20): Promise<RecentlyPlayedItem[]> => {
spotifyApi.setAccessToken(accessToken);
const response = await spotifyApi.getMyRecentlyPlayedTracks({ limit });
return response.items;
};
export const fetchTopTracks = async (accessToken: string, timeRange = 'short_term', limit = 20): Promise<SpotifyTrack[]> => {
spotifyApi.setAccessToken(accessToken);
const response = await spotifyApi.getMyTopTracks({ time_range: timeRange, limit });
return response.items;
};
export const fetchTopArtists = async (accessToken: string, timeRange = 'short_term', limit = 20) => {
spotifyApi.setAccessToken(accessToken);
return spotifyApi.getMyTopArtists({ time_range: timeRange, limit });
};
export const createPlaylist = async (
accessToken: string,
userId: string,
name: string,
description: string
): Promise<SpotifyPlaylist> => {
spotifyApi.setAccessToken(accessToken);
const playlist = await spotifyApi.createPlaylist(userId, {
name,
description,
public: false,
});
return playlist;
};
export const addTracksToPlaylist = async (
accessToken: string,
playlistId: string,
trackUris: string[]
): Promise<void> => {
spotifyApi.setAccessToken(accessToken);
await spotifyApi.addTracksToPlaylist(playlistId, trackUris);
};
export const playTrack = async (accessToken: string, trackUri: string): Promise<void> => {
spotifyApi.setAccessToken(accessToken);
await spotifyApi.play({
uris: [trackUri],
});
};
export const pausePlayback = async (accessToken: string): Promise<void> => {
spotifyApi.setAccessToken(accessToken);
await spotifyApi.pause();
};
export const getCurrentPlayback = async (accessToken: string) => {
spotifyApi.setAccessToken(accessToken);
return spotifyApi.getMyCurrentPlaybackState();
};
// Utility function to extract track features for playlist generation
export const analyzeTrackFeatures = (tracks: SpotifyTrack[]) => {
const features = {
genres: new Set<string>(),
artists: new Set<string>(),
avgPopularity: 0,
totalDuration: 0,
decades: new Set<string>(),
};
tracks.forEach(track => {
// Extract genres from artists (simplified)
track.artists.forEach(artist => features.artists.add(artist.name));
// Calculate average popularity
features.avgPopularity += track.popularity;
// Add duration
features.totalDuration += track.duration_ms;
// Estimate decade from album name or other clues (simplified)
// This would typically use more sophisticated analysis
});
features.avgPopularity /= tracks.length;
return features;
};
// Generate mixed playlist based on both users' music
export const generateMixedPlaylist = (
user1Tracks: SpotifyTrack[],
user2Tracks: SpotifyTrack[],
playlistName: string = "Our Mixed Vibes"
): SpotifyTrack[] => {
const allTracks = [...user1Tracks, ...user2Tracks];
// Remove duplicates based on track ID
const uniqueTracks = allTracks.filter((track, index, self) =>
index === self.findIndex(t => t.id === track.id)
);
// Sort by popularity and mix them
const sortedByPopularity = uniqueTracks.sort((a, b) => b.popularity - a.popularity);
// Take top tracks and shuffle them for variety
const topTracks = sortedByPopularity.slice(0, 30);
const shuffled = topTracks.sort(() => Math.random() - 0.5);
return shuffled.slice(0, 25); // Return 25 tracks for the playlist
};