您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动刷nodeloc.com文章
// ==UserScript== // @name Auto Read Nodeloc.com Ultra(自动阅读,点赞) // @namespace http://tampermonkey.net/ // @version 2.1.1 // @description 自动刷nodeloc.com文章 // @author yuanly666 // @match https://meta.discourse.org/* // @match https://linux.do/* // @match https://www.nodeloc.com/* // @match https://meta.appinn.net/* // @match https://community.openai.com/* // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_notification // @license MIT // @icon https://www.google.com/s2/favicons?domain=nodeloc.com // ==/UserScript== (function () { 'use strict'; // 配置常量 const config = { commentLimit: 1000, topicListLimit: 50, likeLimit: 50, defaultScrollSpeed: 40, minScrollSpeed: 1, maxScrollSpeed: 200, scrollStep: 1, scrollDelay: 30, checkDelay: 800, likeInterval: 2500, retryDelay: 3000, maxRetries: 3 }; // 站点匹配 const possibleBaseURLs = [ "https://www.nodeloc.com", "https://linux.do", "https://meta.discourse.org", "https://meta.appinn.net", "https://community.openai.com" ]; // 获取当前BASE_URL const currentURL = window.location.href; let BASE_URL = possibleBaseURLs.find(url => currentURL.startsWith(url)) || possibleBaseURLs[0]; // 初始化存储 function initStorage() { if (GM_getValue("isFirstRun") === undefined) { GM_setValue("read", false); GM_setValue("autoLikeEnabled", false); GM_setValue("clickCounter", 0); GM_setValue("clickCounterTimestamp", Date.now()); GM_setValue("scrollSpeed", config.defaultScrollSpeed); GM_setValue("isFirstRun", false); GM_setValue("topicList", JSON.stringify([])); GM_setValue("latestPage", 0); } // 每日重置计数器 const currentTime = Date.now(); const storedTime = GM_getValue("clickCounterTimestamp") || new Date("1999-01-01T00:00:00Z").getTime(); if (currentTime - storedTime > 24 * 60 * 60 * 1000) { GM_setValue("clickCounter", 0); GM_setValue("clickCounterTimestamp", currentTime); } } // 创建UI面板 - 确保显示的版本 function createUIPanel() { // 先移除可能存在的旧元素 removeExistingElements(); // 添加确保显示的全局样式 GM_addStyle(` #autoReadPanel { display: block !important; opacity: 1 !important; visibility: visible !important; position: fixed !important; bottom: 30px !important; left: 30px !important; z-index: 2147483647 !important; background: rgba(255, 255, 255, 0.98) !important; border-radius: 16px !important; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15) !important; padding: 20px !important; width: 320px !important; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif !important; border: 1px solid rgba(0, 0, 0, 0.08) !important; backdrop-filter: blur(10px) !important; transform: none !important; } #autoReadPanel.minimized { width: 50px !important; height: 50px !important; padding: 0 !important; overflow: hidden !important; } #autoReadPanel.minimized .panel-body { display: none !important; } #showPanelBtn { display: flex !important; position: fixed !important; bottom: 20px !important; left: 20px !important; width: 50px !important; height: 50px !important; border-radius: 50% !important; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; color: white !important; border: none !important; box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2) !important; cursor: pointer !important; align-items: center !important; justify-content: center !important; font-size: 20px !important; z-index: 2147483646 !important; } `); // 创建主面板 const panel = document.createElement('div'); panel.id = 'autoReadPanel'; panel.innerHTML = ` <div class="panel-header"> <h3 class="panel-title"> <span class="status-indicator ${GM_getValue("read") ? 'status-active' : 'status-inactive'}"></span> <span>自动阅读控制</span> </h3> <div class="panel-controls"> <button class="panel-btn minimize-btn" title="最小化">−</button> <button class="panel-btn close-btn" title="隐藏面板">×</button> </div> </div> <div class="panel-body"> <div class="control-group"> <div class="btn-group"> <button id="toggleReadBtn" class="btn btn-primary"> ${GM_getValue("read") ? '停止阅读' : '开始阅读'} </button> <button id="toggleSpeedControlBtn" class="btn btn-icon btn-secondary" title="速度设置"> ⚙️ </button> </div> </div> <div class="speed-control" id="speedControl"> <div class="progress-label"> <span>滚动速度控制</span> <span class="speed-value" id="speedValueDisplay">${GM_getValue("scrollSpeed") || config.defaultScrollSpeed}</span> </div> <div class="speed-slider-container"> <span style="color: #718096; font-size: 12px;">1</span> <input type="range" min="${config.minScrollSpeed}" max="${config.maxScrollSpeed}" value="${GM_getValue("scrollSpeed") || config.defaultScrollSpeed}" step="${config.scrollStep}" class="speed-slider" id="speedSlider"> <span style="color: #718096; font-size: 12px;">200</span> </div> <div class="speed-labels"> <span>超慢</span> <span>慢</span> <span>中</span> <span>快</span> <span>超快</span> </div> <div class="speed-presets"> <button class="speed-preset-btn" data-speed="10">慢速 (10)</button> <button class="speed-preset-btn" data-speed="40">中速 (40)</button> <button class="speed-preset-btn" data-speed="80">快速 (80)</button> <button class="speed-preset-btn" data-speed="150">极速 (150)</button> </div> </div> <div class="control-group"> <button id="toggleLikeBtn" class="btn btn-secondary" style="width: 100%;"> ${GM_getValue("autoLikeEnabled") ? '禁用自动点赞' : '启用自动点赞'} </button> </div> <div class="progress-container" id="likeProgressContainer" style="${GM_getValue("autoLikeEnabled") ? '' : 'display: none;'}"> <div class="progress-label"> <span>今日点赞进度</span> <span id="likeProgressText">${GM_getValue("clickCounter") || 0}/${config.likeLimit}</span> </div> <div class="progress-bar"> <div class="progress-fill" id="likeProgressFill" style="width: ${(GM_getValue("clickCounter") || 0) / config.likeLimit * 100}%"></div> </div> </div> <div class="stats"> <div> <span class="site-indicator" style="background-color: ${getSiteColor(BASE_URL)}"></span> <span>当前站点: ${BASE_URL.replace('https://', '')}</span> </div> <div id="pageStatus">准备就绪</div> </div> </div> `; document.body.appendChild(panel); // 创建显示面板的浮动按钮 const showPanelBtn = document.createElement('button'); showPanelBtn.id = 'showPanelBtn'; showPanelBtn.innerHTML = '⚙️'; showPanelBtn.style.display = 'none'; document.body.appendChild(showPanelBtn); // 添加拖拽功能 makeDraggable(panel); // 添加事件监听器 panel.querySelector('.minimize-btn').addEventListener('click', () => { panel.classList.toggle('minimized'); }); panel.querySelector('.close-btn').addEventListener('click', () => { panel.style.display = 'none'; showPanelBtn.style.display = 'flex'; }); showPanelBtn.addEventListener('click', () => { panel.style.display = 'block'; showPanelBtn.style.display = 'none'; }); document.getElementById('toggleReadBtn').addEventListener('click', toggleRead); document.getElementById('toggleLikeBtn').addEventListener('click', toggleAutoLike); document.getElementById('toggleSpeedControlBtn').addEventListener('click', () => { const speedControl = document.getElementById('speedControl'); speedControl.classList.toggle('active'); }); const speedSlider = document.getElementById('speedSlider'); const speedValueDisplay = document.getElementById('speedValueDisplay'); speedSlider.addEventListener('input', () => { const speed = parseInt(speedSlider.value); speedValueDisplay.textContent = speed; GM_setValue("scrollSpeed", speed); updatePresetHighlight(speed); if (GM_getValue("read") && scrollInterval) { stopScrolling(); startScrolling(); } updateStatus(`滚动速度已设置为: ${speed}`); }); document.querySelectorAll('.speed-preset-btn').forEach(btn => { btn.addEventListener('click', function() { const speed = parseInt(this.dataset.speed); document.getElementById('speedSlider').value = speed; document.getElementById('speedValueDisplay').textContent = speed; GM_setValue("scrollSpeed", speed); const event = new Event('input', { bubbles: true }); document.getElementById('speedSlider').dispatchEvent(event); updateStatus(`已设置预设速度: ${speed}`); }); }); const currentSpeed = GM_getValue("scrollSpeed") || config.defaultScrollSpeed; updatePresetHighlight(currentSpeed); // 确保面板显示在最前面 panel.style.zIndex = '2147483647'; } function removeExistingElements() { const elements = [ '#autoReadPanel', '#showPanelBtn', 'style[data-auto-read-style]' ].forEach(selector => { const el = document.querySelector(selector); if (el) el.remove(); }); } function updatePresetHighlight(speed) { const presets = [10, 40, 80, 150]; const closestPreset = presets.reduce((prev, curr) => Math.abs(curr - speed) < Math.abs(prev - speed) ? curr : prev ); document.querySelectorAll('.speed-preset-btn').forEach(btn => { btn.classList.toggle('active', parseInt(btn.dataset.speed) === closestPreset); }); } function getSiteColor(url) { const colors = { 'www.nodeloc.com': '#FF6B6B', 'linux.do': '#4ECDC4', 'meta.discourse.org': '#45B7D1', 'meta.appinn.net': '#FFA07A', 'community.openai.com': '#9B59B6' }; return colors[url.replace('https://', '')] || '#95a5a6'; } function makeDraggable(element) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; const header = element.querySelector('.panel-header'); header.onmousedown = dragMouseDown; function dragMouseDown(e) { e = e || window.event; e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; element.style.top = (element.offsetTop - pos2) + "px"; element.style.left = (element.offsetLeft - pos1) + "px"; } function closeDragElement() { document.onmouseup = null; document.onmousemove = null; } } function toggleRead() { const currentlyReading = GM_getValue("read"); const newReadState = !currentlyReading; GM_setValue("read", newReadState); const btn = document.getElementById('toggleReadBtn'); const statusIndicator = document.querySelector('.panel-header .status-indicator'); btn.textContent = newReadState ? '停止阅读' : '开始阅读'; statusIndicator.className = `status-indicator ${newReadState ? 'status-active' : 'status-inactive'}`; updateStatus(newReadState ? '自动阅读已启动' : '自动阅读已停止'); if (!newReadState) { stopScrolling(); } else { if (BASE_URL == "https://www.nodeloc.com") { window.location.href = "https://www.nodeloc.com/t/topic/54798/1"; } else { window.location.href = `${BASE_URL}/latest`; } startScrolling(); } } function toggleAutoLike() { const currentlyEnabled = GM_getValue("autoLikeEnabled"); const newEnabledState = !currentlyEnabled; GM_setValue("autoLikeEnabled", newEnabledState); const btn = document.getElementById('toggleLikeBtn'); const progressContainer = document.getElementById('likeProgressContainer'); btn.textContent = newEnabledState ? '禁用自动点赞' : '启用自动点赞'; progressContainer.style.display = newEnabledState ? 'block' : 'none'; updateStatus(newEnabledState ? '自动点赞已启用' : '自动点赞已禁用'); if (newEnabledState) { autoLike(); } else { stopAutoLike(); } } function updateStatus(message) { const statusElement = document.getElementById('pageStatus'); if (statusElement) { statusElement.textContent = message; } } let scrollInterval = null; let checkScrollTimeout = null; let autoLikeInterval = null; function startScrolling() { if (scrollInterval) clearInterval(scrollInterval); const speed = GM_getValue("scrollSpeed") || config.defaultScrollSpeed; scrollInterval = setInterval(() => { window.scrollBy(0, speed); }, config.scrollDelay); checkScroll(); } function stopScrolling() { if (scrollInterval) { clearInterval(scrollInterval); scrollInterval = null; } if (checkScrollTimeout) { clearTimeout(checkScrollTimeout); checkScrollTimeout = null; } } function checkScroll() { if (!GM_getValue("read")) return; const isAtBottom = () => { const scrollPosition = window.scrollY || window.pageYOffset; const windowHeight = window.innerHeight; const documentHeight = Math.max( document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight ); return (windowHeight + scrollPosition >= documentHeight - 100) || (documentHeight <= windowHeight + 200); }; if (isAtBottom()) { updateStatus('已到达页面底部,正在准备下一篇文章...'); stopScrolling(); setTimeout(async () => { const success = await openNewTopic(); if (!success) { updateStatus('获取新文章失败,3秒后重试...'); setTimeout(checkScroll, config.retryDelay); } }, 500); } else { if (checkScrollTimeout) clearTimeout(checkScrollTimeout); checkScrollTimeout = setTimeout(checkScroll, config.checkDelay); } } async function openNewTopic() { try { let topicList = JSON.parse(GM_getValue("topicList") || "[]"); if (topicList.length === 0) { updateStatus('正在获取最新文章列表...'); await getLatestTopic(); topicList = JSON.parse(GM_getValue("topicList") || "[]"); if (topicList.length === 0) { updateStatus('没有可用的新文章'); return false; } } const topic = topicList.shift(); GM_setValue("topicList", JSON.stringify(topicList)); const topicUrl = topic.last_read_post_number ? `${BASE_URL}/t/${topic.slug || 'topic'}/${topic.id}/${topic.last_read_post_number}` : `${BASE_URL}/t/${topic.slug || 'topic'}/${topic.id}`; window.location.href = topicUrl; return true; } catch (error) { console.error('跳转失败:', error); updateStatus('跳转出错: ' + error.message); return false; } } async function getLatestTopic() { return new Promise((resolve) => { let latestPage = parseInt(GM_getValue("latestPage") || 0); let topicList = []; let isDataSufficient = false; let retryCount = 0; const fetchPage = () => { latestPage++; const url = `${BASE_URL}/latest.json?no_definitions=true&page=${latestPage}`; $.ajax({ url: url, success: function(result) { if (result?.topic_list?.topics?.length > 0) { result.topic_list.topics.forEach(topic => { if (config.commentLimit > topic.posts_count) { topicList.push(topic); } }); if (topicList.length >= config.topicListLimit) { isDataSufficient = true; } } if (isDataSufficient || !result?.topic_list?.topics?.length) { GM_setValue("topicList", JSON.stringify(topicList)); GM_setValue("latestPage", latestPage); resolve(); } else { fetchPage(); } }, error: function(xhr, status, error) { console.error('获取话题列表失败:', status, error); if (retryCount < config.maxRetries) { retryCount++; updateStatus(`获取列表失败,第${retryCount}次重试...`); setTimeout(fetchPage, config.retryDelay); } else { GM_setValue("topicList", JSON.stringify(topicList)); resolve(); } } }); }; fetchPage(); }); } function autoLike() { const clickCounter = GM_getValue("clickCounter") || 0; if (clickCounter >= config.likeLimit) { updateStatus(`今日点赞已达上限 (${config.likeLimit})`); GM_setValue("autoLikeEnabled", false); document.getElementById('toggleLikeBtn').textContent = '启用自动点赞'; document.getElementById('likeProgressContainer').style.display = 'none'; return; } const buttons = document.querySelectorAll('.discourse-reactions-reaction-button, .like-button'); if (buttons.length === 0) { updateStatus('未找到点赞按钮,5秒后重试...'); setTimeout(autoLike, 5000); return; } let likesPerformed = 0; buttons.forEach((button, index) => { if ((button.title !== "点赞此帖子" && button.title !== "Like this post" && !button.classList.contains('like-button')) || (GM_getValue("clickCounter") || 0) >= config.likeLimit) { return; } setTimeout(() => { if ((GM_getValue("clickCounter") || 0) >= config.likeLimit) return; try { button.click(); likesPerformed++; const newCount = (GM_getValue("clickCounter") || 0) + 1; GM_setValue("clickCounter", newCount); const progressText = document.getElementById('likeProgressText'); const progressFill = document.getElementById('likeProgressFill'); if (progressText && progressFill) { progressText.textContent = `${newCount}/${config.likeLimit}`; progressFill.style.width = `${(newCount / config.likeLimit) * 100}%`; } updateStatus(`已点赞 ${likesPerformed}/${buttons.length} 个按钮 (今日 ${newCount}/${config.likeLimit})`); if (newCount >= config.likeLimit) { updateStatus(`今日点赞已达上限 (${config.likeLimit})`); GM_setValue("autoLikeEnabled", false); document.getElementById('toggleLikeBtn').textContent = '启用自动点赞'; document.getElementById('likeProgressContainer').style.display = 'none'; } } catch (error) { console.error('点赞失败:', error); } }, index * config.likeInterval); }); } function stopAutoLike() { if (autoLikeInterval) { clearTimeout(autoLikeInterval); autoLikeInterval = null; } } function init() { initStorage(); createUIPanel(); if (GM_getValue("read")) { startScrolling(); } if (GM_getValue("autoLikeEnabled")) { autoLike(); } } if (typeof GM_registerMenuCommand !== 'undefined') { GM_registerMenuCommand('打开控制面板', () => { const panel = document.getElementById('autoReadPanel'); if (panel) { panel.style.display = 'block'; document.getElementById('showPanelBtn').style.display = 'none'; } }); GM_registerMenuCommand('重置点赞计数器', () => { GM_setValue("clickCounter", 0); GM_setValue("clickCounterTimestamp", Date.now()); updateStatus('点赞计数器已重置'); const progressText = document.getElementById('likeProgressText'); const progressFill = document.getElementById('likeProgressFill'); if (progressText && progressFill) { progressText.textContent = `0/${config.likeLimit}`; progressFill.style.width = '0%'; } }); } // 确保脚本在页面完全加载后运行 if (document.readyState === 'complete') { init(); } else { window.addEventListener('load', init); // 双重保险 setTimeout(init, 2000); } // 额外检查:5秒后如果面板仍未显示,强制显示 setTimeout(() => { if (!document.getElementById('autoReadPanel')) { console.warn('面板未正常加载,正在强制创建...'); createUIPanel(); } }, 5000); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址