MusicRRPC: Improve listenbrainz support (#4492)
Co-authored-by: Angel Wang <angel@angelcube.dev> Co-authored-by: V <vendicated@riseup.net>
This commit is contained in:
parent
305eac5d26
commit
d25aa03536
1 changed files with 74 additions and 48 deletions
|
|
@ -34,81 +34,107 @@ function fallbackToYoutubeThumbnail(originUrl: string | undefined): string | und
|
|||
return match ? `https://i.ytimg.com/vi/${match[5]}/maxresdefault.jpg` : undefined;
|
||||
}
|
||||
|
||||
async function fetchCoverArt(releaseGroupMBID: string, originUrl?: string): Promise<string | undefined> {
|
||||
if (!releaseGroupMBID) return fallbackToYoutubeThumbnail(originUrl);
|
||||
|
||||
if (coverArtCache.has(releaseGroupMBID)) {
|
||||
return coverArtCache.get(releaseGroupMBID);
|
||||
async function fetchCoverArtArchive(id: string, path: string) {
|
||||
if (coverArtCache.has(id)) {
|
||||
return coverArtCache.get(id);
|
||||
}
|
||||
|
||||
const res = await fetch(`https://coverartarchive.org/release-group/${releaseGroupMBID}`);
|
||||
if (!res.ok) return fallbackToYoutubeThumbnail(originUrl);
|
||||
|
||||
const url = await res.json()
|
||||
.then(json => json.images[0].thumbnails.large ?? fallbackToYoutubeThumbnail(originUrl));
|
||||
coverArtCache.set(releaseGroupMBID, url);
|
||||
const url = await fetch(`https://coverartarchive.org${path}`)
|
||||
.then(res =>
|
||||
res.ok
|
||||
? res.json()
|
||||
: Promise.reject(new Error(`${res.status} ${res.statusText}`))
|
||||
)
|
||||
.then(json => json.images[0].thumbnails.large);
|
||||
|
||||
coverArtCache.set(id, url);
|
||||
return url;
|
||||
}
|
||||
|
||||
async function getUrls(additionalInfo: Record<string, string> | undefined, trackName: string, artistName: string, releaseName: string): Promise<Partial<TrackData>> {
|
||||
async function fetchCoverArt(releaseMBID: string, releaseGroupMBID: string, originUrl?: string): Promise<string | undefined> {
|
||||
return (
|
||||
(releaseMBID && await fetchCoverArtArchive(releaseMBID, `/release/${releaseMBID}`).catch(() => null)) ||
|
||||
(releaseGroupMBID && await fetchCoverArtArchive(releaseGroupMBID, `/release-group/${releaseGroupMBID}`).catch(() => null)) ||
|
||||
fallbackToYoutubeThumbnail(originUrl)
|
||||
);
|
||||
}
|
||||
|
||||
async function tryLookup(query: string): Promise<Record<string, any> | undefined> {
|
||||
const params = new URLSearchParams({
|
||||
fmt: "json",
|
||||
limit: "1"
|
||||
});
|
||||
return await fetch("https://musicbrainz.org/ws/2/recording/?" + params + "&query=" + query, {
|
||||
headers: { "User-Agent": VENCORD_USER_AGENT }
|
||||
})
|
||||
.then(res => res.ok ? res.json() : Promise.reject(new Error(`${res.status} ${res.statusText}`)))
|
||||
.then(json => json.recordings?.[0]);
|
||||
}
|
||||
|
||||
async function getUrls(
|
||||
additionalInfo: Record<string, string> | undefined,
|
||||
trackName: string,
|
||||
artistName: string,
|
||||
releaseName: string
|
||||
): Promise<Partial<TrackData>> {
|
||||
// Well tagged music will have MBIDs which we can use directly. These are optional but highly recommended in ListenBrainz scrobbles.
|
||||
// If your music doesn't have these, it's highly recommended to use https://picard.musicbrainz.org/ to automatically add them
|
||||
if (additionalInfo?.recording_mbid) {
|
||||
const { release_group_mbid, release_mbid, recording_mbid, artist_mbids, origin_url } = additionalInfo;
|
||||
|
||||
return {
|
||||
imageURL: await fetchCoverArt(release_group_mbid, origin_url),
|
||||
trackURL: recording_mbid ? url(`/track/${recording_mbid}`) : undefined,
|
||||
albumURL: release_group_mbid
|
||||
? url(`/release-group/${release_group_mbid}`)
|
||||
: release_mbid
|
||||
? url(`/release/${release_mbid}`)
|
||||
imageURL: await fetchCoverArt(release_mbid, release_group_mbid, origin_url),
|
||||
trackURL: url(`/track/${recording_mbid}`),
|
||||
albumURL: release_mbid
|
||||
? url(`/release/${release_mbid}`)
|
||||
: release_group_mbid
|
||||
? url(`/release-group/${release_group_mbid}`)
|
||||
: undefined,
|
||||
artistURL: artist_mbids?.length ? url(`/artist/${artist_mbids[0]}`) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
// If no MBIDs are present, try to search for the track on MusicBrainz
|
||||
// If no MBIDs are present, try searching MusicBrainz: first by ISRC (if present), then by name.
|
||||
const nameQuery = encodeURIComponent(
|
||||
`artist:"${artistName}" AND recording:"${trackName}"${releaseName ? ` AND release:"${releaseName}"` : ""}`
|
||||
).replace(/[!()*\-~]/g, "\\$&");
|
||||
|
||||
let rawQuery = `artist:"${artistName}" AND recording:"${trackName}"`;
|
||||
if (releaseName)
|
||||
rawQuery += ` AND album:"${releaseName}"`;
|
||||
const query = encodeURIComponent(rawQuery);
|
||||
const queries = additionalInfo?.isrc
|
||||
? [encodeURIComponent(`isrc:${additionalInfo.isrc}`), nameQuery]
|
||||
: [nameQuery];
|
||||
|
||||
if (metadataCache.has(query)) {
|
||||
return metadataCache.get(query) ?? {};
|
||||
}
|
||||
for (const query of queries) {
|
||||
if (metadataCache.has(query)) {
|
||||
return metadataCache.get(query) ?? {};
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
fmt: "json",
|
||||
limit: "1"
|
||||
});
|
||||
const metadata = await tryLookup(query);
|
||||
if (!metadata) continue;
|
||||
|
||||
const metadata = await fetch("https://musicbrainz.org/ws/2/recording/?" + params + "&query=" + query, {
|
||||
headers: { "User-Agent": VENCORD_USER_AGENT }
|
||||
})
|
||||
.then(res => res.ok ? res.json() : Promise.reject(new Error(`${res.status} ${res.statusText}`)))
|
||||
.then(json => json.recordings?.[0]);
|
||||
const artist = metadata["artist-credit"]?.[0]?.artist;
|
||||
const release = metadata.releases?.find((release: { title: string; }) => release.title === releaseName) || metadata.releases?.[0];
|
||||
|
||||
if (!metadata) {
|
||||
const data = additionalInfo?.origin_url ? { imageURL: fallbackToYoutubeThumbnail(additionalInfo.origin_url) } : {};
|
||||
const data: Partial<TrackData> = {
|
||||
imageURL: await fetchCoverArt(release?.id, release?.["release-group"]?.id, additionalInfo?.origin_url),
|
||||
trackURL: url(`/track/${metadata.id}/`),
|
||||
albumURL: release?.id
|
||||
? url(`/release/${release.id}/`)
|
||||
: release?.["release-group"]?.id
|
||||
? url(`/release-group/${release["release-group"].id}/`)
|
||||
: undefined,
|
||||
artistURL: artist?.id ? url(`/artist/${artist.id}/`) : undefined,
|
||||
album: additionalInfo?.release_name ?? release?.title ?? "Unknown",
|
||||
};
|
||||
metadataCache.set(query, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
const artist = metadata["artist-credit"]?.[0]?.artist;
|
||||
const release = metadata.releases?.[0];
|
||||
const fallback: Partial<TrackData> = additionalInfo?.origin_url
|
||||
? { imageURL: fallbackToYoutubeThumbnail(additionalInfo.origin_url) }
|
||||
: {};
|
||||
metadataCache.set(queries.at(-1)!, fallback);
|
||||
|
||||
const data: Partial<TrackData> = {
|
||||
imageURL: await fetchCoverArt(release?.["release-group"]?.id, additionalInfo?.origin_url),
|
||||
trackURL: url(`/track/${metadata.id}/`),
|
||||
albumURL: release?.id ? url(`/release/${release.id}/`) : release?.["release-group"]?.id ? url(`/release-group/${release["release-group"].id}/`) : undefined,
|
||||
artistURL: artist?.id ? url(`/artist/${artist.id}/`) : undefined,
|
||||
};
|
||||
metadataCache.set(query, data);
|
||||
|
||||
return data;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export const ListenBrainzScrobbler: ScrobblerBackend = {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue