@ihazz/bitrix24 1.0.3 → 1.1.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.
- package/README.md +236 -42
- package/package.json +1 -1
- package/src/access-control.ts +31 -8
- package/src/api.ts +76 -7
- package/src/channel.ts +1025 -137
- package/src/commands.ts +7 -7
- package/src/config-schema.ts +24 -1
- package/src/config.ts +4 -2
- package/src/group-access.ts +279 -0
- package/src/history-cache.ts +122 -0
- package/src/i18n.ts +140 -50
- package/src/inbound-handler.ts +91 -8
- package/src/polling-service.ts +4 -0
- package/src/send-service.ts +14 -5
- package/src/types.ts +67 -3
- package/tests/access-control.test.ts +43 -0
- package/tests/api.test.ts +131 -0
- package/tests/channel-flow.test.ts +1692 -0
- package/tests/channel.test.ts +88 -2
- package/tests/config.test.ts +120 -0
- package/tests/group-access.test.ts +340 -0
- package/tests/history-cache.test.ts +117 -0
- package/tests/i18n.test.ts +55 -12
- package/tests/inbound-handler.test.ts +388 -3
- package/tests/polling-service.test.ts +38 -0
- package/tests/send-service.test.ts +17 -0
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:
|
|
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
|
|
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?:
|
|
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
|
});
|