99 lines
3.0 KiB
TypeScript
99 lines
3.0 KiB
TypeScript
import { createSpotifyApiClient, type SpotifyApiClient } from "./api-client.js";
|
|
import type { CliDeps, ParsedCli } from "./cli.js";
|
|
import type { SpotifyPlaylist, SpotifyTrack } from "./types.js";
|
|
|
|
export interface TrackOutput {
|
|
id: string;
|
|
uri: string;
|
|
name: string;
|
|
artists: string[];
|
|
album?: string;
|
|
externalUrl?: string;
|
|
}
|
|
|
|
export interface PlaylistOutput {
|
|
id: string;
|
|
name: string;
|
|
public: boolean | null;
|
|
owner: string;
|
|
externalUrl?: string;
|
|
}
|
|
|
|
function numberOption(value: string | undefined, fallback: number): number {
|
|
if (!value) {
|
|
return fallback;
|
|
}
|
|
const parsed = Number(value);
|
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
}
|
|
|
|
export function clamp(value: number, min: number, max: number): number {
|
|
return Math.min(max, Math.max(min, Math.trunc(value)));
|
|
}
|
|
|
|
export function mapTrack(track: SpotifyTrack): TrackOutput {
|
|
return {
|
|
id: track.id,
|
|
uri: track.uri,
|
|
name: track.name,
|
|
artists: track.artists.map((artist) => artist.name),
|
|
album: track.album?.name,
|
|
externalUrl: track.external_urls?.spotify
|
|
};
|
|
}
|
|
|
|
export function mapPlaylist(playlist: SpotifyPlaylist): PlaylistOutput {
|
|
return {
|
|
id: playlist.id,
|
|
name: playlist.name,
|
|
public: playlist.public,
|
|
owner: playlist.owner.display_name ?? playlist.owner.id,
|
|
externalUrl: playlist.external_urls?.spotify
|
|
};
|
|
}
|
|
|
|
export function formatTrack(track: TrackOutput): string {
|
|
const album = track.album ? ` (${track.album})` : "";
|
|
return `${track.uri} | ${track.name} | ${track.artists.join(", ")}${album}`;
|
|
}
|
|
|
|
export function formatPlaylist(playlist: PlaylistOutput): string {
|
|
const visibility = playlist.public === true ? "public" : playlist.public === false ? "private" : "unknown";
|
|
return `${playlist.id} | ${visibility} | ${playlist.owner} | ${playlist.name}`;
|
|
}
|
|
|
|
export async function runSearchCommand(
|
|
args: ParsedCli,
|
|
deps: CliDeps,
|
|
client: Pick<SpotifyApiClient, "searchTracks"> = createSpotifyApiClient()
|
|
): Promise<number> {
|
|
const query = args.positional.join(" ").trim();
|
|
if (!query) {
|
|
throw new Error("Missing search query.");
|
|
}
|
|
const limit = clamp(numberOption(args.limit, 5), 1, 10);
|
|
const tracks = (await client.searchTracks(query, limit)).map(mapTrack);
|
|
if (args.json) {
|
|
deps.stdout.write(`${JSON.stringify({ tracks }, null, 2)}\n`);
|
|
} else {
|
|
deps.stdout.write(`${tracks.map(formatTrack).join("\n")}${tracks.length ? "\n" : ""}`);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
export async function runListPlaylistsCommand(
|
|
args: ParsedCli,
|
|
deps: CliDeps,
|
|
client: Pick<SpotifyApiClient, "listPlaylists"> = createSpotifyApiClient()
|
|
): Promise<number> {
|
|
const limit = clamp(numberOption(args.limit, 50), 1, 50);
|
|
const offset = Math.max(0, numberOption(args.offset, 0));
|
|
const playlists = (await client.listPlaylists(limit, offset)).map(mapPlaylist);
|
|
if (args.json) {
|
|
deps.stdout.write(`${JSON.stringify({ playlists }, null, 2)}\n`);
|
|
} else {
|
|
deps.stdout.write(`${playlists.map(formatPlaylist).join("\n")}${playlists.length ? "\n" : ""}`);
|
|
}
|
|
return 0;
|
|
}
|