Harmony: Enhancements

Adds some convenience features, various UI and behavior settings, as well as an improved language detection to Harmony.

当前为 2025-08-05 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Harmony: Enhancements
// @namespace    https://musicbrainz.org/user/chaban
// @version      1.1.1
// @tag          ai-created
// @description  Adds some convenience features, various UI and behavior settings, as well as an improved language detection to Harmony.
// @author       chaban
// @license      MIT
// @match        https://harmony.pulsewidth.org.uk/*
// @connect      none
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_addStyle
// @grant        GM_info
// ==/UserScript==

(function() {
    'use strict';

    const SCRIPT_NAME = GM_info.script.name;
    const TOOLTIP_DISPLAY_DURATION = 2000;

    // --- CONFIGURATION ---

    const SETTINGS_CONFIG = {
        // Seeder Behavior
        skipConfirmation: { key: 'enhancements.seeder.skipConfirmation', label: 'Skip MusicBrainz confirmation page when adding a new release', description: 'Automatically skips the interstitial page when seeing data from external pages.', defaultValue: false, section: 'Seeder Behavior', type: 'checkbox' },
        updateProperties: { key: 'enhancements.seeder.updateProperties', label: 'Include GTIN and packaging when updating an existing release', description: 'When using the "Update external links in MusicBrainz" button, also include the GTIN (barcode) and set packaging to "None".', defaultValue: false, section: 'Seeder Behavior', type: 'checkbox' },
        // UI Settings
        hideDebugMessages: { key: 'enhancements.ui.hideDebugMessages', label: 'Hide debug messages on release pages', description: 'Hides the boxes containing debug information from Harmony, such as guessed languages and scripts.', defaultValue: true, section: 'UI Settings', type: 'checkbox' },
        hideReleaseInfo: { key: 'enhancements.ui.hideReleaseInfo', label: 'Hide Availability, Sources, and External Links sections', description: 'Hides the verbose and redundant release info sections.', defaultValue: false, section: 'UI Settings', type: 'checkbox' },
        // Convenience Features
        clipboardRelookup: { key: 'enhancements.ui.clipboardRelookup', label: `Add 'Re-Lookup with MBID from Clipboard' button`, description: 'Adds a button to the "Release Lookup" page to redo the lookup using a MusicBrainz Release ID found in the clipboard.', defaultValue: true, section: 'Convenience Features', type: 'checkbox' },
        actionsRelookup: { key: 'enhancements.ui.actionsRelookup', label: 'Add "Re-Lookup" link on Release Actions page', description: 'Adds a link to re-lookup a release from the Harmony release actions page.', defaultValue: true, section: 'Convenience Features', type: 'checkbox' },
        copyTracklist: { key: 'enhancements.ui.copyTracklist', label: `Enable copying tracklist by clicking "Track" header`, description: 'Copies the tracklist in MusicBrainz track parser compatible format to clipboard when clicking on track header', defaultValue: true, section: 'Convenience Features', type: 'checkbox' },
        // Language Detection
        languageDetection: { key: 'enhancements.lang.enabled', label: 'Enable browser-based language detection', description: 'Uses your browser\'s built-in API for a secondary language analysis, which can be more accurate than Harmony\'s default.<br>Only works in <a href="https://developer.mozilla.org/en-US/docs/Web/API/LanguageDetector#browser_compatibility" rel="noopener noreferrer">Chrome >138</a> as of July 2025', defaultValue: true, section: 'Language Detection', type: 'checkbox' },
        confidenceThreshold: { key: 'enhancements.lang.confidenceThreshold', label: 'Confidence Threshold', description: 'The minimum confidence level (in percent) required for the browser-detected language to be applied.', defaultValue: 50, section: 'Language Detection', type: 'range' },
        conflictThreshold: { key: 'enhancements.lang.conflictThreshold', label: 'Harmony Conflict Threshold', description: 'If Harmony\'s confidence is below this level, this script will overwrite its guess. Otherwise, it will not.', defaultValue: 90, section: 'Language Detection', type: 'range' },
        detectSingles: { key: 'enhancements.lang.detectSingles', label: 'Analyze single-track releases', description: 'By default, language detection is skipped for releases with only one track. Enable this to analyze them.', defaultValue: false, section: 'Language Detection', type: 'checkbox' },
        ignoreHarmony: { key: 'enhancements.lang.ignoreHarmony', label: `Force overwrite Harmony's guess`, description: 'Always replace Harmony\'s language guess result with the browser-detected result, regardless of confidence scores.', defaultValue: false, section: 'Language Detection', type: 'checkbox' },
        stopWords: { key: 'enhancements.lang.stopWords', label: 'Stop Words (one per line)', description: 'These common words will be ignored during language analysis to improve accuracy. Add or remove words as needed.', defaultValue: ['a', 'an', 'and', 'are', 'as', 'at', 'be', 'by', 'bye', 'for', 'from', 'is', 'it', 'of', 'off', 'on', 'the', 'to', 'was', 'with'], section: 'Language Detection', type: 'textarea' },
        techTerms: { key: 'enhancements.lang.techTerms', label: 'Technical Terms (one per line, regex supported)', description: 'Terms that are not specific to any language (like "remix" or "live") will be removed from titles before analysis.', defaultValue: ['live', 'remix(es)?', 'edit(ion)?', 'medley', 'mix', 'version(s)?', 'instrumental', 'album', 'radio', 'single', 'vocal', 'dub', 'club', 'extended', 'original', 'acoustic', 'unplugged', 'mono', 'stereo', 'demo', 'remaster(ed)?', 'f(ea)?t\\.?', 'sped up', 'slowed', 'chopped', 'screwed', '8d'], section: 'Language Detection', type: 'textarea' },
    };

    const ISO_639_1_TO_3_MAP = {'aa':'aar','ab':'abk','ae':'ave','af':'afr','ak':'aka','am':'amh','an':'arg','ar':'ara','as':'asm','av':'ava','ay':'aym','az':'aze','ba':'bak','be':'bel','bg':'bul','bi':'bis','bm':'bam','bn':'ben','bo':'bod','br':'bre','bs':'bos','ca':'cat','ce':'che','ch':'cha','co':'cos','cr':'cre','cs':'ces','cu':'chu','cv':'chv','cy':'cym','da':'dan','de':'deu','dv':'div','dz':'dzo','ee':'ewe','el':'ell','en':'eng','eo':'epo','es':'spa','et':'est','eu':'eus','fa':'fas','ff':'ful','fi':'fin','fj':'fij','fo':'fao','fr':'fra','fy':'fry','ga':'gle','gd':'gla','gl':'glg','gn':'grn','gu':'guj','gv':'glv','ha':'hau','he':'heb','hi':'hin','ho':'hmo','hr':'hrv','ht':'hat','hu':'hun','hy':'hye','hz':'her','ia':'ina','id':'ind','ie':'ile','ig':'ibo','ii':'iii','ik':'ipk','io':'ido','is':'isl','it':'ita','iu':'iku','ja':'jpn','jv':'jav','ka':'kat','kg':'kon','ki':'kik','kj':'kua','kk':'kaz','kl':'kal','km':'khm','kn':'kan','ko':'kor','kr':'kau','ks':'kas','ku':'kur','kv':'kom','kw':'cor','ky':'kir','la':'lat','lb':'ltz','lg':'lug','li':'lim','ln':'lin','lo':'lao','lt':'lit','lu':'lub','lv':'lav','mg':'mlg','mh':'mah','mi':'mri','mk':'mkd','ml':'mal','mn':'mon','mr':'mar','ms':'msa','mt':'mlt','my':'mya','na':'nau','nb':'nob','nd':'nde','ne':'nep','ng':'ndo','nl':'nld','nn':'nno','no':'nor','nr':'nbl','nv':'nav','ny':'nya','oc':'oci','oj':'oji','om':'orm','or':'ori','os':'oss','pa':'pan','pi':'pli','pl':'pol','ps':'pus','pt':'por','qu':'que','rm':'roh','rn':'run','ro':'ron','ru':'rus','rw':'kin','sa':'san','sc':'srd','sd':'snd','se':'sme','sg':'sag','si':'sin','sk':'slv','sl':'slv','sm':'smo','sn':'sna','so':'som','sq':'sqi','sr':'srp','ss':'ssw','st':'sot','su':'sun','sv':'swe','sw':'swa','ta':'tam','te':'tel','tg':'tgk','th':'tha','ti':'tir','tk':'tuk','tl':'tgl','tn':'tsn','to':'ton','tr':'tur','ts':'tso','tt':'tat','tw':'twi','ty':'tah','ug':'uig','uk':'ukr','ur':'urd','uz':'uzb','ve':'ven','vi':'vie','vo':'vol','wa':'wln','wo':'wol','xh':'xho','yi':'yid','yo':'yor','za':'zha','zh':'zho','zu':'zul'};
    const getISO639_3_Code = (code) => ISO_639_1_TO_3_MAP[code] || null;

    const langDetectState = {
        detector: null,
        apiFailed: false,
        result: null,
    };

    // --- UTILITY FUNCTIONS ---

    async function getSettings() {
        const settings = {};
        for (const config of Object.values(SETTINGS_CONFIG)) {
            settings[config.key] = await GM_getValue(config.key, config.defaultValue);
        }
        return settings;
    }

    function showTooltip(message, type, event) {
        const tooltip = document.createElement('div');
        tooltip.textContent = message;
        tooltip.style.cssText = `
            position: fixed; background-color: ${type === 'success' ? '#4CAF50' : '#f44336'};
            color: white; padding: 5px 10px; border-radius: 4px; font-size: 12px;
            z-index: 10001; opacity: 0; transition: opacity 0.3s;
            pointer-events: none; white-space: nowrap;
        `;
        document.body.appendChild(tooltip);
        tooltip.style.left = `${event.clientX - (tooltip.offsetWidth / 2)}px`;
        tooltip.style.top = `${event.clientY - tooltip.offsetHeight - 10}px`;
        setTimeout(() => { tooltip.style.opacity = '1'; }, 10);
        setTimeout(() => {
            tooltip.style.opacity = '0';
            tooltip.addEventListener('transitionend', () => tooltip.remove());
        }, TOOLTIP_DISPLAY_DURATION);
    }

    function showConfirmationModal({ title, message, confirmText = 'Confirm', cancelText = 'Cancel' }) {
        return new Promise((resolve) => {
            const overlay = document.createElement('div');
            overlay.className = 'harmony-enhancements-modal-overlay';
            overlay.style.cssText = `
                position: fixed; top: 0; left: 0; width: 100%; height: 100%;
                background-color: rgba(0, 0, 0, 0.6); z-index: 10000;
                display: flex; align-items: center; justify-content: center;
            `;

            const modal = document.createElement('div');
            modal.className = 'harmony-enhancements-modal-content';
            modal.style.cssText = `
                background-color: var(--theme-fill); padding: 20px; border-radius: 8px;
                box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); max-width: 400px;
                text-align: center;
            `;

            modal.innerHTML = `
                <h3 style="margin-top: 0;">${title}</h3>
                <p>${message}</p>
                <div class="harmony-enhancements-modal-actions" style="margin-top: 20px;">
                    <button class="cancel-button" style="margin-right: 10px; padding: 8px 16px; border-radius: 4px; border: 1px solid #ccc; background-color: #f0f0f0; cursor: pointer;">${cancelText}</button>
                    <button class="confirm-button" style="padding: 8px 16px; border-radius: 4px; border: none; background-color: #f44336; color: white; cursor: pointer;">${confirmText}</button>
                </div>
            `;

            overlay.appendChild(modal);
            document.body.appendChild(overlay);

            const close = (value) => {
                overlay.remove();
                resolve(value);
            };

            modal.querySelector('.confirm-button').onclick = () => close(true);
            modal.querySelector('.cancel-button').onclick = () => close(false);
            overlay.onclick = (e) => {
                if (e.target === overlay) close(false);
            };
        });
    }


    // --- SETTINGS PAGE ---

    function initSettingsPage(settings) {
        const main = document.querySelector('main');
        if (!main || main.querySelector('.harmony-enhancements-settings-container')) return;

        const sections = Object.values(SETTINGS_CONFIG).reduce((acc, config) => {
            (acc[config.section] = acc[config.section] || []).push(config);
            return acc;
        }, {});

        const container = document.createElement('div');
        container.className = 'harmony-enhancements-settings-container';
        container.style.marginTop = '2em';

        for (const [name, configs] of Object.entries(sections)) {
            const header = document.createElement('div');
            header.style.cssText = 'display: flex; justify-content: space-between; align-items: center;';

            const h3 = document.createElement('h3');
            h3.textContent = name;
            header.appendChild(h3);

            if (name === 'Language Detection') {
                const resetButton = document.createElement('button');
                resetButton.textContent = 'Reset Language Settings';
                resetButton.className = 'reset-button';
                resetButton.style.cssText = `
                    padding: 4px 8px; font-size: 12px; border-radius: 4px;
                    border: 1px solid #ccc; background-color: #f0f0f0; cursor: pointer;
                `;
                resetButton.onclick = async (e) => {
                    e.preventDefault();
                    const confirmed = await showConfirmationModal({
                        title: 'Reset Language Settings',
                        message: 'Are you sure you want to reset all language detection settings to their defaults? This cannot be undone.',
                        confirmText: 'Reset'
                    });
                    if (confirmed) {
                        await resetLanguageSettings();
                        showTooltip('Language settings have been reset.', 'success', e);
                    }
                };
                header.appendChild(resetButton);
            }

            container.appendChild(header);


            configs.forEach(config => {
                const wrap = document.createElement('div');
                wrap.className = 'row';
                wrap.style.alignItems = 'flex-start'; // Align items to the top

                const textContainer = document.createElement('div');
                textContainer.style.flex = '1';

                const lbl = document.createElement('label');
                lbl.htmlFor = config.key;
                lbl.textContent = config.label;
                lbl.style.cursor = 'pointer';
                lbl.style.display = 'inline-block'; // Make label behave better with margin
                textContainer.appendChild(lbl);

                let input;
                let descriptionEl;

                if (config.description) {
                    descriptionEl = document.createElement('small');
                    descriptionEl.id = `${config.key}-desc`;
                    descriptionEl.innerHTML = config.description; // Use innerHTML to render HTML tags
                    descriptionEl.style.cssText = `display: block; color: #666; margin-top: 4px;`;
                    textContainer.appendChild(descriptionEl);
                }

                switch (config.type) {
                    case 'checkbox':
                        input = document.createElement('input');
                        input.type = 'checkbox';
                        input.checked = settings[config.key];
                        input.style.marginTop = '4px';
                        wrap.append(input, textContainer);
                        break;
                    case 'range':
                        wrap.style.flexDirection = 'column';
                        input = document.createElement('input');
                        input.type = 'range';
                        input.min = 0;
                        input.max = 100;
                        input.value = settings[config.key];
                        const val = document.createElement('span');
                        val.textContent = ` ${settings[config.key]}%`;
                        input.addEventListener('input', () => val.textContent = ` ${input.value}%`);
                        const rangeWrap = document.createElement('div');
                        rangeWrap.style.display = 'flex';
                        rangeWrap.style.alignItems = 'center';
                        rangeWrap.append(input, val);
                        wrap.append(textContainer, rangeWrap);
                        break;
                    case 'textarea':
                        wrap.style.flexDirection = 'column';
                        input = document.createElement('textarea');
                        input.rows = 5;
                        input.value = Array.isArray(settings[config.key]) ? settings[config.key].join('\n') : '';
                        input.style.width = '100%';
                        input.style.marginTop = '4px';
                        wrap.append(textContainer, input);
                        break;
                }

                if (input) {
                    input.id = config.key;
                    if (descriptionEl) {
                        input.setAttribute('aria-describedby', descriptionEl.id);
                    }

                    const save = () => {
                        let value;
                        if (config.type === 'checkbox') value = input.checked;
                        else if (config.type === 'range') value = parseInt(input.value, 10);
                        else if (config.type === 'textarea') value = input.value.split('\n').map(s => s.trim()).filter(Boolean);
                        GM_setValue(config.key, value);
                    };
                    input.addEventListener('change', save);
                    if (config.type === 'range' || config.type === 'textarea') {
                        input.addEventListener('input', save);
                    }
                }
                container.appendChild(wrap);
            });
        }
        main.appendChild(container);
    }

    async function resetLanguageSettings() {
        const langConfigs = Object.values(SETTINGS_CONFIG).filter(c => c.section === 'Language Detection');
        for (const config of langConfigs) {
            await GM_setValue(config.key, config.defaultValue);
            const input = document.getElementById(config.key);
            if (!input) continue;

            switch (config.type) {
                case 'checkbox':
                    input.checked = config.defaultValue;
                    break;
                case 'range':
                    input.value = config.defaultValue;
                    input.nextElementSibling.textContent = ` ${config.defaultValue}%`;
                    break;
                case 'textarea':
                    input.value = config.defaultValue.join('\n');
                    break;
            }
        }
    }


    // --- ENHANCEMENT MODULES ---

    const enhancements = {
        skipConfirmation: (form, enabled) => {
            if (!enabled || !form || form.action.includes('skip_confirmation=1')) return;
            try {
                const url = new URL(form.action);
                url.searchParams.set('skip_confirmation', '1');
                form.action = url.toString();
            } catch (e) {
                console.error(`${SCRIPT_NAME}: Could not parse form action URL.`, e);
            }
        },

        updateProperties: (form, enabled) => {
            if (!enabled || !form) return;
            const append = (name, value) => {
                if (!value) return;
                let input = form.querySelector(`input[type="hidden"][name="${name}"]`);
                if (!input) {
                    input = document.createElement('input');
                    input.type = 'hidden';
                    input.name = name;
                    form.appendChild(input);
                }
                if (input.value !== value) input.value = value;
            };
            const gtin = Array.from(document.querySelectorAll('th')).find(th => th.textContent?.trim() === 'GTIN')?.nextElementSibling?.textContent?.trim();
            append('barcode', gtin);
            append('packaging', 'None');
        },

        toggleReleaseInfo: (enabled) => {
            if (!enabled) return;
            const terms = ['Availability', 'Sources', 'External links'];
            document.querySelectorAll('.release-info > tbody > tr').forEach(row => {
                const header = row.querySelector('th');
                if (header && terms.includes(header.innerText.trim())) {
                    row.style.display = 'none';
                }
            });
        },

        addClipboardButton: (enabled) => {
            if (!enabled || document.getElementById('redo-lookup-mbid-button')) return;
            const lookupBtn = document.querySelector('input[type="submit"][value="Lookup"]');
            const container = lookupBtn?.closest('.input-with-overlay');
            if (!container) return;

            const newBtn = document.createElement('input');
            newBtn.type = 'submit';
            newBtn.value = 'Re-Lookup with MBID from Clipboard';
            newBtn.id = 'redo-lookup-mbid-button';
            newBtn.className = lookupBtn.className;

            container.parentElement.insertBefore(newBtn, container.nextSibling);

            newBtn.addEventListener('click', async (e) => {
                e.preventDefault();
                try {
                    const text = await navigator.clipboard.readText();
                    const mbid = (text.match(/musicbrainz\.org\/release\/([a-f0-9\-]{36})/i) || [])[1];
                    if (mbid) {
                        const url = new URL(window.location.href);
                        url.searchParams.set('musicbrainz', mbid);
                        window.location.href = url.toString();
                    } else {
                        showTooltip('No MBID Found in Clipboard!', 'error', e);
                    }
                } catch (err) {
                    const message = err.name === 'NotAllowedError' ? 'Clipboard permission denied!' : 'Could not read clipboard!';
                    showTooltip(message, 'error', e);
                }
            });
        },

        addActionsRelookupLink: (enabled) => {
            if (!enabled || document.getElementById('relookup-link-container')) return;
            const h2 = Array.from(document.querySelectorAll('h2')).find(h => h.textContent.includes('Release Actions'));
            if (!h2) return;

            const mbid = new URLSearchParams(window.location.search).get('release_mbid');
            if (!mbid) return;

            const params = new URLSearchParams({ musicbrainz: mbid });
            document.querySelectorAll('.provider-list li').forEach(item => {
                const pName = item.getAttribute('data-provider')?.toLowerCase();
                const pId = item.querySelector('.provider-id')?.textContent.trim();
                if (pName && pId) params.set(pName, pId);
            });

            const url = `/release?${params.toString()}`;
            const container = document.createElement('div');
            container.id = 'relookup-link-container';
            container.className = 'message';
            container.innerHTML = `<svg class="icon" width="24" height="24" stroke-width="2"><use xlink:href="/icon-sprite.svg#brand-metabrainz"></use></svg><p><a href="${url}">Re-Lookup with Harmony</a></p>`;
            h2.parentNode.insertBefore(container, h2.nextSibling);
        },

        makeTracklistCopyable: (header, enabled) => {
            if (!enabled || !header || header.dataset.copyApplied) return;
            header.dataset.copyApplied = 'true';
            header.title = 'Click to copy this tracklist';

            header.addEventListener('click', async (e) => {
                const table = header.closest('table.tracklist');
                if (!table) return;

                const getCleanText = (element, selectorsToRemove) => {
                    if (!element) return '';
                    const clone = element.cloneNode(true);
                    clone.querySelectorAll(selectorsToRemove.join(',')).forEach(el => el.remove());
                    return clone.textContent.trim();
                };

                const headers = Array.from(table.querySelectorAll('thead th')).map(th => th.textContent.trim());
                const requiredCols = ['Track', 'Title', 'Artists', 'Length'];
                const colIndices = Object.fromEntries(requiredCols.map(name => [name, headers.indexOf(name)]));

                if (Object.values(colIndices).some(i => i === -1)) {
                    showTooltip(`Required columns missing: ${requiredCols.join(', ')}`, 'error', e);
                    return;
                }

                const lines = Array.from(table.querySelectorAll('tbody tr')).map(row => {
                    const cells = row.children;
                    const num = cells[colIndices.Track]?.textContent.trim() || '';
                    const title = getCleanText(cells[colIndices.Title], ['ul.alt-values']);
                    const artist = getCleanText(cells[colIndices.Artists]?.querySelector('.artist-credit'), ['ul.alt-values']);
                    const len = (getCleanText(cells[colIndices.Length], ['ul.alt-values'])).split('.')[0];
                    return `${num}. ${title}${artist ? ` - ${artist}` : ''} (${len})`;
                });

                if (lines.length > 0) {
                    try {
                        await navigator.clipboard.writeText(lines.join('\n'));
                        showTooltip('Tracklist copied!', 'success', e);
                        header.style.color = 'green';
                        setTimeout(() => header.style.color = '', TOOLTIP_DISPLAY_DURATION);
                    } catch (err) {
                        showTooltip('Failed to copy tracklist!', 'error', e);
                        header.style.color = 'red';
                        setTimeout(() => header.style.color = '', TOOLTIP_DISPLAY_DURATION);
                    }
                }
            });
        },

        runLanguageDetection: async (settings) => {
            if (langDetectState.result !== null) {
                enhancements.applyLanguageDetectionResult(settings);
                return;
            }
            if (!langDetectState.detector && !langDetectState.apiFailed) {
                if ('LanguageDetector' in window) {
                    try {
                        const nativeDetector = await window.LanguageDetector.create();
                        langDetectState.detector = (text) => nativeDetector.detect(text);
                    } catch (error) {
                        console.error(`${SCRIPT_NAME} LanguageDetector API failed to initialize.`, error);
                        langDetectState.apiFailed = true;
                    }
                } else {
                    langDetectState.apiFailed = true;
                }
            }
            if (langDetectState.apiFailed) {
                langDetectState.result = { skipped: true, debugInfo: { analyzedText: 'LanguageDetector API not available or failed to load.' } };
                enhancements.applyLanguageDetectionResult(settings);
                return;
            }

            const scriptElement = document.querySelector('script[id^="__FRSH_STATE_"]');
            if (!scriptElement?.textContent) return;
            const data = JSON.parse(scriptElement.textContent);
            const releaseData = data.v?.flat().find(prop => prop?.release)?.release;
            if (!releaseData) return;

            const { title: releaseTitle, media } = releaseData;
            const trackTitles = media?.flatMap(m => m.tracklist.map(t => t.title)) || [];
            const trackCount = media?.reduce((sum, m) => sum + (m.tracklist?.length || 0), 0) || 0;

            if (trackCount === 1 && !settings[SETTINGS_CONFIG.detectSingles.key]) {
                langDetectState.result = { skipped: true, debugInfo: { analyzedText: 'Skipped: Single track release detection is disabled.' } };
                enhancements.applyLanguageDetectionResult(settings);
                return;
            }

            const allTitles = [releaseTitle, ...trackTitles].filter(Boolean);
            if (allTitles.length === 0) return;

            const techTerms = settings[SETTINGS_CONFIG.techTerms.key];
            const stopWords = new Set(settings[SETTINGS_CONFIG.stopWords.key]);
            const enclosedRegex = new RegExp(`\\s*(?:\\([^)]*\\b(${techTerms.join('|')})\\b[^)]*\\)|\\[[^\\]]*\\b(${techTerms.join('|')})\\b[^\\]]*\\])`, 'ig');
            const trailingRegex = new RegExp(`\\s+[-–]\\s+.*(?:${techTerms.map(t => `\\b${t}\\b`).join('|')}).*`, 'ig');

            // --- Multi-stage Filtering Algorithm ---

            // Stage 1: Initial Cleaning (remove bracketed/hyphenated technical terms)
            let cleanedTitles = allTitles.map(title =>
                title.replace(enclosedRegex, '').replace(trailingRegex, '').trim()
            ).filter(Boolean);

            // Stage 2: Contextual "Core Title" Cleaning (find a common base title)
            const titleCounts = new Map();
            cleanedTitles.forEach(title => titleCounts.set(title, (titleCounts.get(title) || 0) + 1));
            let coreTitle = null;
            let maxCount = 0;
            if (titleCounts.size > 1) {
                for (const [title, count] of titleCounts.entries()) {
                    if (count > maxCount) {
                        maxCount = count;
                        coreTitle = title;
                    }
                }
            }
            if (maxCount > 1) {
                cleanedTitles = cleanedTitles.map(title => (title.startsWith(coreTitle) && title !== coreTitle) ? coreTitle : title);
            }

            // Stage 3: Stop Word Removal (from within titles)
            const stopWordsRegex = new RegExp(`\\b(${Array.from(stopWords).join('|')})\\b`, 'gi');
            let surgicallyCleanedTitles = cleanedTitles.map(title =>
                title.replace(stopWordsRegex, '').replace(/\s{2,}/g, ' ').trim()
            );

            // Stage 4: Whole Title Filtering (remove titles that are now just stop words)
            const finalFilteredTitles = surgicallyCleanedTitles.filter(title => {
                if (!title) return false;
                const normalizedTitle = title.toLowerCase().replace(/[\s.]+/g, '');
                return !stopWords.has(normalizedTitle);
            });

            // Stage 5: De-duplication & Analysis
            const uniqueTitles = [...new Set(finalFilteredTitles)];
            const titlesToAnalyze = uniqueTitles.length > 0 ? uniqueTitles : [...new Set(allTitles)];

            let textToAnalyze = titlesToAnalyze.join(' . ');
            if (titlesToAnalyze.length <= 3) {
                textToAnalyze += ' .';
            }

            if (!textToAnalyze.replaceAll(/\P{Letter}/gu, '')) {
                langDetectState.result = { languageName: '[No linguistic content]', confidence: 100, languageCode3: 'zxx', isZxx: true, debugInfo: { allResults: [], analyzedText: textToAnalyze } };
            } else {
                const results = await langDetectState.detector(textToAnalyze);
                if (results.length === 0) return;
                const final = results[0];
                langDetectState.result = {
                    languageName: new Intl.DisplayNames(['en'], { type: 'language' }).of(final.detectedLanguage),
                    confidence: Math.round(final.confidence * 100),
                    languageCode3: getISO639_3_Code(final.detectedLanguage),
                    isZxx: false,
                    skipped: false,
                    debugInfo: { allResults: results, analyzedText: textToAnalyze }
                };
            }
            enhancements.applyLanguageDetectionResult(settings);
        },

        applyLanguageDetectionResult: (settings) => {
            if (!langDetectState.result) return;
            const container = document.querySelector('div.release');
            if (!container) return;
            document.getElementById('harmony-enhancements-language-analysis')?.remove();

            const { languageName, confidence, languageCode3, isZxx, skipped, debugInfo } = langDetectState.result;
            const confidenceThreshold = settings[SETTINGS_CONFIG.confidenceThreshold.key];

            const messageDiv = document.createElement('div');
            messageDiv.id = 'harmony-enhancements-language-analysis';
            messageDiv.className = 'message debug';
            messageDiv.innerHTML = `<svg class="icon" width="24" height="24" stroke-width="2"><use xlink:href="/icon-sprite.svg#bug"></use></svg><div><p></p></div>`;
            const p = messageDiv.querySelector('p');

            if (skipped) {
                p.textContent = debugInfo.analyzedText;
            } else {
                const b = document.createElement('b');
                b.textContent = languageName;
                p.append('Guessed language (LanguageDetector API): ', b, ` (${confidence}% confidence)`);
                if (confidence < confidenceThreshold) {
                    const i = document.createElement('i');
                    i.textContent = ` - below ${confidenceThreshold}% threshold, no changes applied.`;
                    p.append(i);
                }
                p.append(document.createElement('br'), `Analyzed block: "${debugInfo.analyzedText}"`);
            }

            const findInsertionAnchor = () => {
                const messages = container.querySelectorAll('.message.debug');
                let langGuessMsg = null;
                let scriptGuessMsg = null;
                for (const msg of messages) {
                    const text = msg.textContent;
                    if (text.includes('Guessed language of the titles:')) langGuessMsg = msg;
                    else if (text.includes('Detected scripts of the titles:')) scriptGuessMsg = msg;
                }
                if (langGuessMsg) return langGuessMsg.nextSibling;
                if (scriptGuessMsg) return scriptGuessMsg.nextSibling;
                return container.querySelector('.message');
            };

            const insertionAnchor = findInsertionAnchor();
            const parent = (insertionAnchor?.parentElement || container.querySelector('.message')?.parentElement) || container;
            parent.insertBefore(messageDiv, insertionAnchor);


            const updateSeeder = (code) => {
                const form = document.querySelector('form[name="release-seeder"]');
                if (!form) return;
                let input = form.querySelector('input[name="language"]');
                if (!input) {
                    input = document.createElement('input');
                    input.type = 'hidden';
                    input.name = 'language';
                    form.appendChild(input);
                }
                if (input.value !== code) input.value = code;
            };

            const langRow = Array.from(document.querySelectorAll('.release-info th')).find(th => th.textContent.trim() === 'Language')?.parentElement;
            if (isZxx) {
                if (langRow) langRow.querySelector('td').textContent = '[No linguistic content]';
                updateSeeder('zxx');
                return;
            }
            if (skipped || confidence < confidenceThreshold) return;

            const newContent = `${languageName} (${confidence}% confidence)`;
            if (langRow) {
                const cell = langRow.querySelector('td');
                const originalText = cell.textContent.trim();
                const originalLang = originalText.replace(/\s*\(.*\)/, '').trim();
                const harmonyConfidence = parseInt((originalText.match(/\((\d+)%\sconfidence\)/) || [])[1] || '0', 10);
                const shouldOverwrite = settings[SETTINGS_CONFIG.ignoreHarmony.key] || originalLang.toLowerCase() === languageName.toLowerCase() || harmonyConfidence < settings[SETTINGS_CONFIG.conflictThreshold.key];
                if (shouldOverwrite && cell.textContent !== newContent) {
                    cell.textContent = newContent;
                    updateSeeder(languageCode3);
                }
            } else if (!document.getElementById('harmony-enhancements-language-row')) {
                const table = document.querySelector('.release-info tbody');
                const scriptRow = Array.from(table.querySelectorAll('th')).find(th => th.textContent.trim() === 'Script')?.parentElement;
                const newRow = table.insertRow(scriptRow ? scriptRow.rowIndex + 1 : -1);
                newRow.id = 'harmony-enhancements-language-row';
                newRow.innerHTML = `<th>Language</th><td>${newContent}</td>`;
                updateSeeder(languageCode3);
            }
        }
    };

    // --- INITIALIZATION AND ROUTING ---

    function applyEnhancements(node, settings) {
        if (node.nodeType !== Node.ELEMENT_NODE) return;

        const applyToNodeAndDescendants = (selector, action) => {
            if (node.matches(selector)) action(node);
            node.querySelectorAll(selector).forEach(action);
        };

        applyToNodeAndDescendants('table.tracklist th', header => {
            if (header.textContent.trim() === 'Track') {
                enhancements.makeTracklistCopyable(header, settings[SETTINGS_CONFIG.copyTracklist.key]);
            }
        });

        applyToNodeAndDescendants('input[type="submit"][value="Lookup"]', () => {
            enhancements.addClipboardButton(settings[SETTINGS_CONFIG.clipboardRelookup.key]);
        });

        applyToNodeAndDescendants('h2', h2 => {
            if (h2.textContent.includes('Release Actions')) {
                enhancements.addActionsRelookupLink(settings[SETTINGS_CONFIG.actionsRelookup.key]);
            }
        });

        applyToNodeAndDescendants('.release-info', () => {
            enhancements.toggleReleaseInfo(settings[SETTINGS_CONFIG.hideReleaseInfo.key]);
        });

        applyToNodeAndDescendants('form[action^="https://musicbrainz.org/release/add"]', form => {
            enhancements.skipConfirmation(form, settings[SETTINGS_CONFIG.skipConfirmation.key]);
        });

        applyToNodeAndDescendants('form[name="release-update-seeder"]', form => {
            enhancements.updateProperties(form, settings[SETTINGS_CONFIG.updateProperties.key]);
        });

        applyToNodeAndDescendants('script[id^="__FRSH_STATE_"]', () => {
            if (settings[SETTINGS_CONFIG.languageDetection.key]) {
                enhancements.runLanguageDetection(settings);
            }
        });
    }

    function applyGlobalStyles(settings) {
        const css = `
            .release-artist::before { content: "by "; }
            .release-artist > :first-child { margin-left: 0.25em; }
            ${settings[SETTINGS_CONFIG.hideDebugMessages.key] ? '.message.debug { display: none !important; }' : ''}
            th[data-copy-applied] {
                cursor: pointer; text-decoration: underline; color: #3B82F6;
                -webkit-user-select: none; user-select: none; transition: color 0.2s;
            }
            th[data-copy-applied]:hover { color: #1D4ED8; }
        `;
        GM_addStyle(css);
    }

    async function main() {
        const settings = await getSettings();
        const path = window.location.pathname;

        applyGlobalStyles(settings);

        if (path.startsWith('/settings')) {
            initSettingsPage(settings);
        } else if (path.startsWith('/release')) {
            const observer = new MutationObserver(() => {
                observer.disconnect();

                applyEnhancements(document.body, settings);

                observer.observe(document.body, { childList: true, subtree: true, attributes: true });
            });

            applyEnhancements(document.body, settings);

            observer.observe(document.body, { childList: true, subtree: true, attributes: true });
        }
    }

    main().catch(e => console.error(`[${SCRIPT_NAME}]`, e));

})();