NovelAI 批量Roll图助手

从本地TXT文件读取提示词,全自动批量生成图像。顺序/随机模式,可自动记忆文件、模板和进度。

当前为 2025-09-24 提交的版本,查看 最新版本

// ==UserScript==
// @name         NovelAI 批量Roll图助手
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  从本地TXT文件读取提示词,全自动批量生成图像。顺序/随机模式,可自动记忆文件、模板和进度。
// @author       Takoro
// @match        https://novelai.net/image*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==

// Gemini(呆)
(function() {
    'use strict';

    let promptsArray = [];
    let currentPromptIndex = 0;
    let isAutoRunning = false;
    let promptsProcessedThisRun = 0;
    let titleElement;
    const STORAGE_KEYS = {
        START_INDEX: 'nai_helper_start_index',
        PROMPT_TEMPLATE: 'nai_helper_prompt_template',
        CHARACTER_PROMPT_TEMPLATE: 'nai_helper_char_template',
        LAST_FILE_NAME: 'nai_helper_last_file_name',
        LAST_FILE_CONTENT: 'nai_helper_last_file_content',
        RANDOM_MODE: 'nai_helper_random_mode',
        RANDOM_COUNT: 'nai_helper_random_count'
    };

    const sleep = ms => new Promise(res => setTimeout(res, ms));

    function getElementByXPath(path) {
        return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    }

    async function waitForElement(selector, parent = document, timeout = 5000) {
        return new Promise((resolve) => {
            const interval = 100; let elapsed = 0;
            const timer = setInterval(() => {
                const element = parent.querySelector(selector);
                if (element) { clearInterval(timer); resolve(element); }
                elapsed += interval;
                if (elapsed >= timeout) { clearInterval(timer); resolve(null); }
            }, interval);
        });
    }

    function createUIPanel() {
        const panel = document.createElement('div');
        panel.id = 'nai-prompt-helper-panel';
        panel.innerHTML = `
            <h3 title="点击折叠/展开">提示词批量助手</h3>
            <div class="content-wrapper">
                <div class="control-group file-loader">
                    <label for="prompt-file-input" class="custom-file-button">1. 选择新文件</label>
                    <input type="file" id="prompt-file-input" accept=".txt" style="display: none;">
                </div>
                <div id="file-info-display">当前未加载文件</div>
                <div class="control-group">
                    <label for="preset-prompt-input">2. 提示词模板</label>
                    <textarea id="preset-prompt-input" rows="3" placeholder="脚本会从本地txt选择一组Prompt来替换该输入框中的 [替换词] 后填入NAI的主提示词框。\n写法:\nartist1, artist2, [替换词], very aesthetic, masterpiece, no text"></textarea>
                </div>
                <div class="control-group">
                    <label for="character-prompt-input">3. 角色模板</label>
                    <textarea id="character-prompt-input" rows="3" placeholder="以主动换行为一条,随机抽取,留空则会清空页面上的角色框内容。"></textarea>
                </div>
                <hr>
                <div class="heading-with-control">
                    <label for="character-prompt-input">4. 自动化设置</label>
                    <div class="toggle-switch-wrapper">
                        <label class="toggle-switch-label" for="random-mode-checkbox">随机模式</label>
                        <label class="toggle-switch">
                            <input type="checkbox" id="random-mode-checkbox">
                            <span class="slider"></span>
                        </label>
                    </div>
                </div>
                <div class="control-group settings">
                    <div>
                        <label for="start-index-input" id="start-index-label">文件行数:</label>
                        <input type="number" id="start-index-input" value="1" min="1">
                    </div>
                    <div>
                        <label for="images-per-prompt-input">每条张数:</label>
                        <input type="number" id="images-per-prompt-input" value="1" min="1">
                    </div>
                    <div>
                        <label for="delay-input">延迟(秒):</label>
                        <input type="number" id="delay-input" value="2" min="0" step="0.5">
                    </div>
                </div>
                <hr>
                <button id="main-control-btn">▶️ 开始自动执行</button>
                <div id="status-display">状态:等待操作...</div>
                <div id="run-counter-display">本次运行: 0 条</div>
            </div>`;
        document.body.appendChild(panel);

        titleElement = document.querySelector('#nai-prompt-helper-panel h3');
        addEventListeners();
        loadSettings();
    }

    function removeUnwantedElement() {
        const xpath = '//*[@id="__next"]/div[2]/div[3]/div[3]/div/div[1]/div[5]/div[2]';
        const interval = 3000;
        let attempts = 0;
        const maxAttempts = 3;

        console.log(`NAI助手: 页面加载完成,将在 ${interval / 1000} 秒后开始首次检测要删除的元素。`);

        const checker = setInterval(() => {
            attempts++;
            // console.log(`NAI助手: 开始第 ${attempts}/${maxAttempts} 次检测...`);
            const element = getElementByXPath(xpath);

            if (element) {
                console.log(`NAI助手: 已找到并移除目标元素。停止检测。`);
                element.remove();
                clearInterval(checker);
            }

            if (attempts >= maxAttempts) {
                if (!element) {
                    console.log(`NAI助手: 已完成 ${maxAttempts} 次检测,未找到目标元素,停止检测。`);
                }
                clearInterval(checker);
            }
        }, interval);
    }

    async function addCharacterSlot() {
        const addCharButton = Array.from(document.querySelectorAll('button')).find(btn => btn.textContent.trim() === 'Add Character');
        if (!addCharButton) {
            console.error("NAI助手: 找不到 'Add Character' 按钮。");
            return false;
        }
        addCharButton.click();
        const popperMenu = await waitForElement('div[data-popper-placement]');
        if (!popperMenu) {
            console.error("NAI助手: 未找到弹出的角色类型菜单。");
            return false;
        }
        const buttonsInMenu = popperMenu.querySelectorAll('button');
        let otherButton = null;
         if (buttonsInMenu.length > 0) {
             otherButton = Array.from(buttonsInMenu).find(b => b.textContent.trim().toLowerCase().includes('other'));
     }

        if (!otherButton) {
            try {
                const xpath = '/html/body/div[12]/button[3]';
                const result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
                otherButton = result.singleNodeValue;
                // if (otherButton) console.log("NAI助手: [方法2] XPath 成功找到一个按钮。");
            } catch (e) {
                console.error("NAI助手: [方法2] XPath 查询失败:", e);
                otherButton = null;
            }
        }

        if (!otherButton) {
            // buttonsInMenu.forEach((btn, i) => console.log(` - 菜单中找到的按钮[${i}] 内容: "${btn.textContent.trim()}"`));
            return false;
        }

        otherButton.click();
        await sleep(300);
        return true;
    }

    async function handleCharacterPrompt() {
        const charTemplate = document.getElementById('character-prompt-input').value.trim();
        const addCharButton = Array.from(document.querySelectorAll('button')).find(btn => btn.textContent.trim() === 'Add Character');
        if (!addCharButton){
            return true;
        }

        const characterInputs = document.querySelectorAll('[class*="character-prompt-input"] [contenteditable="true"] p');
        if (!charTemplate) {
            if (characterInputs.length > 0) {
                characterInputs.forEach(p => {
                    if (p.textContent) {
                        p.textContent = '';
                        p.dispatchEvent(new Event('input', { bubbles: true }));
                    }
                });
            }
            return true;
        }
        const charPrompts = charTemplate.split(/\r?\n/).filter(line => line.trim() !== '');
        if (charPrompts.length === 0) return true;
        const randomCharPrompt = charPrompts[Math.floor(Math.random() * charPrompts.length)];
        let promptElement = document.querySelector('[class*="character-prompt-input-1"] [contenteditable="true"] p');
        if (!promptElement) {
            if(!(await addCharacterSlot())) return false;
            promptElement = await waitForElement('[class*="character-prompt-input-1"] [contenteditable="true"] p');
            if (!promptElement) return false;
        }
        if (promptElement.textContent !== randomCharPrompt) {
            promptElement.textContent = randomCharPrompt;
            promptElement.dispatchEvent(new Event('input', { bubbles: true }));
        }
        return true;
    }

    function findPromptElement() {
        const selectors = [
            '[class*="prompt-input-box-prompt"] [contenteditable="true"] p',
            '[class*="prompt-input-box-base-prompt"] [contenteditable="true"] p'
        ];
        return document.querySelector(selectors.join(', '));
    }

    function fillPrompt(promptText) {
        const promptElement = findPromptElement();
        if (!promptElement) {
            updateStatus('错误: 找不到主提示词输入框', 'error');
            return false;
        }
        const template = document.getElementById('preset-prompt-input').value;
        promptElement.textContent = template.includes('[替换词]') ? template.replace('[替换词]', promptText) : promptText;
        promptElement.dispatchEvent(new Event('input', { bubbles: true }));
        return true;
    }

    function findGenerateButton() {
        return Array.from(document.querySelectorAll('button')).find(btn => btn.textContent.trim().toLowerCase().startsWith('generate'));
    }

    async function waitForGeneration() {
        return new Promise((resolve, reject) => {
            const timeout = 30000;
            const interval = 500;
            let elapsed = 0;

            const id = setInterval(() => {
                elapsed += interval;
                const generateBtn = findGenerateButton();

                if (generateBtn && !generateBtn.disabled) {
                    clearInterval(id);
                    resolve();
                    return;
                }

                if (elapsed >= timeout) {
                    clearInterval(id);
                    reject(new Error('生成超时 (30秒)'));
                }
            }, interval);
        });
    }

    async function processSinglePrompt(promptText, statusMsg, imagesPerPrompt, delay) {
        for (let i = 0; i < imagesPerPrompt; i++) {
            if (!isAutoRunning) return false;

            if (!await handleCharacterPrompt()) {
                updateStatus('错误: 处理角色模板失败,任务中止', 'error');
                stopAutomation();
                return false;
            }
            if (!fillPrompt(promptText)) {
                stopAutomation();
                return false;
            }

            await sleep(200);

            updateStatus(`${statusMsg}, 生成第 ${i + 1}/${imagesPerPrompt} 张...`);
            const generateButton = findGenerateButton();
            if (!generateButton || generateButton.disabled) {
                updateStatus('错误: 生成按钮不可用,任务暂停', 'error');
                return false;
            }

            generateButton.click();

            try {
                await waitForGeneration();
            } catch (error) {
                updateStatus(`错误: ${error.message}`, 'error');
                stopAutomation();
                return false;
            }

            if (!isAutoRunning) return false;

            updateStatus(`${statusMsg}, 第 ${i + 1}/${imagesPerPrompt} 张图生成完毕,等待 ${delay / 1000} 秒...`);
            await sleep(delay);
        }

        promptsProcessedThisRun++;
        document.getElementById('run-counter-display').textContent = `本次运行: ${promptsProcessedThisRun} 条`;
        return true;
    }

    function loadSettings() {
        const isRandom = GM_getValue(STORAGE_KEYS.RANDOM_MODE, false);
        document.getElementById('random-mode-checkbox').checked = isRandom;
        toggleRandomModeUI(isRandom);
        const savedTemplate = GM_getValue(STORAGE_KEYS.PROMPT_TEMPLATE, 'masterpiece, best quality, [替换词]');
        document.getElementById('preset-prompt-input').value = savedTemplate;
        const savedCharTemplate = GM_getValue(STORAGE_KEYS.CHARACTER_PROMPT_TEMPLATE, '');
        document.getElementById('character-prompt-input').value = savedCharTemplate;
        const savedFileName = GM_getValue(STORAGE_KEYS.LAST_FILE_NAME);
        const savedFileContent = GM_getValue(STORAGE_KEYS.LAST_FILE_CONTENT);
        if (savedFileName && savedFileContent) {
            promptsArray = savedFileContent.split(/\r?\n/).filter(line => line.trim() !== '');
            if (promptsArray.length > 0) {
                document.getElementById('start-index-input').max = promptsArray.length;
                updateFileInfoDisplay(savedFileName, promptsArray.length);
                updateStatus(`已自动加载记忆的文件`, 'info');
            }
        }
    }
    function addEventListeners() {
        document.getElementById('prompt-file-input').addEventListener('change', handleFileSelect);
        document.getElementById('main-control-btn').addEventListener('click', mainButtonHandler);
        if(titleElement) {
            titleElement.addEventListener('click', (e) => e.currentTarget.parentElement.classList.toggle('collapsed'));
        }
        const mainInput = document.getElementById('start-index-input');
        mainInput.addEventListener('change', () => {
            const isRandom = document.getElementById('random-mode-checkbox').checked;
            const key = isRandom ? STORAGE_KEYS.RANDOM_COUNT : STORAGE_KEYS.START_INDEX;
            GM_setValue(key, mainInput.value);
        });
        document.getElementById('preset-prompt-input').addEventListener('input', (e) => GM_setValue(STORAGE_KEYS.PROMPT_TEMPLATE, e.target.value));
        document.getElementById('character-prompt-input').addEventListener('input', (e) => GM_setValue(STORAGE_KEYS.CHARACTER_PROMPT_TEMPLATE, e.target.value));
        const randomCheckbox = document.getElementById('random-mode-checkbox');
        randomCheckbox.addEventListener('change', (e) => {
            toggleRandomModeUI(e.target.checked);
            GM_setValue(STORAGE_KEYS.RANDOM_MODE, e.target.checked);
        });
    }
    function toggleRandomModeUI(isRandom) {
        const input = document.getElementById('start-index-input');
        const label = document.getElementById('start-index-label');
        if (isRandom) {
            label.textContent = "随机次数:";
            input.value = GM_getValue(STORAGE_KEYS.RANDOM_COUNT, '1');
            input.max = 999;
        } else {
            label.textContent = "文件行数:";
            input.value = GM_getValue(STORAGE_KEYS.START_INDEX, '1');
            input.max = promptsArray.length > 0 ? promptsArray.length : 9999;
        }
    }
    function handleFileSelect(event) {
        const file = event.target.files[0];
        if (!file) return;
        const reader = new FileReader();
        reader.onload = (e) => {
            const content = e.target.result;
            promptsArray = content.split(/\r?\n/).filter(line => line.trim() !== '');
            if (promptsArray.length > 0) {
                const total = promptsArray.length;
                if (!document.getElementById('random-mode-checkbox').checked) {
                    document.getElementById('start-index-input').max = total;
                }
                updateFileInfoDisplay(file.name, total);
                updateStatus(`新文件加载成功,准备就绪`, 'info');
                GM_setValue(STORAGE_KEYS.LAST_FILE_NAME, file.name);
                GM_setValue(STORAGE_KEYS.LAST_FILE_CONTENT, content);
            } else {
                updateFileInfoDisplay('文件为空或格式错误', 0, true);
                updateStatus('请选择一个有效的 .txt 文件', 'error');
            }
            event.target.value = null;
        };
        reader.readAsText(file);
    }
    function updateFileInfoDisplay(fileName, count, isError = false) {
        const display = document.getElementById('file-info-display');
        display.textContent = isError ? fileName : `当前文件: ${fileName} (${count}条)`;
        display.style.color = isError ? '#e06c75' : '#98c379';
    }
    function mainButtonHandler() {
        isAutoRunning ? stopAutomation() : startAutomation();
    }
    function startAutomation() {
        const fileContent = GM_getValue(STORAGE_KEYS.LAST_FILE_CONTENT, '');
        promptsArray = fileContent.split(/\r?\n/).filter(line => line.trim() !== '');
        if (promptsArray.length === 0) {
            alert('请先选择或自动加载一个有效的提示词文件!');
            return;
        }
        isAutoRunning = true;
        promptsProcessedThisRun = 0;
        document.getElementById('run-counter-display').textContent = '本次运行: 0 条';
        document.getElementById('main-control-btn').textContent = '⏹️ 停止执行';
        document.getElementById('main-control-btn').className = 'running';

        if (titleElement) {
            titleElement.textContent = '提示词批量助手 [队列运行中]';
        }
        const imagesPerPrompt = parseInt(document.getElementById('images-per-prompt-input').value, 10);
        const delay = parseFloat(document.getElementById('delay-input').value) * 1000;
        if (document.getElementById('random-mode-checkbox').checked) {
            const randomCount = parseInt(document.getElementById('start-index-input').value, 10);
            runRandomAutomation(randomCount, imagesPerPrompt, delay);
        } else {
            currentPromptIndex = Math.max(0, parseInt(document.getElementById('start-index-input').value, 10) - 1);
            runSequentialAutomation(imagesPerPrompt, delay);
        }
    }
    function stopAutomation() {
        isAutoRunning = false;
        document.getElementById('main-control-btn').textContent = '▶️ 开始自动执行';
        document.getElementById('main-control-btn').className = '';
        if (titleElement) {
            titleElement.textContent = '提示词批量助手';
        }
        updateStatus('任务已手动停止', 'info');
    }
    async function runSequentialAutomation(imagesPerPrompt, delay) {
        while (currentPromptIndex < promptsArray.length && isAutoRunning) {
            const promptNum = currentPromptIndex + 1;
            const statusMsg = `处理中: 第 ${promptNum}/${promptsArray.length} 条`;
            if (!await processSinglePrompt(promptsArray[currentPromptIndex], statusMsg, imagesPerPrompt, delay)) {
                return;
            }
            if(isAutoRunning) {
                currentPromptIndex++;
                document.getElementById('start-index-input').value = currentPromptIndex + 1;
                GM_setValue(STORAGE_KEYS.START_INDEX, (currentPromptIndex + 1).toString());
            }
        }
        if (isAutoRunning) { updateStatus(`顺序任务处理完毕!`, 'done'); stopAutomation(); }
    }
    async function runRandomAutomation(randomCount, imagesPerPrompt, delay) {
        for (let i = 0; i < randomCount; i++) {
            if (!isAutoRunning) break;
            const randomIndex = Math.floor(Math.random() * promptsArray.length);
            const statusMsg = `随机模式: 第 ${i + 1}/${randomCount} 次 (抽中第 ${randomIndex + 1} 条)`;
            if (!await processSinglePrompt(promptsArray[randomIndex], statusMsg, imagesPerPrompt, delay)) {
                return;
            }
        }
        if (isAutoRunning) { updateStatus(`随机任务 ${randomCount} 次已全部完成!`, 'done'); stopAutomation(); }
    }
    function updateStatus(message, type = 'info') {
        const el = document.getElementById('status-display');
        if (el) { el.textContent = message; el.className = type; }
    }
    GM_addStyle(`
    #nai-prompt-helper-panel { position: fixed; top: 70px; right: 15px; z-index: 9999; width: 320px; background: #1c1f26; color: #c8ccd4; border-radius: 12px; border: 1px solid #3a414f; box-shadow: 0 8px 25px rgba(0,0,0,0.3); font-family: "Segoe UI", sans-serif; transition: all 0.3s ease-in-out; }
    #nai-prompt-helper-panel.collapsed { top: 20px; }
    #nai-prompt-helper-panel h3, #nai-prompt-helper-panel h4 { margin: 0; padding: 0; color: #82aaff; font-size: 16px; font-weight: 600; }
    #nai-prompt-helper-panel h3 { padding: 12px 16px; border-bottom: 1px solid #3a414f; cursor: pointer; user-select: none; display: flex; justify-content: space-between; align-items: center; }
    #nai-prompt-helper-panel.collapsed h3 { border-bottom: none; }
    #nai-prompt-helper-panel h3::after { content: '−'; }
    #nai-prompt-helper-panel.collapsed h3::after { content: '+'; }
    #nai-prompt-helper-panel .content-wrapper { padding: 16px; display: flex; flex-direction: column; gap: 14px; transition: all 0.3s ease-in-out, visibility 0s 0.3s; max-height: 80vh; overflow-y: auto; }
    #nai-prompt-helper-panel.collapsed .content-wrapper { max-height: 0; padding-top: 0; padding-bottom: 0; opacity: 0; visibility: hidden; transition-delay: 0s; }
    #nai-prompt-helper-panel hr { border: none; border-top: 1px solid #3a414f; margin: 4px 0; }
    #nai-prompt-helper-panel .control-group { display: flex; flex-direction: column; gap: 8px; }
    #nai-prompt-helper-panel label { font-size: 14px; color: #a6accd; }
    #nai-prompt-helper-panel .custom-file-button { background-color: #4f586a; border-radius: 6px; text-align: center; padding: 10px; cursor: pointer; transition: background-color 0.2s; }
    #nai-prompt-helper-panel .custom-file-button:hover { background-color: #5a657a; }
    #nai-prompt-helper-panel #file-info-display { font-size: 13px; text-align: center; padding: 6px 10px; background-color: #282c34; border-radius: 6px; border: 1px dashed #4f586a; word-break: break-all; margin-top: -6px; transition: color 0.3s; }
    #nai-prompt-helper-panel .heading-with-control { display: flex; justify-content: space-between; align-items: center; }
    #nai-prompt-helper-panel .toggle-switch-wrapper { display: flex; align-items: center; gap: 8px; }
    #nai-prompt-helper-panel .toggle-switch-label { font-size: 14px; color: #a6accd; }
    #nai-prompt-helper-panel .toggle-switch { position: relative; display: inline-block; width: 40px; height: 22px; }
    #nai-prompt-helper-panel .toggle-switch input { opacity: 0; width: 0; height: 0; }
    #nai-prompt-helper-panel .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #4f586a; border-radius: 22px; transition: .4s; }
    #nai-prompt-helper-panel .slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 3px; bottom: 3px; background-color: white; border-radius: 50%; transition: .4s; }
    #nai-prompt-helper-panel input:checked + .slider { background-color: #82aaff; }
    #nai-prompt-helper-panel input:checked + .slider:before { transform: translateX(18px); }
    #nai-prompt-helper-panel input, #nai-prompt-helper-panel textarea, #nai-prompt-helper-panel button { width: 100%; padding: 10px; box-sizing: border-box; font-size: 14px; border-radius: 6px; border: 1px solid #4f586a; background-color: #282c34; color: #c8ccd4; }
    #nai-prompt-helper-panel textarea { resize: vertical; }
    #nai-prompt-helper-panel #preset-prompt-input { min-height: 160px; }
    #nai-prompt-helper-panel #character-prompt-input { min-height: 100px; }
    #nai-prompt-helper-panel .settings { flex-direction: row; justify-content: space-between; gap: 10px; }
    #nai-prompt-helper-panel .settings > div { flex: 1; text-align: center; }
    #nai-prompt-helper-panel .settings label { font-size: 13px; margin-bottom: 4px; display: block; }
    #nai-prompt-helper-panel .settings input { width: 100%; padding: 8px; text-align: center; }
    #nai-prompt-helper-panel #main-control-btn { font-weight: bold; background-color: #82aaff; border-color: #82aaff; cursor: pointer; transition: all 0.2s; }
    #nai-prompt-helper-panel #main-control-btn:hover { background-color: #90b8ff; }
    #nai-prompt-helper-panel #main-control-btn.running { background-color: #e06c75; border-color: #e06c75; }
    #nai-prompt-helper-panel #main-control-btn.running:hover { background-color: #f07f88; }
    #nai-prompt-helper-panel #status-display { padding: 10px; border-radius: 6px; font-size: 13px; text-align: center; word-wrap: break-word; line-height: 1.4; background-color: #282c34; }
    #nai-prompt-helper-panel #run-counter-display { font-size: 13px; text-align: center; color: #98c379; }
    #nai-prompt-helper-panel #status-display.error { background-color: #e06c75; color: #1c1f26; font-weight: bold; }
    #nai-prompt-helper-panel #status-display.done { background-color: #98c379; color: #1c1f26; font-weight: bold; }
    `);

    function waitForNAI() {
        let attempts = 0;
        const maxAttempts = 30;
        console.log("NAI助手: 正在等待 NovelAI 界面加载 (每2秒检查一次)...");
        const checkInterval = setInterval(() => {
            attempts++;
            const generateBtn = findGenerateButton();
            if (attempts > 1) {
                // console.log(`  - 尝试次数: ${attempts}/${maxAttempts}... | 找到“Generate”按钮? ${!!generateBtn}`);
            }
            if (generateBtn) {
                clearInterval(checkInterval);
                console.log("NAI助手: 成功检测到界面,脚本已加载。");
                createUIPanel();
                removeUnwantedElement();
            } else if (attempts >= maxAttempts) {
                clearInterval(checkInterval);
                console.error(`NAI助手: 等待界面超时 (${maxAttempts * 2}秒)。可能是网站更新导致脚本无法启动。`);
                alert("NAI 提示词批量助手:等待界面超时,脚本可能需要更新。");
            }
        }, 2000);
    }

    waitForNAI();
})();