moetwa / Google Drive Authuser Redirector

// ==UserScript==
// @name         Google Drive Authuser Redirector
// @name:tr      Google Drive Authuser Yönlendirici
// @namespace    https://github.com/moetwa/userscripts
// @version      1.0.1
// @description  Forces Google Drive file links to open with the selected account index.
// @description:tr Google Drive bağlantılarının seçilen hesap dizini ile açılmasını sağlar.
// @author       moetwa
// @match        *://*/*
// @run-at       document-start
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const currentHost = window.location.hostname;
    const currentPath = window.location.pathname;

    // load saved auth index dynamically
    // always fetch the freshest setting directly from db to avoid stale tab issues
    function getAuthUser() {
        return GM_getValue('defaultAuthUser', '0');
    }

    // Early redirect
    // catches direct url pastes and google's server-side access denied redirects
    const isDriveFilePage = currentPath.includes('/file/d/') || currentPath.includes('/drivesharing/');

    if (currentHost === 'drive.google.com' && isDriveFilePage) {
        const currentUrl = new URL(window.location.href);
        const currentAuthUser = currentUrl.searchParams.get('authuser');
        const targetAuth = getAuthUser();

        // bounce to the correct account immediately
        if (currentAuthUser !== targetAuth) {
            currentUrl.searchParams.set('pli', '1');
            currentUrl.searchParams.set('authuser', targetAuth);

            // use replace() so we don't trap the user in a back-button loop
            window.location.replace(currentUrl.toString());
            return; // halt execution until the new page renders
        }
    }

    // External Link Interceptor
    // intercept clicks on external sites. doing this inside drive breaks their SPA routing.
    if (currentHost !== 'drive.google.com') {
        const targetPattern = 'drive.google.com/';

        // function to modify links with the absolute latest auth value
        const updateLinkHref = (link) => {
            if (link.href.includes('/file/d/') || link.href.includes('id=')) {
                try {
                    const targetAuth = getAuthUser();
                    let url = new URL(link.href);
                    if (url.searchParams.get('authuser') !== targetAuth) {
                        url.searchParams.set('pli', '1');
                        url.searchParams.set('authuser', targetAuth);
                        link.href = url.toString();
                    }
                } catch (e) {
                    // sssshh!
                }
            }
        };

        // exact-millisecond interception
        // fires right before browser navigation (covers clicks and middle clicks)
        const interactionHandler = (event) => {
            const link = event.target.closest('a');
            if (link && link.href && link.href.includes(targetPattern)) {
                updateLinkHref(link);
            }
        };

        document.addEventListener('click', interactionHandler, true);
        document.addEventListener('auxclick', interactionHandler, true);

        // modify links for right click copying
        const scanDOM = () => {
            document.querySelectorAll(`a[href*="${targetPattern}"]`).forEach(updateLinkHref);
        };

        // wait for DOM before parsing links
        document.addEventListener('DOMContentLoaded', () => {
            scanDOM();

            // disabled MutationObserver here to save cpu on all web pages
            // click interception handles dynamic links anyway.
        });
    }

    // Settings UI
    if (currentHost === 'drive.google.com') {
        document.addEventListener('DOMContentLoaded', () => {
            injectSettingsPanel();
        });
    }

    function injectSettingsPanel() {
        // base styling for the floating panel
        GM_addStyle(`
            #gd-auth-panel { position: fixed; top: 50%; right: -250px; width: 250px; background: #ffffff; border: 1px solid #dadce0; border-radius: 8px 0 0 8px; box-shadow: -2px 0 8px rgba(0,0,0,0.1); transition: right 0.3s ease; z-index: 999999; font-family: Arial, sans-serif; color: #202124; transform: translateY(-50%); }
            #gd-auth-panel.open { right: 0; }
            #gd-auth-toggle { position: absolute; left: -40px; top: 20px; width: 40px; height: 40px; background: #1a73e8; color: white; border-radius: 8px 0 0 8px; cursor: pointer; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 12px; box-shadow: -2px 0 5px rgba(0,0,0,0.1); }
            #gd-auth-content { padding: 16px; }
            #gd-auth-content h3 { margin: 0 0 12px 0; font-size: 15px; color: #1a73e8; }
            .gd-radio-group { margin-bottom: 10px; }
            .gd-radio-group label { cursor: pointer; font-size: 14px; display: flex; align-items: center; gap: 8px; }
            #gd-auth-save { margin-top: 15px; background: #1a73e8; color: white; border: none; padding: 10px 12px; border-radius: 4px; cursor: pointer; width: 100%; font-weight: bold; }
            #gd-auth-save:hover { background: #1557b0; }
        `);

        // build DOM manually to respect Google's strict CSP (no innerHTML allowed)
        const container = document.createElement('div');
        container.id = 'gd-auth-panel';

        const toggleBtn = document.createElement('div');
        toggleBtn.id = 'gd-auth-toggle';
        toggleBtn.textContent = 'ACC';

        const content = document.createElement('div');
        content.id = 'gd-auth-content';

        const header = document.createElement('h3');
        header.textContent = 'Select Default Account';
        content.appendChild(header);

        const currentSavedAuth = getAuthUser();

        // generate radio inputs for standard account indexes (0-4)
        for (let i = 0; i <= 4; i++) {
            const group = document.createElement('div');
            group.className = 'gd-radio-group';

            const label = document.createElement('label');
            const radioInput = document.createElement('input');
            radioInput.type = 'radio';
            radioInput.name = 'authuser_index';
            radioInput.value = i.toString();

            if (parseInt(currentSavedAuth) === i) radioInput.checked = true;

            const labelText = document.createTextNode(' Account Index ' + i);

            label.appendChild(radioInput);
            label.appendChild(labelText);
            group.appendChild(label);
            content.appendChild(group);
        }

        const saveBtn = document.createElement('button');
        saveBtn.id = 'gd-auth-save';
        saveBtn.textContent = 'Save Selection';
        content.appendChild(saveBtn);

        container.appendChild(toggleBtn);
        container.appendChild(content);
        document.body.appendChild(container);

        // interactions
        toggleBtn.addEventListener('click', () => {
            container.classList.toggle('open');
        });

        saveBtn.addEventListener('click', () => {
            const selectedRadio = document.querySelector('input[name="authuser_index"]:checked');
            if (selectedRadio) {
                const targetValue = selectedRadio.value;
                GM_setValue('defaultAuthUser', targetValue);

                // instantly force the current tab to navigate to the new account
                const currentUrl = new URL(window.location.href);
                currentUrl.searchParams.set('pli', '1');
                currentUrl.searchParams.set('authuser', targetValue);
                window.location.replace(currentUrl.toString());
            }
        });
    }

})();