// ==UserScript== // @name MB: QoL: Inline all recording's tracks on releases // @version 2024.7.25 // @description Display all tracks and releases on which a recording appears from the release page. // @author ROpdebee // @license MIT; https://opensource.org/licenses/MIT // @namespace https://github.com/ROpdebee/mb-userscripts // @downloadURL https://raw.github.com/ROpdebee/mb-userscripts/main/mb_qol_inline_recording_tracks.user.js // @updateURL https://raw.github.com/ROpdebee/mb-userscripts/main/mb_qol_inline_recording_tracks.user.js // @match *://*.musicbrainz.eu/release/* // @match *://*.musicbrainz.org/release/* // @exclude */release/*/* // @exclude */release/add // @run-at document-end // @grant none // ==/UserScript== function splitChunks(arr, chunkSize) { let chunks = []; for (let i = 0; i < arr.length; i += chunkSize) { chunks.push(arr.slice(i, i + chunkSize)); } return chunks; } const queuedFetch = (() => { let fetchQueue = []; // This may make multiple concurrent requests if an old one is still pending // while the new one is queued. // FIXME: This runs continuously even though no fetches are queued. setInterval(async () => { let url, resolve; try { [url, resolve] = fetchQueue.shift(); } catch { return; } try { let resp = await fetch(url); if (!resp.ok) fetchQueue.push([url, resolve]); else resolve(resp); } catch { fetchQueue.push([url, resolve]); } }, 500); return function(url) { return new Promise((resolve) => fetchQueue.push([url, resolve])); } })(); async function loadRecordingInfo(rids) { const query = rids.map((rid) => 'rid:' + rid).join(' OR '); const url = location.origin + '/ws/2/recording?fmt=json&query=' + query; let resp = await (await queuedFetch(url)).json(); let perRecId = {}; resp.recordings.forEach((rec) => perRecId[rec.id] = rec); return perRecId; } function getTrackIndex(track, mediumPosition, mediumTrackCount) { return `#${mediumPosition}.${track.number}`; } function getTrackIndices(media) { return media.flatMap((medium) => medium.track.map((track) => getTrackIndex(track, medium.position, medium['track-count']))) .join(', '); } function getReleaseName(release) { return `${release.title}` + (release.disambiguation ? ` (${release.disambiguation})` : ''); } function formatRow(release) { return `${getReleaseName(release)} (${getTrackIndices(release.media)})`; } function insertRows(recordingTd, recordingInfo) { let rowElements = recordingInfo.releases .sort(compareReleases) .map(formatRow) .map(row => '