Files
stef-openclaw-skills/skills/spotify/src/playlists.ts
T

119 lines
4.3 KiB
TypeScript

import { createSpotifyApiClient, type SpotifyApiClient } from "./api-client.js";
import type { CliDeps, ParsedCli } from "./cli.js";
import { mapPlaylist, mapTrack } from "./search.js";
import type { SpotifyTrack } from "./types.js";
type PlaylistClient = Pick<SpotifyApiClient, "createPlaylist" | "addItemsToPlaylist" | "removeItemsFromPlaylist" | "searchTracks">;
export function validateTrackUris(uris: string[]): string[] {
if (uris.length === 0) {
throw new Error("At least one spotify:track URI is required.");
}
for (const uri of uris) {
if (!uri.startsWith("spotify:track:")) {
throw new Error(`Invalid Spotify track URI: ${uri}`);
}
}
return uris;
}
function snapshotIds(results: Array<{ snapshot_id?: string }>): string[] {
return results.map((result) => result.snapshot_id).filter((id): id is string => Boolean(id));
}
export async function runCreatePlaylistCommand(
args: ParsedCli,
deps: CliDeps,
client: PlaylistClient = createSpotifyApiClient()
): Promise<number> {
const name = args.positional.join(" ").trim();
if (!name) {
throw new Error("Missing playlist name.");
}
const playlist = mapPlaylist(await client.createPlaylist(name, { description: args.description, public: Boolean(args.public) }));
if (args.json) {
deps.stdout.write(`${JSON.stringify({ playlist }, null, 2)}\n`);
} else {
deps.stdout.write(`Created playlist ${playlist.id}: ${playlist.name}\n`);
}
return 0;
}
export async function runAddToPlaylistCommand(
args: ParsedCli,
deps: CliDeps,
client: PlaylistClient = createSpotifyApiClient()
): Promise<number> {
const [playlistId, ...uris] = args.positional;
if (!playlistId) {
throw new Error("Missing playlist id.");
}
const validUris = validateTrackUris(uris);
const results = await client.addItemsToPlaylist(playlistId, validUris);
const output = { playlistId, count: validUris.length, snapshotIds: snapshotIds(results) };
deps.stdout.write(args.json ? `${JSON.stringify(output, null, 2)}\n` : `Added ${output.count} track(s) to ${playlistId}.\n`);
return 0;
}
export async function runRemoveFromPlaylistCommand(
args: ParsedCli,
deps: CliDeps,
client: PlaylistClient = createSpotifyApiClient()
): Promise<number> {
const [playlistId, ...uris] = args.positional;
if (!playlistId) {
throw new Error("Missing playlist id.");
}
const validUris = validateTrackUris(uris);
const results = await client.removeItemsFromPlaylist(playlistId, validUris);
const output = { playlistId, count: validUris.length, snapshotIds: snapshotIds(results) };
deps.stdout.write(args.json ? `${JSON.stringify(output, null, 2)}\n` : `Removed ${output.count} track(s) from ${playlistId}.\n`);
return 0;
}
export async function searchAndAdd(
playlistId: string,
queries: string[],
client: Pick<PlaylistClient, "searchTracks" | "addItemsToPlaylist">
): Promise<{ added: Array<{ query: string; uri: string; name: string; artists: string[] }>; missed: string[]; snapshotIds: string[] }> {
if (!playlistId) {
throw new Error("Missing playlist id.");
}
if (queries.length === 0) {
throw new Error("At least one search query is required.");
}
const added: Array<{ query: string; uri: string; name: string; artists: string[] }> = [];
const missed: string[] = [];
for (const query of queries) {
const tracks: SpotifyTrack[] = await client.searchTracks(query, 1);
const first = tracks[0];
if (!first) {
missed.push(query);
continue;
}
const output = mapTrack(first);
added.push({ query, uri: output.uri, name: output.name, artists: output.artists });
}
const mutationResults = added.length > 0
? await client.addItemsToPlaylist(playlistId, added.map((entry) => entry.uri))
: [];
return { added, missed, snapshotIds: snapshotIds(mutationResults) };
}
export async function runSearchAndAddCommand(
args: ParsedCli,
deps: CliDeps,
client: PlaylistClient = createSpotifyApiClient()
): Promise<number> {
const [playlistId, ...queries] = args.positional;
const output = await searchAndAdd(playlistId, queries, client);
if (args.json) {
deps.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
} else {
deps.stdout.write(`Added ${output.added.length} track(s) to ${playlistId}; missed ${output.missed.length}.\n`);
}
return 0;
}