我给浏览器写了个用户脚本管理器,叫「狂暴鲨」:从第一行代码到上架应用商店

开发 · 10 分钟阅读· 17 次阅读

把一块 Windows 时代留下来的移动硬盘插到 Mac 上想拷电影,访达会告诉你 NTFS 只读;把一个油猴脚本发给朋友,对方会先问你"Tampermonkey 是什么"。这两个问题我解决了一个,这次轮到另一个:我把天天在用的用户脚本这件事,从零写成了一个完整的浏览器扩展——狂暴鲨 ScriptDock,六天时间版本号从 1.5.0 跑到 1.9.29,现在正在上架各家的应用商店。

狂暴鲨 ScriptDock 封面

它不只是一个"油猴替代品"。一个扩展里塞了四件事:

  • 用户脚本管理器:兼容 Tampermonkey 元数据格式,老脚本拖进来就能跑
  • 脚本市场:3400+ 个脚本在线安装,支持审核、付费与作者分成
  • 资源嗅探 + HLS 合并下载:网页里的音视频自动翻出来,m3u8 直接合并成单个 MP4
  • 可视化元素屏蔽:点一下广告就消失,规则可跨设备同步

这篇文章把开发过程、功能设计和踩的坑一次讲完。下载地址在文末。

先看成品

浏览器右上角的弹窗,当前页面有几个脚本生效、嗅探到几个资源,一眼就能看到:

扩展弹窗

管理面板承担所有重活:脚本列表、批量启停、代码查看、云同步、运行日志:

管理面板

配套的脚本市场站点,3400+ 个脚本,热门、分类、评分、版本历史一应俱全:

脚本市场

为什么自己写一个

市面上的方案不是不好,是各有各的别扭:

  • 油猴Tampermonkey 很强,但闭源,而且没有配套的市场和账号体系
  • 想给爸妈装个"视频站去广告",得教会他们三步:装扩展、找脚本、粘贴安装
  • 我自己要的资源嗅探、m3u8 下载、元素屏蔽,散落在三个扩展里,互相打架
  • 换个浏览器,脚本列表又得重头来

所以目标从一开始就很明确:一个扩展 + 一个站点。扩展管本地运行,站点管分发和账号,两边共享同一套身份。顺带把"发给别人"这件事的成本压到零——发一个链接就行。

架构:三层隔离,互不信任

MV3(Manifest V3)把扩展开发的游戏规则改了一遍,最终形态长这样:

扩展架构

三个关键决策:

1. 主世界与沙箱双轨运行。 不需要特权 API 的脚本直接注入页面主世界,快,还能拿到 unsafeWindow;声明了 GM_xmlhttpRequest 这类权限的脚本跑在扩展沙箱里,页面摸不到它的内部状态。按脚本声明的 @grant 自动路由。

2. 嗅探钩子必须进主世界。 网络层要看到 fetch/XHR 的真实请求,只能钩在页面自己的世界里。但钩子代码要经内容脚本模板转义后逐字节一致地 eval 进去——这条约束后面踩过坑。

3. Service Worker 随时会死。 MV3 的后台不常驻,任何内存状态都是借来的。所有状态进 chrome.storage,所有逻辑做成消息驱动:醒了干活,干完就睡。这条纪律贯穿了整个开发过程。

资源嗅探:三路互补

页面里的音视频和压缩包,从三个方向抓,覆盖面互为补集:

  • DOM 扫描:<video>、<audio>、<source>、a[href],含 data-src 懒加载属性
  • performance 资源条目:JS 动态加载、fetch 拉过的,都在浏览器自己的账本上
  • MutationObserver:后插入的播放器, SPA 页面和无限滚动的信息流靠它

文件大小优先用 performance 的传输量,拿不准就发一个 HEAD 探真实 Content-Length。名字里没有扩展名的,按 Content-Type 自动补 .mp4、.zip。

资源嗅探面板

HLS 合并下载:最硬的一块骨头

嗅探只能告诉你"这里有资源",但对 m3u8 这类流媒体,用户要的是一个能拖进度条的 MP4 文件。这就要把几百个 TS 切片拉下来、解密、重新封装:

HLS 合并下载链路

整个链路跑在 offscreen 文档里(Service Worker 不能做重活),用 mux.js 转封装之后,还要手写一遍 MP4 的 box 结构——因为直接拼接 fMP4 的 mvhd 时长字段是 0xFFFFFFFF,macOS 的 AVFoundation 会把它算成天文数字,进度条直接废掉。

手写 box 的三个坑,每一个都值一个通宵:

  1. stts/stss/stsc/stsz/stco 全是 FullBox,版本和标志位那 8 字节少写一个,样本表全错位
  2. stsc 的 entry 是 12 字节不是 8 字节,文档没写错,是我看错了
  3. 样本必须包进 mdat,漏 8 字节头,播放器打开就是"文件已损坏"

验收标准也很硬:合并出来的 MP4 要过 macOS 的 mdimport 索引,QuickTime 能拖进度条,迅雷接手继续下也没问题。

顺带说下取流的姿势:CDN 回 Access-Control-Allow-Origin: * 的时候,带 Cookie 的请求必被 CORS 拒掉,所以取流必须做 include→omit 降级,而不是硬重试。切片识别也不能看扩展名——TS 文件经常伪装成 .jpg/.png,所有非注释行都是切片,再用黑名单挡掉密钥、图片、字幕和字体。

元素屏蔽:一条规则,三重执行

这是我最喜欢也最折腾的功能。点扩展上的"屏蔽元素",进入选取模式,点一下广告:

可视化元素屏蔽

但"隐藏一个元素"背后是三重执行,缺一不可:

元素屏蔽原理

最痛的教训是 iframe:一开始给 iframe 加了 pointer-events:none 想让点击穿透,结果冻结后 elementFromPoint 直接漏到背后的容器——用户想屏蔽一个小广告位,一点,整页都没了。改成每个 frame 自己跑选取器之后才稳定。

子框架里还有个细节:没有工具条,点一下即落库(zapper 语义,带撤销),空闲 25 秒自动退出,不打扰。

云同步:删掉的东西不能复活

多设备同步最容易翻车的不是上传下载,是删除。最早的版本只做并集,结果"删了再同步又回来",往复循环。现在的方案:

  • 同步是双向合并:云端多的落地、本机多的上传、冲突以本机为准
  • 删除靠墓碑:删脚本时记一条删除标记(保留 60 天),所有设备跟着删,不会复活
  • 换账号且本机有脚本时跳过自动同步,防止把 A 账号的脚本带进 B 账号
  • 服务端限流 1 分钟 12 次,触发后扩展退避 65 秒

踩坑实录(节选)

rsync 把全站 API 干挂了。 一次例行部署,rsync -a 把本机的 501:games 属主带到了服务器,config.php 变成 0640,PHP 进程(www 用户)读不了配置,MySQL 连接全挂,全站 500。此后部署命令永久固定为 rsync --no-owner --no-group。

3433 个脚本源码大面积错位。 站点最初批量导入脚本时,源码文件按下载到货顺序落盘,和脚本 id 完全对不上——用户点开"AC-baidu 重定向优化",装上的是"超星学习通助手"。修复方案是按 sourceUrl 从上游重新拉取全部 3433 个源码(6 分片并行,0 失败),再按文件实际大小与 @version 双向核对回写。

Firefox 没有 offscreen。 Chrome/Edge 的合并下载依赖 offscreen 文档做重活,Firefox 压根没有这个 API。老老实实在文档里写清楚:Firefox 端支持嗅探和直接下载,合并下载暂不可用——比假装支持然后交付一个坏文件强。

改一行钩子代码,全量回归。 sniffer-hook.js 改动后必须重建内联 HOOK_SRC,模板转义后与源文件逐字节一致——差一个反斜杠,主世界钩子就静默失效,而且没有任何报错。

上架应用商店

功能稳定后开始和四家商店的审核流程打交道,画风完全不同:

商店 关键要求 现状
Edge Add-ons 免费、审核较快、接受 MV3 已提交(商店页面)
Chrome Web Store 注册费 $5,<all_urls> + webRequest 触发深度人工审查,权限逐条写用途 提交中
Firefox AMO 检查 manifest 兼容性,background.scripts 与 service_worker 写法不同 提交中
Safari 必须包一层 Xcode App,走 Apple Developer Program($99/年) 已提供本地构建方案

共同要准备的东西:隐私政策 URL、128px 图标、商店宣传图(1400×560 / 440×280 / 300×300 一套)、每条权限的用途说明。<all_urls> 是绕不开的——脚本管理器的天职就是全站注入,所以审核注定走人工通道,权限说明写得越具体越好。

安全设计上,审核规则跟着产品走:读取或外发 Cookie 的脚本重点人工复核;包含广告、返佣跳转的脚本必须 @antifeature 声明,瞒报直接下架;代码混淆压缩成一行的不予过审。这个行业的信任门槛就在权限上——用户装的不是脚本,是一段跑在自己浏览器里的代码。

下载

如果你是脚本作者:元数据格式兼容 Tampermonkey,老脚本零改动发布;支持免费或按月/年/永久定价,售出有分成。

六天,29 个版本,四端交付。接下来是把 Chrome 和 Firefox 的审核流程走完,以及把文档站和常见问题补齐——如果您装上之后遇到任何问题,欢迎在站点反馈页留言。

评论 (0)

还没有评论,来说两句吧~

发表评论

验证码

提交后需站长审核,通过后才会显示。