Greasy Fork is available in English.

斗鱼月度活动批处理脚本

斗鱼月度活动批处理脚本,通过点击按钮自动化抽卡和回收

// ==UserScript==
// @name         斗鱼月度活动批处理脚本
// @namespace    http://tampermonkey.net/
// @version      2024.11.28.001
// @description  斗鱼月度活动批处理脚本,通过点击按钮自动化抽卡和回收
// @author       You
// @match			*://*.douyu.com/0*
// @match			*://*.douyu.com/1*
// @match			*://*.douyu.com/2*
// @match			*://*.douyu.com/3*
// @match			*://*.douyu.com/4*
// @match			*://*.douyu.com/5*
// @match			*://*.douyu.com/6*
// @match			*://*.douyu.com/7*
// @match			*://*.douyu.com/8*
// @match			*://*.douyu.com/9*
// @match			*://*.douyu.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=douyu.com
// @grant        none
// @license        MIT License
// ==/UserScript==

(function() {
    'use strict';
    let totalWaitTime = 0;
    const waitInterval = 1000; // 每次检查之间等待时间(毫秒)
    const maxWaitTime = 45000; // 最大等待时间(毫秒)
    let isDrawAvaiale = true;
    let isCardRecycleAvaiale = true;
    let shouldStop = false;
    // 活动入口classname
    const actInteractiveButton = '.actAnnual202311Interactive-container';
    // 活动子窗口ifram
    // const actIframe = 'iframe[src*="festival202404"]';


    const actIframe = 'iframe[class="Live-Act-Annual-Panel-iframe"]';

    const drawTabClassName = '.layout-tab-item.mine';

    const pkTabClassName = '.layout-tab-item.anchor';

    const recycleFailClassName = ".PageMineBackpackRecycleEntry.isRecycling"

    const cardDrawSteps = ['.MineLottery-lotteryBtn.batch10', '.LotteryResult-btn'];
    const cardRecycleSteps = ['.PageMineBackpackRecycleEntry', '.PageMineBackpack-autoRecycleBtn', '.PageMineBackpackModal-btn'];
    //const priority = ["白银双倍卡", "钻石双倍卡", "黄金双倍卡", "白银双倍卡碎片", "钻石双倍卡碎片", "黄金双倍卡碎片"];
    const priority = ["白银双倍卡x1", "黄金双倍卡x1", "钻石双倍卡x1", "白银双倍卡碎片", "黄金双倍卡碎片","钻石双倍卡碎片"];
    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    function waitForElement(className, interval, maxTime, onSuccess, onFail, elementMatcher) {
        let elapsedTime = 0;


        let targetElement = null;
        const checkInterval = setInterval(() => {
            const iframe = document.querySelector(actIframe);
            const iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
            const elements = Array.from(iframeDocument.querySelectorAll(className));
            if(elements.length > 1){
                targetElement = elements.find(el => !elementMatcher || elementMatcher(el));
            } else {
                targetElement = elements[0];
            }
            if(shouldStop){
                return;
            }
            if (targetElement) {
                clearInterval(checkInterval);
                console.log(`Element with class '${className}' found.`);
                if (onSuccess) onSuccess(targetElement);
            } else if (elapsedTime >= maxTime) {
                clearInterval(checkInterval);
                console.log(`Timeout: Element with class '${className}' not found within ${maxTime} ms.`);
                if (onFail) onFail();
            }

            elapsedTime += interval;
        }, interval);
    }

    // 定义了一些公用动作
    // 1. 找到活动入口并打开
    // 2. 转到合适的tab标签


    function processSteps(steps, onFinish) {
        let currentStepIndex = 0; // 当前步骤索引

        const executeStep = () => {
            if (currentStepIndex >= steps.length) {
                // 完成一轮步骤后重置索引并重新开始
                currentStepIndex = 0;
                console.log('一轮流程完成,重新开始');
            }

            const step = steps[currentStepIndex];

            waitForElement(
                step.className, // 要检查的div classname
                step.interval, // 等待间隔
                step.maxTime, // 最大等待时间
                (element) => {
                    // 成功找到元素后执行的操作
                    step.onSuccess(element);
                    currentStepIndex++; // 移动到下一个步骤
                    executeStep(); // 递归调用执行下一步
                },
                () => {
                    // 超时或失败后执行的操作
                    if (step.onFail) step.onFail();
                    if (step.continueOnFail) {
                        // 如果当前步骤失败了但是设置了继续执行,移动到下一个步骤
                        currentStepIndex++;
                        executeStep();
                    } else {
                        // 如果不继续执行,调用onFinish回调并结束递归循环
                        if (onFinish) onFinish();
                    }
                },
                step.elementMatcher
            );
        };

        executeStep(); // 开始执行步骤
    }

    function commonStartActions(tabClassName) {
        document.querySelector(actInteractiveButton).click();
        return new Promise((resolve, reject) => {
            // 假设这里有一些异步操作,比如等待某个元素出现
            // 一旦完成,调用resolve();如果出现错误,调用reject()

            // 示例:
            waitForElement(tabClassName, 1000, 10000, (element) => {
                element.click();
                console.log('Element found or some start action completed.');
                resolve(); // 操作成功完成
            }, () => {
                console.log('Failed to complete start actions.');
                reject(); // 操作失败
            });
        });
    }

    function autoDraw(callback) {
        shouldStop = false;
        const loop = () => {
            if (shouldStop) {
                console.log('autoDraw stopped');
                return;
            }
            let stepExecuted = false;
            const steps = [
                {
                    className: cardDrawSteps[0],
                    interval: 1000,
                    maxTime: 10000,
                    onSuccess: (element) => {
                        element.click();
                    },
                    onFail: () => {},
                    continueOnFail: true
                },
                {
                    className: cardDrawSteps[1],
                    interval: 1000,
                    maxTime: 10000,
                    onSuccess: (element) => {
                        element.click();
                    },
                    onFail: () => {},
                    continueOnFail: true
                }

            ];

            processSteps(steps, () => {
                if (stepExecuted) {
                    console.log('抽卡流程至少部分成功');
                    callback(true); // 操作成功
                } else {
                    console.log('抽卡流程完全失败');
                    callback(false); // 操作失败
                }
            });
        };
        loop();
    }

    function autoCardRecycle(callback) {
        shouldStop = false;
        const loop = () => {
            if (shouldStop) {
                console.log('autoDraw stopped');
                return;
            }
            let stepExecuted = false;
            function recycleFail(){
                const iframe = document.querySelector(actIframe);
                const iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
                iframeDocument.querySelector(recycleFailClassName).click();

            }
            const steps = [
                {
                    className: cardRecycleSteps[0],
                    interval: 1000,
                    maxTime: 10000,
                    onSuccess: (element) => element.click(),
                    onFail: () => recycleFail(),
                    continueOnFail: true
                },
                {
                    className: cardRecycleSteps[1],
                    interval: 1000,
                    maxTime: 10000,
                    onSuccess: (element) => element.click(),
                    onFail: () => recycleFail(),
                    continueOnFail: true
                },
                {
                    className: cardRecycleSteps[2],
                    interval: 1000,
                    maxTime: 10000,
                    onSuccess: (element) => {
                        element.click();
                        stepExecuted = true; // 标记为成功执行
                    },
                    onFail: () => recycleFail(),
                    continueOnFail: true
                }
            ];


            processSteps(steps, () => {
                if (stepExecuted) {
                    console.log('抽卡流程至少部分成功');
                    callback(true); // 操作成功
                } else {
                    console.log('抽卡流程完全失败');
                    callback(false); // 操作失败
                }
            });
        };
        loop();
    }


    function autoPetsRecycle(callback) {
        shouldStop = false;
        const loop = () => {
            if (shouldStop) {
                console.log('autoDraw stopped');
                return;
            }
            let stepExecuted = false;
            function petsRecycleFail(){
                const iframe = document.querySelector(actIframe);
                const iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
                iframeDocument.querySelector('.CommonModal-close.MyCollectModal-close').click();

            }

            const steps = [
                {
                    className: '.MineCollectMedal',
                    interval: 1000,
                    maxTime: 10000,
                    onSuccess: (element) => {
                        element.click();
                        console.log('成功点击宠物卡回收按钮');
                    },
                    onFail: () => petsRecycleFail(),
                    continueOnFail: true
                },
                {
                    className: '.MyCollectModal-navItem',
                    interval: 1000,
                    maxTime: 10000,
                    onSuccess: (element) => {
                        element.click();
                        console.log('成功点击宠物卡tab');
                    },
                    onFail: () => petsRecycleFail(),
                    continueOnFail: true,
                    elementMatcher: (el) => el.textContent.includes('宠物卡')

                },
                {
                    className: '.CollectPetCard-recycle',
                    interval: 1000,
                    maxTime: 10000,
                    onSuccess: (element) => {
                        element.click();
                        console.log('成功点击宠物卡回收按钮');
                    },
                    onFail: () => console.log('点击宠物卡回收按钮失败'),
                    continueOnFail: true
                },
                {
                    className: '.PetCardRecycle-controlBtn.auto',
                    interval: 1000,
                    maxTime: 10000,
                    onSuccess: (element) => {
                        element.click();
                        console.log('成功点击自动回收按钮');
                    },
                    onFail: () => console.log('点击自动回收按钮失败'),
                    continueOnFail: true
                },
                {
                    className: '.AutoRecycleSurePop-btn',
                    interval: 1000,
                    maxTime: 5000,
                    onSuccess: (element) => {
                        element.click();

                        stepExecuted = true;
                        console.log('成功确认回收');
                        petsRecycleFail();
                        // 如果需要在回收成功后执行额外的操作,可以在这里添加
                    },
                    onFail: () => petsRecycleFail(),
                    continueOnFail: true
                }
            ];

            // 调用通用的 processSteps 函数来处理宠物卡回收的步骤
            // 可以根据需要传入完成整个流程后的回调函数
            processSteps(steps, () => {
                if (stepExecuted) {
                    console.log('抽卡流程至少部分成功');
                    callback(true); // 操作成功
                } else {
                    console.log('抽卡流程完全失败');
                    callback(false); // 操作失败
                }
            });
        };
        loop();
    }

    function allAuto() {
        shouldStop = false;
        commonStartActions(drawTabClassName).then(() => {
            console.log('Common start actions completed, now running auto functions in parallel.');
            // 由于不需要等待这些函数的结果,可以直接并行调用
            autoDraw();
            autoCardRecycle();
            autoPetsRecycle();
        }).catch(() => {
            console.log('Failed to execute common start actions.');
            // 如果commonStartActions失败了,这里可以处理错误或决定是否继续执行其它操作
        });
    }

    function sortArrayByPriorityAndGetIndicesAndSortedArray(arr, priority) {
        // 将优先级数组转换为一个映射,用于快速查找优先级
        const priorityMap = new Map(priority.map((item, index) => [item, index]));

        // 创建一个包含原始索引的新数组
        const arrWithIndices = arr.map((item, index) => ({item, index}));

        // 对这个新数组进行排序
        arrWithIndices.sort((a, b) => {
            // 获取第二列元素的优先级,如果找不到则设为最大值
            const priorityAIndex = Array.from(priorityMap.keys()).findIndex(key => a.item[1].includes(key));
            const priorityBIndex = Array.from(priorityMap.keys()).findIndex(key => b.item[1].includes(key));

            // 如果未找到,则设为最大值
            const priorityA = priorityAIndex !== -1 ? priorityAIndex : Number.MAX_SAFE_INTEGER;
            const priorityB = priorityBIndex !== -1 ? priorityBIndex : Number.MAX_SAFE_INTEGER;

            // 如果第二列的优先级相同,则根据第一列的值排序
            if (priorityA === priorityB) {
                // 注意:如果第一列也是字符串,保持使用 localeCompare
                // 如果第一列是数字或其他类型,需要相应地调整比较逻辑
                // 为第一列元素找到优先级
                const firstColPriorityAIndex = Array.from(priorityMap.keys()).findIndex(key => a.item[0].includes(key));
                const firstColPriorityBIndex = Array.from(priorityMap.keys()).findIndex(key => b.item[0].includes(key));

                const firstColPriorityA = firstColPriorityAIndex !== -1 ? firstColPriorityAIndex : Number.MAX_SAFE_INTEGER;
                const firstColPriorityB = firstColPriorityBIndex !== -1 ? firstColPriorityBIndex : Number.MAX_SAFE_INTEGER;

                // 根据第一列的优先级排序
                return firstColPriorityA - firstColPriorityB;
            }
            // 根据第二列的优先级排序
            return priorityA - priorityB;
        });

        // 从排序后的数组中提取原始索引和排序后的数组元素
        const indices = arrWithIndices.map(el => el.index);
        const sortedArray = arrWithIndices.map(el => el.item);

        // 返回一个包含索引和排序后的数组的对象
        return {
            indices: indices,
            sortedArray: sortedArray
        };
    }
    async function checkPKAwards(){
        const iframe = document.querySelector(actIframe);
        const iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
        const items = iframeDocument.querySelectorAll('.AnchorAwardSelect-item');
        const awardsArray = Array.from(items).map(item => {
            const awards = item.querySelectorAll('.AnchorAwardSelect-itemAward');
            return Array.from(awards).map(award => award.textContent);
        });

        const result = sortArrayByPriorityAndGetIndicesAndSortedArray(awardsArray, priority);
        for(let i = 0; i < result.indices.length;i++){

            let selectedReward = items[result.indices[i]];
            selectedReward.click();

            const modalButton = iframeDocument.querySelector('.modalBtn.modalBtn1');
            if (modalButton) {
                modalButton.click();
            } else {
                if(iframeDocument.querySelectorAll('.modal-close')){
                    iframeDocument.querySelectorAll('.modal-close')[0].click();
                }

            }

            await sleep(1000);
            // 检查是否没有剩余的items,如果是则退出函数
            if(iframeDocument.querySelectorAll('.modalBtn.modalBtn1').length < 1){
                console.log("No more items, exiting...");
                break;
            }
        }
        const closeBtn = iframeDocument.querySelector('.modal-close');
        if (closeBtn) {
            closeBtn.click();
        }

    }

    function PKProcess(){
        shouldStop = false;
        const steps = [
            {
                className: '.modal-close.is-mini',
                interval: 1000,
                maxTime: 10000,
                onSuccess: (element) => {
                    element.click();
                    console.log('成功点击选择奖励按钮');
                },
                onFail: () => console.log('点击选择奖励按钮失败'),
                continueOnFail: true
            },
            {
                className: '.awardBtn',
                interval: 1000,
                maxTime: 10000,
                onSuccess: (element) => {
                    element.click();
                    console.log('成功点击选择奖励按钮');
                },
                onFail: () => console.log('点击选择奖励按钮失败'),
                continueOnFail: true
            },
            {
                className: '.AnchorAwardSelect-item',
                interval: 1000,
                maxTime: 5000,
                onSuccess: (element) => {
                    checkPKAwards()
                },
                onFail: () => console.log(''),
                continueOnFail: true
            },
            {
                className: '.modalBtn.modalBtn1',
                interval: 1000,
                maxTime: 5000,
                onSuccess: (element) => {
                    element.click();
                },
                onFail: () => console.log('点击自动回收按钮失败'),
                continueOnFail: true
            },
            {
                className: '.userInfo-pic',
                interval: 1000,
                maxTime: 5000,
                onSuccess: (element) => {
                    // element.click();
                    if(element && element.alt === "虚位以待") {
                        element.click();
                    } else {
                       console.log("img的alt属性不是'虚位以待'");
                    }


                },
                onFail: () => console.log('点击自动回收按钮失败'),
                continueOnFail: true
            },
            {
                className: '.modalBtn.modalBtn1',
                interval: 1000,
                maxTime: 5000,
                onSuccess: (element) => {
                    element.click();
                },
                onFail: () => console.log('点击自动回收按钮失败'),
                continueOnFail: true
            }
        ];



        let executeRound = () => {

            let currentStepIndex = 0;
            const executeStep = () => {
                if(shouldStop || currentStepIndex >= steps.length) {
                    console.log('本轮流程结束,或者停止标志被设置。');
                    onFinish(); // 完成本轮后的回调
                    return;
                }

                const step = steps[currentStepIndex];
                waitForElement(
                    step.className,
                    step.interval,
                    step.maxTime,
                    (element) => {
                        step.onSuccess(element);
                        currentStepIndex++;
                        executeStep(); // 继续下一步
                    },
                    () => {
                        if (step.onFail) step.onFail();
                        if (step.continueOnFail) {
                            currentStepIndex++;
                            executeStep(); // 即便失败也继续
                        } else {
                            onFinish(); // 如果不允许失败则结束
                        }
                    },
                    step.elementMatcher
                );
            };

            executeStep(); // 开始执行步骤
        };

        // 定义完成后的行为
        let onFinish = () => {
            console.log('一轮流程完成。');
            // 可以在这里设置一个条件判断是否要开始下一轮
            if(!shouldStop) {
                console.log('开始新的一轮流程。');
                executeRound(); // 开始新的一轮
            } else {
                console.log('流程被停止。');
            }
        };

        executeRound(); // 开始第一轮
    }

    function autoPK(){
        shouldStop = false;
        commonStartActions(pkTabClassName).then(() => {
            PKProcess();
        }).catch()
    }
    // 添加按钮到页面
    function addButton(text, className, idx, onclick) {
        const elements = Array.from(document.querySelectorAll(className));
        const container = elements[idx];
        const buttonDiv = document.createElement('div');
        buttonDiv.className = 'DiamondFansMatchEntrance';
        const label = document.createElement('label');
        label.textContent = text;
        label.style.cursor = 'pointer';
        label.onclick = onclick;
        buttonDiv.appendChild(label);
        container.appendChild(buttonDiv);
    }

    function checkAndAddButtons() {
        // 检查页面中是否存在指定的div
        const giftNamingEntranceExists = document.querySelector('.DiamondFansMatchEntrance');
        const cardBodyExists = document.querySelector(actInteractiveButton);

        // 如果两个div都存在,则添加按钮
        if (giftNamingEntranceExists && cardBodyExists) {
            console.log("找到了指定的div,添加按钮。");
            addButton('自动抽卡','.Title-col.is-left', 0, autoDraw);
            addButton('自动回收', '.Title-col.is-left', 0,autoCardRecycle);
            //addButton('自动回收宠物卡', '.Title-col.is-left', 0,autoPetsRecycle);
            //addButton('全自动', '.Title-col.is-left', 0,allAuto);
            addButton('终止全部操作', '.Title-col.is-left', 0, ()=>{shouldStop = true;});
            addButton('自动pk', '.Title-col.is-normal', 1,autoPK);
            clearInterval(checkInterval); // 停止定时器
        }
    }

    const checkInterval = setInterval(checkAndAddButtons, waitInterval);

})();