From 2e6f3f9f0d2c365168f933b32a517694c62b8c1f Mon Sep 17 00:00:00 2001 From: YoGo9 <162524691+YoGo9@users.noreply.github.com> Date: Tue, 15 Sep 2026 14:03:43 -0400 Subject: [PATCH 1/2] Update user script for batch adding recording aliases --- BatchAddRecordingAliases.user.js | 641 +++++++++++++++++++++++++------ 1 file changed, 520 insertions(+), 121 deletions(-) diff --git a/BatchAddRecordingAliases.user.js b/BatchAddRecordingAliases.user.js index b353a52..9102aba 100644 --- a/BatchAddRecordingAliases.user.js +++ b/BatchAddRecordingAliases.user.js @@ -4,15 +4,15 @@ * MIT License */ -/* global $ helper edits sidebar requests GM_info aliases */ +/* global $ helper edits requests aliases */ 'use strict'; // ==UserScript== // @name Batch Add Recording Aliases from another Release // @namespace YoGo9 // @author YoGo9 -// @version 12/26/25 -// @description Paste a source release URL/MBID; copy its track titles as recording aliases on the current (target) release's recordings. +// @version 2026.09.15 +// @description Copy track titles from another MusicBrainz release to recording aliases on the current release. // @homepage https://github.com/YoGo9/Scripts // @updateURL https://raw.githubusercontent.com/YoGo9/Scripts/main/BatchAddRecordingAliases.user.js // @downloadURL https://raw.githubusercontent.com/YoGo9/Scripts/main/BatchAddRecordingAliases.user.js @@ -23,13 +23,11 @@ // @grant GM_info // @run-at document-end // ==/UserScript== -'use strict'; if (!/^\/release\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(location.pathname)) { return; } - (function () { const HOST = location.origin; const WS_HOST = 'https://musicbrainz.org'; @@ -51,18 +49,26 @@ if (!/^\/release\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ async function wsRelease(releaseMbid) { const url = `${WS_HOST}/ws/2/release/${releaseMbid}?inc=recordings+media&fmt=json`; - const r = await fetch(url, { headers: { Accept: 'application/json' } }); - if (!r.ok) throw new Error(`WS fetch failed ${r.status} for release ${releaseMbid}`); + const r = await fetch(url, { + headers: { Accept: 'application/json' }, + }); + + if (!r.ok) { + throw new Error(`MusicBrainz returned HTTP ${r.status}`); + } + return r.json(); } function flattenTracks(releaseJson) { const out = []; + for (const medium of (releaseJson.media || [])) { - const mPos = medium.position; + const mediumPosition = medium.position; + for (const track of (medium.tracks || [])) { out.push({ - mediumPosition: mPos, + mediumPosition, trackPosition: track.position, trackTitle: track.title, recordingMbid: track.recording?.id || null, @@ -70,130 +76,423 @@ if (!/^\/release\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ }); } } + return out; } function mapByRecording(tracks) { - const m = new Map(); - for (const t of tracks) { - if (t.recordingMbid) m.set(t.recordingMbid, t.trackTitle); + const map = new Map(); + + for (const track of tracks) { + if (track.recordingMbid) { + map.set(track.recordingMbid, track.trackTitle); + } } - return m; + + return map; } function mapByPosition(tracks) { - const m = new Map(); - for (const t of tracks) { - m.set(`${t.mediumPosition}-${t.trackPosition}`, t.trackTitle); + const map = new Map(); + + for (const track of tracks) { + map.set( + `${track.mediumPosition}-${track.trackPosition}`, + track.trackTitle + ); } - return m; + + return map; } - function esc(s) { - return String(s ?? '').replace(/[&<>"']/g, c => ( - { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c] - )); + function esc(value) { + return String(value ?? '').replace(/[&<>"']/g, char => ({ + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + })[char]); } function editNote(sourceReleaseUrl) { return `Batch Add Recording Alias from release ${sourceReleaseUrl}`; } - function injectUI() { - const box = document.createElement('div'); - box.style.border = '1px solid #ccc'; - box.style.padding = '10px'; - box.style.margin = '10px 0'; - box.style.background = '#fff'; + function injectStyles() { + const style = document.createElement('style'); + + style.textContent = ` + #yomo-alias-launch { + margin-left: .5em; + white-space: nowrap; + } + + #yomo-alias-panel { + display: none; + margin: 8px 0 12px; + padding: 8px 10px; + border: 1px solid #d8d8d8; + background: #fafafa; + font-size: 13px; + } + + #yomo-alias-panel.yomo-open { + display: block; + } + + #yomo-alias-panel .yomo-line { + display: flex; + align-items: center; + gap: 7px; + flex-wrap: wrap; + } + + #yomo-alias-panel .yomo-line + .yomo-line { + margin-top: 6px; + } + + #yomo-src { + flex: 1 1 420px; + min-width: 220px; + max-width: 720px; + } + + #yomo-locale { + width: 54px; + } + + #yomo-summary { + min-height: 18px; + margin-top: 6px; + } + + #yomo-summary.yomo-error, + .yomo-row-error { + color: #a40000; + } + + #yomo-summary.yomo-warning { + color: #9a6700; + } + + #yomo-actions { + margin-left: auto; + } + + #yomo-table-wrap { + display: none; + margin-top: 6px; + max-height: 330px; + overflow: auto; + border-top: 1px solid #ddd; + } + + #yomo-table-wrap.yomo-visible { + display: block; + } + + #yomo-table { + width: 100%; + margin-top: 5px; + } + + #yomo-table th, + #yomo-table td { + padding: 3px 5px; + vertical-align: top; + } + + #yomo-table th:first-child, + #yomo-table td:first-child { + width: 24px; + text-align: center; + } + + #yomo-table .yomo-disc, + #yomo-table .yomo-track { + width: 44px; + white-space: nowrap; + } + + #yomo-table .yomo-recording { + width: 43%; + } + + #yomo-table .yomo-alias { + width: 43%; + } + + #yomo-table tr.yomo-done { + opacity: .55; + } + + #yomo-table tr.yomo-failed { + background: #fff2f2; + } + + .yomo-row-error { + margin-top: 2px; + font-size: 11px; + } + + #yomo-type-wrap select { + max-width: 145px; + } + + @media (max-width: 700px) { + #yomo-actions { + margin-left: 0; + } + + #yomo-alias-panel { + padding: 7px; + } - box.innerHTML = ` -
- Copy track titles → recording aliases + #yomo-table .yomo-recording, + #yomo-table .yomo-alias { + width: auto; + } + } + `; - + document.head.appendChild(style); + } -