自动浏览linux.do的帖子和话题

自动浏览linux.do的帖子和话题,智能滚动和加载检测,基于https://greasyfork.org/zh-CN/scripts/490382-%E8%87%AA%E5%8A%A8%E6%B5%8F%E8%A7%88linux-do-autobrowse-linux-do,二开

// ==UserScript==
// @name        自动浏览linux.do的帖子和话题
// @description 自动浏览linux.do的帖子和话题,智能滚动和加载检测,基于https://greasyfork.org/zh-CN/scripts/490382-%E8%87%AA%E5%8A%A8%E6%B5%8F%E8%A7%88linux-do-autobrowse-linux-do,二开
// @namespace   Violentmonkey Scripts
// @match       https://linux.do/*
// @match       https://*.linux.do/*
// @grant       GM_registerMenuCommand
// @grant       GM_unregisterMenuCommand
// @grant       GM_addStyle
// @run-at      document-idle
// @noframes
// @version     1.2.6
// @author      ryen & GPT-5
// @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,

        // 人性化滚动参数(鼠标滚轮风格)
        wheelStepMin: 12,      // 单次滚轮步进(px)
        wheelStepMax: 45,
        wheelBurstMin: 3,      // 一次滚轮操作包含的步数
        wheelBurstMax: 8,
        wheelIntervalMin: 14,  // 步与步之间的间隔(ms),模拟帧
        wheelIntervalMax: 28,

        microPauseChance: 0.35,   // 突发后微停顿概率
        microPauseMin: 250,
        microPauseMax: 1100,

        upScrollChance: 0,        // 关闭上划回看
        upScrollMin: 20,
        upScrollMax: 90,

        longRestChance: 0.03,     // 偶发较长休息(模拟看手机/思考)
        longRestMin: 2500,
        longRestMax: 7000,

        dwellSelectors: 'h1, h2, h3, h4, h5, h6, img, pre, code, blockquote, .onebox, .lightbox',
        dwellMin: 800,
        dwellMax: 2000
    },
    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;
        }
    }
};

// 默认设置(可在油猴弹窗中快捷开关)
const DEFAULT_SETTINGS = {
    enableMicroPause: true,
    enableLongRest: true,
    enableDwell: true,
    enableUpScroll: false, // 保持“取消回看”的默认
    speedPreset: 'normal',   // 可选: 'slow' | 'normal' | 'fast'
    // 概率参数(0~1)
    microPauseChance: 0.35,
    longRestChance: 0.03,
    upScrollChance: 0
};

const Settings = {
    key: 'autoBrowse_settings',
    load() {
        return Object.assign({}, DEFAULT_SETTINGS, Storage.get(this.key, {}));
    },
    save(newSettings) {
        Storage.set(this.key, newSettings);
    }
};

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.settings = Settings.load();
        this.applySettings();

        // 菜单项句柄缓存
        this.menuItems = {};

        this.setupButton();

        // 注册油猴菜单
        this.registerMenu();

        // 绑定快捷键(Alt+Shift+A 开始/停止;Alt+Shift+S 设置)
        this.bindShortcuts();

        // 如果是第一次使用,先处理必读文章
        if (!this.firstUseChecked) {
            this.handleFirstUse();
        } else if (this.autoRunning) {
            if (this.isTopicPage) {
                this.startScrolling();
            } else {
                this.getLatestTopics().then(() => this.navigateNextTopic());
            }
        }
    }

    // 根据设置动态调整 CONFIG 行为
    applySettings() {
        // 概率项:读取设置并按开关启用(范围 0~1)
        const clamp01 = (v) => Math.max(0, Math.min(1, Number(v)) || 0);
        CONFIG.scroll.upScrollChance = this.settings.enableUpScroll ? clamp01(this.settings.upScrollChance) : 0;
        CONFIG.scroll.microPauseChance = this.settings.enableMicroPause ? clamp01(this.settings.microPauseChance) : 0;
        CONFIG.scroll.longRestChance = this.settings.enableLongRest ? clamp01(this.settings.longRestChance) : 0;

        // 驻留
        this.dwellEnabled = !!this.settings.enableDwell;

        // 速度预设:调整步进与间隔
        if (this.settings.speedPreset === 'slow') {
            CONFIG.scroll.wheelStepMin = 10;
            CONFIG.scroll.wheelStepMax = 28;
            CONFIG.scroll.wheelIntervalMin = 18;
            CONFIG.scroll.wheelIntervalMax = 35;
        } else if (this.settings.speedPreset === 'fast') {
            CONFIG.scroll.wheelStepMin = 20;
            CONFIG.scroll.wheelStepMax = 55;
            CONFIG.scroll.wheelIntervalMin = 10;
            CONFIG.scroll.wheelIntervalMax = 22;
        } else {
            // normal
            CONFIG.scroll.wheelStepMin = 12;
            CONFIG.scroll.wheelStepMax = 45;
            CONFIG.scroll.wheelIntervalMin = 14;
            CONFIG.scroll.wheelIntervalMax = 28;
        }
    }

    // 创建/更新控制按钮
    setupButton() {
        // 由于站点 CSP 禁止 inline-style,这里不设置任何样式,仅放置一个原生按钮。
        // 为提升可见性:
        // - 按钮拥有固定 id:ab-start-stop
        // - 始终插入到 <body> 最前面
        // - 若被页面移除(SPA 重渲染),将自动重建
        let btn = document.getElementById('ab-start-stop');
        if (!btn) {
            btn = document.createElement('button');
            btn.id = 'ab-start-stop';
            btn.type = 'button';
            btn.title = 'AutoBrowse 开始/停止(Alt+Shift+A)';
            btn.addEventListener('click', () => this.handleButtonClick());
        }
        btn.textContent = this.autoRunning ? '停止' : '开始阅读';
        btn.classList.toggle('ab-running', !!this.autoRunning);

        // 优先挂到“话题页右侧时间轴”以实现接近“右侧中部悬浮”的视觉位置(无需样式)
        // 话题页常见容器:.timeline-container 或 .topic-timeline
        const rightTimeline = this.isTopicPage && (document.querySelector('.timeline-container') || document.querySelector('.topic-timeline'));
        // 非话题页或不存在时间轴时,退化到站点头部(.d-header / header),再退到 body
        const header = document.querySelector('.d-header') || document.querySelector('header');
        let container = rightTimeline || header || document.body;
        if (!container.contains(btn)) {
            // 尽量作为容器的第一个元素,便于发现
            container.insertBefore(btn, container.firstChild || null);
        }
        this.button = btn;

        // 注入样式(右侧中部悬浮胶囊按钮),在严格 CSP 下尝试以 <style> 注入;若被拦截则忽略
        this.ensureButtonStyles();

        // 监听 body 变化,若按钮被移除则重建
        if (!this._btnObserver) {
            this._btnObserver = new MutationObserver(() => {
                const exists = document.getElementById('ab-start-stop');
                if (!exists) {
                    // 重建
                    const b = document.createElement('button');
                    b.id = 'ab-start-stop';
                    b.type = 'button';
                    b.title = 'AutoBrowse 开始/停止(Alt+Shift+A)';
                    b.textContent = this.autoRunning ? '停止' : '开始阅读';
                    b.classList.toggle('ab-running', !!this.autoRunning);
                    b.addEventListener('click', () => this.handleButtonClick());
                    const rt = this.isTopicPage && (document.querySelector('.timeline-container') || document.querySelector('.topic-timeline'));
                    const hdr = document.querySelector('.d-header') || document.querySelector('header');
                    const parent = rt || hdr || document.body;
                    parent.insertBefore(b, parent.firstChild || null);
                    this.button = b;
                }
            });
            const target = (this.isTopicPage && (document.querySelector('.timeline-container') || document.querySelector('.topic-timeline'))) 
                || document.querySelector('.d-header') 
                || document.querySelector('header') 
                || document.body;
            this._btnObserver.observe(target, { childList: true, subtree: true });
        }

        // 兜底定时器,防止极端情况下丢失
        if (!this._btnInterval) {
            this._btnInterval = setInterval(() => {
                const exists = document.getElementById('ab-start-stop');
                if (!exists) {
                    this.setupButton();
                }
            }, 3000);
        }
    }

    // 确保开始/停止按钮的样式被注入(右侧中部悬浮胶囊按钮)
    ensureButtonStyles() {
        try {
            if (this._styleInjected || document.getElementById('ab-style')) return;
            const css = `
            #ab-start-stop {
              position: fixed;
              top: 50%;
              right: 12px;
              transform: translateY(-50%);
              z-index: 2147483647;
              padding: 8px 14px;
              border-radius: 999px;
              border: 1px solid rgba(0,0,0,.12);
              background: #1f6feb;
              color: #fff;
              font: 600 14px/1 system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji";
              cursor: pointer;
              box-shadow: 0 6px 16px rgba(0,0,0,.12);
            }
            #ab-start-stop.ab-running {
              background: #d11a2a;
              border-color: rgba(0,0,0,.12);
            }
            #ab-start-stop:hover, #ab-start-stop:focus {
              filter: brightness(0.95);
              outline: none;
            }
            #ab-start-stop.ab-running:hover, #ab-start-stop.ab-running:focus {
              filter: brightness(0.92);
            }
            @media (max-width: 900px) {
              #ab-start-stop { right: 8px; padding: 7px 12px; }
            }`;
            if (typeof GM_addStyle === 'function') {
                GM_addStyle(css);
                this._styleInjected = true;
                return;
            }
            const style = document.createElement('style');
            style.id = 'ab-style';
            style.type = 'text/css';
            style.appendChild(document.createTextNode(css));
            document.documentElement.appendChild(style);
            this._styleInjected = true;
        } catch (e) {
            // 在严格 CSP 下可能被拦截,保持静默;按钮仍然可用
        }
    }

    // 在扩展图标弹出的菜单中提供快捷开关(动态标签),并提供“控制面板”入口
    registerMenu() {
        this.updateMenuCommands();
    }

    updateMenuCommands() {
        try {
            if (typeof GM_registerMenuCommand !== 'function') return;
            console.log('[autoBrowse] 注册菜单: GM_registerMenuCommand 可用');

            // 移除旧菜单
            if (typeof GM_unregisterMenuCommand === 'function' && this.menuItems) {
                Object.values(this.menuItems).forEach(id => {
                    try { GM_unregisterMenuCommand(id); } catch {}
                });
            }
            this.menuItems = {};
            console.log('[autoBrowse] 清理旧菜单完成, 开始注册新菜单');

            const s = this.settings;

            // 启动/停止(避免按钮受 CSP 影响不可见)
            this.menuItems.startStop = GM_registerMenuCommand(this.autoRunning ? '■ 停止' : '▶ 开始阅读', () => this.handleButtonClick());

            // 控制面板入口
            this.menuItems.controlPanel = GM_registerMenuCommand('⚙️ 打开控制面板', () => this.showControlPanel());
            console.log('[autoBrowse] 菜单已注册: 控制面板、开关与速度预设');

            // 快速开关(保留动态菜单)
            this.menuItems.microPause = GM_registerMenuCommand(`⏸ 微停顿:${s.enableMicroPause ? '开' : '关'}`, () => this.toggleSetting('enableMicroPause'));
            this.menuItems.longRest   = GM_registerMenuCommand(`😴 长休息:${s.enableLongRest ? '开' : '关'}`, () => this.toggleSetting('enableLongRest'));
            this.menuItems.dwell      = GM_registerMenuCommand(`📌 驻留元素:${s.enableDwell ? '开' : '关'}`, () => this.toggleSetting('enableDwell'));
            this.menuItems.upScroll   = GM_registerMenuCommand(`🔼 回看上划:${s.enableUpScroll ? '开' : '关'}`, () => this.toggleSetting('enableUpScroll'));
            this.menuItems.speed      = GM_registerMenuCommand(`🚀 速度预设:${s.speedPreset}`, () => {
                const order = ['slow', 'normal', 'fast'];
                const idx = order.indexOf(this.settings.speedPreset);
                this.settings.speedPreset = order[(idx + 1) % order.length];
                Settings.save(this.settings);
                this.applySettings();
                this.updateMenuCommands();
            });
        } catch (e) {
            console.warn('[autoBrowse] 注册菜单失败:', e);
        }
    }

    toggleSetting(key) {
        this.settings[key] = !this.settings[key];
        Settings.save(this.settings);
        this.applySettings();
        this.updateMenuCommands();
    }

    // 使用 <dialog> 构建控制面板,避免任何 inline-style 以绕过严苛 CSP
    showControlPanel() {
        const exist = document.getElementById('autoBrowse-control-panel');
        if (exist) exist.remove();

        const dlg = document.createElement('dialog');
        dlg.id = 'autoBrowse-control-panel';
        // 仅使用语义元素,不设置 style 属性;采用表格与分组布局,提升可读性与“视觉宽度”
        dlg.innerHTML = `
<form method="dialog" aria-label="AutoBrowse 控制面板">
  <header>
    <h2>AutoBrowse 控制面板</h2>
  </header>
  <section>
    <fieldset>
      <legend>滚动行为</legend>
      <table width="100%">
        <colgroup>
          <col span="1">
          <col span="1">
        </colgroup>
        <tbody>
          <tr>
            <td><label for="ab-speed">速度预设</label></td>
            <td>
              <select id="ab-speed">
                <option value="slow" ${this.settings.speedPreset==='slow'?'selected':''}>slow(慢速)</option>
                <option value="normal" ${this.settings.speedPreset==='normal'?'selected':''}>normal(默认)</option>
                <option value="fast" ${this.settings.speedPreset==='fast'?'selected':''}>fast(快速)</option>
              </select>
            </td>
          </tr>
          <tr>
            <td><label for="ab-micro">微停顿</label></td>
            <td>
              <input id="ab-micro" type="checkbox" ${this.settings.enableMicroPause ? 'checked' : ''}>
              <small>模拟滚动后短暂停留,更像真人浏览</small>
            </td>
          </tr>
          <tr>
            <td><label for="ab-microChance">微停顿概率</label></td>
            <td>
              <input id="ab-microChance" type="number" step="0.01" min="0" max="1" value="${this.settings.microPauseChance}">
              <small>0~1,数值越大越频繁</small>
            </td>
          </tr>
          <tr>
            <td><label for="ab-long">长休息</label></td>
            <td>
              <input id="ab-long" type="checkbox" ${this.settings.enableLongRest ? 'checked' : ''}>
              <small>偶尔进行较长休息,模拟思考/看手机</small>
            </td>
          </tr>
          <tr>
            <td><label for="ab-longChance">长休息概率</label></td>
            <td>
              <input id="ab-longChance" type="number" step="0.01" min="0" max="1" value="${this.settings.longRestChance}">
              <small>0~1,建议较小(如 0.03)</small>
            </td>
          </tr>
          <tr>
            <td><label for="ab-dwell">驻留可读元素</label></td>
            <td>
              <input id="ab-dwell" type="checkbox" ${this.settings.enableDwell ? 'checked' : ''}>
              <small>在标题/图片/代码块等位置稍作停留</small>
            </td>
          </tr>
          <tr>
            <td><label for="ab-up">回看上划</label></td>
            <td>
              <input id="ab-up" type="checkbox" ${this.settings.enableUpScroll ? 'checked' : ''}>
              <small>偶尔向上滚动回看</small>
            </td>
          </tr>
          <tr>
            <td><label for="ab-upChance">回看概率</label></td>
            <td>
              <input id="ab-upChance" type="number" step="0.01" min="0" max="1" value="${this.settings.upScrollChance}">
              <small>0~1,默认 0(关闭)</small>
            </td>
          </tr>
        </tbody>
      </table>
    </fieldset>

    <hr>
    <p><small>提示:按 Enter 立即保存,按 Esc 关闭面板。</small></p>
  </section>
  <menu>
    <button id="ab-cancel" value="cancel">取消</button>
    <button id="ab-save" value="default">保存</button>
  </menu>
</form>`;

        document.body.appendChild(dlg);
        if (typeof dlg.showModal === 'function') dlg.showModal();

        const close = () => { try { dlg.close(); } catch {} dlg.remove(); };
        dlg.querySelector('#ab-close').addEventListener('click', (e) => { e.preventDefault(); close(); });
        dlg.querySelector('#ab-cancel').addEventListener('click', (e) => { e.preventDefault(); close(); });
        dlg.querySelector('#ab-save').addEventListener('click', (e) => {
            e.preventDefault();
            const clamp01 = (v) => Math.max(0, Math.min(1, Number(v) || 0));
            const next = {
                enableMicroPause: dlg.querySelector('#ab-micro').checked,
                enableLongRest: dlg.querySelector('#ab-long').checked,
                enableDwell: dlg.querySelector('#ab-dwell').checked,
                enableUpScroll: dlg.querySelector('#ab-up').checked,
                speedPreset: dlg.querySelector('#ab-speed').value,
                microPauseChance: clamp01(dlg.querySelector('#ab-microChance').value),
                longRestChance: clamp01(dlg.querySelector('#ab-longChance').value),
                upScrollChance: clamp01(dlg.querySelector('#ab-upChance').value)
            };
            this.settings = Object.assign({}, this.settings, next);
            Settings.save(this.settings);
            this.applySettings();
            this.updateMenuCommands();
            close();
            window.location.reload();
        });
    }

    // 键盘快捷键,避免按钮受 CSP 限制不可见
    bindShortcuts() {
        window.addEventListener('keydown', (ev) => {
            // Alt+Shift+A 开始/停止
            if (ev.altKey && ev.shiftKey && (ev.key === 'A' || ev.key === 'a')) {
                ev.preventDefault();
                this.handleButtonClick();
            }
            // Alt+Shift+S 打开设置
            if (ev.altKey && ev.shiftKey && (ev.key === 'S' || ev.key === 's')) {
                ev.preventDefault();
                this.showControlPanel();
            }
        }, { passive: false });
    }

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

handleButtonClick() {
    if (this.isScrolling || this.autoRunning) {
        // 停止所有操作
        this.stopScrolling();
        this.autoRunning = false;
        Storage.set('autoRunning', false);
        if (this.button) {
            this.button.textContent = "开始阅读";
            this.button.classList.remove('ab-running');
        }
        // 刷新菜单文案
        this.updateMenuCommands();
    } else {
        // 开始运行
        this.autoRunning = true;
        Storage.set('autoRunning', true);
        if (this.button) {
            this.button.textContent = "停止";
            this.button.classList.add('ab-running');
        }
        // 刷新菜单文案
        this.updateMenuCommands();

        if (!this.firstUseChecked) {
            // 开始处理必读文章
            this.handleFirstUse();
        } else if (this.isTopicPage) {
            this.startScrolling();
        } else {
            this.getLatestTopics().then(() => this.navigateNextTopic());
        }
    }
}

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;
    if (this.button) {
        this.button.textContent = "停止";
        this.button.classList.add('ab-running');
    }
    this.lastActionTime = Date.now();

        while (this.isScrolling) {
            // 1) 鼠标滚轮“突发”滚动
            await this.performWheelBurst(1);

            // 2) 累计时间
            this.accumulateTime();

            // 3) 微停顿与偶发长休息(取消回看)
            await this.maybeMicroPause();
            await this.maybeLongRest();

            // 4) 对可读元素短驻留
            await this.maybeDwellOnReadable();

            // 5) 接近底部则尝试翻到下一篇
            if (Utils.isNearBottom()) {
                await Utils.sleep(600);
                if (Utils.isNearBottom() && Utils.isPageLoaded()) {
                    console.log("已到达页面底部,准备导航到下一篇文章...");
                    await Utils.sleep(800);
                    await this.navigateNextTopic();
                    break;
                }
            }
        }
    }

    // 模拟一次“滚轮突发”滚动(多步) direction: 1 向下, -1 向上
    async performWheelBurst(direction = 1) {
        const steps = Utils.random(CONFIG.scroll.wheelBurstMin, CONFIG.scroll.wheelBurstMax);
        for (let i = 0; i < steps && this.isScrolling; i++) {
            const step = Utils.random(CONFIG.scroll.wheelStepMin, CONFIG.scroll.wheelStepMax);
            const jitter = Utils.random(-2, 3); // 轻微抖动
            window.scrollBy({ top: direction * (step + jitter) });
            const interval = Utils.random(CONFIG.scroll.wheelIntervalMin, CONFIG.scroll.wheelIntervalMax);
            await Utils.sleep(interval);
        }
    }

    async maybeMicroPause() {
        if (Math.random() < CONFIG.scroll.microPauseChance) {
            const pause = Utils.random(CONFIG.scroll.microPauseMin, CONFIG.scroll.microPauseMax);
            await Utils.sleep(pause);
        }
    }

    async maybeLongRest() {
        if (Math.random() < CONFIG.scroll.longRestChance) {
            const rest = Utils.random(CONFIG.scroll.longRestMin, CONFIG.scroll.longRestMax);
            await Utils.sleep(rest);
        }
    }

    async maybeUpScroll() {
        if (Math.random() < CONFIG.scroll.upScrollChance) {
            const amount = Utils.random(CONFIG.scroll.upScrollMin, CONFIG.scroll.upScrollMax);
            // 上划通常步数少、幅度小
            await this.performWheelBurst(-1);
            // 再补一个更小的回看
            window.scrollBy({ top: -amount });
            await Utils.sleep(Utils.random(80, 160));
        }
    }

    async maybeDwellOnReadable() {
        if (!this.dwellEnabled) return;
        const el = this.findReadableElementNearCenter();
        if (!el) return;
        // 25% 概率驻留
        if (Math.random() < 0.25) {
            // 如果元素不在中心附近,则滚动至居中
            el.scrollIntoView({ behavior: 'smooth', block: 'center' });
            await Utils.sleep(Utils.random(200, 400));
            const dwell = Utils.random(CONFIG.scroll.dwellMin, CONFIG.scroll.dwellMax);
            await Utils.sleep(dwell);
        }
    }

    findReadableElementNearCenter() {
        const selectors = CONFIG.scroll.dwellSelectors;
        const nodes = Array.from(document.querySelectorAll(selectors));
        if (!nodes.length) return null;

        const vh = window.innerHeight;
        const vw = window.innerWidth;
        const centerY = vh / 2;
        const centerX = vw / 2;

        let best = null;
        let bestScore = Infinity;

        for (const n of nodes) {
            const r = n.getBoundingClientRect();
            // 忽略不可见或非常小的元素
            if (r.width < 20 || r.height < 16) continue;
            if (r.bottom < 0 || r.top > vh) continue;

            const elCenterY = r.top + r.height / 2;
            const elCenterX = r.left + r.width / 2;
            const dy = Math.abs(elCenterY - centerY);
            const dx = Math.abs(elCenterX - centerX);
            const score = dy + dx * 0.3; // 垂直更重要

            if (score < bestScore) {
                bestScore = score;
                best = n;
            }
        }

        // 只在中心阈值内才认为值得驻留
        if (best && bestScore < 180) return best;
        return null;
    }

    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);
        if (this.button) {
            this.button.textContent = "开始阅读";
            this.button.classList.remove('ab-running');
        }
    }

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