您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在NodeSeek论坛方便地管理抽奖活动并获取开奖提醒
// ==UserScript== // @name NodeSeek抽奖提醒助手 // @namespace https://nodeseek.com/ // @version 0.6 // @description 在NodeSeek论坛方便地管理抽奖活动并获取开奖提醒 // @author luofengyuan // @match https://nodeseek.com/* // @match https://www.nodeseek.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @license GPLV3 // ==/UserScript== (function() { 'use strict'; // ===== 用户配置 ===== // 请在这里设置你的NodeSeek用户ID(从个人主页链接中获取,如 /space/11723 中的 11723) const USER_ID = '11723'; // 请修改为你的实际用户ID // ===== 核心功能 ===== // URL标准化函数 - 用于统一URL格式进行比较 function normalizeUrl(url) { try { // 创建URL对象进行标准化 const urlObj = new URL(url); // 统一协议为https urlObj.protocol = 'https:'; // 统一域名(移除www前缀,统一为www.nodeseek.com) if (urlObj.hostname === 'nodeseek.com') { urlObj.hostname = 'www.nodeseek.com'; } // 移除末尾斜杠 if (urlObj.pathname.endsWith('/') && urlObj.pathname.length > 1) { urlObj.pathname = urlObj.pathname.slice(0, -1); } // 移除hash部分 urlObj.hash = ''; // 返回标准化的URL return urlObj.toString(); } catch (error) { console.error('URL标准化失败:', error); // 如果标准化失败,返回原URL return url; } } // 防抖函数 function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // 样式定义 - 简化为小圆点系统 const styles = ` /* 小圆点指示器 */ #lottery-dot { position: fixed; right: 20px; top: 50%; transform: translateY(-50%); width: 20px; height: 20px; border-radius: 50%; cursor: pointer; z-index: 9999; border: 2px solid #ccc; transition: all 0.3s ease; box-shadow: 0 2px 8px rgba(0,0,0,0.2); } /* 圆点状态颜色 */ #lottery-dot.default { background-color: white; border-color: #ccc; } #lottery-dot.won { background-color: #4caf50; border-color: #388e3c; } #lottery-dot.found { background-color: #ffd700; border-color: #ffb300; animation: pulse 2s infinite; } #lottery-dot.added { background-color: #ff4444; border-color: #cc0000; } /* 脉动动画 */ @keyframes pulse { 0% { transform: translateY(-50%) scale(1); } 50% { transform: translateY(-50%) scale(1.1); } 100% { transform: translateY(-50%) scale(1); } } /* 简化的管理器 */ #lottery-manager { position: fixed; right: 50px; top: 50%; transform: translateY(-50%); background: white; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); width: 350px; max-height: 400px; z-index: 10000; font-size: 14px; display: none; } #lottery-manager.show { display: block; } .lottery-manager-header { background: #f8f9fa; padding: 12px 15px; border-radius: 8px 8px 0 0; font-weight: bold; color: #333; border-bottom: 1px solid #eee; } .lottery-stats { font-size: 12px; font-weight: normal; color: #666; margin-top: 5px; display: flex; gap: 15px; } .lottery-stats .stat-item { display: flex; align-items: center; gap: 5px; } .lottery-stats .win-rate { color: #4caf50; font-weight: bold; } .refresh-btn { background: #4caf50; color: white; border: none; padding: 4px 8px; border-radius: 4px; font-size: 12px; cursor: pointer; margin-left: 10px; transition: background-color 0.2s; } .refresh-btn:hover { background: #388e3c; } .clear-all-btn { background: #ff4444; color: white; border: none; padding: 4px 8px; border-radius: 4px; font-size: 12px; cursor: pointer; margin-left: 10px; transition: background-color 0.2s; } .clear-all-btn:hover { background: #cc0000; } .lottery-manager-content { padding: 15px; max-height: 300px; overflow-y: auto; } .lottery-item { padding: 10px; border: 1px solid #eee; border-radius: 6px; margin-bottom: 10px; background: #f9f9f9; } .lottery-item.won { background: #e8f5e9; border-color: #4caf50; } .lottery-title { font-weight: bold; color: #333; margin-bottom: 5px; } .lottery-won-badge { background: #4caf50; color: white; padding: 2px 8px; border-radius: 12px; font-size: 12px; margin-left: 10px; } .lottery-participated-tag { background-color: #e8f5e9; color: #388e3c; padding: 2px 6px; border-radius: 4px; font-size: 0.8em; margin-left: 8px; font-weight: bold; } `; // 添加样式 GM_addStyle(styles); // 创建小圆点UI系统 function createDotUI() { // 创建小圆点 const dot = document.createElement('div'); dot.id = 'lottery-dot'; dot.className = 'default'; document.body.appendChild(dot); // 创建简化的管理器 const manager = document.createElement('div'); manager.id = 'lottery-manager'; manager.innerHTML = ` <div class="lottery-manager-header"> 中奖抽奖列表 <div class="lottery-stats"></div> </div> <div class="lottery-manager-content"> <div class="lottery-list"></div> </div> `; document.body.appendChild(manager); // 绑定圆点点击事件 dot.addEventListener('click', () => { if (dot.classList.contains('found')) { // 黄色状态:添加当前页面抽奖 addCurrentPageLottery(); } else if (dot.classList.contains('default') || dot.classList.contains('added') || dot.classList.contains('won')) { // 白色、红色或绿色状态:显示/隐藏管理器 manager.classList.toggle('show'); } }); // 点击管理器外部关闭 document.addEventListener('click', (e) => { if (!manager.contains(e.target) && !dot.contains(e.target)) { manager.classList.remove('show'); } }); return { dot, manager }; } // 检测当前页面是否有抽奖链接 function detectLotteryOnCurrentPage() { // 检查页面中是否有抽奖链接 const lotteryLinks = document.querySelectorAll('a[href*="/lucky?"], a[href*="nodeseek.com/lucky?"]'); return lotteryLinks.length > 0; } // 更新圆点状态并检查中奖状态 async function updateDotStatus() { const dot = document.getElementById('lottery-dot'); if (!dot) return; // 开始更新圆点状态和检查中奖状态 // 首先清理过期记录 const hasCleanedExpired = cleanExpiredLotteries(); if (hasCleanedExpired) { console.log('✅ 已清理过期抽奖记录'); } const currentUrl = normalizeUrl(window.location.href); let reminders = GM_getValue('lottery_reminders', []); const now = Date.now(); let hasUpdates = false; // 检查所有任务的开奖时间和中奖状态 for (const reminder of reminders) { // 检查是否有开奖时间 if (!reminder.drawTime) { console.log(`⏰ 跳过无开奖时间的任务: ${reminder.title}`); continue; } // 检查是否已开奖 const isDrawn = reminder.drawTime <= now; // 简化任务状态检查信息 if (!reminder.checked && isDrawn) { console.log(`🔍 检查任务: ${reminder.title}`); } // 如果已开奖且未检查过,进行中奖判断 if (isDrawn && !reminder.checked && reminder.luckyUrl) { console.log(`🔍 开始检查中奖状态: ${reminder.title}`); try { const luckyPageHtml = await fetchLuckyPage(reminder.luckyUrl); const winnerIds = parseWinnerIds(luckyPageHtml); // 简化的中奖判断信息 console.log(`🎯 中奖ID列表: [${winnerIds.join(', ')}]`); console.log(`🎯 当前用户ID: ${USER_ID}`); // 检查用户是否中奖 - 使用多种比较方式 const isWonStrict = winnerIds.includes(USER_ID); const isWonLoose = winnerIds.some(id => id == USER_ID); const isWonString = winnerIds.some(id => String(id) === String(USER_ID)); // 使用最宽松的比较方式 const isWon = isWonStrict || isWonLoose || isWonString; // 更新中奖状态 reminder.isWon = isWon; reminder.checked = true; hasUpdates = true; // 更新独立统计数据(只在首次检查时更新) if (!reminder.statsUpdated) { updateIndependentStats(isWon); reminder.statsUpdated = true; // 标记已更新统计,避免重复计算 } console.log(`🎉 最终中奖判断结果: ${reminder.title} - ${isWon ? '🎊 中奖了!' : '😔 未中奖'}`); } catch (error) { console.error(`❌ 检查中奖状态失败: ${reminder.title}`, error); // 标记为已检查,避免重复检查 reminder.checked = true; hasUpdates = true; } } } // 如果有更新,保存数据并刷新显示 if (hasUpdates || hasCleanedExpired) { GM_setValue('lottery_reminders', reminders); refreshLotteryList(); console.log('💾 已保存更新的抽奖数据'); } // 检查当前页面是否有有效的抽奖链接 let hasValidLotteryOnPage = false; let isCurrentPageAdded = false; // 先简单检测是否可能有抽奖链接 const hasLotteryOnPage = detectLotteryOnCurrentPage(); if (hasLotteryOnPage) { try { // 获取当前页面的抽奖信息 const postHtml = await fetchPostFirstPage(currentUrl); const luckyUrl = extractLuckyUrl(postHtml); if (luckyUrl) { // 只有找到有效抽奖链接才设置为true hasValidLotteryOnPage = true; const drawTime = getLuckyPageDrawTime(luckyUrl); const currentDrawTime = drawTime ? drawTime.getTime() : null; // 检查是否存在相同帖子URL + 相同开奖时间的记录 isCurrentPageAdded = reminders.some(r => normalizeUrl(r.postUrl) === currentUrl && r.drawTime === currentDrawTime ); // 调试日志 if (isCurrentPageAdded) { console.log('🔴 检测到已添加的抽奖:', currentUrl, '开奖时间:', currentDrawTime); } else { console.log('🟡 检测到有效抽奖链接:', luckyUrl); } } else { console.log('⚪ 页面有抽奖相关链接但未找到有效抽奖链接'); } } catch (error) { console.error('❌ 检查页面抽奖状态失败:', error); // 出错时不显示黄色,保持默认状态 hasValidLotteryOnPage = false; } } // 检查是否有中奖记录 const hasWonLotteries = reminders.some(r => r.isWon); // 更新圆点状态 let dotStatus = ''; if (isCurrentPageAdded) { // 当前页面已添加相同开奖时间的抽奖 - 红色 dot.className = 'added'; dotStatus = '红色(已添加)'; } else if (hasValidLotteryOnPage) { // 发现有效抽奖链接但未添加 - 黄色 dot.className = 'found'; dotStatus = '黄色(发现抽奖)'; } else if (hasWonLotteries) { // 有中奖记录 - 绿色 dot.className = 'won'; dotStatus = '绿色(有中奖)'; } else { // 默认状态 - 白色 dot.className = 'default'; dotStatus = '白色(默认)'; } console.log(`🎨 圆点状态更新完成: ${dotStatus}`); } // 修改提醒数据结构 function createReminderObject(postUrl, title) { return { postUrl: postUrl, luckyUrl: null, // 抽奖链接 title: title, drawTime: null, added: Date.now(), isWon: false, // 是否中奖 checked: false // 是否已检查过中奖状态 }; } // 从页面内容中提取抽奖链接 function extractLuckyUrl(html) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); // 查找所有链接 const allLinks = doc.querySelectorAll('a'); // 遍历所有链接查找抽奖链接 for (const link of allLinks) { const href = link.href || link.getAttribute('href') || ''; // 处理各种可能的链接格式 if ( href.includes('/lucky?') || // 相对路径 href.includes('nodeseek.com/lucky?') // 完整路径 ) { // 确保返回完整的URL if (href.startsWith('/')) { return 'https://www.nodeseek.com' + href; } return href; } } // 如果在链接中没找到,尝试在文本中查找 const textContent = doc.body.textContent; const luckyUrlPattern = /https?:\/\/(?:www\.)?nodeseek\.com\/lucky\?[^\s"')>]*/g; const matches = textContent.match(luckyUrlPattern); if (matches && matches.length > 0) { return matches[0]; } // 输出调试信息 console.log('页面中的所有链接:'); allLinks.forEach(link => { console.log(link.href || link.getAttribute('href')); }); return null; } // 从抽奖链接中获取开奖时间 function getLuckyPageDrawTime(luckyUrl) { try { const url = new URL(luckyUrl); const timeParam = url.searchParams.get('time'); if (timeParam) { const timestamp = parseInt(timeParam); if (!isNaN(timestamp)) { return new Date(timestamp); } } } catch (error) { console.error('解析抽奖链接时间失败:', error); } return null; } // 获取帖子第一页链接 function getFirstPageUrl(postUrl) { try { // 移除URL中的hash部分(如果有) const urlWithoutHash = postUrl.split('#')[0]; // 匹配帖子ID和页码 const match = urlWithoutHash.match(/post-(\d+)(?:-(\d+))?/); if (match) { const postId = match[1]; // 始终返回第一页的URL return `https://www.nodeseek.com/post-${postId}-1`; } } catch (error) { console.error('处理帖子链接失败:', error); } return postUrl; } // 获取帖子第一页内容 async function fetchPostFirstPage(postUrl) { return new Promise((resolve, reject) => { const firstPageUrl = getFirstPageUrl(postUrl); console.log('获取帖子页面:', firstPageUrl); GM_xmlhttpRequest({ method: 'GET', url: firstPageUrl, onload: response => { console.log('帖子页面获取状态:', response.status); if (response.status === 200) { resolve(response.responseText); } else { reject(new Error(`获取帖子页面失败: ${response.status}`)); } }, onerror: (error) => { console.error('获取帖子页面错误:', error); reject(error); } }); }); } // 获取抽奖页面内容 - 支持动态加载的Vue.js页面 async function fetchLuckyPage(luckyUrl) { console.log('🌐 开始获取抽奖页面:', luckyUrl); // 方法1: 尝试直接在当前页面打开抽奖链接获取动态内容 return new Promise((resolve, reject) => { // 创建一个隐藏的iframe来加载抽奖页面 const iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.style.width = '0'; iframe.style.height = '0'; document.body.appendChild(iframe); let timeoutId; let resolved = false; const cleanup = () => { if (timeoutId) clearTimeout(timeoutId); if (iframe.parentNode) { iframe.parentNode.removeChild(iframe); } }; const checkContent = () => { try { const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; const body = iframeDoc.body; if (!body) { console.log('⏳ iframe body未加载,继续等待...'); return false; } const loadingElement = body.querySelector('#lucky-mount img[src*="loading"]'); const hasRealContent = body.querySelector('.rank-row, div[class*="rank-row"], a[href*="/space/"]'); const hasJsonData = body.querySelector('#temp-script[type="application/json"]'); console.log('� iframe内容检查:'); console.log(' 是否有loading图片:', !!loadingElement); console.log(' 是否有真实内容:', !!hasRealContent); console.log(' 是否有JSON数据:', !!hasJsonData); console.log(' body内容长度:', body.innerHTML.length); // 不再需要从iframe获取用户ID // 检查是否内容已加载完成:没有loading图片且(有真实内容或有JSON数据) const contentReady = !loadingElement && (hasRealContent || hasJsonData); const timeoutReady = body.innerHTML.length > 1000; // 超时备用条件 if (contentReady || timeoutReady) { console.log('✅ 检测到动态内容已加载完成'); if (!resolved) { resolved = true; cleanup(); resolve(iframeDoc.documentElement.outerHTML); } return true; } return false; } catch (error) { console.log('⚠️ 检查iframe内容时出错:', error.message); return false; } }; iframe.onload = () => { console.log('📡 iframe加载完成,开始等待动态内容...'); // 每1秒检查一次内容,减少频率 const checkInterval = setInterval(() => { if (checkContent()) { clearInterval(checkInterval); } }, 1000); // 10秒超时 timeoutId = setTimeout(() => { clearInterval(checkInterval); if (!resolved) { console.log('⏰ 等待动态内容超时,使用当前内容'); resolved = true; try { const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; cleanup(); resolve(iframeDoc.documentElement.outerHTML); } catch (error) { cleanup(); reject(new Error('获取iframe内容失败: ' + error.message)); } } }, 10000); }; iframe.onerror = () => { console.error('❌ iframe加载失败'); cleanup(); reject(new Error('iframe加载失败')); }; // 开始加载页面 iframe.src = luckyUrl; }); } // 解析中奖名单,提取中奖用户ID function parseWinnerIds(html) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); // 查找中奖名单区域 let winnerSection = doc.querySelector('div[data-v-23190e9e][style*="padding: 0px 20px 20px"]'); if (!winnerSection) { winnerSection = doc.querySelector('div[style*="padding: 0px 20px 20px"]'); } if (!winnerSection) { const rankRows = doc.querySelectorAll('.rank-row, div[class*="rank-row"]'); if (rankRows.length > 0) { winnerSection = rankRows[0].parentElement; } } if (!winnerSection) { const allDivs = doc.querySelectorAll('div'); for (let div of allDivs) { const text = div.textContent || ''; if (text.includes('中奖名单') || text.includes('中奖') || text.includes('获奖') || text.includes('winner')) { winnerSection = div; break; } } } if (!winnerSection) { // 尝试解析页面中的JSON数据获取中奖信息 const scriptElement = doc.querySelector('#temp-script[type="application/json"]'); if (scriptElement) { try { const base64Data = scriptElement.textContent; const jsonData = JSON.parse(atob(base64Data)); // 检查是否有中奖相关数据 if (jsonData.winners || jsonData.luckyUsers || jsonData.result) { const winners = jsonData.winners || jsonData.luckyUsers || jsonData.result || []; const jsonIds = []; winners.forEach(winner => { if (winner.member_id || winner.id || winner.user_id) { const id = String(winner.member_id || winner.id || winner.user_id); jsonIds.push(id); } }); if (jsonIds.length > 0) { return jsonIds; } } } catch (error) { // 静默处理JSON解析错误 } } // 尝试查找所有包含/space/链接的元素 const allSpaceLinks = doc.querySelectorAll('a[href*="/space/"]'); if (allSpaceLinks.length > 0) { const directIds = []; allSpaceLinks.forEach((link) => { const href = link.getAttribute('href'); const match = href.match(/\/space\/(\d+)/); if (match) { directIds.push(match[1]); } }); if (directIds.length > 0) { return directIds; } } return []; } // 提取所有中奖用户的ID const winnerIds = []; // 方法1: 查找/space/链接 const winnerLinks = winnerSection.querySelectorAll('a[href*="/space/"]'); winnerLinks.forEach((link) => { const href = link.getAttribute('href'); const match = href.match(/\/space\/(\d+)/); if (match) { winnerIds.push(match[1]); } }); // 方法2: 如果没找到链接,尝试从头像src中提取 if (winnerIds.length === 0) { const avatarImgs = winnerSection.querySelectorAll('img[src*="/avatar/"]'); avatarImgs.forEach((img) => { const src = img.getAttribute('src'); const match = src.match(/\/avatar\/(\d+)\.png/); if (match) { winnerIds.push(match[1]); } }); } // 方法3: 如果还是没找到,尝试正则表达式匹配整个HTML if (winnerIds.length === 0) { const spaceRegex = /\/space\/(\d+)/g; const avatarRegex = /\/avatar\/(\d+)\.png/g; let match; const foundIds = new Set(); while ((match = spaceRegex.exec(winnerSection.innerHTML)) !== null) { foundIds.add(match[1]); } while ((match = avatarRegex.exec(winnerSection.innerHTML)) !== null) { foundIds.add(match[1]); } winnerIds.push(...Array.from(foundIds)); } return winnerIds; } // 清理过期抽奖记录 function cleanExpiredLotteries() { const reminders = GM_getValue('lottery_reminders', []); const now = Date.now(); const oneDayMs = 24 * 60 * 60 * 1000; // 1天的毫秒数 // 过滤掉开奖超过1天的记录 const filteredReminders = reminders.filter(reminder => { if (!reminder.drawTime) return true; // 保留没有开奖时间的记录 const timeSinceDraw = now - reminder.drawTime; return timeSinceDraw <= oneDayMs; // 只保留开奖1天内的记录 }); // 如果有记录被删除,更新存储 if (filteredReminders.length !== reminders.length) { console.log(`清理了 ${reminders.length - filteredReminders.length} 条过期抽奖记录`); GM_setValue('lottery_reminders', filteredReminders); return true; } return false; } // 手动刷新状态 async function manualRefresh() { console.log('🔄 手动刷新触发...'); try { await updateDotStatus(); console.log('✅ 手动刷新完成'); } catch (error) { console.error('❌ 手动刷新失败:', error); } } // 清理所有抽奖记录 function clearAllLotteries() { const clearTasks = confirm('确定要清理所有抽奖记录吗?此操作不可恢复!'); if (clearTasks) { GM_setValue('lottery_reminders', []); // 询问是否同时重置统计数据 const clearStats = confirm('是否同时重置统计数据(总参与数、中奖数、中奖率)?\n\n点击"确定"重置统计数据\n点击"取消"保留统计数据'); if (clearStats) { resetIndependentStats(); console.log('已清理所有抽奖记录并重置统计数据'); } else { console.log('已清理所有抽奖记录,统计数据已保留'); } refreshLotteryList(); updateDotStatus().catch(console.error); } } // 检查中奖状态 (已集成到updateDotStatus中,保留此函数用于向后兼容) async function checkWinningStatus() { console.log('⚠️ checkWinningStatus() 已弃用,中奖检查逻辑已集成到 updateDotStatus() 中'); // 直接调用updateDotStatus来执行检查 await updateDotStatus(); } // ===== 独立统计存储管理 ===== // 获取独立统计数据 function getIndependentStats() { return GM_getValue('lottery_stats', { total: 0, // 总参与数 won: 0, // 中奖数 rate: '0.0' // 中奖率 }); } // 更新独立统计数据 function updateIndependentStats(isWon) { const stats = getIndependentStats(); // 增加总参与数 stats.total += 1; // 如果中奖,增加中奖数 if (isWon) { stats.won += 1; } // 重新计算中奖率 stats.rate = stats.total > 0 ? ((stats.won / stats.total) * 100).toFixed(1) : '0.0'; // 保存到独立存储 GM_setValue('lottery_stats', stats); console.log(`📊 更新独立统计: 总参与${stats.total}, 中奖${stats.won}, 中奖率${stats.rate}%`); return stats; } // 计算中奖统计信息(使用独立存储) function calculateWinningStats() { return getIndependentStats(); } // 重置独立统计数据 function resetIndependentStats() { const defaultStats = { total: 0, won: 0, rate: '0.0' }; GM_setValue('lottery_stats', defaultStats); console.log('🔄 已重置独立统计数据'); return defaultStats; } // 数据迁移:将现有任务数据迁移到独立统计(仅执行一次) function migrateToIndependentStats() { // 检查是否已经迁移过 const migrated = GM_getValue('stats_migrated', false); if (migrated) { return; // 已经迁移过,跳过 } console.log('🔄 开始迁移现有数据到独立统计...'); const reminders = GM_getValue('lottery_reminders', []); const checkedReminders = reminders.filter(r => r.checked); if (checkedReminders.length > 0) { const totalCount = checkedReminders.length; const wonCount = checkedReminders.filter(r => r.isWon).length; const winRate = totalCount > 0 ? ((wonCount / totalCount) * 100).toFixed(1) : '0.0'; // 设置独立统计数据 const stats = { total: totalCount, won: wonCount, rate: winRate }; GM_setValue('lottery_stats', stats); console.log(`✅ 数据迁移完成: 总参与${totalCount}, 中奖${wonCount}, 中奖率${winRate}%`); // 标记现有任务为已更新统计,避免重复计算 checkedReminders.forEach(reminder => { reminder.statsUpdated = true; }); GM_setValue('lottery_reminders', reminders); } else { console.log('📊 没有现有数据需要迁移'); } // 标记为已迁移 GM_setValue('stats_migrated', true); } // 添加当前页面抽奖 async function addCurrentPageLottery() { const url = normalizeUrl(window.location.href); const title = document.title.replace(' - NodeSeek', '') || '抽奖活动'; // 获取现有提醒列表 let reminders = GM_getValue('lottery_reminders', []); // 创建新的提醒对象 const reminder = createReminderObject(url, title); try { // 获取帖子第一页内容 const postHtml = await fetchPostFirstPage(url); const luckyUrl = extractLuckyUrl(postHtml); if (luckyUrl) { reminder.luckyUrl = luckyUrl; // 从抽奖链接中获取开奖时间 const drawTime = getLuckyPageDrawTime(luckyUrl); if (drawTime) { reminder.drawTime = drawTime.getTime(); } else { console.log('未能从抽奖链接获取开奖时间'); } // 增强的重复检查:同一帖子 + 同一开奖时间 = 同一抽奖 const existingReminder = reminders.find(r => normalizeUrl(r.postUrl) === url && r.drawTime === reminder.drawTime ); if (existingReminder) { const status = existingReminder.isWon ? '已中奖' : existingReminder.checked ? '未中奖' : '待开奖'; alert(`该抽奖已经添加过了!\n状态:${status}\n标题:${existingReminder.title}`); return; } } else { console.log('未在帖子中找到抽奖链接'); } // 添加新提醒 reminders.push(reminder); GM_setValue('lottery_reminders', reminders); // 刷新列表 refreshLotteryList(); // 更新圆点状态 updateDotStatus().catch(console.error); // 不再设置通知提醒 if (!reminder.luckyUrl) { alert('添加成功,但未找到抽奖链接!'); } else if (!reminder.drawTime) { alert('添加成功,但未找到开奖时间!请手动查看抽奖页面。'); } else { alert('添加成功!'); } } catch (error) { console.error('添加抽奖失败:', error); alert('添加失败,请重试!'); } } // 刷新抽奖列表显示 - 只显示中奖的抽奖 function refreshLotteryList() { const listContainer = document.querySelector('.lottery-list'); const statsContainer = document.querySelector('.lottery-stats'); const reminders = GM_getValue('lottery_reminders', []); // 更新统计信息 if (statsContainer) { const stats = calculateWinningStats(); statsContainer.innerHTML = ` <div class="stat-item">总参与: ${stats.total}</div> <div class="stat-item">中奖: ${stats.won}</div> <div class="stat-item">中奖率: <span class="win-rate">${stats.rate}%</span></div> <button class="refresh-btn">手动刷新</button> <button class="clear-all-btn">清理所有</button> `; // 绑定手动刷新按钮事件 const refreshBtn = statsContainer.querySelector('.refresh-btn'); if (refreshBtn) { refreshBtn.addEventListener('click', manualRefresh); } // 绑定清理按钮事件 const clearBtn = statsContainer.querySelector('.clear-all-btn'); if (clearBtn) { clearBtn.addEventListener('click', clearAllLotteries); } } // 只显示中奖的抽奖 const wonReminders = reminders.filter(reminder => reminder.isWon); if (wonReminders.length === 0) { listContainer.innerHTML = '<div style="text-align: center; color: #666; padding: 20px;">暂无中奖记录</div>'; return; } // 按开奖时间排序 const sortedReminders = [...wonReminders].sort((a, b) => { if (!a.drawTime) return 1; if (!b.drawTime) return -1; return b.drawTime - a.drawTime; // 最新的在前面 }); listContainer.innerHTML = sortedReminders.map(reminder => ` <div class="lottery-item won"> <div class="lottery-title"> ${reminder.title} <span class="lottery-won-badge">中奖</span> </div> <div class="lottery-links"> <div>帖子链接: <a href="${reminder.postUrl}" target="_blank">查看帖子</a></div> ${reminder.luckyUrl ? `<div>抽奖链接: <a href="${reminder.luckyUrl}" target="_blank">查看开奖结果</a></div>` : ''} </div> <div class="lottery-time"> ${reminder.drawTime ? `开奖时间: ${new Date(reminder.drawTime).toLocaleString('zh-CN')}` : '开奖时间未知'} </div> </div> `).join(''); } // 计算倒计时 function getCountdown(targetDate) { const now = new Date(); const diff = targetDate - now; if (diff <= 0) return '已开奖'; const days = Math.floor(diff / (1000 * 60 * 60 * 24)); const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); let countdown = ''; if (days > 0) countdown += `${days}天`; if (hours > 0) countdown += `${hours}小时`; countdown += `${minutes}分钟`; return countdown; } // 在主页标记已参加的抽奖 function markParticipatedLotteries() { const reminders = GM_getValue('lottery_reminders', []); if (reminders.length === 0) return; // 从提醒中提取帖子ID const reminderPostIds = new Set(reminders.map(r => { const match = r.postUrl.match(/post-(\d+)/); return match ? match[1] : null; }).filter(id => id)); if (reminderPostIds.size === 0) return; // 查找页面上所有的帖子链接, 使用属性选择器以提高兼容性 document.querySelectorAll('a[href*="/post-"]').forEach(link => { // 排除管理器内部的链接 if (link.closest('#lottery-manager')) { return; } // 排除指向评论的链接 if (link.href.includes('#')) { return; } const postUrl = link.href; const match = postUrl.match(/post-(\d+)/); if (!match) return; const postId = match[1]; // 检查此帖子是否已添加 if (reminderPostIds.has(postId)) { // 避免重复添加标签, 检查后面是否已经有标签了 if (link.nextElementSibling && link.nextElementSibling.classList.contains('lottery-participated-tag')) return; // 简单的启发式方法,判断是否是主标题链接 (通常标题链接文本较长, 且不是纯数字的分页链接) if (link.textContent.trim().length < 5 || /^\d+$/.test(link.textContent.trim())) return; const tag = document.createElement('span'); tag.textContent = '已参加抽奖'; tag.className = 'lottery-participated-tag'; // 插入标签 link.insertAdjacentElement('afterend', tag); } }); } // 定期更新倒计时显示 function updateCountdowns() { // 更新管理器中的倒计时 document.querySelectorAll('.lottery-item').forEach(item => { const timeElement = item.querySelector('.lottery-time'); const drawTimeStr = timeElement.textContent.match(/开奖时间: (.*?)(?=\(|$)/)[1].trim(); const drawTime = new Date(drawTimeStr); const countdownElement = item.querySelector('.lottery-countdown'); countdownElement.textContent = `(${getCountdown(drawTime)})`; }); } // 初始化 function init() { console.log('🚀 NodeSeek抽奖助手初始化开始...'); // 数据迁移(仅首次运行) migrateToIndependentStats(); // 显示配置的用户ID console.log(`👤 配置的用户ID: ${USER_ID}`); if (USER_ID === '11723') { console.log('💡 提示: 请确认用户ID是否正确'); } // 显示独立统计信息 const stats = getIndependentStats(); console.log(`📊 历史统计数据: 总参与${stats.total}次, 中奖${stats.won}次, 中奖率${stats.rate}%`); // 创建小圆点UI系统 createDotUI(); refreshLotteryList(); // 页面刷新时不自动检查,减少服务器负载 console.log('🔄 页面刷新完成,等待定时器或手动刷新...'); // 首次加载时标记,并设置观察器以处理动态加载 markParticipatedLotteries(); const debouncedMarker = debounce(markParticipatedLotteries, 1000); const debouncedUpdateDot = debounce(() => { console.log('🔄 页面内容变化,重新检查任务状态...'); updateDotStatus().catch(console.error); }, 2000); const observer = new MutationObserver((mutations) => { // 只在有意义的DOM变化时触发 const hasSignificantChange = mutations.some(mutation => mutation.type === 'childList' && mutation.addedNodes.length > 0 && Array.from(mutation.addedNodes).some(node => node.nodeType === Node.ELEMENT_NODE && (node.tagName === 'A' || node.querySelector('a')) ) ); if (hasSignificantChange) { debouncedMarker(); debouncedUpdateDot(); // 只在有链接相关变化时更新 } }); observer.observe(document.body, { childList: true, subtree: true, attributes: false, // 不监听属性变化 characterData: false // 不监听文本变化 }); // 定时器检查机制(默认5分钟) setInterval(() => { console.log('⏰ 定时器触发,检查任务状态...'); updateDotStatus().catch(console.error); }, 5 * 60 * 1000); // 每分钟更新一次倒计时 setInterval(updateCountdowns, 60000); console.log('✅ NodeSeek抽奖助手初始化完成'); } // 页面加载完成后执行 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址