MW Idle 无所事事自动挤奶

每5分钟检测“无所事事”,若存在则自动执行:挤奶 → 奶牛 → 开始(每步1秒),并确认状态切换为“奶牛 [∞]”。

// ==UserScript==
// @name         MW Idle 无所事事自动挤奶
// @namespace    mwidle-auto
// @version      1.0.1
// @description  每5分钟检测“无所事事”,若存在则自动执行:挤奶 → 奶牛 → 开始(每步1秒),并确认状态切换为“奶牛 [∞]”。
// @match        https://www.milkywayidle.com/*
// @grant        none
// @run-at       document-idle
// @license         MIT
// ==/UserScript==

(function () {
    'use strict';

    // 5分钟检查一次
    const CHECK_INTERVAL_MS = 5 * 60 * 1000;
    const CLICK_DELAY_MS = 1000;
    const ENABLE_TEST_BUTTON = false; // 如需开启手动测试按钮,将此值改为 true
    const ENABLE_IDLE_OBSERVER = true; // 即时监听“无所事事”,检测到后立即尝试执行
    const ENABLE_KEEPALIVE = false; // 保活(降低后台节流),如需开启改为 true

    // 你提供的 XPath
    const XPATHS = {
        idle_indicator: '//*[@id="root"]/div/div/div[1]/div/div[1]/div[2]/div[1]/div/div[1]/div[2]',
        nav_milk: '//*[@id="root"]/div/div/div[2]/div[1]/div/div[2]/div[2]/div[5]/div[1]/div/div[1]/span[1]',
        cow_name: '//*[@id="root"]/div/div/div[2]/div[2]/div[1]/div[1]/div/div[1]/div/div[1]/div/div/div/div/div/div/div[1]/div[1]',
        start_button: '//*[@id="root"]/div/div/div[2]/div[2]/div[1]/div[1]/div/div[1]/div/div[3]/div[2]/div[1]/div/div[3]/div[7]/button',
        header_cow: '//*[@id="root"]/div/div/div[1]/div/div[1]/div[2]/div[1]/div/div[1]/div[2]'
    };

    function isVisible(element) {
        if (!element) return false;
        const rect = element.getBoundingClientRect();
        const style = window.getComputedStyle(element);
        return rect.width > 0 && rect.height > 0 && style.visibility !== 'hidden' && style.display !== 'none';
    }

    function evalXPath(xpath) {
        try {
            const res = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
            return res.singleNodeValue || null;
        } catch (e) {
            return null;
        }
    }

    function safeClickElement(el) {
        if (!el || !isVisible(el)) return false;
        try {
            el.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
            el.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
            el.click();
            return true;
        } catch (e) {
            return false;
        }
    }

    function clickByXPath(xpath) {
        const el = evalXPath(xpath);
        return safeClickElement(el);
    }

    function getTextByXPath(xpath) {
        const el = evalXPath(xpath);
        return (el && (el.textContent || '').trim()) || '';
    }

    function isIdle() {
        const text = getTextByXPath(XPATHS.idle_indicator);
        return text.includes('无所事事');
    }

    function isCowHeader() {
        const text = getTextByXPath(XPATHS.header_cow);
        return text.includes('奶牛');
    }

    function sleep(ms) {
        return new Promise((resolve) => setTimeout(resolve, ms));
    }

    async function startMilkingSequence() {
        // 1) 挤奶(导航)
        clickByXPath(XPATHS.nav_milk);
        await sleep(CLICK_DELAY_MS);

        // 2) 奶牛(名称元素)
        clickByXPath(XPATHS.cow_name);
        await sleep(CLICK_DELAY_MS);

        // 3) 开始(按钮)
        clickByXPath(XPATHS.start_button);
        await sleep(CLICK_DELAY_MS);
    }

    async function runOnce() {
        try {
            // 仅当“无所事事”时才启动挤奶流程
            if (!isIdle()) return;

            await startMilkingSequence();

            // 尝试确认状态切换为“奶牛 [∞]”,最多等待3秒(分3次,每次1秒)
            for (let i = 0; i < 3; i++) {
                if (isCowHeader()) break;
                await sleep(1000);
            }
        } catch (e) {
            // 忽略错误,避免影响页面
        }
    }

    // 周期轮询
    setInterval(runOnce, CHECK_INTERVAL_MS);

    // 初始化首次检查(避免刚好空闲时需等5分钟)
    setTimeout(runOnce, 3000);

    // 即时监听“无所事事”状态变更
    function setupIdleObserver() {
        if (!ENABLE_IDLE_OBSERVER) return;
        let scheduled = false;
        const observer = new MutationObserver(() => {
            if (scheduled) return;
            scheduled = true;
            setTimeout(() => {
                scheduled = false;
                if (isIdle()) runOnce();
            }, 250);
        });
        try {
            observer.observe(document.documentElement, { childList: true, subtree: true, characterData: true });
        } catch (e) {
            // 忽略
        }
        // 标签页重新可见时也尝试一次
        document.addEventListener('visibilitychange', () => {
            if (document.visibilityState === 'visible') {
                setTimeout(runOnce, 500);
            }
        });
    }

    // 保活:使用 WebAudio 降低后台节流概率
    function setupKeepAlive() {
        if (!ENABLE_KEEPALIVE) return;
        try {
            const AudioCtx = window.AudioContext || window.webkitAudioContext;
            if (!AudioCtx) return;
            const ctx = new AudioCtx();
            const osc = ctx.createOscillator();
            const gain = ctx.createGain();
            gain.gain.value = 0.00001; // 接近静音
            osc.connect(gain).connect(ctx.destination);
            osc.start();
            // 定期尝试恢复,防止被挂起
            setInterval(() => {
                if (ctx.state !== 'running') ctx.resume().catch(() => {});
            }, 15000);
            // 重新可见时恢复
            document.addEventListener('visibilitychange', () => {
                if (document.visibilityState === 'visible' && ctx.state !== 'running') {
                    ctx.resume().catch(() => {});
                }
            });
        } catch (e) {
            // 忽略
        }
    }

    // 可选:页面上的手动测试按钮
    function createTestButton() {
        if (!ENABLE_TEST_BUTTON) return;
        if (document.getElementById('mwidle-milk-test')) return;
        if (!document.body) return;
        const btn = document.createElement('button');
        btn.id = 'mwidle-milk-test';
        btn.textContent = '测试挤奶';
        Object.assign(btn.style, {
            position: 'fixed',
            right: '16px',
            bottom: '64px', // 避开其它按钮
            zIndex: '99999',
            padding: '8px 12px',
            fontSize: '14px',
            borderRadius: '6px',
            border: '1px solid #888',
            background: '#2a5',
            color: '#fff',
            cursor: 'pointer',
            opacity: '0.9'
        });
        btn.addEventListener('mouseenter', () => btn.style.opacity = '1');
        btn.addEventListener('mouseleave', () => btn.style.opacity = '0.9');
        btn.addEventListener('click', async () => {
            btn.disabled = true;
            btn.textContent = '测试中...';
            try {
                await runOnce();
            } finally {
                btn.disabled = false;
                btn.textContent = '测试挤奶';
            }
        });
        document.body.appendChild(btn);
    }

    if (document.readyState === 'loading') {
        window.addEventListener('DOMContentLoaded', () => {
            createTestButton();
            setupIdleObserver();
            setupKeepAlive();
        }, { once: true });
    } else {
        createTestButton();
        setupIdleObserver();
        setupKeepAlive();
    }
})();