ISRC Hunt: Highlight ISRC matches and differences

Highlights matching ISRCs in green and non-matches red.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

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

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

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

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==UserScript==
// @name         ISRC Hunt: Highlight ISRC matches and differences
// @namespace    https://musicbrainz.org/user/chaban
// @version      1.2.1
// @description  Highlights matching ISRCs in green and non-matches red.
// @tag          ai-created
// @author       chaban
// @license      MIT
// @match        *://isrchunt.com/spotify/importisrc*
// @match        *://isrchunt.com/deezer/importisrc*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    /**
     * Injects a CSS string into the document head if it hasn't been injected already.
     * @param {string} id - A unique ID for the style element.
     * @param {string} css - The CSS string to inject.
     */
    function addGlobalStyle(id, css) {
        if (!document.getElementById(id)) {
            const style = document.createElement('style');
            style.id = id;
            style.textContent = css;
            document.head.appendChild(style);
        }
    }

    const isrcStyleId = 'isrc-highlight-userscript-style';
    const isrcCss = `
        .isrc-segment-container .isrc-part:not(.designation)::after {
            content: '\\2011';
        }

        .isrc-base-style {
            font-family: monospace !important;
            font-size: 1.05em !important;
            white-space: nowrap !important;
        }

        .isrc-match-highlight {
            background-color: lightgreen !important;
        }

        .isrc-diff-highlight {
            background-color: salmon !important;
        }
    `;
    addGlobalStyle(isrcStyleId, isrcCss);

    /**
     * Parses a comma-separated string of ISRCs from a cell's text content,
     * normalizing them to uppercase for consistent display and comparison.
     * @param {string} textContent - The raw text content from the table cell.
     * @returns {string[]} An array of normalized (uppercase) ISRC strings.
     */
    function parseIsrcs(textContent) {
        const trimmedText = textContent.trim();
        if (!trimmedText) {
            return [];
        }
        return trimmedText.split(',').map(isrc => isrc.trim().toUpperCase());
    }

    /**
     * Creates a <code> element containing <span> elements for each ISRC part.
     * Hyphens are rendered via CSS pseudo-elements for non-selection.
     * The text color is applied directly to this <code> element to ensure it takes precedence.
     * @param {string} isrc - The 12-character ISRC string (expected to be uppercase).
     * @param {string} textColor - The desired text color for this ISRC (always 'black' in this version).
     * @returns {HTMLElement} A <code> element with nested spans for ISRC segments.
     */
    function formatIsrcForDisplay(isrc, textColor) {
        const codeContainer = document.createElement('code');
        codeContainer.style.setProperty('color', textColor, 'important');

        if (typeof isrc !== 'string' || isrc.length !== 12) {
            codeContainer.textContent = isrc;
            return codeContainer;
        }

        codeContainer.classList.add('isrc-segment-container');

        const partLengths = [2, 3, 2, 5];
        const partClasses = ['country', 'registrant', 'year', 'designation'];
        let currentIndex = 0;

        for (let i = 0; i < partLengths.length; i++) {
            const length = partLengths[i];
            const part = isrc.substring(currentIndex, currentIndex + length);
            const span = document.createElement('span');
            span.classList.add('isrc-part', partClasses[i]);
            span.textContent = part;
            codeContainer.appendChild(span);
            currentIndex += length;
        }

        return codeContainer;
    }

    /**
     * Highlights ISRCs within a given cell based on comparison with a set of other ISRCs,
     * using direct DOM manipulation for safer rendering and precise control.
     * @param {HTMLElement} cell - The table cell element to modify.
     * @param {string[]} isrcsToHighlightNormalized - Array of normalized (uppercase) ISRCs in this cell.
     * @param {Set<string>} comparisonSetNormalized - A Set of normalized (uppercase) ISRCs from the other cell for comparison.
     */
    function highlightIsrcsInCell(cell, isrcsToHighlightNormalized, comparisonSetNormalized) {
        cell.innerHTML = '';

        isrcsToHighlightNormalized.forEach((isrc, index) => {
            const isrcDisplayWrapper = document.createElement('span');

            const bgColor = comparisonSetNormalized.has(isrc) ? 'lightgreen' : 'salmon';
            const textColor = 'black';

            isrcDisplayWrapper.classList.add('isrc-base-style');
            isrcDisplayWrapper.style.setProperty('background-color', bgColor, 'important');

            isrcDisplayWrapper.appendChild(formatIsrcForDisplay(isrc, textColor));

            cell.appendChild(isrcDisplayWrapper);

            if (index < isrcsToHighlightNormalized.length - 1) {
                cell.appendChild(document.createTextNode(', '));
            }
        });
    }

    /**
     * Highlights ISRCs in two cells by comparing them against each other.
     * This function encapsulates the symmetrical calls to highlightIsrcsInCell.
     * @param {HTMLElement} cell1 - The first table cell.
     * @param {string[]} isrcs1Normalized - Normalized ISRCs for the first cell.
     * @param {HTMLElement} cell2 - The second table cell.
     * @param {string[]} isrcs2Normalized - Normalized ISRCs for the second cell.
     */
    function crossHighlightCells(cell1, isrcs1Normalized, cell2, isrcs2Normalized) {
        const set1 = new Set(isrcs1Normalized);
        const set2 = new Set(isrcs2Normalized);

        highlightIsrcsInCell(cell1, isrcs1Normalized, set2);
        highlightIsrcsInCell(cell2, isrcs2Normalized, set1);
    }

    /**
     * Processes a single table row to highlight ISRCs in the Spotify and MusicBrainz cells.
     * @param {HTMLElement} row - The table row element to process.
     */
    function processRowIsrcs(row) {
        const spotifyIsrcCell = row.querySelector('td:nth-child(4)');
        const mbIsrcCell = row.querySelector('td:nth-child(7)');

        if (spotifyIsrcCell && mbIsrcCell) {
            const spotifyIsrcsNormalized = parseIsrcs(spotifyIsrcCell.textContent);
            const mbIsrcsNormalized = parseIsrcs(mbIsrcCell.textContent);

            crossHighlightCells(
                spotifyIsrcCell, spotifyIsrcsNormalized,
                mbIsrcCell, mbIsrcsNormalized
            );
        }
    }

    const table = document.querySelector('.table');
    if (!table) {
        return;
    }

    const rows = table.querySelectorAll('tr');

    for (let i = 1; i < rows.length; i++) {
        processRowIsrcs(rows[i]);
    }
})();