CurseForge 附加包更新(技术路线)
本文描述 sfmc packs / addon 的第三方 BP/RP 远程更新完整设计与实现细节(与 SFMC 业务模块 mod / GitHub registry 无关)。
相关代码:
通用收件箱安装见 附加包。
1. 目标与边界
目标:为已装进世界目录的第三方 addon(如 OrdinaryWorld 的 Slash Blade BP)找到 CurseForge 更新源,并在启动或手动命令时检查/应用更新。
边界:
- 只管理世界侧
behavior_packs/resource_packs,不管理 SFMCmodules/packages业务模块。 - 多来源预留
SourceProvider抽象;当前唯一实现是 CurseForge Bedrock。 - 下载物仍走既有
installPackDirectory/enableInstalledPack(DRY,无第二套拷贝逻辑)。
验收样例:本地「Slash Blade v4」BP ↔ CF 项目 slug slash-blade-addon。
2. 总览流程
3. 配置与播种(DRY)
3.1 文件位置
apiKey 也可用环境变量 CURSEFORGE_API_KEY 覆盖(避免把 key 写进可分享配置)。
3.2 启动时如何生成配置
不再从 configs-default 拷贝。createServices() / ensurePackUpdateConfigFile() 在文件缺失时写入代码内 DEFAULTS,并附带 $schema(见 @sfmc-bds/sdk/schemas/pack_update.schema.json)。
权威实现:sfmc/src/pack-update/config.ts 的 ensurePackUpdateConfigFile。
3.3 pack-update.json 关键字段
3.4 绑定文件示例
关闭某包自动更新:将该条 enabled 设为 false,或 packs unbind <id>。
packs list 文案:开启 src=cf:slug,关闭 src=cf:slug:off(与 sources 的开/关一致,不再丢成单独的 src=off)。
检查/应用:仅在成功写入世界目录后才更新 lastAppliedFileId。同一 fileId 已应用则跳过下载;版本未更高但尚未 apply 过该文件时仍会覆盖安装(同步 CF 内容)。
4. CurseForge API 鉴权(易踩坑)
CurseForge 存在两套完全不同的「API Token」,不可混用。
本仓库请求官方 API 时只发送 x-api-key。若 key 形如 UUID,403 错误信息会提示换 Studios Key。
JSON 中 $ 无需加倍;仅当把 key 放进 shell / docker-compose 环境变量 时,$ 可能被展开,需按运行环境转义(常见写法是 $$)。
5. 搜索端点与镜像回退
实测(Studios Key):
实现策略(providers/curseforge.ts):
- 先打官方
baseUrl+/v1/mods/search。 - 若返回 403 → 静默改打
searchBaseUrl(默认https://api.curse.tools/v1/cf)的/mods/search。 - getMod / files / download-url / CDN 下载仍走官方 + Studios Key(下载也带
x-api-key,以应对 CDN 鉴权收紧)。
6. 搜索与匹配(name + slug)
6.1 CF Bedrock 命名规律
- 项目 slug 无空格:小写 + 连字符,例如
slash-blade-addon。 - 展示名可有空格/本地化:
Slash Blade Addon。 - 本地文件夹常带标签与版本:
[BA] [玩法] …Slash Blade v4 BP。
因此「只把带空格的清洗名丢进 searchFilter、只比 name」成功率偏低。
6.2 查询词派生 buildSearchQueries
探测与 packs search 共用。安装后探测固定两源:manifest.header.name(header)与安装文件夹名(folder),分别派生后再轮询合并,避免某一源占满配额。
每个原始字符串先经 collectQuerySeeds 做多级清洗,再派生 slug。
只要合并结果里已有拉丁字母候选,就丢弃纯中文查询(CF Bedrock 几乎不靠中文命中)。
- 去
§、方括号标签[BP]/[BA]/[玩法]、扩展名 - CJK/拉丁粘连分界:
拔刀剑Slash→拔刀剑 Slash - 去整词包角色:
BP/RP/Addon/行为包… - 去版本尾巴:
v4、1.21.100 - 提取拉丁短语(CF 主路径):
Slash Blade v4 BP→ … →Slash Blade - 对每个种子再生成:
slash-blade、Slash-Blade、slash-blade-addon(大小写不敏感去重,故Slash-Blade与slash-blade只保留一条)
样例文件夹 [BP] [BA] [玩法] 拔刀剑Slash Blade v4 BP 的种子链约等于:
查询列表按「slug-addon > slug > Title-Slug > 拉丁短语 > 含 CJK」排序并截断(默认最多 14 条),避免 API 打爆同时保住核心 slug。
仅读 header 若全是中文/无英文,仍可靠文件夹名命中 CF。
6.3 综合打分 packSourceScore
对每个候选 hit,取 max(按 name 相似度, 按 slug 相似度):
安装后自动绑定要求 score >= match.nameMinScore(默认 0.6)。
样例:本地 Slash Blade v4 ↔ CF slash-blade-addon → slug 前缀规则约 0.96,会排在 Terra Blade 等弱相关结果之前。
6.4 CLI 行为
packs search <q>:多查询 + 按 score 降序;每行显示id/slug/score。packs bind <id> <projectId|slug|url>:手动绑定,不依赖探测分数。- 安装成功后的探测:分数达标后写入
packs/pack-sources.json(enabled取defaultBindingEnabled,默认关)。TTY 且askConfirmOnBind=true时先确认;非 TTY 自动写入绑定。开启自动更新请把对应条改为"enabled": true。
7. 版本策略(以 BP 为准)
权威:本地 BP header.version vs 远程归档内 BP header.version。
忽略用户曾对手改 RP 小版本号的干扰。
lastAppliedFileId 仅在成功写入世界目录后更新;不可在「仅下载比较」阶段提前写入。
配对 RP:绑定里的 pairedResourceUuid,或新 BP dependencies[].uuid,或 mcaddon 内与之对齐的 resource 包。
抬版原语(DRY):bds-tools 的 nextEnabledVersion / ensureVersionGreaterThan(写回 manifest)+ writePackHeaderVersion。策略层 sfmc/pack-update/version-policy 再导出同一套纯函数,禁止 while 追赶。
8. 检查 / 应用 / 启动钩子
8.1 CLI
8.2 BDS beforeStart 顺序
scanAndInstallInbox(收件箱)runPackUpdatesOnBdsStart(若checkOnBdsStart;若applyOnBdsStart则一并应用)ensurePacksReady(SFMC 模块聚合 BP/RP)
逐个 binding 顺序处理(startup.sequential);失败默认 failMode: continue,不阻断开服。
8.3 应用步骤摘要
- Provider
getLatestFile(projectId)→ 带进度下载到临时目录。 resolvePackRoots(解压顶层归档 + 展开嵌套.mcpack+ 发现manifest.json包根)。- 读远程 BP 版本 →
decideVersionPolicy。 installPackDirectory(..., force: true)装 BP / RP。- 按需
ensureVersionGreaterThan(内部nextEnabledVersion)抬 RP。 enableInstalledPack刷新世界 enable 列表。- 更新 binding 的
lastAppliedFileId/lastCheckedAt。
进度条:createTerminalProgress(stderr);日志 sink / REPL 写行前 pauseAllProgress、写完 resumeAllProgress(与 BDS 更新器共用,DRY)。
9. 代码结构(OCP / DIP)
新增来源(如 Modrinth)时:实现 PackSourceProvider,在配置 providers 注册,不必改核心 switch 链。
10. 排障清单
11. 相关命令速查
别名:addon ≡ packs。