// ==UserScript==
// @name 原神激励领奖
// @namespace http://tampermonkey.net/
// @version 2025-06-13
// @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
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
let clickInterval = null;
let isAutoClicking = false;
let clickIntervalMs = 100; // Default click interval
let timeUpdateInterval = null;
let errorCheckInterval = 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 verification notice below clock
const noticeContainer = document.createElement('div');
noticeContainer.id = 'verification-notice';
noticeContainer.style.cssText = `
position: fixed;
top: 120px;
left: 20px;
z-index: 9999;
background: linear-gradient(135deg, rgba(116, 185, 255, 0.9), rgba(162, 210, 255, 0.9));
color: #1a365d;
padding: 12px 16px;
border-radius: 8px;
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
text-align: center;
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
backdrop-filter: blur(8px);
min-width: 160px;
max-width: 220px;
`;
const noticeText = document.createElement('div');
noticeText.style.cssText = `
font-size: 12px;
font-weight: 500;
line-height: 1.4;
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.5);
`;
noticeText.innerHTML = '💡 提示
请先手动领奖一次
通过验证码检测';
noticeContainer.appendChild(noticeText);
// 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(noticeContainer);
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);
}
// Function to check for page load error dialog
function checkForErrorDialog() {
const errorDialog = document.querySelector('body > div.v-dialog > div.v-dialog__wrap > div > div.v-dialog__body');
if (errorDialog) {
console.log('Error dialog detected, refreshing page...');
showMessage('检测到页面错误,正在刷新...', 'info');
// Stop all intervals before refresh
if (errorCheckInterval) {
clearInterval(errorCheckInterval);
}
if (timeUpdateInterval) {
clearInterval(timeUpdateInterval);
}
if (clickInterval) {
clearInterval(clickInterval);
}
// Refresh page after a short delay
setTimeout(() => {
window.location.reload();
}, 1000);
return true;
}
return false;
}
// Function to start error monitoring
function startErrorMonitoring() {
// Check immediately
if (checkForErrorDialog()) return;
// Check every 1 second permanently
errorCheckInterval = setInterval(() => {
checkForErrorDialog();
}, 1000);
}
// Initialize when page loads
function init() {
// Wait for page to fully load
setTimeout(() => {
loadClickInterval(); // Load saved click interval
removeDisableClass();
createControlButton();
createIntervalSelector();
createRealTimeClock();
startErrorMonitoring();
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);
})();