- // ==UserScript==
- // @name 自动浏览linux.do,autoBrowse-linux.do
- // @description 自动浏览linux.do的帖子和话题,智能滚动和加载检测
- // @namespace http://tampermonkey.net/
- // @match https://linux.do/*
- // @grant none
- // @version 1.2.4
- // @author quantumcat
- // @license MIT
- // @icon https://www.google.com/s2/favicons?domain=linux.do
- // ==/UserScript==
-
- // 配置项
- const CONFIG = {
- scroll: {
- minSpeed: 10,
- maxSpeed: 15,
- minDistance: 2,
- maxDistance: 4,
- checkInterval: 500,
- fastScrollChance: 0.08,
- fastScrollMin: 80,
- fastScrollMax: 200
- },
- time: {
- browseTime: 3600000,
- restTime: 600000,
- minPause: 300,
- maxPause: 500,
- loadWait: 1500,
- },
- article: {
- commentLimit: 1000,
- topicListLimit: 100,
- retryLimit: 3
- },
-
- mustRead: {
- posts: [
- {
- id: '1051',
- url: 'https://linux.do/t/topic/1051/'
- },
- {
- id: '5973',
- url: 'https://linux.do/t/topic/5973'
- },
- // 在这里添加更多文章
- {
- id: '102770',
- url: 'https://linux.do/t/topic/102770'
- },
- // 示例格式
- {
- id: '154010',
- url: 'https://linux.do/t/topic/154010'
- },
- {
- id: '149576',
- url: 'https://linux.do/t/topic/149576'
- },
- {
- id: '22118',
- url: 'https://linux.do/t/topic/22118'
- },
- ],
- likesNeeded: 5 // 需要点赞的数量
- }
- };
-
- // 工具函数
- const Utils = {
- random: (min, max) => Math.floor(Math.random() * (max - min + 1)) + min,
-
- sleep: (ms) => new Promise(resolve => setTimeout(resolve, ms)),
-
- isPageLoaded: () => {
- const loadingElements = document.querySelectorAll('.loading, .infinite-scroll');
- return loadingElements.length === 0;
- },
-
- isNearBottom: () => {
- const {scrollHeight, clientHeight, scrollTop} = document.documentElement;
- return (scrollTop + clientHeight) >= (scrollHeight - 200);
- },
-
- debounce: (func, wait) => {
- let timeout;
- return function(...args) {
- clearTimeout(timeout);
- timeout = setTimeout(() => func.apply(this, args), wait);
- };
- }
- };
-
- // 存储管理
- const Storage = {
- get: (key, defaultValue = null) => {
- try {
- const value = localStorage.getItem(key);
- return value ? JSON.parse(value) : defaultValue;
- } catch {
- return defaultValue;
- }
- },
-
- set: (key, value) => {
- try {
- localStorage.setItem(key, JSON.stringify(value));
- return true;
- } catch (error) {
- console.error('Storage error:', error);
- return false;
- }
- }
- };
-
-
- class BrowseController {
- constructor() {
- this.isScrolling = false;
- this.scrollInterval = null;
- this.pauseTimeout = null;
- this.accumulatedTime = Storage.get('accumulatedTime', 0);
- this.lastActionTime = Date.now();
- this.isTopicPage = window.location.href.includes("/t/topic/");
- this.autoRunning = Storage.get('autoRunning', false);
- this.topicList = Storage.get('topicList', []);
- this.firstUseChecked = Storage.get('firstUseChecked', false);
- this.likesCount = Storage.get('likesCount', 0);
- this.selectedPost = Storage.get('selectedPost', null);
-
- this.setupButton();
-
- // 如果是第一次使用,先处理必读文章
- if (!this.firstUseChecked) {
- this.handleFirstUse();
- } else if (this.autoRunning) {
- if (this.isTopicPage) {
- this.startScrolling();
- } else {
- this.getLatestTopics().then(() => this.navigateNextTopic());
- }
- }
- }
-
- setupButton() {
- this.button = document.createElement("button");
- Object.assign(this.button.style, {
- position: "fixed",
- right: "15%",
- bottom: "30%",
- padding: "10px 20px",
- fontSize: "20px",
- backgroundColor: "white",
- border: "1px solid #ddd",
- borderRadius: "5px",
- color: "black",
- cursor: "pointer",
- zIndex: "9999",
- boxShadow: "0 2px 5px rgba(0,0,0,0.2)"
- });
- this.button.textContent = this.autoRunning ? "停止" : "开始阅读";
- this.button.addEventListener("click", () => this.handleButtonClick());
- document.body.appendChild(this.button);
- }
-
- async handleFirstUse() {
- if (!this.autoRunning) return; // 如果没有运行,直接返回
-
- // 如果还没有选择文章
- if (!this.selectedPost) {
- // 随机选择一篇必读文章
- const randomIndex = Math.floor(Math.random() * CONFIG.mustRead.posts.length);
- this.selectedPost = CONFIG.mustRead.posts[randomIndex];
- Storage.set('selectedPost', this.selectedPost);
- console.log(`随机选择文章: ${this.selectedPost.url}`);
-
- // 导航到选中的文章
- window.location.href = this.selectedPost.url;
- return;
- }
-
- const currentUrl = window.location.href;
-
- // 如果在选中的文章页面
- if (currentUrl.includes(this.selectedPost.url)) {
- console.log(`当前在选中的文章页面,已点赞数: ${this.likesCount}`);
-
- while (this.likesCount < CONFIG.mustRead.likesNeeded && this.autoRunning) {
- // 尝试点赞随机评论
- await this.likeRandomComment();
-
- if (this.likesCount >= CONFIG.mustRead.likesNeeded) {
- console.log('完成所需点赞数量,开始正常浏览');
- Storage.set('firstUseChecked', true);
- this.firstUseChecked = true;
- await this.getLatestTopics();
- await this.navigateNextTopic();
- break;
- }
-
- await Utils.sleep(1000); // 点赞间隔
- }
- } else {
- // 如果不在选中的文章页面,导航过去
- window.location.href = this.selectedPost.url;
- }
- }
-
- handleButtonClick() {
- if (this.isScrolling || this.autoRunning) {
- // 停止所有操作
- this.stopScrolling();
- this.autoRunning = false;
- Storage.set('autoRunning', false);
- this.button.textContent = "开始";
- } else {
- // 开始运行
- this.autoRunning = true;
- Storage.set('autoRunning', true);
- this.button.textContent = "停止";
-
- if (!this.firstUseChecked) {
- // 开始处理必读文章
- this.handleFirstUse();
- } else if (this.isTopicPage) {
- this.startScrolling();
- } else {
- this.getLatestTopics().then(() => this.navigateNextTopic());
- }
- }
- }
-
- async likeRandomComment() {
- if (!this.autoRunning) return false; // 如果停止运行,立即返回
-
- // 获取所有评论的点赞按钮
- const likeButtons = Array.from(document.querySelectorAll('.like-button, .like-count, [data-like-button], .discourse-reactions-reaction-button'))
- .filter(button =>
- button &&
- button.offsetParent !== null &&
- !button.classList.contains('has-like') &&
- !button.classList.contains('liked')
- );
-
- if (likeButtons.length > 0) {
- // 随机选择一个未点赞的按钮
- const randomButton = likeButtons[Math.floor(Math.random() * likeButtons.length)];
- // 滚动到按钮位置
- randomButton.scrollIntoView({ behavior: 'smooth', block: 'center' });
- await Utils.sleep(1000);
-
- if (!this.autoRunning) return false; // 再次检查是否停止运行
-
- console.log('找到可点赞的评论,准备点赞');
- randomButton.click();
- this.likesCount++;
- Storage.set('likesCount', this.likesCount);
- await Utils.sleep(1000);
- return true;
- }
-
- // 如果找不到可点赞的按钮,往下滚动一段距离
- window.scrollBy({
- top: 500,
- behavior: 'smooth'
- });
- await Utils.sleep(1000);
-
- console.log('当前位置没有找到可点赞的评论,继续往下找');
- return false;
- }
-
- async getLatestTopics() {
- let page = 1;
- let topicList = [];
- let retryCount = 0;
-
- while (topicList.length < CONFIG.article.topicListLimit && retryCount < CONFIG.article.retryLimit) {
- try {
- const response = await fetch(`https://linux.do/latest.json?no_definitions=true&page=${page}`);
- const data = await response.json();
-
- if (data?.topic_list?.topics) {
- const filteredTopics = data.topic_list.topics.filter(topic =>
- topic.posts_count < CONFIG.article.commentLimit
- );
- topicList.push(...filteredTopics);
- page++;
- } else {
- break;
- }
- } catch (error) {
- console.error('获取文章列表失败:', error);
- retryCount++;
- await Utils.sleep(1000);
- }
- }
-
- if (topicList.length > CONFIG.article.topicListLimit) {
- topicList = topicList.slice(0, CONFIG.article.topicListLimit);
- }
-
- this.topicList = topicList;
- Storage.set('topicList', topicList);
- console.log(`已获取 ${topicList.length} 篇文章`);
- }
-
- async getNextTopic() {
- if (this.topicList.length === 0) {
- await this.getLatestTopics();
- }
-
- if (this.topicList.length > 0) {
- const topic = this.topicList.shift();
- Storage.set('topicList', this.topicList);
- return topic;
- }
-
- return null;
- }
-
- async startScrolling() {
- if (this.isScrolling) return;
-
- this.isScrolling = true;
- this.button.textContent = "停止";
- this.lastActionTime = Date.now();
-
- while (this.isScrolling) {
- const speed = Utils.random(CONFIG.scroll.minSpeed, CONFIG.scroll.maxSpeed);
- const distance = Utils.random(CONFIG.scroll.minDistance, CONFIG.scroll.maxDistance);
- const scrollStep = distance * 2.5;
-
- window.scrollBy({
- top: scrollStep,
- behavior: 'smooth'
- });
-
- if (Utils.isNearBottom()) {
- await Utils.sleep(800);
-
- if (Utils.isNearBottom() && Utils.isPageLoaded()) {
- console.log("已到达页面底部,准备导航到下一篇文章...");
- await Utils.sleep(1000);
- await this.navigateNextTopic();
- break;
- }
- }
-
- await Utils.sleep(speed);
- this.accumulateTime();
-
- if (Math.random() < CONFIG.scroll.fastScrollChance) {
- const fastScroll = Utils.random(CONFIG.scroll.fastScrollMin, CONFIG.scroll.fastScrollMax);
- window.scrollBy({
- top: fastScroll,
- behavior: 'smooth'
- });
- await Utils.sleep(200);
- }
- }
- }
-
- async waitForPageLoad() {
- let attempts = 0;
- const maxAttempts = 5;
-
- while (attempts < maxAttempts) {
- if (Utils.isPageLoaded()) {
- return true;
- }
- await Utils.sleep(300);
- attempts++;
- }
-
- return false;
- }
-
- stopScrolling() {
- this.isScrolling = false;
- clearInterval(this.scrollInterval);
- clearTimeout(this.pauseTimeout);
- this.button.textContent = "开始";
- }
-
- accumulateTime() {
- const now = Date.now();
- this.accumulatedTime += now - this.lastActionTime;
- Storage.set('accumulatedTime', this.accumulatedTime);
- this.lastActionTime = now;
-
- if (this.accumulatedTime >= CONFIG.time.browseTime) {
- this.accumulatedTime = 0;
- Storage.set('accumulatedTime', 0);
- this.pauseForRest();
- }
- }
-
- async pauseForRest() {
- this.stopScrolling();
- console.log("休息10分钟...");
- await Utils.sleep(CONFIG.time.restTime);
- console.log("休息结束,继续浏览...");
- this.startScrolling();
- }
-
- async navigateNextTopic() {
- const nextTopic = await this.getNextTopic();
- if (nextTopic) {
- console.log("导航到新文章:", nextTopic.title);
- const url = nextTopic.last_read_post_number
- ? `https://linux.do/t/topic/${nextTopic.id}/${nextTopic.last_read_post_number}`
- : `https://linux.do/t/topic/${nextTopic.id}`;
- window.location.href = url;
- } else {
- console.log("没有更多文章,返回首页");
- window.location.href = "https://linux.do/latest";
- }
- }
-
- // 添加重置方法(可选,用于测试)
- resetFirstUse() {
- Storage.set('firstUseChecked', false);
- Storage.set('likesCount', 0);
- Storage.set('selectedPost', null);
- this.firstUseChecked = false;
- this.likesCount = 0;
- this.selectedPost = null;
- console.log('已重置首次使用状态');
- }
- }
-
- // 初始化
- (function() {
- new BrowseController();
- })();