@ihazz/bitrix24 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/types.ts CHANGED
@@ -66,6 +66,8 @@ export interface B24V2User {
66
66
  }
67
67
 
68
68
  /** Message entity from V2 events */
69
+ export type B24V2MessageParams = Record<string, unknown> | unknown[];
70
+
69
71
  export interface B24V2Message {
70
72
  id: number;
71
73
  chatId: number;
@@ -75,7 +77,7 @@ export interface B24V2Message {
75
77
  isSystem: boolean;
76
78
  uuid: string;
77
79
  forward: { id: number; userId: number; chatId: number; date: string } | null;
78
- params: Record<string, unknown>;
80
+ params: B24V2MessageParams;
79
81
  viewedByOthers: boolean;
80
82
  }
81
83
 
@@ -131,6 +133,14 @@ export interface B24V2MessageEventData {
131
133
  language: string;
132
134
  }
133
135
 
136
+ /** ONIMV2MESSAGEADD / ONIMV2MESSAGEUPDATE data */
137
+ export interface B24V2UserMessageEventData {
138
+ message: B24V2Message;
139
+ chat: B24V2Chat;
140
+ user: B24V2User;
141
+ language: string;
142
+ }
143
+
134
144
  /** ONIMBOTV2MESSAGEDELETE data */
135
145
  export interface B24V2MessageDeleteEventData {
136
146
  bot: B24V2Bot | B24V2WebhookBot;
@@ -178,6 +188,7 @@ export interface B24V2ReactionEventData {
178
188
  /** Union of V2 event data */
179
189
  export type B24V2EventData =
180
190
  | B24V2MessageEventData
191
+ | B24V2UserMessageEventData
181
192
  | B24V2MessageDeleteEventData
182
193
  | B24V2JoinChatEventData
183
194
  | B24V2CommandEventData
@@ -244,10 +255,24 @@ export interface B24V2BotListResult {
244
255
 
245
256
  /** Response from imbot.v2.Chat.Message.send */
246
257
  export interface B24V2SendMessageResult {
247
- id: number;
258
+ id?: number | null;
248
259
  uuidMap: Record<string, number>;
249
260
  }
250
261
 
262
+ /** Response from imbot.v2.Chat.Message.get */
263
+ export interface B24V2GetMessageResult {
264
+ message: B24V2Message;
265
+ user: B24V2User;
266
+ }
267
+
268
+ /** Response from imbot.v2.Chat.Message.getContext */
269
+ export interface B24V2GetMessageContextResult {
270
+ messages: B24V2Message[];
271
+ users: B24V2User[];
272
+ hasPrevPage: boolean;
273
+ hasNextPage: boolean;
274
+ }
275
+
251
276
  /** Available status codes for imbot.v2.Chat.InputAction.notify */
252
277
  export type B24InputActionStatusCode =
253
278
  | 'IMBOT_AGENT_ACTION_THINKING'
@@ -337,6 +362,34 @@ export interface FetchContext {
337
362
 
338
363
  // ─── Plugin Config Types ─────────────────────────────────────────────────────
339
364
 
365
+ export type Bitrix24DmPolicy = 'pairing' | 'webhookUser' | 'allowlist' | 'open';
366
+
367
+ export type Bitrix24GroupPolicy =
368
+ | 'disabled'
369
+ | 'webhookUser'
370
+ | 'pairing'
371
+ | 'allowlist'
372
+ | 'open';
373
+
374
+ export interface Bitrix24GroupWatchConfig {
375
+ userId: string;
376
+ topics?: string[];
377
+ mode?: 'reply' | 'notifyOwnerDm';
378
+ }
379
+
380
+ export interface Bitrix24AgentWatchConfig {
381
+ userId: string;
382
+ topics?: string[];
383
+ mode?: 'notifyOwnerDm';
384
+ }
385
+
386
+ export interface Bitrix24GroupConfig {
387
+ groupPolicy?: Bitrix24GroupPolicy;
388
+ requireMention?: boolean;
389
+ allowFrom?: string[];
390
+ watch?: Bitrix24GroupWatchConfig[];
391
+ }
392
+
340
393
  export interface Bitrix24AccountConfig {
341
394
  enabled?: boolean;
342
395
  webhookUrl?: string;
@@ -350,7 +403,13 @@ export interface Bitrix24AccountConfig {
350
403
  agentMode?: boolean;
351
404
  pollingIntervalMs?: number;
352
405
  pollingFastIntervalMs?: number;
353
- dmPolicy?: 'pairing' | 'webhookUser';
406
+ dmPolicy?: Bitrix24DmPolicy;
407
+ groupPolicy?: Bitrix24GroupPolicy;
408
+ groupAllowFrom?: string[];
409
+ requireMention?: boolean;
410
+ historyLimit?: number;
411
+ groups?: Record<string, Bitrix24GroupConfig>;
412
+ agentWatch?: Record<string, Bitrix24AgentWatchConfig[]>;
354
413
  showTyping?: boolean;
355
414
  streamUpdates?: boolean;
356
415
  updateIntervalMs?: number;
@@ -364,20 +423,25 @@ export interface Bitrix24PluginConfig extends Bitrix24AccountConfig {
364
423
 
365
424
  export interface B24MsgContext {
366
425
  channel: 'bitrix24';
426
+ eventScope?: 'bot' | 'user';
367
427
  senderId: string;
368
428
  senderName: string;
369
429
  senderFirstName?: string;
370
430
  chatId: string;
371
431
  chatInternalId: string;
432
+ chatName?: string;
433
+ chatType?: string;
372
434
  messageId: string;
373
435
  replyToMessageId?: string;
374
436
  isForwarded?: boolean;
437
+ wasMentioned?: boolean;
375
438
  text: string;
376
439
  isDm: boolean;
377
440
  isGroup: boolean;
378
441
  media: B24MediaItem[];
379
442
  platform?: string;
380
443
  language?: string;
444
+ timestamp?: number;
381
445
  raw: B24V2FetchEventItem | B24V2WebhookEvent;
382
446
  botId: number;
383
447
  memberId: string;
@@ -100,6 +100,16 @@ describe('checkAccess', () => {
100
100
  it('returns false for pairing mode without runtime state', () => {
101
101
  expect(checkAccess('1', { dmPolicy: 'pairing' })).toBe(false);
102
102
  });
103
+
104
+ it('allows any sender in open mode', () => {
105
+ expect(checkAccess('1', { dmPolicy: 'open' })).toBe(true);
106
+ expect(checkAccess('77', { dmPolicy: 'open' })).toBe(true);
107
+ });
108
+
109
+ it('allows only configured identities in allowlist mode', () => {
110
+ expect(checkAccess('42', { dmPolicy: 'allowlist', allowFrom: ['bitrix24:42'] })).toBe(true);
111
+ expect(checkAccess('77', { dmPolicy: 'allowlist', allowFrom: ['bitrix24:42'] })).toBe(false);
112
+ });
103
113
  });
104
114
 
105
115
  function makeMockRuntime(storeAllowFrom: string[] = []): PluginRuntime {
@@ -266,6 +276,39 @@ describe('checkAccessWithPairing', () => {
266
276
  expect(runtime.channel.pairing.upsertPairingRequest).not.toHaveBeenCalled();
267
277
  });
268
278
 
279
+ it('returns allow for any sender in open mode', async () => {
280
+ const runtime = makeMockRuntime();
281
+
282
+ const result = await checkAccessWithPairing({
283
+ senderId: '77',
284
+ config: { dmPolicy: 'open' },
285
+ runtime,
286
+ accountId: 'default',
287
+ pairingAdapter: mockAdapter,
288
+ sendReply: vi.fn(),
289
+ logger: silentLogger,
290
+ });
291
+
292
+ expect(result).toBe('allow');
293
+ });
294
+
295
+ it('returns deny for non-allowlisted sender in allowlist mode', async () => {
296
+ const runtime = makeMockRuntime();
297
+
298
+ const result = await checkAccessWithPairing({
299
+ senderId: '77',
300
+ config: { dmPolicy: 'allowlist', allowFrom: ['bitrix24:42'] },
301
+ runtime,
302
+ accountId: 'default',
303
+ pairingAdapter: mockAdapter,
304
+ sendReply: vi.fn(),
305
+ logger: silentLogger,
306
+ });
307
+
308
+ expect(result).toBe('deny');
309
+ expect(runtime.channel.pairing.upsertPairingRequest).not.toHaveBeenCalled();
310
+ });
311
+
269
312
  it('uses senderId as the pairing identity even when direct dialogId differs', async () => {
270
313
  const runtime = makeMockRuntime(['42']);
271
314
 
package/tests/api.test.ts CHANGED
@@ -92,4 +92,135 @@ describe('Bitrix24Api', () => {
92
92
  code: 'INVALID_RESPONSE',
93
93
  });
94
94
  });
95
+
96
+ it('sends native forwards via forwardIds', async () => {
97
+ const api = new Bitrix24Api({ logger: silentLogger });
98
+ const callWebhookSpy = vi.spyOn(api, 'callWebhook').mockResolvedValueOnce({
99
+ result: { id: null, uuidMap: {} } as never,
100
+ });
101
+
102
+ await expect(api.sendMessage(
103
+ 'https://test.bitrix24.com/rest/1/token/',
104
+ { botId: 7, botToken: 'bot_token' },
105
+ '42',
106
+ null,
107
+ { forwardMessages: [31304] },
108
+ )).resolves.toBe(0);
109
+
110
+ expect(callWebhookSpy).toHaveBeenCalledWith(
111
+ 'https://test.bitrix24.com/rest/1/token/',
112
+ 'imbot.v2.Chat.Message.send',
113
+ expect.objectContaining({
114
+ botId: 7,
115
+ botToken: 'bot_token',
116
+ dialogId: '42',
117
+ fields: expect.any(Object),
118
+ }),
119
+ );
120
+ const callParams = callWebhookSpy.mock.calls[0][2] as Record<string, unknown>;
121
+ const fields = callParams.fields as Record<string, unknown>;
122
+ expect(fields.forwardIds).toBeTypeOf('object');
123
+ expect(fields.forwardIds).not.toBeNull();
124
+ expect(Object.values(fields.forwardIds as Record<string, number>)).toEqual([31304]);
125
+ expect(Object.keys(fields.forwardIds as Record<string, number>)[0]).toMatch(
126
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
127
+ );
128
+ });
129
+
130
+ it('fetches a single message by id', async () => {
131
+ const api = new Bitrix24Api({ logger: silentLogger });
132
+ const callWebhookSpy = vi.spyOn(api, 'callWebhook').mockResolvedValueOnce({
133
+ result: {
134
+ message: { id: 789 },
135
+ user: { id: 1 },
136
+ } as never,
137
+ });
138
+
139
+ await expect(api.getMessage(
140
+ 'https://test.bitrix24.com/rest/1/token/',
141
+ { botId: 7, botToken: 'bot_token' },
142
+ 789,
143
+ )).resolves.toEqual({
144
+ message: { id: 789 },
145
+ user: { id: 1 },
146
+ });
147
+
148
+ expect(callWebhookSpy).toHaveBeenCalledWith(
149
+ 'https://test.bitrix24.com/rest/1/token/',
150
+ 'imbot.v2.Chat.Message.get',
151
+ {
152
+ botId: 7,
153
+ botToken: 'bot_token',
154
+ messageId: 789,
155
+ },
156
+ );
157
+ });
158
+
159
+ it('fetches message context window by id', async () => {
160
+ const api = new Bitrix24Api({ logger: silentLogger });
161
+ const callWebhookSpy = vi.spyOn(api, 'callWebhook').mockResolvedValueOnce({
162
+ result: {
163
+ messages: [],
164
+ users: [],
165
+ hasPrevPage: false,
166
+ hasNextPage: false,
167
+ } as never,
168
+ });
169
+
170
+ await expect(api.getMessageContext(
171
+ 'https://test.bitrix24.com/rest/1/token/',
172
+ { botId: 7, botToken: 'bot_token' },
173
+ 789,
174
+ 5,
175
+ )).resolves.toEqual({
176
+ messages: [],
177
+ users: [],
178
+ hasPrevPage: false,
179
+ hasNextPage: false,
180
+ });
181
+
182
+ expect(callWebhookSpy).toHaveBeenCalledWith(
183
+ 'https://test.bitrix24.com/rest/1/token/',
184
+ 'imbot.v2.Chat.Message.getContext',
185
+ {
186
+ botId: 7,
187
+ botToken: 'bot_token',
188
+ messageId: 789,
189
+ range: 5,
190
+ },
191
+ );
192
+ });
193
+
194
+ it('fetches events with withUserEvents when agent mode is enabled', async () => {
195
+ const api = new Bitrix24Api({ logger: silentLogger });
196
+ const callWebhookSpy = vi.spyOn(api, 'callWebhook').mockResolvedValueOnce({
197
+ result: {
198
+ events: [],
199
+ lastEventId: 0,
200
+ hasMore: false,
201
+ } as never,
202
+ });
203
+
204
+ await expect(api.fetchEvents(
205
+ 'https://test.bitrix24.com/rest/1/token/',
206
+ { botId: 7, botToken: 'bot_token' },
207
+ { offset: 100, limit: 50, withUserEvents: true },
208
+ )).resolves.toEqual({
209
+ events: [],
210
+ lastEventId: 0,
211
+ hasMore: false,
212
+ });
213
+
214
+ expect(callWebhookSpy).toHaveBeenCalledWith(
215
+ 'https://test.bitrix24.com/rest/1/token/',
216
+ 'imbot.v2.Event.get',
217
+ {
218
+ botId: 7,
219
+ botToken: 'bot_token',
220
+ offset: 100,
221
+ limit: 50,
222
+ withUserEvents: true,
223
+ },
224
+ );
225
+ });
95
226
  });