NodeSeek & DeepFlood 增强插件

标记已读帖子、显示用户详细信息(等级、主题量、鸡腿数、评论量)、自动签到 - 支持 NodeSeek 和 DeepFlood

当前为 2025-11-19 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         NodeSeek & DeepFlood 增强插件
// @namespace    http://tampermonkey.net/
// @version      1.5.5
// @description  标记已读帖子、显示用户详细信息(等级、主题量、鸡腿数、评论量)、自动签到 - 支持 NodeSeek 和 DeepFlood
// @author       da niao
// @match        https://www.nodeseek.com/*
// @match        https://nodeseek.com/*
// @match        https://www.deepflood.com/*
// @match        https://deepflood.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // ==================== 站点识别 ====================
    const CURRENT_SITE = (() => {
        const host = location.hostname;
        if (host.includes('nodeseek.com')) {
            return {
                code: 'ns',
                name: 'NodeSeek',
                host: host
            };
        } else if (host.includes('deepflood.com')) {
            return {
                code: 'df',
                name: 'DeepFlood',
                host: host
            };
        }
        return null;
    })();

    // ==================== 配置管理 ====================
    class ConfigManager {
        static defaults = {
            visitedColor: '#ff6b6b',
            enableVisitedMark: true,
            enableUserInfo: true,
            cacheExpireTime: 24, // 小时
            badgeBackground: 'transparent', // 透明背景
            badgeTextColor: '#000000', // 默认黑色字体
            requestDelay: 300,
            autoSignIn: true,
            signInMode_ns: 'random', // NodeSeek 签到模式
            signInMode_df: 'random'  // DeepFlood 签到模式
        };

        static get(key) {
            const config = GM_getValue('nse_config', this.defaults);
            return config[key] !== undefined ? config[key] : this.defaults[key];
        }

        static set(key, value) {
            const config = GM_getValue('nse_config', this.defaults);
            config[key] = value;
            GM_setValue('nse_config', config);
        }

        static getAll() {
            return GM_getValue('nse_config', this.defaults);
        }
    }

    // 使用配置
    const CONFIG = {
        get visitedColor() { return ConfigManager.get('visitedColor'); },
        get enableVisitedMark() { return ConfigManager.get('enableVisitedMark'); },
        get enableUserInfo() { return ConfigManager.get('enableUserInfo'); },
        get cacheExpireTime() { return ConfigManager.get('cacheExpireTime') * 3600000; }, // 转换为毫秒
        get badgeBackground() { return ConfigManager.get('badgeBackground'); },
        get badgeTextColor() { return ConfigManager.get('badgeTextColor'); },
        get requestDelay() { return ConfigManager.get('requestDelay'); },
        get autoSignIn() { return ConfigManager.get('autoSignIn'); },
        get signInMode() { 
            const siteCode = CURRENT_SITE ? CURRENT_SITE.code : 'ns';
            return ConfigManager.get(`signInMode_${siteCode}`); 
        }
    };

    // ==================== 请求队列管理 ====================
    class RequestQueue {
        constructor(delay = 300) {
            this.queue = [];
            this.processing = false;
            this.delay = delay;
            this.pendingRequests = new Map(); // 防止重复请求
        }

        async add(userId, fetchFn) {
            // 如果已经有相同的请求在处理,返回该 Promise
            if (this.pendingRequests.has(userId)) {
                return this.pendingRequests.get(userId);
            }

            const promise = new Promise((resolve) => {
                this.queue.push({ userId, fetchFn, resolve });
            });

            this.pendingRequests.set(userId, promise);
            
            if (!this.processing) {
                this.process();
            }

            return promise;
        }

        async process() {
            if (this.queue.length === 0) {
                this.processing = false;
                return;
            }

            this.processing = true;
            const { userId, fetchFn, resolve } = this.queue.shift();

            try {
                const result = await fetchFn();
                resolve(result);
            } catch (error) {
                console.error(`请求用户 ${userId} 信息失败:`, error);
                resolve(null);
            } finally {
                this.pendingRequests.delete(userId);
                // 延迟后处理下一个请求
                await new Promise(r => setTimeout(r, this.delay));
                this.process();
            }
        }
    }

    const requestQueue = new RequestQueue(CONFIG.requestDelay);

    // ==================== 菜单管理 ====================
    class MenuManager {
        static registerMenus() {
            const siteCode = CURRENT_SITE ? CURRENT_SITE.code : 'ns';
            const siteName = CURRENT_SITE ? CURRENT_SITE.name : 'NodeSeek';
            
            // 签到设置(区分站点)
            const signInMode = ConfigManager.get(`signInMode_${siteCode}`);
            const signInText = {
                'random': '🎲 随机鸡腿',
                'fixed': '📌 固定5个',
                'disabled': '❌ 已关闭'
            }[signInMode];
            
            GM_registerMenuCommand(`[${siteName}] 签到: ${signInText}`, () => {
                const modes = ['random', 'fixed', 'disabled'];
                const current = ConfigManager.get(`signInMode_${siteCode}`);
                const currentIndex = modes.indexOf(current);
                const nextMode = modes[(currentIndex + 1) % modes.length];
                ConfigManager.set(`signInMode_${siteCode}`, nextMode);
                alert(`${siteName} 签到模式已切换为: ${{'random':'随机鸡腿','fixed':'固定5个','disabled':'关闭'}[nextMode]}`);
                location.reload();
            });

            // 已读颜色设置
            GM_registerMenuCommand('🎨 设置已读颜色', () => {
                const current = ConfigManager.get('visitedColor');
                const color = prompt('请输入已读帖子颜色(CSS颜色值):', current);
                if (color && color !== current) {
                    ConfigManager.set('visitedColor', color);
                    alert('已读颜色已更新,刷新页面生效');
                    location.reload();
                }
            });

            // 徽章样式切换
            const badgeStyles = {
                'transparent': { name: '透明背景', bg: 'transparent', color: '#000000' },
                'purple': { name: '紫色渐变', bg: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', color: '#ffffff' },
                'blue': { name: '蓝色渐变', bg: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)', color: '#ffffff' },
                'green': { name: '绿色渐变', bg: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)', color: '#ffffff' },
                'orange': { name: '橙色渐变', bg: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)', color: '#ffffff' },
                'pink': { name: '粉色渐变', bg: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)', color: '#ffffff' },
                'dark': { name: '深色背景', bg: '#2c3e50', color: '#ecf0f1' },
                'light': { name: '浅色背景', bg: '#ecf0f1', color: '#2c3e50' }
            };
            
            // 获取当前样式
            const currentBg = ConfigManager.get('badgeBackground');
            let currentStyleName = '自定义';
            for (const [key, style] of Object.entries(badgeStyles)) {
                if (style.bg === currentBg) {
                    currentStyleName = style.name;
                    break;
                }
            }
            
            GM_registerMenuCommand(`🎨 徽章样式: ${currentStyleName}`, () => {
                const styleKeys = Object.keys(badgeStyles);
                const options = styleKeys.map((key, index) => 
                    `${index + 1}. ${badgeStyles[key].name}`
                ).join('\n');
                
                const choice = prompt(
                    `请选择徽章样式(输入数字):\n\n${options}\n\n当前: ${currentStyleName}`,
                    '1'
                );
                
                if (choice) {
                    const index = parseInt(choice) - 1;
                    if (index >= 0 && index < styleKeys.length) {
                        const selectedKey = styleKeys[index];
                        const selectedStyle = badgeStyles[selectedKey];
                        ConfigManager.set('badgeBackground', selectedStyle.bg);
                        ConfigManager.set('badgeTextColor', selectedStyle.color);
                        alert(`徽章样式已切换为: ${selectedStyle.name}`);
                        location.reload();
                    } else {
                        alert('无效的选择');
                    }
                }
            });

            // 字体颜色切换
            const textColors = {
                'black': { name: '黑色', color: '#000000' },
                'gray': { name: '深灰', color: '#333333' },
                'blue': { name: '蓝色', color: '#1e90ff' },
                'purple': { name: '紫色', color: '#9b59b6' },
                'green': { name: '绿色', color: '#27ae60' },
                'orange': { name: '橙色', color: '#e67e22' },
                'red': { name: '红色', color: '#e74c3c' }
            };
            
            // 获取当前字体颜色名称
            const currentTextColor = ConfigManager.get('badgeTextColor');
            let currentColorName = '自定义';
            for (const [key, colorObj] of Object.entries(textColors)) {
                if (colorObj.color === currentTextColor) {
                    currentColorName = colorObj.name;
                    break;
                }
            }
            
            GM_registerMenuCommand(`🖍️ 字体颜色: ${currentColorName}`, () => {
                const colorKeys = Object.keys(textColors);
                const options = colorKeys.map((key, index) => 
                    `${index + 1}. ${textColors[key].name}`
                ).join('\n');
                
                const choice = prompt(
                    `请选择字体颜色(输入数字):\n\n${options}\n\n当前: ${currentColorName}`,
                    '1'
                );
                
                if (choice) {
                    const index = parseInt(choice) - 1;
                    if (index >= 0 && index < colorKeys.length) {
                        const selectedKey = colorKeys[index];
                        const selectedColor = textColors[selectedKey];
                        ConfigManager.set('badgeTextColor', selectedColor.color);
                        alert(`字体颜色已切换为: ${selectedColor.name}`);
                        location.reload();
                    } else {
                        alert('无效的选择');
                    }
                }
            });

            // 缓存时间设置
            const cacheHours = ConfigManager.get('cacheExpireTime');
            GM_registerMenuCommand(`⏰ 缓存时间: ${cacheHours}小时`, () => {
                const hours = prompt('请输入用户信息缓存时间(小时):', cacheHours);
                if (hours && !isNaN(hours) && hours > 0) {
                    ConfigManager.set('cacheExpireTime', parseInt(hours));
                    alert(`缓存时间已设置为 ${hours} 小时`);
                }
            });

            // 切换已读标记
            const visitedEnabled = ConfigManager.get('enableVisitedMark');
            GM_registerMenuCommand(`${visitedEnabled ? '✅' : '❌'} 已读标记`, () => {
                ConfigManager.set('enableVisitedMark', !visitedEnabled);
                alert(`已读标记已${!visitedEnabled ? '开启' : '关闭'}`);
                location.reload();
            });

            // 切换用户信息显示
            const userInfoEnabled = ConfigManager.get('enableUserInfo');
            GM_registerMenuCommand(`${userInfoEnabled ? '✅' : '❌'} 用户信息显示`, () => {
                ConfigManager.set('enableUserInfo', !userInfoEnabled);
                alert(`用户信息显示已${!userInfoEnabled ? '开启' : '关闭'}`);
                location.reload();
            });

            // 清除缓存
            GM_registerMenuCommand('🗑️ 清除用户信息缓存', () => {
                if (confirm('确定要清除所有用户信息缓存吗?')) {
                    GM_setValue('userInfoCache', {});
                    alert('缓存已清除');
                }
            });

            // 重置所有设置
            GM_registerMenuCommand('🔄 重置所有设置', () => {
                if (confirm('确定要重置所有设置为默认值吗?')) {
                    GM_setValue('nse_config', ConfigManager.defaults);
                    GM_setValue('userInfoCache', {});
                    GM_setValue('visitedPosts', {});
                    alert('所有设置已重置,页面即将刷新');
                    location.reload();
                }
            });
        }
    }

    // ==================== 签到管理 ====================
    class SignInManager {
        // 获取当前日期(北京时间)
        static getCurrentDate() {
            const localTimezoneOffset = (new Date()).getTimezoneOffset();
            const beijingOffset = 8 * 60;
            const beijingTime = new Date(Date.now() + (localTimezoneOffset + beijingOffset) * 60 * 1000);
            return `${beijingTime.getFullYear()}/${(beijingTime.getMonth() + 1)}/${beijingTime.getDate()}`;
        }

        // 获取上次签到日期(区分站点)
        static getLastSignInDate() {
            const siteCode = CURRENT_SITE ? CURRENT_SITE.code : 'ns';
            return GM_getValue(`lastSignInDate_${siteCode}`, '');
        }

        // 设置签到日期(区分站点)
        static setLastSignInDate(date) {
            const siteCode = CURRENT_SITE ? CURRENT_SITE.code : 'ns';
            GM_setValue(`lastSignInDate_${siteCode}`, date);
        }

        // 检查今天是否已签到
        static hasSignedInToday() {
            const today = this.getCurrentDate();
            const lastDate = this.getLastSignInDate();
            return today === lastDate;
        }

        // 执行签到
        static async signIn() {
            if (!CURRENT_SITE) {
                console.log('[增强插件] 未识别的站点');
                return;
            }
            
            const signInMode = CONFIG.signInMode;
            const siteName = CURRENT_SITE.name;
            
            if (signInMode === 'disabled') {
                console.log(`[${siteName}增强] 自动签到已禁用`);
                return;
            }

            if (this.hasSignedInToday()) {
                console.log(`[${siteName}增强] 今天已经签到过了`);
                return;
            }

            const isRandom = signInMode === 'random';
            
            try {
                const response = await fetch(`/api/attendance?random=${isRandom}`, {
                    method: 'POST',
                    credentials: 'include',
                    headers: {
                        'Content-Type': 'application/json'
                    }
                });

                const data = await response.json();
                
                if (data.success) {
                    const today = this.getCurrentDate();
                    this.setLastSignInDate(today);
                    const siteName = CURRENT_SITE ? CURRENT_SITE.name : 'NodeSeek';
                    console.log(`[${siteName}增强] 签到成功!获得 ${data.gain} 个鸡腿,当前共有 ${data.current} 个鸡腿`);
                    
                    // 可选:显示通知
                    this.showNotification(`${siteName} 签到成功!获得 ${data.gain} 个🍗`);
                } else {
                    const siteName = CURRENT_SITE ? CURRENT_SITE.name : 'NodeSeek';
                    console.warn(`[${siteName}增强] 签到失败:`, data.message);
                }
            } catch (error) {
                const siteName = CURRENT_SITE ? CURRENT_SITE.name : 'NodeSeek';
                console.error(`[${siteName}增强] 签到请求失败:`, error);
            }
        }

        // 显示通知(简单的页面提示)
        static showNotification(message) {
            const notification = document.createElement('div');
            notification.style.cssText = `
                position: fixed;
                top: 20px;
                right: 20px;
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white;
                padding: 15px 20px;
                border-radius: 8px;
                box-shadow: 0 4px 12px rgba(0,0,0,0.15);
                z-index: 10000;
                font-size: 14px;
                animation: slideIn 0.3s ease-out;
            `;
            notification.textContent = message;
            
            // 添加动画样式
            const style = document.createElement('style');
            style.textContent = `
                @keyframes slideIn {
                    from {
                        transform: translateX(400px);
                        opacity: 0;
                    }
                    to {
                        transform: translateX(0);
                        opacity: 1;
                    }
                }
            `;
            document.head.appendChild(style);
            
            document.body.appendChild(notification);
            
            // 3秒后自动消失
            setTimeout(() => {
                notification.style.animation = 'slideIn 0.3s ease-out reverse';
                setTimeout(() => notification.remove(), 300);
            }, 3000);
        }
    }

    // ==================== 存储管理 ====================
    class Storage {
        static getVisitedPosts() {
            return GM_getValue('visitedPosts', {});
        }

        static markPostAsVisited(postId) {
            const visited = this.getVisitedPosts();
            visited[postId] = Date.now();
            GM_setValue('visitedPosts', visited);
        }

        static isPostVisited(postId) {
            const visited = this.getVisitedPosts();
            return !!visited[postId];
        }

        static getUserInfoCache(userId) {
            const cache = GM_getValue('userInfoCache', {});
            const userCache = cache[userId];
            if (userCache && (Date.now() - userCache.timestamp < CONFIG.cacheExpireTime)) {
                return userCache.data;
            }
            return null;
        }

        static setUserInfoCache(userId, data) {
            const cache = GM_getValue('userInfoCache', {});
            cache[userId] = {
                data: data,
                timestamp: Date.now()
            };
            GM_setValue('userInfoCache', cache);
        }
    }

    // ==================== 用户信息获取 ====================
    class UserInfoFetcher {
        // 从用户主页获取信息(使用请求队列)
        static async fetchUserInfo(userId) {
            // 先检查缓存
            const cached = Storage.getUserInfoCache(userId);
            if (cached) {
                return cached;
            }

            // 使用请求队列,避免并发请求过多
            return requestQueue.add(userId, async () => {
                try {
                    // 再次检查缓存(可能在队列等待期间已被其他请求缓存)
                    const cached = Storage.getUserInfoCache(userId);
                    if (cached) {
                        return cached;
                    }

                    // 方法1: 从 API 获取
                    const apiData = await this.fetchFromAPI(userId);
                    if (apiData) {
                        Storage.setUserInfoCache(userId, apiData);
                        return apiData;
                    }

                    // 如果 API 返回 429,不再尝试备用方案,直接返回 null
                    return null;
                } catch (error) {
                    console.error('获取用户信息失败:', error);
                    return null;
                }
            });
        }

        // 从 API 获取(使用浏览器 fetch,自动带 cookies)
        static async fetchFromAPI(userId) {
            try {
                // 根据当前站点构建 API URL
                const apiUrl = CURRENT_SITE 
                    ? `https://${CURRENT_SITE.host}/api/account/getInfo/${userId}`
                    : `https://www.nodeseek.com/api/account/getInfo/${userId}`;
                
                const response = await fetch(apiUrl, {
                    method: 'GET',
                    credentials: 'include', // 自动包含 cookies
                    headers: {
                        'Accept': 'application/json',
                        'X-Requested-With': 'XMLHttpRequest'
                    }
                });

                if (!response.ok) {
                    if (response.status === 429) {
                        console.warn(`API 请求过于频繁 (429),跳过用户 ${userId}`);
                    }
                    return null;
                }

                const data = await response.json();
                // API 返回的是 detail 而不是 data
                if (data.success && data.detail) {
                    const user = data.detail;
                    
                    // 计算加入天数
                    let joinDays = 0;
                    if (user.created_at) {
                        const joinDate = new Date(user.created_at);
                        const now = new Date();
                        joinDays = Math.floor((now - joinDate) / (1000 * 60 * 60 * 24));
                    }
                    
                    return {
                        level: user.rank || 0,
                        topicCount: user.nPost || 0,
                        drumstickCount: user.coin || 0,
                        commentCount: user.nComment || 0,
                        joinDays: joinDays
                    };
                } else {
                    return null;
                }
            } catch (error) {
                console.error('API 请求失败:', error);
                return null;
            }
        }


    }

    // ==================== 收藏夹搜索 ====================
    class CollectionSearch {
        // 从HTML中提取当前用户ID
        static getCurrentUserId() {
            const userStyleLink = document.querySelector('link[href*="/userstyle/"]');
            if (userStyleLink) {
                const match = userStyleLink.href.match(/\/userstyle\/(\d+)\.css/);
                if (match) return match[1];
            }
            return null;
        }

        // 获取所有收藏的帖子(分页获取)
        static async fetchAllCollections() {
            const collections = [];
            let page = 1;

            while (true) {
                try {
                    const apiUrl = CURRENT_SITE 
                        ? `https://${CURRENT_SITE.host}/api/statistics/list-collection?page=${page}`
                        : `https://www.nodeseek.com/api/statistics/list-collection?page=${page}`;
                    
                    const response = await fetch(apiUrl, {
                        method: 'GET',
                        credentials: 'include',
                        headers: {
                            'Accept': 'application/json',
                            'X-Requested-With': 'XMLHttpRequest'
                        }
                    });

                    if (!response.ok) break;

                    const data = await response.json();
                    
                    // API 返回的是 collections 字段
                    if (data.success && data.collections && data.collections.length > 0) {
                        collections.push(...data.collections);
                        page++;
                    } else {
                        // 没有数据了,停止搜索
                        break;
                    }
                } catch (error) {
                    console.error(`获取收藏夹第${page}页失败:`, error);
                    break;
                }
            }

            return collections;
        }

        // 搜索收藏夹
        static async searchCollections(keyword) {
            if (!keyword || keyword.trim() === '') {
                return [];
            }

            const collections = await this.fetchAllCollections();
            const lowerKeyword = keyword.toLowerCase();

            return collections.filter(item => {
                const title = (item.title || '').toLowerCase();
                return title.includes(lowerKeyword);
            });
        }

        // 创建搜索框UI
        static createSearchBox() {
            // 查找"收藏"元素(包含"收藏 X"文本的span)
            const collectionSpan = Array.from(document.querySelectorAll('span[data-v-244123cf]')).find(
                span => span.textContent.includes('收藏')
            );
            
            if (!collectionSpan) {
                console.log('[收藏夹搜索] 未找到收藏元素,可能不在收藏页面');
                return;
            }

            // 找到 .user-stat 容器(黄色统计框)
            const userStat = collectionSpan.closest('.user-stat');
            if (!userStat) {
                console.log('[收藏夹搜索] 未找到 user-stat 容器');
                return;
            }

            // 检查是否已经添加过
            if (document.querySelector('.collection-search-box')) {
                return;
            }

            // 创建容器
            const container = document.createElement('div');
            container.className = 'collection-search-box';
            container.style.cssText = `
                display: block;
                margin: 15px 0;
                padding: 0;
                position: relative;
                width: 100%;
            `;

            // 创建搜索输入框
            const input = document.createElement('input');
            input.type = 'text';
            input.placeholder = '搜索收藏...';
            input.style.cssText = `
                height: 36px;
                padding: 0 12px;
                border: 1px solid #ddd;
                border-radius: 4px;
                font-size: 14px;
                width: 100%;
                outline: none;
                box-sizing: border-box;
            `;

            // 创建结果容器
            const resultsBox = document.createElement('div');
            resultsBox.className = 'collection-search-results';
            resultsBox.style.cssText = `
                position: absolute;
                top: 100%;
                left: 0;
                right: 0;
                background: white;
                border: 1px solid #ddd;
                border-radius: 4px;
                margin-top: 5px;
                max-height: 400px;
                overflow-y: auto;
                display: none;
                z-index: 1000;
                box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            `;

            // 搜索处理
            let searchTimeout;
            input.addEventListener('input', (e) => {
                clearTimeout(searchTimeout);
                const keyword = e.target.value.trim();

                if (keyword === '') {
                    resultsBox.style.display = 'none';
                    return;
                }

                searchTimeout = setTimeout(async () => {
                    resultsBox.innerHTML = '<div style="padding: 10px; text-align: center; color: #999;">搜索中...</div>';
                    resultsBox.style.display = 'block';

                    const results = await this.searchCollections(keyword);

                    if (results.length === 0) {
                        resultsBox.innerHTML = '<div style="padding: 10px; text-align: center; color: #999;">未找到相关收藏</div>';
                    } else {
                        resultsBox.innerHTML = results.map(item => `
                            <a href="/post-${item.post_id}-1" style="
                                display: block;
                                padding: 10px;
                                border-bottom: 1px solid #f0f0f0;
                                color: #333;
                                text-decoration: none;
                                transition: background 0.2s;
                            " onmouseover="this.style.background='#f5f5f5'" onmouseout="this.style.background='white'">
                                <div style="
                                    font-size: 14px; 
                                    font-weight: 500;
                                    white-space: nowrap;
                                    overflow: hidden;
                                    text-overflow: ellipsis;
                                ">${item.title || '无标题'}</div>
                            </a>
                        `).join('');
                    }
                }, 300);
            });

            // 点击外部关闭结果框
            document.addEventListener('click', (e) => {
                if (!container.contains(e.target)) {
                    resultsBox.style.display = 'none';
                }
            });

            container.appendChild(input);
            container.appendChild(resultsBox);

            // 插入到 user-stat 容器后面(黄色框外面)
            userStat.insertAdjacentElement('afterend', container);
        }
    }

    // ==================== UI 增强 ====================
    class UIEnhancer {
        // 创建用户信息标签
        static createUserInfoBadge(userInfo) {
            if (!userInfo) return null;

            const badge = document.createElement('span');
            badge.className = 'nodeseek-user-info';
            badge.style.cssText = `
                display: inline-block;
                margin-left: 8px;
                padding: 2px 8px;
                background: ${CONFIG.badgeBackground};
                color: ${CONFIG.badgeTextColor};
                border-radius: 10px;
                font-size: 11px;
                white-space: nowrap;
                vertical-align: middle;
                line-height: 1.5;
            `;
            
            // 格式化加入天数显示
            let joinText = '';
            if (userInfo.joinDays !== undefined) {
                joinText = `<span title="加入 ${userInfo.joinDays} 天" style="margin-left: 4px;">📅${userInfo.joinDays}天</span>`;
            }
            
            badge.innerHTML = `
                <span title="等级">⭐${userInfo.level}</span>
                <span title="主题数" style="margin-left: 4px;">📝${userInfo.topicCount}</span>
                <span title="鸡腿数" style="margin-left: 4px;">🍗${userInfo.drumstickCount}</span>
                <span title="评论数" style="margin-left: 4px;">💬${userInfo.commentCount}</span>
                ${joinText}
            `;
            return badge;
        }

        // 标记已访问的帖子
        static markVisitedPost(postElement, postId) {
            if (Storage.isPostVisited(postId)) {
                const titleElement = postElement.querySelector('.post-title a');
                if (titleElement) {
                    titleElement.style.color = CONFIG.visitedColor;
                }
            }
        }

        // 为帖子列表添加用户信息(使用 IntersectionObserver 懒加载)
        static enhancePostList() {
            // 帖子列表项选择器
            const postItems = document.querySelectorAll('.post-list-item');
            
            // 创建 IntersectionObserver 用于懒加载
            const observer = new IntersectionObserver((entries) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        const postItem = entry.target;
                        // 只处理一次
                        observer.unobserve(postItem);
                        this.enhancePostItem(postItem);
                    }
                });
            }, {
                rootMargin: '100px' // 提前 100px 开始加载
            });
            
            // 观察所有帖子项
            postItems.forEach(postItem => {
                // 先标记已访问的帖子(不需要等待)
                const postId = this.extractPostId(postItem);
                if (postId && CONFIG.enableVisitedMark) {
                    this.markVisitedPost(postItem, postId);
                }
                
                // 使用 IntersectionObserver 懒加载用户信息
                if (CONFIG.enableUserInfo) {
                    observer.observe(postItem);
                }
            });
        }

        // 增强单个帖子项
        static async enhancePostItem(postItem) {
            // 检查是否已经添加过用户信息(防止重复)
            if (postItem.dataset.enhanced === 'true') {
                return;
            }
            
            // 标记为已处理
            postItem.dataset.enhanced = 'true';
            
            const userLink = postItem.querySelector('a[href^="/space/"]');
            if (userLink) {
                const userId = this.extractUserId(userLink);
                
                if (userId) {
                    const userInfo = await UserInfoFetcher.fetchUserInfo(userId);
                    const badge = this.createUserInfoBadge(userInfo);
                    if (badge) {
                        // 找到帖子标题的链接
                        const titleLink = postItem.querySelector('.post-title > a');
                        if (titleLink && !titleLink.nextElementSibling?.classList.contains('nodeseek-user-info')) {
                            titleLink.insertAdjacentElement('afterend', badge);
                        }
                    }
                }
            }
        }

        // 为帖子详情页添加用户信息(使用懒加载)
        static enhancePostDetail() {
            // 标记当前帖子为已访问
            const postId = this.extractPostIdFromURL();
            if (postId && CONFIG.enableVisitedMark) {
                Storage.markPostAsVisited(postId);
            }

            if (!CONFIG.enableUserInfo) return;

            // 获取所有评论项(包括主帖)
            const contentItems = document.querySelectorAll('.content-item');
            
            // 创建 IntersectionObserver 用于懒加载
            const observer = new IntersectionObserver((entries) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        const contentItem = entry.target;
                        // 只处理一次
                        observer.unobserve(contentItem);
                        this.enhanceContentItem(contentItem);
                    }
                });
            }, {
                rootMargin: '100px' // 提前 100px 开始加载
            });
            
            // 观察所有内容项
            contentItems.forEach(contentItem => {
                if (CONFIG.enableUserInfo) {
                    observer.observe(contentItem);
                }
            });
        }

        // 增强单个内容项(帖子详情页)
        static async enhanceContentItem(contentItem) {
            // 检查是否已经添加过用户信息
            if (contentItem.dataset.enhanced === 'true') {
                return;
            }
            
            // 标记为已处理
            contentItem.dataset.enhanced = 'true';
            
            // 查找用户链接(在 .author-info 或 .nsk-content-meta-info 中)
            const userLink = contentItem.querySelector('.author-info a[href^="/space/"], .nsk-content-meta-info a[href^="/space/"]');
            
            if (userLink) {
                const userId = this.extractUserId(userLink);
                
                if (userId) {
                    const userInfo = await UserInfoFetcher.fetchUserInfo(userId);
                    const badge = this.createUserInfoBadge(userInfo);
                    if (badge) {
                        // 在用户名后面插入徽章
                        const authorInfo = contentItem.querySelector('.author-info');
                        if (authorInfo && !authorInfo.querySelector('.nodeseek-user-info')) {
                            // 找到用户名链接后插入
                            const authorNameLink = authorInfo.querySelector('a[href^="/space/"]');
                            if (authorNameLink) {
                                authorNameLink.insertAdjacentElement('afterend', badge);
                            }
                        }
                    }
                }
            }
        }

        // 辅助方法:从元素提取帖子ID
        static extractPostId(element) {
            // 从帖子标题链接提取
            const link = element.querySelector('a[href^="/post-"]');
            if (link) {
                const match = link.href.match(/\/post-(\d+)-/);
                if (match) return match[1];
            }
            
            return null;
        }

        // 从 URL 提取帖子ID
        static extractPostIdFromURL() {
            const match = window.location.pathname.match(/\/post-(\d+)-/);
            return match ? match[1] : null;
        }

        // 提取用户ID
        static extractUserId(userLink) {
            const match = userLink.href.match(/\/space\/(\d+)/);
            return match ? match[1] : null;
        }
    }

    // ==================== 主程序 ====================
    class NodeSeekEnhancer {
        static init() {
            const siteName = CURRENT_SITE ? CURRENT_SITE.name : 'Unknown';
            console.log(`${siteName} 增强插件已启动`);
            
            // 根据页面类型执行不同的增强
            if (this.isPostDetailPage()) {
                UIEnhancer.enhancePostDetail();
            } else if (this.isPostListPage()) {
                UIEnhancer.enhancePostList();
            }

            // 监听页面变化(适用于 SPA)
            this.observePageChanges();
        }

        static isPostDetailPage() {
            return /\/post-\d+-/.test(window.location.pathname);
        }

        static isPostListPage() {
            return window.location.pathname === '/' || 
                   /\/(categories|page-)/.test(window.location.pathname);
        }

        static observePageChanges() {
            const observer = new MutationObserver((mutations) => {
                for (const mutation of mutations) {
                    if (mutation.addedNodes.length) {
                        // 延迟执行,等待内容加载完成
                        setTimeout(() => {
                            if (this.isPostDetailPage()) {
                                UIEnhancer.enhancePostDetail();
                            } else if (this.isPostListPage()) {
                                UIEnhancer.enhancePostList();
                            }
                        }, 500);
                        break;
                    }
                }
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        }
    }

    // ==================== 样式注入 ====================
    function injectStyles() {
        const style = document.createElement('style');
        style.textContent = `
            .post-list .post-title a:visited {
                color: ${CONFIG.visitedColor} !important;
            }
        `;
        document.head.appendChild(style);
    }

    // ==================== 初始化 ====================
    function initialize() {
        // 注册菜单
        MenuManager.registerMenus();
        
        // 注入样式
        injectStyles();
        
        // 启动主功能
        NodeSeekEnhancer.init();
        
        // 添加收藏夹搜索框
        setTimeout(() => CollectionSearch.createSearchBox(), 1000);
        
        // 延迟执行签到
        setTimeout(() => SignInManager.signIn(), 2000);
    }

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

})();