沙箱插件教程
工坊 沙箱插件 与 本地插件 不是同一套运行时。本页讲 SDK 里的 plugin-package 从 hello 到联网 catalog。索引 → 创意工坊 SDK。
sources:provide / sources:direct 只能暴露你有权提供的 HTTP(S) 直链,不是接入网易云、Spotify 等平台。法律说明 → 下载与插件音源法律边界。
项目结构(插件)
Section titled “项目结构(插件)”my-plugin/├── echo.workshop.project.json├── content/│ ├── echo.workshop.json # 外层清单 + networkHosts(若联网)│ └── community.echo # check/sync 生成,勿手改├── src/│ ├── plugin.js # 入口,你主要改这里│ ├── panel.html / panel.js # complete / catalog 等预设可能有│ └── …└── .echo-sdk/ └── echo-workshop-plugin.d.ts文件顶行建议保留类型引用:
/// <reference path="../.echo-sdk/echo-workshop-plugin.d.ts" />实战 A:Hello 插件
Section titled “实战 A:Hello 插件”- 拷贝示例:
Terminal window node .\bin\echo-workshop-sdk.mjs example hello-plugin .\my-hellocd .\my-hello - 读
src/plugin.js:注册一条命令,调用echo.playback.getStatus(),弹echo.ui.notify。 - 权限只有
playback:read。若加echo.library.*,须先:Terminal window npm run add -- --permission library:read - 每次改完:
sync 会把
Terminal window npm run checksrc/打进content/community.echo并重算哈希。 npm run dev在 mock host 里点 fixture;未声明的echo.*调用会 capability-denied。
最小插件逻辑示例:
echo.commands.register('hello-echo', { title: 'Hello ECHO' }, async () => { const status = await echo.playback.getStatus(); await echo.ui.notify( status.currentTrackId ? '插件已连接,当前有歌在播。' : '插件已连接,还没有播放。' );});启用后用户要在 ECHO 右下角 插件 里找到命令;订阅工坊后还须点 使用,见 Steam 创意工坊。
实战 B:音源目录(catalog)
Section titled “实战 B:音源目录(catalog)”适合作者自托管 JSON API + 直链音频,不是平台爬虫。
- 按 recipe 新建:
Terminal window node .\bin\echo-workshop-sdk.mjs init .\my-catalog --recipe source-catalogcd .\my-catalog - 默认已有
sources:provide、sources:direct、fs:plugin等权限;联网还要:Terminal window npm run add -- --permission network:request - 在外层
content/echo.workshop.json(或通过项目配置)声明 networkHosts,例如你的 API 域名(纯域名,无https://、无端口)。 - 在
src/plugin.js注册 provider,实现search/browse/listCollection/resolve:- 前三者只返回条目列表;
resolve必须返回一条可播放的http/https直链。
npm run check— mock 会拒绝未在 networkHosts 里的域名和自定义端口。- 用户第一次播某域名时,ECHO 还会再确认来源。
Provider 骨架(域名换成你的,且须在 networkHosts 中):
echo.sources.registerProvider('owned-catalog', { title: '我的目录' }, { search: async ({ query, page, pageSize }) => { const res = await echo.network.get( `https://audio.example.com/catalog?q=${encodeURIComponent(query || '')}&page=${page}&pageSize=${pageSize}` ); const payload = JSON.parse(res.body); return { tracks: payload.tracks ?? [], total: payload.total ?? 0, hasMore: payload.hasMore === true, }; }, resolve: async ({ providerTrackId }) => { const res = await echo.network.get( `https://audio.example.com/resolve/${encodeURIComponent(providerTrackId)}` ); const payload = JSON.parse(res.body); return { url: payload.url, title: payload.title, artist: payload.artist }; },});不要填 Cookie、Token、Steam Guard;不要解析平台网页冒充官方曲库;不要自建播放后端绕过 Audio Core。
插件预设对照
Section titled “插件预设对照”--preset / recipe | 包含能力 |
|---|---|
basic / hello 示例 | 单命令 |
complete / plugin-complete | 命令、面板、Agent、metadata/lyrics/source 提供器 |
catalog / source-catalog | 直链目录 + 面板 |
lyrics / lyrics-source | 歌词 provide + 当前歌词面板 |
复杂项目先用 snippet list 复制片段,再 npm run next 看还能加什么权限。
node .\bin\echo-workshop-sdk.mjs apinode .\bin\echo-workshop-sdk.mjs api echo.queue.moveItemnode .\bin\echo-workshop-sdk.mjs api errors| 常见权限 | 用途 |
|---|---|
playback:read | 读播放状态 |
playback:control | 播放/暂停/切歌等 |
library:read | 分页读曲库公开字段 |
sources:provide | 注册音源 provider |
sources:direct | resolve 返回直链 |
network:request | echo.network.*(须配 networkHosts) |
lyrics:provide | 歌词候选 |
lyrics:read | 读当前清理后歌词文本 |
fs:plugin | 插件私有存储 |
agent:runtime | Agent 能力(complete 预设) |
用户拒绝直链或一起听上传后不得循环弹窗;限流、只读保护才可退避重试(见 api errors)。
包体与 apiVersion
Section titled “包体与 apiVersion”- 最多 32 文件,单文件 512 KiB,整包 2 MiB。
- 内层
manifest.apiVersion必须与外层compatibility.pluginApiVersion一致(新项用 2)。 - 扩展名限
.css、.html、.js、.mjs、.json。
与本地插件文档的关系
Section titled “与本地插件文档的关系”插件创作指南 讲主程序 plugins/ 目录与更宽的 API 面。工坊包 不要 按那份文档改 community.echo 结构或假设有 Node / SQLite 访问。
本地插件页 → 工坊:重新用 SDK init --recipe …,不要直接复制文件夹到 Steam。
进阶能力:metadata、歌词、一起听
Section titled “进阶能力:metadata、歌词、一起听”完整示例清单 → 官方示例与片段。下面是三类常见进阶插件能力。
元数据 / 封面候选
Section titled “元数据 / 封面候选”用户选歌时,宿主向 provider 要候选,由用户决定是否采用;插件不能自动改库。
echo.metadata.registerProvider('clean-tags', { title: '标签候选' }, async ({ track }) => ({ candidates: [{ title: track.title, artist: track.artist, album: track.album, source: '作者目录', confidence: 0.7 }],}));需声明 metadata provider 贡献 + 对应权限(见 examples/metadata-provider/ 与 snippet list)。
与 JSON 歌词场面 不同:这是动态歌词候选,供宿主歌词选择器使用。
node .\bin\echo-workshop-sdk.mjs init .\my-lyrics --recipe lyrics-sourcenode .\bin\echo-workshop-sdk.mjs snippet lyrics-providerlyrics:provide 只提交候选;保存、同步、播放仍由 ECHO 拥有。读当前曲清理后歌词文本用 lyrics:read(不含路径或账号)。
一起听(playback:share)
Section titled “一起听(playback:share)”API 2 下上传当前本地曲到作者声明的上传 URL(不是把文件路径交给插件)。
- 权限:
playback:share+network:request networkHosts必须包含上传域名- 用户拒绝上传后不得自动重试弹窗
参考 examples/listen-together/plugin.js。生产环境会先解析公网地址再连接,并保留 Host/TLS 身份。
-
npm run check全绿,fixture 通过 - 权限列表与用户看到的授权框一致,能少则少
- networkHosts 与代码里实际请求的域名一致
- README 写清数据来源与用户需确认的直链性质
- 创作台校验通过后发布