自動點擊生成 (Grok Imagine) v1.5

自動生成影片,生成結果永遠顯示,進度百分比顯示,低於閾值播放音效,高於閾值自動重試,自動模式控制,生成成功播放 Cmaj7 arpeggio。新增登出按鈕,會檢查側邊欄。

目前為 2025-11-01 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         自動點擊生成 (Grok Imagine) v1.5
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  自動生成影片,生成結果永遠顯示,進度百分比顯示,低於閾值播放音效,高於閾值自動重試,自動模式控制,生成成功播放 Cmaj7 arpeggio。新增登出按鈕,會檢查側邊欄。
// @match        https://grok.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const targetClassString = 'text-xs font-semibold w-[4ch] mb-[1px]';
    const selector = `div[class="${targetClassString}"]`;

    let lastValue = null;
    let wasPresent = false;
    let autoMode = true;
    let threshold = 30;
    let playBeepOnLow = true;
    let playBeepOnLimit = true;
    let beepVolume = 0.005;
    let limitAlertShown = false;

    let zeroCount = 0;
    const zeroThreshold = 20;
    const checkInterval = 500;
    const zeroMaxCount = zeroThreshold * 1000 / checkInterval;

    function getTimeString() {
        const now = new Date();
        const pad = n => n.toString().padStart(2, '0');
        return `[${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}]`;
    }

    function parseNumber(text) {
        if (!text) return null;
        const match = text.match(/-?\d+(\.\d+)?/);
        return match ? parseFloat(match[0]) : null;
    }

    function beepTriple(frequency = 880, duration = 0.1, volume = beepVolume) {
        const ctx = new (window.AudioContext || window.webkitAudioContext)();
        const playBeep = () => {
            const oscillator = ctx.createOscillator();
            const gain = ctx.createGain();
            oscillator.connect(gain);
            gain.connect(ctx.destination);
            oscillator.type = 'square';
            oscillator.frequency.value = frequency;
            gain.gain.value = volume;
            oscillator.start();
            oscillator.stop(ctx.currentTime + duration);
        };
        playBeep();
        setTimeout(playBeep, duration * 1000 + 100);
        setTimeout(playBeep, 2 * (duration * 1000 + 100));
    }

    function playCmaj7Arpeggio(volume = beepVolume, duration = 0.25) {
        const ctx = new (window.AudioContext || window.webkitAudioContext)();
        const d = 48;
        const notes = [16.35*d, 20.6*d, 24.5*d, 30.87*d]; // C, E, G, B
        notes.forEach((freq, i) => {
            const osc = ctx.createOscillator();
            const gain = ctx.createGain();
            osc.connect(gain);
            gain.connect(ctx.destination);
            osc.type = 'triangle';
            osc.frequency.value = freq;
            gain.gain.setValueAtTime(volume, ctx.currentTime + i*duration); // 音量隨滑桿設定
            osc.start(ctx.currentTime + i*duration);
            osc.stop(ctx.currentTime + (i+1)*duration);
        });
    }

    function createControlPanel() {
        const panel = document.createElement('div');
        panel.style.position = 'fixed';
        panel.style.top = '80px';
        panel.style.right = '10px';
        panel.style.zIndex = '99999';
        panel.style.background = 'rgba(30,30,30,0.9)';
        panel.style.color = '#fff';
        panel.style.padding = '10px 15px';
        panel.style.borderRadius = '10px';
        panel.style.fontSize = '14px';
        panel.style.fontFamily = 'monospace';
        panel.style.boxShadow = '0 4px 12px rgba(0,0,0,0.6)';
        panel.style.backdropFilter = 'blur(5px)';
        panel.style.width = '320px';
        panel.style.maxHeight = '480px';
        panel.style.display = 'flex';
        panel.style.flexDirection = 'column';
        panel.style.pointerEvents = 'auto';

        const controlsHTML = `
            <div style="margin-bottom:6px; font-weight:bold;">Grok 檢查控制</div>
            <label style="display:flex; align-items:center; gap:6px; margin-bottom:4px;">
                <input type="checkbox" id="autoModeToggle" checked />
                自動模式
            </label>
            <label style="display:flex; align-items:center; gap:6px; margin-bottom:4px;">
                閥值(%):
                <input type="number" id="thresholdInput" value="${threshold}" min="0" max="100" step="1"
                       style="width:60px; padding:2px; border-radius:4px; border:none; text-align:center;">
            </label>
            <label style="display:flex; align-items:center; gap:6px; margin-bottom:4px;">
                <input type="checkbox" id="beepToggle" checked />
                低於閾值播放音效
            </label>
            <label style="display:flex; align-items:center; gap:6px; margin-bottom:4px;">
                <input type="checkbox" id="limitToggle" checked />
                額度用完提醒音效
            </label>
            <label style="display:flex; align-items:center; gap:6px; margin-bottom:6px;">
                音量:
                <input type="range" id="volumeSlider" min="0" max="0.05" step="0.005" value="${beepVolume}" style="flex:1;">
                <span id="volumeDisplay">${beepVolume.toFixed(3)}</span>
            </label>
        `;

        const consoleBox = document.createElement('div');
        consoleBox.id = 'grok-console';
        consoleBox.style.flex = '1';
        consoleBox.style.background = 'rgba(0,0,0,0.3)';
        consoleBox.style.padding = '6px';
        consoleBox.style.borderRadius = '6px';
        consoleBox.style.overflowY = 'auto';
        consoleBox.style.fontSize = '12px';
        consoleBox.style.lineHeight = '1.4';
        consoleBox.style.whiteSpace = 'pre-wrap';

        const clearBtn = document.createElement('button');
        clearBtn.textContent = '🧹 清空紀錄';
        clearBtn.style.marginTop = '6px';
        clearBtn.style.background = 'rgba(255,255,255,0.1)';
        clearBtn.style.border = 'none';
        clearBtn.style.color = '#ccc';
        clearBtn.style.padding = '4px 8px';
        clearBtn.style.borderRadius = '4px';
        clearBtn.style.cursor = 'pointer';
        clearBtn.style.fontSize = '12px';
        clearBtn.addEventListener('mouseenter', () => clearBtn.style.color = '#fff');
        clearBtn.addEventListener('mouseleave', () => clearBtn.style.color = '#ccc');
        clearBtn.addEventListener('click', () => (consoleBox.innerHTML = ''));

        panel.innerHTML = controlsHTML;
        panel.appendChild(consoleBox);
        panel.appendChild(clearBtn);

        // 新增登出按鈕
        const logoutBtn = document.createElement('button');
        logoutBtn.textContent = '🚪 登出';
        logoutBtn.style.marginTop = '6px';
        logoutBtn.style.background = 'rgba(255,50,50,0.8)';
        logoutBtn.style.border = 'none';
        logoutBtn.style.color = '#fff';
        logoutBtn.style.padding = '6px 10px';
        logoutBtn.style.borderRadius = '4px';
        logoutBtn.style.cursor = 'pointer';
        logoutBtn.style.fontSize = '14px';
        logoutBtn.addEventListener('mouseenter', () => logoutBtn.style.opacity = '0.8');
        logoutBtn.addEventListener('mouseleave', () => logoutBtn.style.opacity = '1');
        logoutBtn.addEventListener('click', tryLogout);
        panel.appendChild(logoutBtn);

        document.body.appendChild(panel);

        // 控制事件綁定
        const autoModeToggle = panel.querySelector('#autoModeToggle');
        const thresholdInput = panel.querySelector('#thresholdInput');
        const beepToggle = panel.querySelector('#beepToggle');
        const limitToggle = panel.querySelector('#limitToggle');
        const volumeSlider = panel.querySelector('#volumeSlider');
        const volumeDisplay = panel.querySelector('#volumeDisplay');

        autoModeToggle.addEventListener('change', () => {
            autoMode = autoModeToggle.checked;
            console.log(`${getTimeString()} 自動模式: ${autoMode ? '啟用' : '停用'}`);
        });
        thresholdInput.addEventListener('change', () => {
            threshold = parseFloat(thresholdInput.value);
            console.log(`${getTimeString()} 閥值更新為 ${threshold}`);
        });
        beepToggle.addEventListener('change', () => {
            playBeepOnLow = beepToggle.checked;
            console.log(`${getTimeString()} 低於閾值播放音效: ${playBeepOnLow ? '啟用' : '停用'}`);
        });
        limitToggle.addEventListener('change', () => {
            playBeepOnLimit = limitToggle.checked;
            console.log(`${getTimeString()} 額度用完提醒音效: ${playBeepOnLimit ? '啟用' : '停用'}`);
        });
        volumeSlider.addEventListener('input', () => {
            beepVolume = parseFloat(volumeSlider.value);
            volumeDisplay.textContent = beepVolume.toFixed(3);
        });

        return consoleBox;
    }

    function hookConsole(consoleBox) {
        const originalLog = console.log;
        console.log = (...args) => {
            originalLog.apply(console, args);
            const msg = args.join(' ');
            const entry = document.createElement('div');
            if (msg.includes('成功')) entry.style.color = '#6eff9f';
            else if (msg.includes('失敗')) entry.style.color = '#ff7b7b';
            else if (msg.includes('閥值') || msg.includes('模式') || msg.includes('額度') || msg.includes('分頁')) entry.style.color = '#ffd966';
            else entry.style.color = '#ccc';
            entry.textContent = msg;
            consoleBox.appendChild(entry);
            consoleBox.scrollTop = consoleBox.scrollHeight;
        };
    }

    function check() {
        // 額度用完只提示一次
        const upgradeElem = document.querySelector('span.text-secondary.font-medium');
        if (upgradeElem && upgradeElem.textContent.includes('Upgrade to unlock more')) {
            if (!limitAlertShown) {
                limitAlertShown = true;
                console.log(`${getTimeString()} 額度已用完!`);
                if (playBeepOnLimit) beepTriple(440, 0.15);
            }
        } else {
            limitAlertShown = false;
        }

        const elem = document.querySelector(selector);
        if (elem) {
            wasPresent = true;
            const val = parseNumber(elem.textContent.trim());
            if (!isNaN(val)) lastValue = val;

            if (val === 0) {
                zeroCount++;
                if (zeroCount >= zeroMaxCount && autoMode) {
                    const button = document.querySelector('button[aria-label="製作影片"]');
                    if (button) {
                        button.click();
                        console.log(`${getTimeString()} 長時間為0,已再次點擊生成按鈕`);
                    } else {
                        console.log(`${getTimeString()} 找不到生成按鈕,無法重點擊`);
                    }
                    zeroCount = 0;
                }
            } else zeroCount = 0;

        } else if (wasPresent) {
            wasPresent = false;
            const container = document.querySelector('div.relative.mx-auto.rounded-2xl.overflow-hidden');
            const progress = lastValue !== null ? `${lastValue}%` : "未知";
            let success = false;

            if (container) {
                const grid = container.querySelector('div.grid');
                if (grid && grid.querySelector('video#sd-video')) {
                    console.log(`${getTimeString()} 生成成功 (進度: ${progress})`);
                    success = true;
                    playCmaj7Arpeggio(beepVolume); // 套用滑桿音量
                }
            }

            if (!success) {
                console.log(`${getTimeString()} 生成失敗 (進度: ${progress})`);
                if (lastValue !== null && lastValue < threshold && playBeepOnLow) {
                    beepTriple();
                }
                if (autoMode && lastValue !== null && lastValue >= threshold) {
                    const button = document.querySelector('button[aria-label="製作影片"]');
                    if (button) {
                        button.click();
                        console.log(`${getTimeString()} 自動模式: 已重新點擊生成按鈕`);
                    } else {
                        console.log(`${getTimeString()} 找不到生成按鈕,無法自動重新生成`);
                    }
                }
            }
            lastValue = null;
        }
    }

    function tryLogout() {
        const attemptLogout = () => {
            const sidebar = document.querySelector('div[data-variant="sidebar"][data-side="left"]');
            if (sidebar) {
                const state = sidebar.getAttribute('data-state');
                if (state === 'expanded') {
                    const triggerBtn = document.querySelector('button[data-sidebar="trigger"]');
                    if (triggerBtn) {
                        triggerBtn.click();
                        console.log('Sidebar is OPEN → 關閉側邊欄');
                        setTimeout(attemptLogout, 300);
                        return;
                    }
                }
            }

            const trigger = document.querySelector('button[id^="radix-"][aria-haspopup="menu"]');
            if (trigger) {
                ['pointerdown', 'mousedown', 'mouseup', 'pointerup', 'click'].forEach(type => {
                    trigger.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true, view: window }));
                });
                setTimeout(() => {
                    const logoutBtn = [...document.querySelectorAll('div[role="menuitem"]')]
                        .find(el => el.textContent.includes('登出'));
                    if (logoutBtn) {
                        logoutBtn.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
                        console.log('✅ 已嘗試點擊登出');
                    } else {
                        console.log('⚠️ 找不到登出按鈕,1秒後重試');
                        setTimeout(attemptLogout, 1000);
                    }
                }, 500);
            } else {
                console.log('❌ 找不到清單觸發按鈕,1秒後重試');
                setTimeout(attemptLogout, 1000);
            }
        };
        attemptLogout();
    }

    const consoleBox = createControlPanel();
    hookConsole(consoleBox);

    function startBackgroundChecker() {
        const workerCode = `
            let interval = ${checkInterval};
            setInterval(() => postMessage('tick'), interval);
        `;
        const blob = new Blob([workerCode], { type: 'application/javascript' });
        const worker = new Worker(URL.createObjectURL(blob));
        worker.onmessage = () => check();
    }
    startBackgroundChecker();

    document.addEventListener('visibilitychange', () => {
        if (!document.hidden) check();
    });
})();