@yunzai-ng/core 0.1.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/LICENSE +661 -0
- package/README.md +51 -0
- package/dist/adapter/accounts.d.ts +174 -0
- package/dist/adapter/accounts.d.ts.map +1 -0
- package/dist/adapter/accounts.js +653 -0
- package/dist/adapter/accounts.js.map +1 -0
- package/dist/adapter/bots.d.ts +142 -0
- package/dist/adapter/bots.d.ts.map +1 -0
- package/dist/adapter/bots.js +454 -0
- package/dist/adapter/bots.js.map +1 -0
- package/dist/adapter/host.d.ts +114 -0
- package/dist/adapter/host.d.ts.map +1 -0
- package/dist/adapter/host.js +90 -0
- package/dist/adapter/host.js.map +1 -0
- package/dist/adapter/login.d.ts +174 -0
- package/dist/adapter/login.d.ts.map +1 -0
- package/dist/adapter/login.js +345 -0
- package/dist/adapter/login.js.map +1 -0
- package/dist/adapter/registry.d.ts +102 -0
- package/dist/adapter/registry.d.ts.map +1 -0
- package/dist/adapter/registry.js +150 -0
- package/dist/adapter/registry.js.map +1 -0
- package/dist/config/core-config.d.ts +151 -0
- package/dist/config/core-config.d.ts.map +1 -0
- package/dist/config/core-config.js +338 -0
- package/dist/config/core-config.js.map +1 -0
- package/dist/config/schema.d.ts +449 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +871 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/store.d.ts +184 -0
- package/dist/config/store.d.ts.map +1 -0
- package/dist/config/store.js +428 -0
- package/dist/config/store.js.map +1 -0
- package/dist/config/yaml.d.ts +23 -0
- package/dist/config/yaml.d.ts.map +1 -0
- package/dist/config/yaml.js +108 -0
- package/dist/config/yaml.js.map +1 -0
- package/dist/http/client.d.ts +82 -0
- package/dist/http/client.d.ts.map +1 -0
- package/dist/http/client.js +658 -0
- package/dist/http/client.js.map +1 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +94 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel/app.d.ts +244 -0
- package/dist/kernel/app.d.ts.map +1 -0
- package/dist/kernel/app.js +654 -0
- package/dist/kernel/app.js.map +1 -0
- package/dist/kernel/kv-sink.d.ts +34 -0
- package/dist/kernel/kv-sink.d.ts.map +1 -0
- package/dist/kernel/kv-sink.js +25 -0
- package/dist/kernel/kv-sink.js.map +1 -0
- package/dist/kernel/policy.d.ts +77 -0
- package/dist/kernel/policy.d.ts.map +1 -0
- package/dist/kernel/policy.js +99 -0
- package/dist/kernel/policy.js.map +1 -0
- package/dist/kernel/runtime.d.ts +117 -0
- package/dist/kernel/runtime.d.ts.map +1 -0
- package/dist/kernel/runtime.js +197 -0
- package/dist/kernel/runtime.js.map +1 -0
- package/dist/kernel/sql-sink.d.ts +36 -0
- package/dist/kernel/sql-sink.d.ts.map +1 -0
- package/dist/kernel/sql-sink.js +114 -0
- package/dist/kernel/sql-sink.js.map +1 -0
- package/dist/logger/format.d.ts +67 -0
- package/dist/logger/format.d.ts.map +1 -0
- package/dist/logger/format.js +218 -0
- package/dist/logger/format.js.map +1 -0
- package/dist/logger/index.d.ts +97 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +363 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/rotate.d.ts +47 -0
- package/dist/logger/rotate.d.ts.map +1 -0
- package/dist/logger/rotate.js +242 -0
- package/dist/logger/rotate.js.map +1 -0
- package/dist/message/segment.d.ts +255 -0
- package/dist/message/segment.d.ts.map +1 -0
- package/dist/message/segment.js +510 -0
- package/dist/message/segment.js.map +1 -0
- package/dist/message/split.d.ts +34 -0
- package/dist/message/split.d.ts.map +1 -0
- package/dist/message/split.js +79 -0
- package/dist/message/split.js.map +1 -0
- package/dist/message/target.d.ts +29 -0
- package/dist/message/target.d.ts.map +1 -0
- package/dist/message/target.js +34 -0
- package/dist/message/target.js.map +1 -0
- package/dist/pipeline/cooldown.d.ts +84 -0
- package/dist/pipeline/cooldown.d.ts.map +1 -0
- package/dist/pipeline/cooldown.js +94 -0
- package/dist/pipeline/cooldown.js.map +1 -0
- package/dist/pipeline/dispatch.d.ts +98 -0
- package/dist/pipeline/dispatch.d.ts.map +1 -0
- package/dist/pipeline/dispatch.js +323 -0
- package/dist/pipeline/dispatch.js.map +1 -0
- package/dist/pipeline/event.d.ts +130 -0
- package/dist/pipeline/event.d.ts.map +1 -0
- package/dist/pipeline/event.js +538 -0
- package/dist/pipeline/event.js.map +1 -0
- package/dist/pipeline/middleware.d.ts +63 -0
- package/dist/pipeline/middleware.d.ts.map +1 -0
- package/dist/pipeline/middleware.js +174 -0
- package/dist/pipeline/middleware.js.map +1 -0
- package/dist/pipeline/prompt.d.ts +80 -0
- package/dist/pipeline/prompt.d.ts.map +1 -0
- package/dist/pipeline/prompt.js +155 -0
- package/dist/pipeline/prompt.js.map +1 -0
- package/dist/pipeline/router.d.ts +98 -0
- package/dist/pipeline/router.d.ts.map +1 -0
- package/dist/pipeline/router.js +489 -0
- package/dist/pipeline/router.js.map +1 -0
- package/dist/platform/detect.d.ts +24 -0
- package/dist/platform/detect.d.ts.map +1 -0
- package/dist/platform/detect.js +178 -0
- package/dist/platform/detect.js.map +1 -0
- package/dist/platform/paths.d.ts +38 -0
- package/dist/platform/paths.d.ts.map +1 -0
- package/dist/platform/paths.js +129 -0
- package/dist/platform/paths.js.map +1 -0
- package/dist/platform/system.d.ts +71 -0
- package/dist/platform/system.d.ts.map +1 -0
- package/dist/platform/system.js +242 -0
- package/dist/platform/system.js.map +1 -0
- package/dist/plugin/context.d.ts +92 -0
- package/dist/plugin/context.d.ts.map +1 -0
- package/dist/plugin/context.js +573 -0
- package/dist/plugin/context.js.map +1 -0
- package/dist/plugin/define.d.ts +92 -0
- package/dist/plugin/define.d.ts.map +1 -0
- package/dist/plugin/define.js +82 -0
- package/dist/plugin/define.js.map +1 -0
- package/dist/plugin/discover.d.ts +106 -0
- package/dist/plugin/discover.d.ts.map +1 -0
- package/dist/plugin/discover.js +254 -0
- package/dist/plugin/discover.js.map +1 -0
- package/dist/plugin/events.d.ts +109 -0
- package/dist/plugin/events.d.ts.map +1 -0
- package/dist/plugin/events.js +209 -0
- package/dist/plugin/events.js.map +1 -0
- package/dist/plugin/hooks.d.ts +266 -0
- package/dist/plugin/hooks.d.ts.map +1 -0
- package/dist/plugin/hooks.js +84 -0
- package/dist/plugin/hooks.js.map +1 -0
- package/dist/plugin/host.d.ts +160 -0
- package/dist/plugin/host.d.ts.map +1 -0
- package/dist/plugin/host.js +546 -0
- package/dist/plugin/host.js.map +1 -0
- package/dist/plugin/market.d.ts +264 -0
- package/dist/plugin/market.d.ts.map +1 -0
- package/dist/plugin/market.js +700 -0
- package/dist/plugin/market.js.map +1 -0
- package/dist/plugin/services.d.ts +116 -0
- package/dist/plugin/services.d.ts.map +1 -0
- package/dist/plugin/services.js +213 -0
- package/dist/plugin/services.js.map +1 -0
- package/dist/plugin/tar.d.ts +65 -0
- package/dist/plugin/tar.d.ts.map +1 -0
- package/dist/plugin/tar.js +277 -0
- package/dist/plugin/tar.js.map +1 -0
- package/dist/render/registry.d.ts +127 -0
- package/dist/render/registry.d.ts.map +1 -0
- package/dist/render/registry.js +306 -0
- package/dist/render/registry.js.map +1 -0
- package/dist/scheduler/index.d.ts +67 -0
- package/dist/scheduler/index.d.ts.map +1 -0
- package/dist/scheduler/index.js +279 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/server/api.d.ts +179 -0
- package/dist/server/api.d.ts.map +1 -0
- package/dist/server/api.js +602 -0
- package/dist/server/api.js.map +1 -0
- package/dist/server/auth.d.ts +83 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/auth.js +178 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/browse.d.ts +60 -0
- package/dist/server/browse.d.ts.map +1 -0
- package/dist/server/browse.js +187 -0
- package/dist/server/browse.js.map +1 -0
- package/dist/server/files.d.ts +27 -0
- package/dist/server/files.d.ts.map +1 -0
- package/dist/server/files.js +76 -0
- package/dist/server/files.js.map +1 -0
- package/dist/server/index.d.ts +189 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +1123 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/table.d.ts +105 -0
- package/dist/server/table.d.ts.map +1 -0
- package/dist/server/table.js +289 -0
- package/dist/server/table.js.map +1 -0
- package/dist/store/index.d.ts +50 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +120 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/json.d.ts +73 -0
- package/dist/store/json.d.ts.map +1 -0
- package/dist/store/json.js +174 -0
- package/dist/store/json.js.map +1 -0
- package/dist/store/kv.d.ts +126 -0
- package/dist/store/kv.d.ts.map +1 -0
- package/dist/store/kv.js +232 -0
- package/dist/store/kv.js.map +1 -0
- package/dist/store/level.d.ts +103 -0
- package/dist/store/level.d.ts.map +1 -0
- package/dist/store/level.js +119 -0
- package/dist/store/level.js.map +1 -0
- package/dist/store/memory.d.ts +61 -0
- package/dist/store/memory.d.ts.map +1 -0
- package/dist/store/memory.js +94 -0
- package/dist/store/memory.js.map +1 -0
- package/dist/store/sql.d.ts +142 -0
- package/dist/store/sql.d.ts.map +1 -0
- package/dist/store/sql.js +246 -0
- package/dist/store/sql.js.map +1 -0
- package/dist/testing/fake.d.ts +109 -0
- package/dist/testing/fake.d.ts.map +1 -0
- package/dist/testing/fake.js +196 -0
- package/dist/testing/fake.js.map +1 -0
- package/dist/testing/index.d.ts +16 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +16 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/mock-adapter.d.ts +205 -0
- package/dist/testing/mock-adapter.d.ts.map +1 -0
- package/dist/testing/mock-adapter.js +309 -0
- package/dist/testing/mock-adapter.js.map +1 -0
- package/dist/util/deep.d.ts +108 -0
- package/dist/util/deep.d.ts.map +1 -0
- package/dist/util/deep.js +275 -0
- package/dist/util/deep.js.map +1 -0
- package/dist/util/defer.d.ts +107 -0
- package/dist/util/defer.d.ts.map +1 -0
- package/dist/util/defer.js +163 -0
- package/dist/util/defer.js.map +1 -0
- package/dist/util/dispose.d.ts +88 -0
- package/dist/util/dispose.d.ts.map +1 -0
- package/dist/util/dispose.js +143 -0
- package/dist/util/dispose.js.map +1 -0
- package/dist/util/duration.d.ts +28 -0
- package/dist/util/duration.d.ts.map +1 -0
- package/dist/util/duration.js +77 -0
- package/dist/util/duration.js.map +1 -0
- package/dist/util/fs.d.ts +110 -0
- package/dist/util/fs.d.ts.map +1 -0
- package/dist/util/fs.js +269 -0
- package/dist/util/fs.js.map +1 -0
- package/dist/util/id.d.ts +49 -0
- package/dist/util/id.d.ts.map +1 -0
- package/dist/util/id.js +87 -0
- package/dist/util/id.js.map +1 -0
- package/dist/util/lru.d.ts +74 -0
- package/dist/util/lru.d.ts.map +1 -0
- package/dist/util/lru.js +156 -0
- package/dist/util/lru.js.map +1 -0
- package/dist/util/queue.d.ts +64 -0
- package/dist/util/queue.d.ts.map +1 -0
- package/dist/util/queue.js +147 -0
- package/dist/util/queue.js.map +1 -0
- package/dist/util/text.d.ts +86 -0
- package/dist/util/text.d.ts.map +1 -0
- package/dist/util/text.js +182 -0
- package/dist/util/text.js.map +1 -0
- package/package.json +44 -0
- package/src/adapter/accounts.ts +758 -0
- package/src/adapter/bots.ts +582 -0
- package/src/adapter/host.ts +224 -0
- package/src/adapter/login.ts +481 -0
- package/src/adapter/registry.ts +202 -0
- package/src/config/core-config.test.ts +60 -0
- package/src/config/core-config.ts +401 -0
- package/src/config/schema.test.ts +179 -0
- package/src/config/schema.ts +1036 -0
- package/src/config/store.test.ts +245 -0
- package/src/config/store.ts +506 -0
- package/src/config/yaml.ts +119 -0
- package/src/http/client.test.ts +568 -0
- package/src/http/client.ts +777 -0
- package/src/index.ts +106 -0
- package/src/kernel/app.test.ts +267 -0
- package/src/kernel/app.ts +843 -0
- package/src/kernel/kv-sink.ts +58 -0
- package/src/kernel/policy.ts +120 -0
- package/src/kernel/runtime.test.ts +542 -0
- package/src/kernel/runtime.ts +307 -0
- package/src/kernel/sql-sink.ts +154 -0
- package/src/logger/format.ts +259 -0
- package/src/logger/index.ts +427 -0
- package/src/logger/rotate.ts +265 -0
- package/src/message/segment.test.ts +335 -0
- package/src/message/segment.ts +569 -0
- package/src/message/split.ts +97 -0
- package/src/message/target.ts +48 -0
- package/src/pipeline/cooldown.ts +136 -0
- package/src/pipeline/dispatch.ts +419 -0
- package/src/pipeline/event.ts +735 -0
- package/src/pipeline/middleware.test.ts +426 -0
- package/src/pipeline/middleware.ts +199 -0
- package/src/pipeline/prompt.ts +212 -0
- package/src/pipeline/router.test.ts +432 -0
- package/src/pipeline/router.ts +575 -0
- package/src/platform/detect.ts +178 -0
- package/src/platform/paths.ts +147 -0
- package/src/platform/system.test.ts +102 -0
- package/src/platform/system.ts +289 -0
- package/src/plugin/context.test.ts +459 -0
- package/src/plugin/context.ts +757 -0
- package/src/plugin/define.test.ts +120 -0
- package/src/plugin/define.ts +142 -0
- package/src/plugin/discover.test.ts +253 -0
- package/src/plugin/discover.ts +353 -0
- package/src/plugin/events.test.ts +162 -0
- package/src/plugin/events.ts +266 -0
- package/src/plugin/hooks.ts +373 -0
- package/src/plugin/host.test.ts +696 -0
- package/src/plugin/host.ts +738 -0
- package/src/plugin/market.test.ts +781 -0
- package/src/plugin/market.ts +870 -0
- package/src/plugin/services.test.ts +135 -0
- package/src/plugin/services.ts +261 -0
- package/src/plugin/tar.test.ts +158 -0
- package/src/plugin/tar.ts +309 -0
- package/src/render/registry.test.ts +127 -0
- package/src/render/registry.ts +439 -0
- package/src/scheduler/index.ts +357 -0
- package/src/server/api.test.ts +802 -0
- package/src/server/api.ts +869 -0
- package/src/server/auth.ts +201 -0
- package/src/server/browse.test.ts +193 -0
- package/src/server/browse.ts +240 -0
- package/src/server/files.ts +97 -0
- package/src/server/index.test.ts +566 -0
- package/src/server/index.ts +1386 -0
- package/src/server/table.ts +351 -0
- package/src/store/index.ts +156 -0
- package/src/store/json.ts +198 -0
- package/src/store/kv.test.ts +307 -0
- package/src/store/kv.ts +256 -0
- package/src/store/level.ts +167 -0
- package/src/store/memory.ts +109 -0
- package/src/store/sql.test.ts +204 -0
- package/src/store/sql.ts +324 -0
- package/src/testing/fake.ts +297 -0
- package/src/testing/index.ts +15 -0
- package/src/testing/mock-adapter.ts +588 -0
- package/src/util/deep.ts +272 -0
- package/src/util/defer.ts +211 -0
- package/src/util/dispose.ts +169 -0
- package/src/util/duration.ts +82 -0
- package/src/util/fs.ts +279 -0
- package/src/util/id.ts +94 -0
- package/src/util/lru.ts +185 -0
- package/src/util/queue.ts +167 -0
- package/src/util/text.ts +189 -0
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模块职责:构造插件上下文(`ctx`)——"一切皆可为插件"的落地实现
|
|
3
|
+
* 依赖方向:依赖 plugin/{hooks,events,services}、util/*、类型包;**不依赖任何子系统实现**
|
|
4
|
+
* 生命周期:每个插件实例一个 ctx,随插件卸载整体失效
|
|
5
|
+
* 注意事项:三条硬规则 ——
|
|
6
|
+
*
|
|
7
|
+
* **零全局变量。** 全部能力须经 ctx 上的具名方法获取,故「某插件用了哪些能力」
|
|
8
|
+
* 可枚举,卸载时可确定性回收。
|
|
9
|
+
*
|
|
10
|
+
* **每个注册型方法都把 disposer 登记进 DisposalRegistry。** 插件作者无从遗漏清理 ——
|
|
11
|
+
* 否则改十次代码就有十份定时器同时在跑。
|
|
12
|
+
*
|
|
13
|
+
* **路径不做算术。** `ctx.resource()` 与模板根由内核依据插件根计算,`safeJoin` 拦
|
|
14
|
+
* `../` 越界;手工拼相对路径会在目录层级一变时给出空白页面。
|
|
15
|
+
*/
|
|
16
|
+
import { join } from "node:path"
|
|
17
|
+
import type {
|
|
18
|
+
AdapterProvider,
|
|
19
|
+
AppView,
|
|
20
|
+
Awaitable,
|
|
21
|
+
CommandBuilder,
|
|
22
|
+
CommandHandler,
|
|
23
|
+
CommandOptions,
|
|
24
|
+
CommandPattern,
|
|
25
|
+
ConfigHandle,
|
|
26
|
+
ContactCache,
|
|
27
|
+
ContactCacheOptions,
|
|
28
|
+
CoreEventMap,
|
|
29
|
+
Disposer,
|
|
30
|
+
DurationLike,
|
|
31
|
+
HttpClient,
|
|
32
|
+
HttpMethod,
|
|
33
|
+
KvDriver,
|
|
34
|
+
KvNamespace,
|
|
35
|
+
Logger,
|
|
36
|
+
MessageScene,
|
|
37
|
+
Middleware,
|
|
38
|
+
MiddlewareOptions,
|
|
39
|
+
PluginContext,
|
|
40
|
+
RenderOptions,
|
|
41
|
+
RenderablePage,
|
|
42
|
+
RenderedImage,
|
|
43
|
+
RendererProvider,
|
|
44
|
+
RouteHandler,
|
|
45
|
+
RouteOptions,
|
|
46
|
+
SqlHandle,
|
|
47
|
+
TaskFn,
|
|
48
|
+
TaskOptions,
|
|
49
|
+
WebSocketHandler,
|
|
50
|
+
WebSocketOptions,
|
|
51
|
+
BotApi,
|
|
52
|
+
CooldownScope,
|
|
53
|
+
ImageSegment
|
|
54
|
+
} from "@yunzai-ng/types"
|
|
55
|
+
import { DisposalRegistry } from "../util/dispose.js"
|
|
56
|
+
import { parseDuration } from "../util/duration.js"
|
|
57
|
+
import { safeJoin } from "../util/fs.js"
|
|
58
|
+
import { LruCache } from "../util/lru.js"
|
|
59
|
+
import type { CoreEventBus } from "./events.js"
|
|
60
|
+
import type { CommandRegistration, KernelHooks } from "./hooks.js"
|
|
61
|
+
import type { ServiceRegistry } from "./services.js"
|
|
62
|
+
|
|
63
|
+
/** 插件内的模板目录名 */
|
|
64
|
+
const TEMPLATE_DIR = "templates"
|
|
65
|
+
|
|
66
|
+
/** 插件内的静态资源目录名 */
|
|
67
|
+
const RESOURCE_DIR = "resources"
|
|
68
|
+
|
|
69
|
+
/** 渲染输出格式对应的 MIME */
|
|
70
|
+
const IMAGE_MIME: Record<string, string> = {
|
|
71
|
+
jpeg: "image/jpeg",
|
|
72
|
+
png: "image/png",
|
|
73
|
+
webp: "image/webp"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** 插件注册计数(供 WebUI 的插件列表展示) */
|
|
77
|
+
export interface PluginCounters {
|
|
78
|
+
/** 已注册命令数 */
|
|
79
|
+
commands: number
|
|
80
|
+
/** 已注册中间件数 */
|
|
81
|
+
middlewares: number
|
|
82
|
+
/** 已注册定时任务数 */
|
|
83
|
+
tasks: number
|
|
84
|
+
/** 已注册路由数 */
|
|
85
|
+
routes: number
|
|
86
|
+
/** 已提供服务数 */
|
|
87
|
+
services: number
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** 构造上下文所需的依赖 */
|
|
91
|
+
export interface PluginContextDeps {
|
|
92
|
+
/** 插件名 */
|
|
93
|
+
name: string
|
|
94
|
+
/** 插件版本 */
|
|
95
|
+
version: string
|
|
96
|
+
/** 插件安装目录(绝对路径) */
|
|
97
|
+
root: string
|
|
98
|
+
/** 插件专属数据目录(绝对路径,调用方已确保存在) */
|
|
99
|
+
dataDir: string
|
|
100
|
+
/** 插件专属日志器 */
|
|
101
|
+
logger: Logger
|
|
102
|
+
/** 插件专属 KV 命名空间 */
|
|
103
|
+
kv: KvNamespace
|
|
104
|
+
/** 插件配置句柄 */
|
|
105
|
+
config: ConfigHandle<unknown>
|
|
106
|
+
/** 应用只读视图 */
|
|
107
|
+
app: AppView
|
|
108
|
+
/** HTTP 客户端 */
|
|
109
|
+
http: HttpClient
|
|
110
|
+
/** 服务注册表 */
|
|
111
|
+
services: ServiceRegistry
|
|
112
|
+
/** 内核事件总线 */
|
|
113
|
+
events: CoreEventBus
|
|
114
|
+
/** 子系统接缝 */
|
|
115
|
+
hooks: KernelHooks
|
|
116
|
+
/** 本插件的回收登记簿 */
|
|
117
|
+
registry: DisposalRegistry
|
|
118
|
+
/** 卸载信号 */
|
|
119
|
+
signal: AbortSignal
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** 构造结果 */
|
|
123
|
+
export interface PluginContextHandle {
|
|
124
|
+
/** 交给插件的上下文 */
|
|
125
|
+
ctx: PluginContext<unknown>
|
|
126
|
+
/** 实时计数,宿主用它填 `PluginState` */
|
|
127
|
+
counters: PluginCounters
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 链式命令构造器
|
|
132
|
+
*
|
|
133
|
+
* 每一环都在改同一份 `CommandRegistration`,路由器持有该对象引用,
|
|
134
|
+
* 因此 `.desc()` / `.master()` 这类不影响索引键的改动无需通知路由器;
|
|
135
|
+
* 只有 `.alias()` 改了索引键才调 `reindex`。
|
|
136
|
+
*/
|
|
137
|
+
class ChainedCommand implements CommandBuilder {
|
|
138
|
+
/** 登记内容 */
|
|
139
|
+
readonly #reg: CommandRegistration
|
|
140
|
+
/** 命令路由 */
|
|
141
|
+
readonly #sink: KernelHooks["commands"]
|
|
142
|
+
/** 注销句柄(已登记进 registry) */
|
|
143
|
+
readonly #undo: Disposer
|
|
144
|
+
/** 日志器,用于提示可疑用法 */
|
|
145
|
+
readonly #logger: Logger
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @param reg 登记内容
|
|
149
|
+
* @param sink 命令路由
|
|
150
|
+
* @param undo 注销句柄
|
|
151
|
+
* @param logger 日志器
|
|
152
|
+
*/
|
|
153
|
+
constructor(reg: CommandRegistration, sink: KernelHooks["commands"], undo: Disposer, logger: Logger) {
|
|
154
|
+
this.#reg = reg
|
|
155
|
+
this.#sink = sink
|
|
156
|
+
this.#undo = undo
|
|
157
|
+
this.#logger = logger
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 绑定处理函数
|
|
162
|
+
* @param fn 处理函数
|
|
163
|
+
* @returns 自身
|
|
164
|
+
*/
|
|
165
|
+
action(fn: CommandHandler): CommandBuilder {
|
|
166
|
+
if (this.#reg.handler) {
|
|
167
|
+
// 覆盖 action 几乎总是复制粘贴的产物:静默覆盖会让人以为前一个还在生效
|
|
168
|
+
this.#logger.warn(`命令 ${describePatterns(this.#reg.patterns)} 的 action 被重复设置,后者生效`)
|
|
169
|
+
}
|
|
170
|
+
this.#reg.handler = fn
|
|
171
|
+
return this
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 追加别名
|
|
176
|
+
* @param patterns 别名模式
|
|
177
|
+
* @returns 自身
|
|
178
|
+
*/
|
|
179
|
+
alias(...patterns: CommandPattern[]): CommandBuilder {
|
|
180
|
+
if (patterns.length === 0) return this
|
|
181
|
+
this.#reg.patterns.push(...patterns)
|
|
182
|
+
this.#sink.reindex(this.#reg)
|
|
183
|
+
return this
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 设置说明
|
|
188
|
+
* @param text 说明文案
|
|
189
|
+
* @returns 自身
|
|
190
|
+
*/
|
|
191
|
+
desc(text: string): CommandBuilder {
|
|
192
|
+
this.#reg.options.desc = text
|
|
193
|
+
return this
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* 限定场景
|
|
198
|
+
* @param scenes 允许的场景
|
|
199
|
+
* @returns 自身
|
|
200
|
+
*/
|
|
201
|
+
scene(...scenes: MessageScene[]): CommandBuilder {
|
|
202
|
+
this.#reg.options.scene = scenes
|
|
203
|
+
return this
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 限定仅主人可用
|
|
208
|
+
* @returns 自身
|
|
209
|
+
*/
|
|
210
|
+
master(): CommandBuilder {
|
|
211
|
+
this.#reg.options.master = true
|
|
212
|
+
return this
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* 限定仅群管可用
|
|
217
|
+
* @returns 自身
|
|
218
|
+
*/
|
|
219
|
+
admin(): CommandBuilder {
|
|
220
|
+
this.#reg.options.admin = true
|
|
221
|
+
return this
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* 设置冷却
|
|
226
|
+
* @param spec 冷却时长
|
|
227
|
+
* @param scope 作用域
|
|
228
|
+
* @returns 自身
|
|
229
|
+
*/
|
|
230
|
+
cooldown(spec: DurationLike, scope?: CooldownScope): CommandBuilder {
|
|
231
|
+
this.#reg.options.cooldown = spec
|
|
232
|
+
if (scope) this.#reg.options.cooldownScope = scope
|
|
233
|
+
return this
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* 设置优先级
|
|
238
|
+
* @param value 数值,小者优先
|
|
239
|
+
* @returns 自身
|
|
240
|
+
*/
|
|
241
|
+
priority(value: number): CommandBuilder {
|
|
242
|
+
this.#reg.options.priority = value
|
|
243
|
+
return this
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** 注销该命令 */
|
|
247
|
+
dispose(): void {
|
|
248
|
+
this.#undo()
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* 把模式数组渲染成可读文本
|
|
254
|
+
* @param patterns 模式数组
|
|
255
|
+
* @returns 可读字符串
|
|
256
|
+
*/
|
|
257
|
+
export function describePatterns(patterns: readonly CommandPattern[]): string {
|
|
258
|
+
return patterns.map(p => (typeof p === "string" ? p : p.source)).join(" | ")
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** 归一化后的一次渲染调用 */
|
|
262
|
+
export interface RenderCall {
|
|
263
|
+
/** 模板名:字符串通路为相对路径,TSX 通路为页面名(仅用于日志与临时文件名) */
|
|
264
|
+
template: string
|
|
265
|
+
/** 模板数据;TSX 通路为空对象 —— 数据已在组件求值时消化完毕 */
|
|
266
|
+
data: Record<string, unknown>
|
|
267
|
+
/** 已渲染好的 HTML;仅 TSX 通路有值 */
|
|
268
|
+
html?: string
|
|
269
|
+
/** 渲染选项 */
|
|
270
|
+
opts: RenderOptions
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* 把两条渲染通路的实参归一为一次调用描述
|
|
275
|
+
*
|
|
276
|
+
* `render()` 有两种形态:`render(page, opts)` 与 `render(template, data, opts)`。
|
|
277
|
+
* 以第一参是否为字符串判别 —— `RenderablePage` 是对象,二者不可能混淆。
|
|
278
|
+
* 判别只做这一处,两个调用点(`ctx.render` 与 `e.render`)共用同一份语义。
|
|
279
|
+
* @param first 页面或模板相对路径
|
|
280
|
+
* @param second 模板数据(字符串通路)或渲染选项(TSX 通路)
|
|
281
|
+
* @param third 渲染选项(字符串通路)
|
|
282
|
+
* @returns 归一化后的调用描述
|
|
283
|
+
*/
|
|
284
|
+
export function splitRenderArgs(
|
|
285
|
+
first: RenderablePage | string,
|
|
286
|
+
second?: Record<string, unknown> | RenderOptions,
|
|
287
|
+
third?: RenderOptions
|
|
288
|
+
): RenderCall {
|
|
289
|
+
if (typeof first === "string") {
|
|
290
|
+
return { template: first, data: (second as Record<string, unknown> | undefined) ?? {}, opts: third ?? {} }
|
|
291
|
+
}
|
|
292
|
+
return { template: first.name, data: {}, html: first.html, opts: (second as RenderOptions | undefined) ?? {} }
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* 插件上下文实现
|
|
297
|
+
*
|
|
298
|
+
* 每个成员都对应 `PluginContext` 的一项能力;除 `resource()` 这类纯计算外,
|
|
299
|
+
* 所有注册都经过 `#own()` 登记回收。
|
|
300
|
+
*/
|
|
301
|
+
class Context implements PluginContext<unknown> {
|
|
302
|
+
/** 依赖 */
|
|
303
|
+
readonly #deps: PluginContextDeps
|
|
304
|
+
/** 计数 */
|
|
305
|
+
readonly #counters: PluginCounters
|
|
306
|
+
/** 已打开的 SQL 库,按库名缓存(含进行中的打开操作) */
|
|
307
|
+
readonly #sqlCache = new Map<string, Promise<SqlHandle>>()
|
|
308
|
+
/** URL 作用域,如 `/plugin/mhy-game` */
|
|
309
|
+
readonly #scope: string
|
|
310
|
+
|
|
311
|
+
/** 插件名 */
|
|
312
|
+
readonly name: string
|
|
313
|
+
/** 插件版本 */
|
|
314
|
+
readonly version: string
|
|
315
|
+
/** 插件安装目录 */
|
|
316
|
+
readonly root: string
|
|
317
|
+
/** 插件数据目录 */
|
|
318
|
+
readonly dataDir: string
|
|
319
|
+
/** 插件日志器 */
|
|
320
|
+
readonly logger: Logger
|
|
321
|
+
/** 插件 KV 命名空间 */
|
|
322
|
+
readonly kv: KvNamespace
|
|
323
|
+
/** 插件配置句柄 */
|
|
324
|
+
readonly config: ConfigHandle<unknown>
|
|
325
|
+
/** 应用只读视图 */
|
|
326
|
+
readonly app: AppView
|
|
327
|
+
/** HTTP 客户端 */
|
|
328
|
+
readonly http: HttpClient
|
|
329
|
+
/** 卸载信号 */
|
|
330
|
+
readonly signal: AbortSignal
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* @param deps 依赖
|
|
334
|
+
* @param counters 计数对象(与宿主共享同一引用)
|
|
335
|
+
*/
|
|
336
|
+
constructor(deps: PluginContextDeps, counters: PluginCounters) {
|
|
337
|
+
this.#deps = deps
|
|
338
|
+
this.#counters = counters
|
|
339
|
+
this.#scope = `/plugin/${deps.name}`
|
|
340
|
+
|
|
341
|
+
this.name = deps.name
|
|
342
|
+
this.version = deps.version
|
|
343
|
+
this.root = deps.root
|
|
344
|
+
this.dataDir = deps.dataDir
|
|
345
|
+
this.logger = deps.logger
|
|
346
|
+
this.kv = deps.kv
|
|
347
|
+
this.config = deps.config
|
|
348
|
+
this.app = deps.app
|
|
349
|
+
this.http = deps.http
|
|
350
|
+
this.signal = deps.signal
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* 声明一条命令
|
|
355
|
+
* @param pattern 触发模式
|
|
356
|
+
* @param opts 命令选项
|
|
357
|
+
* @returns 链式构造器
|
|
358
|
+
*/
|
|
359
|
+
command(pattern: CommandPattern, opts: CommandOptions = {}): CommandBuilder {
|
|
360
|
+
const patterns: CommandPattern[] = [pattern]
|
|
361
|
+
if (opts.alias) patterns.push(...(Array.isArray(opts.alias) ? opts.alias : [opts.alias]))
|
|
362
|
+
|
|
363
|
+
// options 拷一份:插件可能复用同一个 options 字面量声明多条命令,
|
|
364
|
+
// 而链式方法会就地改它
|
|
365
|
+
const reg: CommandRegistration = {
|
|
366
|
+
plugin: this.name,
|
|
367
|
+
patterns,
|
|
368
|
+
options: { ...opts },
|
|
369
|
+
handler: undefined
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const undo = this.#deps.hooks.commands.register(reg)
|
|
373
|
+
this.#counters.commands++
|
|
374
|
+
const disposer = this.#own(() => {
|
|
375
|
+
this.#counters.commands--
|
|
376
|
+
undo()
|
|
377
|
+
}, `command:${describePatterns(patterns)}`)
|
|
378
|
+
|
|
379
|
+
return new ChainedCommand(reg, this.#deps.hooks.commands, disposer, this.logger)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* 注册中间件
|
|
384
|
+
* @param fn 中间件函数
|
|
385
|
+
* @param opts 注册选项
|
|
386
|
+
* @returns 注销句柄
|
|
387
|
+
*/
|
|
388
|
+
middleware(fn: Middleware, opts: MiddlewareOptions = {}): Disposer {
|
|
389
|
+
const undo = this.#deps.hooks.middlewares.register({ plugin: this.name, fn, options: opts })
|
|
390
|
+
this.#counters.middlewares++
|
|
391
|
+
return this.#own(() => {
|
|
392
|
+
this.#counters.middlewares--
|
|
393
|
+
undo()
|
|
394
|
+
}, "middleware")
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* 监听内核事件
|
|
399
|
+
* @param event 事件名
|
|
400
|
+
* @param handler 处理函数
|
|
401
|
+
* @returns 注销句柄
|
|
402
|
+
*/
|
|
403
|
+
on<K extends keyof CoreEventMap>(event: K, handler: (...args: CoreEventMap[K]) => Awaitable<void>): Disposer {
|
|
404
|
+
return this.#own(this.#deps.events.on(event, handler, this.name), `on:${String(event)}`)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* 注册 cron 定时任务
|
|
409
|
+
* @param expression cron 表达式
|
|
410
|
+
* @param fn 任务体
|
|
411
|
+
* @param opts 任务选项
|
|
412
|
+
* @returns 注销句柄
|
|
413
|
+
*/
|
|
414
|
+
cron(expression: string, fn: TaskFn, opts: TaskOptions = {}): Disposer {
|
|
415
|
+
return this.#task("cron", expression, fn, opts)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* 注册固定间隔任务
|
|
420
|
+
* @param interval 间隔
|
|
421
|
+
* @param fn 任务体
|
|
422
|
+
* @param opts 任务选项
|
|
423
|
+
* @returns 注销句柄
|
|
424
|
+
* @throws 间隔小于 1 秒时(几乎总是写错了单位)
|
|
425
|
+
*/
|
|
426
|
+
every(interval: DurationLike, fn: TaskFn, opts: TaskOptions = {}): Disposer {
|
|
427
|
+
const ms = parseDuration(interval, 0)
|
|
428
|
+
if (ms < 1000) {
|
|
429
|
+
throw new Error(`插件 ${this.name} 的间隔任务周期 ${ms}ms 过短:最小 1s,请确认单位是否写错`)
|
|
430
|
+
}
|
|
431
|
+
return this.#task("interval", ms, fn, opts)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* 对外提供服务
|
|
436
|
+
* @param key 服务键
|
|
437
|
+
* @param value 服务实例
|
|
438
|
+
* @returns 注销句柄
|
|
439
|
+
* @throws 键名非法或已被占用时
|
|
440
|
+
*/
|
|
441
|
+
provide<T>(key: string, value: T): Disposer {
|
|
442
|
+
const undo = this.#deps.services.provide(key, value, this.name)
|
|
443
|
+
this.#counters.services++
|
|
444
|
+
return this.#own(() => {
|
|
445
|
+
this.#counters.services--
|
|
446
|
+
undo()
|
|
447
|
+
}, `provide:${key}`)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* 取用其他插件提供的服务
|
|
452
|
+
* @param key 服务键
|
|
453
|
+
* @returns 服务实例;未提供时 undefined
|
|
454
|
+
*/
|
|
455
|
+
inject<T>(key: string): T | undefined {
|
|
456
|
+
return this.#deps.services.get<T>(key)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* 取用服务,缺失即抛错
|
|
461
|
+
* @param key 服务键
|
|
462
|
+
* @returns 服务实例
|
|
463
|
+
* @throws 服务未注册时
|
|
464
|
+
*/
|
|
465
|
+
require<T>(key: string): T {
|
|
466
|
+
return this.#deps.services.require<T>(key, this.name)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* 等待某服务就绪
|
|
471
|
+
*
|
|
472
|
+
* 插件被卸载时立刻以 undefined 结束等待:否则一个等不到的服务会把
|
|
473
|
+
* 已卸载插件的闭包一直挂在内存里。
|
|
474
|
+
* @param key 服务键
|
|
475
|
+
* @param timeout 等待超时,缺省 30s
|
|
476
|
+
* @returns 服务实例;超时或插件已卸载时 undefined
|
|
477
|
+
*/
|
|
478
|
+
async waitFor<T>(key: string, timeout?: DurationLike): Promise<T | undefined> {
|
|
479
|
+
if (this.signal.aborted) return undefined
|
|
480
|
+
const ready = this.#deps.services.get<T>(key)
|
|
481
|
+
if (ready !== undefined) return ready
|
|
482
|
+
|
|
483
|
+
return new Promise<T | undefined>(resolve => {
|
|
484
|
+
let settled = false
|
|
485
|
+
/** 只兑现一次 */
|
|
486
|
+
const finish = (value: T | undefined): void => {
|
|
487
|
+
if (settled) return
|
|
488
|
+
settled = true
|
|
489
|
+
this.signal.removeEventListener("abort", onAbort)
|
|
490
|
+
resolve(value)
|
|
491
|
+
}
|
|
492
|
+
/** 卸载时放弃等待 */
|
|
493
|
+
function onAbort(): void {
|
|
494
|
+
finish(undefined)
|
|
495
|
+
}
|
|
496
|
+
this.signal.addEventListener("abort", onAbort, { once: true })
|
|
497
|
+
void this.#deps.services.waitFor<T>(key, timeout).then(finish, () => finish(undefined))
|
|
498
|
+
})
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* 注册 HTTP 路由
|
|
503
|
+
* @param method HTTP 方法
|
|
504
|
+
* @param path 路径
|
|
505
|
+
* @param handler 处理函数
|
|
506
|
+
* @param opts 选项
|
|
507
|
+
* @returns 注销句柄
|
|
508
|
+
* @throws 服务器未启用时
|
|
509
|
+
*/
|
|
510
|
+
route(method: HttpMethod, path: string, handler: RouteHandler, opts?: RouteOptions): Disposer {
|
|
511
|
+
const undo = this.#deps.hooks.server.route(this.#scope, method, path, handler, opts)
|
|
512
|
+
this.#counters.routes++
|
|
513
|
+
return this.#own(() => {
|
|
514
|
+
this.#counters.routes--
|
|
515
|
+
undo()
|
|
516
|
+
}, `route:${method} ${path}`)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* 注册 WebSocket 路径
|
|
521
|
+
* @param path 路径
|
|
522
|
+
* @param handler 连接处理函数
|
|
523
|
+
* @param opts 选项
|
|
524
|
+
* @returns 注销句柄
|
|
525
|
+
* @throws 服务器未启用时
|
|
526
|
+
*/
|
|
527
|
+
websocket(path: string, handler: WebSocketHandler, opts?: WebSocketOptions): Disposer {
|
|
528
|
+
const undo = this.#deps.hooks.server.websocket(this.#scope, path, handler, opts)
|
|
529
|
+
this.#counters.routes++
|
|
530
|
+
return this.#own(() => {
|
|
531
|
+
this.#counters.routes--
|
|
532
|
+
undo()
|
|
533
|
+
}, `ws:${path}`)
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* 挂载静态目录
|
|
538
|
+
* @param urlPath URL 前缀
|
|
539
|
+
* @param dir 本地目录绝对路径
|
|
540
|
+
* @returns 注销句柄
|
|
541
|
+
* @throws 服务器未启用时
|
|
542
|
+
*/
|
|
543
|
+
static(urlPath: string, dir: string): Disposer {
|
|
544
|
+
return this.#own(this.#deps.hooks.server.static(this.#scope, urlPath, dir), `static:${urlPath}`)
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* 接管站点根路径,替换内置面板
|
|
549
|
+
*
|
|
550
|
+
* 不经过 `this.#scope`:接管面板的意义就在于占据 `/`,挂在
|
|
551
|
+
* `/plugin/<插件名>` 之下的页面无法成为默认入口。归属标识写成
|
|
552
|
+
* `plugin:<插件名>`,`claimant("/")` 因此能回答"面板由谁提供"。
|
|
553
|
+
* 根路径已被占用时抛错,不静默覆盖 —— 见 types 里的说明。
|
|
554
|
+
* @param dir 单页应用产物目录(绝对路径)
|
|
555
|
+
* @returns 注销句柄
|
|
556
|
+
* @throws 服务器未启用、或根路径已被占用时
|
|
557
|
+
*/
|
|
558
|
+
panel(dir: string): Disposer {
|
|
559
|
+
const undo = this.#deps.hooks.server.panel(`plugin:${this.name}`, dir)
|
|
560
|
+
this.#deps.logger.info(`已接管面板根路径:${dir}`)
|
|
561
|
+
return this.#own(undo, "panel:/")
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* 注册适配器
|
|
566
|
+
* @param provider 适配器实现
|
|
567
|
+
* @returns 注销句柄
|
|
568
|
+
*/
|
|
569
|
+
registerAdapter(provider: AdapterProvider): Disposer {
|
|
570
|
+
return this.#own(this.#deps.hooks.adapters.register(provider, this.name), `adapter:${provider.id}`)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* 注册渲染器
|
|
575
|
+
* @param provider 渲染器实现
|
|
576
|
+
* @returns 注销句柄
|
|
577
|
+
*/
|
|
578
|
+
registerRenderer(provider: RendererProvider): Disposer {
|
|
579
|
+
return this.#own(this.#deps.hooks.render.register(provider, this.name), `renderer:${provider.id}`)
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* 注册 KV 驱动
|
|
584
|
+
* @param driver 驱动实现
|
|
585
|
+
* @returns 注销句柄
|
|
586
|
+
*/
|
|
587
|
+
registerKvDriver(driver: KvDriver): Disposer {
|
|
588
|
+
return this.#own(this.#deps.hooks.kvDrivers.register(driver, this.name), `kv-driver:${driver.id}`)
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* 渲染 TSX 页面为图片段
|
|
593
|
+
* @param page 由 `defineTemplate()` 产出的页面
|
|
594
|
+
* @param opts 渲染选项
|
|
595
|
+
* @returns 图片段;分页时为数组
|
|
596
|
+
* @throws 无可用渲染器、渲染失败或渲染器返回空结果时
|
|
597
|
+
*/
|
|
598
|
+
async render(page: RenderablePage, opts?: RenderOptions): Promise<RenderedImage>
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* 渲染字符串模板为图片段
|
|
602
|
+
*
|
|
603
|
+
* 模板根与资源根由内核按插件目录算好(`<root>/templates`、`<root>/resources`),
|
|
604
|
+
* 插件只写 `"player/daily-note"` 这样的相对名。
|
|
605
|
+
* @param template 模板相对路径
|
|
606
|
+
* @param data 模板数据
|
|
607
|
+
* @param opts 渲染选项
|
|
608
|
+
* @returns 图片段;分页时为数组
|
|
609
|
+
* @throws 无可用渲染器、渲染失败或渲染器返回空结果时
|
|
610
|
+
*/
|
|
611
|
+
async render(template: string, data?: Record<string, unknown>, opts?: RenderOptions): Promise<RenderedImage>
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* 渲染实现
|
|
615
|
+
* @param first 页面或模板相对路径
|
|
616
|
+
* @param second 模板数据(字符串通路)或渲染选项(TSX 通路)
|
|
617
|
+
* @param third 渲染选项(字符串通路)
|
|
618
|
+
* @returns 图片段;分页时为数组
|
|
619
|
+
* @throws 无可用渲染器、渲染失败或渲染器返回空结果时
|
|
620
|
+
*/
|
|
621
|
+
async render(
|
|
622
|
+
first: RenderablePage | string,
|
|
623
|
+
second?: Record<string, unknown> | RenderOptions,
|
|
624
|
+
third?: RenderOptions
|
|
625
|
+
): Promise<RenderedImage> {
|
|
626
|
+
const call = splitRenderArgs(first, second, third)
|
|
627
|
+
|
|
628
|
+
const result = await this.#deps.hooks.render.render({
|
|
629
|
+
...call.opts,
|
|
630
|
+
...(call.html !== undefined ? { html: call.html } : {}),
|
|
631
|
+
template: call.template,
|
|
632
|
+
data: call.data,
|
|
633
|
+
templateRoot: join(this.root, TEMPLATE_DIR),
|
|
634
|
+
resourceRoot: join(this.root, RESOURCE_DIR),
|
|
635
|
+
origin: this.name
|
|
636
|
+
})
|
|
637
|
+
|
|
638
|
+
if (result.images.length === 0) {
|
|
639
|
+
throw new Error(`渲染器 ${result.renderer} 处理模板 ${call.template} 后没有产出图片`)
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const type = call.opts.type ?? "jpeg"
|
|
643
|
+
const mime = IMAGE_MIME[type] ?? "image/jpeg"
|
|
644
|
+
const segments: ImageSegment[] = result.images.map((bytes, index) => ({
|
|
645
|
+
type: "image",
|
|
646
|
+
// buffer 直传:渲染器刚出的图就在内存里,转 base64 只是白占一倍内存
|
|
647
|
+
file: { kind: "buffer", data: bytes, name: `${this.name}-${index}.${type}`, mime }
|
|
648
|
+
}))
|
|
649
|
+
|
|
650
|
+
return segments.length === 1 ? segments[0]! : segments
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* 打开本插件专属的 SQLite 库
|
|
655
|
+
*
|
|
656
|
+
* 同名库只打开一次:插件在多处 `await ctx.sql()` 拿到的是同一个连接,
|
|
657
|
+
* 否则 WAL 下多连接互相等锁,症状是"偶发变慢",极难定位。
|
|
658
|
+
* @param name 库名,缺省 `"main"`
|
|
659
|
+
* @returns SQL 句柄
|
|
660
|
+
* @throws better-sqlite3 不可用时
|
|
661
|
+
*/
|
|
662
|
+
async sql(name = "main"): Promise<SqlHandle> {
|
|
663
|
+
const cached = this.#sqlCache.get(name)
|
|
664
|
+
if (cached) return cached
|
|
665
|
+
|
|
666
|
+
const opening = this.#deps.hooks.sql.open(this.name, name)
|
|
667
|
+
this.#sqlCache.set(name, opening)
|
|
668
|
+
// 打开失败不留下坏缓存,否则重试永远拿到同一个已拒绝的 promise
|
|
669
|
+
opening.catch(() => this.#sqlCache.delete(name))
|
|
670
|
+
|
|
671
|
+
if (this.#sqlCache.size === 1) {
|
|
672
|
+
this.#own(() => void this.#deps.hooks.sql.close(this.name), "sql:close")
|
|
673
|
+
}
|
|
674
|
+
return opening
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* 创建 LRU + TTL 缓存
|
|
679
|
+
* @param opts 缓存参数
|
|
680
|
+
* @returns 缓存实例,随插件卸载自动清空
|
|
681
|
+
*/
|
|
682
|
+
cache<V>(opts: ContactCacheOptions): ContactCache<V> {
|
|
683
|
+
const cache = new LruCache<V>(opts)
|
|
684
|
+
this.#own(() => cache.clear(), "cache")
|
|
685
|
+
return cache
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* 拼出插件内资源的绝对路径
|
|
690
|
+
*
|
|
691
|
+
* 走 `safeJoin`:插件传入 `"../../../etc/passwd"` 会被拒绝。参数很可能来自
|
|
692
|
+
* 用户消息(如"看某个模板"),不挡住就是任意文件读。
|
|
693
|
+
* @param parts 相对插件根的路径片段
|
|
694
|
+
* @returns 绝对路径
|
|
695
|
+
* @throws 结果越出插件根目录时
|
|
696
|
+
*/
|
|
697
|
+
resource(...parts: string[]): string {
|
|
698
|
+
return safeJoin(this.root, join(...parts))
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* 注册清理回调
|
|
703
|
+
* @param fn 清理函数
|
|
704
|
+
*/
|
|
705
|
+
onDispose(fn: Disposer): void {
|
|
706
|
+
this.#deps.registry.add(fn, "onDispose")
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* 选一个可用账号
|
|
711
|
+
* @param bot 账号记录 id 或平台 selfId
|
|
712
|
+
* @returns Bot;无可用账号时 undefined
|
|
713
|
+
*/
|
|
714
|
+
pickBot(bot?: string): BotApi | undefined {
|
|
715
|
+
return this.#deps.hooks.bots.pick(bot)
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* 登记定时任务
|
|
720
|
+
* @param kind 任务类型
|
|
721
|
+
* @param schedule cron 表达式或间隔毫秒
|
|
722
|
+
* @param fn 任务体
|
|
723
|
+
* @param opts 任务选项
|
|
724
|
+
* @returns 注销句柄
|
|
725
|
+
*/
|
|
726
|
+
#task(kind: "cron" | "interval", schedule: string | number, fn: TaskFn, opts: TaskOptions): Disposer {
|
|
727
|
+
const undo = this.#deps.hooks.tasks.register({ plugin: this.name, kind, schedule, fn, options: opts })
|
|
728
|
+
this.#counters.tasks++
|
|
729
|
+
return this.#own(() => {
|
|
730
|
+
this.#counters.tasks--
|
|
731
|
+
undo()
|
|
732
|
+
}, `task:${opts.name ?? schedule}`)
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* 把一个 disposer 登记进本插件的回收簿
|
|
737
|
+
*
|
|
738
|
+
* 插件卸载后仍然发生的注册(异步 setup 慢一步)会被立即回收 ——
|
|
739
|
+
* 这是 `DisposalRegistry.add` 的既有行为,此处依赖它。
|
|
740
|
+
* @param fn 回收函数
|
|
741
|
+
* @param label 标签
|
|
742
|
+
* @returns 提前回收的句柄
|
|
743
|
+
*/
|
|
744
|
+
#own(fn: Disposer, label: string): Disposer {
|
|
745
|
+
return this.#deps.registry.add(fn, label)
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* 构造插件上下文
|
|
751
|
+
* @param deps 依赖
|
|
752
|
+
* @returns 上下文与计数对象
|
|
753
|
+
*/
|
|
754
|
+
export function createPluginContext(deps: PluginContextDeps): PluginContextHandle {
|
|
755
|
+
const counters: PluginCounters = { commands: 0, middlewares: 0, tasks: 0, routes: 0, services: 0 }
|
|
756
|
+
return { ctx: new Context(deps, counters), counters }
|
|
757
|
+
}
|