您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在NodeSeek论坛方便地管理抽奖活动并获取开奖提醒
// ==UserScript== // @name NodeSeek抽奖提醒助手 // @namespace https://nodeseek.com/ // @version 0.4 // @description 在NodeSeek论坛方便地管理抽奖活动并获取开奖提醒 // @author Your name // @match https://nodeseek.com/* // @match https://www.nodeseek.com/* // @grant GM_notification // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // ==/UserScript== (function() { 'use strict'; // 防抖函数 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-reminder { background: rgba(255, 255, 255, 0.95); border: 1px solid #e3e3e3; border-radius: 4px; padding: 8px 12px; margin: 10px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .lottery-title { font-weight: bold; color: #333; margin-bottom: 5px; } .lottery-time { color: #666; font-size: 0.9em; } .lottery-countdown { color: #ff6b6b; font-weight: bold; } #lottery-manager { position: fixed; right: 20px; bottom: 20px; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); width: 300px; /* Base width */ z-index: 9999; font-size: 14px; transform-origin: bottom; /* Set scale origin to bottom-center */ } #lottery-manager.minimized { width: auto; height: auto; transform: none !important; /* Do not scale when minimized */ } .lottery-manager-header { background: #f8f9fa; padding: 10px; border-radius: 8px 8px 0 0; cursor: move; display: flex; justify-content: space-between; align-items: center; color: #333; } .lottery-manager-content { padding: 15px; max-height: 400px; overflow-y: auto; } .lottery-manager-footer { padding: 10px; border-top: 1px solid #eee; text-align: right; } .lottery-list { margin-bottom: 15px; } .lottery-item { padding: 8px; border: 1px solid #eee; border-radius: 4px; margin-bottom: 8px; } .lottery-item:hover { background: #f8f9fa; } .lottery-delete { color: #dc3545; cursor: pointer; float: right; } #lottery-manager .btn { padding: 6px 12px; border-radius: 4px; border: 1px solid #ddd; background: #fff; cursor: pointer; margin-left: 8px; color: #333; } #lottery-manager .btn-primary { background: #007bff; color: white; border-color: #0056b3; } #lottery-manager .btn-toggle { padding: 4px 8px; font-size: 12px; } #lottery-manager .btn-add-current { width: 100%; margin: 0 0 10px 0; background: #28a745; color: white; border-color: #28a745; } #lottery-manager.minimized .lottery-manager-content, #lottery-manager.minimized .lottery-manager-footer { display: none; } .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 createManagerUI() { const manager = document.createElement('div'); manager.id = 'lottery-manager'; // 获取保存的位置 const savedLeft = GM_getValue('ui_pos_left'); const savedTop = GM_getValue('ui_pos_top'); if (savedLeft && savedTop) { manager.style.left = savedLeft; manager.style.top = savedTop; manager.style.right = 'auto'; manager.style.bottom = 'auto'; } // 获取保存的缩放比例 const savedScale = GM_getValue('ui_scale', 1); manager.style.transform = `scale(${savedScale})`; // 获取默认状态设置 const isMinimized = GM_getValue('ui_minimized', true); if (isMinimized) { manager.classList.add('minimized'); } manager.innerHTML = ` <div class="lottery-manager-header"> <span>抽奖提醒管理器</span> <button class="btn btn-toggle">${isMinimized ? '展开' : '最小化'}</button> </div> <div class="lottery-manager-content"> <button class="btn btn-add-current">添加当前页面抽奖</button> <div class="lottery-list"></div> </div> <div class="lottery-manager-footer"> <div class="settings"> <label> <input type="checkbox" id="default-minimized" ${isMinimized ? 'checked' : ''}> 默认最小化 </label> <div id="scale-control" style="margin-left: 10px; font-size: 12px; display: flex; align-items: center; gap: 5px;"> 缩放: <input type="range" id="scale-slider" min="0.5" max="1.5" step="0.05" style="width: 80px;"> <span id="scale-value">100%</span> </div> </div> <div> <button class="btn" id="reset-btn">重置位置</button> <button class="btn" id="refresh-btn">刷新列表</button> </div> </div> `; document.body.appendChild(manager); // 添加设置相关的样式 GM_addStyle(` #lottery-manager .lottery-manager-footer { display: flex; justify-content: space-between; align-items: center; padding: 10px; } #lottery-manager .settings { display: flex; align-items: center; } #lottery-manager .settings label { display: flex; align-items: center; gap: 5px; cursor: pointer; color: #333; } #lottery-manager .settings input[type="checkbox"] { cursor: pointer; } `); // 使管理器可拖动 makeDraggable(manager); // 绑定事件 const toggleBtn = manager.querySelector('.btn-toggle'); toggleBtn.addEventListener('click', () => { manager.classList.toggle('minimized'); toggleBtn.textContent = manager.classList.contains('minimized') ? '展开' : '最小化'; }); // 绑定设置事件 const defaultMinimizedCheckbox = manager.querySelector('#default-minimized'); defaultMinimizedCheckbox.addEventListener('change', (e) => { GM_setValue('ui_minimized', e.target.checked); }); const addCurrentBtn = manager.querySelector('.btn-add-current'); addCurrentBtn.addEventListener('click', addCurrentPageLottery); const refreshBtn = manager.querySelector('#refresh-btn'); refreshBtn.addEventListener('click', refreshLotteryList); const resetBtn = manager.querySelector('#reset-btn'); resetBtn.addEventListener('click', () => { GM_setValue('ui_pos_left', null); GM_setValue('ui_pos_top', null); GM_setValue('ui_scale', 1); alert('管理器位置和大小已重置,页面将刷新。'); location.reload(); }); // 缩放控制逻辑 const scaleSlider = manager.querySelector('#scale-slider'); const scaleValueDisplay = manager.querySelector('#scale-value'); scaleSlider.value = savedScale; scaleValueDisplay.textContent = `${Math.round(savedScale * 100)}%`; scaleSlider.addEventListener('input', () => { const newScale = scaleSlider.value; manager.style.transform = `scale(${newScale})`; scaleValueDisplay.textContent = `${Math.round(newScale * 100)}%`; GM_setValue('ui_scale', newScale); }); return manager; } // 修改提醒数据结构 function createReminderObject(postUrl, title) { return { postUrl: postUrl, luckyUrl: null, // 抽奖链接 title: title, drawTime: null, added: Date.now() }; } // 从页面内容中提取抽奖链接 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(html, 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); } }); }); } // 获取抽奖页面内容 async function fetchLuckyPage(luckyUrl) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: luckyUrl, onload: response => resolve(response.responseText), onerror: reject }); }); } // 检查并设置临近开奖提醒 function checkUpcomingDraws() { const reminders = GM_getValue('lottery_reminders', []); const now = Date.now(); reminders.forEach(reminder => { if (!reminder.drawTime) return; const timeUntilDraw = reminder.drawTime - now; // 如果距离开奖时间不到1分钟且没有提醒过 if (timeUntilDraw > 0 && timeUntilDraw <= 60000 && !reminder.nearDrawNotified) { // 标记为已提醒 reminder.nearDrawNotified = true; GM_setValue('lottery_reminders', reminders); // 发送通知 GM_notification({ title: '抽奖即将开奖', text: `${reminder.title}\n还有不到1分钟就要开奖了!`, timeout: 0, // 不自动关闭 onclick: () => { // 点击通知时打开抽奖链接 if (reminder.luckyUrl) { window.open(reminder.luckyUrl, '_blank'); } } }); } }); } // 添加当前页面抽奖 async function addCurrentPageLottery() { const url = window.location.href; const title = document.title.replace(' - NodeSeek', '') || '抽奖活动'; // 获取现有提醒列表 let reminders = GM_getValue('lottery_reminders', []); // 检查是否已经添加过 if (reminders.some(r => r.postUrl === url)) { alert('该抽奖已经添加过了!'); return; } // 创建新的提醒对象 const reminder = createReminderObject(url, title); try { // 获取帖子第一页内容 const postHtml = await fetchPostFirstPage(url); const luckyUrl = extractLuckyUrl(postHtml); if (luckyUrl) { reminder.luckyUrl = luckyUrl; // 从抽奖链接中获取开奖时间 const drawTime = getLuckyPageDrawTime(null, luckyUrl); if (drawTime) { reminder.drawTime = drawTime.getTime(); } else { console.log('未能从抽奖链接获取开奖时间'); } } else { console.log('未在帖子中找到抽奖链接'); } // 添加新提醒 reminders.push(reminder); GM_setValue('lottery_reminders', reminders); // 刷新列表 refreshLotteryList(); // 设置提醒 if (reminder.drawTime) { const timeUntilDraw = reminder.drawTime - Date.now(); if (timeUntilDraw > 0) { // 开奖提醒 setTimeout(() => { GM_notification({ title: '抽奖开奖提醒', text: `${title}\n已经开奖了!`, timeout: 0, // 不自动关闭 onclick: () => { // 点击通知时打开抽奖链接 if (reminder.luckyUrl) { window.open(reminder.luckyUrl, '_blank'); } } }); }, timeUntilDraw); } } if (!reminder.luckyUrl) { alert('添加成功,但未找到抽奖链接!'); } else if (!reminder.drawTime) { alert('添加成功,但未找到开奖时间!请手动查看抽奖页面。'); } else { alert('添加成功!'); } } catch (error) { console.error('添加抽奖失败:', error); alert('添加失败,请重试!'); } } // 使元素可拖动 function makeDraggable(element) { const header = element.querySelector('.lottery-manager-header'); let isDragging = false; let currentX; let currentY; let initialX; let initialY; header.addEventListener('mousedown', dragStart); document.addEventListener('mousemove', drag); document.addEventListener('mouseup', dragEnd); function dragStart(e) { // Do not drag if the element is minimized and scaled down if (element.classList.contains('minimized')) return; initialX = e.clientX - element.offsetLeft; initialY = e.clientY - element.offsetTop; isDragging = true; } function drag(e) { if (isDragging) { e.preventDefault(); // Calculate new theoretical position let newX = e.clientX - initialX; let newY = e.clientY - initialY; // Temporarily apply to calculate boundaries with getBoundingClientRect element.style.left = `${newX}px`; element.style.top = `${newY}px`; // Get visual dimensions and position const rect = element.getBoundingClientRect(); const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; // Check and correct boundaries if (rect.left < 0) { newX -= rect.left; } if (rect.top < 0) { newY -= rect.top; } if (rect.right > viewportWidth) { newX -= (rect.right - viewportWidth); } if (rect.bottom > viewportHeight) { newY -= (rect.bottom - viewportHeight); } // Apply the corrected position element.style.left = `${newX}px`; element.style.top = `${newY}px`; element.style.right = 'auto'; element.style.bottom = 'auto'; } } function dragEnd() { if (isDragging) { isDragging = false; // 保存位置 GM_setValue('ui_pos_left', element.style.left); GM_setValue('ui_pos_top', element.style.top); } } } // 刷新抽奖列表显示 function refreshLotteryList() { const listContainer = document.querySelector('.lottery-list'); const reminders = GM_getValue('lottery_reminders', []); // 按开奖时间排序 const sortedReminders = [...reminders].sort((a, b) => { // 如果没有开奖时间的放到最后 if (!a.drawTime) return 1; if (!b.drawTime) return -1; return a.drawTime - b.drawTime; }); listContainer.innerHTML = sortedReminders.map(reminder => ` <div class="lottery-item"> <span class="lottery-delete" data-url="${reminder.postUrl}">×</span> <div class="lottery-title">${reminder.title}</div> <div class="lottery-links"> <div>帖子链接: <a href="${reminder.postUrl}" target="_blank">${reminder.postUrl}</a></div> ${reminder.luckyUrl ? `<div>抽奖链接: <a href="${reminder.luckyUrl}" target="_blank">${reminder.luckyUrl}</a></div>` : ''} </div> <div class="lottery-time"> ${reminder.drawTime ? ` 开奖时间: ${new Date(reminder.drawTime).toLocaleString('zh-CN')} <span class="lottery-countdown">(${getCountdown(new Date(reminder.drawTime))})</span> ` : '未找到开奖时间'} </div> </div> `).join(''); // 绑定删除事件 document.querySelectorAll('.lottery-delete').forEach(btn => { btn.addEventListener('click', (e) => { const url = e.target.dataset.url; const reminders = GM_getValue('lottery_reminders', []); const newReminders = reminders.filter(r => r.postUrl !== url); GM_setValue('lottery_reminders', newReminders); refreshLotteryList(); }); }); } // 计算倒计时 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() { createManagerUI(); refreshLotteryList(); // 注册(不可用)菜单命令 GM_registerMenuCommand('重置管理器位置和大小', () => { GM_setValue('ui_pos_left', null); GM_setValue('ui_pos_top', null); GM_setValue('ui_scale', 1); alert('管理器位置和大小已重置,页面将刷新。'); location.reload(); }); // 首次加载时标记,并设置观察器以处理动态加载 markParticipatedLotteries(); const debouncedMarker = debounce(markParticipatedLotteries, 200); const observer = new MutationObserver(debouncedMarker); observer.observe(document.body, { childList: true, subtree: true }); // 每30秒检查一次临近开奖 setInterval(checkUpcomingDraws, 30000); // 每分钟更新一次倒计时 setInterval(updateCountdowns, 60000); } // 页面加载完成后执行 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址