2048-预览

为2048核基地论坛帖子添加图片、链接预览

// ==UserScript==
// @name         2048-预览
// @namespace    https://sleazyfork.org/zh-CN/users/1461640-%E6%98%9F%E5%AE%BF%E8%80%81%E9%AD%94
// @version      1.0.6
// @description  为2048核基地论坛帖子添加图片、链接预览
// @author       星宿老魔
// @match        https://hjd2048.com/2048/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=hjd2048.com
// @license      MIT
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';
    
    // ========== 配置管理 ==========
    const CONFIG = {
        defaults: {
            MAX_PREVIEW_IMAGES: 3,
            CONCURRENT_LIMIT: 9,
            SMALL_IMAGE_WIDTH_THRESHOLD: 250,
            SMALL_IMAGE_HEIGHT_THRESHOLD: 150,
            SMALL_IMAGE_AMPLIFY_FACTOR: 3,
        },
        get(key) {
            return GM_getValue(key, this.defaults[key]);
        },
        set(key, value) {
            GM_setValue(key, value);
        },
        getAll() {
            const allSettings = {};
            for (const key in this.defaults) {
                allSettings[key] = this.get(key);
            }
            return allSettings;
        }
    };

    // ========== UI & 设置面板 ==========
    function createSettingsPanel() {
        const panelHtml = `
            <div id="preview-settings-panel" style="display:none; position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); background:white; padding:25px; border-radius:8px; box-shadow:0 4px 20px rgba(0,0,0,0.2); z-index:10001;">
                <div id="settings-form">
                    <h4 class="settings-subtitle">预览图设置</h4>
                    <div class="settings-item">
                        <label for="MAX_PREVIEW_IMAGES">最大预览图数</label>
                        <input type="number" name="MAX_PREVIEW_IMAGES" id="MAX_PREVIEW_IMAGES">
                        <p class="setting-desc">帖子列表中,每个帖子下方最多显示的预览图数量。</p>
                    </div>
                    <div class="settings-item">
                        <label for="CONCURRENT_LIMIT">并发加载上限</label>
                        <input type="number" name="CONCURRENT_LIMIT" id="CONCURRENT_LIMIT">
                        <p class="setting-desc">同时加载预览内容的最大帖子数量,可缓解网络压力。</p>
                    </div>

                    <hr class="settings-separator">

                    <h4 class="settings-subtitle">小图放大设置</h4>
                    <div class="settings-item">
                        <label for="SMALL_IMAGE_WIDTH_THRESHOLD">小图宽度阈值 (px)</label>
                        <input type="number" name="SMALL_IMAGE_WIDTH_THRESHOLD" id="SMALL_IMAGE_WIDTH_THRESHOLD">
                        <p class="setting-desc">当图片宽度和高度都小于阈值时,查看大图时会自动放大。</p>
                    </div>
                    <div class="settings-item">
                        <label for="SMALL_IMAGE_HEIGHT_THRESHOLD">小图高度阈值 (px)</label>
                        <input type="number" name="SMALL_IMAGE_HEIGHT_THRESHOLD" id="SMALL_IMAGE_HEIGHT_THRESHOLD">
                        <p class="setting-desc">与宽度阈值一同判断是否为需要放大的小图。</p>
                    </div>
                    <div class="settings-item">
                        <label for="SMALL_IMAGE_AMPLIFY_FACTOR">小图放大倍数</label>
                        <input type="number" name="SMALL_IMAGE_AMPLIFY_FACTOR" id="SMALL_IMAGE_AMPLIFY_FACTOR">
                        <p class="setting-desc">符合上述条件的小图,在查看大图时宽度被放大的倍数。</p>
                    </div>
                </div>
                <div class="settings-buttons">
                    <button id="save-settings-btn">保存并刷新</button>
                    <button id="close-settings-btn" style="margin-left:10px;">关闭</button>
                </div>
            </div>
            <div id="preview-settings-overlay" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.5); z-index:10000;"></div>
        `;
        document.body.insertAdjacentHTML('beforeend', panelHtml);

        const panel = document.getElementById('preview-settings-panel');
        const overlay = document.getElementById('preview-settings-overlay');
        const form = document.getElementById('settings-form');

        // 加载当前配置到表单
        const currentConfig = CONFIG.getAll();
        for (const key in currentConfig) {
            const input = form.querySelector(`[name="${key}"]`);
            if (input) {
                input.value = currentConfig[key];
            }
        }

        // 事件监听
        document.getElementById('save-settings-btn').addEventListener('click', () => {
            const inputs = form.querySelectorAll('input');
            inputs.forEach(input => {
                const value = input.type === 'number' ? parseInt(input.value, 10) : input.value;
                CONFIG.set(input.name, value);
            });
            alert('设置已保存,页面将刷新以应用更改。');
            window.location.reload();
        });

        const closePanel = () => {
            panel.style.display = 'none';
            overlay.style.display = 'none';
        };

        document.getElementById('close-settings-btn').addEventListener('click', closePanel);
        overlay.addEventListener('click', closePanel);

        return {
            show: () => {
                panel.style.display = 'block';
                overlay.style.display = 'block';
            }
        };
    }

    function addSettingsButton() {
        // 尝试将按钮添加到主导航栏的“搜索”项前
        const searchLink = document.querySelector('#nav-pc a[href="/search.php"]');
        const searchLi = searchLink ? searchLink.parentElement : null;

        if (searchLi) {
            const settingsLi = document.createElement('li');
            const settingsBtn = document.createElement('a');
            settingsBtn.href = 'javascript:;';
            settingsBtn.textContent = '脚本配置';
            
            settingsLi.appendChild(settingsBtn);
            // 将新 li 插入到 “搜索” li 之后
            searchLi.parentElement.insertBefore(settingsLi, searchLi.nextSibling);
            
            const settingsPanel = createSettingsPanel();
            settingsBtn.addEventListener('click', (e) => {
                e.preventDefault();
                settingsPanel.show();
            });
        } else {
            // 如果找不到主导航栏,则回退到旧方法(可能布局不佳)
            const searchForm = document.getElementById('nav-s');
            if (searchForm) {
                const settingsBtn = document.createElement('a');
                settingsBtn.href = 'javascript:;';
                settingsBtn.textContent = '脚本配置';
                settingsBtn.style.color = '#fff';
                settingsBtn.style.marginRight = '15px';
                settingsBtn.className = 'fr'; // 借用现有样式
                
                searchForm.insertBefore(settingsBtn, searchForm.firstChild);
                
                const settingsPanel = createSettingsPanel();
                settingsBtn.addEventListener('click', (e) => {
                    e.preventDefault();
                    settingsPanel.show();
                });
            }
        }
    }
    
    // --- 冒泡提示函数 (移至全局作用域) ---
    function showClickTip(text, e) {
        let tip = document.querySelector('.click-tip');
        if (tip) {
            tip.remove();
        }
        tip = document.createElement('div');
        tip.className = 'click-tip';
        tip.textContent = text;
        document.body.appendChild(tip);
        tip.style.left = `${e.clientX}px`;
        tip.style.top = `${e.clientY}px`;
        setTimeout(() => { tip.style.opacity = '1'; }, 10);
        setTimeout(() => {
            tip.style.opacity = '0';
            setTimeout(() => { if (tip.parentElement) tip.remove(); }, 200);
        }, 1000);
    }

    // 等待页面加载完成
    window.addEventListener('load', main);

    async function main() {
        // 在所有页面都添加设置按钮
        addSettingsButton();

        if (window.location.href.includes('read.php?tid=')) {
            // 内容页:禁止网站的复制弹窗
            suppressCopyPopupOnContentPage();
            // 内容页:处理磁力链接
            await processContentPageLinks();
        } else {
            // 列表页:移除广告
            removeAds();
            // 列表页:执行预览功能
            await displayThreadImages();
        }
    }

    // ========== 移除置顶帖 ==========
    function removeAds() {
        // 查找所有置顶帖并直接移除
        const threadRows = document.querySelectorAll('tr.tr3.t_one');
        threadRows.forEach(tr => {
            const tdContent = tr.querySelector('td.tal');
            if (tdContent && tdContent.innerHTML.includes('headtopic_3.gif')) {
                tr.remove();
            }
        });
    }

    function suppressCopyPopupOnContentPage() {
        const textareas = document.querySelectorAll('textarea[readonly]');
        textareas.forEach(textarea => {
            const textToCopy = textarea.value;
            if (!textToCopy.startsWith('magnet:?xt=urn:btih:')) return;

            let searchScope = textarea.parentElement;
            if (!searchScope) return;

            const copyButton = Array.from(searchScope.querySelectorAll('a, button, input, span')).find(el => {
                return (el.textContent.trim() === '复制' || el.value === '复制');
            });

            if (copyButton) {
                const newButton = copyButton.cloneNode(true);
                copyButton.parentNode.replaceChild(newButton, copyButton);

                newButton.addEventListener('click', e => {
                    e.stopImmediatePropagation();
                    e.preventDefault();
                    navigator.clipboard.writeText(textToCopy).then(() => {
                         showClickTip('已复制', e);
                    });
                }, true);
            }
        });
    }

    async function processContentPageLinks() {
        const contentSelectors = ['#read_tpc', '.tpc_content', '.f14.cc', 'div[id="read_tpc"]', '.t_f'];
        let contentDiv;
        for (const selector of contentSelectors) {
            contentDiv = document.querySelector(selector);
            if (contentDiv) break;
        }
        if (!contentDiv) return;

        const contentText = contentDiv.innerHTML;

        if (/magnet:\?xt=urn:btih:/.test(contentText)) {
            console.log('脚本提示:页面已存在磁力链接,跳过自动提取。');
            return;
        }

        let magnet = '';
        const btLinkEl = contentDiv.querySelector('a[href*="bt.ivcbt.com/list.php?name="], a[href*="bt.bxmho.cn/list.php?name="]');
        if (btLinkEl) {
            const fetchedMagnet = await fetchMagnetFromBtPage(btLinkEl.href);
            if (fetchedMagnet) magnet = fetchedMagnet;
        }

        if (!magnet) {
            const hashMatch = contentText.match(/([A-F0-9]{40})/i);
            if (hashMatch && hashMatch[1]) {
                magnet = `magnet:?xt=urn:btih:${hashMatch[1]}`;
            }
        }

        if (magnet) {
            const magnetDiv = document.createElement('div');
            magnetDiv.className = 'preview-magnet';
            magnetDiv.textContent = magnet;
            magnetDiv.title = '点击链接可复制';
            magnetDiv.onclick = function(e) {
                navigator.clipboard.writeText(magnet).then(() => {
                    showClickTip('已复制', e);
                });
            };

            const infoContainer = document.createElement('div');
            infoContainer.className = 'preview-section';
            infoContainer.style.marginTop = '20px';

            const infoSectionTitle = document.createElement('div');
            infoSectionTitle.className = 'preview-section-title';
            infoSectionTitle.textContent = '脚本提取-磁力链接';

            infoContainer.appendChild(infoSectionTitle);
            infoContainer.appendChild(magnetDiv);

            contentDiv.appendChild(infoContainer);
        }
    }

    // ========== 样式管理模块 ==========
    const StyleManager = {
        styleInjected: false,
        
        injectStyles() {
            if (this.styleInjected || document.getElementById('preview-styles')) {
            return;
        }
        
        const style = document.createElement('style');
            style.id = 'preview-styles';
            style.textContent = this.getStylesCSS();
            document.head.appendChild(style);
            this.styleInjected = true;
        },
        
        getStylesCSS() {
            return `
            /* 标题行样式 */
            .thread-title-highlighted {
                background-color: #f0f8ff !important;
                position: relative;
                border-top-left-radius: 6px;
                border-top-right-radius: 6px;
                transition: all 0.3s ease;
            }
            .thread-title-highlighted:hover {
                background-color: #e3f2fd !important;
            }

            /* 点击复制后的冒泡提示 */
            .click-tip {
                position: fixed;
                background: #333;
                color: white;
                padding: 5px 10px;
                border-radius: 4px;
                font-size: 12px;
                z-index: 10000;
                pointer-events: none;
                    transform: translate(15px, -15px);
                transition: opacity 0.2s;
                opacity: 0;
            }

            /* 预览容器样式 */
            .preview-container {
                margin: 0 0 10px 0;
                border: 1px solid #c8e1ff;
                border-top: none;
                border-bottom-left-radius: 6px;
                border-bottom-right-radius: 6px;
                padding: 15px;
                background-color: #f5faff;
                box-shadow: 0 2px 4px rgba(0,0,0,0.05);
            }
                
            .preview-section {
                margin-bottom: 12px;
                animation: fadeIn 0.4s ease-in-out;
            }
                
            .preview-section-title {
                font-size: 13px;
                color: #666;
                margin-bottom: 5px;
                font-weight: bold;
                padding-left: 5px;
                border-left: 3px solid #3498db;
            }
                
            @keyframes fadeIn {
                from { opacity: 0; transform: translateY(-10px); }
                to { opacity: 1; transform: translateY(0); }
            }
                
            .preview-images {
                display: grid;
                grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
                gap: 10px;
                margin-bottom: 15px;
            }
                
            .preview-image {
                width: 100%;
                height: 220px;
                object-fit: contain;
                    background-color: #f0f0f0;
                border: 1px solid #ddd;
                border-radius: 4px;
                transition: transform 0.2s;
                cursor: pointer;
            }
                
            .preview-image:hover {
                transform: scale(1.03);
            }
                
            .preview-filesize {
                color: #e74c3c;
                font-weight: bold;
                font-size: 14px;
            }
                
            .preview-magnet, .preview-bt-link {
                font-size: 14px;
                word-break: break-all;
                cursor: pointer;
                padding: 8px;
                background-color: #f5f5f5;
                border-radius: 4px;
                margin-bottom: 8px;
                transition: all 0.3s ease;
                box-shadow: 0 1px 2px rgba(0,0,0,0.05);
                position: relative;
            }
                
            .preview-magnet:hover, .preview-bt-link:hover {
                background-color: #e8f4ff;
                box-shadow: 0 2px 5px rgba(0,0,0,0.1);
                transform: translateY(-1px);
            }

            /* 大图查看样式 */
            .lightbox {
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background-color: rgba(0, 0, 0, 0.85);
                display: flex;
                align-items: center;
                justify-content: center;
                z-index: 9999;
                opacity: 0;
                visibility: hidden;
                transition: opacity 0.3s ease, visibility 0.3s ease;
            }
                
            .lightbox.active {
                opacity: 1;
                visibility: visible;
            }
                
            .lightbox-content {
                position: relative;
                display: flex;
                justify-content: center;
                align-items: center;
                max-width: 95%;
                max-height: 95%;
                transition: max-width 0.3s ease, max-height 0.3s ease;
            }
                
            .lightbox-content.landscape {
                max-width: 80vw; 
                max-height: 90vh;
            }
                
            .lightbox-content.portrait {
                max-width: 60vw;
                max-height: 90vh;
            }
                
            .lightbox-image {
                display: block;
                max-width: 100%;
                max-height: 100%;
                width: auto;
                height: auto;
                object-fit: contain;
                box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
            }
                
            .lightbox-prev, .lightbox-next {
                position: absolute;
                top: 50%;
                transform: translateY(-50%);
                font-size: 50px;
                color: #fff;
                cursor: pointer;
                z-index: 10000;
                padding: 0 20px;
                user-select: none;
                transition: color .2s ease;
            }
                
            .lightbox-prev:hover, .lightbox-next:hover {
                color: #ccc;
            }
                
            .lightbox-prev { left: 15px; }
            .lightbox-next { right: 15px; }
                
            .lightbox-close {
                position: absolute;
                top: 20px;
                right: 20px;
                color: #fff;
                font-size: 30px;
                cursor: pointer;
            }
                
            .lightbox-loading {
                color: white;
                font-size: 16px;
            }

                /* 设置面板样式 */
            #preview-settings-panel {
                width: 480px;
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            }
                
            #preview-settings-panel h3 {
                margin: 0 0 20px 0;
                padding-bottom: 15px;
                border-bottom: 1px solid #e0e0e0;
                font-size: 18px;
                color: #333;
            }
                
            .settings-subtitle {
                font-size: 16px;
                color: #444;
                margin-top: 25px;
                margin-bottom: 15px;
                padding-bottom: 8px;
            }
                
            .settings-subtitle:first-of-type {
                margin-top: 0;
            }
                
            .settings-separator {
                border: 0;
                border-top: 1px solid #eaeaea;
                margin: 25px 0;
            }
                
            .settings-item {
                display: grid;
                grid-template-columns: 1fr 120px;
                gap: 10px 15px;
                align-items: center;
                margin-bottom: 18px;
            }
                
            .settings-item label {
                font-size: 14px;
                color: #555;
            }
                
            .settings-item input[type="number"] {
                padding: 6px 8px;
                border: 1px solid #ccc;
                border-radius: 4px;
                font-size: 14px;
                width: 100%;
                box-sizing: border-box;
            }
                
            .settings-item .setting-desc {
                grid-column: 1 / -1;
                font-size: 12px;
                color: #888;
                margin: -10px 0 5px 0;
            }
                
            .settings-buttons {
                margin-top: 20px;
                padding-top: 20px;
                border-top: 1px solid #e0e0e0;
                text-align: right;
            }
                
            .settings-buttons button {
                padding: 8px 16px;
                font-size: 14px;
                border: none;
                border-radius: 5px;
                cursor: pointer;
                transition: opacity 0.2s;
            }
                
            .settings-buttons button:hover {
                opacity: 0.85;
            }
                
            #save-settings-btn {
                background-color: #28a745;
                color: white;
            }
                
            #close-settings-btn {
                background-color: #6c757d;
                color: white;
            }
        `;
        }
    };

    // ========== 工具函数模块 ==========
    const Utils = {
        // 复制到剪贴板
        copyToClipboard(text, event) {
            navigator.clipboard.writeText(text).then(() => {
                showClickTip('已复制', event);
            });
        },
        
        // 移除版规
        removeRules() {
            try {
                const collapseHeader = document.querySelector('.collapse-header');
                if (collapseHeader) {
                    const ruleContainer = collapseHeader.closest('div, section, .rule-container, .collapse-container');
                    if (ruleContainer) {
                        ruleContainer.remove();
                    }
                }
            } catch (e) {
                console.error('移除版规时出错:', e);
            }
        },
        
        // 检查是否为内容页
        isContentPage() {
            return window.location.href.includes('read.php?tid=');
        },
        
        // 并发控制
        async asyncPool(poolLimit, array, iteratorFn) {
            const ret = [];
            const executing = [];
            for (const item of array) {
                const p = Promise.resolve().then(() => iteratorFn(item, array));
                ret.push(p);

                if (poolLimit <= array.length) {
                    const e = p.then(() => executing.splice(executing.indexOf(e), 1));
                    executing.push(e);
                    if (executing.length >= poolLimit) {
                        await Promise.race(executing);
                    }
                }
            }
            return Promise.all(ret);
        }
    };

    // ========== Lightbox管理模块 ==========
    const LightboxManager = {
        lightbox: null,
        lightboxImg: null,
        lightboxContent: null,
        loadingText: null,
        currentImages: [],
        currentIndex: 0,
        config: {},
        
        init(widthThreshold, heightThreshold, amplifyFactor) {
            this.config = { widthThreshold, heightThreshold, amplifyFactor };
            this.createLightbox();
            this.setupEventListeners();
        },
        
        createLightbox() {
            if (this.lightbox) return; // 避免重复创建
            
            this.lightbox = document.createElement('div');
            this.lightbox.className = 'lightbox';
            
            this.lightboxContent = document.createElement('div');
            this.lightboxContent.className = 'lightbox-content';
            
            this.lightboxImg = document.createElement('img');
            this.lightboxImg.className = 'lightbox-image';
            
        const closeBtn = document.createElement('div');
        closeBtn.className = 'lightbox-close';
        closeBtn.innerHTML = '×';
            
        const prevBtn = document.createElement('div');
        prevBtn.className = 'lightbox-prev';
        prevBtn.innerHTML = '‹';
            
        const nextBtn = document.createElement('div');
        nextBtn.className = 'lightbox-next';
        nextBtn.innerHTML = '›';
            
            this.loadingText = document.createElement('div');
            this.loadingText.className = 'lightbox-loading';
            this.loadingText.textContent = '加载中...';

            this.lightboxContent.appendChild(this.loadingText);
            this.lightboxContent.appendChild(this.lightboxImg);
            this.lightbox.appendChild(this.lightboxContent);
            this.lightbox.appendChild(closeBtn);
            this.lightbox.appendChild(prevBtn);
            this.lightbox.appendChild(nextBtn);
            document.body.appendChild(this.lightbox);
            
            // 保存按钮引用以便事件监听
            this.closeBtn = closeBtn;
            this.prevBtn = prevBtn;
            this.nextBtn = nextBtn;
        },
        
        setupEventListeners() {
            this.closeBtn.addEventListener('click', () => this.closeLightbox());
            
            this.prevBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                this.currentIndex = (this.currentIndex - 1 + this.currentImages.length) % this.currentImages.length;
                this.updateLightboxImage();
            });
            
            this.nextBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                this.currentIndex = (this.currentIndex + 1) % this.currentImages.length;
                this.updateLightboxImage();
            });
            
            this.lightbox.addEventListener('click', (e) => {
                if (e.target === this.lightbox) this.closeLightbox();
            });
            
            document.addEventListener('keydown', (e) => {
                if (!this.lightbox.classList.contains('active')) return;
                if (e.key === 'Escape') this.closeLightbox();
                else if (e.key === 'ArrowLeft') this.prevBtn.click();
                else if (e.key === 'ArrowRight') this.nextBtn.click();
            });
        },
        
        updateLightboxImage() {
            const imgSrc = this.currentImages[this.currentIndex];
            this.loadingText.textContent = '加载中...';
            this.loadingText.style.display = 'block';
            this.lightboxImg.style.display = 'none';

            // 重置样式
            this.lightboxImg.style.width = '';
            this.lightboxImg.style.height = '';
            this.lightboxContent.classList.remove('landscape', 'portrait');

            this.lightboxImg.src = imgSrc;
            this.lightboxImg.onload = () => {
                this.loadingText.style.display = 'none';
                this.lightboxImg.style.display = 'block';

                // 检查是否为需要放大的小图
                if (this.lightboxImg.naturalWidth < this.config.widthThreshold && 
                    this.lightboxImg.naturalHeight < this.config.heightThreshold) {
                    this.lightboxImg.style.width = (this.lightboxImg.naturalWidth * this.config.amplifyFactor) + 'px';
                } else {
                    // 对于大图,根据图片宽高比调整容器尺寸
                    if (this.lightboxImg.naturalWidth > this.lightboxImg.naturalHeight) {
                        this.lightboxContent.classList.add('landscape');
                    } else {
                        this.lightboxContent.classList.add('portrait');
                    }
                }
            };
            this.lightboxImg.onerror = () => {
                this.loadingText.textContent = '图片加载失败';
            };
        },
        
        showLightbox(images, index) {
            this.currentImages = images;
            this.currentIndex = index;
            this.updateLightboxImage();
            this.lightbox.classList.add('active');
        },
        
        closeLightbox() {
            this.lightbox.classList.remove('active');
        }
    };

    // ========== 事件管理模块 ==========
    const EventManager = {
        initEd2kInterception() {
        // 拦截页面上原有的ed2k链接点击事件,防止跳转,改为复制
        document.addEventListener('click', function(e) {
            if (e.target.tagName === 'A' && e.target.href && e.target.href.startsWith('ed2k://')) {
                e.preventDefault();
                    Utils.copyToClipboard(e.target.href, e);
                }
            });
        }
    };

    // ========== 数据提取模块 ==========
    const DataExtractor = {
        async extractFromPage(doc) {
            const data = {
                imgSrcs: await this.extractImages(doc),
                fileSize: this.extractFileSize(doc),
                magnet: this.extractMagnet(doc),
                thunderLink: this.extractThunderLink(doc),
                ed2kLink: this.extractEd2kLink(doc),
                btIvcbtLink: this.extractBtLink(doc)
            };
            
            // 如果没有直接的磁力链接,但有bt链接,则尝试获取
            if (!data.magnet && data.btIvcbtLink) {
                const fetchedMagnet = await fetchMagnetFromBtPage(data.btIvcbtLink);
                if (fetchedMagnet) {
                    data.magnet = fetchedMagnet;
                }
            }
            
            return data;
        },
        
        extractImages(doc) {
            const MAX_PREVIEW_IMAGES = CONFIG.get('MAX_PREVIEW_IMAGES');
            
            // 查找图片元素
                let imgElements = [];
                const possibleSelectors = ['#read_tpc img', '.tpc_content img', '.f14.cc img', 'div[id="read_tpc"] img'];
                for (const selector of possibleSelectors) {
                    imgElements = Array.from(doc.querySelectorAll(selector));
                    if (imgElements.length > 0) break;
                }

            // 过滤掉隐藏的图片
                const visibleImgElements = imgElements.filter(img => {
                    const imgStyle = img.getAttribute('style') || '';
                    if (imgStyle.includes('display: none') || imgStyle.includes('display:none')) {
                        return false;
                    }
                    
                    // 检查父元素是否隐藏
                    let parent = img.parentElement;
                    let levels = 0;
                while (parent && levels < 5) {
                        const parentStyle = parent.getAttribute('style') || '';
                        if (parentStyle.includes('display: none') || parentStyle.includes('display:none')) {
                            return false;
                        }
                        const parentClass = parent.getAttribute('class') || '';
                        if (parentClass.includes('hidden') || parentClass.includes('hide')) {
                            return false;
                        }
                        parent = parent.parentElement;
                        levels++;
                    }
                    
                    return true;
                });

            // 智能过滤无用图片
                let imgSrcsWithPriority = visibleImgElements
                .map(img => ({
                    src: img.getAttribute('data-original') || img.getAttribute('src') || '',
                    img
                }))
                    .filter(item => {
                        if (!item.src || !item.src.startsWith('http')) return false;
                        
                        // 基础过滤
                        if (item.src.includes('loading.') || item.src.includes('placeholder.')) return false;
                        
                        // 过滤常见的无用图片模式
                        const src = item.src.toLowerCase();
                        const filename = src.split('/').pop() || '';
                        
                        const badPatterns = [
                            /^(none|empty|blank|default)\./, // 占位图
                            /^(icon|logo|banner|ad)_/,   // 图标、logo、广告
                            /\.(ico|cur)$/,              // 图标文件
                            /^(loading|wait|spinner)/,   // 加载图标
                        ];
                        
                        if (badPatterns.some(pattern => pattern.test(filename))) {
                            return false;
                        }
                        
                    // 过滤过小的图片
                        if (item.img.width && item.img.height) {
                            if (item.img.width < 100 || item.img.height < 100) {
                                return false;
                            }
                        }
                        
                        // 过滤特定class的图片
                        const imgClass = item.img.getAttribute('class') || '';
                        if (imgClass.includes('icon') || imgClass.includes('emoji') || imgClass.includes('smiley')) {
                            return false;
                        }
                        
                        return true;
                    });

            // 优先显示jpg/jpeg/png图片
                imgSrcsWithPriority.sort((a, b) => {
                    const aIsMain = /\.(jpg|jpeg|png)$/i.test(a.src);
                    const bIsMain = /\.(jpg|jpeg|png)$/i.test(b.src);
                    if (aIsMain && !bIsMain) return -1;
                    if (!aIsMain && bIsMain) return 1;
                    return 0;
                });

                const imgSrcs = imgSrcsWithPriority.map(item => item.src).slice(0, MAX_PREVIEW_IMAGES);
                
                // 调试日志
                if (imgElements.length > 0) {
                    console.log(`图片处理: 总共找到 ${imgElements.length} 张图片,可见 ${visibleImgElements.length} 张,智能过滤后 ${imgSrcsWithPriority.length} 张,最终选择 ${imgSrcs.length}/${MAX_PREVIEW_IMAGES} 张预览`);
                    
                    const filteredOut = visibleImgElements.length - imgSrcsWithPriority.length;
                    if (filteredOut > 0) {
                        console.log(`过滤掉 ${filteredOut} 张无用图片(占位图、图标等)`);
                    }
                }

            return imgSrcs;
        },
        
        extractFileSize(doc) {
                let fileSize = '';
                const contentSelectors = ['#read_tpc', '.tpc_content', '.f14.cc', 'div[id="read_tpc"]', '.t_f'];
                for (const selector of contentSelectors) {
                    const contentDiv = doc.querySelector(selector);
                    if (contentDiv) {
                        const contentText = contentDiv.innerHTML;
                        let fileSizeMatch = contentText.match(/【影片容量】:([^<]+)<br/i) || contentText.match(/【影片大小】:([^<]+)</i);
                        if (fileSizeMatch && fileSizeMatch[1]) {
                            fileSize = fileSizeMatch[1].trim();
                            break;
                        }
                    }
                }
            return fileSize;
        },

        extractMagnet(doc) {
                let magnet = '';
                let magnetText = doc.querySelector('textarea[readonly], textarea#copytext');
                if (magnetText) {
                    magnet = magnetText.value.trim();
                } else {
                    let magnetA = doc.querySelector('a[href^="magnet:?xt=urn:btih:"]');
                    if (magnetA) {
                        magnet = magnetA.getAttribute('href');
                    } else {
                         const contentText = doc.body.innerHTML;
                         const hashMatch = contentText.match(/([A-F0-9]{40})/i);
                    if (hashMatch && hashMatch[1]) {
                            magnet = `magnet:?xt=urn:btih:${hashMatch[1]}`;
                         }
                    }
                }
            return magnet;
        },
                
        extractThunderLink(doc) {
                const contentText = doc.body.innerHTML;
                const thunderMatch = contentText.match(/thunder:\/\/[A-Za-z0-9+\/=]+/i);
            return thunderMatch ? thunderMatch[0] : '';
        },
                
        extractEd2kLink(doc) {
                // 先尝试从<a>标签的href属性中获取ed2k链接
                let ed2kA = doc.querySelector('a[href^="ed2k://"]');
                if (ed2kA) {
                return ed2kA.getAttribute('href');
                } else {
                    // 如果没有找到,再从纯文本中匹配
                const contentText = doc.body.innerHTML;
                    const ed2kMatch = contentText.match(/ed2k:\/\/\|file\|[^|]+\|\d+\|[A-F0-9]{32}\|\//i);
                return ed2kMatch ? ed2kMatch[0] : '';
                    }
        },
                
        extractBtLink(doc) {
// 检查bt.ivcbt.com链接
let btLinkEl = doc.querySelector('a[href*="bt.ivcbt.com/list.php?name="]');
if (btLinkEl) {
                return btLinkEl.getAttribute('href');
} else {
    // 检查bt.bxmho.cn链接
    btLinkEl = doc.querySelector('a[href*="bt.bxmho.cn/list.php?name="]');
                return btLinkEl ? btLinkEl.getAttribute('href') : '';
            }
        }
    };

    // 主函数:为每条帖子添加图片预览
    async function displayThreadImages() {
        const MAX_PREVIEW_IMAGES = CONFIG.get('MAX_PREVIEW_IMAGES');
        const CONCURRENT_LIMIT = CONFIG.get('CONCURRENT_LIMIT');
        const SMALL_IMAGE_WIDTH_THRESHOLD = CONFIG.get('SMALL_IMAGE_WIDTH_THRESHOLD');
        const SMALL_IMAGE_HEIGHT_THRESHOLD = CONFIG.get('SMALL_IMAGE_HEIGHT_THRESHOLD');
        const SMALL_IMAGE_AMPLIFY_FACTOR = CONFIG.get('SMALL_IMAGE_AMPLIFY_FACTOR');
        
        // 检查当前页面是否为内容页
        if (Utils.isContentPage()) {
            console.log('当前页面为内容页,跳过预览功能');
            return;
        }
        
        // 初始化样式和移除版规
        StyleManager.injectStyles();
        Utils.removeRules();

        // 初始化Lightbox和事件监听
        LightboxManager.init(SMALL_IMAGE_WIDTH_THRESHOLD, SMALL_IMAGE_HEIGHT_THRESHOLD, SMALL_IMAGE_AMPLIFY_FACTOR);
        EventManager.initEd2kInterception();

        // 获取所有需要处理的帖子链接并批量处理
        const postLinks = document.querySelectorAll('a[target="_self"], a[target="_blank"]');
        if (postLinks.length) {
            await Utils.asyncPool(CONCURRENT_LIMIT, Array.from(postLinks), PreviewProcessor.processThreadLink);
        }
    }

    // ========== UI组件模块 ==========
    const UIComponents = {
        buildPreviewUI(tr, previewData) {
            const { imgSrcs, fileSize, magnet, btIvcbtLink, thunderLink, ed2kLink } = previewData;

            if (tr.nextElementSibling && tr.nextElementSibling.classList.contains('imagePreviewTr')) return;

            tr.classList.add('thread-title-highlighted');

            const newTr = document.createElement('tr');
            newTr.className = 'imagePreviewTr';
            const newTd = document.createElement('td');
            newTd.colSpan = tr.children.length;

            const previewContainer = document.createElement('div');
            previewContainer.className = 'preview-container';
            previewContainer.style.borderTop = 'none';

            // 添加图片预览
            if (imgSrcs.length) {
                previewContainer.appendChild(this.createImageSection(imgSrcs));
            }

            // 添加资源信息
            if (fileSize || magnet || (btIvcbtLink && !magnet) || thunderLink || ed2kLink) {
                previewContainer.appendChild(this.createInfoSection({
                    fileSize, magnet, btIvcbtLink, thunderLink, ed2kLink
                }));
            }

            newTd.appendChild(previewContainer);
            newTr.appendChild(newTd);
            tr.parentNode.insertBefore(newTr, tr.nextSibling);
        },

        createImageSection(imgSrcs) {
            const imgSection = document.createElement('div');
            imgSection.className = 'preview-section';
            
            const imgSectionTitle = document.createElement('div');
            imgSectionTitle.className = 'preview-section-title';
            imgSectionTitle.textContent = '预览图片';
            imgSection.appendChild(imgSectionTitle);
            
            const imgContainer = document.createElement('div');
            imgContainer.className = 'preview-images';

            imgSrcs.forEach((imgSrc, index) => {
                if (imgSrc && imgSrc.startsWith('http')) {
                    const img = document.createElement('img');
                    img.src = imgSrc;
                    img.className = 'preview-image';
                    img.onerror = () => { img.style.display = 'none'; };
                    img.addEventListener('click', () => LightboxManager.showLightbox(imgSrcs, index));
                    imgContainer.appendChild(img);
                }
            });
            
            imgSection.appendChild(imgContainer);
            return imgSection;
        },

        createInfoSection({ fileSize, magnet, btIvcbtLink, thunderLink, ed2kLink }) {
            const infoContainer = document.createElement('div');
            infoContainer.className = 'preview-section';
            
            const infoSectionTitle = document.createElement('div');
            infoSectionTitle.className = 'preview-section-title';
            infoSectionTitle.textContent = '资源信息';
            infoContainer.appendChild(infoSectionTitle);

            if (fileSize) {
                infoContainer.appendChild(this.createFileSizeElement(fileSize));
            }

            if (magnet) {
                infoContainer.appendChild(this.createLinkElement(magnet, 'magnet'));
            }

            if (thunderLink) {
                infoContainer.appendChild(this.createLinkElement(thunderLink, 'thunder'));
            }

            if (ed2kLink) {
                infoContainer.appendChild(this.createLinkElement(ed2kLink, 'ed2k'));
            }

            if (btIvcbtLink && !magnet) {
                infoContainer.appendChild(this.createLinkElement(btIvcbtLink, 'bt'));
            }

            return infoContainer;
        },

        createFileSizeElement(fileSize) {
            const fileSizeDiv = document.createElement('div');
            fileSizeDiv.className = 'preview-filesize';
            const sizeLabel = fileSize.includes('MB') || fileSize.includes('mb') ? '【影片大小】' : '【影片容量】';
            fileSizeDiv.innerHTML = `${sizeLabel}:${fileSize}`;
            fileSizeDiv.style.marginBottom = '10px';
            return fileSizeDiv;
        },

        createLinkElement(linkText, linkType) {
            const linkDiv = document.createElement('div');
            linkDiv.className = 'preview-magnet';
            linkDiv.textContent = linkText;
            linkDiv.title = '点击链接可复制';
            linkDiv.onclick = function(e) {
                Utils.copyToClipboard(linkText, e);
            };
            return linkDiv;
        }
    };

    // ========== 预览处理器模块 ==========
    const PreviewProcessor = {
        async processThreadLink(link) {
            const threadURL = link.href;
            if (!threadURL || !threadURL.includes('read.php?tid=')) return;

            const tr = link.closest('tr');
            if (!tr || tr.querySelector('img[src*="headtopic"]')) return; // 跳过置顶帖
            
            try {
                const response = await fetch(threadURL);
                const pageContent = await response.text();
                const parser = new DOMParser();
                const doc = parser.parseFromString(pageContent, 'text/html');

                // 提取数据
                const previewData = await DataExtractor.extractFromPage(doc);
                
                // 检查是否有内容需要预览
                if (!previewData.imgSrcs.length && !previewData.fileSize && 
                    !previewData.magnet && !previewData.btIvcbtLink && 
                    !previewData.thunderLink && !previewData.ed2kLink) {
                    return;
                }
                
                // 构建UI
                UIComponents.buildPreviewUI(tr, previewData);

            } catch (e) {
                console.error('预览加载失败:', e);
            }
            }
        };

    // 从bt页面获取磁力链接的辅助函数
    function fetchMagnetFromBtPage(url) {
        console.log('尝试从以下URL获取磁力链接:', url);
        return new Promise((resolve) => {
            if (typeof GM_xmlhttpRequest === 'undefined') {
                console.error("GM_xmlhttpRequest is not available. Please add '// @grant GM_xmlhttpRequest' to the script header.");
                resolve(null);
                return;
            }
            GM_xmlhttpRequest({
                method: "GET",
                url: url,
                onload: function(response) {
                    console.log('GM_xmlhttpRequest 响应状态:', response.status);
                    if (response.status >= 200 && response.status < 300) {
                        const html = response.responseText;
                        // console.log('获取到的HTML内容:', html); // 暂时注释掉,因为可能很长
                        
                        // 使用统一的磁力链接匹配规则(适用于所有BT域名)
                        const magnetRegex = /magnet:\?xt=urn:btih:[a-zA-Z0-9]+/;
                        const match = html.match(magnetRegex);
                        if (match) {
                            console.log('成功匹配到磁力链接:', match[0]);
                            resolve(match[0]);
                        } else {
                            console.log('页面中未匹配到磁力链接');
                            console.log('获取到的HTML内容(前500字符):', html.substring(0, 500)); // 打印前500个字符看下
                            resolve(null);
                        }
                    } else {
                        console.error('GM_xmlhttpRequest 请求失败,状态码:', response.status);
                        resolve(null);
                    }
                },
                onerror: function(error) {
                    console.error('GM_xmlhttpRequest 请求出错:', error);
                    resolve(null);
                }
            });
        });
    }
})();