@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/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 +88 -6
- 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 +341 -3
- package/tests/polling-service.test.ts +38 -0
- package/tests/send-service.test.ts +17 -0
package/README.md
CHANGED
|
@@ -5,13 +5,15 @@ OpenClaw channel plugin for Bitrix24 Messenger based on Bot Platform 2.0 (`imbot
|
|
|
5
5
|
## Current Status
|
|
6
6
|
|
|
7
7
|
- Works with Bitrix24 Bot Platform 2.0
|
|
8
|
-
- Supports direct messages
|
|
8
|
+
- Supports direct messages and group chats
|
|
9
|
+
- Uses secure defaults: `dmPolicy: "webhookUser"` and `groupPolicy: "webhookUser"`
|
|
10
|
+
- Supports optional pairing, allowlist and open access modes
|
|
11
|
+
- Uses mention gating in groups by default with `requireMention: true`
|
|
12
|
+
- Stores all delivered messages from chats where the bot is present in fast RAM history and relies on normal OpenClaw session files for long-term transcripts
|
|
9
13
|
- Supports inbound and outbound media
|
|
10
|
-
- Uses secure default access policy: `webhookUser`
|
|
11
|
-
- Supports optional pairing flow for controlled approval
|
|
12
14
|
- Production recommendation today: one portal per plugin instance
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
If a group is disabled by policy or blocked by `groupAllowFrom`, the bot sends a short notice and leaves the chat.
|
|
15
17
|
|
|
16
18
|
## Installation
|
|
17
19
|
|
|
@@ -36,9 +38,9 @@ Create an inbound webhook in Bitrix24:
|
|
|
36
38
|
1. Open **Apps** > **Developer resources** > **Other** > **Inbound webhook**
|
|
37
39
|
2. Create a webhook for your OpenClaw bot
|
|
38
40
|
3. Grant scopes:
|
|
39
|
-
- `imbot`
|
|
40
|
-
- `im`
|
|
41
|
-
-
|
|
41
|
+
- `imbot` for normal bot operation
|
|
42
|
+
- `im` if you enable `agentMode` to ingest user-visible events and agent-side watches
|
|
43
|
+
- add any extra scopes required by your Bitrix24 scenario
|
|
42
44
|
4. Save the webhook and copy the URL
|
|
43
45
|
|
|
44
46
|
Example webhook URL:
|
|
@@ -60,7 +62,49 @@ Minimal configuration:
|
|
|
60
62
|
"webhookUrl": "https://your-portal.bitrix24.com/rest/1/abc123xyz456/",
|
|
61
63
|
"botName": "OpenClaw",
|
|
62
64
|
"dmPolicy": "webhookUser",
|
|
63
|
-
"
|
|
65
|
+
"groupPolicy": "webhookUser",
|
|
66
|
+
"requireMention": true,
|
|
67
|
+
"historyLimit": 100,
|
|
68
|
+
"showTyping": true,
|
|
69
|
+
"capabilities": [
|
|
70
|
+
"inlineButtons",
|
|
71
|
+
"reactions"
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Example with group overrides:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"channels": {
|
|
83
|
+
"bitrix24": {
|
|
84
|
+
"webhookUrl": "https://your-portal.bitrix24.com/rest/1/abc123xyz456/",
|
|
85
|
+
"botName": "OpenClaw",
|
|
86
|
+
"dmPolicy": "webhookUser",
|
|
87
|
+
"groupPolicy": "webhookUser",
|
|
88
|
+
"groupAllowFrom": ["chat208", "615"],
|
|
89
|
+
"requireMention": true,
|
|
90
|
+
"historyLimit": 100,
|
|
91
|
+
"groups": {
|
|
92
|
+
"*": {
|
|
93
|
+
"requireMention": true
|
|
94
|
+
},
|
|
95
|
+
"chat208": {
|
|
96
|
+
"groupPolicy": "open",
|
|
97
|
+
"requireMention": false
|
|
98
|
+
},
|
|
99
|
+
"615": {
|
|
100
|
+
"groupPolicy": "allowlist",
|
|
101
|
+
"requireMention": true,
|
|
102
|
+
"allowFrom": ["20", "42"],
|
|
103
|
+
"watch": [
|
|
104
|
+
{ "userId": "77", "topics": ["secret", "contract"] }
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
64
108
|
}
|
|
65
109
|
}
|
|
66
110
|
}
|
|
@@ -90,20 +134,93 @@ If `eventMode` is omitted, the plugin uses:
|
|
|
90
134
|
| Parameter | Default | Description |
|
|
91
135
|
|---|---|---|
|
|
92
136
|
| `webhookUrl` | — | Bitrix24 REST webhook URL. Required. |
|
|
93
|
-
| `callbackUrl` | — | Public HTTPS URL for webhook delivery mode. Required only for `eventMode: "webhook"`. |
|
|
94
137
|
| `eventMode` | auto | `fetch` or `webhook`. Auto-selects from `callbackUrl` when omitted. |
|
|
138
|
+
| `callbackUrl` | — | Public HTTPS URL for webhook delivery mode. Required only for `eventMode: "webhook"`. |
|
|
95
139
|
| `botName` | `"OpenClaw"` | Bot display name. |
|
|
96
|
-
| `botCode` | auto | Optional explicit bot code. If omitted, the plugin registers the bot as `openclaw_<webhookUserId
|
|
97
|
-
| `botToken` | `md5(webhookUrl)` | Bot token for `imbot.v2` authentication
|
|
140
|
+
| `botCode` | auto | Optional explicit bot code. If omitted, the plugin registers the bot as `openclaw_<webhookUserId>` and falls back to `_2`, `_3`, and so on if needed. |
|
|
141
|
+
| `botToken` | `md5(webhookUrl)` | Bot token for `imbot.v2` authentication. Strongly recommended to set explicitly if your `webhookUrl` may change. |
|
|
98
142
|
| `botAvatar` | — | Optional base64 avatar override. |
|
|
99
|
-
| `
|
|
143
|
+
| `allowFrom` | — | Sender allowlist for `dmPolicy: "allowlist"` and as an approved-sender source for pairing/group overrides. |
|
|
144
|
+
| `dmPolicy` | `"webhookUser"` | Direct-message access policy: `webhookUser`, `pairing`, `allowlist`, `open`. |
|
|
145
|
+
| `groupPolicy` | `"webhookUser"` | Group-chat access policy: `disabled`, `webhookUser`, `pairing`, `allowlist`, `open`. |
|
|
146
|
+
| `groupAllowFrom` | — | Allowlist for group chats themselves. Values may be `dialogId` like `"chat208"` or numeric `chatId` like `"208"`. |
|
|
147
|
+
| `requireMention` | `true` | When `true`, the bot answers in groups only when mentioned. Group messages are still added to RAM history. |
|
|
148
|
+
| `historyLimit` | `100` | Maximum number of RAM history entries kept per conversation. |
|
|
149
|
+
| `groups` | — | Per-group overrides keyed by `dialogId`, numeric `chatId`, or `"*"`. Each override may set `groupPolicy`, `requireMention`, `allowFrom`, and `watch`. |
|
|
100
150
|
| `showTyping` | `true` | Sends typing indicator before response. |
|
|
101
151
|
| `enabled` | `true` | Enables or disables the account. |
|
|
102
|
-
| `agentMode` | `false` |
|
|
152
|
+
| `agentMode` | `false` | Enables combined polling with `imbot.v2.Event.get(withUserEvents=true)`, subscribes the webhook owner to `im.v2` event recording in fetch mode, and stores delivered user-visible messages in RAM history without answering them directly. |
|
|
153
|
+
| `agentWatch` | — | Watch rules for user-visible events captured through `agentMode`. Keys follow the same matching pattern as chats: direct dialog like `"77"`, group dialog like `"chat520"`, numeric chat id like `"520"`, or wildcard `"*"`. Only `mode: "notifyOwnerDm"` is supported. |
|
|
103
154
|
| `pollingIntervalMs` | `3000` | Base poll interval for `fetch` mode. |
|
|
104
155
|
| `pollingFastIntervalMs` | `100` | Fast follow-up poll interval when more events are pending. |
|
|
105
156
|
|
|
106
|
-
|
|
157
|
+
Group override matching order:
|
|
158
|
+
|
|
159
|
+
1. Exact `dialogId`, for example `"chat208"`
|
|
160
|
+
2. Exact numeric `chatId`, for example `"208"`
|
|
161
|
+
3. Fallback wildcard `"*"`
|
|
162
|
+
|
|
163
|
+
## Scenario Presets
|
|
164
|
+
|
|
165
|
+
The direct-message scenario is configured independently through `dmPolicy`. Group scenarios are controlled by `groupPolicy`, `requireMention`, optional `allowFrom`, and optional per-group `watch`.
|
|
166
|
+
|
|
167
|
+
| Scenario | Behavior | Minimal settings |
|
|
168
|
+
|---|---|---|
|
|
169
|
+
| `1. One-on-one chat with the bot.` | The bot answers in a direct chat according to the selected DM access policy. | `dmPolicy: "webhookUser" \| "pairing" \| "allowlist" \| "open"` |
|
|
170
|
+
| `2. Add the bot to a group chat where it responds to any message from any participant.` | The bot stores all delivered group messages in RAM history and answers every participant without mention. | `groupPolicy: "open"`, `requireMention: false` |
|
|
171
|
+
| `3. Add the bot to a group chat where it responds only to mentions from any participant.` | The bot stores all delivered group messages in RAM history, but answers only when mentioned. | `groupPolicy: "open"`, `requireMention: true` |
|
|
172
|
+
| `4. Add the bot to a group chat where it responds to all of my messages in that chat.` | The bot stores all delivered group messages in RAM history, but answers only the webhook owner and does not require mention. | `groupPolicy: "webhookUser"`, `requireMention: false` |
|
|
173
|
+
| `5. Add the bot to a group chat where it responds only to my mentions in that chat.` | The bot stores all delivered group messages in RAM history, but answers only the webhook owner and only on mention. | `groupPolicy: "webhookUser"`, `requireMention: true` |
|
|
174
|
+
| `6. Add the bot to a group chat where scenarios 2 through 5 apply only to users from my allowlist.` | The bot stores all delivered group messages in RAM history, but group replies are limited to the merged allowlist. | `groupPolicy: "allowlist"`, `allowFrom: ["77", "42"]`, plus `requireMention: false` or `true` |
|
|
175
|
+
| `7. The bot can summarize or search messages from any direct or group chat it participates in.` | This is baseline behavior. All delivered messages are recorded in RAM history and can later be used for in-chat context or explicit cross-chat lookup. | No extra flag. Usually `historyLimit: 100`. Use explicit Bitrix24 chat mention like `[CHAT=520]Green Chat 15[/CHAT]` for cross-chat lookup. |
|
|
176
|
+
| `8. In a group chat, the bot can watch a specific user and answer only on selected topics.` | The bot records all delivered messages and may answer watched users without mention when a watch rule matches. | `groups.<chat>.watch: [{ "userId": "77", "topics": ["secret", "contract"] }]` |
|
|
177
|
+
| `9. In a group chat, the bot can watch a specific user or all users and notify the bot owner in DM with a forwarded copy of the matched message.` | The bot records all delivered messages. When a watch rule matches, it does not answer in the group and instead sends the webhook owner a DM notice with a context URL, plus a native forwarded message copy. | `groups.<chat>.watch: [{ "userId": "*", "topics": ["incident"], "mode": "notifyOwnerDm" }]` |
|
|
178
|
+
| `10. The bot can watch all accessible group chats for a phrase and notify the bot owner in DM with a forwarded copy of the matched message.` | The bot records all delivered group messages. A wildcard group watch applies across every allowed group chat, even when a specific chat also has its own overrides. | `groups["*"].watch: [{ "userId": "*", "topics": ["incident"], "mode": "notifyOwnerDm" }]` |
|
|
179
|
+
|
|
180
|
+
Default profile:
|
|
181
|
+
|
|
182
|
+
- `dmPolicy: "webhookUser"`
|
|
183
|
+
- `groupPolicy: "webhookUser"`
|
|
184
|
+
- `requireMention: true`
|
|
185
|
+
- `historyLimit: 100`
|
|
186
|
+
|
|
187
|
+
## Agent Mode
|
|
188
|
+
|
|
189
|
+
When `agentMode: true` is enabled in `fetch` mode, the plugin:
|
|
190
|
+
|
|
191
|
+
- subscribes the webhook owner to `im.v2` event recording
|
|
192
|
+
- polls `imbot.v2.Event.get` with `withUserEvents: true`
|
|
193
|
+
- receives both normal bot events and additional user-visible message events
|
|
194
|
+
- stores those additional user-visible messages in RAM history
|
|
195
|
+
- never answers user-visible events directly as the bot
|
|
196
|
+
|
|
197
|
+
This is mainly useful for owner-side watch rules and future agent-style retrieval scenarios.
|
|
198
|
+
|
|
199
|
+
Example:
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"channels": {
|
|
204
|
+
"bitrix24": {
|
|
205
|
+
"webhookUrl": "https://your-portal.bitrix24.com/rest/1/abc123xyz456/",
|
|
206
|
+
"agentMode": true,
|
|
207
|
+
"agentWatch": {
|
|
208
|
+
"*": [
|
|
209
|
+
{ "userId": "*", "topics": ["incident"], "mode": "notifyOwnerDm" }
|
|
210
|
+
],
|
|
211
|
+
"77": [
|
|
212
|
+
{ "userId": "*", "topics": ["urgent"], "mode": "notifyOwnerDm" }
|
|
213
|
+
],
|
|
214
|
+
"chat520": [
|
|
215
|
+
{ "userId": "*", "topics": ["release"], "mode": "notifyOwnerDm" }
|
|
216
|
+
]
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Agent-Driven Reactions
|
|
107
224
|
|
|
108
225
|
To enable agent-driven reactions, add `reactions` to the channel capabilities in your OpenClaw config:
|
|
109
226
|
|
|
@@ -118,9 +235,9 @@ To enable agent-driven reactions, add `reactions` to the channel capabilities in
|
|
|
118
235
|
}
|
|
119
236
|
```
|
|
120
237
|
|
|
121
|
-
The plugin maps standard Unicode emoji
|
|
238
|
+
The plugin maps standard Unicode emoji to Bitrix24 reaction codes automatically. Native Bitrix24 reaction codes like `like`, `fire` and `eyes` also work.
|
|
122
239
|
|
|
123
|
-
|
|
240
|
+
## Inline Buttons
|
|
124
241
|
|
|
125
242
|
To enable agent-driven inline buttons, add `inlineButtons` to the channel capabilities in your OpenClaw config:
|
|
126
243
|
|
|
@@ -143,33 +260,74 @@ The plugin supports two button input forms:
|
|
|
143
260
|
For converted generic buttons:
|
|
144
261
|
|
|
145
262
|
- `callback_data` starting with `/` becomes a native Bitrix24 slash command button
|
|
146
|
-
- any other `callback_data` becomes `ACTION=
|
|
147
|
-
- `primary`, `attention
|
|
263
|
+
- any other `callback_data` becomes `ACTION=SEND`
|
|
264
|
+
- `primary`, `attention` and `danger` button styles are mapped to Bitrix24 color tokens
|
|
148
265
|
|
|
149
266
|
## Access Policies
|
|
150
267
|
|
|
151
|
-
###
|
|
268
|
+
### Direct Messages
|
|
152
269
|
|
|
153
|
-
|
|
270
|
+
- `webhookUser`: only the Bitrix24 user from the webhook URL may talk to the bot
|
|
271
|
+
- `pairing`: user must be approved through the OpenClaw pairing flow
|
|
272
|
+
- `allowlist`: user must be present in `allowFrom`
|
|
273
|
+
- `open`: any direct-message sender is allowed
|
|
154
274
|
|
|
155
|
-
|
|
275
|
+
### Group Chats
|
|
156
276
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
277
|
+
- `disabled`: bot sends a short notice and leaves the chat
|
|
278
|
+
- `webhookUser`: only the webhook owner may use the bot in that group
|
|
279
|
+
- `pairing`: sender must be approved; the public group receives only a short notice and never a pairing code
|
|
280
|
+
- `allowlist`: sender must be allowed by merged `allowFrom` and per-group `groups.<key>.allowFrom`
|
|
281
|
+
- `open`: any sender in the allowed group may use the bot
|
|
160
282
|
|
|
161
|
-
|
|
283
|
+
`groupAllowFrom` controls whether the group itself is allowed at all. A matching explicit entry in `groups` also enables that group even if the top-level `groupAllowFrom` is set to a different list.
|
|
162
284
|
|
|
163
|
-
###
|
|
285
|
+
### Group Message Memory
|
|
164
286
|
|
|
165
|
-
|
|
287
|
+
All delivered group messages are added to RAM history, even when the sender is not allowed to use the bot or when `requireMention: true` prevents an answer.
|
|
166
288
|
|
|
167
|
-
|
|
289
|
+
This means:
|
|
168
290
|
|
|
169
|
-
|
|
170
|
-
|
|
291
|
+
- `groupPolicy` controls who may receive a reply in the group
|
|
292
|
+
- `requireMention` controls whether an explicit Bitrix24 mention is required for that reply
|
|
293
|
+
- RAM-backed context and cross-chat lookup still see the delivered group history regardless of those reply rules
|
|
294
|
+
|
|
295
|
+
When `agentMode: true` is enabled, delivered user-visible messages are also written to RAM history. They are stored for context and watches only; the bot does not answer those user events directly.
|
|
296
|
+
|
|
297
|
+
### Group Watches
|
|
298
|
+
|
|
299
|
+
Per-group `watch` rules let the bot react to a specific user and only on selected topics:
|
|
300
|
+
|
|
301
|
+
```json
|
|
302
|
+
{
|
|
303
|
+
"channels": {
|
|
304
|
+
"bitrix24": {
|
|
305
|
+
"webhookUrl": "https://your-portal.bitrix24.com/rest/1/abc123xyz456/",
|
|
306
|
+
"groupPolicy": "webhookUser",
|
|
307
|
+
"requireMention": true,
|
|
308
|
+
"groups": {
|
|
309
|
+
"chat615": {
|
|
310
|
+
"watch": [
|
|
311
|
+
{ "userId": "77", "topics": ["secret", "contract"], "mode": "reply" }
|
|
312
|
+
]
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
171
318
|
```
|
|
172
319
|
|
|
320
|
+
For a matching `watch` rule:
|
|
321
|
+
|
|
322
|
+
- the sender is matched by Bitrix24 user ID; use `"*"` to match any sender
|
|
323
|
+
- `topics` are matched by normalized text inclusion / token prefix matching
|
|
324
|
+
- `mode: "reply"` answers in the group even if the normal group policy would otherwise require a mention
|
|
325
|
+
- `mode: "notifyOwnerDm"` sends the webhook owner a DM notice with a user mention and a deep link back to the original chat context, plus a native forwarded copy of the matched message instead of replying in the group
|
|
326
|
+
- `groups["*"].watch` applies the same rule set to every allowed group chat
|
|
327
|
+
- if a specific group also defines `watch`, those chat-specific rules are checked first and the wildcard rules remain available as a fallback
|
|
328
|
+
|
|
329
|
+
If `mode` is omitted, `reply` is used.
|
|
330
|
+
|
|
173
331
|
## Delivery Modes
|
|
174
332
|
|
|
175
333
|
### `fetch`
|
|
@@ -187,19 +345,37 @@ openclaw pairing approve bitrix24 <CODE>
|
|
|
187
345
|
## Supported Behavior
|
|
188
346
|
|
|
189
347
|
- Direct messages
|
|
348
|
+
- Group chats with policy-driven access control
|
|
349
|
+
- Mention gating in groups with `requireMention`
|
|
350
|
+
- Group-specific overrides through `groups`
|
|
190
351
|
- Typing indicators
|
|
191
352
|
- Text replies with BBCode conversion
|
|
192
|
-
- Inline keyboard buttons
|
|
193
|
-
- Reactions
|
|
194
|
-
- File upload
|
|
353
|
+
- Inline keyboard buttons
|
|
354
|
+
- Reactions via `imbot.v2.Chat.Message.Reaction.*`
|
|
355
|
+
- File upload via `imbot.v2.File.upload`
|
|
195
356
|
- File download via `imbot.v2.File.download`
|
|
196
357
|
- Slash command registration via `imbot.v2.Command.*`
|
|
197
358
|
- Deduplication for repeated deliveries
|
|
198
359
|
- Rate limiting for Bitrix24 API calls
|
|
360
|
+
- RAM history cache for recent chat context
|
|
361
|
+
- RAM history capture for all delivered group messages
|
|
362
|
+
- Reply-to-message lookup from RAM cache with Bitrix24 API fallback
|
|
363
|
+
- Forwarded-message hydration from Bitrix24 message context, including merge with buffered neighboring context when related events arrive close together
|
|
364
|
+
- Cross-chat lookup from RAM memory by explicit Bitrix24 chat mention like `[CHAT=520]Green Chat 15[/CHAT]`
|
|
365
|
+
- Topic watches for specific group users
|
|
366
|
+
- Owner-DM watch notifications with native forwarded messages
|
|
367
|
+
- Agent-mode ingestion of user-visible messages through combined polling
|
|
368
|
+
- Agent-mode owner-DM watch notifications through `agentWatch`
|
|
369
|
+
|
|
370
|
+
The RAM cache keeps up to `historyLimit` entries per conversation and uses an LRU cap of `1000` conversation keys per process.
|
|
199
371
|
|
|
200
372
|
## Not Supported
|
|
201
373
|
|
|
202
|
-
-
|
|
374
|
+
- Persistent SQLite or search-based history layer
|
|
375
|
+
- Persistent reply or forward search across chats beyond what Bitrix24 `Chat.Message.get` / `getContext` can return
|
|
376
|
+
- “Reply to me in DM because this started in a group” behavior
|
|
377
|
+
- Sending replies as the user through `im.v2.Chat.Message.*`
|
|
378
|
+
- Agent-triggered user auto-replies or one-shot autoresponder flows
|
|
203
379
|
- Production-ready multi-account operation in one process
|
|
204
380
|
|
|
205
381
|
There is already account-config scaffolding in the plugin, but the current production recommendation is still one active Bitrix24 portal per instance.
|
|
@@ -207,18 +383,36 @@ There is already account-config scaffolding in the plugin, but the current produ
|
|
|
207
383
|
## Verification
|
|
208
384
|
|
|
209
385
|
1. Start OpenClaw with the plugin enabled.
|
|
210
|
-
2. Open a direct chat with the bot in Bitrix24.
|
|
211
|
-
3.
|
|
212
|
-
4.
|
|
213
|
-
5.
|
|
386
|
+
2. Open a direct chat with the bot in Bitrix24 and send a message.
|
|
387
|
+
3. Verify the bot shows typing and returns a response.
|
|
388
|
+
4. If `dmPolicy` is `webhookUser`, verify that only the webhook owner can use direct messages.
|
|
389
|
+
5. Add the bot to an allowed group and mention it with the Bitrix24 format `[USER=<botId>]BotName[/USER]`.
|
|
390
|
+
6. Verify the bot answers when mentioned and stays silent when `requireMention: true` and no mention is present.
|
|
391
|
+
7. Verify that non-mentioned group messages still appear later in RAM-backed context.
|
|
392
|
+
8. Reply to a recently seen message in the same chat and verify the bot keeps reply context.
|
|
393
|
+
9. Forward a recent message and verify the plugin merges context when the forwarded event pair arrives together.
|
|
394
|
+
10. In DM or another group, reference a visible chat as `[CHAT=<chatId>]ChatName[/CHAT]` and verify the bot can use RAM history from that chat.
|
|
395
|
+
|
|
396
|
+
Local development checks:
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
npm test
|
|
400
|
+
npm run build
|
|
401
|
+
```
|
|
214
402
|
|
|
215
403
|
## Troubleshooting
|
|
216
404
|
|
|
217
|
-
-
|
|
218
|
-
- If
|
|
219
|
-
-
|
|
220
|
-
-
|
|
405
|
+
- If the bot leaves a group immediately, check `groupPolicy`, `groupAllowFrom` and `groups`. A disabled or disallowed group is expected to receive a short notice and then be left.
|
|
406
|
+
- If the bot stays in the group but does not answer, check `requireMention`. In live Bitrix24 payloads, mention markup arrives inside `message.text` as `[USER=<botId>]Name[/USER]`.
|
|
407
|
+
- If the bot does not answer a user in group chat, check `groupPolicy`, `allowFrom`, and per-group overrides in `groups`.
|
|
408
|
+
- If a group message seems to vanish from later context, verify that the message was actually delivered to the bot process and that it has not been evicted by `historyLimit` or the global RAM LRU.
|
|
409
|
+
- If a per-group override does not apply, verify the key matching order: exact `dialogId`, then exact numeric `chatId`, then `"*"`.
|
|
410
|
+
- If reply context is missing, the referenced message may be unavailable to the current bot type, no longer accessible through Bitrix24 `Chat.Message.get`, or absent from local RAM history.
|
|
411
|
+
- If cross-chat lookup does not work, verify the query contains an explicit Bitrix24 chat mention like `[CHAT=520]Green Chat 15[/CHAT]` and that this chat has already appeared in RAM history during the current process lifetime.
|
|
412
|
+
- If `eventMode: "webhook"` is used, verify `callbackUrl` is reachable from the internet over HTTPS.
|
|
413
|
+
- If `agentMode` is enabled but user-visible events never appear, verify the webhook has the `im` scope and that `im.v2.Event.subscribe` succeeds for the webhook owner.
|
|
221
414
|
- If file transfer fails, verify the file size and Bitrix24-side availability of the attachment.
|
|
415
|
+
- If the bot still leaves every group after you changed config, make sure the deployed plugin code is current. Older builds contained unconditional “group chat is not supported, leaving” logic.
|
|
222
416
|
|
|
223
417
|
## Development
|
|
224
418
|
|
package/package.json
CHANGED
package/src/access-control.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Bitrix24AccountConfig } from './types.js';
|
|
1
|
+
import type { Bitrix24AccountConfig, Bitrix24DmPolicy } from './types.js';
|
|
2
2
|
import type { PluginRuntime, ChannelPairingAdapter } from './runtime.js';
|
|
3
3
|
import { stripChannelPrefix } from './utils.js';
|
|
4
4
|
|
|
@@ -22,6 +22,10 @@ export function normalizeAllowList(entries: string[] | undefined): string[] {
|
|
|
22
22
|
)];
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
export function isIdentityAllowed(identity: string, allowFrom: string[] | undefined): boolean {
|
|
26
|
+
return normalizeAllowList(allowFrom).includes(identity);
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
/**
|
|
26
30
|
* Extract the owner user ID from a Bitrix24 inbound webhook URL.
|
|
27
31
|
* Format: https://{portal}/rest/{user_id}/{webhook_token}/
|
|
@@ -55,15 +59,22 @@ export function checkAccess(
|
|
|
55
59
|
config: Bitrix24AccountConfig,
|
|
56
60
|
_options?: { dialogId?: string; isDirect?: boolean },
|
|
57
61
|
): boolean {
|
|
58
|
-
const dmPolicy = config.dmPolicy
|
|
62
|
+
const dmPolicy = resolveDmPolicy(config.dmPolicy);
|
|
59
63
|
const identity = resolveAccessIdentity(senderId);
|
|
60
64
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
switch (dmPolicy) {
|
|
66
|
+
case 'webhookUser': {
|
|
67
|
+
const webhookUserId = getWebhookUserId(config.webhookUrl);
|
|
68
|
+
return webhookUserId === identity;
|
|
69
|
+
}
|
|
70
|
+
case 'open':
|
|
71
|
+
return true;
|
|
72
|
+
case 'allowlist':
|
|
73
|
+
return isIdentityAllowed(identity, config.allowFrom);
|
|
74
|
+
case 'pairing':
|
|
75
|
+
default:
|
|
76
|
+
return false;
|
|
64
77
|
}
|
|
65
|
-
|
|
66
|
-
return false;
|
|
67
78
|
}
|
|
68
79
|
|
|
69
80
|
export type AccessResult = 'allow' | 'deny' | 'pairing';
|
|
@@ -96,7 +107,15 @@ export async function checkAccessWithPairing(params: {
|
|
|
96
107
|
logger,
|
|
97
108
|
} = params;
|
|
98
109
|
const identity = resolveAccessIdentity(senderId);
|
|
99
|
-
const dmPolicy = config.dmPolicy
|
|
110
|
+
const dmPolicy = resolveDmPolicy(config.dmPolicy);
|
|
111
|
+
|
|
112
|
+
if (dmPolicy === 'open') {
|
|
113
|
+
return 'allow';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (dmPolicy === 'allowlist') {
|
|
117
|
+
return isIdentityAllowed(identity, config.allowFrom) ? 'allow' : 'deny';
|
|
118
|
+
}
|
|
100
119
|
|
|
101
120
|
if (dmPolicy === 'webhookUser') {
|
|
102
121
|
const webhookUserId = getWebhookUserId(config.webhookUrl);
|
|
@@ -151,3 +170,7 @@ export async function checkAccessWithPairing(params: {
|
|
|
151
170
|
function resolveAccessIdentity(senderId: string): string {
|
|
152
171
|
return normalizeAllowEntry(String(senderId));
|
|
153
172
|
}
|
|
173
|
+
|
|
174
|
+
function resolveDmPolicy(policy: Bitrix24AccountConfig['dmPolicy']): Bitrix24DmPolicy {
|
|
175
|
+
return policy ?? 'webhookUser';
|
|
176
|
+
}
|
package/src/api.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import type {
|
|
2
3
|
B24ApiResult,
|
|
3
4
|
B24V2BotResult,
|
|
4
5
|
B24V2BotListResult,
|
|
6
|
+
B24V2GetMessageContextResult,
|
|
7
|
+
B24V2GetMessageResult,
|
|
5
8
|
B24V2SendMessageResult,
|
|
6
9
|
B24V2ReadMessageResult,
|
|
7
10
|
B24InputActionStatusCode,
|
|
@@ -261,28 +264,94 @@ export class Bitrix24Api {
|
|
|
261
264
|
webhookUrl: string,
|
|
262
265
|
bot: BotContext,
|
|
263
266
|
dialogId: string,
|
|
264
|
-
message: string,
|
|
265
|
-
options?: {
|
|
267
|
+
message: string | null,
|
|
268
|
+
options?: {
|
|
269
|
+
keyboard?: B24Keyboard;
|
|
270
|
+
attach?: unknown;
|
|
271
|
+
system?: boolean;
|
|
272
|
+
urlPreview?: boolean;
|
|
273
|
+
forwardMessages?: number[];
|
|
274
|
+
},
|
|
266
275
|
): Promise<number> {
|
|
267
|
-
const fields: Record<string, unknown> = {
|
|
276
|
+
const fields: Record<string, unknown> = {};
|
|
277
|
+
if (typeof message === 'string') {
|
|
278
|
+
fields.message = message;
|
|
279
|
+
}
|
|
268
280
|
if (options?.keyboard) fields.keyboard = options.keyboard;
|
|
269
281
|
if (options?.attach) fields.attach = options.attach;
|
|
270
282
|
if (options?.system !== undefined) fields.system = options.system;
|
|
271
283
|
if (options?.urlPreview !== undefined) fields.urlPreview = options.urlPreview;
|
|
284
|
+
if (options?.forwardMessages?.length) {
|
|
285
|
+
fields.forwardIds = Object.fromEntries(
|
|
286
|
+
options.forwardMessages.map((forwardMessageId) => [randomUUID(), forwardMessageId]),
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const params: Record<string, unknown> = {
|
|
291
|
+
botId: bot.botId,
|
|
292
|
+
botToken: bot.botToken,
|
|
293
|
+
dialogId,
|
|
294
|
+
fields,
|
|
295
|
+
};
|
|
272
296
|
|
|
273
297
|
const result = await this.callWebhook<B24V2SendMessageResult>(
|
|
274
298
|
webhookUrl,
|
|
275
299
|
'imbot.v2.Chat.Message.send',
|
|
276
|
-
|
|
300
|
+
params,
|
|
277
301
|
);
|
|
278
302
|
const messageId = result.result?.id;
|
|
279
|
-
if (!Number.isFinite(messageId) || messageId <= 0) {
|
|
303
|
+
if (!Number.isFinite(messageId) || Number(messageId) <= 0) {
|
|
304
|
+
if (options?.forwardMessages?.length && typeof message !== 'string') {
|
|
305
|
+
return 0;
|
|
306
|
+
}
|
|
280
307
|
throw new Bitrix24ApiError(
|
|
281
308
|
'INVALID_RESPONSE',
|
|
282
309
|
'imbot.v2.Chat.Message.send returned response without valid message id',
|
|
283
310
|
);
|
|
284
311
|
}
|
|
285
|
-
return messageId;
|
|
312
|
+
return Number(messageId);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Get a single message by id (imbot.v2.Chat.Message.get).
|
|
317
|
+
*/
|
|
318
|
+
async getMessage(
|
|
319
|
+
webhookUrl: string,
|
|
320
|
+
bot: BotContext,
|
|
321
|
+
messageId: number,
|
|
322
|
+
): Promise<B24V2GetMessageResult> {
|
|
323
|
+
const result = await this.callWebhook<B24V2GetMessageResult>(
|
|
324
|
+
webhookUrl,
|
|
325
|
+
'imbot.v2.Chat.Message.get',
|
|
326
|
+
{
|
|
327
|
+
botId: bot.botId,
|
|
328
|
+
botToken: bot.botToken,
|
|
329
|
+
messageId,
|
|
330
|
+
},
|
|
331
|
+
);
|
|
332
|
+
return result.result;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Get message window around a message id (imbot.v2.Chat.Message.getContext).
|
|
337
|
+
*/
|
|
338
|
+
async getMessageContext(
|
|
339
|
+
webhookUrl: string,
|
|
340
|
+
bot: BotContext,
|
|
341
|
+
messageId: number,
|
|
342
|
+
range?: number,
|
|
343
|
+
): Promise<B24V2GetMessageContextResult> {
|
|
344
|
+
const result = await this.callWebhook<B24V2GetMessageContextResult>(
|
|
345
|
+
webhookUrl,
|
|
346
|
+
'imbot.v2.Chat.Message.getContext',
|
|
347
|
+
{
|
|
348
|
+
botId: bot.botId,
|
|
349
|
+
botToken: bot.botToken,
|
|
350
|
+
messageId,
|
|
351
|
+
...(range ? { range } : {}),
|
|
352
|
+
},
|
|
353
|
+
);
|
|
354
|
+
return result.result;
|
|
286
355
|
}
|
|
287
356
|
|
|
288
357
|
/**
|
|
@@ -503,7 +572,7 @@ export class Bitrix24Api {
|
|
|
503
572
|
async fetchEvents(
|
|
504
573
|
webhookUrl: string,
|
|
505
574
|
bot: BotContext,
|
|
506
|
-
params: { offset?: number; limit?: number },
|
|
575
|
+
params: { offset?: number; limit?: number; withUserEvents?: boolean },
|
|
507
576
|
): Promise<B24V2EventGetResult> {
|
|
508
577
|
const result = await this.callWebhook<B24V2EventGetResult>(
|
|
509
578
|
webhookUrl,
|