Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/spotify

src/lib/normalize.ts

94 lines · 3.2 KB · TypeScript
export function requireNonEmptyString(value: unknown, message: string): string {
	const stringValue = typeof value === 'string' ? value.trim() : ''
	if (!stringValue) throw new Error(message)
	return stringValue
}

export function chunkArray<T>(items: T[], size: number): T[][] {
	const chunks: T[][] = []
	for (let index = 0; index < items.length; index += size) {
		chunks.push(items.slice(index, index + size))
	}
	return chunks
}

export function parseLimit(value: unknown, defaultValue: number, max: number): number {
	const parsed = Number(value ?? defaultValue)
	if (!Number.isFinite(parsed)) return defaultValue
	return Math.min(max, Math.max(1, Math.round(parsed)))
}

export function normalizeSpotifyPlaylistUri(value: unknown): string {
	const stringValue = requireNonEmptyString(
		value,
		'Spotify playlist IDs or URIs must be non-empty strings.',
	)
	if (stringValue.startsWith('spotify:playlist:')) return stringValue
	const match = stringValue.match(/open\.spotify\.com\/playlist\/([^?/#]+)/)
	const playlistId = match ? match[1] : stringValue
	return 'spotify:playlist:' + playlistId
}

export function normalizeSpotifyTrackId(value: unknown): string {
	const stringValue = requireNonEmptyString(
		value,
		'Spotify track IDs or URIs must be non-empty strings.',
	)
	if (stringValue.startsWith('spotify:track:')) {
		return stringValue.split(':')[2] ?? stringValue
	}
	const match = stringValue.match(/open\.spotify\.com\/track\/([^?/#]+)/)
	return match ? match[1] : stringValue
}

export function normalizeSpotifyTrackUri(value: unknown): string {
	return 'spotify:track:' + normalizeSpotifyTrackId(value)
}

export function normalizeTrackUris(uris: unknown): string[] {
	if (!Array.isArray(uris)) {
		throw new Error('An array of Spotify track URIs is required.')
	}
	const withUris = uris.map((uri) =>
		typeof uri === 'string' && uri.trim() ? normalizeSpotifyTrackUri(uri) : '',
	)
	const invalid = withUris
		.map((uri, index) =>
			uri && uri.startsWith('spotify:track:') ? null : JSON.stringify(uris[index] ?? null),
		)
		.filter((entry) => entry !== null)
	if (invalid.length) {
		throw new Error(
			'Invalid Spotify track URI(s) (expected non-empty strings like spotify:track:xxx): ' +
				invalid.join(', '),
		)
	}
	return withUris
}

export function normalizeIdList(values: unknown, message: string, max?: number): string[] {
	if (!Array.isArray(values)) throw new Error(message)
	const invalid = values
		.filter((value) => typeof value !== 'string' || !value.trim())
		.map((value) => JSON.stringify(value ?? null))
	if (invalid.length) {
		throw new Error(message + ' Invalid entries: ' + invalid.join(', '))
	}
	const ids = values.map((value) => String(value).trim()).map(normalizeSpotifyTrackId)
	if (max && ids.length > max) {
		throw new Error('Spotify accepts at most ' + max + ' track ids.')
	}
	return ids
}

export function normalizeArtistNames(artists: unknown): string[] {
	return Array.isArray(artists) ? artists.map((artist: any) => artist?.name).filter(Boolean) : []
}

export function isDryRun(params: { dryRun?: unknown } = {}): boolean {
	return params.dryRun === true
}

export function dryRunPreview<T extends Record<string, unknown>>(payload: T) {
	return { dryRun: true as const, ...payload }
}