妖火回帖引用显示(他到底说了啥)

在妖火论坛的回帖楼层中显示被引用楼层的内容,并可直接跳转到引用楼层

目前为 2025-02-11 提交的版本。查看 最新版本

// ==UserScript==
// @name         妖火回帖引用显示(他到底说了啥)
// @namespace    https://www.yaohuo.me/bbs/userinfo.aspx?touserid=20740
// @version      1.1.0
// @description  在妖火论坛的回帖楼层中显示被引用楼层的内容,并可直接跳转到引用楼层
// @author       SiXi
// @match        https://www.yaohuo.me/bbs*
// @match        https://yaohuo.me/bbs*
// @icon         https://www.yaohuo.me/css/favicon.ico
// @license      Apache 2
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const loadedFloors = new Map();
    let isLoading = false;
    let loadAttempts = 0;
    const MAX_ATTEMPTS = 5;

    function log(message, data = '') {
        console.log(`[引用显示] ${message}`, data);
    }

    function throttle(func, limit) {
        let inThrottle;
        return function(...args) {
            if (!inThrottle) {
                func.apply(this, args);
                inThrottle = true;
                setTimeout(() => inThrottle = false, limit);
            }
        };
    }

    function getFloorNumber(text) {
        const floorMap = {
            "沙发": 1,
            "椅子": 2,
            "板凳": 3
        };
        return floorMap[text] || parseInt(text);
    }

    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    async function waitForNewContent() {
        const startCount = document.querySelectorAll('.forum-post, .list-reply').length;
        let attempts = 0;
        while (attempts < 5) {
            await sleep(800);
            const currentCount = document.querySelectorAll('.forum-post, .list-reply').length;
            if (currentCount > startCount) return true;
            attempts++;
        }
        return false;
    }

    async function getQuotedContent(floorNumber, retryCount = 0) {
        if (loadedFloors.has(floorNumber)) return loadedFloors.get(floorNumber);

        if (isLoading) {
            if (retryCount < 3) {
                await sleep(1000);
                return getQuotedContent(floorNumber, retryCount + 1);
            }
            return { text: '加载超时', isError: true };
        }

        const findFloorContent = () => {
            const floors = document.querySelectorAll('.forum-post, .list-reply');
            for (const floor of floors) {
                const floorInfo = floor.querySelector('.floor-info, .floornumber');
                if (!floorInfo) continue;

                const currentFloor = getFloorNumber(floorInfo.textContent.replace(/[楼\s]/g, ''));
                if (currentFloor === floorNumber) {
                    const contentElement = floor.querySelector('.retext') || floor.querySelector('.post-content');
                    let content = contentElement ? contentElement.innerHTML : '';

                    const tempDiv = document.createElement('div');
                    tempDiv.innerHTML = content;
                    const quoteElement = tempDiv.querySelector('.replay-other, .reother');
                    if (quoteElement) quoteElement.remove();
                    content = tempDiv.innerHTML.trim();

                    const userNick = floor.querySelector('.user-nick a, .renick a')?.textContent || '未知用户';
                    const userId = floor.querySelector('.user-id a, .reidlink .renickid')?.textContent.replace(/[()]/g, '') || '未知ID';

                    return {
                        userNick,
                        userId,
                        floorNumber: currentFloor,
                        content,
                        text: `<strong>${userNick}(${userId}) ${currentFloor}楼:</strong>${content}`
                    };
                }
            }
            return null;
        };

        let content = findFloorContent();
        if (content) {
            loadedFloors.set(floorNumber, content);
            return content;
        }

        if (loadAttempts >= MAX_ATTEMPTS) return { text: '加载失败:已达到最大尝试次数', isError: true };

        const loadMoreBtn = document.querySelector('#KL_show_tip, #YH_show_tip');
        if (!loadMoreBtn || loadMoreBtn.textContent.includes('没有了')) {
            return { text: '未找到该楼层内容', isError: true };
        }

        log('点击加载更多按钮');
        isLoading = true;
        loadAttempts++;

        try {
            loadMoreBtn.click();
            const hasNewContent = await waitForNewContent();
            isLoading = false;
            return hasNewContent ? await getQuotedContent(floorNumber) : { text: '加载失败', isError: true };
        } catch (error) {
            console.error('加载失败:', error);
            return { text: '加载失败', isError: true };
        } finally {
            isLoading = false;
        }
    }

    function addAnchorsToFloors() {
        document.querySelectorAll('.forum-post, .list-reply').forEach(floor => {
            const floorInfo = floor.querySelector('.floor-info, .floornumber');
            if (!floorInfo) return;

            const floorText = floorInfo.textContent.replace(/[楼\s]/g, '');
            const floorNumber = getFloorNumber(floorText);

            if (!floor.id && !isNaN(floorNumber)) {
                floor.id = `floor-${floorNumber}`;
            }
        });
    }

    function scrollToFloor(floorNumber) {
        const targetId = `floor-${floorNumber}`;
        const targetFloor = document.getElementById(targetId);

        if (targetFloor) {
            targetFloor.scrollIntoView({
                behavior: 'smooth',
                block: 'center',
                inline: 'nearest'
            });

            const originalBg = targetFloor.style.backgroundColor;
            targetFloor.style.transition = 'background-color 1s ease';
            targetFloor.style.backgroundColor = '#fff3cd';

            setTimeout(() => {
                targetFloor.style.backgroundColor = originalBg;
                setTimeout(() => {
                    targetFloor.style.transition = '';
                }, 1000);
            }, 2000);

            return true;
        }

        console.warn(`未找到目标楼层: ${targetId}`);
        return false;
    }

    function createImageOverlay(img) {
        const overlay = document.createElement('div');
        overlay.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100vw;
            height: 100vh;
            background: rgba(0,0,0,0.8);
            z-index: 999999;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: zoom-out;
        `;

        const overlayImg = new Image();
        overlayImg.src = img.src;
        overlayImg.style.cssText = `
            max-width: 90vw;
            max-height: 90vh;
            object-fit: contain;
            cursor: default;
        `;

        overlay.appendChild(overlayImg);

        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) {
                document.body.removeChild(overlay);
            }
        });

        return overlay;
    }

    function processImage(img) {
        const MAX_HEIGHT = 250;
        const TARGET_WIDTH = 120;
        const TARGET_HEIGHT = 200;

        const applyScaling = () => {
            if (img.naturalHeight <= MAX_HEIGHT) return;

            // 计算缩放比例
            const ratio = Math.min(
                TARGET_WIDTH / img.naturalWidth,
                TARGET_HEIGHT / img.naturalHeight
            );

            // 应用缩放样式
            img.style.cssText = `
                width: ${img.naturalWidth * ratio}px;
                height: ${img.naturalHeight * ratio}px;
                object-fit: contain;
                display: inline-block;
            `;
        };

        if (img.complete) {
            applyScaling();
        } else {
            img.addEventListener('load', applyScaling);
        }
    }

    function processImages(contentDiv) {
        contentDiv.querySelectorAll('img').forEach(processImage);
    }

    function createQuoteBox(quoteData) {
        const box = document.createElement('div');
        box.className = 'quoted-content';
        box.style.cssText = `
            margin: 5px 0;
            padding: 8px;
            background-color: #f5f5f5;
            border-left: 3px solid #4CAF50;
            border-radius: 3px;
            font-size: 14px;
            color: #666;
            display: flex;
            align-items: flex-start;
            justify-content: space-between;
            margin-bottom: 10px;
        `;

        const contentDiv = document.createElement('div');
        contentDiv.style.flex = '1';

        const userInfo = typeof quoteData === 'string' ? '' : `<strong>${quoteData.userNick}(${quoteData.userId}) ${quoteData.floorNumber}楼:</strong>`;
        const mainContent = typeof quoteData === 'string' ? quoteData : quoteData.content;
        contentDiv.innerHTML = userInfo + mainContent;

        const textContent = contentDiv.textContent;
        if (textContent.length > 70 && !quoteData.isError) {
            const mainContentText = typeof quoteData === 'string' ? quoteData : quoteData.content;
            const truncatedContent = mainContentText.substring(0, 70) + '...';
            contentDiv.innerHTML = userInfo + truncatedContent;

            const expandBtn = document.createElement('button');
            expandBtn.className = 'bt4';
            expandBtn.textContent = '展开';
            expandBtn.style.marginLeft = '5px';
            expandBtn.style.cursor = 'pointer';
            expandBtn.style.width = '50px';
            expandBtn.addEventListener('click', () => {
                contentDiv.innerHTML = userInfo + mainContent;
                processImages(contentDiv);
                expandBtn.remove();
            });
            contentDiv.appendChild(expandBtn);
        }

        processImages(contentDiv);

        const jumpBtn = document.createElement('a');
        jumpBtn.innerHTML = '跳转';
        jumpBtn.style.cssText = `
            margin-left: 10px;
            color: #4CAF50;
            cursor: pointer;
            white-space: nowrap;
            font-size: 12px;
            text-decoration: none;
        `;
        if (quoteData.isError) jumpBtn.style.display = 'none';

        jumpBtn.addEventListener('click', async (e) => {
            e.preventDefault();
            if (quoteData.floorNumber) {
                if (!scrollToFloor(quoteData.floorNumber)) {
                    const loadBtn = document.querySelector('#KL_show_tip, #YH_show_tip');
                    if (loadBtn && !loadBtn.textContent.includes('没有了')) {
                        alert('正在加载更多内容...');
                        loadBtn.click();
                        await waitForNewContent();
                        let retry = 0;
                        while (retry++ < 3 && !scrollToFloor(quoteData.floorNumber)) {
                            await sleep(800);
                        }
                        if (!scrollToFloor(quoteData.floorNumber)) alert('未找到目标楼层');
                    }
                }
            }
        });

        box.appendChild(contentDiv);
        box.appendChild(jumpBtn);
        return box;
    }

    const handleQuoteReplies = throttle(async () => {
        addAnchorsToFloors();
        document.querySelectorAll('.forum-post, .list-reply').forEach(reply => {
            if (reply.dataset.processed) return;
            const quoteElement = reply.querySelector('.replay-other, .reother');
            if (!quoteElement) return;

            const floorMatch = quoteElement.textContent.match(/回复(\d+)楼|回复(沙发|椅子|板凳)/);
            if (!floorMatch) return;

            reply.dataset.processed = 'true';
            const floorNumber = getFloorNumber(floorMatch[1] || floorMatch[2]);

            getQuotedContent(floorNumber).then(content => {
                if (!reply.querySelector('.quoted-content')) {
                    const replyContent = reply.querySelector('.retext') || reply.querySelector('.post-content');
                    if (replyContent) replyContent.prepend(createQuoteBox(content));
                }
            });
        });
    }, 500);

    function unifyFontColor() {
        document.querySelectorAll('.recontent font[color]').forEach(text => {
            text.style.color = '#000';
        });
    }

    const observer = new MutationObserver(mutations => {
        mutations.forEach(mutation => {
            if (mutation.addedNodes.length) {
                setTimeout(() => {
                    addAnchorsToFloors();
                    handleQuoteReplies();
                    unifyFontColor();
                }, 300);
            }
        });
    });

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

    handleQuoteReplies();
    unifyFontColor();
})();