@jsbot-tellus/core 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -4
- package/dist/core/dispatch/command.d.ts +5 -2
- package/dist/core/dispatch/command.d.ts.map +1 -1
- package/dist/core/dispatch/command.js +33 -7
- package/dist/core/dispatch/command.js.map +1 -1
- package/dist/core/dispatch/handler.d.ts +5 -3
- package/dist/core/dispatch/handler.d.ts.map +1 -1
- package/dist/core/dispatch/handler.js +6 -1
- package/dist/core/dispatch/handler.js.map +1 -1
- package/dist/core/dispatch/router.d.ts +4 -2
- package/dist/core/dispatch/router.d.ts.map +1 -1
- package/dist/core/dispatch/router.js +9 -5
- package/dist/core/dispatch/router.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/kernel.d.ts +1 -1
- package/dist/core/kernel.d.ts.map +1 -1
- package/dist/core/kernel.js +4 -1
- package/dist/core/kernel.js.map +1 -1
- package/dist/core/plugin/context.d.ts.map +1 -1
- package/dist/core/plugin/context.js +12 -0
- package/dist/core/plugin/context.js.map +1 -1
- package/dist/core/plugin/loader.d.ts +1 -1
- package/dist/core/plugin/loader.d.ts.map +1 -1
- package/dist/core/plugin/loader.js +31 -46
- package/dist/core/plugin/loader.js.map +1 -1
- package/dist/core/plugin/types.d.ts +9 -1
- package/dist/core/plugin/types.d.ts.map +1 -1
- package/dist/core/plugin/types.js.map +1 -1
- package/docs/architecture.md +3 -3
- package/docs/configuration.md +3 -3
- package/docs/docker.md +166 -0
- package/docs/feature-parity.md +2 -2
- package/docs/migration.md +1 -1
- package/docs/plugin-api.md +20 -5
- package/docs/plugin-development.md +30 -27
- package/docs/release.md +50 -8
- package/package.json +2 -2
- package/src/core/dispatch/command.ts +53 -1
- package/src/core/dispatch/handler.ts +19 -1
- package/src/core/dispatch/router.ts +20 -3
- package/src/core/index.ts +4 -0
- package/src/core/kernel.ts +8 -1
- package/src/core/plugin/context.ts +21 -0
- package/src/core/plugin/loader.ts +41 -66
- package/src/core/plugin/types.ts +20 -0
package/docs/plugin-api.md
CHANGED
|
@@ -184,11 +184,11 @@ const installed = await ctx.core.installer.list()
|
|
|
184
184
|
ctx.core.restart()
|
|
185
185
|
```
|
|
186
186
|
|
|
187
|
-
核心服务由内核注入,适合帮助、状态类插件。`ctx.core.commands.list(event)` 会按当前会话的 `enabled`、`groups`、`private` gating 过滤命令;不传 `event` 时返回全部命令。`ctx.core.installer` 额外提供 `install` / `remove` / `update` / `list`;它不会检查调用者角色,权限判断由调用方负责(`admin` 的 `/plugin` 命令要求 SuperAdmin)。安装器默认关闭,必须在配置中显式启用。`ctx.core.restart()` 请求应用重建运行时并重新加载插件;第三方插件不应把这些方法暴露给普通用户。
|
|
187
|
+
核心服务由内核注入,适合帮助、状态类插件。`ctx.core.commands.list(event)` 会按当前会话的 `enabled`、`groups`、`private` gating 以及命令的 `messageType` 过滤命令;不传 `event` 时返回全部命令。`ctx.core.installer` 额外提供 `install` / `remove` / `update` / `list`;它不会检查调用者角色,权限判断由调用方负责(`admin` 的 `/plugin` 命令要求 SuperAdmin)。安装器默认关闭,必须在配置中显式启用。`ctx.core.restart()` 请求应用重建运行时并重新加载插件;第三方插件不应把这些方法暴露给普通用户。
|
|
188
188
|
|
|
189
189
|
安装的插件与内置插件运行在同一个宿主进程中,可访问文件系统、网络和 `process.env`,属于完全可信代码,不是安全沙箱。
|
|
190
190
|
|
|
191
|
-
自动重启会在同一个 Node.js 进程内重建 Kernel,不会清空 Node 的 ESM
|
|
191
|
+
自动重启会在同一个 Node.js 进程内重建 Kernel,不会清空 Node 的 ESM 模块缓存。每次加载都会重新构建带 `build` 脚本的插件,并给入口文件附加基于修改时间的查询参数来绕过缓存;但入口相对导入的辅助模块仍可能复用旧代码。更新多文件插件包后,请完整退出并重新启动进程。
|
|
192
192
|
|
|
193
193
|
## 命令
|
|
194
194
|
|
|
@@ -215,11 +215,14 @@ ctx.commands.register(
|
|
|
215
215
|
| `aliases` | `[]` | 别名 |
|
|
216
216
|
| `prefixes` | `['/']` | 前缀;空字符串表示无前缀 |
|
|
217
217
|
| `permission` | `'User'` | `User`、`Admin`、`SuperAdmin` |
|
|
218
|
+
| `messageType` | 无 | `'group'`、`'private'` 或数组,限定生效会话 |
|
|
218
219
|
| `description` | 无 | `/help` 中展示 |
|
|
219
220
|
| `params` | 无 | Koishi 风格参数声明;省略时 handler 行为不变 |
|
|
220
221
|
|
|
221
222
|
`name` 和 `aliases` 的每一项只能是一个路径段;多级命令请使用 `commands.group()`。
|
|
222
223
|
|
|
224
|
+
`messageType` 省略时群聊和私聊都可触发;`messageType: 'group'` 只匹配群消息,私聊中的同名文本会继续进入 `onMessage`。命令组会继承该设置,子命令可以覆盖。
|
|
225
|
+
|
|
223
226
|
### 参数
|
|
224
227
|
|
|
225
228
|
`params` 用字符串声明位置参数,解析结果从 handler 的第四个参数开始按位置传入:
|
|
@@ -344,7 +347,7 @@ ctx.commands.group(
|
|
|
344
347
|
|
|
345
348
|
- `group()` 的 `name` 和 `aliases` 同样是单个路径段;通过嵌套 `group()` 组成任意层级。
|
|
346
349
|
- 子命令自动拼接组路径,例如上面的 `plugin` + `install` 对应 `/plugin install`。
|
|
347
|
-
- 子命令默认继承组的 `prefixes`、`permission`,也可以在子命令上覆盖。
|
|
350
|
+
- 子命令默认继承组的 `prefixes`、`permission`、`messageType`,也可以在子命令上覆盖。
|
|
348
351
|
- 组别名和子命令别名会自动组合;组 `plugin` 别名 `p`、子命令 `install` 别名 `i` 时,`/p i` 也能命中。
|
|
349
352
|
- 组根没有提供 `handler` 时,会自动注册根命令并生成用法提示,列出直接子命令、参数用法和描述。
|
|
350
353
|
- 组根提供了 `handler` 时,未命中子命令的输入(例如 `/plugin`、`/plugin help`、`/plugin unknown`)会进入该 handler。
|
|
@@ -385,6 +388,16 @@ ctx.events.onMessage(async (event, context) => {
|
|
|
385
388
|
}
|
|
386
389
|
})
|
|
387
390
|
|
|
391
|
+
ctx.events.onGroupMessage(async (event, context) => {
|
|
392
|
+
// 只处理群消息,event.message_type 收窄为 'group'
|
|
393
|
+
await context.send(`${event.group_id} 收到消息`)
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
ctx.events.onPrivateMessage(async (_event, context) => {
|
|
397
|
+
// 只处理私聊消息
|
|
398
|
+
await context.send('私聊收到')
|
|
399
|
+
})
|
|
400
|
+
|
|
388
401
|
ctx.events.onNotice(
|
|
389
402
|
{ noticeType: 'notify', subType: 'poke' },
|
|
390
403
|
async (event, context) => {
|
|
@@ -402,6 +415,8 @@ ctx.events.onRequest({ requestType: 'friend' }, async (event) => {
|
|
|
402
415
|
|
|
403
416
|
未匹配命令的消息才会进入 message handler。notice/request handler 会先按类型过滤,再检查插件 gating。
|
|
404
417
|
|
|
418
|
+
`onGroupMessage` / `onPrivateMessage` 与 SDK 的同名方法对齐,在注册时按 `message_type` 过滤,事件类型分别收窄为群消息和私聊消息。
|
|
419
|
+
|
|
405
420
|
## 中间件
|
|
406
421
|
|
|
407
422
|
```ts
|
|
@@ -488,7 +503,7 @@ manifest: {
|
|
|
488
503
|
|
|
489
504
|
完整的目录约定、创建步骤和本地调试流程见[插件开发指南](plugin-development.md)。
|
|
490
505
|
|
|
491
|
-
本地插件固定放在 `plugins/<id>/dist
|
|
506
|
+
本地插件固定放在 `plugins/<id>/dist`,应用启动和运行时重启时自动扫描,不需要路径参数:
|
|
492
507
|
|
|
493
508
|
```bash
|
|
494
509
|
npm run build
|
|
@@ -534,7 +549,7 @@ npm run build:plugin
|
|
|
534
549
|
node dist/app.js
|
|
535
550
|
```
|
|
536
551
|
|
|
537
|
-
|
|
552
|
+
应用每次加载都会扫描 `plugins/<id>/dist`;带有 `build` 脚本且依赖已安装的插件会先自动构建再加载,构建失败时回退已有 `dist`,没有可用产物才记录警告并跳过。示例见 [`examples/plugins/hello`](../examples/plugins/hello)。
|
|
538
553
|
|
|
539
554
|
## 通过 npm 发布与安装
|
|
540
555
|
|
|
@@ -6,11 +6,11 @@ API 细节(`PluginContext`、命令、事件、中间件、KV、Session 等)
|
|
|
6
6
|
|
|
7
7
|
## 先选插件类型
|
|
8
8
|
|
|
9
|
-
| 类型 | 推荐位置 | 是否进入本仓库 | 加载方式
|
|
10
|
-
| -------- | ------------------- | -------------- |
|
|
11
|
-
| 内置插件 | `src/plugins/<id>/` | 是 | 注册到 `src/plugins/index.ts`
|
|
12
|
-
| 本地插件 | `plugins/<id>/` | 否 |
|
|
13
|
-
| npm 插件 | 独立仓库或独立包 | 独立管理 | `/plugin install <包名>[@版本]`
|
|
9
|
+
| 类型 | 推荐位置 | 是否进入本仓库 | 加载方式 | 适用场景 |
|
|
10
|
+
| -------- | ------------------- | -------------- | -------------------------------------------------- | ---------------------------- |
|
|
11
|
+
| 内置插件 | `src/plugins/<id>/` | 是 | 注册到 `src/plugins/index.ts` | 随内核发布的官方功能 |
|
|
12
|
+
| 本地插件 | `plugins/<id>/` | 否 | 启动和重启时扫描 `plugins/<id>/dist`,每次尝试重建 | 个人使用、内部功能、开发调试 |
|
|
13
|
+
| npm 插件 | 独立仓库或独立包 | 独立管理 | `/plugin install <包名>[@版本]` | 发布给其他用户安装 |
|
|
14
14
|
|
|
15
15
|
位置约定:
|
|
16
16
|
|
|
@@ -39,7 +39,7 @@ plugins/my-plugin/
|
|
|
39
39
|
|
|
40
40
|
- `src/index.ts` 是开发入口。
|
|
41
41
|
- `dist/index.js` 是运行时入口。
|
|
42
|
-
- 构建产物固定放在 `plugins/<id>/dist
|
|
42
|
+
- 构建产物固定放在 `plugins/<id>/dist`,每次加载时自动扫描;只要 `package.json` 中存在 `build` 脚本且依赖已安装,就会先自动重建,不要指向或加载 `src/`。
|
|
43
43
|
- 每个本地插件都是独立包,有自己的 `package.json`、lockfile 和 `node_modules`。
|
|
44
44
|
|
|
45
45
|
### 内置插件
|
|
@@ -113,7 +113,7 @@ npm install --prefix plugins/my-plugin
|
|
|
113
113
|
node dist/app.js
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
应用在启动和每次运行时重启时都会执行 `npm run build --prefix plugins/my-plugin`,前提是已经安装依赖;构建失败或缺少依赖时会回退加载已有 `dist`。也可以手动提前构建:
|
|
117
117
|
|
|
118
118
|
```bash
|
|
119
119
|
npm run build --prefix plugins/my-plugin
|
|
@@ -221,7 +221,7 @@ export default definePlugin({
|
|
|
221
221
|
- `setup()` 只注册命令、事件和中间件;定时器、连接等运行时资源放在 `start()`,并在 `stop()` 中清理。
|
|
222
222
|
- 命令可以用 `params` 声明位置参数;解析结果从 handler 第四个参数开始传入,用法会自动出现在 `/help` 和指令组提示中。详见[插件 API 的参数章节](plugin-api.md#参数)。
|
|
223
223
|
- 多级命令(例如 `/plugin install`)使用 `ctx.commands.group()`,不要直接注册带空格的命令名。详见[插件 API 的命令章节](plugin-api.md#命令)。
|
|
224
|
-
-
|
|
224
|
+
- 修改命令、事件或配置后,自动重启会重新构建并刷新插件入口模块;入口相对导入的辅助模块仍可能命中 ESM 缓存,修改多文件插件时完整重启进程最可靠。
|
|
225
225
|
|
|
226
226
|
### 4. 安装依赖
|
|
227
227
|
|
|
@@ -230,7 +230,7 @@ npm install --prefix plugins/my-plugin
|
|
|
230
230
|
npm run build
|
|
231
231
|
```
|
|
232
232
|
|
|
233
|
-
根目录的 `npm run build`
|
|
233
|
+
根目录的 `npm run build` 构建内核。插件自身在每次加载时都会自动构建;需要提前校验时也可以手动运行 `npm run build --prefix plugins/my-plugin`。
|
|
234
234
|
|
|
235
235
|
根目录的 `npm run build:plugin` / `npm run check:plugin` 会自动发现 `plugins/*` 中包含 `src/`、`tsconfig.json` 且声明了对应 npm script 的目录;没有安装 `node_modules` 的目录会跳过。新增插件不需要修改根 `package.json`,也可以单独运行 `npm run build --prefix plugins/<id>` 和 `npm run check --prefix plugins/<id>`。
|
|
236
236
|
|
|
@@ -242,7 +242,7 @@ npm run watch --prefix plugins/my-plugin
|
|
|
242
242
|
|
|
243
243
|
### 5. 启动并加载
|
|
244
244
|
|
|
245
|
-
本地插件目录固定为 `plugins
|
|
245
|
+
本地插件目录固定为 `plugins/`,相对当前工作目录解析。应用启动和运行时重启时都会自动扫描:
|
|
246
246
|
|
|
247
247
|
```text
|
|
248
248
|
plugins/
|
|
@@ -264,7 +264,9 @@ node dist/app.js
|
|
|
264
264
|
npm run dev
|
|
265
265
|
```
|
|
266
266
|
|
|
267
|
-
|
|
267
|
+
`npm run dev` 会监听内核 `src/` 和本地插件 `plugins/*/src/`,并忽略插件构建产物 `plugins/*/dist/`。修改插件源码会自动重启并重建插件;修改 `dist` 不会触发重启,避免“构建 → 重启 → 再构建”的循环。
|
|
268
|
+
|
|
269
|
+
不需要额外参数;多个本地插件按 `plugins/<id>/dist` 并存。只要带有 `build` 脚本且 `node_modules` 存在,每次加载都会先执行 `npm run build` 再加载;构建失败或缺少依赖时会回退加载已有 `dist`,没有可用 `dist` 时记录警告并跳过。没有 `build` 脚本的插件直接加载现有 `dist`。
|
|
268
270
|
|
|
269
271
|
### 6. 配置插件
|
|
270
272
|
|
|
@@ -295,28 +297,29 @@ npm run dev
|
|
|
295
297
|
- `export const plugins = [...]`
|
|
296
298
|
- 没有导出插件定义的 helper 模块会被跳过。
|
|
297
299
|
- 生产环境应加载编译后的 JavaScript;TypeScript 源码必须先构建,或者在使用 `tsx` 的开发环境中运行。
|
|
298
|
-
-
|
|
299
|
-
- 通过 `/plugin install`
|
|
300
|
+
- 每次加载时,如果插件目录中有 `package.json`、声明了 `scripts.build`,并且 `node_modules` 存在,加载器都会执行 `npm run build` 后再加载;构建失败或缺少依赖时,已有 `dist` 会回退加载,没有可用 `dist` 时只跳过该插件。
|
|
301
|
+
- 通过 `/plugin install` 安装的包和手动放在 `plugins/` 的本地插件使用同一套重建规则;安装包通常自带 `dist`,缺少构建依赖时会回退使用自带产物。
|
|
300
302
|
- 空目录不会报错;如果目录中存在无法导入或无法识别的文件,并且最终没有加载到任何有效插件,启动失败。只要至少有一个有效插件,其他无法识别的文件会记录警告并跳过。
|
|
301
303
|
- 本地插件和在线安装的插件都从 `plugins/<目录>/dist` 自动加载,没有单独的安装目录。
|
|
302
304
|
- 同一个 ID 同时来自内置插件和 `plugins/` 中的多个目录时,只有最先加载的那个生效。
|
|
303
305
|
|
|
304
306
|
## 调试与常见问题
|
|
305
307
|
|
|
306
|
-
| 现象 | 可能原因
|
|
307
|
-
| ---------------------------------------------------- |
|
|
308
|
-
| `skipping local plugin ... dist directory not found` | 没有 `build`
|
|
309
|
-
| `failed to build local plugin ...` | `build` 脚本执行失败或超时
|
|
310
|
-
| `
|
|
311
|
-
| `
|
|
312
|
-
| `
|
|
313
|
-
| `
|
|
314
|
-
| `
|
|
315
|
-
|
|
|
316
|
-
|
|
|
317
|
-
|
|
|
318
|
-
|
|
|
319
|
-
|
|
|
308
|
+
| 现象 | 可能原因 | 处理 |
|
|
309
|
+
| ---------------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
|
|
310
|
+
| `skipping local plugin ... dist directory not found` | 没有 `build` 脚本且缺少 `dist`,或构建失败/缺依赖且无可用旧 `dist` | 执行 `npm install --prefix plugins/<id>`,检查 `scripts.build`,或手动执行 `npm run build --prefix plugins/<id>` |
|
|
311
|
+
| `failed to build local plugin ...` | `build` 脚本执行失败或超时 | 查看 npm 输出和插件源码,修复后重启 |
|
|
312
|
+
| `using existing dist ... rebuild failed` | 重建失败,但旧 `dist` 仍可用 | 检查构建错误;确认插件运行的是预期产物 |
|
|
313
|
+
| `build finished but dist directory not found` | `build` 脚本没有把产物输出到插件的 `dist` 目录 | 检查 `tsconfig.json` 的 `outDir` 和 `scripts.build` |
|
|
314
|
+
| `failed to import ...` | TypeScript 未编译、入口有运行时错误 | 先构建;查看日志中的具体导入错误 |
|
|
315
|
+
| `dist` 目录没有有效插件 | 没有导出 `default` / `plugin` / `plugins` | 检查入口导出和 `dist` 中的构建输出 |
|
|
316
|
+
| `skipping ... plugin id already loaded` | 插件 ID 重复 | 修改 `manifest.id`,或移除重复的插件目录 |
|
|
317
|
+
| `unknown plugin id` | 配置里写了未加载的插件 ID | 启用插件、修正 ID,或删除 `plugins.<id>` 配置 |
|
|
318
|
+
| 命令没有响应 | 插件未启用、权限不足、群组 gating、命令前缀不匹配 | 检查 `plugins.<id>.enabled`、角色、群组配置和命令前缀 |
|
|
319
|
+
| 修改代码后行为没变 | ESM 缓存命中辅助模块,或构建失败回退了旧 `dist` | 检查构建日志;完整退出并重新启动进程 |
|
|
320
|
+
| `Cannot find module '@jsbot-tellus/core'` | 没有安装 devDependency,或 `file:` 依赖尚未构建 | 在插件目录执行 `npm install`;使用 `file:` 时先构建根项目 |
|
|
321
|
+
| `/plugin install` 拒绝包 | 使用了路径、`file:`、git/url、范围版本或未启用安装器 | 改用 npm 包名 + 精确版本或 dist-tag;确认 `installer.enabled` 和 allowlist |
|
|
322
|
+
| 安装完成但提示网络错误 | 安装后的通知发送失败,不代表安装失败 | 用 `/plugin list` 确认;必要时手动重启进程 |
|
|
320
323
|
|
|
321
324
|
注意:安装器在安装或更新成功后会请求重建运行时,但安装后的通知发送失败不会回滚安装。遇到“网络连接异常”时优先检查 `/plugin list` 和 `plugins/.jsbot-installed.json`。
|
|
322
325
|
|
package/docs/release.md
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
`@jsbot-tellus/core` 通过 GitHub Actions 自动发布:
|
|
4
4
|
|
|
5
|
-
- CI 工作流:[`.github/workflows/ci.yaml`](../.github/workflows/ci.yaml)
|
|
6
|
-
- CD 工作流:[`.github/workflows/cd.yaml`](../.github/workflows/cd.yaml),在 `main` 的 CI 成功后发布到 npm
|
|
5
|
+
- CI 工作流:[`.github/workflows/ci.yaml`](../.github/workflows/ci.yaml),负责类型检查、格式检查、依赖审计、测试、构建、smoke 和 Docker 镜像构建校验
|
|
6
|
+
- CD 工作流:[`.github/workflows/cd.yaml`](../.github/workflows/cd.yaml),在 `main` 的 CI 成功后发布到 npm,并构建推送 Docker 镜像
|
|
7
7
|
- 触发方式:推送到 `main`,或在 `main` 上手动运行 `CI`;不需要创建或推送版本标签
|
|
8
8
|
- 发布方式:npm Trusted Publisher(GitHub OIDC),不需要长期 `NPM_TOKEN`
|
|
9
|
-
-
|
|
10
|
-
-
|
|
9
|
+
- Docker 镜像:同时推送 GHCR 和 Docker Hub;使用 `GITHUB_TOKEN` 推送 GHCR,Docker Hub 使用仓库 Secret
|
|
10
|
+
- 幂等处理:`package.json` 中的版本已经存在于 npm 时跳过 npm 发布;同版本镜像已存在于某个镜像仓库时只跳过该仓库
|
|
11
|
+
- 发布产物带 npm provenance;Docker 镜像带 provenance 和 SBOM
|
|
11
12
|
|
|
12
13
|
CD 只会在 CI 结论为成功、且 CI 运行来自 `main` 的 `push` 或 `workflow_dispatch` 时执行。`pull_request` 运行和其他分支不会触发发布。
|
|
13
14
|
|
|
@@ -25,15 +26,25 @@ CD 只会在 CI 结论为成功、且 CI 运行来自 `main` 的 `push` 或 `wor
|
|
|
25
26
|
|
|
26
27
|
工作流文件名必须与实际文件一致。如果以后重命名 `cd.yaml`,需要同步修改 npm 的 Trusted Publisher 配置。
|
|
27
28
|
|
|
29
|
+
Docker Hub 需要在仓库的 **Settings → Secrets and variables → Actions** 中配置:
|
|
30
|
+
|
|
31
|
+
| Secret | 值 |
|
|
32
|
+
| -------------------- | --------------------------------------- |
|
|
33
|
+
| `DOCKERHUB_USERNAME` | Docker Hub 用户名 |
|
|
34
|
+
| `DOCKERHUB_TOKEN` | Docker Hub Access Token(Read & Write) |
|
|
35
|
+
|
|
36
|
+
首次镜像推送后,如果希望匿名拉取,在 GitHub 仓库的 **Packages** 设置里把 `jsbot-tellus` 的可见性改为 public。GHCR 使用工作流自带的 `GITHUB_TOKEN`,不需要额外 Secret。
|
|
37
|
+
|
|
28
38
|
CD 工作流已经声明:
|
|
29
39
|
|
|
30
40
|
```yaml
|
|
31
41
|
permissions:
|
|
32
42
|
contents: read
|
|
33
43
|
id-token: write
|
|
44
|
+
packages: write
|
|
34
45
|
```
|
|
35
46
|
|
|
36
|
-
`id-token: write` 用于 OIDC 和 provenance
|
|
47
|
+
`id-token: write` 用于 npm OIDC 和 provenance,不能删除;`packages: write` 用于推送 GHCR。
|
|
37
48
|
|
|
38
49
|
## 发布新版本
|
|
39
50
|
|
|
@@ -46,15 +57,32 @@ git commit -m "Release v$(node -p "require('./package.json').version")"
|
|
|
46
57
|
git push
|
|
47
58
|
```
|
|
48
59
|
|
|
49
|
-
将 `patch` 换成 `minor` 或 `major` 可以发布对应版本。提交并通过 Pull Request 合并到 `main` 后,CI 会先运行;CI 成功后 CD
|
|
60
|
+
将 `patch` 换成 `minor` 或 `major` 可以发布对应版本。提交并通过 Pull Request 合并到 `main` 后,CI 会先运行;CI 成功后 CD 自动发布该版本到 npm,并构建推送 Docker 镜像。`main` 受分支保护,不能直接推送。
|
|
61
|
+
|
|
62
|
+
如果 `main` 上的版本已经在 npm 存在,CD 只会输出提示并跳过,不会失败。镜像按仓库独立判断:某个仓库已有该版本时跳过该仓库,缺失的仓库会补推。版本提交重复合并不影响已发布版本。
|
|
63
|
+
|
|
64
|
+
## Docker 镜像
|
|
65
|
+
|
|
66
|
+
CD 会推送以下标签:
|
|
67
|
+
|
|
68
|
+
- `latest`
|
|
69
|
+
- 完整版本号,例如 `1.2.3`
|
|
70
|
+
- 主次版本,例如 `1.2`
|
|
71
|
+
- 主版本,例如 `1`
|
|
72
|
+
- `sha-<短哈希>`
|
|
50
73
|
|
|
51
|
-
|
|
74
|
+
镜像地址:
|
|
75
|
+
|
|
76
|
+
- `ghcr.io/jsbot-dev/jsbot-tellus`
|
|
77
|
+
- `docker.io/<DOCKERHUB_USERNAME>/jsbot-tellus`
|
|
78
|
+
|
|
79
|
+
只构建 `linux/amd64`。CI 在每次推送时构建镜像并执行 smoke,但不会推送。使用方式见 [Docker 部署](docker.md)。
|
|
52
80
|
|
|
53
81
|
## 手动运行
|
|
54
82
|
|
|
55
83
|
在 GitHub Actions 中打开 `CI`,选择 `Run workflow`,并选择 `main` 分支。CI 成功后,CD 会像普通推送一样自动发布。
|
|
56
84
|
|
|
57
|
-
如果 CD 因临时故障失败,可以在 Actions 中打开对应的 `CD` 运行并选择 `Re-run failed jobs`,不需要重新推送代码。
|
|
85
|
+
如果 CD 因临时故障失败,可以在 Actions 中打开对应的 `CD` 运行并选择 `Re-run failed jobs`,不需要重新推送代码。Docker job 会重新检查两个镜像仓库,只补推缺失的版本标签。
|
|
58
86
|
|
|
59
87
|
## 验证
|
|
60
88
|
|
|
@@ -72,10 +100,24 @@ npm install -g @jsbot-tellus/core@latest
|
|
|
72
100
|
jsbot --help
|
|
73
101
|
```
|
|
74
102
|
|
|
103
|
+
Docker 镜像验证:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
docker pull ghcr.io/jsbot-dev/jsbot-tellus:latest
|
|
107
|
+
docker run --rm ghcr.io/jsbot-dev/jsbot-tellus:latest --help
|
|
108
|
+
|
|
109
|
+
docker pull <DOCKERHUB_USERNAME>/jsbot-tellus:latest
|
|
110
|
+
docker run --rm <DOCKERHUB_USERNAME>/jsbot-tellus:latest --help
|
|
111
|
+
```
|
|
112
|
+
|
|
75
113
|
## 常见失败
|
|
76
114
|
|
|
77
115
|
- **`ENEEDAUTH` / `E403`**:检查 npm Trusted Publisher 的组织、仓库和工作流文件名是否完全匹配(文件名必须是 `cd.yaml`)。
|
|
78
116
|
- **`npm publish` 提示版本已存在**:说明该版本此前已经发布;提升 `package.json` 版本后重新推送到 `main`。正常情况下 CD 会自动跳过已发布版本。
|
|
79
117
|
- **provenance 失败**:确认 CD 工作流仍有 `id-token: write` 权限,并且仓库是公开的。
|
|
118
|
+
- **Docker Hub 登录失败 / `unauthorized`**:确认 `DOCKERHUB_USERNAME`、`DOCKERHUB_TOKEN` 已配置,Token 有 Read & Write 权限。
|
|
119
|
+
- **GHCR 推送 `denied`**:确认工作流仍有 `packages: write`,并且组织没有禁止 `GITHUB_TOKEN` 创建包。
|
|
120
|
+
- **镜像已存在**:CD 会输出 notice 并跳过对应仓库,不会失败;如果两个仓库都有该版本,提升版本号后再发布。
|
|
121
|
+
- **Docker job 被跳过**:它依赖 npm 发布 job;npm 发布失败或 CD 未在 `main` 触发时不会执行。
|
|
80
122
|
- **CD 没有触发**:确认 `CI` 已在 `main` 上成功完成;`pull_request` 运行和其他分支不会触发发布。
|
|
81
123
|
- **CD 找不到要发布的提交**:确认 `ci.yaml` 和 `cd.yaml` 都已经在默认分支上。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsbot-tellus/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": "^24.22.1 || >=26.12.1"
|
|
6
6
|
},
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"jsbot": "dist/app.js"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
|
-
"dev": "tsx watch src/app.ts",
|
|
47
|
+
"dev": "tsx watch --exclude \"plugins/**/dist/**\" --include \"plugins/*/src/**\" src/app.ts",
|
|
48
48
|
"dev:webui": "vite --config webui/vite.config.ts",
|
|
49
49
|
"start": "node dist/app.js",
|
|
50
50
|
"start:build": "npm run build && npm start",
|
|
@@ -11,17 +11,42 @@ import type {
|
|
|
11
11
|
CommandInfo,
|
|
12
12
|
CommandMatch,
|
|
13
13
|
CommandOptions,
|
|
14
|
+
MessageType,
|
|
15
|
+
MessageTypeFilter,
|
|
14
16
|
} from '../plugin/types.js'
|
|
15
17
|
import type { Permission } from '../security/permission.js'
|
|
16
18
|
|
|
17
19
|
type AnyCommandHandler = CommandHandler<OneBotMessageEvent, any[]>
|
|
18
20
|
|
|
21
|
+
const DEFAULT_MESSAGE_TYPES: MessageType[] = ['group', 'private']
|
|
22
|
+
|
|
23
|
+
export function normalizeMessageTypes(
|
|
24
|
+
value: MessageTypeFilter | undefined
|
|
25
|
+
): MessageType[] {
|
|
26
|
+
if (value === undefined) return [...DEFAULT_MESSAGE_TYPES]
|
|
27
|
+
|
|
28
|
+
const values = typeof value === 'string' ? [value] : value
|
|
29
|
+
const result: MessageType[] = []
|
|
30
|
+
|
|
31
|
+
for (const item of values) {
|
|
32
|
+
if (
|
|
33
|
+
(item === 'group' || item === 'private') &&
|
|
34
|
+
!result.includes(item)
|
|
35
|
+
) {
|
|
36
|
+
result.push(item)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return result.length > 0 ? result : [...DEFAULT_MESSAGE_TYPES]
|
|
41
|
+
}
|
|
42
|
+
|
|
19
43
|
export interface CommandRegistration {
|
|
20
44
|
pluginId: string
|
|
21
45
|
name: string
|
|
22
46
|
aliases: string[]
|
|
23
47
|
prefixes: string[]
|
|
24
48
|
permission: Permission
|
|
49
|
+
messageTypes: MessageType[]
|
|
25
50
|
description?: string | undefined
|
|
26
51
|
params?: string | undefined
|
|
27
52
|
paramsParser?: CompiledCommandParams | undefined
|
|
@@ -35,6 +60,7 @@ export interface CommandGroupRegistration {
|
|
|
35
60
|
aliases: string[]
|
|
36
61
|
prefixes: string[]
|
|
37
62
|
permission: Permission
|
|
63
|
+
messageTypes: MessageType[]
|
|
38
64
|
description?: string | undefined
|
|
39
65
|
params?: string | undefined
|
|
40
66
|
handler?: AnyCommandHandler | undefined
|
|
@@ -188,6 +214,7 @@ export class CommandRegistry {
|
|
|
188
214
|
bases,
|
|
189
215
|
prefixes,
|
|
190
216
|
registration.permission,
|
|
217
|
+
registration.messageTypes,
|
|
191
218
|
children
|
|
192
219
|
)
|
|
193
220
|
|
|
@@ -199,6 +226,7 @@ export class CommandRegistry {
|
|
|
199
226
|
bases,
|
|
200
227
|
prefixes,
|
|
201
228
|
registration.permission,
|
|
229
|
+
registration.messageTypes,
|
|
202
230
|
registration.description,
|
|
203
231
|
registration.params,
|
|
204
232
|
registration.handler,
|
|
@@ -253,6 +281,7 @@ export class CommandRegistry {
|
|
|
253
281
|
name: registration.name,
|
|
254
282
|
aliases: [...registration.aliases],
|
|
255
283
|
permission: registration.permission,
|
|
284
|
+
messageTypes: [...registration.messageTypes],
|
|
256
285
|
...(registration.description !== undefined
|
|
257
286
|
? { description: registration.description }
|
|
258
287
|
: {}),
|
|
@@ -263,7 +292,10 @@ export class CommandRegistry {
|
|
|
263
292
|
}))
|
|
264
293
|
}
|
|
265
294
|
|
|
266
|
-
match(
|
|
295
|
+
match(
|
|
296
|
+
rawMessage: string,
|
|
297
|
+
messageType?: MessageType
|
|
298
|
+
): RegisteredCommand | null {
|
|
267
299
|
const raw = stripLeadingReply(rawMessage.trim()).trim()
|
|
268
300
|
|
|
269
301
|
if (!raw) return null
|
|
@@ -298,6 +330,13 @@ export class CommandRegistry {
|
|
|
298
330
|
|
|
299
331
|
if (!registration) continue
|
|
300
332
|
|
|
333
|
+
if (
|
|
334
|
+
messageType !== undefined &&
|
|
335
|
+
!registration.messageTypes.includes(messageType)
|
|
336
|
+
) {
|
|
337
|
+
continue
|
|
338
|
+
}
|
|
339
|
+
|
|
301
340
|
const start = first.index ?? 0
|
|
302
341
|
const end = (last.index ?? 0) + last[0].length
|
|
303
342
|
|
|
@@ -404,6 +443,7 @@ export class CommandRegistry {
|
|
|
404
443
|
bases: string[],
|
|
405
444
|
prefixes: string[],
|
|
406
445
|
permission: Permission,
|
|
446
|
+
messageTypes: MessageType[],
|
|
407
447
|
children: GroupChild[]
|
|
408
448
|
): GroupScope {
|
|
409
449
|
const disposers: Array<() => void> = []
|
|
@@ -464,6 +504,10 @@ export class CommandRegistry {
|
|
|
464
504
|
aliases: childBases.slice(1),
|
|
465
505
|
prefixes: options.prefixes ?? prefixes,
|
|
466
506
|
permission: options.permission ?? permission,
|
|
507
|
+
messageTypes:
|
|
508
|
+
options.messageType !== undefined
|
|
509
|
+
? normalizeMessageTypes(options.messageType)
|
|
510
|
+
: [...messageTypes],
|
|
467
511
|
...(options.description !== undefined
|
|
468
512
|
? { description: options.description }
|
|
469
513
|
: {}),
|
|
@@ -506,12 +550,17 @@ export class CommandRegistry {
|
|
|
506
550
|
)
|
|
507
551
|
const nestedPrefixes = options.prefixes ?? prefixes
|
|
508
552
|
const nestedPermission = options.permission ?? permission
|
|
553
|
+
const nestedMessageTypes =
|
|
554
|
+
options.messageType !== undefined
|
|
555
|
+
? normalizeMessageTypes(options.messageType)
|
|
556
|
+
: [...messageTypes]
|
|
509
557
|
const nestedChildren: GroupChild[] = []
|
|
510
558
|
const nestedScope = this.createGroupScope(
|
|
511
559
|
pluginId,
|
|
512
560
|
nestedBases,
|
|
513
561
|
nestedPrefixes,
|
|
514
562
|
nestedPermission,
|
|
563
|
+
nestedMessageTypes,
|
|
515
564
|
nestedChildren
|
|
516
565
|
)
|
|
517
566
|
|
|
@@ -523,6 +572,7 @@ export class CommandRegistry {
|
|
|
523
572
|
nestedBases,
|
|
524
573
|
nestedPrefixes,
|
|
525
574
|
nestedPermission,
|
|
575
|
+
nestedMessageTypes,
|
|
526
576
|
options.description,
|
|
527
577
|
options.params,
|
|
528
578
|
options.handler,
|
|
@@ -567,6 +617,7 @@ export class CommandRegistry {
|
|
|
567
617
|
bases: string[],
|
|
568
618
|
prefixes: string[],
|
|
569
619
|
permission: Permission,
|
|
620
|
+
messageTypes: MessageType[],
|
|
570
621
|
description: string | undefined,
|
|
571
622
|
params: string | undefined,
|
|
572
623
|
handler: AnyCommandHandler | undefined,
|
|
@@ -584,6 +635,7 @@ export class CommandRegistry {
|
|
|
584
635
|
aliases: bases.slice(1),
|
|
585
636
|
prefixes,
|
|
586
637
|
permission,
|
|
638
|
+
messageTypes: [...messageTypes],
|
|
587
639
|
...(description !== undefined ? { description } : {}),
|
|
588
640
|
...(params !== undefined ? { params } : {}),
|
|
589
641
|
handler: handler ?? createGroupUsageHandler(children),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
OneBotMessageEvent,
|
|
2
3
|
OneBotNoticeEvent,
|
|
3
4
|
OneBotRequestEvent,
|
|
4
5
|
SnowLumaEvent,
|
|
@@ -9,6 +10,7 @@ import type {
|
|
|
9
10
|
EventContext,
|
|
10
11
|
EventType,
|
|
11
12
|
MessageHandler,
|
|
13
|
+
MessageType,
|
|
12
14
|
NoticeHandler,
|
|
13
15
|
RequestHandler,
|
|
14
16
|
} from '../plugin/types.js'
|
|
@@ -16,6 +18,7 @@ import type { MessageSender } from '../messaging/sender.js'
|
|
|
16
18
|
|
|
17
19
|
export interface MessageRegistration {
|
|
18
20
|
pluginId: string
|
|
21
|
+
messageType?: MessageType | undefined
|
|
19
22
|
handler: MessageHandler
|
|
20
23
|
}
|
|
21
24
|
|
|
@@ -96,9 +99,14 @@ export class HandlerRegistry {
|
|
|
96
99
|
private noticeRegistrations: NoticeRegistration[] = []
|
|
97
100
|
private requestRegistrations: RequestRegistration[] = []
|
|
98
101
|
|
|
99
|
-
registerMessage(
|
|
102
|
+
registerMessage(
|
|
103
|
+
pluginId: string,
|
|
104
|
+
handler: MessageHandler,
|
|
105
|
+
messageType?: MessageType
|
|
106
|
+
): () => void {
|
|
100
107
|
const registration: MessageRegistration = {
|
|
101
108
|
pluginId,
|
|
109
|
+
...(messageType !== undefined ? { messageType } : {}),
|
|
102
110
|
handler,
|
|
103
111
|
}
|
|
104
112
|
|
|
@@ -156,6 +164,16 @@ export class HandlerRegistry {
|
|
|
156
164
|
}
|
|
157
165
|
}
|
|
158
166
|
|
|
167
|
+
export function isMessageOf(
|
|
168
|
+
event: OneBotMessageEvent,
|
|
169
|
+
registration: MessageRegistration
|
|
170
|
+
): boolean {
|
|
171
|
+
return (
|
|
172
|
+
registration.messageType === undefined ||
|
|
173
|
+
registration.messageType === event.message_type
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
159
177
|
export function isNoticeOf(
|
|
160
178
|
event: OneBotNoticeEvent,
|
|
161
179
|
registration: NoticeRegistration
|
|
@@ -15,6 +15,8 @@ import type {
|
|
|
15
15
|
CommandInfo,
|
|
16
16
|
EventType,
|
|
17
17
|
MessageHandler,
|
|
18
|
+
MessageType,
|
|
19
|
+
MessageTypeFilter,
|
|
18
20
|
MiddlewareOptions,
|
|
19
21
|
NoticeHandler,
|
|
20
22
|
NoticeOptions,
|
|
@@ -27,6 +29,7 @@ import { PermissionRefuser } from '../security/refuse.js'
|
|
|
27
29
|
import type { Permission } from '../security/permission.js'
|
|
28
30
|
import {
|
|
29
31
|
CommandRegistry,
|
|
32
|
+
normalizeMessageTypes,
|
|
30
33
|
type CommandGroupRegistration,
|
|
31
34
|
type CommandRegistration,
|
|
32
35
|
} from './command.js'
|
|
@@ -34,6 +37,7 @@ import {
|
|
|
34
37
|
createEventContext,
|
|
35
38
|
eventTypeOf,
|
|
36
39
|
HandlerRegistry,
|
|
40
|
+
isMessageOf,
|
|
37
41
|
isNoticeOf,
|
|
38
42
|
isRequestOf,
|
|
39
43
|
} from './handler.js'
|
|
@@ -64,6 +68,7 @@ export interface RegisterCommandOptions {
|
|
|
64
68
|
aliases?: string[] | undefined
|
|
65
69
|
prefixes?: string[] | undefined
|
|
66
70
|
permission?: Permission | undefined
|
|
71
|
+
messageType?: MessageTypeFilter | undefined
|
|
67
72
|
description?: string | undefined
|
|
68
73
|
params?: string | undefined
|
|
69
74
|
handler: AnyCommandHandler
|
|
@@ -75,6 +80,7 @@ export interface RegisterCommandGroupOptions {
|
|
|
75
80
|
aliases?: string[] | undefined
|
|
76
81
|
prefixes?: string[] | undefined
|
|
77
82
|
permission?: Permission | undefined
|
|
83
|
+
messageType?: MessageTypeFilter | undefined
|
|
78
84
|
description?: string | undefined
|
|
79
85
|
params?: string | undefined
|
|
80
86
|
handler?: AnyCommandHandler | undefined
|
|
@@ -160,7 +166,10 @@ export class Router {
|
|
|
160
166
|
if (context.stopped) return
|
|
161
167
|
|
|
162
168
|
if (isMessageEvent(event)) {
|
|
163
|
-
const found = this.commands.match(
|
|
169
|
+
const found = this.commands.match(
|
|
170
|
+
event.raw_message,
|
|
171
|
+
event.message_type
|
|
172
|
+
)
|
|
164
173
|
|
|
165
174
|
if (found) {
|
|
166
175
|
sdkContext.stopPropagation()
|
|
@@ -257,6 +266,7 @@ export class Router {
|
|
|
257
266
|
aliases: options.aliases ?? [],
|
|
258
267
|
prefixes: options.prefixes ?? ['/'],
|
|
259
268
|
permission: options.permission ?? 'User',
|
|
269
|
+
messageTypes: normalizeMessageTypes(options.messageType),
|
|
260
270
|
...(options.description !== undefined
|
|
261
271
|
? { description: options.description }
|
|
262
272
|
: {}),
|
|
@@ -277,6 +287,7 @@ export class Router {
|
|
|
277
287
|
aliases: options.aliases ?? [],
|
|
278
288
|
prefixes: options.prefixes ?? ['/'],
|
|
279
289
|
permission: options.permission ?? 'User',
|
|
290
|
+
messageTypes: normalizeMessageTypes(options.messageType),
|
|
280
291
|
...(options.description !== undefined
|
|
281
292
|
? { description: options.description }
|
|
282
293
|
: {}),
|
|
@@ -289,8 +300,12 @@ export class Router {
|
|
|
289
300
|
return this.commands.group(registration, setup)
|
|
290
301
|
}
|
|
291
302
|
|
|
292
|
-
registerMessage(
|
|
293
|
-
|
|
303
|
+
registerMessage(
|
|
304
|
+
pluginId: string,
|
|
305
|
+
handler: MessageHandler,
|
|
306
|
+
messageType?: MessageType
|
|
307
|
+
): () => void {
|
|
308
|
+
return this.handlers.registerMessage(pluginId, handler, messageType)
|
|
294
309
|
}
|
|
295
310
|
|
|
296
311
|
registerNotice(options: RegisterNoticeOptions): () => void {
|
|
@@ -431,6 +446,8 @@ export class Router {
|
|
|
431
446
|
for (const registration of this.handlers.messages()) {
|
|
432
447
|
if (messageContext.stopped) return
|
|
433
448
|
|
|
449
|
+
if (!isMessageOf(event, registration)) continue
|
|
450
|
+
|
|
434
451
|
if (!this.gate.isEnabled(registration.pluginId, event)) {
|
|
435
452
|
continue
|
|
436
453
|
}
|
package/src/core/index.ts
CHANGED
|
@@ -30,8 +30,11 @@ export type {
|
|
|
30
30
|
EventContext,
|
|
31
31
|
EventRegistrar,
|
|
32
32
|
EventType,
|
|
33
|
+
GroupMessageHandler,
|
|
33
34
|
InstalledPackageInfo,
|
|
34
35
|
MessageHandler,
|
|
36
|
+
MessageType,
|
|
37
|
+
MessageTypeFilter,
|
|
35
38
|
MiddlewareOptions,
|
|
36
39
|
MiddlewareRegistrar,
|
|
37
40
|
NoticeHandler,
|
|
@@ -46,6 +49,7 @@ export type {
|
|
|
46
49
|
PluginStatus,
|
|
47
50
|
PluginStatusEntry,
|
|
48
51
|
PluginUpdateResult,
|
|
52
|
+
PrivateMessageHandler,
|
|
49
53
|
RequestHandler,
|
|
50
54
|
RequestOptions,
|
|
51
55
|
} from './plugin/types.js'
|