您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动浏览、点赞、只看楼主、楼层号、保存帖子到本地、清爽模式。
// ==UserScript== // @name linux.do 小助手 // @description 自动浏览、点赞、只看楼主、楼层号、保存帖子到本地、清爽模式。 // @namespace Violentmonkey Scripts // @match https://linux.do/* // @grant none // @version 1.0.1 // @author quantumcat & nulluser // @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.autoLikeEnabled = Storage.get('autoLikeEnabled', false); this.cleanModeEnabled = Storage.get('cleanModeEnabled', false); this.likedTopics = Storage.get('likedTopics', []); // 新增:记录已点赞的主题ID this.setupButton(); this.initFloorNumberDisplay(); this.applyCleanModeStyles(); this.initOnlyOwnerView(); if (!this.firstUseChecked) { this.handleFirstUse(); } else if (this.autoRunning) { if (this.isTopicPage) { this.startScrolling(); if (this.autoLikeEnabled) { this.autoLikeTopic(); } } else { this.getLatestTopics().then(() => this.navigateNextTopic()); } } if (this.autoLikeEnabled && this.isTopicPage) { this.autoLikeTopic(); } } setupButton() { this.container = document.createElement("div"); Object.assign(this.container.style, { position: "fixed", right: "20px", bottom: "30%", display: "flex", flexDirection: "column", gap: "10px", zIndex: "9999" }); this.button = document.createElement("button"); Object.assign(this.button.style, { padding: "12px 24px", fontSize: "16px", backgroundColor: this.autoRunning ? "#ff6b6b" : "#4caf50", border: "none", borderRadius: "6px", color: "white", cursor: "pointer", boxShadow: "0 4px 8px rgba(0,0,0,0.2)", transition: "background-color 0.3s, transform 0.2s" }); this.button.textContent = this.autoRunning ? "停止阅读" : "开始阅读"; this.button.addEventListener("click", () => this.handleButtonClick()); this.button.addEventListener("mouseover", () => { this.button.style.transform = "scale(1.05)"; }); this.button.addEventListener("mouseout", () => { this.button.style.transform = "scale(1)"; }); this.toggleContainer = document.createElement("div"); Object.assign(this.toggleContainer.style, { display: "flex", alignItems: "center", gap: "8px", backgroundColor: "white", padding: "8px 12px", borderRadius: "6px", boxShadow: "0 2px 5px rgba(0,0,0,0.1)" }); this.toggleLabel = document.createElement("label"); this.toggleLabel.textContent = "自动点赞主题"; Object.assign(this.toggleLabel.style, { fontSize: "14px", color: "#333", cursor: "pointer" }); this.toggleSwitch = document.createElement("input"); this.toggleSwitch.type = "checkbox"; this.toggleSwitch.checked = this.autoLikeEnabled; Object.assign(this.toggleSwitch.style, { width: "40px", height: "20px", cursor: "pointer" }); this.toggleSwitch.addEventListener("change", () => { this.autoLikeEnabled = this.toggleSwitch.checked; Storage.set('autoLikeEnabled', this.autoLikeEnabled); console.log(`自动点赞主题: ${this.autoLikeEnabled ? '开启' : '关闭'}`); if (this.autoLikeEnabled && this.isTopicPage) { this.autoLikeTopic(); } }); this.cleanModeContainer = document.createElement("div"); Object.assign(this.cleanModeContainer.style, { display: "flex", alignItems: "center", gap: "8px", backgroundColor: "white", padding: "8px 12px", borderRadius: "6px", boxShadow: "0 2px 5px rgba(0,0,0,0.1)" }); this.cleanModeLabel = document.createElement("label"); this.cleanModeLabel.textContent = "清爽模式"; Object.assign(this.cleanModeLabel.style, { fontSize: "14px", color: "#333", cursor: "pointer" }); this.cleanModeSwitch = document.createElement("input"); this.cleanModeSwitch.type = "checkbox"; this.cleanModeSwitch.checked = this.cleanModeEnabled; Object.assign(this.cleanModeSwitch.style, { width: "40px", height: "20px", cursor: "pointer" }); this.cleanModeSwitch.addEventListener("change", () => { this.cleanModeEnabled = this.cleanModeSwitch.checked; Storage.set('cleanModeEnabled', this.cleanModeEnabled); console.log(`清爽模式: ${this.cleanModeEnabled ? '开启' : '关闭'}`); this.toggleCleanMode(); }); this.toggleContainer.appendChild(this.toggleSwitch); this.toggleContainer.appendChild(this.toggleLabel); this.cleanModeContainer.appendChild(this.cleanModeSwitch); this.cleanModeContainer.appendChild(this.cleanModeLabel); this.container.appendChild(this.button); this.container.appendChild(this.toggleContainer); this.container.appendChild(this.cleanModeContainer); document.body.appendChild(this.container); } toggleCleanMode() { const sidebarToggle = document.querySelector('button.btn-sidebar-toggle'); if (sidebarToggle && this.cleanModeEnabled) { if (sidebarToggle.getAttribute('aria-expanded') === 'true') { console.log('清爽模式启用,收起边栏'); sidebarToggle.click(); } } this.applyCleanModeStyles(); } applyCleanModeStyles() { let styleElement = document.getElementById('clean-mode-styles'); if (styleElement) { styleElement.remove(); } if (this.cleanModeEnabled) { styleElement = document.createElement('style'); styleElement.id = 'clean-mode-styles'; styleElement.textContent = ` p:contains("希望你喜欢这里。有问题,请提问,或搜索现有帖子。") { display: none !important; } div#global-notice-alert-global-notice.alert.alert-info.alert-global-notice { display: none !important; } a[href="https://linux.do/t/topic/482293"] { display: none !important; } div.link-bottom-line a.badge-category__wrapper { display: none !important; } td.posters.topic-list-data { display: none !important; } a.discourse-tag.box[href^="/tag/"] { display: none !important; } `; document.head.appendChild(styleElement); } } initOnlyOwnerView() { this.createToggleButton(); this.observePageChanges(); this.toggleVisibility(); } toggleVisibility() { const displayMode = localStorage.getItem("on_off") || "当前查看全部"; const userId = document.getElementById("post_1")?.getAttribute('data-user-id'); if (userId) { document.querySelectorAll('article').forEach(article => { article.style.display = (displayMode === "当前只看楼主" && article.dataset.userId !== userId) ? 'none' : ''; }); } } createToggleButton() { if (document.getElementById("toggleVisibilityBtn")) { return; } const btn = document.createElement("button"); btn.id = "toggleVisibilityBtn"; btn.textContent = localStorage.getItem("on_off") || "当前查看全部"; btn.onclick = () => { const newText = btn.textContent === '当前查看全部' ? '当前只看楼主' : '当前查看全部'; document.getElementsByClassName("start-date")[0]?.click(); btn.textContent = newText; localStorage.setItem("on_off", newText); this.toggleVisibility(); }; btn.style.backgroundColor = "#333"; btn.style.color = "#FFF"; btn.style.border = "none"; btn.style.padding = "8px 16px"; btn.style.marginLeft = "10px"; btn.style.borderRadius = "5px"; btn.style.cursor = "pointer"; const saveButton = document.querySelector('.save-to-local-btn'); if (saveButton) { saveButton.parentElement.appendChild(btn); } else { const firstPostContent = document.querySelector('.boxed.onscreen-post[data-post-id] .cooked'); if (firstPostContent) { firstPostContent.appendChild(btn); } } } observePageChanges() { const observer = new MutationObserver(() => { if (document.querySelector(".timeline-footer-controls") && !document.getElementById("toggleVisibilityBtn")) { this.createToggleButton(); } this.toggleVisibility(); }); observer.observe(document.body, { childList: true, subtree: true }); } initFloorNumberDisplay() { this.addFloorNumbers(); this.initMutationObserver(); this.setupRandomJumpButton(); this.monitorURLChangeAndUpdateButton(); } addFloorNumbers() { document.querySelectorAll('.boxed.onscreen-post').forEach((post) => { if (!post.querySelector('.floor-number')) { const floorNumber = document.createElement('div'); floorNumber.className = 'floor-number'; floorNumber.textContent = '楼层: ' + post.id.split("_")[1]; floorNumber.style.cssText = 'color: grey; margin-left: 10px;'; post.querySelector('.topic-meta-data').appendChild(floorNumber); } }); this.setupSaveButton(); } initMutationObserver() { const observer = new MutationObserver(() => { this.addFloorNumbers(); this.setupSaveButton(); this.toggleCleanMode(); }); observer.observe(document.body, { childList: true, subtree: true }); } randomJump() { fetch(window.location.href + '.json') .then(response => response.json()) .then(data => { if (data && data.posts_count) { const postId = 1 + Math.floor(Math.random() * data.posts_count); const currentUrl = new URL(window.location.href); const list1 = currentUrl.pathname.split("/"); if (list1[list1.length - 2] === "topic") { list1.push(postId); } else if (list1[list1.length - 3] === "topic") { list1[list1.length - 1] = postId; } const newUrl = list1.join("/"); window.location.href = newUrl; alert('恭喜楼层【' + postId + '】的用户被抽中!'); } }) .catch(error => console.error('Error:', error)); } setupRandomJumpButton() { const randomButton = document.createElement('button'); randomButton.id = "randomButton1"; randomButton.textContent = '随机楼层'; Object.assign(randomButton.style, { position: "fixed", bottom: "25%", right: "20px", width: "80px", height: "30px", backgroundColor: "#007bff", color: "white", border: "none", borderRadius: "5px", fontSize: "15px", cursor: "pointer", zIndex: "9999", display: this.isTopicPage ? 'block' : 'none' }); randomButton.onclick = () => this.randomJump(); this.container.appendChild(randomButton); } setupSaveButton() { const firstPost = document.querySelector('.boxed.onscreen-post[data-post-id]'); if (firstPost && firstPost.id.includes('post_1')) { if (!firstPost.querySelector('.save-to-local-btn')) { const saveButton = document.createElement('button'); saveButton.className = 'save-to-local-btn'; saveButton.textContent = '保存到本地'; Object.assign(saveButton.style, { padding: '8px 16px', fontSize: '16px', backgroundColor: '#ff9800', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer', marginTop: '10px', boxShadow: '0 2px 5px rgba(0,0,0,0.2)', transition: 'background-color 0.3s, transform 0.2s' }); saveButton.addEventListener('mouseover', () => { saveButton.style.transform = 'scale(1.05)'; }); saveButton.addEventListener('mouseout', () => { saveButton.style.transform = 'scale(1)'; }); saveButton.addEventListener('click', () => this.savePostToLocal(firstPost)); const postContent = firstPost.querySelector('.cooked'); if (postContent) { postContent.appendChild(saveButton); } } } } async savePostToLocal(postElement) { try { const topicTitle = document.querySelector('.fancy-title')?.textContent.trim() || 'Untitled_Topic'; const postContent = postElement.querySelector('.cooked'); if (!postContent) { alert('无法获取帖子内容!'); return; } const contentClone = postContent.cloneNode(true); contentClone.querySelector('.save-to-local-btn')?.remove(); const images = contentClone.querySelectorAll('img'); for (const img of images) { try { const response = await fetch(img.src); const blob = await response.blob(); const reader = new FileReader(); await new Promise((resolve) => { reader.onload = resolve; reader.readAsDataURL(blob); }); img.src = reader.result; } catch (error) { console.error('图片加载失败:', img.src, error); img.alt = '[图片加载失败]'; } } const htmlContent = ` <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>${topicTitle}</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } .post-content { max-width: 800px; margin: 0 auto; } img { max-width: 100%; height: auto; } </style> </head> <body> <div class="post-content"> <h1>${topicTitle}</h1> ${contentClone.innerHTML} </div> </body> </html> `; const blob = new Blob([htmlContent], { type: 'text/html' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; const fileName = topicTitle .replace(/[\\/:*?"<>|]/g, '_') .replace(/\s+/g, '_') + '.html'; link.download = fileName; link.click(); URL.revokeObjectURL(url); alert('帖子内容已保存到本地!'); } catch (error) { console.error('保存帖子失败:', error); alert('保存失败,请查看控制台错误信息。'); } } monitorURLChangeAndUpdateButton() { let lastURL = location.href; setInterval(() => { const currentURL = location.href; if (currentURL !== lastURL) { lastURL = currentURL; this.updateButtonVisibility(); this.toggleCleanMode(); if (this.autoLikeEnabled && /^https:\/\/linux\.do\/t\/topic\//.test(currentURL)) { this.autoLikeTopic(); } } }, 1000); } updateButtonVisibility() { const isTopicPage = /^https:\/\/linux\.do\/t\/topic\//.test(location.href); const randomButton = document.getElementById('randomButton1'); if (randomButton) { randomButton.style.display = isTopicPage ? 'block' : 'none'; } } handleButtonClick() { if (this.isScrolling || this.autoRunning) { this.stopScrolling(); this.autoRunning = false; Storage.set('autoRunning', false); this.button.textContent = "开始阅读"; this.button.style.backgroundColor = "#4caf50"; } else { this.autoRunning = true; Storage.set('autoRunning', true); this.button.textContent = "停止阅读"; this.button.style.backgroundColor = "#ff6b6b"; if (!this.firstUseChecked) { this.handleFirstUse(); } else if (this.isTopicPage) { this.startScrolling(); if (this.autoLikeEnabled) { this.autoLikeTopic(); } } else { this.getLatestTopics().then(() => this.navigateNextTopic()); } } } async autoLikeTopic() { if (!this.autoLikeEnabled) return; // 获取当前主题ID const match = window.location.pathname.match(/\/t\/topic\/(\d+)/); if (!match) { console.log("无法获取当前主题ID"); return; } const topicId = match[1]; // 检查是否已经点赞过此主题 if (this.likedTopics.includes(topicId)) { console.log(`主题 ${topicId} 已经点赞过,跳过点赞操作`); return; } console.log("正在检查是否需要自动点赞主题..."); await Utils.sleep(2000); const likeButton = document.querySelector('div.discourse-reactions-reaction-button button.btn-toggle-reaction-like'); if (likeButton && !likeButton.classList.contains('has-like') && !likeButton.classList.contains('liked')) { likeButton.scrollIntoView({ behavior: 'smooth', block: 'center' }); await Utils.sleep(1000); console.log("找到主题点赞按钮,执行点击操作"); likeButton.click(); // 记录已点赞的主题ID this.likedTopics.push(topicId); Storage.set('likedTopics', this.likedTopics); console.log(`已记录点赞主题 ${topicId}`); } else { console.log("未找到可点赞的主题按钮或已点赞"); // 如果页面显示已点赞,也记录到列表中,防止重复操作 if (likeButton && (likeButton.classList.contains('has-like') || likeButton.classList.contains('liked'))) { if (!this.likedTopics.includes(topicId)) { this.likedTopics.push(topicId); Storage.set('likedTopics', this.likedTopics); console.log(`主题 ${topicId} 已点赞,记录到列表`); } } } } 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; } } 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.button.style.backgroundColor = "#ff6b6b"; 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 = "开始阅读"; this.button.style.backgroundColor = "#4caf50"; } 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(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址