@zhin.js/core 1.1.0 → 1.1.2
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/lib/adapter.d.ts +1 -26
- package/lib/adapter.d.ts.map +1 -1
- package/lib/adapter.js +20 -117
- package/lib/adapter.js.map +1 -1
- package/lib/built/adapter-process.d.ts +0 -4
- package/lib/built/adapter-process.d.ts.map +1 -1
- package/lib/built/adapter-process.js +0 -95
- package/lib/built/adapter-process.js.map +1 -1
- package/lib/built/agent-preset.d.ts +2 -0
- package/lib/built/agent-preset.d.ts.map +1 -1
- package/lib/built/agent-preset.js +4 -0
- package/lib/built/agent-preset.js.map +1 -1
- package/lib/built/command.d.ts +4 -0
- package/lib/built/command.d.ts.map +1 -1
- package/lib/built/command.js +6 -0
- package/lib/built/command.js.map +1 -1
- package/lib/built/component.d.ts.map +1 -1
- package/lib/built/component.js +1 -0
- package/lib/built/component.js.map +1 -1
- package/lib/built/dispatcher.d.ts.map +1 -1
- package/lib/built/dispatcher.js +0 -13
- package/lib/built/dispatcher.js.map +1 -1
- package/lib/built/message-filter.d.ts +2 -0
- package/lib/built/message-filter.d.ts.map +1 -1
- package/lib/built/message-filter.js +5 -0
- package/lib/built/message-filter.js.map +1 -1
- package/lib/built/skill.d.ts +11 -0
- package/lib/built/skill.d.ts.map +1 -1
- package/lib/built/skill.js +14 -0
- package/lib/built/skill.js.map +1 -1
- package/lib/built/tool.d.ts +11 -44
- package/lib/built/tool.d.ts.map +1 -1
- package/lib/built/tool.js +14 -353
- package/lib/built/tool.js.map +1 -1
- package/lib/plugin.d.ts +1 -25
- package/lib/plugin.d.ts.map +1 -1
- package/lib/plugin.js +1 -77
- package/lib/plugin.js.map +1 -1
- package/lib/types.d.ts +0 -25
- package/lib/types.d.ts.map +1 -1
- package/package.json +10 -7
- package/CHANGELOG.md +0 -561
- package/REFACTORING_COMPLETE.md +0 -178
- package/REFACTORING_STATUS.md +0 -263
- package/src/adapter.ts +0 -275
- package/src/ai/index.ts +0 -55
- package/src/ai/providers/anthropic.ts +0 -379
- package/src/ai/providers/base.ts +0 -175
- package/src/ai/providers/index.ts +0 -13
- package/src/ai/providers/ollama.ts +0 -302
- package/src/ai/providers/openai.ts +0 -174
- package/src/ai/types.ts +0 -348
- package/src/bot.ts +0 -37
- package/src/built/adapter-process.ts +0 -177
- package/src/built/agent-preset.ts +0 -136
- package/src/built/ai-trigger.ts +0 -259
- package/src/built/command.ts +0 -108
- package/src/built/common-adapter-tools.ts +0 -242
- package/src/built/component.ts +0 -130
- package/src/built/config.ts +0 -335
- package/src/built/cron.ts +0 -156
- package/src/built/database.ts +0 -134
- package/src/built/dispatcher.ts +0 -496
- package/src/built/login-assist.ts +0 -131
- package/src/built/message-filter.ts +0 -390
- package/src/built/permission.ts +0 -151
- package/src/built/schema-feature.ts +0 -190
- package/src/built/skill.ts +0 -221
- package/src/built/tool.ts +0 -948
- package/src/command.ts +0 -87
- package/src/component.ts +0 -565
- package/src/cron.ts +0 -4
- package/src/errors.ts +0 -46
- package/src/feature.ts +0 -7
- package/src/index.ts +0 -53
- package/src/jsx-dev-runtime.ts +0 -2
- package/src/jsx-runtime.ts +0 -12
- package/src/jsx.ts +0 -135
- package/src/message.ts +0 -48
- package/src/models/system-log.ts +0 -20
- package/src/models/user.ts +0 -15
- package/src/notice.ts +0 -98
- package/src/plugin.ts +0 -896
- package/src/prompt.ts +0 -293
- package/src/request.ts +0 -95
- package/src/scheduler/index.ts +0 -19
- package/src/scheduler/scheduler.ts +0 -372
- package/src/scheduler/types.ts +0 -74
- package/src/tool-zod.ts +0 -115
- package/src/types-generator.ts +0 -78
- package/src/types.ts +0 -505
- package/src/utils.ts +0 -227
- package/tests/adapter.test.ts +0 -638
- package/tests/ai/ai-trigger.test.ts +0 -368
- package/tests/ai/providers.integration.test.ts +0 -227
- package/tests/ai/setup.ts +0 -308
- package/tests/ai/tool.test.ts +0 -800
- package/tests/bot.test.ts +0 -151
- package/tests/command.test.ts +0 -737
- package/tests/component-new.test.ts +0 -361
- package/tests/config.test.ts +0 -372
- package/tests/cron.test.ts +0 -82
- package/tests/dispatcher.test.ts +0 -293
- package/tests/errors.test.ts +0 -21
- package/tests/expression-evaluation.test.ts +0 -258
- package/tests/features-builtin.test.ts +0 -191
- package/tests/jsx-runtime.test.ts +0 -45
- package/tests/jsx.test.ts +0 -319
- package/tests/message-filter.test.ts +0 -566
- package/tests/message.test.ts +0 -402
- package/tests/notice.test.ts +0 -198
- package/tests/plugin.test.ts +0 -779
- package/tests/prompt.test.ts +0 -78
- package/tests/redos-protection.test.ts +0 -198
- package/tests/request.test.ts +0 -221
- package/tests/schema.test.ts +0 -248
- package/tests/skill-feature.test.ts +0 -179
- package/tests/test-utils.ts +0 -59
- package/tests/tool-feature.test.ts +0 -254
- package/tests/types.test.ts +0 -162
- package/tests/utils.test.ts +0 -135
- package/tsconfig.json +0 -24
package/src/plugin.ts
DELETED
|
@@ -1,896 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plugin 类 - 基于 zhinjs/next 的 Hooks 实现
|
|
3
|
-
* 移除 Dependency 继承,使用 AsyncLocalStorage 管理上下文
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
7
|
-
import { EventEmitter } from "events";
|
|
8
|
-
import { createRequire } from "module";
|
|
9
|
-
import type { Database, Definition } from "@zhin.js/database";
|
|
10
|
-
import { Schema } from "@zhin.js/schema";
|
|
11
|
-
import type { Models, RegisteredAdapters, AITool, ToolContext, PluginManifest } from "./types.js";
|
|
12
|
-
import * as fs from "fs";
|
|
13
|
-
import * as path from "path";
|
|
14
|
-
import { fileURLToPath } from "url";
|
|
15
|
-
import logger, { Logger } from "@zhin.js/logger";
|
|
16
|
-
import { compose, remove, resolveEntry } from "./utils.js";
|
|
17
|
-
import { MessageMiddleware, RegisteredAdapter, MaybePromise, ArrayItem, SendOptions } from "./types.js";
|
|
18
|
-
import { Adapter, Adapters } from "./adapter.js";
|
|
19
|
-
import { createHash } from "crypto";
|
|
20
|
-
import { Feature } from "./feature.js";
|
|
21
|
-
const contextsKey = Symbol("contexts");
|
|
22
|
-
const loadedModules = new Map<string, Plugin>(); // 记录已加载的模块
|
|
23
|
-
const require = createRequire(import.meta.url);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
export type SideEffect<A extends (keyof Plugin.Contexts)[]> = {
|
|
27
|
-
(...args: ContextList<A>): MaybePromise<void | DisposeFn<ContextList<A>>>;
|
|
28
|
-
finished?: boolean
|
|
29
|
-
}
|
|
30
|
-
export type DisposeFn<A> = (context: ArrayItem<A>) => MaybePromise<void>
|
|
31
|
-
export type ContextList<CS extends (keyof Plugin.Contexts)[]> = CS extends [infer L, ...infer R] ? R extends (keyof Plugin.Contexts)[] ? [ContextItem<L>, ...ContextList<R>] : never[] : never[]
|
|
32
|
-
type ContextItem<L> = L extends keyof Plugin.Contexts ? Plugin.Contexts[L] : never
|
|
33
|
-
// ============================================================================
|
|
34
|
-
// AsyncLocalStorage 上下文
|
|
35
|
-
// ============================================================================
|
|
36
|
-
|
|
37
|
-
export const storage = new AsyncLocalStorage<Plugin>();
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 获取当前文件路径(调用者)
|
|
41
|
-
*/
|
|
42
|
-
function getCurrentFile(metaUrl = import.meta.url): string {
|
|
43
|
-
const previousPrepareStackTrace = Error.prepareStackTrace;
|
|
44
|
-
Error.prepareStackTrace = function (_, stack) {
|
|
45
|
-
return stack;
|
|
46
|
-
};
|
|
47
|
-
const stack = new Error().stack as unknown as NodeJS.CallSite[];
|
|
48
|
-
Error.prepareStackTrace = previousPrepareStackTrace;
|
|
49
|
-
const stackFiles = Array.from(
|
|
50
|
-
new Set(stack.map((site) => site.getFileName()))
|
|
51
|
-
);
|
|
52
|
-
const idx = stackFiles.findIndex(
|
|
53
|
-
(f) => f === fileURLToPath(metaUrl) || f === metaUrl
|
|
54
|
-
);
|
|
55
|
-
const result = stackFiles[idx + 1];
|
|
56
|
-
if (!result) throw new Error("Cannot resolve current file path");
|
|
57
|
-
try {
|
|
58
|
-
return fileURLToPath(result);
|
|
59
|
-
} catch {
|
|
60
|
-
return result;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* usePlugin - 获取或创建当前插件实例
|
|
66
|
-
* 类似 React Hooks 的设计,根据调用文件自动创建插件树
|
|
67
|
-
* 同一上下文中同一文件多次调用返回同一实例
|
|
68
|
-
*/
|
|
69
|
-
export function usePlugin(): Plugin {
|
|
70
|
-
const callerFile = getCurrentFile();
|
|
71
|
-
// 如果当前 store 已是同一文件创建的插件,直接返回
|
|
72
|
-
const current = storage.getStore();
|
|
73
|
-
if (current && current.filePath.replace(/\?t=\d+$/, '') === callerFile.replace(/\?t=\d+$/, '')) {
|
|
74
|
-
return current;
|
|
75
|
-
}
|
|
76
|
-
const parentPlugin = current;
|
|
77
|
-
const newPlugin = new Plugin(callerFile, parentPlugin);
|
|
78
|
-
storage.enterWith(newPlugin);
|
|
79
|
-
return newPlugin;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* getPlugin - 获取当前 AsyncLocalStorage 中的插件实例
|
|
84
|
-
* 用于 extensions 等场景,不创建新插件
|
|
85
|
-
*/
|
|
86
|
-
export function getPlugin(): Plugin {
|
|
87
|
-
const plugin = storage.getStore();
|
|
88
|
-
if (!plugin) {
|
|
89
|
-
throw new Error('getPlugin() must be called within a plugin context');
|
|
90
|
-
}
|
|
91
|
-
return plugin;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// ============================================================================
|
|
95
|
-
// 文件工具函数
|
|
96
|
-
// ============================================================================
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* 获取文件 Hash(用于检测变更)
|
|
100
|
-
*/
|
|
101
|
-
function getFileHash(filePath: string): string {
|
|
102
|
-
try {
|
|
103
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
104
|
-
return createHash("md5")
|
|
105
|
-
.update(content)
|
|
106
|
-
.digest("hex");
|
|
107
|
-
} catch {
|
|
108
|
-
return "";
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* 监听文件变化
|
|
114
|
-
*/
|
|
115
|
-
function watchFile(filePath: string, callback: () => void): () => void {
|
|
116
|
-
try {
|
|
117
|
-
const watcher = fs.watch(filePath, callback);
|
|
118
|
-
watcher.on("error", (_error) => { });
|
|
119
|
-
return () => watcher.close();
|
|
120
|
-
} catch (error) {
|
|
121
|
-
return () => { };
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// ============================================================================
|
|
126
|
-
// Plugin 类
|
|
127
|
-
// ============================================================================
|
|
128
|
-
|
|
129
|
-
export interface Plugin extends Plugin.Extensions { }
|
|
130
|
-
/**
|
|
131
|
-
* Plugin 类 - 核心插件系统
|
|
132
|
-
* 直接继承 EventEmitter
|
|
133
|
-
*/
|
|
134
|
-
export class Plugin extends EventEmitter<Plugin.Lifecycle> {
|
|
135
|
-
static [contextsKey] = [] as string[];
|
|
136
|
-
|
|
137
|
-
#cachedName?: string;
|
|
138
|
-
#manifest?: PluginManifest | null;
|
|
139
|
-
adapters: (keyof Plugin.Contexts)[] = [];
|
|
140
|
-
started = false;
|
|
141
|
-
|
|
142
|
-
// 上下文存储
|
|
143
|
-
$contexts: Map<string, Context<any>> = new Map();
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// 子插件
|
|
147
|
-
children: Plugin[] = [];
|
|
148
|
-
|
|
149
|
-
// 文件信息
|
|
150
|
-
filePath: string;
|
|
151
|
-
fileHash: string = "";
|
|
152
|
-
|
|
153
|
-
// Logger
|
|
154
|
-
logger: Logger;
|
|
155
|
-
|
|
156
|
-
#messageMiddleware: MessageMiddleware<RegisteredAdapter> = async (message, next) => {
|
|
157
|
-
const commandService = this.inject('command');
|
|
158
|
-
if (!commandService) return await next();
|
|
159
|
-
const result = await commandService.handle(message, this);
|
|
160
|
-
if (!result) return await next();
|
|
161
|
-
const adapter = this.inject(message.$adapter) as Adapter;
|
|
162
|
-
if (!adapter || !(adapter instanceof Adapter)) return await next();
|
|
163
|
-
await message.$reply(result);
|
|
164
|
-
await next();
|
|
165
|
-
};
|
|
166
|
-
// 插件功能
|
|
167
|
-
#middlewares: MessageMiddleware<RegisteredAdapter>[] = [this.#messageMiddleware];
|
|
168
|
-
|
|
169
|
-
// AI 工具
|
|
170
|
-
#tools: Map<string, AITool> = new Map();
|
|
171
|
-
|
|
172
|
-
// Feature 贡献追踪
|
|
173
|
-
#featureContributions = new Map<string, Set<string>>();
|
|
174
|
-
|
|
175
|
-
// 统一的清理函数集合
|
|
176
|
-
#disposables: Set<() => void | Promise<void>> = new Set();
|
|
177
|
-
|
|
178
|
-
get middleware(): MessageMiddleware<RegisteredAdapter> {
|
|
179
|
-
return compose<RegisteredAdapter>(this.#middlewares);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
recordFeatureContribution(featureName: string, itemName: string): void {
|
|
183
|
-
if (!this.#featureContributions.has(featureName)) {
|
|
184
|
-
this.#featureContributions.set(featureName, new Set());
|
|
185
|
-
}
|
|
186
|
-
this.#featureContributions.get(featureName)!.add(itemName);
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
|
-
* 构造函数
|
|
190
|
-
*/
|
|
191
|
-
constructor(filePath: string = "", public parent?: Plugin) {
|
|
192
|
-
super();
|
|
193
|
-
|
|
194
|
-
// 增加 EventEmitter 监听器限制,避免警告
|
|
195
|
-
// 因为插件可能注册多个命令/组件/中间件,每个都会添加 dispose 监听器
|
|
196
|
-
this.setMaxListeners(50);
|
|
197
|
-
|
|
198
|
-
this.filePath = filePath.replace(/\?t=\d+$/, "");
|
|
199
|
-
this.logger = this.name ? logger.getLogger(this.name) : logger;
|
|
200
|
-
|
|
201
|
-
// 自动添加到父节点
|
|
202
|
-
if (parent && !parent.children.includes(this)) {
|
|
203
|
-
parent.children.push(this);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// 绑定方法以支持解构使用
|
|
207
|
-
this.$bindMethods();
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// 标记是否已绑定方法
|
|
211
|
-
#methodsBound = false;
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* 添加中间件
|
|
215
|
-
* 中间件用于处理消息流转
|
|
216
|
-
*/
|
|
217
|
-
addMiddleware<T extends RegisteredAdapter>(middleware: MessageMiddleware<T>, name?: string) {
|
|
218
|
-
this.#middlewares.push(middleware as MessageMiddleware<RegisteredAdapter>);
|
|
219
|
-
const dispose = () => {
|
|
220
|
-
remove(this.#middlewares, middleware);
|
|
221
|
-
this.#disposables.delete(dispose);
|
|
222
|
-
};
|
|
223
|
-
this.#disposables.add(dispose);
|
|
224
|
-
return dispose;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* 添加 AI 工具
|
|
229
|
-
* 工具可以被 AI 服务调用来执行操作
|
|
230
|
-
* @param tool 工具定义
|
|
231
|
-
* @returns 返回一个移除工具的函数
|
|
232
|
-
*/
|
|
233
|
-
addTool(tool: AITool): () => void {
|
|
234
|
-
// 自动添加插件源标识
|
|
235
|
-
const toolWithSource: AITool = {
|
|
236
|
-
...tool,
|
|
237
|
-
source: tool.source || `plugin:${this.name}`,
|
|
238
|
-
tags: [...(tool.tags || []), 'plugin', this.name],
|
|
239
|
-
};
|
|
240
|
-
this.#tools.set(tool.name, toolWithSource);
|
|
241
|
-
const dispose = () => {
|
|
242
|
-
this.#tools.delete(tool.name);
|
|
243
|
-
this.#disposables.delete(dispose);
|
|
244
|
-
};
|
|
245
|
-
this.#disposables.add(dispose);
|
|
246
|
-
return dispose;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* 获取当前插件注册的所有工具
|
|
251
|
-
*/
|
|
252
|
-
getTools(): AITool[] {
|
|
253
|
-
return Array.from(this.#tools.values());
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* 获取当前插件及所有子插件注册的工具
|
|
258
|
-
*/
|
|
259
|
-
getAllTools(): AITool[] {
|
|
260
|
-
const tools: AITool[] = [...this.getTools()];
|
|
261
|
-
for (const child of this.children) {
|
|
262
|
-
tools.push(...child.getAllTools());
|
|
263
|
-
}
|
|
264
|
-
return tools;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* 根据名称获取工具
|
|
269
|
-
*/
|
|
270
|
-
getTool(name: string): AITool | undefined {
|
|
271
|
-
// 先在当前插件查找
|
|
272
|
-
const tool = this.#tools.get(name);
|
|
273
|
-
if (tool) return tool;
|
|
274
|
-
// 再在子插件中查找
|
|
275
|
-
for (const child of this.children) {
|
|
276
|
-
const childTool = child.getTool(name);
|
|
277
|
-
if (childTool) return childTool;
|
|
278
|
-
}
|
|
279
|
-
return undefined;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* 收集所有可用的工具(包括适配器提供的)
|
|
284
|
-
* 这是 AI 服务获取工具的主入口
|
|
285
|
-
*/
|
|
286
|
-
collectAllTools(): AITool[] {
|
|
287
|
-
const tools: AITool[] = [];
|
|
288
|
-
|
|
289
|
-
// 收集插件树中的所有工具
|
|
290
|
-
const rootPlugin = this.root;
|
|
291
|
-
tools.push(...rootPlugin.getAllTools());
|
|
292
|
-
|
|
293
|
-
// 收集所有适配器的工具
|
|
294
|
-
for (const [name, context] of rootPlugin.contexts) {
|
|
295
|
-
const value = context.value;
|
|
296
|
-
if (value && typeof value === 'object' && 'getTools' in value) {
|
|
297
|
-
const adapter = value as Adapter;
|
|
298
|
-
tools.push(...adapter.getTools());
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
return tools;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* 插件名称
|
|
307
|
-
*/
|
|
308
|
-
get name(): string {
|
|
309
|
-
if (this.#cachedName) return this.#cachedName;
|
|
310
|
-
|
|
311
|
-
this.#cachedName = path
|
|
312
|
-
.relative(process.cwd(), this.filePath)
|
|
313
|
-
.replace(/\?t=\d+$/, "")
|
|
314
|
-
.replace(/\\/g, "/")
|
|
315
|
-
.replace(/\/index\.(js|ts)x?$/, "")
|
|
316
|
-
.replace(/\/(lib|src|dist)$/, "")
|
|
317
|
-
.replace(/.*\/node_modules\//, "")
|
|
318
|
-
.replace(/.*\//, "")
|
|
319
|
-
.replace(/\.(js|ts)x?$/, "");
|
|
320
|
-
|
|
321
|
-
return this.#cachedName;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* 插件清单(从 plugin.yml 或 package.json 延迟读取)
|
|
326
|
-
*/
|
|
327
|
-
get manifest(): PluginManifest | undefined {
|
|
328
|
-
if (this.#manifest !== undefined) return this.#manifest ?? undefined;
|
|
329
|
-
if (!this.filePath) {
|
|
330
|
-
this.#manifest = null;
|
|
331
|
-
return undefined;
|
|
332
|
-
}
|
|
333
|
-
const dir = path.dirname(this.filePath);
|
|
334
|
-
// 优先读取 plugin.yml
|
|
335
|
-
const ymlPath = path.join(dir, 'plugin.yml');
|
|
336
|
-
if (fs.existsSync(ymlPath)) {
|
|
337
|
-
try {
|
|
338
|
-
const content = fs.readFileSync(ymlPath, 'utf-8');
|
|
339
|
-
const match = content.match(/^name:\s*(.+)$/m);
|
|
340
|
-
const descMatch = content.match(/^description:\s*(.+)$/m);
|
|
341
|
-
const verMatch = content.match(/^version:\s*(.+)$/m);
|
|
342
|
-
if (match) {
|
|
343
|
-
this.#manifest = {
|
|
344
|
-
name: match[1].trim(),
|
|
345
|
-
description: descMatch?.[1]?.trim(),
|
|
346
|
-
version: verMatch?.[1]?.trim(),
|
|
347
|
-
};
|
|
348
|
-
return this.#manifest;
|
|
349
|
-
}
|
|
350
|
-
} catch { /* ignore */ }
|
|
351
|
-
}
|
|
352
|
-
// Fallback: package.json
|
|
353
|
-
const pkgPath = path.join(dir, 'package.json');
|
|
354
|
-
if (fs.existsSync(pkgPath)) {
|
|
355
|
-
try {
|
|
356
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
357
|
-
if (pkg.name) {
|
|
358
|
-
this.#manifest = {
|
|
359
|
-
name: pkg.name,
|
|
360
|
-
description: pkg.description,
|
|
361
|
-
version: pkg.version,
|
|
362
|
-
};
|
|
363
|
-
return this.#manifest;
|
|
364
|
-
}
|
|
365
|
-
} catch { /* ignore */ }
|
|
366
|
-
}
|
|
367
|
-
this.#manifest = null;
|
|
368
|
-
return undefined;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* 根插件
|
|
373
|
-
*/
|
|
374
|
-
get root(): Plugin {
|
|
375
|
-
if (!this.parent) return this;
|
|
376
|
-
return this.parent.root;
|
|
377
|
-
}
|
|
378
|
-
get contexts(): Map<string, Context> {
|
|
379
|
-
const result = new Map<string, Context>();
|
|
380
|
-
for (const [key, value] of this.$contexts) {
|
|
381
|
-
result.set(key, value);
|
|
382
|
-
}
|
|
383
|
-
for (const child of this.children) {
|
|
384
|
-
for (const [key, value] of child.contexts) {
|
|
385
|
-
result.set(key, value);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
return result;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
useContext<T extends (keyof Plugin.Contexts)[]>(...args: [...T, SideEffect<T>]) {
|
|
393
|
-
const contexts = args.slice(0, -1) as T
|
|
394
|
-
const sideEffect = args[args.length - 1] as SideEffect<T>
|
|
395
|
-
const contextReadyCallback = async () => {
|
|
396
|
-
if (sideEffect.finished) return;
|
|
397
|
-
sideEffect.finished = true;
|
|
398
|
-
const args = contexts.map(item => this.inject(item))
|
|
399
|
-
const dispose = await sideEffect(...args as ContextList<T>)
|
|
400
|
-
if (!dispose) return;
|
|
401
|
-
const disposeFn = async (name: keyof Plugin.Contexts) => {
|
|
402
|
-
if (contexts.includes(name)) {
|
|
403
|
-
await dispose(this.inject(name) as any)
|
|
404
|
-
}
|
|
405
|
-
this.off('context.dispose', disposeFn)
|
|
406
|
-
sideEffect.finished = false;
|
|
407
|
-
}
|
|
408
|
-
this.on('context.dispose', disposeFn)
|
|
409
|
-
// 确保 dispose 时清理监听器(只注册一次)
|
|
410
|
-
const cleanupOnDispose = () => {
|
|
411
|
-
this.off('context.dispose', disposeFn)
|
|
412
|
-
dispose(this.inject(args[0] as any) as any)
|
|
413
|
-
}
|
|
414
|
-
this.once('dispose', cleanupOnDispose)
|
|
415
|
-
}
|
|
416
|
-
const onContextMounted = async (name: keyof Plugin.Contexts) => {
|
|
417
|
-
if (!this.#contextsIsReady(contexts) || !(contexts).includes(name)) return
|
|
418
|
-
await contextReadyCallback()
|
|
419
|
-
}
|
|
420
|
-
this.on('context.mounted', onContextMounted)
|
|
421
|
-
// 插件销毁时移除 context.mounted 监听器
|
|
422
|
-
this.once('dispose', () => this.off('context.mounted', onContextMounted))
|
|
423
|
-
if (!this.#contextsIsReady(contexts)) return
|
|
424
|
-
contextReadyCallback()
|
|
425
|
-
}
|
|
426
|
-
inject<T extends keyof Plugin.Contexts>(name: T): Plugin.Contexts[T]|undefined;
|
|
427
|
-
inject(name: string): unknown;
|
|
428
|
-
inject(name: string): unknown {
|
|
429
|
-
const context = this.root.contexts.get(name);
|
|
430
|
-
return context?.value;
|
|
431
|
-
}
|
|
432
|
-
#contextsIsReady<CS extends (keyof Plugin.Contexts)[]>(contexts: CS) {
|
|
433
|
-
if (!contexts.length) return true
|
|
434
|
-
return contexts.every(name => this.contextIsReady(name))
|
|
435
|
-
}
|
|
436
|
-
contextIsReady<T extends keyof Plugin.Contexts>(name: T) {
|
|
437
|
-
try {
|
|
438
|
-
return !!this.inject(name)
|
|
439
|
-
} catch {
|
|
440
|
-
return false
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
// ============================================================================
|
|
444
|
-
// 生命周期方法
|
|
445
|
-
// ============================================================================
|
|
446
|
-
|
|
447
|
-
/**
|
|
448
|
-
* 启动插件
|
|
449
|
-
*/
|
|
450
|
-
async start(t?:number): Promise<void> {
|
|
451
|
-
if (this.started) return;
|
|
452
|
-
this.started = true; // 提前设置,防止重复启动
|
|
453
|
-
|
|
454
|
-
// 启动所有服务
|
|
455
|
-
for (const context of this.$contexts.values()) {
|
|
456
|
-
if (typeof context.mounted === "function") {
|
|
457
|
-
const result = await context.mounted(this);
|
|
458
|
-
// 仅在没有预设 value 时才用 mounted 返回值赋值
|
|
459
|
-
if (!context.value) {
|
|
460
|
-
context.value = result;
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
// 注册扩展方法到 Plugin.prototype
|
|
464
|
-
if (context.extensions) {
|
|
465
|
-
for (const [name, fn] of Object.entries(context.extensions)) {
|
|
466
|
-
if (typeof fn === 'function') {
|
|
467
|
-
Reflect.set(Plugin.prototype, name, fn);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
this.dispatch('context.mounted', context.name)
|
|
472
|
-
}
|
|
473
|
-
await this.broadcast("mounted");
|
|
474
|
-
// 先启动子插件,再打印当前插件启动日志
|
|
475
|
-
for (const child of this.children) {
|
|
476
|
-
await child.start(t);
|
|
477
|
-
}
|
|
478
|
-
// 输出启动日志(使用 debug 级别,避免重复输出)
|
|
479
|
-
// 只在根插件或重要插件时使用 info 级别
|
|
480
|
-
if (!this.parent || this.name === 'setup') {
|
|
481
|
-
this.logger.info(`Plugin "${this.name}" ${t ? `reloaded in ${Date.now() - t}ms` : "started"}`);
|
|
482
|
-
} else {
|
|
483
|
-
this.logger.debug(`Plugin "${this.name}" ${t ? `reloaded in ${Date.now() - t}ms` : "started"}`);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
* 获取插件提供的功能
|
|
488
|
-
* 从各个服务中获取数据
|
|
489
|
-
*/
|
|
490
|
-
get features(): Plugin.Features {
|
|
491
|
-
const commandService = this.inject('command');
|
|
492
|
-
const componentService = this.inject('component')
|
|
493
|
-
const cronService = this.inject('cron');
|
|
494
|
-
|
|
495
|
-
return {
|
|
496
|
-
commands: commandService ? commandService.items.map(c => c.pattern) : [],
|
|
497
|
-
components: componentService ? componentService.getAllNames() : [],
|
|
498
|
-
crons: cronService ? cronService.items.map(c => c.cronExpression) : [],
|
|
499
|
-
middlewares: this.#middlewares.map((m, i) => m.name || `middleware_${i}`),
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* 获取插件功能摘要(数组形式)
|
|
505
|
-
* 返回各功能类型的名称和数量
|
|
506
|
-
*/
|
|
507
|
-
getFeatures(): Array<{ name: string; count: number }> {
|
|
508
|
-
const result: Array<{ name: string; count: number }> = [];
|
|
509
|
-
const f = this.features;
|
|
510
|
-
if (f.commands.length > 0) result.push({ name: 'command', count: f.commands.length });
|
|
511
|
-
if (f.components.length > 0) result.push({ name: 'component', count: f.components.length });
|
|
512
|
-
if (f.crons.length > 0) result.push({ name: 'cron', count: f.crons.length });
|
|
513
|
-
// #middlewares includes the default command middleware, only count user-added ones
|
|
514
|
-
const userMiddlewareCount = this.#middlewares.length - 1; // subtract default #messageMiddleware
|
|
515
|
-
if (userMiddlewareCount > 0) result.push({ name: 'middleware', count: userMiddlewareCount });
|
|
516
|
-
if (this.#tools.size > 0) result.push({ name: 'tool', count: this.#tools.size });
|
|
517
|
-
return result;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
info(): Record<string, any> {
|
|
521
|
-
return {
|
|
522
|
-
[this.name]: {
|
|
523
|
-
features: this.features,
|
|
524
|
-
children: this.children.map(child => child.info())
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
/**
|
|
530
|
-
* 停止插件
|
|
531
|
-
*/
|
|
532
|
-
async stop(): Promise<void> {
|
|
533
|
-
if (!this.started) return;
|
|
534
|
-
this.logger.debug(`Stopping plugin "${this.name}"`);
|
|
535
|
-
this.started = false;
|
|
536
|
-
|
|
537
|
-
// 停止子插件
|
|
538
|
-
for (const child of this.children) {
|
|
539
|
-
await child.stop();
|
|
540
|
-
}
|
|
541
|
-
this.children = [];
|
|
542
|
-
|
|
543
|
-
// 停止服务
|
|
544
|
-
for (const [name, context] of this.$contexts) {
|
|
545
|
-
remove(Plugin[contextsKey], name);
|
|
546
|
-
// 移除扩展方法
|
|
547
|
-
if (context.extensions) {
|
|
548
|
-
for (const key of Object.keys(context.extensions)) {
|
|
549
|
-
delete (Plugin.prototype as any)[key];
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
if (typeof context.dispose === "function") {
|
|
553
|
-
await context.dispose(context.value);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
// 清理 contexts Map
|
|
557
|
-
this.$contexts.clear();
|
|
558
|
-
|
|
559
|
-
// 清空缓存的名称
|
|
560
|
-
this.#cachedName = undefined;
|
|
561
|
-
|
|
562
|
-
// 触发 dispose 事件
|
|
563
|
-
this.emit("dispose");
|
|
564
|
-
|
|
565
|
-
// 执行所有清理函数
|
|
566
|
-
for (const dispose of this.#disposables) {
|
|
567
|
-
try {
|
|
568
|
-
await dispose();
|
|
569
|
-
} catch (e) {
|
|
570
|
-
this.logger.warn(`Dispose callback failed: ${e}`);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
this.#disposables.clear();
|
|
574
|
-
|
|
575
|
-
// 清理 middlewares 数组(保留默认的消息中间件)
|
|
576
|
-
this.#middlewares.length = 1;
|
|
577
|
-
|
|
578
|
-
// 清理 feature 贡献记录
|
|
579
|
-
this.#featureContributions.clear();
|
|
580
|
-
|
|
581
|
-
if (this.parent) {
|
|
582
|
-
remove(this.parent?.children, this);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
// 从全局 loadedModules Map 中移除,防止内存泄漏
|
|
586
|
-
if (this.filePath) {
|
|
587
|
-
try {
|
|
588
|
-
const realPath = fs.realpathSync(this.filePath);
|
|
589
|
-
loadedModules.delete(realPath);
|
|
590
|
-
} catch {
|
|
591
|
-
// 文件可能已不存在,忽略错误
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
this.removeAllListeners();
|
|
596
|
-
this.logger.debug(`Plugin "${this.name}" stopped`);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// ============================================================================
|
|
600
|
-
// 生命周期钩子
|
|
601
|
-
// ============================================================================
|
|
602
|
-
|
|
603
|
-
onMounted(callback: () => void | Promise<void>): void {
|
|
604
|
-
this.on("mounted", callback);
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
onDispose(callback: () => void | Promise<void>): () => void {
|
|
608
|
-
this.#disposables.add(callback);
|
|
609
|
-
return () => {
|
|
610
|
-
this.#disposables.delete(callback);
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
// ============================================================================
|
|
615
|
-
// 事件广播
|
|
616
|
-
// ============================================================================
|
|
617
|
-
|
|
618
|
-
/**
|
|
619
|
-
* dispatch - 向上冒泡到父插件,或在根节点广播
|
|
620
|
-
*/
|
|
621
|
-
async dispatch<K extends keyof Plugin.Lifecycle>(
|
|
622
|
-
name: K,
|
|
623
|
-
...args: Plugin.Lifecycle[K]
|
|
624
|
-
): Promise<void> {
|
|
625
|
-
if (this.parent) {
|
|
626
|
-
return this.parent.dispatch(name, ...args);
|
|
627
|
-
}
|
|
628
|
-
return this.broadcast(name, ...args);
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
* broadcast - 向下广播到所有子插件
|
|
633
|
-
*/
|
|
634
|
-
async broadcast<K extends keyof Plugin.Lifecycle>(
|
|
635
|
-
name: K,
|
|
636
|
-
...args: Plugin.Lifecycle[K]
|
|
637
|
-
): Promise<void> {
|
|
638
|
-
const listeners = this.listeners(name) as ((...args: any[]) => any)[];
|
|
639
|
-
for (const listener of listeners) {
|
|
640
|
-
await listener(...args);
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
for (const child of this.children) {
|
|
644
|
-
await child.broadcast(name, ...args);
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
// ============================================================================
|
|
649
|
-
// 依赖注入
|
|
650
|
-
// ============================================================================
|
|
651
|
-
|
|
652
|
-
/**
|
|
653
|
-
* 注册上下文
|
|
654
|
-
*/
|
|
655
|
-
provide<T extends keyof Plugin.Contexts>(context: Context<T>): this;
|
|
656
|
-
provide(feature: Feature<unknown>): this;
|
|
657
|
-
provide(target: Context<keyof Plugin.Contexts> | Feature<unknown>): this {
|
|
658
|
-
if (target instanceof Feature) {
|
|
659
|
-
const feature = target;
|
|
660
|
-
const ctx: Context<keyof Plugin.Contexts> = {
|
|
661
|
-
name: feature.name as keyof Plugin.Contexts,
|
|
662
|
-
description: feature.desc,
|
|
663
|
-
value: feature as Plugin.Contexts[keyof Plugin.Contexts],
|
|
664
|
-
mounted: feature.mounted
|
|
665
|
-
? async (plugin: Plugin) => {
|
|
666
|
-
await feature.mounted!(plugin);
|
|
667
|
-
return feature as Plugin.Contexts[keyof Plugin.Contexts];
|
|
668
|
-
}
|
|
669
|
-
: undefined,
|
|
670
|
-
dispose: feature.dispose
|
|
671
|
-
? async () => { await feature.dispose!(); }
|
|
672
|
-
: undefined,
|
|
673
|
-
extensions: feature.extensions,
|
|
674
|
-
};
|
|
675
|
-
return this.provide(ctx);
|
|
676
|
-
}
|
|
677
|
-
const context = target;
|
|
678
|
-
if (!Plugin[contextsKey].includes(context.name as string)) {
|
|
679
|
-
Plugin[contextsKey].push(context.name as string);
|
|
680
|
-
}
|
|
681
|
-
this.logger.debug(`Context "${context.name as string}" provided`);
|
|
682
|
-
this.$contexts.set(context.name as string, context);
|
|
683
|
-
// 立即注册扩展方法到 Plugin.prototype,确保后续 import 的插件可用
|
|
684
|
-
if (context.extensions) {
|
|
685
|
-
for (const [name, fn] of Object.entries(context.extensions)) {
|
|
686
|
-
if (typeof fn === 'function') {
|
|
687
|
-
Reflect.set(Plugin.prototype, name, fn);
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
return this;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// ============================================================================
|
|
695
|
-
// 插件加载
|
|
696
|
-
// ============================================================================
|
|
697
|
-
|
|
698
|
-
/**
|
|
699
|
-
* 导入插件
|
|
700
|
-
*/
|
|
701
|
-
async import(entry: string,t?:number): Promise<Plugin> {
|
|
702
|
-
if (!entry) throw new Error(`Plugin entry not found: ${entry}`);
|
|
703
|
-
const resolved = resolveEntry(path.isAbsolute(entry) ?
|
|
704
|
-
entry :
|
|
705
|
-
path.resolve(path.dirname(this.filePath), entry)) || entry;
|
|
706
|
-
let realPath: string;
|
|
707
|
-
try {
|
|
708
|
-
realPath = fs.realpathSync(resolved);
|
|
709
|
-
} catch {
|
|
710
|
-
realPath = resolved;
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// 避免重复加载同一路径的插件
|
|
714
|
-
const normalized = realPath.replace(/\?t=\d+$/, '').replace(/\\/g, '/');
|
|
715
|
-
const existing = this.children.find(child =>
|
|
716
|
-
child.filePath.replace(/\?t=\d+$/, '').replace(/\\/g, '/') === normalized
|
|
717
|
-
);
|
|
718
|
-
if (existing) {
|
|
719
|
-
this.logger.debug(`Plugin "${entry}" already loaded, skipping...`);
|
|
720
|
-
if (this.started && !existing.started) await existing.start(t);
|
|
721
|
-
return existing;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
const plugin = await Plugin.create(realPath, this);
|
|
725
|
-
if (this.started) await plugin.start(t);
|
|
726
|
-
|
|
727
|
-
if (process.env.NODE_ENV === 'development') {
|
|
728
|
-
plugin.watch((p) => p.reload())
|
|
729
|
-
}
|
|
730
|
-
return plugin;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
/**
|
|
734
|
-
* 重载插件
|
|
735
|
-
*/
|
|
736
|
-
async reload(plugin: Plugin = this): Promise<void> {
|
|
737
|
-
this.logger.info(`Plugin "${plugin.name}" reloading...`);
|
|
738
|
-
const now=Date.now();
|
|
739
|
-
if (!plugin.parent) {
|
|
740
|
-
// 根插件重载 = 退出进程(由 CLI 重启)
|
|
741
|
-
return process.exit(51);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
await plugin.stop();
|
|
745
|
-
await plugin.parent.import(plugin.filePath, now);
|
|
746
|
-
await plugin.broadcast("mounted");
|
|
747
|
-
this.logger.debug(`Plugin "${plugin.name}" reloaded`);
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
/**
|
|
751
|
-
* 监听文件变化
|
|
752
|
-
*/
|
|
753
|
-
watch(
|
|
754
|
-
callback: (p: Plugin) => void | Promise<void>,
|
|
755
|
-
recursive = false
|
|
756
|
-
): void {
|
|
757
|
-
if (!this.filePath || this.filePath.includes("node_modules")) return;
|
|
758
|
-
|
|
759
|
-
const unwatch = watchFile(this.filePath, () => {
|
|
760
|
-
const newHash = getFileHash(this.filePath);
|
|
761
|
-
if (newHash === this.fileHash) return;
|
|
762
|
-
|
|
763
|
-
this.logger.debug(`Plugin "${this.name}" file changed, reloading...`);
|
|
764
|
-
callback(this);
|
|
765
|
-
this.fileHash = newHash;
|
|
766
|
-
});
|
|
767
|
-
|
|
768
|
-
this.on("dispose", unwatch);
|
|
769
|
-
|
|
770
|
-
if (recursive) {
|
|
771
|
-
for (const child of this.children) {
|
|
772
|
-
child.watch(callback, recursive);
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
// ============================================================================
|
|
778
|
-
// 辅助方法
|
|
779
|
-
// ============================================================================
|
|
780
|
-
|
|
781
|
-
// 核心方法列表(需要绑定的方法)
|
|
782
|
-
static #coreMethods = new Set([
|
|
783
|
-
'addMiddleware', 'useContext', 'inject', 'contextIsReady',
|
|
784
|
-
'start', 'stop', 'onMounted', 'onDispose',
|
|
785
|
-
'dispatch', 'broadcast', 'provide', 'import', 'reload', 'watch', 'info'
|
|
786
|
-
]);
|
|
787
|
-
|
|
788
|
-
/**
|
|
789
|
-
* 自动绑定核心方法(只在构造函数中调用一次)
|
|
790
|
-
*/
|
|
791
|
-
$bindMethods(): void {
|
|
792
|
-
if (this.#methodsBound) return;
|
|
793
|
-
this.#methodsBound = true;
|
|
794
|
-
|
|
795
|
-
const proto = Object.getPrototypeOf(this);
|
|
796
|
-
for (const key of Plugin.#coreMethods) {
|
|
797
|
-
const value = proto[key];
|
|
798
|
-
if (typeof value === "function") {
|
|
799
|
-
(this as any)[key] = value.bind(this);
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
// ============================================================================
|
|
805
|
-
// 静态方法
|
|
806
|
-
// ============================================================================
|
|
807
|
-
|
|
808
|
-
/**
|
|
809
|
-
* 创建插件实例(异步加载)
|
|
810
|
-
*/
|
|
811
|
-
static async create(entry: string, parent?: Plugin): Promise<Plugin> {
|
|
812
|
-
entry = path.resolve(
|
|
813
|
-
path.dirname(parent?.filePath || fileURLToPath(import.meta.url)),
|
|
814
|
-
entry
|
|
815
|
-
);
|
|
816
|
-
const entryFile = fs.existsSync(entry) ? entry : require.resolve(entry);
|
|
817
|
-
const realPath = fs.realpathSync(entryFile);
|
|
818
|
-
|
|
819
|
-
// 检查模块是否已加载
|
|
820
|
-
const existing = loadedModules.get(realPath);
|
|
821
|
-
if (existing) {
|
|
822
|
-
return existing;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
const plugin = new Plugin(realPath, parent);
|
|
826
|
-
plugin.fileHash = getFileHash(entryFile);
|
|
827
|
-
|
|
828
|
-
// 先记录,防止循环依赖时重复加载
|
|
829
|
-
loadedModules.set(realPath, plugin);
|
|
830
|
-
|
|
831
|
-
await storage.run(plugin, async () => {
|
|
832
|
-
await import(`${import.meta.resolve(entryFile)}?t=${Date.now()}`);
|
|
833
|
-
});
|
|
834
|
-
|
|
835
|
-
return plugin;
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
export function defineContext<T extends keyof Plugin.Contexts, E extends Partial<Plugin.Extensions> = {}>(options: Context<T, E>): Context<T, E> {
|
|
839
|
-
return options;
|
|
840
|
-
}
|
|
841
|
-
export interface Context<T extends keyof Plugin.Contexts = keyof Plugin.Contexts, E extends Partial<Plugin.Extensions> = {}> {
|
|
842
|
-
name: T;
|
|
843
|
-
description: string
|
|
844
|
-
value?: Plugin.Contexts[T];
|
|
845
|
-
mounted?: (parent: Plugin) => Plugin.Contexts[T] | Promise<Plugin.Contexts[T]>;
|
|
846
|
-
dispose?: (value: Plugin.Contexts[T]) => void;
|
|
847
|
-
/** 扩展方法,会自动挂载到 Plugin.prototype 上 */
|
|
848
|
-
extensions?: E;
|
|
849
|
-
}
|
|
850
|
-
// ============================================================================
|
|
851
|
-
// 类型定义
|
|
852
|
-
// ============================================================================
|
|
853
|
-
export namespace Plugin {
|
|
854
|
-
/**
|
|
855
|
-
* 插件提供的功能
|
|
856
|
-
*/
|
|
857
|
-
export interface Features {
|
|
858
|
-
commands: string[];
|
|
859
|
-
components: string[];
|
|
860
|
-
crons: string[];
|
|
861
|
-
middlewares: string[];
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
/**
|
|
865
|
-
* 生命周期事件
|
|
866
|
-
*/
|
|
867
|
-
export interface Lifecycle {
|
|
868
|
-
mounted: [];
|
|
869
|
-
dispose: [];
|
|
870
|
-
"before-start": [Plugin];
|
|
871
|
-
started: [Plugin];
|
|
872
|
-
"before-mount": [Plugin];
|
|
873
|
-
"before-dispose": [Plugin];
|
|
874
|
-
"call.recallMessage": [string, string, string];
|
|
875
|
-
'before.sendMessage': [SendOptions];
|
|
876
|
-
"context.mounted": [keyof Plugin.Contexts];
|
|
877
|
-
"context.dispose": [keyof Plugin.Contexts];
|
|
878
|
-
"message.receive": [any];
|
|
879
|
-
"bot.login.pending": [import('./built/login-assist.js').PendingLoginTask];
|
|
880
|
-
"request.receive": [any];
|
|
881
|
-
"notice.receive": [any];
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
/**
|
|
885
|
-
* 服务类型扩展点
|
|
886
|
-
* 各个 Context 通过 declare module 扩展此接口
|
|
887
|
-
*/
|
|
888
|
-
export interface Contexts extends Adapters {}
|
|
889
|
-
|
|
890
|
-
/**
|
|
891
|
-
* Service 扩展方法类型
|
|
892
|
-
* 这些方法由各个 Context 的 extensions 提供
|
|
893
|
-
* 在 Plugin.start() 时自动注册到 Plugin.prototype
|
|
894
|
-
*/
|
|
895
|
-
export interface Extensions {}
|
|
896
|
-
}
|