您需要先安装一个扩展,例如 篡改猴、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或关注我们的公众号极客氢云获取最新地址