PHPWind Reddit Style Refactor

将 PHPWind 论坛回复重构为 Reddit 嵌套风格,支持全量拉取和布局切换。消除原样式闪烁。

اعتبارا من 14-05-2026. شاهد أحدث إصدار.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

// ==UserScript==
// @name         PHPWind Reddit Style Refactor
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  将 PHPWind 论坛回复重构为 Reddit 嵌套风格,支持全量拉取和布局切换。消除原样式闪烁。
// @author       ai生成
// @match        *://*.blue-plus.net/read.php?*
// @match        *://*.east-plus.net/read.php?*
// @match        *://*.imoutolove.me/read.php?*
// @match        *://*.level-plus.net/read.php?*
// @match        *://*.north-plus.net/read.php?*
// @match        *://*.snow-plus.net/read.php?*
// @match        *://*.soul-plus.net/read.php?*
// @match        *://*.south-plus.net/read.php?*
// @match        *://*.south-plus.org/read.php?*
// @match        *://*.spring-plus.net/read.php?*
// @match        *://*.summer-plus.net/read.php?*
// @match        *://*.white-plus.net/read.php?*
// @run-at       document-start
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==
(function () {
    'use strict';

    // 第一优先级:立即隐藏页面,消除从论坛列表点进帖子时的原始排版闪烁。
    // 直接操作 documentElement + 注入内联 style 标签,比 GM_addStyle 更快,
    // 因为在 document-start 时机 head 元素可能尚未解析。
    const docEl = document.documentElement;
    docEl.style.visibility = 'hidden';
    const antiFlashStyle = document.createElement('style');
    antiFlashStyle.textContent = 'html,body{visibility:hidden!important}';
    docEl.appendChild(antiFlashStyle);

    function showPage() {
        docEl.style.visibility = '';
        if (antiFlashStyle.parentNode) antiFlashStyle.remove();
        document.body.classList.add('reddit-ready');
    }

    // --- 配置与常量 ---
    const CONFIG = {
        DEBUG: true,
        MAX_CONCURRENT_REQUESTS: 5,
        BATCH_DELAY: 200,
        NESTING_LIMIT: 10,
    };
    const LAYOUT_STORAGE_KEY = 'phpwind-reddit-layout-mode';

    // --- CSS 样式 ---
    const styles = `
        /* 初始完全隐藏 body,消除原样式闪烁,reddit 就绪后再显示 */
        body { visibility: hidden !important; }
        body.reddit-ready { visibility: visible !important; }
        /* 原始排版模式下隐藏原始元素 */
        .original-hidden { display: none !important; }

        /* Reddit 容器 */
        #reddit-container {
            max-width: 800px;
            margin: 20px auto;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            background: #fff;
            padding: 20px;
            color: #1c1c1c;
        }

        .reddit-post-wrapper {
            position: relative;
            margin-top: 12px;
        }

        .reddit-comment {
            display: flex;
            flex-direction: row;
            min-height: 40px; /* 确保即使内容很少,也有足够高度容纳头像和基础信息 */
        }

        .reddit-comment-left {
            display: flex;
            flex-direction: column;
            align-items: center;
            width: 32px;
            margin-right: 12px;
            position: relative;
            flex-shrink: 0;
        }

        .reddit-avatar-wrapper {
            position: relative;
            width: 32px;
            height: 32px;
            z-index: 3;
        }

        .reddit-avatar {
            width: 100%;
            height: 100%;
            border-radius: 50%;
            object-fit: cover;
            background-color: #f6f7f8;
            border: 2px solid #fff; /* 给头像加个白边,防止被线穿过 */
        }

        .reddit-threadline {
            position: absolute;
            top: 32px; /* 从头像底部开始 */
            bottom: -12px; /* 延伸到容器底部 */
            width: 2px;
            background-color: #edeff1;
            cursor: pointer;
            transition: background-color 0.2s;
            z-index: 2;
            left: 15px; /* 居中 */
        }

        .reddit-threadline:hover {
            background-color: #0079d3;
        }

        .reddit-comment-right {
            flex-grow: 1;
            display: flex;
            flex-direction: column;
            min-width: 0;
        }

        .reddit-comment-header {
            display: flex;
            align-items: center;
            font-size: 13px;
            margin-bottom: 11px;
            line-height: 16px;
            min-height: 18px;
        }

        .reddit-author {
            font-weight: 600;
            color: #1c1c1c;
            text-decoration: none;
            margin-right: 4px;
        }

        .reddit-author:hover {
            text-decoration: underline;
        }

        .reddit-time {
            color: #787c7e;
        }

        .reddit-comment-body {
            font-size: 14px;
            line-height: 21px;
            word-wrap: break-word;
            margin-bottom: 4px;
            color: #1c1c1c;
        }

        /* 隐藏原始引用框,但排除购买框 (jumbotron) 及其子元素 */
        .reddit-comment-body .quote:not(.jumbotron),
        .reddit-comment-body .blockquote:not(.jumbotron),
        .reddit-comment-body .blockquote2,
        .reddit-comment-body .blockquote3,
        .reddit-comment-body .f12,
        .reddit-comment-body h1[id^="subject_"] {
            display: none !important;
        }
        /* 隐藏紧跟在引用框后的 <br>,避免引用框被隐藏后留下多余空行 */
        .reddit-comment-body .blockquote:not(.jumbotron) + br,
        .reddit-comment-body .blockquote2 + br,
        .reddit-comment-body .blockquote3 + br {
            display: none !important;
        }

        /* 购买框样式微调 */
        .reddit-comment-body .jumbotron {
            display: block !important;
            margin: 10px 0;
            padding: 12px;
            background-color: #fff5f5;
            border: 1px solid #feb2b2;
            border-radius: 6px;
        }
        /* 购买框内的所有子元素保持可见,不被上述隐藏规则影响 */
        .reddit-comment-body .jumbotron .blockquote,
        .reddit-comment-body .jumbotron .blockquote2,
        .reddit-comment-body .jumbotron .blockquote3,
        .reddit-comment-body .jumbotron .f12,
        .reddit-comment-body .jumbotron .quote {
            display: revert !important;
        }

        .reddit-comment-actions {
            display: flex;
            align-items: center;
            gap: 4px;
            margin-bottom: 8px;
        }

        .reddit-action-btn {
            display: flex;
            align-items: center;
            gap: 6px;
            padding: 6px 8px;
            border-radius: 9999px;
            border: none;
            background: transparent;
            color: #576f76;
            font-size: 12px;
            font-weight: 600;
            cursor: pointer;
            text-decoration: none;
            line-height: 16px;
        }

        .reddit-action-btn:hover {
            background-color: #eaedef;
        }

        .reddit-action-icon {
            width: 16px;
            height: 16px;
            fill: currentColor;
        }

        /* 嵌套样式与曲线设计 */
        .reddit-children {
            position: relative;
            margin-left: 36px; /* 增加缩进,为大弧度留出空间 */
            padding-left: 0;
        }

        /* 曲线连接线:连接父级垂直线到子级头像 */
        .reddit-children > .reddit-post-wrapper::before {
            content: "";
            position: absolute;
            left: -21px; /* 36(margin) - 15(parent threadline) = 21 */
            top: -12px;
            width: 37px; /* 延伸到子头像中心 (36 - 15 + 16 = 37) */
            height: 28px; /* 到达头像中心高度 */
            border-left: 2px solid #edeff1;
            border-bottom: 2px solid #edeff1;
            border-bottom-left-radius: 24px; /* 更大、更平缓的弧度 */
            pointer-events: none;
            z-index: 1;
        }

        /* 垂直补充线:如果当前评论之后还有兄弟评论,则垂直线继续向下延伸 */
        .reddit-children > .reddit-post-wrapper:not(:last-child)::after {
            content: "";
            position: absolute;
            left: -21px;
            top: 16px;
            bottom: -12px;
            border-left: 2px solid #edeff1;
            pointer-events: none;
            z-index: 1;
        }

        /* 折叠按钮 */
        .reddit-collapse-btn {
            position: absolute;
            left: 6px; /* 居中对齐到 threadline (15px) */
            top: 36px;
            width: 20px;
            height: 20px;
            background: #fff;
            border: 1px solid #edeff1;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            z-index: 4;
            color: #878a8c;
            opacity: 1; /* 常显,更符合直觉 */
            transition: all 0.2s;
            padding: 0;
        }

        .reddit-comment:hover .reddit-collapse-btn {
            border-color: #d7d9db;
            color: #576f76;
        }

        .reddit-collapse-btn:hover {
            background-color: #eaedef;
            border-color: #878a8c;
            color: #1c1c1c;
        }

        .reddit-collapse-btn svg {
            width: 12px;
            height: 12px;
        }

        /* 针对折叠状态 */
        .reddit-post-wrapper.collapsed > .reddit-comment > .reddit-comment-right > .reddit-comment-body,
        .reddit-post-wrapper.collapsed > .reddit-comment > .reddit-comment-right > .reddit-comment-actions,
        .reddit-post-wrapper.collapsed > .reddit-children,
        .reddit-post-wrapper.collapsed > .reddit-comment > .reddit-comment-left > .reddit-threadline,
        .reddit-post-wrapper.collapsed > .reddit-comment > .reddit-comment-left > .reddit-collapse-btn {
            display: none !important;
        }

        .reddit-post-wrapper.collapsed > .reddit-comment > .reddit-comment-left > .reddit-avatar-wrapper {
            opacity: 0.6;
        }

        /* 头部折叠/展开按钮 */
        .reddit-expand-inline,
        .reddit-collapse-inline {
            display: none;
            margin-left: 8px;
            width: 16px;
            height: 16px;
            cursor: pointer;
            vertical-align: middle;
            fill: #787c7e;
            transition: fill 0.2s;
        }

        .reddit-expand-inline {
            fill: #0079d3;
        }

        .reddit-comment-header:hover .reddit-collapse-inline {
            display: inline-block;
        }

        .reddit-post-wrapper.collapsed > .reddit-comment > .reddit-comment-right > .reddit-comment-header .reddit-expand-inline {
            display: inline-block;
        }

        .reddit-post-wrapper.collapsed > .reddit-comment > .reddit-comment-right > .reddit-comment-header .reddit-collapse-inline {
            display: none !important;
        }

        /* 切换按钮 */
        #layout-toggle-btn {
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 9999;
            padding: 10px 15px;
            background: #0079d3;
            color: white;
            border: none;
            border-radius: 20px;
            cursor: pointer;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            font-weight: bold;
        }

        #layout-toggle-btn:hover { background: #005fa3; }

        /* Loading 状态 */
        #reddit-loader {
            text-align: center;
            padding: 50px;
            font-size: 18px;
            color: #666;
        }

        /* 回复框样式 */
        .reddit-reply-box {
            margin-top: 10px;
            margin-bottom: 15px;
            padding: 12px;
            background: #f6f7f8;
            border-radius: 4px;
            display: flex;
            flex-direction: column;
            gap: 8px;
        }

        .reddit-reply-textarea {
            width: 100%;
            min-height: 100px;
            padding: 8px;
            border: 1px solid #edeff1;
            border-radius: 4px;
            font-family: inherit;
            resize: vertical;
        }

        .reddit-reply-footer {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 8px;
        }

        .reddit-reply-footer-actions {
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .reddit-reply-footer-submit {
            display: flex;
            align-items: center;
            justify-content: flex-end;
            gap: 8px;
        }

        .reddit-reply-btn {
            padding: 4px 16px;
            border-radius: 999px;
            font-weight: 700;
            cursor: pointer;
            border: none;
            font-size: 12px;
        }

        .reddit-reply-submit {
            background-color: #0079d3;
            color: white;
        }

        .reddit-reply-submit:hover { background-color: #005fa3; }

        .reddit-reply-cancel {
            background-color: transparent;
            color: #576f76;
        }

        .reddit-reply-cancel:hover { background-color: #eaedef; }

        /* 表情相关 */
        .reddit-reply-actions {
            display: flex;
            align-items: center;
            gap: 12px;
            margin-bottom: 4px;
        }

        .reddit-emoji-btn {
            background: transparent;
            border: none;
            cursor: pointer;
            padding: 4px;
            display: flex;
            align-items: center;
            color: #576f76;
            border-radius: 4px;
            transition: background-color 0.2s;
        }

        .reddit-emoji-btn:hover {
            background-color: #eaedef;
        }

        .reddit-emoji-container {
            display: none;
            margin: 6px 0 8px;
        }

        .reddit-emoji-container.active {
            display: block;
        }

        #reddit-smilie-storage {
            display: none !important;
        }

        #smiliebox {
            display: block !important;
            border: 1px solid #edeff1 !important;
            padding: 10px !important;
            background: #fff !important;
            max-width: 100%;
            margin: 8px 0 !important;
            border-radius: 4px;
        }

        #menu_face, #menu_generalface {
            z-index: 10001 !important;
        }

        /* 投票容器样式 */
        .reddit-vote-container {
            margin: 15px 0;
            padding: 10px;
            border: 1px solid #edeff1;
            border-radius: 8px;
            background: #f8f9fa;
        }
        .reddit-vote-container form {
            display: block !important;
            margin: 0 !important;
        }
        .reddit-vote-container .tt2 {
            display: block !important;
            border: 1px solid #edeff1 !important;
            border-radius: 6px;
            overflow: hidden;
            background: #fff;
        }
        .reddit-vote-container table {
            width: 100% !important;
            border-collapse: collapse;
            background: #fff !important;
        }
        .reddit-vote-container th,
        .reddit-vote-container td {
            padding: 8px !important;
            border-bottom: 1px solid #edeff1;
            font-size: 13px;
            text-align: left !important;
            font-weight: normal !important;
            line-height: 1.6 !important;
        }
        .reddit-vote-container tr:last-child th,
        .reddit-vote-container tr:last-child td {
            border-bottom: none !important;
        }
        .reddit-vote-container .h {
            background: #f1f3f5 !important;
            color: #1c1c1c !important;
            font-weight: 600 !important;
        }
        .reddit-vote-container input[type="radio"],
        .reddit-vote-container input[type="checkbox"] {
            appearance: auto !important;
            -webkit-appearance: auto !important;
            width: 16px !important;
            height: 16px !important;
            margin: 0 8px 0 0 !important;
            cursor: pointer;
            vertical-align: middle;
            display: inline-block !important;
            opacity: 1 !important;
            position: static !important;
        }
        .reddit-vote-container input[type="submit"],
        .reddit-vote-container input[type="button"] {
            padding: 6px 16px;
            background: #0079d3;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-weight: bold;
            margin-top: 10px;
        }
        .reddit-vote-container input[type="submit"]:hover {
            background: #005fa3;
        }
        .reddit-vote-container img {
            vertical-align: middle;
            max-width: 100%;
        }

        .reddit-title-row {
            display: flex;
            align-items: flex-start;
            justify-content: space-between;
            gap: 16px;
            margin-bottom: 20px;
        }

        .reddit-title-text {
            font-size: 24px;
            font-weight: bold;
            color: #1c1c1c;
            min-width: 0;
        }

        .reddit-topic-actions {
            display: flex;
            align-items: center;
            justify-content: flex-end;
            gap: 8px;
            flex-shrink: 0;
        }

        .reddit-topic-btn {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            min-height: 32px;
            padding: 0 12px;
            border-radius: 999px;
            background: #0079d3;
            color: #fff !important;
            font-size: 13px;
            font-weight: 700;
            text-decoration: none !important;
            white-space: nowrap;
        }

        .reddit-topic-btn:hover {
            background: #005fa3;
        }

        /* Apple 风格悬浮导航栏 */
        #reddit-sidebar {
            position: fixed;
            left: 24px;
            top: 50%;
            transform: translateY(-50%);
            z-index: 10002;
            display: flex;
            flex-direction: column;
            gap: 16px;
            transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1), transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
        }

        #reddit-sidebar.hidden {
            opacity: 0;
            pointer-events: none;
            transform: translateY(-50%) translateX(-30px);
        }

        .sidebar-item {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            width: 64px;
            height: 64px;
            background: rgba(255, 255, 255, 0.72);
            backdrop-filter: blur(20px) saturate(180%);
            -webkit-backdrop-filter: blur(20px) saturate(180%);
            border-radius: 18px;
            box-shadow: 0 4px 24px rgba(0, 0, 0, 0.04);
            text-decoration: none;
            color: #1d1d1f;
            transition: all 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
            border: 1px solid rgba(255, 255, 255, 0.4);
            overflow: hidden;
        }

        .sidebar-item:hover {
            background: rgba(255, 255, 255, 0.92);
            transform: scale(1.08) translateX(4px);
            box-shadow: 0 12px 32px rgba(0, 0, 0, 0.08);
            border-color: rgba(255, 255, 255, 0.6);
            color: #0071e3; /* Apple Blue */
        }

        .sidebar-icon {
            width: 22px;
            height: 22px;
            margin-bottom: 4px;
            fill: none;
            stroke: currentColor;
            stroke-width: 1.5;
            stroke-linecap: round;
            stroke-linejoin: round;
        }

        .sidebar-text {
            font-size: 11px;
            font-weight: 600;
            letter-spacing: 0.02em;
        }
    `;
    GM_addStyle(styles);

    // --- 全局状态 ---
    let redditContainer = null;
    let isRedditMode = getStoredLayoutMode() !== 'original';
    let allPosts = new Map(); // floorNumber -> PostObject
    let rootPosts = [];
    let originalElements = [];
    let activeTextarea = null;
    let originalSmilieParent = null;
    let originalSmilieNextSibling = null;

    function getStoredLayoutMode() {
        try {
            return localStorage.getItem(LAYOUT_STORAGE_KEY) || 'reddit';
        } catch (e) {
            return 'reddit';
        }
    }

    function setStoredLayoutMode(mode) {
        try {
            localStorage.setItem(LAYOUT_STORAGE_KEY, mode);
        } catch (e) {
            // localStorage may be unavailable in strict privacy contexts.
        }
    }

    // --- 初始化 ---
    async function init() {
        // 1. 识别并隐藏原始所有主要容器,防止它们在 Reddit 模式下露出来
        const selectors = ['#toptool', '#wrapA', '#main', '#footer', '#breadcrumbs', '.bdbA', '.t3', 'audio#music'];
        selectors.forEach(s => {
            const el = document.querySelector(s);
            if (el) {
                if (isRedditMode) el.classList.add('original-hidden');
                originalElements.push(el);
            }
        });

        if (originalElements.length === 0) {
            showPage();
            return;
        }

        const originalSmilieBox = document.getElementById('smiliebox');
        if (originalSmilieBox) {
            originalSmilieParent = originalSmilieBox.parentNode;
            originalSmilieNextSibling = originalSmilieBox.nextSibling;
        }

        // 覆盖原生的 addsmile 函数,使其支持我们的动态回复框
        const originalAddSmile = window.addsmile;
        window.addsmile = function (code) {
            if (insertSmileCode(code)) {
                return;
            } else if (originalAddSmile) {
                originalAddSmile(code);
            }
        };

        // 2. 创建 Reddit 容器
        redditContainer = document.createElement('div');
        redditContainer.id = 'reddit-container';
        redditContainer.addEventListener('click', handlePurchaseClick, true); // capture phase to intercept before inline onclick
        document.body.appendChild(redditContainer);

        // 3. 创建切换按钮
        const toggleBtn = document.createElement('button');
        toggleBtn.id = 'layout-toggle-btn';
        toggleBtn.innerText = isRedditMode ? '返回原始排版' : '切换到 Reddit 样式';
        toggleBtn.onclick = toggleLayout;
        document.body.appendChild(toggleBtn);

        // 4. 创建悬浮导航栏
        createFloatingSidebar();

        // 5. 开始抓取数据
        try {
            parsePosts(document);
            buildTree();
            renderReddit();
            if (!isRedditMode) redditContainer.style.display = 'none';
            // 首次渲染完成后显示页面,消除原样式闪烁
            showPage();

            await startFetching();
            buildTree();
            renderReddit();
            if (!isRedditMode) redditContainer.style.display = 'none';
        } catch (e) {
            console.error('Reddit Refactor Error:', e);
            redditContainer.innerHTML = '<div style="text-align:center;padding:20px;">加载失败: ' + e.message + '</div>';
            showPage();
        }
    }

    function toggleLayout() {
        isRedditMode = !isRedditMode;
        const sidebar = document.getElementById('reddit-sidebar');
        if (isRedditMode) {
            originalElements.forEach(el => el.classList.add('original-hidden'));
            redditContainer.style.display = 'block';
            if (sidebar) sidebar.classList.remove('hidden');
            document.getElementById('layout-toggle-btn').innerText = '返回原始排版';
            setStoredLayoutMode('reddit');
        } else {
            restoreOriginalSmilieBox();
            originalElements.forEach(el => el.classList.remove('original-hidden'));
            redditContainer.style.display = 'none';
            if (sidebar) sidebar.classList.add('hidden');
            document.getElementById('layout-toggle-btn').innerText = '切换到 Reddit 样式';
            setStoredLayoutMode('original');
        }
    }

    function createFloatingSidebar() {
        if (document.getElementById('reddit-sidebar')) return;

        const sidebar = document.createElement('div');
        sidebar.id = 'reddit-sidebar';
        if (!isRedditMode) sidebar.classList.add('hidden');

        // 提取当前版块链接
        const forumLink = document.querySelector('.bdbA #breadcrumbs a[href*="fid-"]') ||
            document.querySelector('#breadcrumbs a[href*="fid-"]') ||
            { href: 'thread.php?fid=9', innerText: '茶馆' };

        const forumName = (forumLink.innerText || forumLink.textContent || '茶馆').trim().replace(/<i>.*<\/i>/g, '');

        sidebar.innerHTML = `
            <a href="index.php" class="sidebar-item" title="首页">
                <svg class="sidebar-icon" viewBox="0 0 24 24"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
                <span class="sidebar-text">首页</span>
            </a>
            <a href="${forumLink.href}" class="sidebar-item" title="${forumName}">
                <svg class="sidebar-icon" viewBox="0 0 24 24"><path d="M18 8h1a4 4 0 0 1 0 8h-1"></path><path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z"></path><line x1="6" y1="1" x2="6" y2="4"></line><line x1="10" y1="1" x2="10" y2="4"></line><line x1="14" y1="1" x2="14" y2="4"></line></svg>
                <span class="sidebar-text">${forumName}</span>
            </a>
            <a href="search.php" class="sidebar-item" title="搜索">
                <svg class="sidebar-icon" viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
                <span class="sidebar-text">搜索</span>
            </a>
            <a href="javascript:void(0)" class="sidebar-item" title="收藏" id="reddit-sidebar-favor">
                <svg class="sidebar-icon" viewBox="0 0 24 24"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path></svg>
                <span class="sidebar-text">收藏</span>
            </a>
        `;
        document.body.appendChild(sidebar);

        // 绑定收藏功能
        const favorBtn = sidebar.querySelector('#reddit-sidebar-favor');
        if (favorBtn) {
            favorBtn.onclick = (e) => {
                e.preventDefault();

                // 优先尝试点击页面原有的收藏按钮,这能触发论坛自带的弹窗提示
                const originalFavorBtn = document.getElementById('favor');
                if (originalFavorBtn && (originalFavorBtn.getAttribute('onclick') || '').includes('favor')) {
                    originalFavorBtn.click();
                    return;
                }

                // 降级方案:自己发送 AJAX 请求,防止跳转导致空白页
                const tid = getTopicId();
                if (!tid) {
                    alert('无法获取帖子ID,收藏失败。');
                    return;
                }

                GM_xmlhttpRequest({
                    method: 'GET',
                    url: `pw_ajax.php?action=favor&tid=${tid}`,
                    onload: function (res) {
                        const text = res.responseText || '';
                        if (text.includes('已经收藏')) {
                            alert('您已经收藏过此帖了!');
                        } else if (text.includes('成功') || text.includes('success')) {
                            alert('收藏成功!');
                        } else if (text.includes('登录')) {
                            alert('收藏失败:请先登录。');
                        } else {
                            // PHPWind 返回的信息通常是用 \t 分隔的
                            const parts = text.split('\t');
                            const msg = parts.length > 1 ? parts[1] : text;
                            alert('收藏提示:\n' + msg.replace(/<[^>]+>/g, '').trim());
                        }
                    },
                    onerror: function () {
                        alert('网络请求失败,收藏失败。');
                    }
                });
            };
        }
    }

    // --- 数据抓取逻辑 ---
    function getTotalPages() {
        let maxPage = 1;

        // 方法1:通过分页链接的数字来判断最大页数
        const pageElements = document.querySelectorAll('.pages a, .pages b, .pages li');
        pageElements.forEach(el => {
            const text = (el.innerText || el.textContent || '').trim();
            if (/^\d+$/.test(text)) {
                const pageNum = parseInt(text, 10);
                if (pageNum > maxPage) maxPage = pageNum;
            }
        });

        // 方法2:通过分页文本提取 "Pages: 1/4" 或 "1 / 4"
        const pagesContainers = document.querySelectorAll('.pages');
        pagesContainers.forEach(container => {
            const text = container.innerText || '';
            const match = text.match(/\/\s*(\d+)/);
            if (match) {
                const pageNum = parseInt(match[1], 10);
                if (pageNum > maxPage) maxPage = pageNum;
            }
        });

        return maxPage;
    }

    function getCurrentPageNumber() {
        const pageMatch = window.location.href.match(/[?&]page=(\d+)|-page-(\d+)/);
        if (!pageMatch) return 1;
        return parseInt(pageMatch[1] || pageMatch[2], 10) || 1;
    }

    function buildPageUrls(totalPages) {
        let baseUrl = window.location.href;
        baseUrl = baseUrl.replace(/[&?]page=\d+/, '').replace(/-page-\d+/, '');

        const urls = [];
        for (let i = 1; i <= totalPages; i++) {
            let url = baseUrl;
            if (url.includes('.html')) {
                url = url.replace('.html', `-page-${i}.html`);
            } else {
                url += (url.includes('?') ? '&' : '?') + `page=${i}`;
            }
            urls.push({ page: i, url });
        }
        return urls;
    }

    async function fetchAndRenderPage(pageEntry) {
        const doc = await fetchPage(pageEntry.url);
        parsePosts(doc);
        buildTree();
        renderReddit();
    }

    async function startFetching() {
        const totalPages = getTotalPages();
        if (totalPages <= 1) return;

        const currentPage = getCurrentPageNumber();
        const fetchedPages = new Set([currentPage]);
        const pageEntries = buildPageUrls(totalPages);

        if (currentPage !== 1) {
            const firstPage = pageEntries.find(entry => entry.page === 1);
            if (firstPage) {
                await fetchAndRenderPage(firstPage);
                fetchedPages.add(1);
            }
        }

        const remainingPages = pageEntries.filter(entry => !fetchedPages.has(entry.page));

        // 分批抓取
        for (let i = 0; i < remainingPages.length; i += CONFIG.MAX_CONCURRENT_REQUESTS) {
            const batch = remainingPages.slice(i, i + CONFIG.MAX_CONCURRENT_REQUESTS);
            const results = await Promise.all(batch.map(entry => fetchPage(entry.url)));
            results.forEach(doc => parsePosts(doc));
            buildTree();
            renderReddit();

            if (i + CONFIG.MAX_CONCURRENT_REQUESTS < remainingPages.length) {
                await new Promise(resolve => setTimeout(resolve, CONFIG.BATCH_DELAY));
            }
        }
    }

    async function fetchPage(url) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                timeout: 10000,
                onload: (res) => {
                    if (res.status !== 200) {
                        reject(new Error(`HTTP ${res.status}`));
                        return;
                    }
                    const parser = new DOMParser();
                    resolve(parser.parseFromString(res.responseText, 'text/html'));
                },
                onerror: () => reject(new Error('网络请求错误')),
                ontimeout: () => reject(new Error('请求超时'))
            });
        });
    }

    function getNodeText(node) {
        return (node?.innerText || node?.textContent || '').replace(/\u00a0/g, ' ').trim();
    }

    function decodeCloudflareEmail(encoded) {
        if (!encoded || encoded.length < 4) return '';
        try {
            const key = parseInt(encoded.slice(0, 2), 16);
            let email = '';
            for (let i = 2; i < encoded.length; i += 2) {
                email += String.fromCharCode(parseInt(encoded.slice(i, i + 2), 16) ^ key);
            }
            return email;
        } catch (e) {
            return '';
        }
    }

    function getDisplayText(node) {
        const protectedEmail = node?.querySelector?.('.__cf_email__[data-cfemail]');
        if (protectedEmail) {
            const decoded = decodeCloudflareEmail(protectedEmail.getAttribute('data-cfemail'));
            if (decoded) return decoded;
        }
        return getNodeText(node);
    }

    function insertSmileCode(code, target = activeTextarea) {
        target = target || document.querySelector('form[name="FORM"] textarea[name="atc_content"]');
        if (!target || code === undefined || code === null || code === '') return false;

        const insert = ' [s:' + code + '] ';
        const start = typeof target.selectionStart === 'number' ? target.selectionStart : target.value.length;
        const end = typeof target.selectionEnd === 'number' ? target.selectionEnd : start;
        const val = target.value || '';
        target.value = val.substring(0, start) + insert + val.substring(end);
        target.focus();
        target.selectionStart = target.selectionEnd = start + insert.length;
        target.dispatchEvent(new Event('input', { bubbles: true }));
        target.dispatchEvent(new Event('change', { bubbles: true }));
        return true;
    }

    function bindSmilieBoxClicks(smilieBox, textarea) {
        if (!smilieBox || smilieBox.dataset.redditSmileBound === '1') return;
        smilieBox.dataset.redditSmileBound = '1';
        smilieBox.addEventListener('click', event => {
            const smile = event.target?.closest?.('img[onclick*="addsmile"]');
            if (!smile || !smilieBox.contains(smile)) return;

            const onclick = smile.getAttribute('onclick') || '';
            const match = onclick.match(/addsmile\(([^)]+)\)/i);
            if (!match) return;

            event.preventDefault();
            event.stopPropagation();
            event.stopImmediatePropagation();
            const code = match[1].replace(/['"\s]/g, '');
            insertSmileCode(code, activeTextarea || textarea);
        }, true);
    }

    function getSmilieStorage() {
        let storage = document.getElementById('reddit-smilie-storage');
        if (!storage) {
            storage = document.createElement('div');
            storage.id = 'reddit-smilie-storage';
            document.body.appendChild(storage);
        }
        return storage;
    }

    function hideSmilieBox() {
        const smilieBox = document.getElementById('smiliebox');
        if (smilieBox) getSmilieStorage().appendChild(smilieBox);
        document.querySelectorAll('.reddit-emoji-container.active').forEach(container => {
            container.classList.remove('active');
        });
    }

    function restoreOriginalSmilieBox() {
        const smilieBox = document.getElementById('smiliebox');
        document.querySelectorAll('.reddit-emoji-container.active').forEach(container => {
            container.classList.remove('active');
        });
        if (!smilieBox || !originalSmilieParent) return;

        if (smilieBox.parentNode !== originalSmilieParent) {
            if (originalSmilieNextSibling && originalSmilieNextSibling.parentNode === originalSmilieParent) {
                originalSmilieParent.insertBefore(smilieBox, originalSmilieNextSibling);
            } else {
                originalSmilieParent.appendChild(smilieBox);
            }
        }
    }

    function showSmilieBoxFor(textarea, emojiContainer) {
        const smilieBox = document.getElementById('smiliebox');
        if (!smilieBox || !emojiContainer) return;

        if (emojiContainer.contains(smilieBox)) {
            hideSmilieBox();
            return;
        }

        activeTextarea = textarea;
        hideSmilieBox();
        emojiContainer.classList.add('active');
        emojiContainer.appendChild(smilieBox);
        bindSmilieBoxClicks(smilieBox, textarea);
        if (window.showDefault && !document.getElementById('menu_show')?.innerHTML) {
            window.showDefault();
        }
        textarea?.focus();
    }

    function bindReplyBox(replyBox, textarea) {
        const emojiBtn = replyBox.querySelector('.reddit-emoji-btn');
        const emojiContainer = replyBox.querySelector('.reddit-emoji-container');
        if (!emojiBtn || !emojiContainer) return;

        textarea.onfocus = () => { activeTextarea = textarea; };
        textarea.onclick = () => { activeTextarea = textarea; };
        emojiBtn.onmousedown = () => { activeTextarea = textarea; };
        emojiBtn.onclick = () => showSmilieBoxFor(textarea, emojiContainer);
    }

    function getOriginalTopicActionLinks() {
        const actionArea = document.querySelector('.t3');
        const scope = actionArea || document;
        const newTopicLink = scope.querySelector('a[href^="post.php?fid"]') || document.querySelector('#menu_post a[href^="post.php?fid"]');
        const replyLink = scope.querySelector('a[href*="action-reply"][href*="tid"]') || document.querySelector('a[href*="action-reply"][href*="tid"]');

        return {
            newTopicHref: newTopicLink ? newTopicLink.href : '',
            replyHref: replyLink ? replyLink.href : ''
        };
    }

    function extractParentFloorFromText(text) {
        const normalized = (text || '').replace(/\u00a0/g, ' ');
        const match = normalized.match(/(?:回|回复|引用第?)\s*(楼主|\d+)\s*楼?/);
        if (!match) return null;
        return match[1] === '楼主' ? 0 : parseInt(match[1], 10);
    }

    function extractParentFloor(table) {
        const subject = table.querySelector('h1[id^="subject_"]');
        let parentFloor = extractParentFloorFromText(getNodeText(subject));
        if (parentFloor !== null) return parentFloor;

        const contentElement = table.querySelector('.tpc_content');
        if (!contentElement) return null;

        const quoteElements = contentElement.querySelectorAll('.quote, blockquote, .blockquote2, .blockquote3, .blockquote');
        for (const quote of quoteElements) {
            parentFloor = extractParentFloorFromText(getNodeText(quote));
            if (parentFloor !== null) return parentFloor;
        }
        return null;
    }

    function findPostActionLink(table, postId, textPattern, hrefPattern) {
        const doc = table.ownerDocument;
        const areas = [table];
        if (postId) {
            const menu = doc.getElementById(`menu_read_${postId}`);
            if (menu) areas.push(menu);
        }

        for (const area of areas) {
            const links = area.querySelectorAll('a');
            for (const link of links) {
                const text = getNodeText(link);
                const title = link.getAttribute('title') || '';
                const href = link.getAttribute('href') || '';
                if (textPattern.test(text) || textPattern.test(title) || hrefPattern.test(href)) {
                    return {
                        href,
                        onclick: link.getAttribute('onclick') || ''
                    };
                }
            }
        }
        return null;
    }

    function getVoteMultiLimit(doc) {
        const scripts = Array.from(doc.querySelectorAll('script'));
        for (const script of scripts) {
            const text = script.textContent || '';
            const match = text.match(/var\s+multi\s*=\s*['"]?(\d+)['"]?/);
            if (match) return parseInt(match[1], 10) || 0;
        }
        return 0;
    }

    function getVoteHtml(doc) {
        const voteForm = doc.querySelector('form[name="vote"], form[action*="action=vote"]');
        const voteContent = voteForm?.querySelector('.tt2') || Array.from(doc.querySelectorAll('.tt2')).find(el => /投票|票数|人参与|本次投票/.test(getNodeText(el)));
        const source = voteForm && voteContent ? voteForm : voteContent;
        if (!source) return '';

        const clone = source.cloneNode(true);
        clone.querySelectorAll('script, style').forEach(el => el.remove());

        if (clone.tagName === 'FORM') {
            clone.classList.add('reddit-vote-form');
            clone.dataset.voteMulti = String(getVoteMultiLimit(doc));
            if (!clone.getAttribute('method')) clone.setAttribute('method', 'post');
        }

        return `<div class="reddit-vote-container">${clone.outerHTML}</div>`;
    }

    // --- 购买按钮点击处理 ---
    function handlePurchaseClick(event) {
        const target = event.target.closest('input[type="button"], input[type="submit"], button, a, span[onclick]');
        if (!target) return;

        const onclick = target.getAttribute('onclick') || '';
        const text = (target.value || target.textContent || '').trim();

        // 只处理购买相关按钮
        const isPurchaseBtn = /购买|付钱|buy/i.test(text) ||
            /sellcontent|buytopic|action=sell/i.test(onclick);
        if (!isPurchaseBtn) return;

        // 检查是否在购买框内
        const jumbotron = target.closest('.jumbotron');
        if (!jumbotron) return;

        // 情况1: location.href 跳转 (免费购买) — 放行原始行为,最可靠
        if (/location\.href/.test(onclick)) {
            // 不拦截,让页面自然跳转
            return;
        }

        // 情况2: sendmsg AJAX调用 (付费购买) — 拦截并用 GM_xmlhttpRequest 处理
        const sendmsgMatch = onclick.match(/sendmsg\s*\(\s*['"]([^'"]+)['"]\s*,\s*['"]([^'"]*)['"]/);
        if (!sendmsgMatch) return; // 无法识别,放行

        event.preventDefault();
        event.stopPropagation();
        event.stopImmediatePropagation();

        // 显示购买中提示
        const loadingDiv = document.createElement('div');
        loadingDiv.id = 'reddit-purchase-loading';
        loadingDiv.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:99999;padding:20px 40px;background:#fff;border:2px solid #0079d3;border-radius:8px;box-shadow:0 4px 20px rgba(0,0,0,0.15);font-size:16px;font-weight:bold;color:#1c1c1c;text-align:center;';
        loadingDiv.innerHTML = '<div style="margin-bottom:8px;">正在购买......请稍等......</div><div style="font-size:12px;color:#787c7e;font-weight:normal;">请勿关闭页面</div>';
        document.body.appendChild(loadingDiv);

        const cleanupLoading = () => {
            if (loadingDiv.parentNode) loadingDiv.parentNode.removeChild(loadingDiv);
        };

        let requestUrl = sendmsgMatch[1];
        let requestData = sendmsgMatch[2];

        // 确保URL是绝对路径
        if (!requestUrl.startsWith('http')) {
            requestUrl = new URL(requestUrl, window.location.href).href;
        }

        if (CONFIG.DEBUG) console.log('Purchase request:', requestMethod, requestUrl, requestData);

        GM_xmlhttpRequest({
            method: requestMethod,
            url: requestUrl,
            data: requestData || undefined,
            headers: requestData ? {
                'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
                'Referer': window.location.href
            } : {},
            timeout: 15000,
            onload: function (res) {
                cleanupLoading();

                if (res.status < 200 || res.status >= 400) {
                    alert('购买失败:服务器返回 HTTP ' + res.status + '\n请尝试刷新页面后重试。');
                    return;
                }

                const responseText = res.responseText || '';

                // 检查是否成功 (PHPWind返回 tab 分隔的响应)
                if (/success|成功/.test(responseText)) {
                    // 购买成功,刷新页面显示购买内容
                    alert('购买成功!页面即将刷新。');
                    window.location.reload();
                } else if (/不足|不够|余额|金钱|SP币/.test(responseText)) {
                    alert('购买失败:余额不足。');
                } else if (/登录/.test(responseText)) {
                    alert('购买失败:请先登录。');
                } else if (responseText.trim().length === 0 || /ok|success/i.test(responseText)) {
                    // 空响应或ok通常表示成功
                    alert('购买操作已完成,页面即将刷新。');
                    window.location.reload();
                } else {
                    // 未知响应,放行刷新
                    if (CONFIG.DEBUG) console.log('Purchase response:', responseText.substring(0, 500));
                    window.location.reload();
                }
            },
            onerror: function () {
                cleanupLoading();
                alert('购买失败:网络请求错误。请检查网络连接后重试。');
            },
            ontimeout: function () {
                cleanupLoading();
                alert('购买失败:请求超时。请稍后重试。');
            }
        });
    }

    function initVoteForms(container = redditContainer) {
        if (!container) return;

        container.querySelectorAll('.reddit-vote-form:not([data-reddit-vote-ready])').forEach(form => {
            form.dataset.redditVoteReady = '1';
            const limit = parseInt(form.dataset.voteMulti || '0', 10);
            if (limit <= 0) return;

            form.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach(input => {
                input.addEventListener('change', () => {
                    if (input.type === 'radio') return;

                    const checked = Array.from(form.querySelectorAll('input[type="checkbox"]:checked'));
                    if (checked.length > limit) {
                        input.checked = false;
                        alert(`本投票最多可选 ${limit} 项`);
                    }
                });
            });
        });
    }

    // --- 数据解析逻辑 ---
    function parsePosts(doc) {
        const tables = doc.querySelectorAll('.t5, .read_t');
        tables.forEach(table => {
            const tiptop = table.querySelector('.tiptop');
            if (!tiptop) return;

            const floorText = getNodeText(tiptop);
            const floorMatch = floorText.match(/(楼主)|(\d+)楼|GF|B(\d+)F/);
            if (!floorMatch) return;

            let floorNum;
            if (floorMatch[1] || floorMatch[0] === 'GF') {
                floorNum = 0;
            } else if (floorMatch[3]) {
                floorNum = parseInt(floorMatch[3], 10);
            } else if (floorMatch[2]) {
                floorNum = parseInt(floorMatch[2], 10);
            } else {
                return;
            }

            if (allPosts.has(floorNum)) return;

            const postIdElement = table.querySelector('th[id^="td_"], td[id^="td_"]');
            const postId = postIdElement ? postIdElement.id.replace(/^td_/, '') : '';

            const authorTags = table.querySelectorAll('a[href^="u.php?action-show"]');
            let author = '匿名';
            let authorHref = '#';
            let avatarUrl = '';
            for (const a of authorTags) {
                const img = a.querySelector('img');
                if (img && !avatarUrl) {
                    avatarUrl = img.src;
                }
                const text = getDisplayText(a);
                if (text && text !== '作者资料' && author === '匿名') {
                    author = text;
                    authorHref = a.href;
                }
            }
            if (!avatarUrl) avatarUrl = 'https://www.redditstatic.com/avatars/defaults/v2/avatar_default_1.png';

            // 提取主内容
            const contentElement = table.querySelector('.tpc_content');
            let content = contentElement ? contentElement.innerHTML : '';

            // 提取附件和签名 (位于 tpc_content 之后,tipad 之前)
            if (contentElement) {
                let next = contentElement.nextElementSibling;
                while (next && !next.classList.contains('tipad') && !next.classList.contains('tiptop')) {
                    // 排除一些已知的干扰元素,但保留购买框 (jumbotron)
                    if (!next.classList.contains('gray') && (!next.tagName.startsWith('H') || next.classList.contains('jumbotron'))) {
                        content += next.outerHTML;
                    }
                    next = next.nextElementSibling;
                }
            }

            // 如果是楼主,提取投票表单
            if (floorNum === 0) {
                content += getVoteHtml(doc);
            }

            let time = '未知时间';
            const timeSpan = tiptop.querySelector('.gray[title]');
            if (timeSpan) {
                time = getNodeText(timeSpan);
            } else {
                const timeMatch = floorText.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}/);
                if (timeMatch) time = timeMatch[0];
            }

            const parentFloor = extractParentFloor(table);

            const tipad = table.querySelector('.tipad');
            const replyBtn = tipad ? tipad.querySelector('.huifu a') : null;
            const replyOnclick = replyBtn ? replyBtn.getAttribute('onclick') : '';

            const quoteBtn = tipad ? tipad.querySelector('.yinyong a') : null;
            const quoteHref = quoteBtn ? quoteBtn.getAttribute('href') : '#';
            const quoteOnclick = quoteBtn ? quoteBtn.getAttribute('onclick') : '';
            const editLink = findPostActionLink(table, postId, /编辑/, /action[-=](?:modify|edit)/i);

            allPosts.set(floorNum, {
                id: floorNum,
                postId,
                author,
                authorHref,
                avatarUrl,
                content,
                time,
                replyOnclick,
                quoteHref,
                quoteOnclick,
                editHref: editLink ? editLink.href : '',
                editOnclick: editLink ? editLink.onclick : '',
                parentFloor,
                children: []
            });
        });
    }

    // --- 树结构构建 ---
    function buildTree() {
        rootPosts = [];
        allPosts.forEach(post => post.children = []);

        const sortedFloors = Array.from(allPosts.keys()).sort((a, b) => a - b);
        sortedFloors.forEach(floorNum => {
            const post = allPosts.get(floorNum);
            if (post.parentFloor !== null && allPosts.has(post.parentFloor) && post.parentFloor < floorNum) {
                allPosts.get(post.parentFloor).children.push(post);
            } else {
                rootPosts.push(post);
            }
        });
    }

    // --- 回复提交逻辑 ---
    function getTopicId() {
        const formTid = document.querySelector('form[name="FORM"] input[name="tid"]')?.value;
        if (formTid) return formTid;

        const urlParams = new URLSearchParams(window.location.search);
        return urlParams.get('tid') || window.location.href.match(/tid-(\d+)/)?.[1] || '';
    }

    function buildReplyFormData(replyText) {
        const form = document.querySelector('form[name="FORM"]');
        const tid = getTopicId();
        if (!tid) throw new Error('无法识别的主题 ID (tid)');

        const formData = new URLSearchParams();
        if (form) {
            Array.from(form.elements).forEach(element => {
                if (!element.name || element.disabled) return;
                if (['file', 'submit', 'button', 'image', 'reset'].includes(element.type)) return;
                if (['checkbox', 'radio'].includes(element.type) && !element.checked) return;
                formData.append(element.name, element.value);
            });
        }

        formData.set('step', '2');
        formData.set('action', 'reply');
        formData.set('tid', tid);
        formData.set('atc_content', replyText);
        if (!formData.get('atc_title')) {
            formData.set('atc_title', document.querySelector('input[name="atc_title"]')?.value || 'Re:');
        }
        if (!formData.get('atc_autourl')) formData.set('atc_autourl', '1');
        formData.set('Submit', ' 提 交 ');

        return formData;
    }

    function getReplySubmitUrl() {
        const form = document.querySelector('form[name="FORM"]');
        return new URL(form?.getAttribute('action') || 'post.php?', window.location.href).href;
    }

    function getPostErrorMessage(responseText) {
        const text = (responseText || '').replace(/\s+/g, ' ');
        const titleError = text.match(/<title>\s*([^<]*(?:错误|失败)[^<]*)<\/title>/i);
        if (titleError) return titleError[1].trim();

        const alertError = text.match(/alert\(['"]([^'"]*(?:错误|失败|登录|权限|验证码|内容|标题|灌水)[^'"]*)['"]\)/i);
        if (alertError) return alertError[1].trim();

        const keywordError = text.match(/(请先登录|您还没有登录|权限不足|非法请求|验证码(?:错误|不正确)?|内容少于|标题不能为空|发帖间隔|灌水预防)/);
        return keywordError ? keywordError[1] : '';
    }

    function isReplySuccess(res) {
        const responseText = res.responseText || '';
        const responseUrl = res.finalUrl || res.responseURL || '';
        if (/read\.php.*(?:tid[=-])\d+/i.test(responseUrl)) return true;
        return /(?:发表成功|回复成功|发帖成功|操作完成|如果您的浏览器没有自动跳转)/.test(responseText);
    }

    function getFloorLabel(floorId) {
        return floorId === 0 ? '楼主' : `${floorId}楼`;
    }

    async function submitReply(post, text) {
        // 如果有post信息则构造引用,否则直接回复
        const replyText = post ? `[quote]回 ${getFloorLabel(post.id)}(${post.author}) 的帖子[/quote]\n${text}` : text;

        const formData = buildReplyFormData(replyText);

        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'POST',
                url: getReplySubmitUrl(),
                data: formData.toString(),
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
                    'Referer': window.location.href
                },
                onload: (res) => {
                    if (res.status < 200 || res.status >= 400) {
                        reject(new Error(`服务器返回 HTTP ${res.status}`));
                        return;
                    }

                    const errorMessage = getPostErrorMessage(res.responseText);
                    if (errorMessage) {
                        reject(new Error(errorMessage));
                        return;
                    }

                    if (isReplySuccess(res)) {
                        resolve();
                    } else {
                        reject(new Error('服务器未返回明确的回复成功信息,请切回原始排版确认后再重试'));
                    }
                },
                onerror: () => reject(new Error('网络请求失败'))
            });
        });
    }

    // --- 渲染逻辑 ---
    function renderReddit() {
        if (isRedditMode) {
            hideSmilieBox();
        } else {
            restoreOriginalSmilieBox();
        }
        redditContainer.innerHTML = '';

        if (rootPosts.length === 0) {
            redditContainer.innerHTML = '<div style="text-align:center;padding:20px;">未发现可解析的内容。</div>';
            return;
        }

        // 渲染发帖标题与原始发帖/回帖入口
        const titleElement = document.querySelector('#subject_tpc');
        const titleText = titleElement ? (titleElement.innerText || titleElement.textContent || '').trim() : '';
        const topicLinks = getOriginalTopicActionLinks();
        if (titleText || topicLinks.replyHref || topicLinks.newTopicHref) {
            const titleRow = document.createElement('div');
            titleRow.className = 'reddit-title-row';

            const titleContainer = document.createElement('div');
            titleContainer.className = 'reddit-title-text';
            titleContainer.innerText = titleText;
            titleRow.appendChild(titleContainer);

            const actions = document.createElement('div');
            actions.className = 'reddit-topic-actions';
            if (topicLinks.replyHref) {
                const replyLink = document.createElement('a');
                replyLink.className = 'reddit-topic-btn';
                replyLink.href = topicLinks.replyHref;
                replyLink.innerText = '回帖';
                actions.appendChild(replyLink);
            }
            if (topicLinks.newTopicHref) {
                const newTopicLink = document.createElement('a');
                newTopicLink.className = 'reddit-topic-btn';
                newTopicLink.href = topicLinks.newTopicHref;
                newTopicLink.innerText = '发新帖';
                actions.appendChild(newTopicLink);
            }
            titleRow.appendChild(actions);
            redditContainer.appendChild(titleRow);
        }

        rootPosts.forEach(post => {
            redditContainer.appendChild(createPostElement(post, 0));
        });

        // 渲染底部回复框
        const replyBox = document.createElement('div');
        replyBox.className = 'reddit-reply-box';
        replyBox.style.marginTop = '40px';
        replyBox.innerHTML = `
            <textarea class="reddit-reply-textarea" placeholder="写下你的回复..."></textarea>
            <div class="reddit-emoji-container"></div>
            <div class="reddit-reply-footer">
                <div class="reddit-reply-footer-actions">
                    <button class="reddit-emoji-btn" title="选择表情">
                        <svg style="width:20px;height:20px" viewBox="0 0 20 20" fill="currentColor"><path d="M10 2a8 8 0 100 16 8 8 0 000-16zM6.5 9a1.5 1.5 0 110-3 1.5 1.5 0 010 3zm7 0a1.5 1.5 0 110-3 1.5 1.5 0 010 3zm-7 4c1.11 0 2.06.67 2.48 1.61.1.2.33.39.52.39s.42-.19.52-.39A2.75 2.75 0 0113.5 13h-7z"></path></svg>
                    </button>
                </div>
                <div class="reddit-reply-footer-submit">
                    <button class="reddit-reply-btn reddit-reply-submit">回复</button>
                </div>
            </div>
        `;

        const textarea = replyBox.querySelector('textarea');
        const submitBtn = replyBox.querySelector('.reddit-reply-submit');
        bindReplyBox(replyBox, textarea);

        submitBtn.onclick = async () => {
            const text = textarea.value.trim();
            if (!text) return;

            submitBtn.disabled = true;
            submitBtn.innerText = '正在提交...';
            try {
                await submitReply(null, text); // 传 null 代表不引用任何特定楼层
                alert('回复成功!页面将刷新以显示新内容。');
                window.location.reload();
            } catch (e) {
                alert('回复失败: ' + e.message);
                submitBtn.disabled = false;
                submitBtn.innerText = '回复';
            }
        };
        redditContainer.appendChild(replyBox);

        initVoteForms();
    }

    function createPostElement(post, depth) {
        const wrapper = document.createElement('div');
        wrapper.className = 'reddit-post-wrapper';

        wrapper.innerHTML = `
            <div class="reddit-comment">
                <div class="reddit-comment-left">
                    <div class="reddit-avatar-wrapper">
                        <img src="${post.avatarUrl}" class="reddit-avatar" />
                    </div>
                    ${post.children.length > 0 ? `
                        <div class="reddit-threadline" title="折叠线程"></div>
                        <button class="reddit-collapse-btn" title="折叠评论">
                            <svg viewBox="0 0 20 20" fill="currentColor"><path d="M15 9H5v2h10V9z"></path></svg>
                        </button>
                    ` : ''}
                </div>
                <div class="reddit-comment-right">
                    <div class="reddit-comment-header">
                        <a href="${post.authorHref}" class="reddit-author" target="_blank">${post.author}</a>
                        <span class="reddit-time">• ${post.time}</span>
                        ${post.children.length > 0 ? `
                            <svg class="reddit-collapse-inline" viewBox="0 0 20 20" fill="currentColor"><path d="M15 9H5v2h10V9z"></path></svg>
                            <svg class="reddit-expand-inline" viewBox="0 0 20 20" fill="currentColor"><path d="M11 9h4v2h-4v4H9v-4H5V9h4V5h2v4z"></path></svg>
                        ` : ''}
                    </div>
                    <div class="reddit-comment-body">${post.content}</div>
                    <div class="reddit-comment-actions">
                        <button class="reddit-action-btn reddit-reply-toggle-btn">
                            <svg class="reddit-action-icon" viewBox="0 0 20 20"><path d="M15.429 16.353A8.44 8.44 0 0 1 9.07 18.5a8.775 8.775 0 0 1-4.704-1.36 1.488 1.488 0 0 1-.582-1.688l.685-2.228-1.579-1.365A6.99 6.99 0 0 1 1.5 6.5C1.5 2.915 5.314.5 10 .5s8.5 2.415 8.5 6c0 3.824-3.528 7.306-3.071 9.853Zm-1.89-1.257c-.198-1.128.535-4.227.535-4.227 1.636-1.536 3.176-3.14 3.176-4.869 0-2.824-3.142-4.75-7.25-4.75-4.108 0-7.25 1.926-7.25 4.75 0 2.224 1.691 3.99 4.316 4.542l.216.045-.065.212-.862 2.802a7.513 7.513 0 0 0 3.655-.918l.21-.112.213.109a7.195 7.195 0 0 0 5.106 2.416Z"></path></svg>
                            回复
                        </button>
                        ${post.editHref ? `
                            <a class="reddit-action-btn reddit-edit-btn" href="${post.editHref}">
                                <svg class="reddit-action-icon" viewBox="0 0 20 20"><path d="M15.9 2.7a2.1 2.1 0 0 0-3 0L4 11.6 3 17l5.4-1 8.9-8.9a2.1 2.1 0 0 0 0-3l-1.4-1.4ZM7.7 14.6l-2.8.5.5-2.8 6.6-6.6 2.3 2.3-6.6 6.6Zm7.7-7.7-2.3-2.3 1-1a.6.6 0 0 1 .8 0L16.3 5a.6.6 0 0 1 0 .8l-.9 1.1Z"></path></svg>
                                编辑
                            </a>
                        ` : ''}
                    </div>
                    <div class="reddit-reply-container"></div>
                </div>
            </div>
        `;

        const toggleCollapse = () => {
            wrapper.classList.toggle('collapsed');
        };
        const threadline = wrapper.querySelector('.reddit-threadline');
        if (threadline) threadline.onclick = toggleCollapse;

        const collapseBtn = wrapper.querySelector('.reddit-collapse-btn');
        if (collapseBtn) collapseBtn.onclick = toggleCollapse;

        const inlineCollapse = wrapper.querySelector('.reddit-collapse-inline');
        if (inlineCollapse) inlineCollapse.onclick = toggleCollapse;

        const inlineExpand = wrapper.querySelector('.reddit-expand-inline');
        if (inlineExpand) inlineExpand.onclick = toggleCollapse;

        // 回复功能逻辑
        const replyBtn = wrapper.querySelector('.reddit-reply-toggle-btn');
        const replyContainer = wrapper.querySelector('.reddit-reply-container');

        replyBtn.onclick = () => {
            if (replyContainer.innerHTML === '') {
                const replyBox = document.createElement('div');
                replyBox.className = 'reddit-reply-box';
                replyBox.innerHTML = `
                    <textarea class="reddit-reply-textarea" placeholder="写下你的回复..."></textarea>
                    <div class="reddit-emoji-container"></div>
                    <div class="reddit-reply-footer">
                        <div class="reddit-reply-footer-actions">
                            <button class="reddit-emoji-btn" title="选择表情">
                                <svg style="width:20px;height:20px" viewBox="0 0 20 20" fill="currentColor"><path d="M10 2a8 8 0 100 16 8 8 0 000-16zM6.5 9a1.5 1.5 0 110-3 1.5 1.5 0 010 3zm7 0a1.5 1.5 0 110-3 1.5 1.5 0 010 3zm-7 4c1.11 0 2.06.67 2.48 1.61.1.2.33.39.52.39s.42-.19.52-.39A2.75 2.75 0 0113.5 13h-7z"></path></svg>
                            </button>
                        </div>
                        <div class="reddit-reply-footer-submit">
                            <button class="reddit-reply-btn reddit-reply-cancel">取消</button>
                            <button class="reddit-reply-btn reddit-reply-submit">回复</button>
                        </div>
                    </div>
                `;

                const textarea = replyBox.querySelector('textarea');
                const submitBtn = replyBox.querySelector('.reddit-reply-submit');
                const cancelBtn = replyBox.querySelector('.reddit-reply-cancel');
                bindReplyBox(replyBox, textarea);

                // 自动聚焦
                setTimeout(() => textarea.focus(), 10);

                cancelBtn.onclick = () => {
                    hideSmilieBox();
                    replyContainer.innerHTML = '';
                };

                submitBtn.onclick = async () => {
                    const text = textarea.value.trim();
                    if (!text) return;

                    submitBtn.disabled = true;
                    submitBtn.innerText = '正在提交...';

                    try {
                        await submitReply(post, text);
                        alert('回复成功!页面将刷新以显示新内容。');
                        window.location.reload();
                    } catch (e) {
                        alert('回复失败: ' + e.message);
                        submitBtn.disabled = false;
                        submitBtn.innerText = '回复';
                    }
                };

                replyContainer.appendChild(replyBox);
            } else {
                if (replyContainer.querySelector('#smiliebox')) hideSmilieBox();
                replyContainer.innerHTML = '';
            }
        };

        if (post.children.length > 0 && depth < CONFIG.NESTING_LIMIT) {
            const childrenDiv = document.createElement('div');
            childrenDiv.className = 'reddit-children';
            post.children.forEach(child => {
                childrenDiv.appendChild(createPostElement(child, depth + 1));
            });
            wrapper.appendChild(childrenDiv);
        }

        return wrapper;
    }

    // 启动
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

})();