您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Hooks into the inline recording search of the release editor to allow searching by ISRC.
// ==UserScript== // @name MusicBrainz: Search by ISRC in release editor // @namespace https://musicbrainz.org/user/chaban // @version 1.1.1 // @tag ai-created // @description Hooks into the inline recording search of the release editor to allow searching by ISRC. // @author chaban // @license MIT // @match *://*.musicbrainz.org/release/*/edit* // @match *://*.musicbrainz.org/release/add* // @icon https://musicbrainz.org/static/images/favicons/android-chrome-512x512.png // @grant none // ==/UserScript== (function() { 'use strict'; const SCRIPT_NAME = GM.info.script.name; // --- ⚙️ DEBUG MODE --- const DEBUG_MODE = false; const log = (...args) => { if (DEBUG_MODE) { console.log(`[${SCRIPT_NAME}]`, ...args); } }; log('Script loaded and running.'); // Regex to identify an ISRC. It matches the 12-character code, allowing for optional hyphens. const ISRC_REGEX = /^([A-Z]{2})-?([A-Z0-9]{3})-?(\d{2})-?(\d{5})$/i; /** * Converts an artist credit object from the /ws/2 API into a simple string. * @param {Array<object>} artistCredit - The artist-credit object from the API response. * @returns {string} The formatted artist credit string. */ function reduceArtistCredit(artistCredit) { if (!artistCredit || !Array.isArray(artistCredit)) return ''; return artistCredit.map(ac => (ac.name || '') + (ac.joinphrase || '')).join(''); } /** * Creates an array of unique elements from an array of objects. * @param {Array<object>} array The array to process. * @param {function} keyFn A function that returns the key to determine uniqueness. * @returns {Array<object>} */ function uniqBy(array, keyFn) { const seen = new Set(); return array.filter(item => { const key = keyFn(item); return seen.has(key) ? false : seen.add(key); }); } /** * Monkey-patches the recording association autocomplete hook to modify the search behavior. */ function patchAutocompleteHook() { const releaseEditor = window.MB?.releaseEditor; const recordingAssociation = releaseEditor?.recordingAssociation; if (!recordingAssociation?.autocompleteHook) { log('Recording association hook not found. Cannot patch.'); return; } const originalAutocompleteHook = recordingAssociation.autocompleteHook; recordingAssociation.autocompleteHook = function(track) { const originalHook = originalAutocompleteHook.call(this, track); return function(requestArgs) { const searchTerm = requestArgs.data.q.trim(); const isrcMatch = searchTerm.match(ISRC_REGEX); if (isrcMatch) { const isrc = isrcMatch.slice(1).join('').toUpperCase(); log(`ISRC detected: ${isrc}. Modifying search query.`); const newRequestArgs = { url: '/ws/2/recording', dataType: 'json', data: { query: `isrc:${releaseEditor.utils.escapeLuceneValue(isrc)}`, limit: 10, fmt: 'json' }, success: function(data) { const recordings = data.recordings || []; const cleanedData = recordings.map(item => { const artistCredit = item['artist-credit']; const appearsOn = uniqBy( item.releases?.map(release => ({ name: release.title, gid: release.id, releaseGroupGID: release['release-group'].id, })) ?? [], x => x.releaseGroupGID ); return { name: item.title, length: item.length, gid: item.id, comment: item.disambiguation, video: item.video || false, artist: reduceArtistCredit(artistCredit), artistCredit: { names: artistCredit }, appearsOn: { hits: appearsOn.length, results: appearsOn, entityType: 'release', }, }; }); const pager = { current: (data.offset || 0) / (data.limit || 10) + 1, pages: Math.ceil((data.count || 0) / (data.limit || 10)), }; cleanedData.push(pager); requestArgs.success(cleanedData); }, error: requestArgs.error }; return newRequestArgs; } return originalHook(requestArgs); }; }; log("Successfully patched the recording association autocomplete hook."); } /** * Waits for the release editor and its utilities to be fully initialized. */ function waitForEditor() { if (window.MB?.releaseEditor?.recordingAssociation?.autocompleteHook) { log("Release editor is ready. Applying patch."); patchAutocompleteHook(); } else { log("Waiting for release editor to initialize..."); setTimeout(waitForEditor, 250); } } // Start the process once the page is ready. if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', waitForEditor); } else { waitForEditor(); } })();