<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>TwilightRain</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://twilightrain.com/</id>
  <link href="https://twilightrain.com/" rel="alternate"/>
  <link href="https://twilightrain.com/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, TwilightRain</rights>
  <subtitle>TwilightRain 的个人博客，记录技术探索与日常思考</subtitle>
  <title>TwilightRain</title>
  <updated>2026-08-26T05:06:32.651Z</updated>
  <entry>
    <author>
      <name>TwilightRain</name>
    </author>
    <category term="技术笔记" scheme="https://twilightrain.com/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="Flash" scheme="https://twilightrain.com/tags/Flash/"/>
    <category term="浏览器" scheme="https://twilightrain.com/tags/%E6%B5%8F%E8%A7%88%E5%99%A8/"/>
    <category term="hosts" scheme="https://twilightrain.com/tags/hosts/"/>
    <category term="排坑" scheme="https://twilightrain.com/tags/%E6%8E%92%E5%9D%91/"/>
    <category term="逆向" scheme="https://twilightrain.com/tags/%E9%80%86%E5%90%91/"/>
    <content>
      <![CDATA[<blockquote><p>2020 年 12 月 31 日，Adobe 官方停止支持 Flash。中国大陆除外，因为”代理”还在用 flash.cn 续命，给你弹广告、弹”版本过旧”、弹闪退。</p></blockquote><p>最近玩 Flash 小游戏，Flash 酱一反常态地暴躁。游戏加载到一半，弹窗”您的 Flash 版本过旧，请升级”，点掉之后直接闪退。而且这待遇是<strong>中国 IP 限定</strong>的，症状集中指向 flash.cn 特供版。</p><h2 id="一、实锤，本机装的是-flash-cn-特供版-34-0-0-282"><a class="heading-anchor" href="#一、实锤，本机装的是-flash-cn-特供版-34-0-0-282" aria-hidden="true">#</a>一、实锤，本机装的是 flash.cn 特供版 34.0.0.282</h2><p>查安装日志 <code>C:\Windows\SysWOW64\Macromed\Flash\FlashInstall32.log</code>，</p><figure class="highlight excel"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs excel">=M/M/<span class="hljs-number">34.0</span>.<span class="hljs-number">0.282</span> <span class="hljs-number">2023</span>-<span class="hljs-number">05</span>-<span class="hljs-number">20</span>+<span class="hljs-number">09</span>-<span class="hljs-number">46</span>-<span class="hljs-number">41.993</span>=<br></code></pre></td></tr></table></figure><p>34.0.0.282？Adobe 官方最后一版是 <strong>32.0.0.465</strong>（2020 年 12 月）。<br>这个34.x是谁编出来的？flash.cn代理商在官方停更后继续”维护”的版本号比官方还高两代，里面塞了自己的更新检查逻辑，连上自家服务器（flash.cn 域名），就通知你”版本过旧”，顺手弹广告。什么叫做最新版版本过旧？！！！！！！（官方正版受害者233）</p><p>你走吧，太老了。（Flash酱的不屑）</p><p>本机 hosts 里原本已有 Adobe 官方域名的屏蔽（<code>geo2.adobe.com</code>、<code>fpdownload.macromedia.com</code>），但漏了 flash.cn 自家的域名。</p><h2 id="二、Resources-里的纪念版方案"><a class="heading-anchor" href="#二、Resources-里的纪念版方案" aria-hidden="true">#</a>二、Resources 里的纪念版方案</h2><a class="card-link" href="https://tieba.baidu.com/p/8576795029" target="_blank"  rel="noopener noreferrer"><div class="lc-titlebar"><span class="link-logo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .75.75 0 0 1 .106-1.06l.106-.106.424-.424a.75.75 0 0 1 .636-.131 2.001 2.001 0 0 0 2.121-.354l2.5-2.5a2 2 0 0 0-2.828-2.828l-1.25 1.25a.75.75 0 0 1-1.042-.018l-.07-.07a.75.75 0 0 1 .036-1.037ZM4.019 7.833l-1.25 1.25a2 2 0 0 0 2.828 2.828l1.25 1.25a.75.75 0 0 1 1.042.018l.07.07a.75.75 0 0 1-.036 1.037l-1.25 1.25a3.5 3.5 0 0 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .75.75 0 0 1-.106-1.06l.106-.106.424-.424a.75.75 0 0 1 .636-.131 2.001 2.001 0 0 0 2.121-.354Z"/></svg></span><span class="lc-title">贴吧链接</span><span class="lc-host">tieba.baidu.com</span></div><div class="lc-description">教程灵感来源</div></a><a class="card-link" href="https://wwa.lanzoub.com/iIaLi16w3bxc" target="_blank"  rel="noopener noreferrer"><div class="lc-titlebar"><span class="link-logo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .75.75 0 0 1 .106-1.06l.106-.106.424-.424a.75.75 0 0 1 .636-.131 2.001 2.001 0 0 0 2.121-.354l2.5-2.5a2 2 0 0 0-2.828-2.828l-1.25 1.25a.75.75 0 0 1-1.042-.018l-.07-.07a.75.75 0 0 1 .036-1.037ZM4.019 7.833l-1.25 1.25a2 2 0 0 0 2.828 2.828l1.25 1.25a.75.75 0 0 1 1.042.018l.07.07a.75.75 0 0 1-.036 1.037l-1.25 1.25a3.5 3.5 0 0 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .75.75 0 0 1-.106-1.06l.106-.106.424-.424a.75.75 0 0 1 .636-.131 2.001 2.001 0 0 0 2.121-.354Z"/></svg></span><span class="lc-title">贴吧对应的网盘</span><span class="lc-host">wwa.lanzoub.com</span></div><div class="lc-description">网盘包来源</div></a><p><code>A:\work_zone\Resources</code> 下有两个包，</p><ol><li><strong>Flash_Player_v32.0.0.465 三件套</strong>，官方最终版（ActiveX &#x2F; NPAPI &#x2F; PPAPI），README 说”最好全装”</li><li><strong>文件夹内文件拖入C盘覆盖</strong>，一个 0 字节的 <code>settings.sol</code> + 一份 hosts 模板</li></ol><p>原理很直白，flash.cn 版的行为状态被写进 Flash 的全局设置（settings.sol），同时它连自家服务器判断要不要弹窗。把 settings.sol 清空 + hosts 屏蔽域名，双管齐下。</p><h2 id="三、动手，备份、清空、补屏蔽"><a class="heading-anchor" href="#三、动手，备份、清空、补屏蔽" aria-hidden="true">#</a>三、动手，备份、清空、补屏蔽</h2><p>备份放到 <code>A:\work_zone\Temp\FlashFix_backup_20260826</code>（原 hosts + 整个 Flash Player 设置目录）。</p><h3 id="settings-sol-清空"><a class="heading-anchor" href="#settings-sol-清空" aria-hidden="true">#</a>settings.sol 清空</h3><p>两个位置，</p><ul><li><code>%APPDATA%\Macromedia\Flash Player\macromedia.com\support\flashplayer\sys\settings.sol</code></li><li>同目录下 <code>#local\settings.sol</code></li></ul><p>用包里的 0 字节文件覆盖。注意同级的 <code>#SharedObjects</code> 目录是<strong>游戏存档</strong>（相当于 localStorage），不能动，README 那句”用 Everything 删掉所有 settings.sol”是有坑的，照做会连游戏进度一起清掉。</p><h3 id="hosts-只补一行"><a class="heading-anchor" href="#hosts-只补一行" aria-hidden="true">#</a>hosts 只补一行</h3><p>包里的 hosts 是纯净模板，但本机 hosts 有 Pixiv、Docker、Steam 的条目（才不告诉你我劫持这些主机用来干什么呢！），<strong>不能整包覆盖</strong>。只追加，<code>127.0.0.1 flash.cn</code></p><p>结果当场翻车，原文件末尾没有换行符，<code>Add-Content</code> 追加把内容拼进了上一行，变成 <code># Adobe End127.0.0.1 flash.cn</code>，整行是注释，屏蔽无效。用正则把行拆开才修好。</p><blockquote class="admonition bdm-caution" data-callout="caution"><div class="bdm-title">教训</div><p>改 hosts 先看文件末尾有没有换行符。追加内容被拼进注释行就是静默失效，比没改还隐蔽。</p></blockquote><h2 id="四、三件套安装，两个顺利，一个装了个寂寞"><a class="heading-anchor" href="#四、三件套安装，两个顺利，一个装了个寂寞" aria-hidden="true">#</a>四、三件套安装，两个顺利，一个装了个寂寞</h2><ul><li><strong>ActiveX</strong>，SFX 后台运行，自己提权装好了（Flash.ocx 32&#x2F;64 位 + 注册表 32.0.0.465）</li><li><strong>PPAPI</strong>，提权脚本一次成功（pepflashplayer.dll 32&#x2F;64 + manifest.json）</li><li><strong>NPAPI</strong>，两次”运行完成”，什么都没装</li></ul><p>NPAPI 那个安装器跑了 45 秒显示完成，但 NPSWF.dll 没落地、注册表没有 FlashPlayerPlugin 键。勾起了我的好奇。</p><h2 id="五、解包取证，批处理第一行就是答案"><a class="heading-anchor" href="#五、解包取证，批处理第一行就是答案" aria-hidden="true">#</a>五、解包取证，批处理第一行就是答案</h2><p>安装器是 7-Zip 自解压包（SFX），里面藏着安装批处理。解包过程，</p><ol><li>Python 的 py7zr 能读到档案列表，但里面的 DLL 用了 BCJ2 过滤器，解不出来</li><li>本机有 msys2，<code>pacman -S p7zip</code> 一条命令装好</li><li><code>7z x</code> 直接解带 PE 头的 exe，7z 会自动跳过 stub</li></ol><p>看到批处理第一行，一切豁然开朗，</p><figure class="highlight bat"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bat">@<span class="hljs-built_in">ECHO</span> OFF&amp;(<span class="hljs-built_in">PUSHD</span> &quot;%~DP0&quot;)&amp;(REG QUERY &quot;HKU\S-<span class="hljs-number">1</span>-<span class="hljs-number">5</span>-<span class="hljs-number">19</span>&quot;&gt;<span class="hljs-built_in">NUL</span> <span class="hljs-number">2</span>&gt;&amp;<span class="hljs-number">1</span>)||(<br>powershell -Command &quot;<span class="hljs-built_in">Start</span>-Process &#x27;%~sdpnx0&#x27; -Verb RunAs&quot;&amp;&amp;<span class="hljs-keyword">EXIT</span>)<br></code></pre></td></tr></table></figure><p>它检测自己是否跑在管理员上下文（<code>HKU\S-1-5-19</code> 是系统账户的注册表 hive），不是就 <code>Start-Process -Verb RunAs</code> 提权重启自己。<strong>UAC 弹窗没人点 → 静默 EXIT</strong>。之前两次失败都卡在这一环，安装器自己提权重启，UAC 没被确认，安装流程就悄悄结束了。</p><blockquote class="admonition bdm-important" data-callout="important"><div class="bdm-title">真相</div><p>“安装器运行了但什么都没装”，大概率是安装器内部有提权重启逻辑，UAC 被忽略就静默退出。看文件、看注册表，别信安装器自己报的”完成”。</p></blockquote><h2 id="六、手动安装-NPAPI"><a class="heading-anchor" href="#六、手动安装-NPAPI" aria-hidden="true">#</a>六、手动安装 NPAPI</h2><p>包已经解开，直接手动装，</p><ul><li><code>x64files\NPSWF.dll</code> → <code>System32\Macromed\Flash\</code></li><li><code>x32files\NPSWF.dll</code> + <code>FlashPlayerPlugin.exe</code> + <code>flashplayer.xpt</code> → <code>SysWOW64\Macromed\Flash\</code></li><li>注册表，<code>Macromedia\FlashPlayerPlugin</code> 和 <code>MozillaPlugins\@adobe.com/FlashPlayer</code>（含 Wow6432Node 32 位视图），Version &#x3D; 32.0.0.465、PlayerPath、XPTPath</li></ul><p>提权跑一遍脚本，验证通过。</p><h2 id="七、最终状态"><a class="heading-anchor" href="#七、最终状态" aria-hidden="true">#</a>七、最终状态</h2><table><thead><tr><th>项目</th><th>状态</th></tr></thead><tbody><tr><td>ActiveX</td><td>Flash.ocx 32&#x2F;64 + 注册表 32.0.0.465</td></tr><tr><td>NPAPI</td><td>NPSWF.dll 32&#x2F;64 + FlashPlayerPlugin 注册表</td></tr><tr><td>PPAPI</td><td>pepflashplayer.dll 32&#x2F;64 + 注册表 32.0.0.465</td></tr><tr><td>hosts</td><td>flash.cn 已屏蔽，原有条目全部保留</td></tr><tr><td>settings.sol</td><td>0 字节（sys\ 与 #local\）</td></tr><tr><td>mms.cfg</td><td>AutoUpdateDisable&#x3D;1、DisableAnalytics&#x3D;1</td></tr><tr><td>备份</td><td>A:\work_zone\Temp\FlashFix_backup_20260826</td></tr></tbody></table><h2 id="经验"><a class="heading-anchor" href="#经验" aria-hidden="true">#</a>经验</h2><ul><li><strong>中国特供版续命软件的原罪</strong>，版本号比官方高、弹窗、更新检查全都自己说了算。能用官方版就不用代理版。（代理商全家活暗暗了）</li><li><strong>安装器”完成”不等于装上</strong>。文件落没落地、注册表有没有键，才是真相。</li><li><strong>SFX 自解压包可以解包看源码</strong>。7z 直接解 exe。py7zr 遇到 BCJ2 就换 p7zip（msys2 一个 pacman 搞定）。</li><li><strong>改 hosts 只补不盖</strong>。先备份，注意末尾换行。</li><li><strong>settings.sol 清空是合法的</strong>，但同目录的 #SharedObjects 是游戏存档，删了就是进度清零。</li></ul><h2 id="尾声"><a class="heading-anchor" href="#尾声" aria-hidden="true">#</a>尾声</h2><p>Flash 酱安静下来了。版本 32.0.0.465，官方最终版，不再有”版本过旧”的问候，也不再有闪退的告别。</p><p>玩你的臭游戏去吧。（什么狗公主，粉红兔子，羔羊，lostlife，才没有听说过呢，笑）</p>]]>
    </content>
    <id>https://twilightrain.com/2026/08/26/flash-cn-special-edition-fix/</id>
    <link href="https://twilightrain.com/2026/08/26/flash-cn-special-edition-fix/"/>
    <published>2026-08-26T04:30:00.000Z</published>
    <summary>flash.cn 最新特供版的版本过旧弹窗和闪退，UAC静默之谜。</summary>
    <title>Flash的中国？！特供！？版。</title>
    <updated>2026-08-26T05:06:32.651Z</updated>
  </entry>
  <entry>
    <author>
      <name>TwilightRain</name>
    </author>
    <category term="技术笔记" scheme="https://twilightrain.com/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="Windows" scheme="https://twilightrain.com/tags/Windows/"/>
    <category term="Windows Terminal" scheme="https://twilightrain.com/tags/Windows-Terminal/"/>
    <category term="调试" scheme="https://twilightrain.com/tags/%E8%B0%83%E8%AF%95/"/>
    <content>
      <![CDATA[<blockquote><p>你所生活的世界，不过是cmd.exe的一场梦。<br>—— 某位戴着红领巾的Lain（不是）</p></blockquote><p>那一天，人类终于回想起了，被<strong>C:\Windows\system32</strong>支配的恐惧。</p><p>说人话，最近 Windows Terminal 酱抽风了。无论从开始菜单、资源管理器地址栏敲 <code>wt</code>，还是在终端里敲 <code>wt</code>，新窗口一律落在 <code>C:\Windows\system32</code>。这一定，是世界的意志吧！（并不是）<br>让Claude帮忙排查，过程比想象中曲折，中间还翻了一次车，差点被它写的脚本卡到干不了活，气晕233。</p><h2 id="一，偽物是-settings-json-里的-CD"><a class="heading-anchor" href="#一，偽物是-settings-json-里的-CD" aria-hidden="true">#</a>一，偽物是 settings.json 里的 %<strong>CD</strong>%</h2><p>第一嫌疑就潜伏在<strong>settings.json</strong>的深处。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-attr">&quot;defaults&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span> <span class="hljs-attr">&quot;startingDirectory&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;%__CD__%&quot;</span> <span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p>文件修改时间是当天 22:05，和「终端开始暴走」对得上。</p><p><code>%__CD__%</code> 是 cmd.exe 的动态伪变量，展开结果是”当前目录加反斜杠”。<br>这类伪变量只活在 cmd 自己的展开逻辑里，不是真实环境变量。<br>Windows Terminal酱试图从自己的进程环境块（Process Environment Block）中读取这个变量，理所当然地，就会有。</p><blockquote class="admonition bdm-caution" data-callout="caution"><div class="bdm-title">失败</div><p><strong>NUL</strong>！！！</p></blockquote><p>解析失败就回退，回退目标的先知之地是 <code>C:\Windows\System32</code>，所以无论从哪个入口打开，都落在 system32，wt酱的蜜汁宽容呢（笑）。</p><p>最迷惑人的一步出现在验证环节。</p><p>我在PowerShell里执行 <code>echo $env:__CD__</code>，居然能打出当前目录，看起来这变量是真的。<br>「什么嘛，这不是有吗——！」<br>我兴奋地把cmd、bash、python、甚至node.js都拉出来测了一圈。<br>注册表HKCU、HKLM，两个根键翻了个底朝天。<br>全部！！！<strong>查询失败</strong>。</p><blockquote class="admonition bdm-important" data-callout="important"><div class="bdm-title">真相只有一个</div><p> <code>$env:__CD__</code> 属于 PowerShell 自己提供的伪动态值，环境块里根本没有这条。<br>网上流传的「用 <code>%__CD__%</code> 让WT跟随当前目录」的说法，八成是哪个萌新在PowerShell里试了一下，「哦！！！！有值！」然后就发帖了www。<br><strong>信了你就输了。</strong></p></blockquote><p>删掉这行之后，落点变成 <code>C:\Users\hxdn</code>，不再回退 system32。system32的噩梦，第一阶段，<strong>解除</strong>了哟。</p><h2 id="二，WT-1-24-根本不继承调用方目录"><a class="heading-anchor" href="#二，WT-1-24-根本不继承调用方目录" aria-hidden="true">#</a>二，WT 1.24 根本不继承调用方目录</h2><p>WT 1.24の掟～ 受け継がれぬディレクトリ</p><p>Claude说，「地址栏敲<code>wt</code>什么的，应该会继承当前目录吧？」</p><p>测了。<strong>错的。</strong> （Claude酱尼在干神魔鸭！！！）</p><p>Windows Terminal 1.24.11911.0 的行为是。</p><ul><li>不设 startingDirectory 时，一律落在 <code>%USERPROFILE%</code>，从终端敲 <code>wt</code> 也一样，不会继承调用方的当前目录。</li><li>地址栏也没救。<code>wt.exe</code> 是应用执行别名（App Execution Alias），从资源管理器启动时，当前目录在别名转发那一步就丢了。</li></ul><p>Windows Terminal酱根本不知道你在哪个文件夹。<br>她只是一如既往地，回到初始の地。想就地打开，只能显式传目录：<code>wt -d .</code>。</p><blockquote><p>就像你不能指望绫波丽对你笑一样。那是设定（什么？！）。</p></blockquote><h2 id="三，顺手修了-Win-X-的空”起始位置”"><a class="heading-anchor" href="#三，顺手修了-Win-X-的空”起始位置”" aria-hidden="true">#</a>三，顺手修了 Win+X 的空”起始位置”</h2><p>Win+X 菜单里 <code>01 - Command Prompt.lnk</code> 和 <code>01a - Windows PowerShell.lnk</code> 的”起始位置”，<strong>空っぽ</strong>（笑）。<br>从那里打开cmd或PowerShell会像被世界遗忘的孩子，孤独地落在system32。<br>我流着泪，把它们改成了 <code>%HOMEDRIVE%%HOMEPATH%</code>，和另外两个（02 和 02a）保持一致。</p><h2 id="四，右键菜单查漏了注册表视图，白装了一个重复项"><a class="heading-anchor" href="#四，右键菜单查漏了注册表视图，白装了一个重复项" aria-hidden="true">#</a>四，右键菜单查漏了注册表视图，白装了一个重复项</h2><p>为了让资源管理器里「就地打开」可靠，先检查了右键菜单是否已装。<br>Claude检查时只看了 <code>Directory\shell</code> 这类 shell 动词。</p><ul><li>没有查 <code>shellex\MenuHandler</code>。</li><li>没有查 <code>ContextMenuHandlers</code></li><li>没有查 WOW6432Node 32 位视图</li><li>没有查 AppX 包清单</li></ul><p>得出”未安装”的结论。（Claude酱尼又在干神魔鸭！！！）</p><p>「没装呢。」Claude酱微笑着装了自己的「Open in Windows Terminal here」。（你为什么要笑啊！！！）</p><p>结果右键菜单里出现了两个终端条目。Windows 10 22H2系统内置的「在终端中打开（T）」一直都在。<br>温柔地，安静地被Claude漏看了。（悲）最后我把加的三个注册表键全部删掉，保留系统项。</p><blockquote><p>教训：查注册表菜单，要从动词、扩展处理器、32位视图到包清单全部过一遍。<br>漏一个，就是命运的岔路。（别TM中二了好吗！！）</p></blockquote><h2 id="五，双击-cmd-的坑：脚本把窗口藏起来了"><a class="heading-anchor" href="#五，双击-cmd-的坑：脚本把窗口藏起来了" aria-hidden="true">#</a>五，双击 .cmd 的坑：脚本把窗口藏起来了</h2><p>修双击 <code>.cmd</code> 文件的问题时，我写了VBS辅助脚本，<code>sh.Run cmdline, 0, False</code><br>0这个数字，带着圣洁的「安静启动」光环，在我脑海中浮现。（我想掐死半个小时前的自己）<br>那是**？！SW_HIDE！？**。<br>0号窗口样式居然是完全隐藏启动进程的主窗口，我的大脑仿佛是离开了地面的猩猩。（离开了Claude酱的人类已经无法生活自理，确信）</p><p>双击 <code>test.cmd</code>。无反应。<br>任务管理器里，「Windows Terminal」进程却在后台，安静地小脑宕机。<br>屏幕空空如也。我当时<strong>急得像碇真嗣看到使徒从海面升起。</strong>（什么狗屁比喻233……）</p><p>查文档得到<code>WScript.Shell.Run</code> 第二个参数，0到11，每个数字都有明确含义。<br>0是隐藏，1是显示。改成 <code>1</code>。  窗口出现了。  落地位置正确。  世界恢复色彩。</p><blockquote><p>VBS是老了。（确信）</p></blockquote><h2 id="六，最终状态和经验"><a class="heading-anchor" href="#六，最终状态和经验" aria-hidden="true">#</a>六，最终状态和经验</h2><table><thead><tr><th>项目</th><th>状态</th></tr></thead><tbody><tr><td>双击 .cmd &#x2F; .bat</td><td>在文件所在目录打开，窗口保持</td></tr><tr><td>右键菜单</td><td>只剩系统内置”在终端中打开（T）”</td></tr><tr><td>settings.json</td><td>无 startingDirectory（<code>%__CD__%</code> 已删）</td></tr><tr><td>Win+X 01 &#x2F; 01a</td><td>起始位置 &#x3D; <code>%HOMEDRIVE%%HOMEPATH%</code></td></tr></tbody></table><p>几条经验。</p><ul><li>伪变量不是环境变量。某个 shell 里”看起来有值”（<code>$env:__CD__</code> 会骗人），不等于进程环境里有。</li><li>Windows 的当前目录继承有三道坑。应用执行别名丢目录、UAC 提权重置目录、Explorer 家族启动回退 <code>%USERPROFILE%</code>。</li><li>排查时留意文件修改时间，<code>settings.json</code> 的 22:05 直接指向”最近改动”。</li><li>一次改一处，备份先行，改错了立刻还原。</li><li>人卡住的时候，先给一条立刻能用的命令，再慢慢修。</li></ul>]]>
    </content>
    <id>https://twilightrain.com/2026/08/25/wt-startdir-debug/</id>
    <link href="https://twilightrain.com/2026/08/25/wt-startdir-debug/"/>
    <published>2026-08-25T14:45:00.000Z</published>
    <summary>%CD%这个伪神欺骗了所有人，我查遍注册表视图，然后用VBS脚本把窗口送进了虚数之海。</summary>
    <title>Windows Terminal酱的暴走！打开目录的连环物语。</title>
    <updated>2026-08-26T05:06:32.651Z</updated>
  </entry>
  <entry>
    <author>
      <name>TwilightRain</name>
    </author>
    <category term="博客" scheme="https://twilightrain.com/tags/%E5%8D%9A%E5%AE%A2/"/>
    <category term="写作" scheme="https://twilightrain.com/tags/%E5%86%99%E4%BD%9C/"/>
    <content>
      <![CDATA[<p>本文是 ink 写作能力的<strong>验收样文</strong>，并附带模板层能力（分享、相关文章、双链等）。</p><p><img src="/img/360px/blog-writing-features.jpg" loading="lazy" decoding="async"></p><p>文内双链示例：[[关于我]]。</p><h2 id="提示块（admonitions）"><a class="heading-anchor" href="#提示块（admonitions）" aria-hidden="true">#</a>提示块（admonitions）</h2><p>用 <code>:::admon[类型]</code> 包裹内容即可生成彩色提示块，类型有五种，各自有独立的颜色与图标：</p><blockquote class="admonition bdm-note" data-callout="note"><div class="bdm-title">Note</div><p>高亮读者需要留意的信息，即使只是扫读也该看到。</p></blockquote><blockquote class="admonition bdm-tip" data-callout="tip"><div class="bdm-title">Tip</div><p>可选信息，帮助读者更顺利地使用。</p></blockquote><blockquote class="admonition bdm-important" data-callout="important"><div class="bdm-title">Important</div><p>读者成功所需的关键信息。</p></blockquote><blockquote class="admonition bdm-warning" data-callout="warning"><div class="bdm-title">Warning</div><p>需要立即注意的潜在风险内容。</p></blockquote><blockquote class="admonition bdm-caution" data-callout="caution"><div class="bdm-title">Caution</div><p>某个操作可能带来的负面后果。</p></blockquote><p>标题可以自定义：</p><blockquote class="admonition bdm-note" data-callout="note"><div class="bdm-title">提示块的标题可以自定义</div><p>默认标题显示类型名（Note &#x2F; Tip 等），用 <code>:::admon[类型 标题]</code> 可以覆盖。块内支持完整的 Markdown：<strong>加粗</strong>、<code>行内代码</code>、列表与代码块。</p></blockquote><h2 id="Mermaid-图表"><a class="heading-anchor" href="#Mermaid-图表" aria-hidden="true">#</a>Mermaid 图表</h2><div class="mermaid"><pre><code>graph TD    A[写文章] --&gt; B{用了新语法?}    B --&gt;|是| C[渲染为图表]    B --&gt;|否| D[普通代码块高亮]    C --&gt; E[发布到 main 分支]    D --&gt; E</code></pre></div><div class="mermaid"><pre><code>sequenceDiagram    participant 作者    participant 博客    作者-&gt;&gt;博客: 提交 Markdown    博客-&gt;&gt;博客: 构建与发布    博客--&gt;&gt;读者: 渲染 HTML</code></pre></div><h2 id="阅读时间、公式与目录"><a class="heading-anchor" href="#阅读时间、公式与目录" aria-hidden="true">#</a>阅读时间、公式与目录</h2><p>文章页显示「约 N 字 · 阅读约 M 分钟」（构建期计算）。行内公式 $E&#x3D;mc^2$，块级公式：</p><p>$$\int_0^1 x^2 , dx &#x3D; \frac{1}{3}$$</p><p>长文自动生成目录卡（与头图并排）；标题 hover 显示 <code>#</code> 锚点。</p><h2 id="通用链接卡片"><a class="heading-anchor" href="#通用链接卡片" aria-hidden="true">#</a>通用链接卡片</h2><a class="card-link" href="https://twilightrain.com/about/" target="_blank"  rel="noopener noreferrer"><div class="lc-titlebar"><span class="link-logo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .75.75 0 0 1 .106-1.06l.106-.106.424-.424a.75.75 0 0 1 .636-.131 2.001 2.001 0 0 0 2.121-.354l2.5-2.5a2 2 0 0 0-2.828-2.828l-1.25 1.25a.75.75 0 0 1-1.042-.018l-.07-.07a.75.75 0 0 1 .036-1.037ZM4.019 7.833l-1.25 1.25a2 2 0 0 0 2.828 2.828l1.25 1.25a.75.75 0 0 1 1.042.018l.07.07a.75.75 0 0 1-.036 1.037l-1.25 1.25a3.5 3.5 0 0 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .75.75 0 0 1-.106-1.06l.106-.106.424-.424a.75.75 0 0 1 .636-.131 2.001 2.001 0 0 0 2.121-.354Z"/></svg></span><span class="lc-title">关于本站</span><span class="lc-host">twilightrain.com</span></div><div class="lc-description">个人介绍与站点说明</div></a><h2 id="GitHub-仓库卡片"><a class="heading-anchor" href="#GitHub-仓库卡片" aria-hidden="true">#</a>GitHub 仓库卡片</h2><a class="card-github" href="https://github.com/TwilightRainDev/TwilightRain" target="_blank"  rel="noopener noreferrer" data-repo="TwilightRainDev/TwilightRain"><div class="gc-titlebar"><div class="gc-titlebar-left"><span class="gc-owner">TwilightRainDev</span><span class="gc-divider">/</span><span class="gc-repo">TwilightRain</span></div><span class="github-logo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg></span></div><div class="gc-description">本博客源码仓库</div><div class="gc-meta"><span class="gc-stars"></span><span class="gc-forks"></span><span class="gc-language"></span><span class="gc-license"></span></div></a><a class="card-github" href="https://github.com/hexojs/hexo" target="_blank"  rel="noopener noreferrer" data-repo="hexojs/hexo"><div class="gc-titlebar"><div class="gc-titlebar-left"><span class="gc-owner">hexojs</span><span class="gc-divider">/</span><span class="gc-repo">hexo</span></div><span class="github-logo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg></span></div><div class="gc-description">Hexo 博客框架</div><div class="gc-meta"><span class="gc-stars"></span><span class="gc-forks"></span><span class="gc-language"></span><span class="gc-license"></span></div></a><h2 id="标签页-tabs"><a class="heading-anchor" href="#标签页-tabs" aria-hidden="true">#</a>标签页 tabs</h2><div class="tabs"><div class="tabs-nav" role="tablist"><button type="button" class="tabs-tab is-active" role="tab" aria-selected="true" data-tab="0">写法</button><button type="button" class="tabs-tab" role="tab" aria-selected="false" data-tab="1">效果</button></div><div class="tabs-panel is-active" role="tabpanel" data-tab="0"><p>在 Markdown 里用 <code>:::tabs</code> 与 <code>--- 标题</code> 分隔子页。</p></div><div class="tabs-panel" role="tabpanel" data-tab="1"><p>点击按钮切换面板，默认激活第一个。</p></div></div><h2 id="文内照片墙"><a class="heading-anchor" href="#文内照片墙" aria-hidden="true">#</a>文内照片墙</h2><div class="md-grid" style="--md-grid-cols:2"><img src="/img/360px/with-her-eyes-1.jpg" alt="图一" loading="lazy" decoding="async"><img src="/img/360px/with-her-eyes-2.jpg" alt="图二" loading="lazy" decoding="async"><img src="/img/360px/blog-writing-features.jpg" alt="图三" loading="lazy" decoding="async"></div><h2 id="隐藏与折叠（推荐-fold）"><a class="heading-anchor" href="#隐藏与折叠（推荐-fold）" aria-hidden="true">#</a>隐藏与折叠（推荐 <code>:::fold</code>）</h2><div class="md-text" tabindex="0" role="button" aria-label="悬停或点击查看剧透"><span class="md-text-hint">悬停或点击查看剧透</span><div class="md-text-body"><p>这是 <code>:::fold[text]</code> 行内揭示块，适合短剧透或答案。</p></div></div><details class="md-details"><summary class="md-details-summary">展开详细说明</summary><div class="md-details-body"><p><code> :::fold[details]</code> 是统一的块级折叠容器，支持 <strong>Markdown</strong> 与列表。</p></div></details><h2 id="文内时间线（Butterfly-批一）"><a class="heading-anchor" href="#文内时间线（Butterfly-批一）" aria-hidden="true">#</a>文内时间线（Butterfly 批一）</h2><div class="post-timeline"><div class="post-timeline-headline">写作特性演进</div><div class="post-timeline-item"><div class="post-timeline-title">Reimu 批一</div><div class="post-timeline-body"><p>段落锚点、懒加载、代码折叠、文章时效</p></div></div><div class="post-timeline-item"><div class="post-timeline-title">Butterfly 批一</div><div class="post-timeline-body"><p>timeline &#x2F; btn·label</p></div></div><div class="post-timeline-item"><div class="post-timeline-title">Butterfly 批二</div><div class="post-timeline-body"><p>B 站嵌入</p></div></div><div class="post-timeline-item"><div class="post-timeline-title">Butterfly 批三</div><div class="post-timeline-body"><p>站点卡、系列文</p></div></div></div><h2 id="B-站懒嵌入（Butterfly-批二）"><a class="heading-anchor" href="#B-站懒嵌入（Butterfly-批二）" aria-hidden="true">#</a>B 站懒嵌入（Butterfly 批二）</h2><figure class="bili-embed" data-bili-src="https://player.bilibili.com/player.html?bvid=BV17x411w7KC&amp;page=1&amp;high_quality=1&amp;danmaku=0"><button type="button" class="bili-embed-trigger" aria-label="加载 B 站视频 BV17x411w7KC"><span class="bili-embed-icon" aria-hidden="true">▶</span><span class="bili-embed-label">B 站视频 · BV17x411w7KC</span><span class="bili-embed-hint">点击或滚动到此处加载播放器</span></button><div class="bili-embed-frame" hidden></div></figure><h2 id="系列文目录"><a class="heading-anchor" href="#系列文目录" aria-hidden="true">#</a>系列文目录</h2><p>本篇 front matter 含 <code>series: 写作特性验收</code>，文内 <code>::series</code> 会输出系列目录（当前仅一篇，作展示）。</p><nav class="post-series" aria-label="系列目录"><p class="post-series-label">系列：写作特性验收</p><ul class="post-series-list"><li class="post-series-item is-current"><span aria-current="page">博客写作全部特性验收</span></li></ul></nav><h2 id="代码块复制与超长折叠"><a class="heading-anchor" href="#代码块复制与超长折叠" aria-hidden="true">#</a>代码块复制与超长折叠</h2><p>下方为 45 行占位代码，应出现「展开全部」按钮且预览区可见正文开头（非空列对行号）。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">// fold-demo line 01</span><br><span class="hljs-comment">// fold-demo line 02</span><br><span class="hljs-comment">// fold-demo line 03</span><br><span class="hljs-comment">// fold-demo line 04</span><br><span class="hljs-comment">// fold-demo line 05</span><br><span class="hljs-comment">// fold-demo line 06</span><br><span class="hljs-comment">// fold-demo line 07</span><br><span class="hljs-comment">// fold-demo line 08</span><br><span class="hljs-comment">// fold-demo line 09</span><br><span class="hljs-comment">// fold-demo line 10</span><br><span class="hljs-comment">// fold-demo line 11</span><br><span class="hljs-comment">// fold-demo line 12</span><br><span class="hljs-comment">// fold-demo line 13</span><br><span class="hljs-comment">// fold-demo line 14</span><br><span class="hljs-comment">// fold-demo line 15</span><br><span class="hljs-comment">// fold-demo line 16</span><br><span class="hljs-comment">// fold-demo line 17</span><br><span class="hljs-comment">// fold-demo line 18</span><br><span class="hljs-comment">// fold-demo line 19</span><br><span class="hljs-comment">// fold-demo line 20</span><br><span class="hljs-comment">// fold-demo line 21</span><br><span class="hljs-comment">// fold-demo line 22</span><br><span class="hljs-comment">// fold-demo line 23</span><br><span class="hljs-comment">// fold-demo line 24</span><br><span class="hljs-comment">// fold-demo line 25</span><br><span class="hljs-comment">// fold-demo line 26</span><br><span class="hljs-comment">// fold-demo line 27</span><br><span class="hljs-comment">// fold-demo line 28</span><br><span class="hljs-comment">// fold-demo line 29</span><br><span class="hljs-comment">// fold-demo line 30</span><br><span class="hljs-comment">// fold-demo line 31</span><br><span class="hljs-comment">// fold-demo line 32</span><br><span class="hljs-comment">// fold-demo line 33</span><br><span class="hljs-comment">// fold-demo line 34</span><br><span class="hljs-comment">// fold-demo line 35</span><br><span class="hljs-comment">// fold-demo line 36</span><br><span class="hljs-comment">// fold-demo line 37</span><br><span class="hljs-comment">// fold-demo line 38</span><br><span class="hljs-comment">// fold-demo line 39</span><br><span class="hljs-comment">// fold-demo line 40</span><br><span class="hljs-comment">// fold-demo line 41</span><br><span class="hljs-comment">// fold-demo line 42</span><br><span class="hljs-comment">// fold-demo line 43</span><br><span class="hljs-comment">// fold-demo line 44</span><br><span class="hljs-comment">// fold-demo line 45</span><br></code></pre></td></tr></table></figure><h2 id="模板层能力（无需正文语法）"><a class="heading-anchor" href="#模板层能力（无需正文语法）" aria-hidden="true">#</a>模板层能力（无需正文语法）</h2><ul><li><strong>分享</strong>：版权声明下方微博 &#x2F; QQ &#x2F; X 链接（<code>post.ejs</code>）</li><li><strong>相关文章</strong>：文末按标签交集推荐（本篇标签「博客」「写作」）</li><li><strong>双链</strong>：文首与文末「链接到 &#x2F; 反向链接」区块</li></ul><blockquote class="admonition bdm-important" data-callout="important"><div class="bdm-title">Important</div><p>验收通过标准：上述块级语法均渲染为对应 DOM 类名；交互类（hide、tabs、代码折叠、B 站占位）可无控制台错误地响应点击&#x2F;滚动。</p></blockquote>]]>
    </content>
    <id>https://twilightrain.com/2026/08/17/blog-writing-features/</id>
    <link href="https://twilightrain.com/2026/08/17/blog-writing-features/"/>
    <published>2026-08-17T02:00:00.000Z</published>
    <summary>本验收文含提示块、Mermaid、链接卡片及大量语法模板功能。</summary>
    <title>博客写作全部特性验收</title>
    <updated>2026-08-26T05:06:32.651Z</updated>
  </entry>
  <entry>
    <author>
      <name>TwilightRain</name>
    </author>
    <category term="代码仓库" scheme="https://twilightrain.com/categories/%E4%BB%A3%E7%A0%81%E4%BB%93%E5%BA%93/"/>
    <category term="AI" scheme="https://twilightrain.com/tags/AI/"/>
    <category term="提问" scheme="https://twilightrain.com/tags/%E6%8F%90%E9%97%AE/"/>
    <category term="开源社区" scheme="https://twilightrain.com/tags/%E5%BC%80%E6%BA%90%E7%A4%BE%E5%8C%BA/"/>
    <content>
      <![CDATA[<h1 id="我把《提问的智慧》改成了-2026-年的样子。">我把《提问的智慧》改成了 2026 年的样子。</h1><p>千禧年那会儿，Eric S. Raymond和Rick Moen这俩老哥写了篇东西，叫提问的智慧，最后一次动笔是2014年，那阵子大伙还在IRC里“&#x2F;me 拍了拍你”，能让人爽一整天。中文圈靠D.H.Grand几位老翻译抬了一手，ryanhanwu那个仓库的版本流传最广，跟祖传秘方似的。<br>但它的时代背景停在”Google 是你的朋友”。2026年的提问现场是 Discord，GitHub Discussions，还有一道当年不存在的前置关卡，AI。<br>道理到今天依然管用。你问得多蠢，决定你被喷得多惨。社区不歧视无知，只过滤不带脑子来的。<br>我把它改写了一版，基于那些早期译本续命 2026年。</p><a class="card-github" href="https://github.com/TwilightRainDev/HowToAskQue" target="_blank"  rel="noopener noreferrer" data-repo="TwilightRainDev/HowToAskQue"><div class="gc-titlebar"><div class="gc-titlebar-left"><span class="gc-owner">TwilightRainDev</span><span class="gc-divider">/</span><span class="gc-repo">HowToAskQue</span></div><span class="github-logo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg></span></div><div class="gc-description">仓库叫HowToAskQue</div><div class="gc-meta"><span class="gc-stars"></span><span class="gc-forks"></span><span class="gc-language"></span><span class="gc-license"></span></div></a><p>改写原则是，骨架不动，血肉换新。</p><h2 id="最大的改动，AI-成了第一道关"><a class="heading-anchor" href="#最大的改动，AI-成了第一道关" aria-hidden="true">#</a>最大的改动，AI 成了第一道关</h2><p>原版的提问前置流程是，搜索，搜论坛，读手册，读 FAQ，做实验，问朋友，读源码。新版在这曹植七步诗之前加了一步，先把问题完整扔给 AI。</p><p>DeepSeek，ChatGPT，Claude这三尊大佛，秒级识别低级问题跟喝汤一样。拼写错误啊，环境变量没配啊，版本不兼容啊，缩进比狗啃的还难看啊，AI都秒了，你就不用去社区社死。</p><blockquote class="admonition bdm-important" data-callout="important"><div class="bdm-title">Important</div><p>千万别找豆包！经典梗图：医生，你的诊断结果怎么跟豆包说的不一样啊？</p></blockquote><p>如果 AI 给的答案没用，就把它给过的回复附在你的提问里。这等于向人类回答者交投名状，不是像个哈巴狗空手来要。</p><h2 id="删掉了能气死人的话"><a class="heading-anchor" href="#删掉了能气死人的话" aria-hidden="true">#</a>删掉了能气死人的话</h2><p>原版里有一段搁现在看，血压直接拉满。有人问”我的 Windows 电脑有问题怎么办”，答案是”扔掉微软的垃圾”。</p><div class="md-text" tabindex="0" role="button" aria-label="这话在现在属于嘉豪"><span class="md-text-hint">这话在现在属于嘉豪</span><div class="md-text-body"><p>嘉豪：一种标新立异以取得关注的人。</p></div></div><p>新版把它改掉了。2026 年的开发者用 Windows加WSL2再正常不过，赛博缝合怪都缝出艺术感了。<br>问题要分两类。</p><ul><li>WSL2 内核，Docker 挂载卷这类开源工具链的问题，带日志去社区。</li><li>公司 AD 域控，组策略，VPN 的问题，找自己公司的 IT。<br>社区没权限也没义务帮你绕过企业安全策略，别把开源社区当你们公司的外包运维。</li></ul><h2 id="新形态的旧问题"><a class="heading-anchor" href="#新形态的旧问题" aria-hidden="true">#</a>新形态的旧问题</h2><p>原版警告过的”我的程序没用了怎么办”这种小脑缺失的信息残缺式提问逐渐销声匿迹，然而有了新形态，跟隔壁宝可梦差不多，居然还进化了（开智了？并没有）。<br>有人甩一个几百 MB 的 zip 说”帮我看看”。新版明确写了，代码片段加Dockerfile的组合远比压缩包受欢迎，你甩个压缩包，谁闲得慌帮你解压？<br>剪裁最小复现用例用 <code>git bisect</code>，别手工逐行注释。手工注释到凌晨，眼睛比熊猫还黑，结果发现是空格问题，这种蠢事咱都干过，别干了。</p><p>XY 问题值得单独拎出来吊着打。它是技术社区最常见也最隐蔽的陷阱。<br>这就好比你肚子饿（X），你自己脑补“我得把冰箱拆了才能拿到吃的”（Y），然后问“冰箱门螺丝拧不动怎么办”（Y）。回答者教你拧螺丝拧到天荒地老（Y），最后你说“其实我就想拿个馒头”（X），对面直接脑溢血。<br>规避方法只有一句话，先说最终目标，再说你打算怎么做，卡在哪，把路径选择权交给社区。</p><h2 id="骨架和嘴臭都没动"><a class="heading-anchor" href="#骨架和嘴臭都没动" aria-hidden="true">#</a>骨架和嘴臭都没动</h2><p>核心速查表，描述问题的六个要点，RTFM 和 STFW 的解读，低声下气和傲慢都不能代替功课，解决后补收尾说明。这些 2001 年就成立的东西，2026 年一字不用改。语气也留了。原版最值钱的是它的直率，新版没有把它软化成<code>亲，这边建议您友好提问哦</code>或者<code>你们太过分了，他们还只是个孩子</code>。</p><div class="md-text" tabindex="0" role="button" aria-label="RTFM 和 STFW 分别是什么？"><span class="md-text-hint">RTFM 和 STFW 分别是什么？</span><div class="md-text-body"><ul><li><strong>RTFM</strong>：你的问题在手册&#x2F;文档里有答案，先去看。</li><li><strong>STFW</strong>：你的问题在网上随便一搜就有答案，先去搜。</li></ul></div></div><p>新版还保留了”如何更好地回答问题”一节。提问指南的另一半其实是写给回答者的.</p><ul><li>态度和善一点，你不欠对方，但也没必要往死里踩。</li><li>对初犯者私下提醒，公开处刑是最后手段。</li><li>不确定就说“据我推测”，别装大尾巴狼。</li></ul><p>社区的知识质量靠所有参与者共同维护，批评一个提问者的时候，你也在维护这个社区。</p><h2 id="引用前先读免责声明"><a class="heading-anchor" href="#引用前先读免责声明" aria-hidden="true">#</a>引用前先读免责声明</h2><p>如果你维护的开源项目想引用这份指南，欢迎，但请按 README 的要求注明，本指南不提供该项目的实际技术支持。</p><p>这条免责声明不是矫情。过往经验表明，真的会有人以为你发布了指南，就等于承诺解答所有问题。</p><p>到那时候，回一句<code>找搜索引擎去，别在这找事</code>，那个人反手把你挂小红书。冤不冤？冤。但咱先把免责声明糊脸上，总比事后扯皮强。</p><hr><p><strong>呐，改完了。</strong> 要我说，你拿出去发，保证有人回你一句“www笑死”，然后默默收藏。</p>]]>
    </content>
    <id>https://twilightrain.com/2026/08/13/how-to-ask-questions/</id>
    <link href="https://twilightrain.com/2026/08/13/how-to-ask-questions/"/>
    <published>2026-08-13T08:00:00.000Z</published>
    <summary>AI 成了提问前置的第一道关卡，回答前先证明你做过预筛选。</summary>
    <title>我把《提问的智慧》改成了 2026 年的人话</title>
    <updated>2026-08-26T05:06:32.651Z</updated>
  </entry>
  <entry>
    <author>
      <name>TwilightRain</name>
    </author>
    <category term="代码仓库" scheme="https://twilightrain.com/categories/%E4%BB%A3%E7%A0%81%E4%BB%93%E5%BA%93/"/>
    <category term="Claude Code" scheme="https://twilightrain.com/tags/Claude-Code/"/>
    <category term="PowerShell" scheme="https://twilightrain.com/tags/PowerShell/"/>
    <category term="技能" scheme="https://twilightrain.com/tags/%E6%8A%80%E8%83%BD/"/>
    <content>
      <![CDATA[<h1 id="把踩过的坑编译成技能">把踩过的坑编译成技能</h1><p>我在 Windows 上用 Claude Code 干活，环境有点特殊，H 盘是 exFAT，路径里有中文，终端默认 GBK，脚本用 PowerShell 写，核心逻辑扔给 Python 跑。这四个条件凑齐，乱码就是日常，不是事故。</p><p>最早几次我还认真排查，编码声明写没写？管道是不是在作怪？后来学乖了，每踩一个坑就把结论写成一条规则，攒多了变成一个技能（skill），让 Claude Code 在需要的时候自己读。</p><a class="card-github" href="https://github.com/TwilightRainDev/claude-skills" target="_blank"  rel="noopener noreferrer" data-repo="TwilightRainDev/claude-skills"><div class="gc-titlebar"><div class="gc-titlebar-left"><span class="gc-owner">TwilightRainDev</span><span class="gc-divider">/</span><span class="gc-repo">claude-skills</span></div><span class="github-logo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg></span></div><div class="gc-description">这个仓库就是攒下来的东西</div><div class="gc-meta"><span class="gc-stars"></span><span class="gc-forks"></span><span class="gc-language"></span><span class="gc-license"></span></div></a><h2 id="EncodingGuide，一条铁律，七个陷阱"><a class="heading-anchor" href="#EncodingGuide，一条铁律，七个陷阱" aria-hidden="true">#</a>EncodingGuide，一条铁律，七个陷阱</h2><p>EncodingGuide 全名”Windows PowerShell 中文编码安全基线”，内容是纯实战。它只有一条铁律。</p><p>绝对不要把含中文的内容通过管道传给 <code>python -c</code>，也不要用 heredoc 传。</p><p>PowerShell 的管道会自己解析 Unicode 和转义符，中文变 <code>??</code>、脚本崩溃、JSON 乱码，根源都在这一条。技能围绕它列了七个致命陷阱，挑几个说说。</p><p><code>Out-File -Encoding UTF8</code> 会往文件头写 BOM（<code>EF BB BF</code>）。TOML 解析器不认这个，直接报 <code>Invalid statement (at line 1, column 1)</code>。所以写 pyproject.toml 得用 <code>[System.IO.File]::WriteAllText</code> 配 <code>UTF8Encoding($false)</code> 写出无 BOM 文件。</p><p><code>json.dump</code> 忘写 <code>ensure_ascii=False</code>，整个文件变成 <code>\uXXXX</code> 地狱。<code>indent=2</code> 和末尾换行也别省，git diff 会感谢你。</p><p><code>-Path</code> 会做通配符展开，文件名里的 <code>[1]</code> 会被吃掉。要用 <code>-LiteralPath</code>。</p><p>这些规则单独看都不高深，值钱的是它们被写成清单，AI 写脚本时逐条对照，而不是临场发挥。</p><p>技能最后一节我尤其喜欢，叫”检查文件内容与元数据时的误判陷阱”。它来自一次真实误判，我用 <code>head -6 SKILL.md | grep description</code> 检查技能描述字段，输出看起来是空的，差点判定文件损坏。其实 YAML 的 <code>|</code> 是多行块标量指示符，正文在后面的缩进行里，head 把它截掉了。这个教训和技能主题同构，在 Windows 环境里，直觉方法的输出不可轻信，下结论前先怀疑读取方法本身。说”文件坏了”之前，至少用两种独立方式交叉验证。</p><h2 id="CreatePrompts，先问清楚，再原样转录"><a class="heading-anchor" href="#CreatePrompts，先问清楚，再原样转录" aria-hidden="true">#</a>CreatePrompts，先问清楚，再原样转录</h2><p>CreatePrompts 是另一个画风。它不是排坑清单，是流程设计，两阶段，刨根问底，然后生成。</p><p>阶段一，苏格拉底式澄清。AI 扮演需求架构师，只提问，不给答案，不评价。六个维度，核心目标、受众、场景、流程、约束、成功标准。规则很硬，每轮一个问题，不超过三十个字。只问开放式问题。不猜测。用户说”没想过”就标记 <code>[未指定]</code>，绝不用自己的知识补空白。</p><p>阶段二，刚性转录。角色整个翻过来，AI 变成编译器，把确认过的需求快照机械翻译成提示词模板。缺失字段必须写 <code>[未指定]</code>。不许出现”建议””可以”这类词。不修饰用户的措辞。输出每句话都要能追溯到快照。</p><p>为什么把角色切得这么死？因为我发现同一个模型，澄清阶段共情越到位，生成阶段就越喜欢顺手把没说清的地方补上，补的往往是它自己的猜测而不是你的需求。两阶段的意义就是，先问清楚，再原样转录，中间不让模型发挥。</p><h2 id="技能和笔记的区别"><a class="heading-anchor" href="#技能和笔记的区别" aria-hidden="true">#</a>技能和笔记的区别</h2><p>这两个技能都不大。但技能和笔记的区别在于，笔记要自己翻，技能在合适的时机自己跳出来。踩坑攒成技能，下次 AI 动手之前先被提醒，比事后再查文档省事得多。</p><p>仓库是MIT 许可，目录拷进 <code>~/.claude/skills/</code> 就能用。后续踩到新坑会继续往里加。</p>]]>
    </content>
    <id>https://twilightrain.com/2026/08/13/claude-skills/</id>
    <link href="https://twilightrain.com/2026/08/13/claude-skills/"/>
    <published>2026-08-13T07:00:00.000Z</published>
    <summary>在特殊环境下，踩坑总结规则，建成技能仓库。</summary>
    <title>把踩过的坑编译成技能</title>
    <updated>2026-08-26T05:06:32.651Z</updated>
  </entry>
  <entry>
    <author>
      <name>TwilightRain</name>
    </author>
    <category term="技术笔记" scheme="https://twilightrain.com/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="Android" scheme="https://twilightrain.com/tags/Android/"/>
    <category term="时间管理" scheme="https://twilightrain.com/tags/%E6%97%B6%E9%97%B4%E7%AE%A1%E7%90%86/"/>
    <category term="TimeTrack" scheme="https://twilightrain.com/tags/TimeTrack/"/>
    <content>
      <![CDATA[<h1 id="如果我想成为时间管理大师">如果我想成为时间管理大师</h1><p>$$ \tau &#x3D; \int_{t_1}^{t_2} \sqrt{1 - \frac{v(t)^2}{c^2}} , dt $$</p><p>我想成为Timegame高手，把一天过出两倍效果，但说实话，咱连自己每天在手机上耗了多少时间都说不清楚。于是咱决定给自己写个应用，让它替咱盯着。反正自己写的东西，总比那些“屏幕使用时间”里的黑盒数据可信吧？说干就干。没想到，两天后它居然真能跑了。应用本身挺糙的，没啥可炫耀的。但就做它的这两天，咱的收获比这几年翻过的所有鸡汤叠起来都大。</p><p>先交代动机，其实很简单。咱总觉得最近好像刷手机有点多，但有点多是几小时？三小时？五小时？咱没概念。没概念就没法管。咱写了 TimeTrack，每15分钟采样一次系统事件流，算亮屏时长，算每个应用占多少，画成 GitHub 提交记录那种绿格子热力图，一年365天铺开。</p><p>两天，22 个任务，92 个单测全绿，装到我的 iQOO 上实测。Token 成本折下来大概 23 块人民币，比买高效能人士的七个习惯还便宜二分之一。这波血赚。</p><a class="card-github" href="https://github.com/TwilightRainDev/TwilightRainTimeTrack" target="_blank"  rel="noopener noreferrer" data-repo="TwilightRainDev/TwilightRainTimeTrack"><div class="gc-titlebar"><div class="gc-titlebar-left"><span class="gc-owner">TwilightRainDev</span><span class="gc-divider">/</span><span class="gc-repo">TwilightRainTimeTrack</span></div><span class="github-logo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg></span></div><div class="gc-description">咱的计数器</div><div class="gc-meta"><span class="gc-stars"></span><span class="gc-forks"></span><span class="gc-language"></span><span class="gc-license"></span></div></a><h2 id="数据给咱的第一记耳光"><a class="heading-anchor" href="#数据给咱的第一记耳光" aria-hidden="true">#</a>数据给咱的第一记耳光</h2><p>应用跑起来后的第一批数字就教育了我。昨天我手机亮了 10.7 个小时。近七天咱在小说应用上花了 881 分钟，排第一。我以为自己是偶尔看看，数据说，不是，你是天天看。</p><p>更离谱的是7月30号那天的格子显示我亮屏54小时。一天只有24小时，咱难道掌握了时间折叠技术？</p><p>我居然这么？！强强！？</p><p>查了半天的结论是，vivo 系统自己的日聚合把时长算了两遍，我的应用忠实复制了系统数据，于是连错误一起复制了过来。修法倒简单，超过 24 小时的天直接标”无效”，画成蓝灰格子白叉，不进统计。</p><p>手机系统自己算自己都能算岔，人脑那点我感觉这周没怎么玩，可信度能高到哪去？咱感觉这周没怎么玩手机这句话，可信度跟 54 小时差不多。时间管理的第一步，压根不是什么番茄钟，而是先让数字和你的感觉当面对质。番茄钟是第二步的事。</p><h2 id="记录可以不完美，但不能不知道哪里不完美"><a class="heading-anchor" href="#记录可以不完美，但不能不知道哪里不完美" aria-hidden="true">#</a>记录可以不完美，但不能不知道哪里不完美</h2><p>后台服务毕竟不是神。有时候它被系统杀掉，有时候它自己睡着了。咱不可能每15分钟都手动记一笔。我的做法是给每个格子标近似，当天第一条样本晚于 00:05，或者最后一条早于 23:55，或者中间断过 45 分钟以上，这个格子的数字就带个小角标，告诉你，这块是估的。</p><p>然后拿系统自带的屏幕使用时间当基准做验收。回填出来的历史日，偏差 1.8%，很满意。有缺口的当天偏差 23%，看着吓人，但缺口补上之后偏差立刻降到2.3%，不错。</p><p>关键是，我心里清楚哪儿不准。全是精确数的假账，还不如一本明明白白写着大概二字的真账呢。</p><h2 id="计划和环境是两回事"><a class="heading-anchor" href="#计划和环境是两回事" aria-hidden="true">#</a>计划和环境是两回事</h2><p>咱的应用碰到了一个硬茬，OriginOS。国产ROM杀后台那是刻进基因里的本能，咱用尽了手段，前台服务常驻，每15分钟采样，Shizuku提权，把白名单灌满，JobScheduler定时兜底。能上的全上了。</p><p>结果深夜它还是输了。进程没被杀，但被系统冻住了17.4个小时。整个应用活着，但跟死了没区别，连系统调度器都不给它醒来的机会。没想到，解冻的时机竟是咱第二天碰了手机。</p><p>唯一的安慰是数据一条没丢。系统的事件流一直在记，打开应用的瞬间能全部补回来，跟系统口径的偏差仅有0.6%。</p><p>打断肯定会来，日程肯定会被打乱。与其琢磨怎么防着不被干扰，不如想想断了之后怎么续上。我的办法就是留底。把做到哪儿了，什么状态都随手记下来，能几乎零成本地捡起来接着干。</p><h2 id="知道什么时候停"><a class="heading-anchor" href="#知道什么时候停" aria-hidden="true">#</a>知道什么时候停</h2><p>TimeTrack 的停止记录是真停。取消后台任务，撤掉白名单，杀进程，保证它不会半夜自己复活。README 里写明白，你不想记了，它就是停的。</p><p>一个记录时间的应用，把停做成硬功能，听着矛盾，其实是一回事。不然，用户会觉得它是流氓软件，咱也一样。881 分钟看小说那一周，咱没什么负罪感，数据只是告诉我，你在干什么。真要说问题，是那两天开发期间，咱手机上使用时长排第一的应用叫 TimeTrack 自己。用刷手机的方式戒刷手机，属实是行为艺术。咱也没办法啊。</p><p>咱还做了个噪音过滤，切到前台 5 秒以内又退出的，算误触，不计入，阈值能在设置里拉滑条调。较真到把误触都算进去，是另一种时间浪费。</p><h2 id="两天-22-个任务，靠的不是自律"><a class="heading-anchor" href="#两天-22-个任务，靠的不是自律" aria-hidden="true">#</a>两天 22 个任务，靠的不是自律</h2><p>计划的文档写了上千行。每个任务写清楚要建哪些文件，接口长什么样，单测断言什么，真机验收怎么验。做完一个提交一次，然后一个评审代理专门挑错，通不过就返工。中间翻过两次车，编辑器重启杀掉了一个正在干活的代理，靠台账恢复。另一个代理把后面三个任务的活提前干了，编译炸了。靠 git 还原，再逐任务收口，救回来了。每次翻车都有记录可查，所以每次都不致命。</p><p>纯逻辑的部分全部测试先行。先挖一个注定失败的坑，再写填坑让它通过的代码。跨午夜的会话怎么按天拆分，锁屏期间算不算亮屏，这类边界全是用例钉死的。</p><p>咱自律水平真的一般。两天能做完，靠的是流程拯救了意志力，每个环节都有验收标准，做完没做完不以感觉为准，以绿不绿为准。</p><p>这笔账算下来也值。整个项目实际消耗约 4 亿 token，折人民币 23 块。两天加 23 块，换一个数据不出手机的时间记录工具，恐怕某些会员上百的软件已经急哭了。做一件小事的成本已经低到这种程度。哼，剩下的借口才不是因为不想做呢。</p><hr><p>如果现在有人问咱怎么成为时间管理大师，咱的建议是先装个计数器。剩下的都是工程问题。咱的计数器数据全在本地，连联网权限都没申请。</p>]]>
    </content>
    <id>https://twilightrain.com/2026/08/13/time-management-master/</id>
    <link href="https://twilightrain.com/2026/08/13/time-management-master/"/>
    <published>2026-08-13T03:40:00.000Z</published>
    <summary>为了搞清每天到底刷多少时间手机，两天写出 TimeTrack。</summary>
    <title>如果我想成为时间管理大师</title>
    <updated>2026-08-26T05:06:32.651Z</updated>
  </entry>
  <entry>
    <author>
      <name>TwilightRain</name>
    </author>
    <category term="技术笔记" scheme="https://twilightrain.com/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="AI" scheme="https://twilightrain.com/tags/AI/"/>
    <category term="Claude Code" scheme="https://twilightrain.com/tags/Claude-Code/"/>
    <category term="MCP" scheme="https://twilightrain.com/tags/MCP/"/>
    <content>
      <![CDATA[<h1 id="带上她的眼睛">带上她的眼睛</h1><p>咳咳，众所粥知。刘慈欣这个老不死的电工（手动狗头），写了个短篇，叫带上她的眼睛。里面那妹子飞船怼进地心，黑得伸手不见五指，得靠别人戴传感眼镜替她看草原。</p><p>咱寻思，咱养在本地这台破电脑里的Claude Code，不也一个德性？</p><p>她没沉在地心，就住在咱C盘角落里瑟瑟发抖，底层套的是DeepSeek。咱给她换了中转，原因很朴素，国内直连Anthropic卡成PPT，还天天封号，谁能遭得住呐。DeepSeek便宜，文本能力没什么好抱怨的，一百万上下文，写代码查资料，稳如老狗。但她看不见，就一睁眼瞎。我甩截图，设计稿，报错弹窗，她只会乖乖回一句：“图我没收到。”（脑补无口萝莉脸好趴）</p><p><img src="/img/360px/with-her-eyes-1.jpg" loading="lazy" decoding="async"></p><p>所以遇到要看图的时候，咱就得充当她的眼睛，截图转文字，一句一句描述喂给她。一次两次还挺浪漫，多了就觉得咱像给盲人指路的傻叉（狗头）。她明明就在这台电脑里，凭什么看一张本地的 jpg 还要咱人肉中转？咱受不了了，给她装了github的<code>me9rez/llm-vision-mcp</code>。自己动手，丰衣足食。</p><a class="card-github" href="https://github.com/me9rez/llm-vision-mcp" target="_blank"  rel="noopener noreferrer" data-repo="me9rez/llm-vision-mcp"><div class="gc-titlebar"><div class="gc-titlebar-left"><span class="gc-owner">me9rez</span><span class="gc-divider">/</span><span class="gc-repo">llm-vision-mcp</span></div><span class="github-logo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg></span></div><div class="gc-description">Github链接</div><div class="gc-meta"><span class="gc-stars"></span><span class="gc-forks"></span><span class="gc-language"></span><span class="gc-license"></span></div></a><blockquote class="admonition bdm-important" data-callout="important"><div class="bdm-title">Important</div><p>得装node依赖！有依赖洁癖的尽快邪灵退散，否则我一个一个退治了！（红白认真脸）</p></blockquote><h2 id="七种姿势"><a class="heading-anchor" href="#七种姿势" aria-hidden="true">#</a>七种姿势</h2><p>这玩意是一个 MCP 服务器，背后起node进程，得把node的依赖加上才成。它的本职是把图片转成文字描述，默认走阿里后端 ModelScope 的 Qwen-VL，免费额度每天 2000 次，对咱这种懒狗来说量大管饱。</p><p>它一口气挂了七个龙珠出来，</p><ul><li>analyze_image，通用看图说话，啥都能唠</li><li>analyze_chart，专治各种折线柱状饼图，金融狗狂喜</li><li>extract_text，OCR，抠字眼比律师还准</li><li>code_from_screenshot，截图直接变前端代码，这个咱直接社保</li><li>describe_ui，描述界面长什么样，UI仔末日</li><li>diagnose_error，从报错截图里找线索，debug不用瞪眼</li><li>understand_diagram，理解流程图之类，脑内渲染终结者</li></ul><p>听着挺唬人，其实没有魔法。Qwen-VL看图产的是文字，真正理解这些文字的，还是DeepSeek的傻白甜文本模型。视觉信号并没有直接灌进她的大脑，只是变成了文字，让她读得懂。就像给盲人姑娘摸盲文，只不过这次是AI念给AI听。</p><h2 id="装上，第一张图就跪了"><a class="heading-anchor" href="#装上，第一张图就跪了" aria-hidden="true">#</a>装上，第一张图就跪了</h2><p>安装一行命令，</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">npm install -g @me9rez/llm-vision-mcp<br></code></pre></td></tr></table></figure><p>然后往 ~&#x2F;.claude.json 的 mcpServers 里加蜜汁酱料，最终形态是这样的，</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-attr">&quot;llm-vision-mcp&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;type&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;stdio&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;command&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;cmd&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;args&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">&quot;/c&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-string">&quot;D:/nodejs/node.exe&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-string">&quot;C:/Users/hxdn/AppData/Roaming/npm/node_modules/@me9rez/llm-vision-mcp/index.js&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-string">&quot;mcp&quot;</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;env&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span> <span class="hljs-attr">&quot;API_KEY&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;ms-xxxx&quot;</span> <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p>这里面两个坑，Win老狗都懂。一是 cmd &#x2F;c 套 node.exe 绝对路径，绕开 npx 那破<code>.cmd</code>垫片。另一个是 npm exec 本地 bin 优先解析的玄学。折腾得我差点砸键盘，最后试出这个写法，我直接对着屏幕比了个中指（并没有）。</p><p>改完配置重启会话，MCP server启动时加载。咱敲了句 claude mcp list，看一眼，看到Connected那一刻，仿佛听见她说了句，欢迎回家，主人。</p><p>呐，咱自觉枪毙去了。（元首：七万个嫂夫人挨个Biu！）</p><p>然后第一张图就失败了。直接给咱返回 403 。</p><p>查了半天，原因荒唐得让咱想笑，ModelScope 要求关联的阿里云账号完成实名认证。没过实名，连免费接口都不给用。咱万万没想到，给 AI 配一双眼睛，还得先向阿里云证明“咱是个人，不是狗”。虽然我确实是条单身狗。<br>认证完再试，终于通了。拿系统自带的壁纸C:\Windows\Web\Wallpaper\Windows\img0.jpg 丢给 analyze_image，中文描述正常返回。那一刻，我差点泪目，蛇了。</p><p>还有个不算坑的小事，1×1的纯色小图，模型啥也没认出来，返回空内容。想了想也算合理，你给朋友看个像素点，朋友回你个白眼，不把你打一顿，说“我涩图呢？”都算好的了。嘛，礼尚往来。</p><p>现在她能看见咱发过去的东西了。报错截图，设计稿，别人转来的图表，咱直接扔过去，她就能说出个子丑寅卯，不用咱再对着屏幕口述半天。剩下的事情反而简单。哪天 DeepSeek 自己长出视觉模型，这双借来的眼睛就该退休了。在那之前，先戴着吧。</p><h2 id="exFAT-的小插曲"><a class="heading-anchor" href="#exFAT-的小插曲" aria-hidden="true">#</a>exFAT 的小插曲</h2><p>装第三方东西之前，咱习惯把源码拉下来扫一眼，确认没藏奇奇怪怪的挖矿脚本。克隆到 H 盘，npm ci 一跑就死，25 分钟，最后报 ENOTEMPTY。咱愣了半天才反应过来 H 盘是 exFAT 格式，根本撑不起 node_modules 这种碎文件森林，跟往豆腐渣里插牙签似的。只好把依赖挪到 C 盘的 NTFS 上装，上游测试 70&#x2F;70 全过，才敢正式接上。审计副本留在咱电脑里，咱想看随时能看，毕竟防人之心不可无。</p><p><img src="/img/360px/with-her-eyes-2.jpg" loading="lazy" decoding="async"></p><p><img src="/img/360px/with-her-eyes-3.jpg" loading="lazy" decoding="async"></p>]]>
    </content>
    <id>https://twilightrain.com/2026/08/13/with-her-eyes/</id>
    <link href="https://twilightrain.com/2026/08/13/with-her-eyes/"/>
    <published>2026-08-13T03:10:00.000Z</published>
    <summary>Claude Code 底层套着 DeepSeek，看得见代码却看不见图。8 月 7 号给她装上 llm-vision-mcp，让她有了眼睛。</summary>
    <title>带上她的眼睛。</title>
    <updated>2026-08-26T05:06:32.651Z</updated>
  </entry>
  <entry>
    <author>
      <name>TwilightRain</name>
    </author>
    <category term="技术笔记" scheme="https://twilightrain.com/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="AI" scheme="https://twilightrain.com/tags/AI/"/>
    <category term="MIDI" scheme="https://twilightrain.com/tags/MIDI/"/>
    <category term="音乐" scheme="https://twilightrain.com/tags/%E9%9F%B3%E4%B9%90/"/>
    <content>
      <![CDATA[<p>有天突发奇想，能不能让AI直接给我写一首MIDI曲子？这个念头本身平平无奇，真正魔幻的是接下来一整个下午的连环事件。装技能，写歌，发现bug，给开源项目发issue，从网上下了一个老掉牙的MIDI文件，然后被那份陈年数据坑到怀疑人生。最后居然全都解决了，忍不住写下来纪念一下。</p><hr><h2 id="一，装技能，16次安装量的小众神作"><a class="heading-anchor" href="#一，装技能，16次安装量的小众神作" aria-hidden="true">#</a>一，装技能，16次安装量的小众神作</h2><p>需求很直接，AI帮我生成MIDI歌曲文件。我本地装了27个技能，没一个干这活的，于是去技能商店搜“midi”。</p><p>搜索结果挺热闹，但点开一看，全是挂羊头卖狗肉的。</p><ul><li><code>midi-synth</code>？那是把已有MIDI渲染成音频的，不是生成。</li><li><code>voice-note-to-midi</code>？人声转MIDI，方向反了。</li><li>ElevenLabs，Suno？那些是API生成音频文件，跟MIDI不搭边。</li></ul><p>真正对口的只有一个，<code>tubone24/midi-agent-skill@midi-generation</code>。安装量16，冷门中的冷门，但描述写的就是“用GM乐器和乐理生成MIDI文件”。工作方式是本地Python脚本加midiutil库生成<code>.mid</code>文件，免费，不用API。</p><p>按惯例先过一遍安全审查，<code>SKILL.md</code>内容干净，四个脚本通读一遍没啥恶意，<code>requirements.txt</code>里只有<code>midiutil</code>。装！</p><p>冒烟测试一次过，三轨多乐器的小样，228字节，跑起来了。</p><hr><h2 id="二，写ZUN歌，1分20秒"><a class="heading-anchor" href="#二，写ZUN歌，1分20秒" aria-hidden="true">#</a>二，写ZUN歌，1分20秒</h2><p>我<code>G:\Downloads</code>里躺着一份<code>主旋律.txt</code>，内容写的是「ZUN节」，车万里那段经典旋律。简谱长这样，<code>-6 2 3 5 3 2</code>循环三遍，再加个<code>-7 1 -7 -5</code>的尾巴。</p><p>第一版用的C大调，120BPM，大钢琴两轨（右手旋律，左手分解和弦），引子加主题四遍加尾声，总共40小节，正好80秒。拿<code>mido</code>库验证时长，80.0秒，完美。</p><p>然后我开始迭代，翻倍速度到240BPM，40秒播完，再把最后10秒剪到中间当bridge。<strong>就是从这一步开始，事情不对劲了。</strong></p><hr><h2 id="三，踩坑，休止符被吞，旋律凭空短了10秒"><a class="heading-anchor" href="#三，踩坑，休止符被吞，旋律凭空短了10秒" aria-hidden="true">#</a>三，踩坑，休止符被吞，旋律凭空短了10秒</h2><p>变速重排之后验证音轨，发现旋律轨30秒就播完了，伴奏轨却还在响。最后10秒只有伴奏孤零零地撑着场面。</p><p>排查下来，是技能的一个隐蔽bug，它的<code>Note</code>对象只有<code>pitch</code>和<code>duration</code>两个字段，生成脚本里音符时间是靠<code>time += duration</code>累加出来的，<strong>它根本没有表达休止符的能力</strong>。AI作曲的时候跳过休止拍，所有音符就全往前挪了，休止拍被吞掉，旋律整体前移。</p><p>第一版其实就有这毛病，但那会儿我只验证了总时长，没细听。80秒的文件，旋律早就结束了，我只听了开头觉得还不错，愣是没发现，直到重排结构时才暴露出来。</p><p>解决办法很简单，给技能打了个本地补丁，<code>Note</code>增加一个可选字段<code>time</code>，用来指定绝对拍位置，向后兼容。修完重新生成，各轨终于对位了。</p><hr><h2 id="四，迭代，从两轨钢琴到六轨小乐队"><a class="heading-anchor" href="#四，迭代，从两轨钢琴到六轨小乐队" aria-hidden="true">#</a>四，迭代，从两轨钢琴到六轨小乐队</h2><p>第一版只是开始。接下来我折腾了好几轮，</p><p><strong>变速加bridge重排。</strong> 想听快点，BPM 直接翻倍到 240，让80秒缩到40秒，又嫌结构平淡，再把最后10秒剪到中间当bridge，前20秒铺垫，中间10秒对比段，后面10秒接回主题，三段式结构有了，不过，也顺便埋了上一节那个大坑。</p><p><strong>补终止。</strong> 剪完发现结尾停在D4上，悬在半空不落地。补了个G-C终止，G和弦分解上行，C琶音上滚，C5长音收尾，四秒搞定。</p><p><strong>加音色。</strong> 纯钢琴太素了。我加了弦乐群铺和声长音，跟和弦走根音和五音，不抢旋律频段。又加了钢片琴在高八度点缀，只在bridge和终止段出现，带点空灵的味道。四轨了。</p><p><strong>换军鼓，加bass。</strong> 钢片琴点缀完，总觉得还缺节奏层。换成军鼓，结果发现技能<strong>根本没有打击乐通道的概念</strong>，它的脚本会特意跳过GM标准的9号通道（打击乐专用）。于是第二次给技能打补丁，<code>Note</code>增加可选<code>channel</code>字段，指定通道就覆盖自动分配。顺手捡了个便宜，军鼓的MIDI音号是38，正好等于音名D2，填<code>&quot;D2&quot;</code>就行，不用改解析器。bass也补上了，根音跟和弦走，低音区撑住。</p><p><strong>小号回归。</strong> 30秒处小号进，主旋律整体高八度再吹一遍，明亮的音色接回主题，跟钢片琴在终止段撞出一个八度齐奏的亮丽结尾。bass再往后压到20秒才进，让每个声部错峰登场。</p><p>最终定格在六轨，大钢琴旋律，大钢琴分解，弦乐pad，军鼓，bass，小号，44秒，240BPM。每一版我都用<code>mido</code>实测验证，时长，音符数，音轨位置全对得上才收工。诺，这里。</p><p><a href="/midi/ZUN_Festival_Ensemble_Full.mid">ZUN_Festival_Ensemble_Full.mid</a></p><hr><h2 id="五，发issue，冷门项目也有被认真对待的一天"><a class="heading-anchor" href="#五，发issue，冷门项目也有被认真对待的一天" aria-hidden="true">#</a>五，发issue，冷门项目也有被认真对待的一天</h2><p>修完顺手想，这个bug对原项目也是有用的，写个issue报上去吧。</p><p>按《提问的智慧》那套流程来，先查仓库有没有现成issue，其实没有，25颗星的小仓库很安静。然后套六要点模板，目标，环境，报错，已做功课，复现步骤，实际与预期。核心事实全是实测数据，160拍的曲子，旋律轨55.5秒结束，伴奏轨79.5秒。附上修复diff，标注了“未验证，可开PR”。</p><p>英文项目就写英文。发完。</p><hr><h2 id="六，老MIDI的复仇，2秒起，噪音炸了"><a class="heading-anchor" href="#六，老MIDI的复仇，2秒起，噪音炸了" aria-hidden="true">#</a>六，老MIDI的复仇，2秒起，噪音炸了</h2><p>issue发完，我转头想继续玩MIDI，从网上下了一份众神眷恋的幻想乡。202秒，3个音轨，931加882个音符。看文件头，GBK中文曲名，乐器名写着“Microsoft GS波表软件合成器”，千禧年代初的。给老资历跪了。</p><p>用<code>mido</code>一读，<strong>？！直接崩溃！？</strong>，<code>data byte must be in range 0..127</code>。</p><p>这可不是小问题。MIDI文件里出现了规范外的字节，严格解析器直接拒收。我用耳朵加PotPlayer试听，那噪音楼上张阿姨都听得见。</p><p>于是开始跟这份老文件斗智斗勇。先怀疑文件损坏，逐字节检查chunk边界，结构完好，三个音轨的长度字段和文件大小精确吻合。再怀疑音符数据损坏，我手动写了个容错解析器，音符流全干净。<strong>那到底在哪崩溃的？</strong></p><p>答案藏在两个地方，</p><p><strong>第一，<code>track0</code>有16个CC7音量消息，值全是<code>0xFF</code>（255）。</strong> MIDI规范里CC值只允许0-127，255是非法的，严格解析器当场暴毙。微软波表播放器大概见多识广，默默钳位成127继续播。</p><p><strong>第二，也是最精彩的，<code>track2</code>有19个CC64延音踏板消息，从1.74秒开始，值从85，86，87……一路递增到104，然后就没然后了，从头到尾没有释放。</strong></p><p>延音踏板是开关量，半踏板最多也是固定值。85→104，每秒两三个地递增，这不是踏板，这是<strong>别的数据被错标成了踏板</strong>，大概率是音量或表情的渐变，被某个老工具映射到了CC64。</p><p>而我听到的“从2-3秒开始的大噪音”，正是从1.74秒踏板踩下开始的。<code>track2</code>的每一个音符都被无限延音，持续叠加，两秒之后就混成了一片，越积越浑浊。时间线严丝合缝。</p><p>修复倒简单，删掉那19个CC64，把CC7改成127，保存。严格模式读取通过，音符一个不少，时长一分不差。试播，Windows自带播放器，PotPlayer，全都正常了。</p><hr><h2 id="七，事后"><a class="heading-anchor" href="#七，事后" aria-hidden="true">#</a>七，事后</h2><p>一整天下来，其实只干了一件小事，让AI写了一首MIDI。但沿途捡到了这些，</p><ul><li>一个安装量16的冷门技能（实测能用）</li><li>一个技能自身的静默bug（已修复，已发issue）</li><li>一份2000年代老东西的病历（CC64数据污染加非法CC7）</li><li>一个深刻教训，<strong>MIDI文件里最可怕的是控制器数据错</strong>。音符错了你听得出来，CC错了它会悄悄毁掉整首歌，还让严格解析器当场去世。</li></ul><p>哦对，还有一版修复好的众神眷恋的幻想乡 <a href="/midi/touhou_fixed.mid">touhou_fixed.mid</a> 躺在下载目录里。有缘人拿去吧，老曲子的修复版，免费。</p>]]>
    </content>
    <id>https://twilightrain.com/2026/08/08/ai-midi-adventure/</id>
    <link href="https://twilightrain.com/2026/08/08/ai-midi-adventure/"/>
    <published>2026-08-08T14:00:00.000Z</published>
    <summary>途中被各种MIDI错误坑到怀疑人生，最后居然全解决了。</summary>
    <title>AI写歌，怒怼老资历MIDI</title>
    <updated>2026-08-26T05:06:32.650Z</updated>
  </entry>
  <entry>
    <author>
      <name>TwilightRain</name>
    </author>
    <category term="技术笔记" scheme="https://twilightrain.com/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="AI" scheme="https://twilightrain.com/tags/AI/"/>
    <category term="Vibe Coding" scheme="https://twilightrain.com/tags/Vibe-Coding/"/>
    <category term="工作流" scheme="https://twilightrain.com/tags/%E5%B7%A5%E4%BD%9C%E6%B5%81/"/>
    <category term="方法论" scheme="https://twilightrain.com/tags/%E6%96%B9%E6%B3%95%E8%AE%BA/"/>
    <category term="LLM" scheme="https://twilightrain.com/tags/LLM/"/>
    <category term="质量保障" scheme="https://twilightrain.com/tags/%E8%B4%A8%E9%87%8F%E4%BF%9D%E9%9A%9C/"/>
    <content>
      <![CDATA[<blockquote><p>三个智能体，各司其职，之间不允许对话，听起来像职场宫斗，但这确实是我最近在用的代码审查工作流。</p></blockquote><p>如果你也经历过这种场景，让同一个 AI 既当找 bug 的、又当修 bug 的、还当审核的，最后它自己推翻自己，在同一个对话里反复横跳，那这篇文章可能对你有用。</p><h2 id="背景：我为什么受不了了"><a class="heading-anchor" href="#背景：我为什么受不了了" aria-hidden="true">#</a>背景：我为什么受不了了</h2><p>有段时间我一直在和 AI 对话写代码，就是那种「我出想法、AI 写实现」的 Vibe Coding 模式。听起来挺高效的对吧？</p><p>但实际操作起来有个很烦的问题：</p><p>同一个 AI 对话窗口里，我说「帮我看一下这段代码有什么 bug」。它列了五条。然后我说「那你改一下吧」。它改了，但改完之后自己又把其中两条「我刚刚说的其实也没那么严重」给推翻了。我再追问一句，它又开始往回找补，最后整个对话变成了一场 AI 和自己的辩论赛。</p><p>左脑搏击右脑，毫无生产力。</p><p>这个问题不是偶然的。LLM 的本质是对话模型，它的回答天然倾向于<strong>调和矛盾、保持一致性、尽量不得罪用户</strong>。你让它检查 bug，它查了。你让它反过来质疑自己查到的 bug，它大概率会说「你说得对，这里可能确实没那么严重」。</p><p>这不是 AI 笨，这是它的<strong>天性</strong>，它被训练成以对话者为中心。</p><p>那怎么办？我的答案是：<strong>不给它对话的机会</strong>。</p><h2 id="设计思路：流水线化与角色固化"><a class="heading-anchor" href="#设计思路：流水线化与角色固化" aria-hidden="true">#</a>设计思路：流水线化与角色固化</h2><p>我参考了制造业质量管理里的一个经典概念，<strong>质量门禁</strong>（Quality Gate）。在生产线上的每个关键节点设一道门，原材料进、成品出，中间单向流动，不允许逆流。</p><p>那么对于 AI 代码审查，我也设了三道门。三扇门后面站着三个 AI，每个有且只有一个角色，角色之间<strong>不对话、不辩论</strong>。</p><div class="mermaid"><pre><code>flowchart TD    A[原始代码] --&gt; B[AI-1 档案员&lt;br/&gt;Archivist]    B --&gt; C[事实锚点&lt;br/&gt;project_context.txt]    C --&gt; D[AI-2 啄木鸟&lt;br/&gt;Woodpecker]    D --&gt; E[审查报告&lt;br/&gt;audit_report_v1.md]    E --&gt; F[AI-3 铁匠&lt;br/&gt;Blacksmith]    F --&gt; G[最终方案&lt;br/&gt;final_action_plan.md]</code></pre></div><p>每个角色只做一件事，做完就输出一份确定的文档，然后<strong>切断对话</strong>，把这份文档原封不动地喂给下一个角色。</p><p>我把这个方法叫做 <strong>三子智能体单向质量门禁法</strong>（Triple-AI One-Way Quality Gate）。</p><p>下面展开说这三个角色的分工。</p><h2 id="第一阶段：AI-1-档案员"><a class="heading-anchor" href="#第一阶段：AI-1-档案员" aria-hidden="true">#</a>第一阶段：AI-1 档案员</h2><p>角色名字叫 Archivist，档案员。它的工作异常简单，<strong>什么也不分析，只收集事实</strong>。</p><p>输入是一堆源代码。</p><p>输出是一份叫 <code>project_context.txt</code> 的结构化文档，内容包括：目录树、项目元信息、配置文件全文、入口文件全文、核心源文件清单，以及一个上下文完整性声明。</p><h3 id="它不做什么"><a class="heading-anchor" href="#它不做什么" aria-hidden="true">#</a>它不做什么</h3><ul><li>不评价代码质量</li><li>不分析架构</li><li>不提出任何建议</li><li>不使用任何推测性词汇（「看起来」「似乎」「可能」「或许」「感觉」，全部禁止）</li></ul><h3 id="它做什么"><a class="heading-anchor" href="#它做什么" aria-hidden="true">#</a>它做什么</h3><p>全量扫描目录树，然后自动发现并读取以下几类文件：</p><ul><li><strong>配置文件</strong>：package.json、tsconfig.json、Dockerfile、.env.example 等</li><li><strong>入口文件</strong>：从 package.json 的 main&#x2F;bin&#x2F;exports 字段定位，找不到就按惯例搜</li><li><strong>核心源文件</strong>：沿 import 链读取直接依赖的核心模块（不超过 3 层、10 个文件）</li></ul><p>如果文件不存在，如实写「该文件不存在」。不解释原因，不脑补内容。</p><p>输出文档的最后一节是一张<strong>完整性声明表</strong>，坦承这个文件覆盖了项目多大比例、哪些类别的文件被收录、有没有已知的遗漏。遗漏处留空，由人工判断是否需要补充。</p><h3 id="为什么要有这道工序"><a class="heading-anchor" href="#为什么要有这道工序" aria-hidden="true">#</a>为什么要有这道工序</h3><p>因为 AI 的「分析」经常建立在错误的事实基础上。它可能猜错了你的目录结构、记混了你的配置文件、或者编造了一个不存在的依赖。<strong>先让一个角色纯粹地确认事实</strong>，后续的分析才不会在沙上建塔。</p><p>输出之后，窗口 A 关闭。</p><h2 id="第二阶段：AI-2-啄木鸟"><a class="heading-anchor" href="#第二阶段：AI-2-啄木鸟" aria-hidden="true">#</a>第二阶段：AI-2 啄木鸟</h2><p>角色叫 Woodpecker，啄木鸟。它是<strong>极端悲观主义者</strong>，只做减法，只找错。</p><p>输入是 <code>project_context.txt</code> + 核心代码片段。它不看原始项目目录，它只认档案员给的事实锚点。</p><h3 id="审查维度"><a class="heading-anchor" href="#审查维度" aria-hidden="true">#</a>审查维度</h3><p>审查分为四个级别：</p><table><thead><tr><th>级别</th><th>含义</th><th>示例</th></tr></thead><tbody><tr><td><strong>Error</strong></td><td>未捕获异常、安全漏洞、版本不兼容</td><td><code>src/api.ts L45 — 未对用户输入 name 做转义，直接拼入 SQL</code></td></tr><tr><td><strong>Risk</strong></td><td>内存泄漏、硬编码密钥、圈复杂度 &gt;10 的函数</td><td><code>utils/handler.ts L120 — setInterval 未存储返回值，页面卸载时未清除</code></td></tr><tr><td><strong>Redundancy</strong></td><td>未使用的依赖&#x2F;变量、重复度超过3次的代码块、过时的 Polyfill</td><td><code>package.json — lodash 仅使用了 _.get 方法，可改用可选链</code></td></tr><tr><td><strong>上下文缺口</strong></td><td>AI-1 遗漏的关键文件（兜底补偿）</td><td><code>src/middleware/auth.ts — 文件存在于目录树但未被 AI-1 收录，其 verifyToken 被多处路由引用</code></td></tr></tbody></table><p>输出格式是一张严格的结构化表格，有级别、文件路径、行号、现象描述、建议修正动作。依然是<strong>无开场白、无废话、无模糊评价</strong>。</p><p>值得一提的是「上下文缺口」这个维度，啄木鸟在审查开始前，会基于目录树检查有没有「存在但未被收录」的关键文件。发现缺口不会阻塞审查，而是如实列出，由人工决定是否补喂。这是一个人机协作的兜底机制。</p><p>输出之后，窗口 B 关闭。</p><h2 id="第三阶段：AI-3-铁匠"><a class="heading-anchor" href="#第三阶段：AI-3-铁匠" aria-hidden="true">#</a>第三阶段：AI-3 铁匠</h2><p>角色叫 Blacksmith，铁匠。它是<strong>极端实用主义者</strong>，不评价啄木鸟的智商，只拿着一份报告去对照真实代码，能做就做，做不了就改。</p><p>输入是 <code>project_context.txt</code> + <code>audit_report_v1.md</code>。</p><h3 id="三条验证准则"><a class="heading-anchor" href="#三条验证准则" aria-hidden="true">#</a>三条验证准则</h3><table><thead><tr><th>准则</th><th>含义</th><th>处理方式</th></tr></thead><tbody><tr><td><strong>妥适性</strong></td><td>建议删除的代码是否真的未被别处引用？</td><td>若被引用，直接删除该条目，不废话</td></tr><tr><td><strong>准确性</strong></td><td>建议的行号是否精确？</td><td>不精确则直接修正</td></tr><tr><td><strong>可落地性</strong></td><td>建议是否受限于框架版本？</td><td>重写该条目的修正动作，使其兼容当前环境</td></tr></tbody></table><p>输出是 <code>final_action_plan.md</code>，格式和啄木鸟的报告完全一样。</p><h3 id="语气约束（强制注入）"><a class="heading-anchor" href="#语气约束（强制注入）" aria-hidden="true">#</a>语气约束（强制注入）</h3><p>我给铁匠设了一段不可绕过的 System Prompt，重点只有一句话：</p><blockquote><p><strong>禁止展示思考过程、对比过程或发现错误的过程。只输出最终的修正结果。</strong></p></blockquote><p>这意味着铁匠看到「AI-2 说这里要删某行」，对照代码发现那行另有引用，它不会说「等等，这里不对，AI-2 你搞错了」，它只是静默地把那条条目从表格里删掉，直接输出修正后的表格。</p><p>矛盾处理遵循<strong>最小改动原则</strong>：如果两条建议冲突（比如一个建议删文件、另一个建议改该文件），保留改动最小的那条，删除改动大的那条。</p><h2 id="完整工作流示意图"><a class="heading-anchor" href="#完整工作流示意图" aria-hidden="true">#</a>完整工作流示意图</h2><div class="mermaid"><pre><code>flowchart TD    A[原始项目代码] --&gt;|AI-1 一次性探测扫描| B[AI-1 档案员&lt;br/&gt;Archivist&lt;br/&gt;只收集事实，不分析不判断]    B --&gt;|只输出结构化事实陈述| C[project_context.txt&lt;br/&gt;事实锚点文件 含完整性声明]    C --&gt;|操作者快速浏览| D{完整性声明确认}    D --&gt;|发现遗漏| E[手动补充至文件]    E --&gt; C    D --&gt;|确认无误&lt;br/&gt;投喂 context + 核心代码片段| F[AI-2 啄木鸟&lt;br/&gt;Woodpecker&lt;br/&gt;只做减法：找错、删减、警告]    F --&gt;|只输出结构化表格 含上下文缺口| G[audit_report_v1.md&lt;br/&gt;初版审查报告]    G --&gt;|投喂 context + audit_report_v1.md| H[AI-3 铁匠&lt;br/&gt;Blacksmith&lt;br/&gt;验证、修正、落地]    H --&gt;|只输出修正后表格| I[final_action_plan.md&lt;br/&gt;铁板钉钉的施工图纸]    I --&gt; J[最终执行&lt;br/&gt;三方再无交流]</code></pre></div><h2 id="实际操作：如何避免左脑搏击右脑"><a class="heading-anchor" href="#实际操作：如何避免左脑搏击右脑" aria-hidden="true">#</a>实际操作：如何避免左脑搏击右脑</h2><p>一开始我用的是三个连续 Prompt 的写法，同一个对话窗口里依次让 AI 扮演档案员、啄木鸟、铁匠。效果有，但不够干净。</p><p>因为 AI 记得它上一轮扮演过档案员、写过哪些文件、发现了什么问题。到铁匠那一步，它天然地放水，「这条是我自己（作为啄木鸟时）写的，应该没问题吧」。</p><p>第一次迭代是：<strong>开三个独立的对话窗口。</strong></p><table><thead><tr><th>窗口</th><th>角色</th><th>操作</th></tr></thead><tbody><tr><td>窗口 A</td><td>档案员</td><td>执行完毕 → 提取 <code>project_context.txt</code> → <strong>关闭窗口 A</strong></td></tr><tr><td>窗口 B</td><td>啄木鸟</td><td>粘贴 <code>project_context.txt</code> 作为输入 → 输出报告 → <strong>关闭窗口 B</strong></td></tr><tr><td>窗口 C</td><td>铁匠</td><td>粘贴 <code>project_context.txt</code> + <code>audit_report_v1.md</code> 作为输入</td></tr></tbody></table><p>关掉窗口意味着物理上切断记忆。新的窗口没有之前的上下文，AI 不知道啄木鸟是谁、档案员是谁、它自己上一轮说过什么。它只能对着当前的事实锚点和报告条目逐一验证。</p><p>这听起来有点笨拙，但实际用下来效果确实好。<strong>上下文隔离是阻止 AI 自我推翻最有效的手段</strong>，没有之一。</p><p>第二次迭代：在子智能体协作模式下，上下文隔离是内置设计，无需人工干预。三个智能体各自运行于独立的、无状态的会话中，彼此不共享任何历史记忆，每一次调用都如同首次见面。数据流由编排层自动驱动，每个智能体完成自己的输出后，其会话即被销毁，下一个智能体只能看到上游产出的文档，而无法感知之前的对话过程。</p><table><thead><tr><th>智能体</th><th>角色</th><th>操作</th></tr></thead><tbody><tr><td>智能体 A</td><td>档案员</td><td>执行完毕，生成 <code>project_context.txt</code>，自动触发智能体 B</td></tr><tr><td>智能体 B</td><td>啄木鸟</td><td>读取 <code>project_context.txt</code> 作为输入，输出 <code>audit_report_v1.md</code>，自动触发智能体 C</td></tr><tr><td>智能体 C</td><td>铁匠</td><td>读取 <code>project_context.txt</code> + <code>audit_report_v1.md</code> 作为输入，输出最终方案</td></tr></tbody></table><p>这套流程不再需要人工开三个窗口、手动关闭。智能体之间的衔接由流水线调度器完成，每个智能体以无状态函数的方式被调用，上下文天然隔离。而在子智能体协作模式里，它不再是笨拙的权宜之计，而是架构层面的原生特性。</p><h2 id="用了几次之后的感受"><a class="heading-anchor" href="#用了几次之后的感受" aria-hidden="true">#</a>用了几次之后的感受</h2><p>目前这个工作流我主要用在项目代码审查和大型重构的预检阶段。说几个实际体感：</p><p><strong>档案员是最容易被低估的角色。</strong> 很多人觉得「扫描目录+读文件」没什么技术含量，直接让 AI 一步到位审查不就行了。但事实是，缺少这一步，啄木鸟的分析经常建立在对代码结构一知半解的基础上。有了事实锚点，后面两阶段的产出质量明显更稳定。</p><p><strong>上下文缺口这个兜底机制有用。</strong> 档案员扫描目录时，可能因为文件过多或者规则限制，漏掉了某些关键文件。啄木鸟从目录树里发现这些缺口并列出，至少让人知道自己「不知道什么」，这比盲审要好得多。</p><p><strong>铁匠的角色定位是关键创新。</strong> 大多数代码审查工具只做「检查→报告」两步。加入一个「验证→修正」的第三阶段，并且强制这个阶段<strong>不输出解释、只输出结果</strong>，有效地过滤掉了 AI 自己的错觉和过度报告。</p><h2 id="总结"><a class="heading-anchor" href="#总结" aria-hidden="true">#</a>总结</h2><p>三子智能体单向质量门禁法的核心就三点：</p><ol><li><strong>角色固化</strong>，每个 AI 只做一件事，不跨界</li><li><strong>单向数据流</strong>，上游输出就是下游输入，没有反馈回路，禁止对话</li><li><strong>上下文隔离</strong>，物理上切断记忆窗口，让 AI 无法自我推翻</li></ol><p>如果你也在用 AI 做代码审查，不妨试试这个模式。不一定需要三个角色完全照搬，但「<strong>把它拆成独立步骤，每一步只做一件事，切掉对话回路</strong>」这个思路，应该是通用的。</p><p>方法总比解法多。尤其是在和 AI 协作这件事上，我觉得与其抱怨 AI 不靠谱，不如想想怎么设计流程，让它不靠谱的部分够不到终点线。</p>]]>
    </content>
    <id>https://twilightrain.com/2026/07/30/triple-ai-quality-gate/</id>
    <link href="https://twilightrain.com/2026/07/30/triple-ai-quality-gate/"/>
    <published>2026-07-30T06:00:00.000Z</published>
    <summary>Vibe Coding 质量由找，审核与修 bug的 AI 各司其职保证。</summary>
    <title>三AI单向门禁的流水线</title>
    <updated>2026-08-26T05:06:32.651Z</updated>
  </entry>
  <entry>
    <author>
      <name>TwilightRain</name>
    </author>
    <category term="项目故事" scheme="https://twilightrain.com/categories/%E9%A1%B9%E7%9B%AE%E6%95%85%E4%BA%8B/"/>
    <category term="TextTool" scheme="https://twilightrain.com/tags/TextTool/"/>
    <category term="Windows" scheme="https://twilightrain.com/tags/Windows/"/>
    <category term=".NET" scheme="https://twilightrain.com/tags/NET/"/>
    <category term="C#" scheme="https://twilightrain.com/tags/C/"/>
    <category term="WinForms" scheme="https://twilightrain.com/tags/WinForms/"/>
    <category term="开源" scheme="https://twilightrain.com/tags/%E5%BC%80%E6%BA%90/"/>
    <category term="工具" scheme="https://twilightrain.com/tags/%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<blockquote><p>集行合并，文件拼接，中文截断修复，标点替换于一体</p></blockquote><p>写代码的人大概都经历过这样的时刻：手头有一批文本文件需要处理，合并短行，拼接文件，修复莫名其妙被截断的中文，批量替换标点符号。需求本身不复杂，但每次都要翻出零散的批处理脚本，PowerShell 命令，或者临时写一段 C# 控制台程序，用完就扔，下次再从头来过。</p><p>这些碎片化的处理方式困扰了我很久。于是我把它们攒到了一起，做成了一个 Windows 桌面工具，<strong>TwilightRainTextTool</strong>。</p><a class="card-github" href="https://github.com/TwilightRainDev/TwilightRainTextTool" target="_blank"  rel="noopener noreferrer" data-repo="TwilightRainDev/TwilightRainTextTool"><div class="gc-titlebar"><div class="gc-titlebar-left"><span class="gc-owner">TwilightRainDev</span><span class="gc-divider">/</span><span class="gc-repo">TwilightRainTextTool</span></div><span class="github-logo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg></span></div><div class="gc-description">项目地址</div><div class="gc-meta"><span class="gc-stars"></span><span class="gc-forks"></span><span class="gc-language"></span><span class="gc-license"></span></div></a><h2 id="为什么会有这个工具"><a class="heading-anchor" href="#为什么会有这个工具" aria-hidden="true">#</a>为什么会有这个工具</h2><p>最开始，我维护着四五个功能重叠的批处理&#x2F;PowerShell 脚本，散落在不同项目的角落里。每次要用的时候，得先回忆”那个处理中文截断的脚本放在哪个文件夹了”，再手动改参数，拖文件，等执行。更麻烦的是，这些脚本的输入输出格式还不统一，有的原地修改，有的生成新文件，有的需要手动指定编码。</p><p>需求是固定的，但每次处理的成本却是重复的。与其每次都写一次胶水代码，不如做一个统一的 GUI 工具，把常用的文本处理能力集成进去，点点鼠标就能完成。</p><h2 id="它能做什么"><a class="heading-anchor" href="#它能做什么" aria-hidden="true">#</a>它能做什么</h2><p>工具目前提供了四个核心功能，分别对应四个标签页。</p><h3 id="1-行合并"><a class="heading-anchor" href="#1-行合并" aria-hidden="true">#</a>1. 行合并</h3><p>这是最常用的功能。把一堆短行按阈值合并成有意义的段落，比如把被意外拆散的文本重新拼接起来。支持按<strong>字节数</strong>或<strong>字符数</strong>两种模式合并，阈值范围 1 到 1000，可以灵活调整。</p><p>合并之后还有三道后处理工序：</p><ul><li><strong>修复中文截断</strong>：如果当前行以中文字符结尾，自动合并下一行（<code>Hello\nWorld</code> → <code>HelloWorld</code>）</li><li><strong>修复标点截断</strong>：如果当前行以自定义标点结尾，自动合并下一行（<code>回家吧，\n孩子</code> → <code>回家吧，孩子</code>）</li><li><strong>行尾不合并</strong>：如果当前行以特定标点（如句号，感叹号，章节标题标记）结尾，则阻止合并，保持独立</li></ul><p>整个处理是<strong>单次文件写入</strong>的流水线：读文件 → 阈值合并 → 中文修复 → 标点截断修复 → 标点替换 → 写输出。</p><h3 id="2-文件拼接"><a class="heading-anchor" href="#2-文件拼接" aria-hidden="true">#</a>2. 文件拼接</h3><p>把一个目录下匹配通配符模式的所有文件按名称排序后合并成一个文件。每个文件会单独检测编码，统一以 <strong>UTF-8 with BOM</strong> 格式输出。</p><h3 id="3-标点替换"><a class="heading-anchor" href="#3-标点替换" aria-hidden="true">#</a>3. 标点替换</h3><p>自由配置查找-替换规则（比如把连续的句号 <code>。。</code> 替换成单个 <code>。</code>），支持规则的增删改和排序。规则以 JSON 格式持久化到 <code>replace_rules.json</code>，重启程序后自动加载。</p><h3 id="4-关于页面"><a class="heading-anchor" href="#4-关于页面" aria-hidden="true">#</a>4. 关于页面</h3><p>显示版本，作者信息和 GitHub 链接，并提供了<strong>语言切换</strong>功能，简体中文，繁體中文，English 三语实时切换，偏好自动保存。</p><h2 id="一些设计上的考量"><a class="heading-anchor" href="#一些设计上的考量" aria-hidden="true">#</a>一些设计上的考量</h2><p>在开发这个工具时，我给自己定了几条原则：</p><p><strong>安全第一</strong>。所有处理结果都保存为 <code>*_Processed.*</code> 格式的新文件，绝不覆盖原始文件。误操作了？原始文件还在，随时可以重来。</p><p><strong>编码自动检测</strong>。文本处理最头疼的问题就是编码。工具会优先检测 BOM，其次尝试 UTF-8 验证，最后 fallback 到 GBK。大多数常见的中文文本文件都能自动识别，不需要用户手动选择编码。</p><p><strong>批量处理</strong>。支持一次拖拽多个文件，全部处理完毕。不用一个一个等。</p><p><strong>拖拽即用</strong>。直接把 <code>.txt</code> 文件拖到窗口上就能开始处理。操作路径尽可能短。</p><p><strong>国际化</strong>。内置了三种语言，并根据系统 UI 语言自动匹配。虽然是个小工具，但多语言支持从第一天就做了。</p><h2 id="技术实现"><a class="heading-anchor" href="#技术实现" aria-hidden="true">#</a>技术实现</h2><p>工具基于 <strong>.NET 7 WinForms</strong> 开发。架构上做了比较清晰的分层：</p><ul><li><strong>Controls&#x2F;</strong>：四个标签页的 UI 控件，从主窗体独立出来，保持 MainForm 只有约 165 行</li><li><strong>Services&#x2F;</strong>：核心业务逻辑，编码检测，行合并算法，文件拼接，标点替换，处理流水线编排</li><li><strong>Localization&#x2F;</strong>：三语 JSON 资源文件 + 运行时切换逻辑</li><li><strong>TextTool.Tests&#x2F;</strong>：63 个单元测试，覆盖各个服务类</li></ul><p>整个项目遵循了比较规整的项目结构，也写了架构文档（<code>ARCHITECTURE.md</code>）和架构决策记录（<code>adr/</code>），方便后续维护和他人理解。</p><h2 id="写在最后"><a class="heading-anchor" href="#写在最后" aria-hidden="true">#</a>写在最后</h2><p>TwilightRainTextTool 不是一个宏大的项目，它解决的是我日常工作中真实遇到的小问题。但正是这些小工具，积少成多，最终能节省下可观的时间。</p><p>如果你也有类似的文本处理需求，欢迎下载试用。项目开源在 GitHub 上，Issue 和 PR 都随时欢迎。</p>]]>
    </content>
    <id>https://twilightrain.com/2026/07/25/windows-text-tool-intro/</id>
    <link href="https://twilightrain.com/2026/07/25/windows-text-tool-intro/"/>
    <published>2026-07-25T05:00:00.000Z</published>
    <summary>把散落四处的文本处理脚本，统一成图形化工具箱。</summary>
    <title>用Win文本工具箱告别散装脚本</title>
    <updated>2026-08-26T05:06:32.651Z</updated>
  </entry>
  <entry>
    <author>
      <name>TwilightRain</name>
    </author>
    <category term="安全研究" scheme="https://twilightrain.com/categories/%E5%AE%89%E5%85%A8%E7%A0%94%E7%A9%B6/"/>
    <category term="逆向" scheme="https://twilightrain.com/tags/%E9%80%86%E5%90%91/"/>
    <category term="安全" scheme="https://twilightrain.com/tags/%E5%AE%89%E5%85%A8/"/>
    <category term="恶意软件" scheme="https://twilightrain.com/tags/%E6%81%B6%E6%84%8F%E8%BD%AF%E4%BB%B6/"/>
    <category term="Joker" scheme="https://twilightrain.com/tags/Joker/"/>
    <category term="Android" scheme="https://twilightrain.com/tags/Android/"/>
    <category term="仿冒网站" scheme="https://twilightrain.com/tags/%E4%BB%BF%E5%86%92%E7%BD%91%E7%AB%99/"/>
    <category term="维权" scheme="https://twilightrain.com/tags/%E7%BB%B4%E6%9D%83/"/>
    <content>
      <![CDATA[<p>从发现仿冒到逆向取证，再到提交网警报案，这是我完整的维权实录。</p><hr><p>前几天，我在搜索”蓝河工具箱”的时候，发现了一个仿冒网站。</p><p><strong><a href="http://www.lanhegongjuxiang.com/" rel="noopener noreferrer">www.lanhegongjuxiang.com</a></strong></p><p>名字起得挺像那么回事，”蓝河工具箱官网”。页面风格也刻意模仿正规工具类软件的调性，口号是什么”免费手机系统优化”、”全局插帧直驱供”……</p><p>要不是我一直在用正版App，差点就信了。</p><h2 id="下载下来的东西，不对劲"><a class="heading-anchor" href="#下载下来的东西，不对劲" aria-hidden="true">#</a>下载下来的东西，不对劲</h2><p>我在这个网站下载了一个APK，文件名是 <code>lhgjx.apk</code>。</p><p>一对比就发现问题了，正版”蓝河工具箱 6.11.apk”只有 <strong>4.7 MB</strong>，而仿冒版有 <strong>35 MB</strong>。</p><p>35 MB 的工具箱？这得塞了多少东西进去？</p><p>直觉告诉我，这事没那么简单。</p><h2 id="解包对比：一个工具箱，一个漫画阅读器"><a class="heading-anchor" href="#解包对比：一个工具箱，一个漫画阅读器" aria-hidden="true">#</a>解包对比：一个工具箱，一个漫画阅读器</h2><p>我虽然不是开发者，但平时也懂一点反编译工具，把两个APK都拆开看了看，结果一目了然。</p><table><thead><tr><th>对比项</th><th>正版</th><th>仿冒版</th></tr></thead><tbody><tr><td>包名</td><td><code>com.bluestudiocjd.app</code></td><td><code>com.bly.dkplat</code></td></tr><tr><td>技术栈</td><td>原生 Android (Kotlin)</td><td>Flutter</td></tr><tr><td>应用类型</td><td>手机工具箱</td><td>漫画阅读器</td></tr><tr><td>大小</td><td>4.7 MB</td><td>35 MB</td></tr><tr><td>原生库</td><td><code>libadb.so</code>、<code>libintegrity.so</code></td><td><code>libflutter.so</code>、多个广告SDK</td></tr></tbody></table><p>说白了，这个仿冒版和”工具箱”半毛钱关系都没有。它就是一个披着”蓝河工具箱”名字的 Flutter 漫画 App。</p><p>里面的 <code>list.json</code> 暴露了一切：</p><ul><li>精选：《这个神兽有点萌之通天嗜宠》</li><li>更新：《展示魅力时刻》</li><li>新作：《我的同学》</li><li>人气：《恶魔在哪里》</li><li>恋爱：《苏三起界》</li><li>热血：《妖娆妻》</li></ul><p>这是工具箱？这分明是漫画阅读器。</p><h2 id="关键问题：它抄了正版代码吗？"><a class="heading-anchor" href="#关键问题：它抄了正版代码吗？" aria-hidden="true">#</a>关键问题：它抄了正版代码吗？</h2><p>我沿着证据往下查，搜索了仿冒版全部 3 个 DEX 文件（共约 16 MB 代码）：</p><ul><li>搜索 <code>bluestudiocjd</code>（正版开发者标识）：正版命中 100+ 处，仿冒版 <strong>0</strong> 处</li><li>搜索”蓝河”&#x2F;<code>lanhe</code>：正版有引用，仿冒版 <strong>0</strong> 处</li><li>搜索 ADB、无线调试等功能关键词：正版有，仿冒版 <strong>0</strong> 处</li></ul><p><strong>结论很明确</strong>：这个仿冒 App <strong>没有使用任何正版源代码</strong>。它只是盗用了”蓝河工具箱”的品牌名称和视觉标识。</p><p>这不是开源协议违规，因为根本没有”衍生”，它完全是另一个东西。</p><p><strong>这是商标侵权加不正当竞争。</strong></p><h2 id="真正可怕的事情，在后面"><a class="heading-anchor" href="#真正可怕的事情，在后面" aria-hidden="true">#</a>真正可怕的事情，在后面</h2><p>既然代码上没有抄袭，那这个 35 MB 的”漫画阅读器”里，到底装了什么？</p><p>我继续深挖，结果让人头皮发麻。</p><h3 id="一、它要了-28-个敏感权限"><a class="heading-anchor" href="#一、它要了-28-个敏感权限" aria-hidden="true">#</a>一、它要了 28 个敏感权限</h3><p>一个漫画阅读器，要这些权限干什么？</p><table><thead><tr><th>权限级别</th><th>具体权限</th></tr></thead><tbody><tr><td><strong>恶意级</strong></td><td><code>INSTALL_PACKAGES</code>（静默安装）、<code>REQUEST_INSTALL_PACKAGES</code>（绕过商店安装）、<code>MANAGE_EXTERNAL_STORAGE</code>（全盘读写）、<code>SYSTEM_ALERT_WINDOW</code>（悬浮窗）</td></tr><tr><td><strong>追踪级</strong></td><td><code>ACCESS_FINE_LOCATION</code>（精确定位）、<code>CAMERA</code>（摄像头）、<code>RECORD_AUDIO</code>（录音）、<code>READ_PHONE_STATE</code>（IMEI）、<code>GET_ACCOUNTS</code>（账户信息）</td></tr><tr><td><strong>高风险</strong></td><td><code>READ_CONTACTS</code>、<code>READ_CALL_LOG</code>、<code>WRITE_CALL_LOG</code>、<code>READ_SMS</code>……</td></tr></tbody></table><h3 id="二、疑似-Joker-家族恶意软件"><a class="heading-anchor" href="#二、疑似-Joker-家族恶意软件" aria-hidden="true">#</a>二、疑似 Joker 家族恶意软件</h3><p><strong>Joker</strong> 是臭名昭著的 Android 木马家族，专门干这种事：</p><ul><li><strong>自动订阅扣费</strong>：拦截短信验证码，后台订阅付费服务，从话费扣款</li><li><strong>动态加载恶意代码</strong>：使用 <code>DexClassLoader</code> 运行时加载恶意 payload，逃避静态检测</li><li><strong>隐藏图标</strong>：<code>FLAG_HINT_HIDE_ICON</code>、<code>setHintHideIcon</code> 相关代码</li><li><strong>拦截或转发短信</strong>：SMS 拦截相关代码</li></ul><p>我在代码里找到了这些特征的实锤：</p><table><thead><tr><th>恶意行为</th><th>证据数量</th></tr></thead><tbody><tr><td>订阅扣费</td><td>5 处</td></tr><tr><td>隐藏图标</td><td>3 处</td></tr><tr><td>自动安装</td><td>2 处</td></tr><tr><td>读取验证码</td><td>1 处</td></tr><tr><td>动态加载 DEX</td><td>5 处</td></tr></tbody></table><h3 id="三、极端隐私窃取"><a class="heading-anchor" href="#三、极端隐私窃取" aria-hidden="true">#</a>三、极端隐私窃取</h3><p>代码中引用的数据采集点数量（反编译统计）：</p><table><thead><tr><th>数据类型</th><th>引用次数</th></tr></thead><tbody><tr><td>IMEI 获取</td><td>36 处</td></tr><tr><td>MAC 地址</td><td>249 处</td></tr><tr><td>位置 &#x2F; GPS</td><td>392 处</td></tr><tr><td>OAID &#x2F; 广告 ID</td><td>100 处</td></tr><tr><td>设备指纹</td><td>199 处</td></tr><tr><td>已安装 App 列表</td><td>22 处</td></tr><tr><td>账户信息</td><td>7 处</td></tr><tr><td>联系人 &#x2F; 通话记录</td><td>5 处</td></tr></tbody></table><p>这些数据一旦外传，可以精确识别每一个用户，你是谁、在哪、用什么手机、装了什么软件、通讯录里都有谁。</p><h3 id="四、堆了-4-家广告-SDK"><a class="heading-anchor" href="#四、堆了-4-家广告-SDK" aria-hidden="true">#</a>四、堆了 4 家广告 SDK</h3><ul><li>穿山甲（Pangle）</li><li>友盟（Umeng）</li><li>Sigmob</li><li>广点通（GDT）</li></ul><p>一个漫画 App 塞 4 家广告 SDK，目的只有一个：<strong>广告欺诈</strong>，伪造设备指纹、刷广告点击、骗广告主预算。</p><h2 id="我给它的定性"><a class="heading-anchor" href="#我给它的定性" aria-hidden="true">#</a>我给它的定性</h2><p>这不是开源协议问题，也不是简单的商标侵权。</p><p><strong>这是一款披着”工具箱”外衣的 Joker 家族恶意软件，通过仿冒网站公开传播：</strong></p><ol><li>盗用品牌名称和视觉标识，诱导用户下载</li><li>索要远超正常需求的 28 项敏感权限</li><li>具备静默安装、动态加载、短信拦截等恶意能力</li><li>采集 IMEI、位置、联系人、通话记录等隐私数据</li><li>堆叠 4 家广告 SDK，疑似广告欺诈</li><li>符合 Joker 自动订阅木马的行为特征</li></ol><p>这已经触犯 <strong>《刑法》第 285 条和第 286 条</strong>，提供专门用于侵入、非法控制计算机信息系统的程序、工具罪。</p><h2 id="我已经提交网报警报了"><a class="heading-anchor" href="#我已经提交网报警报了" aria-hidden="true">#</a>我已经提交网报警报了</h2><table><thead><tr><th>项目</th><th>内容</th></tr></thead><tbody><tr><td>举报编号</td><td>J2026072510234300239</td></tr><tr><td>举报类型</td><td>计算机破坏 &#x2F; 传播病毒</td></tr><tr><td>违法网站</td><td><a href="https://www.lanhegongjuxiang.com/" rel="noopener noreferrer">https://www.lanhegongjuxiang.com</a></td></tr><tr><td>仿冒 APK 哈希</td><td><code>22b22eba760c3232c97e77009726501d0c7092370e1b06b2aaa867aa1a07df7d</code></td></tr><tr><td>状态</td><td>待人工核查</td></tr></tbody></table><p>举报描述中，我把品牌混淆到用户误信下载，再到逆向分析取证，最后到恶意代码定性的完整证据链都提交了。</p><h2 id="给你的提醒"><a class="heading-anchor" href="#给你的提醒" aria-hidden="true">#</a>给你的提醒</h2><p>如果你也在用”蓝河工具箱”或者类似的工具类软件，请注意：</p><ol><li><strong>认准官方渠道</strong>：不要从搜索引擎随便点开一个”官网”就下载</li><li><strong>安装时仔细看权限</strong>：一个工具箱要你的通讯录、短信、通话记录？直接拒绝</li><li><strong>对比 APK 大小</strong>：4.7 MB 变成 35 MB，一定有鬼</li><li><strong>这个仿冒网站的”工具箱”实际上是一个漫画阅读器加木马</strong></li></ol><p>我已经走了报案流程。如果你也下载过这个 APK，建议：</p><ul><li><strong>立即卸载</strong> <code>lhgjx.apk</code> 及任何来自 <code>lanhegongjuxiang.com</code> 的软件</li><li><strong>修改重要账号密码</strong>，尤其是短信验证码可能已被拦截的情况下</li><li><strong>向同一平台举报</strong>，增加这个仿冒网站的下架压力</li></ul><h2 id="最后"><a class="heading-anchor" href="#最后" aria-hidden="true">#</a>最后</h2><p>我本来只是想查一下这个仿冒网站到底怎么回事，结果挖出了这么个东西。</p><p>从”有人盗用正版名字”到”有人在分发病毒”，这个跨度，我自己都没预料到。</p><p>网络安全无小事。如果你身边有朋友在用”蓝河工具箱”，把这篇转给他看看。别让恶意软件披着正经工具的外衣，躺进他的手机。</p><p>后续进展我会持续跟进。有新的消息再跟大家同步。</p>]]>
    </content>
    <id>https://twilightrain.com/2026/07/25/fake-toolbox-malware-investigation/</id>
    <link href="https://twilightrain.com/2026/07/25/fake-toolbox-malware-investigation/"/>
    <published>2026-07-25T03:00:00.000Z</published>
    <summary>这是从发现仿冒站，确认Joker病毒到报案的全过程维权实录。</summary>
    <title>举报仿冒网站，实为病毒</title>
    <updated>2026-08-26T05:06:32.651Z</updated>
  </entry>
  <entry>
    <author>
      <name>TwilightRain</name>
    </author>
    <category term="代码仓库" scheme="https://twilightrain.com/categories/%E4%BB%A3%E7%A0%81%E4%BB%93%E5%BA%93/"/>
    <category term="BiliCompact" scheme="https://twilightrain.com/tags/BiliCompact/"/>
    <category term="Tampermonkey" scheme="https://twilightrain.com/tags/Tampermonkey/"/>
    <category term="JavaScript" scheme="https://twilightrain.com/tags/JavaScript/"/>
    <category term="源码" scheme="https://twilightrain.com/tags/%E6%BA%90%E7%A0%81/"/>
    <category term="前端" scheme="https://twilightrain.com/tags/%E5%89%8D%E7%AB%AF/"/>
    <category term="产品设计" scheme="https://twilightrain.com/tags/%E4%BA%A7%E5%93%81%E8%AE%BE%E8%AE%A1/"/>
    <category term="架构" scheme="https://twilightrain.com/tags/%E6%9E%B6%E6%9E%84/"/>
    <content>
      <![CDATA[<blockquote><p>以下为 BiliCompact 用户脚本 v2.6.0 的完整源码，MIT 协议，可自由使用、修改、分发。<br>安装方法：复制到 Tampermonkey &#x2F; Violentmonkey 新建脚本中保存即可。<br>从动机、设计、技术三个维度完整回顾这个非侵入式的 B 站首页精简用户脚本项目。</p></blockquote><a class="card-github" href="https://github.com/TwilightRainDev/TwilightRainBiliCompact" target="_blank"  rel="noopener noreferrer" data-repo="TwilightRainDev/TwilightRainBiliCompact"><div class="gc-titlebar"><div class="gc-titlebar-left"><span class="gc-owner">TwilightRainDev</span><span class="gc-divider">/</span><span class="gc-repo">TwilightRainBiliCompact</span></div><span class="github-logo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg></span></div><div class="gc-description">BiliCompact 官方仓库：完整源码、版本发布与 issue 追踪</div><div class="gc-meta"><span class="gc-stars"></span><span class="gc-forks"></span><span class="gc-language"></span><span class="gc-license"></span></div></a><p>项目地址：<a href="https://greasyfork.org/scripts/585777" rel="noopener noreferrer">GreasyFork - BiliCompact</a></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br><span class="line">376</span><br><span class="line">377</span><br><span class="line">378</span><br><span class="line">379</span><br><span class="line">380</span><br><span class="line">381</span><br><span class="line">382</span><br><span class="line">383</span><br><span class="line">384</span><br><span class="line">385</span><br><span class="line">386</span><br><span class="line">387</span><br><span class="line">388</span><br><span class="line">389</span><br><span class="line">390</span><br><span class="line">391</span><br><span class="line">392</span><br><span class="line">393</span><br><span class="line">394</span><br><span class="line">395</span><br><span class="line">396</span><br><span class="line">397</span><br><span class="line">398</span><br><span class="line">399</span><br><span class="line">400</span><br><span class="line">401</span><br><span class="line">402</span><br><span class="line">403</span><br><span class="line">404</span><br><span class="line">405</span><br><span class="line">406</span><br><span class="line">407</span><br><span class="line">408</span><br><span class="line">409</span><br><span class="line">410</span><br><span class="line">411</span><br><span class="line">412</span><br><span class="line">413</span><br><span class="line">414</span><br><span class="line">415</span><br><span class="line">416</span><br><span class="line">417</span><br><span class="line">418</span><br><span class="line">419</span><br><span class="line">420</span><br><span class="line">421</span><br><span class="line">422</span><br><span class="line">423</span><br><span class="line">424</span><br><span class="line">425</span><br><span class="line">426</span><br><span class="line">427</span><br><span class="line">428</span><br><span class="line">429</span><br><span class="line">430</span><br><span class="line">431</span><br><span class="line">432</span><br><span class="line">433</span><br><span class="line">434</span><br><span class="line">435</span><br><span class="line">436</span><br><span class="line">437</span><br><span class="line">438</span><br><span class="line">439</span><br><span class="line">440</span><br><span class="line">441</span><br><span class="line">442</span><br><span class="line">443</span><br><span class="line">444</span><br><span class="line">445</span><br><span class="line">446</span><br><span class="line">447</span><br><span class="line">448</span><br><span class="line">449</span><br><span class="line">450</span><br><span class="line">451</span><br><span class="line">452</span><br><span class="line">453</span><br><span class="line">454</span><br><span class="line">455</span><br><span class="line">456</span><br><span class="line">457</span><br><span class="line">458</span><br><span class="line">459</span><br><span class="line">460</span><br><span class="line">461</span><br><span class="line">462</span><br><span class="line">463</span><br><span class="line">464</span><br><span class="line">465</span><br><span class="line">466</span><br><span class="line">467</span><br><span class="line">468</span><br><span class="line">469</span><br><span class="line">470</span><br><span class="line">471</span><br><span class="line">472</span><br><span class="line">473</span><br><span class="line">474</span><br><span class="line">475</span><br><span class="line">476</span><br><span class="line">477</span><br><span class="line">478</span><br><span class="line">479</span><br><span class="line">480</span><br><span class="line">481</span><br><span class="line">482</span><br><span class="line">483</span><br><span class="line">484</span><br><span class="line">485</span><br><span class="line">486</span><br><span class="line">487</span><br><span class="line">488</span><br><span class="line">489</span><br><span class="line">490</span><br><span class="line">491</span><br><span class="line">492</span><br><span class="line">493</span><br><span class="line">494</span><br><span class="line">495</span><br><span class="line">496</span><br><span class="line">497</span><br><span class="line">498</span><br><span class="line">499</span><br><span class="line">500</span><br><span class="line">501</span><br><span class="line">502</span><br><span class="line">503</span><br><span class="line">504</span><br><span class="line">505</span><br><span class="line">506</span><br><span class="line">507</span><br><span class="line">508</span><br><span class="line">509</span><br><span class="line">510</span><br><span class="line">511</span><br><span class="line">512</span><br><span class="line">513</span><br><span class="line">514</span><br><span class="line">515</span><br><span class="line">516</span><br><span class="line">517</span><br><span class="line">518</span><br><span class="line">519</span><br><span class="line">520</span><br><span class="line">521</span><br><span class="line">522</span><br><span class="line">523</span><br><span class="line">524</span><br><span class="line">525</span><br><span class="line">526</span><br><span class="line">527</span><br><span class="line">528</span><br><span class="line">529</span><br><span class="line">530</span><br><span class="line">531</span><br><span class="line">532</span><br><span class="line">533</span><br><span class="line">534</span><br><span class="line">535</span><br><span class="line">536</span><br><span class="line">537</span><br><span class="line">538</span><br><span class="line">539</span><br><span class="line">540</span><br><span class="line">541</span><br><span class="line">542</span><br><span class="line">543</span><br><span class="line">544</span><br><span class="line">545</span><br><span class="line">546</span><br><span class="line">547</span><br><span class="line">548</span><br><span class="line">549</span><br><span class="line">550</span><br><span class="line">551</span><br><span class="line">552</span><br><span class="line">553</span><br><span class="line">554</span><br><span class="line">555</span><br><span class="line">556</span><br><span class="line">557</span><br><span class="line">558</span><br><span class="line">559</span><br><span class="line">560</span><br><span class="line">561</span><br><span class="line">562</span><br><span class="line">563</span><br><span class="line">564</span><br><span class="line">565</span><br><span class="line">566</span><br><span class="line">567</span><br><span class="line">568</span><br><span class="line">569</span><br><span class="line">570</span><br><span class="line">571</span><br><span class="line">572</span><br><span class="line">573</span><br><span class="line">574</span><br><span class="line">575</span><br><span class="line">576</span><br><span class="line">577</span><br><span class="line">578</span><br><span class="line">579</span><br><span class="line">580</span><br><span class="line">581</span><br><span class="line">582</span><br><span class="line">583</span><br><span class="line">584</span><br><span class="line">585</span><br><span class="line">586</span><br><span class="line">587</span><br><span class="line">588</span><br><span class="line">589</span><br><span class="line">590</span><br><span class="line">591</span><br><span class="line">592</span><br><span class="line">593</span><br><span class="line">594</span><br><span class="line">595</span><br><span class="line">596</span><br><span class="line">597</span><br><span class="line">598</span><br><span class="line">599</span><br><span class="line">600</span><br><span class="line">601</span><br><span class="line">602</span><br><span class="line">603</span><br><span class="line">604</span><br><span class="line">605</span><br><span class="line">606</span><br><span class="line">607</span><br><span class="line">608</span><br><span class="line">609</span><br><span class="line">610</span><br><span class="line">611</span><br><span class="line">612</span><br><span class="line">613</span><br><span class="line">614</span><br><span class="line">615</span><br><span class="line">616</span><br><span class="line">617</span><br><span class="line">618</span><br><span class="line">619</span><br><span class="line">620</span><br><span class="line">621</span><br><span class="line">622</span><br><span class="line">623</span><br><span class="line">624</span><br><span class="line">625</span><br><span class="line">626</span><br><span class="line">627</span><br><span class="line">628</span><br><span class="line">629</span><br><span class="line">630</span><br><span class="line">631</span><br><span class="line">632</span><br><span class="line">633</span><br><span class="line">634</span><br><span class="line">635</span><br><span class="line">636</span><br><span class="line">637</span><br><span class="line">638</span><br><span class="line">639</span><br><span class="line">640</span><br><span class="line">641</span><br><span class="line">642</span><br><span class="line">643</span><br><span class="line">644</span><br><span class="line">645</span><br><span class="line">646</span><br><span class="line">647</span><br><span class="line">648</span><br><span class="line">649</span><br><span class="line">650</span><br><span class="line">651</span><br><span class="line">652</span><br><span class="line">653</span><br><span class="line">654</span><br><span class="line">655</span><br><span class="line">656</span><br><span class="line">657</span><br><span class="line">658</span><br><span class="line">659</span><br><span class="line">660</span><br><span class="line">661</span><br><span class="line">662</span><br><span class="line">663</span><br><span class="line">664</span><br><span class="line">665</span><br><span class="line">666</span><br><span class="line">667</span><br><span class="line">668</span><br><span class="line">669</span><br><span class="line">670</span><br><span class="line">671</span><br><span class="line">672</span><br><span class="line">673</span><br><span class="line">674</span><br><span class="line">675</span><br><span class="line">676</span><br><span class="line">677</span><br><span class="line">678</span><br><span class="line">679</span><br><span class="line">680</span><br><span class="line">681</span><br><span class="line">682</span><br><span class="line">683</span><br><span class="line">684</span><br><span class="line">685</span><br><span class="line">686</span><br><span class="line">687</span><br><span class="line">688</span><br><span class="line">689</span><br><span class="line">690</span><br><span class="line">691</span><br><span class="line">692</span><br><span class="line">693</span><br><span class="line">694</span><br><span class="line">695</span><br><span class="line">696</span><br><span class="line">697</span><br><span class="line">698</span><br><span class="line">699</span><br><span class="line">700</span><br><span class="line">701</span><br><span class="line">702</span><br><span class="line">703</span><br><span class="line">704</span><br><span class="line">705</span><br><span class="line">706</span><br><span class="line">707</span><br><span class="line">708</span><br><span class="line">709</span><br><span class="line">710</span><br><span class="line">711</span><br><span class="line">712</span><br><span class="line">713</span><br><span class="line">714</span><br><span class="line">715</span><br><span class="line">716</span><br><span class="line">717</span><br><span class="line">718</span><br><span class="line">719</span><br><span class="line">720</span><br><span class="line">721</span><br><span class="line">722</span><br><span class="line">723</span><br><span class="line">724</span><br><span class="line">725</span><br><span class="line">726</span><br><span class="line">727</span><br><span class="line">728</span><br><span class="line">729</span><br><span class="line">730</span><br><span class="line">731</span><br><span class="line">732</span><br><span class="line">733</span><br><span class="line">734</span><br><span class="line">735</span><br><span class="line">736</span><br><span class="line">737</span><br><span class="line">738</span><br><span class="line">739</span><br><span class="line">740</span><br><span class="line">741</span><br><span class="line">742</span><br><span class="line">743</span><br><span class="line">744</span><br><span class="line">745</span><br><span class="line">746</span><br><span class="line">747</span><br><span class="line">748</span><br><span class="line">749</span><br><span class="line">750</span><br><span class="line">751</span><br><span class="line">752</span><br><span class="line">753</span><br><span class="line">754</span><br><span class="line">755</span><br><span class="line">756</span><br><span class="line">757</span><br><span class="line">758</span><br><span class="line">759</span><br><span class="line">760</span><br><span class="line">761</span><br><span class="line">762</span><br><span class="line">763</span><br><span class="line">764</span><br><span class="line">765</span><br><span class="line">766</span><br><span class="line">767</span><br><span class="line">768</span><br><span class="line">769</span><br><span class="line">770</span><br><span class="line">771</span><br><span class="line">772</span><br><span class="line">773</span><br><span class="line">774</span><br><span class="line">775</span><br><span class="line">776</span><br><span class="line">777</span><br><span class="line">778</span><br><span class="line">779</span><br><span class="line">780</span><br><span class="line">781</span><br><span class="line">782</span><br><span class="line">783</span><br><span class="line">784</span><br><span class="line">785</span><br><span class="line">786</span><br><span class="line">787</span><br><span class="line">788</span><br><span class="line">789</span><br><span class="line">790</span><br><span class="line">791</span><br><span class="line">792</span><br><span class="line">793</span><br><span class="line">794</span><br><span class="line">795</span><br><span class="line">796</span><br><span class="line">797</span><br><span class="line">798</span><br><span class="line">799</span><br><span class="line">800</span><br><span class="line">801</span><br><span class="line">802</span><br><span class="line">803</span><br><span class="line">804</span><br><span class="line">805</span><br><span class="line">806</span><br><span class="line">807</span><br><span class="line">808</span><br><span class="line">809</span><br><span class="line">810</span><br><span class="line">811</span><br><span class="line">812</span><br><span class="line">813</span><br><span class="line">814</span><br><span class="line">815</span><br><span class="line">816</span><br><span class="line">817</span><br><span class="line">818</span><br><span class="line">819</span><br><span class="line">820</span><br><span class="line">821</span><br><span class="line">822</span><br><span class="line">823</span><br><span class="line">824</span><br><span class="line">825</span><br><span class="line">826</span><br><span class="line">827</span><br><span class="line">828</span><br><span class="line">829</span><br><span class="line">830</span><br><span class="line">831</span><br><span class="line">832</span><br><span class="line">833</span><br><span class="line">834</span><br><span class="line">835</span><br><span class="line">836</span><br><span class="line">837</span><br><span class="line">838</span><br><span class="line">839</span><br><span class="line">840</span><br><span class="line">841</span><br><span class="line">842</span><br><span class="line">843</span><br><span class="line">844</span><br><span class="line">845</span><br><span class="line">846</span><br><span class="line">847</span><br><span class="line">848</span><br><span class="line">849</span><br><span class="line">850</span><br><span class="line">851</span><br><span class="line">852</span><br><span class="line">853</span><br><span class="line">854</span><br><span class="line">855</span><br><span class="line">856</span><br><span class="line">857</span><br><span class="line">858</span><br><span class="line">859</span><br><span class="line">860</span><br><span class="line">861</span><br><span class="line">862</span><br><span class="line">863</span><br><span class="line">864</span><br><span class="line">865</span><br><span class="line">866</span><br><span class="line">867</span><br><span class="line">868</span><br><span class="line">869</span><br><span class="line">870</span><br><span class="line">871</span><br><span class="line">872</span><br><span class="line">873</span><br><span class="line">874</span><br><span class="line">875</span><br><span class="line">876</span><br><span class="line">877</span><br><span class="line">878</span><br><span class="line">879</span><br><span class="line">880</span><br><span class="line">881</span><br><span class="line">882</span><br><span class="line">883</span><br><span class="line">884</span><br><span class="line">885</span><br><span class="line">886</span><br><span class="line">887</span><br><span class="line">888</span><br><span class="line">889</span><br><span class="line">890</span><br><span class="line">891</span><br><span class="line">892</span><br><span class="line">893</span><br><span class="line">894</span><br><span class="line">895</span><br><span class="line">896</span><br><span class="line">897</span><br><span class="line">898</span><br><span class="line">899</span><br><span class="line">900</span><br><span class="line">901</span><br><span class="line">902</span><br><span class="line">903</span><br><span class="line">904</span><br><span class="line">905</span><br><span class="line">906</span><br><span class="line">907</span><br><span class="line">908</span><br><span class="line">909</span><br><span class="line">910</span><br><span class="line">911</span><br><span class="line">912</span><br><span class="line">913</span><br><span class="line">914</span><br><span class="line">915</span><br><span class="line">916</span><br><span class="line">917</span><br><span class="line">918</span><br><span class="line">919</span><br><span class="line">920</span><br><span class="line">921</span><br><span class="line">922</span><br><span class="line">923</span><br><span class="line">924</span><br><span class="line">925</span><br><span class="line">926</span><br><span class="line">927</span><br><span class="line">928</span><br><span class="line">929</span><br><span class="line">930</span><br><span class="line">931</span><br><span class="line">932</span><br><span class="line">933</span><br><span class="line">934</span><br><span class="line">935</span><br><span class="line">936</span><br><span class="line">937</span><br><span class="line">938</span><br><span class="line">939</span><br><span class="line">940</span><br><span class="line">941</span><br><span class="line">942</span><br><span class="line">943</span><br><span class="line">944</span><br><span class="line">945</span><br><span class="line">946</span><br><span class="line">947</span><br><span class="line">948</span><br><span class="line">949</span><br><span class="line">950</span><br><span class="line">951</span><br><span class="line">952</span><br><span class="line">953</span><br><span class="line">954</span><br><span class="line">955</span><br><span class="line">956</span><br><span class="line">957</span><br><span class="line">958</span><br><span class="line">959</span><br><span class="line">960</span><br><span class="line">961</span><br><span class="line">962</span><br><span class="line">963</span><br><span class="line">964</span><br><span class="line">965</span><br><span class="line">966</span><br><span class="line">967</span><br><span class="line">968</span><br><span class="line">969</span><br><span class="line">970</span><br><span class="line">971</span><br><span class="line">972</span><br><span class="line">973</span><br><span class="line">974</span><br><span class="line">975</span><br><span class="line">976</span><br><span class="line">977</span><br><span class="line">978</span><br><span class="line">979</span><br><span class="line">980</span><br><span class="line">981</span><br><span class="line">982</span><br><span class="line">983</span><br><span class="line">984</span><br><span class="line">985</span><br><span class="line">986</span><br><span class="line">987</span><br><span class="line">988</span><br><span class="line">989</span><br><span class="line">990</span><br><span class="line">991</span><br><span class="line">992</span><br><span class="line">993</span><br><span class="line">994</span><br><span class="line">995</span><br><span class="line">996</span><br><span class="line">997</span><br><span class="line">998</span><br><span class="line">999</span><br><span class="line">1000</span><br><span class="line">1001</span><br><span class="line">1002</span><br><span class="line">1003</span><br><span class="line">1004</span><br><span class="line">1005</span><br><span class="line">1006</span><br><span class="line">1007</span><br><span class="line">1008</span><br><span class="line">1009</span><br><span class="line">1010</span><br><span class="line">1011</span><br><span class="line">1012</span><br><span class="line">1013</span><br><span class="line">1014</span><br><span class="line">1015</span><br><span class="line">1016</span><br><span class="line">1017</span><br><span class="line">1018</span><br><span class="line">1019</span><br><span class="line">1020</span><br><span class="line">1021</span><br><span class="line">1022</span><br><span class="line">1023</span><br><span class="line">1024</span><br><span class="line">1025</span><br><span class="line">1026</span><br><span class="line">1027</span><br><span class="line">1028</span><br><span class="line">1029</span><br><span class="line">1030</span><br><span class="line">1031</span><br><span class="line">1032</span><br><span class="line">1033</span><br><span class="line">1034</span><br><span class="line">1035</span><br><span class="line">1036</span><br><span class="line">1037</span><br><span class="line">1038</span><br><span class="line">1039</span><br><span class="line">1040</span><br><span class="line">1041</span><br><span class="line">1042</span><br><span class="line">1043</span><br><span class="line">1044</span><br><span class="line">1045</span><br><span class="line">1046</span><br><span class="line">1047</span><br><span class="line">1048</span><br><span class="line">1049</span><br><span class="line">1050</span><br><span class="line">1051</span><br><span class="line">1052</span><br><span class="line">1053</span><br><span class="line">1054</span><br><span class="line">1055</span><br><span class="line">1056</span><br><span class="line">1057</span><br><span class="line">1058</span><br><span class="line">1059</span><br><span class="line">1060</span><br><span class="line">1061</span><br><span class="line">1062</span><br><span class="line">1063</span><br><span class="line">1064</span><br><span class="line">1065</span><br><span class="line">1066</span><br><span class="line">1067</span><br><span class="line">1068</span><br><span class="line">1069</span><br><span class="line">1070</span><br><span class="line">1071</span><br><span class="line">1072</span><br><span class="line">1073</span><br><span class="line">1074</span><br><span class="line">1075</span><br><span class="line">1076</span><br><span class="line">1077</span><br><span class="line">1078</span><br><span class="line">1079</span><br><span class="line">1080</span><br><span class="line">1081</span><br><span class="line">1082</span><br><span class="line">1083</span><br><span class="line">1084</span><br><span class="line">1085</span><br><span class="line">1086</span><br><span class="line">1087</span><br><span class="line">1088</span><br><span class="line">1089</span><br><span class="line">1090</span><br><span class="line">1091</span><br><span class="line">1092</span><br><span class="line">1093</span><br><span class="line">1094</span><br><span class="line">1095</span><br><span class="line">1096</span><br><span class="line">1097</span><br><span class="line">1098</span><br><span class="line">1099</span><br><span class="line">1100</span><br><span class="line">1101</span><br><span class="line">1102</span><br><span class="line">1103</span><br><span class="line">1104</span><br><span class="line">1105</span><br><span class="line">1106</span><br><span class="line">1107</span><br><span class="line">1108</span><br><span class="line">1109</span><br><span class="line">1110</span><br><span class="line">1111</span><br><span class="line">1112</span><br><span class="line">1113</span><br><span class="line">1114</span><br><span class="line">1115</span><br><span class="line">1116</span><br><span class="line">1117</span><br><span class="line">1118</span><br><span class="line">1119</span><br><span class="line">1120</span><br><span class="line">1121</span><br><span class="line">1122</span><br><span class="line">1123</span><br><span class="line">1124</span><br><span class="line">1125</span><br><span class="line">1126</span><br><span class="line">1127</span><br><span class="line">1128</span><br><span class="line">1129</span><br><span class="line">1130</span><br><span class="line">1131</span><br><span class="line">1132</span><br><span class="line">1133</span><br><span class="line">1134</span><br><span class="line">1135</span><br><span class="line">1136</span><br><span class="line">1137</span><br><span class="line">1138</span><br><span class="line">1139</span><br><span class="line">1140</span><br><span class="line">1141</span><br><span class="line">1142</span><br><span class="line">1143</span><br><span class="line">1144</span><br><span class="line">1145</span><br><span class="line">1146</span><br><span class="line">1147</span><br><span class="line">1148</span><br><span class="line">1149</span><br><span class="line">1150</span><br><span class="line">1151</span><br><span class="line">1152</span><br><span class="line">1153</span><br><span class="line">1154</span><br><span class="line">1155</span><br><span class="line">1156</span><br><span class="line">1157</span><br><span class="line">1158</span><br><span class="line">1159</span><br><span class="line">1160</span><br><span class="line">1161</span><br><span class="line">1162</span><br><span class="line">1163</span><br><span class="line">1164</span><br><span class="line">1165</span><br><span class="line">1166</span><br><span class="line">1167</span><br><span class="line">1168</span><br><span class="line">1169</span><br><span class="line">1170</span><br><span class="line">1171</span><br><span class="line">1172</span><br><span class="line">1173</span><br><span class="line">1174</span><br><span class="line">1175</span><br><span class="line">1176</span><br><span class="line">1177</span><br><span class="line">1178</span><br><span class="line">1179</span><br><span class="line">1180</span><br><span class="line">1181</span><br><span class="line">1182</span><br><span class="line">1183</span><br><span class="line">1184</span><br><span class="line">1185</span><br><span class="line">1186</span><br><span class="line">1187</span><br><span class="line">1188</span><br><span class="line">1189</span><br><span class="line">1190</span><br><span class="line">1191</span><br><span class="line">1192</span><br><span class="line">1193</span><br><span class="line">1194</span><br><span class="line">1195</span><br><span class="line">1196</span><br><span class="line">1197</span><br><span class="line">1198</span><br><span class="line">1199</span><br><span class="line">1200</span><br><span class="line">1201</span><br><span class="line">1202</span><br><span class="line">1203</span><br><span class="line">1204</span><br><span class="line">1205</span><br><span class="line">1206</span><br><span class="line">1207</span><br><span class="line">1208</span><br><span class="line">1209</span><br><span class="line">1210</span><br><span class="line">1211</span><br><span class="line">1212</span><br><span class="line">1213</span><br><span class="line">1214</span><br><span class="line">1215</span><br><span class="line">1216</span><br><span class="line">1217</span><br><span class="line">1218</span><br><span class="line">1219</span><br><span class="line">1220</span><br><span class="line">1221</span><br><span class="line">1222</span><br><span class="line">1223</span><br><span class="line">1224</span><br><span class="line">1225</span><br><span class="line">1226</span><br><span class="line">1227</span><br><span class="line">1228</span><br><span class="line">1229</span><br><span class="line">1230</span><br><span class="line">1231</span><br><span class="line">1232</span><br><span class="line">1233</span><br><span class="line">1234</span><br><span class="line">1235</span><br><span class="line">1236</span><br><span class="line">1237</span><br><span class="line">1238</span><br><span class="line">1239</span><br><span class="line">1240</span><br><span class="line">1241</span><br><span class="line">1242</span><br><span class="line">1243</span><br><span class="line">1244</span><br><span class="line">1245</span><br><span class="line">1246</span><br><span class="line">1247</span><br><span class="line">1248</span><br><span class="line">1249</span><br><span class="line">1250</span><br><span class="line">1251</span><br><span class="line">1252</span><br><span class="line">1253</span><br><span class="line">1254</span><br><span class="line">1255</span><br><span class="line">1256</span><br><span class="line">1257</span><br><span class="line">1258</span><br><span class="line">1259</span><br><span class="line">1260</span><br><span class="line">1261</span><br><span class="line">1262</span><br><span class="line">1263</span><br><span class="line">1264</span><br><span class="line">1265</span><br><span class="line">1266</span><br><span class="line">1267</span><br><span class="line">1268</span><br><span class="line">1269</span><br><span class="line">1270</span><br><span class="line">1271</span><br><span class="line">1272</span><br><span class="line">1273</span><br><span class="line">1274</span><br><span class="line">1275</span><br><span class="line">1276</span><br><span class="line">1277</span><br><span class="line">1278</span><br><span class="line">1279</span><br><span class="line">1280</span><br><span class="line">1281</span><br><span class="line">1282</span><br><span class="line">1283</span><br><span class="line">1284</span><br><span class="line">1285</span><br><span class="line">1286</span><br><span class="line">1287</span><br><span class="line">1288</span><br><span class="line">1289</span><br><span class="line">1290</span><br><span class="line">1291</span><br><span class="line">1292</span><br><span class="line">1293</span><br><span class="line">1294</span><br><span class="line">1295</span><br><span class="line">1296</span><br><span class="line">1297</span><br><span class="line">1298</span><br><span class="line">1299</span><br><span class="line">1300</span><br><span class="line">1301</span><br><span class="line">1302</span><br><span class="line">1303</span><br><span class="line">1304</span><br><span class="line">1305</span><br><span class="line">1306</span><br><span class="line">1307</span><br><span class="line">1308</span><br><span class="line">1309</span><br><span class="line">1310</span><br><span class="line">1311</span><br><span class="line">1312</span><br><span class="line">1313</span><br><span class="line">1314</span><br><span class="line">1315</span><br><span class="line">1316</span><br><span class="line">1317</span><br><span class="line">1318</span><br><span class="line">1319</span><br><span class="line">1320</span><br><span class="line">1321</span><br><span class="line">1322</span><br><span class="line">1323</span><br><span class="line">1324</span><br><span class="line">1325</span><br><span class="line">1326</span><br><span class="line">1327</span><br><span class="line">1328</span><br><span class="line">1329</span><br><span class="line">1330</span><br><span class="line">1331</span><br><span class="line">1332</span><br><span class="line">1333</span><br><span class="line">1334</span><br><span class="line">1335</span><br><span class="line">1336</span><br><span class="line">1337</span><br><span class="line">1338</span><br><span class="line">1339</span><br><span class="line">1340</span><br><span class="line">1341</span><br><span class="line">1342</span><br><span class="line">1343</span><br><span class="line">1344</span><br><span class="line">1345</span><br><span class="line">1346</span><br><span class="line">1347</span><br><span class="line">1348</span><br><span class="line">1349</span><br><span class="line">1350</span><br><span class="line">1351</span><br><span class="line">1352</span><br><span class="line">1353</span><br><span class="line">1354</span><br><span class="line">1355</span><br><span class="line">1356</span><br><span class="line">1357</span><br><span class="line">1358</span><br><span class="line">1359</span><br><span class="line">1360</span><br><span class="line">1361</span><br><span class="line">1362</span><br><span class="line">1363</span><br><span class="line">1364</span><br><span class="line">1365</span><br><span class="line">1366</span><br><span class="line">1367</span><br><span class="line">1368</span><br><span class="line">1369</span><br><span class="line">1370</span><br><span class="line">1371</span><br><span class="line">1372</span><br><span class="line">1373</span><br><span class="line">1374</span><br><span class="line">1375</span><br><span class="line">1376</span><br><span class="line">1377</span><br><span class="line">1378</span><br><span class="line">1379</span><br><span class="line">1380</span><br><span class="line">1381</span><br><span class="line">1382</span><br><span class="line">1383</span><br><span class="line">1384</span><br><span class="line">1385</span><br><span class="line">1386</span><br><span class="line">1387</span><br><span class="line">1388</span><br><span class="line">1389</span><br><span class="line">1390</span><br><span class="line">1391</span><br><span class="line">1392</span><br><span class="line">1393</span><br><span class="line">1394</span><br><span class="line">1395</span><br><span class="line">1396</span><br><span class="line">1397</span><br><span class="line">1398</span><br><span class="line">1399</span><br><span class="line">1400</span><br><span class="line">1401</span><br><span class="line">1402</span><br><span class="line">1403</span><br><span class="line">1404</span><br><span class="line">1405</span><br><span class="line">1406</span><br><span class="line">1407</span><br><span class="line">1408</span><br><span class="line">1409</span><br><span class="line">1410</span><br><span class="line">1411</span><br><span class="line">1412</span><br><span class="line">1413</span><br><span class="line">1414</span><br><span class="line">1415</span><br><span class="line">1416</span><br><span class="line">1417</span><br><span class="line">1418</span><br><span class="line">1419</span><br><span class="line">1420</span><br><span class="line">1421</span><br><span class="line">1422</span><br><span class="line">1423</span><br><span class="line">1424</span><br><span class="line">1425</span><br><span class="line">1426</span><br><span class="line">1427</span><br><span class="line">1428</span><br><span class="line">1429</span><br><span class="line">1430</span><br><span class="line">1431</span><br><span class="line">1432</span><br><span class="line">1433</span><br><span class="line">1434</span><br><span class="line">1435</span><br><span class="line">1436</span><br><span class="line">1437</span><br><span class="line">1438</span><br><span class="line">1439</span><br><span class="line">1440</span><br><span class="line">1441</span><br><span class="line">1442</span><br><span class="line">1443</span><br><span class="line">1444</span><br><span class="line">1445</span><br><span class="line">1446</span><br><span class="line">1447</span><br><span class="line">1448</span><br><span class="line">1449</span><br><span class="line">1450</span><br><span class="line">1451</span><br><span class="line">1452</span><br><span class="line">1453</span><br><span class="line">1454</span><br><span class="line">1455</span><br><span class="line">1456</span><br><span class="line">1457</span><br><span class="line">1458</span><br><span class="line">1459</span><br><span class="line">1460</span><br><span class="line">1461</span><br><span class="line">1462</span><br><span class="line">1463</span><br><span class="line">1464</span><br><span class="line">1465</span><br><span class="line">1466</span><br><span class="line">1467</span><br><span class="line">1468</span><br><span class="line">1469</span><br><span class="line">1470</span><br><span class="line">1471</span><br><span class="line">1472</span><br><span class="line">1473</span><br><span class="line">1474</span><br><span class="line">1475</span><br><span class="line">1476</span><br><span class="line">1477</span><br><span class="line">1478</span><br><span class="line">1479</span><br><span class="line">1480</span><br><span class="line">1481</span><br><span class="line">1482</span><br><span class="line">1483</span><br><span class="line">1484</span><br><span class="line">1485</span><br><span class="line">1486</span><br><span class="line">1487</span><br><span class="line">1488</span><br><span class="line">1489</span><br><span class="line">1490</span><br><span class="line">1491</span><br><span class="line">1492</span><br><span class="line">1493</span><br><span class="line">1494</span><br><span class="line">1495</span><br><span class="line">1496</span><br><span class="line">1497</span><br><span class="line">1498</span><br><span class="line">1499</span><br><span class="line">1500</span><br><span class="line">1501</span><br><span class="line">1502</span><br><span class="line">1503</span><br><span class="line">1504</span><br><span class="line">1505</span><br><span class="line">1506</span><br><span class="line">1507</span><br><span class="line">1508</span><br><span class="line">1509</span><br><span class="line">1510</span><br><span class="line">1511</span><br><span class="line">1512</span><br><span class="line">1513</span><br><span class="line">1514</span><br><span class="line">1515</span><br><span class="line">1516</span><br><span class="line">1517</span><br><span class="line">1518</span><br><span class="line">1519</span><br><span class="line">1520</span><br><span class="line">1521</span><br><span class="line">1522</span><br><span class="line">1523</span><br><span class="line">1524</span><br><span class="line">1525</span><br><span class="line">1526</span><br><span class="line">1527</span><br><span class="line">1528</span><br><span class="line">1529</span><br><span class="line">1530</span><br><span class="line">1531</span><br><span class="line">1532</span><br><span class="line">1533</span><br><span class="line">1534</span><br><span class="line">1535</span><br><span class="line">1536</span><br><span class="line">1537</span><br><span class="line">1538</span><br><span class="line">1539</span><br><span class="line">1540</span><br><span class="line">1541</span><br><span class="line">1542</span><br><span class="line">1543</span><br><span class="line">1544</span><br><span class="line">1545</span><br><span class="line">1546</span><br><span class="line">1547</span><br><span class="line">1548</span><br><span class="line">1549</span><br><span class="line">1550</span><br><span class="line">1551</span><br><span class="line">1552</span><br><span class="line">1553</span><br><span class="line">1554</span><br><span class="line">1555</span><br><span class="line">1556</span><br><span class="line">1557</span><br><span class="line">1558</span><br><span class="line">1559</span><br><span class="line">1560</span><br><span class="line">1561</span><br><span class="line">1562</span><br><span class="line">1563</span><br><span class="line">1564</span><br><span class="line">1565</span><br><span class="line">1566</span><br><span class="line">1567</span><br><span class="line">1568</span><br><span class="line">1569</span><br><span class="line">1570</span><br><span class="line">1571</span><br><span class="line">1572</span><br><span class="line">1573</span><br><span class="line">1574</span><br><span class="line">1575</span><br><span class="line">1576</span><br><span class="line">1577</span><br><span class="line">1578</span><br><span class="line">1579</span><br><span class="line">1580</span><br><span class="line">1581</span><br><span class="line">1582</span><br><span class="line">1583</span><br><span class="line">1584</span><br><span class="line">1585</span><br><span class="line">1586</span><br><span class="line">1587</span><br><span class="line">1588</span><br><span class="line">1589</span><br><span class="line">1590</span><br><span class="line">1591</span><br><span class="line">1592</span><br><span class="line">1593</span><br><span class="line">1594</span><br><span class="line">1595</span><br><span class="line">1596</span><br><span class="line">1597</span><br><span class="line">1598</span><br><span class="line">1599</span><br><span class="line">1600</span><br><span class="line">1601</span><br><span class="line">1602</span><br><span class="line">1603</span><br><span class="line">1604</span><br><span class="line">1605</span><br><span class="line">1606</span><br><span class="line">1607</span><br><span class="line">1608</span><br><span class="line">1609</span><br><span class="line">1610</span><br><span class="line">1611</span><br><span class="line">1612</span><br><span class="line">1613</span><br><span class="line">1614</span><br><span class="line">1615</span><br><span class="line">1616</span><br><span class="line">1617</span><br><span class="line">1618</span><br><span class="line">1619</span><br><span class="line">1620</span><br><span class="line">1621</span><br><span class="line">1622</span><br><span class="line">1623</span><br><span class="line">1624</span><br><span class="line">1625</span><br><span class="line">1626</span><br><span class="line">1627</span><br><span class="line">1628</span><br><span class="line">1629</span><br><span class="line">1630</span><br><span class="line">1631</span><br><span class="line">1632</span><br><span class="line">1633</span><br><span class="line">1634</span><br><span class="line">1635</span><br><span class="line">1636</span><br><span class="line">1637</span><br><span class="line">1638</span><br><span class="line">1639</span><br><span class="line">1640</span><br><span class="line">1641</span><br><span class="line">1642</span><br><span class="line">1643</span><br><span class="line">1644</span><br><span class="line">1645</span><br><span class="line">1646</span><br><span class="line">1647</span><br><span class="line">1648</span><br><span class="line">1649</span><br><span class="line">1650</span><br><span class="line">1651</span><br><span class="line">1652</span><br><span class="line">1653</span><br><span class="line">1654</span><br><span class="line">1655</span><br><span class="line">1656</span><br><span class="line">1657</span><br><span class="line">1658</span><br><span class="line">1659</span><br><span class="line">1660</span><br><span class="line">1661</span><br><span class="line">1662</span><br><span class="line">1663</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">// ==UserScript==</span><br><span class="hljs-comment">// @name         网页端B站主页精简~ BiliCompact</span><br><span class="hljs-comment">// @namespace    http://tampermonkey.net/</span><br><span class="hljs-comment">// @version      2.6.0</span><br><span class="hljs-comment">// @license MIT</span><br><span class="hljs-comment">// @description  你是否厌倦了B站网页端极多视频？想要更简要的界面？这个插件将帮助你只显示指定数量的视频，支持多种页面、黑/白名单、配置持久化。非侵入式设计，不在B站页面注入任何UI元素。支持简中，繁中，英语。</span><br><span class="hljs-comment">// @author       TwilightRain</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/?*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/index/*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/v/popular/*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/v/*/*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/video/*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/dynamic*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/search*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/anime/*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/guochuang/*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/music/*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/dance/*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/game/*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/technology/*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/life/*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/food/*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/car/*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/animal/*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/kichiku/*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/fashion/*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/ent/*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/cinephile/*</span><br><span class="hljs-comment">// @match        https://www.bilibili.com/popular/*</span><br><span class="hljs-comment">// @run-at       document-end</span><br><span class="hljs-comment">// @grant        GM_setValue</span><br><span class="hljs-comment">// @grant        GM_getValue</span><br><span class="hljs-comment">// @grant        GM_registerMenuCommand</span><br><span class="hljs-comment">// @grant        GM_addStyle</span><br><span class="hljs-comment">// @grant        GM_log</span><br><span class="hljs-comment">// @icon         https://www.bilibili.com/favicon.ico</span><br><span class="hljs-comment">// @downloadURL https://update.greasyfork.org/scripts/585777/%E7%BD%91%E9%A1%B5%E7%AB%AFB%E7%AB%99%E4%B8%BB%E9%A1%B5%E7%B2%BE%E7%AE%80~%20BiliCompact.user.js</span><br><span class="hljs-comment">// @updateURL https://update.greasyfork.org/scripts/585777/%E7%BD%91%E9%A1%B5%E7%AB%AFB%E7%AB%99%E4%B8%BB%E9%A1%B5%E7%B2%BE%E7%AE%80~%20BiliCompact.meta.js</span><br><span class="hljs-comment">// ==/UserScript==</span><br><br>(<span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) &#123;<br>    <span class="hljs-string">&#x27;use strict&#x27;</span>;<br><br>    <span class="hljs-comment">// ======================== 国际化 (i18n) ========================</span><br>    <span class="hljs-keyword">const</span> <span class="hljs-variable constant_">I18N</span> = &#123;<br>        <span class="hljs-attr">zh_CN</span>: &#123;<br>            <span class="hljs-comment">// Log</span><br>            <span class="hljs-title class_">LogPrefix</span>: <span class="hljs-string">&#x27;[B站精简]&#x27;</span>,<br>            <span class="hljs-title class_">LogSelectorData</span>: <span class="hljs-string">&#x27;通过data属性探测到选择器:&#x27;</span>,<br>            <span class="hljs-title class_">LogSelectorFound</span>: <span class="hljs-string">&#x27;探测到选择器:&#x27;</span>,<br>            <span class="hljs-title class_">LogSelectorFallback</span>: <span class="hljs-string">&#x27;通过链接回退探测到选择器:&#x27;</span>,<br>            <span class="hljs-title class_">LogNoCards</span>: <span class="hljs-string">&#x27;未找到视频卡片，跳过&#x27;</span>,<br>            <span class="hljs-title class_">LogStillNoCards</span>: <span class="hljs-string">&#x27;仍未找到视频卡片&#x27;</span>,<br>            <span class="hljs-title class_">LogProcessed</span>: <span class="hljs-string">&#x27;已处理: 总视频 &#123;0&#125;, 显示 &#123;1&#125;, 隐藏 &#123;2&#125;&#x27;</span>,<br>            <span class="hljs-title class_">LogErrorLimit</span>: <span class="hljs-string">&#x27;limitVideos 出错:&#x27;</span>,<br>            <span class="hljs-title class_">LogContainerFound</span>: <span class="hljs-string">&#x27;探测到容器:&#x27;</span>,<br>            <span class="hljs-title class_">LogConfigLoaded</span>: <span class="hljs-string">&#x27;配置加载完成:&#x27;</span>,<br>            <span class="hljs-title class_">LogStatus</span>: <span class="hljs-string">&#x27;精简状态: &#123;0&#125;&#x27;</span>,<br>            <span class="hljs-title class_">LogStatusOn</span>: <span class="hljs-string">&#x27;已开启&#x27;</span>,<br>            <span class="hljs-title class_">LogStatusOff</span>: <span class="hljs-string">&#x27;已关闭&#x27;</span>,<br>            <span class="hljs-title class_">LogQuickSet</span>: <span class="hljs-string">&#x27;已设置最大数量:&#x27;</span>,<br>            <span class="hljs-title class_">LogTimerRetry</span>: <span class="hljs-string">&#x27;定时器检测到可见视频过多，重新执行限制&#x27;</span>,<br>            <span class="hljs-title class_">LogInitDone</span>: <span class="hljs-string">&#x27;脚本初始化完成，当前配置:&#x27;</span>,<br>            <span class="hljs-title class_">LogInitError</span>: <span class="hljs-string">&#x27;初始化失败:&#x27;</span>,<br>            <span class="hljs-title class_">LogObserverStarted</span>: <span class="hljs-string">&#x27;MutationObserver 已启动，监听容器:&#x27;</span>,<br>            <span class="hljs-title class_">LogUrlChanged</span>: <span class="hljs-string">&#x27;URL变化:&#x27;</span>,<br><br>            <span class="hljs-comment">// Menu</span><br>            <span class="hljs-title class_">MenuSettings</span>: <span class="hljs-string">&#x27;B站精简设置&#x27;</span>,<br>            <span class="hljs-title class_">MenuRefresh</span>: <span class="hljs-string">&#x27;手动刷新精简&#x27;</span>,<br>            <span class="hljs-title class_">MenuToggle</span>: <span class="hljs-string">&#x27;切换精简状态&#x27;</span>,<br>            <span class="hljs-title class_">MenuQuickSet</span>: <span class="hljs-string">&#x27;快速设数量&#x27;</span>,<br>            <span class="hljs-title class_">MenuCommentPurifier</span>: <span class="hljs-string">&#x27;切换评论净化&#x27;</span>,<br><br>            <span class="hljs-comment">// Panel</span><br>            <span class="hljs-title class_">PanelTitle</span>: <span class="hljs-string">&#x27;B站精简设置&#x27;</span>,<br>            <span class="hljs-title class_">PanelStatusLabel</span>: <span class="hljs-string">&#x27;当前状态&#x27;</span>,<br>            <span class="hljs-title class_">PanelStatusActive</span>: <span class="hljs-string">&#x27;精简中&#x27;</span>,<br>            <span class="hljs-title class_">PanelStatusPaused</span>: <span class="hljs-string">&#x27;已暂停&#x27;</span>,<br>            <span class="hljs-title class_">PanelMaxVideos</span>: <span class="hljs-string">&#x27;最大显示数量&#x27;</span>,<br>            <span class="hljs-title class_">PanelExcludeLive</span>: <span class="hljs-string">&#x27;排除直播&#x27;</span>,<br>            <span class="hljs-title class_">PanelExcludeAd</span>: <span class="hljs-string">&#x27;排除广告&#x27;</span>,<br>            <span class="hljs-title class_">PanelExcludeBangumi</span>: <span class="hljs-string">&#x27;排除番剧&#x27;</span>,<br>            <span class="hljs-title class_">PanelExcludePaid</span>: <span class="hljs-string">&#x27;排除付费课程&#x27;</span>,<br>            <span class="hljs-title class_">PanelKeepPromoted</span>: <span class="hljs-string">&#x27;保留推广位&#x27;</span>,<br>            <span class="hljs-title class_">PanelKeepUpids</span>: <span class="hljs-string">&#x27;保留UP主ID（逗号分隔）&#x27;</span>,<br>            <span class="hljs-title class_">PanelDebug</span>: <span class="hljs-string">&#x27;调试模式&#x27;</span>,<br>            <span class="hljs-title class_">PanelEnableCommentPurifier</span>: <span class="hljs-string">&#x27;启用评论净化（删除@提及，隐藏短评论）&#x27;</span>,<br>            <span class="hljs-title class_">PanelLanguage</span>: <span class="hljs-string">&#x27;界面语言 / Language&#x27;</span>,<br>            <span class="hljs-title class_">PanelLanguageAuto</span>: <span class="hljs-string">&#x27;自动 (Auto)&#x27;</span>,<br>            <span class="hljs-title class_">PanelBtnPause</span>: <span class="hljs-string">&#x27;暂停精简&#x27;</span>,<br>            <span class="hljs-title class_">PanelBtnResume</span>: <span class="hljs-string">&#x27;启用精简&#x27;</span>,<br>            <span class="hljs-title class_">PanelBtnReset</span>: <span class="hljs-string">&#x27;恢复默认&#x27;</span>,<br>            <span class="hljs-title class_">PanelBtnSave</span>: <span class="hljs-string">&#x27;保存并应用&#x27;</span>,<br>            <span class="hljs-title class_">PanelBtnClose</span>: <span class="hljs-string">&#x27;关闭&#x27;</span>,<br>            <span class="hljs-title class_">PanelColorMode</span>: <span class="hljs-string">&#x27;颜色模式&#x27;</span>,<br>            <span class="hljs-title class_">PanelColorAuto</span>: <span class="hljs-string">&#x27;跟随系统&#x27;</span>,<br>            <span class="hljs-title class_">PanelColorDark</span>: <span class="hljs-string">&#x27;深色&#x27;</span>,<br>            <span class="hljs-title class_">PanelColorLight</span>: <span class="hljs-string">&#x27;浅色&#x27;</span>,<br><br>            <span class="hljs-comment">// Prompt</span><br>            <span class="hljs-title class_">PromptQuickSet</span>: <span class="hljs-string">&#x27;输入最大显示视频数量（1-100）：&#x27;</span>,<br>        &#125;,<br>        <span class="hljs-attr">zh_TW</span>: &#123;<br>            <span class="hljs-comment">// Log</span><br>            <span class="hljs-title class_">LogPrefix</span>: <span class="hljs-string">&#x27;[B站精簡]&#x27;</span>,<br>            <span class="hljs-title class_">LogSelectorData</span>: <span class="hljs-string">&#x27;透過data屬性探測到選擇器:&#x27;</span>,<br>            <span class="hljs-title class_">LogSelectorFound</span>: <span class="hljs-string">&#x27;探測到選擇器:&#x27;</span>,<br>            <span class="hljs-title class_">LogSelectorFallback</span>: <span class="hljs-string">&#x27;透過連結回退探測到選擇器:&#x27;</span>,<br>            <span class="hljs-title class_">LogNoCards</span>: <span class="hljs-string">&#x27;未找到影片卡片，跳過&#x27;</span>,<br>            <span class="hljs-title class_">LogStillNoCards</span>: <span class="hljs-string">&#x27;仍未找到影片卡片&#x27;</span>,<br>            <span class="hljs-title class_">LogProcessed</span>: <span class="hljs-string">&#x27;已處理: 總影片 &#123;0&#125;, 顯示 &#123;1&#125;, 隱藏 &#123;2&#125;&#x27;</span>,<br>            <span class="hljs-title class_">LogErrorLimit</span>: <span class="hljs-string">&#x27;limitVideos 出錯:&#x27;</span>,<br>            <span class="hljs-title class_">LogContainerFound</span>: <span class="hljs-string">&#x27;探測到容器:&#x27;</span>,<br>            <span class="hljs-title class_">LogConfigLoaded</span>: <span class="hljs-string">&#x27;設定載入完成:&#x27;</span>,<br>            <span class="hljs-title class_">LogStatus</span>: <span class="hljs-string">&#x27;精簡狀態: &#123;0&#125;&#x27;</span>,<br>            <span class="hljs-title class_">LogStatusOn</span>: <span class="hljs-string">&#x27;已開啟&#x27;</span>,<br>            <span class="hljs-title class_">LogStatusOff</span>: <span class="hljs-string">&#x27;已關閉&#x27;</span>,<br>            <span class="hljs-title class_">LogQuickSet</span>: <span class="hljs-string">&#x27;已設定最大數量:&#x27;</span>,<br>            <span class="hljs-title class_">LogTimerRetry</span>: <span class="hljs-string">&#x27;定時器偵測到可見影片過多，重新執行限制&#x27;</span>,<br>            <span class="hljs-title class_">LogInitDone</span>: <span class="hljs-string">&#x27;指令碼初始化完成（非侵入式），目前設定:&#x27;</span>,<br>            <span class="hljs-title class_">LogInitError</span>: <span class="hljs-string">&#x27;初始化失敗:&#x27;</span>,<br>            <span class="hljs-title class_">LogObserverStarted</span>: <span class="hljs-string">&#x27;MutationObserver 已啟動，監聽容器:&#x27;</span>,<br>            <span class="hljs-title class_">LogUrlChanged</span>: <span class="hljs-string">&#x27;URL變化:&#x27;</span>,<br><br>            <span class="hljs-comment">// Menu</span><br>            <span class="hljs-title class_">MenuSettings</span>: <span class="hljs-string">&#x27;B站精簡設定&#x27;</span>,<br>            <span class="hljs-title class_">MenuRefresh</span>: <span class="hljs-string">&#x27;手動重新整理精簡&#x27;</span>,<br>            <span class="hljs-title class_">MenuToggle</span>: <span class="hljs-string">&#x27;切換精簡狀態&#x27;</span>,<br>            <span class="hljs-title class_">MenuQuickSet</span>: <span class="hljs-string">&#x27;快速設數量&#x27;</span>,<br>            <span class="hljs-title class_">MenuCommentPurifier</span>: <span class="hljs-string">&#x27;切換評論淨化&#x27;</span>,<br><br>            <span class="hljs-comment">// Panel</span><br>            <span class="hljs-title class_">PanelTitle</span>: <span class="hljs-string">&#x27;B站精簡設定&#x27;</span>,<br>            <span class="hljs-title class_">PanelStatusLabel</span>: <span class="hljs-string">&#x27;目前狀態&#x27;</span>,<br>            <span class="hljs-title class_">PanelStatusActive</span>: <span class="hljs-string">&#x27;精簡中&#x27;</span>,<br>            <span class="hljs-title class_">PanelStatusPaused</span>: <span class="hljs-string">&#x27;已暫停&#x27;</span>,<br>            <span class="hljs-title class_">PanelMaxVideos</span>: <span class="hljs-string">&#x27;最大顯示數量&#x27;</span>,<br>            <span class="hljs-title class_">PanelExcludeLive</span>: <span class="hljs-string">&#x27;排除直播&#x27;</span>,<br>            <span class="hljs-title class_">PanelExcludeAd</span>: <span class="hljs-string">&#x27;排除廣告&#x27;</span>,<br>            <span class="hljs-title class_">PanelExcludeBangumi</span>: <span class="hljs-string">&#x27;排除番劇&#x27;</span>,<br>            <span class="hljs-title class_">PanelExcludePaid</span>: <span class="hljs-string">&#x27;排除付費課程&#x27;</span>,<br>            <span class="hljs-title class_">PanelKeepPromoted</span>: <span class="hljs-string">&#x27;保留推廣位&#x27;</span>,<br>            <span class="hljs-title class_">PanelKeepUpids</span>: <span class="hljs-string">&#x27;保留UP主ID（逗號分隔）&#x27;</span>,<br>            <span class="hljs-title class_">PanelDebug</span>: <span class="hljs-string">&#x27;除錯模式&#x27;</span>,<br>            <span class="hljs-title class_">PanelEnableCommentPurifier</span>: <span class="hljs-string">&#x27;啟用評論淨化（刪除@提及，隱藏短評論）&#x27;</span>,<br>            <span class="hljs-title class_">PanelLanguage</span>: <span class="hljs-string">&#x27;介面語言 / Language&#x27;</span>,<br>            <span class="hljs-title class_">PanelLanguageAuto</span>: <span class="hljs-string">&#x27;自動 (Auto)&#x27;</span>,<br>            <span class="hljs-title class_">PanelBtnPause</span>: <span class="hljs-string">&#x27;暫停精簡&#x27;</span>,<br>            <span class="hljs-title class_">PanelBtnResume</span>: <span class="hljs-string">&#x27;啟用精簡&#x27;</span>,<br>            <span class="hljs-title class_">PanelBtnReset</span>: <span class="hljs-string">&#x27;回復預設&#x27;</span>,<br>            <span class="hljs-title class_">PanelBtnSave</span>: <span class="hljs-string">&#x27;儲存並套用&#x27;</span>,<br>            <span class="hljs-title class_">PanelBtnClose</span>: <span class="hljs-string">&#x27;關閉&#x27;</span>,<br>            <span class="hljs-title class_">PanelColorMode</span>: <span class="hljs-string">&#x27;顏色模式&#x27;</span>,<br>            <span class="hljs-title class_">PanelColorAuto</span>: <span class="hljs-string">&#x27;跟隨系統&#x27;</span>,<br>            <span class="hljs-title class_">PanelColorDark</span>: <span class="hljs-string">&#x27;深色&#x27;</span>,<br>            <span class="hljs-title class_">PanelColorLight</span>: <span class="hljs-string">&#x27;淺色&#x27;</span>,<br><br>            <span class="hljs-comment">// Prompt</span><br>            <span class="hljs-title class_">PromptQuickSet</span>: <span class="hljs-string">&#x27;輸入最大顯示影片數量（1-100）：&#x27;</span>,<br>        &#125;,<br>        <span class="hljs-attr">en_US</span>: &#123;<br>            <span class="hljs-comment">// Log</span><br>            <span class="hljs-title class_">LogPrefix</span>: <span class="hljs-string">&#x27;[BiliCompact]&#x27;</span>,<br>            <span class="hljs-title class_">LogSelectorData</span>: <span class="hljs-string">&#x27;Selector detected via data attribute:&#x27;</span>,<br>            <span class="hljs-title class_">LogSelectorFound</span>: <span class="hljs-string">&#x27;Selector detected:&#x27;</span>,<br>            <span class="hljs-title class_">LogSelectorFallback</span>: <span class="hljs-string">&#x27;Selector detected via link fallback:&#x27;</span>,<br>            <span class="hljs-title class_">LogNoCards</span>: <span class="hljs-string">&#x27;No video cards found, skipping&#x27;</span>,<br>            <span class="hljs-title class_">LogStillNoCards</span>: <span class="hljs-string">&#x27;Still no video cards found&#x27;</span>,<br>            <span class="hljs-title class_">LogProcessed</span>: <span class="hljs-string">&#x27;Processed: total &#123;0&#125;, shown &#123;1&#125;, hidden &#123;2&#125;&#x27;</span>,<br>            <span class="hljs-title class_">LogErrorLimit</span>: <span class="hljs-string">&#x27;limitVideos error:&#x27;</span>,<br>            <span class="hljs-title class_">LogContainerFound</span>: <span class="hljs-string">&#x27;Container detected:&#x27;</span>,<br>            <span class="hljs-title class_">LogConfigLoaded</span>: <span class="hljs-string">&#x27;Config loaded:&#x27;</span>,<br>            <span class="hljs-title class_">LogStatus</span>: <span class="hljs-string">&#x27;Compact status: &#123;0&#125;&#x27;</span>,<br>            <span class="hljs-title class_">LogStatusOn</span>: <span class="hljs-string">&#x27;Enabled&#x27;</span>,<br>            <span class="hljs-title class_">LogStatusOff</span>: <span class="hljs-string">&#x27;Disabled&#x27;</span>,<br>            <span class="hljs-title class_">LogQuickSet</span>: <span class="hljs-string">&#x27;Max videos set to:&#x27;</span>,<br>            <span class="hljs-title class_">LogTimerRetry</span>: <span class="hljs-string">&#x27;Timer detected too many visible videos, re-running limit&#x27;</span>,<br>            <span class="hljs-title class_">LogInitDone</span>: <span class="hljs-string">&#x27;BiliCompact initialized (non-invasive), config:&#x27;</span>,<br>            <span class="hljs-title class_">LogInitError</span>: <span class="hljs-string">&#x27;Initialization failed:&#x27;</span>,<br>            <span class="hljs-title class_">LogObserverStarted</span>: <span class="hljs-string">&#x27;MutationObserver started, watching container:&#x27;</span>,<br>            <span class="hljs-title class_">LogUrlChanged</span>: <span class="hljs-string">&#x27;URL changed:&#x27;</span>,<br><br>            <span class="hljs-comment">// Menu</span><br>            <span class="hljs-title class_">MenuSettings</span>: <span class="hljs-string">&#x27;BiliCompact Settings&#x27;</span>,<br>            <span class="hljs-title class_">MenuRefresh</span>: <span class="hljs-string">&#x27;Refresh Compact&#x27;</span>,<br>            <span class="hljs-title class_">MenuToggle</span>: <span class="hljs-string">&#x27;Toggle Compact&#x27;</span>,<br>            <span class="hljs-title class_">MenuQuickSet</span>: <span class="hljs-string">&#x27;Quick Set Count&#x27;</span>,<br>            <span class="hljs-title class_">MenuCommentPurifier</span>: <span class="hljs-string">&#x27;Toggle Comment Purifier&#x27;</span>,<br><br>            <span class="hljs-comment">// Panel</span><br>            <span class="hljs-title class_">PanelTitle</span>: <span class="hljs-string">&#x27;BiliCompact Settings&#x27;</span>,<br>            <span class="hljs-title class_">PanelStatusLabel</span>: <span class="hljs-string">&#x27;Status&#x27;</span>,<br>            <span class="hljs-title class_">PanelStatusActive</span>: <span class="hljs-string">&#x27;Active&#x27;</span>,<br>            <span class="hljs-title class_">PanelStatusPaused</span>: <span class="hljs-string">&#x27;Paused&#x27;</span>,<br>            <span class="hljs-title class_">PanelMaxVideos</span>: <span class="hljs-string">&#x27;Max videos&#x27;</span>,<br>            <span class="hljs-title class_">PanelExcludeLive</span>: <span class="hljs-string">&#x27;Exclude live streams&#x27;</span>,<br>            <span class="hljs-title class_">PanelExcludeAd</span>: <span class="hljs-string">&#x27;Exclude ads&#x27;</span>,<br>            <span class="hljs-title class_">PanelExcludeBangumi</span>: <span class="hljs-string">&#x27;Exclude bangumi&#x27;</span>,<br>            <span class="hljs-title class_">PanelExcludePaid</span>: <span class="hljs-string">&#x27;Exclude paid courses&#x27;</span>,<br>            <span class="hljs-title class_">PanelKeepPromoted</span>: <span class="hljs-string">&#x27;Keep promoted items&#x27;</span>,<br>            <span class="hljs-title class_">PanelKeepUpids</span>: <span class="hljs-string">&#x27;Whitelist UP IDs (comma-separated)&#x27;</span>,<br>            <span class="hljs-title class_">PanelDebug</span>: <span class="hljs-string">&#x27;Debug mode&#x27;</span>,<br>            <span class="hljs-title class_">PanelEnableCommentPurifier</span>: <span class="hljs-string">&#x27;Enable comment purifier (remove @mentions, hide short comments)&#x27;</span>,<br>            <span class="hljs-title class_">PanelLanguage</span>: <span class="hljs-string">&#x27;Language / 語言&#x27;</span>,<br>            <span class="hljs-title class_">PanelLanguageAuto</span>: <span class="hljs-string">&#x27;Auto&#x27;</span>,<br>            <span class="hljs-title class_">PanelBtnPause</span>: <span class="hljs-string">&#x27;Pause&#x27;</span>,<br>            <span class="hljs-title class_">PanelBtnResume</span>: <span class="hljs-string">&#x27;Resume&#x27;</span>,<br>            <span class="hljs-title class_">PanelBtnReset</span>: <span class="hljs-string">&#x27;Reset Defaults&#x27;</span>,<br>            <span class="hljs-title class_">PanelBtnSave</span>: <span class="hljs-string">&#x27;Save &amp;amp; Apply&#x27;</span>,<br>            <span class="hljs-title class_">PanelBtnClose</span>: <span class="hljs-string">&#x27;Close&#x27;</span>,<br>            <span class="hljs-title class_">PanelColorMode</span>: <span class="hljs-string">&#x27;Color Mode&#x27;</span>,<br>            <span class="hljs-title class_">PanelColorAuto</span>: <span class="hljs-string">&#x27;Auto (System)&#x27;</span>,<br>            <span class="hljs-title class_">PanelColorDark</span>: <span class="hljs-string">&#x27;Dark&#x27;</span>,<br>            <span class="hljs-title class_">PanelColorLight</span>: <span class="hljs-string">&#x27;Light&#x27;</span>,<br><br>            <span class="hljs-comment">// Prompt</span><br>            <span class="hljs-title class_">PromptQuickSet</span>: <span class="hljs-string">&#x27;Enter max videos to show (1-100):&#x27;</span>,<br>        &#125;<br>    &#125;;<br><br>    <span class="hljs-comment">// 当前生效的语言</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-title class_">CurrentLang</span> = <span class="hljs-string">&#x27;zh_CN&#x27;</span>;<br><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">ResolveLanguage</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Config</span>.<span class="hljs-property">Language</span> &amp;amp;&amp;amp; <span class="hljs-title class_">Config</span>.<span class="hljs-property">Language</span> !== <span class="hljs-string">&#x27;auto&#x27;</span>) &#123;<br>            <span class="hljs-keyword">return</span> <span class="hljs-title class_">Config</span>.<span class="hljs-property">Language</span>;<br>        &#125;<br>        <span class="hljs-keyword">const</span> <span class="hljs-title class_">Nav</span> = (navigator.<span class="hljs-property">language</span> || <span class="hljs-string">&#x27;&#x27;</span>).<span class="hljs-title function_">toLowerCase</span>();<br>        <span class="hljs-keyword">if</span> (<span class="hljs-regexp">/^zh-(tw|hk|mo)$/i</span>.<span class="hljs-title function_">test</span>(<span class="hljs-title class_">Nav</span>) || <span class="hljs-regexp">/^zh-(hant)$/i</span>.<span class="hljs-title function_">test</span>(<span class="hljs-title class_">Nav</span>)) <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;zh_TW&#x27;</span>;<br>        <span class="hljs-keyword">if</span> (<span class="hljs-regexp">/^zh/i</span>.<span class="hljs-title function_">test</span>(<span class="hljs-title class_">Nav</span>)) <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;zh_CN&#x27;</span>;<br>        <span class="hljs-keyword">if</span> (<span class="hljs-regexp">/^en/i</span>.<span class="hljs-title function_">test</span>(<span class="hljs-title class_">Nav</span>)) <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;en_US&#x27;</span>;<br>        <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;zh_CN&#x27;</span>; <span class="hljs-comment">// fallback</span><br>    &#125;<br><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">T</span>(<span class="hljs-params">Key, ...Args</span>) &#123;<br>        <span class="hljs-keyword">const</span> <span class="hljs-title class_">Map</span> = <span class="hljs-variable constant_">I18N</span>[<span class="hljs-title class_">CurrentLang</span>] || <span class="hljs-variable constant_">I18N</span>[<span class="hljs-string">&#x27;zh_CN&#x27;</span>];<br>        <span class="hljs-keyword">let</span> <span class="hljs-title class_">Str</span> = <span class="hljs-title class_">Map</span>[<span class="hljs-title class_">Key</span>];<br>        <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Str</span> === <span class="hljs-literal">undefined</span>) &#123;<br>            <span class="hljs-comment">// Fallback to zh_CN if key missing in current locale</span><br>            <span class="hljs-title class_">Str</span> = <span class="hljs-variable constant_">I18N</span>[<span class="hljs-string">&#x27;zh_CN&#x27;</span>][<span class="hljs-title class_">Key</span>];<br>        &#125;<br>        <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Str</span> === <span class="hljs-literal">undefined</span>) <span class="hljs-keyword">return</span> <span class="hljs-title class_">Key</span>; <span class="hljs-comment">// ultimate fallback: show the key itself</span><br>        <span class="hljs-comment">// Replace placeholders &#123;0&#125;, &#123;1&#125;, &#123;2&#125;...</span><br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> I = <span class="hljs-number">0</span>; I &amp;lt; <span class="hljs-title class_">Args</span>.<span class="hljs-property">length</span>; I++) &#123;<br>            <span class="hljs-title class_">Str</span> = <span class="hljs-title class_">Str</span>.<span class="hljs-title function_">replace</span>(<span class="hljs-string">&#x27;&#123;&#x27;</span> + I + <span class="hljs-string">&#x27;&#125;&#x27;</span>, <span class="hljs-title class_">Args</span>[I]);<br>        &#125;<br>        <span class="hljs-keyword">return</span> <span class="hljs-title class_">Str</span>;<br>    &#125;<br><br>    <span class="hljs-comment">// ======================== 配置（默认值，会从GM存储读取） ========================</span><br>    <span class="hljs-keyword">const</span> <span class="hljs-variable constant_">DEFAULTS</span> = &#123;<br>        <span class="hljs-title class_">MaxVideos</span>: <span class="hljs-number">10</span>,                 <span class="hljs-comment">// 最大显示数量</span><br>        <span class="hljs-title class_">ExcludeLive</span>: <span class="hljs-literal">true</span>,             <span class="hljs-comment">// 排除直播</span><br>        <span class="hljs-title class_">ExcludeAd</span>: <span class="hljs-literal">true</span>,              <span class="hljs-comment">// 排除广告</span><br>        <span class="hljs-title class_">ExcludeBangumi</span>: <span class="hljs-literal">true</span>,         <span class="hljs-comment">// 排除番剧</span><br>        <span class="hljs-title class_">ExcludePaid</span>: <span class="hljs-literal">true</span>,            <span class="hljs-comment">// 排除付费课程</span><br>        <span class="hljs-title class_">KeepSpecialUPIDs</span>: [],         <span class="hljs-comment">// 保留的UP主ID列表（数字）</span><br>        <span class="hljs-title class_">KeepPromoted</span>: <span class="hljs-literal">false</span>,          <span class="hljs-comment">// 保留推广位（不计入数量）</span><br>        <span class="hljs-title class_">Language</span>: <span class="hljs-string">&#x27;auto&#x27;</span>,             <span class="hljs-comment">// 界面语言: auto | zh_CN | zh_TW | en_US</span><br>        <span class="hljs-title class_">Debug</span>: <span class="hljs-literal">false</span>,                 <span class="hljs-comment">// 调试模式</span><br>        <span class="hljs-title class_">EnableCommentPurifier</span>: <span class="hljs-literal">false</span>, <span class="hljs-comment">// 评论净化器 (删除@提及，隐藏短评论)</span><br>        <span class="hljs-title class_">RemovedElements</span>: &#123;&#125;,          <span class="hljs-comment">// 元素去除: &#123; presetId: true/false &#125;</span><br>        <span class="hljs-title class_">ColorMode</span>: <span class="hljs-string">&#x27;auto&#x27;</span>,            <span class="hljs-comment">// 颜色模式: auto | dark | light</span><br>    &#125;;<br><br>    <span class="hljs-comment">// ======================== 状态 ========================</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-title class_">Config</span> = &#123;&#125;;<br>    <span class="hljs-keyword">let</span> <span class="hljs-title class_">IsActive</span> = <span class="hljs-literal">true</span>;               <span class="hljs-comment">// 是否启用精简（切换开关）</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-title class_">EffectiveSelector</span> = <span class="hljs-literal">null</span>;      <span class="hljs-comment">// 缓存的有效选择器</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-title class_">VideoListContainer</span> = <span class="hljs-literal">null</span>;     <span class="hljs-comment">// 缓存的列表容器</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-title class_">Observer</span> = <span class="hljs-literal">null</span>;<br>    <span class="hljs-keyword">let</span> <span class="hljs-title class_">DebounceTimer</span> = <span class="hljs-literal">null</span>;<br>    <span class="hljs-keyword">let</span> <span class="hljs-title class_">PurifierStarted</span> = <span class="hljs-literal">false</span>;         <span class="hljs-comment">// 评论净化器是否已启动</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-title class_">PurifierObservers</span> = [];          <span class="hljs-comment">// 评论净化器的 MutationObserver 列表</span><br>    <span class="hljs-keyword">let</span> <span class="hljs-title class_">LastRun</span> = <span class="hljs-number">0</span>;<br>    <span class="hljs-keyword">const</span> <span class="hljs-variable constant_">THROTTLE_INTERVAL</span> = <span class="hljs-number">200</span>;     <span class="hljs-comment">// 节流间隔（ms）</span><br><br>    <span class="hljs-comment">// ======================== 工具函数 ========================</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">Log</span>(<span class="hljs-params">...Args</span>) &#123;<br>        <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Config</span>.<span class="hljs-property">Debug</span>) <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogPrefix&#x27;</span>), ...<span class="hljs-title class_">Args</span>);<br>    &#125;<br><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">ErrorLog</span>(<span class="hljs-params">...Args</span>) &#123;<br>        <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogPrefix&#x27;</span>), ...<span class="hljs-title class_">Args</span>);<br>    &#125;<br><br>    <span class="hljs-comment">// 安全获取存储</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">GetConfig</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-keyword">const</span> <span class="hljs-title class_">Cfg</span> = &#123;&#125;;<br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> [<span class="hljs-title class_">Key</span>, <span class="hljs-title class_">Def</span>] <span class="hljs-keyword">of</span> <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">entries</span>(<span class="hljs-variable constant_">DEFAULTS</span>)) &#123;<br>            <span class="hljs-keyword">try</span> &#123;<br>                <span class="hljs-keyword">const</span> <span class="hljs-title class_">Val</span> = <span class="hljs-title function_">GM_getValue</span>(<span class="hljs-title class_">Key</span>, <span class="hljs-title class_">Def</span>);<br>                <span class="hljs-title class_">Cfg</span>[<span class="hljs-title class_">Key</span>] = <span class="hljs-title class_">Val</span>;<br>            &#125; <span class="hljs-keyword">catch</span> (E) &#123;<br>                <span class="hljs-title class_">Cfg</span>[<span class="hljs-title class_">Key</span>] = <span class="hljs-title class_">Def</span>;<br>            &#125;<br>        &#125;<br>        <span class="hljs-keyword">return</span> <span class="hljs-title class_">Cfg</span>;<br>    &#125;<br><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">SaveConfig</span>(<span class="hljs-params">Cfg</span>) &#123;<br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> [<span class="hljs-title class_">Key</span>, <span class="hljs-title class_">Val</span>] <span class="hljs-keyword">of</span> <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">entries</span>(<span class="hljs-title class_">Cfg</span>)) &#123;<br>            <span class="hljs-keyword">try</span> &#123;<br>                <span class="hljs-title function_">GM_setValue</span>(<span class="hljs-title class_">Key</span>, <span class="hljs-title class_">Val</span>);<br>            &#125; <span class="hljs-keyword">catch</span> (E) &#123;&#125;<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-comment">// ======================== 评论净化器 (Comment Purifier) ========================</span><br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 检查页面中是否存在 BilibiliBlocker 的标记</span><br><span class="hljs-comment">     * Blocker 会在 bili-comment-user-info 的 shadowRoot 中插入 button[gz_type]</span><br><span class="hljs-comment">     * 一次同步检测，不做任何等待；没装就是没装，直接跳过协调。</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">purifierHasBlockerInstalled</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-keyword">const</span> comments = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">&#x27;bili-comments&#x27;</span>);<br>            <span class="hljs-keyword">if</span> (!comments?.<span class="hljs-property">shadowRoot</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>            <span class="hljs-keyword">const</span> threads = comments.<span class="hljs-property">shadowRoot</span>.<span class="hljs-title function_">querySelectorAll</span>(<span class="hljs-string">&#x27;bili-comment-thread-renderer&#x27;</span>);<br>            <span class="hljs-keyword">if</span> (threads.<span class="hljs-property">length</span> === <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>            <span class="hljs-keyword">return</span> <span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>(threads).<span class="hljs-title function_">some</span>(thread =&amp;gt; &#123;<br>                <span class="hljs-keyword">const</span> comment = thread.<span class="hljs-property">shadowRoot</span>?.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;comment&#x27;</span>);<br>                <span class="hljs-keyword">if</span> (!comment?.<span class="hljs-property">shadowRoot</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>                <span class="hljs-keyword">const</span> userInfo = comment.<span class="hljs-property">shadowRoot</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">&#x27;bili-comment-user-info&#x27;</span>);<br>                <span class="hljs-keyword">if</span> (!userInfo?.<span class="hljs-property">shadowRoot</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>                <span class="hljs-keyword">const</span> info = userInfo.<span class="hljs-property">shadowRoot</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;info&#x27;</span>);<br>                <span class="hljs-keyword">return</span> !!info?.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">&#x27;button[gz_type]&#x27;</span>);<br>            &#125;);<br>        &#125; <span class="hljs-keyword">catch</span> &#123; <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>; &#125;<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 从 bili-comment-renderer 内部获取 #contents 容器</span><br><span class="hljs-comment">     * 穿透 2 层 Shadow DOM: bili-comment-renderer -&amp;gt; bili-rich-text</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">purifierGetContentsEl</span>(<span class="hljs-params">renderer</span>) &#123;<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-keyword">const</span> richText = renderer.<span class="hljs-property">shadowRoot</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">&#x27;bili-rich-text&#x27;</span>);<br>            <span class="hljs-keyword">if</span> (richText &amp;amp;&amp;amp; richText.<span class="hljs-property">shadowRoot</span>) &#123;<br>                <span class="hljs-keyword">return</span> richText.<span class="hljs-property">shadowRoot</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;contents&#x27;</span>);<br>            &#125;<br>        &#125; <span class="hljs-keyword">catch</span> (_) &#123;&#125;<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 查找页面上所有 &amp;lt;bili-comment-renderer&amp;gt;</span><br><span class="hljs-comment">     * 从 &amp;lt;bili-comments&amp;gt; 开始穿透 Shadow DOM</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">purifierFindRenderers</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-keyword">const</span> list = [];<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-keyword">const</span> comments = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">&#x27;bili-comments&#x27;</span>);<br>            <span class="hljs-keyword">if</span> (comments &amp;amp;&amp;amp; comments.<span class="hljs-property">shadowRoot</span>) &#123;<br>                <span class="hljs-keyword">const</span> threads = comments.<span class="hljs-property">shadowRoot</span>.<span class="hljs-title function_">querySelectorAll</span>(<span class="hljs-string">&#x27;bili-comment-thread-renderer&#x27;</span>);<br>                <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> thread <span class="hljs-keyword">of</span> threads) &#123;<br>                    <span class="hljs-keyword">if</span> (thread.<span class="hljs-property">shadowRoot</span>) &#123;<br>                        thread.<span class="hljs-property">shadowRoot</span>.<span class="hljs-title function_">querySelectorAll</span>(<span class="hljs-string">&#x27;bili-comment-renderer&#x27;</span>).<span class="hljs-title function_">forEach</span>(r =&amp;gt; list.<span class="hljs-title function_">push</span>(r));<br>                    &#125;<br>                &#125;<br>            &#125;<br>        &#125; <span class="hljs-keyword">catch</span> (_) &#123;&#125;<br>        <span class="hljs-keyword">return</span> list;<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 处理单条评论：</span><br><span class="hljs-comment">     *   a. 删除所有 @提及 &amp;lt;a data-type=&quot;mention&quot;&amp;gt;</span><br><span class="hljs-comment">     *   b. 清理后有效字符 &amp;lt; 5 -&amp;gt; 隐藏整条评论</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">purifierProcessRenderer</span>(<span class="hljs-params">renderer</span>) &#123;<br>        <span class="hljs-keyword">const</span> doneKey = <span class="hljs-string">&#x27;bcPurified&#x27;</span>;<br>        <span class="hljs-keyword">if</span> (renderer.<span class="hljs-property">dataset</span>[doneKey]) <span class="hljs-keyword">return</span>;<br>        renderer.<span class="hljs-property">dataset</span>[doneKey] = <span class="hljs-string">&#x27;1&#x27;</span>;<br><br>        <span class="hljs-keyword">const</span> contents = <span class="hljs-title function_">purifierGetContentsEl</span>(renderer);<br>        <span class="hljs-keyword">if</span> (!contents) <span class="hljs-keyword">return</span>;<br><br>        <span class="hljs-comment">// 删除所有 @提及标签</span><br>        <span class="hljs-keyword">const</span> mentions = contents.<span class="hljs-title function_">querySelectorAll</span>(<span class="hljs-string">&#x27;a[data-type=&quot;mention&quot;]&#x27;</span>);<br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> m <span class="hljs-keyword">of</span> mentions) &#123;<br>            m.<span class="hljs-title function_">remove</span>();<br>        &#125;<br><br>        <span class="hljs-comment">// 计算剩余有效字符</span><br>        <span class="hljs-keyword">const</span> remaining = contents.<span class="hljs-property">textContent</span>.<span class="hljs-title function_">replace</span>(<span class="hljs-regexp">/\s+/g</span>, <span class="hljs-string">&#x27;&#x27;</span>).<span class="hljs-title function_">trim</span>();<br><br>        <span class="hljs-comment">// 不足5字 -&amp;gt; 隐藏整条评论</span><br>        <span class="hljs-keyword">if</span> (remaining.<span class="hljs-property">length</span> &amp;lt; <span class="hljs-number">5</span>) &#123;<br>            renderer.<span class="hljs-property">style</span>.<span class="hljs-property">display</span> = <span class="hljs-string">&#x27;none&#x27;</span>;<br>            <span class="hljs-keyword">try</span> &#123;<br>                <span class="hljs-keyword">const</span> threadRenderer = renderer.<span class="hljs-title function_">getRootNode</span>().<span class="hljs-property">host</span>;<br>                <span class="hljs-keyword">if</span> (threadRenderer) threadRenderer.<span class="hljs-property">style</span>.<span class="hljs-property">display</span> = <span class="hljs-string">&#x27;none&#x27;</span>;<br>            &#125; <span class="hljs-keyword">catch</span> (_) &#123;&#125;<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 启动评论净化器</span><br><span class="hljs-comment">     * - 监听评论区动态加载，自动处理新增评论</span><br><span class="hljs-comment">     * - 首次扫描零延迟；检测到 Blocker 只在日志中标记，不影响流程</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">startCommentPurifier</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-keyword">if</span> (!<span class="hljs-title class_">Config</span>.<span class="hljs-property">EnableCommentPurifier</span>) <span class="hljs-keyword">return</span>;<br>        <span class="hljs-keyword">if</span> (<span class="hljs-title class_">PurifierStarted</span>) <span class="hljs-keyword">return</span>;<br>        <span class="hljs-title class_">PurifierStarted</span> = <span class="hljs-literal">true</span>;<br><br>        <span class="hljs-keyword">let</span> scanTimer = <span class="hljs-literal">null</span>;<br><br>        <span class="hljs-keyword">function</span> <span class="hljs-title function_">scheduleScan</span>(<span class="hljs-params"></span>) &#123;<br>            <span class="hljs-keyword">if</span> (scanTimer) <span class="hljs-built_in">clearTimeout</span>(scanTimer);<br>            scanTimer = <span class="hljs-built_in">setTimeout</span>(() =&amp;gt; &#123;<br>                <span class="hljs-title function_">purifierFindRenderers</span>().<span class="hljs-title function_">forEach</span>(purifierProcessRenderer);<br>                scanTimer = <span class="hljs-literal">null</span>;<br>            &#125;, <span class="hljs-number">800</span>);<br>        &#125;<br><br>        <span class="hljs-comment">// MutationObserver 监听 bili-comments 的 shadowRoot</span><br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-keyword">const</span> comments = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">&#x27;bili-comments&#x27;</span>);<br>            <span class="hljs-keyword">if</span> (comments?.<span class="hljs-property">shadowRoot</span>) &#123;<br>                <span class="hljs-keyword">const</span> obs = <span class="hljs-keyword">new</span> <span class="hljs-title class_">MutationObserver</span>(() =&amp;gt; <span class="hljs-title function_">scheduleScan</span>());<br>                obs.<span class="hljs-title function_">observe</span>(comments.<span class="hljs-property">shadowRoot</span>, &#123; <span class="hljs-attr">childList</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">subtree</span>: <span class="hljs-literal">true</span> &#125;);<br>                <span class="hljs-title class_">PurifierObservers</span>.<span class="hljs-title function_">push</span>(obs);<br>            &#125;<br>        &#125; <span class="hljs-keyword">catch</span> (_) &#123;&#125;<br><br>        <span class="hljs-comment">// MutationObserver 监听 document.body（兜底）</span><br>        <span class="hljs-keyword">const</span> obsBody = <span class="hljs-keyword">new</span> <span class="hljs-title class_">MutationObserver</span>(() =&amp;gt; <span class="hljs-title function_">scheduleScan</span>());<br>        obsBody.<span class="hljs-title function_">observe</span>(<span class="hljs-variable language_">document</span>.<span class="hljs-property">body</span>, &#123; <span class="hljs-attr">childList</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">subtree</span>: <span class="hljs-literal">true</span> &#125;);<br>        <span class="hljs-title class_">PurifierObservers</span>.<span class="hljs-title function_">push</span>(obsBody);<br><br>        <span class="hljs-comment">// 首次扫描——不等待、不轮询，直接执行</span><br>        <span class="hljs-title function_">purifierFindRenderers</span>().<span class="hljs-title function_">forEach</span>(purifierProcessRenderer);<br><br>        <span class="hljs-keyword">const</span> hasBlocker = <span class="hljs-title function_">purifierHasBlockerInstalled</span>();<br>        <span class="hljs-title class_">Log</span>(<span class="hljs-string">&#x27;评论净化器已启动&#x27;</span> + (hasBlocker ? <span class="hljs-string">&#x27;（检测到 BilibiliBlocker，兼容模式）&#x27;</span> : <span class="hljs-string">&#x27;&#x27;</span>));<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 停止评论净化器</span><br><span class="hljs-comment">     * - 断开所有 MutationObserver</span><br><span class="hljs-comment">     * - 已处理的评论保持状态不变</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">stopCommentPurifier</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-title class_">PurifierStarted</span> = <span class="hljs-literal">false</span>;<br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> obs <span class="hljs-keyword">of</span> <span class="hljs-title class_">PurifierObservers</span>) &#123;<br>            <span class="hljs-keyword">try</span> &#123; obs.<span class="hljs-title function_">disconnect</span>(); &#125; <span class="hljs-keyword">catch</span> (_) &#123;&#125;<br>        &#125;<br>        <span class="hljs-title class_">PurifierObservers</span> = [];<br>        <span class="hljs-title class_">Log</span>(<span class="hljs-string">&#x27;评论净化器已停止&#x27;</span>);<br>    &#125;<br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 切换评论净化器（供 TM 菜单命令调用）</span><br><span class="hljs-comment">     * 同时保存配置变更</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">toggleCommentPurifier</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-title class_">Config</span>.<span class="hljs-property">EnableCommentPurifier</span> = !<span class="hljs-title class_">Config</span>.<span class="hljs-property">EnableCommentPurifier</span>;<br>        <span class="hljs-title class_">SaveConfig</span>(&#123; <span class="hljs-title class_">EnableCommentPurifier</span>: <span class="hljs-title class_">Config</span>.<span class="hljs-property">EnableCommentPurifier</span> &#125;);<br>        <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Config</span>.<span class="hljs-property">EnableCommentPurifier</span>) &#123;<br>            <span class="hljs-title function_">startCommentPurifier</span>();<br>        &#125; <span class="hljs-keyword">else</span> &#123;<br>            <span class="hljs-title function_">stopCommentPurifier</span>();<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-comment">// ======================== 元素去除 (Element Removal) ========================</span><br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 可去除的元素预设列表</span><br><span class="hljs-comment">     * 每条包含：唯一 id、适用域名 host、显示名称 name、CSS选择器列表 selectors（多版本兼容）</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">const</span> <span class="hljs-variable constant_">ELEMENT_REMOVAL_PRESETS</span> = [<br>        &#123;<br>            <span class="hljs-attr">id</span>: <span class="hljs-string">&#x27;carousel&#x27;</span>,<br>            <span class="hljs-attr">host</span>: <span class="hljs-string">&#x27;www.bilibili.com&#x27;</span>,<br>            <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;首页轮播图&#x27;</span>,<br>            <span class="hljs-attr">selectors</span>: [<br>                <span class="hljs-string">&#x27;#i_cecream &amp;gt; div.bili-feed4:last-child &amp;gt; main.bili-feed4-layout:nth-child(3) &amp;gt; div.feed2:last-child &amp;gt; div.recommended-container_floor-aside &amp;gt; div.container.is-version8:first-child &amp;gt; div.recommended-swipe.grid-anchor:first-child &amp;gt; div.recommended-swipe-core &amp;gt; div.recommended-swipe-body:last-child &amp;gt; div.carousel-area &amp;gt; div.carousel&#x27;</span>,<br>                <span class="hljs-string">&#x27;#app &amp;gt; div.bili-feed4:last-child &amp;gt; main.bili-feed4-layout:nth-child(2) &amp;gt; div.feed2:last-child &amp;gt; div.recommended-container_floor-aside &amp;gt; div.container.is-version8:first-child &amp;gt; div.recommended-swipe:first-child&#x27;</span>,<br>                <span class="hljs-string">&#x27;#app &amp;gt; div.bili-feed4:last-child &amp;gt; main.bili-feed4-layout:nth-child(3) &amp;gt; div.feed2:last-child &amp;gt; div.recommended-container_floor-aside &amp;gt; div.container.is-version8:first-child &amp;gt; div.recommended-swipe:first-child&#x27;</span><br>            ]<br>        &#125;,<br>        &#123;<br>            <span class="hljs-attr">id</span>: <span class="hljs-string">&#x27;right-channel&#x27;</span>,<br>            <span class="hljs-attr">host</span>: <span class="hljs-string">&#x27;www.bilibili.com&#x27;</span>,<br>            <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;右侧频道导航&#x27;</span>,<br>            <span class="hljs-attr">selectors</span>: [<br>                <span class="hljs-string">&#x27;#i_cecream &amp;gt; div.bili-feed4:last-child &amp;gt; div.bili-header.large-header:first-child &amp;gt; div.bili-header__channel:nth-child(3) &amp;gt; div.right-channel-container:last-child&#x27;</span>,<br>                <span class="hljs-string">&#x27;#app &amp;gt; div.bili-feed4:last-child &amp;gt; div.bili-header.large-header:first-child &amp;gt; div.bili-header__channel:last-child &amp;gt; div.right-channel-container:last-child&#x27;</span><br>            ]<br>        &#125;,<br>        &#123;<br>            <span class="hljs-attr">id</span>: <span class="hljs-string">&#x27;channel-icons&#x27;</span>,<br>            <span class="hljs-attr">host</span>: <span class="hljs-string">&#x27;www.bilibili.com&#x27;</span>,<br>            <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;频道图标行&#x27;</span>,<br>            <span class="hljs-attr">selectors</span>: [<br>                <span class="hljs-string">&#x27;#i_cecream &amp;gt; div.bili-feed4:last-child &amp;gt; div.bili-header.large-header:first-child &amp;gt; div.bili-header__channel:nth-child(3) &amp;gt; div.channel-icons:first-child&#x27;</span>,<br>                <span class="hljs-string">&#x27;#app &amp;gt; div.bili-feed4:last-child &amp;gt; div.bili-header.large-header:first-child &amp;gt; div.bili-header__channel:last-child &amp;gt; div.channel-icons:first-child&#x27;</span><br>            ]<br>        &#125;,<br>        &#123;<br>            <span class="hljs-attr">id</span>: <span class="hljs-string">&#x27;channel-bar&#x27;</span>,<br>            <span class="hljs-attr">host</span>: <span class="hljs-string">&#x27;www.bilibili.com&#x27;</span>,<br>            <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;频道栏（整体）&#x27;</span>,<br>            <span class="hljs-attr">selectors</span>: [<br>                <span class="hljs-string">&#x27;#app &amp;gt; div.bili-feed4:last-child &amp;gt; div.bili-header.large-header:first-child &amp;gt; div.bili-header__channel:last-child&#x27;</span><br>            ]<br>        &#125;,<br>        &#123;<br>            <span class="hljs-attr">id</span>: <span class="hljs-string">&#x27;creation-entry&#x27;</span>,<br>            <span class="hljs-attr">host</span>: <span class="hljs-string">&#x27;www.bilibili.com&#x27;</span>,<br>            <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;创作中心入口&#x27;</span>,<br>            <span class="hljs-attr">selectors</span>: [<br>                <span class="hljs-string">&#x27;#i_cecream &amp;gt; div.bili-feed4:last-child &amp;gt; div.bili-header.large-header:first-child &amp;gt; div.bili-header__bar:first-child &amp;gt; ul.left-entry:first-child &amp;gt; li.v-popover-wrap.left-loc-entry:nth-child(8) &amp;gt; div&#x27;</span><br>            ]<br>        &#125;,<br>        &#123;<br>            <span class="hljs-attr">id</span>: <span class="hljs-string">&#x27;upload-entry&#x27;</span>,<br>            <span class="hljs-attr">host</span>: <span class="hljs-string">&#x27;www.bilibili.com&#x27;</span>,<br>            <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;投稿入口&#x27;</span>,<br>            <span class="hljs-attr">selectors</span>: [<br>                <span class="hljs-string">&#x27;#i_cecream &amp;gt; div.bili-feed4:last-child &amp;gt; div.bili-header.large-header:first-child &amp;gt; div.bili-header__bar:first-child &amp;gt; ul.left-entry:first-child &amp;gt; li.v-popover-wrap.left-loc-entry:nth-child(9) &amp;gt; div &amp;gt; a.loc-entry.loc-moveclip&#x27;</span><br>            ]<br>        &#125;,<br>        &#123;<br>            <span class="hljs-attr">id</span>: <span class="hljs-string">&#x27;live-entry&#x27;</span>,<br>            <span class="hljs-attr">host</span>: <span class="hljs-string">&#x27;www.bilibili.com&#x27;</span>,<br>            <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;直播入口&#x27;</span>,<br>            <span class="hljs-attr">selectors</span>: [<br>                <span class="hljs-string">&#x27;#i_cecream &amp;gt; div.bili-feed4:last-child &amp;gt; div.bili-header.large-header:first-child &amp;gt; div.bili-header__bar:first-child &amp;gt; ul.left-entry:first-child &amp;gt; li.v-popover-wrap:last-child&#x27;</span><br>            ]<br>        &#125;,<br>        &#123;<br>            <span class="hljs-attr">id</span>: <span class="hljs-string">&#x27;dynamic-entry&#x27;</span>,<br>            <span class="hljs-attr">host</span>: <span class="hljs-string">&#x27;www.bilibili.com&#x27;</span>,<br>            <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;动态入口&#x27;</span>,<br>            <span class="hljs-attr">selectors</span>: [<br>                <span class="hljs-string">&#x27;#i_cecream &amp;gt; div.bili-feed4:last-child &amp;gt; div.bili-header.large-header:first-child &amp;gt; div.bili-header__bar:first-child &amp;gt; ul.left-entry:first-child &amp;gt; li.v-popover-wrap:nth-child(5)&#x27;</span><br>            ]<br>        &#125;,<br>        &#123;<br>            <span class="hljs-attr">id</span>: <span class="hljs-string">&#x27;vip-entry&#x27;</span>,<br>            <span class="hljs-attr">host</span>: <span class="hljs-string">&#x27;www.bilibili.com&#x27;</span>,<br>            <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;大会员VIP&#x27;</span>,<br>            <span class="hljs-attr">selectors</span>: [<br>                <span class="hljs-string">&#x27;#i_cecream &amp;gt; div.bili-feed4:last-child &amp;gt; div.bili-header.large-header:first-child &amp;gt; div.bili-header__bar:first-child &amp;gt; ul.right-entry:last-child &amp;gt; div.vip-wrap:nth-child(2)&#x27;</span><br>            ]<br>        &#125;,<br>        &#123;<br>            <span class="hljs-attr">id</span>: <span class="hljs-string">&#x27;adblock-tips&#x27;</span>,<br>            <span class="hljs-attr">host</span>: <span class="hljs-string">&#x27;www.bilibili.com&#x27;</span>,<br>            <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;广告提示条&#x27;</span>,<br>            <span class="hljs-attr">selectors</span>: [<br>                <span class="hljs-string">&#x27;#i_cecream &amp;gt; div.adblock-tips:nth-child(2)&#x27;</span><br>            ]<br>        &#125;,<br>        &#123;<br>            <span class="hljs-attr">id</span>: <span class="hljs-string">&#x27;left-entries&#x27;</span>,<br>            <span class="hljs-attr">host</span>: <span class="hljs-string">&#x27;www.bilibili.com&#x27;</span>,<br>            <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;左侧全部入口&#x27;</span>,<br>            <span class="hljs-attr">selectors</span>: [<br>                <span class="hljs-string">&#x27;#app &amp;gt; div.bili-feed4:last-child &amp;gt; div.bili-header.large-header:first-child &amp;gt; div.bili-header__bar:first-child &amp;gt; ul.left-entry:first-child&#x27;</span><br>            ]<br>        &#125;,<br>        &#123;<br>            <span class="hljs-attr">id</span>: <span class="hljs-string">&#x27;palette-btn&#x27;</span>,<br>            <span class="hljs-attr">host</span>: <span class="hljs-string">&#x27;www.bilibili.com&#x27;</span>,<br>            <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;调色板浮窗&#x27;</span>,<br>            <span class="hljs-attr">selectors</span>: [<br>                <span class="hljs-string">&#x27;#app &amp;gt; div.bili-feed4:last-child &amp;gt; div.palette-button-outer.palette-feed4:nth-child(4)&#x27;</span><br>            ]<br>        &#125;,<br>        &#123;<br>            <span class="hljs-attr">id</span>: <span class="hljs-string">&#x27;space-notif&#x27;</span>,<br>            <span class="hljs-attr">host</span>: <span class="hljs-string">&#x27;space.bilibili.com&#x27;</span>,<br>            <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;空间-消息通知&#x27;</span>,<br>            <span class="hljs-attr">selectors</span>: [<br>                <span class="hljs-string">&#x27;#biliMainHeader &amp;gt; div.bili-header &amp;gt; div.bili-header__bar.mini-header:first-child &amp;gt; ul.left-entry:first-child &amp;gt; li.v-popover-wrap:last-child&#x27;</span><br>            ]<br>        &#125;<br>    ];<br><br>    <span class="hljs-keyword">let</span> <span class="hljs-title class_">ElementRemovalStates</span> = &#123;&#125;;  <span class="hljs-comment">// &#123; presetId: &#123; element, originalDisplay &#125; &#125;</span><br><br>    <span class="hljs-comment">/**</span><br><span class="hljs-comment">     * 应用/恢复元素去除</span><br><span class="hljs-comment">     * 按配置隐藏勾选的元素，恢复取消勾选的元素</span><br><span class="hljs-comment">     */</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">applyElementRemoval</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-keyword">const</span> enabled = <span class="hljs-title class_">Config</span>.<span class="hljs-property">RemovedElements</span> || &#123;&#125;;<br>        <span class="hljs-keyword">const</span> host = location.<span class="hljs-property">hostname</span>;<br><br>        <span class="hljs-comment">// 先恢复已取消勾选的元素</span><br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> id <span class="hljs-keyword">of</span> <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">keys</span>(<span class="hljs-title class_">ElementRemovalStates</span>)) &#123;<br>            <span class="hljs-keyword">if</span> (!enabled[id]) &#123;<br>                <span class="hljs-title class_">ElementRemovalStates</span>[id].<span class="hljs-property">element</span>.<span class="hljs-property">style</span>.<span class="hljs-property">display</span> = <span class="hljs-title class_">ElementRemovalStates</span>[id].<span class="hljs-property">originalDisplay</span>;<br>                <span class="hljs-keyword">delete</span> <span class="hljs-title class_">ElementRemovalStates</span>[id];<br>            &#125;<br>        &#125;<br><br>        <span class="hljs-comment">// 再应用新勾选的元素</span><br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> preset <span class="hljs-keyword">of</span> <span class="hljs-variable constant_">ELEMENT_REMOVAL_PRESETS</span>) &#123;<br>            <span class="hljs-keyword">if</span> (preset.<span class="hljs-property">host</span> !== host) <span class="hljs-keyword">continue</span>;<br>            <span class="hljs-keyword">if</span> (!enabled[preset.<span class="hljs-property">id</span>]) <span class="hljs-keyword">continue</span>;<br>            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">ElementRemovalStates</span>[preset.<span class="hljs-property">id</span>]) <span class="hljs-keyword">continue</span>; <span class="hljs-comment">// 已隐藏</span><br><br>            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> sel <span class="hljs-keyword">of</span> preset.<span class="hljs-property">selectors</span>) &#123;<br>                <span class="hljs-keyword">try</span> &#123;<br>                    <span class="hljs-keyword">const</span> el = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(sel);<br>                    <span class="hljs-keyword">if</span> (el) &#123;<br>                        <span class="hljs-title class_">ElementRemovalStates</span>[preset.<span class="hljs-property">id</span>] = &#123;<br>                            <span class="hljs-attr">element</span>: el,<br>                            <span class="hljs-attr">originalDisplay</span>: el.<span class="hljs-property">style</span>.<span class="hljs-property">display</span> || <span class="hljs-string">&#x27;&#x27;</span><br>                        &#125;;<br>                        el.<span class="hljs-property">style</span>.<span class="hljs-property">display</span> = <span class="hljs-string">&#x27;none&#x27;</span>;<br>                        <span class="hljs-keyword">break</span>; <span class="hljs-comment">// 找到一个版本即隐藏，不再试其他</span><br>                    &#125;<br>                &#125; <span class="hljs-keyword">catch</span> (_) &#123;&#125;<br>            &#125;<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-comment">// ======================== 选择器探测与缓存 ========================</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">DetectSelector</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-keyword">if</span> (<span class="hljs-title class_">EffectiveSelector</span>) <span class="hljs-keyword">return</span> <span class="hljs-title class_">EffectiveSelector</span>;<br><br>        <span class="hljs-comment">// 第一梯队：具体选择器（优先使用，构建联合选择器）</span><br>        <span class="hljs-keyword">const</span> <span class="hljs-title class_">SpecificCandidates</span> = [<br>            <span class="hljs-string">&#x27;.bili-video-card&#x27;</span>,<br>            <span class="hljs-string">&#x27;.feed-card&#x27;</span>,<br>            <span class="hljs-string">&#x27;.bili-feed-card&#x27;</span>,<br>            <span class="hljs-string">&#x27;.floor-single-card&#x27;</span>,<br>            <span class="hljs-string">&#x27;.floor-card&#x27;</span>,<br>            <span class="hljs-string">&#x27;.video-item&#x27;</span>,<br>            <span class="hljs-string">&#x27;.feed-item&#x27;</span><br>        ];<br><br>        <span class="hljs-comment">// 第二梯队：宽泛选择器（仅在具体选择器全部失败时使用）</span><br>        <span class="hljs-keyword">const</span> <span class="hljs-title class_">BroadCandidates</span> = [<br>            <span class="hljs-string">&#x27;[class*=&quot;video-card&quot;]&#x27;</span>,<br>            <span class="hljs-string">&#x27;[class*=&quot;bili-video&quot;]&#x27;</span>,<br>            <span class="hljs-string">&#x27;[class*=&quot;feed-card&quot;]&#x27;</span>,<br>            <span class="hljs-string">&#x27;[class*=&quot;feed-item&quot;]&#x27;</span>,<br>            <span class="hljs-string">&#x27;[class*=&quot;floor-card&quot;]&#x27;</span><br>        ];<br><br>        <span class="hljs-comment">// 收集所有能匹配到元素的具体选择器，构建联合选择器</span><br>        <span class="hljs-keyword">const</span> <span class="hljs-title class_">MatchedSelectors</span> = [];<br><br>        <span class="hljs-comment">// 首先尝试通过 data 属性查找</span><br>        <span class="hljs-keyword">const</span> <span class="hljs-title class_">DataCandidates</span> = [<br>            <span class="hljs-string">&#x27;[data-video-id]&#x27;</span>,<br>            <span class="hljs-string">&#x27;[data-aid]&#x27;</span><br>        ];<br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-title class_">Sel</span> <span class="hljs-keyword">of</span> <span class="hljs-title class_">DataCandidates</span>) &#123;<br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">Els</span> = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelectorAll</span>(<span class="hljs-title class_">Sel</span>);<br>            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Els</span>.<span class="hljs-property">length</span> &amp;gt; <span class="hljs-number">0</span>) &#123;<br>                <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-title class_">El</span> <span class="hljs-keyword">of</span> <span class="hljs-title class_">Els</span>) &#123;<br>                    <span class="hljs-keyword">const</span> <span class="hljs-title class_">Card</span> = <span class="hljs-title class_">El</span>.<span class="hljs-title function_">closest</span>(<span class="hljs-string">&#x27;.bili-video-card, .feed-card, .bili-feed-card, .video-item, .feed-item, .floor-single-card, .floor-card, [class*=&quot;video-card&quot;], [class*=&quot;feed-card&quot;], [class*=&quot;feed-item&quot;], [class*=&quot;floor-card&quot;]&#x27;</span>);<br>                    <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Card</span>) &#123;<br>                        <span class="hljs-keyword">const</span> <span class="hljs-title class_">CardSel</span> = (<span class="hljs-title class_">Card</span>.<span class="hljs-property">tagName</span> === <span class="hljs-title class_">El</span>.<span class="hljs-property">tagName</span>) ? <span class="hljs-title class_">Sel</span> :<br>                            <span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>(<span class="hljs-title class_">Card</span>.<span class="hljs-property">classList</span>).<span class="hljs-title function_">map</span>(C =&amp;gt; <span class="hljs-string">&#x27;.&#x27;</span> + C).<span class="hljs-title function_">join</span>(<span class="hljs-string">&#x27;&#x27;</span>);<br>                        <span class="hljs-keyword">if</span> (!<span class="hljs-title class_">MatchedSelectors</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-title class_">CardSel</span>)) &#123;<br>                            <span class="hljs-title class_">MatchedSelectors</span>.<span class="hljs-title function_">push</span>(<span class="hljs-title class_">CardSel</span>);<br>                        &#125;<br>                    &#125;<br>                &#125;<br>            &#125;<br>        &#125;<br><br>        <span class="hljs-comment">// 遍历具体候选选择器，收集所有匹配到的</span><br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-title class_">Sel</span> <span class="hljs-keyword">of</span> <span class="hljs-title class_">SpecificCandidates</span>) &#123;<br>            <span class="hljs-keyword">try</span> &#123;<br>                <span class="hljs-keyword">const</span> <span class="hljs-title class_">Els</span> = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelectorAll</span>(<span class="hljs-title class_">Sel</span>);<br>                <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Els</span>.<span class="hljs-property">length</span> &amp;gt; <span class="hljs-number">0</span>) &#123;<br>                    <span class="hljs-keyword">if</span> (!<span class="hljs-title class_">MatchedSelectors</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-title class_">Sel</span>)) &#123;<br>                        <span class="hljs-title class_">MatchedSelectors</span>.<span class="hljs-title function_">push</span>(<span class="hljs-title class_">Sel</span>);<br>                    &#125;<br>                &#125;<br>            &#125; <span class="hljs-keyword">catch</span> (E) &#123;<br>                <span class="hljs-comment">// 跳过无效选择器</span><br>            &#125;<br>        &#125;<br><br>        <span class="hljs-keyword">if</span> (<span class="hljs-title class_">MatchedSelectors</span>.<span class="hljs-property">length</span> &amp;gt; <span class="hljs-number">0</span>) &#123;<br>            <span class="hljs-title class_">EffectiveSelector</span> = <span class="hljs-title class_">MatchedSelectors</span>.<span class="hljs-title function_">join</span>(<span class="hljs-string">&#x27;, &#x27;</span>);<br>            <span class="hljs-title class_">Log</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogSelectorFound&#x27;</span>), <span class="hljs-title class_">EffectiveSelector</span>);<br>            <span class="hljs-keyword">return</span> <span class="hljs-title class_">EffectiveSelector</span>;<br>        &#125;<br><br>        <span class="hljs-comment">// 具体选择器全部失败时，尝试宽泛选择器（仅取第一个匹配的）</span><br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-title class_">Sel</span> <span class="hljs-keyword">of</span> <span class="hljs-title class_">BroadCandidates</span>) &#123;<br>            <span class="hljs-keyword">try</span> &#123;<br>                <span class="hljs-keyword">const</span> <span class="hljs-title class_">Els</span> = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelectorAll</span>(<span class="hljs-title class_">Sel</span>);<br>                <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Els</span>.<span class="hljs-property">length</span> &amp;gt; <span class="hljs-number">0</span>) &#123;<br>                    <span class="hljs-title class_">EffectiveSelector</span> = <span class="hljs-title class_">Sel</span>;<br>                    <span class="hljs-title class_">Log</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogSelectorFound&#x27;</span>), <span class="hljs-title class_">EffectiveSelector</span>);<br>                    <span class="hljs-keyword">return</span> <span class="hljs-title class_">EffectiveSelector</span>;<br>                &#125;<br>            &#125; <span class="hljs-keyword">catch</span> (E) &#123;<br>                <span class="hljs-comment">// 跳过无效选择器</span><br>            &#125;<br>        &#125;<br><br>        <span class="hljs-comment">// 最后回退：查找包含 /video/, /bangumi/ 或 live.bilibili.com 链接的父级卡片</span><br>        <span class="hljs-keyword">const</span> <span class="hljs-title class_">LinkSelectors</span> = [<br>            <span class="hljs-string">&#x27;a[href*=&quot;/video/&quot;]&#x27;</span>,<br>            <span class="hljs-string">&#x27;a[href*=&quot;/bangumi/&quot;]&#x27;</span>,<br>            <span class="hljs-string">&#x27;a[href*=&quot;live.bilibili.com&quot;]&#x27;</span><br>        ];<br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-title class_">LinkSel</span> <span class="hljs-keyword">of</span> <span class="hljs-title class_">LinkSelectors</span>) &#123;<br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">Links</span> = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelectorAll</span>(<span class="hljs-title class_">LinkSel</span>);<br>            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-title class_">Link</span> <span class="hljs-keyword">of</span> <span class="hljs-title class_">Links</span>) &#123;<br>                <span class="hljs-keyword">let</span> <span class="hljs-title class_">Parent</span> = <span class="hljs-title class_">Link</span>.<span class="hljs-property">parentElement</span>;<br>                <span class="hljs-keyword">let</span> <span class="hljs-title class_">Depth</span> = <span class="hljs-number">0</span>;<br>                <span class="hljs-keyword">while</span> (<span class="hljs-title class_">Parent</span> &amp;amp;&amp;amp; <span class="hljs-title class_">Depth</span> &amp;lt; <span class="hljs-number">5</span>) &#123;<br>                    <span class="hljs-keyword">const</span> <span class="hljs-title class_">Cls</span> = <span class="hljs-title class_">Parent</span>.<span class="hljs-property">className</span> || <span class="hljs-string">&#x27;&#x27;</span>;<br>                    <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Cls</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;card&#x27;</span>) || <span class="hljs-title class_">Cls</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;item&#x27;</span>) || <span class="hljs-title class_">Cls</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;feed&#x27;</span>) || <span class="hljs-title class_">Cls</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;video&#x27;</span>) || <span class="hljs-title class_">Cls</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;floor&#x27;</span>)) &#123;<br>                        <span class="hljs-title class_">EffectiveSelector</span> = <span class="hljs-string">&#x27;.&#x27;</span> + <span class="hljs-title class_">Cls</span>.<span class="hljs-title function_">split</span>(<span class="hljs-string">&#x27; &#x27;</span>).<span class="hljs-title function_">join</span>(<span class="hljs-string">&#x27;.&#x27;</span>);<br>                        <span class="hljs-title class_">Log</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogSelectorFallback&#x27;</span>), <span class="hljs-title class_">EffectiveSelector</span>);<br>                        <span class="hljs-keyword">return</span> <span class="hljs-title class_">EffectiveSelector</span>;<br>                    &#125;<br>                    <span class="hljs-title class_">Parent</span> = <span class="hljs-title class_">Parent</span>.<span class="hljs-property">parentElement</span>;<br>                    <span class="hljs-title class_">Depth</span>++;<br>                &#125;<br>            &#125;<br>        &#125;<br><br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br>    &#125;<br><br>    <span class="hljs-comment">// 获取视频列表容器（缩小观察范围）</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">DetectContainer</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-keyword">if</span> (<span class="hljs-title class_">VideoListContainer</span>) <span class="hljs-keyword">return</span> <span class="hljs-title class_">VideoListContainer</span>;<br>        <span class="hljs-keyword">const</span> <span class="hljs-title class_">Containers</span> = [<br>            <span class="hljs-string">&#x27;.bili-feed4&#x27;</span>,<br>            <span class="hljs-string">&#x27;.bili-feed&#x27;</span>,<br>            <span class="hljs-string">&#x27;.feed2&#x27;</span>,<br>            <span class="hljs-string">&#x27;.feed-list&#x27;</span>,<br>            <span class="hljs-string">&#x27;.video-list&#x27;</span>,<br>            <span class="hljs-string">&#x27;.bili-video-list&#x27;</span>,<br>            <span class="hljs-string">&#x27;.recommend-container&#x27;</span>,<br>            <span class="hljs-string">&#x27;.recommended-container_floor-aside&#x27;</span><br>        ];<br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-title class_">Sel</span> <span class="hljs-keyword">of</span> <span class="hljs-title class_">Containers</span>) &#123;<br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">El</span> = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-title class_">Sel</span>);<br>            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">El</span>) &#123;<br>                <span class="hljs-title class_">VideoListContainer</span> = <span class="hljs-title class_">El</span>;<br>                <span class="hljs-title class_">Log</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogContainerFound&#x27;</span>), <span class="hljs-title class_">Sel</span>);<br>                <span class="hljs-keyword">return</span> <span class="hljs-title class_">El</span>;<br>            &#125;<br>        &#125;<br>        <span class="hljs-title class_">VideoListContainer</span> = <span class="hljs-variable language_">document</span>.<span class="hljs-property">body</span>;<br>        <span class="hljs-keyword">return</span> <span class="hljs-title class_">VideoListContainer</span>;<br>    &#125;<br><br>    <span class="hljs-comment">// ======================== 卡片显示/隐藏辅助函数 ========================</span><br>    <span class="hljs-comment">// 需要同时隐藏的祖先包装器类名（解决 B站 CSS Grid 单元格不塌陷问题）</span><br>    <span class="hljs-keyword">const</span> <span class="hljs-variable constant_">WRAPPER_CLASSES</span> = [<span class="hljs-string">&#x27;bili-feed-card&#x27;</span>, <span class="hljs-string">&#x27;feed-card&#x27;</span>];<br><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">ApplyHideStyles</span>(<span class="hljs-params">El</span>) &#123;<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">classList</span>.<span class="hljs-title function_">add</span>(<span class="hljs-string">&#x27;BiliLimitedHide&#x27;</span>);<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">display</span> = <span class="hljs-string">&#x27;none&#x27;</span>;<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">visibility</span> = <span class="hljs-string">&#x27;hidden&#x27;</span>;<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">opacity</span> = <span class="hljs-string">&#x27;0&#x27;</span>;<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">height</span> = <span class="hljs-string">&#x27;0&#x27;</span>;<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">margin</span> = <span class="hljs-string">&#x27;0&#x27;</span>;<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">padding</span> = <span class="hljs-string">&#x27;0&#x27;</span>;<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">overflow</span> = <span class="hljs-string">&#x27;hidden&#x27;</span>;<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">flex</span> = <span class="hljs-string">&#x27;0 0 0&#x27;</span>;<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">position</span> = <span class="hljs-string">&#x27;absolute&#x27;</span>;<br>    &#125;<br><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">ClearHideStyles</span>(<span class="hljs-params">El</span>) &#123;<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">classList</span>.<span class="hljs-title function_">remove</span>(<span class="hljs-string">&#x27;BiliLimitedHide&#x27;</span>);<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">display</span> = <span class="hljs-string">&#x27;&#x27;</span>;<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">visibility</span> = <span class="hljs-string">&#x27;&#x27;</span>;<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">opacity</span> = <span class="hljs-string">&#x27;&#x27;</span>;<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">height</span> = <span class="hljs-string">&#x27;&#x27;</span>;<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">margin</span> = <span class="hljs-string">&#x27;&#x27;</span>;<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">padding</span> = <span class="hljs-string">&#x27;&#x27;</span>;<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">overflow</span> = <span class="hljs-string">&#x27;&#x27;</span>;<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">position</span> = <span class="hljs-string">&#x27;&#x27;</span>;<br>        <span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">flex</span> = <span class="hljs-string">&#x27;&#x27;</span>;<br>    &#125;<br><br>    <span class="hljs-comment">// 隐藏卡片及其祖先包装器（.bili-feed-card, .feed-card）</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">HideCardTree</span>(<span class="hljs-params">Card</span>) &#123;<br>        <span class="hljs-title class_">ApplyHideStyles</span>(<span class="hljs-title class_">Card</span>);<br>        <span class="hljs-keyword">let</span> <span class="hljs-title class_">Ancestor</span> = <span class="hljs-title class_">Card</span>.<span class="hljs-property">parentElement</span>;<br>        <span class="hljs-keyword">while</span> (<span class="hljs-title class_">Ancestor</span> &amp;amp;&amp;amp; <span class="hljs-title class_">Ancestor</span> !== <span class="hljs-variable language_">document</span>.<span class="hljs-property">body</span>) &#123;<br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">Cls</span> = (<span class="hljs-title class_">Ancestor</span>.<span class="hljs-property">className</span> || <span class="hljs-string">&#x27;&#x27;</span>).<span class="hljs-title function_">toLowerCase</span>();<br>            <span class="hljs-keyword">let</span> <span class="hljs-title class_">IsWrapper</span> = <span class="hljs-literal">false</span>;<br>            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> W = <span class="hljs-number">0</span>; W &amp;lt; <span class="hljs-variable constant_">WRAPPER_CLASSES</span>.<span class="hljs-property">length</span>; W++) &#123;<br>                <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Cls</span>.<span class="hljs-title function_">indexOf</span>(<span class="hljs-variable constant_">WRAPPER_CLASSES</span>[W]) !== -<span class="hljs-number">1</span>) &#123;<br>                    <span class="hljs-title class_">IsWrapper</span> = <span class="hljs-literal">true</span>;<br>                    <span class="hljs-keyword">break</span>;<br>                &#125;<br>            &#125;<br>            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">IsWrapper</span>) &#123;<br>                <span class="hljs-title class_">ApplyHideStyles</span>(<span class="hljs-title class_">Ancestor</span>);<br>            &#125;<br>            <span class="hljs-title class_">Ancestor</span> = <span class="hljs-title class_">Ancestor</span>.<span class="hljs-property">parentElement</span>;<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-comment">// 显示卡片及其祖先包装器</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">ShowCardTree</span>(<span class="hljs-params">Card</span>) &#123;<br>        <span class="hljs-title class_">ClearHideStyles</span>(<span class="hljs-title class_">Card</span>);<br>        <span class="hljs-keyword">let</span> <span class="hljs-title class_">Ancestor</span> = <span class="hljs-title class_">Card</span>.<span class="hljs-property">parentElement</span>;<br>        <span class="hljs-keyword">while</span> (<span class="hljs-title class_">Ancestor</span> &amp;amp;&amp;amp; <span class="hljs-title class_">Ancestor</span> !== <span class="hljs-variable language_">document</span>.<span class="hljs-property">body</span>) &#123;<br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">Cls</span> = (<span class="hljs-title class_">Ancestor</span>.<span class="hljs-property">className</span> || <span class="hljs-string">&#x27;&#x27;</span>).<span class="hljs-title function_">toLowerCase</span>();<br>            <span class="hljs-keyword">let</span> <span class="hljs-title class_">IsWrapper</span> = <span class="hljs-literal">false</span>;<br>            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> W = <span class="hljs-number">0</span>; W &amp;lt; <span class="hljs-variable constant_">WRAPPER_CLASSES</span>.<span class="hljs-property">length</span>; W++) &#123;<br>                <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Cls</span>.<span class="hljs-title function_">indexOf</span>(<span class="hljs-variable constant_">WRAPPER_CLASSES</span>[W]) !== -<span class="hljs-number">1</span>) &#123;<br>                    <span class="hljs-title class_">IsWrapper</span> = <span class="hljs-literal">true</span>;<br>                    <span class="hljs-keyword">break</span>;<br>                &#125;<br>            &#125;<br>            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">IsWrapper</span>) &#123;<br>                <span class="hljs-title class_">ClearHideStyles</span>(<span class="hljs-title class_">Ancestor</span>);<br>            &#125;<br>            <span class="hljs-title class_">Ancestor</span> = <span class="hljs-title class_">Ancestor</span>.<span class="hljs-property">parentElement</span>;<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-comment">// ======================== 核心过滤逻辑 ========================</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">LimitVideos</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-keyword">if</span> (!<span class="hljs-title class_">IsActive</span>) &#123;<br>            <span class="hljs-title class_">RestoreAllVideos</span>();<br>            <span class="hljs-keyword">return</span>;<br>        &#125;<br><br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">Selector</span> = <span class="hljs-title class_">DetectSelector</span>();<br>            <span class="hljs-keyword">if</span> (!<span class="hljs-title class_">Selector</span>) &#123;<br>                <span class="hljs-title class_">Log</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogNoCards&#x27;</span>));<br>                <span class="hljs-keyword">return</span>;<br>            &#125;<br><br>            <span class="hljs-comment">// 获取所有卡片</span><br>            <span class="hljs-keyword">let</span> <span class="hljs-title class_">Cards</span> = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelectorAll</span>(<span class="hljs-title class_">Selector</span>);<br>            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Cards</span>.<span class="hljs-property">length</span> === <span class="hljs-number">0</span>) &#123;<br>                <span class="hljs-comment">// 扩展回退：同时查找 /video/, /bangumi/ 和 live.bilibili.com 链接</span><br>                <span class="hljs-keyword">const</span> <span class="hljs-title class_">LinkSelectors</span> = [<br>                    <span class="hljs-string">&#x27;a[href*=&quot;/video/&quot;]&#x27;</span>,<br>                    <span class="hljs-string">&#x27;a[href*=&quot;/bangumi/&quot;]&#x27;</span>,<br>                    <span class="hljs-string">&#x27;a[href*=&quot;live.bilibili.com&quot;]&#x27;</span><br>                ];<br>                <span class="hljs-keyword">const</span> <span class="hljs-title class_">ParentCards</span> = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Set</span>();<br>                <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-title class_">LinkSel</span> <span class="hljs-keyword">of</span> <span class="hljs-title class_">LinkSelectors</span>) &#123;<br>                    <span class="hljs-keyword">const</span> <span class="hljs-title class_">Links</span> = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelectorAll</span>(<span class="hljs-title class_">LinkSel</span>);<br>                    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-title class_">Link</span> <span class="hljs-keyword">of</span> <span class="hljs-title class_">Links</span>) &#123;<br>                        <span class="hljs-keyword">let</span> <span class="hljs-title class_">Parent</span> = <span class="hljs-title class_">Link</span>.<span class="hljs-property">parentElement</span>;<br>                        <span class="hljs-keyword">let</span> <span class="hljs-title class_">Depth</span> = <span class="hljs-number">0</span>;<br>                        <span class="hljs-keyword">while</span> (<span class="hljs-title class_">Parent</span> &amp;amp;&amp;amp; <span class="hljs-title class_">Depth</span> &amp;lt; <span class="hljs-number">5</span>) &#123;<br>                            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Parent</span>.<span class="hljs-property">className</span> &amp;amp;&amp;amp; (<span class="hljs-title class_">Parent</span>.<span class="hljs-property">className</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;card&#x27;</span>) || <span class="hljs-title class_">Parent</span>.<span class="hljs-property">className</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;item&#x27;</span>) || <span class="hljs-title class_">Parent</span>.<span class="hljs-property">className</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;feed&#x27;</span>) || <span class="hljs-title class_">Parent</span>.<span class="hljs-property">className</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;floor&#x27;</span>))) &#123;<br>                                <span class="hljs-title class_">ParentCards</span>.<span class="hljs-title function_">add</span>(<span class="hljs-title class_">Parent</span>);<br>                                <span class="hljs-keyword">break</span>;<br>                            &#125;<br>                            <span class="hljs-title class_">Parent</span> = <span class="hljs-title class_">Parent</span>.<span class="hljs-property">parentElement</span>;<br>                            <span class="hljs-title class_">Depth</span>++;<br>                        &#125;<br>                    &#125;<br>                &#125;<br>                <span class="hljs-title class_">Cards</span> = <span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>(<span class="hljs-title class_">ParentCards</span>);<br>                <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Cards</span>.<span class="hljs-property">length</span> === <span class="hljs-number">0</span>) &#123;<br>                    <span class="hljs-title class_">Log</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogStillNoCards&#x27;</span>));<br>                    <span class="hljs-keyword">return</span>;<br>                &#125;<br>            &#125;<br><br>            <span class="hljs-keyword">let</span> <span class="hljs-title class_">VideoCards</span> = <span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>(<span class="hljs-title class_">Cards</span>);<br><br>            <span class="hljs-comment">// 去重：移除嵌套包装器，只保留最内层卡片（bili-video-card &amp;gt; 其他包装器）</span><br>            <span class="hljs-comment">// 避免同一个卡片被多次计数</span><br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">NestedRemoval</span> = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Set</span>();<br>            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> I = <span class="hljs-number">0</span>; I &amp;lt; <span class="hljs-title class_">VideoCards</span>.<span class="hljs-property">length</span>; I++) &#123;<br>                <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> J = <span class="hljs-number">0</span>; J &amp;lt; <span class="hljs-title class_">VideoCards</span>.<span class="hljs-property">length</span>; J++) &#123;<br>                    <span class="hljs-keyword">if</span> (I !== J &amp;amp;&amp;amp; <span class="hljs-title class_">VideoCards</span>[I].<span class="hljs-title function_">contains</span>(<span class="hljs-title class_">VideoCards</span>[J])) &#123;<br>                        <span class="hljs-comment">// VideoCards[I] 是 VideoCards[J] 的祖先 → 移除祖先</span><br>                        <span class="hljs-title class_">NestedRemoval</span>.<span class="hljs-title function_">add</span>(I);<br>                        <span class="hljs-keyword">break</span>;<br>                    &#125;<br>                &#125;<br>            &#125;<br>            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">NestedRemoval</span>.<span class="hljs-property">size</span> &amp;gt; <span class="hljs-number">0</span>) &#123;<br>                <span class="hljs-title class_">VideoCards</span> = <span class="hljs-title class_">VideoCards</span>.<span class="hljs-title function_">filter</span>((_, <span class="hljs-title class_">Idx</span>) =&amp;gt; !<span class="hljs-title class_">NestedRemoval</span>.<span class="hljs-title function_">has</span>(<span class="hljs-title class_">Idx</span>));<br>            &#125;<br><br>            <span class="hljs-comment">// 辅助函数：检查卡片链接是否指向特定域名/路径</span><br>            <span class="hljs-keyword">function</span> <span class="hljs-title function_">CardLinksTo</span>(<span class="hljs-params">Card, Pattern</span>) &#123;<br>                <span class="hljs-keyword">const</span> <span class="hljs-title class_">Links</span> = <span class="hljs-title class_">Card</span>.<span class="hljs-title function_">querySelectorAll</span>(<span class="hljs-string">&#x27;a[href]&#x27;</span>);<br>                <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-title class_">Link</span> <span class="hljs-keyword">of</span> <span class="hljs-title class_">Links</span>) &#123;<br>                    <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Link</span>.<span class="hljs-property">href</span>.<span class="hljs-title function_">indexOf</span>(<span class="hljs-title class_">Pattern</span>) !== -<span class="hljs-number">1</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>                &#125;<br>                <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>            &#125;<br><br>            <span class="hljs-comment">// 过滤非视频内容 —— 收集被排除的卡片，稍后统一隐藏</span><br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">ExcludedCards</span> = [];<br>            <span class="hljs-title class_">VideoCards</span> = <span class="hljs-title class_">VideoCards</span>.<span class="hljs-title function_">filter</span>(<span class="hljs-title class_">Card</span> =&amp;gt; &#123;<br>                <span class="hljs-keyword">const</span> <span class="hljs-title class_">Text</span> = (<span class="hljs-title class_">Card</span>.<span class="hljs-property">textContent</span> || <span class="hljs-string">&#x27;&#x27;</span>).<span class="hljs-title function_">toLowerCase</span>();<br>                <span class="hljs-keyword">const</span> <span class="hljs-title class_">Cls</span> = (<span class="hljs-title class_">Card</span>.<span class="hljs-property">className</span> || <span class="hljs-string">&#x27;&#x27;</span>).<span class="hljs-title function_">toLowerCase</span>();<br>                <span class="hljs-comment">// 检查 floor-title 标签（B站新版卡片分类标签）</span><br>                <span class="hljs-keyword">const</span> <span class="hljs-title class_">FloorTitle</span> = <span class="hljs-title class_">Card</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">&#x27;.floor-title&#x27;</span>);<br>                <span class="hljs-keyword">const</span> <span class="hljs-title class_">FloorTitleText</span> = <span class="hljs-title class_">FloorTitle</span> ? (<span class="hljs-title class_">FloorTitle</span>.<span class="hljs-property">textContent</span> || <span class="hljs-string">&#x27;&#x27;</span>).<span class="hljs-title function_">toLowerCase</span>() : <span class="hljs-string">&#x27;&#x27;</span>;<br><br>                <span class="hljs-keyword">let</span> <span class="hljs-title class_">ShouldExclude</span> = <span class="hljs-literal">false</span>;<br><br>                <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Config</span>.<span class="hljs-property">ExcludeLive</span> &amp;amp;&amp;amp; (<br>                    <span class="hljs-title class_">Cls</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;live&#x27;</span>) ||<br>                    <span class="hljs-title class_">Text</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;直播&#x27;</span>) || <span class="hljs-title class_">Text</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;正在直播&#x27;</span>) || <span class="hljs-title class_">Text</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;直播中&#x27;</span>) ||<br>                    <span class="hljs-title class_">FloorTitleText</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;直播&#x27;</span>) || <span class="hljs-title class_">FloorTitleText</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;赛事&#x27;</span>) ||<br>                    <span class="hljs-title class_">CardLinksTo</span>(<span class="hljs-title class_">Card</span>, <span class="hljs-string">&#x27;live.bilibili.com&#x27;</span>)<br>                )) &#123;<br>                    <span class="hljs-title class_">ShouldExclude</span> = <span class="hljs-literal">true</span>;<br>                &#125;<br>                <span class="hljs-keyword">if</span> (!<span class="hljs-title class_">ShouldExclude</span> &amp;amp;&amp;amp; <span class="hljs-title class_">Config</span>.<span class="hljs-property">ExcludeAd</span> &amp;amp;&amp;amp; (<span class="hljs-title class_">Cls</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;ad&#x27;</span>) || <span class="hljs-title class_">Cls</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;advert&#x27;</span>) || <span class="hljs-title class_">Text</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;广告&#x27;</span>) || <span class="hljs-title class_">Text</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;sponsor&#x27;</span>))) &#123;<br>                    <span class="hljs-title class_">ShouldExclude</span> = <span class="hljs-literal">true</span>;<br>                &#125;<br>                <span class="hljs-keyword">if</span> (!<span class="hljs-title class_">ShouldExclude</span> &amp;amp;&amp;amp; <span class="hljs-title class_">Config</span>.<span class="hljs-property">ExcludeBangumi</span> &amp;amp;&amp;amp; (<br>                    <span class="hljs-title class_">Cls</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;bangumi&#x27;</span>) ||<br>                    <span class="hljs-title class_">Text</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;番剧&#x27;</span>) || <span class="hljs-title class_">Text</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;追番&#x27;</span>) ||<br>                    <span class="hljs-title class_">Text</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;国创&#x27;</span>) ||<br>                    <span class="hljs-title class_">FloorTitleText</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;番剧&#x27;</span>) || <span class="hljs-title class_">FloorTitleText</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;国创&#x27;</span>) ||<br>                    <span class="hljs-title class_">CardLinksTo</span>(<span class="hljs-title class_">Card</span>, <span class="hljs-string">&#x27;/bangumi/&#x27;</span>)<br>                )) &#123;<br>                    <span class="hljs-title class_">ShouldExclude</span> = <span class="hljs-literal">true</span>;<br>                &#125;<br>                <span class="hljs-keyword">if</span> (!<span class="hljs-title class_">ShouldExclude</span> &amp;amp;&amp;amp; <span class="hljs-title class_">Config</span>.<span class="hljs-property">ExcludePaid</span> &amp;amp;&amp;amp; (<span class="hljs-title class_">Text</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;付费&#x27;</span>) || <span class="hljs-title class_">Text</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;课程&#x27;</span>) || <span class="hljs-title class_">Text</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;￥&#x27;</span>) || <span class="hljs-title class_">Text</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;¥&#x27;</span>))) &#123;<br>                    <span class="hljs-title class_">ShouldExclude</span> = <span class="hljs-literal">true</span>;<br>                &#125;<br><br>                <span class="hljs-keyword">if</span> (<span class="hljs-title class_">ShouldExclude</span>) &#123;<br>                    <span class="hljs-title class_">ExcludedCards</span>.<span class="hljs-title function_">push</span>(<span class="hljs-title class_">Card</span>);<br>                    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>                &#125;<br>                <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>            &#125;);<br><br>            <span class="hljs-comment">// 处理特殊保留（UP主ID）</span><br>            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Config</span>.<span class="hljs-property">KeepSpecialUPIDs</span> &amp;amp;&amp;amp; <span class="hljs-title class_">Config</span>.<span class="hljs-property">KeepSpecialUPIDs</span>.<span class="hljs-property">length</span> &amp;gt; <span class="hljs-number">0</span>) &#123;<br>                <span class="hljs-keyword">const</span> <span class="hljs-title class_">KeepSet</span> = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Set</span>(<span class="hljs-title class_">Config</span>.<span class="hljs-property">KeepSpecialUPIDs</span>.<span class="hljs-title function_">map</span>(<span class="hljs-title class_">Id</span> =&amp;gt; <span class="hljs-title class_">String</span>(<span class="hljs-title class_">Id</span>)));<br>                <span class="hljs-keyword">const</span> <span class="hljs-title class_">Kept</span> = [];<br>                <span class="hljs-keyword">const</span> <span class="hljs-title class_">Rest</span> = [];<br>                <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-title class_">Card</span> <span class="hljs-keyword">of</span> <span class="hljs-title class_">VideoCards</span>) &#123;<br>                    <span class="hljs-keyword">const</span> <span class="hljs-title class_">UpLink</span> = <span class="hljs-title class_">Card</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">&#x27;a[href*=&quot;/space/&quot;]&#x27;</span>);<br>                    <span class="hljs-keyword">let</span> <span class="hljs-title class_">Upid</span> = <span class="hljs-literal">null</span>;<br>                    <span class="hljs-keyword">if</span> (<span class="hljs-title class_">UpLink</span>) &#123;<br>                        <span class="hljs-keyword">const</span> <span class="hljs-title class_">Match</span> = <span class="hljs-title class_">UpLink</span>.<span class="hljs-property">href</span>.<span class="hljs-title function_">match</span>(<span class="hljs-regexp">/\/space\/(\d+)/</span>);<br>                        <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Match</span>) <span class="hljs-title class_">Upid</span> = <span class="hljs-title class_">Match</span>[<span class="hljs-number">1</span>];<br>                    &#125;<br>                    <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Upid</span> &amp;amp;&amp;amp; <span class="hljs-title class_">KeepSet</span>.<span class="hljs-title function_">has</span>(<span class="hljs-title class_">Upid</span>)) &#123;<br>                        <span class="hljs-title class_">Kept</span>.<span class="hljs-title function_">push</span>(<span class="hljs-title class_">Card</span>);<br>                    &#125; <span class="hljs-keyword">else</span> &#123;<br>                        <span class="hljs-title class_">Rest</span>.<span class="hljs-title function_">push</span>(<span class="hljs-title class_">Card</span>);<br>                    &#125;<br>                &#125;<br>                <span class="hljs-title class_">VideoCards</span> = <span class="hljs-title class_">Kept</span>.<span class="hljs-title function_">concat</span>(<span class="hljs-title class_">Rest</span>);<br>            &#125;<br><br>            <span class="hljs-comment">// 保留推广位</span><br>            <span class="hljs-keyword">let</span> <span class="hljs-title class_">PromotedCards</span> = [];<br>            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Config</span>.<span class="hljs-property">KeepPromoted</span>) &#123;<br>                <span class="hljs-title class_">PromotedCards</span> = <span class="hljs-title class_">VideoCards</span>.<span class="hljs-title function_">filter</span>(<span class="hljs-title class_">Card</span> =&amp;gt; &#123;<br>                    <span class="hljs-keyword">const</span> <span class="hljs-title class_">Text</span> = (<span class="hljs-title class_">Card</span>.<span class="hljs-property">textContent</span> || <span class="hljs-string">&#x27;&#x27;</span>).<span class="hljs-title function_">toLowerCase</span>();<br>                    <span class="hljs-keyword">return</span> <span class="hljs-title class_">Text</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;推广&#x27;</span>) || <span class="hljs-title class_">Text</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;广告&#x27;</span>) || <span class="hljs-title class_">Text</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-string">&#x27;sponsor&#x27;</span>);<br>                &#125;);<br>                <span class="hljs-title class_">VideoCards</span> = <span class="hljs-title class_">VideoCards</span>.<span class="hljs-title function_">filter</span>(<span class="hljs-title class_">Card</span> =&amp;gt; !<span class="hljs-title class_">PromotedCards</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-title class_">Card</span>));<br>            &#125;<br><br>            <span class="hljs-comment">// 处理置顶/推荐卡片</span><br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">TopSelectors</span> = [<span class="hljs-string">&#x27;.bili-feed__banner&#x27;</span>, <span class="hljs-string">&#x27;.bili-feed__top&#x27;</span>, <span class="hljs-string">&#x27;.top-banner&#x27;</span>, <span class="hljs-string">&#x27;.recommend-banner&#x27;</span>];<br>            <span class="hljs-keyword">let</span> <span class="hljs-title class_">TopCards</span> = [];<br>            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-title class_">Sel</span> <span class="hljs-keyword">of</span> <span class="hljs-title class_">TopSelectors</span>) &#123;<br>                <span class="hljs-keyword">const</span> <span class="hljs-title class_">Tops</span> = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelectorAll</span>(<span class="hljs-title class_">Sel</span>);<br>                <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-title class_">Top</span> <span class="hljs-keyword">of</span> <span class="hljs-title class_">Tops</span>) &#123;<br>                    <span class="hljs-keyword">const</span> <span class="hljs-title class_">InnerCards</span> = <span class="hljs-title class_">Top</span>.<span class="hljs-title function_">querySelectorAll</span>(<span class="hljs-title class_">Selector</span>);<br>                    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-title class_">Card</span> <span class="hljs-keyword">of</span> <span class="hljs-title class_">InnerCards</span>) &#123;<br>                        <span class="hljs-keyword">if</span> (<span class="hljs-title class_">VideoCards</span>.<span class="hljs-title function_">includes</span>(<span class="hljs-title class_">Card</span>)) &#123;<br>                            <span class="hljs-title class_">TopCards</span>.<span class="hljs-title function_">push</span>(<span class="hljs-title class_">Card</span>);<br>                            <span class="hljs-keyword">const</span> <span class="hljs-title class_">Idx</span> = <span class="hljs-title class_">VideoCards</span>.<span class="hljs-title function_">indexOf</span>(<span class="hljs-title class_">Card</span>);<br>                            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Idx</span> !== -<span class="hljs-number">1</span>) <span class="hljs-title class_">VideoCards</span>.<span class="hljs-title function_">splice</span>(<span class="hljs-title class_">Idx</span>, <span class="hljs-number">1</span>);<br>                        &#125;<br>                    &#125;<br>                &#125;<br>            &#125;<br><br>            <span class="hljs-comment">// 限制数量</span><br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">Max</span> = <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">max</span>(<span class="hljs-number">1</span>, <span class="hljs-title class_">Number</span>(<span class="hljs-title class_">Config</span>.<span class="hljs-property">MaxVideos</span>) || <span class="hljs-number">10</span>);<br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">ToShow</span> = <span class="hljs-title class_">VideoCards</span>.<span class="hljs-title function_">slice</span>(<span class="hljs-number">0</span>, <span class="hljs-title class_">Max</span>);<br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">ToHide</span> = <span class="hljs-title class_">VideoCards</span>.<span class="hljs-title function_">slice</span>(<span class="hljs-title class_">Max</span>);<br><br>            <span class="hljs-comment">// 显示前max个</span><br>            <span class="hljs-title class_">ToShow</span>.<span class="hljs-title function_">forEach</span>(<span class="hljs-title class_">Card</span> =&amp;gt; &#123; <span class="hljs-title class_">ShowCardTree</span>(<span class="hljs-title class_">Card</span>); &#125;);<br><br>            <span class="hljs-comment">// 隐藏超出限制的卡片 以及 被过滤规则排除的卡片</span><br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">AllToHide</span> = <span class="hljs-title class_">ToHide</span>.<span class="hljs-title function_">concat</span>(<span class="hljs-title class_">ExcludedCards</span>);<br>            <span class="hljs-title class_">AllToHide</span>.<span class="hljs-title function_">forEach</span>(<span class="hljs-title class_">Card</span> =&amp;gt; &#123; <span class="hljs-title class_">HideCardTree</span>(<span class="hljs-title class_">Card</span>); &#125;);<br><br>            <span class="hljs-comment">// 确保推广位和置顶卡片可见</span><br>            [...<span class="hljs-title class_">PromotedCards</span>, ...<span class="hljs-title class_">TopCards</span>].<span class="hljs-title function_">forEach</span>(<span class="hljs-title class_">Card</span> =&amp;gt; &#123; <span class="hljs-title class_">ShowCardTree</span>(<span class="hljs-title class_">Card</span>); &#125;);<br><br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">Total</span> = <span class="hljs-title class_">VideoCards</span>.<span class="hljs-property">length</span> + <span class="hljs-title class_">ExcludedCards</span>.<span class="hljs-property">length</span> + <span class="hljs-title class_">PromotedCards</span>.<span class="hljs-property">length</span> + <span class="hljs-title class_">TopCards</span>.<span class="hljs-property">length</span>;<br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">Shown</span> = <span class="hljs-title class_">ToShow</span>.<span class="hljs-property">length</span> + <span class="hljs-title class_">PromotedCards</span>.<span class="hljs-property">length</span> + <span class="hljs-title class_">TopCards</span>.<span class="hljs-property">length</span>;<br>            <span class="hljs-title class_">Log</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogProcessed&#x27;</span>, <span class="hljs-title class_">Total</span>, <span class="hljs-title class_">Shown</span>, <span class="hljs-title class_">ToHide</span>.<span class="hljs-property">length</span> + <span class="hljs-title class_">ExcludedCards</span>.<span class="hljs-property">length</span>));<br><br>        &#125; <span class="hljs-keyword">catch</span> (E) &#123;<br>            <span class="hljs-title class_">ErrorLog</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogErrorLimit&#x27;</span>), E);<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">RestoreAllVideos</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-keyword">const</span> <span class="hljs-title class_">Selector</span> = <span class="hljs-title class_">DetectSelector</span>();<br>        <span class="hljs-keyword">if</span> (!<span class="hljs-title class_">Selector</span>) <span class="hljs-keyword">return</span>;<br>        <span class="hljs-keyword">const</span> <span class="hljs-title class_">Cards</span> = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelectorAll</span>(<span class="hljs-title class_">Selector</span>);<br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-title class_">Card</span> <span class="hljs-keyword">of</span> <span class="hljs-title class_">Cards</span>) &#123;<br>            <span class="hljs-title class_">ShowCardTree</span>(<span class="hljs-title class_">Card</span>);<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-comment">// ======================== CSS 样式（仅过滤类和动态配置面板） ========================</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">InjectStyles</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-title function_">GM_addStyle</span>(<span class="hljs-string">`</span><br><span class="hljs-string">            .BiliLimitedHide &#123;</span><br><span class="hljs-string">                display: none !important;</span><br><span class="hljs-string">                visibility: hidden !important;</span><br><span class="hljs-string">                opacity: 0 !important;</span><br><span class="hljs-string">                height: 0 !important;</span><br><span class="hljs-string">                margin: 0 !important;</span><br><span class="hljs-string">                padding: 0 !important;</span><br><span class="hljs-string">                overflow: hidden !important;</span><br><span class="hljs-string">                flex: 0 0 0 !important;</span><br><span class="hljs-string">                position: absolute !important;</span><br><span class="hljs-string">                pointer-events: none !important;</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">        `</span>);<br>    &#125;<br><br>    <span class="hljs-comment">// ======================== 配置面板（非侵入式：按需创建/销毁） ========================</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">InjectPanelStyles</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;BiliCompactPanelStyles&#x27;</span>)) <span class="hljs-keyword">return</span>;<br>        <span class="hljs-keyword">const</span> <span class="hljs-title class_">StyleEl</span> = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">createElement</span>(<span class="hljs-string">&#x27;style&#x27;</span>);<br>        <span class="hljs-title class_">StyleEl</span>.<span class="hljs-property">id</span> = <span class="hljs-string">&#x27;BiliCompactPanelStyles&#x27;</span>;<br>        <span class="hljs-title class_">StyleEl</span>.<span class="hljs-property">textContent</span> = <span class="hljs-string">`</span><br><span class="hljs-string">            .BiliCompactOverlay &#123;</span><br><span class="hljs-string">                position: fixed;</span><br><span class="hljs-string">                top: 0;</span><br><span class="hljs-string">                left: 0;</span><br><span class="hljs-string">                width: 100%;</span><br><span class="hljs-string">                height: 100%;</span><br><span class="hljs-string">                background: rgba(0,0,0,0.4);</span><br><span class="hljs-string">                z-index: 2147483646;</span><br><span class="hljs-string">                display: flex;</span><br><span class="hljs-string">                align-items: center;</span><br><span class="hljs-string">                justify-content: center;</span><br><span class="hljs-string">                font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, &quot;Helvetica Neue&quot;, Arial, sans-serif;</span><br><span class="hljs-string">                font-size: 14px;</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel &#123;</span><br><span class="hljs-string">                /* Dark theme (default) variables */</span><br><span class="hljs-string">                --bg: #1e1e1e;</span><br><span class="hljs-string">                --text: #eee;</span><br><span class="hljs-string">                --text-secondary: #ccc;</span><br><span class="hljs-string">                --text-heading: #fff;</span><br><span class="hljs-string">                --input-bg: #2a2a2a;</span><br><span class="hljs-string">                --border: #333;</span><br><span class="hljs-string">                --border-light: #444;</span><br><span class="hljs-string">                --hr: #333;</span><br><span class="hljs-string">                --accent: #fb7299;</span><br><span class="hljs-string">                --accent-hover: #ff85a8;</span><br><span class="hljs-string">                --badge-off: #666;</span><br><span class="hljs-string">                --btn-secondary-bg: #444;</span><br><span class="hljs-string">                --btn-secondary-hover: #555;</span><br><span class="hljs-string">                --btn-secondary-text: #fff;</span><br><span class="hljs-string">                --collapse-hover: #333;</span><br><span class="hljs-string"></span><br><span class="hljs-string">                background: var(--bg);</span><br><span class="hljs-string">                color: var(--text);</span><br><span class="hljs-string">                padding: 24px 30px;</span><br><span class="hljs-string">                border-radius: 16px;</span><br><span class="hljs-string">                box-shadow: 0 8px 40px rgba(0,0,0,0.6);</span><br><span class="hljs-string">                min-width: 340px;</span><br><span class="hljs-string">                max-width: 420px;</span><br><span class="hljs-string">                border: 1px solid var(--border);</span><br><span class="hljs-string">                display: flex;</span><br><span class="hljs-string">                flex-direction: column;</span><br><span class="hljs-string">                gap: 12px;</span><br><span class="hljs-string">                position: relative;</span><br><span class="hljs-string">                max-height: 85vh;</span><br><span class="hljs-string">                overflow-y: auto;</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel.light-mode &#123;</span><br><span class="hljs-string">                --bg: #ffffff;</span><br><span class="hljs-string">                --text: #333;</span><br><span class="hljs-string">                --text-secondary: #555;</span><br><span class="hljs-string">                --text-heading: #111;</span><br><span class="hljs-string">                --input-bg: #f5f5f5;</span><br><span class="hljs-string">                --border: #ddd;</span><br><span class="hljs-string">                --border-light: #e0e0e0;</span><br><span class="hljs-string">                --hr: #eee;</span><br><span class="hljs-string">                --accent: #00AEEC;</span><br><span class="hljs-string">                --accent-hover: #33c0f0;</span><br><span class="hljs-string">                --badge-off: #bbb;</span><br><span class="hljs-string">                --btn-secondary-bg: #eee;</span><br><span class="hljs-string">                --btn-secondary-hover: #ddd;</span><br><span class="hljs-string">                --btn-secondary-text: #333;</span><br><span class="hljs-string">                --collapse-hover: #eee;</span><br><span class="hljs-string">                box-shadow: 0 4px 24px rgba(0,0,0,0.12);</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel h3 &#123;</span><br><span class="hljs-string">                margin: 0 0 4px 0;</span><br><span class="hljs-string">                font-weight: 500;</span><br><span class="hljs-string">                color: var(--text-heading);</span><br><span class="hljs-string">                font-size: 16px;</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel label &#123;</span><br><span class="hljs-string">                display: flex;</span><br><span class="hljs-string">                justify-content: space-between;</span><br><span class="hljs-string">                align-items: center;</span><br><span class="hljs-string">                font-size: 14px;</span><br><span class="hljs-string">                color: var(--text-secondary);</span><br><span class="hljs-string">                gap: 8px;</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel input[type=&quot;number&quot;],</span><br><span class="hljs-string">            .BiliCompactPanel input[type=&quot;text&quot;] &#123;</span><br><span class="hljs-string">                background: var(--input-bg);</span><br><span class="hljs-string">                border: 1px solid var(--border-light);</span><br><span class="hljs-string">                color: var(--text);</span><br><span class="hljs-string">                padding: 4px 10px;</span><br><span class="hljs-string">                border-radius: 6px;</span><br><span class="hljs-string">                width: 80px;</span><br><span class="hljs-string">                font-size: 14px;</span><br><span class="hljs-string">                font-family: inherit;</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel input[type=&quot;text&quot;] &#123;</span><br><span class="hljs-string">                width: 140px;</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel select &#123;</span><br><span class="hljs-string">                background: var(--input-bg);</span><br><span class="hljs-string">                border: 1px solid var(--border-light);</span><br><span class="hljs-string">                color: var(--text);</span><br><span class="hljs-string">                padding: 4px 8px;</span><br><span class="hljs-string">                border-radius: 6px;</span><br><span class="hljs-string">                font-size: 14px;</span><br><span class="hljs-string">                font-family: inherit;</span><br><span class="hljs-string">                cursor: pointer;</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel input[type=&quot;checkbox&quot;] &#123;</span><br><span class="hljs-string">                accent-color: var(--accent);</span><br><span class="hljs-string">                width: 18px;</span><br><span class="hljs-string">                height: 18px;</span><br><span class="hljs-string">                cursor: pointer;</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel .BtnRow &#123;</span><br><span class="hljs-string">                display: flex;</span><br><span class="hljs-string">                gap: 10px;</span><br><span class="hljs-string">                justify-content: flex-end;</span><br><span class="hljs-string">                margin-top: 6px;</span><br><span class="hljs-string">                flex-wrap: wrap;</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel button &#123;</span><br><span class="hljs-string">                background: var(--accent);</span><br><span class="hljs-string">                border: none;</span><br><span class="hljs-string">                color: #fff;</span><br><span class="hljs-string">                padding: 6px 18px;</span><br><span class="hljs-string">                border-radius: 20px;</span><br><span class="hljs-string">                cursor: pointer;</span><br><span class="hljs-string">                font-size: 14px;</span><br><span class="hljs-string">                font-family: inherit;</span><br><span class="hljs-string">                transition: background 0.2s;</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel button.Secondary &#123;</span><br><span class="hljs-string">                background: var(--btn-secondary-bg);</span><br><span class="hljs-string">                color: var(--btn-secondary-text);</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel button:hover &#123;</span><br><span class="hljs-string">                background: var(--accent-hover);</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel button.Secondary:hover &#123;</span><br><span class="hljs-string">                background: var(--btn-secondary-hover);</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel .Hint &#123;</span><br><span class="hljs-string">                font-size: 12px;</span><br><span class="hljs-string">                color: #888;</span><br><span class="hljs-string">                margin-top: -4px;</span><br><span class="hljs-string">                line-height: 1.4;</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel .StatusRow &#123;</span><br><span class="hljs-string">                display: flex;</span><br><span class="hljs-string">                align-items: center;</span><br><span class="hljs-string">                justify-content: space-between;</span><br><span class="hljs-string">                font-size: 14px;</span><br><span class="hljs-string">                color: var(--text-secondary);</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel .StatusBadge &#123;</span><br><span class="hljs-string">                background: var(--accent);</span><br><span class="hljs-string">                color: #fff;</span><br><span class="hljs-string">                border-radius: 12px;</span><br><span class="hljs-string">                padding: 2px 12px;</span><br><span class="hljs-string">                font-size: 12px;</span><br><span class="hljs-string">                font-weight: bold;</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel .StatusBadge.Off &#123;</span><br><span class="hljs-string">                background: var(--badge-off);</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            /* Collapsible section */</span><br><span class="hljs-string">            .BiliCompactPanel .CollapseHeader &#123;</span><br><span class="hljs-string">                display: flex;</span><br><span class="hljs-string">                align-items: center;</span><br><span class="hljs-string">                gap: 6px;</span><br><span class="hljs-string">                cursor: pointer;</span><br><span class="hljs-string">                padding: 6px 8px;</span><br><span class="hljs-string">                border-radius: 6px;</span><br><span class="hljs-string">                user-select: none;</span><br><span class="hljs-string">                font-size: 13px;</span><br><span class="hljs-string">                color: var(--accent);</span><br><span class="hljs-string">                font-weight: 500;</span><br><span class="hljs-string">                transition: background 0.15s;</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel .CollapseHeader:hover &#123;</span><br><span class="hljs-string">                background: var(--collapse-hover);</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel .CollapseArrow &#123;</span><br><span class="hljs-string">                transition: transform 0.2s;</span><br><span class="hljs-string">                font-size: 12px;</span><br><span class="hljs-string">                line-height: 1;</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel .CollapseArrow.open &#123;</span><br><span class="hljs-string">                transform: rotate(90deg);</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel .CollapseContent &#123;</span><br><span class="hljs-string">                display: flex;</span><br><span class="hljs-string">                flex-direction: column;</span><br><span class="hljs-string">                gap: 8px;</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">            .BiliCompactPanel .CollapseContent.collapsed &#123;</span><br><span class="hljs-string">                display: none;</span><br><span class="hljs-string">            &#125;</span><br><span class="hljs-string">        `</span>;<br>        <span class="hljs-variable language_">document</span>.<span class="hljs-property">head</span>.<span class="hljs-title function_">appendChild</span>(<span class="hljs-title class_">StyleEl</span>);<br>    &#125;<br><br>    <span class="hljs-keyword">let</span> <span class="hljs-title class_">PanelDestroyFn</span> = <span class="hljs-literal">null</span>;<br><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">OpenConfigPanel</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-keyword">if</span> (<span class="hljs-title class_">PanelDestroyFn</span>) &#123;<br>            <span class="hljs-title class_">PanelDestroyFn</span>();<br>            <span class="hljs-title class_">PanelDestroyFn</span> = <span class="hljs-literal">null</span>;<br>        &#125;<br><br>        <span class="hljs-title class_">InjectPanelStyles</span>();<br><br>        <span class="hljs-keyword">const</span> <span class="hljs-title class_">Overlay</span> = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">createElement</span>(<span class="hljs-string">&#x27;div&#x27;</span>);<br>        <span class="hljs-title class_">Overlay</span>.<span class="hljs-property">className</span> = <span class="hljs-string">&#x27;BiliCompactOverlay&#x27;</span>;<br><br>        <span class="hljs-keyword">const</span> <span class="hljs-title class_">Panel</span> = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">createElement</span>(<span class="hljs-string">&#x27;div&#x27;</span>);<br>        <span class="hljs-title class_">Panel</span>.<span class="hljs-property">className</span> = <span class="hljs-string">&#x27;BiliCompactPanel&#x27;</span>;<br><br>        <span class="hljs-comment">// 应用颜色模式</span><br>        <span class="hljs-keyword">const</span> effectiveColorMode = (<span class="hljs-title class_">Config</span>.<span class="hljs-property">ColorMode</span> || <span class="hljs-string">&#x27;auto&#x27;</span>) === <span class="hljs-string">&#x27;auto&#x27;</span><br>            ? (<span class="hljs-variable language_">window</span>.<span class="hljs-title function_">matchMedia</span>(<span class="hljs-string">&#x27;(prefers-color-scheme: dark)&#x27;</span>).<span class="hljs-property">matches</span> ? <span class="hljs-string">&#x27;dark&#x27;</span> : <span class="hljs-string">&#x27;light&#x27;</span>)<br>            : <span class="hljs-title class_">Config</span>.<span class="hljs-property">ColorMode</span>;<br>        <span class="hljs-keyword">if</span> (effectiveColorMode === <span class="hljs-string">&#x27;light&#x27;</span>) &#123;<br>            <span class="hljs-title class_">Panel</span>.<span class="hljs-property">classList</span>.<span class="hljs-title function_">add</span>(<span class="hljs-string">&#x27;light-mode&#x27;</span>);<br>        &#125;<br><br>        <span class="hljs-comment">// 语言选项</span><br>        <span class="hljs-keyword">const</span> <span class="hljs-title class_">LangOptions</span> = [<br>            &#123; <span class="hljs-title class_">Value</span>: <span class="hljs-string">&#x27;auto&#x27;</span>,  <span class="hljs-title class_">Label</span>: <span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;PanelLanguageAuto&#x27;</span>) &#125;,<br>            &#123; <span class="hljs-title class_">Value</span>: <span class="hljs-string">&#x27;zh_CN&#x27;</span>, <span class="hljs-title class_">Label</span>: <span class="hljs-string">&#x27;简体中文&#x27;</span> &#125;,<br>            &#123; <span class="hljs-title class_">Value</span>: <span class="hljs-string">&#x27;zh_TW&#x27;</span>, <span class="hljs-title class_">Label</span>: <span class="hljs-string">&#x27;繁體中文&#x27;</span> &#125;,<br>            &#123; <span class="hljs-title class_">Value</span>: <span class="hljs-string">&#x27;en_US&#x27;</span>, <span class="hljs-title class_">Label</span>: <span class="hljs-string">&#x27;English&#x27;</span> &#125;,<br>        ];<br>        <span class="hljs-keyword">const</span> <span class="hljs-title class_">LangSelectHTML</span> = <span class="hljs-title class_">LangOptions</span>.<span class="hljs-title function_">map</span>(<span class="hljs-title class_">Opt</span> =&amp;gt;<br>            <span class="hljs-string">`&amp;lt;option value=&quot;<span class="hljs-subst">$&#123;Opt.Value&#125;</span>&quot; <span class="hljs-subst">$&#123;Config.Language === Opt.Value ? <span class="hljs-string">&#x27;selected&#x27;</span> : <span class="hljs-string">&#x27;&#x27;</span>&#125;</span>&amp;gt;<span class="hljs-subst">$&#123;Opt.Label&#125;</span>&amp;lt;/option&amp;gt;`</span><br>        ).<span class="hljs-title function_">join</span>(<span class="hljs-string">&#x27;&#x27;</span>);<br><br>        <span class="hljs-title class_">Panel</span>.<span class="hljs-property">innerHTML</span> = <span class="hljs-string">`</span><br><span class="hljs-string">            &amp;lt;h3&amp;gt;<span class="hljs-subst">$&#123;T(<span class="hljs-string">&#x27;PanelTitle&#x27;</span>)&#125;</span>&amp;lt;/h3&amp;gt;</span><br><span class="hljs-string">            &amp;lt;div class=&quot;StatusRow&quot;&amp;gt;</span><br><span class="hljs-string">                &amp;lt;span&amp;gt;<span class="hljs-subst">$&#123;T(<span class="hljs-string">&#x27;PanelStatusLabel&#x27;</span>)&#125;</span>&amp;lt;/span&amp;gt;</span><br><span class="hljs-string">                &amp;lt;span class=&quot;StatusBadge <span class="hljs-subst">$&#123;IsActive ? <span class="hljs-string">&#x27;&#x27;</span> : <span class="hljs-string">&#x27;Off&#x27;</span>&#125;</span>&quot; id=&quot;CfgStatusBadge&quot;&amp;gt;<span class="hljs-subst">$&#123;IsActive ? T(<span class="hljs-string">&#x27;PanelStatusActive&#x27;</span>) : T(<span class="hljs-string">&#x27;PanelStatusPaused&#x27;</span>)&#125;</span>&amp;lt;/span&amp;gt;</span><br><span class="hljs-string">            &amp;lt;/div&amp;gt;</span><br><span class="hljs-string">            &amp;lt;label&amp;gt;<span class="hljs-subst">$&#123;T(<span class="hljs-string">&#x27;PanelMaxVideos&#x27;</span>)&#125;</span> &amp;lt;input type=&quot;number&quot; id=&quot;CfgMax&quot; value=&quot;<span class="hljs-subst">$&#123;Config.MaxVideos&#125;</span>&quot; min=&quot;1&quot; max=&quot;100&quot;&amp;gt;&amp;lt;/label&amp;gt;</span><br><span class="hljs-string">            &amp;lt;label&amp;gt;<span class="hljs-subst">$&#123;T(<span class="hljs-string">&#x27;PanelExcludeLive&#x27;</span>)&#125;</span> &amp;lt;input type=&quot;checkbox&quot; id=&quot;CfgExcludeLive&quot; <span class="hljs-subst">$&#123;Config.ExcludeLive ? <span class="hljs-string">&#x27;checked&#x27;</span> : <span class="hljs-string">&#x27;&#x27;</span>&#125;</span>&amp;gt;&amp;lt;/label&amp;gt;</span><br><span class="hljs-string">            &amp;lt;label&amp;gt;<span class="hljs-subst">$&#123;T(<span class="hljs-string">&#x27;PanelExcludeAd&#x27;</span>)&#125;</span> &amp;lt;input type=&quot;checkbox&quot; id=&quot;CfgExcludeAd&quot; <span class="hljs-subst">$&#123;Config.ExcludeAd ? <span class="hljs-string">&#x27;checked&#x27;</span> : <span class="hljs-string">&#x27;&#x27;</span>&#125;</span>&amp;gt;&amp;lt;/label&amp;gt;</span><br><span class="hljs-string">            &amp;lt;label&amp;gt;<span class="hljs-subst">$&#123;T(<span class="hljs-string">&#x27;PanelExcludeBangumi&#x27;</span>)&#125;</span> &amp;lt;input type=&quot;checkbox&quot; id=&quot;CfgExcludeBangumi&quot; <span class="hljs-subst">$&#123;Config.ExcludeBangumi ? <span class="hljs-string">&#x27;checked&#x27;</span> : <span class="hljs-string">&#x27;&#x27;</span>&#125;</span>&amp;gt;&amp;lt;/label&amp;gt;</span><br><span class="hljs-string">            &amp;lt;label&amp;gt;<span class="hljs-subst">$&#123;T(<span class="hljs-string">&#x27;PanelExcludePaid&#x27;</span>)&#125;</span> &amp;lt;input type=&quot;checkbox&quot; id=&quot;CfgExcludePaid&quot; <span class="hljs-subst">$&#123;Config.ExcludePaid ? <span class="hljs-string">&#x27;checked&#x27;</span> : <span class="hljs-string">&#x27;&#x27;</span>&#125;</span>&amp;gt;&amp;lt;/label&amp;gt;</span><br><span class="hljs-string">            &amp;lt;label&amp;gt;<span class="hljs-subst">$&#123;T(<span class="hljs-string">&#x27;PanelKeepPromoted&#x27;</span>)&#125;</span> &amp;lt;input type=&quot;checkbox&quot; id=&quot;CfgKeepPromoted&quot; <span class="hljs-subst">$&#123;Config.KeepPromoted ? <span class="hljs-string">&#x27;checked&#x27;</span> : <span class="hljs-string">&#x27;&#x27;</span>&#125;</span>&amp;gt;&amp;lt;/label&amp;gt;</span><br><span class="hljs-string">            &amp;lt;label&amp;gt;<span class="hljs-subst">$&#123;T(<span class="hljs-string">&#x27;PanelDebug&#x27;</span>)&#125;</span> &amp;lt;input type=&quot;checkbox&quot; id=&quot;CfgDebug&quot; <span class="hljs-subst">$&#123;Config.Debug ? <span class="hljs-string">&#x27;checked&#x27;</span> : <span class="hljs-string">&#x27;&#x27;</span>&#125;</span>&amp;gt;&amp;lt;/label&amp;gt;</span><br><span class="hljs-string">            &amp;lt;label&amp;gt;<span class="hljs-subst">$&#123;T(<span class="hljs-string">&#x27;PanelEnableCommentPurifier&#x27;</span>)&#125;</span> &amp;lt;input type=&quot;checkbox&quot; id=&quot;CfgEnablePurifier&quot; <span class="hljs-subst">$&#123;Config.EnableCommentPurifier ? <span class="hljs-string">&#x27;checked&#x27;</span> : <span class="hljs-string">&#x27;&#x27;</span>&#125;</span>&amp;gt;&amp;lt;/label&amp;gt;</span><br><span class="hljs-string">            &amp;lt;label&amp;gt;<span class="hljs-subst">$&#123;T(<span class="hljs-string">&#x27;PanelLanguage&#x27;</span>)&#125;</span> &amp;lt;select id=&quot;CfgLanguage&quot;&amp;gt;<span class="hljs-subst">$&#123;LangSelectHTML&#125;</span>&amp;lt;/select&amp;gt;&amp;lt;/label&amp;gt;</span><br><span class="hljs-string">            &amp;lt;label&amp;gt;<span class="hljs-subst">$&#123;T(<span class="hljs-string">&#x27;PanelColorMode&#x27;</span>)&#125;</span> &amp;lt;select id=&quot;CfgColorMode&quot;&amp;gt;</span><br><span class="hljs-string">                &amp;lt;option value=&quot;auto&quot; <span class="hljs-subst">$&#123;(Config.ColorMode || <span class="hljs-string">&#x27;auto&#x27;</span>) === <span class="hljs-string">&#x27;auto&#x27;</span> ? <span class="hljs-string">&#x27;selected&#x27;</span> : <span class="hljs-string">&#x27;&#x27;</span>&#125;</span>&amp;gt;<span class="hljs-subst">$&#123;T(<span class="hljs-string">&#x27;PanelColorAuto&#x27;</span>)&#125;</span>&amp;lt;/option&amp;gt;</span><br><span class="hljs-string">                &amp;lt;option value=&quot;dark&quot; <span class="hljs-subst">$&#123;Config.ColorMode === <span class="hljs-string">&#x27;dark&#x27;</span> ? <span class="hljs-string">&#x27;selected&#x27;</span> : <span class="hljs-string">&#x27;&#x27;</span>&#125;</span>&amp;gt;<span class="hljs-subst">$&#123;T(<span class="hljs-string">&#x27;PanelColorDark&#x27;</span>)&#125;</span>&amp;lt;/option&amp;gt;</span><br><span class="hljs-string">                &amp;lt;option value=&quot;light&quot; <span class="hljs-subst">$&#123;Config.ColorMode === <span class="hljs-string">&#x27;light&#x27;</span> ? <span class="hljs-string">&#x27;selected&#x27;</span> : <span class="hljs-string">&#x27;&#x27;</span>&#125;</span>&amp;gt;<span class="hljs-subst">$&#123;T(<span class="hljs-string">&#x27;PanelColorLight&#x27;</span>)&#125;</span>&amp;lt;/option&amp;gt;</span><br><span class="hljs-string">            &amp;lt;/select&amp;gt;&amp;lt;/label&amp;gt;</span><br><span class="hljs-string">            &amp;lt;label&amp;gt;<span class="hljs-subst">$&#123;T(<span class="hljs-string">&#x27;PanelKeepUpids&#x27;</span>)&#125;</span> &amp;lt;input type=&quot;text&quot; id=&quot;CfgKeepUids&quot; value=&quot;<span class="hljs-subst">$&#123;(Config.KeepSpecialUPIDs || []).join(<span class="hljs-string">&#x27;,&#x27;</span>)&#125;</span>&quot;&amp;gt;&amp;lt;/label&amp;gt;</span><br><span class="hljs-string">            &amp;lt;hr style=&quot;margin:8px 0;border:none;border-top:1px solid var(--hr, #333)&quot;&amp;gt;</span><br><span class="hljs-string">            &amp;lt;div class=&quot;CollapseHeader&quot; id=&quot;CfgCollapseRm&quot;&amp;gt;</span><br><span class="hljs-string">                &amp;lt;span class=&quot;CollapseArrow&quot; id=&quot;CfgCollapseRmArrow&quot;&amp;gt;▸&amp;lt;/span&amp;gt;</span><br><span class="hljs-string">                &amp;lt;span&amp;gt;<span class="hljs-subst">$&#123;<span class="hljs-string">&#x27;去除元素&#x27;</span>&#125;</span>&amp;lt;/span&amp;gt;</span><br><span class="hljs-string">            &amp;lt;/div&amp;gt;</span><br><span class="hljs-string">            &amp;lt;div class=&quot;CollapseContent collapsed&quot; id=&quot;CfgCollapseRmContent&quot;&amp;gt;</span><br><span class="hljs-string">            <span class="hljs-subst">$&#123;ELEMENT_REMOVAL_PRESETS.map(<span class="hljs-keyword">function</span>(P) &#123;</span></span><br><span class="hljs-subst"><span class="hljs-string">                <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;&amp;lt;label&amp;gt;&amp;lt;span style=&quot;flex:1&quot;&amp;gt;&#x27;</span> + P.name + <span class="hljs-string">&#x27;&amp;lt;/span&amp;gt; &amp;lt;input type=&quot;checkbox&quot; id=&quot;CfgRm_&#x27;</span> + P.id + <span class="hljs-string">&#x27;&quot; &#x27;</span> + ((Config.RemovedElements || &#123;&#125;)[P.id] ? <span class="hljs-string">&#x27;checked&#x27;</span> : <span class="hljs-string">&#x27;&#x27;</span>) + <span class="hljs-string">&#x27;&amp;gt;&amp;lt;/label&amp;gt;&#x27;</span>;</span></span><br><span class="hljs-subst"><span class="hljs-string">            &#125;).join(<span class="hljs-string">&#x27;&#x27;</span>)&#125;</span></span><br><span class="hljs-string">            &amp;lt;/div&amp;gt;</span><br><span class="hljs-string">            &amp;lt;div class=&quot;BtnRow&quot;&amp;gt;</span><br><span class="hljs-string">                &amp;lt;button class=&quot;Secondary&quot; id=&quot;CfgToggle&quot;&amp;gt;<span class="hljs-subst">$&#123;IsActive ? T(<span class="hljs-string">&#x27;PanelBtnPause&#x27;</span>) : T(<span class="hljs-string">&#x27;PanelBtnResume&#x27;</span>)&#125;</span>&amp;lt;/button&amp;gt;</span><br><span class="hljs-string">                &amp;lt;button class=&quot;Secondary&quot; id=&quot;CfgReset&quot;&amp;gt;<span class="hljs-subst">$&#123;T(<span class="hljs-string">&#x27;PanelBtnReset&#x27;</span>)&#125;</span>&amp;lt;/button&amp;gt;</span><br><span class="hljs-string">                &amp;lt;button id=&quot;CfgSave&quot;&amp;gt;<span class="hljs-subst">$&#123;T(<span class="hljs-string">&#x27;PanelBtnSave&#x27;</span>)&#125;</span>&amp;lt;/button&amp;gt;</span><br><span class="hljs-string">            &amp;lt;/div&amp;gt;</span><br><span class="hljs-string">        `</span>;<br><br>        <span class="hljs-title class_">Overlay</span>.<span class="hljs-title function_">appendChild</span>(<span class="hljs-title class_">Panel</span>);<br>        <span class="hljs-variable language_">document</span>.<span class="hljs-property">body</span>.<span class="hljs-title function_">appendChild</span>(<span class="hljs-title class_">Overlay</span>);<br><br>        <span class="hljs-comment">// —— 事件绑定 ——</span><br><br>        <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgSave&#x27;</span>).<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;click&#x27;</span>, <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) &#123;<br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">Max</span> = <span class="hljs-built_in">parseInt</span>(<span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgMax&#x27;</span>).<span class="hljs-property">value</span>) || <span class="hljs-number">10</span>;<br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">NewLang</span> = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgLanguage&#x27;</span>).<span class="hljs-property">value</span>;<br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">LangChanged</span> = <span class="hljs-title class_">NewLang</span> !== <span class="hljs-title class_">Config</span>.<span class="hljs-property">Language</span>;<br><br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">NewConfig</span> = &#123;<br>                <span class="hljs-title class_">MaxVideos</span>: <span class="hljs-title class_">Max</span>,<br>                <span class="hljs-title class_">ExcludeLive</span>: <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgExcludeLive&#x27;</span>).<span class="hljs-property">checked</span>,<br>                <span class="hljs-title class_">ExcludeAd</span>: <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgExcludeAd&#x27;</span>).<span class="hljs-property">checked</span>,<br>                <span class="hljs-title class_">ExcludeBangumi</span>: <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgExcludeBangumi&#x27;</span>).<span class="hljs-property">checked</span>,<br>                <span class="hljs-title class_">ExcludePaid</span>: <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgExcludePaid&#x27;</span>).<span class="hljs-property">checked</span>,<br>                <span class="hljs-title class_">KeepPromoted</span>: <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgKeepPromoted&#x27;</span>).<span class="hljs-property">checked</span>,<br>                <span class="hljs-title class_">Language</span>: <span class="hljs-title class_">NewLang</span>,<br>                <span class="hljs-title class_">ColorMode</span>: <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgColorMode&#x27;</span>).<span class="hljs-property">value</span>,<br>                <span class="hljs-title class_">KeepSpecialUPIDs</span>: <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgKeepUids&#x27;</span>).<span class="hljs-property">value</span>.<span class="hljs-title function_">split</span>(<span class="hljs-string">&#x27;,&#x27;</span>).<span class="hljs-title function_">map</span>(S =&amp;gt; S.<span class="hljs-title function_">trim</span>()).<span class="hljs-title function_">filter</span>(<span class="hljs-title class_">Boolean</span>).<span class="hljs-title function_">map</span>(<span class="hljs-title class_">Number</span>),<br>                <span class="hljs-title class_">Debug</span>: <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgDebug&#x27;</span>).<span class="hljs-property">checked</span>,<br>                <span class="hljs-title class_">EnableCommentPurifier</span>: <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgEnablePurifier&#x27;</span>).<span class="hljs-property">checked</span>,<br>                <span class="hljs-title class_">RemovedElements</span>: (<span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) &#123;<br>                    <span class="hljs-keyword">var</span> obj = &#123;&#125;;<br>                    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> I = <span class="hljs-number">0</span>; I &amp;lt; <span class="hljs-variable constant_">ELEMENT_REMOVAL_PRESETS</span>.<span class="hljs-property">length</span>; I++) &#123;<br>                        <span class="hljs-keyword">var</span> cb = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgRm_&#x27;</span> + <span class="hljs-variable constant_">ELEMENT_REMOVAL_PRESETS</span>[I].<span class="hljs-property">id</span>);<br>                        <span class="hljs-keyword">if</span> (cb) obj[<span class="hljs-variable constant_">ELEMENT_REMOVAL_PRESETS</span>[I].<span class="hljs-property">id</span>] = cb.<span class="hljs-property">checked</span>;<br>                    &#125;<br>                    <span class="hljs-keyword">return</span> obj;<br>                &#125;)()<br>            &#125;;<br>            <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">assign</span>(<span class="hljs-title class_">Config</span>, <span class="hljs-title class_">NewConfig</span>);<br>            <span class="hljs-title class_">SaveConfig</span>(<span class="hljs-title class_">Config</span>);<br><br>            <span class="hljs-comment">// 语言变更时立即生效</span><br>            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">LangChanged</span>) &#123;<br>                <span class="hljs-title class_">CurrentLang</span> = <span class="hljs-title class_">ResolveLanguage</span>();<br>            &#125;<br><br>            <span class="hljs-title class_">DestroyPanel</span>();<br>            <span class="hljs-title class_">LimitVideos</span>();<br>            <span class="hljs-title function_">applyElementRemoval</span>();<br><br>            <span class="hljs-comment">// 评论净化器开关变更后立即启停</span><br>            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">NewConfig</span>.<span class="hljs-property">EnableCommentPurifier</span>) &#123;<br>                <span class="hljs-title function_">startCommentPurifier</span>();<br>            &#125; <span class="hljs-keyword">else</span> &#123;<br>                <span class="hljs-title function_">stopCommentPurifier</span>();<br>            &#125;<br><br>            <span class="hljs-comment">// 语言变更后重新打开面板（让用户看到新语言）</span><br>            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">LangChanged</span>) &#123;<br>                <span class="hljs-built_in">setTimeout</span>(() =&amp;gt; <span class="hljs-title class_">OpenConfigPanel</span>(), <span class="hljs-number">100</span>);<br>            &#125;<br>        &#125;);<br><br>        <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgReset&#x27;</span>).<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;click&#x27;</span>, <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) &#123;<br>            <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">assign</span>(<span class="hljs-title class_">Config</span>, <span class="hljs-variable constant_">DEFAULTS</span>);<br>            <span class="hljs-title class_">SaveConfig</span>(<span class="hljs-title class_">Config</span>);<br>            <span class="hljs-title class_">CurrentLang</span> = <span class="hljs-title class_">ResolveLanguage</span>();<br>            <span class="hljs-comment">// 刷新面板输入</span><br>            <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgMax&#x27;</span>).<span class="hljs-property">value</span> = <span class="hljs-title class_">Config</span>.<span class="hljs-property">MaxVideos</span>;<br>            <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgExcludeLive&#x27;</span>).<span class="hljs-property">checked</span> = <span class="hljs-title class_">Config</span>.<span class="hljs-property">ExcludeLive</span>;<br>            <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgExcludeAd&#x27;</span>).<span class="hljs-property">checked</span> = <span class="hljs-title class_">Config</span>.<span class="hljs-property">ExcludeAd</span>;<br>            <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgExcludeBangumi&#x27;</span>).<span class="hljs-property">checked</span> = <span class="hljs-title class_">Config</span>.<span class="hljs-property">ExcludeBangumi</span>;<br>            <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgExcludePaid&#x27;</span>).<span class="hljs-property">checked</span> = <span class="hljs-title class_">Config</span>.<span class="hljs-property">ExcludePaid</span>;<br>            <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgKeepPromoted&#x27;</span>).<span class="hljs-property">checked</span> = <span class="hljs-title class_">Config</span>.<span class="hljs-property">KeepPromoted</span>;<br>            <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgDebug&#x27;</span>).<span class="hljs-property">checked</span> = <span class="hljs-title class_">Config</span>.<span class="hljs-property">Debug</span>;<br>            <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&quot;CfgEnablePurifier&quot;</span>).<span class="hljs-property">checked</span> = <span class="hljs-literal">false</span>;<br>            <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgColorMode&#x27;</span>).<span class="hljs-property">value</span> = <span class="hljs-title class_">Config</span>.<span class="hljs-property">ColorMode</span> || <span class="hljs-string">&#x27;auto&#x27;</span>;<br>            <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgKeepUids&#x27;</span>).<span class="hljs-property">value</span> = <span class="hljs-string">&#x27;&#x27;</span>;<br>            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> I = <span class="hljs-number">0</span>; I &amp;lt; <span class="hljs-variable constant_">ELEMENT_REMOVAL_PRESETS</span>.<span class="hljs-property">length</span>; I++) &#123;<br>                <span class="hljs-keyword">var</span> cb = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgRm_&#x27;</span> + <span class="hljs-variable constant_">ELEMENT_REMOVAL_PRESETS</span>[I].<span class="hljs-property">id</span>);<br>                <span class="hljs-keyword">if</span> (cb) cb.<span class="hljs-property">checked</span> = <span class="hljs-literal">false</span>;<br>            &#125;<br>            <span class="hljs-title function_">stopCommentPurifier</span>();<br>            <span class="hljs-comment">// 重置后重新应用颜色模式主题</span><br>            <span class="hljs-keyword">const</span> resetPanel = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">&#x27;.BiliCompactPanel&#x27;</span>);<br>            <span class="hljs-keyword">if</span> (resetPanel) &#123;<br>                <span class="hljs-keyword">const</span> resetMode = (<span class="hljs-title class_">Config</span>.<span class="hljs-property">ColorMode</span> || <span class="hljs-string">&#x27;auto&#x27;</span>) === <span class="hljs-string">&#x27;auto&#x27;</span><br>                    ? (<span class="hljs-variable language_">window</span>.<span class="hljs-title function_">matchMedia</span>(<span class="hljs-string">&#x27;(prefers-color-scheme: dark)&#x27;</span>).<span class="hljs-property">matches</span> ? <span class="hljs-string">&#x27;dark&#x27;</span> : <span class="hljs-string">&#x27;light&#x27;</span>)<br>                    : <span class="hljs-title class_">Config</span>.<span class="hljs-property">ColorMode</span>;<br>                resetPanel.<span class="hljs-property">classList</span>.<span class="hljs-title function_">toggle</span>(<span class="hljs-string">&#x27;light-mode&#x27;</span>, resetMode === <span class="hljs-string">&#x27;light&#x27;</span>);<br>            &#125;<br>            <span class="hljs-title class_">LimitVideos</span>();<br>            <span class="hljs-title function_">applyElementRemoval</span>();<br>        &#125;);<br><br>        <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgToggle&#x27;</span>).<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;click&#x27;</span>, <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) &#123;<br>            <span class="hljs-title class_">IsActive</span> = !<span class="hljs-title class_">IsActive</span>;<br>            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">IsActive</span>) &#123;<br>                <span class="hljs-title class_">LimitVideos</span>();<br>            &#125; <span class="hljs-keyword">else</span> &#123;<br>                <span class="hljs-title class_">RestoreAllVideos</span>();<br>            &#125;<br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">Badge</span> = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgStatusBadge&#x27;</span>);<br>            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Badge</span>) &#123;<br>                <span class="hljs-title class_">Badge</span>.<span class="hljs-property">textContent</span> = <span class="hljs-title class_">IsActive</span> ? <span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;PanelStatusActive&#x27;</span>) : <span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;PanelStatusPaused&#x27;</span>);<br>                <span class="hljs-title class_">Badge</span>.<span class="hljs-property">className</span> = <span class="hljs-string">&#x27;StatusBadge&#x27;</span> + (<span class="hljs-title class_">IsActive</span> ? <span class="hljs-string">&#x27;&#x27;</span> : <span class="hljs-string">&#x27; Off&#x27;</span>);<br>            &#125;<br>            <span class="hljs-variable language_">this</span>.<span class="hljs-property">textContent</span> = <span class="hljs-title class_">IsActive</span> ? <span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;PanelBtnPause&#x27;</span>) : <span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;PanelBtnResume&#x27;</span>);<br>        &#125;);<br><br>        <span class="hljs-comment">// 去除元素折叠切换</span><br>        <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgCollapseRm&#x27;</span>).<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;click&#x27;</span>, <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) &#123;<br>            <span class="hljs-keyword">const</span> content = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgCollapseRmContent&#x27;</span>);<br>            <span class="hljs-keyword">const</span> arrow = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;CfgCollapseRmArrow&#x27;</span>);<br>            <span class="hljs-keyword">const</span> isCollapsed = content.<span class="hljs-property">classList</span>.<span class="hljs-title function_">contains</span>(<span class="hljs-string">&#x27;collapsed&#x27;</span>);<br>            <span class="hljs-keyword">if</span> (isCollapsed) &#123;<br>                content.<span class="hljs-property">classList</span>.<span class="hljs-title function_">remove</span>(<span class="hljs-string">&#x27;collapsed&#x27;</span>);<br>                arrow.<span class="hljs-property">classList</span>.<span class="hljs-title function_">add</span>(<span class="hljs-string">&#x27;open&#x27;</span>);<br>            &#125; <span class="hljs-keyword">else</span> &#123;<br>                content.<span class="hljs-property">classList</span>.<span class="hljs-title function_">add</span>(<span class="hljs-string">&#x27;collapsed&#x27;</span>);<br>                arrow.<span class="hljs-property">classList</span>.<span class="hljs-title function_">remove</span>(<span class="hljs-string">&#x27;open&#x27;</span>);<br>            &#125;<br>        &#125;);<br><br>        <span class="hljs-title class_">Overlay</span>.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;click&#x27;</span>, <span class="hljs-keyword">function</span>(<span class="hljs-params">E</span>) &#123;<br>            <span class="hljs-keyword">if</span> (E.<span class="hljs-property">target</span> === <span class="hljs-title class_">Overlay</span>) &#123;<br>                <span class="hljs-title class_">DestroyPanel</span>();<br>            &#125;<br>        &#125;);<br><br>        <span class="hljs-keyword">function</span> <span class="hljs-title function_">OnKeyDown</span>(<span class="hljs-params">E</span>) &#123;<br>            <span class="hljs-keyword">if</span> (E.<span class="hljs-property">key</span> === <span class="hljs-string">&#x27;Escape&#x27;</span>) &#123;<br>                <span class="hljs-title class_">DestroyPanel</span>();<br>            &#125;<br>        &#125;<br>        <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;keydown&#x27;</span>, <span class="hljs-title class_">OnKeyDown</span>);<br><br>        <span class="hljs-keyword">function</span> <span class="hljs-title function_">DestroyPanel</span>(<span class="hljs-params"></span>) &#123;<br>            <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">removeEventListener</span>(<span class="hljs-string">&#x27;keydown&#x27;</span>, <span class="hljs-title class_">OnKeyDown</span>);<br>            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Overlay</span>.<span class="hljs-property">parentNode</span>) &#123;<br>                <span class="hljs-title class_">Overlay</span>.<span class="hljs-property">parentNode</span>.<span class="hljs-title function_">removeChild</span>(<span class="hljs-title class_">Overlay</span>);<br>            &#125;<br>            <span class="hljs-title class_">PanelDestroyFn</span> = <span class="hljs-literal">null</span>;<br>        &#125;<br><br>        <span class="hljs-title class_">PanelDestroyFn</span> = <span class="hljs-title class_">DestroyPanel</span>;<br>    &#125;<br><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">CloseConfigPanel</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-keyword">if</span> (<span class="hljs-title class_">PanelDestroyFn</span>) &#123;<br>            <span class="hljs-title class_">PanelDestroyFn</span>();<br>            <span class="hljs-title class_">PanelDestroyFn</span> = <span class="hljs-literal">null</span>;<br>        &#125;<br>    &#125;<br><br><br>    <span class="hljs-comment">// ======================== 观察者 ========================</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">InitObserver</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Observer</span>) &#123;<br>            <span class="hljs-title class_">Observer</span>.<span class="hljs-title function_">disconnect</span>();<br>            <span class="hljs-title class_">Observer</span> = <span class="hljs-literal">null</span>;<br>        &#125;<br><br>        <span class="hljs-keyword">const</span> <span class="hljs-title class_">Container</span> = <span class="hljs-title class_">DetectContainer</span>();<br>        <span class="hljs-keyword">if</span> (!<span class="hljs-title class_">Container</span>) <span class="hljs-keyword">return</span>;<br><br>        <span class="hljs-title class_">Observer</span> = <span class="hljs-keyword">new</span> <span class="hljs-title class_">MutationObserver</span>(<span class="hljs-keyword">function</span>(<span class="hljs-params">Mutations</span>) &#123;<br>            <span class="hljs-keyword">let</span> <span class="hljs-title class_">ShouldProcess</span> = <span class="hljs-literal">false</span>;<br>            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-title class_">Mutation</span> <span class="hljs-keyword">of</span> <span class="hljs-title class_">Mutations</span>) &#123;<br>                <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Mutation</span>.<span class="hljs-property">type</span> === <span class="hljs-string">&#x27;childList&#x27;</span> &amp;amp;&amp;amp; (<span class="hljs-title class_">Mutation</span>.<span class="hljs-property">addedNodes</span>.<span class="hljs-property">length</span> &amp;gt; <span class="hljs-number">0</span> || <span class="hljs-title class_">Mutation</span>.<span class="hljs-property">removedNodes</span>.<span class="hljs-property">length</span> &amp;gt; <span class="hljs-number">0</span>)) &#123;<br>                    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-title class_">Node</span> <span class="hljs-keyword">of</span> <span class="hljs-title class_">Mutation</span>.<span class="hljs-property">addedNodes</span>) &#123;<br>                        <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Node</span>.<span class="hljs-property">nodeType</span> === <span class="hljs-number">1</span>) &#123;<br>                            <span class="hljs-keyword">const</span> <span class="hljs-title class_">Sel</span> = <span class="hljs-title class_">DetectSelector</span>();<br>                            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Sel</span> &amp;amp;&amp;amp; (<span class="hljs-title class_">Node</span>.<span class="hljs-title function_">matches</span>(<span class="hljs-title class_">Sel</span>) || <span class="hljs-title class_">Node</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-title class_">Sel</span>))) &#123;<br>                                <span class="hljs-title class_">ShouldProcess</span> = <span class="hljs-literal">true</span>;<br>                                <span class="hljs-keyword">break</span>;<br>                            &#125;<br>                        &#125;<br>                    &#125;<br>                    <span class="hljs-keyword">if</span> (!<span class="hljs-title class_">ShouldProcess</span>) &#123;<br>                        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-title class_">Node</span> <span class="hljs-keyword">of</span> <span class="hljs-title class_">Mutation</span>.<span class="hljs-property">removedNodes</span>) &#123;<br>                            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Node</span>.<span class="hljs-property">nodeType</span> === <span class="hljs-number">1</span>) &#123;<br>                                <span class="hljs-keyword">const</span> <span class="hljs-title class_">Sel</span> = <span class="hljs-title class_">DetectSelector</span>();<br>                                <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Sel</span> &amp;amp;&amp;amp; (<span class="hljs-title class_">Node</span>.<span class="hljs-title function_">matches</span>(<span class="hljs-title class_">Sel</span>) || <span class="hljs-title class_">Node</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-title class_">Sel</span>))) &#123;<br>                                    <span class="hljs-title class_">ShouldProcess</span> = <span class="hljs-literal">true</span>;<br>                                    <span class="hljs-keyword">break</span>;<br>                                &#125;<br>                            &#125;<br>                        &#125;<br>                    &#125;<br>                &#125;<br>                <span class="hljs-keyword">if</span> (<span class="hljs-title class_">ShouldProcess</span>) <span class="hljs-keyword">break</span>;<br>            &#125;<br><br>            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">ShouldProcess</span>) &#123;<br>                <span class="hljs-keyword">const</span> <span class="hljs-title class_">Now</span> = <span class="hljs-title class_">Date</span>.<span class="hljs-title function_">now</span>();<br>                <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Now</span> - <span class="hljs-title class_">LastRun</span> &amp;lt; <span class="hljs-variable constant_">THROTTLE_INTERVAL</span>) &#123;<br>                    <span class="hljs-built_in">clearTimeout</span>(<span class="hljs-title class_">DebounceTimer</span>);<br>                    <span class="hljs-title class_">DebounceTimer</span> = <span class="hljs-built_in">setTimeout</span>(() =&amp;gt; &#123;<br>                        <span class="hljs-title class_">LastRun</span> = <span class="hljs-title class_">Date</span>.<span class="hljs-title function_">now</span>();<br>                        <span class="hljs-title class_">LimitVideos</span>();<br>                    &#125;, <span class="hljs-number">300</span>);<br>                &#125; <span class="hljs-keyword">else</span> &#123;<br>                    <span class="hljs-title class_">LastRun</span> = <span class="hljs-title class_">Now</span>;<br>                    <span class="hljs-title class_">LimitVideos</span>();<br>                &#125;<br>            &#125;<br>        &#125;);<br><br>        <span class="hljs-title class_">Observer</span>.<span class="hljs-title function_">observe</span>(<span class="hljs-title class_">Container</span>, &#123;<br>            <span class="hljs-attr">childList</span>: <span class="hljs-literal">true</span>,<br>            <span class="hljs-attr">subtree</span>: <span class="hljs-literal">true</span>,<br>            <span class="hljs-attr">attributes</span>: <span class="hljs-literal">false</span><br>        &#125;);<br><br>        <span class="hljs-title class_">Log</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogObserverStarted&#x27;</span>), <span class="hljs-title class_">Container</span>);<br>    &#125;<br><br>    <span class="hljs-comment">// ======================== 路由变化监听 ========================</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">WatchUrlChange</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-keyword">let</span> <span class="hljs-title class_">LastUrl</span> = location.<span class="hljs-property">href</span>;<br>        <span class="hljs-built_in">setInterval</span>(() =&amp;gt; &#123;<br>            <span class="hljs-keyword">if</span> (location.<span class="hljs-property">href</span> !== <span class="hljs-title class_">LastUrl</span>) &#123;<br>                <span class="hljs-title class_">LastUrl</span> = location.<span class="hljs-property">href</span>;<br>                <span class="hljs-title class_">Log</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogUrlChanged&#x27;</span>), <span class="hljs-title class_">LastUrl</span>);<br>                <span class="hljs-title class_">EffectiveSelector</span> = <span class="hljs-literal">null</span>;<br>                <span class="hljs-title class_">VideoListContainer</span> = <span class="hljs-literal">null</span>;<br>                <span class="hljs-built_in">setTimeout</span>(() =&amp;gt; &#123;<br>                    <span class="hljs-title class_">DetectSelector</span>();<br>                    <span class="hljs-title class_">DetectContainer</span>();<br>                    <span class="hljs-title class_">LimitVideos</span>();<br>                    <span class="hljs-title function_">applyElementRemoval</span>();<br>                &#125;, <span class="hljs-number">500</span>);<br>            &#125;<br>        &#125;, <span class="hljs-number">1000</span>);<br>    &#125;<br><br>    <span class="hljs-comment">// ======================== 菜单命令（唯一入口，在语言解析后注册） ========================</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">RegisterMenu</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-title function_">GM_registerMenuCommand</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;MenuSettings&#x27;</span>), <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) &#123;<br>            <span class="hljs-title class_">OpenConfigPanel</span>();<br>        &#125;);<br>        <span class="hljs-title function_">GM_registerMenuCommand</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;MenuRefresh&#x27;</span>), <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) &#123;<br>            <span class="hljs-title class_">EffectiveSelector</span> = <span class="hljs-literal">null</span>;<br>            <span class="hljs-title class_">VideoListContainer</span> = <span class="hljs-literal">null</span>;<br>            <span class="hljs-title class_">LimitVideos</span>();<br>        &#125;);<br>        <span class="hljs-title function_">GM_registerMenuCommand</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;MenuToggle&#x27;</span>), <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) &#123;<br>            <span class="hljs-title class_">IsActive</span> = !<span class="hljs-title class_">IsActive</span>;<br>            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">IsActive</span>) &#123;<br>                <span class="hljs-title class_">LimitVideos</span>();<br>            &#125; <span class="hljs-keyword">else</span> &#123;<br>                <span class="hljs-title class_">RestoreAllVideos</span>();<br>            &#125;<br>            <span class="hljs-title class_">Log</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogStatus&#x27;</span>), <span class="hljs-title class_">IsActive</span> ? <span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogStatusOn&#x27;</span>) : <span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogStatusOff&#x27;</span>));<br>        &#125;);<br>        <span class="hljs-title function_">GM_registerMenuCommand</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;MenuCommentPurifier&#x27;</span>), <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) &#123;<br>            <span class="hljs-title function_">toggleCommentPurifier</span>();<br>        &#125;);<br>        <span class="hljs-title function_">GM_registerMenuCommand</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;MenuQuickSet&#x27;</span>), <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) &#123;<br>            <span class="hljs-keyword">const</span> <span class="hljs-title class_">Num</span> = <span class="hljs-title function_">prompt</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;PromptQuickSet&#x27;</span>), <span class="hljs-title class_">Config</span>.<span class="hljs-property">MaxVideos</span>);<br>            <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Num</span> !== <span class="hljs-literal">null</span>) &#123;<br>                <span class="hljs-keyword">const</span> N = <span class="hljs-built_in">parseInt</span>(<span class="hljs-title class_">Num</span>);<br>                <span class="hljs-keyword">if</span> (N &amp;gt;= <span class="hljs-number">1</span> &amp;amp;&amp;amp; N &amp;lt;= <span class="hljs-number">100</span>) &#123;<br>                    <span class="hljs-title class_">Config</span>.<span class="hljs-property">MaxVideos</span> = N;<br>                    <span class="hljs-title class_">SaveConfig</span>(&#123; <span class="hljs-title class_">MaxVideos</span>: N &#125;);<br>                    <span class="hljs-keyword">if</span> (!<span class="hljs-title class_">IsActive</span>) &#123;<br>                        <span class="hljs-title class_">IsActive</span> = <span class="hljs-literal">true</span>;<br>                    &#125;<br>                    <span class="hljs-title class_">LimitVideos</span>();<br>                    <span class="hljs-title class_">Log</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogQuickSet&#x27;</span>), N);<br>                &#125;<br>            &#125;<br>        &#125;);<br>    &#125;<br><br>    <span class="hljs-comment">// ======================== 初始化 ========================</span><br>    <span class="hljs-keyword">function</span> <span class="hljs-title function_">Init</span>(<span class="hljs-params"></span>) &#123;<br>        <span class="hljs-keyword">try</span> &#123;<br>            <span class="hljs-comment">// 加载配置</span><br>            <span class="hljs-title class_">Config</span> = <span class="hljs-title class_">GetConfig</span>();<br><br>            <span class="hljs-comment">// 解析语言（必须在任何 T() 调用之前）</span><br>            <span class="hljs-title class_">CurrentLang</span> = <span class="hljs-title class_">ResolveLanguage</span>();<br><br>            <span class="hljs-title class_">Log</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogConfigLoaded&#x27;</span>), <span class="hljs-title class_">Config</span>);<br><br>            <span class="hljs-comment">// 注入样式（仅过滤类，不注入任何UI节点）</span><br>            <span class="hljs-title class_">InjectStyles</span>();<br><br>            <span class="hljs-comment">// 注册菜单（TM菜单是唯一入口，不在页面注入UI）</span><br>            <span class="hljs-title class_">RegisterMenu</span>();<br><br>            <span class="hljs-comment">// 启动评论净化器（如果启用）</span><br>            <span class="hljs-title function_">startCommentPurifier</span>();<br><br>            <span class="hljs-comment">// 启动观察</span><br>            <span class="hljs-title class_">InitObserver</span>();<br><br>            <span class="hljs-comment">// 路由监听</span><br>            <span class="hljs-title class_">WatchUrlChange</span>();<br><br>            <span class="hljs-comment">// 初次执行</span><br>            <span class="hljs-built_in">setTimeout</span>(() =&amp;gt; &#123;<br>                <span class="hljs-title class_">DetectSelector</span>();<br>                <span class="hljs-title class_">DetectContainer</span>();<br>                <span class="hljs-title class_">LimitVideos</span>();<br>                <span class="hljs-title function_">applyElementRemoval</span>();<br>            &#125;, <span class="hljs-number">500</span>);<br><br>            <span class="hljs-comment">// 定时后备检查</span><br>            <span class="hljs-built_in">setInterval</span>(() =&amp;gt; &#123;<br>                <span class="hljs-keyword">if</span> (<span class="hljs-title class_">IsActive</span>) &#123;<br>                    <span class="hljs-keyword">const</span> <span class="hljs-title class_">Selector</span> = <span class="hljs-title class_">DetectSelector</span>();<br>                    <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Selector</span>) &#123;<br>                        <span class="hljs-keyword">const</span> <span class="hljs-title class_">Cards</span> = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelectorAll</span>(<span class="hljs-title class_">Selector</span>);<br>                        <span class="hljs-keyword">let</span> <span class="hljs-title class_">VisibleCount</span> = <span class="hljs-number">0</span>;<br>                        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> <span class="hljs-title class_">Card</span> <span class="hljs-keyword">of</span> <span class="hljs-title class_">Cards</span>) &#123;<br>                            <span class="hljs-keyword">if</span> (!(<span class="hljs-title class_">Card</span>.<span class="hljs-property">style</span>.<span class="hljs-property">display</span> === <span class="hljs-string">&#x27;none&#x27;</span> || <span class="hljs-title class_">Card</span>.<span class="hljs-property">classList</span>.<span class="hljs-title function_">contains</span>(<span class="hljs-string">&#x27;BiliLimitedHide&#x27;</span>))) &#123;<br>                                <span class="hljs-title class_">VisibleCount</span>++;<br>                            &#125;<br>                        &#125;<br>                        <span class="hljs-keyword">if</span> (<span class="hljs-title class_">VisibleCount</span> &amp;gt; <span class="hljs-title class_">Config</span>.<span class="hljs-property">MaxVideos</span>) &#123;<br>                            <span class="hljs-title class_">Log</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogTimerRetry&#x27;</span>));<br>                            <span class="hljs-title class_">LimitVideos</span>();<br>                        &#125;<br>                    &#125;<br>                &#125;<br>                <span class="hljs-comment">// 定时重试元素去除（B站SPA可能动态替换DOM）</span><br>                <span class="hljs-title function_">applyElementRemoval</span>();<br>            &#125;, <span class="hljs-number">5000</span>);<br><br>            <span class="hljs-title class_">Log</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogInitDone&#x27;</span>), <span class="hljs-title class_">Config</span>);<br><br>        &#125; <span class="hljs-keyword">catch</span> (E) &#123;<br>            <span class="hljs-title class_">ErrorLog</span>(<span class="hljs-title function_">T</span>(<span class="hljs-string">&#x27;LogInitError&#x27;</span>), E);<br>        &#125;<br>    &#125;<br><br>    <span class="hljs-comment">// 页面加载完成</span><br>    <span class="hljs-keyword">if</span> (<span class="hljs-variable language_">document</span>.<span class="hljs-property">readyState</span> === <span class="hljs-string">&#x27;loading&#x27;</span>) &#123;<br>        <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;DOMContentLoaded&#x27;</span>, <span class="hljs-title class_">Init</span>);<br>    &#125; <span class="hljs-keyword">else</span> &#123;<br>        <span class="hljs-title class_">Init</span>();<br>    &#125;<br><br>&#125;)();<br><br></code></pre></td></tr></table></figure><hr><h2 id="一键安装"><a class="heading-anchor" href="#一键安装" aria-hidden="true">#</a>一键安装</h2><p><span class="install-badge">推荐</span></p><h3 id="方式一：Greasyfork-直接安装（推荐）"><a class="heading-anchor" href="#方式一：Greasyfork-直接安装（推荐）" aria-hidden="true">#</a>方式一：Greasyfork 直接安装（推荐）</h3><p>点击下方按钮，一键安装到 Tampermonkey &#x2F; Violentmonkey：</p><div class="install-block">    <a class="install-btn" href="https://greasyfork.org/scripts/585777" target="_blank"  rel="noopener noreferrer">        从 Greasyfork 安装    </a>    <p class="install-hint">已上架 Greasyfork，自动更新，省心省力。</p></div><h3 id="方式二：复制源码，手动创建"><a class="heading-anchor" href="#方式二：复制源码，手动创建" aria-hidden="true">#</a>方式二：复制源码，手动创建</h3><p>如果你想审查每一行代码再安装，也可以复制上面的源码，在 Tampermonkey 中新建脚本，粘贴保存即可。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs bash">1. 打开浏览器的 Tampermonkey / Violentmonkey 扩展<br>2. 点击「新建脚本」<br>3. 删除默认内容，粘贴上方源码<br>4. Ctrl+S 保存<br></code></pre></td></tr></table></figure><p>两种方式效果完全一致，推荐方式一，后续新版本会自动推送更新。<br><img src="/img/360px/bilicompact-rainbow.jpg" loading="lazy" decoding="async"></p><h2 id="为什么要写这个脚本"><a class="heading-anchor" href="#为什么要写这个脚本" aria-hidden="true">#</a>为什么要写这个脚本</h2><p>写了一天代码，晚上瘫在椅子上，打开 B 站想刷两个视频放松一下。</p><p>结果首页一刷新，直播，番剧，推广，付费课程铺了一整屏。我就想看两三个视频，信息量却先灌一脸，反而更累了。</p><p>这种憋屈感持续了挺久。不是说直播没人爱看，番剧没人追，但 B 站不管你想看什么，一股脑全塞给你。</p><p>我忍过，也试过假装看不见。但每次打开首页都像被按着头喂饭。后来觉得与其每次都烦，不如自己动手。</p><p>于是有了 <strong>BiliCompact</strong>。</p><p>要求很简单，首页想显示几个视频就显示几个。十个太多？五个。五个还多？三个也行。直播，广告，番剧推广，不想看就别出现。评论区满屏的 @某某某 刷屏，能自动清掉最好。</p><p>然后就动手了。</p><figure class="bili-embed" data-bili-src="https://player.bilibili.com/player.html?bvid=BV1KEgk64EHJ&amp;page=1&amp;high_quality=1&amp;danmaku=0"><button type="button" class="bili-embed-trigger" aria-label="加载 B 站视频 BV1KEgk64EHJ"><span class="bili-embed-icon" aria-hidden="true">▶</span><span class="bili-embed-label">B 站视频 · BV1KEgk64EHJ</span><span class="bili-embed-hint">点击或滚动到此处加载播放器</span></button><div class="bili-embed-frame" hidden></div></figure><h2 id="B-站首页的信息过载"><a class="heading-anchor" href="#B-站首页的信息过载" aria-hidden="true">#</a>B 站首页的信息过载</h2><p>打开 B 站首页，扑面而来的是：</p><ul><li>直播流，大量正在直播的推荐</li><li>番剧推荐，你不一定感兴趣</li><li>广告与推广，商业内容</li><li>付费课程，推销入口</li></ul><p>真正想看的视频，被淹没在其中。</p><figure class="highlight erlang-repl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs erlang-repl">直播 ████████████████████████████████████ <span class="hljs-number">100</span><span class="hljs-comment">%</span><br>番剧 ██████████████████████████████ <span class="hljs-number">80</span><span class="hljs-comment">%</span><br>广告 ████████████████████ <span class="hljs-number">50</span><span class="hljs-comment">%</span><br>付费 ██████████ <span class="hljs-number">20</span><span class="hljs-comment">%</span><br>想看 ██████ <span class="hljs-number">12</span><span class="hljs-comment">%</span><br></code></pre></td></tr></table></figure><h2 id="非侵入式的设计哲学："><a class="heading-anchor" href="#非侵入式的设计哲学：" aria-hidden="true">#</a>非侵入式的设计哲学：</h2><p>很多同类脚本会在页面注入 UI 元素，按钮、浮窗、侧边栏。好处是交互直观，坏处是<strong>耦合太深</strong>。B 站的前端频繁改版，每次 DOM 结构调整，注入的 UI 就可能错位、失效，甚至阻塞页面渲染。</p><p>BiliCompact 的做法相反：</p><ul><li><strong>不在 B 站页面注入任何 UI 元素</strong></li><li>配置面板通过 Tampermonkey 的 <code>GM_registerMenuCommand</code> 唤起，以浮层形式展示</li><li>所有过滤逻辑在后台执行，页面外观无变化</li></ul><figure class="highlight lisp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs lisp">GM_registerMenuCommand(<span class="hljs-name">T</span>(&#x27;MenuSettings&#x27;), () =&gt; OpenConfigPanel())<span class="hljs-comment">;</span><br>GM_registerMenuCommand(<span class="hljs-name">T</span>(&#x27;MenuRefresh&#x27;), () =&gt; LimitVideos())<span class="hljs-comment">;</span><br>GM_registerMenuCommand(<span class="hljs-name">T</span>(&#x27;MenuToggle&#x27;), () =&gt; &#123; IsActive = !IsActive<span class="hljs-comment">; &#125;);</span><br></code></pre></td></tr></table></figure><p>这样做的代价是交互入口不那么直观，藏在扩展菜单里，但换来的是<strong>高稳定性</strong>，B 站改版几次了，脚本还能正常工作。</p><h2 id="功能速览"><a class="heading-anchor" href="#功能速览" aria-hidden="true">#</a>功能速览</h2><table><thead><tr><th>功能</th><th>说明</th></tr></thead><tbody><tr><td>数量限制</td><td>设置每页最大显示视频数量，不同页面可独立设置</td></tr><tr><td>智能过滤</td><td>排除直播 &#x2F; 广告 &#x2F; 番剧 &#x2F; 付费课程</td></tr><tr><td>UP 主白名单</td><td>保留指定 UP 主的内容</td></tr><tr><td>一键切换</td><td>快捷开关，随时恢复原始首页</td></tr><tr><td>评论净化</td><td>删除 @ 提及，隐藏短评论</td></tr><tr><td>配置持久化</td><td>跨页面保存所有设置</td></tr></tbody></table><h3 id="配置面板"><a class="heading-anchor" href="#配置面板" aria-hidden="true">#</a>配置面板</h3><p>通过 Tampermonkey 菜单打开：</p><table><thead><tr><th>设置项</th><th>说明</th><th>默认</th></tr></thead><tbody><tr><td>最大显示数量</td><td>每页最多显示的视频数</td><td>10</td></tr><tr><td>排除直播</td><td>隐藏直播推荐</td><td>[Checked]</td></tr><tr><td>排除广告</td><td>隐藏推广内容</td><td>[Checked]</td></tr><tr><td>排除番剧</td><td>隐藏番剧推荐</td><td>[Checked]</td></tr><tr><td>排除付费课程</td><td>隐藏付费内容</td><td>[Checked]</td></tr><tr><td>保留推广位</td><td>推广内容不计入数量</td><td>[Unchecked]</td></tr><tr><td>调试模式</td><td>输出日志到控制台</td><td>[Unchecked]</td></tr><tr><td>评论净化</td><td>自动删除 @ 提及</td><td>[Unchecked]</td></tr></tbody></table><p>每种页面可以独立配置数量：首页 10、热门 8、分区 10、动态 6、搜索 10。</p><h2 id="技术架构"><a class="heading-anchor" href="#技术架构" aria-hidden="true">#</a>技术架构</h2><h3 id="选择器探测机制"><a class="heading-anchor" href="#选择器探测机制" aria-hidden="true">#</a>选择器探测机制</h3><p>B 站首页的 DOM 结构经常变化，直接写死 CSS 选择器是行不通的。脚本实现了一套<strong>多层级降级探测</strong>策略：</p><figure class="highlight prolog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs prolog">第一层  id / class 精准选择器              覆盖 <span class="hljs-number">2</span><span class="hljs-comment">%</span><br>第二层  data-* 属性探测 (data-video-id)     +<span class="hljs-number">3</span><span class="hljs-comment">%</span><br>第三层  宽泛属性选择器 [class*=<span class="hljs-string">&quot;video&quot;</span>]      +<span class="hljs-number">5</span><span class="hljs-comment">%</span><br>第四层  链接文本回退 (&lt;a href=<span class="hljs-string">&quot;/video/&quot;</span>&gt;)    +<span class="hljs-number">80</span><span class="hljs-comment">%</span><br>────────────────────────────────────────<br>最终覆盖率                                  <span class="hljs-number">99</span><span class="hljs-comment">%+</span><br></code></pre></td></tr></table></figure><p>具体实现上，每层依次尝试：</p><ol><li><strong>精确类名</strong>（<code>.bili-video-card</code>, <code>.feed-card</code> 等）</li><li><strong>通配类名</strong>（<code>[class*=&quot;video-card&quot;]</code> 等）</li><li><strong>通过链接回退</strong>（查找包含 <code>/video/</code> 链接的父级卡片）</li></ol><p>选择器被缓存，在 URL 变化时自动失效重新探测，适应 SPA 路由切换。</p><h3 id="过滤引擎"><a class="heading-anchor" href="#过滤引擎" aria-hidden="true">#</a>过滤引擎</h3><p>过滤逻辑分为几个阶段，顺序执行：</p><ol><li><strong>去重</strong>：消除嵌套卡片，避免同一个视频被重复计数</li><li><strong>规则过滤</strong>：按配置排除直播、广告、番剧、付费内容</li><li><strong>UP 主白名单</strong>：指定 ID 的 UP 主视频始终保留</li><li><strong>数量截断</strong>：超出限制的视频隐藏</li><li><strong>推广位保护</strong>：配置为保留推广位时，不计入数量限制</li></ol><h3 id="Grid-塌陷处理"><a class="heading-anchor" href="#Grid-塌陷处理" aria-hidden="true">#</a>Grid 塌陷处理</h3><p>B 站使用 CSS Grid 布局，简单地 <code>display: none</code> 卡片元素不会让网格塌陷，会留下空白。</p><p>BiliCompact 的处理方式是同时设置多个样式：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">display</span> = <span class="hljs-string">&#x27;none&#x27;</span>;<br><span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">visibility</span> = <span class="hljs-string">&#x27;hidden&#x27;</span>;<br><span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">opacity</span> = <span class="hljs-string">&#x27;0&#x27;</span>;<br><span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">height</span> = <span class="hljs-string">&#x27;0&#x27;</span>;<br><span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">margin</span> = <span class="hljs-string">&#x27;0&#x27;</span>;<br><span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">padding</span> = <span class="hljs-string">&#x27;0&#x27;</span>;<br><span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">overflow</span> = <span class="hljs-string">&#x27;hidden&#x27;</span>;<br><span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">flex</span> = <span class="hljs-string">&#x27;0 0 0&#x27;</span>;<br><span class="hljs-title class_">El</span>.<span class="hljs-property">style</span>.<span class="hljs-property">position</span> = <span class="hljs-string">&#x27;absolute&#x27;</span>;<br></code></pre></td></tr></table></figure><p>通过 <code>height: 0</code> + <code>padding: 0</code> + <code>position: absolute</code> + <code>overflow: hidden</code> 的组合，确保网格完全塌陷，不留视觉空白。</p><h3 id="MutationObserver-节流"><a class="heading-anchor" href="#MutationObserver-节流" aria-hidden="true">#</a>MutationObserver + 节流</h3><p>为了应对 B 站 SPA 动态加载内容，BiliCompact 使用 <code>MutationObserver</code> 监听 DOM 变化：</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs nginx"><span class="hljs-attribute">DOM</span> 变化 → MutationObserver 触发 → 防抖/节流(200ms) → 执行精简<br>                                                              ↕<br>                                                      定时回查(<span class="hljs-number">5s</span>)<br></code></pre></td></tr></table></figure><ul><li>200ms <strong>节流</strong>：防止高频触发</li><li>300ms <strong>防抖</strong>：等待 DOM 稳定后再执行</li><li>5s <strong>定时回查</strong>：兜底处理动态加载的内容</li></ul><h3 id="Shadow-DOM-穿透"><a class="heading-anchor" href="#Shadow-DOM-穿透" aria-hidden="true">#</a>Shadow DOM 穿透</h3><p>B 站新版评论区使用 Web Components 技术，评论内容在多层 Shadow DOM 内部：</p><figure class="highlight mipsasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs mipsasm">&lt;<span class="hljs-keyword">bili-comments&gt;</span><br><span class="hljs-keyword"></span>  └─ <span class="hljs-keyword">shadowRoot </span>→<br>    &lt;<span class="hljs-keyword">bili-comment-thread-renderer&gt;</span><br><span class="hljs-keyword"></span>      └─ <span class="hljs-keyword">shadowRoot </span>→<br>        &lt;<span class="hljs-keyword">bili-comment-renderer&gt;</span><br><span class="hljs-keyword"></span>          └─ <span class="hljs-keyword">shadowRoot </span>→<br>            &lt;<span class="hljs-keyword">bili-rich-text&gt;</span><br><span class="hljs-keyword"></span>              └─ <span class="hljs-keyword">shadowRoot </span>→ <span class="hljs-comment">#contents</span><br></code></pre></td></tr></table></figure><p>净化器递归访问 <code>element.shadowRoot</code> 属性，穿透所有 Shadow DOM 边界，找到评论内容容器：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">purifierGetContentsEl</span>(<span class="hljs-params">renderer</span>) &#123;<br>    <span class="hljs-keyword">const</span> richText = renderer.<span class="hljs-property">shadowRoot</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">&#x27;bili-rich-text&#x27;</span>);<br>    <span class="hljs-keyword">if</span> (richText &amp;&amp; richText.<span class="hljs-property">shadowRoot</span>) &#123;<br>        <span class="hljs-keyword">return</span> richText.<span class="hljs-property">shadowRoot</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">&#x27;contents&#x27;</span>);<br>    &#125;<br>    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;<br>&#125;<br></code></pre></td></tr></table></figure><p>删除所有 <code>a[data-type=&quot;mention&quot;]</code>（@ 提及标签），然后检查剩余文字长度，不足 5 字的评论整体隐藏。通过 MutationObserver 监听评论区动态加载，新评论出现时自动处理。同时兼容同页面其他脚本，检测到共存时仅做日志标记，不冲突。</p><h3 id="元素去除系统"><a class="heading-anchor" href="#元素去除系统" aria-hidden="true">#</a>元素去除系统</h3><p>除了核心的视频数量限制，脚本还提供了一组可选的 UI 元素去除预设，涵盖首页轮播图、频道导航、创作中心入口、直播入口等 13 个可去除元素。每个预设包含多版本选择器，适配 B 站不同的 DOM 版本。</p><h3 id="多语言支持"><a class="heading-anchor" href="#多语言支持" aria-hidden="true">#</a>多语言支持</h3><p>内置完整的 i18n 系统，支持简体中文、繁体中文、英文，自动根据浏览器语言切换：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">ResolveLanguage</span>(<span class="hljs-params"></span>) &#123;<br>    <span class="hljs-keyword">if</span> (<span class="hljs-title class_">Config</span>.<span class="hljs-property">Language</span> &amp;&amp; <span class="hljs-title class_">Config</span>.<span class="hljs-property">Language</span> !== <span class="hljs-string">&#x27;auto&#x27;</span>) &#123;<br>        <span class="hljs-keyword">return</span> <span class="hljs-title class_">Config</span>.<span class="hljs-property">Language</span>;<br>    &#125;<br>    <span class="hljs-keyword">const</span> <span class="hljs-title class_">Nav</span> = (navigator.<span class="hljs-property">language</span> || <span class="hljs-string">&#x27;&#x27;</span>).<span class="hljs-title function_">toLowerCase</span>();<br>    <span class="hljs-keyword">if</span> (<span class="hljs-regexp">/^zh-(tw|hk|mo)$/i</span>.<span class="hljs-title function_">test</span>(<span class="hljs-title class_">Nav</span>) || <span class="hljs-regexp">/^zh-(hant)$/i</span>.<span class="hljs-title function_">test</span>(<span class="hljs-title class_">Nav</span>)) <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;zh_TW&#x27;</span>;<br>    <span class="hljs-keyword">if</span> (<span class="hljs-regexp">/^zh/i</span>.<span class="hljs-title function_">test</span>(<span class="hljs-title class_">Nav</span>)) <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;zh_CN&#x27;</span>;<br>    <span class="hljs-keyword">if</span> (<span class="hljs-regexp">/^en/i</span>.<span class="hljs-title function_">test</span>(<span class="hljs-title class_">Nav</span>)) <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;en_US&#x27;</span>;<br>    <span class="hljs-keyword">return</span> <span class="hljs-string">&#x27;zh_CN&#x27;</span>;<br>&#125;<br></code></pre></td></tr></table></figure><p>所有用户可见文本通过 <code>T(key, ...args)</code> 函数统一翻译，键缺失时自动回退到中文，不会出现英文报错。语言可在配置面板中手动切换，切换后立即生效。</p><h3 id="持久化与实时生效"><a class="heading-anchor" href="#持久化与实时生效" aria-hidden="true">#</a>持久化与实时生效</h3><p>所有配置通过 <code>GM_setValue</code> &#x2F; <code>GM_getValue</code> 持久化，刷新页面不丢失。配置变更后立即生效，无需手动刷新。同时包含定时后备检查（每 5 秒），防止 B 站 SPA 的动态加载导致视频数量超出限制，这是一种防御性编程，确保极端情况下也能正常工作。</p><h2 id="用户反馈"><a class="heading-anchor" href="#用户反馈" aria-hidden="true">#</a>用户反馈</h2><p>没想到会有人愿意用这东西。</p><p>一开始只是自己用，顺手传到了 GreasyFork，想着万一有人需要呢。后来真有人说不用被首页轰炸了，还有人问能不能加某某功能。</p><p>看到这些还挺高兴的。</p><p>现在的互联网产品越做越重，功能越堆越多。但用户想要的，有时候就是清静一点。在产品和用户之间，BiliCompact 尝试稍微往用户这边拉一拉。</p><p>如果你也装了它，觉得有用，那我很高兴。觉得哪里不好用，也欢迎提。</p><p>代码完全开源，MIT 协议，想改就改，想删就删，没什么藏着掖着的。</p><h2 id="总结"><a class="heading-anchor" href="#总结" aria-hidden="true">#</a>总结</h2><p>BiliCompact 在功能上并不复杂，它的设计重心在<strong>稳定性</strong>和<strong>非侵入性</strong>上。一些值得借鉴的点：</p><ol><li><strong>选择器多梯队探测</strong>，应对频繁的前端改版</li><li><strong>非侵入式 UI</strong>，不污染页面 DOM，降低耦合</li><li><strong>Shadow DOM 穿透</strong>，处理现代前端框架的封装</li><li><strong>防御性定时检查</strong>，SPA 动态加载的兜底策略</li></ol>]]>
    </content>
    <id>https://twilightrain.com/2026/07/22/bilicompact-source/</id>
    <link href="https://twilightrain.com/2026/07/22/bilicompact-source/"/>
    <published>2026-07-22T04:00:00.000Z</published>
    <summary>BiliCompact 用户脚本 v2.6.0 源码与回顾。</summary>
    <title>BiliCompact 完整源码与解析</title>
    <updated>2026-08-26T05:06:32.651Z</updated>
  </entry>
  <entry>
    <author>
      <name>TwilightRain</name>
    </author>
    <category term="技术笔记" scheme="https://twilightrain.com/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="Hexo" scheme="https://twilightrain.com/tags/Hexo/"/>
    <category term="Cloudflare" scheme="https://twilightrain.com/tags/Cloudflare/"/>
    <category term="博客部署" scheme="https://twilightrain.com/tags/%E5%8D%9A%E5%AE%A2%E9%83%A8%E7%BD%B2/"/>
    <category term="运维" scheme="https://twilightrain.com/tags/%E8%BF%90%E7%BB%B4/"/>
    <content>
      <![CDATA[<h2 id="缘起"><a class="heading-anchor" href="#缘起" aria-hidden="true">#</a>缘起</h2><p>事情是这样的。</p><p>我的 Hexo 博客之前一直部署在 GitHub Pages 上，但我发现它在国内的访问体验很不稳定，时而能打开，时而转圈圈，时而直接超时。这是因为 <code>github.io</code> 域名在国内的 CDN 节点访问不畅，加上可能存在的 DNS 污染，导致博客对国内读者不太友好。</p><p>于是我开始寻找解决方案。最直接的办法是使用 Cloudflare Pages 来托管博客，利用它的全球 CDN 加速，让国内访问更稳定。</p><p><img src="/img/360px/hexo-to-cloudflare-pages.jpg" loading="lazy" decoding="async"></p><hr><h2 id="第一步：在-Cloudflare-Pages-创建项目"><a class="heading-anchor" href="#第一步：在-Cloudflare-Pages-创建项目" aria-hidden="true">#</a>第一步：在 Cloudflare Pages 创建项目</h2><p>操作本身不复杂：</p><ol><li>登录 Cloudflare Dashboard → <strong>Workers &amp; Pages</strong></li><li>点击 <strong>Create application</strong> → 选择 <strong>Pages</strong> 选项卡</li><li>点击 <strong>Connect to Git</strong>，授权 Cloudflare 访问你的 GitHub</li><li>选择你的博客仓库，填入构建设置：</li></ol><table><thead><tr><th>配置项</th><th>填写值</th></tr></thead><tbody><tr><td>Production branch</td><td>main</td></tr><tr><td>Framework preset</td><td>None（列表中没有 Hexo）</td></tr><tr><td>Build command</td><td><code>npm run build</code></td></tr><tr><td>Build directory</td><td>public</td></tr></tbody></table><ol><li>点击 <strong>Save and Deploy</strong></li></ol><p>一切都看起来很简单……直到我看到了红色的报错。</p><hr><h2 id="第二步：踩坑，package-json-找不到了？"><a class="heading-anchor" href="#第二步：踩坑，package-json-找不到了？" aria-hidden="true">#</a>第二步：踩坑，<code>package.json</code> 找不到了？</h2><p>Cloudflare Pages 的构建日志赫然写着：</p><figure class="highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs lua">npm <span class="hljs-built_in">error</span> code ENOENT<br>npm <span class="hljs-built_in">error</span> <span class="hljs-built_in">path</span> /opt/buildhome/repo/<span class="hljs-built_in">package</span>.json<br>npm <span class="hljs-built_in">error</span> enoent Could <span class="hljs-keyword">not</span> <span class="hljs-built_in">read</span> <span class="hljs-built_in">package</span>.json<br></code></pre></td></tr></table></figure><p>嗯？我的仓库里明明有 <code>package.json</code> 啊。</p><p>回头一看 GitHub 仓库，我明白了，</p><h3 id="根因：推送错了内容"><a class="heading-anchor" href="#根因：推送错了内容" aria-hidden="true">#</a>根因：推送错了内容</h3><p>我的 GitHub 仓库里只有这些文件：</p><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs nix">index.html<br><span class="hljs-number">2026</span><span class="hljs-operator">/</span><span class="hljs-number">07</span><span class="hljs-operator">/</span><span class="hljs-number">13</span><span class="hljs-operator">/</span>hello-world<span class="hljs-symbol">/</span><br>archives<span class="hljs-symbol">/</span><br>css<span class="hljs-symbol">/</span><br>fancybox<span class="hljs-symbol">/</span><br>js<span class="hljs-operator">/</span><br></code></pre></td></tr></table></figure><p>全是 <code>hexo generate</code> 生成的<strong>静态文件</strong>，没有 <code>package.json</code>，没有 <code>_config.yml</code>，没有 <code>source/</code> 文件夹。</p><p>换句话说，我之前一直用 <code>hexo deploy</code> 把编译后的 <code>public/</code> 目录推到了仓库里。这在 GitHub Pages 的流程下是正常的，它只需要静态文件。但 <strong>Cloudflare Pages 需要的是源代码</strong>，因为它要在服务端执行 <code>npm install</code> 和 <code>npm run build</code>。</p><h3 id="修复：推送正确的源代码"><a class="heading-anchor" href="#修复：推送正确的源代码" aria-hidden="true">#</a>修复：推送正确的源代码</h3><p>解决方案很简单：把 Hexo 的源代码推上去。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 在博客根目录执行</span><br>git init<br>git add .<br>git commit -m <span class="hljs-string">&quot;推送 Hexo 源码到 Cloudflare Pages&quot;</span><br>git remote add origin https://github.com/你的用户名/你的仓库名.git<br>git push -u origin main --force<br></code></pre></td></tr></table></figure><p>注意 <code>.gitignore</code> 要确保忽略了 <code>node_modules/</code> 和 <code>public/</code>：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs text">node_modules/<br>public/<br>db.json<br>.deploy*/<br></code></pre></td></tr></table></figure><p>这样推送上去的就是干净的源代码，Cloudflare 拉取后会自动安装依赖并构建。</p><hr><h2 id="第三步：又踩一坑，分支名称"><a class="heading-anchor" href="#第三步：又踩一坑，分支名称" aria-hidden="true">#</a>第三步：又踩一坑，分支名称</h2><p>推送成功后，我兴冲冲地点了 <strong>Retry deployment</strong>，结果……还是同样的错误。</p><p>再次查看构建日志，发现 Cloudflare 拉取的依然是旧 commit：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs text"> * branch 8eb197c... -&gt; FETCH_HEAD<br>HEAD is now at 8eb197c Site updated: 2026-07-13 18:25:29<br></code></pre></td></tr></table></figure><p>奇怪，明明推送成功了，为什么还拉取旧代码？</p><p>原因在于 <strong>Cloudflare Pages 配置的 Production branch 还是 <code>master</code></strong>，而我的仓库默认分支已经改成了 <code>main</code>（且 <code>master</code> 已经不存在了）。Cloudflare 找不到 <code>master</code> 分支，就用了一个缓存中的旧提交。</p><p>解决方案：在 Cloudflare Pages 项目设置中，把 <strong>Production branch</strong> 从 <code>master</code> 改为 <code>main</code>，然后再次重试部署。</p><p>这次终于成功了！</p><hr><h2 id="最终效果"><a class="heading-anchor" href="#最终效果" aria-hidden="true">#</a>最终效果</h2><p>博客成功部署到了 <code>twilightrain.pages.dev</code>，构建和部署都自动完成。以后更新博客只需要：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs bash">hexo new post <span class="hljs-string">&quot;我的新文章&quot;</span><br><span class="hljs-comment"># 写文章...</span><br>git add .<br>git commit -m <span class="hljs-string">&quot;新文章&quot;</span><br>git push<br></code></pre></td></tr></table></figure><p>Cloudflare Pages 会自动检测到 push 事件，拉取代码、安装依赖、构建、部署，整个流程全自动。</p><hr><h2 id="总结几个要点"><a class="heading-anchor" href="#总结几个要点" aria-hidden="true">#</a>总结几个要点</h2><ol><li><strong>GitHub Pages 推静态文件，Cloudflare Pages 推源代码</strong>，这两种部署方式对仓库内容的要求完全不同，搞清楚区别才能避免踩坑。</li><li><strong>Production branch 要匹配</strong>，Cloudflare Pages 配置的部署分支必须和 GitHub 仓库的默认分支一致。</li><li><strong>Cloudflare Pages 的构建命令记得用 <code>npm run build</code></strong>，而不是 <code>hexo generate</code>。确保 <code>package.json</code> 的 <code>scripts</code> 中有 <code>&quot;build&quot;: &quot;hexo generate&quot;</code>。</li></ol><p>希望这篇文章能帮到同样想迁移到 Cloudflare Pages 的朋友。如果你也遇到了类似的报错，对照上面的步骤检查一下，大概率能解决问题。</p>]]>
    </content>
    <id>https://twilightrain.com/2026/07/21/hexo-to-cloudflare-pages/</id>
    <link href="https://twilightrain.com/2026/07/21/hexo-to-cloudflare-pages/"/>
    <published>2026-07-21T06:45:00.000Z</published>
    <summary>动机与建项目，以及两次部署踩坑。</summary>
    <title>Hexo博客从GHP到CFP迁移记录</title>
    <updated>2026-08-26T05:06:32.651Z</updated>
  </entry>
</feed>
