您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动刷阅读回复,仅支持Linux.do社区
// ==UserScript== // @name Auto Read (Linux.do Only) // @namespace http://tampermonkey.net/ // @version 2.1.0 // @description 自动刷阅读回复,仅支持Linux.do社区 // @author XinSong(https://blog.warhut.cn)自 // @match https://linux.do/* // @grant unsafeWindow // @license MIT // @icon https://www.google.com/s2/favicons?domain=linux.do // @require https://cdn.tailwindcss.com // ==/UserScript== (() => { 'use strict'; // 挂载全局对象(避免作用域污染) const { document, window } = unsafeWindow; // 配置中心(常量集中管理) const CONFIG = { BASE_URL: 'https://linux.do', // 基础URL LIKE_LIMIT: 20, // 每日点赞上限 MAX_RETRIES: 3, // 错误页面最大重试次数 SCROLL_OPTIONS: { // 滚动配置 speed: 50, // 滚动速度(像素/次) interval: 100, // 滚动间隔(毫秒) }, LIKE_INTERVAL: { // 点赞间隔配置 min: 2000, // 最小间隔(毫秒) max: 5000 // 最大间隔(毫秒) }, UPDATE_INTERVAL: 500 // 状态更新间隔(毫秒) }; /** * 状态管理类 * 负责本地存储管理和状态初始化 */ class StateManager { constructor() { this.initState(); // 初始化默认状态 this.loadFromStorage(); // 从本地存储加载状态 } // 初始化默认状态 initState() { this.isReading = false; // 是否正在阅读 this.isLiking = false; // 是否启用自动点赞 this.errorRetries = 0; // 错误页面重试次数 this.unseenHrefs = []; // 未读帖子链接列表 this.currentTask = null; // 当前任务(导航/滚动等) this.scrollTimer = null; // 滚动定时器 } // 从localStorage加载状态 loadFromStorage() { // 解析存储的状态对象,默认空对象 const state = JSON.parse(localStorage.getItem('autoReadState')) || {}; // 合并默认状态与存储状态 Object.assign(this, { isReading: !!state.isReading, // 布尔值转换 isLiking: state.isLiking ?? false, // 安全默认值 errorRetries: state.errorRetries || 0, unseenHrefs: state.unseenHrefs || [] }); this.resetLikeCounter(); // 重置每日点赞计数 } // 保存状态到localStorage saveToStorage() { localStorage.setItem('autoReadState', JSON.stringify(this)); } // 每日点赞计数重置(超过24小时) resetLikeCounter() { const lastUpdate = localStorage.getItem('likeTimestamp'); if (lastUpdate && Date.now() - +lastUpdate > 86400000) { // 86400000ms = 24小时 localStorage.setItem('likeCount', 0); // 重置计数 localStorage.setItem('likeTimestamp', Date.now()); // 更新时间戳 } } } /** * 自动阅读核心类 * 负责业务逻辑处理和用户交互 */ class AutoReader { constructor() { this.state = new StateManager(); // 初始化状态管理器 this.init(); // 初始化脚本 } // 初始化入口 init() { window.addEventListener('load', () => { this.createControlPanel(); // 创建控制面板 this.handleRoute(); // 处理当前路由 setInterval(() => this.updateStatus(), CONFIG.UPDATE_INTERVAL); // 定期更新状态 }); } /** * 路由处理 * 根据当前页面路径执行不同逻辑 */ handleRoute() { if (window.location.pathname === '/unseen') { // 未读页面 this.fetchUnseenLinks(); // 获取未读链接 } else if (this.state.isReading) { // 阅读中状态 this.processCurrentPage(); // 处理当前页面内容 } } /** * 获取未读帖子链接 */ fetchUnseenLinks() { // 使用CSS选择器获取所有未读帖子链接 const links = Array.from(document.querySelectorAll('a.title.raw-link.raw-topic-link')) .map(link => link.getAttribute('href')); // 提取链接 if (links.length) { // 存在未读链接 this.state.unseenHrefs = links; // 更新状态 this.state.saveToStorage(); // 保存到本地 this.openNextTopic(); // 打开下一个帖子 } else { // 无未读内容 alert('未发现未读内容'); } } /** * 打开下一个帖子 */ openNextTopic() { const nextUrl = this.state.unseenHrefs.shift(); // 取出队列中第一个链接 if (nextUrl) { // 存在有效链接 this.state.currentTask = 'navigating'; // 设置任务状态为导航 this.state.saveToStorage(); // 保存状态 window.location.href = `${CONFIG.BASE_URL}${nextUrl}`; // 跳转页面 } else { // 链接队列已空 this.navigateToUnseen(); // 回到未读页面重新获取 } } /** * 处理当前页面内容(阅读逻辑) */ processCurrentPage() { if (this.isErrorPage()) return this.handleError(); // 先检查错误页面 // 判断是不是帖子详情页,如果不是,打开第一个未读链接 if (!document.querySelector('article[data-post-id]')) { this.openNextTopic(); return; } // 判断是否存在返回上次阅读的按钮 const backButton = document.querySelector('[title="返回上一个未读帖子"]'); if (backButton) { backButton.click(); // 点击按钮返回 } // 获取当前页面所有帖子 this.state.posts = Array.from(document.querySelectorAll('article[data-post-id]')); this.state.currentTask = 'scrolling'; // 设置任务状态为滚动 this.startSmoothScroll(); // 启动平滑滚动 if (this.state.isLiking) this.runAutoLike(); // 启用点赞则执行点赞逻辑 } /** * 启动平滑滚动 */ startSmoothScroll() { if (this.state.scrollTimer) return; // 避免重复启动 // 记录上一次滚动时间 let lastScrollTime = 0; // 滚动速度(像素/帧) const scrollSpeed = CONFIG.SCROLL_OPTIONS.speed; // 使用requestAnimationFrame实现平滑滚动 const scrollStep = () => { let timestamp = performance.now(); // 获取当前时间戳 // 控制滚动频率,防止过快 if (timestamp - lastScrollTime < CONFIG.SCROLL_OPTIONS.interval) { this.state.scrollTimer = requestAnimationFrame(scrollStep); return; } lastScrollTime = timestamp; // 更新上一次滚动时间 window.scrollBy(0, scrollSpeed); // 执行滚动 // 判断是否阅读完毕 const divReplies = document.querySelector('div.timeline-replies'); // 查找底部元素 if (divReplies) { const parts = divReplies.textContent.trim().replace(/[^0-9/]/g, '').split('/'); // 判断是否相等(如:1/1),表示已到达底部 if (parts.length >= 2 && parts[0] === parts[1]) { this.stopScrolling(); // 停止滚动 this.openNextTopic(); // 打开下一个帖子 return; } } this.markReadPosts(); // 标记已读帖子 this.state.scrollTimer = requestAnimationFrame(scrollStep); // 继续下一帧 }; // 开始滚动动画 this.state.scrollTimer = requestAnimationFrame(scrollStep); } /** * 停止滚动 */ stopScrolling() { if (this.state.scrollTimer) { cancelAnimationFrame(this.state.scrollTimer); // 取消动画帧 this.state.scrollTimer = null; // 重置定时器引用 } this.state.currentTask = null; // 清除当前任务 } /** * 标记可见帖子为已读 */ markReadPosts() { document.querySelectorAll('article[data-post-id]').forEach(post => { const rect = post.getBoundingClientRect(); // 获取元素位置信息 // 元素完全在视口内时并且是已读状态,标记为已读, if (rect.top >= 0 && rect.bottom <= window.innerHeight) { post.classList.add('read-state'); // 添加已读类 } }); } /** * 自动点赞逻辑(递归调用实现随机间隔) */ runAutoLike() { const likeCount = parseInt(localStorage.getItem('likeCount')) || 0; // 当前点赞数 if (likeCount >= CONFIG.LIKE_LIMIT) return; // 达到上限则停止 // 查找未点赞的按钮(优先使用明确的选择器) const likeButton = document.querySelector('.discourse-reactions-reaction-button:not(.liked)'); if (likeButton) { likeButton.click(); // 模拟点击 // 更新点赞计数和时间戳 localStorage.setItem('likeCount', likeCount + 1); localStorage.setItem('likeTimestamp', Date.now()); // 生成随机间隔(递归调用实现链式延迟) const randomDelay = Math.random() * (CONFIG.LIKE_INTERVAL.max - CONFIG.LIKE_INTERVAL.min) + CONFIG.LIKE_INTERVAL.min; setTimeout(() => this.runAutoLike(), randomDelay); } } /** * 检测是否为错误页面 * @returns {boolean} 是否为404页面 */ isErrorPage() { return document.title.includes('找不到页面'); } /** * 错误页面处理 */ handleError() { this.state.errorRetries++; // 重试次数加一 if (this.state.errorRetries > CONFIG.MAX_RETRIES) { // 超过最大重试次数 this.resetState(); // 重置所有状态 return; } this.openNextTopic(); // 尝试打开下一个帖子 } /** * 重置所有状态(用于错误处理或用户重置) */ resetState() { this.state.initState(); // 恢复初始状态 this.state.saveToStorage(); // 保存到本地 } /** * 创建控制面板 */ createControlPanel() { const controls = document.createElement('div'); // 容器元素 controls.className = 'fixed bottom-4 left-4 z-50 bg-white flex flex-col gap-2'; // 样式 // 创建阅读控制按钮 this.createControlButton(controls, 'openRead', '开始阅读', '停止阅读', () => { this.state.isReading = !this.state.isReading; // 切换阅读状态 this.state.saveToStorage(); // 保存状态 this.state.isReading ? this.processCurrentPage() : this.stopScrolling();// 根据状态执行相应操作 this.updateStatus();// 更新状态 document.getElementById('openRead').textContent = this.state.isReading ? '停止阅读' : '开始阅读';// 更新按钮文本 }); // 创建点赞控制按钮 this.createControlButton(controls, 'openUP', '启用点赞', '禁用点赞', () => { this.state.isLiking = !this.state.isLiking; // 切换点赞状态 this.state.saveToStorage(); // 保存状态 this.updateStatus(); // 更新状态 document.getElementById('openUP').textContent = this.state.isLiking ? '禁用点赞' : '启用点赞';// 更新按钮文本 }); // 创建重置列表按钮 this.createControlButton(controls, 'resetList', '重置列表', '重置列表', () => { if (confirm('确定要重置未读列表吗?')) { // 确认提示 this.resetState(); // 重置状态 alert('未读列表已重置'); } }); // 创建状态显示面板 const status = document.createElement('div'); // 状态面板 status.id = 'auto-read-status'; // 唯一ID // 在按钮的上面显示,并且在左侧顶上 status.className = 'fixed top-20 left-5 z-9999 bg-white shadow-lg rounded-lg p-2 flex flex-col gap-1'; controls.appendChild(status); // 添加到控制面板 this.updateStatus(); // 初始化状态显示 document.body.appendChild(controls); // 添加到页面 } /** * 创建通用控制按钮 * @param {HTMLElement} parent - 父容器 * @param {string} id - 唯一ID * @param {string} startText - 初始文本 * @param {string} stopText - 激活后文本 * @param {Function} onClick - 点击事件处理函数 */ createControlButton(parent, id, startText, stopText, onClick) { const button = document.createElement('button'); // 创建按钮元素 // 基础样式 button.id = id; button.className = 'px-4 py-2 rounded-lg shadow-lg hover:scale-105 transition-all duration-300 bg-white text-black font-bold'; // 初始文本(根据当前状态判断) button.textContent = this.state.isReading && startText === '开始阅读' ? stopText : startText; button.addEventListener('click', onClick); // 绑定点击事件 parent.appendChild(button); // 添加到父容器 } /** * 更新状态显示面板 */ updateStatus() { const status = document.getElementById('auto-read-status'); if (!status) return; // 面板不存在时返回 const likeCount = parseInt(localStorage.getItem('likeCount')) || 0; // 获取点赞计数 // 使用模板字符串更新面板内容 status.innerHTML = ` <div class="font-bold text-sm"> 阅读状态:${this.state.isReading ? '<span class="text-green-600">运行中</span>' : '<span class="text-red-600">已停止</span>'}<br /> 点赞状态:${this.state.isLiking ? '<span class="text-green-600">启用</span>' : '<span class="text-red-600">禁用</span>'}<br /> 今日点赞:${likeCount}/${CONFIG.LIKE_LIMIT}<br /> 剩余帖子:${this.state.unseenHrefs.length}<br /> 错误重试:<span class="text-red-600">${this.state.errorRetries}/${CONFIG.MAX_RETRIES}</span> </div> `; } /** * 导航到未读页面 */ navigateToUnseen() { window.location.href = `${CONFIG.BASE_URL}/unseen`; // 跳转URL } } // 初始化脚本入口 new AutoReader(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址