@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 +36 -2
- package/package.json +1 -1
- package/src/bot-avatar.ts +6 -0
- package/src/channel.ts +97 -13
- package/src/message-utils.ts +165 -35
- package/tests/message-utils.test.ts +1 -1
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
|
@@ -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:
|
|
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: '
|
|
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
|
|
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
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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
|
-
|
|
772
|
-
|
|
838
|
+
welcomeText,
|
|
839
|
+
welcomeOpts,
|
|
773
840
|
);
|
|
774
|
-
}
|
|
775
|
-
|
|
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
|
},
|
package/src/message-utils.ts
CHANGED
|
@@ -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
|
|
11
|
+
* Convert Markdown (CommonMark + GFM subset) to Bitrix24 BB-code chat format.
|
|
5
12
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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 — placeholders → BB-code equivalents
|
|
14
20
|
*/
|
|
15
21
|
export function markdownToBbCode(md: string): string {
|
|
16
|
-
let
|
|
17
|
-
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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:  → [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
|
|
31
|
+
const expected = '[CODE]const x = 1;[/CODE]';
|
|
32
32
|
expect(markdownToBbCode(md)).toBe(expected);
|
|
33
33
|
});
|
|
34
34
|
|