Request for a User Script

Hi All

There is this wonder user script to import releases to MB. Every artist and release has this beautiful pink thingie if it has a link to MB

Schermafbeelding 2025-07-30 145459

Question: is someone willing to make a user script that shows this wonderful pink thingie with the artists and works on Second Hand Songs? I would save me so so so much time and I dont have to look everything up.

The script does not have to do anything else, just show that thingie, when the artist of work has a link to MB.

I would be grateful forever :slight_smile:

Greets Tis

2 Likes

i think i need more information about what u want on what site and where exactly.

because i don’t understand what you actually want…

you are talking about an already existing userscript, which one? link it somewhere. give as much information as possible

The icons are a feature of the MBlinks library and is commonly used by userscript-based importers

The site @Tiske_Tisja would like an integration for is https://secondhandsongs.com/

2 Likes

Thank you Chaban, that is what I mean. Your English is better then mine :slight_smile:

The userscript I mean is: Import Discogs releases to MusicBrainz from Murdos. That usercript shows you those pink thingies on Discogs.

@ sanojjonas For me the userscript does not have to import anything. The only thing I want is to be able to see on https://secondhandsongs.com/ is which artists and works already have a Second Hand Song URL in MB.

Like this:

2 Likes

That would be usefull indeed

2 Likes

I looooove that feature as well!

I banged my head into chatGPT for hours trying to get one to work for rateyourmusic but the result is a bit crap quite frankly. I think it needs an expert…

+1 to someone making a this userscript work for other sites! It makes me add more Discogs links, to get those icons to pop up, that’s for sure.

3 Likes

Get in line @aerozol I was first :rofl: :rofl: :rofl: :rofl:

2 Likes

Here is a quick-and-dirty script I’ve whipped up with Gemini 2.5 Pro:

// ==UserScript==
// @name         SecondHandSongs to MusicBrainz Linker
// @namespace    https://musicbrainz.org/user/chaban
// @version      1.3
// @tag          ai-created
// @description  Adds links from secondhandsongs.com to MusicBrainz entities.
// @author       chaban
// @license      MIT
// @match        https://secondhandsongs.com/*
// @require      https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
// @require      https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/lib/mblinks.js
// @require      https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/lib/mbimportstyle.js
// @grant        none
// ==/UserScript==

// prevent JQuery conflicts, see http://wiki.greasespot.net/@grant
this.$ = this.jQuery = jQuery.noConflict(true);

$(document).ready(function () {
    MBSearchItStyle();
    const mblinks = new MBLinks('SHS_MBLINKS_CACHE');

    /**
     * Cleans up a SecondHandSongs URL to its canonical form.
     * @param {string} url - The URL to clean.
     * @returns {string} The cleaned URL.
     */
    function cleanUrl(url) {
        return url.replace(/^(https:\/\/secondhandsongs\.com\/(?:artist|performance|work|release|label)\/\d+).*/, '$1');
    }

    /**
     * Maps SecondHandSongs URL paths to MusicBrainz entity types.
     * @param {string} path - The path segment from a SecondHandSongs URL.
     * @returns {string|null} The corresponding MusicBrainz entity type or null.
     */
    function getMbTypeFromPath(path) {
        const typeMap = {
            'artist': 'artist',
            'performance': 'recording',
            'work': 'work',
            'release': 'release',
            'label': 'label',
        };
        return typeMap[path] || null;
    }

    /**
     * Gathers all relevant entity links from a given context, groups them by type, and processes them in batches.
     * @param {Node} context - The DOM node to search within for links.
     */
    function addLinksToPage(context) {
        const $context = $(context || document);
        const canonicalPathRegex = /^\/(?:artist|performance|work|release|label)\/\d+\/?$/;
        const urlsToProcess = {}; // Group URLs by mb_type

        // Main entity on the page
        if (!context || context === document) {
            const pageUrl = cleanUrl(window.location.href);
            const path = pageUrl.split('/')[3];
            const pageType = getMbTypeFromPath(path);

            if (pageType) {
                if (!urlsToProcess[pageType]) {
                    urlsToProcess[pageType] = [];
                }
                urlsToProcess[pageType].push({
                    url: pageUrl,
                    mb_type: pageType,
                    insert_func: function (link) {
                        const $mbLink = $(link).css({
                            'margin-left': '8px',
                            'vertical-align': 'middle',
                        });
                        $('h1[itemprop="name"]').append($mbLink);
                    }
                });
            }
        }

        // Other entity links within the context
        $context.find('a[href*="/artist/"], a[href*="/performance/"], a[href*="/work/"], a[href*="/release/"], a[href*="/label/"]').each(function () {
            const $anchor = $(this);
            if ($anchor.data('mblinks-added')) {
                return;
            }
            $anchor.data('mblinks-added', true);

            const urlObject = new URL($anchor.attr('href'), window.location.href);

            if (!canonicalPathRegex.test(urlObject.pathname)) {
                return;
            }

            const url = cleanUrl(urlObject.href);
            const path = url.split('/')[3];
            const type = getMbTypeFromPath(path);

            if (type) {
                if (!urlsToProcess[type]) {
                    urlsToProcess[type] = [];
                }
                urlsToProcess[type].push({
                    url: url,
                    mb_type: type,
                    insert_func: function (link) {
                        const $mbLink = $(link).css({
                            'margin-left': '4px',
                            'vertical-align': 'middle',
                        });
                         const $parentSpan = $anchor.closest('span[itemprop="name"]');
                        if ($parentSpan.length) {
                            $parentSpan.after($mbLink);
                        } else {
                            $anchor.after($mbLink);
                        }
                    }
                });
            }
        });

        // Process each group of URLs with the correct entity type
        for (const type in urlsToProcess) {
            if (urlsToProcess.hasOwnProperty(type)) {
                mblinks.searchAndDisplayMbLinks(urlsToProcess[type]);
            }
        }
    }

    // Initial run on page load
    addLinksToPage();

    // Observe for dynamically loaded content
    const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            if (mutation.addedNodes.length) {
                mutation.addedNodes.forEach((node) => {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        addLinksToPage(node);
                    }
                });
            }
        });
    });

    const targetNode = document.getElementById('root');
    if (targetNode) {
        observer.observe(targetNode, {
            childList: true,
            subtree: true,
        });
    }
});

However the performance is quite bad due to many links existing on SecondHandSongs and MBlinks.js apparently not optimized yet to query multiple URLs per API call.

@aerozol You might want to try Gemini if ChatGPT struggles. For the above I basically pointed it at the murdos repo, uploaded an example artist page and asked it to create a userscript similar to the importer but with just the linking feature.

Afterwards I only needed to instruct it to fix the icon position (supplying a screenshot) and ask for support of other entities by feeding it with the URL clean-up code from MBS as reference.

3 Likes

Oww @Chaban thank you so very much. Can you please explain to me how I can install this?

1 Like

Please don’t use it. It’s very rough around the edges. I’m currently trying to polish it a bit but since I don’t use SecondHandSongs there are probably many things I’m missing and it will still be catastrophic.

But if you really want to see Creating a userscript - Violentmonkey
Copy the code to the clipboard

image

and then paste it in the new script window overwriting the entire placeholder text and save it.

2 Likes

Hahah I will wait, thank you so much again Chaban

1 Like

A new function for batch-processing has been contributed recently, but so far it is only used by the Bandcamp importer:

It should be trivial to adapt all other importers / scripts which use this feature to use the new function. If I would have had the time to import releases into MB lately, I probably would have done it myself already :sweat_smile:

3 Likes

I gave Gemini two more nudges and now URL queries are grouped by type and batched.

@Tiske_Tisja I think you can test it now. Grab the newest version 1.3 from my previous post

3 Likes

Thank you so much Chaban!!!

Fantastic !!! I’m so so happy :slight_smile:

Look how BEAUTIFULLLLLLLL

3 Likes

I’ve bashed my head against a wall with Gemini… It very quickly got the following script right, which checks for artist backlinks (for instance on VA albums):

// ==UserScript==
// @name         RateYourMusic to MusicBrainz Linker
// @namespace    https://musicbrainz.org/user/yourusername
// @version      1.0
// @description  Adds links from RateYourMusic to MusicBrainz entities.
// @author       Gemini
// @license      MIT
// @match        https://rateyourmusic.com/*
// @require      https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
// @require      https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/lib/mblinks.js
// @require      https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/lib/mbimportstyle.js
// @grant        none
// ==/UserScript==

// Prevent JQuery conflicts
this.$ = this.jQuery = jQuery.noConflict(true);

$(document).ready(function() {
    MBSearchItStyle();
    const mblinks = new MBLinks('RYM_MBLINKS_CACHE');

    /**
     * Cleans up a Rate Your Music URL to its canonical form.
     * @param {string} url - The URL to clean.
     * @returns {string} The cleaned URL.
     */
    function cleanUrl(url) {
        return url.replace(/^(https:\/\/rateyourmusic\.com\/(?:artist|release|label)\/[^/]+).*/, '$1');
    }

    /**
     * Maps Rate Your Music URL paths to MusicBrainz entity types.
     * @param {string} path - The path segment from a RYM URL.
     * @returns {string|null} The corresponding MusicBrainz entity type or null.
     */
    function getMbTypeFromPath(path) {
        const typeMap = {
            'artist': 'artist',
            'release': 'release',
            'label': 'label',
        };
        return typeMap[path] || null;
    }

    /**
     * Gathers all relevant entity links from a given context, groups them by type, and processes them in batches.
     * @param {Node} context - The DOM node to search within for links.
     */
    function addLinksToPage(context) {
        const $context = $(context || document);
        const canonicalPathRegex = /^\/(?:artist|release|label)\/.+/;
        const urlsToProcess = {};

        // Main entity on the page
        if (!context || context === document) {
            const pageUrl = cleanUrl(window.location.href);
            const path = pageUrl.split('/')[3];
            const pageType = getMbTypeFromPath(path);

            if (pageType) {
                if (!urlsToProcess[pageType]) {
                    urlsToProcess[pageType] = [];
                }
                urlsToProcess[pageType].push({
                    url: pageUrl,
                    mb_type: pageType,
                    insert_func: function(link) {
                        const $mbLink = $(link).css({
                            'margin-left': '8px',
                            'vertical-align': 'middle',
                        });
                        // Target the main page title for link insertion
                        const $titleElement = $('h1.artist_name_hdr, h1.release_title_hdr, h1.label_title_hdr').first();
                        if ($titleElement.length) {
                            $titleElement.append($mbLink);
                        }
                    }
                });
            }
        }

        // Other entity links within the context (e.g., links in lists or tracklists)
        $context.find('a[href*="/artist/"], a[href*="/release/"], a[href*="/label/"]').each(function() {
            const $anchor = $(this);
            if ($anchor.data('mblinks-added')) {
                return;
            }
            $anchor.data('mblinks-added', true);

            const urlObject = new URL($anchor.attr('href'), window.location.href);

            if (!canonicalPathRegex.test(urlObject.pathname)) {
                return;
            }

            const url = cleanUrl(urlObject.href);
            const path = url.split('/')[3];
            const type = getMbTypeFromPath(path);

            if (type) {
                if (!urlsToProcess[type]) {
                    urlsToProcess[type] = [];
                }
                urlsToProcess[type].push({
                    url: url,
                    mb_type: type,
                    insert_func: function(link) {
                        const $mbLink = $(link).css({
                            'margin-left': '4px',
                            'vertical-align': 'middle',
                        });
                        // Insert the link after the anchor element
                        $anchor.after($mbLink);
                    }
                });
            }
        });

        // Process each group of URLs with the correct entity type
        for (const type in urlsToProcess) {
            if (urlsToProcess.hasOwnProperty(type)) {
                mblinks.searchAndDisplayMbLinks(urlsToProcess[type]);
            }
        }
    }

    // Initial run on page load
    addLinksToPage();

    // Observe for dynamically loaded content, as RYM pages can load content asynchronously
    const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            if (mutation.addedNodes.length) {
                mutation.addedNodes.forEach((node) => {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        addLinksToPage(node);
                    }
                });
            }
        });
    });

    const targetNode = document.body;
    if (targetNode) {
        observer.observe(targetNode, {
            childList: true,
            subtree: true,
        });
    }
});

But we are at something like v70 trying to get something to work for RYM artist pages/releases (to match to MB release groups)!

I recently got side tracked arguing with Gemini about why it keeps adding “Final” to the script names when it knows damn well it’s doubtful that v71 is suddenly going to work* :sweat_smile:

Maybe you are better at giving prompts @chaban? Teach me your ways…

I am also curious, what did you mean by “uploaded an example artist page”? And “pointed it at the murdos repo”? I have been feeding it URL’s but I’m unclear how much it scrapes/takes from them

*this is a joke btw peeps, I’m aware that LLM doesn’t “know” anything

I meant the feature to import a whole GitHub repo (“Code importieren”) or directory:

But, alas, it seems to be an exclusive of the “AI Pro” Plan.

How to get Google AI Pro for free

There are ways to get it for free, for example by being “invited” from a subscriber
It works for up to 3 people who can then use it for free up to 4 months:

By “uploaded an example artist page” I meant to save the page and upload it as a single HTML file. Gemini can’t directly access many pages presumably because lots of sites are blocking “AI” bots nowadays.

I’m not sure I can actually teach you how to use it. I’m still learning and experimenting myself :slight_smile:

This is the chat I had with Gemini to create the SecondHandSongs script:
https://g.co/gemini/share/d2c18a2c022b

3 Likes

Thanks @chaban! That is very useful.

Looking at your chat log it also confirms that I struggle a bit because I can’t help Gemini along, since I can’t spot weird or obviously incorrect code.

But uploading website files has already been helpful! Maybe this will give me the energy to give RYM releases another go this weekend.

If possible: A script that can change work credits from the release edit relationships page. For example, I’m editing a jazz release that is 100% instrumental. All the works have “writer” instead of “composer”. It’d be awesome if I could just mark all writers on highlighted works and change them from “writer” to “composer” or without having to do each one by one. Other examples when some editors use “composer” when it should have been “written by”, so the opposite. Common on many pop releases, etc.

1 Like

Sounds like a job for Group Therapy. Maybe it can already do this, haven’t used it much yet.

1 Like

It can in convoluted way, not as a single action. I wanted this myself for the very same reason @tigerman325 requested.

Created issue for it: Option to replace role · Issue #470 · majkinetor/musicbrainz-userscripts · GitHub

1 Like