# 扩展删除与在线下载(v0.30.0) > 2026-10-05 · 用户已定的范围:删除 = 卸载并清掉它的存档数据;在线扩展先只支持我们自己发布的,包含代码;放在本仓库的 GitHub 上;第三方以后再说。 > 用户 2026-10-05:「0.29、0.30 都先做掉」——这份设计边写边做,做完一起给用户试。 ## 1. 扩展有三种状态 | 状态 | 主菜单 / 商店 / 图鉴 | 后台结算 | 存档数据 | |---|---|---|---| | 已安装 · 开 | 出现 | 照常 | 有 | | 已安装 · 关 | 不出现 | 不结算 | **保留** | | 未安装(删除了,或者还没下载) | 不出现 | 不结算 | **没有**(删除时清掉了) | - 存档里:开关照旧放在 `state.extensions`;删掉的内置扩展记在 `state.extensionsRemoved`;下载来的扩展的数据放在 `state.extData[key]`,只有这个扩展自己能改。 - 不升存档版本:新字段用 `ensure…` 补。 ## 2. 删除 - 扩展 App 里每个已安装扩展的卡片下面有「删除」。点了先在卡片里问一次:「删掉以后它的数据都清空,以后可以在下面重新下载,确定吗?」→「删除」/「算了」(不用浏览器弹窗,桌面版和网页版都一样)。 - 删除时先按「关掉」的规矩收尾(番茄钟放弃这一个、自动钓鱼叫回来退鱼饵),然后清数据: | 扩展 | 清掉什么 | |---|---| | 番茄钟 | 今天的计数、累计数、正在进行的那个 | | 钓鱼 | 鱼篓里的鱼、图鉴里的鱼、背包里的鱼饵、正在钓的那条、自动钓鱼次数 | | 下载的扩展 | `state.extData[key]`,以及存档目录里 `extensions//` 的文件 | ## 3. 在线扩展 **扩展目录**:仓库里的 `extensions/registry.json`,游戏通过 GitHub 读取: ``` https://raw.githubusercontent.com/CLICGGER-TYPES/dsh-piggy/main/extensions/registry.json ``` ```json { "version": 1, "extensions": [ { "key": "pomodoro", "label": "番茄钟", "emoji": "🍅", "version": "1.0.0", "builtin": true, "description": "…" }, { "key": "blindbox", "label": "盲盒", "emoji": "🎁", "version": "1.0.0", "minGame": "0.31.0", "files": { "manifest.json": { "url": "https://github.com/CLICGGER-TYPES/dsh-piggy/releases/download/ext-blindbox-1.0.0/manifest.json", "sha256": "…" }, "server.js": { "url": "…/server.js", "sha256": "…" }, "client.js": { "url": "…/client.js", "sha256": "…" } } } ] } ``` - **内置扩展**(番茄钟、钓鱼)代码本来就在游戏里,`builtin: true`,删掉后「重新安装」不用下载,从零开始。 - **下载的扩展**:文件挂在本仓库的 GitHub Release(标签 `ext--<版本>`)。游戏只从上面这个固定地址读目录,每个文件下载后必须和目录里的 sha256 一致,否则不装。 - `minGame`:游戏版本低于它时显示「需要先更新游戏到 vX」,不让装。 - 地址固定写在游戏里,不能从页面改,所以只会装我们自己发布的。 **装在哪**:和存档放在一起的 `extensions//` 目录(DSH 里的猪和桌面版的猪各自一份)。 ## 4. 下载的扩展怎么运行 一个扩展包 = 三个文件: | 文件 | 在哪跑 | 做什么 | |---|---|---| | `manifest.json` | — | key、名称、emoji、说明、版本、`minGame`、主菜单格子(`app: { emoji, label }`) | | `server.js` | 宿主(Node,DSH 和桌面版都是) | ES 模块,`export default { init(), actions: { 名字(data, payload, api) }, view(data, api) }` | | `client.js` | 面板 | 普通脚本,调用 `window.dshPiggyExtensions.register(key, { render(ui) })` 画自己的 App 页 | - 扩展**只能改自己的数据**(`state.extData[key]`)。要动猪身上的东西,只能用游戏给的几个口子:`api.coins()`、`api.spend(n)`、`api.earn(n)`、`api.give(itemKey, n)`、`api.say(text)`、`api.now`。花钱不够会被拒绝。 - 面板按 `ext::<动作>` 把操作发给宿主,宿主找到这个扩展的 `actions[动作]` 来执行;扩展的 `view` 放进快照的 `extViews[key]` 给它的 App 页用。 - 扩展出错(抛异常、文件坏了)只影响它自己:动作返回失败,App 页显示「这个扩展出错了」,猪和其他玩法照常。 扩展可通过可选的 `api.emit(name, payload)` 和只读 `progress(data)` 报告已完成经历,宿主统一记录成就;协议、版本兼容和四扩展发布顺序见[扩展成就与公共进度事件](extension-achievements.md)。 ## 5. 界面 「扩展」App 分两段: ``` 本地扩展 ┌───────────────────────────────┐ │ 🍅 番茄钟 [ 开 ] │ │ 专注 15/25/45 分钟…… │ │ 删除 │ └───────────────────────────────┘ 在线扩展 🔄 刷新 ┌───────────────────────────────┐ │ 🎣 钓鱼 · 内置 重新安装 │ │ 🎁 盲盒 1.0.0 下载 │ │ 需要游戏 v0.31.0,先更新游戏 │ └───────────────────────────────┘ ``` - 在线一段:已经装了的不重复列;读不到目录时写「连不上 GitHub,稍后点刷新」。 ## 6. 下载的可靠性(2026-10-06 补) 国内直连 GitHub 的发行版附件很抖:同一个地址常常第一次连不上、几秒后就好。之前一次失败就整单失败, 错误只有一句 `fetch failed`,界面上也毫无反馈,用户看到的就是「点了没反应」。现在: - 每个文件 **12 秒超时、最多试 3 次**(间隔 400ms / 1200ms),**三个文件并行**下载,慢的那个不拖累另外两个; - HTTP 4xx 和超过 512 KB 不重试,连接超时 / 被中断 / 坏包会重试; - 错误文案带文件名、主机和原因:`server.js 下载失败:连接 github.com 超时(试了 3 次)`; - 每次失败的尝试都写进运行日志(`ext` 范围),导出日志里能看到是第几次、哪个地址; - 界面点下去立刻变「下载中…」,失败原因留在卡片上并给一个「重试」,不再只闪 2.4 秒的气泡。 **已知的根因没解决**:插件进程用的是 Node 的 `fetch`(undici),它不读系统代理,而浏览器读, 所以在需要代理出网的环境里,插件下载会一直失败(详见 HANDOFF 第 12 节)。 ## 7. 测试与验收 - 核心:删除收尾并清数据;删除后开关拒绝;重新安装从零开始;`extData` 只能被它自己的动作改。 - 宿主:用本地假目录和假扩展包测下载、sha256 不对拒绝、`minGame` 太高拒绝、扩展动作、扩展抛错不影响别的。 - 下载可靠性:模拟「前两次连不上、第三次成功」要能装上;三次都失败时错误里要有文件名和重试次数;目录读不到要有 error 文案。 - 界面:点下载立刻显示「下载中…」并挡住重复点击;失败留在卡片上并能重试;装上后卡片消失。 - 网页版(DSH 隔离实例)和桌面版都实际点一遍:删除钓鱼 → 数据没了 → 重新安装;在线装一个真实扩展(2026-10-06 已在隔离实例上装成菜园)。 ## 8. 扩展自带图片(v0.36,2026-10-11) 用户要求扩展界面换成画好的素材(动森风,codex 出图、用户逐轮审),扩展格式因此加了图片文件夹,不往 `client.js` 里塞 data URI。 - **格式**:`extensions//assets/*.webp|png`,manifest 的 `assets` 列出全部名字。规则集中在 `store/ext-files.js`:名字 `[a-z0-9-]+.(webp|png)`、单张 ≤512KB;0.37.0 起不限制图片张数和合计大小(旧宿主最多 256 张、合计 ≤4MB)。 - **在线目录**:`files` 里记作 `"assets/<名字>"`,地址指向同一个 `ext--<版本>` 发行版里平铺的附件(附件名不能带斜杠)。`scripts/extension-entry.mjs` 自动生成。 - **下载**:代码和图片一起下(同时最多 6 个),逐个核对 sha256;下完核对 manifest 的名单和目录的文件一一对应,对不上整个不装。放好时跟原来一样先写临时目录再换名。 - **本地导入**:扩展包 `.piggyext` 多一个 `assets: { 名字: base64 }`;官方识别要所有文件(含图片)都对上,换一张图就算非官方。 - **取图**:`GET /dsh-piggy/ext//assets/`,只给 manifest 名单里的,地址带 `?v=版本` 所以缓存一天;面板里 `app.asset(name)` 拼好地址,`app.pigArt()` 给玩家现在的猪图。 - **兼容**:带图片的扩展 `minGame` 设成 0.36.0,老游戏看到的是「要更新游戏」,不会装出一个缺图的版本。 - **整包下载(v0.36.1,2026-10-11)**:用户嫌发行版堆几十张图、请求太多。目录条目加 `bundle: { url, sha256 }`,指向同一发行版的 `ext--<版本>.piggyext`;新游戏只下这一个文件(超时 60 秒、重试同上),核对 sha256 后用本地导入同一个 `parseBundle` 拆开,再核对名字和版本(`store/ext-download.js`)。`files` 照旧保留:0.36.0 及更早的游戏只认它,官方识别也按它对。`minGame` ≥ 0.36.1 的扩展版本发行版只传整包,`files` 里的地址不会有人下载。 - **素材来源**:菜园、矿洞的原稿、逐字提示词、审图拼图和切图脚本在总目录 `art-source/ext-ui/`(不进仓库),`tools/pack-assets.py` 把审过的图缩成两倍显示大小的 WebP 放进 `extensions//assets/`。