Raw Source
yyoung / MusicBrainz Import from Music Forest

// ==UserScript==
// @name         MusicBrainz Import from Music Forest
// @namespace    https://github.com/y-young
// @version      2021.10.25
// @description  Import releases from Music Forest into MusicBrainz.
// @author       y-young
// @licence      MIT; https://opensource.org/licenses/MIT
// @supportURL   https://github.com/y-young/userscripts/labels/mb-import-from-music-forest
// @downloadURL  https://github.com/y-young/userscripts/raw/master/musicbrainz-import-from-music-forest.user.js
// @match        https://search.minc.or.jp/music/list*
// @match        https://search.minc.or.jp/product/list*
// @icon         https://search.minc.or.jp/favicon.ico
// @require      https://cdn.jsdelivr.net/gh/murdos/musicbrainz-userscripts@e84565918e728252753a6e24d350b995dfae2953/lib/mbimport.js
// @grant        GM_registerMenuCommand
// ==/UserScript==

/*
 * Usage:
 *   Open a modal that contains release information,
 *   find "MusicBrainz Import from Music Forest" in right-click menu
 *   and click "Import into MusicBrainz".
 *
 * Things to check before submission:
 *   - Album language and script type, default to "Japanese"
 *   - Release country, default to "Japan"
 */

'use strict';

/*
 * Parse Catalog No
 * "ABCD-12345" -> [{ catno: "ABCD-12345" }]
 * "ABCD-12345/6" -> [{ catno: "ABCD-12345" }, { catno: "ABCD-12346" }]
 * "ABCD-59/60" -> [{ catno: "ABCD-59" }, { catno: "ABCD-60" }]
 */
function parseCatNo(catNoStr) {
    const parts = catNoStr.split('/');
    const first = parts[0];
    const catNos = [
        { catno: first }
    ];
    const endStr = parts[1];
    if (endStr) {
        const end = parseInt(endStr);
        const start = parseInt(first.slice(0 - endStr.length));
        for (let i = start + 1; i <= end; ++i) {
            catNos.push({ catno: first.slice(0, 0 - i.toString().length) + i.toString() });
        }
    }
    return catNos;
}

/*
 * Parse Release Date
 * "2021/1/1" -> { year: "2021", month: "1", day: "1" }
 */
function parseDate(date) {
    const data = date.split('/');
    if (data.length !== 3) {
        return {
            year: '',
            month: '',
            day: ''
        };
    }
    return {
        year: data[0],
        month: data[1],
        day: data[2]
    };
}

function parseDiscFormat(discFormat) {
    if (!discFormat) {
        return '';
    }
    const match = discFormat.innerText.match(/\b(CD|DVD)/);
    return match ? match[1] : '';
}

/*
 * Parse track duration
 * `1:33:33` -> "1:33:33`
 * `3'33"` -> "3:33"
 * `33"` -> "0:33"
 */
function parseDuration(duration) {
    let data = duration.split('\'');
    let hours = '';
    let minutes = '';
    if (data.length > 2) {
        hours = data[0];
        data = data.slice(1);
    }
    if (data.length > 1) {
        minutes = data[0];
        data = data.slice(1);
    } else {
        minutes = '0';
    }
    const seconds = data[0].slice(0, -1);
    return (hours ? hours + ':' : '') + minutes + ':' + seconds;
}

function parseArtistCredit(artists) {
    return artists.split('<br>').reduce((result, artist, index, array) => {
        result.push({
            artist_name: artist,
            joinphrase: index < (array.length - 1) ? " & " : ''
        });
        return result;
    }, []);
}

function parseTrackList(trackList) {
    const rows = trackList.querySelectorAll("tr:not(.header)");
    const tracks = [];
    rows.forEach(row => {
        const cols = Array.from(row.children).map((col, index) => index === 5 ? col.innerHTML : col.innerText);
        const medly = cols[1];
        // Skip parts of a medly, e.g: JECN-358/9
        if (medly !== "0") {
            return;
        }
        const title = cols[2];
        const duration = parseDuration(cols[4]);
        const artist_credit = parseArtistCredit(cols[5]);
        tracks.push({
            title,
            duration,
            artist_credit
        });
    });
    return tracks;
}

function parseModalContent() {
    const modal = document.querySelector("div#cd_detail");
    if (!modal.classList.contains("in")) {
        alert("Please open a modal first.");
        return;
    }

    const title = modal.querySelector("h4.modal-title").innerText;
    const metaItems = Array.from(
        modal.querySelectorAll("div.detail_data div.col-sm-3")
    ).map(item => item.innerText);
    const labels = parseCatNo(metaItems[0].substr(3));
    const date = parseDate(metaItems[1].substr(4));
    const barcode = metaItems[4].substr(4);

    const discFormats = modal.querySelectorAll("div.disk_data div.col-sm-2:first-child");
    const trackLists = modal.querySelectorAll("table.cd-detail2-track-list");
    const discs = [];
    trackLists.forEach((trackList, index) => {
        const format = parseDiscFormat(discFormats[index]);
        const tracks = parseTrackList(trackList);
        discs.push({
            format,
            tracks
        });
    });
    const release = {
        title,
        type: "album",
        status: "official",
        language: "jpn",
        script: "Jpan",
        ...date,
        country: "JP",
        barcode,
        labels,
        discs,
        urls: []
    };

    const note = `

---------------
${location.href}
Imported from Music Forest using https://github.com/y-young/userscripts#import-from-music-forest`;
    const parameters = MBImport.buildFormParameters(release, note);
    const formHtml = MBImport.buildFormHTML(parameters);
    const form = document.createElement("div");
    form.className = "clearfix";
    form.style.display = "none";
    form.innerHTML = formHtml;
    modal.querySelector("div.modal-body").prepend(form);
    form.querySelector("button[type='submit']").click();
}

GM_registerMenuCommand("Import into MusicBrainz", parseModalContent);