LINUXDO ReadBot

LINUXDO ReadBot是一个LINUXDO刷取已读帖量脚本,支持latest页面自动阅读和单个帖子阅读,理论上支持所有Discourse论坛

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        LINUXDO ReadBot
// @namespace   linux.do_ReadBot
// @match       https://linux.do/*
// @grant       GM_setValue
// @grant       GM_getValue
// @grant       GM_registerMenuCommand
// @version     1.0.2
// @author      Jay
// @description LINUXDO ReadBot是一个LINUXDO刷取已读帖量脚本,支持latest页面自动阅读和单个帖子阅读,理论上支持所有Discourse论坛
// @description:zh-TW LINUXDO ReadBot是一個LINUXDO刷取已讀帖量腳本,支持latest頁面自動閱讀和單個帖子閱讀,理論上支持所有Discourse論壇
// @description:en LINUXDO ReadBot is a script for LINUXDO to boost the number of read posts. It supports automatic reading from latest page and individual topics, theoretically supports all Discourse forums.
// ==/UserScript==

(function () {
    'use strict'
    const hasAgreed = GM_getValue("hasAgreed", false)
    if (!hasAgreed) {
        const userInput = prompt("[ LINUXDO ReadBot ]\n检测到这是你第一次使用LINUXDO ReadBoost,使用前你必须知晓:使用该第三方脚本可能会导致包括并不限于账号被限制、被封禁的潜在风险,脚本不对出现的任何风险负责,这是一个开源脚本,你可以自由审核其中的内容,如果你同意以上内容,请输入'明白'")
        if (userInput !== "明白") {
            alert("您未同意风险提示,脚本已停止运行。")
            return
        }
        GM_setValue("hasAgreed", true)
    }

    // 默认参数
    const DEFAULT_CONFIG = {
        baseDelay: 2500,
        randomDelayRange: 800,
        minReqSize: 8,
        maxReqSize: 20,
        minReadTime: 800,
        maxReadTime: 3000,
        autoStart: false,
        startFromCurrent: false,
        enableLatestMode: false,
        latestMaxTopics: 5,
        latestReadReplies: false,
        latestRequestDelay: 3000,
        latestRequestDelayRange: 2000,
        simulateBrowsing: true,
        pageStayTime: 8000,
        pageStayTimeRange: 4000,
        scrollBehavior: true,
        mouseMovement: true,
        latestModeSimulateBrowsing: false,
        latestModePageStayTime: 3000,
        latestModePageStayTimeRange: 2000
    }

    let config = { ...DEFAULT_CONFIG, ...getStoredConfig() }
    let isRunning = false
    let shouldStop = false
    let statusLabel = null
    let initTimeout = null
    let latestModeInterval = null
    let isLatestModeActive = GM_getValue("isLatestModeActive", false)
    function isTopicPage() {
        return /^https:\/\/linux\.do\/t\/[^/]+\/\d+/.test(window.location.href)
    }

    function isLatestPage() {
        return /^https:\/\/linux\.do\/latest/.test(window.location.href)
    }

    function getPageInfo() {
        if (!isTopicPage()) {
            throw new Error("不在帖子页面")
        }
        const topicID = window.location.pathname.split("/")[3]
        const repliesElement = document.querySelector("div[class=timeline-replies]")
        const csrfElement = document.querySelector("meta[name=csrf-token]")

        if (!repliesElement || !csrfElement) {
            throw new Error("无法获取页面信息,请确认在正确的帖子页面")
        }

        const repliesInfo = repliesElement.textContent.trim()
        const [currentPosition, totalReplies] = repliesInfo.split("/").map(part => parseInt(part.trim(), 10))
        const csrfToken = csrfElement.getAttribute("content")

        return { topicID, currentPosition, totalReplies, csrfToken }
    }

    function isPinnedTopic(element) {
        // 检查是否为置顶帖子的多种方法
        const checks = [
            // 检查是否有置顶图标
            () => element.querySelector('.d-icon-pin-sidebar, .d-icon-thumbtack, .pinned-icon, .topic-status .pinned, .fa-thumbtack, .icon-thumbtack'),
            // 检查CSS类名
            () => element.classList.contains('pinned') || element.classList.contains('topic-pinned') || element.classList.contains('is-pinned'),
            // 检查父元素的置顶状态
            () => element.closest('.pinned, .topic-pinned, .topic-list-item.pinned, .is-pinned'),
            // 检查数据属性
            () => element.hasAttribute('data-pinned') || element.closest('[data-pinned="true"]') || element.closest('[data-is-pinned="true"]'),
            // 检查特定的置顶标识文本
            () => {
                const text = element.textContent.toLowerCase()
                return text.includes('置顶') || text.includes('pinned') || text.includes('公告') || text.includes('公告:') || text.includes('【公告】')
            },
            // 检查置顶标签
            () => {
                const pinnedBadge = element.querySelector('.pinned-badge, .topic-badge.pinned, .badge.pinned')
                return pinnedBadge !== null
            },
            // 检查置顶状态图标
            () => {
                const statusIcons = element.querySelectorAll('.topic-status .d-icon')
                return Array.from(statusIcons).some(icon => 
                    icon.classList.contains('d-icon-pin-sidebar') || 
                    icon.classList.contains('d-icon-thumbtack') ||
                    icon.title?.toLowerCase().includes('pinned') ||
                    icon.title?.includes('置顶')
                )
            }
        ]
        
        return checks.some(check => {
            try {
                return check()
            } catch (e) {
                return false
            }
        })
    }

    function getLatestTopics() {
        if (!isLatestPage()) {
            throw new Error("不在latest页面")
        }
        
        console.log("开始查找latest页面的帖子...")
        
        // 调试信息:检查页面结构
        const debugInfo = {
            url: window.location.href,
            title: document.title,
            topicLinks: document.querySelectorAll('a.topic-title[href^="/t/"]').length,
            titleLinks: document.querySelectorAll('.topic-list a.title[href^="/t/"]').length,
            bodyLinks: document.querySelectorAll('.topic-body a[href^="/t/"]').length,
            topicListItems: document.querySelectorAll('.topic-list-item[data-topic-id]').length,
            allTopicLinks: document.querySelectorAll('a[href^="/t/"]').length,
            pinnedTopics: document.querySelectorAll('.pinned, .topic-pinned, [data-pinned="true"]').length
        }
        console.log("页面调试信息:", debugInfo)
        
        // 尝试多种选择器来找到帖子链接
        const topicLinks = document.querySelectorAll('a.topic-title[href^="/t/"], .topic-list a.title[href^="/t/"], .topic-body a[href^="/t/"]')
        const topics = []
        let skippedPinned = 0
        
        console.log(`使用选择器找到 ${topicLinks.length} 个链接`)
        
        topicLinks.forEach((link, index) => {
            const href = link.getAttribute('href')
            const text = link.textContent.trim()
            console.log(`链接 ${index + 1}:`, { href, text })
            
            // 检查是否为置顶帖子
            if (isPinnedTopic(link)) {
                console.log(`跳过置顶帖子: ${text}`)
                skippedPinned++
                return
            }
            
            const match = href.match(/\/t\/[^/]+\/(\d+)/)
            if (match) {
                const topicId = match[1]
                const title = text
                if (title && topicId) {
                    topics.push({ id: topicId, title, url: `https://linux.do${href}` })
                }
            }
        })
        
        // 如果没有找到,尝试从数据属性中获取
        if (topics.length === 0) {
            console.log("尝试从数据属性获取帖子...")
            const topicElements = document.querySelectorAll('.topic-list-item[data-topic-id]')
            topicElements.forEach((element, index) => {
                // 检查是否为置顶帖子
                if (isPinnedTopic(element)) {
                    console.log(`跳过置顶帖子元素: ${index + 1}`)
                    skippedPinned++
                    return
                }
                
                const topicId = element.getAttribute('data-topic-id')
                const titleElement = element.querySelector('.topic-title')
                const title = titleElement ? titleElement.textContent.trim() : `帖子${topicId}`
                console.log(`数据属性 ${index + 1}:`, { topicId, title })
                
                if (topicId) {
                    topics.push({ id: topicId, title, url: `https://linux.do/t/topic/${topicId}` })
                }
            })
        }
        
        // 如果还是没有找到,尝试所有包含/t/的链接
        if (topics.length === 0) {
            console.log("尝试所有包含/t/的链接...")
            const allLinks = document.querySelectorAll('a[href^="/t/"]')
            allLinks.forEach((link, index) => {
                const href = link.getAttribute('href')
                const match = href.match(/\/t\/[^/]+\/(\d+)/)
                if (match && index < 20) { // 限制数量避免过多
                    // 检查是否为置顶帖子
                    if (isPinnedTopic(link)) {
                        console.log(`跳过置顶帖子链接: ${index + 1}`)
                        skippedPinned++
                        return
                    }
                    
                    const topicId = match[1]
                    const title = link.textContent.trim() || `帖子${topicId}`
                    topics.push({ id: topicId, title, url: `https://linux.do${href}` })
                }
            })
        }
        
        console.log(`找到${topics.length}个普通帖子,跳过${skippedPinned}个置顶帖子`, topics)
        return topics.slice(0, config.latestMaxTopics)
    }

    // 显示设置界面 (前置声明)
    function showSettingsUI() {
        // 实际实现在文件后面
        console.log("Settings UI not ready yet")
    }

    // 创建状态标签
    function createStatusLabel() {
        // 移除已存在的状态标签
        const existingLabel = document.getElementById("readBoostStatusLabel")
        if (existingLabel) {
            existingLabel.remove()
        }

        const headerButtons = document.querySelector(".header-buttons")
        if (!headerButtons) return null

        const labelSpan = document.createElement("span")
        labelSpan.id = "readBoostStatusLabel"
        labelSpan.style.cssText = `
            margin-left: 10px;
            margin-right: 10px;
            padding: 5px 10px;
            border-radius: 4px;
            background: var(--primary-low);
            color: var(--primary);
            font-size: 12px;
            font-weight: bold;
            cursor: pointer;
        `
        labelSpan.textContent = "ReadBot"+" ⚙️"
        labelSpan.addEventListener("click", showSettingsUI)

        headerButtons.appendChild(labelSpan)
        return labelSpan
    }

    // 增强的状态显示函数,支持动态倒计时
    function updateEnhancedStatus(text, type = "info", duration = null) {
        if (!statusLabel) return

        const colors = {
            info: "var(--primary)",
            success: "#2e8b57",
            warning: "#ff8c00",
            error: "#dc3545",
            running: "#007bff"
        }

        statusLabel.style.color = colors[type] || colors.info
        
        if (duration && duration > 0) {
            // 显示倒计时状态
            let remainingTime = Math.ceil(duration / 1000)
            const originalText = text
            
            const countdownInterval = setInterval(() => {
                if (shouldStop || remainingTime <= 0) {
                    clearInterval(countdownInterval)
                    return
                }
                
                statusLabel.textContent = `${originalText} (${remainingTime}s) ⚙️`
                remainingTime--
            }, 1000)
            
            // 设置清理定时器
            setTimeout(() => {
                clearInterval(countdownInterval)
            }, duration)
        } else {
            statusLabel.textContent = text + " ⚙️"
        }
    }

    // 模拟浏览行为
    async function simulateBrowsingBehavior(topic) {
        // 检查是否启用模拟浏览
        const isLatestMode = isLatestPage()
        const shouldSimulate = isLatestMode ? config.latestModeSimulateBrowsing : config.simulateBrowsing
        
        if (!shouldSimulate) {
            console.log("模拟浏览功能已禁用")
            return
        }

        console.log(`开始模拟浏览帖子: ${topic.title}`)
        updateEnhancedStatus(`正在浏览: ${topic.title}`, "running")
        
        // 根据模式选择不同的停留时间配置
        const baseStayTime = isLatestMode ? config.latestModePageStayTime : config.pageStayTime
        const stayTimeRange = isLatestMode ? config.latestModePageStayTimeRange : config.pageStayTimeRange
        const stayTime = baseStayTime + Math.floor(Math.random() * stayTimeRange)
        
        console.log(`模式: ${isLatestMode ? 'Latest' : '普通'}, 将在页面停留${stayTime}ms`)
        updateEnhancedStatus(`浏览中: ${topic.title} (${Math.round(stayTime/1000)}s)`, "running", stayTime)
        
        // 模拟访问页面
        try {
            // 尝试使用iframe模式
            await simulateWithIframe(topic, stayTime)
        } catch (error) {
            console.warn(`iframe模式失败,使用简单模式: ${error.message}`)
            // 如果iframe失败,使用简单模式
            await simulateSimpleBrowsing(topic, stayTime)
        }
    }

    // 使用iframe模拟浏览
    async function simulateWithIframe(topic, stayTime) {
        const iframe = document.createElement('iframe')
        iframe.style.cssText = `
            position: fixed;
            top: -9999px;
            left: -9999px;
            width: 800px;
            height: 600px;
            border: none;
            opacity: 0;
            pointer-events: none;
        `
        iframe.src = topic.url
        
        document.body.appendChild(iframe)
        
        try {
            // 等待iframe加载
            await new Promise((resolve, reject) => {
                iframe.onload = resolve
                setTimeout(() => reject(new Error('iframe加载超时')), 5000)
            })
            
            console.log(`iframe已加载: ${topic.url}`)
            
            // 在iframe中模拟浏览行为
            if (iframe.contentDocument) {
                await simulateInFrame(iframe.contentDocument, stayTime)
            }
            
            // 等待指定的停留时间
            await new Promise(resolve => setTimeout(resolve, stayTime))
            
        } finally {
            // 清理iframe
            if (iframe.parentNode) {
                document.body.removeChild(iframe)
            }
        }
    }

    // 简单浏览模式(仅等待和模拟基本行为)
    async function simulateSimpleBrowsing(topic, stayTime) {
        console.log(`使用简单浏览模式: ${topic.title}`)
        
        // 模拟页面访问
        await fetch(topic.url, {
            method: 'GET',
            headers: {
                'User-Agent': navigator.userAgent,
                'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
            },
            credentials: 'include'
        })
        
        // 在当前页面模拟一些简单的行为
        await simulateSimpleActions()
        
        // 等待指定的停留时间
        await new Promise(resolve => setTimeout(resolve, stayTime))
    }

    // 模拟简单的页面行为
    async function simulateSimpleActions() {
        const actions = [
            () => {
                // 模拟鼠标移动
                const x = Math.random() * window.innerWidth
                const y = Math.random() * window.innerHeight
                const event = new MouseEvent('mousemove', {
                    clientX: x,
                    clientY: y,
                    bubbles: true
                })
                document.dispatchEvent(event)
            },
            () => {
                // 模拟点击空白区域
                const x = Math.random() * window.innerWidth
                const y = Math.random() * window.innerHeight
                const event = new MouseEvent('click', {
                    clientX: x,
                    clientY: y,
                    bubbles: true
                })
                document.elementFromPoint(x, y)?.dispatchEvent(event)
            },
            () => {
                // 模拟滚动
                window.scrollBy(0, (Math.random() - 0.5) * 100)
            }
        ]
        
        // 随机执行一些动作
        for (let i = 0; i < 3 + Math.random() * 3; i++) {
            if (shouldStop) break
            const action = actions[Math.floor(Math.random() * actions.length)]
            try {
                action()
            } catch (e) {
                // 忽略错误
            }
            await new Promise(resolve => setTimeout(resolve, 500 + Math.random() * 1000))
        }
    }

    // 在iframe中模拟浏览行为
    async function simulateInFrame(doc, stayTime) {
        if (!doc) return
        
        const startTime = Date.now()
        const endTime = startTime + stayTime
        
        while (Date.now() < endTime && !shouldStop) {
            // 模拟滚动行为
            if (config.scrollBehavior && Math.random() > 0.7) {
                await simulateScrolling(doc)
            }
            
            // 模拟鼠标移动
            if (config.mouseMovement && Math.random() > 0.8) {
                await simulateMouseMovement(doc)
            }
            
            // 随机间隔
            await new Promise(resolve => setTimeout(resolve, 500 + Math.random() * 1500))
        }
    }

    // 模拟滚动行为
    async function simulateScrolling(doc) {
        const scrollableElements = doc.querySelectorAll('body, .topic-body, .posts, .topic-post')
        if (scrollableElements.length === 0) return
        
        const element = scrollableElements[Math.floor(Math.random() * scrollableElements.length)]
        const currentScroll = element.scrollTop || 0
        const maxScroll = element.scrollHeight - element.clientHeight
        const targetScroll = Math.min(maxScroll, currentScroll + 100 + Math.random() * 200)
        
        console.log(`模拟滚动: ${currentScroll} -> ${targetScroll}`)
        
        // 平滑滚动
        const steps = 10
        const stepSize = (targetScroll - currentScroll) / steps
        
        for (let i = 0; i < steps && !shouldStop; i++) {
            element.scrollTop = currentScroll + stepSize * i
            await new Promise(resolve => setTimeout(resolve, 50 + Math.random() * 100))
        }
        
        // 等待一段时间
        await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 2000))
        
        // 随机滚动回顶部
        if (Math.random() > 0.6) {
            for (let i = steps; i >= 0 && !shouldStop; i--) {
                element.scrollTop = currentScroll + stepSize * i
                await new Promise(resolve => setTimeout(resolve, 50 + Math.random() * 100))
            }
        }
    }

    // 模拟鼠标移动
    async function simulateMouseMovement(doc) {
        const interactiveElements = doc.querySelectorAll('a, button, .topic-body p, .topic-body span')
        if (interactiveElements.length === 0) return
        
        const element = interactiveElements[Math.floor(Math.random() * interactiveElements.length)]
        const rect = element.getBoundingClientRect()
        
        if (rect.width > 0 && rect.height > 0) {
            const x = rect.left + Math.random() * rect.width
            const y = rect.top + Math.random() * rect.height
            
            console.log(`模拟鼠标移动到: (${Math.round(x)}, ${Math.round(y)})`)
            
            // 创建鼠标移动事件
            const events = ['mouseover', 'mouseenter', 'mousemove']
            for (const eventType of events) {
                if (shouldStop) break
                const event = new MouseEvent(eventType, {
                    view: doc.defaultView,
                    bubbles: true,
                    cancelable: true,
                    clientX: x,
                    clientY: y
                })
                element.dispatchEvent(event)
                await new Promise(resolve => setTimeout(resolve, 50 + Math.random() * 100))
            }
            
            // 短暂停留
            await new Promise(resolve => setTimeout(resolve, 500 + Math.random() * 1000))
        }
    }


    function getStoredConfig() {
        return {
            baseDelay: GM_getValue("baseDelay", DEFAULT_CONFIG.baseDelay),
            randomDelayRange: GM_getValue("randomDelayRange", DEFAULT_CONFIG.randomDelayRange),
            minReqSize: GM_getValue("minReqSize", DEFAULT_CONFIG.minReqSize),
            maxReqSize: GM_getValue("maxReqSize", DEFAULT_CONFIG.maxReqSize),
            minReadTime: GM_getValue("minReadTime", DEFAULT_CONFIG.minReadTime),
            maxReadTime: GM_getValue("maxReadTime", DEFAULT_CONFIG.maxReadTime),
            autoStart: GM_getValue("autoStart", DEFAULT_CONFIG.autoStart),
            startFromCurrent: GM_getValue("startFromCurrent", DEFAULT_CONFIG.startFromCurrent),
            enableLatestMode: GM_getValue("enableLatestMode", DEFAULT_CONFIG.enableLatestMode),
            latestMaxTopics: GM_getValue("latestMaxTopics", DEFAULT_CONFIG.latestMaxTopics),
            latestReadReplies: GM_getValue("latestReadReplies", DEFAULT_CONFIG.latestReadReplies),
            latestRequestDelay: GM_getValue("latestRequestDelay", DEFAULT_CONFIG.latestRequestDelay),
            latestRequestDelayRange: GM_getValue("latestRequestDelayRange", DEFAULT_CONFIG.latestRequestDelayRange),
            simulateBrowsing: GM_getValue("simulateBrowsing", DEFAULT_CONFIG.simulateBrowsing),
            pageStayTime: GM_getValue("pageStayTime", DEFAULT_CONFIG.pageStayTime),
            pageStayTimeRange: GM_getValue("pageStayTimeRange", DEFAULT_CONFIG.pageStayTimeRange),
            scrollBehavior: GM_getValue("scrollBehavior", DEFAULT_CONFIG.scrollBehavior),
            mouseMovement: GM_getValue("mouseMovement", DEFAULT_CONFIG.mouseMovement),
            latestModeSimulateBrowsing: GM_getValue("latestModeSimulateBrowsing", DEFAULT_CONFIG.latestModeSimulateBrowsing),
            latestModePageStayTime: GM_getValue("latestModePageStayTime", DEFAULT_CONFIG.latestModePageStayTime),
            latestModePageStayTimeRange: GM_getValue("latestModePageStayTimeRange", DEFAULT_CONFIG.latestModePageStayTimeRange)
        }
    }

    function saveConfig(newConfig) {
        Object.keys(newConfig).forEach(key => {
            GM_setValue(key, newConfig[key])
            config[key] = newConfig[key]
        })
        location.reload()
    }

    
    // 更新状态
    function updateStatus(text, type = "info") {
        if (!statusLabel) return

        const colors = {
            info: "var(--primary)",
            success: "#2e8b57",
            warning: "#ff8c00",
            error: "#dc3545",
            running: "#007bff"
        }

        statusLabel.textContent = text + " ⚙️"
        statusLabel.style.color = colors[type] || colors.info
    }

    
    function showSettingsUIImpl() {
        const settingsDiv = document.createElement("div")
        settingsDiv.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            padding: 25px;
            border-radius: 12px;
            z-index: 10000;
            background: var(--secondary);
            color: var(--primary);
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
            border: 1px solid var(--primary-low);
            min-width: 400px;
            max-width: 500px;
        `

        const autoStartChecked = config.autoStart ? "checked" : ""
        const startFromCurrentChecked = config.startFromCurrent ? "checked" : ""
        const enableLatestModeChecked = config.enableLatestMode ? "checked" : ""
        const latestReadRepliesChecked = config.latestReadReplies ? "checked" : ""
        const simulateBrowsingChecked = config.simulateBrowsing ? "checked" : ""
        const scrollBehaviorChecked = config.scrollBehavior ? "checked" : ""
        const mouseMovementChecked = config.mouseMovement ? "checked" : ""
        const latestModeSimulateBrowsingChecked = config.latestModeSimulateBrowsing ? "checked" : ""
        settingsDiv.innerHTML = `
            <h3 style="margin-top: 0; color: var(--primary); text-align: center;">ReadBot 设置</h3>
            <div style="display: grid; gap: 15px;">
                <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
                    <label style="display: flex; flex-direction: column; gap: 5px;">
                        <span>基础延迟(ms):</span>
                        <input id="baseDelay" type="number" value="${config.baseDelay}"
                               style="padding: 8px; border: 1px solid var(--primary-low); border-radius: 4px; background: var(--secondary);">
                    </label>
                    <label style="display: flex; flex-direction: column; gap: 5px;">
                        <span>随机延迟范围(ms):</span>
                        <input id="randomDelayRange" type="number" value="${config.randomDelayRange}"
                               style="padding: 8px; border: 1px solid var(--primary-low); border-radius: 4px; background: var(--secondary);">
                    </label>
                </div>
                <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
                    <label style="display: flex; flex-direction: column; gap: 5px;">
                        <span>最小每次请求量:</span>
                        <input id="minReqSize" type="number" value="${config.minReqSize}"
                               style="padding: 8px; border: 1px solid var(--primary-low); border-radius: 4px; background: var(--secondary);">
                    </label>
                    <label style="display: flex; flex-direction: column; gap: 5px;">
                        <span>最大每次请求量:</span>
                        <input id="maxReqSize" type="number" value="${config.maxReqSize}"
                               style="padding: 8px; border: 1px solid var(--primary-low); border-radius: 4px; background: var(--secondary);">
                    </label>
                </div>
                <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
                    <label style="display: flex; flex-direction: column; gap: 5px;">
                        <span>最小阅读时间(ms):</span>
                        <input id="minReadTime" type="number" value="${config.minReadTime}"
                               style="padding: 8px; border: 1px solid var(--primary-low); border-radius: 4px; background: var(--secondary);">
                    </label>
                    <label style="display: flex; flex-direction: column; gap: 5px;">
                        <span>最大阅读时间(ms):</span>
                        <input id="maxReadTime" type="number" value="${config.maxReadTime}"
                               style="padding: 8px; border: 1px solid var(--primary-low); border-radius: 4px; background: var(--secondary);">
                    </label>
                </div>
                <div style="border-top: 1px solid var(--primary-low); padding-top: 15px;">
                    <h4 style="margin: 0 0 10px 0; color: var(--primary);">Latest模式设置</h4>
                    <div style="display: grid; grid-template-columns: 1fr; gap: 10px;">
                        <label style="display: flex; flex-direction: column; gap: 5px;">
                            <span>最大帖子数:</span>
                            <input id="latestMaxTopics" type="number" value="${config.latestMaxTopics}"
                                   style="padding: 8px; border: 1px solid var(--primary-low); border-radius: 4px; background: var(--secondary);">
                        </label>
                    </div>
                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 10px;">
                        <label style="display: flex; flex-direction: column; gap: 5px;">
                            <span>请求间隔(ms):</span>
                            <input id="latestRequestDelay" type="number" value="${config.latestRequestDelay}"
                                   style="padding: 8px; border: 1px solid var(--primary-low); border-radius: 4px; background: var(--secondary);">
                        </label>
                        <label style="display: flex; flex-direction: column; gap: 5px;">
                            <span>随机间隔范围(ms):</span>
                            <input id="latestRequestDelayRange" type="number" value="${config.latestRequestDelayRange}"
                                   style="padding: 8px; border: 1px solid var(--primary-low); border-radius: 4px; background: var(--secondary);">
                        </label>
                    </div>
                    <div style="display: flex; gap: 15px; align-items: center; flex-wrap: wrap; margin-top: 10px;">
                        <label style="display: flex; align-items: center; gap: 8px;">
                            <input type="checkbox" id="enableLatestMode" ${enableLatestModeChecked} style="transform: scale(1.2);">
                            <span>启用Latest模式</span>
                        </label>
                        <label style="display: none; align-items: center; gap: 8px;">
                            <input type="checkbox" id="latestReadReplies" ${latestReadRepliesChecked} style="transform: scale(1.2);">
                            <span>阅读帖子回复</span>
                        </label>
                        <label style="display: flex; align-items: center; gap: 8px;">
                            <input type="checkbox" id="latestModeSimulateBrowsing" ${latestModeSimulateBrowsingChecked} style="transform: scale(1.2);">
                            <span>Latest模式模拟浏览</span>
                        </label>
                    </div>
                </div>
                <div style="border-top: 1px solid var(--primary-low); padding-top: 15px;">
                    <h4 style="margin: 0 0 10px 0; color: var(--primary);">模拟浏览设置</h4>
                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
                        <label style="display: flex; flex-direction: column; gap: 5px;">
                            <span>页面停留时间(ms):</span>
                            <input id="pageStayTime" type="number" value="${config.pageStayTime}"
                                   style="padding: 8px; border: 1px solid var(--primary-low); border-radius: 4px; background: var(--secondary);">
                        </label>
                        <label style="display: flex; flex-direction: column; gap: 5px;">
                            <span>随机停留范围(ms):</span>
                            <input id="pageStayTimeRange" type="number" value="${config.pageStayTimeRange}"
                                   style="padding: 8px; border: 1px solid var(--primary-low); border-radius: 4px; background: var(--secondary);">
                        </label>
                    </div>
                    <div style="display: flex; gap: 15px; align-items: center; flex-wrap: wrap; margin-top: 10px;">
                        <label style="display: flex; align-items: center; gap: 8px;">
                            <input type="checkbox" id="simulateBrowsing" ${simulateBrowsingChecked} style="transform: scale(1.2);">
                            <span>启用模拟浏览</span>
                        </label>
                        <label style="display: flex; align-items: center; gap: 8px;">
                            <input type="checkbox" id="scrollBehavior" ${scrollBehaviorChecked} style="transform: scale(1.2);">
                            <span>模拟滚动行为</span>
                        </label>
                        <label style="display: flex; align-items: center; gap: 8px;">
                            <input type="checkbox" id="mouseMovement" ${mouseMovementChecked} style="transform: scale(1.2);">
                            <span>模拟鼠标移动</span>
                        </label>
                    </div>
                    <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 10px;">
                        <label style="display: flex; flex-direction: column; gap: 5px;">
                            <span>Latest模式停留时间(ms):</span>
                            <input id="latestModePageStayTime" type="number" value="${config.latestModePageStayTime}"
                                   style="padding: 8px; border: 1px solid var(--primary-low); border-radius: 4px; background: var(--secondary);">
                        </label>
                        <label style="display: flex; flex-direction: column; gap: 5px;">
                            <span>Latest模式随机范围(ms):</span>
                            <input id="latestModePageStayTimeRange" type="number" value="${config.latestModePageStayTimeRange}"
                                   style="padding: 8px; border: 1px solid var(--primary-low); border-radius: 4px; background: var(--secondary);">
                        </label>
                    </div>
                </div>
                <div style="display: flex; gap: 15px; align-items: center; flex-wrap: wrap;">
                    <label style="display: flex; align-items: center; gap: 8px;">
                        <input type="checkbox" id="advancedMode" style="transform: scale(1.2);">
                        <span>高级设置模式</span>
                    </label>
                    <label style="display: flex; align-items: center; gap: 8px;">
                        <input type="checkbox" id="autoStart" ${autoStartChecked} style="transform: scale(1.2);">
                        <span>自动运行</span>
                    </label>
                    <label style="display: flex; align-items: center; gap: 8px;">
                        <input type="checkbox" id="startFromCurrent" ${startFromCurrentChecked} style="transform: scale(1.2);">
                        <span>从当前浏览位置开始</span>
                    </label>
                </div>
                <div style="display: flex; gap: 10px; justify-content: center; margin-top: 10px;">
                    <button id="saveSettings" style="padding: 10px 20px; border: none; border-radius: 6px; background: #007bff; color: white; cursor: pointer;">保存设置</button>
                    <button id="resetDefaults" style="padding: 10px 20px; border: none; border-radius: 6px; background: #6c757d; color: white; cursor: pointer;">重置默认</button>
                    <button id="closeSettings" style="padding: 10px 20px; border: none; border-radius: 6px; background: #dc3545; color: white; cursor: pointer;">关闭</button>
                </div>
            </div>
        `

        document.body.appendChild(settingsDiv)

        toggleAdvancedInputs(false)

        document.getElementById("advancedMode").addEventListener("change", (e) => {
            if (e.target.checked) {
                const confirmed = confirm("高级设置可能增加账号风险,确定要启用吗?")
                if (!confirmed) {
                    e.target.checked = false
                    return
                }
            }
            toggleAdvancedInputs(e.target.checked)
        })

        document.getElementById("saveSettings").addEventListener("click", () => {
            const newConfig = {
                baseDelay: parseInt(document.getElementById("baseDelay").value, 10),
                randomDelayRange: parseInt(document.getElementById("randomDelayRange").value, 10),
                minReqSize: parseInt(document.getElementById("minReqSize").value, 10),
                maxReqSize: parseInt(document.getElementById("maxReqSize").value, 10),
                minReadTime: parseInt(document.getElementById("minReadTime").value, 10),
                maxReadTime: parseInt(document.getElementById("maxReadTime").value, 10),
                autoStart: document.getElementById("autoStart").checked,
                startFromCurrent: document.getElementById("startFromCurrent").checked,
                enableLatestMode: document.getElementById("enableLatestMode").checked,
                latestMaxTopics: parseInt(document.getElementById("latestMaxTopics").value, 10),
                latestReadReplies: document.getElementById("latestReadReplies").checked,
                latestRequestDelay: parseInt(document.getElementById("latestRequestDelay").value, 10),
                latestRequestDelayRange: parseInt(document.getElementById("latestRequestDelayRange").value, 10),
                simulateBrowsing: document.getElementById("simulateBrowsing").checked,
                pageStayTime: parseInt(document.getElementById("pageStayTime").value, 10),
                pageStayTimeRange: parseInt(document.getElementById("pageStayTimeRange").value, 10),
                scrollBehavior: document.getElementById("scrollBehavior").checked,
                mouseMovement: document.getElementById("mouseMovement").checked,
                latestModeSimulateBrowsing: document.getElementById("latestModeSimulateBrowsing").checked,
                latestModePageStayTime: parseInt(document.getElementById("latestModePageStayTime").value, 10),
                latestModePageStayTimeRange: parseInt(document.getElementById("latestModePageStayTimeRange").value, 10)
            }

            saveConfig(newConfig)
            settingsDiv.remove()
            updateStatus("设置已保存", "success")

        })

        document.getElementById("resetDefaults").addEventListener("click", () => {
            if (confirm("确定要重置为默认设置吗?")) {
                saveConfig(DEFAULT_CONFIG)
                settingsDiv.remove()
                updateStatus("已重置为默认设置", "info")
            }
        })

        document.getElementById("closeSettings").addEventListener("click", () => {
            settingsDiv.remove()
        })

        function toggleAdvancedInputs(enabled) {
            const inputs = ["baseDelay", "randomDelayRange", "minReqSize", "maxReqSize", "minReadTime", "maxReadTime"]
            inputs.forEach(id => {
                const input = document.getElementById(id)
                if (input) {
                    input.disabled = !enabled
                    input.style.opacity = enabled ? "1" : "0.6"
                }
            })
        }
    }

    async function startReading() {
        if (isRunning) {
            updateStatus("脚本正在运行中...", "warning")
            return
        }

        try {
            isRunning = true
            shouldStop = false

            if (isLatestPage()) {
                if (config.enableLatestMode) {
                    updateEnhancedStatus("启动Latest模式...", "running")
                    await startLatestMode()
                } else {
                    throw new Error("Latest模式未启用,请在设置中启用")
                }
            } else if (!isLatestModeActive && isTopicPage()) {
                const pageInfo = getPageInfo()
                updateEnhancedStatus("启动Topic模式...", "running")
                await processReading(pageInfo)
            } else {
                throw new Error("请在帖子页面或latest页面使用")
            }

            updateStatus("处理完成", "success")
        } catch (error) {
            console.error("执行错误:", error)
            if (error.message === "用户停止执行") {
                updateStatus("ReadBot", "info")
            } else {
                updateStatus("执行失败: " + error.message, "error")
            }
        } finally {
            isRunning = false
        }
    }


    function stopReading() {
        shouldStop = true
        isLatestModeActive = false
        GM_setValue("isLatestModeActive", false)
        if (latestModeInterval) {
            clearInterval(latestModeInterval)
            latestModeInterval = null
        }
        if (isLatestPage()) {
            updateStatus("正在停止Latest模式...", "warning")
        } else if (isTopicPage()) {
            updateStatus("正在停止Topic模式...", "warning")
        } else {
            updateStatus("正在停止...", "warning")
        }
    }

    async function startLatestMode() {
        isLatestModeActive = true
        GM_setValue("isLatestModeActive", true)
        updateEnhancedStatus("Latest模式启动中...", "running")
        console.log("Latest模式启动,配置:", {
            maxTopics: config.latestMaxTopics,
            mode: "仅处理帖子主题(不阅读回复)",
            requestDelay: config.latestRequestDelay,
            requestDelayRange: config.latestRequestDelayRange,
            isLatestModeActive: isLatestModeActive
        })
        
        const processLatestTopics = async () => {
            if (shouldStop) {
                console.log("Latest模式收到停止信号")
                return
            }
            
            try {
                console.log("开始获取latest页面帖子...")
                const topics = getLatestTopics()
                console.log("获取到的帖子:", topics)
                
                if (topics.length === 0) {
                    updateStatus("未找到普通帖子", "warning")
                    return
                }
                
                updateStatus(`找到${topics.length}个普通帖子,开始处理帖子主题...`, "running")
                
                let processedCount = 0
                for (const topic of topics) {
                    if (shouldStop) break
                    
                    try {
                        console.log(`正在处理帖子主题: ${topic.title} (${topic.id})`)
                        updateEnhancedStatus(`开始处理: ${topic.title}`, "running")
                        
                        // Latest页面不执行阅读回复功能,只处理帖子主题
                        await processTopicOnly(topic)
                        
                        processedCount++
                        updateStatus(`已完成: ${topic.title} (${processedCount}/${topics.length})`, "success")
                        
                        if (shouldStop) break
                        
                        // 计算下一个帖子的间隔时间
                        const baseDelay = config.latestRequestDelay
                        const randomRange = config.latestRequestDelayRange
                        const delay = baseDelay + Math.floor(Math.random() * randomRange)
                        
                        console.log(`Latest模式间隔配置: 基础${baseDelay}ms + 随机${randomRange}ms = ${delay}ms`)
                        console.log(`等待${delay}ms后处理下一个帖子...`)
                        updateEnhancedStatus(`等待下个帖子 (${Math.round(delay/1000)}s)`, "info", delay)
                        
                        await new Promise(r => setTimeout(r, delay))
                    } catch (error) {
                        console.error(`处理帖子${topic.id}失败:`, error)
                        updateStatus(`处理失败: ${topic.title}`, "error")
                    }
                }
                
                if (!shouldStop) {
                    isLatestModeActive = false
                    GM_setValue("isLatestModeActive", false)
                    updateStatus("Latest模式完成", "success")
                }
                
            } catch (error) {
                console.error("Latest模式错误:", error)
                updateStatus("Latest模式错误: " + error.message, "error")
            }
        }
        
        await processLatestTopics()
        
        // 移除定时器,只在刷新页面时运行一次
        console.log("Latest模式执行完成,不再设置定时器")
    }

    async function processTopicOnly(topic) {
        console.log(`处理帖子主题: ${topic.title} (${topic.id})`)
        
        try {
            // 首先模拟浏览行为
            await simulateBrowsingBehavior(topic)
            
            const response = await fetch(topic.url)
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}: ${response.statusText}`)
            }
            
            const text = await response.text()
            const parser = new DOMParser()
            const doc = parser.parseFromString(text, 'text/html')
            
            const csrfElement = doc.querySelector("meta[name=csrf-token]")
            if (!csrfElement) {
                throw new Error("无法获取CSRF令牌")
            }
            
            const csrfToken = csrfElement.getAttribute("content")
            const topicTime = (1000 + Math.random() * 2000).toString()
            
            const params = new URLSearchParams()
            params.append('topic_time', topicTime)
            params.append('topic_id', topic.id)
            
            const timingResponse = await fetch("https://linux.do/topics/timings", {
                method: "POST",
                headers: {
                    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
                    "X-CSRF-Token": csrfToken,
                    "X-Requested-With": "XMLHttpRequest"
                },
                body: params,
                credentials: "include"
            })
            
            if (!timingResponse.ok) {
                throw new Error(`Timing请求失败: ${timingResponse.status}`)
            }
            
            console.log(`帖子主题处理完成: ${topic.title}`)
            
        } catch (error) {
            console.error(`处理帖子主题失败: ${topic.title}`, error)
            throw error
        }
    }

    async function processTopicWithReplies(topic) {
        console.log(`处理帖子及回复: ${topic.title} (${topic.id})`)
        
        try {
            // 首先模拟浏览行为
            await simulateBrowsingBehavior(topic)
            
            const response = await fetch(topic.url)
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}: ${response.statusText}`)
            }
            
            const text = await response.text()
            const parser = new DOMParser()
            const doc = parser.parseFromString(text, 'text/html')
            
            const repliesElement = doc.querySelector("div[class=timeline-replies]")
            const csrfElement = doc.querySelector("meta[name=csrf-token]")
            
            if (!repliesElement || !csrfElement) {
                throw new Error("无法获取帖子信息")
            }
            
            const repliesInfo = repliesElement.textContent.trim()
            const [currentPosition, totalReplies] = repliesInfo.split("/").map(part => parseInt(part.trim(), 10))
            const csrfToken = csrfElement.getAttribute("content")
            
            console.log(`帖子 ${topic.title} 有 ${totalReplies} 个回复`)
            
            if (totalReplies === 0) {
                await processTopicOnly(topic)
                return
            }
            
            const pageInfo = {
                topicID: topic.id,
                currentPosition: 1,
                totalReplies: totalReplies,
                csrfToken: csrfToken
            }
            
            await processReading(pageInfo)
            console.log(`帖子及回复处理完成: ${topic.title}`)
            
        } catch (error) {
            console.error(`处理帖子及回复失败: ${topic.title}`, error)
            throw error
        }
    }

    // 处理阅读逻辑
    async function processReading(pageInfo) {
        const { topicID, currentPosition, totalReplies, csrfToken } = pageInfo
        const startPosition = config.startFromCurrent ? currentPosition : 1

        console.log(`开始处理,起始位置: ${startPosition}, 总回复: ${totalReplies}`)

        function getRandomInt(min, max) {
            return Math.floor(Math.random() * (max - min + 1)) + min
        }

        async function sendBatch(startId, endId, retryCount = 3) {
            // 停止检查
            if (shouldStop) throw new Error("用户停止执行")

            const params = new URLSearchParams()

            for (let i = startId; i <= endId; i++) {
                params.append(`timings[${i}]`, getRandomInt(config.minReadTime, config.maxReadTime).toString())
            }

            const topicTime = getRandomInt(
                config.minReadTime * (endId - startId + 1),
                config.maxReadTime * (endId - startId + 1)
            ).toString()

            params.append('topic_time', topicTime)
            params.append('topic_id', topicID)

            try {
                const response = await fetch("https://linux.do/topics/timings", {
                    method: "POST",
                    headers: {
                        "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
                        "X-CSRF-Token": csrfToken,
                        "X-Requested-With": "XMLHttpRequest"
                    },
                    body: params,
                    credentials: "include"
                })

                if (!response.ok) {
                    throw new Error(`HTTP ${response.status}`)
                }

                // 再次检查是否应该停止
                if (shouldStop) throw new Error("用户停止执行")

                const progress = Math.round((endId / totalReplies) * 100)
                updateEnhancedStatus(`阅读回复 ${startId}-${endId} (${progress}%)`, "running")

            } catch (error) {
                if (shouldStop) throw error // 如果是停止信号,直接抛出

                if (retryCount > 0) {
                    updateEnhancedStatus(`重试 ${startId}-${endId} (剩余${retryCount}次, 2s)`, "warning", 2000)
                    await new Promise(r => setTimeout(r, 2000))
                    return await sendBatch(startId, endId, retryCount - 1)
                }
                throw error
            }

            // 延迟期间也检查停止信号
            const delay = config.baseDelay + getRandomInt(0, config.randomDelayRange)
            if (delay > 1000) {
                updateEnhancedStatus(`批次间等待 (${Math.round(delay/1000)}s)`, "info", delay)
            }
            for (let i = 0; i < delay; i += 100) {
                if (shouldStop) throw new Error("用户停止执行")
                await new Promise(r => setTimeout(r, Math.min(100, delay - i)))
            }
        }

        // 批量处理
        for (let i = startPosition; i <= totalReplies;) {
            if (shouldStop) break

            const batchSize = getRandomInt(config.minReqSize, config.maxReqSize)
            const startId = i
            const endId = Math.min(i + batchSize - 1, totalReplies)

            await sendBatch(startId, endId)
            i = endId + 1
        }
    }

    // 注册菜单命令
    GM_registerMenuCommand("🚀 开始执行", startReading)
    GM_registerMenuCommand("⏹️ 停止执行", stopReading)
    GM_registerMenuCommand("⚙️ 设置", showSettingsUI)

    function init() {
        statusLabel = createStatusLabel()
        // 检查是否需要重置latest模式状态
        // 只有在手动停止或完成时才重置,页面导航时保持状态
        if (!isLatestModeActive) {
            // 强制停止之前的任务
            shouldStop = true
            if (latestModeInterval) {
                clearInterval(latestModeInterval)
                latestModeInterval = null
            }
        }

        // 等待当前任务停止后再继续
        if (isRunning) {
            setTimeout(init, 1000)
            return
        }

        // 重置状态
        isRunning = false
        shouldStop = false

        // 清除之前的超时
        if (initTimeout) {
            clearTimeout(initTimeout)
        }
        
        // 根据页面类型和当前模式使用不同的初始化逻辑
        if (isLatestModeActive && isTopicPage()) {
            // 如果是latest模式激活状态下进入topic页面,不进行topic初始化
            console.log("Latest模式激活状态下进入topic页面,跳过topic初始化")
            updateEnhancedStatus("Latest模式运行中...", "running")
            return
        } else if (isTopicPage()) {
            initTopicPage()
        } else if (isLatestPage()) {
            initLatestPage()
        }
    }

    function initTopicPage() {
        try {
            console.log("initTopicPage 调用", isLatestModeActive)
            if (isLatestModeActive) {
                // 如果是latest模式激活状态下进入topic页面,不进行topic初始化
                console.log("Latest模式激活状态下进入topic页面,跳过topic初始化")
                updateEnhancedStatus("Latest模式运行中...", "running")
                return
            }
            const pageInfo = getPageInfo()
            console.log("LINUXDO ReadBot Topic模式已加载")
            console.log(`帖子ID: ${pageInfo.topicID}, 总回复: ${pageInfo.totalReplies}`)
            updateEnhancedStatus(`Topic模式: ${pageInfo.totalReplies}回复`, "info")
            
            if (config.autoStart) {
                updateEnhancedStatus("Topic模式自动启动 (1s)", "info", 1000)
                initTimeout = setTimeout(startReading, 1000)
            }
        } catch (error) {
            console.error("Topic页面初始化失败:", error)
            // 只在非停止状态下重试
            if (!shouldStop) {
                initTimeout = setTimeout(initTopicPage, 1000)
            }
        }
    }

    function initLatestPage() {
        try {
            console.log("LINUXDO ReadBot Latest模式已加载")
            if (config.enableLatestMode) {
                updateEnhancedStatus("Latest模式已就绪", "success")
                
                if (config.autoStart) {
                    updateEnhancedStatus("Latest模式自动启动 (1s)", "info", 1000)
                    initTimeout = setTimeout(startReading, 1000)
                }
            } else {
                updateEnhancedStatus("Latest模式已禁用", "warning")
            }
        } catch (error) {
            console.error("Latest页面初始化失败:", error)
            // 只在非停止状态下重试
            if (!shouldStop) {
                initTimeout = setTimeout(initLatestPage, 1000)
            }
        }
    }
    // 监听 URL 变化
    function setupRouteListener() {
        let lastUrl = location.href
        const originalPushState = history.pushState
        history.pushState = function () {
            originalPushState.apply(history, arguments)
            if (location.href !== lastUrl) {
                lastUrl = location.href
                setTimeout(init, 500)
            }
        }
    }
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            init()
            setupRouteListener()
        })
    } else {
        init()
        setupRouteListener()
    }
    
    // 重新定义 showSettingsUI 函数,指向实际实现
    function showSettingsUI() {
        return showSettingsUIImpl.apply(this, arguments)
    }
})()