@ihazz/bitrix24 0.2.0 → 0.2.4

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
@@ -45,12 +45,13 @@ Add to your `openclaw.json`:
45
45
  "channels": {
46
46
  "bitrix24": {
47
47
  "webhookUrl": "https://your-portal.bitrix24.com/rest/1/abc123xyz456/",
48
+ "callbackUrl": "https://your-server.com/hooks/bitrix24",
48
49
  "botName": "OpenClaw",
49
50
  "botCode": "openclaw",
50
- "callbackUrl": "https://your-server.com/hooks/bitrix24",
51
51
  "dmPolicy": "open",
52
52
  "allowFrom": ["*"],
53
- "showTyping": true
53
+ "showTyping": true,
54
+ "capabilities": ["inlineButtons"]
54
55
  }
55
56
  }
56
57
  }
@@ -82,6 +83,26 @@ Only `webhookUrl` is required. The gateway will not start without it.
82
83
  | `streamUpdates` | `false` | Stream response via message updates |
83
84
  | `updateIntervalMs` | `10000` | Throttle interval for streaming updates (ms, min 500) |
84
85
  | `enabled` | `true` | Whether this account is enabled |
86
+ | `capabilities` | `[]` | Runtime capabilities to enable (see below) |
87
+
88
+ ### Capabilities
89
+
90
+ The `capabilities` array activates additional runtime features for the AI agent. Add them to the channel config in `openclaw.json`:
91
+
92
+ ```json
93
+ {
94
+ "channels": {
95
+ "bitrix24": {
96
+ ...
97
+ "capabilities": ["inlineButtons"]
98
+ }
99
+ }
100
+ }
101
+ ```
102
+
103
+ | Capability | Description |
104
+ |---|---|
105
+ | `inlineButtons` | Enables inline keyboard buttons in bot messages. The AI agent will be able to send interactive buttons using `action=send` with `buttons=[[{text, callback_data, style?}]]`. Without this capability the agent will not attempt to generate buttons. |
85
106
 
86
107
  ### Access Policies
87
108
 
@@ -204,6 +225,19 @@ SendService:
204
225
  - **Deduplication**: B24 retries webhooks if it doesn't get a 200 in time — the plugin stores MESSAGE_IDs for 5 minutes
205
226
  - **Text conversion**: B24 uses BB-code, not Markdown — automatic conversion (`**bold**` -> `[B]bold[/B]`, etc.)
206
227
 
228
+ ## Customization
229
+
230
+ ### Bot Avatar
231
+
232
+ The default bot avatar is stored in `src/bot-avatar.ts` as a base64-encoded JPEG string. To change the avatar:
233
+
234
+ 1. Prepare a JPEG or PNG image (recommended: 200×200 px, ≤ 50 KB)
235
+ 2. Convert it to base64
236
+ 3. Replace the `DEFAULT_AVATAR_BASE64` value in `src/bot-avatar.ts`
237
+ 4. Rebuild and redeploy
238
+
239
+ The avatar can also be overridden per-account via the `botAvatar` config option (base64 string).
240
+
207
241
  ## Development
208
242
 
209
243
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ihazz/bitrix24",
3
- "version": "0.2.0",
3
+ "version": "0.2.4",
4
4
  "description": "Bitrix24 Messenger channel for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Default bot avatar image (JPEG, 200×200).
3
+ * To change the avatar, replace this base64 string with a new one.
4
+ * Recommended: JPEG or PNG, 200×200 px, ≤ 50 KB.
5
+ */
6
+ export const DEFAULT_AVATAR_BASE64 = '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';
package/src/channel.ts CHANGED
@@ -8,6 +8,7 @@ import { MediaService } from './media-service.js';
8
8
  import type { DownloadedMedia } from './media-service.js';
9
9
  import { InboundHandler } from './inbound-handler.js';
10
10
  import { normalizeAllowEntry, checkAccessWithPairing } from './access-control.js';
11
+ import { DEFAULT_AVATAR_BASE64 } from './bot-avatar.js';
11
12
  import { defaultLogger } from './utils.js';
12
13
  import { getBitrix24Runtime } from './runtime.js';
13
14
  import type { ChannelPairingAdapter } from './runtime.js';
@@ -38,6 +39,23 @@ interface GatewayState {
38
39
 
39
40
  let gatewayState: GatewayState | null = null;
40
41
 
42
+ // ─── i18n helpers ────────────────────────────────────────────────────────────
43
+
44
+ const I18N_MEDIA_DOWNLOAD_FAILED: Record<string, (files: string) => string> = {
45
+ en: (f) => `⚠️ Could not download file(s): ${f}.\n\nFile processing is currently only available for the primary user (webhook owner). This limitation will be removed in a future release.`,
46
+ ru: (f) => `⚠️ Не удалось загрузить файл(ы): ${f}.\n\nОбработка файлов пока доступна только для основного пользователя (автора вебхука). В будущих версиях это ограничение будет снято.`,
47
+ de: (f) => `⚠️ Datei(en) konnten nicht heruntergeladen werden: ${f}.\n\nDateiverarbeitung ist derzeit nur für den Hauptbenutzer (Webhook-Besitzer) verfügbar. Diese Einschränkung wird in einer zukünftigen Version behoben.`,
48
+ es: (f) => `⚠️ No se pudo descargar el/los archivo(s): ${f}.\n\nEl procesamiento de archivos actualmente solo está disponible para el usuario principal (propietario del webhook). Esta limitación se eliminará en una versión futura.`,
49
+ fr: (f) => `⚠️ Impossible de télécharger le(s) fichier(s) : ${f}.\n\nLe traitement des fichiers est actuellement réservé à l'utilisateur principal (propriétaire du webhook). Cette limitation sera levée dans une prochaine version.`,
50
+ pt: (f) => `⚠️ Não foi possível baixar o(s) arquivo(s): ${f}.\n\nO processamento de arquivos está disponível apenas para o usuário principal (dono do webhook). Essa limitação será removida em uma versão futura.`,
51
+ };
52
+
53
+ function mediaDownloadFailedMsg(lang: string | undefined, fileNames: string): string {
54
+ const code = (lang ?? 'en').toLowerCase().slice(0, 2);
55
+ const fn = I18N_MEDIA_DOWNLOAD_FAILED[code] ?? I18N_MEDIA_DOWNLOAD_FAILED.en;
56
+ return fn(fileNames);
57
+ }
58
+
41
59
  // ─── Default command keyboard ────────────────────────────────────────────────
42
60
 
43
61
  /** Default keyboard shown with command responses and welcome messages. */
@@ -155,9 +173,16 @@ async function ensureBotRegistered(
155
173
 
156
174
  if (existing) {
157
175
  logger.info(`Bot "${code}" already registered (ID=${existing.ID}), updating EVENT_HANDLER`);
176
+ const updateProps: Record<string, unknown> = {
177
+ NAME: name,
178
+ WORK_POSITION: 'AI Assistant',
179
+ COLOR: 'RED',
180
+ };
181
+ updateProps.PERSONAL_PHOTO = config.botAvatar || DEFAULT_AVATAR_BASE64;
182
+
158
183
  await api.updateBot(webhookUrl, existing.ID, {
159
184
  EVENT_HANDLER: callbackUrl,
160
- PROPERTIES: { NAME: name },
185
+ PROPERTIES: updateProps,
161
186
  });
162
187
  return existing.ID;
163
188
  }
@@ -174,7 +199,8 @@ async function ensureBotRegistered(
174
199
  PROPERTIES: {
175
200
  NAME: name,
176
201
  WORK_POSITION: 'AI Assistant',
177
- COLOR: 'AZURE',
202
+ COLOR: 'RED',
203
+ PERSONAL_PHOTO: config.botAvatar || DEFAULT_AVATAR_BASE64,
178
204
  },
179
205
  });
180
206
  logger.info(`Bot "${code}" registered (ID=${botId})`);
@@ -292,6 +318,22 @@ export const bitrix24Plugin = {
292
318
  reactions: false,
293
319
  threads: false,
294
320
  nativeCommands: true,
321
+ inlineButtons: 'all',
322
+ },
323
+
324
+ messaging: {
325
+ targetResolver: {
326
+ hint: 'Use a numeric chat/dialog ID, e.g. "1" or "chat42".',
327
+ /**
328
+ * Recognize any numeric string as a valid Bitrix24 target ID.
329
+ * B24 dialog IDs can be short (e.g. "1"), so the default 6+ digit check is too strict.
330
+ */
331
+ looksLikeId: (raw: string, _normalized: string) => {
332
+ // raw may include channel prefix like "bitrix24:1" — strip it first
333
+ const stripped = raw.trim().replace(/^(bitrix24|b24|bx24):/i, '');
334
+ return /^\d+$/.test(stripped);
335
+ },
336
+ },
295
337
  },
296
338
 
297
339
  config: {
@@ -524,6 +566,21 @@ export const bitrix24Plugin = {
524
566
  MediaUrls: downloaded.map((m) => m.path),
525
567
  MediaTypes: downloaded.map((m) => m.contentType),
526
568
  };
569
+ } else {
570
+ // All file downloads failed — notify the user
571
+ const fileNames = msgCtx.media.map((m) => m.name).join(', ');
572
+ logger.warn('All media downloads failed, notifying user', { fileNames });
573
+ const errSendCtx = {
574
+ webhookUrl: config.webhookUrl,
575
+ clientEndpoint: msgCtx.clientEndpoint,
576
+ botToken: msgCtx.botToken,
577
+ dialogId: msgCtx.chatId,
578
+ };
579
+ await sendService.sendText(
580
+ errSendCtx,
581
+ mediaDownloadFailedMsg(msgCtx.language, fileNames),
582
+ );
583
+ return;
527
584
  }
528
585
  }
529
586
 
@@ -558,7 +615,7 @@ export const bitrix24Plugin = {
558
615
  RawBody: body,
559
616
  From: `bitrix24:${msgCtx.chatId}`,
560
617
  To: `bitrix24:${msgCtx.chatId}`,
561
- SessionKey: route.sessionKey,
618
+ SessionKey: `${route.sessionKey}:bitrix24:${msgCtx.chatId}`,
562
619
  AccountId: route.accountId,
563
620
  ChatType: msgCtx.isDm ? 'direct' : 'group',
564
621
  ConversationLabel: msgCtx.senderName,
@@ -695,7 +752,7 @@ export const bitrix24Plugin = {
695
752
  CommandBody: commandText,
696
753
  CommandAuthorized: true,
697
754
  CommandSource: 'native',
698
- CommandTargetSessionKey: route.sessionKey,
755
+ CommandTargetSessionKey: `${route.sessionKey}:bitrix24:${dialogId}`,
699
756
  From: `bitrix24:${dialogId}`,
700
757
  To: `slash:${senderId}`,
701
758
  SessionKey: slashSessionKey,
@@ -756,23 +813,50 @@ export const bitrix24Plugin = {
756
813
  },
757
814
 
758
815
  onJoinChat: async (event: B24JoinChatEvent) => {
816
+ const dialogId = event.data.PARAMS.DIALOG_ID;
817
+ const botEntry = Object.values(event.data.BOT)[0];
759
818
  logger.info('Bot joined chat', {
760
- dialogId: event.data.PARAMS.DIALOG_ID,
819
+ dialogId,
761
820
  userId: event.data.PARAMS.USER_ID,
821
+ hasBotEntry: !!botEntry,
822
+ botId: botEntry?.BOT_ID,
823
+ hasEndpoint: !!botEntry?.client_endpoint,
824
+ hasToken: !!botEntry?.access_token,
762
825
  });
763
- const dialogId = event.data.PARAMS.DIALOG_ID;
764
- const botEntry = Object.values(event.data.BOT)[0];
765
- if (botEntry && dialogId) {
766
- try {
826
+ if (!dialogId) return;
827
+
828
+ const welcomeText = `${config.botName ?? 'OpenClaw'} ready. Send me a message or pick a command below.`;
829
+ const welcomeOpts = { KEYBOARD: DEFAULT_COMMAND_KEYBOARD };
830
+
831
+ try {
832
+ // Prefer token-based call; fall back to webhook URL
833
+ if (botEntry?.client_endpoint && botEntry?.access_token) {
767
834
  await api.sendMessageWithToken(
768
835
  botEntry.client_endpoint,
769
836
  botEntry.access_token,
770
837
  dialogId,
771
- `${config.botName ?? 'OpenClaw'} ready. Send me a message or pick a command below.`,
772
- { KEYBOARD: DEFAULT_COMMAND_KEYBOARD },
838
+ welcomeText,
839
+ welcomeOpts,
773
840
  );
774
- } catch (err) {
775
- logger.error('Failed to send welcome message', err);
841
+ } else if (config.webhookUrl) {
842
+ await api.sendMessage(config.webhookUrl, dialogId, welcomeText, welcomeOpts);
843
+ } else {
844
+ logger.warn('No way to send welcome message — no token and no webhookUrl');
845
+ return;
846
+ }
847
+ logger.info('Welcome message sent', { dialogId });
848
+ } catch (err: unknown) {
849
+ const errMsg = err instanceof Error ? err.message : String(err);
850
+ logger.error('Failed to send welcome message', { error: errMsg, dialogId });
851
+ // Retry via webhook if token-based call failed
852
+ if (botEntry?.client_endpoint && config.webhookUrl) {
853
+ try {
854
+ await api.sendMessage(config.webhookUrl, dialogId, welcomeText, welcomeOpts);
855
+ logger.info('Welcome message sent via webhook fallback', { dialogId });
856
+ } catch (err2: unknown) {
857
+ const errMsg2 = err2 instanceof Error ? err2.message : String(err2);
858
+ logger.error('Welcome message webhook fallback also failed', { error: errMsg2, dialogId });
859
+ }
776
860
  }
777
861
  }
778
862
  },
@@ -1,44 +1,174 @@
1
1
  import type { KeyboardButton, B24Keyboard } from './types.js';
2
2
 
3
+ // ─── Placeholder helpers ──────────────────────────────────────────────────────
4
+
5
+ const PH_ESC = '\x00ESC'; // escape sequences
6
+ const PH_FCODE = '\x00FC'; // fenced code blocks
7
+ const PH_ICODE = '\x00IC'; // inline code
8
+ const PH_HR = '\x00HR'; // horizontal rules
9
+
3
10
  /**
4
- * Convert Markdown text to Bitrix24 BB-code chat format.
11
+ * Convert Markdown (CommonMark + GFM subset) to Bitrix24 BB-code chat format.
5
12
  *
6
- * Supported conversions:
7
- * - **bold** / __bold__[B]bold[/B]
8
- * - *italic* / _italic_ [I]italic[/I]
9
- * - ~~strikethrough~~ → [S]strikethrough[/S]
10
- * - `inline code` [CODE]inline code[/CODE]
11
- * - ```code block``` → [CODE]code block[/CODE]
12
- * - [text](url)[URL=url]text[/URL]
13
- * - > blockquote → >>blockquote
13
+ * 4-phase pipeline:
14
+ * 1. Protect literals escape sequences, fenced code, inline code placeholders
15
+ * 2. Block rules — indented code, setext headings, horizontal rules, ATX headings,
16
+ * blockquotes, unordered lists
17
+ * 3. Inline rules — images, bold+italic, bold, italic, strikethrough, HTML inline
18
+ * formatting, links, autolinks
19
+ * 4. Restore — placeholdersBB-code equivalents
14
20
  */
15
21
  export function markdownToBbCode(md: string): string {
16
- let result = md;
17
-
18
- // Code blocks first (to avoid processing markdown inside them)
19
- result = result.replace(/```[\w]*\n?([\s\S]*?)```/g, '[CODE]$1[/CODE]');
20
-
21
- // Inline code
22
- result = result.replace(/`([^`]+)`/g, '[CODE]$1[/CODE]');
23
-
24
- // Bold: **text** or __text__
25
- result = result.replace(/\*\*(.+?)\*\*/g, '[B]$1[/B]');
26
- result = result.replace(/__(.+?)__/g, '[B]$1[/B]');
27
-
28
- // Italic: *text* or _text_ (but not inside words with underscores)
29
- result = result.replace(/(?<!\w)\*([^*]+?)\*(?!\w)/g, '[I]$1[/I]');
30
- result = result.replace(/(?<!\w)_([^_]+?)_(?!\w)/g, '[I]$1[/I]');
31
-
32
- // Strikethrough: ~~text~~
33
- result = result.replace(/~~(.+?)~~/g, '[S]$1[/S]');
34
-
35
- // Links: [text](url)
36
- result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '[URL=$2]$1[/URL]');
37
-
38
- // Blockquotes: > text >>text
39
- result = result.replace(/^>\s?(.*)$/gm, '>>$1');
40
-
41
- return result;
22
+ let text = md;
23
+
24
+ // ── Phase 1: Protect literals ─────────────────────────────────────────────
25
+
26
+ // 1a. Escape sequences: \* \# \_ etc. → placeholders
27
+ const escapes: string[] = [];
28
+ text = text.replace(/\\([\\`*_{}[\]()#+\-.!~|>])/g, (_match, ch: string) => {
29
+ escapes.push(ch);
30
+ return `${PH_ESC}${escapes.length - 1}\x00`;
31
+ });
32
+
33
+ // 1b. Fenced code blocks (backticks and tildes)
34
+ const fencedBlocks: string[] = [];
35
+ text = text.replace(/^(`{3,})[^\n`]*\n([\s\S]*?)^\1[ \t]*$/gm, (_match, _fence, code: string) => {
36
+ fencedBlocks.push(code.replace(/\n$/, ''));
37
+ return `${PH_FCODE}${fencedBlocks.length - 1}\x00`;
38
+ });
39
+ text = text.replace(/^(~{3,})[^\n~]*\n([\s\S]*?)^\1[ \t]*$/gm, (_match, _fence, code: string) => {
40
+ fencedBlocks.push(code.replace(/\n$/, ''));
41
+ return `${PH_FCODE}${fencedBlocks.length - 1}\x00`;
42
+ });
43
+
44
+ // 1c. Inline code (backticks) — single and double backtick
45
+ const inlineCodes: string[] = [];
46
+ text = text.replace(/``([^`]+)``/g, (_match, code: string) => {
47
+ inlineCodes.push(code);
48
+ return `${PH_ICODE}${inlineCodes.length - 1}\x00`;
49
+ });
50
+ text = text.replace(/`([^`\n]+)`/g, (_match, code: string) => {
51
+ inlineCodes.push(code);
52
+ return `${PH_ICODE}${inlineCodes.length - 1}\x00`;
53
+ });
54
+
55
+ // ── Phase 2: Block rules (line-level, order matters) ──────────────────────
56
+
57
+ // 2a. Indented code blocks (4 spaces or 1 tab after a blank line)
58
+ // Collect consecutive indented lines preceded by a blank line
59
+ text = text.replace(/(?:^|\n)\n((?:(?: |\t).+\n?)+)/g, (_match, block: string) => {
60
+ const code = block.replace(/^(?: |\t)/gm, '').replace(/\n$/, '');
61
+ fencedBlocks.push(code);
62
+ return `\n${PH_FCODE}${fencedBlocks.length - 1}\x00`;
63
+ });
64
+
65
+ // 2b. Setext headings (BEFORE horizontal rules — `---` under text is H2, not HR)
66
+ text = text.replace(/^([^\n]+)\n={2,}[ \t]*$/gm, '[SIZE=24][B]$1[/B][/SIZE]');
67
+ text = text.replace(/^([^\n]+)\n-{2,}[ \t]*$/gm, '[SIZE=20][B]$1[/B][/SIZE]');
68
+
69
+ // 2c. Horizontal rules: ---, ***, ___, - - -, * * * (on their own line)
70
+ // Use placeholder to prevent underscores from being caught by bold/italic rules
71
+ text = text.replace(/^[ \t]*([-*_])[ \t]*\1[ \t]*\1(?:[ \t]*\1)*[ \t]*$/gm, `${PH_HR}\x00`);
72
+ text = text.replace(/^[ \t]*[-*_](?:[ \t]+[-*_]){2,}[ \t]*$/gm, `${PH_HR}\x00`);
73
+
74
+ // 2d. ATX headings: # through ######
75
+ text = text.replace(/^######\s+(.+?)(?:\s+#+)?$/gm, '[SIZE=16][B]$1[/B][/SIZE]');
76
+ text = text.replace(/^#####\s+(.+?)(?:\s+#+)?$/gm, '[SIZE=16][B]$1[/B][/SIZE]');
77
+ text = text.replace(/^####\s+(.+?)(?:\s+#+)?$/gm, '[SIZE=16][B]$1[/B][/SIZE]');
78
+ text = text.replace(/^###\s+(.+?)(?:\s+#+)?$/gm, '[SIZE=18][B]$1[/B][/SIZE]');
79
+ text = text.replace(/^##\s+(.+?)(?:\s+#+)?$/gm, '[SIZE=20][B]$1[/B][/SIZE]');
80
+ text = text.replace(/^#\s+(.+?)(?:\s+#+)?$/gm, '[SIZE=24][B]$1[/B][/SIZE]');
81
+
82
+ // 2e. Blockquotes: > text → >>text (multi-level: >> text → >>>>text)
83
+ text = text.replace(/^(>{1,})\s?(.*)$/gm, (_m, arrows: string, content: string) => {
84
+ return '>'.repeat(arrows.length * 2) + content;
85
+ });
86
+
87
+ // 2f. Task lists (GFM): - [x] done / - [ ] todo → emoji checkboxes
88
+ // Must run BEFORE generic list rules to avoid double-processing
89
+ text = text.replace(/^([ \t]*)[-*+]\s+\[x\]\s+(.*)$/gmi, (_m, indent: string, content: string) => {
90
+ const depth = Math.floor(indent.replace(/\t/g, ' ').length / 2);
91
+ return '\t'.repeat(depth) + '✅ ' + content;
92
+ });
93
+ text = text.replace(/^([ \t]*)[-*+]\s+\[ \]\s+(.*)$/gm, (_m, indent: string, content: string) => {
94
+ const depth = Math.floor(indent.replace(/\t/g, ' ').length / 2);
95
+ return '\t'.repeat(depth) + '☑️ ' + content;
96
+ });
97
+
98
+ // 2g. Ordered lists: 1. item / 2. item → number with dot
99
+ // Handle nested lists with tab indentation
100
+ text = text.replace(/^([ \t]*)(\d+)\.\s+(.*)$/gm, (_m, indent: string, num: string, content: string) => {
101
+ const depth = Math.floor(indent.replace(/\t/g, ' ').length / 2);
102
+ return '\t'.repeat(depth) + num + '. ' + content;
103
+ });
104
+
105
+ // 2h. Unordered lists: - item / * item / + item → • item
106
+ // Handle nested lists with tab indentation
107
+ text = text.replace(/^([ \t]*)[-*+]\s+(.*)$/gm, (_m, indent: string, content: string) => {
108
+ const depth = Math.floor(indent.replace(/\t/g, ' ').length / 2);
109
+ return '\t'.repeat(depth) + '• ' + content;
110
+ });
111
+
112
+ // ── Phase 3: Inline rules (order matters) ─────────────────────────────────
113
+
114
+ // 3a. Images: ![alt](url) → [IMG size=medium]url [/IMG] (note: space before closing tag is required by B24)
115
+ text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '[IMG size=medium]$2 [/IMG]');
116
+
117
+ // 3b. Bold+Italic combined: ***text*** or ___text___ → [B][I]text[/I][/B]
118
+ text = text.replace(/\*\*\*(.+?)\*\*\*/g, '[B][I]$1[/I][/B]');
119
+ text = text.replace(/___(.+?)___/g, '[B][I]$1[/I][/B]');
120
+
121
+ // 3c. Bold: **text** or __text__
122
+ text = text.replace(/\*\*(.+?)\*\*/g, '[B]$1[/B]');
123
+ text = text.replace(/__(.+?)__/g, '[B]$1[/B]');
124
+
125
+ // 3d. Italic: *text* or _text_ (word boundaries to avoid false positives)
126
+ text = text.replace(/(?<!\w)\*([^*]+?)\*(?!\w)/g, '[I]$1[/I]');
127
+ text = text.replace(/(?<!\w)_([^_]+?)_(?!\w)/g, '[I]$1[/I]');
128
+
129
+ // 3e. Strikethrough: ~~text~~
130
+ text = text.replace(/~~(.+?)~~/g, '[S]$1[/S]');
131
+
132
+ // 3f. HTML inline formatting tags
133
+ text = text.replace(/<u>([\s\S]*?)<\/u>/gi, '[U]$1[/U]');
134
+ text = text.replace(/<b>([\s\S]*?)<\/b>/gi, '[B]$1[/B]');
135
+ text = text.replace(/<strong>([\s\S]*?)<\/strong>/gi, '[B]$1[/B]');
136
+ text = text.replace(/<i>([\s\S]*?)<\/i>/gi, '[I]$1[/I]');
137
+ text = text.replace(/<em>([\s\S]*?)<\/em>/gi, '[I]$1[/I]');
138
+ text = text.replace(/<s>([\s\S]*?)<\/s>/gi, '[S]$1[/S]');
139
+ text = text.replace(/<del>([\s\S]*?)<\/del>/gi, '[S]$1[/S]');
140
+ text = text.replace(/<strike>([\s\S]*?)<\/strike>/gi, '[S]$1[/S]');
141
+
142
+ // 3g. Links: [text](url) → [URL=url]text[/URL]
143
+ text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '[URL=$2]$1[/URL]');
144
+
145
+ // 3h. Autolink URL: <https://...> → [URL]https://...[/URL]
146
+ text = text.replace(/<(https?:\/\/[^>]+)>/g, '[URL]$1[/URL]');
147
+
148
+ // 3i. Autolink email: <user@example.com> → [URL]mailto:user@example.com[/URL]
149
+ text = text.replace(/<([a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,})>/g, '[URL]mailto:$1[/URL]');
150
+
151
+ // ── Phase 4: Restore placeholders ─────────────────────────────────────────
152
+
153
+ // 4a. Horizontal rules → visual separator
154
+ text = text.replace(new RegExp(`${PH_HR.replace(/\x00/g, '\\x00')}\\x00`, 'g'), '____________');
155
+
156
+ // 4b. Inline code → [CODE]...[/CODE]
157
+ text = text.replace(new RegExp(`${PH_ICODE.replace(/\x00/g, '\\x00')}(\\d+)\\x00`, 'g'), (_m, idx: string) => {
158
+ return `[CODE]${inlineCodes[Number(idx)]}[/CODE]`;
159
+ });
160
+
161
+ // 4b. Fenced/indented code blocks → [CODE]...[/CODE]
162
+ text = text.replace(new RegExp(`${PH_FCODE.replace(/\x00/g, '\\x00')}(\\d+)\\x00`, 'g'), (_m, idx: string) => {
163
+ return `[CODE]${fencedBlocks[Number(idx)]}[/CODE]`;
164
+ });
165
+
166
+ // 4c. Escape sequences → literal characters
167
+ text = text.replace(new RegExp(`${PH_ESC.replace(/\x00/g, '\\x00')}(\\d+)\\x00`, 'g'), (_m, idx: string) => {
168
+ return escapes[Number(idx)];
169
+ });
170
+
171
+ return text;
42
172
  }
43
173
 
44
174
  /**
@@ -28,7 +28,7 @@ describe('markdownToBbCode', () => {
28
28
 
29
29
  it('converts code blocks', () => {
30
30
  const md = '```js\nconst x = 1;\n```';
31
- const expected = '[CODE]const x = 1;\n[/CODE]';
31
+ const expected = '[CODE]const x = 1;[/CODE]';
32
32
  expect(markdownToBbCode(md)).toBe(expected);
33
33
  });
34
34