browse.wf关键词高亮

高亮显示歼灭/虚空/捕获等任务类型关键词

// ==UserScript==
// @name         browse.wf关键词高亮
// @namespace    http://tampermonkey.net/
// @version      6
// @license      MIT
// @description  高亮显示歼灭/虚空/捕获等任务类型关键词
// @author       Sariabell
// @match        https://browse.wf/live
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 配置关键词及其颜色
    const keywords = [
        { word: "歼灭", color: "red" },
        { word: "虚空", color: "lightblue" },
        { word: "捕获", color: "gray" },
        { word: "中断", color: "brown" },
        { word: "防御", color: "#074d26" }
    ];

    // 黑名单完整匹配词
    const blacklist = ["虚空洪流", "虚空决战", "虚空覆涌", "移动防御"];

    // 创建样式表
    const style = document.createElement('style');
    style.textContent = `
        .wf-highlight {
            padding: 0 2px;
            border-radius: 3px;
            font-weight: bold;
        }
    `;
    document.head.appendChild(style);

    // 转义正则特殊字符
    function escapeRegExp(str) {
        return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    }

    // 创建优化的正则表达式
    const keywordRegex = new RegExp(
        keywords
            .sort((a, b) => b.word.length - a.word.length) // 优先匹配长词
            .map(k => escapeRegExp(k.word))
            .join('|'),
        'gi'
    );

    // 创建预编译的正则表达式
    const blacklistRegex = new RegExp(
        `(${blacklist.map(escapeRegExp).join('|')})`,
        'gi'
    );

    // 高亮处理函数
    function highlightNode(node) {
        // 前置安全检查
        if (!shouldProcessNode(node)) return;

        // 黑名单检测
        if (blacklistRegex.test(node.textContent)) {
            console.debug('跳过黑名单内容:', node.textContent.trim());
            return;
        }

        const tempDiv = document.createElement('div');
        let highlighted = false;

        const newContent = node.textContent.replace(keywordRegex, match => {
            const keyword = keywords.find(k => k.word.toLowerCase() === match.toLowerCase());
            if (!keyword) return match;

            highlighted = true;
            return `<span class="wf-highlight" style="background:${keyword.color}">${match}</span>`;
        });

        if (highlighted) {
            tempDiv.innerHTML = newContent;
            const parent = node.parentNode;
            parent.replaceChild(tempDiv, node);

            // 将临时div的内容提升到父节点中
            while (tempDiv.firstChild) {
                parent.insertBefore(tempDiv.firstChild, tempDiv);
            }
            parent.removeChild(tempDiv);
        }
    }

    // 节点处理资格验证
    function shouldProcessNode(node) {
        return (
            node.nodeType === Node.TEXT_NODE &&
            node.parentNode &&
            node.parentNode.nodeType === Node.ELEMENT_NODE &&
            !['SCRIPT', 'STYLE', 'TEXTAREA'].includes(node.parentNode.tagName) &&
            node.textContent.trim().length > 0
        );
    }

    // 递归遍历DOM
    function walkDOM(node) {
        console.log('walk dom');
        let currentNode = node;
        while (currentNode) {
            if (currentNode.nodeType === Node.ELEMENT_NODE) {
                // 优先处理子节点
                if (currentNode.childNodes.length > 0) {
                    walkDOM(currentNode.firstChild);
                }

                // 处理Shadow DOM
                if (currentNode.shadowRoot) {
                    walkDOM(currentNode.shadowRoot);
                }
            }
            highlightNode(currentNode);
            currentNode = currentNode.nextSibling;
        }
    }

    // 监听DOM变化
    const observer = new MutationObserver(mutations => {
        observer.disconnect();
        mutations.forEach(mutation => {
            if (mutation.type === 'childList') {
                mutation.addedNodes.forEach(node => {
                    walkDOM(node);
                });
            }
        });
        observer.observe(document.body, observerConfig);
    });

    const observerConfig = {
        childList: true,
        subtree: true,
        characterData: true,
        attributes: false
    };

    // 初始高亮
    walkDOM(document.body);
    observer.observe(document.body, observerConfig);

    // 兼容Shadow DOM
    const originalAttachShadow = Element.prototype.attachShadow;
    Element.prototype.attachShadow = function() {
        const shadowRoot = originalAttachShadow.apply(this, arguments);
        setTimeout(() => walkDOM(shadowRoot), 0);
        return shadowRoot;
    };
})();