Click buttons across tabs

Clicks specified buttons across tabs using the BroadcastChannel API.

As of 2025-06-10. See the latest version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

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.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        Click buttons across tabs
// @namespace   https://musicbrainz.org/user/chaban
// @version     1.0
// @tag         ai-created
// @description Clicks specified buttons across tabs using the BroadcastChannel API.
// @author      chaban
// @license     MIT
// @match       *://*.musicbrainz.org/*
// @match       *://magicisrc.kepstin.ca/*
// @match       *://magicisrc-beta.kepstin.ca/*
// @grant       GM.info
// @grant       GM_registerMenuCommand
// ==/UserScript==

(function () {
    'use strict';
    /**
     * @typedef {Object} SiteConfig
     * @property {string|string[]} hostnames - A single hostname string or an array of hostname strings.
     * @property {string|string[]} paths - A single path string or an array of path strings (can be partial matches).
     * @property {string} channelName - The name of the BroadcastChannel to use for this site.
     * @property {string} messageTrigger - The message data that triggers the button click.
     * @property {string} buttonSelector - The CSS selector for the button to be clicked.
     * @property {string} menuCommandName - The name to display in the Tampermonkey/Greasemonkey menu.
     */

    /**
     * Configuration for different websites and their button click settings.
     * @type {SiteConfig[]}
     */
    const siteConfigurations = [
        {
            hostnames: 'musicbrainz.org',
            paths: ['/edit', '/add-cover-art'],
            channelName: 'mb_edit_channel',
            messageTrigger: 'submit-edit',
            buttonSelector: 'button.submit.positive[type="submit"]',
            menuCommandName: 'MusicBrainz: Submit Edit (All Tabs)'
        },

        {
            hostnames: ['magicisrc.kepstin.ca','magicisrc-beta.kepstin.ca'],
            paths: ['/'],
            channelName: 'magicisrc_submit_channel',
            messageTrigger: 'submit-isrcs',
            buttonSelector: '[onclick^="doSubmitISRCs"]',
            menuCommandName: 'MagicISRC: Submit ISRCs (All Tabs)'
        }
    ];

    const scriptName = GM.info.script.name;

    /**
     * Sends a message to the specified BroadcastChannel.
     * @param {string} channelName
     * @param {string} message
     */
    function sendMessageToChannel(channelName, message) {
        try {
            new BroadcastChannel(channelName).postMessage(message);
            console.log(`[${scriptName}] Sent message "${message}" to channel "${channelName}".`);
        } catch (error) {
            console.error(`[${scriptName}] Error sending message to channel "${channelName}":`, error);
        }
    }

    /**
     * Initializes the BroadcastChannel listener for the current site if a match is found.
     * Also registers a menu command if `GM_registerMenuCommand` is available.
     */
    function initializeBroadcastChannelListener() {
        const currentHostname = location.hostname;
        const currentPathname = location.pathname;

        for (const config of siteConfigurations) {
            const hostnames = Array.isArray(config.hostnames) ? config.hostnames : [config.hostnames];
            const paths = Array.isArray(config.paths) ? config.paths : [config.paths];

            const hostnameMatches = hostnames.some(hostname => currentHostname.endsWith(hostname));
            const pathMatches = paths.some(path => currentPathname.includes(path));

            if (hostnameMatches && pathMatches) {
                try {
                    const channel = new BroadcastChannel(config.channelName);
                    channel.addEventListener('message', (event) => {
                        if (event.data === config.messageTrigger) {
                            const btn = document.querySelector(config.buttonSelector);
                            if (btn) {
                                btn.click();
                                console.log(`[${scriptName}][${scriptName}] Button clicked for selector "${config.buttonSelector}" on channel "${config.channelName}".`);
                            } else {
                                console.warn(`[${scriptName}] Button with selector "${config.buttonSelector}" not found.`);
                            }
                        }
                    });
                    console.log(`[${scriptName}] Listener active for channel "${config.channelName}".`);
                } catch (error) {
                    console.error(`[${scriptName}] Error initializing BroadcastChannel for "${config.channelName}":`, error);
                }

                if (typeof GM_registerMenuCommand !== 'undefined' && config.menuCommandName) {
                    GM_registerMenuCommand(config.menuCommandName, () => {
                        sendMessageToChannel(config.channelName, config.messageTrigger);
                    });
                    console.log(`[${scriptName}] Registered menu command "${config.menuCommandName}".`);
                } else {
                   console.warn(`[${scriptName}] Couldn't register menu command "${config.menuCommandName}".`)
                }

                // Only activate one listener and register one command per tab for the first matching configuration
                return;
            }
        }
    }

    initializeBroadcastChannelListener();
})();