自动浏览linux.do,autoBrowse-linux.do

自动浏览linux.do的帖子和话题,智能滚动和加载检测

  1. // ==UserScript==
  2. // @name 自动浏览linux.do,autoBrowse-linux.do
  3. // @description 自动浏览linux.do的帖子和话题,智能滚动和加载检测
  4. // @namespace http://tampermonkey.net/
  5. // @match https://linux.do/*
  6. // @grant none
  7. // @version 1.2.4
  8. // @author quantumcat
  9. // @license MIT
  10. // @icon https://www.google.com/s2/favicons?domain=linux.do
  11. // ==/UserScript==
  12.  
  13. // 配置项
  14. const CONFIG = {
  15. scroll: {
  16. minSpeed: 10,
  17. maxSpeed: 15,
  18. minDistance: 2,
  19. maxDistance: 4,
  20. checkInterval: 500,
  21. fastScrollChance: 0.08,
  22. fastScrollMin: 80,
  23. fastScrollMax: 200
  24. },
  25. time: {
  26. browseTime: 3600000,
  27. restTime: 600000,
  28. minPause: 300,
  29. maxPause: 500,
  30. loadWait: 1500,
  31. },
  32. article: {
  33. commentLimit: 1000,
  34. topicListLimit: 100,
  35. retryLimit: 3
  36. },
  37.  
  38. mustRead: {
  39. posts: [
  40. {
  41. id: '1051',
  42. url: 'https://linux.do/t/topic/1051/'
  43. },
  44. {
  45. id: '5973',
  46. url: 'https://linux.do/t/topic/5973'
  47. },
  48. // 在这里添加更多文章
  49. {
  50. id: '102770',
  51. url: 'https://linux.do/t/topic/102770'
  52. },
  53. // 示例格式
  54. {
  55. id: '154010',
  56. url: 'https://linux.do/t/topic/154010'
  57. },
  58. {
  59. id: '149576',
  60. url: 'https://linux.do/t/topic/149576'
  61. },
  62. {
  63. id: '22118',
  64. url: 'https://linux.do/t/topic/22118'
  65. },
  66. ],
  67. likesNeeded: 5 // 需要点赞的数量
  68. }
  69. };
  70.  
  71. // 工具函数
  72. const Utils = {
  73. random: (min, max) => Math.floor(Math.random() * (max - min + 1)) + min,
  74.  
  75. sleep: (ms) => new Promise(resolve => setTimeout(resolve, ms)),
  76.  
  77. isPageLoaded: () => {
  78. const loadingElements = document.querySelectorAll('.loading, .infinite-scroll');
  79. return loadingElements.length === 0;
  80. },
  81.  
  82. isNearBottom: () => {
  83. const {scrollHeight, clientHeight, scrollTop} = document.documentElement;
  84. return (scrollTop + clientHeight) >= (scrollHeight - 200);
  85. },
  86.  
  87. debounce: (func, wait) => {
  88. let timeout;
  89. return function(...args) {
  90. clearTimeout(timeout);
  91. timeout = setTimeout(() => func.apply(this, args), wait);
  92. };
  93. }
  94. };
  95.  
  96. // 存储管理
  97. const Storage = {
  98. get: (key, defaultValue = null) => {
  99. try {
  100. const value = localStorage.getItem(key);
  101. return value ? JSON.parse(value) : defaultValue;
  102. } catch {
  103. return defaultValue;
  104. }
  105. },
  106.  
  107. set: (key, value) => {
  108. try {
  109. localStorage.setItem(key, JSON.stringify(value));
  110. return true;
  111. } catch (error) {
  112. console.error('Storage error:', error);
  113. return false;
  114. }
  115. }
  116. };
  117.  
  118.  
  119. class BrowseController {
  120. constructor() {
  121. this.isScrolling = false;
  122. this.scrollInterval = null;
  123. this.pauseTimeout = null;
  124. this.accumulatedTime = Storage.get('accumulatedTime', 0);
  125. this.lastActionTime = Date.now();
  126. this.isTopicPage = window.location.href.includes("/t/topic/");
  127. this.autoRunning = Storage.get('autoRunning', false);
  128. this.topicList = Storage.get('topicList', []);
  129. this.firstUseChecked = Storage.get('firstUseChecked', false);
  130. this.likesCount = Storage.get('likesCount', 0);
  131. this.selectedPost = Storage.get('selectedPost', null);
  132.  
  133. this.setupButton();
  134.  
  135. // 如果是第一次使用,先处理必读文章
  136. if (!this.firstUseChecked) {
  137. this.handleFirstUse();
  138. } else if (this.autoRunning) {
  139. if (this.isTopicPage) {
  140. this.startScrolling();
  141. } else {
  142. this.getLatestTopics().then(() => this.navigateNextTopic());
  143. }
  144. }
  145. }
  146.  
  147. setupButton() {
  148. this.button = document.createElement("button");
  149. Object.assign(this.button.style, {
  150. position: "fixed",
  151. right: "15%",
  152. bottom: "30%",
  153. padding: "10px 20px",
  154. fontSize: "20px",
  155. backgroundColor: "white",
  156. border: "1px solid #ddd",
  157. borderRadius: "5px",
  158. color: "black",
  159. cursor: "pointer",
  160. zIndex: "9999",
  161. boxShadow: "0 2px 5px rgba(0,0,0,0.2)"
  162. });
  163. this.button.textContent = this.autoRunning ? "停止" : "开始阅读";
  164. this.button.addEventListener("click", () => this.handleButtonClick());
  165. document.body.appendChild(this.button);
  166. }
  167.  
  168. async handleFirstUse() {
  169. if (!this.autoRunning) return; // 如果没有运行,直接返回
  170.  
  171. // 如果还没有选择文章
  172. if (!this.selectedPost) {
  173. // 随机选择一篇必读文章
  174. const randomIndex = Math.floor(Math.random() * CONFIG.mustRead.posts.length);
  175. this.selectedPost = CONFIG.mustRead.posts[randomIndex];
  176. Storage.set('selectedPost', this.selectedPost);
  177. console.log(`随机选择文章: ${this.selectedPost.url}`);
  178.  
  179. // 导航到选中的文章
  180. window.location.href = this.selectedPost.url;
  181. return;
  182. }
  183.  
  184. const currentUrl = window.location.href;
  185.  
  186. // 如果在选中的文章页面
  187. if (currentUrl.includes(this.selectedPost.url)) {
  188. console.log(`当前在选中的文章页面,已点赞数: ${this.likesCount}`);
  189.  
  190. while (this.likesCount < CONFIG.mustRead.likesNeeded && this.autoRunning) {
  191. // 尝试点赞随机评论
  192. await this.likeRandomComment();
  193.  
  194. if (this.likesCount >= CONFIG.mustRead.likesNeeded) {
  195. console.log('完成所需点赞数量,开始正常浏览');
  196. Storage.set('firstUseChecked', true);
  197. this.firstUseChecked = true;
  198. await this.getLatestTopics();
  199. await this.navigateNextTopic();
  200. break;
  201. }
  202.  
  203. await Utils.sleep(1000); // 点赞间隔
  204. }
  205. } else {
  206. // 如果不在选中的文章页面,导航过去
  207. window.location.href = this.selectedPost.url;
  208. }
  209. }
  210.  
  211. handleButtonClick() {
  212. if (this.isScrolling || this.autoRunning) {
  213. // 停止所有操作
  214. this.stopScrolling();
  215. this.autoRunning = false;
  216. Storage.set('autoRunning', false);
  217. this.button.textContent = "开始";
  218. } else {
  219. // 开始运行
  220. this.autoRunning = true;
  221. Storage.set('autoRunning', true);
  222. this.button.textContent = "停止";
  223.  
  224. if (!this.firstUseChecked) {
  225. // 开始处理必读文章
  226. this.handleFirstUse();
  227. } else if (this.isTopicPage) {
  228. this.startScrolling();
  229. } else {
  230. this.getLatestTopics().then(() => this.navigateNextTopic());
  231. }
  232. }
  233. }
  234.  
  235. async likeRandomComment() {
  236. if (!this.autoRunning) return false; // 如果停止运行,立即返回
  237.  
  238. // 获取所有评论的点赞按钮
  239. const likeButtons = Array.from(document.querySelectorAll('.like-button, .like-count, [data-like-button], .discourse-reactions-reaction-button'))
  240. .filter(button =>
  241. button &&
  242. button.offsetParent !== null &&
  243. !button.classList.contains('has-like') &&
  244. !button.classList.contains('liked')
  245. );
  246.  
  247. if (likeButtons.length > 0) {
  248. // 随机选择一个未点赞的按钮
  249. const randomButton = likeButtons[Math.floor(Math.random() * likeButtons.length)];
  250. // 滚动到按钮位置
  251. randomButton.scrollIntoView({ behavior: 'smooth', block: 'center' });
  252. await Utils.sleep(1000);
  253.  
  254. if (!this.autoRunning) return false; // 再次检查是否停止运行
  255.  
  256. console.log('找到可点赞的评论,准备点赞');
  257. randomButton.click();
  258. this.likesCount++;
  259. Storage.set('likesCount', this.likesCount);
  260. await Utils.sleep(1000);
  261. return true;
  262. }
  263.  
  264. // 如果找不到可点赞的按钮,往下滚动一段距离
  265. window.scrollBy({
  266. top: 500,
  267. behavior: 'smooth'
  268. });
  269. await Utils.sleep(1000);
  270.  
  271. console.log('当前位置没有找到可点赞的评论,继续往下找');
  272. return false;
  273. }
  274.  
  275. async getLatestTopics() {
  276. let page = 1;
  277. let topicList = [];
  278. let retryCount = 0;
  279.  
  280. while (topicList.length < CONFIG.article.topicListLimit && retryCount < CONFIG.article.retryLimit) {
  281. try {
  282. const response = await fetch(`https://linux.do/latest.json?no_definitions=true&page=${page}`);
  283. const data = await response.json();
  284.  
  285. if (data?.topic_list?.topics) {
  286. const filteredTopics = data.topic_list.topics.filter(topic =>
  287. topic.posts_count < CONFIG.article.commentLimit
  288. );
  289. topicList.push(...filteredTopics);
  290. page++;
  291. } else {
  292. break;
  293. }
  294. } catch (error) {
  295. console.error('获取文章列表失败:', error);
  296. retryCount++;
  297. await Utils.sleep(1000);
  298. }
  299. }
  300.  
  301. if (topicList.length > CONFIG.article.topicListLimit) {
  302. topicList = topicList.slice(0, CONFIG.article.topicListLimit);
  303. }
  304.  
  305. this.topicList = topicList;
  306. Storage.set('topicList', topicList);
  307. console.log(`已获取 ${topicList.length} 篇文章`);
  308. }
  309.  
  310. async getNextTopic() {
  311. if (this.topicList.length === 0) {
  312. await this.getLatestTopics();
  313. }
  314.  
  315. if (this.topicList.length > 0) {
  316. const topic = this.topicList.shift();
  317. Storage.set('topicList', this.topicList);
  318. return topic;
  319. }
  320.  
  321. return null;
  322. }
  323.  
  324. async startScrolling() {
  325. if (this.isScrolling) return;
  326.  
  327. this.isScrolling = true;
  328. this.button.textContent = "停止";
  329. this.lastActionTime = Date.now();
  330.  
  331. while (this.isScrolling) {
  332. const speed = Utils.random(CONFIG.scroll.minSpeed, CONFIG.scroll.maxSpeed);
  333. const distance = Utils.random(CONFIG.scroll.minDistance, CONFIG.scroll.maxDistance);
  334. const scrollStep = distance * 2.5;
  335.  
  336. window.scrollBy({
  337. top: scrollStep,
  338. behavior: 'smooth'
  339. });
  340.  
  341. if (Utils.isNearBottom()) {
  342. await Utils.sleep(800);
  343.  
  344. if (Utils.isNearBottom() && Utils.isPageLoaded()) {
  345. console.log("已到达页面底部,准备导航到下一篇文章...");
  346. await Utils.sleep(1000);
  347. await this.navigateNextTopic();
  348. break;
  349. }
  350. }
  351.  
  352. await Utils.sleep(speed);
  353. this.accumulateTime();
  354.  
  355. if (Math.random() < CONFIG.scroll.fastScrollChance) {
  356. const fastScroll = Utils.random(CONFIG.scroll.fastScrollMin, CONFIG.scroll.fastScrollMax);
  357. window.scrollBy({
  358. top: fastScroll,
  359. behavior: 'smooth'
  360. });
  361. await Utils.sleep(200);
  362. }
  363. }
  364. }
  365.  
  366. async waitForPageLoad() {
  367. let attempts = 0;
  368. const maxAttempts = 5;
  369.  
  370. while (attempts < maxAttempts) {
  371. if (Utils.isPageLoaded()) {
  372. return true;
  373. }
  374. await Utils.sleep(300);
  375. attempts++;
  376. }
  377.  
  378. return false;
  379. }
  380.  
  381. stopScrolling() {
  382. this.isScrolling = false;
  383. clearInterval(this.scrollInterval);
  384. clearTimeout(this.pauseTimeout);
  385. this.button.textContent = "开始";
  386. }
  387.  
  388. accumulateTime() {
  389. const now = Date.now();
  390. this.accumulatedTime += now - this.lastActionTime;
  391. Storage.set('accumulatedTime', this.accumulatedTime);
  392. this.lastActionTime = now;
  393.  
  394. if (this.accumulatedTime >= CONFIG.time.browseTime) {
  395. this.accumulatedTime = 0;
  396. Storage.set('accumulatedTime', 0);
  397. this.pauseForRest();
  398. }
  399. }
  400.  
  401. async pauseForRest() {
  402. this.stopScrolling();
  403. console.log("休息10分钟...");
  404. await Utils.sleep(CONFIG.time.restTime);
  405. console.log("休息结束,继续浏览...");
  406. this.startScrolling();
  407. }
  408.  
  409. async navigateNextTopic() {
  410. const nextTopic = await this.getNextTopic();
  411. if (nextTopic) {
  412. console.log("导航到新文章:", nextTopic.title);
  413. const url = nextTopic.last_read_post_number
  414. ? `https://linux.do/t/topic/${nextTopic.id}/${nextTopic.last_read_post_number}`
  415. : `https://linux.do/t/topic/${nextTopic.id}`;
  416. window.location.href = url;
  417. } else {
  418. console.log("没有更多文章,返回首页");
  419. window.location.href = "https://linux.do/latest";
  420. }
  421. }
  422.  
  423. // 添加重置方法(可选,用于测试)
  424. resetFirstUse() {
  425. Storage.set('firstUseChecked', false);
  426. Storage.set('likesCount', 0);
  427. Storage.set('selectedPost', null);
  428. this.firstUseChecked = false;
  429. this.likesCount = 0;
  430. this.selectedPost = null;
  431. console.log('已重置首次使用状态');
  432. }
  433. }
  434.  
  435. // 初始化
  436. (function() {
  437. new BrowseController();
  438. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址