@ihazz/bitrix24 1.0.0 → 1.0.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 CHANGED
@@ -37,7 +37,7 @@ Create an inbound webhook in Bitrix24:
37
37
  2. Create a webhook for your OpenClaw bot
38
38
  3. Grant scopes:
39
39
  - `imbot` — the minimum scope required for full bot operation
40
- - `im` — additionally required only for `agentMode`, so the bot can read incoming messages addressed to the webhook owner
40
+ - `im` — reserved for future `agentMode` support; `agentMode` is not production-ready yet and will be available in future versions
41
41
  - you may also grant any extra scopes beyond this set if they are needed for your Bitrix24 scenarios
42
42
  4. Save the webhook and copy the URL
43
43
 
@@ -94,15 +94,17 @@ If `eventMode` is omitted, the plugin uses:
94
94
  | `eventMode` | auto | `fetch` or `webhook`. Auto-selects from `callbackUrl` when omitted. |
95
95
  | `botName` | `"OpenClaw"` | Bot display name. |
96
96
  | `botCode` | auto | Optional explicit bot code. If omitted, the plugin registers the bot as `openclaw_<webhookUserId>`, and if that code is occupied it tries `openclaw_<webhookUserId>_2`, `_3`, and so on. |
97
- | `botToken` | derived from `webhookUrl` | Optional explicit bot token for `imbot.v2.Bot.*`. If omitted, it is derived automatically from `webhookUrl`. |
97
+ | `botToken` | `md5(webhookUrl)` | Bot token for `imbot.v2` authentication (32-char hex string). If omitted, derived as `md5(webhookUrl)`. **Strongly recommended to set explicitly** if your `webhookUrl` may change (e.g. ngrok, dynamic DNS) — otherwise the plugin loses control of the previously registered bot. You can find the token in the Bitrix24 admin panel or compute it as `md5` of the original webhook URL. |
98
98
  | `botAvatar` | — | Optional base64 avatar override. |
99
99
  | `dmPolicy` | `"webhookUser"` | Access policy: `webhookUser` or `pairing`. |
100
100
  | `showTyping` | `true` | Sends typing indicator before response. |
101
101
  | `enabled` | `true` | Enables or disables the account. |
102
- | `agentMode` | `false` | Enables additional user-event integration. Requires `im` scope. |
102
+ | `agentMode` | `false` | Reserved for future user-event integration. Not working yet; planned for future versions. |
103
103
  | `pollingIntervalMs` | `3000` | Base poll interval for `fetch` mode. |
104
104
  | `pollingFastIntervalMs` | `100` | Fast follow-up poll interval when more events are pending. |
105
105
 
106
+ ### Agent-Driven Reactions
107
+
106
108
  To enable agent-driven reactions, add `reactions` to the channel capabilities in your OpenClaw config:
107
109
 
108
110
  ```json
@@ -118,6 +120,32 @@ To enable agent-driven reactions, add `reactions` to the channel capabilities in
118
120
 
119
121
  The plugin maps standard Unicode emoji (👍, 🔥, 👀, etc.) to Bitrix24 reaction codes automatically. B24 reaction codes (like `like`, `fire`, `eyes`) can also be used directly.
120
122
 
123
+ ### Inline Buttons
124
+
125
+ To enable agent-driven inline buttons, add `inlineButtons` to the channel capabilities in your OpenClaw config:
126
+
127
+ ```json
128
+ {
129
+ "channels": {
130
+ "bitrix24": {
131
+ "webhookUrl": "...",
132
+ "capabilities": ["inlineButtons"]
133
+ }
134
+ }
135
+ }
136
+ ```
137
+
138
+ The plugin supports two button input forms:
139
+
140
+ - native Bitrix24 keyboard payload via `channelData.bitrix24.keyboard`
141
+ - generic OpenClaw button rows via `channelData.telegram.buttons`, which the plugin converts to Bitrix24 `KEYBOARD`
142
+
143
+ For converted generic buttons:
144
+
145
+ - `callback_data` starting with `/` becomes a native Bitrix24 slash command button
146
+ - any other `callback_data` becomes `ACTION=PUT`
147
+ - `primary`, `attention`, and `danger` button styles are mapped to Bitrix24 color tokens
148
+
121
149
  ## Access Policies
122
150
 
123
151
  ### `webhookUser`
@@ -161,7 +189,7 @@ openclaw pairing approve bitrix24 <CODE>
161
189
  - Direct messages
162
190
  - Typing indicators
163
191
  - Text replies with BBCode conversion
164
- - Inline keyboard buttons
192
+ - Inline keyboard buttons (`channelData.bitrix24.keyboard` and converted generic button rows)
165
193
  - Reactions (agent can add/remove reactions on messages via `imbot.v2.Chat.Message.Reaction.*`)
166
194
  - File upload to chat via `imbot.v2.File.upload`
167
195
  - File download via `imbot.v2.File.download`
@@ -188,7 +216,7 @@ There is already account-config scaffolding in the plugin, but the current produ
188
216
 
189
217
  - Verify `webhookUrl` is valid and active.
190
218
  - If you use `eventMode: "webhook"`, verify `callbackUrl` is reachable from the internet over HTTPS.
191
- - If `agentMode` is enabled, verify the webhook also has `im` scope.
219
+ - Do not rely on `agentMode` yet. It is not working in the current release and is planned for future versions.
192
220
  - Do not expect the bot to work in group chats.
193
221
  - If file transfer fails, verify the file size and Bitrix24-side availability of the attachment.
194
222
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ihazz/bitrix24",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Bitrix24 Messenger channel for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -63,8 +63,34 @@ When you see a message from a Bitrix24 user, you can react to acknowledge it bef
63
63
 
64
64
  **Important:** Do NOT invent or guess messageId values. Either omit `messageId` to react to the current message, or use a messageId that was explicitly provided to you.
65
65
 
66
+ ## Inline Keyboard Buttons
67
+
68
+ You can attach inline buttons to a message. The `callback_data` value is sent back when the user taps the button.
69
+
70
+ ```json
71
+ {
72
+ "action": "send",
73
+ "channel": "bitrix24",
74
+ "message": "Выберите вариант:",
75
+ "buttons": [
76
+ [
77
+ { "text": "Да", "callback_data": "Да" },
78
+ { "text": "Нет", "callback_data": "Нет" }
79
+ ]
80
+ ]
81
+ }
82
+ ```
83
+
84
+ **Important rules for buttons:**
85
+
86
+ - `callback_data` **must match** the button `text` exactly — when the user taps a button, `callback_data` is sent as a message in the chat. If they differ, the user sees confusing text.
87
+ - Write button labels in the **same language as the conversation** (e.g. Russian if the user writes in Russian).
88
+ - Use `"style": "primary"` for the recommended option, `"style": "danger"` for destructive actions.
89
+ - Each inner array is one row of buttons.
90
+
66
91
  ## Writing Style (Bitrix24)
67
92
 
68
93
  - Direct, professional, moderate length.
94
+ - **Always reply in the same language the user writes in.** If the user writes in Russian, reply in Russian. If in English, reply in English.
69
95
  - Bitrix24 uses BBCode for formatting (conversion is automatic).
70
96
  - Inline keyboard buttons are supported for interactive responses.
package/src/channel.ts CHANGED
@@ -552,7 +552,7 @@ export function convertButtonsToKeyboard(rows: ChannelButton[][]): B24Keyboard {
552
552
  b24Btn.COMMAND_PARAMS = parts.slice(1).join(' ');
553
553
  }
554
554
  } else if (btn.callback_data) {
555
- b24Btn.ACTION = 'PUT';
555
+ b24Btn.ACTION = 'SEND';
556
556
  b24Btn.ACTION_VALUE = btn.callback_data;
557
557
  }
558
558
 
@@ -1152,11 +1152,11 @@ export const bitrix24Plugin = {
1152
1152
 
1153
1153
  actions: {
1154
1154
  listActions: (_params: { cfg: Record<string, unknown> }): string[] => {
1155
- return ['react'];
1155
+ return ['react', 'send'];
1156
1156
  },
1157
1157
 
1158
1158
  supportsAction: (params: { action: string }): boolean => {
1159
- return params.action === 'react';
1159
+ return params.action === 'react' || params.action === 'send';
1160
1160
  },
1161
1161
 
1162
1162
  handleAction: async (ctx: {
@@ -1167,6 +1167,56 @@ export const bitrix24Plugin = {
1167
1167
  params: Record<string, unknown>;
1168
1168
  [key: string]: unknown;
1169
1169
  }): Promise<Record<string, unknown> | null> => {
1170
+ // ─── Send with buttons ──────────────────────────────────────────────
1171
+ if (ctx.action === 'send') {
1172
+ // Only intercept send when buttons are present; otherwise let gateway handle normally
1173
+ const rawButtons = ctx.params.buttons;
1174
+ if (!rawButtons) return null;
1175
+
1176
+ const { config } = resolveAccount(ctx.cfg, ctx.accountId);
1177
+ if (!config.webhookUrl || !gatewayState) return null;
1178
+
1179
+ const bot = gatewayState.bot;
1180
+ const to = String(ctx.params.to ?? '').trim();
1181
+ if (!to) return null;
1182
+
1183
+ const sendCtx: SendContext = { webhookUrl: config.webhookUrl, bot, dialogId: to };
1184
+ const message = String(ctx.params.message ?? '').trim();
1185
+
1186
+ // Parse buttons: may be array or JSON string
1187
+ let buttons: ChannelButton[][] | undefined;
1188
+ try {
1189
+ const parsed = typeof rawButtons === 'string' ? JSON.parse(rawButtons) : rawButtons;
1190
+ if (Array.isArray(parsed)) buttons = parsed;
1191
+ } catch {
1192
+ // invalid buttons JSON — send without keyboard
1193
+ }
1194
+
1195
+ const keyboard = buttons?.length ? convertButtonsToKeyboard(buttons) : undefined;
1196
+
1197
+ const toolResult = (payload: Record<string, unknown>) => ({
1198
+ content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],
1199
+ details: payload,
1200
+ });
1201
+
1202
+ try {
1203
+ const result = await gatewayState.sendService.sendText(
1204
+ sendCtx, message || ' ', keyboard ? { keyboard } : undefined,
1205
+ );
1206
+ return toolResult({
1207
+ channel: 'bitrix24',
1208
+ to,
1209
+ via: 'direct',
1210
+ mediaUrl: null,
1211
+ result: { messageId: String(result.messageId ?? '') },
1212
+ });
1213
+ } catch (err) {
1214
+ const errMsg = err instanceof Error ? err.message : String(err);
1215
+ return toolResult({ ok: false, error: errMsg });
1216
+ }
1217
+ }
1218
+
1219
+ // ─── React ────────────────────────────────────────────────────────────
1170
1220
  if (ctx.action !== 'react') return null;
1171
1221
 
1172
1222
  // Helper: wrap payload as gateway-compatible tool result
@@ -105,7 +105,7 @@ describe('convertButtonsToKeyboard', () => {
105
105
  const btn = kb[0];
106
106
  if (isButton(btn)) {
107
107
  expect(btn.COMMAND).toBeUndefined();
108
- expect(btn.ACTION).toBe('PUT');
108
+ expect(btn.ACTION).toBe('SEND');
109
109
  expect(btn.ACTION_VALUE).toBe('some_value');
110
110
  }
111
111
  });