diff --git a/artifacts/spotify-playwright/spotify-playwright-1.1.0.tgz b/artifacts/spotify-playwright/spotify-playwright-1.1.0.tgz new file mode 100644 index 0000000..26b28a5 Binary files /dev/null and b/artifacts/spotify-playwright/spotify-playwright-1.1.0.tgz differ diff --git a/connector-index.json b/connector-index.json index efbf5aa..b7b0af4 100644 --- a/connector-index.json +++ b/connector-index.json @@ -779,6 +779,40 @@ "iconKey": "spotify", "defaultScope": "spotify.savedTracks" } + }, + { + "connectorId": "spotify-playwright", + "company": "spotify", + "version": "1.1.0", + "name": "Spotify", + "status": "stable", + "description": "Exports your Spotify library, playlists, listening history, and preferences.", + "sourceFiles": { + "script": "spotify/spotify-playwright.js", + "metadata": "spotify/spotify-playwright.json" + }, + "publishedAt": "2026-07-13T00:00:00Z", + "sourceTag": "volod/spotify-profile-identity", + "sourceCommit": "09a6745b31b57ab4b574af11cc53f6a7fb2fc853", + "releaseId": "connectors-09a6745b31b5", + "pageApiVersion": 1, + "manifestSha256": "sha256:e5f5df94f724a2417b66b52edd0a4f12a3278b2566c9aaf5a2a69b31f80cbb2a", + "scriptSha256": "sha256:0cca7383277f4b4d24747d684e67a8c76c2af77180f8bf3d51ce722521e91e84", + "artifactSha256": "sha256:d76579a2d38ccec46f3f78d12316e144df41ba948734e4f2f01daf3bd6bdd30b", + "artifactPath": "artifacts/spotify-playwright/spotify-playwright-1.1.0.tgz", + "artifactUrl": "https://raw.githubusercontent.com/vana-com/data-connectors/09a6745b31b57ab4b574af11cc53f6a7fb2fc853/artifacts/spotify-playwright/spotify-playwright-1.1.0.tgz", + "scopes": [ + "spotify.profile", + "spotify.savedTracks", + "spotify.playlists" + ], + "consumerMetadata": { + "sourceId": "spotify", + "displayName": "Spotify", + "brandDomain": "spotify.com", + "iconKey": "spotify", + "defaultScope": "spotify.savedTracks" + } } ], "steam-playwright": [ diff --git a/connectors/spotify/spotify-playwright.js b/connectors/spotify/spotify-playwright.js index 51e0515..a99b185 100644 --- a/connectors/spotify/spotify-playwright.js +++ b/connectors/spotify/spotify-playwright.js @@ -554,28 +554,60 @@ const spClientFetch = async (path) => { message: 'Loading profile data...', }); - // Use spclient for profile (always works, returns JSON directly) - const profileData = await spClientFetch('/user-profile-view/v3/profile/me'); - - // Also get profileAttributes via GraphQL for username/uri + // IDENTITY comes from the session-bound GraphQL `me` query, never from the + // profile-view path. `spClientFetch('/user-profile-view/v3/profile/me')` is + // NOT a self-alias: the trailing segment is a literal vanity username, and a + // real Spotify user grabbed the username "me" (open.spotify.com/user/me => + // "Micael Widell"). So "me" resolved to that stranger's public profile for + // every user, stamping his name/avatar/counts onto their spotify.profile + // while the ids and collected data stayed their own. const profileAttrs = await gqlFetch('profileAttributes', {}); const pa = profileAttrs?.data?.me?.profile; - if (!profileData && !pa) { - await page.setData('error', 'Could not fetch profile. Token may be invalid.'); - return { error: 'Could not fetch profile' }; + if (!pa?.username) { + await page.setData( + 'error', + 'Could not confirm which Spotify account is signed in. Please sign in again.' + ); + return { error: 'Could not resolve the signed-in Spotify profile' }; } + const identityUri = pa.uri || 'spotify:user:' + pa.username; + + // spclient is enrichment ONLY (avatar + counts, which GraphQL does not + // expose). Fetch the profile-view for the RESOLVED username — never the + // literal "me" — and still verify it describes the same account before + // trusting it, in case the username itself is ever ambiguous. + const profileData = await spClientFetch( + '/user-profile-view/v3/profile/' + encodeURIComponent(pa.username) + ); + const enrichment = + profileData && profileData.uri === identityUri ? profileData : null; + + if (profileData && !enrichment) { + await page.setData( + 'status', + 'Profile details unavailable for this account; continuing with the signed-in identity.' + ); + } + + // Avatar: prefer the session-bound GraphQL field when Spotify exposes it, + // and only fall back to the (verified-same-account) spclient image. + const avatar = pa.imageUrl || enrichment?.image_url || null; + state.profile = { - id: pa?.username || '', - display_name: profileData?.name || pa?.name || '', - uri: pa?.uri || profileData?.uri || '', - followers: profileData?.followers_count || 0, - following: profileData?.following_count || 0, - images: profileData?.image_url ? [profileData.image_url] : [], + id: pa.username, + display_name: pa.name || '', + uri: identityUri, + followers: enrichment?.followers_count || 0, + following: enrichment?.following_count || 0, + images: avatar ? [avatar] : [], }; - await page.setData('status', 'Logged in as ' + state.profile.display_name); + await page.setData( + 'status', + 'Logged in as ' + (state.profile.display_name || state.profile.id) + ); // Step 2: Liked Songs (paginated via GraphQL) await page.setProgress({ diff --git a/connectors/spotify/spotify-playwright.json b/connectors/spotify/spotify-playwright.json index d1a5e00..bfd8c3a 100644 --- a/connectors/spotify/spotify-playwright.json +++ b/connectors/spotify/spotify-playwright.json @@ -2,7 +2,7 @@ "manifest_version": "1.0", "connector_id": "spotify-playwright", "source_id": "spotify", - "version": "1.0.0", + "version": "1.1.0", "name": "Spotify", "company": "Spotify", "description": "Exports your Spotify library, playlists, listening history, and preferences.", diff --git a/registry.json b/registry.json index 27fdab1..33d289e 100644 --- a/registry.json +++ b/registry.json @@ -127,7 +127,7 @@ { "id": "spotify-playwright", "company": "spotify", - "version": "1.0.0", + "version": "1.1.0", "name": "Spotify", "status": "stable", "description": "Exports your Spotify library, playlists, listening history, and preferences.",