wujinjun / UnoAnalyzer 优诺 自动登录

// ==UserScript==
// @name         UnoAnalyzer 优诺 自动登录
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  自动完成登录,支持手动输入账号或自动填入账号,右上角显示操作进度
// @author       wujinjun
// @license      MIT
// @match        https://cn.unoanalyzer.com/*
// @match        https://unoanalyzer.com/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    const scriptName = GM_info.script.name;
    const LOG_PREFIX = `[${scriptName}] `;
    const log = (...args) => console.log(LOG_PREFIX, ...args);
    const warn = (...args) => console.warn(LOG_PREFIX, ...args);
    const error = (...args) => console.error(LOG_PREFIX, ...args);
    // =========================================================================================
    const SAVED_EMAIL = 'YOUR_SAVED_EMAIL'; // <-- 替换为您的邮箱 (例如: 'FirstName.LastName01@example.com')
    const SAVED_PASSWORD = 'YOUR_SAVED_PASSWORD'; // <-- 替换为您的密码 (例如: 'MySecurePassword')
    // =========================================================================================


    // --- 元素选择器定义 ---
    const EMAIL_SELECTOR = '#email';
    const PASSWORD_SELECTOR = '#password';
    const SUBMIT_SELECTOR = 'button[type="submit"]';

    // --- 辅助函数:等待元素出现 ---
    function waitForElement(selector) {
        return new Promise(resolve => {
            const element = document.querySelector(selector);
            if (element) {
                return resolve(element);
            }

            const observer = new MutationObserver((mutationsList, observer) => {
                const target = document.querySelector(selector);
                if (target) {
                    observer.disconnect();
                    resolve(target);
                }
            });

            observer.observe(document.body, { childList: true, subtree: true });
        });
    }

    // --- 辅助函数: React "Hack" 设置输入框内容 ---
    // Credit: https://stackoverflow.com/questions/52120524/type-text-into-a-react-input-using-javascript-tampermonkey-script
        // Archive: https://web.archive.org/web/20250805153214/https://stackoverflow.com/questions/52120524/type-text-into-a-react-input-using-javascript-tampermonkey-script
        // Specific Answer: https://web.archive.org/web/20250805153214/https://stackoverflow.com/a/79077165
    function setReactInputValue(el, value) {
        // Store the last value to update React's internal tracker
        const last = el.value;
        // Use the native value setter to change the input's value
        const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
            window.HTMLInputElement.prototype,
            'value'
        ).set;
        nativeInputValueSetter.call(el, value);
        // Update React's internal value tracker
        const event = new Event('input', { bubbles: true });
        const tracker = el._valueTracker;
        if (tracker) {
            tracker.setValue(last);
        }
        // Dispatch the input event to notify React of the change
        el.dispatchEvent(event);
    }

    // --- 主执行函数 ---
    // --- URL change detection for SPA navigation ---
    function hookHistoryEvents() {
        const _wr = (type) => {
            const orig = history[type];
            return function() {
                const res = orig.apply(this, arguments);
                window.dispatchEvent(new Event('locationchange'));
                return res;
            };
        };
        history.pushState = _wr('pushState');
        history.replaceState = _wr('replaceState');
        window.addEventListener('popstate', () => window.dispatchEvent(new Event('locationchange')));
    }

    function isLoginUrl(href) {
        try {
            const u = new URL(href, location.origin);
            const p = u.pathname || '';
            return p.startsWith('/app/login') || p.startsWith('/login');
        } catch (e) {
            return false;
        }
    }

    let _autoLoginRunning = false;
    async function main() {
        if (_autoLoginRunning) {
            log('自动登录已在运行,跳过');
            return;
        }
        _autoLoginRunning = true;

        // 使用 Promise.all 等待关键元素加载
        try {
            const [emailEl, submitEl] = await Promise.all([
                waitForElement(EMAIL_SELECTOR),
                waitForElement(SUBMIT_SELECTOR),
            ]);

            const sleep = (ms) => new Promise(r => setTimeout(r, ms));
            const triggerInputEvents = (el) => {
                el.dispatchEvent(new Event('input', { bubbles: true }));
                el.dispatchEvent(new Event('change', { bubbles: true }));
            };

            // 填写邮箱并提交
            if (SAVED_EMAIL && SAVED_EMAIL !== 'YOUR_SAVED_EMAIL') {
                emailEl.focus();
                setReactInputValue(emailEl, SAVED_EMAIL);
                triggerInputEvents(emailEl);
                log('邮箱已填写');
            } else {
                log('SAVED_EMAIL 未设置,跳过填写邮箱');
            }

            await sleep(200);
            if (submitEl) {
                submitEl.focus();
                await sleep(500);
                submitEl.click();
                log('已点击提交(邮箱阶段)');
            } else {
                warn('未找到提交按钮(邮箱阶段)');
            }

            // 等待密码输入框出现并填写
            const passwordEl = await waitForElement(PASSWORD_SELECTOR);
            if (passwordEl) {
                if (SAVED_PASSWORD && SAVED_PASSWORD !== 'YOUR_SAVED_PASSWORD') {
                    passwordEl.focus();
                    setReactInputValue(passwordEl, SAVED_PASSWORD);
                    triggerInputEvents(passwordEl);
                    log('密码已填写');
                } else {
                    log('SAVED_PASSWORD 未设置,跳过填写密码');
                }

                await sleep(200);
                const submitEl2 = document.querySelector(SUBMIT_SELECTOR);
                if (submitEl2) {
                    submitEl2.click();
                    log('已点击提交(密码阶段)');
                } else {
                    warn('未找到提交按钮(密码阶段)');
                }
            } else {
                warn('未找到密码输入框');
            }
        } catch (e) {
            error('自动登录出错', e);
        } finally {
            _autoLoginRunning = false;
        }
    }

    // 运行主函数
    main();

})();