MusicBrainz: Auto login MusicBrainz ISRC importers

Attempts to login on MusicBrainz ISRC submission sites like ISRC Hunt or MagicISRC and automatically handle OAuth authorization

当前为 2025-05-27 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         MusicBrainz: Auto login MusicBrainz ISRC importers
// @namespace    https://musicbrainz.org/user/chaban
// @version      2.0
// @description  Attempts to login on MusicBrainz ISRC submission sites like ISRC Hunt or MagicISRC and automatically handle OAuth authorization
// @tag          ai-created
// @author       chaban
// @license      MIT
// @match        https://*.musicbrainz.org/oauth2/authorize*
// @match        https://magicisrc.kepstin.ca/*
// @match        https://magicisrc-beta.kepstin.ca/
// @match        https://isrchunt.com/*
// @exclude      https://magicisrc.kepstin.ca/?code=*
// @exclude      https://magicisrc.kepstin.ca/?state=*
// @exclude      https://magicisrc-beta.kepstin.ca/?code=*
// @exclude      https://magicisrc-beta.kepstin.ca/?state=*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // Helper function for logging messages to the console, useful for debugging.
    function log(message) {
        console.log(`[MusicBrainz Auto Login] ${message}`);
    }

    // --- Configuration for Trusted Clients ---
    // Defines trusted client IDs, their associated redirect URI base URLs, and expected OAuth scopes for validation.
    const trustedClients = {
        'oxqZoCJWy9BQXgS7UTikeA': {
            redirectUriBase: 'https://magicisrc.kepstin.ca',
            expectedScopes: ['profile', 'submit_isrc'],
            name: 'MagicISRC (main)'
        },
        'flI-ayzX2u2pzMWosH27FQ': {
            redirectUriBase: 'https://magicisrc-beta.kepstin.ca',
            expectedScopes: ['profile', 'submit_isrc'],
            name: 'MagicISRC (beta)'
        },
        'BzRD1-z1sMBfKVnOaJiMLIFL6_7WSaL5': {
            redirectUriBase: 'https://isrchunt.com',
            expectedScopes: ['profile', 'submit_isrc'],
            name: 'ISRCHunt'
        }
    };

    // --- Derived Configuration for Importer Pages ---
    // Extract the unique origins of the trusted ISRC importer sites.
    const trustedImporterOrigins = Object.values(trustedClients)
        .map(client => new URL(client.redirectUriBase).origin + '/')
        .filter((value, index, self) => self.indexOf(value) === index)

    // --- Helper function for Scope Validation ---
    // Checks if the requested scopes exactly match the expected scopes, ignoring order.
    function isValidScope(requestedScopeString, expectedScopes) {
        if (!requestedScopeString) {
            log("Scope validation FAILED: No 'scope' parameter found in URL.");
            return false;
        }

        const requestedScopes = requestedScopeString.split(/[\s+]/).filter(s => s).sort();
        const sortedExpectedScopes = [...expectedScopes].sort(); // Create a copy to avoid modifying original

        if (requestedScopes.length !== sortedExpectedScopes.length) {
            log(`Scope validation FAILED: Length mismatch. Requested: ${requestedScopes.length}, Expected: ${sortedExpectedScopes.length}`);
            return false;
        }

        const allMatch = requestedScopes.every((scope, index) => scope === sortedExpectedScopes[index]);
        if (!allMatch) {
            log(`Scope validation FAILED: Content mismatch. Requested: [${requestedScopes.join(', ')}], Expected: [${sortedExpectedScopes.join(', ')}]`);
        }
        return allMatch;
    }

    // --- Function to handle the MusicBrainz OAuth Authorization Page ---
    // This function attempts to auto-click the 'Allow Access' or 'Confirm' button
    // after validating the requesting client's redirect URI, client ID, and scopes.
    function handleOAuthAuthorizationPage() {
        log('Detected MusicBrainz OAuth authorization page.');

        const urlParams = new URLSearchParams(window.location.search);
        const redirectUri = urlParams.get('redirect_uri');
        const clientId = urlParams.get('client_id');
        const requestedScopeString = urlParams.get('scope');

        let isTrustedClient = false;
        let clientName = 'Unknown';
        let redirectUriOrigin = null;

        try {
            redirectUriOrigin = redirectUri ? new URL(redirectUri).origin : null;

            for (const id in trustedClients) {
                const clientInfo = trustedClients[id];
                const trustedOrigin = new URL(clientInfo.redirectUriBase).origin;

                // Step 1: Validate Client ID and Redirect URI Origin
                if (clientId === id && redirectUriOrigin && redirectUriOrigin === trustedOrigin) {
                    clientName = clientInfo.name;
                    log(`Client ID and Redirect URI Origin matched for: ${clientName}`);

                    // Step 2: Validate Scopes
                    if (isValidScope(requestedScopeString, clientInfo.expectedScopes)) {
                        isTrustedClient = true;
                        log(`Scope validation passed for ${clientName}.`);
                    } else {
                        log(`Final validation FAILED: Scopes did not match for ${clientName}.`);
                    }
                    break;
                }
            }
        } catch (e) {
            log(`Error during OAuth validation: ${e.message}. Script will not auto-confirm.`);
            return;
        }

        if (isTrustedClient) {
            log(`OAuth request is fully validated for trusted client: ${clientName}
                 (Redirect URI Origin: ${redirectUriOrigin}, Client ID: ${clientId || 'N/A'})`);

            const confirmButton = document.querySelector('button[name="confirm.submit"]');
            if (confirmButton) {
                const buttonText = confirmButton.textContent.trim().toLowerCase();
                if (buttonText.includes('allow access') || buttonText.includes('confirm')) {
                    log(`Found "${buttonText}" button. Clicking it...`);
                    confirmButton.click();
                } else {
                    log('OAuth confirmation button found, but text content mismatch. Not clicking.');
                }
            } else {
                log('OAuth confirmation button not found.');
            }
        } else {
            log(`OAuth request is NOT fully validated for auto-confirmation.
                 Detected Redirect URI: ${redirectUri}, Detected Client ID: ${clientId}, Detected Scopes: ${requestedScopeString || 'N/A'}`);
        }
    }

    // --- Function to handle ISRC Importer Login Pages ---
    // This function attempts to automatically initiate the login process
    // on ISRC Hunt or MagicISRC sites.
    function handleISRCImporterLoginPage() {
        log('Detected ISRC importer page.');

        // Attempt to click the login button specific to MagicISRC.
        const magicisrcLoginButton = document.querySelector('button[onclick^="doLogin();"]');
        if (magicisrcLoginButton) {
            log('Found MagicISRC login button with doLogin(). Clicking it...');
            magicisrcLoginButton.click();
        } else {
            log('MagicISRC login button not found.');
        }

        // Attempt to click the login link specific to ISRC Hunt.
        const isrchuntLoginLink = document.querySelector('a[href^="https://musicbrainz.org/oauth2/authorize"]');
        if (isrchuntLoginLink) {
            log(`Found ISRC Hunt login link. Clicking it...`);
            isrchuntLoginLink.click();
        } else {
              log('ISRC Hunt login link not found.');
        }
    }

    // --- Main Execution Flow ---
    // Determines which handler function to call based on the current URL.
    const currentUrl = window.location.href;

    if (currentUrl.includes('musicbrainz.org/oauth2/authorize')) {
        handleOAuthAuthorizationPage();
    } else if (trustedImporterOrigins.some(origin => currentUrl.startsWith(origin))) {
        handleISRCImporterLoginPage();
    } else {
        log('Current URL does not match any known handler.');
    }

})();