119 lines
4.3 KiB
TypeScript
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;
|
|
}
|