@ihazz/bitrix24 1.1.1 → 1.1.3

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 (127) hide show
  1. package/README.md +5 -0
  2. package/dist/index.d.ts +30 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +55 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/src/access-control.d.ts +43 -0
  7. package/dist/src/access-control.d.ts.map +1 -0
  8. package/dist/src/access-control.js +128 -0
  9. package/dist/src/access-control.js.map +1 -0
  10. package/dist/src/api.d.ts +161 -0
  11. package/dist/src/api.d.ts.map +1 -0
  12. package/dist/src/api.js +357 -0
  13. package/dist/src/api.js.map +1 -0
  14. package/dist/src/bot-avatar.d.ts +7 -0
  15. package/dist/src/bot-avatar.d.ts.map +1 -0
  16. package/dist/src/bot-avatar.js +7 -0
  17. package/dist/src/bot-avatar.js.map +1 -0
  18. package/dist/src/channel.d.ts +216 -0
  19. package/dist/src/channel.d.ts.map +1 -0
  20. package/dist/src/channel.js +2324 -0
  21. package/dist/src/channel.js.map +1 -0
  22. package/dist/src/commands.d.ts +22 -0
  23. package/dist/src/commands.d.ts.map +1 -0
  24. package/dist/src/commands.js +160 -0
  25. package/dist/src/commands.js.map +1 -0
  26. package/dist/src/config-schema.d.ts +356 -0
  27. package/dist/src/config-schema.d.ts.map +1 -0
  28. package/dist/src/config-schema.js +43 -0
  29. package/dist/src/config-schema.js.map +1 -0
  30. package/dist/src/config.d.ts +11 -0
  31. package/dist/src/config.d.ts.map +1 -0
  32. package/dist/src/config.js +50 -0
  33. package/dist/src/config.js.map +1 -0
  34. package/dist/src/dedup.d.ts +22 -0
  35. package/dist/src/dedup.d.ts.map +1 -0
  36. package/dist/src/dedup.js +49 -0
  37. package/dist/src/dedup.js.map +1 -0
  38. package/dist/src/group-access.d.ts +52 -0
  39. package/dist/src/group-access.d.ts.map +1 -0
  40. package/dist/src/group-access.js +180 -0
  41. package/dist/src/group-access.js.map +1 -0
  42. package/dist/src/history-cache.d.ts +41 -0
  43. package/dist/src/history-cache.d.ts.map +1 -0
  44. package/dist/src/history-cache.js +82 -0
  45. package/dist/src/history-cache.js.map +1 -0
  46. package/dist/src/i18n.d.ts +22 -0
  47. package/dist/src/i18n.d.ts.map +1 -0
  48. package/dist/src/i18n.js +175 -0
  49. package/dist/src/i18n.js.map +1 -0
  50. package/dist/src/inbound-handler.d.ts +92 -0
  51. package/dist/src/inbound-handler.d.ts.map +1 -0
  52. package/dist/src/inbound-handler.js +417 -0
  53. package/dist/src/inbound-handler.js.map +1 -0
  54. package/dist/src/media-service.d.ts +52 -0
  55. package/dist/src/media-service.d.ts.map +1 -0
  56. package/dist/src/media-service.js +423 -0
  57. package/dist/src/media-service.js.map +1 -0
  58. package/dist/src/message-utils.d.ts +34 -0
  59. package/dist/src/message-utils.d.ts.map +1 -0
  60. package/dist/src/message-utils.js +392 -0
  61. package/dist/src/message-utils.js.map +1 -0
  62. package/dist/src/polling-service.d.ts +39 -0
  63. package/dist/src/polling-service.d.ts.map +1 -0
  64. package/dist/src/polling-service.js +204 -0
  65. package/dist/src/polling-service.js.map +1 -0
  66. package/dist/src/rate-limiter.d.ts +22 -0
  67. package/dist/src/rate-limiter.d.ts.map +1 -0
  68. package/dist/src/rate-limiter.js +72 -0
  69. package/dist/src/rate-limiter.js.map +1 -0
  70. package/dist/src/runtime.d.ts +106 -0
  71. package/dist/src/runtime.d.ts.map +1 -0
  72. package/dist/src/runtime.js +11 -0
  73. package/dist/src/runtime.js.map +1 -0
  74. package/dist/src/send-service.d.ts +66 -0
  75. package/dist/src/send-service.d.ts.map +1 -0
  76. package/dist/src/send-service.js +177 -0
  77. package/dist/src/send-service.js.map +1 -0
  78. package/dist/src/state-paths.d.ts +3 -0
  79. package/dist/src/state-paths.d.ts.map +1 -0
  80. package/dist/src/state-paths.js +23 -0
  81. package/dist/src/state-paths.js.map +1 -0
  82. package/dist/src/types.d.ts +381 -0
  83. package/dist/src/types.d.ts.map +1 -0
  84. package/dist/src/types.js +3 -0
  85. package/dist/src/types.js.map +1 -0
  86. package/dist/src/utils.d.ts +60 -0
  87. package/dist/src/utils.d.ts.map +1 -0
  88. package/dist/src/utils.js +131 -0
  89. package/dist/src/utils.js.map +1 -0
  90. package/index.ts +1 -1
  91. package/openclaw.plugin.json +278 -1
  92. package/package.json +19 -2
  93. package/src/api.ts +0 -3
  94. package/src/channel.ts +76 -73
  95. package/src/config-schema.ts +1 -2
  96. package/src/config.ts +6 -8
  97. package/src/group-access.ts +1 -8
  98. package/src/inbound-handler.ts +128 -15
  99. package/src/media-service.ts +229 -61
  100. package/src/polling-service.ts +2 -3
  101. package/src/send-service.ts +4 -3
  102. package/src/state-paths.ts +28 -0
  103. package/src/types.ts +1 -2
  104. package/src/utils.ts +31 -4
  105. package/tests/access-control.test.ts +0 -398
  106. package/tests/api.test.ts +0 -226
  107. package/tests/channel-flow.test.ts +0 -1692
  108. package/tests/channel.test.ts +0 -842
  109. package/tests/commands.test.ts +0 -57
  110. package/tests/config.test.ts +0 -210
  111. package/tests/dedup.test.ts +0 -50
  112. package/tests/fixtures/onimbotjoinchat.json +0 -48
  113. package/tests/fixtures/onimbotmessageadd-file.json +0 -86
  114. package/tests/fixtures/onimbotmessageadd-text.json +0 -59
  115. package/tests/fixtures/onimcommandadd.json +0 -45
  116. package/tests/group-access.test.ts +0 -340
  117. package/tests/history-cache.test.ts +0 -117
  118. package/tests/i18n.test.ts +0 -90
  119. package/tests/inbound-handler.test.ts +0 -1033
  120. package/tests/index.test.ts +0 -94
  121. package/tests/media-service.test.ts +0 -319
  122. package/tests/message-utils.test.ts +0 -184
  123. package/tests/polling-service.test.ts +0 -115
  124. package/tests/rate-limiter.test.ts +0 -52
  125. package/tests/send-service.test.ts +0 -162
  126. package/tsconfig.json +0 -22
  127. package/vitest.config.ts +0 -9
@@ -1,1033 +0,0 @@
1
- import { stringify as stringifyQueryString } from 'qs';
2
- import { describe, it, expect, vi, afterEach } from 'vitest';
3
- import { InboundHandler } from '../src/inbound-handler.js';
4
- import type { B24MsgContext, FetchContext } from '../src/types.js';
5
-
6
- const silentLogger = {
7
- info: () => {},
8
- warn: () => {},
9
- error: () => {},
10
- debug: () => {},
11
- };
12
-
13
- const fetchCtx: FetchContext = {
14
- webhookUrl: 'https://test.bitrix24.com/rest/1/token/',
15
- botId: 2081,
16
- botToken: 'bot_token_test_abc123',
17
- };
18
-
19
- const baseBot = {
20
- id: 2081,
21
- code: 'openclaw',
22
- type: 'bot',
23
- isHidden: false,
24
- isSupportOpenline: false,
25
- isReactionsEnabled: true,
26
- backgroundId: null,
27
- language: 'ru',
28
- eventMode: 'fetch',
29
- };
30
-
31
- const baseUser = {
32
- id: 1,
33
- active: true,
34
- name: 'Test User',
35
- firstName: 'Test',
36
- lastName: 'User',
37
- workPosition: 'Developer',
38
- color: '#df532d',
39
- avatar: '',
40
- gender: 'M',
41
- birthday: '',
42
- extranet: false,
43
- bot: false,
44
- connector: false,
45
- externalAuthId: 'default',
46
- status: 'online',
47
- idle: false,
48
- lastActivityDate: false,
49
- absent: false,
50
- departments: [1],
51
- phones: false,
52
- type: 'employee',
53
- };
54
-
55
- function createFetchMessageEvent(overrides: Record<string, unknown> = {}) {
56
- return {
57
- eventId: 1001,
58
- type: 'ONIMBOTV2MESSAGEADD',
59
- date: '2026-03-18T08:00:00+02:00',
60
- data: {
61
- bot: baseBot,
62
- message: {
63
- id: 490659,
64
- chatId: 40985,
65
- authorId: 1,
66
- date: '2026-03-18T08:00:00+02:00',
67
- text: 'Hello, how are you?',
68
- isSystem: false,
69
- uuid: '',
70
- forward: null,
71
- params: {},
72
- viewedByOthers: false,
73
- },
74
- chat: {
75
- id: 40985,
76
- dialogId: '1',
77
- type: 'private',
78
- name: 'Test User',
79
- entityType: '',
80
- owner: 1,
81
- avatar: '',
82
- color: '#ab7761',
83
- },
84
- user: baseUser,
85
- language: 'ru',
86
- ...overrides,
87
- },
88
- };
89
- }
90
-
91
- function createWebhookEvent(eventType: string, data: Record<string, unknown>) {
92
- return {
93
- event: eventType,
94
- data,
95
- };
96
- }
97
-
98
- describe('InboundHandler', () => {
99
- let handler: InboundHandler;
100
-
101
- afterEach(() => {
102
- handler?.destroy();
103
- });
104
-
105
- it('dispatches fetch message events', async () => {
106
- const onMessage = vi.fn();
107
- handler = new InboundHandler({
108
- config: { dmPolicy: 'pairing' },
109
- logger: silentLogger,
110
- onMessage,
111
- });
112
-
113
- const result = await handler.handleFetchEvent(createFetchMessageEvent(), fetchCtx);
114
- expect(result).toBe(true);
115
- expect(onMessage).toHaveBeenCalledOnce();
116
-
117
- const ctx = onMessage.mock.calls[0][0] as B24MsgContext;
118
- expect(ctx.channel).toBe('bitrix24');
119
- expect(ctx.eventScope).toBe('bot');
120
- expect(ctx.text).toBe('Hello, how are you?');
121
- expect(ctx.senderId).toBe('1');
122
- expect(ctx.senderName).toBe('Test User');
123
- expect(ctx.senderFirstName).toBe('Test');
124
- expect(ctx.chatId).toBe('1');
125
- expect(ctx.chatInternalId).toBe('40985');
126
- expect(ctx.chatName).toBe('Test User');
127
- expect(ctx.messageId).toBe('490659');
128
- expect(ctx.replyToMessageId).toBeUndefined();
129
- expect(ctx.isForwarded).toBe(false);
130
- expect(ctx.isDm).toBe(true);
131
- expect(ctx.isGroup).toBe(false);
132
- expect(ctx.media).toEqual([]);
133
- expect(ctx.language).toBe('ru');
134
- expect(ctx.timestamp).toBe(Date.parse('2026-03-18T08:00:00+02:00'));
135
- expect(ctx.botId).toBe(2081);
136
- });
137
-
138
- it('dispatches user message events from combined fetch in agent mode', async () => {
139
- const onMessage = vi.fn();
140
- handler = new InboundHandler({
141
- config: { dmPolicy: 'pairing', agentMode: true },
142
- logger: silentLogger,
143
- onMessage,
144
- });
145
-
146
- const event = {
147
- eventId: 1007,
148
- type: 'ONIMV2MESSAGEADD',
149
- date: '2026-03-19T14:36:03+02:00',
150
- data: {
151
- message: {
152
- id: 490660,
153
- chatId: 520,
154
- authorId: 77,
155
- date: '2026-03-19T14:36:03+02:00',
156
- text: 'Авария в личке',
157
- isSystem: false,
158
- uuid: '',
159
- forward: null,
160
- params: {},
161
- viewedByOthers: false,
162
- },
163
- chat: {
164
- id: 520,
165
- dialogId: '77',
166
- type: 'private',
167
- name: 'Sergey',
168
- entityType: '',
169
- owner: 1,
170
- avatar: '',
171
- color: '#ab7761',
172
- },
173
- user: {
174
- ...baseUser,
175
- id: 77,
176
- name: 'Sergey',
177
- firstName: 'Sergey',
178
- },
179
- language: 'ru',
180
- },
181
- };
182
-
183
- const result = await handler.handleFetchEvent(event as never, fetchCtx);
184
- expect(result).toBe(true);
185
- expect(onMessage).toHaveBeenCalledOnce();
186
- expect(onMessage.mock.calls[0][0]).toMatchObject({
187
- eventScope: 'user',
188
- chatId: '77',
189
- senderId: '77',
190
- text: 'Авария в личке',
191
- wasMentioned: false,
192
- });
193
- });
194
-
195
- it('processes bot and user message events separately when they share one message id', async () => {
196
- const onMessage = vi.fn();
197
- handler = new InboundHandler({
198
- config: { dmPolicy: 'pairing', agentMode: true },
199
- logger: silentLogger,
200
- onMessage,
201
- });
202
-
203
- const botEvent = createFetchMessageEvent({
204
- message: {
205
- id: 555001,
206
- chatId: 520,
207
- authorId: 77,
208
- date: '2026-03-19T14:36:03+02:00',
209
- text: 'shared message',
210
- isSystem: false,
211
- uuid: '',
212
- forward: null,
213
- params: {},
214
- viewedByOthers: false,
215
- },
216
- chat: {
217
- id: 520,
218
- dialogId: 'chat520',
219
- type: 'chat',
220
- name: 'Group Chat',
221
- entityType: '',
222
- owner: 1,
223
- avatar: '',
224
- color: '#ab7761',
225
- },
226
- user: {
227
- ...baseUser,
228
- id: 77,
229
- name: 'Sergey',
230
- },
231
- });
232
-
233
- const userEvent = {
234
- eventId: 1008,
235
- type: 'ONIMV2MESSAGEADD',
236
- date: '2026-03-19T14:36:04+02:00',
237
- data: {
238
- message: {
239
- id: 555001,
240
- chatId: 520,
241
- authorId: 77,
242
- date: '2026-03-19T14:36:03+02:00',
243
- text: 'shared message',
244
- isSystem: false,
245
- uuid: '',
246
- forward: null,
247
- params: {},
248
- viewedByOthers: false,
249
- },
250
- chat: {
251
- id: 520,
252
- dialogId: 'chat520',
253
- type: 'chat',
254
- name: 'Group Chat',
255
- entityType: '',
256
- owner: 1,
257
- avatar: '',
258
- color: '#ab7761',
259
- },
260
- user: {
261
- ...baseUser,
262
- id: 77,
263
- name: 'Sergey',
264
- },
265
- language: 'ru',
266
- },
267
- };
268
-
269
- await handler.handleFetchEvent(botEvent as never, fetchCtx);
270
- await handler.handleFetchEvent(userEvent as never, fetchCtx);
271
-
272
- expect(onMessage).toHaveBeenCalledTimes(2);
273
- expect((onMessage.mock.calls[0][0] as B24MsgContext).eventScope).toBe('bot');
274
- expect((onMessage.mock.calls[1][0] as B24MsgContext).eventScope).toBe('user');
275
- });
276
-
277
- it('still deduplicates repeated events within the same scope', async () => {
278
- const onMessage = vi.fn();
279
- handler = new InboundHandler({
280
- config: { dmPolicy: 'pairing', agentMode: true },
281
- logger: silentLogger,
282
- onMessage,
283
- });
284
-
285
- const botEvent = createFetchMessageEvent({
286
- message: {
287
- id: 555002,
288
- chatId: 520,
289
- authorId: 77,
290
- date: '2026-03-19T14:36:03+02:00',
291
- text: 'same scope duplicate',
292
- isSystem: false,
293
- uuid: '',
294
- forward: null,
295
- params: {},
296
- viewedByOthers: false,
297
- },
298
- chat: {
299
- id: 520,
300
- dialogId: 'chat520',
301
- type: 'chat',
302
- name: 'Group Chat',
303
- entityType: '',
304
- owner: 1,
305
- avatar: '',
306
- color: '#ab7761',
307
- },
308
- user: {
309
- ...baseUser,
310
- id: 77,
311
- name: 'Sergey',
312
- },
313
- });
314
-
315
- await handler.handleFetchEvent(botEvent as never, fetchCtx);
316
- await handler.handleFetchEvent(botEvent as never, fetchCtx);
317
-
318
- expect(onMessage).toHaveBeenCalledTimes(1);
319
- expect((onMessage.mock.calls[0][0] as B24MsgContext).eventScope).toBe('bot');
320
- });
321
-
322
- it('keeps incoming dialogId for direct chats without replacing it by user id', async () => {
323
- const onMessage = vi.fn();
324
- handler = new InboundHandler({
325
- config: { dmPolicy: 'pairing' },
326
- logger: silentLogger,
327
- onMessage,
328
- });
329
-
330
- const event = createFetchMessageEvent({
331
- chat: {
332
- id: 40985,
333
- dialogId: '123',
334
- type: 'private',
335
- name: 'Test User',
336
- entityType: '',
337
- owner: 55,
338
- avatar: '',
339
- color: '#ab7761',
340
- },
341
- user: {
342
- ...baseUser,
343
- id: 55,
344
- },
345
- });
346
-
347
- await handler.handleFetchEvent(event, { ...fetchCtx, botId: 6809 });
348
-
349
- const ctx = onMessage.mock.calls[0][0] as B24MsgContext;
350
- expect(ctx.chatId).toBe('123');
351
- expect(ctx.senderId).toBe('55');
352
- });
353
-
354
- it('deduplicates fetch messages by message id', async () => {
355
- const onMessage = vi.fn();
356
- handler = new InboundHandler({
357
- config: { dmPolicy: 'pairing' },
358
- logger: silentLogger,
359
- onMessage,
360
- });
361
-
362
- const event = createFetchMessageEvent();
363
- await handler.handleFetchEvent(event, { ...fetchCtx, botId: 6809 });
364
- await handler.handleFetchEvent(event, { ...fetchCtx, botId: 6809 });
365
-
366
- expect(onMessage).toHaveBeenCalledOnce();
367
- });
368
-
369
- it('extracts media ids from V2 message params', async () => {
370
- const onMessage = vi.fn();
371
- handler = new InboundHandler({
372
- config: { dmPolicy: 'pairing' },
373
- logger: silentLogger,
374
- onMessage,
375
- });
376
-
377
- const event = createFetchMessageEvent({
378
- message: {
379
- id: 492035,
380
- chatId: 40985,
381
- authorId: 1,
382
- date: '2026-03-18T08:00:00+02:00',
383
- text: '',
384
- isSystem: false,
385
- uuid: '',
386
- forward: null,
387
- params: { FILE_ID: [94611, '94612'] },
388
- viewedByOthers: false,
389
- },
390
- });
391
-
392
- await handler.handleFetchEvent(event, fetchCtx);
393
-
394
- const ctx = onMessage.mock.calls[0][0] as B24MsgContext;
395
- expect(ctx.media).toEqual([
396
- {
397
- id: '94611',
398
- name: 'file_94611',
399
- extension: '',
400
- size: 0,
401
- type: 'file',
402
- },
403
- {
404
- id: '94612',
405
- name: 'file_94612',
406
- extension: '',
407
- size: 0,
408
- type: 'file',
409
- },
410
- ]);
411
- });
412
-
413
- it('extracts reply target id from V2 message params', async () => {
414
- const onMessage = vi.fn();
415
- handler = new InboundHandler({
416
- config: { dmPolicy: 'pairing' },
417
- logger: silentLogger,
418
- onMessage,
419
- });
420
-
421
- const event = createFetchMessageEvent({
422
- message: {
423
- id: 492036,
424
- chatId: 40985,
425
- authorId: 1,
426
- date: '2026-03-18T08:00:00+02:00',
427
- text: 'reply text',
428
- isSystem: false,
429
- uuid: '',
430
- forward: null,
431
- params: { REPLY_ID: '490600' },
432
- viewedByOthers: false,
433
- },
434
- });
435
-
436
- await handler.handleFetchEvent(event, fetchCtx);
437
-
438
- const ctx = onMessage.mock.calls[0][0] as B24MsgContext;
439
- expect(ctx.replyToMessageId).toBe('490600');
440
- });
441
-
442
- it('marks forwarded V2 messages', async () => {
443
- const onMessage = vi.fn();
444
- handler = new InboundHandler({
445
- config: { dmPolicy: 'pairing' },
446
- logger: silentLogger,
447
- onMessage,
448
- });
449
-
450
- const event = createFetchMessageEvent({
451
- message: {
452
- id: 492037,
453
- chatId: 40985,
454
- authorId: 1,
455
- date: '2026-03-18T08:00:00+02:00',
456
- text: 'forwarded body',
457
- isSystem: false,
458
- uuid: '',
459
- forward: { id: 'chat208/6717', userId: 1, chatType: 'chat' },
460
- params: {},
461
- viewedByOthers: false,
462
- },
463
- });
464
-
465
- await handler.handleFetchEvent(event, fetchCtx);
466
-
467
- const ctx = onMessage.mock.calls[0][0] as B24MsgContext;
468
- expect(ctx.isForwarded).toBe(true);
469
- });
470
-
471
- it('detects bot mention in a group message from real Bitrix payload format', async () => {
472
- const onMessage = vi.fn();
473
- handler = new InboundHandler({
474
- config: { dmPolicy: 'pairing', botName: 'OpenClaw' },
475
- logger: silentLogger,
476
- onMessage,
477
- });
478
-
479
- const event = createFetchMessageEvent({
480
- eventId: 9745,
481
- message: {
482
- id: 30857,
483
- chatId: 520,
484
- authorId: 1,
485
- date: '2026-03-19T14:36:03+02:00',
486
- text: '[USER=6809]OpenClaw[/USER] привет',
487
- isSystem: false,
488
- uuid: '',
489
- forward: null,
490
- params: [],
491
- viewedByOthers: false,
492
- },
493
- chat: {
494
- id: 520,
495
- dialogId: 'chat520',
496
- type: 'chat',
497
- name: 'Group Chat',
498
- entityType: '',
499
- owner: 1,
500
- avatar: '',
501
- color: '#ab7761',
502
- },
503
- });
504
-
505
- await handler.handleFetchEvent(event, { ...fetchCtx, botId: 6809 });
506
-
507
- const ctx = onMessage.mock.calls[0][0] as B24MsgContext;
508
- expect(ctx.chatId).toBe('chat520');
509
- expect(ctx.chatInternalId).toBe('520');
510
- expect(ctx.chatName).toBe('Group Chat');
511
- expect(ctx.chatType).toBe('chat');
512
- expect(ctx.isDm).toBe(false);
513
- expect(ctx.isGroup).toBe(true);
514
- expect(ctx.wasMentioned).toBe(true);
515
- });
516
-
517
- it('does not mark plain group messages as mentions', async () => {
518
- const onMessage = vi.fn();
519
- handler = new InboundHandler({
520
- config: { dmPolicy: 'pairing', botName: 'OpenClaw' },
521
- logger: silentLogger,
522
- onMessage,
523
- });
524
-
525
- const event = createFetchMessageEvent({
526
- message: {
527
- id: 30858,
528
- chatId: 520,
529
- authorId: 1,
530
- date: '2026-03-19T14:36:10+02:00',
531
- text: 'привет',
532
- isSystem: false,
533
- uuid: '',
534
- forward: null,
535
- params: [],
536
- viewedByOthers: false,
537
- },
538
- chat: {
539
- id: 520,
540
- dialogId: 'chat520',
541
- type: 'chat',
542
- name: 'Group Chat',
543
- entityType: '',
544
- owner: 1,
545
- avatar: '',
546
- color: '#ab7761',
547
- },
548
- });
549
-
550
- await handler.handleFetchEvent(event, fetchCtx);
551
-
552
- const ctx = onMessage.mock.calls[0][0] as B24MsgContext;
553
- expect(ctx.wasMentioned).toBe(false);
554
- });
555
-
556
- it('dispatches webhook message events from JSON body', async () => {
557
- const onMessage = vi.fn();
558
- handler = new InboundHandler({
559
- config: { webhookUrl: fetchCtx.webhookUrl, botToken: fetchCtx.botToken, dmPolicy: 'pairing' },
560
- logger: silentLogger,
561
- onMessage,
562
- });
563
-
564
- const payload = createWebhookEvent('ONIMBOTV2MESSAGEADD', {
565
- bot: {
566
- id: '2081',
567
- code: 'openclaw',
568
- auth: {
569
- access_token: 'bot_token_test_abc123',
570
- refresh_token: 'refresh_token_test',
571
- },
572
- },
573
- message: {
574
- id: '490659',
575
- chatId: '40985',
576
- authorId: '1',
577
- date: '2026-03-18T08:00:00+02:00',
578
- text: 'Hello from webhook',
579
- isSystem: '0',
580
- uuid: '',
581
- forward: '',
582
- params: {},
583
- viewedByOthers: '0',
584
- },
585
- chat: {
586
- id: '40985',
587
- dialogId: '1',
588
- type: 'private',
589
- name: 'Test User',
590
- entityType: '',
591
- owner: '1',
592
- avatar: '',
593
- color: '#ab7761',
594
- },
595
- user: {
596
- ...baseUser,
597
- id: '1',
598
- active: '1',
599
- extranet: '0',
600
- bot: '0',
601
- connector: '0',
602
- idle: '0',
603
- lastActivityDate: '0',
604
- absent: '0',
605
- departments: ['1'],
606
- phones: '0',
607
- },
608
- language: 'ru',
609
- });
610
-
611
- const result = await handler.handleWebhook(JSON.stringify(payload));
612
- expect(result).toBe(true);
613
- expect(onMessage).toHaveBeenCalledOnce();
614
-
615
- const ctx = onMessage.mock.calls[0][0] as B24MsgContext;
616
- expect(ctx.text).toBe('Hello from webhook');
617
- expect(ctx.chatId).toBe('1');
618
- expect(ctx.messageId).toBe('490659');
619
- });
620
-
621
- it('dispatches webhook message events from query string body', async () => {
622
- const onMessage = vi.fn();
623
- handler = new InboundHandler({
624
- config: { webhookUrl: fetchCtx.webhookUrl, botToken: fetchCtx.botToken, dmPolicy: 'pairing' },
625
- logger: silentLogger,
626
- onMessage,
627
- });
628
-
629
- const payload = createWebhookEvent('ONIMBOTV2MESSAGEADD', {
630
- bot: {
631
- id: '2081',
632
- code: 'openclaw',
633
- auth: {
634
- access_token: 'bot_token_test_abc123',
635
- refresh_token: 'refresh_token_test',
636
- },
637
- },
638
- message: {
639
- id: '490660',
640
- chatId: '40985',
641
- authorId: '1',
642
- date: '2026-03-18T08:00:00+02:00',
643
- text: 'Hello from qs',
644
- isSystem: '0',
645
- uuid: '',
646
- forward: '',
647
- params: {},
648
- viewedByOthers: '0',
649
- },
650
- chat: {
651
- id: '40985',
652
- dialogId: '1',
653
- type: 'private',
654
- name: 'Test User',
655
- entityType: '',
656
- owner: '1',
657
- avatar: '',
658
- color: '#ab7761',
659
- },
660
- user: {
661
- ...baseUser,
662
- id: '1',
663
- active: '1',
664
- extranet: '0',
665
- bot: '0',
666
- connector: '0',
667
- idle: '0',
668
- lastActivityDate: '0',
669
- absent: '0',
670
- departments: ['1'],
671
- phones: '0',
672
- },
673
- language: 'ru',
674
- });
675
-
676
- const result = await handler.handleWebhook(stringifyQueryString(payload));
677
- expect(result).toBe(true);
678
- expect(onMessage).toHaveBeenCalledOnce();
679
- });
680
-
681
- it('dispatches join chat events', async () => {
682
- const onJoinChat = vi.fn();
683
- handler = new InboundHandler({
684
- config: { dmPolicy: 'pairing' },
685
- logger: silentLogger,
686
- onJoinChat,
687
- });
688
-
689
- const event = {
690
- eventId: 1002,
691
- type: 'ONIMBOTV2JOINCHAT',
692
- date: '2026-03-18T08:00:00+02:00',
693
- data: {
694
- bot: baseBot,
695
- dialogId: '1',
696
- chat: {
697
- id: 40985,
698
- dialogId: '1',
699
- type: 'private',
700
- name: 'Test User',
701
- entityType: '',
702
- owner: 1,
703
- avatar: '',
704
- color: '#ab7761',
705
- },
706
- user: baseUser,
707
- language: 'ru',
708
- },
709
- };
710
-
711
- const result = await handler.handleFetchEvent(event, fetchCtx);
712
- expect(result).toBe(true);
713
- expect(onJoinChat).toHaveBeenCalledOnce();
714
- expect(onJoinChat).toHaveBeenCalledWith({
715
- senderId: '1',
716
- dialogId: '1',
717
- chatId: '40985',
718
- chatType: 'private',
719
- language: 'ru',
720
- fetchCtx,
721
- });
722
- });
723
-
724
- it('dispatches group join chat events with numeric chat id', async () => {
725
- const onJoinChat = vi.fn();
726
- handler = new InboundHandler({
727
- config: { dmPolicy: 'pairing' },
728
- logger: silentLogger,
729
- onJoinChat,
730
- });
731
-
732
- const event = {
733
- eventId: 1006,
734
- type: 'ONIMBOTV2JOINCHAT',
735
- date: '2026-03-19T14:36:03+02:00',
736
- data: {
737
- bot: baseBot,
738
- dialogId: 'chat520',
739
- chat: {
740
- id: 520,
741
- dialogId: 'chat520',
742
- type: 'chat',
743
- name: 'Group Chat',
744
- entityType: '',
745
- owner: 1,
746
- avatar: '',
747
- color: '#ab7761',
748
- },
749
- user: baseUser,
750
- language: 'ru',
751
- },
752
- };
753
-
754
- const result = await handler.handleFetchEvent(event as never, fetchCtx);
755
- expect(result).toBe(true);
756
- expect(onJoinChat).toHaveBeenCalledOnce();
757
- expect(onJoinChat).toHaveBeenCalledWith({
758
- senderId: '1',
759
- dialogId: 'chat520',
760
- chatId: '520',
761
- chatType: 'chat',
762
- language: 'ru',
763
- fetchCtx,
764
- });
765
- });
766
-
767
- it('dispatches command events', async () => {
768
- const onCommand = vi.fn();
769
- handler = new InboundHandler({
770
- config: { dmPolicy: 'pairing' },
771
- logger: silentLogger,
772
- onCommand,
773
- });
774
-
775
- const event = {
776
- eventId: 1003,
777
- type: 'ONIMBOTV2COMMANDADD',
778
- date: '2026-03-18T08:00:00+02:00',
779
- data: {
780
- bot: baseBot,
781
- command: {
782
- id: 53,
783
- command: '/help',
784
- params: 'topic',
785
- context: 'textarea',
786
- },
787
- message: {
788
- id: 490667,
789
- chatId: 40985,
790
- authorId: 1,
791
- date: '2026-03-18T08:00:00+02:00',
792
- text: '/help topic',
793
- isSystem: false,
794
- uuid: '',
795
- forward: null,
796
- params: {},
797
- viewedByOthers: false,
798
- },
799
- chat: {
800
- id: 40985,
801
- dialogId: '1',
802
- type: 'private',
803
- name: 'Test User',
804
- entityType: '',
805
- owner: 1,
806
- avatar: '',
807
- color: '#ab7761',
808
- },
809
- user: baseUser,
810
- language: 'ru',
811
- },
812
- };
813
-
814
- const result = await handler.handleFetchEvent(event, fetchCtx);
815
- expect(result).toBe(true);
816
- expect(onCommand).toHaveBeenCalledOnce();
817
- expect(onCommand).toHaveBeenCalledWith({
818
- commandId: 53,
819
- commandName: 'help',
820
- commandParams: 'topic',
821
- commandText: '/help topic',
822
- senderId: '1',
823
- dialogId: '1',
824
- chatId: '40985',
825
- chatType: 'P',
826
- messageId: '490667',
827
- language: 'ru',
828
- fetchCtx,
829
- });
830
- });
831
-
832
- it('dispatches keyboard command events even when message payload is incomplete', async () => {
833
- const onCommand = vi.fn();
834
- handler = new InboundHandler({
835
- config: { dmPolicy: 'pairing' },
836
- logger: silentLogger,
837
- onCommand,
838
- });
839
-
840
- const event = {
841
- eventId: 1004,
842
- type: 'ONIMBOTV2COMMANDADD',
843
- date: '2026-03-18T08:00:00+02:00',
844
- data: {
845
- bot: baseBot,
846
- command: {
847
- id: 54,
848
- command: '/status',
849
- context: 'keyboard',
850
- },
851
- chat: {
852
- id: 40985,
853
- dialogId: 1,
854
- type: 'private',
855
- name: 'Test User',
856
- entityType: '',
857
- owner: 1,
858
- avatar: '',
859
- color: '#ab7761',
860
- },
861
- user: baseUser,
862
- language: 'ru',
863
- },
864
- };
865
-
866
- const result = await handler.handleFetchEvent(event as never, fetchCtx);
867
- expect(result).toBe(true);
868
- expect(onCommand).toHaveBeenCalledOnce();
869
- expect(onCommand).toHaveBeenCalledWith({
870
- commandId: 54,
871
- commandName: 'status',
872
- commandParams: '',
873
- commandText: '/status',
874
- senderId: '1',
875
- dialogId: '1',
876
- chatId: '40985',
877
- chatType: 'P',
878
- messageId: '1004',
879
- language: 'ru',
880
- fetchCtx,
881
- });
882
- });
883
-
884
- it('dispatches group command events with numeric chat id', async () => {
885
- const onCommand = vi.fn();
886
- handler = new InboundHandler({
887
- config: { dmPolicy: 'pairing' },
888
- logger: silentLogger,
889
- onCommand,
890
- });
891
-
892
- const event = {
893
- eventId: 1007,
894
- type: 'ONIMBOTV2COMMANDADD',
895
- date: '2026-03-19T14:36:03+02:00',
896
- data: {
897
- bot: baseBot,
898
- command: {
899
- id: 56,
900
- command: '/help',
901
- params: '',
902
- context: 'textarea',
903
- },
904
- message: {
905
- id: 30857,
906
- chatId: 520,
907
- authorId: 1,
908
- date: '2026-03-19T14:36:03+02:00',
909
- text: '/help',
910
- isSystem: false,
911
- uuid: '',
912
- forward: null,
913
- params: [],
914
- viewedByOthers: false,
915
- },
916
- chat: {
917
- id: 520,
918
- dialogId: 'chat520',
919
- type: 'chat',
920
- name: 'Group Chat',
921
- entityType: '',
922
- owner: 1,
923
- avatar: '',
924
- color: '#ab7761',
925
- },
926
- user: baseUser,
927
- language: 'ru',
928
- },
929
- };
930
-
931
- const result = await handler.handleFetchEvent(event as never, fetchCtx);
932
- expect(result).toBe(true);
933
- expect(onCommand).toHaveBeenCalledOnce();
934
- expect(onCommand).toHaveBeenCalledWith({
935
- commandId: 56,
936
- commandName: 'help',
937
- commandParams: '',
938
- commandText: '/help',
939
- senderId: '1',
940
- dialogId: 'chat520',
941
- chatId: '520',
942
- chatType: 'chat',
943
- messageId: '30857',
944
- language: 'ru',
945
- fetchCtx,
946
- });
947
- });
948
-
949
- it('builds command text from command payload instead of message text', async () => {
950
- const onCommand = vi.fn();
951
- handler = new InboundHandler({
952
- config: { dmPolicy: 'pairing' },
953
- logger: silentLogger,
954
- onCommand,
955
- });
956
-
957
- const event = {
958
- eventId: 1005,
959
- type: 'ONIMBOTV2COMMANDADD',
960
- date: '2026-03-18T08:00:00+02:00',
961
- data: {
962
- bot: baseBot,
963
- command: {
964
- id: 55,
965
- command: '/status',
966
- params: '',
967
- context: 'keyboard',
968
- },
969
- message: {
970
- id: 490668,
971
- chatId: 40985,
972
- authorId: 1,
973
- date: '2026-03-18T08:00:00+02:00',
974
- text: '/commands',
975
- isSystem: false,
976
- uuid: '',
977
- forward: null,
978
- params: {},
979
- viewedByOthers: false,
980
- },
981
- chat: {
982
- id: 40985,
983
- dialogId: '1',
984
- type: 'private',
985
- name: 'Test User',
986
- entityType: '',
987
- owner: 1,
988
- avatar: '',
989
- color: '#ab7761',
990
- },
991
- user: baseUser,
992
- language: 'ru',
993
- },
994
- };
995
-
996
- const result = await handler.handleFetchEvent(event as never, fetchCtx);
997
- expect(result).toBe(true);
998
- expect(onCommand).toHaveBeenCalledOnce();
999
- expect(onCommand).toHaveBeenCalledWith({
1000
- commandId: 55,
1001
- commandName: 'status',
1002
- commandParams: '',
1003
- commandText: '/status',
1004
- senderId: '1',
1005
- dialogId: '1',
1006
- chatId: '40985',
1007
- chatType: 'P',
1008
- messageId: '490668',
1009
- language: 'ru',
1010
- fetchCtx,
1011
- });
1012
- });
1013
-
1014
- it('returns true for unknown event types to acknowledge delivery', async () => {
1015
- handler = new InboundHandler({
1016
- config: { dmPolicy: 'pairing' },
1017
- logger: silentLogger,
1018
- });
1019
-
1020
- const result = await handler.handleWebhook({ event: 'UNKNOWN_EVENT', data: {} });
1021
- expect(result).toBe(true);
1022
- });
1023
-
1024
- it('returns false for missing event type', async () => {
1025
- handler = new InboundHandler({
1026
- config: { dmPolicy: 'pairing' },
1027
- logger: silentLogger,
1028
- });
1029
-
1030
- const result = await handler.handleWebhook({});
1031
- expect(result).toBe(false);
1032
- });
1033
- });