@zhin.js/core 1.0.57 → 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.
Files changed (126) hide show
  1. package/lib/adapter.d.ts +1 -26
  2. package/lib/adapter.d.ts.map +1 -1
  3. package/lib/adapter.js +20 -117
  4. package/lib/adapter.js.map +1 -1
  5. package/lib/ai/index.d.ts +2 -0
  6. package/lib/ai/index.d.ts.map +1 -1
  7. package/lib/ai/index.js +1 -0
  8. package/lib/ai/index.js.map +1 -1
  9. package/lib/built/adapter-process.d.ts +0 -4
  10. package/lib/built/adapter-process.d.ts.map +1 -1
  11. package/lib/built/adapter-process.js +0 -95
  12. package/lib/built/adapter-process.js.map +1 -1
  13. package/lib/built/agent-preset.d.ts +2 -0
  14. package/lib/built/agent-preset.d.ts.map +1 -1
  15. package/lib/built/agent-preset.js +4 -0
  16. package/lib/built/agent-preset.js.map +1 -1
  17. package/lib/built/command.d.ts +4 -0
  18. package/lib/built/command.d.ts.map +1 -1
  19. package/lib/built/command.js +6 -0
  20. package/lib/built/command.js.map +1 -1
  21. package/lib/built/component.d.ts.map +1 -1
  22. package/lib/built/component.js +1 -0
  23. package/lib/built/component.js.map +1 -1
  24. package/lib/built/dispatcher.d.ts.map +1 -1
  25. package/lib/built/dispatcher.js +0 -13
  26. package/lib/built/dispatcher.js.map +1 -1
  27. package/lib/built/message-filter.d.ts +2 -0
  28. package/lib/built/message-filter.d.ts.map +1 -1
  29. package/lib/built/message-filter.js +5 -0
  30. package/lib/built/message-filter.js.map +1 -1
  31. package/lib/built/skill.d.ts +11 -0
  32. package/lib/built/skill.d.ts.map +1 -1
  33. package/lib/built/skill.js +14 -0
  34. package/lib/built/skill.js.map +1 -1
  35. package/lib/built/tool.d.ts +11 -44
  36. package/lib/built/tool.d.ts.map +1 -1
  37. package/lib/built/tool.js +14 -353
  38. package/lib/built/tool.js.map +1 -1
  39. package/lib/plugin.d.ts +1 -25
  40. package/lib/plugin.d.ts.map +1 -1
  41. package/lib/plugin.js +1 -77
  42. package/lib/plugin.js.map +1 -1
  43. package/lib/types.d.ts +0 -25
  44. package/lib/types.d.ts.map +1 -1
  45. package/package.json +10 -7
  46. package/CHANGELOG.md +0 -538
  47. package/REFACTORING_COMPLETE.md +0 -178
  48. package/REFACTORING_STATUS.md +0 -263
  49. package/src/adapter.ts +0 -275
  50. package/src/ai/index.ts +0 -52
  51. package/src/ai/providers/anthropic.ts +0 -379
  52. package/src/ai/providers/base.ts +0 -175
  53. package/src/ai/providers/index.ts +0 -13
  54. package/src/ai/providers/ollama.ts +0 -302
  55. package/src/ai/providers/openai.ts +0 -174
  56. package/src/ai/types.ts +0 -348
  57. package/src/bot.ts +0 -37
  58. package/src/built/adapter-process.ts +0 -177
  59. package/src/built/agent-preset.ts +0 -136
  60. package/src/built/ai-trigger.ts +0 -259
  61. package/src/built/command.ts +0 -108
  62. package/src/built/common-adapter-tools.ts +0 -242
  63. package/src/built/component.ts +0 -130
  64. package/src/built/config.ts +0 -335
  65. package/src/built/cron.ts +0 -156
  66. package/src/built/database.ts +0 -134
  67. package/src/built/dispatcher.ts +0 -496
  68. package/src/built/login-assist.ts +0 -131
  69. package/src/built/message-filter.ts +0 -390
  70. package/src/built/permission.ts +0 -151
  71. package/src/built/schema-feature.ts +0 -190
  72. package/src/built/skill.ts +0 -221
  73. package/src/built/tool.ts +0 -948
  74. package/src/command.ts +0 -87
  75. package/src/component.ts +0 -565
  76. package/src/cron.ts +0 -4
  77. package/src/errors.ts +0 -46
  78. package/src/feature.ts +0 -7
  79. package/src/index.ts +0 -53
  80. package/src/jsx-dev-runtime.ts +0 -2
  81. package/src/jsx-runtime.ts +0 -12
  82. package/src/jsx.ts +0 -135
  83. package/src/message.ts +0 -48
  84. package/src/models/system-log.ts +0 -20
  85. package/src/models/user.ts +0 -15
  86. package/src/notice.ts +0 -98
  87. package/src/plugin.ts +0 -896
  88. package/src/prompt.ts +0 -293
  89. package/src/request.ts +0 -95
  90. package/src/scheduler/index.ts +0 -19
  91. package/src/scheduler/scheduler.ts +0 -372
  92. package/src/scheduler/types.ts +0 -74
  93. package/src/tool-zod.ts +0 -115
  94. package/src/types-generator.ts +0 -78
  95. package/src/types.ts +0 -505
  96. package/src/utils.ts +0 -227
  97. package/tests/adapter.test.ts +0 -638
  98. package/tests/ai/ai-trigger.test.ts +0 -368
  99. package/tests/ai/providers.integration.test.ts +0 -227
  100. package/tests/ai/setup.ts +0 -308
  101. package/tests/ai/tool.test.ts +0 -800
  102. package/tests/bot.test.ts +0 -151
  103. package/tests/command.test.ts +0 -737
  104. package/tests/component-new.test.ts +0 -361
  105. package/tests/config.test.ts +0 -372
  106. package/tests/cron.test.ts +0 -82
  107. package/tests/dispatcher.test.ts +0 -293
  108. package/tests/errors.test.ts +0 -21
  109. package/tests/expression-evaluation.test.ts +0 -258
  110. package/tests/features-builtin.test.ts +0 -191
  111. package/tests/jsx-runtime.test.ts +0 -45
  112. package/tests/jsx.test.ts +0 -319
  113. package/tests/message-filter.test.ts +0 -566
  114. package/tests/message.test.ts +0 -402
  115. package/tests/notice.test.ts +0 -198
  116. package/tests/plugin.test.ts +0 -779
  117. package/tests/prompt.test.ts +0 -78
  118. package/tests/redos-protection.test.ts +0 -198
  119. package/tests/request.test.ts +0 -221
  120. package/tests/schema.test.ts +0 -248
  121. package/tests/skill-feature.test.ts +0 -179
  122. package/tests/test-utils.ts +0 -59
  123. package/tests/tool-feature.test.ts +0 -254
  124. package/tests/types.test.ts +0 -162
  125. package/tests/utils.test.ts +0 -135
  126. package/tsconfig.json +0 -24
@@ -1,566 +0,0 @@
1
- /**
2
- * MessageFilterFeature 测试
3
- */
4
- import { describe, it, expect, beforeEach, vi } from 'vitest';
5
- import {
6
- MessageFilterFeature,
7
- FilterRules,
8
- type FilterRule,
9
- type MessageFilterConfig,
10
- } from '../src/built/message-filter.js';
11
- import type { Message, MessageBase } from '../src/message.js';
12
-
13
- // ============================================================================
14
- // 辅助函数
15
- // ============================================================================
16
-
17
- function makeMessage(overrides: Partial<MessageBase> = {}): Message<any> {
18
- return {
19
- $id: '1',
20
- $adapter: 'test' as any,
21
- $bot: 'bot1',
22
- $content: [],
23
- $raw: '',
24
- $sender: { id: 'user1', name: 'User' },
25
- $channel: { id: 'ch1', type: 'group' },
26
- $timestamp: Date.now(),
27
- $reply: vi.fn(),
28
- $recall: vi.fn(),
29
- ...overrides,
30
- } as any;
31
- }
32
-
33
- // ============================================================================
34
- // 元数据
35
- // ============================================================================
36
-
37
- describe('MessageFilterFeature', () => {
38
- let filter: MessageFilterFeature;
39
-
40
- beforeEach(() => {
41
- filter = new MessageFilterFeature();
42
- });
43
-
44
- it('应有正确的元数据', () => {
45
- expect(filter.name).toBe('message-filter');
46
- expect(filter.icon).toBe('Filter');
47
- expect(filter.desc).toBe('消息过滤');
48
- });
49
-
50
- it('默认策略为 allow', () => {
51
- expect(filter.defaultPolicy).toBe('allow');
52
- });
53
-
54
- it('无规则时所有消息允许通过', () => {
55
- const result = filter.test(makeMessage());
56
- expect(result.allowed).toBe(true);
57
- expect(result.matchedRule).toBeNull();
58
- });
59
-
60
- // ============================================================================
61
- // 默认策略
62
- // ============================================================================
63
-
64
- describe('默认策略', () => {
65
- it('设置 deny 后无规则匹配时应拒绝', () => {
66
- filter.defaultPolicy = 'deny';
67
- const result = filter.test(makeMessage());
68
- expect(result.allowed).toBe(false);
69
- expect(result.matchedRule).toBeNull();
70
- expect(result.reason).toContain('deny');
71
- });
72
-
73
- it('设置 allow 后无规则匹配时应放行', () => {
74
- filter.defaultPolicy = 'allow';
75
- const result = filter.test(makeMessage());
76
- expect(result.allowed).toBe(true);
77
- });
78
- });
79
-
80
- // ============================================================================
81
- // 规则 CRUD
82
- // ============================================================================
83
-
84
- describe('规则 CRUD', () => {
85
- it('add 应注册规则', () => {
86
- const rule: FilterRule = { name: 'r1', action: 'deny' };
87
- filter.add(rule, 'test-plugin');
88
-
89
- expect(filter.count).toBe(1);
90
- expect(filter.getRule('r1')).toBe(rule);
91
- expect(filter.byName.has('r1')).toBe(true);
92
- });
93
-
94
- it('add 返回的 dispose 应移除规则', () => {
95
- const rule: FilterRule = { name: 'r1', action: 'deny' };
96
- const dispose = filter.add(rule, 'test-plugin');
97
-
98
- dispose();
99
- expect(filter.count).toBe(0);
100
- expect(filter.getRule('r1')).toBeUndefined();
101
- });
102
-
103
- it('remove 应移除规则并清理索引', () => {
104
- const rule: FilterRule = { name: 'r1', action: 'deny' };
105
- filter.add(rule, 'test-plugin');
106
-
107
- filter.remove(rule);
108
- expect(filter.count).toBe(0);
109
- expect(filter.byName.has('r1')).toBe(false);
110
- });
111
-
112
- it('getByPlugin 应返回该插件的规则', () => {
113
- filter.add({ name: 'a', action: 'deny' }, 'plugin-a');
114
- filter.add({ name: 'b', action: 'deny' }, 'plugin-b');
115
- filter.add({ name: 'c', action: 'deny' }, 'plugin-a');
116
-
117
- expect(filter.getByPlugin('plugin-a')).toHaveLength(2);
118
- expect(filter.getByPlugin('plugin-b')).toHaveLength(1);
119
- });
120
- });
121
-
122
- // ============================================================================
123
- // 优先级排序
124
- // ============================================================================
125
-
126
- describe('优先级排序', () => {
127
- it('高优先级规则应先匹配', () => {
128
- filter.add({ name: 'low', action: 'deny', priority: 0 }, 'test');
129
- filter.add({ name: 'high', action: 'allow', priority: 100 }, 'test');
130
-
131
- const result = filter.test(makeMessage());
132
- expect(result.matchedRule).toBe('high');
133
- expect(result.allowed).toBe(true);
134
- });
135
-
136
- it('相同优先级应按添加顺序', () => {
137
- filter.add({ name: 'first', action: 'deny' }, 'test');
138
- filter.add({ name: 'second', action: 'allow' }, 'test');
139
-
140
- const result = filter.test(makeMessage());
141
- expect(result.matchedRule).toBe('first');
142
- });
143
-
144
- it('添加/移除规则后缓存应刷新', () => {
145
- filter.add({ name: 'r1', action: 'deny', priority: 10 }, 'test');
146
- expect(filter.sortedRules[0].name).toBe('r1');
147
-
148
- const dispose = filter.add({ name: 'r2', action: 'allow', priority: 20 }, 'test');
149
- expect(filter.sortedRules[0].name).toBe('r2');
150
-
151
- dispose();
152
- expect(filter.sortedRules[0].name).toBe('r1');
153
- });
154
- });
155
-
156
- // ============================================================================
157
- // enabled 字段
158
- // ============================================================================
159
-
160
- describe('enabled 字段', () => {
161
- it('disabled 规则应被跳过', () => {
162
- filter.add({ name: 'disabled', action: 'deny', enabled: false, priority: 100 }, 'test');
163
- filter.add({ name: 'enabled', action: 'allow' }, 'test');
164
-
165
- const result = filter.test(makeMessage());
166
- expect(result.matchedRule).toBe('enabled');
167
- });
168
-
169
- it('enabled: true 应正常匹配', () => {
170
- filter.add({ name: 'r1', action: 'deny', enabled: true }, 'test');
171
- const result = filter.test(makeMessage());
172
- expect(result.matchedRule).toBe('r1');
173
- });
174
-
175
- it('未设置 enabled 默认为启用', () => {
176
- filter.add({ name: 'r1', action: 'deny' }, 'test');
177
- expect(filter.sortedRules).toHaveLength(1);
178
- });
179
- });
180
-
181
- // ============================================================================
182
- // scope 匹配
183
- // ============================================================================
184
-
185
- describe('scope 匹配', () => {
186
- it('scope 匹配时应命中', () => {
187
- filter.add({ name: 'r1', action: 'deny', scopes: ['group'] }, 'test');
188
-
189
- const msg = makeMessage({ $channel: { id: 'ch1', type: 'group' } });
190
- expect(filter.test(msg).allowed).toBe(false);
191
- });
192
-
193
- it('scope 不匹配时应跳过', () => {
194
- filter.add({ name: 'r1', action: 'deny', scopes: ['private'] }, 'test');
195
-
196
- const msg = makeMessage({ $channel: { id: 'ch1', type: 'group' } });
197
- expect(filter.test(msg).allowed).toBe(true); // 无规则匹配,走默认 allow
198
- });
199
-
200
- it('多 scope 匹配任一即可', () => {
201
- filter.add({ name: 'r1', action: 'deny', scopes: ['group', 'channel'] }, 'test');
202
-
203
- expect(filter.test(makeMessage({ $channel: { id: 'c1', type: 'channel' } })).allowed).toBe(false);
204
- expect(filter.test(makeMessage({ $channel: { id: 'g1', type: 'group' } })).allowed).toBe(false);
205
- expect(filter.test(makeMessage({ $channel: { id: 'p1', type: 'private' } })).allowed).toBe(true);
206
- });
207
-
208
- it('未设置 scope 应匹配所有类型', () => {
209
- filter.add({ name: 'r1', action: 'deny' }, 'test');
210
-
211
- expect(filter.test(makeMessage({ $channel: { id: 'g1', type: 'group' } })).allowed).toBe(false);
212
- expect(filter.test(makeMessage({ $channel: { id: 'p1', type: 'private' } })).allowed).toBe(false);
213
- expect(filter.test(makeMessage({ $channel: { id: 'c1', type: 'channel' } })).allowed).toBe(false);
214
- });
215
- });
216
-
217
- // ============================================================================
218
- // 多维条件匹配
219
- // ============================================================================
220
-
221
- describe('多维条件匹配', () => {
222
- it('adapter 匹配', () => {
223
- filter.add({ name: 'r1', action: 'deny', adapters: ['icqq'] }, 'test');
224
-
225
- expect(filter.test(makeMessage({ $adapter: 'icqq' as any })).allowed).toBe(false);
226
- expect(filter.test(makeMessage({ $adapter: 'discord' as any })).allowed).toBe(true);
227
- });
228
-
229
- it('bot 匹配', () => {
230
- filter.add({ name: 'r1', action: 'deny', bots: ['bot-a'] }, 'test');
231
-
232
- expect(filter.test(makeMessage({ $bot: 'bot-a' })).allowed).toBe(false);
233
- expect(filter.test(makeMessage({ $bot: 'bot-b' })).allowed).toBe(true);
234
- });
235
-
236
- it('channel 匹配', () => {
237
- filter.add({ name: 'r1', action: 'deny', channels: ['group-123'] }, 'test');
238
-
239
- expect(filter.test(makeMessage({ $channel: { id: 'group-123', type: 'group' } })).allowed).toBe(false);
240
- expect(filter.test(makeMessage({ $channel: { id: 'group-456', type: 'group' } })).allowed).toBe(true);
241
- });
242
-
243
- it('sender 匹配', () => {
244
- filter.add({ name: 'r1', action: 'deny', senders: ['baduser'] }, 'test');
245
-
246
- expect(filter.test(makeMessage({ $sender: { id: 'baduser', name: 'Bad' } })).allowed).toBe(false);
247
- expect(filter.test(makeMessage({ $sender: { id: 'gooduser', name: 'Good' } })).allowed).toBe(true);
248
- });
249
-
250
- it('AND 逻辑:所有条件必须同时满足', () => {
251
- filter.add({
252
- name: 'r1',
253
- action: 'deny',
254
- scopes: ['group'],
255
- adapters: ['icqq'],
256
- senders: ['spammer'],
257
- }, 'test');
258
-
259
- // 全部满足 → deny
260
- expect(filter.test(makeMessage({
261
- $adapter: 'icqq' as any,
262
- $channel: { id: 'g1', type: 'group' },
263
- $sender: { id: 'spammer', name: 'S' },
264
- })).allowed).toBe(false);
265
-
266
- // scope 不满足 → 跳过规则
267
- expect(filter.test(makeMessage({
268
- $adapter: 'icqq' as any,
269
- $channel: { id: 'p1', type: 'private' },
270
- $sender: { id: 'spammer', name: 'S' },
271
- })).allowed).toBe(true);
272
-
273
- // adapter 不满足 → 跳过规则
274
- expect(filter.test(makeMessage({
275
- $adapter: 'discord' as any,
276
- $channel: { id: 'g1', type: 'group' },
277
- $sender: { id: 'spammer', name: 'S' },
278
- })).allowed).toBe(true);
279
-
280
- // sender 不满足 → 跳过规则
281
- expect(filter.test(makeMessage({
282
- $adapter: 'icqq' as any,
283
- $channel: { id: 'g1', type: 'group' },
284
- $sender: { id: 'normal', name: 'N' },
285
- })).allowed).toBe(true);
286
- });
287
-
288
- it('OR 逻辑:条件内多个值匹配任一', () => {
289
- filter.add({ name: 'r1', action: 'deny', channels: ['g1', 'g2', 'g3'] }, 'test');
290
-
291
- expect(filter.test(makeMessage({ $channel: { id: 'g1', type: 'group' } })).allowed).toBe(false);
292
- expect(filter.test(makeMessage({ $channel: { id: 'g2', type: 'group' } })).allowed).toBe(false);
293
- expect(filter.test(makeMessage({ $channel: { id: 'g4', type: 'group' } })).allowed).toBe(true);
294
- });
295
- });
296
-
297
- // ============================================================================
298
- // Pattern 匹配
299
- // ============================================================================
300
-
301
- describe('Pattern 匹配', () => {
302
- it('精确字符串匹配', () => {
303
- filter.add({ name: 'r1', action: 'deny', channels: ['exact-id'] }, 'test');
304
- expect(filter.test(makeMessage({ $channel: { id: 'exact-id', type: 'group' } })).allowed).toBe(false);
305
- expect(filter.test(makeMessage({ $channel: { id: 'exact-id-extra', type: 'group' } })).allowed).toBe(true);
306
- });
307
-
308
- it('通配符 * 匹配所有', () => {
309
- filter.add({ name: 'r1', action: 'deny', scopes: ['group'], channels: ['*'] }, 'test');
310
- expect(filter.test(makeMessage({ $channel: { id: 'any-id', type: 'group' } })).allowed).toBe(false);
311
- expect(filter.test(makeMessage({ $channel: { id: 'another', type: 'group' } })).allowed).toBe(false);
312
- });
313
-
314
- it('正则表达式匹配', () => {
315
- filter.add({ name: 'r1', action: 'deny', channels: [/^test-/] }, 'test');
316
- expect(filter.test(makeMessage({ $channel: { id: 'test-abc', type: 'group' } })).allowed).toBe(false);
317
- expect(filter.test(makeMessage({ $channel: { id: 'prod-abc', type: 'group' } })).allowed).toBe(true);
318
- });
319
-
320
- it('混合 pattern 类型', () => {
321
- filter.add({ name: 'r1', action: 'deny', senders: ['exact-user', /^bot-/] }, 'test');
322
- expect(filter.test(makeMessage({ $sender: { id: 'exact-user', name: 'U' } })).allowed).toBe(false);
323
- expect(filter.test(makeMessage({ $sender: { id: 'bot-123', name: 'B' } })).allowed).toBe(false);
324
- expect(filter.test(makeMessage({ $sender: { id: 'normal', name: 'N' } })).allowed).toBe(true);
325
- });
326
- });
327
-
328
- // ============================================================================
329
- // first-match-wins 语义
330
- // ============================================================================
331
-
332
- describe('first-match-wins', () => {
333
- it('高优先级 allow 应覆盖低优先级 deny', () => {
334
- filter.add({ name: 'deny-all', action: 'deny', priority: 0 }, 'test');
335
- filter.add({ name: 'allow-vip', action: 'allow', priority: 100, senders: ['vip'] }, 'test');
336
-
337
- const vipMsg = makeMessage({ $sender: { id: 'vip', name: 'VIP' } });
338
- const normalMsg = makeMessage({ $sender: { id: 'normal', name: 'N' } });
339
-
340
- expect(filter.test(vipMsg).allowed).toBe(true);
341
- expect(filter.test(vipMsg).matchedRule).toBe('allow-vip');
342
-
343
- expect(filter.test(normalMsg).allowed).toBe(false);
344
- expect(filter.test(normalMsg).matchedRule).toBe('deny-all');
345
- });
346
-
347
- it('白名单模式:allow 特定 + deny 兜底', () => {
348
- filter.add({ name: 'allow-group', action: 'allow', scopes: ['group'], channels: ['allowed-group'], priority: 1 }, 'test');
349
- filter.add({ name: 'deny-rest', action: 'deny', scopes: ['group'], priority: -100 }, 'test');
350
-
351
- expect(filter.test(makeMessage({ $channel: { id: 'allowed-group', type: 'group' } })).allowed).toBe(true);
352
- expect(filter.test(makeMessage({ $channel: { id: 'other-group', type: 'group' } })).allowed).toBe(false);
353
- // private 消息不受影响
354
- expect(filter.test(makeMessage({ $channel: { id: 'p1', type: 'private' } })).allowed).toBe(true);
355
- });
356
- });
357
-
358
- // ============================================================================
359
- // 配置加载
360
- // ============================================================================
361
-
362
- describe('配置加载', () => {
363
- it('从 config 加载 default_policy', () => {
364
- const f = new MessageFilterFeature({ default_policy: 'deny' });
365
- expect(f.defaultPolicy).toBe('deny');
366
- });
367
-
368
- it('从 config 加载规则', () => {
369
- const config: MessageFilterConfig = {
370
- rules: [
371
- { name: 'r1', action: 'deny', scopes: ['group'], channels: ['123'] },
372
- { name: 'r2', action: 'allow', priority: 10, senders: ['admin'] },
373
- ],
374
- };
375
- const f = new MessageFilterFeature(config);
376
-
377
- expect(f.count).toBe(2);
378
- expect(f.getRule('r1')).toBeDefined();
379
- expect(f.getRule('r2')).toBeDefined();
380
- });
381
-
382
- it('配置中的正则字符串应被解析', () => {
383
- const config: MessageFilterConfig = {
384
- rules: [
385
- { name: 'regex-rule', action: 'deny', channels: ['/^test-/i'] },
386
- ],
387
- };
388
- const f = new MessageFilterFeature(config);
389
-
390
- expect(f.test(makeMessage({ $channel: { id: 'Test-123', type: 'group' } })).allowed).toBe(false);
391
- expect(f.test(makeMessage({ $channel: { id: 'prod-123', type: 'group' } })).allowed).toBe(true);
392
- });
393
-
394
- it('配置中的普通字符串不应被误解析为正则', () => {
395
- const config: MessageFilterConfig = {
396
- rules: [
397
- { name: 'str-rule', action: 'deny', channels: ['plain-string'] },
398
- ],
399
- };
400
- const f = new MessageFilterFeature(config);
401
-
402
- expect(f.test(makeMessage({ $channel: { id: 'plain-string', type: 'group' } })).allowed).toBe(false);
403
- expect(f.test(makeMessage({ $channel: { id: 'plain-string-extra', type: 'group' } })).allowed).toBe(true);
404
- });
405
-
406
- it('空 config 应无规则', () => {
407
- const f = new MessageFilterFeature({});
408
- expect(f.count).toBe(0);
409
- expect(f.defaultPolicy).toBe('allow');
410
- });
411
-
412
- it('undefined config 应无规则', () => {
413
- const f = new MessageFilterFeature(undefined);
414
- expect(f.count).toBe(0);
415
- });
416
- });
417
-
418
- // ============================================================================
419
- // FilterRules 工厂
420
- // ============================================================================
421
-
422
- describe('FilterRules 工厂', () => {
423
- it('deny() 创建 deny 规则', () => {
424
- const rule = FilterRules.deny('test-deny', { scopes: ['group'], channels: ['g1'] });
425
- expect(rule.name).toBe('test-deny');
426
- expect(rule.action).toBe('deny');
427
- expect(rule.scopes).toEqual(['group']);
428
- });
429
-
430
- it('allow() 创建 allow 规则', () => {
431
- const rule = FilterRules.allow('test-allow', { senders: ['admin'] });
432
- expect(rule.name).toBe('test-allow');
433
- expect(rule.action).toBe('allow');
434
- });
435
-
436
- it('blacklist() 创建 deny 规则', () => {
437
- const rule = FilterRules.blacklist('group', ['g1', 'g2']);
438
- expect(rule.action).toBe('deny');
439
- expect(rule.scopes).toEqual(['group']);
440
- expect(rule.channels).toEqual(['g1', 'g2']);
441
- expect(rule.name).toBe('group-blacklist');
442
- });
443
-
444
- it('blacklist() 支持自定义名称', () => {
445
- const rule = FilterRules.blacklist('private', ['u1'], 'custom-name');
446
- expect(rule.name).toBe('custom-name');
447
- });
448
-
449
- it('whitelist() 创建 [allow, deny-catch-all] 规则对', () => {
450
- const [allow, deny] = FilterRules.whitelist('channel', ['c1', 'c2']);
451
- expect(allow.action).toBe('allow');
452
- expect(allow.scopes).toEqual(['channel']);
453
- expect(allow.channels).toEqual(['c1', 'c2']);
454
- expect(allow.priority).toBe(1);
455
-
456
- expect(deny.action).toBe('deny');
457
- expect(deny.scopes).toEqual(['channel']);
458
- expect(deny.priority).toBe(-100);
459
- });
460
-
461
- it('whitelist 集成到 filter 的完整行为', () => {
462
- const [allow, denyRest] = FilterRules.whitelist('group', ['allowed-group']);
463
- filter.add(allow, 'test');
464
- filter.add(denyRest, 'test');
465
-
466
- expect(filter.test(makeMessage({ $channel: { id: 'allowed-group', type: 'group' } })).allowed).toBe(true);
467
- expect(filter.test(makeMessage({ $channel: { id: 'other-group', type: 'group' } })).allowed).toBe(false);
468
- expect(filter.test(makeMessage({ $channel: { id: 'p1', type: 'private' } })).allowed).toBe(true);
469
- });
470
- });
471
-
472
- // ============================================================================
473
- // toJSON
474
- // ============================================================================
475
-
476
- describe('toJSON', () => {
477
- it('应序列化所有规则', () => {
478
- filter.add({ name: 'r1', action: 'deny', channels: ['ch1'] }, 'p1');
479
- filter.add({ name: 'r2', action: 'allow', senders: [/^admin/], priority: 10 }, 'p2');
480
-
481
- const json = filter.toJSON();
482
- expect(json.name).toBe('message-filter');
483
- expect(json.count).toBe(2);
484
- expect(json.items).toHaveLength(2);
485
- });
486
-
487
- it('应按插件名过滤', () => {
488
- filter.add({ name: 'r1', action: 'deny' }, 'p1');
489
- filter.add({ name: 'r2', action: 'deny' }, 'p2');
490
-
491
- expect(filter.toJSON('p1').count).toBe(1);
492
- expect(filter.toJSON('p1').items[0].name).toBe('r1');
493
- });
494
-
495
- it('RegExp 应被序列化为 source 字符串', () => {
496
- filter.add({ name: 'r1', action: 'deny', channels: [/^test-/] }, 'test');
497
- const json = filter.toJSON();
498
- expect(json.items[0].channels).toEqual(['^test-']);
499
- });
500
-
501
- it('字符串 pattern 应保持原样', () => {
502
- filter.add({ name: 'r1', action: 'deny', channels: ['exact'] }, 'test');
503
- const json = filter.toJSON();
504
- expect(json.items[0].channels).toEqual(['exact']);
505
- });
506
- });
507
-
508
- // ============================================================================
509
- // 综合场景
510
- // ============================================================================
511
-
512
- describe('综合场景', () => {
513
- it('防火墙规则链:VIP 放行 + 群黑名单 + 默认 allow', () => {
514
- filter.add({ name: 'vip', action: 'allow', priority: 100, senders: ['vip-user'] }, 'test');
515
- filter.add({ name: 'block-groups', action: 'deny', scopes: ['group'], channels: ['spam-group'] }, 'test');
516
-
517
- // VIP 即使在黑名单群也能通过
518
- expect(filter.test(makeMessage({
519
- $sender: { id: 'vip-user', name: 'V' },
520
- $channel: { id: 'spam-group', type: 'group' },
521
- })).allowed).toBe(true);
522
-
523
- // 普通用户在黑名单群被拦截
524
- expect(filter.test(makeMessage({
525
- $sender: { id: 'normal', name: 'N' },
526
- $channel: { id: 'spam-group', type: 'group' },
527
- })).allowed).toBe(false);
528
-
529
- // 普通用户在正常群通过
530
- expect(filter.test(makeMessage({
531
- $sender: { id: 'normal', name: 'N' },
532
- $channel: { id: 'good-group', type: 'group' },
533
- })).allowed).toBe(true);
534
- });
535
-
536
- it('纯白名单模式:default deny + 特定规则放行', () => {
537
- filter.defaultPolicy = 'deny';
538
- filter.add({ name: 'allow-admin', action: 'allow', senders: ['admin'] }, 'test');
539
- filter.add({ name: 'allow-work', action: 'allow', scopes: ['group'], channels: ['work-group'] }, 'test');
540
-
541
- expect(filter.test(makeMessage({ $sender: { id: 'admin', name: 'A' } })).allowed).toBe(true);
542
- expect(filter.test(makeMessage({ $channel: { id: 'work-group', type: 'group' } })).allowed).toBe(true);
543
- expect(filter.test(makeMessage({ $channel: { id: 'random-group', type: 'group' } })).allowed).toBe(false);
544
- expect(filter.test(makeMessage({ $channel: { id: 'p1', type: 'private' } })).allowed).toBe(false);
545
- });
546
-
547
- it('多适配器隔离:只允许 QQ 群聊,拒绝 QQ 私聊', () => {
548
- filter.add({ name: 'deny-qq-private', action: 'deny', adapters: ['icqq'], scopes: ['private'] }, 'test');
549
-
550
- expect(filter.test(makeMessage({
551
- $adapter: 'icqq' as any,
552
- $channel: { id: 'p1', type: 'private' },
553
- })).allowed).toBe(false);
554
-
555
- expect(filter.test(makeMessage({
556
- $adapter: 'icqq' as any,
557
- $channel: { id: 'g1', type: 'group' },
558
- })).allowed).toBe(true);
559
-
560
- expect(filter.test(makeMessage({
561
- $adapter: 'discord' as any,
562
- $channel: { id: 'p1', type: 'private' },
563
- })).allowed).toBe(true);
564
- });
565
- });
566
- });