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:

1 Like

That would be usefull indeed

1 Like

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.

1 Like

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.

2 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.