【免费下载链接】Mornye-Extension
Extension Registry for Mornye Browse, submit, and download community-built extensions.
本文逆向解析 Mornye-Extension 仓库中的 Amazon Music 插件(v2.3.11),重点拆解两个最不易被察觉的核心机制:跨目录链接解析(如何把 Spotify/Deezer 链接转换为 Amazon ASIN)与 CSRF 会话刷新原理(插件如何对 Amazon Music Web API 维持一个"永不掉线"的会话)。全文以源码为据,所有结论均可在仓库中逐行验证。
🧩 先认识插件:它在仓库里的位置
Mornye-Extension 是一个面向 SpotiFLAC Mobile 的扩展注册表(Extension Registry),社区提交的扩展以.sflx压缩包形式存放于 extensions/,并通过 registry.json 记录版本与 SHA-256 摘要。
与"只交压缩包"的社区扩展不同,Amazon 插件属于附带可审查源码的内置 Provider:
| 文件/目录 | 作用 |
|---|---|
| sources/amazon/index.js | 插件主体(约 4000 行),含会话管理、链接解析、下载逻辑 |
| sources/amazon/manifest.json | 权限白名单、下载质量档位(FLAC/Opus/Dolby Digital Plus/Atmos)等元信息 |
| tests/amazon_session.test.cjs | CSRF 会话刷新的离线回归测试 |
| tests/amazon_resolution.test.cjs | 跨目录解析与 URL 校验的离线回归测试 |
| tests/amazon_region.test.cjs | 区域目录归一化与 404 ASIN 重映射测试 |
| extensions/amzn.sflx | 由 scripts/build_packages.py 构建出的可安装包 |
获取源码并运行回归测试(需要 Node.js 22+,测试全程离线、不访问真实音乐服务):
git clone https://gitcode.com/gh_mirrors/sp/Mornye-Extension cd Mornye-Extension node --test tests/*.test.cjs🔐 CSRF 会话刷新原理
为什么必须用 POST 拿 token
Amazon Music 的 Web API(skill.music.a2z.com)要求每个请求携带一组设备凭据与 CSRF 令牌。插件的会话初始化函数 initSession 向以下地址发起POST请求:
https://music.amazon.com/config.json?skipToken=false&clientApplication=skyfire这里有个关键的逆向发现,源码注释写得很直白:
// POST issues CSRF credentials; GET returns configuration without a token.也就是说GET 请求虽然返回 HTTP 200,但响应中不再包含 CSRF token;只有 POST 才能拿到csrf.token / ts / rnd三件套,连同deviceId、sessionId一起存入会话对象 _session。
x-amzn-csrf 请求头的构造
每次调用 API 前,buildHeaders 都会拼装一套约 25 个x-amzn-*请求头,其中两个是 JSON 字符串:
"interface": "CSRFInterface.v1_0.CSRFHeaderElement", "token": "...", "timestamp": "...", "rndNonce": "..."分别对应x-amzn-csrf(防跨站伪造)与x-amzn-authentication(客户端令牌)。rndNonce在缺失时会本地随机生成,避免请求因格式校验被拒。
失败即刷新:一次性的 refresh 与重试
initSession有一个兜底分支:若config.json拉取失败,就本地随机生成 deviceId/sessionId(源码注释坦承 "may get 429")。真正让会话"自愈"的是 refreshSession:
- 调用失败(如 403/429)时,把
_session.initialized置为false并重新走一遍initSession; - 各 API 封装(如 callShowHome)在首次失败后会
refreshSession()+ 短退避,然后只重试一次。
tests/amazon_session.test.cjs 用vm沙箱加载真实源码验证了这条链路:搜索请求只 POST 一次 config.json 就复用会话;当模拟服务端以 403 拒绝首个搜索时,插件会重新 POST 刷新,第二次请求携带了token-2,断言序列为['token-1', 'token-2']。
🔗 跨目录链接解析原理
当 SpotiFLAC 拿到一首歌的 Spotify ID、Deezer ID 或 ISRC 时,插件需要在 Amazon 目录里"找到同一首歌"。入口函数是 resolveAmazonURL,它按优先级组织了一条回退链:
| 顺序 | 输入 | 手段 |
|---|---|---|
| 1 | Deezer ID(或用 ISRC 经 Deezer API 换得) | 抓取 Songlink 页面提取 amazonMusic 链接 |
| 2 | Spotify ID(必须形如 22 位字母数字,否则直接忽略) | 同上 |
| 3 | ISRC | 抓取 Songstats 页面,递归扫描 JSON-LD 的sameAs字段 |
| 4 | 全部落空 | 交给调用方回退到精确的 Amazon 目录搜索 |
从 Songlink 页面里"抠"出链接
callSongLinkPage 抓取https://song.link/{编码后的源URL}后,并不是正则匹配 HTML,而是 extractSongLinkNextDataJSON 直接定位 Next.js 注入的<script id="__NEXT_DATA__">,再解析props.pageProps.pageData.sections[].links[],挑出platform === "amazonMusic"的条目。数据来自结构化 JSON,比解析可见文本稳定得多。
严格校验:拒绝"看起来像"的链接
拿到候选 URL 后必须通过 acceptResolvedAmazonTrackURL → extractResolvedTrackASIN 的双重把关:
- 域名必须命中 12 个官方
music.amazon.*区域的白名单正则(L165),测试中music.amazon.com.evil.test这类仿冒域名会被拒; - 只接受明确的 track 路径或携带
trackAsin参数的专辑深链,/albums/{id}、/artists/{id}一律不接受——因为外部解析器有时返回一个"ID 恰好长得像 ASIN"的艺人页; - ASIN 必须匹配
^B[0-9A-Z]{9}$(ASIN_REGEX)。
源码注释还记录了一个真实坑:Apple Music 等 Provider 可能把外部数字 ID 塞进spotify_id字段,若不先用isLikelySpotifyId过滤,会拼出无效 Spotify 链接并"污染"整条 Spotify 链路(L3281-L3292)。
区域目录归一化与 404 时的 ASIN 重映射
Amazon Music 在不同国家站点(.com、.co.jp、.de……)使用互不相同的 ASIN。createAmazonContext 的策略是:用户贴来任何区域链接时,把请求上下文统一归一到标准目录music.amazon.com,但原区域 ID 先保留,等 Amazon 返回对应的标准 ID 后再"换票"。
下载时的兜底逻辑在 download 的 requestMedia:
- 先按原 ASIN 请求下载;
- 若收到目录级 404(
TRACK_NOT_FOUND等),调用 getCanonicalCatalogTrack 做一次标准目录直查; - 只有当返回的元数据携带一个不同的、经过校验的新 ASIN 时,才用它重试一次;
- 新 ASIN 仍 404、或鉴权失败(401/429)等情况,直接保留原始错误,绝不进入重试循环。
tests/amazon_region.test.cjs 对上述行为做了矩阵式覆盖:12 个区域域名全部归一、trackAsin参数在归一化中不丢失、"无新 ID 不重试"、"新 ID 仍 404 不循环"、"会话类错误不得触发 ASIN 重映射"等十余条断言。
✅ 小结
- CSRF 会话刷新:POST 换取 token → JSON 请求头随每请求下发 → 失败后
refreshSession()重建会话并只重试一次,兜底随机会话保证"永不卡死"; - 跨目录链接解析:Deezer → Spotify → ISRC/Songstats 三级回退,结构化提取
__NEXT_DATA__,配合域名白名单 + track 级 ASIN 校验,杜绝仿冒链接与字段污染; - 区域目录:请求统一走标准目录,下载 404 时最多一次"直查 + 换 ID 重试",保证失败可解释、不无限循环。
整套机制的每个关键分支都有对应的离线回归测试守护(见 tests/),这也是该仓库"源码 + 测试 + 构建产物"三位一体提交方式(见 README.md)的价值所在。
【免费下载链接】Mornye-Extension
Extension Registry for Mornye Browse, submit, and download community-built extensions.
相关推荐
TeamPass后台任务管理:自动化维护和清理操作手册
TeamPass后台任务管理:自动化维护和清理操作手册 TeamPass作为一款协作密码管理器,其后台任务管理功能是确保系统高效稳定运行的核心组件。本文将详细介
后端应用安全yandex-music-downloader源码剖析(一):逆向Yandex Music get-file-info接口与HMAC签名原理
yandex music downloader源码剖析(一):逆向Yandex Music get file info接口与HMAC签名原理 🎵 本文以 Py
OneMore插件双向链接确认对话框优化解析
OneMore插件双向链接确认对话框优化解析 痛点场景:打断工作流的确认提示 在使用OneNote进行知识管理时,双向链接(Bi directional Lin
桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考