Skip to content
Open
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
131 changes: 91 additions & 40 deletions src/play.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,31 +45,43 @@ const playMusic: tool<{
spotifyUri = `spotify:${type}:${id}`;
}

await handleSpotifyRequest(async (spotifyApi) => {
const device = deviceId || '';

if (!spotifyUri) {
await spotifyApi.player.startResumePlayback(device);
return;
}

if (type === 'track') {
await spotifyApi.player.startResumePlayback(device, undefined, [
spotifyUri,
]);
} else {
await spotifyApi.player.startResumePlayback(device, spotifyUri);
}
});
try {
await handleSpotifyRequest(async (spotifyApi) => {
const device = deviceId || '';

if (!spotifyUri) {
await spotifyApi.player.startResumePlayback(device);
return;
}

if (type === 'track') {
await spotifyApi.player.startResumePlayback(device, undefined, [
spotifyUri,
]);
} else {
await spotifyApi.player.startResumePlayback(device, spotifyUri);
}
});

return {
content: [
{
type: 'text',
text: `Started playing ${type || 'music'} ${id ? `(ID: ${id})` : ''}`,
},
],
};
return {
content: [
{
type: 'text',
text: `Started playing ${type || 'music'} ${id ? `(ID: ${id})` : ''}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error starting playback: ${error instanceof Error ? error.message : String(error)}`,
isError: true,
},
],
};
}
},
};

Expand Down Expand Up @@ -182,24 +194,48 @@ const createPlaylist: tool<{
handler: async (args, _extra: SpotifyHandlerExtra) => {
const { name, description, public: isPublic = false } = args;

const result = await handleSpotifyRequest(async (spotifyApi) => {
const me = await spotifyApi.currentUser.profile();

return await spotifyApi.playlists.createPlaylist(me.id, {
name,
description,
public: isPublic,
try {
const result = await handleSpotifyRequest(async (spotifyApi) => {
const me = await spotifyApi.currentUser.profile();

return await spotifyApi.playlists.createPlaylist(me.id, {
name,
description,
public: isPublic,
});
});
});

return {
content: [
{
type: 'text',
text: `Successfully created playlist "${name}"\nPlaylist ID: ${result.id}\nPlaylist URL: ${result.external_urls.spotify}`,
},
],
};
if (!result?.id) {
return {
content: [
{
type: 'text',
text: `Error: Playlist creation failed — Spotify API returned no data. The operation may not have succeeded.`,
isError: true,
},
],
};
}

return {
content: [
{
type: 'text',
text: `Successfully created playlist "${name}"\nPlaylist ID: ${result.id}\nPlaylist URL: ${result.external_urls.spotify}`,
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error creating playlist: ${error instanceof Error ? error.message : String(error)}`,
isError: true,
},
],
};
}
},
};

Expand Down Expand Up @@ -236,14 +272,29 @@ const addTracksToPlaylist: tool<{
try {
const trackUris = trackIds.map((id) => `spotify:track:${id}`);

let addSucceeded = true;
await handleSpotifyRequest(async (spotifyApi) => {
await spotifyApi.playlists.addItemsToPlaylist(
playlistId,
trackUris,
position,
);
}).catch(() => {
addSucceeded = false;
});

if (!addSucceeded) {
return {
content: [
{
type: 'text',
text: `Warning: Tracks may not have been added — Spotify API returned no confirmation. Verify the playlist manually.`,
isError: true,
},
],
};
}

return {
content: [
{
Expand Down
11 changes: 8 additions & 3 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,16 +355,21 @@ export async function handleSpotifyRequest<T>(
const spotifyApi = await createSpotifyApi();
return await action(spotifyApi);
} catch (error) {
// Skip JSON parsing errors as these are actually successful operations
const errorMessage = error instanceof Error ? error.message : String(error);
const errorMessage =
error instanceof Error ? error.message : String(error);
// JSON parse errors typically occur on successful 204 No Content responses
// from void operations (play, pause, skip, etc.). The operation succeeded,
// but the SDK fails to parse the empty response body.
if (
errorMessage.includes('Unexpected token') ||
errorMessage.includes('Unexpected non-whitespace character') ||
errorMessage.includes('Exponent part is missing a number in JSON')
) {
console.warn(
`[Spotify] JSON parse error on response (void operation likely succeeded): ${errorMessage}`,
);
return undefined as T;
}
// Rethrow other errors
throw error;
}
}