Something went wrong. Try again.
A decentralized music tracking and discovery platform built on AT Protocol 🎵 rocksky.app
spotify atproto lastfm musicbrainz scrobbling listenbrainz
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667import _ from "lodash";import * as R from "ramda";
// Canonical Last.fm "no image" placeholder. Persisted/published in place of// null/undefined album art so downstream consumers always see a non-null URL.export const DEFAULT_ALBUM_ART = "https://lastfm.freetls.fastly.net/i/u/300x300/2a96cbd8b46e442fc41c2b86b821562f.png";
export const withFallbackAlbumArt = (url: string | null | undefined): string => url ?? DEFAULT_ALBUM_ART;
export const dedupeTracksKeepLyrics = (tracks) => { const trackMap = new Map();
for (const track of tracks) { const key = `${track.discNumber} - ${track.trackNumber}`;
if (!key) continue;
const existing = trackMap.get(key);
// If current has lyrics and either no existing or existing has no lyrics, replace it if (!existing || (!existing.lyrics && track.lyrics)) { trackMap.set(key, track); } }
return Array.from(trackMap.values());};
type AnyObject = Record<string, any>;
const isObject = (val: unknown): val is AnyObject => typeof val === "object" && val !== null && !Array.isArray(val);
export const deepCamelCaseKeys = <T>(obj: T): any => { if (Array.isArray(obj)) { return obj.map(deepCamelCaseKeys); } else if (isObject(obj)) { return R.pipe( R.toPairs, R.map( ([key, value]) => [_.camelCase(String(key)), deepCamelCaseKeys(value)] as [string, any], ), R.fromPairs, )(obj as object); } return obj;};
export const deepSnakeCaseKeys = <T>(obj: T): any => { if (Array.isArray(obj)) { return obj.map(deepSnakeCaseKeys); } else if (isObject(obj)) { return R.pipe( R.toPairs, R.map( ([key, value]) => [_.snakeCase(String(key)), deepSnakeCaseKeys(value)] as [string, any], ), R.fromPairs, )(obj as object); } return obj;};