EMS 審核自動化(手動觸發+iframe支援)

點擊畫面按鈕後才開始自動審核流程,支援 iframe 和小循環操作,修復確定按鈕偵測問題,偵測到按鈕才跳金鑰驗證

// ==UserScript==
// @name         EMS 審核自動化(手動觸發+iframe支援)
// @namespace    http://tampermonkey.net/
// @version      3.0(金鑰版)
// @description  點擊畫面按鈕後才開始自動審核流程,支援 iframe 和小循環操作,修復確定按鈕偵測問題,偵測到按鈕才跳金鑰驗證
// @match        https://ems.mohw.gov.tw/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    const KEY_URL = "https://catjohnny.github.io/ems-key-auth/emsauth.json";

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

    function waitForSelector(selector, root = document, timeout = 30000) {
        return new Promise((resolve, reject) => {
            const start = Date.now();
            const timer = setInterval(() => {
                const el = root.querySelector(selector);
                if (el) {
                    clearInterval(timer);
                    resolve(el);
                } else if (Date.now() - start > timeout) {
                    clearInterval(timer);
                    reject(new Error("等待超時:" + selector));
                }
            }, 500);
        });
    }

    function decodeBase64(str) {
        try {
            return atob(str);
        } catch {
            return "";
        }
    }

    async function fetchValidKeys() {
        try {
            const res = await fetch(KEY_URL + "?t=" + Date.now());
            const data = await res.json();
            return data.valid_keys.map(decodeBase64);
        } catch (e) {
            alert("❌ 金鑰伺服器連線失敗!");
            return [];
        }
    }

    async function verifyKey() {
        const inputKey = prompt("🔒 請輸入金鑰(陳家和設計):");
        const validKeys = await fetchValidKeys();
        if (!validKeys.includes(inputKey)) {
            alert("❌ 金鑰錯誤,無法使用本腳本。");
            return false;
        }
        return true;
    }

    async function processAudit() {
        const start = parseInt(prompt("請輸入起始筆數,例如 1:"), 10);
        const end = parseInt(prompt("請輸入結束筆數,例如 206:"), 10);

        const iframe = document.querySelector("iframe");
        const root = iframe?.contentDocument || document;

        const buttons = root.querySelectorAll("button.btn-info[onclick^='rowAction.OpenStatus']");
        if (!buttons.length) {
            alert("⚠️ 找不到任何審核按鈕!");
            return;
        }

        for (let i = start - 1; i < end && i < buttons.length; i++) {
            try {
                console.log(`✅ 點擊第 ${i + 1} 筆審核`);
                buttons[i].scrollIntoView();
                buttons[i].click();
                await delay(800);

                await waitForSelector("#CP_STATUS_A");
                document.querySelector("#CP_STATUS_A").click();
                await delay(500);

                await waitForSelector("button.btn-success[onclick='setCourseStatus()']");
                document.querySelector("button.btn-success[onclick='setCourseStatus()']").click();
                await delay(500);

                const confirmBtn = [...document.querySelectorAll("span.l-btn-text")].find(span => span.textContent.trim() === "確定");
                if (confirmBtn) {
                    confirmBtn.click();
                } else {
                    throw new Error("找不到確定按鈕");
                }

                await delay(2500);

            } catch (err) {
                console.warn(`❌ 第 ${i + 1} 筆失敗:`, err);
                alert(`第 ${i + 1} 筆失敗,請手動處理後按確定繼續`);
            }
        }

        alert("🎉 所有審核完成!");
    }

    function addStartButton() {
        const btn = document.createElement("button");
        btn.textContent = "▶️ 開始自動審核 3.0版";
        btn.style.position = "fixed";
        btn.style.bottom = "20px";
        btn.style.right = "20px";
        btn.style.zIndex = "9999";
        btn.style.padding = "10px 15px";
        btn.style.backgroundColor = "#28a745";
        btn.style.color = "white";
        btn.style.border = "none";
        btn.style.borderRadius = "5px";
        btn.style.boxShadow = "0 2px 6px rgba(0,0,0,0.2)";
        btn.style.cursor = "pointer";
        btn.addEventListener("click", processAudit);
        document.body.appendChild(btn);
    }

    // ✅ 保留原功能 + 改成偵測到按鈕才跳金鑰
    waitForSelector("button.btn-info[onclick^='rowAction.OpenStatus']")
        .then(async () => {
            console.log("✅ 偵測到審核按鈕");

            const passed = await verifyKey();
            if (!passed) return;

            console.log("🔓 金鑰驗證成功,加入開始按鈕");
            addStartButton();
        })
        .catch(err => console.error("❌ 未能偵測到審核按鈕:", err));

})();