Greasy Fork

Greasy Fork is available in English.

Auto Read

自动刷linuxdo文章

当前为 2025-08-16 提交的版本,查看 最新版本

// ==UserScript==
// @name         Auto Read
// @namespace    http://tampermonkey.net/
// @version      1.5.0
// @description  自动刷linuxdo文章
// @author       liuweiqing
// @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: 80px;
      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;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .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: 50, // 滚动速度
    SCROLL_INTERVAL: 100, // 滚动间隔
    LIKE_LIMIT: 30, // 每日点赞限制
    LIKE_INTERVAL_MIN: 2000, // 点赞最小间隔
    LIKE_INTERVAL_MAX: 5000, // 点赞最大间隔
    MAX_RETRIES: 3, // 最大重试次数
  };

  // 统计管理类
  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();
      }
    }

    recordTopicVisit(topicId, title = "") {
      if (!this.stats.topics[topicId]) {
        this.stats.topics[topicId] = {
          title: title,
          visitCount: 0,
          lastVisit: Date.now(),
          maxPostRead: 0,
        };
        this.stats.topicsVisited++;
      }

      this.stats.topics[topicId].visitCount++;
      this.stats.topics[topicId].lastVisit = Date.now();
      if (title) {
        this.stats.topics[topicId].title = title;
      }
      this.save();
    }

    recordPostRead(topicId, postNumber) {
      if (this.stats.topics[topicId]) {
        const previousMax = this.stats.topics[topicId].maxPostRead || 0;
        if (postNumber > previousMax) {
          const newPostsRead = postNumber - previousMax;
          this.stats.postsRead += newPostsRead;
          this.stats.topics[topicId].maxPostRead = postNumber;
          this.save();
        }
      }
    }

    recordLike() {
      this.stats.likesGiven++;
      this.stats.todayLikes++;
      this.save();
    }

    getProgress() {
      const daysElapsed = Math.floor((Date.now() - this.stats.startDate) / (1000 * 60 * 60 * 24));

      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),
        },
        days: {
          elapsed: daysElapsed,
          remaining: Math.max(0, this.goals.days - daysElapsed),
        },
      };
    }
  }

  // Discourse API 交互类
  class DiscourseAPI {
    constructor() {
      this.baseURL = this.getCurrentBaseURL();
      this.csrfToken = this.getCSRFToken();
    }

    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 = parsed.postNumber;
      let totalPosts = 0;

      // 从时间线获取准确信息
      const timelineReplies = document.querySelector("div.timeline-replies");
      if (timelineReplies) {
        const timelineText = timelineReplies.textContent.trim();
        const parts = timelineText.replace(/[^0-9/]/g, "").split("/");
        if (parts.length >= 2) {
          currentPost = parseInt(parts[0]) || currentPost;
          totalPosts = parseInt(parts[1]) || totalPosts;
        }
      }

      // 备用方法:从进度条获取
      if (totalPosts === 0) {
        const progressBar = document.querySelector(".topic-navigation .post-info");
        if (progressBar) {
          const match = progressBar.textContent.match(/(\d+)\s*\/\s*(\d+)/);
          if (match) {
            currentPost = parseInt(match[1]);
            totalPosts = parseInt(match[2]);
          }
        }
      }

      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]");

      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 < window.innerHeight && rect.bottom > 0;

          posts.push({
            id: postId,
            number: parseInt(postNumber),
            element: element,
            isVisible: isVisible,
            height: rect.height,
          });
        }
      });

      return posts;
    }

    async likePost(postId) {
      try {
        const selectors = [
          `#post_${postId} .discourse-reactions-reaction-button button`,
          `[data-post-id="${postId}"] .discourse-reactions-reaction-button button`,
          `#post_${postId} .like-button`,
          `[data-post-id="${postId}"] .like-button`,
          `#discourse-reactions-actions-${postId}-right .btn-toggle-reaction-like`,
        ];

        for (const selector of selectors) {
          const likeButton = document.querySelector(selector);
          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() {
      // 检查timeline-replies元素(最可靠的方法)
      const timelineReplies = document.querySelector("div.timeline-replies");
      if (timelineReplies) {
        const timelineText = timelineReplies.textContent.trim();
        const parts = timelineText.replace(/[^0-9/]/g, "").split("/");

        // 如果格式为 "x/y" 且 x === y,说明已到底部
        if (parts.length >= 2 && parts[0] === parts[1]) {
          return true;
        }
      }

      // 备用检测:检查是否有建议话题等结束标识
      const endIndicators = [".suggested-topics", ".topic-footer-buttons", ".topic-map.--op", ".topic-status-info"];

      for (const selector of endIndicators) {
        const element = document.querySelector(selector);
        if (element) {
          const rect = element.getBoundingClientRect();
          if (rect.top < window.innerHeight) {
            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.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("启动中...");

      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));
    }

    async readCurrentTopic() {
      if (!this.isReading) return;

      const topicInfo = this.api.getCurrentTopicInfo();
      if (!topicInfo) {
        this.updateStatus("获取话题失败,返回列表...");
        setTimeout(() => this.navigateToNextTopic(), 2000);
        return;
      }

      this.stats.recordTopicVisit(topicInfo.topicId, topicInfo.title);
      this.currentTopic = topicInfo;
      this.updateStatus(`正在浏览: ${topicInfo.title} `);

      if (this.isPageVisible) {
        this.startSmoothScrolling();
      }
    }

    // 借鉴第二个脚本的平滑滚动方法
    startSmoothScrolling() {
      if (this.scrollTimer) return;

      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();
          setTimeout(() => {
            this.navigateToNextTopic();
          }, 2000);
          return;
        }

        // 执行滚动
        window.scrollBy(0, CONFIG.SCROLL_SPEED);

        // 处理可见帖子
        this.markReadPosts();

        // 继续下一帧
        this.scrollTimer = requestAnimationFrame(scrollStep);
      };

      // 开始滚动动画
      this.scrollTimer = requestAnimationFrame(scrollStep);
    }

    markReadPosts() {
      const visiblePosts = this.api.getVisiblePosts();

      // 更新当前楼层信息
      if (visiblePosts.length > 0) {
        const topicInfo = this.api.getCurrentTopicInfo();
        if (topicInfo) {
          this.updateStatus(`正在浏览: ${topicInfo.title} (楼层:${topicInfo.currentPost ? topicInfo.currentPost : "?"} / ${topicInfo.totalPosts || "?"})`);
        }
      }

      // 记录已读帖子
      visiblePosts.forEach((post) => {
        if (post.isVisible && !this.readPosts.has(post.id)) {
          this.readPosts.add(post.id);
          this.stats.recordPostRead(this.currentTopic.topicId, post.number);

          // 自动点赞逻辑
          if (this.config.get("autoLike") && this.shouldLikePost(post)) {
            setTimeout(() => {
              this.api.likePost(post.id).then((success) => {
                if (success) this.stats.recordLike();
              });
            }, Math.random() * (CONFIG.LIKE_INTERVAL_MAX - CONFIG.LIKE_INTERVAL_MIN) + CONFIG.LIKE_INTERVAL_MIN);
          }
        }
      });
    }

    shouldLikePost(post) {
      const progress = this.stats.getProgress();

      if (this.stats.stats.todayLikes >= CONFIG.LIKE_LIMIT) {
        return false;
      }

      if (progress.likes.current >= progress.likes.goal) {
        return false;
      }

      const likeChance = this.config.get("humanMode") ? 0.1 : 0.2;
      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();

      if (this.topicQueue.length === 0) {
        this.updateStatus("返回话题列表...");
        setTimeout(() => {
          window.location.href = `${this.api.baseURL}/latest`;
        }, 1500);
        return;
      }

      const nextTopic = this.topicQueue.shift();
      this.updateStatus(`准备进入: ${nextTopic.title.substring(0, 20)}...`);

      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") ? 2000 + Math.random() * 3000 : 1000;

      setTimeout(() => {
        window.location.href = url;
      }, delay);
    }
  }

  // 配置管理类
  class ConfigManager {
    constructor() {
      this.defaults = {
        autoRead: false,
        autoLike: false,
        scrollSpeed: 50,
        scrollDelay: 100,
        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 class="dar-status-item">
                <span>剩余天数</span>
                <span class="dar-status-value" id="dar-days-left">100</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";
        titleEl.textContent = status.includes(":") ? status.split(":")[0] : "当前状态";
        floorEl.textContent = status.includes(":") ? status.split(":")[1] || status : 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;
      document.querySelector("#dar-days-left").textContent = progress.days.remaining;

      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();
})();