npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

koishi-plugin-tomori-ournotes

v1.2.0

Published

BanG Dream! Our Notes 查询插件,对接 Tomori 后端(tsugu 兼容协议)

Readme

koishi-plugin-tomori-ournotes

《BanG Dream! Our Notes》查询插件,对接 Tomori 后端。

Tomori 后端出图或出 JSON,响应协议与 tsugu 兼容;本插件是它的 Koishi 前端。

与 bestdori-tsugu-extra 的关系

指令形态与消息设置沿用参考项目,但定位完全不同:

| | bestdori-tsugu-extra | 本插件 | | --- | --- | --- | | 模糊搜索 | 客户端自己调 /fuzzySearch 再二次请求 | 后端 /searchSong 等内部已做,一次请求 | | 谱面渲染 | 客户端 @napi-rs/canvas 自绘(BestdoriPreview.cjs) | 后端 /songChart 画好,插件只收图 | | 素材 | 仓库自带 SongChart/note/*.png、nickname_song.xlsx | 无,全部来自后端 | | 数据源 | 直连 bestdori.com | 只连 Tomori 后端 | | 分数线 | /cutoff* | 后端未实现,相关指令是占位桩 |

所以本插件没有 canvas、没有素材目录、没有 BestdoriPreview.cjs,也不碰 Bestdori。

安装

npm install            # 开发
npm run build          # 产物在 lib/

Koishi 里加载 koishi-plugin-tomori-ournotes(npm 上同名,https://www.npmjs.com/package/koishi-plugin-tomori-ournotes), 然后把 backendURL 指向你的 Tomori 后端。

版本与兼容(回退指引)

插件与后端是成对的:请求体字段变过一次,两边对不上时不会报错,只会查错服或查不到。

| 插件版本 | 对应 Tomori 后端 | 请求体契约 | | --- | --- | --- | | 1.2.x(本版) | ≥ 1.2.0,且含账号包端点(/playerCard、/b25、/deckBuilder、/playerBind/*) | 查询输入统一为 id,服务器统一为 displayedServerList | | 1.1.x | ≥ 1.1.0 | 同上(还没有绑定 / 发名片 / b25 / 组卡) | | 1.0.0(npm 上的旧版) | 1.0.x 及以前 | 查询输入用 text/songId/…,服务器用 server/mainServer |

1.2.0 起新增的绑定 / 发名片 / b25 / 组卡 打到没有这些端点的旧后端上会回 404 Not Found (插件原样透出这一句),其它指令不受影响。

回退办法:后端还是 1.0.x(或更早)时,别用本版插件 —— 它发的 displayedServerList 会被旧后端 静默忽略,于是 公告列表 jp、ycx … jp 这类会查成港澳台服。装回 npm 上的 1.0.0 即可:

npm i [email protected]

反过来,后端升到 1.2.0 之后再用本版;不确定时先敲「后端状态」,它会在后端契约版本不符时直接把上面 这句回退指引打出来。Koishi 配置页的「后端设置」里也写着同样一句。

服务器(区域)

后端有四个区域,短码 tw / jp / kr / en(别名也认:hk-tw-mo、台服、日服、国际服、韩服……)。 服务器词写在哪个位置都认:查曲 迷星叫 jp 与 查曲 jp 迷星叫 等价。

请求体里服务器只有一个字段 displayedServerList(单值、数组、缺省都收),差别在后端取谁:

| | 静态游戏数据(多服) | 动态用户数据(单服) | | --- | --- | --- | | 指令 | 查曲 / 查卡 / 查角色 / 查乐团 / 查活动 / 查卡池 / 查谱面 / 曲效率 / 随机曲 / 查贴纸 | 查排行 / 活动歌榜 / 活动榜线(ycx) / 活动推荐 / 抽卡模拟 / 查玩家 / 发名片 / b25 / 组卡 / 公告 | | 出图 | 一图多服,每个实体一个区块、区块内每服一行 | 单服一图(后端取列表第一个,不回退) | | 默认 | 全部四服(配置 defaultServers) | tw(配置 defaultServer) |

「查玩家 / 发名片 / b25 / 组卡」是例外:这四个命令的服务器只在写了玩家 ID 时有意义 —— 写了 ID 就按 ID 首位推断(2→港澳台、3→国际、4→韩;日服没有前缀规则,必须显式写 jp); 不写 ID 时用绑定的默认账号,服务器由绑定记录决定。 只写服务器不写 ID(查玩家 jp)会被插件直接拦下并说明原因 —— 那种情况下后端会静默忽略服务器, 凭空让人以为查的是日服。

配置

| 配置项 | 默认值 | 说明 | | --- | --- | --- | | backendURL | http://127.0.0.1:3002 | Tomori 后端地址。后端 .env.example 写的是 3000,实际部署常在 3002,以 .env 里的 PORT 为准 | | backupURLs | [] | 备用地址,仅在主地址连不上时按顺序重试;后端返回 4xx/5xx 不会切换 | | compress | true | 出 JPEG(快、体积小);关闭出 PNG(更清晰) | | reply / at | true | 是否引用回复 / @ 用户 | | defaultServers | 全部四服 | 静态实体查询默认展示哪些服 | | defaultServer | tw | 单服指令(排行/玩家/抽卡/公告)的默认服 | | stubCommands | true | 是否注册后端未实现功能的占位桩指令 | | announcementStream | true | 是否连接公告推流 | | subscriptionFile | 空 | 订阅落盘路径;空则用 <Koishi 目录>/data/tomori-ournotes/subscriptions.json | | requestTimeout | 120000 | 单次请求超时(毫秒)。谱面要现画,别设太小 |

指令

查询输入:所有「查什么」的参数都支持数字 ID 或名字。后端统一按 id 处理 —— 纯数字(或纯数字串)按 ID 直查,其余文本走模糊搜索(可拼词条);名字命中多个时出该种类的列表图。 所以插件不需要客户端模糊搜索,谱面也是一次请求直达。

玩家类指令(查玩家 / 发名片 / b25 / 组卡)是例外:它们只认裸数字的玩家 ID(可省略 = 用绑定账号), 没有名字搜索,多写一个数字会被明确拒绝而不是替你挑一个。

| 指令 | 说明 | | --- | --- | | 查曲 <ID或名字> | 命中单首出详情图,多首出列表图 | | 随机曲 [筛选词] | 随机一首,可限定范围(lv25 这类筛选词后端认);单服取服务器列表首个 | | 曲效率 [服务器…](别名 曲表、分数表、查询分数表) | 歌曲 meta 效率排行:一张图两榜,击奏live 与 自由live 各自效率最高的前 15 张谱面,四难度混排 | | 查谱面 <ID或名字>(别名 查谱) | 24 轨谱面预览图。难度 ez/nm/hd/ex(默认 ex),末尾加 镜像 或 -m 出镜像谱,速度7.5 调流速(1~12)。歌名命中多首时出列表图并提示用 ID | | 谱面数据 <歌名或ID> [难度] [镜像] | 谱面摘要(BPM / 物量 / Fever / 滑条);歌名多命中时后端会回一句「请用 songId 精确指定」。要原始 JSON 请直接调后端 /songChartData | | 查卡 <关键词> | 角色卡 + 支援卡一起查;可写卡种 角色卡/支援卡 | | 查角色卡 / 查支援卡 <关键词> | 只查其中一类 | | 查卡面 <卡面ID>(别名 卡面) | 卡面原图;种类 member/support,也认 角色卡/支援卡/留影 | | 查卡池 <卡池ID或名字> | 卡池详情(名字多命中出卡池列表图) | | 抽卡模拟 [次数] [卡池ID或名字](别名 抽卡、卡池模拟) | 默认 10 连;不指定卡池则用当前开放卡池;上限 10000。卡池写名字时只在该服索引里搜,多命中会回一句「请用 gachaId 精确指定」 | | 查贴纸 <ID> | 官方贴纸原图 | | 查贴纸 <关键词> [角色\|团体] | 搜索贴纸,出列表图(带 ID 与名称) | | 查贴纸 全部 | 列出该服全部贴纸(每 30 张一页) | | 查角色 / 查乐团 <ID或名字> | 角色搜索 / 乐团搜索(乐团也能挂自定义关键词) | | 查活动 <关键词> | 活动搜索:只写一个服且该服有收录时出丰富详情图,否则出多服组合表;命中多个出活动列表图 | | 活动歌榜 [名次] [活动ID或名字] [服务器](别名 活动榜线) | 该活动各乐曲榜(名次/玩家名/综合力/出分)。名次挑一段十名:给 100 就是第 91~100 名,省略则前十。活动省略时取当前活动 | | ycx [档位] [活动ID或名字] [服务器] | 活动榜线:各档分数线随时间的折线图(单档位)。档位 10/100/1000/5000/10000,省略用 100 | | ycxall [活动ID或名字] [服务器] | 同上,但画数据支持的全部档位 | | 活动推荐 [活动ID或名字] [服务器](别名 推荐曲) | 推荐曲:击奏live / 自由live 各按目标评级 SS/S/A/B,加一截挑战live;附局/小时与活动报酬估算 | | 查排行 <歌名或ID> | 该曲前十用户与出分(单服)。歌名只在该服索引里搜,多命中出歌曲列表图 | | 查玩家 [玩家ID](别名 查账号) | 玩家档案图(最爱卡面/名称/等级/应援数);有网页账号包时再附一张账号包图。ID 省略 = 用绑定的默认账号 | | 发名片 [玩家ID] [页N] [服务器] | 玩家自己设计的那套名片的原图(最多 3 页)。页码写成 页2 / 第2页(不能写裸数字,裸数字是玩家 ID);省略则按页依次发全部 | | b25 [玩家ID] [服务器](别名 查b25;指令名不分大小写,B25 也认) | B25 计分榜:账号最强 25 张谱面(计入值 / 分数 / 总计值)。需要账号包的歌曲公开 | | 组卡 [玩家ID] [常规\|活动[ID]\|自动] [服务器](别名 组队、配队) | 组卡工具:推荐队伍与收益/效率曲。活动 ID 写在「活动」后面(活动 123 / 活动123);需要账号包的卡片与道具公开 | | 绑定玩家 <绑定码> | 用网页生成的绑定码绑定一个游戏账号(一个 QQ 可绑多个,首个自动设默认) | | 玩家绑定(别名 玩家状态、玩家状态列表) | 列出已绑定的账号(★ = 默认),给出解绑/切换默认的写法 | | 默认玩家 [ID或序号] | 切换默认账号(不传 ID 的查玩家 / 发名片 / b25 / 组卡 都用它) | | 解除绑定 [ID或序号] | 解绑一个账号。序号看「玩家绑定」列表,1~3 位数字按序号、更长的按玩家 ID | | 公告列表 [服务器] | 该服公告列表图 | | 公告 <公告ID> [服务器] | 该条公告详情图(正文过长会分页) | | 公告 最新 [服务器] | 该服最新一条公告的详情图(后端的 -1 哨兵值;写成 公告 "-1" 也认,但裸 -1 会被 Koishi 当选项吞掉) | | 公告订阅 <服务器…> / 公告退订 [服务器…] | 见下方「公告推流」 | | 公告 <服务器> / 公告 关闭 [服务器] | 上面两条的简写,与参考项目习惯一致 | | 新增关键词 <类型> <实体ID> <关键词> | 给角色/角色卡/支援卡/歌曲/乐团挂一个检索别名,该别名参与模糊搜索(依赖后端数据库)。类型写 角色/角色卡/支援卡/歌曲/乐团 | | 删除关键词 <类型> <实体ID> <关键词> | 只能删自己上传的 | | 车来(别名 ycm、有车吗) | 车站列表图 | | 上传车牌 <房间号> [备注] | 房间号 5-7 位数字;同房号重复提交 = 刷新有效期 | | 交友登记 <游戏ID> [服务器] | 一人一条,重复登记覆盖 | | 解除交友(别名 删除交友)/ 交友列表 | 删除自己的名片 / 列表图 | | 后端状态 | 后端连通性、四服 master 版本、玩家数据源、运行时长。排查问题先用它 |

曲效率 改过语义。 它对应的 /songMeta 早期是「全歌曲表」(ID/标题/乐队/等级/时长,每 20 首一页), 现在是效率排行(一张图两榜,各前 15 张谱面)。请求体没变,所以旧名 曲表/分数表/查询分数表 全部保留为别名, 但看到的东西已经完全是另一张图了。

占位桩指令(stubCommands: false 可关掉):lsycx、查试炼、查自制谱、逮捕。

这些在 tsugu 系插件里存在,但 Tomori 后端没有对应端点(/cutoffDetail、/cutoffListOfRecentEvent、tsugu 的 /user 都是 404 占位)。 注册它们是为了从旧配置迁移过来时能得到「暂不支持」而不是「未知指令」。

「查玩家」不在占位之列 —— 后端的 /searchPlayer 早就实现了。 ycx / ycxall 也已经是真指令了:后端的 /cutoffAll 现在是各档分数线随时间的折线图。 绑定管理(绑定玩家 / 玩家绑定 / 默认玩家 / 解除绑定)曾经是桩,1.2.0 起接上了后端的 /playerBind/*,也是真指令了。 只有 lsycx(最近活动榜线)还挡着,它对应的 /cutoffListOfRecentEvent 仍是 404。

玩家绑定与网页账号包

后端的账号数据分两路:上游玩家档案(名字/等级/最爱卡面,看 ID 就能查)与 网页账号包(玩家自己在网页平台导入的游戏数据 —— 歌曲完成度、乐队道具、卡片)。 绑定只是把 QQ 与游戏账号对上号,让下面这些指令不用每次输玩家 ID:

  1. 到网页「我的账号」里给账号包填好玩家 ID,生成绑定码(8 位、一次性、15 分钟);
  2. 在群里发 绑定玩家 <绑定码>;
  3. 之后 查玩家 / 发名片 / b25 / 组卡(都不写 ID)就用这个账号。
  • 一个 QQ 可绑多个账号:玩家绑定 看列表,默认玩家 2 切换默认(序号或玩家 ID 都行),解除绑定 2 解绑。
  • 绑定只做两件事:免输玩家 ID + 证明账号归属(防冒绑)。它不解锁任何隐藏数据 —— 群里的展示一律按网页上的公开开关(卡片 / 道具 / 歌曲各自是否公开),绑定本人也一样,隐藏即不显示。
  • 因此:查玩家 的账号包图需要对应类别公开;b25 需要歌曲公开; 组卡 需要卡片与道具公开。隐藏时后端会提示去网页调整。
  • 账号包数据不是实时的(导入时算好),且 b25 / 组卡 还需要网页平台在线。
  • 绑定管理依赖后端数据库(ENABLE_DB);没开时这几条会回「错误: 服务器未启用数据库」。
  • 绑定管理的回复是文字(列表/成功提示),不是图 —— 这是后端定的,插件原样透出。

公告推流

后端只在公告新增或修改时推送该条的详情图,所以:

  • 连接时不发快照,订阅之后只有新公告会推过来;想看全量列表用「公告列表」
  • 下架不推(需要的话定期用「公告列表」对账)
  • 更新来自后端轮询上游(默认 300s),所以有最多约 5 分钟的延迟
  • 场景侧默认没有订阅,要用 公告订阅 tw(可累积多个服)注册;公告退订 [服务器] 取消
  • 机器人侧总开关是配置里的 announcementStream;关掉时仍可登记,但会回复一句「推流未启用」

插件只对有订阅者的服建连接,最后一个订阅者退订就断开 —— 后端也只在有订阅者时才轮询上游。

关于后端的一些事实(踩过的坑)

  • 1.1.0 起请求体只有一套叫法:查询输入一律 id(后端按 id > 专属 ID 字段 > text > fuzzySearchResult 取第一个有值的),服务器一律 displayedServerList(单值 / 数组 / 缺省都收)。 旧的 server / mainServer 已被删除 —— 传了不报错,但会被静默忽略,于是「查 jp」实际查的是缺省服。 改版时别再写回去。
  • 区域命名有三套并存:hk-tw-mo 只是 current_version.json 的清单键,master 表路径段是 tw; 拿 hk-tw-mo 去拼路径会 404。对本插件而言它只是 tw 的别名。 日服素材只有 ja 目录,且日服的模糊索引里只有日文曲名 —— 拿中文曲名搜 jp 搜不到是正常的。
  • 难度只有 4 个:difficultyId 0-3 = easy/normal/hard/expert,没有 special。sp 会被插件本地拦下(发出去必然 400)。
  • 域内错误的形状不止一种:多数是 HTTP 200 + ['错误: …'],但占位路由是 404 + {status:'fail', data:'错误: 服务器未启用数据库'}, 参数校验失败是 400 + {status:'failed', data:'参数错误', error:[…]}(带字段级原因), 「一个查询输入都没给」是 422 + {status:'failed', data:'需要提供查询输入: …'}(没有 error 数组), 兜底 404 干脆是纯文本 404 Not Found。 所以插件不用 ctx.http.post()(那个只回 response.data,会丢状态码),而是把 HTTP 服务当函数调用来拿完整响应。
  • 图片 MIME 靠嗅探:compress=true 出 JPEG,/getStampImage 原来只出 webp、现在原图仍可能是 webp, /getCardIllustration 出 PNG。写死 image/png 会让适配器拿到错的类型,所以按魔术字节判断。
  • 分页端点会连发多条 base64(贴纸列表 30/页、车站与交友 30/条、抽卡 >10 连、公告详情 42 行/页), 中间不夹文本。插件原样渲染成多张图。 (/songMeta 改成语义之后不再分页,一张图两榜。)
  • /songChart 的流速默认 7.5、范围 1~12;插件不写这个字段时由后端用它自己的默认值。
  • /station/submitRoomNumber 的 time 是毫秒时间戳,后端视图拿它算「x 秒前」。 (参考项目传的是 120,那是个 bug,别照抄。)
  • 贪心的 text 参数会吞掉 Koishi 选项:查谱面 100008 ex -m 的 -m 会进到文本里,.option('mirror') 拿不到值。 所以谱面类指令的 -m 是插件自己从 token 里解析的。
  • 交友 / 车站 / 关键词依赖后端的两件事:.env 里 ENABLE_DB=true 且 本机 MongoDB 在跑。 两者缺一,后端就回「错误: 服务器未启用数据库」,插件原样透出。
  • 关键词有两道查重:同一实体上不能重复,且不得与任何现有实体名/别名重合(乐团、角色、歌曲、卡池、活动……的全部语言别名与去标点变体)。 命中就拒绝,所以「挂不上去」多半是撞了官方名,不是插件的问题。 关键词参与模糊搜索(所以外号能搜到),但模糊索引按区域分片,某个服挂的别名在别的服搜不到。
  • 活动:上游活动数据由各服独立上传,所以「某服暂时没有」不等于活动不存在 —— /searchEvent 对这种情况出多服组合表 + 未收录占位,只有四个服都没有才当作 ID 不存在。 活动歌榜 取的是活动内的挑战曲榜(/events/{id}/challenges/{挑战曲id}/ranking), 不是 /music/{id}/ranking 那个曲子历史最高分榜 —— 活动曲在后者常常查不到数据。
  • 「活动歌榜」与「活动榜线(ycx)」是两张不同的图,别被名字带偏: 歌榜(/eventSongRanking)是当前排行榜;榜线(/cutoffAll)是各档分数线随时间的折线。 榜线数据上游没有历史接口,由后端按小时自己采样攒 —— 所以刚接入的活动可能一个点都没有, 后端会在图上附一句说明,那不是插件的问题。
  • 名次/档位与活动 ID 都是纯数字,插件严格按位置认,不去猜: 活动歌榜 100 1 与 ycx 100 1 都是「第一个数字是名次/档位、第二个是活动 ID」(ycxall 只有活动 ID); 多出来的数字一律算参数错误。 早先的写法是「落在档位表 10/100/1000/5000/10000 里就算档位」,那样 ycx 100 1 与 ycx 1 100 里 同样两个数字的含义会随取值对调 —— 用户没法从写法上判断哪个是哪个。现在只有一条规则。 代价是 ycx 1 会被当成「档位 1」而不是「活动 1」;不过后端对不适配的档位会回一句 「当前可用档位: …」,照着改即可,比含义漂移好。
  • 查谱面一步直达:/songChart 自己收文本(唯一命中出谱面图、多命中出歌曲列表图), 所以插件不再先调 /fuzzySearch 拿 ID —— 难度/镜像/流速一次请求带齐,不会在中转那一跳丢掉。 (后端还给 /searchSong 留了个 singleDraw: 'chart' 开关,1.1.0 起对 ID 直查也生效;本插件用不到它。)
  • 公告 最新 走 id = -1 这个哨兵值:后端 /announcements 收到 -1 时先拉列表挑出该服 发布时间最新的一条,再出它的详情图(读列表时跳过 TTL —— 问「最新」却拿缓存里的旧列表会答错)。 插件侧 最新 / -1 / new 都映射到它;公告 23 这类真实 ID 原样透传。 坑:裸 -1 在 Koishi 里会被当成选项吃掉,根本轮不到指令参数(公告 -1 只会回用法提示)—— 所以对外只宣传 最新;真要用数字就写带引号的 公告 "-1"。
  • 查玩家默认只能查到「在 StarMoe 已验证且设为公开」的账号,其余回「该账号未公开或不存在」; 想让任意 ID 可查要在后端配 MOENOTES_API_BASE。
  • 玩家类端点的服务器解析有差异:/searchPlayer 收 ID 后自己按首位推断服务器; /playerCard、/b25、/deckBuilder 则是「有 displayedServerList 取首个、没有用缺省服」—— 所以插件替后三条补了同一套前缀推断(2→tw / 3→en / 4→kr)。 四条都是不写 ID 时服务器由绑定记录决定,这时显式传的服务器会被静默忽略; 插件对这种写法(查玩家 jp)直接报错,而不是发一个必然被忽略的字段。
  • /searchPlayer 的输出是组合的:档案图与账号包图是两个独立的块; 上游档案查不到而账号包存在时只出账号包图(页脚注明),两边都没有才回原错误文案。 档案图只带当前使用的那一张名片,其余页要用「发名片」单独取。
  • 绑定码兑换走网页平台:/playerBind/bind 内部调网页的 /api/bot/bind(需要 bot 侧 WEB_PLATFORM_TOKEN 与网页侧配对好),换来账号包的身份(账号 ID / 玩家 ID / 服); 绑定码一次性、15 分钟,只能证明归属,不解除任何隐藏。
  • 名片原图直出:/playerCard 不加框不加字,逐页取上游图床的原图(走外链白名单); 上游只给当前使用的那套的 1~3 页。个别页取不到时跳过,全取不到才报错。
  • 组卡与网页组卡器是同一套算法(移植 + 数值对照脚本):活动模式以实际收益为目标 (点数吃活动点加成、道具吃交换所加成;同评级下加成高者胜),出三块榜——自由live 混池 3 首、 自由live 活动加成乐队 1 首、挑战live 活动挑战曲 1 首,每行都自带它自己的编队(图上用编号引用)。 谱面效率数据回退到降级源(缺技能权重)时它拒绝出图,而不是给一个算不准的推荐。

测试

npm test                    # 单元测试 + 集成测试(后端不可达时集成部分自动跳过)
npm run typecheck
npm run typecheck:loose     # 用非 strict 配置再查一遍(见下)

为什么有 typecheck:loose:Koishi 实例(yukamo 等模板)的 tsconfig 常常没开 strict, 而 strictNullChecks 关掉时真值收窄(if (!x.ok))对可辨识联合不生效, 只有等值收窄(== false / === false)生效 —— 本仓库开着 strict 测不出来, 装进实例才会报 TS2339: Property 'error' does not exist。 新代码里判断 parseArgs / parsePlayerArgs / parseDeckArgs 的结果一律写 == false。

测试分两层:

  1. test/unit/ —— 模拟输入 / 模拟输出。跑的是真实 Koishi 运行时(指令注册、参数解析、before-send 钩子都是 Koishi 自己的实现), 只把 ctx.http 换成记录型假实现:既能断言插件到底发了什么请求体,又能完全控制后端回什么。 输入用官方的 @koishijs/plugin-mock 发消息。

    • parse.test.ts —— 参数解析(服务器抽取、难度/镜像/速度、玩家/组卡/绑定的入参、keepQuery 边界)
    • commands.test.ts —— 每个指令的请求体与各种成功/失败/畸形响应
    • backend.test.ts —— HTTP 层与响应渲染的畸形形状
    • sse.test.ts / subscription.test.ts / manager.test.ts —— 公告推流的解析、订阅表、连接生命周期
  2. test/integration/ —— 打真实后端,验证请求体字段是否真被接受、大图 base64 能否变成可发送的图片元素、 以及 ctx.http 的流式响应能不能读到真实的 SSE 握手。默认 http://127.0.0.1:3002,可用环境变量指向别处:

    TOMORI_TEST_BACKEND=http://127.0.0.1:3102 npm test

    跑之前会先探一次 /health:连不上跳过,连上了但不是这一版契约的后端(没有四区域列表) 也跳过,并打印一句说明。这样「端口上蹲着另一个旧实例」和「插件改坏了」不会混为一谈。

    集成断言刻意宽松(只验「有图」或用正则匹配若干可能文案),后端改版式不该让插件测试变红。 对「后端有没有启用数据库」也宽容:交友/车站那几条只要求插件把后端的答复清楚地转达给用户。

两个测试环境上的注意点

  • vitest.config.ts 把 koishi 别名到了 @koishijs/core。因为 koishi 主入口 = @koishijs/core + @koishijs/loader,而 loader 的产物里有 import Loader from './shared.js' 这种「ESM 引 CJS」的相对导入,在 Vite 的转换管线里会解析成 Class extends value #<Object> is not a constructor 直接炸掉。 插件用到的 Context / Schema / h 与全部类型都来自 core,所以测试环境指向 core 即可。
  • 夹具里 announcementStream 默认是 false:推流是后台长连接,而测试夹具故意不调 app.stop(), 开着会把 vitest 挂住。推流的测试单独构造 manager 并显式 stop()。

许可

MIT。本项目与 Bushiroad、Craft Egg 及《BanG Dream!》官方无任何关联。