@zhin.js/core 1.0.16 → 1.0.18

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.
Files changed (118) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/REFACTORING_COMPLETE.md +178 -0
  3. package/REFACTORING_STATUS.md +263 -0
  4. package/lib/adapter.d.ts +44 -19
  5. package/lib/adapter.d.ts.map +1 -1
  6. package/lib/adapter.js +81 -50
  7. package/lib/adapter.js.map +1 -1
  8. package/lib/bot.d.ts +7 -12
  9. package/lib/bot.d.ts.map +1 -1
  10. package/lib/built/adapter-process.d.ts +36 -0
  11. package/lib/built/adapter-process.d.ts.map +1 -0
  12. package/lib/built/adapter-process.js +77 -0
  13. package/lib/built/adapter-process.js.map +1 -0
  14. package/lib/built/command.d.ts +46 -0
  15. package/lib/built/command.d.ts.map +1 -0
  16. package/lib/built/command.js +54 -0
  17. package/lib/built/command.js.map +1 -0
  18. package/lib/built/component.d.ts +42 -0
  19. package/lib/built/component.d.ts.map +1 -0
  20. package/lib/built/component.js +66 -0
  21. package/lib/built/component.js.map +1 -0
  22. package/lib/built/config.d.ts +31 -0
  23. package/lib/built/config.d.ts.map +1 -0
  24. package/lib/built/config.js +141 -0
  25. package/lib/built/config.js.map +1 -0
  26. package/lib/built/cron.d.ts +53 -0
  27. package/lib/built/cron.d.ts.map +1 -0
  28. package/lib/built/cron.js +79 -0
  29. package/lib/built/cron.js.map +1 -0
  30. package/lib/built/database.d.ts +17 -0
  31. package/lib/built/database.d.ts.map +1 -0
  32. package/lib/built/database.js +28 -0
  33. package/lib/built/database.js.map +1 -0
  34. package/lib/{permissions.d.ts → built/permission.d.ts} +5 -10
  35. package/lib/built/permission.d.ts.map +1 -0
  36. package/lib/{permissions.js → built/permission.js} +11 -10
  37. package/lib/built/permission.js.map +1 -0
  38. package/lib/command.d.ts +18 -7
  39. package/lib/command.d.ts.map +1 -1
  40. package/lib/command.js +36 -15
  41. package/lib/command.js.map +1 -1
  42. package/lib/component.d.ts +1 -1
  43. package/lib/component.d.ts.map +1 -1
  44. package/lib/component.js.map +1 -1
  45. package/lib/cron.d.ts +4 -12
  46. package/lib/cron.d.ts.map +1 -1
  47. package/lib/cron.js +33 -64
  48. package/lib/cron.js.map +1 -1
  49. package/lib/index.d.ts +11 -3
  50. package/lib/index.d.ts.map +1 -1
  51. package/lib/index.js +14 -4
  52. package/lib/index.js.map +1 -1
  53. package/lib/jsx-runtime.d.ts +2 -2
  54. package/lib/jsx.d.ts +2 -3
  55. package/lib/jsx.d.ts.map +1 -1
  56. package/lib/jsx.js.map +1 -1
  57. package/lib/message.d.ts +4 -7
  58. package/lib/message.d.ts.map +1 -1
  59. package/lib/message.js.map +1 -1
  60. package/lib/plugin.d.ts +164 -51
  61. package/lib/plugin.d.ts.map +1 -1
  62. package/lib/plugin.js +520 -137
  63. package/lib/plugin.js.map +1 -1
  64. package/lib/prompt.d.ts +1 -1
  65. package/lib/prompt.d.ts.map +1 -1
  66. package/lib/prompt.js +2 -1
  67. package/lib/prompt.js.map +1 -1
  68. package/lib/types.d.ts +33 -33
  69. package/lib/types.d.ts.map +1 -1
  70. package/lib/utils.d.ts +16 -1
  71. package/lib/utils.d.ts.map +1 -1
  72. package/lib/utils.js +166 -66
  73. package/lib/utils.js.map +1 -1
  74. package/package.json +17 -11
  75. package/src/adapter.ts +131 -80
  76. package/src/bot.ts +8 -13
  77. package/src/built/adapter-process.ts +77 -0
  78. package/src/built/command.ts +102 -0
  79. package/src/built/component.ts +111 -0
  80. package/src/built/config.ts +126 -0
  81. package/src/built/cron.ts +140 -0
  82. package/src/built/database.ts +38 -0
  83. package/src/{permissions.ts → built/permission.ts} +9 -12
  84. package/src/command.ts +48 -20
  85. package/src/component.ts +2 -3
  86. package/src/cron.ts +35 -70
  87. package/src/index.ts +15 -5
  88. package/src/jsx.ts +2 -3
  89. package/src/message.ts +3 -4
  90. package/src/plugin.ts +671 -184
  91. package/src/prompt.ts +4 -3
  92. package/src/types.ts +41 -35
  93. package/src/utils.ts +418 -296
  94. package/test/minimal-bot.ts +31 -0
  95. package/test/stress-test.ts +123 -0
  96. package/tests/command.test.ts +124 -44
  97. package/ASYNC-JSX-SUPPORT.md +0 -173
  98. package/lib/app.d.ts +0 -191
  99. package/lib/app.d.ts.map +0 -1
  100. package/lib/app.js +0 -604
  101. package/lib/app.js.map +0 -1
  102. package/lib/config.d.ts +0 -54
  103. package/lib/config.d.ts.map +0 -1
  104. package/lib/config.js +0 -308
  105. package/lib/config.js.map +0 -1
  106. package/lib/log-transport.d.ts +0 -37
  107. package/lib/log-transport.d.ts.map +0 -1
  108. package/lib/log-transport.js +0 -136
  109. package/lib/log-transport.js.map +0 -1
  110. package/lib/permissions.d.ts.map +0 -1
  111. package/lib/permissions.js.map +0 -1
  112. package/src/app.ts +0 -772
  113. package/src/config.ts +0 -397
  114. package/src/log-transport.ts +0 -163
  115. package/tests/app.test.ts +0 -265
  116. package/tests/permissions.test.ts +0 -358
  117. package/tests/plugin.test.ts +0 -234
  118. package/tests/prompt.test.ts +0 -223
package/lib/plugin.js CHANGED
@@ -1,179 +1,562 @@
1
+ /**
2
+ * Plugin 类 - 基于 zhinjs/next 的 Hooks 实现
3
+ * 移除 Dependency 继承,使用 AsyncLocalStorage 管理上下文
4
+ */
5
+ import { AsyncLocalStorage } from "async_hooks";
6
+ import { EventEmitter } from "events";
7
+ import { createRequire } from "module";
8
+ import * as fs from "fs";
9
+ import * as path from "path";
10
+ import { fileURLToPath } from "url";
11
+ import logger from "@zhin.js/logger";
12
+ import { compose, remove, resolveEntry } from "./utils.js";
13
+ import { Adapter } from "./adapter.js";
14
+ import { createHash } from "crypto";
15
+ const contextsKey = Symbol("contexts");
16
+ const loadedModules = new Map(); // 记录已加载的模块
17
+ const require = createRequire(import.meta.url);
1
18
  // ============================================================================
2
- // 插件类型定义(定义插件中间件、生命周期、命令、组件等核心类型)
19
+ // AsyncLocalStorage 上下文
3
20
  // ============================================================================
4
- import * as fs from 'fs';
5
- import { Dependency, } from "@zhin.js/hmr";
6
- import { renderComponents } from "./component.js";
7
- import { PluginError, MessageError, errorManager } from './errors.js';
8
- import { remove } from "./utils.js";
9
- import { Prompt } from "./prompt.js";
10
- import { Cron } from './cron.js';
21
+ export const storage = new AsyncLocalStorage();
22
+ /**
23
+ * 获取当前文件路径(调用者)
24
+ */
25
+ function getCurrentFile(metaUrl = import.meta.url) {
26
+ const previousPrepareStackTrace = Error.prepareStackTrace;
27
+ Error.prepareStackTrace = function (_, stack) {
28
+ return stack;
29
+ };
30
+ const stack = new Error().stack;
31
+ Error.prepareStackTrace = previousPrepareStackTrace;
32
+ const stackFiles = Array.from(new Set(stack.map((site) => site.getFileName())));
33
+ const idx = stackFiles.findIndex((f) => f === fileURLToPath(metaUrl) || f === metaUrl);
34
+ const result = stackFiles[idx + 1];
35
+ if (!result)
36
+ throw new Error("Cannot resolve current file path");
37
+ try {
38
+ return fileURLToPath(result);
39
+ }
40
+ catch {
41
+ return result;
42
+ }
43
+ }
44
+ /**
45
+ * usePlugin - 获取或创建当前插件实例
46
+ * 类似 React Hooks 的设计,根据调用文件自动创建插件树
47
+ */
48
+ export function usePlugin() {
49
+ const callerFile = getCurrentFile();
50
+ const parentPlugin = storage.getStore();
51
+ const newPlugin = new Plugin(callerFile, parentPlugin);
52
+ storage.enterWith(newPlugin);
53
+ return newPlugin;
54
+ }
55
+ /**
56
+ * getPlugin - 获取当前 AsyncLocalStorage 中的插件实例
57
+ * 用于 extensions 等场景,不创建新插件
58
+ */
59
+ export function getPlugin() {
60
+ const plugin = storage.getStore();
61
+ if (!plugin) {
62
+ throw new Error('getPlugin() must be called within a plugin context');
63
+ }
64
+ return plugin;
65
+ }
11
66
  // ============================================================================
12
- // Plugin 类(插件的生命周期、命令/中间件/组件/定时任务等管理)
67
+ // 文件工具函数
13
68
  // ============================================================================
14
69
  /**
15
- * 插件类:继承自 Dependency,提供机器人特定功能与生命周期管理。
16
- * 支持命令注册、中间件、组件、定时任务、模型等。
70
+ * 获取文件 Hash(用于检测变更)
17
71
  */
18
- export class Plugin extends Dependency {
19
- middlewares = [];
20
- components = new Map();
21
- permissions = [];
22
- definitions = new Map();
23
- commands = [];
24
- crons = [];
25
- #logger;
72
+ function getFileHash(filePath) {
73
+ try {
74
+ const content = fs.readFileSync(filePath, "utf-8");
75
+ return createHash("md5")
76
+ .update(content)
77
+ .digest("hex");
78
+ }
79
+ catch {
80
+ return "";
81
+ }
82
+ }
83
+ /**
84
+ * 监听文件变化
85
+ */
86
+ function watchFile(filePath, callback) {
87
+ try {
88
+ const watcher = fs.watch(filePath, callback);
89
+ watcher.on("error", (_error) => { });
90
+ return () => watcher.close();
91
+ }
92
+ catch (error) {
93
+ return () => { };
94
+ }
95
+ }
96
+ /**
97
+ * Plugin 类 - 核心插件系统
98
+ * 直接继承 EventEmitter
99
+ */
100
+ export class Plugin extends EventEmitter {
101
+ parent;
102
+ static [contextsKey] = [];
103
+ #cachedName;
104
+ adapters = [];
105
+ started = false;
106
+ // 上下文存储
107
+ $contexts = new Map();
108
+ // 子插件
109
+ children = [];
110
+ // 文件信息
111
+ filePath;
112
+ fileHash = "";
113
+ // Logger
114
+ logger;
115
+ #messageMiddleware = async (message, next) => {
116
+ const commandService = this.inject('command');
117
+ if (!commandService)
118
+ return await next();
119
+ const result = await commandService.handle(message, this);
120
+ if (!result)
121
+ return await next();
122
+ const adapter = this.inject(message.$adapter);
123
+ if (!adapter || !(adapter instanceof Adapter))
124
+ return await next();
125
+ await adapter.emit('call.sendMessage', message.$bot, {
126
+ context: message.$adapter,
127
+ bot: message.$bot,
128
+ content: result,
129
+ id: message.$channel.id,
130
+ type: message.$channel.type,
131
+ });
132
+ await next();
133
+ };
134
+ // 插件功能
135
+ #middlewares = [this.#messageMiddleware];
136
+ // 统一的清理函数集合
137
+ #disposables = new Set();
138
+ get middleware() {
139
+ return compose(this.#middlewares);
140
+ }
26
141
  /**
27
- * 构造函数:初始化插件,注册消息事件、命令中间件、资源清理等
28
- * @param parent 所属 App 实例
29
- * @param name 插件名
30
- * @param filePath 插件文件路径
142
+ * 构造函数
31
143
  */
32
- constructor(parent, name, filePath) {
33
- // 在测试环境中跳过文件检查
34
- if (process.env.NODE_ENV !== 'test' && !filePath.startsWith('/mock/')) {
35
- filePath = fs.realpathSync(filePath);
36
- }
37
- super(parent, name, filePath);
38
- this.logger.debug(`plugin ${name} created at ${filePath}`);
39
- // 绑定消息事件,自动分发到命令和中间件
40
- // 发送前渲染组件
41
- this.beforeSend((options) => renderComponents(this.components, options));
42
- // 资源清理:卸载时清空模型、定时任务等
43
- this.on('dispose', () => {
44
- for (const name of this.definitions.keys()) {
45
- this.app.database?.models.delete(name);
46
- }
47
- this.definitions.clear();
48
- for (const cron of this.crons) {
49
- cron.dispose();
50
- }
51
- this.crons.length = 0;
52
- });
53
- // 挂载时启动定时任务
54
- this.on('mounted', () => {
55
- for (const cron of this.crons) {
56
- cron.run();
57
- }
58
- });
144
+ constructor(filePath = "", parent) {
145
+ super();
146
+ this.parent = parent;
147
+ // 增加 EventEmitter 监听器限制,避免警告
148
+ // 因为插件可能注册多个命令/组件/中间件,每个都会添加 dispose 监听器
149
+ this.setMaxListeners(50);
150
+ this.filePath = filePath.replace(/\?t=\d+$/, "");
151
+ this.logger = this.name ? logger.getLogger(this.name) : logger;
152
+ // 自动添加到父节点
153
+ if (parent && !parent.children.includes(this)) {
154
+ parent.children.push(this);
155
+ }
156
+ // 绑定方法以支持解构使用
157
+ this.$bindMethods();
59
158
  }
60
- get config() {
61
- return this.app.getConfig(this.name);
159
+ // 标记是否已绑定方法
160
+ #methodsBound = false;
161
+ /**
162
+ * 添加中间件
163
+ * 中间件用于处理消息流转
164
+ */
165
+ addMiddleware(middleware, name) {
166
+ this.#middlewares.push(middleware);
167
+ const dispose = () => {
168
+ remove(this.#middlewares, middleware);
169
+ this.#disposables.delete(dispose);
170
+ };
171
+ this.#disposables.add(dispose);
172
+ return dispose;
62
173
  }
63
- defineSchema(rules) {
64
- const result = super.defineSchema(rules);
65
- this.app.changeSchema(this.name, this.schema);
66
- return result;
174
+ /**
175
+ * 插件名称
176
+ */
177
+ get name() {
178
+ if (this.#cachedName)
179
+ return this.#cachedName;
180
+ this.#cachedName = path
181
+ .relative(process.cwd(), this.filePath)
182
+ .replace(/\?t=\d+$/, "")
183
+ .replace(/\\/g, "/")
184
+ .replace(/\/index\.(js|ts)x?$/, "")
185
+ .replace(/\/(lib|src|dist)$/, "")
186
+ .replace(/.*\/node_modules\//, "")
187
+ .replace(/.*\//, "")
188
+ .replace(/\.(js|ts)x?$/, "");
189
+ return this.#cachedName;
67
190
  }
68
- set config(newConfig) {
69
- this.app.setConfig(this.name, newConfig);
191
+ /**
192
+ * 根插件
193
+ */
194
+ get root() {
195
+ if (!this.parent)
196
+ return this;
197
+ return this.parent.root;
70
198
  }
71
- addPermit(name, check) {
72
- this.permissions.push({ name, check });
73
- return this;
199
+ get contexts() {
200
+ const result = new Map();
201
+ for (const [key, value] of this.$contexts) {
202
+ result.set(key, value);
203
+ }
204
+ for (const child of this.children) {
205
+ for (const [key, value] of child.contexts) {
206
+ result.set(key, value);
207
+ }
208
+ }
209
+ return result;
74
210
  }
75
- getPermit(name) {
76
- return this.app.permissions.get(name);
211
+ useContext(...args) {
212
+ const contexts = args.slice(0, -1);
213
+ const sideEffect = args[args.length - 1];
214
+ const contextReadyCallback = async () => {
215
+ if (sideEffect.finished)
216
+ return;
217
+ sideEffect.finished = true;
218
+ const args = contexts.map(item => this.inject(item));
219
+ const dispose = await sideEffect(...args);
220
+ if (!dispose)
221
+ return;
222
+ const disposeFn = async (name) => {
223
+ if (contexts.includes(name)) {
224
+ await dispose(this.inject(name));
225
+ }
226
+ this.off('context.dispose', disposeFn);
227
+ sideEffect.finished = false;
228
+ };
229
+ this.on('context.dispose', disposeFn);
230
+ // 确保 dispose 时清理监听器(只注册一次)
231
+ const cleanupOnDispose = () => {
232
+ this.off('context.dispose', disposeFn);
233
+ dispose(this.inject(args[0]));
234
+ };
235
+ this.once('dispose', cleanupOnDispose);
236
+ };
237
+ const onContextMounted = async (name) => {
238
+ if (!this.#contextsIsReady(contexts) || !(contexts).includes(name))
239
+ return;
240
+ await contextReadyCallback();
241
+ };
242
+ this.on('context.mounted', onContextMounted);
243
+ // 插件销毁时移除 context.mounted 监听器
244
+ this.once('dispose', () => this.off('context.mounted', onContextMounted));
245
+ if (!this.#contextsIsReady(contexts))
246
+ return;
247
+ contextReadyCallback();
77
248
  }
78
- cron(cronExpression, callback) {
79
- const cronJob = new Cron(cronExpression, callback);
80
- this.crons.push(cronJob);
81
- return this;
249
+ inject(name) {
250
+ const context = this.root.contexts.get(name);
251
+ return context?.value;
82
252
  }
83
- async #runMiddlewares(message, index) {
84
- const middlewareList = [...this.app.middlewares, ...this.middlewares];
85
- if (index >= middlewareList.length)
86
- return;
87
- const middleware = middlewareList[index];
253
+ #contextsIsReady(contexts) {
254
+ if (!contexts.length)
255
+ return true;
256
+ return contexts.every(name => this.contextIsReady(name));
257
+ }
258
+ contextIsReady(name) {
88
259
  try {
89
- await middleware(message, () => this.#runMiddlewares(message, index + 1));
260
+ return !!this.inject(name);
90
261
  }
91
- catch (error) {
92
- throw new PluginError(`中间件执行失败: ${error.message}`, this.name, { middlewareIndex: index, originalError: error });
262
+ catch {
263
+ return false;
93
264
  }
94
265
  }
95
- defineModel(name, definition) {
96
- this.definitions.set(name, definition);
97
- return this;
98
- }
99
- beforeSend(handler) {
100
- this.before('message.send', handler);
266
+ // ============================================================================
267
+ // 生命周期方法
268
+ // ============================================================================
269
+ /**
270
+ * 启动插件
271
+ */
272
+ async start(t) {
273
+ if (this.started)
274
+ return;
275
+ this.started = true; // 提前设置,防止重复启动
276
+ // 启动所有服务
277
+ for (const context of this.$contexts.values()) {
278
+ if (typeof context.mounted === "function" && !context.value) {
279
+ context.value = await context.mounted(this);
280
+ }
281
+ // 注册扩展方法到 Plugin.prototype
282
+ if (context.extensions) {
283
+ for (const [name, fn] of Object.entries(context.extensions)) {
284
+ if (typeof fn === 'function') {
285
+ Reflect.set(Plugin.prototype, name, fn);
286
+ }
287
+ }
288
+ }
289
+ this.dispatch('context.mounted', context.name);
290
+ }
291
+ await this.broadcast("mounted");
292
+ // 先启动子插件,再打印当前插件启动日志
293
+ for (const child of this.children) {
294
+ await child.start(t);
295
+ }
296
+ // 输出启动日志(使用 debug 级别,避免重复输出)
297
+ // 只在根插件或重要插件时使用 info 级别
298
+ if (!this.parent || this.name === 'setup') {
299
+ this.logger.info(`Plugin "${this.name}" ${t ? `reloaded in ${Date.now() - t}ms` : "started"}`);
300
+ }
301
+ else {
302
+ this.logger.debug(`Plugin "${this.name}" ${t ? `reloaded in ${Date.now() - t}ms` : "started"}`);
303
+ }
101
304
  }
102
- before(event, listener) {
103
- this.on(`before-${event}`, listener);
305
+ /**
306
+ * 获取插件提供的功能
307
+ * 从各个服务中获取数据
308
+ */
309
+ get features() {
310
+ const commandService = this.inject('command');
311
+ const componentService = this.inject('component');
312
+ const cronService = this.inject('cron');
313
+ return {
314
+ commands: commandService ? commandService.items.map(c => c.pattern) : [],
315
+ components: componentService ? componentService.getAllNames() : [],
316
+ crons: cronService ? cronService.items.map(c => c.cronExpression) : [],
317
+ middlewares: this.#middlewares.map((m, i) => m.name || `middleware_${i}`),
318
+ };
104
319
  }
105
- /** 获取所属的App实例 */
106
- get app() {
107
- return this.parent;
320
+ info() {
321
+ return {
322
+ [this.name]: {
323
+ features: this.features,
324
+ children: this.children.map(child => child.info())
325
+ }
326
+ };
108
327
  }
109
- get logger() {
110
- if (this.#logger)
111
- return this.#logger;
112
- const names = [];
113
- let temp = this;
114
- while (temp.parent) {
115
- names.unshift(temp.name);
116
- temp = temp.parent;
328
+ /**
329
+ * 停止插件
330
+ */
331
+ async stop() {
332
+ if (!this.started)
333
+ return;
334
+ this.logger.debug(`Stopping plugin "${this.name}"`);
335
+ this.started = false;
336
+ // 停止子插件
337
+ for (const child of this.children) {
338
+ await child.stop();
117
339
  }
118
- return temp.getLogger(names.join('/'));
119
- }
120
- /** 添加组件 */
121
- addComponent(component) {
122
- this.components.set(component.name, component);
340
+ this.children = [];
341
+ // 停止服务
342
+ for (const [name, context] of this.$contexts) {
343
+ remove(Plugin[contextsKey], name);
344
+ // 移除扩展方法
345
+ if (context.extensions) {
346
+ for (const key of Object.keys(context.extensions)) {
347
+ delete Plugin.prototype[key];
348
+ }
349
+ }
350
+ if (typeof context.dispose === "function") {
351
+ await context.dispose(context.value);
352
+ }
353
+ }
354
+ // 清理 contexts Map
355
+ this.$contexts.clear();
356
+ // 清空缓存的名称
357
+ this.#cachedName = undefined;
358
+ // 触发 dispose 事件
359
+ this.emit("dispose");
360
+ // 执行所有清理函数
361
+ for (const dispose of this.#disposables) {
362
+ try {
363
+ await dispose();
364
+ }
365
+ catch (e) {
366
+ this.logger.warn(`Dispose callback failed: ${e}`);
367
+ }
368
+ }
369
+ this.#disposables.clear();
370
+ // 清理 middlewares 数组(保留默认的消息中间件)
371
+ this.#middlewares.length = 1;
372
+ if (this.parent) {
373
+ remove(this.parent?.children, this);
374
+ }
375
+ // 从全局 loadedModules Map 中移除,防止内存泄漏
376
+ if (this.filePath) {
377
+ try {
378
+ const realPath = fs.realpathSync(this.filePath);
379
+ loadedModules.delete(realPath);
380
+ }
381
+ catch {
382
+ // 文件可能已不存在,忽略错误
383
+ }
384
+ }
385
+ this.removeAllListeners();
386
+ this.logger.debug(`Plugin "${this.name}" stopped`);
123
387
  }
124
- /** 添加中间件 */
125
- addCommand(command) {
126
- this.commands.push(command);
127
- this.dispatch('command.add', command);
388
+ // ============================================================================
389
+ // 生命周期钩子
390
+ // ============================================================================
391
+ onMounted(callback) {
392
+ this.on("mounted", callback);
128
393
  }
129
- /** 添加中间件 */
130
- addMiddleware(middleware) {
131
- this.middlewares.push(middleware);
132
- this.dispatch('middleware.add', middleware);
394
+ onDispose(callback) {
395
+ this.#disposables.add(callback);
133
396
  return () => {
134
- remove(this.middlewares, middleware);
397
+ this.#disposables.delete(callback);
135
398
  };
136
399
  }
137
- prompt(message) {
138
- return new Prompt(this, message);
400
+ // ============================================================================
401
+ // 事件广播
402
+ // ============================================================================
403
+ /**
404
+ * dispatch - 向上冒泡到父插件,或在根节点广播
405
+ */
406
+ async dispatch(name, ...args) {
407
+ if (this.parent) {
408
+ return this.parent.dispatch(name, ...args);
409
+ }
410
+ return this.broadcast(name, ...args);
139
411
  }
140
- /** 发送消息 */
141
- async sendMessage(options) {
142
- try {
143
- await this.app.sendMessage(options);
412
+ /**
413
+ * broadcast - 向下广播到所有子插件
414
+ */
415
+ async broadcast(name, ...args) {
416
+ const listeners = this.listeners(name);
417
+ for (const listener of listeners) {
418
+ await listener(...args);
144
419
  }
145
- catch (error) {
146
- const messageError = new MessageError(`发送消息失败: ${error.message}`, undefined, options.channel_id, { pluginName: this.name, sendOptions: options, originalError: error });
147
- await errorManager.handle(messageError);
148
- throw messageError;
420
+ for (const child of this.children) {
421
+ await child.broadcast(name, ...args);
149
422
  }
150
423
  }
151
- recallMessage(adapter, bot, id) {
424
+ // ============================================================================
425
+ // 依赖注入
426
+ // ============================================================================
427
+ /**
428
+ * 注册上下文
429
+ */
430
+ provide(context) {
431
+ if (!Plugin[contextsKey].includes(context.name)) {
432
+ Plugin[contextsKey].push(context.name);
433
+ }
434
+ this.logger.debug(`Context "${context.name}" provided`);
435
+ this.$contexts.set(context.name, context);
436
+ return this;
437
+ }
438
+ // ============================================================================
439
+ // 插件加载
440
+ // ============================================================================
441
+ /**
442
+ * 导入插件
443
+ */
444
+ async import(entry, t) {
445
+ if (!entry)
446
+ throw new Error(`Plugin entry not found: ${entry}`);
447
+ const resolved = resolveEntry(path.isAbsolute(entry) ?
448
+ entry :
449
+ path.resolve(path.dirname(this.filePath), entry)) || entry;
450
+ let realPath;
152
451
  try {
153
- this.app.recallMessage(adapter, bot, id);
452
+ realPath = fs.realpathSync(resolved);
453
+ }
454
+ catch {
455
+ realPath = resolved;
154
456
  }
155
- catch (error) {
156
- const messageError = new MessageError(`撤回消息失败: ${error.message}`, id, undefined, { originalError: error });
157
- errorManager.handle(messageError);
158
- throw messageError;
457
+ // 避免重复加载同一路径的插件
458
+ const normalized = realPath.replace(/\?t=\d+$/, '').replace(/\\/g, '/');
459
+ const existing = this.children.find(child => child.filePath.replace(/\?t=\d+$/, '').replace(/\\/g, '/') === normalized);
460
+ if (existing) {
461
+ this.logger.debug(`Plugin "${entry}" already loaded, skipping...`);
462
+ if (this.started && !existing.started)
463
+ await existing.start(t);
464
+ return existing;
159
465
  }
466
+ const plugin = await Plugin.create(realPath, this);
467
+ if (this.started)
468
+ await plugin.start(t);
469
+ if (process.env.NODE_ENV === 'development') {
470
+ plugin.watch((p) => p.reload());
471
+ }
472
+ return plugin;
160
473
  }
161
- /** 销毁插件 */
162
- dispose() {
163
- try {
164
- // 移除所有中间件
165
- for (const middleware of this.middlewares) {
166
- this.dispatch('middleware.remove', middleware);
474
+ /**
475
+ * 重载插件
476
+ */
477
+ async reload(plugin = this) {
478
+ this.logger.info(`Plugin "${plugin.name}" reloading...`);
479
+ const now = Date.now();
480
+ if (!plugin.parent) {
481
+ // 根插件重载 = 退出进程(由 CLI 重启)
482
+ return process.exit(51);
483
+ }
484
+ await plugin.stop();
485
+ await plugin.parent.import(plugin.filePath, now);
486
+ await plugin.broadcast("mounted");
487
+ this.logger.debug(`Plugin "${plugin.name}" reloaded`);
488
+ }
489
+ /**
490
+ * 监听文件变化
491
+ */
492
+ watch(callback, recursive = false) {
493
+ if (!this.filePath || this.filePath.includes("node_modules"))
494
+ return;
495
+ const unwatch = watchFile(this.filePath, () => {
496
+ const newHash = getFileHash(this.filePath);
497
+ if (newHash === this.fileHash)
498
+ return;
499
+ this.logger.debug(`Plugin "${this.name}" file changed, reloading...`);
500
+ callback(this);
501
+ this.fileHash = newHash;
502
+ });
503
+ this.on("dispose", unwatch);
504
+ if (recursive) {
505
+ for (const child of this.children) {
506
+ child.watch(callback, recursive);
507
+ }
508
+ }
509
+ }
510
+ // ============================================================================
511
+ // 辅助方法
512
+ // ============================================================================
513
+ // 核心方法列表(需要绑定的方法)
514
+ static #coreMethods = new Set([
515
+ 'addMiddleware', 'useContext', 'inject', 'contextIsReady',
516
+ 'start', 'stop', 'onMounted', 'onDispose',
517
+ 'dispatch', 'broadcast', 'provide', 'import', 'reload', 'watch', 'info'
518
+ ]);
519
+ /**
520
+ * 自动绑定核心方法(只在构造函数中调用一次)
521
+ */
522
+ $bindMethods() {
523
+ if (this.#methodsBound)
524
+ return;
525
+ this.#methodsBound = true;
526
+ const proto = Object.getPrototypeOf(this);
527
+ for (const key of Plugin.#coreMethods) {
528
+ const value = proto[key];
529
+ if (typeof value === "function") {
530
+ this[key] = value.bind(this);
167
531
  }
168
- this.middlewares = [];
169
- // 调用父类的dispose方法
170
- super.dispose();
171
532
  }
172
- catch (error) {
173
- const pluginError = new PluginError(`插件销毁失败: ${error.message}`, this.name, { originalError: error });
174
- errorManager.handle(pluginError).catch(console.error);
175
- throw pluginError;
533
+ }
534
+ // ============================================================================
535
+ // 静态方法
536
+ // ============================================================================
537
+ /**
538
+ * 创建插件实例(异步加载)
539
+ */
540
+ static async create(entry, parent) {
541
+ entry = path.resolve(path.dirname(parent?.filePath || fileURLToPath(import.meta.url)), entry);
542
+ const entryFile = fs.existsSync(entry) ? entry : require.resolve(entry);
543
+ const realPath = fs.realpathSync(entryFile);
544
+ // 检查模块是否已加载
545
+ const existing = loadedModules.get(realPath);
546
+ if (existing) {
547
+ return existing;
176
548
  }
549
+ const plugin = new Plugin(realPath, parent);
550
+ plugin.fileHash = getFileHash(entryFile);
551
+ // 先记录,防止循环依赖时重复加载
552
+ loadedModules.set(realPath, plugin);
553
+ await storage.run(plugin, async () => {
554
+ await import(`${import.meta.resolve(entryFile)}?t=${Date.now()}`);
555
+ });
556
+ return plugin;
177
557
  }
178
558
  }
559
+ export function defineContext(options) {
560
+ return options;
561
+ }
179
562
  //# sourceMappingURL=plugin.js.map