斗鱼全民星推荐自动领取

自动打开、领取并切换直播间处理全民星推荐活动红包

// ==UserScript==
// @name         斗鱼全民星推荐自动领取
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  自动打开、领取并切换直播间处理全民星推荐活动红包
// @author       ysl
// @match        *://www.douyu.com/6657*
// @match        *://www.douyu.com/*
// @match        *://www.douyu.com/topic/*?rid=[0-9]*
// @grant        GM_openInTab
// @grant        GM_closeTab
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        GM_log
// @grant        GM_xmlhttpRequest
// @connect      list-www.douyu.com
// @run-at       document-idle
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- 常量与配置 ---
    const CONTROL_ROOM_ID = "6657"; // 控制页面房间号
    const SCRIPT_PREFIX = "[全民星推荐助手]";
    const CHECK_INTERVAL = 5000; // 主循环检查间隔 (ms)
    const POPUP_WAIT_TIMEOUT = 15000; // 等待中间红包弹窗超时 (ms)
    const POPUP_CHECK_INTERVAL = 1000; // 检查中间红包弹窗间隔 (ms)
    const CLOSE_POPUP_DELAY = 4000; // 打开红包后等待多久关闭弹窗 (ms)
    const PANEL_WAIT_TIMEOUT = 10000; // 等待活动面板/容器出现超时 (ms) - 稍微增加
    const ELEMENT_WAIT_TIMEOUT = 30000; // 等待关键页面元素加载的超时时间 (ms) - 新增
    const LOAD_DELAY_THRESHOLD = 3; // 连续多少次找不到红包区域算作领完
    const MIN_DELAY = 1000; // 随机延迟最小值 (ms)
    const MAX_DELAY = 2500; // 随机延迟最大值 (ms) - 稍微增加
    const OPEN_TAB_DELAY = 1000; // 打开每个新标签页之间的延迟 (ms) - 稍微增加
    const CLOSE_TAB_DELAY = 1500; // 尝试关闭标签页前的延迟 (ms) - 稍微增加
    const INITIAL_SCRIPT_DELAY = 3000; // 脚本整体初始化的延迟 (ms) - 稍微增加
    const DRAGGABLE_BUTTON_ID = 'douyu-qmx-starter-button'; // 启动按钮 ID
    const BUTTON_POS_STORAGE_KEY = 'douyu_qmx_button_position'; // 存储按钮位置的 Key
    const API_URL = "https://www.douyu.com/japi/livebiznc/web/anchorstardiscover/redbag/square/list";
    const MAX_TAB_LIFETIME_MS = 10 * 60 * 1000; // 设置单个网页最大生存时间为 20 分钟 (可调整)

    // --- 选择器 (请根据实际情况调整,特别是 redEnvelopeContainer) ---
    const SELECTORS = {
        // 右下角红包相关
        // 这个选择器路径较长,如果失效,需要用开发者工具检查更新
        redEnvelopeContainer: "#layout-Player-aside div.LiveNewAnchorSupportT-enter", // 尝试简化选择器,使其更通用
        // 备用选择器 (如果上面那个不行,可以试试这个更具体的)
        // redEnvelopeContainer: "#layout-Player-aside > div.layout-Player-asideMainTop > div.layout-Player-effect > div.LiveNewAnchorSupportT-enter",
        countdownTimer: "span.LiveNewAnchorSupportT-enter--bottom", // 含倒计时或"抢红包"文字

        // 中间弹窗相关
        popupModal: "body > div.LiveNewAnchorSupportT-pop",
        openButton: "div.LiveNewAnchorSupportT-singleBag--btnOpen",
        closeButton: "div.LiveNewAnchorSupportT-pop--close",

        // 用于初始化时等待的关键元素 (选择一个页面加载后肯定会存在的元素)
        criticalElement: "#js-player-video", // 播放器视频元素,通常加载较快且稳定
        // criticalElement: "div.PlayerToolbar-Wealth", // 或者右下角礼物栏的部分
    };

    // --- 状态变量 ---
    let mainIntervalId = null;
    let isWaitingForPopup = false;
    let isSwitchingRoom = false;
    let notFoundCounter = 0;
    let isDragging = false; // 用于拖拽按钮
    let dragOffsetX = 0;
    let dragOffsetY = 0;
    let openedRoomIds = new Set();
    let tabStartTime = 0;  // 记录工作标签页启动时间戳

    // --- 辅助函数 ---
    function log(message) {
        GM_log(`${SCRIPT_PREFIX} ${message}`);
        console.log(`${SCRIPT_PREFIX} ${message}`); // 同时在 Tampermonkey 日志和浏览器控制台输出
    }

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

    function getRandomDelay(min = MIN_DELAY, max = MAX_DELAY) {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }

    // 封装点击操作,加入随机延迟和日志
    async function safeClick(element, description, delayBefore = true, delayAfter = true) {
        if (!element) {
            log(`[点击失败] 无法找到元素: ${description}`);
            return false;
        }
        try {
            // 检查元素是否可见可交互 (基本检查)
            const style = window.getComputedStyle(element);
            if (style.display === 'none' || style.visibility === 'hidden' || element.offsetParent === null || element.disabled) {
                log(`[点击失败] 元素存在但不可见或不可交互: ${description}`);
                return false;
            }

            if (delayBefore) {
                const waitBefore = getRandomDelay(MIN_DELAY / 2, MAX_DELAY / 2); // 点击前的延迟可以短一些
                log(`准备点击 ${description},先等待 ${waitBefore}ms`);
                await sleep(waitBefore);
            }

            log(`尝试点击: ${description}`);
            element.click();

            if (delayAfter) {
                const waitAfter = getRandomDelay();
                log(`点击 ${description} 后等待 ${waitAfter}ms`);
                await sleep(waitAfter);
            }
            return true;
        } catch (error) {
            log(`[点击异常] ${description} 时发生错误: ${error.message}`);
            console.error(`Click error on ${description}:`, error);
            return false;
        }
    }

    // 查找元素并等待其出现 (检查可见性)
    async function findElement(selector, timeout = PANEL_WAIT_TIMEOUT, parent = document) {
        log(`开始查找元素: ${selector} (超时 ${timeout}ms)`);
        const startTime = Date.now();
        while (Date.now() - startTime < timeout) {
            const element = parent.querySelector(selector);
            if (element) {
                const style = window.getComputedStyle(element);
                // 检查 display 和 visibility,以及尺寸
                if (style.display !== 'none' && style.visibility !== 'hidden' && element.offsetWidth > 0 && element.offsetHeight > 0) {
                    log(`找到可见元素: ${selector}`);
                    return element;
                }
            }
            await sleep(300); // 短暂等待后重试
        }
        log(`查找元素超时: ${selector}`);
        return null;
    }

    // --- API 调用 ---
    function getRoomsFromApi(count) {
        return new Promise((resolve, reject) => {
            log(`开始调用 API 获取房间列表: ${API_URL}`);
            GM_xmlhttpRequest({
                method: "GET",
                url: API_URL,
                headers: {
                    'Referer': 'https://www.douyu.com/', // 添加 Referer
                    'User-Agent': navigator.userAgent
                },
                responseType: "json",
                timeout: 10000, // 10秒超时
                onload: function(response) {
                    log(`API 响应状态: ${response.status}`);
                    // console.log("API 完整响应:", response.response); // 调试时可以取消注释看完整响应
                    if (response.status >= 200 && response.status < 300 && response.response) {
                        const data = response.response;
                        if (data.error === 0 && data.data && Array.isArray(data.data.redBagList)) {
                            log(`API 返回成功,原始找到 ${data.data.redBagList.length} 个房间。`);
                            const roomUrls = data.data.redBagList
                                .map(item => item.rid)
                                .filter(rid => rid)
                                .slice(0, count * 2) // 多获取一些备用,以防重复
                                .map(rid => `https://www.douyu.com/${rid}`);
                            log(`提取到 ${roomUrls.length} 个 URL。`);
                            resolve(roomUrls);
                        } else {
                            log(`API 返回数据格式错误或 error 不为 0: error=${data.error}, msg=${data.msg}`);
                            reject(new Error(`API 数据错误: ${data.msg || '未知错误'}`));
                        }
                    } else {
                        log(`API 请求失败,状态码: ${response.status}`);
                        reject(new Error(`API 请求失败,状态码: ${response.status}`));
                    }
                },
                onerror: function(error) {
                    log(`API 请求网络错误: ${error.statusText || '未知网络错误'}`);
                    console.error("API onerror:", error);
                    reject(new Error(`API 网络错误: ${error.statusText || '未知'}`));
                },
                ontimeout: function() {
                    log("API 请求超时。");
                    reject(new Error("API 请求超时"));
                }
            });
        });
    }

    // --- 标签页关闭函数 ---
    async function closeCurrentTab() {
        log("尝试关闭当前标签页...");
        // 停止可能仍在运行的定时器
        if (mainIntervalId) {
            log("关闭前停止主循环定时器。");
            clearInterval(mainIntervalId);
            mainIntervalId = null;
        }
        await sleep(500); // 短暂延迟确保状态更新
        try {
            log("优先尝试 GM_closeTab()...");
            GM_closeTab();
            // 如果上面成功,脚本实例应该结束了
            log("GM_closeTab() 已调用 (若标签页未关闭,则无效或被阻止)。");
        } catch (e) {
            log(`GM_closeTab() 失败或不可用: ${e.message}`);
            log("尝试备用方法: window.close()..."); // window.open('', '_self').close() 基本无效了
            try {
                window.close();
                log("备用关闭方法 window.close() 已调用。");
            } catch (e2) {
                log(`备用关闭方法也失败: ${e2.message}`);
            }
        }
    }

    // --- 控制页面 (/6657) 相关函数 ---
    function makeDraggable(element) {
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        const savedPos = GM_getValue(BUTTON_POS_STORAGE_KEY);
        if (savedPos && savedPos.top && savedPos.left) {
            element.style.top = savedPos.top;
            element.style.left = savedPos.left;
            log(`恢复按钮位置: top=${savedPos.top}, left=${savedPos.left}`);
        } else {
            element.style.top = '100px'; // 默认位置
            element.style.left = '20px';
        }
        element.onmousedown = dragMouseDown;

        function dragMouseDown(e) {
            e = e || window.event;
            // 仅当鼠标左键按下时触发拖拽
            if (e.button !== 0) return;
            e.preventDefault();
            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDragElement;
            document.onmousemove = elementDrag;
            isDragging = true;
            element.style.cursor = 'grabbing';
            log("开始拖拽按钮");
        }

        function elementDrag(e) {
            if (!isDragging) return;
            e = e || window.event;
            e.preventDefault();
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;
            let newTop = element.offsetTop - pos2;
            let newLeft = element.offsetLeft - pos1;
            newTop = Math.max(0, Math.min(window.innerHeight - element.offsetHeight, newTop));
            newLeft = Math.max(0, Math.min(window.innerWidth - element.offsetWidth, newLeft));
            element.style.top = newTop + "px";
            element.style.left = newLeft + "px";
        }

        function closeDragElement(e) {
            // 确保是鼠标左键松开
            if (e.button !== 0 && isDragging) return; // 如果不是左键松开,或者没有在拖拽,忽略

            document.onmouseup = null;
            document.onmousemove = null;
            if (isDragging) { // 只有真正拖拽了才保存位置和改光标
                 isDragging = false;
                 element.style.cursor = 'grab';
                 log("结束拖拽按钮");
                 GM_setValue(BUTTON_POS_STORAGE_KEY, { top: element.style.top, left: element.style.left });
                 log(`保存按钮位置: top=${element.style.top}, left=${element.style.left}`);
            } else {
                // 如果只是点击没有拖拽,确保光标恢复
                element.style.cursor = 'grab';
            }
        }
    }

async function openOneNewTab() {
    const startButton = document.getElementById(DRAGGABLE_BUTTON_ID);
    if (!startButton || startButton.disabled) return; // 防止重复点击

    startButton.disabled = true;
    startButton.innerHTML = '正在查找... <span class="count">(已开: ' + openedRoomIds.size + ')</span>';
    log("开始通过 API 查找下一个可打开的房间...");

    try {
        // 获取 API 房间列表 (可以多获取一些,比如前 10 个)
        const apiRoomList = await getRoomsFromApi(10); // 调用 API 函数

        let foundNewUrl = null;
        let foundNewRid = null;

        if (apiRoomList && apiRoomList.length > 0) {
            log(`API 返回 ${apiRoomList.length} 个房间,开始查找未打开的...`);
            for (const url of apiRoomList) {
                const ridMatch = url.match(/\/(\d+)/); // 从 URL 提取 rid
                if (ridMatch && ridMatch[1]) {
                    const rid = ridMatch[1];
                    if (!openedRoomIds.has(rid)) { // 检查此 rid 是否已在 Set 中
                        foundNewUrl = url;
                        foundNewRid = rid;
                        log(`找到未打开的房间: rid=${rid}, url=${url}`);
                        break; // 找到第一个就停止查找
                    } else {
                        // log(`房间 rid=${rid} 已打开,跳过。`); // 可选调试日志
                    }
                }
            }
        } else {
            log("API 未返回有效的房间列表。");
        }

        if (foundNewUrl && foundNewRid) {
            log(`准备打开新标签页: ${foundNewUrl}`);
            try {
                GM_openInTab(foundNewUrl, { active: false, setParent: true });
                openedRoomIds.add(foundNewRid); // 将新打开的 rid 加入 Set
                log(`房间 rid=${foundNewRid} 已添加到打开列表。当前列表大小: ${openedRoomIds.size}`);
                startButton.innerHTML = '再打开一个 <span class="count">(已开: ' + openedRoomIds.size + ')</span>'; // 更新按钮文字和计数
                await sleep(OPEN_TAB_DELAY); // 短暂延迟
            } catch (e) {
                log(`打开标签页 ${foundNewUrl} 时出错: ${e.message}`);
                // 打开失败,不应该将 rid 加入列表,按钮文字也应该恢复
                startButton.innerHTML = '打开出错,重试? <span class="count">(已开: ' + openedRoomIds.size + ')</span>';
            }
        } else {
            log("在 API 列表中未能找到新的、未打开的房间。");
            startButton.innerHTML = '无新房间可开 <span class="count">(已开: ' + openedRoomIds.size + ')</span>';
            // 这里可以选择让按钮保持禁用,或者几秒后恢复
            await sleep(2000); // 等待 2 秒后恢复按钮文字
            if (!startButton.disabled) { // 再次检查,以防期间状态改变
                 startButton.innerHTML = '再打开一个 <span class="count">(已开: ' + openedRoomIds.size + ')</span>';
            }

        }
    } catch (error) {
         log(`查找或打开房间时发生错误: ${error.message}`);
         startButton.innerHTML = '查找出错,重试? <span class="count">(已开: ' + openedRoomIds.size + ')</span>';
    } finally {
         startButton.disabled = false; // 无论结果如何,最终都恢复按钮可用性
         log("按钮已恢复可用。");
    }
}

    function setupLauncherUI() {
        log("设置控制页面 UI...");
        GM_addStyle(`
            #${DRAGGABLE_BUTTON_ID} {
                position: fixed; /* 使用 fixed 更好 */
                z-index: 99999; /* 提高层级 */
                background-color: #ff5d23;
                color: white;
                border: none;
                padding: 10px 15px;
                border-radius: 5px;
                cursor: grab;
                font-size: 14px;
                box-shadow: 0 2px 5px rgba(0,0,0,0.2);
                transition: background-color 0.2s, opacity 0.3s;
                opacity: 0.9; /* 默认稍微透明 */
            }
            #${DRAGGABLE_BUTTON_ID}:hover {
                background-color: #e04a10;
                opacity: 1; /* 悬停时不透明 */
            }
            #${DRAGGABLE_BUTTON_ID}:active {
                cursor: grabbing;
                background-color: #c8400a;
            }
            #${DRAGGABLE_BUTTON_ID}:disabled {
                background-color: #cccccc;
                cursor: not-allowed;
                opacity: 0.7;
            }
                    #${DRAGGABLE_BUTTON_ID} span.count { /* 新增样式用于显示计数 */
            font-size: 10px;
            margin-left: 5px;
            opacity: 0.7;
        }
        `);

        if (document.getElementById(DRAGGABLE_BUTTON_ID)) {
             log("启动按钮已存在。");
             return;
        }

    const button = document.createElement('button');
    button.id = DRAGGABLE_BUTTON_ID;
    button.innerHTML = '打开一个房间 <span class="count">(已开: 0)</span>'; // <-- 修改按钮文字和添加计数显示
    button.onclick = openOneNewTab; // <-- 修改点击事件处理函数

    document.body.appendChild(button);
    makeDraggable(button);
    log("启动按钮已创建并可拖拽。");
}

    // --- 工作页面 相关函数 ---

    // 等待并点击中间的红包弹窗及关闭
    async function waitForPopupAndClick() {
        log("开始等待中间红包弹窗...");
        isWaitingForPopup = true; // 标记开始等待

        const popup = await findElement(SELECTORS.popupModal, POPUP_WAIT_TIMEOUT);

        if (!popup) {
            log("等待红包弹窗超时或未找到。");
            isWaitingForPopup = false; // 超时重置状态
            return false; // 返回失败
        }

        log("红包弹窗已出现。查找打开按钮...");
        const openBtn = popup.querySelector(SELECTORS.openButton);
        if (await safeClick(openBtn, "红包弹窗的打开按钮")) {
            log(`红包打开按钮已点击,等待 ${CLOSE_POPUP_DELAY}ms 后尝试关闭弹窗...`);
            await sleep(CLOSE_POPUP_DELAY);

            log("尝试关闭领取结果弹窗...");
            // 重新获取弹窗和关闭按钮,因为 DOM 可能已更新
            // 注意:此时弹窗选择器可能不变,也可能改变,这里假设不变
            const finalPopup = document.querySelector(SELECTORS.popupModal); // 重新查找当前的弹窗
            if (finalPopup) {
                 const closeBtn = finalPopup.querySelector(SELECTORS.closeButton);
                 if (!await safeClick(closeBtn, "领取结果弹窗的关闭按钮", true, false)) { // 点击前延迟,点击后不延迟
                     log("关闭按钮未找到或点击失败。");
                     // 即使关闭失败,也认为本次领取操作流程结束
                 } else {
                     log("关闭按钮已点击。");
                 }
            } else {
                 log("领取结果弹窗似乎已自动消失或无法重新找到。");
            }
            isWaitingForPopup = false; // 重置状态
            return true; // 返回成功
        } else {
            log("错误:找到了弹窗,但找不到或无法点击打开按钮。");
            isWaitingForPopup = false; // 重置状态
            return false; // 返回失败
        }
    }

    // 处理切换房间 (API优先)
    async function handleSwitchRoom() {
        if (isSwitchingRoom) {
            log("已在执行切换房间操作,本次跳过。");
            return;
        }
        isSwitchingRoom = true;
        log("开始尝试通过 API 获取下一个房间并切换...");

        try {
            const currentRoomId = window.location.pathname.match(/\/(\d+)/)?.[1] || window.location.search.match(/rid=(\d+)/)?.[1];
            log(`当前房间 ID: ${currentRoomId || '未知'}`);

            const roomList = await getRoomsFromApi(5); // 多获取几个备选

            let nextUrl = null;
            if (roomList && roomList.length > 0) {
                // 查找第一个与当前房间不同的 URL
                for (const url of roomList) {
                    const nextRoomId = url.match(/\/(\d+)/)?.[1];
                    if (nextRoomId && nextRoomId !== currentRoomId) {
                        log(`找到下一个不同的房间: ${url}`);
                        nextUrl = url;
                        break;
                    } else if (!currentRoomId && nextRoomId) {
                         // 如果当前房间 ID 未知,随便找一个有效的就行
                         log(`当前房间 ID 未知,选择第一个获取到的房间: ${url}`);
                         nextUrl = url;
                         break;
                    }
                }
                if (!nextUrl && roomList.length > 0 && roomList[0].match(/\/(\d+)/)?.[1] !== currentRoomId) {
                    // 如果循环完还没找到不同的(例如API只返回了当前房间),但列表里还有其他房间,就用第一个
                    // 这种情况比较少见,除非API有问题或者只有一个房间有活动了
                    log("未找到与当前不同的房间,但API列表非空,尝试使用列表第一个");
                    nextUrl = roomList[0];
                }
            }

            if (nextUrl) {
                log(`确定下一个房间链接: ${nextUrl}`);
                log("准备打开新标签页并关闭当前页...");
                try {
                    GM_openInTab(nextUrl, { active: false, setParent: true });
                    log(`新标签页打开指令已发送: ${nextUrl}`);

                    // 立即停止当前页的主循环定时器
                    if (mainIntervalId) {
                        clearInterval(mainIntervalId);
                        mainIntervalId = null;
                        log("当前页主循环已停止。");
                    }

                    await sleep(CLOSE_TAB_DELAY + getRandomDelay(0, 500));
                    await closeCurrentTab(); // 尝试关闭当前标签页

                    // closeCurrentTab 之后理论上脚本会停止,下面的代码可能不会执行
                    isSwitchingRoom = false; // 以防万一,重置状态

                } catch (tabError) {
                    log(`打开或关闭标签页时发生错误: ${tabError.message}`);
                    if (mainIntervalId) clearInterval(mainIntervalId); // 确保停止
                    isSwitchingRoom = false; // 出错,重置状态
                }
                // 如果 closeCurrentTab 成功,脚本会结束
            } else {
                log("未能从 API 获取到合适的下一个房间链接。可能没有其他活动房间了。停止当前页面脚本。");
                if (mainIntervalId) clearInterval(mainIntervalId);
                mainIntervalId = null;
                isSwitchingRoom = false;
                // 可以在这里尝试调用 closeCurrentTab 关闭最后一个标签页
                log("尝试关闭这个最后的标签页...");
                await sleep(CLOSE_TAB_DELAY);
                await closeCurrentTab();
            }

        } catch (error) {
            log(`通过 API 切换房间时发生严重错误: ${error.message}`);
            console.error(error);
            isSwitchingRoom = false; // 出错重置
            // 发生错误时也尝试停止循环
             if (mainIntervalId) {
                clearInterval(mainIntervalId);
                mainIntervalId = null;
                log("因错误停止当前页主循环。");
             }
        }
    }

    // 主循环 (工作页面)
    async function mainLoop() {
        // 添加 try...catch 保证循环健壮性
        try {
            if (isWaitingForPopup || isSwitchingRoom) {
                // log("状态繁忙 (等待弹窗或切换中),跳过此次检查。");
                return;
            }

            // log("主循环检查..."); // 减少日志频率,需要时再打开

            const redEnvelopeDiv = document.querySelector(SELECTORS.redEnvelopeContainer);

            if (!redEnvelopeDiv) {
                notFoundCounter++;
                // log(`未找到右下角红包区域 (连续次数: ${notFoundCounter})`); // 减少日志频率
                if (notFoundCounter === 1) log("首次未找到红包区域,可能已领完或加载中..."); // 第一次未找到时提示
                if (notFoundCounter >= LOAD_DELAY_THRESHOLD) {
                    log(`红包区域连续 ${notFoundCounter} 次未找到,判定为活动结束或页面异常,触发切换房间。`);
                    notFoundCounter = 0; // 重置计数器
                    await handleSwitchRoom(); // 调用切换房间函数
                }
                return; // 未找到则不继续执行本次循环
            }

            // 如果找到了红包区域,重置未找到计数器
            if (notFoundCounter > 0) {
                log("重新找到了红包区域。");
                notFoundCounter = 0;
            }

            // 检查红包区域是否可见(有时元素存在但被隐藏)
            const style = window.getComputedStyle(redEnvelopeDiv);
             if (style.display === 'none' || style.visibility === 'hidden' || redEnvelopeDiv.offsetParent === null) {
                // log("红包区域元素存在但不可见,等待下次检查..."); // 减少日志频率
                return;
            }


            // 查找状态显示元素
            const statusSpan = redEnvelopeDiv.querySelector(SELECTORS.countdownTimer);

            if (statusSpan) {
                const statusText = statusSpan.textContent.trim();
                if (statusText.includes(':')) { // 包含冒号,认为是倒计时
                    // log(`等待倒计时: ${statusText}`); // 减少日志频率
                } else if (statusText.includes('抢') || statusText.includes('领')) { // "抢红包" 或类似的文字
                    log(`检测到可点击状态: "${statusText}"`);
                    // 点击整个红包区域触发弹窗
                    if (await safeClick(redEnvelopeDiv, "右下角红包区域")) {
                        // isWaitingForPopup = true; // waitForPopupAndClick 内部会设置
                        await waitForPopupAndClick(); // 开始等待并处理中间弹窗
                        // 不论 waitForPopupAndClick 成功与否,本次循环任务完成
                    } else {
                         log("尝试点击右下角红包区域失败。");
                         // 可以选择在这里稍微等待后重试,或者等待下个循环周期
                         await sleep(getRandomDelay());
                    }
                } else if (statusText === "") {
                    // log("红包状态文本为空,可能在加载中,等待下次检查..."); // 减少日志频率
                }
                 else {
                    log(`红包区域状态未知: "${statusText}",等待下次检查...`);
                }
            } else {
                log("警告:在红包区域内找不到状态元素 (span.LiveNewAnchorSupportT-enter--bottom)。");
                // 即使找不到状态文字,如果红包区域可见,也可以尝试点击(可选逻辑)
                // log("尝试直接点击红包区域...");
                // if (await safeClick(redEnvelopeDiv, "右下角红包区域 (无状态文字)")) {
                //     await waitForPopupAndClick();
                // }
            }
        } catch (error) {
            log(`主循环发生未捕获错误: ${error.message}`);
            console.error("Main loop error:", error);
            // 考虑是否需要停止循环或进行其他错误处理
             if (mainIntervalId) {
                 log("因主循环错误,停止定时器。");
                 clearInterval(mainIntervalId);
                 mainIntervalId = null;
                 // 也可以尝试切换房间
                 // await handleSwitchRoom();
             }
        }
    }

    // --- 脚本初始化 ---
    async function initializeScript() {
        log("脚本初始化...");
        const currentUrl = window.location.href;

        if (currentUrl.includes(`/${CONTROL_ROOM_ID}`)) {
            log(`当前是控制页面 (${CONTROL_ROOM_ID})。`);
            setupLauncherUI();
        } else if (currentUrl.match(/douyu\.com\/(\d+)/) || currentUrl.match(/douyu\.com\/topic\/.*rid=(\d+)/)) {
            log("当前是工作页面。");
            tabStartTime = Date.now(); // <--- 在这里记录启动时间

            // **关键改进:等待关键元素出现后再启动主循环**
            log(`等待关键元素 "${SELECTORS.criticalElement}" 出现 (最长 ${ELEMENT_WAIT_TIMEOUT / 1000} 秒)...`);
            const criticalElement = await findElement(SELECTORS.criticalElement, ELEMENT_WAIT_TIMEOUT);

            if (criticalElement) {
                log(`关键元素已找到。将在 ${CHECK_INTERVAL}ms 后开始主循环检测...`);
                // 确保之前的定时器被清除
                if(mainIntervalId) clearInterval(mainIntervalId);
                // 稍微延迟启动,给页面更多渲染时间
                await sleep(CHECK_INTERVAL);
                log("开始主循环...");
                mainIntervalId = setInterval(mainLoop, CHECK_INTERVAL);
            } else {
                log(`等待关键元素超时或未找到。脚本在当前页面可能无法正常工作。`);
                // 可以选择尝试关闭标签页
                log("尝试关闭此无法正常初始化的标签页...");
                await sleep(CLOSE_TAB_DELAY);
                await closeCurrentTab();
            }
        } else {
            log("当前页面不是指定的控制页或直播间工作页,脚本不活动。 URL:", currentUrl);
        }
    }

    // --- 启动 ---
    // 使用 setTimeout 延迟执行初始化,给页面一些基础加载时间
    log(`脚本将在 ${INITIAL_SCRIPT_DELAY}ms 后开始初始化...`);
    setTimeout(initializeScript, INITIAL_SCRIPT_DELAY);

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址