Skip to content

fix(drivers/lanzou): support new /fn download page structure - #147

Open
Wudarensheng wants to merge 2 commits into
OpenListTeam:mainfrom
Wudarensheng:fix/lanzou-driver
Open

Wudarensheng wants to merge 2 commits into
OpenListTeam:mainfrom
Wudarensheng:fix/lanzou-driver

Conversation

@Wudarensheng

@Wudarensheng Wudarensheng commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

fix(drivers/lanzou): 适配蓝奏云 2026-10 新版 /fn 下载页

Branch: fix/lanzou-driver | Commit: 4cc1e2f | Base: main (82bd05d) | Diff: 4 files, +352 / −26

Summary / 摘要

蓝奏云在 2026-10 改版了单文件分享页:下载参数被移进 /fn?<token> 内页,下载接口从相对路径
'/ajaxm.php?file=N' 变成绝对地址 https://apifile.woozooo.com/ajaxfile.php?file=N,并且必须
携带签名参数(页面里的 wp_sign / ajaxdata → action=downprocess、websign=2、kd=1、ves=1)。

旧正则在改版页面上不再命中。更麻烦的是它不会直接报错:宽松正则会从 apifile 绝对地址里「蒙对」
file ID,然后 POST 到错误的主机、带着缺字段的表单,最终表现成 fetch failed。结果是 url(公开
分享)模式下所有单文件分享都取不到直链。

用户可感知的行为变化 / User-visible changes

  • url(公开分享)模式:单文件分享恢复可用,能正常拿到文件名、大小、时间和下载直链。
  • cookie / account 模式不受影响(走 doupload.php 链路,本次未改动)。
  • 带提取码的分享分支行为不变(上游 Go 版同样未改动该分支)。

重要实现变化 / Implementation changes

  • 新增 parseFnPage():从 /fn 内页提取 apifile 绝对地址与签名表单;未命中改版结构时返回 null。
  • getFilesByShareUrl() 的无密码分支改为三级解析:旧版严格正则 → parseFnPage()(原样 POST 到
    绝对地址)→ 原有宽松兜底。旧版流程保持不变。
  • 新增私有 postShareAjax(),统一「POST → JSON 解析 → zt 校验」。
  • removeJSComment() 不再把 https:// 里的 // 当作行注释。改版页面大量使用绝对地址,原实现会
    把 src="https://..." 从 // 处截断,导致连 iframe 都取不到。
  • iframe src 已是绝对地址时直接使用,不再拼 shareBaseDomain(原实现会拼成
    https://hosthttps://host/fn?...)。
  • 测试:新增 src/backend/drivers/lanzou/util.test.ts(6 例);修正 scripts/test-lanzou.mts 中
    一直失效的 mock(它只匹配 ajaxm.php,而驱动一直 POST 的是 ajaxfile.php,所以那条用例从未真正
    跑过),并补一条改版结构的驱动级用例。

兼容性 / Compatibility

  • 无破坏性变更:旧版页面结构仍走原分支,行为不变。

  • 无配置项、存储格式、迁移或公开 API 变化。

  • 与上游 Go 实现 OpenListTeam/OpenList@e1d88b0(#3157)语义对齐。

  • This PR has breaking changes.
    / 此 PR 包含破坏性变更。

  • This PR changes public API, config, storage format, or migration behavior.
    / 此 PR 修改了公开 API、配置、存储格式或迁移行为。

  • This PR requires corresponding changes in related repositories.
    / 此 PR 需要关联仓库同步修改。

Related repository PRs / 关联仓库 PR:

  • OpenList: 无需。同源修复已由上游 e1d88b0(#3157)合入,本 PR 是 Worker 侧移植。
  • OpenList-Docs: 无需。未新增配置项,驱动行为只是恢复可用。

Related Issues / 关联 Issue

Relates to OpenListTeam/OpenList#3157

Testing / 测试

  • go test ./... —— 不适用:本仓库是 TypeScript(Hono + Workers),Go 实现在上游仓库。
  • Manual test / 手动测试

自动化

npx tsx --test src/backend/drivers/lanzou/util.test.ts   # 6 pass / 0 fail
npx tsx scripts/test-lanzou.mts                          # 5 pass / 0 fail
npx tsc -p tsconfig.json --noEmit                        # lanzou 目录 0 报错
npm run build:edge                                       # ✓ Edge build complete

说明:

  • tsc 全量仍有 8 处既有报错(src/backend/drivers/quark_uc_tv/driver.test.ts 6 处、
    src/backend/internal/model/db_cipher.test.ts 2 处),与本 PR 无关,未一并处理。
  • npm run build 的前端拉取步骤在本机失败(git clone 触发 schannel
    CRYPT_E_NO_REVOCATION_CHECK,环境网络问题),因此只跑了 build:edge(TS 打包步骤)验证。

实机测试(生产环境,2026-10-22,本机直连)

分享 文件 结果
pan.lanzoui.com/b158157 → iYROC0ey8sj 追书神器3.138正式版换源.apk(21.4 M) 解析出 apifile…file=2425169;直链 Range 请求 HTTP 206,首字节 PK
pan.lanzoui.com/b174576 → 134136697 zip(306 B) 同上,HTTP 206

修复前后对照(同一张真实 /fn 页面):

修复前 修复后
旧版严格正则 不命中 不命中(按设计)
宽松正则 从绝对地址蒙对 file=2425169 不再使用该分支
实际 POST pan.lanzoui.com/ajaxfile.php?file=2425169 → HTTP 407(空 body) apifile.woozooo.com/ajaxfile.php?file=2425169 → HTTP 200、zt=1
表单 缺 websign,kd=0 完整签名,kd=1
结果 fetch failed,无直链 拿到 CDN 直链并可下载

Checklist / 检查清单

  • I have read CONTRIBUTING.
    / 我已阅读 CONTRIBUTING。
  • I confirm this contribution follows the repository license, contribution policy, and code of conduct.
    / 我确认此贡献符合仓库许可证、贡献规范和行为准则。(待作者确认)
  • I have formatted the changed code with gofmt, go fmt, or prettier where applicable.
    / 我已按适用情况使用 gofmt、go fmt 或 prettier 格式化变更代码。
  • I have requested review from relevant maintainers or code owners where applicable.
    / 我已在适用情况下请求相关维护者或代码所有者审查。(待作者操作)

关于格式化的说明:util.ts / help.ts 在基线上本就不符合仓库 prettier 配置——.prettierrc
只设了 semi: false,printWidth 走默认 80,而既有代码按约 100 列书写。对整文件执行
prettier --write 会产生数百行与本 PR 无关的重排,因此新增代码只沿用文件既有风格,未整体重排;
新增的 util.test.ts 通过 prettier --check。若维护者希望顺带统一格式,建议单独开一个
style: PR。CI 目前没有格式校验。

AI Disclosure / AI 使用声明

  • This PR includes AI-assisted content.
    / 此 PR 包含 AI 辅助内容。

Tools used / 使用工具:

  • ChatGPT
  • Codex
  • GitHub Copilot
  • Claude
  • Gemini
  • Other (please specify) / 其他(请注明): WorkBuddy AI

Usage scope / 使用范围:

  • Code generation / 代码生成

  • Refactoring / 重构

  • Documentation / 文档

  • Tests / 测试

  • Translation / 翻译

  • Review assistance / 审查辅助

  • I have reviewed and validated all AI-assisted content included in this PR.
    / 我已审核并验证此 PR 中的所有 AI 辅助内容。

  • I have ensured that all AI-assisted commits include Co-Authored-By attribution.
    / 我已确保所有 AI 辅助提交都包含 Co-Authored-By 归属信息。

  • I can reproduce all AI-assisted content included in this PR without any AI tools.
    / 我可以在没有任何 AI 工具的情况下重现此 PR 中包含的所有 AI 辅助内容。

Lanzou moved the share download params into the /fn?<token> inner page, and
the download API is now an absolute URL
(https://apifile.woozooo.com/ajaxfile.php?file=N) that requires sign params
(wp_sign / ajaxdata, action=downprocess, websign=2, kd=1, ves=1).

The old relative-path regex ('/ajaxm.php?file=N') no longer matches, so
parsing silently fell back to the wrong host with an incomplete form.

- add parseFnPage() to extract the apifile URL and sign form, and use it
  when the legacy regex does not match (legacy flow kept untouched)
- keep the previous lenient extraction as a last-resort fallback
- fix removeJSComment() treating the '//' in https:// as a line comment,
  which truncated absolute URLs on the share page
- use an absolute iframe src as-is instead of prefixing the share domain

Adds unit tests for the new parser and the driver-level flow, and fixes the
stale mock in scripts/test-lanzou.mts (it matched ajaxm.php while the driver
posts to ajaxfile.php, so that case never actually ran).

Ports OpenListTeam/OpenList@e1d88b0 (fix(drivers/lanzou): support new /fn
download page structure, #3157).
pikachuren

This comment was marked as outdated.

pikachuren
pikachuren previously approved these changes Oct 26, 2026

@pikachuren pikachuren left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

代码评审总结

这是一个针对蓝奏云 2026-10 改版的精准适配 PR,解决了新版 /fn 内页结构变化导致的下载链接解析失败问题。

✅ 优点

  1. 问题定位准确: 清楚识别了改版后的两个关键变化:
    • 下载接口从相对路径改为 apifile 绝对地址
    • 签名参数从 data 对象改为独立变量(wp_sign/ajaxdata)
  2. 向后兼容: 同时支持新旧两种内页结构,不破坏已有功能
  3. 测试覆盖充分: 新增专门测试用例验证改版结构,覆盖绝对地址解析、签名透传、kd参数等关键逻辑
  4. 注释清晰: help.ts 中对 https:// 不当作注释的处理有详细说明
  5. 测试用例设计细致: mock 返回结构贴近真实改版页面,断言覆盖 URL、签名字段、参数值

📝 代码质量

help.ts 变更:

  • ✅ JS 注释解析修复合理: !result.endsWith(":") 避免将 https:// 误判为注释
  • ✅ 注释说明清晰,指出改版后大量使用绝对地址的背景

util.test.ts 新增测试:

  • ✅ mock fetch 设计完善: 覆盖 /fn 内页、POST 下载接口、302 跳转、最终 CDN 链接
  • ✅ 断言粒度细: 验证 URL 前缀、sign/signs/kd 参数值、最终 raw_url
  • ✅ 测试用例命名清晰: "适配改版 /fn 内页(apifile 绝对地址)"

driver.test.ts 变更:

  • ✅ 向后兼容: ajaxm.php 和 ajaxfile.php 双重判断,支持新旧接口
  • ✅ 测试数据真实: mockIframe 结构符合实际改版页面(domain1/wp_sign/ajaxdata)

💡 改进建议

1. 增加旧版结构回归测试

当前新增测试只覆盖改版结构,建议确认旧版测试仍然通过:

test("Lanzou Driver get() 保持旧版 /fn 内页兼容性", async () => {
  // 使用 LEGACY_FN_PAGE 结构(相对路径 /ajaxm.php + data 对象)
  // 验证 $.ajax 解析和参数传递仍正常工作
})

2. 错误处理增强

如果遇到既不是新版也不是旧版的意外结构,建议增加友好错误提示:

if (!domain1 && !ajaxUrl) {
  throw new Error(
    "蓝奏云页面结构无法识别,可能需要更新驱动适配新版本"
  )
}

3. 正则表达式健壮性

https:// 判断逻辑依赖 endsWith(":")。如果未来出现其他 URL scheme(如 http://),建议改为更通用的正则:

// 改进: 检查 // 前是否为 URL scheme
else if (nextChar === "/" && !/[a-z]+:$/i.test(result)) {
  // 这不是 http:// 或 https://,按行注释处理
  inSingleLineComment = true
}

4. 测试覆盖边界场景

建议增加以下测试:

test("Lanzou get() 处理混合结构(新旧字段共存)", async () => {
  // 如果页面同时包含旧版和新版字段,验证优先级
})

test("Lanzou get() 处理签名字段缺失", async () => {
  // wp_sign 或 ajaxdata 为空时的降级行为
})

🔍 潜在风险评估

  1. API 稳定性: 蓝奏云已进行一次改版,未来可能再次变更。建议在生产环境监控解析失败率
  2. 向后兼容测试: 需确认旧版结构的现有测试仍然通过,避免回归
  3. 特殊字符处理: https:// 判断依赖字符串尾部检查,如果 URL 前有其他字符可能误判

🎯 总体评价

这是一个精准且务实的适配 PR。问题定位准确,解决方案简洁,测试覆盖到位。同时保持向后兼容,不破坏现有功能。注释清晰,测试用例设计贴近真实场景。

评分: 9/10

建议:

  • ✅ 可以立即合并
  • 🧪 建议补充旧版结构回归测试,确认向后兼容
  • 📋 建议在文档中记录改版适配历史,便于未来维护
  • 🔍 生产环境监控解析失败率,及时发现新的结构变化

优秀的快速适配工作! 👍

Review follow-ups for the /fn page structure fix:

- document parseFnPage()'s return contract: null means "not a 2026-10 /fn
  page", not an error, and the caller must fall back to the legacy parse
- report an actionable error when the download page matches neither the
  legacy nor the /fn structure (previously "not find file id", which reads
  like a parse bug rather than "Lanzou changed again")
- tests: parseFnPage returns null when wp_sign/ajaxdata are missing, legacy
  markers take priority on a page carrying both structures, unrecognized
  page yields the new error, and a driver-level legacy regression case so
  backward compatibility is asserted explicitly

No behavior change for legacy pages.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants