// ==UserScript== // @name LINUX DO ReadBoost // @author hmjz100 // @namespace github.com/hmjz100 // @version 1.0.0 // @description 基于【LINUXDO ReadBoost】改编,支持了响应式更新内容的论坛;LINUX DO ReadBoost 是一个刷取 LINUX DO 论坛已读帖量脚本,理论上支持所有的 Discourse 论坛 // @icon data:image/webp;base64,UklGRtQCAABXRUJQVlA4TMcCAAAvH8AHEB8EtbZt18rc9+fosKCwdEX/Jqez23Aiybar9LnkqMCCwrL/VeFyeNeJJNuu0veRo8NmNPtfCe4XFpkzvAu3tm1V1XyCRWTuLh0QQQ1UzKASIjKH73ICEPjFPwJBKbf/EMiQI0GCSrumbWbUczuh287jjJr9vkm34Y/IyG6038M1wD8Ch3//03m0vhZ3DWoTW6yCtHeElhrib+prO+wcwAZVlNDC7O/33b/fH18YYo0MGQm/bZDLX1XzK2ZUWVy93YrNoVHVfy7KN2nyKpI+q/FdRyTSRx385eR9skeqREUqSCTSqBAlGf5KNijKd4G0/A3xuhyUVxWryt9x2c+SiCQSQkISdrOOJCQhCauKJCTh+bJ13089nuZWFQ/HmafzQgLhuOzB93U7KSasKl6uu51DoiTJpm3NtW3btu+zbevatvHpR2vH+4E1vTAR/Z8ACpuWAaC7t78gz7ODEGDTEG06IavPXxwfrK06ObAXvi8LSI8iA+jJPdmKT1ha34BeErflbQ8hI0ImsgdOlxNWdvd2nHOOLYws64Ec8mEykJ9ylLS6t7UZpdtbhUnRlAEgnXLcYeLu9maMAs3OUDqlAQNHet8q9NoIwBnoOUvyW0o9zjVZZN2LS15cUOqtkSxg8MHlzdW1Um3lAROmClNTlIvRuB2bvBug59shHuEAPWOYR4MQtsIBC//3EpRYliCELdgH4RmOJ6YDdHxL40XVVcqtM4Ta+08fPlJsn2ghNN/5++e3YmPuNKODGp7Nv/uilOdtAzoqCU2v5t59VchzRgOVaEN555u595++RSfasO8sRxuAVpSZ1/Pf332OBqyN7SyjVoS2o7zp+c+ZHx/ef/xijPHvHWbsc11O7QjfCtSXvvg8++/Xd621t7OQUkNwiNwGtDQWP375NuQti280QBuireggoKuupqTY6xp2ADqEwgIA // @license MIT // @match *://linux.do/* // @grant GM_setValue // @grant GM_getValue // @grant unsafeWindow // @run-at document-body // @require https://unpkg.com/jquery@3.6.3/dist/jquery.min.js // @downloadURL none // ==/UserScript== (function ReadBoost() { 'use strict'; let reading = []; let readed = []; let originPushState = history.pushState; unsafeWindow.history.pushState = function (state, title, src) { setTimeout(() => { boost(new URL(src, location.href)); }, 1500) return originPushState.call(unsafeWindow.history, state, title, src); }; let originReplaceState = history.replaceState; unsafeWindow.history.replaceState = function (state, title, src) { setTimeout(() => { boost(new URL(src, location.href)); }, 1500) return originReplaceState.call(unsafeWindow.history, state, title, src); }; let settingsButton = $('') let statusLabel = $('ReadBoost 待命中') settingsButton.on('click', showSettingsUI) waitForKeyElements('.header-buttons', (element) => { element.append(statusLabel) element.append(settingsButton) }, true) let defaultConfig = { baseDelay: 2500, randomDelayRange: 800, minReqSize: 8, maxReqSize: 20, minReadTime: 800, maxReadTime: 3000, autoStart: false } let config = { ...defaultConfig, ...getStoredConfig() } let csrfToken = $('meta[name=csrf-token]').attr('content') function boost(url = (new URL(location.href))) { console.log(`【LINUX DO ReadBoost】Init\n收到新链接`, `\n链接:${url.href}`) // 初始化 let topicId = url?.pathname?.split("/")?.[3] let repliesInfo = $('div[class=timeline-replies]').text().trim() if (!topicId || !csrfToken || !repliesInfo) { console.log(`【LINUX DO ReadBoost】Init\n缺失关键标识,跳过`) return; }; let [currentPosition, totalReplies] = repliesInfo?.split("/")?.map(part => parseInt(part?.trim(), 10)) // 自启动处理 if (config.autoStart) { startReading(topicId, totalReplies) } } boost() /** * 开始刷取已读话题 * @param {string} topicId 主题ID * @param {number} totalReplies 总帖子数 */ async function startReading(topicId, totalReplies) { if (!reading.includes(topicId)) { reading.push(topicId); } else { console.log(`【LINUX DO ReadBoost】Read\n正在处理此话题,跳过`) return; } if (readed.includes(topicId)) { console.log(`【LINUX DO ReadBoost】Read\n已读过此话题,跳过`) let index = reading.indexOf(topicId); if (index !== -1) { reading.splice(index, 1); } return; } console.log(`【LINUX DO ReadBoost】Read\n开始阅读……`, `\n话题标识:${topicId}`, `\n帖子数量:${totalReplies}`) let baseRequestDelay = config.baseDelay let randomDelayRange = config.randomDelayRange let minBatchReplyCount = config.minReqSize let maxBatchReplyCount = config.maxReqSize let minReadTime = config.minReadTime let maxReadTime = config.maxReadTime // 随机数生成 function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min } // 发起读帖请求 async function sendBatch(startId, endId, retryCount = 3) { let params = createBatchParams(startId, endId) try { let response = await fetch("https://linux.do/topics/timings", { headers: { "accept": "*/*", "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "discourse-background": "true", "discourse-logged-in": "true", "discourse-present": "true", "priority": "u=1, i", "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-origin", "x-csrf-token": csrfToken, "x-requested-with": "XMLHttpRequest", "x-silence-logger": "true" }, referrer: `https://linux.do/`, body: params.toString(), method: "POST", mode: "cors", credentials: "include" }) if (!response.ok) { throw new Error(`请求失败,状态:${response.status}`) } console.log(`【LINUX DO ReadBoost】Read\n处理成功`, `\n话题标识:${topicId}`, `\n帖子标识:${startId}~${endId}`) updateStatus(`话题 ${topicId} 的帖子 ${startId}~${endId} 处理成功`, "green") } catch (error) { console.error(`【LINUX DO ReadBoost】Read\n处理失败`, `\n话题标识:${topicId}`, `\n帖子标识:${startId}~${endId}`, `\n错误详情:`, error) if (retryCount > 0) { console.error(`【LINUX DO ReadBoost】Read\n重新处理`, `\n话题标识:${topicId}`, `\n帖子标识:${startId}~${endId}`) updateStatus(`重新处理话题 ${topicId} 的帖子 ${startId}~${endId}(${retryCount})`, "orange") let retryDelay = 2000 await new Promise(r => setTimeout(r, retryDelay)) await sendBatch(startId, endId, retryCount - 1) } else { console.error(`【LINUX DO ReadBoost】Read\n处理失败`, `\n话题标识:${topicId}`, `\n帖子标识:${startId}~${endId}`, `\n错误详情:`, error) updateStatus(`话题 ${topicId} 的帖子 ${startId}~${endId} 处理失败`, "red") } } let delay = baseRequestDelay + getRandomInt(0, randomDelayRange) await new Promise(r => setTimeout(r, delay)) } function createBatchParams(startId, endId) { let params = new URLSearchParams() for (let i = startId; i <= endId; i++) { params.append(`timings[${i}]`, getRandomInt(minReadTime, maxReadTime).toString()) } let topicTime = getRandomInt(minReadTime * (endId - startId + 1), maxReadTime * (endId - startId + 1)).toString() params.append('topic_time', topicTime) params.append('topic_id', topicId) return params } // 批量阅读处理 for (let i = 1; i <= totalReplies;) { let batchSize = getRandomInt(minBatchReplyCount, maxBatchReplyCount) let startId = i let endId = Math.min(i + batchSize - 1, totalReplies) await sendBatch(startId, endId) i = endId + 1 } console.log(`【LINUX DO ReadBoost】Read\n处理完成`, `\n话题标识:${topicId}`) updateStatus(`话题 ${topicId} 处理完成`, "green") if (!readed.includes(topicId)) { readed.push(topicId); } let index = reading.indexOf(topicId); if (index !== -1) { reading.splice(index, 1); } setTimeout(() => { updateStatus("ReadBoost 待命中", "") }, 3000) } /** * 更新状态标签内容 */ function updateStatus(text, color = "#555") { statusLabel.text(text) statusLabel.css({ 'background-color': color }) } /** * 显示设置UI界面 */ function showSettingsUI() { let settingsDiv = $(`