@ihazz/bitrix24 1.1.4 → 1.1.6
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/dist/src/channel.d.ts +1 -0
- package/dist/src/channel.d.ts.map +1 -1
- package/dist/src/channel.js +39 -17
- package/dist/src/channel.js.map +1 -1
- package/dist/src/i18n.d.ts +9 -0
- package/dist/src/i18n.d.ts.map +1 -1
- package/dist/src/i18n.js +47 -0
- package/dist/src/i18n.js.map +1 -1
- package/dist/src/message-utils.d.ts.map +1 -1
- package/dist/src/message-utils.js +117 -8
- package/dist/src/message-utils.js.map +1 -1
- package/dist/src/send-service.d.ts.map +1 -1
- package/dist/src/send-service.js +2 -6
- package/dist/src/send-service.js.map +1 -1
- package/package.json +1 -1
- package/src/channel.ts +49 -20
- package/src/i18n.ts +57 -0
- package/src/message-utils.ts +146 -8
- package/src/send-service.ts +2 -7
package/src/message-utils.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { KeyboardButton, B24Keyboard } from './types.js';
|
|
|
3
3
|
// ─── Placeholder helpers ──────────────────────────────────────────────────────
|
|
4
4
|
|
|
5
5
|
const PH_ESC = '\x00ESC'; // escape sequences
|
|
6
|
+
const PH_BBCODE = '\x00BB'; // existing BBCode blocks
|
|
6
7
|
const PH_FCODE = '\x00FC'; // fenced code blocks
|
|
7
8
|
const PH_ICODE = '\x00IC'; // inline code
|
|
8
9
|
const PH_HR = '\x00HR'; // horizontal rules
|
|
@@ -25,14 +26,21 @@ export function markdownToBbCode(md: string): string {
|
|
|
25
26
|
|
|
26
27
|
// ── Phase 1: Protect literals ─────────────────────────────────────────────
|
|
27
28
|
|
|
28
|
-
// 1a.
|
|
29
|
+
// 1a. Existing [CODE]...[/CODE] blocks should pass through untouched.
|
|
30
|
+
const bbCodeCodeBlocks: string[] = [];
|
|
31
|
+
text = text.replace(/\[code(?:=[^\]]+)?\][\s\S]*?\[\/code\]/gi, (block: string) => {
|
|
32
|
+
bbCodeCodeBlocks.push(block);
|
|
33
|
+
return `${PH_BBCODE}${bbCodeCodeBlocks.length - 1}\x00`;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// 1b. Escape sequences: \* \# \_ etc. → placeholders
|
|
29
37
|
const escapes: string[] = [];
|
|
30
38
|
text = text.replace(/\\([\\`*_{}[\]()#+\-.!~|>])/g, (_match, ch: string) => {
|
|
31
39
|
escapes.push(ch);
|
|
32
40
|
return `${PH_ESC}${escapes.length - 1}\x00`;
|
|
33
41
|
});
|
|
34
42
|
|
|
35
|
-
//
|
|
43
|
+
// 1c. Fenced code blocks (backticks and tildes)
|
|
36
44
|
const fencedBlocks: string[] = [];
|
|
37
45
|
text = text.replace(/^(`{3,})[^\n`]*\n([\s\S]*?)^\1[ \t]*$/gm, (_match, _fence, code: string) => {
|
|
38
46
|
fencedBlocks.push(code.replace(/\n$/, ''));
|
|
@@ -43,7 +51,7 @@ export function markdownToBbCode(md: string): string {
|
|
|
43
51
|
return `${PH_FCODE}${fencedBlocks.length - 1}\x00`;
|
|
44
52
|
});
|
|
45
53
|
|
|
46
|
-
//
|
|
54
|
+
// 1d. Inline code (backticks) — single and double backtick
|
|
47
55
|
const inlineCodes: string[] = [];
|
|
48
56
|
text = text.replace(/``([^`]+)``/g, (_match, code: string) => {
|
|
49
57
|
inlineCodes.push(code);
|
|
@@ -182,10 +190,16 @@ export function markdownToBbCode(md: string): string {
|
|
|
182
190
|
// 4b. Fenced/indented code blocks → [CODE]...[/CODE]
|
|
183
191
|
text = text.replace(new RegExp(`${PH_FCODE.replace(/\x00/g, '\\x00')}(\\d+)\\x00`, 'g'), (_m, idx: string) => {
|
|
184
192
|
const value = fencedBlocks[Number(idx)];
|
|
185
|
-
return value != null ? `[CODE]${value}[/CODE]` : _m;
|
|
193
|
+
return value != null ? `[CODE]${preserveCodeBlockIndentation(value)}[/CODE]` : _m;
|
|
186
194
|
});
|
|
187
195
|
|
|
188
|
-
// 4c.
|
|
196
|
+
// 4c. Existing [CODE]...[/CODE] blocks → original source, untouched
|
|
197
|
+
text = text.replace(new RegExp(`${PH_BBCODE.replace(/\x00/g, '\\x00')}(\\d+)\\x00`, 'g'), (_m, idx: string) => {
|
|
198
|
+
const value = bbCodeCodeBlocks[Number(idx)];
|
|
199
|
+
return value != null ? restoreBbCodeCodeBlock(value) : _m;
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// 4d. Escape sequences → literal characters
|
|
189
203
|
text = text.replace(new RegExp(`${PH_ESC.replace(/\x00/g, '\\x00')}(\\d+)\\x00`, 'g'), (_m, idx: string) => {
|
|
190
204
|
const value = escapes[Number(idx)];
|
|
191
205
|
return value != null ? value : _m;
|
|
@@ -196,13 +210,137 @@ export function markdownToBbCode(md: string): string {
|
|
|
196
210
|
|
|
197
211
|
function isInlineAccentToken(value: string): boolean {
|
|
198
212
|
const trimmed = value.trim();
|
|
199
|
-
if (!trimmed ||
|
|
213
|
+
if (!trimmed || /[\r\n\t]/.test(trimmed) || trimmed.length > 64) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
200
216
|
|
|
201
|
-
if (
|
|
217
|
+
if (isWrappedInlineAccentToken(trimmed)) {
|
|
202
218
|
return true;
|
|
203
219
|
}
|
|
204
220
|
|
|
205
|
-
|
|
221
|
+
if (looksLikeInlineCodeSnippet(trimmed)) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return isInlineAccentCandidate(trimmed);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function looksLikeInlineCodeSnippet(value: string): boolean {
|
|
229
|
+
if (/[`"'{};]/.test(value)) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (/(===|!==|==|!=|<=|>=|=>|\+\+|--|\|\||&&|\?\?)/.test(value)) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return /\b(?:const|let|var|function|class|return|await|async|import|export|from|yield|switch|case)\b/.test(value);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function isWrappedInlineAccentToken(value: string): boolean {
|
|
241
|
+
const match = value.match(/^([("'`])(.+)([)"'`])$/);
|
|
242
|
+
if (!match) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const open = match[1];
|
|
247
|
+
const inner = match[2].trim();
|
|
248
|
+
const close = match[3];
|
|
249
|
+
if (!inner || inner.length > 48) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if ((open === '(' && close !== ')')
|
|
254
|
+
|| (open === '[' && close !== ']')
|
|
255
|
+
|| (open === '{' && close !== '}')
|
|
256
|
+
|| ((open === '"' || open === "'") && close !== open)
|
|
257
|
+
|| (open === '`' && close !== '`')) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (looksLikeInlineCodeSnippet(inner)) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return isInlineAccentCandidate(inner);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function isLowercaseNaturalLanguageWord(value: string): boolean {
|
|
269
|
+
if (!value) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return /^[\p{Ll}\p{N}-]+$/u.test(value);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function isInlineAccentWord(value: string): boolean {
|
|
277
|
+
if (!value) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (/^\/[a-z0-9][a-z0-9._:-]*$/i.test(value)) {
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (/^[vV]?\d+(?:\.\d+)*$/.test(value)) {
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (/^(?:[\p{L}\p{N}_-]+)(?:\.[\p{L}\p{N}_*:-]+)+(?:\([^"'`;{}]*\))?$/u.test(value)) {
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (/^[\p{L}\p{N}_][\p{L}\p{N}._/:=+*()[\],-]{0,47}$/u.test(value)) {
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return /^(?=.*\p{Lu}.*\p{Lu})(?=.*\p{Ll})[\p{L}\p{N}+._/-]{2,24}$/u.test(value);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function isInlineAccentCandidate(value: string): boolean {
|
|
301
|
+
if (/^\/[a-z0-9][a-z0-9._:-]*$/i.test(value)) {
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const words = value.split(/\s+/).filter(Boolean);
|
|
306
|
+
if (words.length === 0 || words.length > 6) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (words.length > 1 && words.every((word) => isLowercaseNaturalLanguageWord(stripInlineAccentPunctuation(word)))) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return words.every((word) => isInlineAccentWord(stripInlineAccentPunctuation(word)));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function stripInlineAccentPunctuation(value: string): string {
|
|
318
|
+
return value.replace(/^[()[\]{}.,:;!?-]+|[()[\]{}.,:;!?-]+$/g, '');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function preserveCodeBlockIndentation(value: string): string {
|
|
322
|
+
if (!value.includes('\n')) {
|
|
323
|
+
return value;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return value
|
|
327
|
+
.split('\n')
|
|
328
|
+
.map((line) => {
|
|
329
|
+
const indentMatch = line.match(/^[ \t]+/);
|
|
330
|
+
if (!indentMatch) {
|
|
331
|
+
return line;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const visibleIndent = indentMatch[0].replace(/\t/g, ' ');
|
|
335
|
+
return `${'\u00A0'.repeat(visibleIndent.length)}${line.slice(indentMatch[0].length)}`;
|
|
336
|
+
})
|
|
337
|
+
.join('\n');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function restoreBbCodeCodeBlock(block: string): string {
|
|
341
|
+
return block.replace(/^(\[code(?:=[^\]]+)?\])([\s\S]*?)(\[\/code\])$/i, (_match, openTag: string, body: string, closeTag: string) => {
|
|
342
|
+
return `${openTag}${preserveCodeBlockIndentation(body)}${closeTag}`;
|
|
343
|
+
});
|
|
206
344
|
}
|
|
207
345
|
|
|
208
346
|
/**
|
package/src/send-service.ts
CHANGED
|
@@ -109,7 +109,7 @@ export class SendService {
|
|
|
109
109
|
ctx.messageId,
|
|
110
110
|
ctx.commandDialogId ?? ctx.dialogId,
|
|
111
111
|
chunks[0],
|
|
112
|
-
|
|
112
|
+
options?.keyboard
|
|
113
113
|
? { keyboard: options.keyboard }
|
|
114
114
|
: undefined,
|
|
115
115
|
);
|
|
@@ -119,18 +119,13 @@ export class SendService {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
for (let i = 1; i < chunks.length; i++) {
|
|
122
|
-
const isLast = i === chunks.length - 1;
|
|
123
|
-
const msgOptions = isLast && options?.keyboard
|
|
124
|
-
? { keyboard: options.keyboard }
|
|
125
|
-
: undefined;
|
|
126
|
-
|
|
127
122
|
try {
|
|
128
123
|
lastMessageId = await this.api.sendMessage(
|
|
129
124
|
ctx.webhookUrl,
|
|
130
125
|
ctx.bot,
|
|
131
126
|
ctx.dialogId,
|
|
132
127
|
chunks[i],
|
|
133
|
-
|
|
128
|
+
undefined,
|
|
134
129
|
);
|
|
135
130
|
} catch (error) {
|
|
136
131
|
this.logger.error('Failed to send command follow-up message', { error: serializeError(error) });
|