// ==UserScript==
// @name Auto Read
// @namespace http://tampermonkey.net/
// @version 1.5.7
// @description 自动刷linuxdo文章,第一作者liuweiqing
// @author liuweiqing,linmew
// @match https://meta.discourse.org/*
// @match https://linux.do/*
// @match https://meta.appinn.net/*
// @match https://community.openai.com/*
// @grant GM_addStyle
// @license MIT
// @icon https://www.google.com/s2/favicons?domain=linux.do
// ==/UserScript==
(function () {
"use strict";
// 注入样式
GM_addStyle(`
.dar-container {
position: fixed;
right: -320px;
top: 50%;
transform: translateY(-50%);
z-index: 9999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
width: 320px;
transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: none;
}
.dar-container.expanded {
right: 0;
pointer-events: auto;
}
.dar-toggle-btn {
position: absolute;
left: -40px;
top: 50%;
transform: translateY(-50%);
width: 40px;
height: 64px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 8px 0 0 8px;
color: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: -2px 2px 10px rgba(0, 0, 0, 0.1);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: auto;
}
.dar-toggle-btn:hover {
background: linear-gradient(135deg, #764ba2 0%, #667eea 100%);
box-shadow: -4px 4px 20px rgba(0, 0, 0, 0.15);
}
.dar-toggle-btn svg {
width: 20px;
height: 20px;
transition: transform 0.3s ease;
}
.dar-container.expanded .dar-toggle-btn svg {
transform: rotate(180deg);
}
.dar-panel {
width: 320px;
background: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(10px);
border-radius: 16px 0 0 16px;
box-shadow: -4px 0 30px rgba(0, 0, 0, 0.1);
max-height: 80vh;
overflow-y: auto;
}
.dar-header {
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px 0 0 0;
color: white;
}
.dar-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
}
.dar-header p {
margin: 8px 0 0 0;
font-size: 12px;
opacity: 0.9;
}
.dar-content {
padding: 20px;
}
.dar-current-topic {
padding: 12px;
background: #f0f7ff;
border-radius: 8px;
margin-bottom: 16px;
border: 1px solid #d0e2ff;
}
.dar-current-topic-title {
font-size: 13px;
font-weight: 600;
color: #2d3748;
margin-bottom: 4px;
}
.dar-current-topic-floor {
font-size: 12px;
color: #4a5568;
}
.dar-progress {
padding: 12px;
background: #f7fafc;
border-radius: 8px;
margin-bottom: 16px;
}
.dar-progress-item {
margin-bottom: 12px;
}
.dar-progress-item:last-child {
margin-bottom: 0;
}
.dar-progress-label {
display: flex;
justify-content: space-between;
font-size: 13px;
color: #4a5568;
margin-bottom: 4px;
}
.dar-progress-bar {
width: 100%;
height: 8px;
background: #e2e8f0;
border-radius: 4px;
overflow: hidden;
}
.dar-progress-fill {
height: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 4px;
transition: width 0.3s ease;
}
.dar-status {
padding: 12px;
background: #edf2f7;
border-radius: 8px;
margin-bottom: 16px;
}
.dar-status-item {
display: flex;
justify-content: space-between;
font-size: 13px;
color: #4a5568;
margin-bottom: 6px;
}
.dar-status-item:last-child {
margin-bottom: 0;
}
.dar-status-value {
font-weight: 600;
color: #2d3748;
}
.dar-status-value.active {
color: #48bb78;
}
.dar-control-group {
margin-bottom: 16px;
}
.dar-control-label {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
color: #2d3748;
}
.dar-switch {
position: relative;
width: 48px;
height: 24px;
background: #cbd5e0;
border-radius: 12px;
cursor: pointer;
transition: background 0.3s ease;
}
.dar-switch.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.dar-switch-handle {
position: absolute;
top: 2px;
left: 2px;
width: 20px;
height: 20px;
background: white;
border-radius: 50%;
transition: transform 0.3s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.dar-switch.active .dar-switch-handle {
transform: translateX(24px);
}
.dar-main-button {
width: 100%;
padding: 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 8px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.dar-main-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
.dar-main-button.stop {
background: linear-gradient(135deg, #fc5c7d 0%, #6a82fb 100%);
}
.dar-divider {
height: 1px;
background: #e2e8f0;
margin: 16px 0;
}
.dar-panel::-webkit-scrollbar {
width: 6px;
}
.dar-panel::-webkit-scrollbar-track {
background: transparent;
}
.dar-panel::-webkit-scrollbar-thumb {
background: #cbd5e0;
border-radius: 3px;
}
`);
// 配置常量
const CONFIG = {
SCROLL_SPEED: 30, // 滚动速度
SCROLL_INTERVAL: 150, // 滚动间隔
SCROLL_VARIATION: 10, // 滚动速度变化幅度
LIKE_LIMIT: 30, // 最大点赞数
LIKE_INTERVAL_MIN: 3000, // 点赞间隔
LIKE_INTERVAL_MAX: 6000,
MAX_RETRIES: 3,
PAGE_TRANSITION_DELAY: 1500, // 页面切换延迟
};
// 统计
class StatsManager {
constructor() {
this.goals = {
topics: 500,
posts: 20000,
likes: 30,
days: 100,
};
this.load();
}
load() {
const stored = localStorage.getItem("dar_stats");
const defaults = {
startDate: Date.now(),
topics: {},
postsRead: 0,
topicsVisited: 0,
likesGiven: 0,
todayLikes: 0,
lastResetDate: new Date().toDateString(),
};
this.stats = stored ? { ...defaults, ...JSON.parse(stored) } : defaults;
this.checkDailyReset();
}
save() {
localStorage.setItem("dar_stats", JSON.stringify(this.stats));
}
checkDailyReset() {
const today = new Date().toDateString();
if (this.stats.lastResetDate !== today) {
this.stats.todayLikes = 0;
this.stats.lastResetDate = today;
this.save();
}
}
v;
// 记录帖子阅读进度
recordTopicVisit(topicId, title = "", startingPost = 1) {
if (!this.stats.topics[topicId]) {
this.stats.topics[topicId] = {
title: title,
visitCount: 0,
lastVisit: Date.now(),
maxPostRead: 0,
totalPostsRead: 0,
firstPostSeen: startingPost,
};
this.stats.topicsVisited++;
}
this.stats.topics[topicId].visitCount++;
this.stats.topics[topicId].lastVisit = Date.now();
// 如果从新的起始位置开始,更新起始楼层
if (startingPost > 0 && (!this.stats.topics[topicId].firstPostSeen || startingPost < this.stats.topics[topicId].firstPostSeen)) {
this.stats.topics[topicId].firstPostSeen = startingPost;
}
if (title) {
this.stats.topics[topicId].title = title;
}
this.save();
}
recordPostRead(topicId, currentPost) {
if (!this.stats.topics[topicId]) return;
const topic = this.stats.topics[topicId];
const previousMax = topic.maxPostRead || 0;
if (currentPost > previousMax) {
// 如果是第一次记录,从起始楼层开始计算
let newPosts = 0;
if (previousMax === 0 && topic.firstPostSeen) {
newPosts = currentPost - topic.firstPostSeen + 1;
} else {
// 只计算新增的帖子
newPosts = currentPost - previousMax;
}
this.stats.postsRead += newPosts;
topic.maxPostRead = currentPost;
topic.totalPostsRead += newPosts;
this.save();
}
}
recordLike() {
this.stats.likesGiven++;
this.stats.todayLikes++;
this.save();
}
getProgress() {
return {
topics: {
current: this.stats.topicsVisited,
goal: this.goals.topics,
percentage: Math.min(100, (this.stats.topicsVisited / this.goals.topics) * 100),
},
posts: {
current: this.stats.postsRead,
goal: this.goals.posts,
percentage: Math.min(100, (this.stats.postsRead / this.goals.posts) * 100),
},
likes: {
current: this.stats.likesGiven,
goal: this.goals.likes,
percentage: Math.min(100, (this.stats.likesGiven / this.goals.likes) * 100),
}
};
}
}
// Discourse API 交互
class DiscourseAPI {
constructor() {
this.baseURL = this.getCurrentBaseURL();
this.csrfToken = this.getCSRFToken();
this._cachedSelectors = {};
}
getCurrentBaseURL() {
const currentURL = window.location.href;
const baseURLs = ["https://linux.do", "https://meta.discourse.org", "https://meta.appinn.net", "https://community.openai.com"];
return baseURLs.find((url) => currentURL.startsWith(url)) || baseURLs[0];
}
getCSRFToken() {
const token = document.querySelector('meta[name="csrf-token"]');
return token ? token.content : "";
}
parseTopicURL(url) {
const match = url.match(/\/t\/(?:[^\/]+\/)?(\d+)(?:\/(\d+))?/);
if (match) {
return {
topicId: parseInt(match[1]),
postNumber: match[2] ? parseInt(match[2]) : 1,
};
}
return null;
}
// 获取当前话题信息
getCurrentTopicInfo() {
const parsed = this.parseTopicURL(window.location.pathname);
if (!parsed) return null;
const titleElement = document.querySelector(".fancy-title, .topic-title, h1");
const title = titleElement ? titleElement.textContent.trim() : "";
let currentPost = 1;
let totalPosts = 0;
// 获取楼层信息
const timelineReplies = document.querySelector("div.timeline-replies");
if (timelineReplies) {
const parts = timelineReplies.textContent
.trim()
.replace(/[^0-9/]/g, "")
.split("/");
if (parts.length >= 2) {
currentPost = parseInt(parts[0]) || 1;
totalPosts = parseInt(parts[1]) || 0;
}
}
return {
topicId: parsed.topicId,
title: title,
currentPost: currentPost,
totalPosts: totalPosts,
};
}
getTopicsFromList() {
const topics = [];
const topicElements = document.querySelectorAll("tr.topic-list-item");
topicElements.forEach((element) => {
const topicId = element.getAttribute("data-topic-id");
if (!topicId) return;
const linkElement = element.querySelector("a.title");
if (!linkElement) return;
const href = linkElement.getAttribute("href");
const title = linkElement.textContent.trim();
const postsElement = element.querySelector(".posts .number");
const postsCount = postsElement ? parseInt(postsElement.textContent) : 0;
const newBadge = element.querySelector(".badge-notification.new-topic");
const unreadBadge = element.querySelector(".badge-notification.unread-posts");
const hasNew = !!(newBadge || unreadBadge);
topics.push({
id: topicId,
title: title,
href: href,
postsCount: postsCount + 1,
hasNew: hasNew,
});
});
return topics;
}
getVisiblePosts() {
const posts = [];
const postElements = document.querySelectorAll(".topic-post, article[data-post-id]");
const viewportHeight = window.innerHeight;
postElements.forEach((element) => {
const postId = element.getAttribute("data-post-id") || element.id.replace("post_", "");
const postNumberEl = element.querySelector(".post-number, .reply-to-tab");
const postNumber = postNumberEl ? postNumberEl.textContent.trim().replace("#", "") : "1";
if (postId && postNumber) {
const rect = element.getBoundingClientRect();
const isVisible = rect.top < viewportHeight && rect.bottom > 0;
posts.push({
id: postId,
number: parseInt(postNumber),
element: element,
isVisible: isVisible,
isFullyVisible: rect.top >= 0 && rect.bottom <= viewportHeight,
height: rect.height,
});
}
});
return posts;
}
async likePost(postId) {
try {
const element = document.getElementById(`post_${postId}`) || document.querySelector(`[data-post-id="${postId}"]`);
if (!element) return false;
const likeButton = element.querySelector(".discourse-reactions-reaction-button button, .like-button");
if (likeButton) {
const container = likeButton.closest(".discourse-reactions-actions");
if (!container || !container.classList.contains("has-reacted")) {
likeButton.click();
return true;
}
}
} catch (error) {
console.error("Error liking post:", error);
}
return false;
}
// 底部检测
isAtBottomOfTopic() {
const timelineReplies = document.querySelector("div.timeline-replies");
if (timelineReplies) {
const parts = timelineReplies.textContent
.trim()
.replace(/[^0-9/]/g, "")
.split("/");
// 判断是否相等(如:35/35),表示已到达底部
if (parts.length >= 2 && parts[0] === parts[1]) {
return true;
}
} else {
// 没有 timeline-replies 元素时,即只有主楼的情况,检查是否已经滚动到页面底部
const scrollHeight = document.documentElement.scrollHeight;
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const clientHeight = document.documentElement.clientHeight;
// 滚动到底部(留 100px 容差)
if (scrollTop + clientHeight >= scrollHeight - 100) {
const posts = document.querySelectorAll("article[data-post-id]");
if (posts.length <= 1) {
return true;
}
}
}
return false;
}
}
// 智能阅读
class SmartReader {
constructor(config, stats, api) {
this.config = config;
this.stats = stats;
this.api = api;
this.isReading = false;
this.currentTopic = null;
this.readPosts = new Set();
this.topicQueue = [];
this.scrollTimer = null;
this.currentStatus = "待机";
this.statusUpdateCallback = null;
this.isPageVisible = true;
this.lastScrollTime = 0;
this.lastRecordedPost = 0;
this.errorRetries = 0;
this.setupVisibilityHandler();
}
setStatusCallback(callback) {
this.statusUpdateCallback = callback;
}
updateStatus(status) {
this.currentStatus = status;
if (this.statusUpdateCallback) {
this.statusUpdateCallback(status);
}
}
setupVisibilityHandler() {
document.addEventListener("visibilitychange", () => {
this.isPageVisible = !document.hidden;
if (this.isPageVisible && this.isReading) {
this.resumeReading();
} else if (!this.isPageVisible && this.isReading) {
this.pauseReading();
}
});
}
start() {
this.isReading = true;
this.config.set("autoRead", true);
this.updateStatus("启动中...");
this.errorRetries = 0;
if (this.isTopicPage()) {
this.readCurrentTopic();
} else if (this.isListPage()) {
this.loadTopicList();
} else {
window.location.href = `${this.api.baseURL}/latest`;
}
}
stop() {
this.isReading = false;
this.config.set("autoRead", false);
this.updateStatus("已停止");
this.clearTimers();
}
pauseReading() {
this.clearTimers();
this.updateStatus("后台暂停中...");
}
resumeReading() {
if (!this.isReading) return;
if (this.isTopicPage()) {
this.startSmoothScrolling();
}
this.updateStatus("继续阅读...");
}
clearTimers() {
if (this.scrollTimer) {
cancelAnimationFrame(this.scrollTimer);
this.scrollTimer = null;
}
}
isTopicPage() {
return window.location.pathname.includes("/t/");
}
isListPage() {
const path = window.location.pathname;
return ["/", "/latest", "/new", "/unread", "/top"].some((p) => path === p || path.startsWith(p));
}
// 检测是否为错误页面
isErrorPage() {
return document.title.includes("找不到页面") || document.title.includes("404") || document.querySelector(".page-not-found");
}
// 处理错误页面
handleError() {
this.errorRetries++;
if (this.errorRetries > CONFIG.MAX_RETRIES) {
this.updateStatus("错误次数过多,返回列表");
this.errorRetries = 0;
setTimeout(() => {
window.location.href = `${this.api.baseURL}/latest`;
}, 2000);
} else {
this.updateStatus(`错误页面,重试 ${this.errorRetries}/${CONFIG.MAX_RETRIES}`);
setTimeout(() => this.navigateToNextTopic(), 2000);
}
}
async readCurrentTopic() {
if (!this.isReading) return;
// 检查错误页面
if (this.isErrorPage()) {
this.handleError();
return;
}
const topicInfo = this.api.getCurrentTopicInfo();
if (!topicInfo) {
this.updateStatus("获取话题失败,返回列表...");
setTimeout(() => this.navigateToNextTopic(), 2000);
return;
}
// 传入起始楼层
this.stats.recordTopicVisit(topicInfo.topicId, topicInfo.title, topicInfo.currentPost);
this.currentTopic = topicInfo;
// 设置最后记录的楼层为当前楼层-1
this.lastRecordedPost = topicInfo.currentPost - 1;
this.updateStatus(`正在浏览: ${topicInfo.title}`);
// 检查返回按钮
const backButton = document.querySelector('[title="返回上一个未读帖子"]');
if (backButton) {
backButton.click();
}
if (this.isPageVisible) {
this.startSmoothScrolling();
}
}
// 滚动行为
startSmoothScrolling() {
if (this.scrollTimer) return;
let scrollSpeed = CONFIG.SCROLL_SPEED;
let lastVariation = 0;
const scrollStep = () => {
if (!this.isReading || !this.isPageVisible) {
this.scrollTimer = null;
return;
}
const timestamp = performance.now();
// 控制滚动频率
if (timestamp - this.lastScrollTime < CONFIG.SCROLL_INTERVAL) {
this.scrollTimer = requestAnimationFrame(scrollStep);
return;
}
this.lastScrollTime = timestamp;
// 检查是否到达底部
if (this.api.isAtBottomOfTopic()) {
this.updateStatus("已到达话题底部,准备跳转...");
this.clearTimers();
// 确保记录最后的楼层
const finalInfo = this.api.getCurrentTopicInfo();
if (finalInfo && finalInfo.currentPost > this.lastRecordedPost) {
this.stats.recordPostRead(this.currentTopic.topicId, finalInfo.currentPost);
}
setTimeout(() => {
this.navigateToNextTopic();
}, CONFIG.PAGE_TRANSITION_DELAY);
return;
}
// 添加随机变化,让滚动更自然
if (Math.random() < 0.1) {
// 10%概率改变速度
lastVariation = (Math.random() - 0.5) * CONFIG.SCROLL_VARIATION;
}
const currentSpeed = Math.max(10, scrollSpeed + lastVariation);
window.scrollBy(0, currentSpeed);
// 处理可见帖子
this.processVisiblePosts();
// 继续下一帧
this.scrollTimer = requestAnimationFrame(scrollStep);
};
// 开始滚动动画
this.scrollTimer = requestAnimationFrame(scrollStep);
}
// 处理可见帖子和更新进度
processVisiblePosts() {
const visiblePosts = this.api.getVisiblePosts();
// 获取当前楼层信息并更新帖子进度
const topicInfo = this.api.getCurrentTopicInfo();
if (topicInfo && topicInfo.currentPost > this.lastRecordedPost) {
// 记录新的帖子阅读进度
this.stats.recordPostRead(this.currentTopic.topicId, topicInfo.currentPost);
this.lastRecordedPost = topicInfo.currentPost;
// 更新状态显示
const floorInfo = topicInfo.totalPosts ? `楼层:${topicInfo.currentPost}/${topicInfo.totalPosts}` : `楼层:${topicInfo.currentPost}`;
this.updateStatus(`正在浏览: ${topicInfo.title} (${floorInfo})`);
}
// 标记完全可见的帖子为已读
visiblePosts.forEach((post) => {
if (post.isFullyVisible && !this.readPosts.has(post.id)) {
this.readPosts.add(post.id);
// 自动点赞逻辑
if (this.config.get("autoLike") && this.shouldLikePost(post)) {
const delay = Math.random() * (CONFIG.LIKE_INTERVAL_MAX - CONFIG.LIKE_INTERVAL_MIN) + CONFIG.LIKE_INTERVAL_MIN;
setTimeout(() => {
this.api.likePost(post.id).then((success) => {
if (success) this.stats.recordLike();
});
}, delay);
}
}
});
}
shouldLikePost(post) {
// 检查今日点赞限制
if (this.stats.stats.todayLikes >= CONFIG.LIKE_LIMIT) {
return false;
}
// 检查总点赞目标
const progress = this.stats.getProgress();
if (progress.likes.current >= progress.likes.goal) {
return false;
}
// 随机点赞概率,拟人模式下概率更低
const likeChance = this.config.get("humanMode") ? 0.08 : 0.15;
return Math.random() < likeChance;
}
loadTopicList() {
this.updateStatus("加载话题列表...");
const topics = this.api.getTopicsFromList();
if (topics.length === 0) {
this.updateStatus("加载更多话题...");
window.scrollTo(0, document.body.scrollHeight);
setTimeout(() => this.loadTopicList(), 2000);
return;
}
// 过滤未读或未完成的话题
const filteredTopics = topics.filter((topic) => {
const topicStats = this.stats.stats.topics[topic.id];
if (!topicStats) return true;
if (topic.hasNew) return true;
if ((topicStats.maxPostRead || 0) < topic.postsCount) return true;
return false;
});
// 优先处理有新内容的话题
filteredTopics.sort((a, b) => {
if (a.hasNew && !b.hasNew) return -1;
if (!a.hasNew && b.hasNew) return 1;
return a.postsCount - b.postsCount;
});
this.topicQueue = filteredTopics.slice(0, this.config.get("topicLimit"));
if (this.topicQueue.length === 0) {
this.updateStatus("没有新话题,滚动加载...");
window.scrollTo(0, document.body.scrollHeight);
setTimeout(() => this.loadTopicList(), 3000);
return;
}
this.navigateToNextTopic();
}
navigateToNextTopic() {
if (!this.isReading) return;
this.clearTimers();
this.readPosts.clear();
this.lastRecordedPost = 0;
if (this.topicQueue.length === 0) {
this.updateStatus("返回话题列表...");
setTimeout(() => {
window.location.href = `${this.api.baseURL}/latest`;
}, CONFIG.PAGE_TRANSITION_DELAY);
return;
}
const nextTopic = this.topicQueue.shift();
this.updateStatus(`准备进入: ${nextTopic.title}`);
let url = `${this.api.baseURL}${nextTopic.href}`;
// 从上次阅读位置继续
const topicStats = this.stats.stats.topics[nextTopic.id];
if (topicStats && topicStats.maxPostRead > 0) {
const targetPost = Math.min(topicStats.maxPostRead + 1, nextTopic.postsCount);
url = url.replace(/\/\d+$/, "") + `/${targetPost}`;
}
// 拟人模式下增加随机延迟
const delay = this.config.get("humanMode") ? CONFIG.PAGE_TRANSITION_DELAY + Math.random() * 2000 : CONFIG.PAGE_TRANSITION_DELAY;
setTimeout(() => {
window.location.href = url;
}, delay);
}
}
// 配置管理
class ConfigManager {
constructor() {
this.defaults = {
autoRead: false,
autoLike: false,
scrollSpeed: 30,
scrollDelay: 150,
readDelay: 2000,
commentLimit: 1000,
topicLimit: 20,
likeLimit: 30,
humanMode: true,
};
this.load();
}
load() {
const stored = localStorage.getItem("dar_config");
this.config = stored ? { ...this.defaults, ...JSON.parse(stored) } : { ...this.defaults };
}
save() {
localStorage.setItem("dar_config", JSON.stringify(this.config));
}
get(key) {
return this.config[key];
}
set(key, value) {
this.config[key] = value;
this.save();
}
}
// UI控制类
class UIController {
constructor(config, stats, reader) {
this.config = config;
this.stats = stats;
this.reader = reader;
this.isExpanded = false;
this.updateTimerId = null;
this.init();
}
init() {
this.createUI();
this.bindEvents();
this.updateDisplay();
this.reader.setStatusCallback((status) => {
this.updateCurrentTopicDisplay(status);
});
this.startUpdateLoop();
document.addEventListener("visibilitychange", () => {
if (!document.hidden && !this.updateTimerId) {
this.startUpdateLoop();
}
});
}
startUpdateLoop() {
const update = () => {
if (document.hidden) {
this.updateTimerId = null;
return;
}
this.updateDisplay();
this.updateTimerId = setTimeout(update, 1000);
};
if (this.updateTimerId) {
clearTimeout(this.updateTimerId);
}
this.updateTimerId = setTimeout(update, 1000);
}
createUI() {
const container = document.createElement("div");
container.className = "dar-container";
container.innerHTML = `
<button class="dar-toggle-btn">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
</button>
<div class="dar-panel">
<div class="dar-header">
<h3>📚 自动阅读</h3>
<p>自动浏览论坛内容</p>
</div>
<div class="dar-content">
<div class="dar-current-topic" id="dar-current-topic" style="display: none;">
<div class="dar-current-topic-title" id="dar-current-title">准备就绪</div>
<div class="dar-current-topic-floor" id="dar-current-floor">等待开始...</div>
</div>
<div class="dar-progress">
<div class="dar-progress-item">
<div class="dar-progress-label">
<span>话题进度</span>
<span id="dar-topics-count">0/500</span>
</div>
<div class="dar-progress-bar">
<div class="dar-progress-fill" id="dar-topics-progress" style="width: 0%"></div>
</div>
</div>
<div class="dar-progress-item">
<div class="dar-progress-label">
<span>帖子进度</span>
<span id="dar-posts-count">0/20000</span>
</div>
<div class="dar-progress-bar">
<div class="dar-progress-fill" id="dar-posts-progress" style="width: 0%"></div>
</div>
</div>
<div class="dar-progress-item">
<div class="dar-progress-label">
<span>点赞进度</span>
<span id="dar-likes-count">0/30</span>
</div>
<div class="dar-progress-bar">
<div class="dar-progress-fill" id="dar-likes-progress" style="width: 0%"></div>
</div>
</div>
</div>
<div class="dar-status">
<div class="dar-status-item">
<span>当前状态</span>
<span class="dar-status-value" id="dar-status">待机</span>
</div>
<div class="dar-status-item">
<span>今日点赞</span>
<span class="dar-status-value" id="dar-today-likes">0</span>
</div>
</div>
<button class="dar-main-button" id="dar-main-btn">
开始阅读
</button>
<div class="dar-divider"></div>
<div class="dar-control-group">
<div class="dar-control-label">
<span>自动点赞</span>
<div class="dar-switch" id="dar-like-switch">
<div class="dar-switch-handle"></div>
</div>
</div>
</div>
<div class="dar-control-group">
<div class="dar-control-label">
<span>拟人模式</span>
<div class="dar-switch" id="dar-human-switch">
<div class="dar-switch-handle"></div>
</div>
</div>
</div>
</div>
</div>
`;
document.body.appendChild(container);
this.container = container;
}
bindEvents() {
this.container.querySelector(".dar-toggle-btn").addEventListener("click", () => {
this.isExpanded = !this.isExpanded;
this.container.classList.toggle("expanded", this.isExpanded);
});
document.querySelector("#dar-main-btn").addEventListener("click", () => {
if (this.reader.isReading) {
this.reader.stop();
} else {
this.reader.start();
}
this.updateDisplay();
});
this.bindSwitch("dar-like-switch", "autoLike");
this.bindSwitch("dar-human-switch", "humanMode");
}
bindSwitch(elementId, configKey) {
const switchEl = document.getElementById(elementId);
switchEl.addEventListener("click", () => {
const newValue = !this.config.get(configKey);
this.config.set(configKey, newValue);
switchEl.classList.toggle("active", newValue);
});
switchEl.classList.toggle("active", this.config.get(configKey));
}
updateCurrentTopicDisplay(status) {
const topicDiv = document.querySelector("#dar-current-topic");
const titleEl = document.querySelector("#dar-current-title");
const floorEl = document.querySelector("#dar-current-floor");
if (this.reader.isReading) {
topicDiv.style.display = "block";
// 解析状态信息
if (status.includes("正在浏览:")) {
const parts = status.split("(");
titleEl.textContent = parts[0].trim();
floorEl.textContent = parts[1] ? parts[1].replace(")", "").trim() : status;
} else {
titleEl.textContent = "当前状态";
floorEl.textContent = status;
}
} else {
topicDiv.style.display = "none";
}
}
updateDisplay() {
const progress = this.stats.getProgress();
// 更新进度条
document.querySelector("#dar-topics-count").textContent = `${progress.topics.current}/${progress.topics.goal}`;
document.querySelector("#dar-topics-progress").style.width = `${progress.topics.percentage}%`;
document.querySelector("#dar-posts-count").textContent = `${progress.posts.current}/${progress.posts.goal}`;
document.querySelector("#dar-posts-progress").style.width = `${progress.posts.percentage}%`;
document.querySelector("#dar-likes-count").textContent = `${progress.likes.current}/${progress.likes.goal}`;
document.querySelector("#dar-likes-progress").style.width = `${progress.likes.percentage}%`;
// 更新状态
const statusEl = document.querySelector("#dar-status");
if (this.reader.isReading) {
statusEl.textContent = "阅读中";
statusEl.classList.add("active");
} else {
statusEl.textContent = "待机";
statusEl.classList.remove("active");
}
document.querySelector("#dar-today-likes").textContent = this.stats.stats.todayLikes;
// 更新按钮
const btn = document.querySelector("#dar-main-btn");
if (this.reader.isReading) {
btn.textContent = "停止阅读";
btn.classList.add("stop");
} else {
btn.textContent = "开始阅读";
btn.classList.remove("stop");
}
}
}
// 初始化
function init() {
if (window.self !== window.top) return;
/* if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
return;
} */
const config = new ConfigManager();
const stats = new StatsManager();
const api = new DiscourseAPI();
const reader = new SmartReader(config, stats, api);
const ui = new UIController(config, stats, reader);
// 自动启动
if (config.get("autoRead")) {
setTimeout(() => {
reader.start();
}, 2000);
}
// 全局暴露
window.DAR = { config, stats, api, reader, ui };
}
init();
})();