Skip to content
This repository was archived by the owner on Jul 18, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
34 changes: 34 additions & 0 deletions connector-index.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
60 changes: 46 additions & 14 deletions connectors/spotify/spotify-playwright.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
2 changes: 1 addition & 1 deletion connectors/spotify/spotify-playwright.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
2 changes: 1 addition & 1 deletion registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
Loading