Greasy Fork

Greasy Fork is available in English.

原神激励领奖

B站原神激励活动自动领奖脚本,支持自动解锁按钮、可调节点击间隔、智能停止检测,提供一键控制和优雅的消息提示系统

当前为 2025-06-07 提交的版本,查看 最新版本

// ==UserScript==
// @name         原神激励领奖
// @namespace    http://tampermonkey.net/
// @version      2025-06-09
// @description  B站原神激励活动自动领奖脚本,支持自动解锁按钮、可调节点击间隔、智能停止检测,提供一键控制和优雅的消息提示系统
// @author       You
// @match        https://www.bilibili.com/blackboard/new-award-exchange.html*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @license      MIT
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    let clickInterval = null;
    let isAutoClicking = false;
    let clickIntervalMs = 100; // Default click interval
    let timeUpdateInterval = null;

    // List of stop texts
    const stopTexts = ["查看奖励", "每日库存已达上限", "暂无领取资格"];

    // Message container for stacking notifications
    let messageContainer = null;

    // Load saved click interval from localStorage
    function loadClickInterval() {
        const saved = localStorage.getItem('auto-click-interval');
        if (saved && [100, 200, 500, 800, 1000].includes(parseInt(saved))) {
            clickIntervalMs = parseInt(saved);
        }
    }

    // Save click interval to localStorage
    function saveClickInterval(interval) {
        localStorage.setItem('auto-click-interval', interval.toString());
    }

    // Function to create real-time clock display
    function createRealTimeClock() {
        // Create clock container
        const clockContainer = document.createElement('div');
        clockContainer.id = 'real-time-clock';
        clockContainer.style.cssText = `
            position: fixed;
            top: 20px;
            left: 20px;
            z-index: 9999;
            background: linear-gradient(135deg, rgba(74, 144, 226, 0.9), rgba(126, 208, 255, 0.9));
            color: #FFFFFF;
            padding: 16px 20px;
            border-radius: 12px;
            font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
            text-align: center;
            border: 1px solid rgba(255, 255, 255, 0.3);
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
            backdrop-filter: blur(10px);
            min-width: 160px;
        `;

        // Create time display element (first line)
        const timeDisplay = document.createElement('div');
        timeDisplay.id = 'time-display';
        timeDisplay.style.cssText = `
            font-size: 20px;
            font-weight: 600;
            line-height: 1.2;
            text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
            margin-bottom: 4px;
        `;
        timeDisplay.textContent = '00:00:00.000';

        // Create date display element (second line)
        const dateDisplay = document.createElement('div');
        dateDisplay.id = 'date-display';
        dateDisplay.style.cssText = `
            font-size: 14px;
            font-weight: 400;
            opacity: 0.9;
            text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
        `;
        dateDisplay.textContent = '2025-01-07';

        clockContainer.appendChild(timeDisplay);
        clockContainer.appendChild(dateDisplay);

        // Create hidden iframe to load bjtime.net
        const iframe = document.createElement('iframe');
        iframe.src = 'https://www.bjtime.net/';
        iframe.style.cssText = `
            position: absolute;
            top: -9999px;
            left: -9999px;
            width: 1px;
            height: 1px;
            border: none;
            opacity: 0;
            pointer-events: none;
        `;
        iframe.id = 'time-iframe';

        document.body.appendChild(clockContainer);
        document.body.appendChild(iframe);

        // Handle iframe load
        iframe.onload = function() {
            try {
                startTimeUpdater(iframe, timeDisplay, dateDisplay);
            } catch (error) {
                console.log('Failed to access iframe content, using local time');
                startLocalTimeUpdater(timeDisplay, dateDisplay);
            }
        };

        // Fallback to local time if iframe fails to load
        iframe.onerror = function() {
            console.log('Failed to load bjtime.net, using local time');
            startLocalTimeUpdater(timeDisplay, dateDisplay);
        };

        // Timeout fallback
        setTimeout(() => {
            if (timeDisplay.textContent === '00:00:00.000') {
                console.log('Timeout loading bjtime.net, using local time');
                startLocalTimeUpdater(timeDisplay, dateDisplay);
            }
        }, 5000);
    }

    // Function to start time updater using iframe
    function startTimeUpdater(iframe, timeDisplay, dateDisplay) {
        timeUpdateInterval = setInterval(() => {
            try {
                const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
                const timeElement = iframeDoc.querySelector('#cTime');
                
                if (timeElement && timeElement.textContent) {
                    const fullTime = timeElement.textContent;
                    updateTimeDisplay(fullTime, timeDisplay, dateDisplay);
                } else {
                    // Fallback to local time if can't find element
                    updateLocalTime(timeDisplay, dateDisplay);
                }
            } catch (error) {
                // Cross-origin error, fallback to local time
                updateLocalTime(timeDisplay, dateDisplay);
            }
        }, 100); // Update every 100ms
    }

    // Function to start local time updater
    function startLocalTimeUpdater(timeDisplay, dateDisplay) {
        timeUpdateInterval = setInterval(() => {
            updateLocalTime(timeDisplay, dateDisplay);
        }, 100);
    }

    // Function to update time display with parsed time
    function updateTimeDisplay(fullTime, timeDisplay, dateDisplay) {
        // Parse time string like "2025-01-07 14:30:25.123"
        const parts = fullTime.split(' ');
        if (parts.length >= 2) {
            dateDisplay.textContent = parts[0]; // Date part
            timeDisplay.textContent = parts[1]; // Time part
        } else {
            // Fallback to local time if parsing fails
            updateLocalTime(timeDisplay, dateDisplay);
        }
    }

    // Function to update with local time
    function updateLocalTime(timeDisplay, dateDisplay) {
        const now = new Date();
        const dateString = now.getFullYear() + '-' + 
                          String(now.getMonth() + 1).padStart(2, '0') + '-' + 
                          String(now.getDate()).padStart(2, '0');
        const timeString = String(now.getHours()).padStart(2, '0') + ':' + 
                          String(now.getMinutes()).padStart(2, '0') + ':' + 
                          String(now.getSeconds()).padStart(2, '0') + '.' + 
                          String(now.getMilliseconds()).padStart(3, '0');
        
        dateDisplay.textContent = dateString;
        timeDisplay.textContent = timeString;
    }

    // Function to create message container
    function createMessageContainer() {
        if (!messageContainer) {
            messageContainer = document.createElement('div');
            messageContainer.id = 'message-container';
            messageContainer.style.cssText = `
                position: fixed;
                top: 70px;
                right: 20px;
                z-index: 10000;
                display: flex;
                flex-direction: column;
                gap: 10px;
                pointer-events: none;
            `;
            document.body.appendChild(messageContainer);
        }
        return messageContainer;
    }

    // Function to show non-blocking message notification
    function showMessage(message, type = 'info') {
        const container = createMessageContainer();
        
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.cssText = `
            padding: 12px 20px;
            background-color: ${type === 'error' ? '#FF6B6B' : type === 'success' ? '#51CF66' : '#339AF0'};
            color: white;
            border-radius: 8px;
            font-size: 14px;
            font-weight: bold;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            max-width: 300px;
            word-wrap: break-word;
            transform: translateX(100%);
            opacity: 0;
            transition: all 0.3s ease-out;
            pointer-events: auto;
        `;
        
        container.appendChild(notification);
        
        // Trigger slide-in animation
        setTimeout(() => {
            notification.style.transform = 'translateX(0)';
            notification.style.opacity = '1';
        }, 10);
        
        // Auto remove after 3 seconds
        setTimeout(() => {
            notification.style.transform = 'translateX(100%)';
            notification.style.opacity = '0';
            setTimeout(() => {
                if (notification.parentNode) {
                    notification.remove();
                }
            }, 300);
        }, 3000);
    }

    // Function to remove disable class from button
    function removeDisableClass() {
        const button = document.querySelector("#app > div > div.home-wrap.select-disable > section.tool-wrap > div.button.exchange-button");
        if (button) {
            button.classList.remove("disable");
            console.log("Disable class removed from button");
        }
    }

    // Function to start/stop auto clicking
    function toggleAutoClick() {
        const button = document.querySelector("#app > div > div.home-wrap.select-disable > section.tool-wrap > div.button.exchange-button");
        
        if (isAutoClicking) {
            // Stop clicking
            if (clickInterval) {
                clearInterval(clickInterval);
                clickInterval = null;
            }
            isAutoClicking = false;
            updateControlButton();
            showMessage("已停止自动领奖", "info");
            return;
        }

        if (!button) {
            showMessage("找不到领奖按钮!", "error");
            return;
        }

        // Start clicking
        isAutoClicking = true;
        updateControlButton();
        showMessage(`开始自动领奖 (${clickIntervalMs}ms间隔)`, "success");

        clickInterval = setInterval(() => {
            if (button) {
                // Check if button text is in stopTexts
                if (stopTexts.includes(button.textContent.trim())) {
                    clearInterval(clickInterval);
                    clickInterval = null;
                    isAutoClicking = false;
                    updateControlButton();
                    showMessage(`已停止领奖!原因:${button.textContent.trim()}`, "info");
                    return;
                }
                button.click();
            } else {
                clearInterval(clickInterval);
                clickInterval = null;
                isAutoClicking = false;
                updateControlButton();
            }
        }, clickIntervalMs); // Use selected interval

        // Stop clicking after 30 seconds for safety
        setTimeout(() => {
            if (clickInterval) {
                clearInterval(clickInterval);
                clickInterval = null;
                isAutoClicking = false;
                updateControlButton();
                showMessage("自动领奖已超时停止", "info");
            }
        }, 30000);
    }

    // Function to create and add control button
    function createControlButton() {
        const controlButton = document.createElement("button");
        controlButton.id = "auto-click-control";
        controlButton.textContent = "开始自动领奖";
        controlButton.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 9999;
            padding: 10px 15px;
            background-color: #00A1D6;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 14px;
            font-weight: bold;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
        `;
        
        controlButton.addEventListener("click", toggleAutoClick);
        document.body.appendChild(controlButton);
        
        // Add hover effect
        controlButton.addEventListener("mouseenter", function() {
            this.style.backgroundColor = "#0088CC";
        });
        controlButton.addEventListener("mouseleave", function() {
            this.style.backgroundColor = isAutoClicking ? "#FF6B6B" : "#00A1D6";
        });
    }

    // Function to update control button text and style
    function updateControlButton() {
        const controlButton = document.getElementById("auto-click-control");
        if (controlButton) {
            if (isAutoClicking) {
                controlButton.textContent = "停止自动领奖";
                controlButton.style.backgroundColor = "#FF6B6B";
            } else {
                controlButton.textContent = "开始自动领奖";
                controlButton.style.backgroundColor = "#00A1D6";
            }
        }
    }

    // Function to create interval selector
    function createIntervalSelector() {
        const selectorContainer = document.createElement("div");
        selectorContainer.style.cssText = `
            position: fixed;
            top: 60px;
            right: 20px;
            z-index: 9999;
            display: flex;
            align-items: center;
            gap: 8px;
        `;

        const label = document.createElement("label");
        label.textContent = "间隔:";
        label.style.cssText = `
            color: #333;
            font-size: 12px;
            font-weight: bold;
            text-shadow: 1px 1px 2px rgba(255,255,255,0.8);
        `;

        const selector = document.createElement("select");
        selector.id = "interval-selector";
        selector.style.cssText = `
            padding: 4px 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
            background-color: white;
            font-size: 12px;
            cursor: pointer;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        `;

        // Add options
        const options = [
            { value: 100, text: "100ms (极快)" },
            { value: 200, text: "200ms (快)" },
            { value: 500, text: "500ms (中)" },
            { value: 800, text: "800ms (慢)" },
            { value: 1000, text: "1000ms (很慢)" }
        ];

        options.forEach(option => {
            const optionElement = document.createElement("option");
            optionElement.value = option.value;
            optionElement.textContent = option.text;
            if (option.value === clickIntervalMs) {
                optionElement.selected = true;
            }
            selector.appendChild(optionElement);
        });

        // Handle selection change
        selector.addEventListener("change", function() {
            clickIntervalMs = parseInt(this.value);
            saveClickInterval(clickIntervalMs);
            showMessage(`点击间隔已设置为 ${clickIntervalMs}ms`, "info");
            
            // If currently auto clicking, restart with new interval
            if (isAutoClicking) {
                toggleAutoClick(); // Stop
                setTimeout(() => {
                    toggleAutoClick(); // Start with new interval
                }, 100);
            }
        });

        selectorContainer.appendChild(label);
        selectorContainer.appendChild(selector);
        document.body.appendChild(selectorContainer);
    }

    // Initialize when page loads
    function init() {
        // Wait for page to fully load
        setTimeout(() => {
            loadClickInterval(); // Load saved click interval
            removeDisableClass();
            createControlButton();
            createIntervalSelector();
            createRealTimeClock();
            console.log("Auto-click userscript initialized");
        }, 1000);
    }

    // Run initialization when DOM is ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    // Also remove disable class periodically in case it gets re-added
    setInterval(removeDisableClass, 2000);

})();