@zhin.js/core 1.0.0 → 1.0.1

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 (121) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +295 -74
  4. package/lib/adapter.d.ts +39 -0
  5. package/lib/adapter.d.ts.map +1 -0
  6. package/{dist → lib}/adapter.js +20 -2
  7. package/lib/adapter.js.map +1 -0
  8. package/lib/app.d.ts +115 -0
  9. package/lib/app.d.ts.map +1 -0
  10. package/{dist → lib}/app.js +148 -78
  11. package/lib/app.js.map +1 -0
  12. package/lib/bot.d.ts +31 -0
  13. package/lib/bot.d.ts.map +1 -0
  14. package/lib/command.d.ts +32 -0
  15. package/lib/command.d.ts.map +1 -0
  16. package/lib/command.js +46 -0
  17. package/lib/command.js.map +1 -0
  18. package/lib/component.d.ts +107 -0
  19. package/lib/component.d.ts.map +1 -0
  20. package/lib/component.js +273 -0
  21. package/lib/component.js.map +1 -0
  22. package/{dist → lib}/config.d.ts.map +1 -1
  23. package/{dist → lib}/config.js +6 -9
  24. package/lib/config.js.map +1 -0
  25. package/lib/cron.d.ts +81 -0
  26. package/lib/cron.d.ts.map +1 -0
  27. package/lib/cron.js +159 -0
  28. package/lib/cron.js.map +1 -0
  29. package/lib/errors.d.ts +165 -0
  30. package/lib/errors.d.ts.map +1 -0
  31. package/lib/errors.js +306 -0
  32. package/lib/errors.js.map +1 -0
  33. package/lib/index.d.ts +15 -0
  34. package/lib/index.d.ts.map +1 -0
  35. package/lib/index.js +17 -0
  36. package/lib/index.js.map +1 -0
  37. package/lib/message.d.ts +44 -0
  38. package/lib/message.d.ts.map +1 -0
  39. package/lib/message.js +11 -0
  40. package/lib/message.js.map +1 -0
  41. package/lib/plugin.d.ts +50 -0
  42. package/lib/plugin.d.ts.map +1 -0
  43. package/lib/plugin.js +170 -0
  44. package/lib/plugin.js.map +1 -0
  45. package/lib/prompt.d.ts +116 -0
  46. package/lib/prompt.d.ts.map +1 -0
  47. package/lib/prompt.js +240 -0
  48. package/lib/prompt.js.map +1 -0
  49. package/lib/schema.d.ts +83 -0
  50. package/lib/schema.d.ts.map +1 -0
  51. package/lib/schema.js +245 -0
  52. package/lib/schema.js.map +1 -0
  53. package/{dist → lib}/types-generator.d.ts.map +1 -1
  54. package/{dist → lib}/types-generator.js +6 -3
  55. package/lib/types-generator.js.map +1 -0
  56. package/lib/types.d.ts +119 -0
  57. package/lib/types.d.ts.map +1 -0
  58. package/lib/utils.d.ts +52 -0
  59. package/lib/utils.d.ts.map +1 -0
  60. package/lib/utils.js +338 -0
  61. package/lib/utils.js.map +1 -0
  62. package/package.json +15 -9
  63. package/src/adapter.ts +25 -9
  64. package/src/app.ts +363 -258
  65. package/src/bot.ts +29 -8
  66. package/src/command.ts +50 -0
  67. package/src/component.ts +318 -0
  68. package/src/config.ts +9 -12
  69. package/src/cron.ts +176 -0
  70. package/src/errors.ts +365 -0
  71. package/src/index.ts +16 -13
  72. package/src/message.ts +44 -0
  73. package/src/plugin.ts +148 -66
  74. package/src/prompt.ts +290 -0
  75. package/src/schema.ts +273 -0
  76. package/src/types-generator.ts +7 -3
  77. package/src/types.ts +77 -30
  78. package/src/utils.ts +312 -0
  79. package/tests/adapter.test.ts +36 -22
  80. package/tests/app.test.ts +30 -0
  81. package/tests/command.test.ts +545 -0
  82. package/tests/component.test.ts +656 -0
  83. package/tests/config.test.ts +1 -1
  84. package/tests/errors.test.ts +311 -0
  85. package/tests/message.test.ts +402 -0
  86. package/tests/plugin.test.ts +275 -143
  87. package/tests/utils.test.ts +80 -0
  88. package/tsconfig.json +3 -4
  89. package/dist/adapter.d.ts +0 -22
  90. package/dist/adapter.d.ts.map +0 -1
  91. package/dist/adapter.js.map +0 -1
  92. package/dist/app.d.ts +0 -69
  93. package/dist/app.d.ts.map +0 -1
  94. package/dist/app.js.map +0 -1
  95. package/dist/bot.d.ts +0 -9
  96. package/dist/bot.d.ts.map +0 -1
  97. package/dist/config.js.map +0 -1
  98. package/dist/index.d.ts +0 -9
  99. package/dist/index.d.ts.map +0 -1
  100. package/dist/index.js +0 -12
  101. package/dist/index.js.map +0 -1
  102. package/dist/logger.d.ts +0 -3
  103. package/dist/logger.d.ts.map +0 -1
  104. package/dist/logger.js +0 -3
  105. package/dist/logger.js.map +0 -1
  106. package/dist/plugin.d.ts +0 -41
  107. package/dist/plugin.d.ts.map +0 -1
  108. package/dist/plugin.js +0 -95
  109. package/dist/plugin.js.map +0 -1
  110. package/dist/types-generator.js.map +0 -1
  111. package/dist/types.d.ts +0 -69
  112. package/dist/types.d.ts.map +0 -1
  113. package/src/logger.ts +0 -3
  114. package/tests/logger.test.ts +0 -170
  115. package/tsconfig.tsbuildinfo +0 -1
  116. /package/{dist → lib}/bot.js +0 -0
  117. /package/{dist → lib}/bot.js.map +0 -0
  118. /package/{dist → lib}/config.d.ts +0 -0
  119. /package/{dist → lib}/types-generator.d.ts +0 -0
  120. /package/{dist → lib}/types.js +0 -0
  121. /package/{dist → lib}/types.js.map +0 -0
@@ -0,0 +1,545 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
2
+ import { MessageCommand } from '../src/command'
3
+ import { Message } from '../src/message'
4
+
5
+ // Mock segment-matcher
6
+ vi.mock('segment-matcher', () => {
7
+ const MatchResult = {
8
+ matched: true,
9
+ args: [],
10
+ options: {}
11
+ }
12
+
13
+ class SegmentMatcher {
14
+ constructor(public pattern: string) {}
15
+
16
+ match(content: any) {
17
+ // 简单的mock实现
18
+ if (Array.isArray(content) && content.length > 0) {
19
+ const text = content[0]?.data?.text || ''
20
+ if (text.includes(this.pattern)) {
21
+ return {
22
+ matched: true,
23
+ args: [text.replace(this.pattern, '').trim()],
24
+ options: {}
25
+ }
26
+ }
27
+ }
28
+ return null
29
+ }
30
+ }
31
+
32
+ return { SegmentMatcher, MatchResult }
33
+ })
34
+
35
+ describe('Command系统测试', () => {
36
+ describe('MessageCommand基础功能测试', () => {
37
+ it('应该正确创建MessageCommand实例', () => {
38
+ const command = new MessageCommand('hello')
39
+ expect(command).toBeInstanceOf(MessageCommand)
40
+ expect(command.pattern).toBe('hello')
41
+ })
42
+
43
+ it('应该支持链式调用', () => {
44
+ const command = new MessageCommand('test')
45
+ .scope('discord')
46
+ .action(() => 'response')
47
+
48
+ expect(command).toBeInstanceOf(MessageCommand)
49
+ expect(typeof command.scope).toBe('function')
50
+ expect(typeof command.action).toBe('function')
51
+ })
52
+ })
53
+
54
+ describe('作用域(scope)测试', () => {
55
+ it('应该正确设置单个作用域', async () => {
56
+ const command = new MessageCommand('hello')
57
+ .scope('discord')
58
+ .action(() => 'Hello from Discord!')
59
+
60
+ // 匹配的适配器
61
+ const discordMessage: Message = {
62
+ $id: '1',
63
+ $adapter: 'discord',
64
+ $bot: 'discord-bot',
65
+ $content: [{ type: 'text', data: { text: 'hello world' } }],
66
+ $sender: { id: 'user1', name: 'User' },
67
+ $reply: vi.fn(),
68
+ $channel: { id: 'channel1', type: 'channel' },
69
+ $timestamp: Date.now(),
70
+ $raw: 'hello world'
71
+ }
72
+
73
+ // 不匹配的适配器
74
+ const telegramMessage: Message = {
75
+ $id: '2',
76
+ $adapter: 'telegram',
77
+ $bot: 'telegram-bot',
78
+ $content: [{ type: 'text', data: { text: 'hello world' } }],
79
+ $sender: { id: 'user2', name: 'User' },
80
+ $reply: vi.fn(),
81
+ $channel: { id: 'channel2', type: 'private' },
82
+ $timestamp: Date.now(),
83
+ $raw: 'hello world'
84
+ }
85
+
86
+ const discordResult = await command.handle(discordMessage)
87
+ const telegramResult = await command.handle(telegramMessage)
88
+
89
+ expect(discordResult).toBe('Hello from Discord!')
90
+ expect(telegramResult).toBeUndefined()
91
+ })
92
+
93
+ it('应该正确设置多个作用域', async () => {
94
+ const command = new MessageCommand('hello')
95
+ .scope('discord', 'telegram')
96
+ .action(() => 'Hello!')
97
+
98
+ const discordMessage: Message = {
99
+ $id: '1',
100
+ $adapter: 'discord',
101
+ $bot: 'discord-bot',
102
+ $content: [{ type: 'text', data: { text: 'hello' } }],
103
+ $sender: { id: 'user1', name: 'User' },
104
+ $reply: vi.fn(),
105
+ $channel: { id: 'channel1', type: 'channel' },
106
+ $timestamp: Date.now(),
107
+ $raw: 'hello'
108
+ }
109
+
110
+ const telegramMessage: Message = {
111
+ $id: '2',
112
+ $adapter: 'telegram',
113
+ $bot: 'telegram-bot',
114
+ $content: [{ type: 'text', data: { text: 'hello' } }],
115
+ $sender: { id: 'user2', name: 'User' },
116
+ $reply: vi.fn(),
117
+ $channel: { id: 'channel2', type: 'private' },
118
+ $timestamp: Date.now(),
119
+ $raw: 'hello'
120
+ }
121
+
122
+ const emailMessage: Message = {
123
+ $id: '3',
124
+ $adapter: 'email',
125
+ $bot: 'email-bot',
126
+ $content: [{ type: 'text', data: { text: 'hello' } }],
127
+ $sender: { id: 'user3', name: 'User' },
128
+ $reply: vi.fn(),
129
+ $channel: { id: 'channel3', type: 'private' },
130
+ $timestamp: Date.now(),
131
+ $raw: 'hello'
132
+ }
133
+
134
+ const discordResult = await command.handle(discordMessage)
135
+ const telegramResult = await command.handle(telegramMessage)
136
+ const emailResult = await command.handle(emailMessage)
137
+
138
+ expect(discordResult).toBe('Hello!')
139
+ expect(telegramResult).toBe('Hello!')
140
+ expect(emailResult).toBeUndefined()
141
+ })
142
+ })
143
+
144
+ describe('动作(action)测试', () => {
145
+ it('应该正确执行单个动作', async () => {
146
+ const actionSpy = vi.fn().mockReturnValue('Action executed!')
147
+
148
+ const command = new MessageCommand('test')
149
+ .action(actionSpy)
150
+
151
+ const message: Message = {
152
+ $id: '1',
153
+ $adapter: 'test',
154
+ $bot: 'test-bot',
155
+ $content: [{ type: 'text', data: { text: 'test message' } }],
156
+ $sender: { id: 'user1', name: 'User' },
157
+ $reply: vi.fn(),
158
+ $channel: { id: 'channel1', type: 'private' },
159
+ $timestamp: Date.now(),
160
+ $raw: 'test message'
161
+ }
162
+
163
+ const result = await command.handle(message)
164
+
165
+ expect(actionSpy).toHaveBeenCalledWith(message, expect.any(Object))
166
+ expect(result).toBe('Action executed!')
167
+ })
168
+
169
+ it('应该正确执行多个动作', async () => {
170
+ const action1 = vi.fn()
171
+ const action2 = vi.fn().mockReturnValue('Second action result')
172
+ const action3 = vi.fn().mockReturnValue('Third action result')
173
+
174
+ const command = new MessageCommand('test')
175
+ .action(action1)
176
+ .action(action2)
177
+ .action(action3)
178
+
179
+ const message: Message = {
180
+ $id: '1',
181
+ $adapter: 'test',
182
+ $bot: 'test-bot',
183
+ $content: [{ type: 'text', data: { text: 'test' } }],
184
+ $sender: { id: 'user1', name: 'User' },
185
+ $reply: vi.fn(),
186
+ $channel: { id: 'channel1', type: 'private' },
187
+ $timestamp: Date.now(),
188
+ $raw: 'test'
189
+ }
190
+
191
+ const result = await command.handle(message)
192
+
193
+ expect(action1).toHaveBeenCalled()
194
+ expect(action2).toHaveBeenCalled()
195
+ expect(action3).not.toHaveBeenCalled() // 第二个动作有返回值,所以第三个不会执行
196
+ expect(result).toBe('Second action result')
197
+ })
198
+
199
+ it('应该正确处理异步动作', async () => {
200
+ const asyncAction = vi.fn().mockResolvedValue('Async result')
201
+
202
+ const command = new MessageCommand('async')
203
+ .action(asyncAction)
204
+
205
+ const message: Message = {
206
+ $id: '1',
207
+ $adapter: 'test',
208
+ $bot: 'test-bot',
209
+ $content: [{ type: 'text', data: { text: 'async test' } }],
210
+ $sender: { id: 'user1', name: 'User' },
211
+ $reply: vi.fn(),
212
+ $channel: { id: 'channel1', type: 'private' },
213
+ $timestamp: Date.now(),
214
+ $raw: 'async test'
215
+ }
216
+
217
+ const result = await command.handle(message)
218
+
219
+ expect(asyncAction).toHaveBeenCalled()
220
+ expect(result).toBe('Async result')
221
+ })
222
+
223
+ it('应该正确传递匹配结果给动作', async () => {
224
+ const actionSpy = vi.fn().mockReturnValue('Got args!')
225
+
226
+ const command = new MessageCommand('echo')
227
+ .action(actionSpy)
228
+
229
+ const message: Message = {
230
+ $id: '1',
231
+ $adapter: 'test',
232
+ $bot: 'test-bot',
233
+ $content: [{ type: 'text', data: { text: 'echo hello world' } }],
234
+ $sender: { id: 'user1', name: 'User' },
235
+ $reply: vi.fn(),
236
+ $channel: { id: 'channel1', type: 'private' },
237
+ $timestamp: Date.now(),
238
+ $raw: 'echo hello world'
239
+ }
240
+
241
+ const result = await command.handle(message)
242
+
243
+ expect(actionSpy).toHaveBeenCalledWith(
244
+ message,
245
+ expect.objectContaining({
246
+ matched: true,
247
+ args: ['hello world']
248
+ })
249
+ )
250
+ expect(result).toBe('Got args!')
251
+ })
252
+ })
253
+
254
+ describe('消息处理(handle)测试', () => {
255
+ it('应该在不匹配时返回undefined', async () => {
256
+ const command = new MessageCommand('hello')
257
+ .action(() => 'Hello!')
258
+
259
+ const message: Message = {
260
+ $id: '1',
261
+ $adapter: 'test',
262
+ $bot: 'test-bot',
263
+ $content: [{ type: 'text', data: { text: 'goodbye' } }],
264
+ $sender: { id: 'user1', name: 'User' },
265
+ $reply: vi.fn(),
266
+ $channel: { id: 'channel1', type: 'private' },
267
+ $timestamp: Date.now(),
268
+ $raw: 'goodbye'
269
+ }
270
+
271
+ const result = await command.handle(message)
272
+ expect(result).toBeUndefined()
273
+ })
274
+
275
+ it('应该正确处理空消息内容', async () => {
276
+ const command = new MessageCommand('test')
277
+ .action(() => 'Response')
278
+
279
+ const message: Message = {
280
+ $id: '1',
281
+ $adapter: 'test',
282
+ $bot: 'test-bot',
283
+ $content: [],
284
+ $sender: { id: 'user1', name: 'User' },
285
+ $reply: vi.fn(),
286
+ $channel: { id: 'channel1', type: 'private' },
287
+ $timestamp: Date.now(),
288
+ $raw: ''
289
+ }
290
+
291
+ const result = await command.handle(message)
292
+ expect(result).toBeUndefined()
293
+ })
294
+
295
+ it('应该正确处理非文本消息', async () => {
296
+ const command = new MessageCommand('test')
297
+ .action(() => 'Response')
298
+
299
+ const message: Message = {
300
+ $id: '1',
301
+ $adapter: 'test',
302
+ $bot: 'test-bot',
303
+ $content: [
304
+ { type: 'image', data: { url: 'https://example.com/image.png' } }
305
+ ],
306
+ $sender: { id: 'user1', name: 'User' },
307
+ $reply: vi.fn(),
308
+ $channel: { id: 'channel1', type: 'private' },
309
+ $timestamp: Date.now(),
310
+ $raw: '[图片]'
311
+ }
312
+
313
+ const result = await command.handle(message)
314
+ expect(result).toBeUndefined()
315
+ })
316
+ })
317
+
318
+ describe('复合条件测试', () => {
319
+ it('应该同时满足作用域和匹配条件', async () => {
320
+ const command = new MessageCommand('admin')
321
+ .scope('discord')
322
+ .action(() => 'Admin command executed')
323
+
324
+ // 正确的适配器和匹配的消息
325
+ const validMessage: Message = {
326
+ $id: '1',
327
+ $adapter: 'discord',
328
+ $bot: 'discord-bot',
329
+ $content: [{ type: 'text', data: { text: 'admin panel' } }],
330
+ $sender: { id: 'admin1', name: 'Admin' },
331
+ $reply: vi.fn(),
332
+ $channel: { id: 'admin-channel', type: 'private' },
333
+ $timestamp: Date.now(),
334
+ $raw: 'admin panel'
335
+ }
336
+
337
+ // 错误的适配器但匹配的消息
338
+ const wrongAdapterMessage: Message = {
339
+ $id: '2',
340
+ $adapter: 'telegram',
341
+ $bot: 'telegram-bot',
342
+ $content: [{ type: 'text', data: { text: 'admin panel' } }],
343
+ $sender: { id: 'admin2', name: 'Admin' },
344
+ $reply: vi.fn(),
345
+ $channel: { id: 'admin-channel', type: 'private' },
346
+ $timestamp: Date.now(),
347
+ $raw: 'admin panel'
348
+ }
349
+
350
+ // 正确的适配器但不匹配的消息
351
+ const nonMatchingMessage: Message = {
352
+ $id: '3',
353
+ $adapter: 'discord',
354
+ $bot: 'discord-bot',
355
+ $content: [{ type: 'text', data: { text: 'hello world' } }],
356
+ $sender: { id: 'user1', name: 'User' },
357
+ $reply: vi.fn(),
358
+ $channel: { id: 'general-channel', type: 'channel' },
359
+ $timestamp: Date.now(),
360
+ $raw: 'hello world'
361
+ }
362
+
363
+ const validResult = await command.handle(validMessage)
364
+ const wrongAdapterResult = await command.handle(wrongAdapterMessage)
365
+ const nonMatchingResult = await command.handle(nonMatchingMessage)
366
+
367
+ expect(validResult).toBe('Admin command executed')
368
+ expect(wrongAdapterResult).toBeUndefined()
369
+ expect(nonMatchingResult).toBeUndefined()
370
+ })
371
+ })
372
+
373
+ describe('错误处理测试', () => {
374
+ it('应该正确处理动作中的同步错误', async () => {
375
+ const errorAction = vi.fn().mockImplementation(() => {
376
+ throw new Error('Action failed')
377
+ })
378
+
379
+ const command = new MessageCommand('error')
380
+ .action(errorAction)
381
+
382
+ const message: Message = {
383
+ $id: '1',
384
+ $adapter: 'test',
385
+ $bot: 'test-bot',
386
+ $content: [{ type: 'text', data: { text: 'error test' } }],
387
+ $sender: { id: 'user1', name: 'User' },
388
+ $reply: vi.fn(),
389
+ $channel: { id: 'channel1', type: 'private' },
390
+ $timestamp: Date.now(),
391
+ $raw: 'error test'
392
+ }
393
+
394
+ await expect(command.handle(message)).rejects.toThrow('Action failed')
395
+ })
396
+
397
+ it('应该正确处理动作中的异步错误', async () => {
398
+ const asyncErrorAction = vi.fn().mockRejectedValue(new Error('Async action failed'))
399
+
400
+ const command = new MessageCommand('async-error')
401
+ .action(asyncErrorAction)
402
+
403
+ const message: Message = {
404
+ $id: '1',
405
+ $adapter: 'test',
406
+ $bot: 'test-bot',
407
+ $content: [{ type: 'text', data: { text: 'async-error test' } }],
408
+ $sender: { id: 'user1', name: 'User' },
409
+ $reply: vi.fn(),
410
+ $channel: { id: 'channel1', type: 'private' },
411
+ $timestamp: Date.now(),
412
+ $raw: 'async-error test'
413
+ }
414
+
415
+ await expect(command.handle(message)).rejects.toThrow('Async action failed')
416
+ })
417
+ })
418
+
419
+ describe('类型系统测试', () => {
420
+ it('应该支持泛型适配器类型约束', () => {
421
+ // 这主要是TypeScript编译时检查
422
+ const discordCommand: MessageCommand<'discord'> = new MessageCommand('test')
423
+ .scope('discord')
424
+ .action((message) => {
425
+ // message 应该有正确的类型
426
+ expect(message.$adapter).toBe('discord')
427
+ return 'Discord response'
428
+ })
429
+
430
+ expect(discordCommand).toBeInstanceOf(MessageCommand)
431
+ })
432
+ })
433
+
434
+ describe('性能测试', () => {
435
+ it('应该高效处理大量消息检查', async () => {
436
+ const action = vi.fn().mockReturnValue('Response')
437
+ const command = new MessageCommand('perf')
438
+ .scope('test')
439
+ .action(action)
440
+
441
+ const messages: Message[] = Array.from({ length: 1000 }, (_, i) => ({
442
+ $id: `msg-${i}`,
443
+ $adapter: i % 2 === 0 ? 'test' : 'other',
444
+ $bot: 'test-bot',
445
+ $content: [{ type: 'text', data: { text: i % 3 === 0 ? 'perf test' : 'other' } }],
446
+ $sender: { id: `user${i}`, name: `User ${i}` },
447
+ $reply: vi.fn(),
448
+ $channel: { id: `channel${i}`, type: 'private' },
449
+ $timestamp: Date.now(),
450
+ $raw: i % 3 === 0 ? 'perf test' : 'other'
451
+ }))
452
+
453
+ const startTime = Date.now()
454
+ const results = await Promise.all(
455
+ messages.map(message => command.handle(message))
456
+ )
457
+ const endTime = Date.now()
458
+
459
+ // 验证执行时间在合理范围内 (< 100ms for 1000 messages)
460
+ expect(endTime - startTime).toBeLessThan(100)
461
+
462
+ // 验证正确的消息被处理
463
+ const validResults = results.filter(r => r !== undefined)
464
+ expect(validResults.length).toBeGreaterThan(0)
465
+ expect(action).toHaveBeenCalled()
466
+ })
467
+ })
468
+
469
+ describe('复杂场景测试', () => {
470
+ it('应该支持命令参数解析', async () => {
471
+ const actionSpy = vi.fn((message, matchResult) => {
472
+ return `参数: ${matchResult.args.join(', ')}`
473
+ })
474
+
475
+ const command = new MessageCommand('say')
476
+ .action(actionSpy)
477
+
478
+ const message: Message = {
479
+ $id: '1',
480
+ $adapter: 'test',
481
+ $bot: 'test-bot',
482
+ $content: [{ type: 'text', data: { text: 'say hello world from bot' } }],
483
+ $sender: { id: 'user1', name: 'User' },
484
+ $reply: vi.fn(),
485
+ $channel: { id: 'channel1', type: 'private' },
486
+ $timestamp: Date.now(),
487
+ $raw: 'say hello world from bot'
488
+ }
489
+
490
+ const result = await command.handle(message)
491
+
492
+ expect(actionSpy).toHaveBeenCalledWith(
493
+ message,
494
+ expect.objectContaining({
495
+ args: ['hello world from bot']
496
+ })
497
+ )
498
+ expect(result).toBe('参数: hello world from bot')
499
+ })
500
+
501
+ it('应该支持条件链式处理', async () => {
502
+ const command = new MessageCommand('multi')
503
+ .scope('discord', 'telegram')
504
+ .action((message, result) => {
505
+ if (message.$channel.type === 'private') {
506
+ return '私人消息响应'
507
+ }
508
+ return null
509
+ })
510
+ .action(() => {
511
+ return '群组消息响应'
512
+ })
513
+
514
+ const privateMessage: Message = {
515
+ $id: '1',
516
+ $adapter: 'discord',
517
+ $bot: 'discord-bot',
518
+ $content: [{ type: 'text', data: { text: 'multi test' } }],
519
+ $sender: { id: 'user1', name: 'User' },
520
+ $reply: vi.fn(),
521
+ $channel: { id: 'dm-channel', type: 'private' },
522
+ $timestamp: Date.now(),
523
+ $raw: 'multi test'
524
+ }
525
+
526
+ const groupMessage: Message = {
527
+ $id: '2',
528
+ $adapter: 'telegram',
529
+ $bot: 'telegram-bot',
530
+ $content: [{ type: 'text', data: { text: 'multi test' } }],
531
+ $sender: { id: 'user2', name: 'User' },
532
+ $reply: vi.fn(),
533
+ $channel: { id: 'group-channel', type: 'group' },
534
+ $timestamp: Date.now(),
535
+ $raw: 'multi test'
536
+ }
537
+
538
+ const privateResult = await command.handle(privateMessage)
539
+ const groupResult = await command.handle(groupMessage)
540
+
541
+ expect(privateResult).toBe('私人消息响应')
542
+ expect(groupResult).toBe('群组消息响应')
543
+ })
544
+ })
545
+ })