diff --git a/BatchAddRecordingAliases.user.js b/BatchAddRecordingAliases.user.js index b353a52..fe68399 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.17 +// @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,448 @@ 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 rgba(127, 127, 127, .32); + background: transparent; + color: inherit; + 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 rgba(127, 127, 127, .28); + } + + #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: rgba(164, 0, 0, .08); + } + + .yomo-row-error { + margin-top: 2px; + font-size: 11px; + } + + #yomo-type-wrap select { + max-width: 145px; + } + + @media (prefers-color-scheme: dark) { + #yomo-alias-panel { + border-color: rgba(220, 220, 220, .24); + background: transparent; + } + + #yomo-table-wrap { + border-top-color: rgba(220, 220, 220, .18); + } + + #yomo-summary.yomo-error, + .yomo-row-error { + color: #ff9b9b; + } + + #yomo-summary.yomo-warning { + color: #e0ba67; + } + + #yomo-table tr.yomo-failed { + background: rgba(255, 120, 120, .08); + } + } + + @media (max-width: 700px) { + #yomo-actions { + margin-left: 0; + } + + #yomo-alias-panel { + padding: 7px; + } + + #yomo-table .yomo-recording, + #yomo-table .yomo-alias { + width: auto; + } + } + `; + + document.head.appendChild(style); + } + + function findTabs() { + return ( + document.querySelector('#content ul.tabs') || + document.querySelector('ul.tabs') + ); + } - box.innerHTML = ` -
- Copy track titles → recording aliases + function injectLauncherAndPanel() { + injectStyles(); + + const tabs = findTabs(); + const content = document.querySelector('#content') || document.body; + + const launch = document.createElement('button'); + launch.id = 'yomo-alias-launch'; + launch.type = 'button'; + launch.textContent = 'Recording aliases'; + + let panelAnchor; + + if (tabs) { + const li = document.createElement('li'); + li.style.float = 'right'; + li.style.marginLeft = '6px'; + li.appendChild(launch); + tabs.appendChild(li); + panelAnchor = tabs; + } else { + const fallback = document.createElement('div'); + fallback.style.textAlign = 'right'; + fallback.style.margin = '4px 0'; + fallback.appendChild(launch); + content.prepend(fallback); + panelAnchor = fallback; + } - + const panel = document.createElement('div'); + panel.id = 'yomo-alias-panel'; + + panel.innerHTML = ` +
+ Source release + + +
-