@@ -227,6 +270,8 @@
Not Playing
+
+
diff --git a/home/js/app.js b/home/js/app.js
index d76cce1..f8b081c 100644
--- a/home/js/app.js
+++ b/home/js/app.js
@@ -81,7 +81,7 @@ document.addEventListener('keydown', (e) => {
// ============================================
async function init() {
debugLog('Initializing D\'Tunes Player...');
-
+
const vCanvas = document.getElementById('visualizer-canvas');
visualizerCtx = vCanvas.getContext('2d');
resizeVisualizer();
@@ -94,21 +94,27 @@ async function init() {
// Load preferences
preferences.load();
+ // Initialize Spotify authentication
+ if (typeof spotifyAuth !== 'undefined') {
+ spotifyAuth.init();
+ ui.updateSpotifyButton();
+ }
+
// Initialize search
searchManager.init();
// Load homepage content from JioSaavn
homeView.load();
-
+
// Initialize UI listeners
ui.initListeners();
-
+
// Start visualizer
viz.startLoop();
-
+
// Render liked songs
ui.renderLikedSongs();
-
+
// Restore last played track metadata in player (without auto-playing)
try {
const lastTrack = JSON.parse(localStorage.getItem('lastPlayedTrack'));
diff --git a/home/js/spotify-api.js b/home/js/spotify-api.js
new file mode 100644
index 0000000..7b9f375
--- /dev/null
+++ b/home/js/spotify-api.js
@@ -0,0 +1,264 @@
+// ============================================
+// SPOTIFY API SERVICE
+// Handles all Spotify Web API interactions
+// ============================================
+
+const spotifyAPI = {
+ baseUrl: 'https://api.spotify.com/v1',
+
+ // Make authenticated API request with retry
+ fetchWithAuth: async (endpoint, options = {}, retries = 3) => {
+ const token = spotifyAuth.getAccessToken();
+
+ if (!token) {
+ throw new Error('Not authenticated with Spotify');
+ }
+
+ for (let i = 0; i < retries; i++) {
+ try {
+ const url = endpoint.startsWith('http') ? endpoint : `${spotifyAPI.baseUrl}${endpoint}`;
+ const response = await fetch(url, {
+ ...options,
+ headers: {
+ 'Authorization': `Bearer ${token}`,
+ 'Content-Type': 'application/json',
+ ...options.headers,
+ },
+ });
+
+ if (response.status === 401) {
+ // Token expired
+ spotifyAuth.logout();
+ throw new Error('Spotify session expired. Please sign in again.');
+ }
+
+ if (response.status === 429) {
+ // Rate limited
+ const retryAfter = response.headers.get('Retry-After') || 1;
+ debugLog(`Spotify: Rate limited, retrying after ${retryAfter}s`);
+ await new Promise(r => setTimeout(r, retryAfter * 1000));
+ continue;
+ }
+
+ if (!response.ok) {
+ throw new Error(`Spotify API error: ${response.status} ${response.statusText}`);
+ }
+
+ return await response.json();
+ } catch (error) {
+ if (i === retries - 1) throw error;
+ await new Promise(r => setTimeout(r, 1000 * (i + 1)));
+ }
+ }
+ },
+
+ // Get current user's profile
+ getCurrentUser: async () => {
+ try {
+ const data = await spotifyAPI.fetchWithAuth('/me');
+ return {
+ id: data.id,
+ name: data.display_name,
+ email: data.email,
+ img: data.images?.[0]?.url || '',
+ followers: data.followers?.total || 0,
+ country: data.country,
+ };
+ } catch (error) {
+ errorHandler.handleApiError(error, 'Spotify getCurrentUser');
+ return null;
+ }
+ },
+
+ // Get all user playlists (with pagination)
+ getAllPlaylists: async () => {
+ try {
+ const playlists = [];
+ let url = '/me/playlists?limit=50';
+
+ while (url) {
+ const data = await spotifyAPI.fetchWithAuth(url);
+ const normalized = data.items.map(spotifyAPI.normalizePlaylist);
+ playlists.push(...normalized);
+
+ // Check for next page
+ url = data.next;
+ }
+
+ debugLog(`Spotify: Fetched ${playlists.length} playlists`);
+ return playlists;
+ } catch (error) {
+ errorHandler.handleApiError(error, 'Spotify getAllPlaylists');
+ return [];
+ }
+ },
+
+ // Get playlist tracks
+ getPlaylistTracks: async (playlistId) => {
+ try {
+ const tracks = [];
+ let url = `/playlists/${playlistId}/tracks?limit=100`;
+
+ while (url) {
+ const data = await spotifyAPI.fetchWithAuth(url);
+
+ // Filter out null tracks (deleted/unavailable songs)
+ const validTracks = data.items
+ .filter(item => item.track && item.track.id)
+ .map(item => spotifyAPI.normalizeTrack(item.track));
+
+ tracks.push(...validTracks);
+
+ // Check for next page
+ url = data.next;
+ }
+
+ debugLog(`Spotify: Fetched ${tracks.length} tracks from playlist ${playlistId}`);
+ return tracks;
+ } catch (error) {
+ errorHandler.handleApiError(error, 'Spotify getPlaylistTracks');
+ return [];
+ }
+ },
+
+ // Get user's saved tracks (liked songs)
+ getSavedTracks: async () => {
+ try {
+ const tracks = [];
+ let url = '/me/tracks?limit=50';
+
+ while (url) {
+ const data = await spotifyAPI.fetchWithAuth(url);
+
+ const validTracks = data.items
+ .filter(item => item.track && item.track.id)
+ .map(item => spotifyAPI.normalizeTrack(item.track));
+
+ tracks.push(...validTracks);
+
+ // Check for next page
+ url = data.next;
+ }
+
+ debugLog(`Spotify: Fetched ${tracks.length} saved tracks`);
+ return tracks;
+ } catch (error) {
+ errorHandler.handleApiError(error, 'Spotify getSavedTracks');
+ return [];
+ }
+ },
+
+ // Search tracks on Spotify (for matching with JioSaavn)
+ searchTracks: async (query, limit = 20) => {
+ try {
+ const data = await spotifyAPI.fetchWithAuth(
+ `/search?q=${encodeURIComponent(query)}&type=track&limit=${limit}`
+ );
+ return data.tracks.items.map(spotifyAPI.normalizeTrack);
+ } catch (error) {
+ errorHandler.handleApiError(error, 'Spotify searchTracks');
+ return [];
+ }
+ },
+
+ // Normalize playlist object
+ normalizePlaylist: (playlist) => {
+ if (!playlist) return null;
+ return {
+ id: playlist.id,
+ name: playlist.name,
+ description: playlist.description || '',
+ img: playlist.images?.[0]?.url || 'https://placehold.co/300/333/fff?text=Playlist',
+ trackCount: playlist.tracks?.total || 0,
+ owner: playlist.owner?.display_name || 'Unknown',
+ isPublic: playlist.public,
+ isCollaborative: playlist.collaborative,
+ source: 'spotify',
+ };
+ },
+
+ // Normalize track object to match app's format
+ normalizeTrack: (track) => {
+ if (!track) return null;
+ return {
+ id: `spotify_${track.id}`, // Prefix to distinguish from JioSaavn
+ spotifyId: track.id, // Keep original ID for Spotify operations
+ name: track.name,
+ artist: track.artists.map(a => a.name).join(', '),
+ artistIds: track.artists.map(a => a.id),
+ album: track.album.name,
+ albumId: track.album.id,
+ img: track.album.images?.[0]?.url || 'https://placehold.co/300/333/fff?text=Music',
+ url: track.preview_url, // Spotify only provides 30s previews for web API
+ duration: Math.floor(track.duration_ms / 1000),
+ year: track.album.release_date?.substring(0, 4) || '',
+ source: 'spotify',
+ isPreview: true, // Mark as preview since we can't stream full tracks
+ spotifyUri: track.uri, // Keep URI for potential future use
+ externalUrl: track.external_urls?.spotify || '',
+ };
+ },
+
+ // Try to find matching JioSaavn track for a Spotify track
+ findJioSaavnMatch: async (spotifyTrack) => {
+ try {
+ // Search JioSaavn with track name and artist
+ const query = `${spotifyTrack.name} ${spotifyTrack.artist}`;
+ const results = await jiosaavnAPI.searchSongs(query, 5);
+
+ if (results.length === 0) {
+ return null;
+ }
+
+ // Simple matching: return the first result
+ // Could be improved with fuzzy matching or similarity scoring
+ return results[0];
+ } catch (error) {
+ debugLog('Error finding JioSaavn match:', error);
+ return null;
+ }
+ },
+
+ // Convert Spotify playlist to playable JioSaavn tracks
+ convertPlaylistToJioSaavn: async (spotifyPlaylistId, onProgress) => {
+ try {
+ const spotifyTracks = await spotifyAPI.getPlaylistTracks(spotifyPlaylistId);
+ const jiosaavnTracks = [];
+ let matched = 0;
+
+ for (let i = 0; i < spotifyTracks.length; i++) {
+ const spotifyTrack = spotifyTracks[i];
+
+ // Report progress
+ if (onProgress) {
+ onProgress({
+ current: i + 1,
+ total: spotifyTracks.length,
+ matched: matched,
+ });
+ }
+
+ // Try to find matching JioSaavn track
+ const jiosaavnTrack = await spotifyAPI.findJioSaavnMatch(spotifyTrack);
+
+ if (jiosaavnTrack) {
+ jiosaavnTracks.push(jiosaavnTrack);
+ matched++;
+ }
+
+ // Add small delay to avoid overwhelming the API
+ await new Promise(r => setTimeout(r, 100));
+ }
+
+ debugLog(`Spotify: Converted ${matched}/${spotifyTracks.length} tracks to JioSaavn`);
+ return {
+ tracks: jiosaavnTracks,
+ total: spotifyTracks.length,
+ matched: matched,
+ };
+ } catch (error) {
+ errorHandler.handleApiError(error, 'Spotify convertPlaylistToJioSaavn');
+ return { tracks: [], total: 0, matched: 0 };
+ }
+ },
+};
diff --git a/home/js/spotify-auth.js b/home/js/spotify-auth.js
new file mode 100644
index 0000000..23509d7
--- /dev/null
+++ b/home/js/spotify-auth.js
@@ -0,0 +1,207 @@
+// ============================================
+// SPOTIFY AUTHENTICATION MODULE
+// Implements OAuth 2.0 with PKCE (Proof Key for Code Exchange)
+// ============================================
+
+const spotifyAuth = {
+ // Spotify App Credentials (REPLACE WITH YOUR OWN)
+ clientId: 'YOUR_SPOTIFY_CLIENT_ID', // Replace with your Spotify Client ID
+ redirectUri: window.location.origin + window.location.pathname,
+ scopes: [
+ 'playlist-read-private',
+ 'playlist-read-collaborative',
+ 'user-library-read'
+ ].join(' '),
+
+ // State management
+ isAuthenticated: false,
+ accessToken: null,
+ tokenExpiry: null,
+
+ // Initialize authentication state from localStorage
+ init: () => {
+ const token = localStorage.getItem('spotify_access_token');
+ const expiry = localStorage.getItem('spotify_token_expiry');
+
+ if (token && expiry) {
+ const expiryTime = parseInt(expiry);
+ if (Date.now() < expiryTime) {
+ spotifyAuth.accessToken = token;
+ spotifyAuth.tokenExpiry = expiryTime;
+ spotifyAuth.isAuthenticated = true;
+ debugLog('Spotify: Restored authentication from localStorage');
+ return true;
+ } else {
+ debugLog('Spotify: Token expired, clearing localStorage');
+ spotifyAuth.logout();
+ }
+ }
+
+ // Check for OAuth callback
+ spotifyAuth.handleCallback();
+ return spotifyAuth.isAuthenticated;
+ },
+
+ // Generate random string for PKCE
+ generateRandomString: (length) => {
+ const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+ const values = crypto.getRandomValues(new Uint8Array(length));
+ return values.reduce((acc, x) => acc + possible[x % possible.length], '');
+ },
+
+ // Generate code challenge for PKCE
+ generateCodeChallenge: async (codeVerifier) => {
+ const digest = await crypto.subtle.digest(
+ 'SHA-256',
+ new TextEncoder().encode(codeVerifier)
+ );
+ return btoa(String.fromCharCode(...new Uint8Array(digest)))
+ .replace(/=/g, '')
+ .replace(/\+/g, '-')
+ .replace(/\//g, '_');
+ },
+
+ // Start OAuth flow
+ login: async () => {
+ if (spotifyAuth.clientId === 'YOUR_SPOTIFY_CLIENT_ID') {
+ errorHandler.show('Spotify Client ID not configured. Please add your Client ID in spotify-auth.js', 6000);
+ return;
+ }
+
+ const codeVerifier = spotifyAuth.generateRandomString(64);
+ const codeChallenge = await spotifyAuth.generateCodeChallenge(codeVerifier);
+
+ // Store code verifier for later use
+ localStorage.setItem('spotify_code_verifier', codeVerifier);
+
+ // Build authorization URL
+ const params = new URLSearchParams({
+ client_id: spotifyAuth.clientId,
+ response_type: 'code',
+ redirect_uri: spotifyAuth.redirectUri,
+ scope: spotifyAuth.scopes,
+ code_challenge_method: 'S256',
+ code_challenge: codeChallenge,
+ });
+
+ const authUrl = `https://accounts.spotify.com/authorize?${params.toString()}`;
+ debugLog('Spotify: Redirecting to authorization URL');
+ window.location.href = authUrl;
+ },
+
+ // Handle OAuth callback
+ handleCallback: async () => {
+ const params = new URLSearchParams(window.location.search);
+ const code = params.get('code');
+ const error = params.get('error');
+
+ if (error) {
+ errorHandler.show(`Spotify authorization failed: ${error}`);
+ // Clean up URL
+ window.history.replaceState({}, document.title, window.location.pathname);
+ return;
+ }
+
+ if (code) {
+ debugLog('Spotify: Authorization code received, exchanging for token');
+ const codeVerifier = localStorage.getItem('spotify_code_verifier');
+
+ if (!codeVerifier) {
+ errorHandler.show('Spotify authentication error: Code verifier not found');
+ window.history.replaceState({}, document.title, window.location.pathname);
+ return;
+ }
+
+ try {
+ // Exchange code for access token
+ const response = await fetch('https://accounts.spotify.com/api/token', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: new URLSearchParams({
+ client_id: spotifyAuth.clientId,
+ grant_type: 'authorization_code',
+ code: code,
+ redirect_uri: spotifyAuth.redirectUri,
+ code_verifier: codeVerifier,
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`Token exchange failed: ${response.status}`);
+ }
+
+ const data = await response.json();
+
+ // Store token
+ spotifyAuth.accessToken = data.access_token;
+ spotifyAuth.tokenExpiry = Date.now() + (data.expires_in * 1000);
+ spotifyAuth.isAuthenticated = true;
+
+ localStorage.setItem('spotify_access_token', data.access_token);
+ localStorage.setItem('spotify_token_expiry', spotifyAuth.tokenExpiry.toString());
+ localStorage.removeItem('spotify_code_verifier');
+
+ debugLog('Spotify: Authentication successful');
+ errorHandler.show('Successfully connected to Spotify!', 3000);
+
+ // Clean up URL and reload to update UI
+ window.history.replaceState({}, document.title, window.location.pathname);
+
+ // Update UI
+ if (typeof ui !== 'undefined' && ui.updateSpotifyButton) {
+ ui.updateSpotifyButton();
+ }
+
+ // Auto-load playlists if we're on the playlists view
+ if (typeof router !== 'undefined' && router.currentView === 'playlists') {
+ if (typeof playlistsView !== 'undefined' && playlistsView.loadSpotifyPlaylists) {
+ playlistsView.loadSpotifyPlaylists();
+ }
+ }
+
+ } catch (error) {
+ debugError('Spotify token exchange error:', error);
+ errorHandler.show('Failed to connect to Spotify. Please try again.');
+ localStorage.removeItem('spotify_code_verifier');
+ window.history.replaceState({}, document.title, window.location.pathname);
+ }
+ }
+ },
+
+ // Logout and clear tokens
+ logout: () => {
+ spotifyAuth.accessToken = null;
+ spotifyAuth.tokenExpiry = null;
+ spotifyAuth.isAuthenticated = false;
+
+ localStorage.removeItem('spotify_access_token');
+ localStorage.removeItem('spotify_token_expiry');
+ localStorage.removeItem('spotify_code_verifier');
+ localStorage.removeItem('spotify_playlists');
+
+ debugLog('Spotify: Logged out');
+
+ // Update UI
+ if (typeof ui !== 'undefined' && ui.updateSpotifyButton) {
+ ui.updateSpotifyButton();
+ }
+ },
+
+ // Check if token is still valid
+ isTokenValid: () => {
+ if (!spotifyAuth.accessToken || !spotifyAuth.tokenExpiry) {
+ return false;
+ }
+ return Date.now() < spotifyAuth.tokenExpiry;
+ },
+
+ // Get valid access token
+ getAccessToken: () => {
+ if (spotifyAuth.isTokenValid()) {
+ return spotifyAuth.accessToken;
+ }
+ return null;
+ }
+};
diff --git a/home/js/state.js b/home/js/state.js
index 589c6dc..8f7915e 100644
--- a/home/js/state.js
+++ b/home/js/state.js
@@ -37,7 +37,20 @@ const state = {
} catch(e) { return {}; }
})(),
isLoading: false,
- searchDebounce: null
+ searchDebounce: null,
+ // Spotify state
+ spotifyPlaylists: (() => {
+ try {
+ const data = JSON.parse(localStorage.getItem('spotify_playlists') || '[]');
+ return Array.isArray(data) ? data : [];
+ } catch(e) { return []; }
+ })(),
+ spotifyUser: (() => {
+ try {
+ const data = JSON.parse(localStorage.getItem('spotify_user') || 'null');
+ return data;
+ } catch(e) { return null; }
+ })()
};
// ============================================
diff --git a/home/js/ui.js b/home/js/ui.js
index 1e44b1c..b9b6b4e 100644
--- a/home/js/ui.js
+++ b/home/js/ui.js
@@ -284,5 +284,17 @@ const ui = {
const id = state.currentTrack.id;
const btn = document.getElementById('p-like-btn');
if(btn) btn.className = state.likedIds.includes(id) ? 'text-red-500 transition ml-2' : 'text-gray-400 hover:text-red-500 transition ml-2';
+ },
+ updateSpotifyButton: () => {
+ const signinBtn = document.getElementById('spotify-signin-btn');
+ const signoutBtn = document.getElementById('spotify-signout-btn');
+
+ if (typeof spotifyAuth !== 'undefined' && spotifyAuth.isAuthenticated) {
+ if (signinBtn) signinBtn.classList.add('hidden');
+ if (signoutBtn) signoutBtn.classList.remove('hidden');
+ } else {
+ if (signinBtn) signinBtn.classList.remove('hidden');
+ if (signoutBtn) signoutBtn.classList.add('hidden');
+ }
}
};
diff --git a/home/js/views.js b/home/js/views.js
index 964fb9a..ffe9c4d 100644
--- a/home/js/views.js
+++ b/home/js/views.js
@@ -25,7 +25,10 @@ const router = {
if (view === 'playlists') {
ui.renderLikedSongs();
ui.renderQueue();
+ playlistsView.load();
}
+
+ router.currentView = view;
}
};
@@ -266,3 +269,124 @@ const albumsView = {
}
}
};
+
+// ============================================
+// PLAYLISTS VIEW (WITH SPOTIFY INTEGRATION)
+// ============================================
+const playlistsView = {
+ loaded: false,
+
+ load: () => {
+ if (typeof spotifyAuth !== 'undefined' && spotifyAuth.isAuthenticated) {
+ playlistsView.loadSpotifyPlaylists();
+ }
+ },
+
+ loadSpotifyPlaylists: async () => {
+ const container = document.getElementById('spotify-playlists-container');
+ if (!container) return;
+
+ container.innerHTML = '
Loading Spotify playlists...
';
+
+ try {
+ // Fetch user info
+ const user = await spotifyAPI.getCurrentUser();
+ if (user) {
+ state.spotifyUser = user;
+ localStorage.setItem('spotify_user', JSON.stringify(user));
+ }
+
+ // Fetch playlists
+ const playlists = await spotifyAPI.getAllPlaylists();
+ state.spotifyPlaylists = playlists;
+ localStorage.setItem('spotify_playlists', JSON.stringify(playlists));
+
+ if (playlists.length === 0) {
+ container.innerHTML = '
No playlists found
';
+ return;
+ }
+
+ // Render playlists
+ container.innerHTML = `
+
+ ${playlists.map(playlist => `
+
+
+

+
+
+
${searchManager.escapeHtml(playlist.name)}
+
${playlist.trackCount} tracks • ${searchManager.escapeHtml(playlist.owner)}
+
Spotify
+
+ `).join('')}
+
+ `;
+
+ } catch (error) {
+ debugError('Error loading Spotify playlists:', error);
+ container.innerHTML = '
Unable to load Spotify playlists
';
+ }
+ },
+
+ openSpotifyPlaylist: async (playlistId) => {
+ const container = document.getElementById('spotify-playlists-container');
+ if (!container) return;
+
+ // Show loading overlay
+ const overlay = document.createElement('div');
+ overlay.id = 'playlist-loading-overlay';
+ overlay.className = 'fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center z-[100]';
+ overlay.innerHTML = `
+
+
Converting Playlist
+
Finding matching tracks on JioSaavn...
+
+
Starting...
+
+ `;
+ document.body.appendChild(overlay);
+
+ try {
+ // Convert Spotify playlist to JioSaavn tracks
+ const result = await spotifyAPI.convertPlaylistToJioSaavn(playlistId, (progress) => {
+ const progressBar = document.getElementById('convert-progress');
+ const statusText = document.getElementById('convert-status');
+
+ if (progressBar && statusText) {
+ const percentage = Math.round((progress.current / progress.total) * 100);
+ progressBar.style.width = `${percentage}%`;
+ statusText.textContent = `Processing ${progress.current} of ${progress.total} tracks (${progress.matched} matched)`;
+ }
+ });
+
+ // Remove overlay
+ overlay.remove();
+
+ if (result.tracks.length === 0) {
+ errorHandler.show('Could not find any matching tracks on JioSaavn', 4000);
+ return;
+ }
+
+ // Play the converted tracks
+ player.setQueue(result.tracks, 0);
+
+ // Show success message
+ const matchRate = Math.round((result.matched / result.total) * 100);
+ errorHandler.show(`Successfully converted ${result.matched} of ${result.total} tracks (${matchRate}%)`, 5000);
+
+ } catch (error) {
+ debugError('Error converting Spotify playlist:', error);
+ overlay.remove();
+ errorHandler.show('Failed to convert playlist. Please try again.');
+ }
+ }
+};
From 8ca30f1d242f1061acaa246d0bb52c2046e989d5 Mon Sep 17 00:00:00 2001
From: Dhairya Shah
Date: Tue, 31 Mar 2026 19:29:36 +0530
Subject: [PATCH 03/11] Update spotify-auth.js
---
home/js/spotify-auth.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/home/js/spotify-auth.js b/home/js/spotify-auth.js
index 23509d7..fed3292 100644
--- a/home/js/spotify-auth.js
+++ b/home/js/spotify-auth.js
@@ -5,7 +5,7 @@
const spotifyAuth = {
// Spotify App Credentials (REPLACE WITH YOUR OWN)
- clientId: 'YOUR_SPOTIFY_CLIENT_ID', // Replace with your Spotify Client ID
+ clientId: '8fba37005d964e2599ce567c69ee7f1d', // Replace with your Spotify Client ID
redirectUri: window.location.origin + window.location.pathname,
scopes: [
'playlist-read-private',
From 7b8511256c630ecc8a8bb3af0ba9dbd3da3fffce Mon Sep 17 00:00:00 2001
From: Dhairya Shah
Date: Tue, 31 Mar 2026 19:32:15 +0530
Subject: [PATCH 04/11] Update redirect URI for Spotify authentication
---
home/js/spotify-auth.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/home/js/spotify-auth.js b/home/js/spotify-auth.js
index fed3292..266b1ad 100644
--- a/home/js/spotify-auth.js
+++ b/home/js/spotify-auth.js
@@ -6,7 +6,7 @@
const spotifyAuth = {
// Spotify App Credentials (REPLACE WITH YOUR OWN)
clientId: '8fba37005d964e2599ce567c69ee7f1d', // Replace with your Spotify Client ID
- redirectUri: window.location.origin + window.location.pathname,
+ redirectUri: 'https://play.dverse.fun/home/index.html',
scopes: [
'playlist-read-private',
'playlist-read-collaborative',
From 99f66596f4fac7950c021ed1512739f278e4c1b8 Mon Sep 17 00:00:00 2001
From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com>
Date: Tue, 31 Mar 2026 14:08:21 +0000
Subject: [PATCH 05/11] Fix Spotify OAuth: replace deprecated Implicit Grant
with PKCE Authorization Code Flow
Agent-Logs-Url: https://github.com/Dcode9/MPLayer/sessions/52ec8d8b-bde4-4316-ba32-6ce3d90d9196
Co-authored-by: Dcode9 <194372792+Dcode9@users.noreply.github.com>
---
index.html | 86 +++++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 75 insertions(+), 11 deletions(-)
diff --git a/index.html b/index.html
index 4378be6..16ce6c5 100644
--- a/index.html
+++ b/index.html
@@ -745,30 +745,94 @@ Audio Quality
};
// ============================================
- // SPOTIFY API INTEGRATION
+ // SPOTIFY API INTEGRATION (OAuth 2.0 with PKCE)
// ============================================
const spotifyManager = {
clientId: '8fba37005d964e2599ce567c69ee7f1d', // 🔴 ADD YOUR SPOTIFY CLIENT ID HERE
redirectUri: window.location.href.split('#')[0].split('?')[0],
token: null,
- login: () => {
+ // Generate random string for PKCE
+ generateRandomString: (length) => {
+ const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+ const values = crypto.getRandomValues(new Uint8Array(length));
+ return values.reduce((acc, x) => acc + possible[x % possible.length], '');
+ },
+
+ // Generate code challenge for PKCE
+ generateCodeChallenge: async (codeVerifier) => {
+ const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier));
+ return btoa(String.fromCharCode(...new Uint8Array(digest)))
+ .replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
+ },
+
+ login: async () => {
if(!spotifyManager.clientId) {
alert("Developer setup required: Please open the HTML file and add your Spotify Client ID to the 'spotifyManager.clientId' variable.");
return;
}
+
+ const codeVerifier = spotifyManager.generateRandomString(64);
+ const codeChallenge = await spotifyManager.generateCodeChallenge(codeVerifier);
+
+ localStorage.setItem('spotify_code_verifier', codeVerifier);
+
const scopes = 'playlist-read-private playlist-read-collaborative';
- const authUrl = `https://accounts.spotify.com/authorize?client_id=${spotifyManager.clientId}&response_type=token&redirect_uri=${encodeURIComponent(spotifyManager.redirectUri)}&scope=${encodeURIComponent(scopes)}`;
+ const authUrl = `https://accounts.spotify.com/authorize?client_id=${spotifyManager.clientId}&response_type=code&redirect_uri=${encodeURIComponent(spotifyManager.redirectUri)}&scope=${encodeURIComponent(scopes)}&code_challenge_method=S256&code_challenge=${codeChallenge}`;
window.location.href = authUrl;
},
-
- checkToken: () => {
- const hash = window.location.hash;
- if (hash && hash.includes('access_token=')) {
- const params = new URLSearchParams(hash.substring(1));
- spotifyManager.token = params.get('access_token');
- window.location.hash = '';
- setTimeout(() => ui.toggleSpotifyModal(true), 500);
+
+ checkToken: async () => {
+ // Check if we have a stored token
+ const storedToken = localStorage.getItem('spotify_access_token');
+ const tokenExpiry = localStorage.getItem('spotify_token_expiry');
+
+ if (storedToken && tokenExpiry && Date.now() < parseInt(tokenExpiry)) {
+ spotifyManager.token = storedToken;
+ setTimeout(() => ui.toggleSpotifyModal(true), 500);
+ return;
+ }
+
+ // Check for OAuth callback with authorization code
+ const params = new URLSearchParams(window.location.search);
+ const code = params.get('code');
+
+ if (code) {
+ const codeVerifier = localStorage.getItem('spotify_code_verifier');
+
+ if (codeVerifier) {
+ try {
+ const response = await fetch('https://accounts.spotify.com/api/token', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+ body: new URLSearchParams({
+ client_id: spotifyManager.clientId,
+ grant_type: 'authorization_code',
+ code: code,
+ redirect_uri: spotifyManager.redirectUri,
+ code_verifier: codeVerifier,
+ })
+ });
+
+ if (response.ok) {
+ const data = await response.json();
+ spotifyManager.token = data.access_token;
+
+ // Store token with expiry
+ localStorage.setItem('spotify_access_token', data.access_token);
+ localStorage.setItem('spotify_token_expiry', (Date.now() + data.expires_in * 1000).toString());
+ localStorage.removeItem('spotify_code_verifier');
+
+ // Clean up URL
+ window.history.replaceState({}, document.title, window.location.pathname);
+
+ setTimeout(() => ui.toggleSpotifyModal(true), 500);
+ }
+ } catch (e) {
+ console.error('Token exchange failed:', e);
+ localStorage.removeItem('spotify_code_verifier');
+ }
+ }
}
},
From 1188a94deb6324ee642a4e8837a5b265f673ad49 Mon Sep 17 00:00:00 2001
From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com>
Date: Tue, 31 Mar 2026 14:16:29 +0000
Subject: [PATCH 06/11] Update redirect URI for Spotify authentication
Agent-Logs-Url: https://github.com/Dcode9/MPLayer/sessions/f1e4f31c-b0f2-42ee-97d1-a75fa2dcf714
Co-authored-by: Dcode9 <194372792+Dcode9@users.noreply.github.com>
---
SPOTIFY_REDIRECT_URI_SETUP.md | 123 ++++++++++++++++++++++++++++++++++
index.html | 2 +-
2 files changed, 124 insertions(+), 1 deletion(-)
create mode 100644 SPOTIFY_REDIRECT_URI_SETUP.md
diff --git a/SPOTIFY_REDIRECT_URI_SETUP.md b/SPOTIFY_REDIRECT_URI_SETUP.md
new file mode 100644
index 0000000..1dae3de
--- /dev/null
+++ b/SPOTIFY_REDIRECT_URI_SETUP.md
@@ -0,0 +1,123 @@
+# Spotify Redirect URI Configuration
+
+## Issue: "redirect_uri: Not matching configuration"
+
+This error occurs when the redirect URI sent in the OAuth request doesn't match exactly what's configured in your Spotify Developer Dashboard.
+
+## Current Redirect URIs
+
+The application uses these redirect URIs:
+
+1. **Root application** (`/index.html`): `https://play.dverse.fun/index.html`
+2. **Home application** (`/home/index.html`): `https://play.dverse.fun/home/index.html`
+
+## How to Configure in Spotify Developer Dashboard
+
+### Step 1: Access Your Spotify App Settings
+
+1. Go to [Spotify Developer Dashboard](https://developer.spotify.com/dashboard)
+2. Click on your app (or create a new one)
+3. Click on **"Settings"** button
+
+### Step 2: Add Redirect URIs
+
+In the **Redirect URIs** section, add **BOTH** of these URIs **EXACTLY** as shown:
+
+```
+https://play.dverse.fun/index.html
+https://play.dverse.fun/home/index.html
+```
+
+**Important Notes:**
+
+- ✅ **Include the full path** including the HTML file name
+- ✅ **Use HTTPS** (not HTTP) for production
+- ✅ **No trailing slashes**
+- ✅ **Match the protocol exactly** (https:// vs http://)
+- ✅ **Match the domain exactly** (including subdomains)
+- ✅ **Match the path exactly** (case-sensitive)
+
+### Step 3: Save Changes
+
+1. Click **"Add"** after entering each URI
+2. Click **"Save"** at the bottom of the page
+3. Wait a few seconds for changes to propagate
+
+### For Local Development
+
+If you're testing locally, also add these URIs:
+
+```
+http://localhost:8000/index.html
+http://localhost:8000/home/index.html
+```
+
+Or adjust the port number if you're using a different local server port.
+
+## Troubleshooting
+
+### Still Getting "redirect_uri: Not matching configuration"?
+
+1. **Check for typos**: The URI must match EXACTLY character-by-character
+2. **Check protocol**: Make sure you're using `https://` not `http://` (or vice versa)
+3. **Check trailing slashes**: The URIs should NOT have trailing slashes
+4. **Check path**: Make sure `/index.html` or `/home/index.html` is included
+5. **Wait**: Sometimes it takes a minute for Spotify to sync changes
+6. **Clear cache**: Try clearing browser cache and localStorage
+7. **Check URL in browser**: Make sure you're actually accessing the URL that matches the redirect URI
+
+### Common Mistakes
+
+❌ `https://play.dverse.fun` (missing the HTML file)
+❌ `https://play.dverse.fun/` (trailing slash)
+❌ `http://play.dverse.fun/index.html` (wrong protocol)
+❌ `https://play.dverse.fun/Index.html` (wrong case)
+❌ `https://play.dverse.fun/home` (missing the HTML file)
+
+✅ `https://play.dverse.fun/index.html` (correct)
+✅ `https://play.dverse.fun/home/index.html` (correct)
+
+## Updating Redirect URI in Code
+
+If you need to change the redirect URI (e.g., for a different domain):
+
+### For Root Application (`/index.html`)
+
+Edit line 752 in `/index.html`:
+
+```javascript
+redirectUri: 'https://your-domain.com/index.html',
+```
+
+### For Home Application (`/home/index.html`)
+
+Edit line 9 in `/home/js/spotify-auth.js`:
+
+```javascript
+redirectUri: 'https://your-domain.com/home/index.html',
+```
+
+**Important**: After changing the redirect URI in code, you MUST also update it in your Spotify Developer Dashboard!
+
+## Why This Error Happens
+
+Spotify requires the redirect URI to match EXACTLY for security reasons. This prevents attackers from:
+- Intercepting OAuth tokens
+- Redirecting users to malicious sites
+- Stealing user credentials
+
+The redirect URI acts as a security whitelist - only URLs you explicitly approve can receive the OAuth response.
+
+## Related Files
+
+- `/index.html` - Line 752 (spotifyManager.redirectUri)
+- `/home/js/spotify-auth.js` - Line 9 (spotifyAuth.redirectUri)
+- `SPOTIFY_SETUP.md` - General Spotify setup documentation
+
+## Support
+
+If you continue to have issues:
+1. Double-check all URIs match exactly
+2. Try removing and re-adding the URIs in Spotify Dashboard
+3. Verify your app is using the correct Client ID
+4. Check browser console for additional error messages
diff --git a/index.html b/index.html
index 16ce6c5..b90a026 100644
--- a/index.html
+++ b/index.html
@@ -749,7 +749,7 @@ Audio Quality
// ============================================
const spotifyManager = {
clientId: '8fba37005d964e2599ce567c69ee7f1d', // 🔴 ADD YOUR SPOTIFY CLIENT ID HERE
- redirectUri: window.location.href.split('#')[0].split('?')[0],
+ redirectUri: 'https://play.dverse.fun/index.html',
token: null,
// Generate random string for PKCE
From 06f8a3dbd75b408adf4f1d00b30ff16cddf09908 Mon Sep 17 00:00:00 2001
From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com>
Date: Tue, 31 Mar 2026 16:05:18 +0000
Subject: [PATCH 07/11] Enable debug mode and add comprehensive logging for
Spotify playlist loading
Agent-Logs-Url: https://github.com/Dcode9/MPLayer/sessions/c4383a86-79a9-419c-ac02-37c7f9a8f441
Co-authored-by: Dcode9 <194372792+Dcode9@users.noreply.github.com>
---
home/js/config.js | 2 +-
home/js/views.js | 58 +++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 54 insertions(+), 6 deletions(-)
diff --git a/home/js/config.js b/home/js/config.js
index 5116be4..f6d20f6 100644
--- a/home/js/config.js
+++ b/home/js/config.js
@@ -25,7 +25,7 @@ function switchToNextApi() {
// ============================================
// DEBUG LOGGING
// ============================================
-const DEBUG = false; // Set to true for development
+const DEBUG = true; // Set to true for development
function debugLog(...args) {
if (DEBUG) console.log('[JioSaavn Player]', ...args);
}
diff --git a/home/js/views.js b/home/js/views.js
index ffe9c4d..5301fbb 100644
--- a/home/js/views.js
+++ b/home/js/views.js
@@ -2,20 +2,23 @@
// ROUTER
// ============================================
const router = {
+ currentView: 'home', // Initialize with default view
go: (view) => {
+ debugLog(`Router: Navigating to ${view}`);
+
document.querySelectorAll('.view-section').forEach(el => el.classList.remove('active'));
const target = document.getElementById('view-'+view);
if(target) target.classList.add('active');
-
+
document.querySelectorAll('.nav-btn').forEach(el => el.classList.remove('active'));
const btns = document.querySelectorAll('.nav-btn');
-
+
const viewMap = { 'home': 0, 'trending': 1, 'albums': 2, 'playlists': 3, 'album-detail': 2, 'search': -1 };
const btnIdx = viewMap[view];
if (btnIdx >= 0 && btns[btnIdx]) {
btns[btnIdx].classList.add('active');
}
-
+
if (view === 'trending' && !trendingView.loaded) {
trendingView.load();
}
@@ -23,12 +26,14 @@ const router = {
albumsView.load();
}
if (view === 'playlists') {
+ debugLog('Playlists view activated');
ui.renderLikedSongs();
ui.renderQueue();
playlistsView.load();
}
router.currentView = view;
+ debugLog(`Router: Current view set to ${router.currentView}`);
}
};
@@ -277,30 +282,56 @@ const playlistsView = {
loaded: false,
load: () => {
+ debugLog('playlistsView.load() called');
+ debugLog('Spotify auth status:', {
+ isDefined: typeof spotifyAuth !== 'undefined',
+ isAuthenticated: typeof spotifyAuth !== 'undefined' ? spotifyAuth.isAuthenticated : false,
+ hasToken: typeof spotifyAuth !== 'undefined' ? !!spotifyAuth.getAccessToken() : false
+ });
+
if (typeof spotifyAuth !== 'undefined' && spotifyAuth.isAuthenticated) {
+ debugLog('User is authenticated, loading Spotify playlists...');
playlistsView.loadSpotifyPlaylists();
+ } else {
+ debugLog('User is not authenticated with Spotify');
}
},
loadSpotifyPlaylists: async () => {
const container = document.getElementById('spotify-playlists-container');
- if (!container) return;
+ if (!container) {
+ debugError('Spotify playlists container not found!');
+ return;
+ }
+ debugLog('Loading Spotify playlists...');
container.innerHTML = 'Loading Spotify playlists...
';
try {
+ // Verify authentication
+ if (!spotifyAuth.isAuthenticated || !spotifyAuth.getAccessToken()) {
+ debugError('Not authenticated with Spotify');
+ container.innerHTML = 'Please sign in with Spotify to see your playlists
';
+ return;
+ }
+
+ debugLog('Fetching user info...');
// Fetch user info
const user = await spotifyAPI.getCurrentUser();
if (user) {
state.spotifyUser = user;
localStorage.setItem('spotify_user', JSON.stringify(user));
+ debugLog('User info fetched:', user.name);
}
+ debugLog('Fetching playlists...');
// Fetch playlists
const playlists = await spotifyAPI.getAllPlaylists();
state.spotifyPlaylists = playlists;
localStorage.setItem('spotify_playlists', JSON.stringify(playlists));
+ debugLog(`Fetched ${playlists.length} playlists`);
+
if (playlists.length === 0) {
container.innerHTML = 'No playlists found
';
return;
@@ -327,9 +358,26 @@ const playlistsView = {
`;
+ debugLog('Playlists rendered successfully');
+
} catch (error) {
debugError('Error loading Spotify playlists:', error);
- container.innerHTML = '
';
+
+ // Show specific error message
+ let errorMsg = 'Unable to load Spotify playlists';
+ if (error.message) {
+ if (error.message.includes('Not authenticated') || error.message.includes('session expired')) {
+ errorMsg = 'Spotify session expired. Please sign in again.';
+ } else if (error.message.includes('network') || error.message.includes('fetch')) {
+ errorMsg = 'Network error. Please check your connection.';
+ } else if (error.message.includes('401')) {
+ errorMsg = 'Spotify session expired. Please sign in again.';
+ spotifyAuth.logout();
+ }
+ }
+
+ container.innerHTML = `
`;
+ errorHandler.show(errorMsg);
}
},
From 30dbaadad6e48c9b9cef00635ff917c1bc04dcc0 Mon Sep 17 00:00:00 2001
From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com>
Date: Tue, 31 Mar 2026 16:19:06 +0000
Subject: [PATCH 08/11] Auto-navigate to playlists view after successful
Spotify authentication
Agent-Logs-Url: https://github.com/Dcode9/MPLayer/sessions/c4383a86-79a9-419c-ac02-37c7f9a8f441
Co-authored-by: Dcode9 <194372792+Dcode9@users.noreply.github.com>
---
home/js/spotify-auth.js | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/home/js/spotify-auth.js b/home/js/spotify-auth.js
index 266b1ad..f57d729 100644
--- a/home/js/spotify-auth.js
+++ b/home/js/spotify-auth.js
@@ -154,11 +154,16 @@ const spotifyAuth = {
ui.updateSpotifyButton();
}
- // Auto-load playlists if we're on the playlists view
- if (typeof router !== 'undefined' && router.currentView === 'playlists') {
- if (typeof playlistsView !== 'undefined' && playlistsView.loadSpotifyPlaylists) {
- playlistsView.loadSpotifyPlaylists();
- }
+ // Auto-navigate to playlists view and load playlists
+ debugLog('Spotify: Auto-navigating to playlists view after authentication');
+ if (typeof router !== 'undefined' && router.go) {
+ // Small delay to ensure DOM is ready
+ setTimeout(() => {
+ router.go('playlists');
+ }, 100);
+ } else if (typeof playlistsView !== 'undefined' && playlistsView.loadSpotifyPlaylists) {
+ // Fallback: just load playlists if router is not available
+ playlistsView.loadSpotifyPlaylists();
}
} catch (error) {
From f9044bb5fb520d02412348ce6d48cfcf98af257a Mon Sep 17 00:00:00 2001
From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com>
Date: Wed, 1 Apr 2026 12:54:03 +0000
Subject: [PATCH 09/11] Enhance Spotify playlist debugging with comprehensive
logging and troubleshooting guide
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Add detailed debug logging to track API responses and data normalization
- Log every step of playlist loading, rendering, and conversion
- Add debug output for track-by-track matching progress
- Improve error messages with stack traces for better diagnostics
- Create comprehensive SPOTIFY_TROUBLESHOOTING.md guide
- Add fallback for track count extraction (playlist.track_count)
- Include visual indicators (✓/✗) for match success/failure in logs
This will help users and developers diagnose why playlists appear incomplete or don't work properly.
Agent-Logs-Url: https://github.com/Dcode9/MPLayer/sessions/10d0aed5-e3c3-4ad0-ab8e-945f1d70ad57
Co-authored-by: Dcode9 <194372792+Dcode9@users.noreply.github.com>
---
SPOTIFY_TROUBLESHOOTING.md | 321 +++++++++++++++++++++++++++++++++++++
home/js/spotify-api.js | 31 +++-
home/js/views.js | 27 +++-
3 files changed, 372 insertions(+), 7 deletions(-)
create mode 100644 SPOTIFY_TROUBLESHOOTING.md
diff --git a/SPOTIFY_TROUBLESHOOTING.md b/SPOTIFY_TROUBLESHOOTING.md
new file mode 100644
index 0000000..1a7f30b
--- /dev/null
+++ b/SPOTIFY_TROUBLESHOOTING.md
@@ -0,0 +1,321 @@
+# Spotify Playlist Troubleshooting Guide
+
+If you're experiencing issues with Spotify playlists not loading or working properly, follow this guide to diagnose and fix the problem.
+
+## Common Issues and Solutions
+
+### Issue 1: "Only the name comes" - Playlists show but no track count or images
+
+**Symptoms:**
+- Playlist cards display only the playlist name
+- Track count shows "0 tracks"
+- No playlist images/thumbnails
+- Or playlists appear blank/incomplete
+
+**Possible Causes & Solutions:**
+
+#### Solution A: Check Browser Console for Debug Logs
+
+1. Open your browser's Developer Tools:
+ - **Chrome/Edge**: Press `F12` or `Ctrl+Shift+I` (Windows) / `Cmd+Option+I` (Mac)
+ - **Firefox**: Press `F12` or `Ctrl+Shift+K` (Windows) / `Cmd+Option+K` (Mac)
+ - **Safari**: Enable Developer menu first, then press `Cmd+Option+I`
+
+2. Click on the **Console** tab
+
+3. Look for debug messages starting with `[JioSaavn Player]` including:
+ - `Spotify API response for playlists:` - Shows raw API data
+ - `First playlist raw data:` - Shows first playlist structure
+ - `Normalizing playlist:` - Shows data extraction process
+ - `Rendering playlist:` - Shows what's being rendered
+
+4. Check if you see any of these error messages:
+ - `Not authenticated with Spotify` - You need to sign in
+ - `Spotify session expired` - Re-authenticate with Spotify
+ - `Network error` - Check your internet connection
+ - API errors (401, 403, 429, etc.) - See specific error solutions below
+
+#### Solution B: Verify Spotify Authentication
+
+1. Make sure you're signed in to Spotify in the app
+2. Check that the "Disconnect Spotify" button is visible (if you see "Connect Spotify", you're not authenticated)
+3. If unsure, sign out and sign in again:
+ - Click "Disconnect Spotify"
+ - Clear browser localStorage (DevTools → Application → Local Storage → Clear)
+ - Click "Connect Spotify" and authorize again
+
+#### Solution C: Check API Response Structure
+
+In the console, look for the message `First playlist raw data:` and check the structure:
+
+**Expected structure:**
+```javascript
+{
+ id: "playlist_id_here",
+ name: "Playlist Name",
+ images: [{url: "https://..."}],
+ tracks: {
+ href: "https://api.spotify.com/v1/playlists/.../tracks",
+ total: 25 // ← This is the track count
+ },
+ owner: {
+ display_name: "Owner Name"
+ }
+}
+```
+
+**If `tracks.total` is missing or 0:**
+- The Spotify API might not be returning full data
+- Try refreshing the playlists (click the refresh button)
+- Check if the playlist is empty on Spotify itself
+
+#### Solution D: Clear Cached Data
+
+Sometimes cached data can be stale or corrupted:
+
+1. Open DevTools → Console
+2. Run this command to clear Spotify cache:
+ ```javascript
+ localStorage.removeItem('spotify_playlists');
+ localStorage.removeItem('spotify_user');
+ ```
+3. Refresh the page
+4. Navigate to Playlists view again
+
+### Issue 2: Playlists don't play when clicked
+
+**Symptoms:**
+- Clicking a playlist does nothing
+- Loading overlay appears but nothing happens
+- Error message appears
+
+**Diagnostic Steps:**
+
+#### Step 1: Check Console for Click Events
+
+When you click a playlist, you should see:
+```
+[JioSaavn Player] Opening Spotify playlist: