Weibo.cn/pub 关键词白字(WAP稳妥版)

在 weibo.cn(含 /pub)把指定关键词渲染为白色;适配早期WAP结构,附带轮询确保生效

目前為 2025-09-22 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Weibo.cn/pub 关键词白字(WAP稳妥版)
// @namespace    local.weibo.kwwhite
// @version      1.0.0
// @description  在 weibo.cn(含 /pub)把指定关键词渲染为白色;适配早期WAP结构,附带轮询确保生效
// @author       you
// @match        *://*.weibo.cn/*
// @match        *://weibo.cn/*
// @match        *://rebang.today/*
// @run-at       document-end
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
  'use strict';

  /** ① 在这里添加/删除你的关键词 **/
  const KEYWORDS = ["日本","中国","乌克兰","以色列","巴勒斯坦","加沙","北京","上海","广东","深圳","广州","南京","杭州","新疆","柬埔寨","网红","警方","官方","官宣","通报","美学","建议","如何","看待","知道","终于","为啥","回应","怎么","评论","评价","是不是","偷偷","啥样","体制内","原生家庭","王安宇","沈月","田栩宁","严浩翔","孙怡","董子健","韩庚","卢靖姗","文章","马伊琍","胡兵","瞿颖","谢霆锋","金晨","黄圣依","檀健次","蔡徐坤", "王一博", "肖战","王俊凯", "王源", "易烊千玺","贾玲", "黄磊", "脱口秀","黄子韬", "沈腾", "赵丽颖","迪丽热巴","热巴","周杰伦","赵露思","邓超","鹿晗","陈赫","王鹤棣","虞书欣","白鹿","小米","华为","鞠婧炜","五月天","中医","中药","雷军","卢伟冰","罗永浩","余承东","陈乔恩","岳云鹏","郭德纲","王楚钦","孙颖莎","张艺兴","时代少年团","iPhone","黄晓明","angelababy","张天爱","吴彦祖","王力宏","韩安冉","李乃文","王冕","服务员","临时工","后续","向佐","向太","张家辉","周星驰","马柏全","张晚意","陈紫函","戴向宇","幽门螺旋杆菌","葛夕","白敬亭","辛芷蕾","黄灿灿","李荣浩","杨丞琳","高圆圆","赵又廷","关晓彤","刘涛","李沁","薛之谦","李一桐","淘宝","美团","马思纯","于正","王晶","王菲","窦靖童","周冬雨","杨蓉","章若楠","台风","航母","唐嫣","白百何"];

  /** ② 可选:是否不区分大小写(中文一般无所谓) **/
  const CASE_INSENSITIVE = true;

  /** ③ 轮询间隔(毫秒)。为兼容 /pub 的局部刷新,建议保留 **/
  const POLL_MS = 1000;

  // --- 正则 ---
  const regex = buildRegex(KEYWORDS, CASE_INSENSITIVE);
  if (!regex) return;

  // 初次执行 + 轮询
  runOnce();
  setInterval(runOnce, POLL_MS);

  function runOnce() {
    // 老页尽量少过滤,直接全页扫;但跳过常见不可见容器
    highlightIn(document.body);
  }

  function buildRegex(words, ci) {
    const parts = (words || []).filter(Boolean).map(escapeRegExp);
    if (!parts.length) return null;
    return new RegExp("(" + parts.join("|") + ")", ci ? "gi" : "g");
  }
  function escapeRegExp(s) {
    return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  }

  // 核心:扫描文本节点,把命中片段用 <span style="color:#fff !important" data-kw-hit> 包裹
  function highlightIn(root) {
    if (!root || !regex) return;

    const ign = node => {
      if (node.nodeType !== 1) return false;
      const tag = node.tagName;
      // 最小化忽略清单:脚本/样式/内嵌对象
      return tag === "SCRIPT" || tag === "STYLE" || tag === "IFRAME" || tag === "OBJECT" || tag === "NOSCRIPT";
    };
    if (ign(root)) return;

    const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null);
    const nodes = [];
    let n;
    while ((n = walker.nextNode())) nodes.push(n);

    for (const textNode of nodes) {
      const parent = textNode.parentNode;
      if (!parent) continue;
      // 如果父节点已经是我们包裹过的片段,跳过
      if (parent.nodeType === 1 && parent.hasAttribute && parent.hasAttribute("data-kw-hit")) continue;

      const text = textNode.nodeValue;
      if (!text || !regex.test(text)) continue;
      regex.lastIndex = 0;

      const frag = document.createDocumentFragment();
      let last = 0;
      text.replace(regex, (m, _g1, idx) => {
        // 前段原样
        if (idx > last) frag.appendChild(document.createTextNode(text.slice(last, idx)));
        // 命中片段 -> 白字
        const span = document.createElement("span");
        span.setAttribute("style", "color:#fff !important;");
        span.setAttribute("data-kw-hit", "1");
        span.textContent = m;
        frag.appendChild(span);
        last = idx + m.length;
        return m;
      });
      // 末尾残余
      if (last < text.length) frag.appendChild(document.createTextNode(text.slice(last)));

      parent.replaceChild(frag, textNode);
    }
  }
})();