@ihazz/bitrix24 1.1.2 → 1.1.3

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.
Files changed (104) hide show
  1. package/README.md +5 -0
  2. package/dist/index.d.ts +30 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +55 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/src/access-control.d.ts +43 -0
  7. package/dist/src/access-control.d.ts.map +1 -0
  8. package/dist/src/access-control.js +128 -0
  9. package/dist/src/access-control.js.map +1 -0
  10. package/dist/src/api.d.ts +161 -0
  11. package/dist/src/api.d.ts.map +1 -0
  12. package/dist/src/api.js +357 -0
  13. package/dist/src/api.js.map +1 -0
  14. package/dist/src/bot-avatar.d.ts +7 -0
  15. package/dist/src/bot-avatar.d.ts.map +1 -0
  16. package/dist/src/bot-avatar.js +7 -0
  17. package/dist/src/bot-avatar.js.map +1 -0
  18. package/dist/src/channel.d.ts +216 -0
  19. package/dist/src/channel.d.ts.map +1 -0
  20. package/dist/src/channel.js +2324 -0
  21. package/dist/src/channel.js.map +1 -0
  22. package/dist/src/commands.d.ts +22 -0
  23. package/dist/src/commands.d.ts.map +1 -0
  24. package/dist/src/commands.js +160 -0
  25. package/dist/src/commands.js.map +1 -0
  26. package/dist/src/config-schema.d.ts +356 -0
  27. package/dist/src/config-schema.d.ts.map +1 -0
  28. package/dist/src/config-schema.js +43 -0
  29. package/dist/src/config-schema.js.map +1 -0
  30. package/dist/src/config.d.ts +11 -0
  31. package/dist/src/config.d.ts.map +1 -0
  32. package/dist/src/config.js +50 -0
  33. package/dist/src/config.js.map +1 -0
  34. package/dist/src/dedup.d.ts +22 -0
  35. package/dist/src/dedup.d.ts.map +1 -0
  36. package/dist/src/dedup.js +49 -0
  37. package/dist/src/dedup.js.map +1 -0
  38. package/dist/src/group-access.d.ts +52 -0
  39. package/dist/src/group-access.d.ts.map +1 -0
  40. package/dist/src/group-access.js +180 -0
  41. package/dist/src/group-access.js.map +1 -0
  42. package/dist/src/history-cache.d.ts +41 -0
  43. package/dist/src/history-cache.d.ts.map +1 -0
  44. package/dist/src/history-cache.js +82 -0
  45. package/dist/src/history-cache.js.map +1 -0
  46. package/dist/src/i18n.d.ts +22 -0
  47. package/dist/src/i18n.d.ts.map +1 -0
  48. package/dist/src/i18n.js +175 -0
  49. package/dist/src/i18n.js.map +1 -0
  50. package/dist/src/inbound-handler.d.ts +92 -0
  51. package/dist/src/inbound-handler.d.ts.map +1 -0
  52. package/dist/src/inbound-handler.js +417 -0
  53. package/dist/src/inbound-handler.js.map +1 -0
  54. package/dist/src/media-service.d.ts +52 -0
  55. package/dist/src/media-service.d.ts.map +1 -0
  56. package/dist/src/media-service.js +423 -0
  57. package/dist/src/media-service.js.map +1 -0
  58. package/dist/src/message-utils.d.ts +34 -0
  59. package/dist/src/message-utils.d.ts.map +1 -0
  60. package/dist/src/message-utils.js +392 -0
  61. package/dist/src/message-utils.js.map +1 -0
  62. package/dist/src/polling-service.d.ts +39 -0
  63. package/dist/src/polling-service.d.ts.map +1 -0
  64. package/dist/src/polling-service.js +204 -0
  65. package/dist/src/polling-service.js.map +1 -0
  66. package/dist/src/rate-limiter.d.ts +22 -0
  67. package/dist/src/rate-limiter.d.ts.map +1 -0
  68. package/dist/src/rate-limiter.js +72 -0
  69. package/dist/src/rate-limiter.js.map +1 -0
  70. package/dist/src/runtime.d.ts +106 -0
  71. package/dist/src/runtime.d.ts.map +1 -0
  72. package/dist/src/runtime.js +11 -0
  73. package/dist/src/runtime.js.map +1 -0
  74. package/dist/src/send-service.d.ts +66 -0
  75. package/dist/src/send-service.d.ts.map +1 -0
  76. package/dist/src/send-service.js +177 -0
  77. package/dist/src/send-service.js.map +1 -0
  78. package/dist/src/state-paths.d.ts +3 -0
  79. package/dist/src/state-paths.d.ts.map +1 -0
  80. package/dist/src/state-paths.js +23 -0
  81. package/dist/src/state-paths.js.map +1 -0
  82. package/dist/src/types.d.ts +381 -0
  83. package/dist/src/types.d.ts.map +1 -0
  84. package/dist/src/types.js +3 -0
  85. package/dist/src/types.js.map +1 -0
  86. package/dist/src/utils.d.ts +60 -0
  87. package/dist/src/utils.d.ts.map +1 -0
  88. package/dist/src/utils.js +131 -0
  89. package/dist/src/utils.js.map +1 -0
  90. package/index.ts +1 -1
  91. package/openclaw.plugin.json +278 -1
  92. package/package.json +11 -2
  93. package/src/api.ts +0 -3
  94. package/src/channel.ts +76 -73
  95. package/src/config-schema.ts +1 -2
  96. package/src/config.ts +6 -8
  97. package/src/group-access.ts +1 -8
  98. package/src/inbound-handler.ts +128 -15
  99. package/src/media-service.ts +160 -45
  100. package/src/polling-service.ts +2 -3
  101. package/src/send-service.ts +4 -3
  102. package/src/state-paths.ts +4 -0
  103. package/src/types.ts +1 -2
  104. package/src/utils.ts +31 -4
@@ -0,0 +1,392 @@
1
+ // ─── Placeholder helpers ──────────────────────────────────────────────────────
2
+ const PH_ESC = '\x00ESC'; // escape sequences
3
+ const PH_FCODE = '\x00FC'; // fenced code blocks
4
+ const PH_ICODE = '\x00IC'; // inline code
5
+ const PH_HR = '\x00HR'; // horizontal rules
6
+ const PH_TSEP = '\x00TS'; // table separators
7
+ const INLINE_ACCENT_COLOR = '#7A1F3D';
8
+ /**
9
+ * Convert Markdown (CommonMark + GFM subset) to Bitrix24 BB-code chat format.
10
+ *
11
+ * 4-phase pipeline:
12
+ * 1. Protect literals — escape sequences, fenced code, inline code → placeholders
13
+ * 2. Block rules — indented code, setext headings, horizontal rules, ATX headings,
14
+ * blockquotes, unordered lists
15
+ * 3. Inline rules — images, bold+italic, bold, italic, strikethrough, HTML inline
16
+ * formatting, links, autolinks
17
+ * 4. Restore — placeholders → BB-code equivalents
18
+ */
19
+ export function markdownToBbCode(md) {
20
+ let text = md;
21
+ // ── Phase 1: Protect literals ─────────────────────────────────────────────
22
+ // 1a. Escape sequences: \* \# \_ etc. → placeholders
23
+ const escapes = [];
24
+ text = text.replace(/\\([\\`*_{}[\]()#+\-.!~|>])/g, (_match, ch) => {
25
+ escapes.push(ch);
26
+ return `${PH_ESC}${escapes.length - 1}\x00`;
27
+ });
28
+ // 1b. Fenced code blocks (backticks and tildes)
29
+ const fencedBlocks = [];
30
+ text = text.replace(/^(`{3,})[^\n`]*\n([\s\S]*?)^\1[ \t]*$/gm, (_match, _fence, code) => {
31
+ fencedBlocks.push(code.replace(/\n$/, ''));
32
+ return `${PH_FCODE}${fencedBlocks.length - 1}\x00`;
33
+ });
34
+ text = text.replace(/^(~{3,})[^\n~]*\n([\s\S]*?)^\1[ \t]*$/gm, (_match, _fence, code) => {
35
+ fencedBlocks.push(code.replace(/\n$/, ''));
36
+ return `${PH_FCODE}${fencedBlocks.length - 1}\x00`;
37
+ });
38
+ // 1c. Inline code (backticks) — single and double backtick
39
+ const inlineCodes = [];
40
+ text = text.replace(/``([^`]+)``/g, (_match, code) => {
41
+ inlineCodes.push(code);
42
+ return `${PH_ICODE}${inlineCodes.length - 1}\x00`;
43
+ });
44
+ text = text.replace(/`([^`\n]+)`/g, (_match, code) => {
45
+ inlineCodes.push(code);
46
+ return `${PH_ICODE}${inlineCodes.length - 1}\x00`;
47
+ });
48
+ const tableSeparators = [];
49
+ // ── Phase 2: Block rules (line-level, order matters) ──────────────────────
50
+ // 2a. Indented code blocks (4 spaces or 1 tab after a blank line)
51
+ // Collect consecutive indented lines preceded by a blank line
52
+ text = text.replace(/(?:^|\n)\n((?:(?: |\t).+\n?)+)/g, (_match, block) => {
53
+ const code = block.replace(/^(?: |\t)/gm, '').replace(/\n$/, '');
54
+ fencedBlocks.push(code);
55
+ return `\n${PH_FCODE}${fencedBlocks.length - 1}\x00`;
56
+ });
57
+ // 2b. Setext headings (BEFORE horizontal rules — `---` under text is H2, not HR)
58
+ text = text.replace(/^([^\n]+)\n={2,}[ \t]*$/gm, '[SIZE=24][B]$1[/B][/SIZE]');
59
+ text = text.replace(/^([^\n]+)\n-{2,}[ \t]*$/gm, '[SIZE=20][B]$1[/B][/SIZE]');
60
+ // 2c. Horizontal rules: ---, ***, ___, - - -, * * * (on their own line)
61
+ // Use placeholder to prevent underscores from being caught by bold/italic rules
62
+ text = text.replace(/^[ \t]*([-*_])[ \t]*\1[ \t]*\1(?:[ \t]*\1)*[ \t]*$/gm, `${PH_HR}\x00`);
63
+ text = text.replace(/^[ \t]*[-*_](?:[ \t]+[-*_]){2,}[ \t]*$/gm, `${PH_HR}\x00`);
64
+ // 2d. ATX headings: # through ######
65
+ text = text.replace(/^######\s+(.+?)(?:\s+#+)?$/gm, '[SIZE=16][B]$1[/B][/SIZE]');
66
+ text = text.replace(/^#####\s+(.+?)(?:\s+#+)?$/gm, '[SIZE=16][B]$1[/B][/SIZE]');
67
+ text = text.replace(/^####\s+(.+?)(?:\s+#+)?$/gm, '[SIZE=16][B]$1[/B][/SIZE]');
68
+ text = text.replace(/^###\s+(.+?)(?:\s+#+)?$/gm, '[SIZE=18][B]$1[/B][/SIZE]');
69
+ text = text.replace(/^##\s+(.+?)(?:\s+#+)?$/gm, '[SIZE=20][B]$1[/B][/SIZE]');
70
+ text = text.replace(/^#\s+(.+?)(?:\s+#+)?$/gm, '[SIZE=24][B]$1[/B][/SIZE]');
71
+ // 2e. Blockquotes: > text → >>text
72
+ // Nested quotes are flattened into Bitrix-compatible quote blocks with separators.
73
+ text = convertBlockquotes(text);
74
+ // 2f. Task lists (GFM): - [x] done / - [ ] todo → emoji checkboxes
75
+ // Must run BEFORE generic list rules to avoid double-processing
76
+ text = text.replace(/^([ \t]*)[-*+]\s+\[x\]\s+(.*)$/gmi, (_m, indent, content) => {
77
+ const depth = Math.floor(indent.replace(/\t/g, ' ').length / 2);
78
+ return '\t'.repeat(depth) + '✅ ' + content;
79
+ });
80
+ text = text.replace(/^([ \t]*)[-*+]\s+\[ \]\s+(.*)$/gm, (_m, indent, content) => {
81
+ const depth = Math.floor(indent.replace(/\t/g, ' ').length / 2);
82
+ return '\t'.repeat(depth) + '☑️ ' + content;
83
+ });
84
+ // 2g. Ordered lists: 1. item / 2. item → number with dot
85
+ // Handle nested lists with tab indentation
86
+ text = text.replace(/^([ \t]*)(\d+)\.\s+(.*)$/gm, (_m, indent, num, content) => {
87
+ const depth = Math.floor(indent.replace(/\t/g, ' ').length / 2);
88
+ return '\t'.repeat(depth) + num + '. ' + content;
89
+ });
90
+ // 2h. Unordered lists: - item / * item / + item → • item
91
+ // Handle nested lists with tab indentation
92
+ text = text.replace(/^([ \t]*)[-*+]\s+(.*)$/gm, (_m, indent, content) => {
93
+ const depth = Math.floor(indent.replace(/\t/g, ' ').length / 2);
94
+ return '\t'.repeat(depth) + '• ' + content;
95
+ });
96
+ // 2i. GFM tables → simplified plain-text tables
97
+ text = convertGfmTables(text, (separator) => {
98
+ tableSeparators.push(separator);
99
+ return `${PH_TSEP}${tableSeparators.length - 1}\x00`;
100
+ });
101
+ // ── Phase 3: Inline rules (order matters) ─────────────────────────────────
102
+ // 3a. Images: ![alt](url) → [img size=medium]url [/img]
103
+ text = text.replace(/!\[[^\]]*\]\(([\s\S]*?)\)/g, (_match, rawTarget) => {
104
+ const imageUrl = extractMarkdownTarget(rawTarget);
105
+ return `[img size=medium]${normalizeBitrixImageUrl(imageUrl)} [/img]`;
106
+ });
107
+ // 3b. Bold+Italic combined: ***text*** or ___text___ → [B][I]text[/I][/B]
108
+ text = text.replace(/\*\*\*(.+?)\*\*\*/g, '[B][I]$1[/I][/B]');
109
+ text = text.replace(/___(.+?)___/g, '[B][I]$1[/I][/B]');
110
+ // 3c. Bold: **text** or __text__
111
+ text = text.replace(/\*\*(.+?)\*\*/g, '[B]$1[/B]');
112
+ text = text.replace(/__(.+?)__/g, '[B]$1[/B]');
113
+ // 3d. Italic: *text* or _text_ (word boundaries to avoid false positives)
114
+ text = text.replace(/(?<!\w)\*([^*]+?)\*(?!\w)/g, '[I]$1[/I]');
115
+ text = text.replace(/(?<!\w)_([^_]+?)_(?!\w)/g, '[I]$1[/I]');
116
+ // 3e. Strikethrough: ~~text~~
117
+ text = text.replace(/~~(.+?)~~/g, '[S]$1[/S]');
118
+ // 3f. HTML inline formatting tags
119
+ text = text.replace(/<u>([\s\S]*?)<\/u>/gi, '[U]$1[/U]');
120
+ text = text.replace(/<b>([\s\S]*?)<\/b>/gi, '[B]$1[/B]');
121
+ text = text.replace(/<strong>([\s\S]*?)<\/strong>/gi, '[B]$1[/B]');
122
+ text = text.replace(/<i>([\s\S]*?)<\/i>/gi, '[I]$1[/I]');
123
+ text = text.replace(/<em>([\s\S]*?)<\/em>/gi, '[I]$1[/I]');
124
+ text = text.replace(/<s>([\s\S]*?)<\/s>/gi, '[S]$1[/S]');
125
+ text = text.replace(/<del>([\s\S]*?)<\/del>/gi, '[S]$1[/S]');
126
+ text = text.replace(/<strike>([\s\S]*?)<\/strike>/gi, '[S]$1[/S]');
127
+ // 3g. Links: [text](url) → [URL=url]text[/URL]
128
+ text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '[URL=$2]$1[/URL]');
129
+ // 3h. Autolink URL: <https://...> → [URL]https://...[/URL]
130
+ text = text.replace(/<(https?:\/\/[^>]+)>/g, '[URL]$1[/URL]');
131
+ // 3i. Autolink email: <user@example.com> → [URL]mailto:user@example.com[/URL]
132
+ text = text.replace(/<([a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,})>/g, '[URL]mailto:$1[/URL]');
133
+ // ── Phase 4: Restore placeholders ─────────────────────────────────────────
134
+ // 4a. Horizontal rules → visual separator
135
+ text = text.replace(new RegExp(`${PH_HR.replace(/\x00/g, '\\x00')}\\x00`, 'g'), '____________');
136
+ // 4aa. Table separators → underscore rows
137
+ text = text.replace(new RegExp(`${PH_TSEP.replace(/\x00/g, '\\x00')}(\\d+)\\x00`, 'g'), (_m, idx) => {
138
+ const value = tableSeparators[Number(idx)];
139
+ return value != null ? value : _m;
140
+ });
141
+ // 4b. Inline code → [CODE]...[/CODE]
142
+ text = text.replace(new RegExp(`${PH_ICODE.replace(/\x00/g, '\\x00')}(\\d+)\\x00`, 'g'), (_m, idx) => {
143
+ const value = inlineCodes[Number(idx)];
144
+ if (value == null)
145
+ return _m;
146
+ return isInlineAccentToken(value)
147
+ ? `[B][COLOR=${INLINE_ACCENT_COLOR}]${value}[/COLOR][/B]`
148
+ : `[CODE]${value}[/CODE]`;
149
+ });
150
+ // 4b. Fenced/indented code blocks → [CODE]...[/CODE]
151
+ text = text.replace(new RegExp(`${PH_FCODE.replace(/\x00/g, '\\x00')}(\\d+)\\x00`, 'g'), (_m, idx) => {
152
+ const value = fencedBlocks[Number(idx)];
153
+ return value != null ? `[CODE]${value}[/CODE]` : _m;
154
+ });
155
+ // 4c. Escape sequences → literal characters
156
+ text = text.replace(new RegExp(`${PH_ESC.replace(/\x00/g, '\\x00')}(\\d+)\\x00`, 'g'), (_m, idx) => {
157
+ const value = escapes[Number(idx)];
158
+ return value != null ? value : _m;
159
+ });
160
+ return text;
161
+ }
162
+ function isInlineAccentToken(value) {
163
+ const trimmed = value.trim();
164
+ if (!trimmed || /\s/.test(trimmed))
165
+ return false;
166
+ if (/^\/[a-z0-9][a-z0-9._:-]*$/i.test(trimmed)) {
167
+ return true;
168
+ }
169
+ return /^[A-Z][A-Z0-9+._/-]{1,15}$/.test(trimmed);
170
+ }
171
+ /**
172
+ * Split a long message into chunks respecting B24's 20000 char limit.
173
+ * Tries to split at line boundaries.
174
+ */
175
+ export function splitMessage(text, maxLen = 20000) {
176
+ if (text.length <= maxLen)
177
+ return [text];
178
+ const chunks = [];
179
+ let remaining = text;
180
+ while (remaining.length > 0) {
181
+ if (remaining.length <= maxLen) {
182
+ chunks.push(remaining);
183
+ break;
184
+ }
185
+ // Find the last newline within the limit
186
+ let splitAt = remaining.lastIndexOf('\n', maxLen);
187
+ if (splitAt <= 0) {
188
+ // No newline found, split at the limit
189
+ splitAt = maxLen;
190
+ }
191
+ chunks.push(remaining.slice(0, splitAt));
192
+ remaining = remaining.slice(splitAt).replace(/^\n/, '');
193
+ }
194
+ return chunks;
195
+ }
196
+ function convertGfmTables(text, registerSeparator) {
197
+ const lines = text.split('\n');
198
+ const result = [];
199
+ for (let index = 0; index < lines.length; index++) {
200
+ const currentLine = lines[index];
201
+ const nextLine = lines[index + 1];
202
+ if (looksLikeMarkdownTableRow(currentLine) && isMarkdownTableSeparator(nextLine)) {
203
+ const rows = [splitMarkdownTableRow(currentLine)];
204
+ let rowIndex = index + 2;
205
+ while (rowIndex < lines.length && looksLikeMarkdownTableRow(lines[rowIndex])) {
206
+ rows.push(splitMarkdownTableRow(lines[rowIndex]));
207
+ rowIndex += 1;
208
+ }
209
+ result.push(formatPlainTextTable(rows, registerSeparator));
210
+ index = rowIndex - 1;
211
+ continue;
212
+ }
213
+ result.push(currentLine);
214
+ }
215
+ return result.join('\n');
216
+ }
217
+ function convertBlockquotes(text) {
218
+ const lines = text.split('\n');
219
+ const result = [];
220
+ for (let index = 0; index < lines.length; index++) {
221
+ const currentQuoteLine = parseMarkdownBlockquoteLine(lines[index]);
222
+ if (!currentQuoteLine) {
223
+ result.push(lines[index]);
224
+ continue;
225
+ }
226
+ const quoteRun = [currentQuoteLine];
227
+ let nextIndex = index + 1;
228
+ while (nextIndex < lines.length) {
229
+ const nextQuoteLine = parseMarkdownBlockquoteLine(lines[nextIndex]);
230
+ if (!nextQuoteLine) {
231
+ break;
232
+ }
233
+ quoteRun.push(nextQuoteLine);
234
+ nextIndex += 1;
235
+ }
236
+ const hasNestedQuote = quoteRun.some((line) => line.depth > 1);
237
+ const normalizedQuoteRun = hasNestedQuote
238
+ ? quoteRun.filter((line) => line.content.trim().length > 0)
239
+ : quoteRun;
240
+ for (const line of normalizedQuoteRun) {
241
+ result.push(`>>${line.content}`);
242
+ if (hasNestedQuote) {
243
+ result.push('>>------------------------------------------------------');
244
+ }
245
+ }
246
+ index = nextIndex - 1;
247
+ }
248
+ return result.join('\n');
249
+ }
250
+ function parseMarkdownBlockquoteLine(line) {
251
+ const indentMatch = /^[ \t]{0,3}/.exec(line);
252
+ let rest = line.slice(indentMatch?.[0].length ?? 0);
253
+ if (!rest.startsWith('>')) {
254
+ return null;
255
+ }
256
+ let depth = 0;
257
+ while (rest.startsWith('>')) {
258
+ depth += 1;
259
+ rest = rest.slice(1);
260
+ if (rest.startsWith(' ')) {
261
+ rest = rest.slice(1);
262
+ }
263
+ }
264
+ return {
265
+ depth,
266
+ content: rest,
267
+ };
268
+ }
269
+ function looksLikeMarkdownTableRow(line) {
270
+ if (typeof line !== 'string')
271
+ return false;
272
+ const trimmed = line.trim();
273
+ if (!trimmed.includes('|'))
274
+ return false;
275
+ const cells = splitMarkdownTableRow(trimmed);
276
+ return cells.length >= 2 && cells.some((cell) => cell.length > 0);
277
+ }
278
+ function isMarkdownTableSeparator(line) {
279
+ if (typeof line !== 'string')
280
+ return false;
281
+ const trimmed = line.trim();
282
+ if (!trimmed.includes('|'))
283
+ return false;
284
+ const cells = splitMarkdownTableRow(trimmed);
285
+ return cells.length >= 2 && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
286
+ }
287
+ function splitMarkdownTableRow(line) {
288
+ let normalized = line.trim();
289
+ if (normalized.startsWith('|')) {
290
+ normalized = normalized.slice(1);
291
+ }
292
+ if (normalized.endsWith('|')) {
293
+ normalized = normalized.slice(0, -1);
294
+ }
295
+ return normalized.split('|').map((cell) => cell.trim());
296
+ }
297
+ function formatPlainTextTable(rows, registerSeparator) {
298
+ const columnCount = Math.max(...rows.map((row) => row.length));
299
+ const normalizedRows = rows.map((row) => {
300
+ const cells = [...row];
301
+ while (cells.length < columnCount) {
302
+ cells.push('');
303
+ }
304
+ return cells;
305
+ });
306
+ const columnWidths = Array.from({ length: columnCount }, (_, columnIndex) => {
307
+ return Math.max(...normalizedRows.map((row) => row[columnIndex].length));
308
+ });
309
+ const formattedRows = normalizedRows.map((row) => {
310
+ return row
311
+ .map((cell, columnIndex) => cell.padEnd(columnWidths[columnIndex]))
312
+ .join(' | ')
313
+ .trimEnd();
314
+ });
315
+ const separator = registerSeparator('_'.repeat(Math.max(formattedRows[0]?.length ?? 0, 3)));
316
+ return [formattedRows[0], separator, ...formattedRows.slice(1)].join('\n');
317
+ }
318
+ function extractMarkdownTarget(rawTarget) {
319
+ const trimmed = rawTarget.trim();
320
+ if (!trimmed)
321
+ return '';
322
+ if (trimmed.startsWith('<')) {
323
+ const closingIndex = trimmed.indexOf('>');
324
+ if (closingIndex > 1) {
325
+ return trimmed.slice(1, closingIndex);
326
+ }
327
+ }
328
+ let depth = 0;
329
+ for (let index = 0; index < trimmed.length; index++) {
330
+ const char = trimmed[index];
331
+ if (char === '(') {
332
+ depth += 1;
333
+ continue;
334
+ }
335
+ if (char === ')') {
336
+ depth = Math.max(0, depth - 1);
337
+ continue;
338
+ }
339
+ if (/\s/.test(char) && depth === 0) {
340
+ return trimmed.slice(0, index);
341
+ }
342
+ }
343
+ return trimmed;
344
+ }
345
+ function normalizeBitrixImageUrl(rawUrl) {
346
+ const trimmed = rawUrl.trim();
347
+ if (!trimmed)
348
+ return trimmed;
349
+ try {
350
+ const url = new URL(trimmed);
351
+ if (hasSupportedBitrixImageExtension(`${url.pathname}${url.hash}`)) {
352
+ return url.toString();
353
+ }
354
+ url.hash = 'image.jpg';
355
+ return url.toString();
356
+ }
357
+ catch {
358
+ if (hasSupportedBitrixImageExtension(trimmed)) {
359
+ return trimmed;
360
+ }
361
+ return `${trimmed}#image.jpg`;
362
+ }
363
+ }
364
+ function hasSupportedBitrixImageExtension(value) {
365
+ return /\.(jpe?g|png|gif)(?:$|[?#])/i.test(value);
366
+ }
367
+ /**
368
+ * Build a KEYBOARD array for imbot.v2.Chat.Message.send.
369
+ *
370
+ * @param rows - Array of button rows. Each row is an array of buttons.
371
+ */
372
+ export function buildKeyboard(rows) {
373
+ const keyboard = [];
374
+ for (let i = 0; i < rows.length; i++) {
375
+ for (const btn of rows[i]) {
376
+ keyboard.push({
377
+ TEXT: btn.text,
378
+ ...(btn.command ? { COMMAND: btn.command, COMMAND_PARAMS: btn.commandParams ?? '' } : {}),
379
+ ...(btn.link ? { LINK: btn.link } : {}),
380
+ BG_COLOR: btn.bgColor ?? '#29619b',
381
+ TEXT_COLOR: btn.textColor ?? '#fff',
382
+ DISPLAY: btn.fullWidth ? 'LINE' : 'BLOCK',
383
+ BLOCK: btn.disableAfterClick ? 'Y' : 'N',
384
+ });
385
+ }
386
+ if (i < rows.length - 1) {
387
+ keyboard.push({ TYPE: 'NEWLINE' });
388
+ }
389
+ }
390
+ return keyboard;
391
+ }
392
+ //# sourceMappingURL=message-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-utils.js","sourceRoot":"","sources":["../../src/message-utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { Bitrix24Api, BotContext } from './api.js';
2
+ import type { B24V2FetchEventItem, Logger } from './types.js';
3
+ export interface PollingServiceOptions {
4
+ api: Bitrix24Api;
5
+ webhookUrl: string;
6
+ bot: BotContext;
7
+ accountId: string;
8
+ pollingIntervalMs: number;
9
+ pollingFastIntervalMs: number;
10
+ withUserEvents?: boolean;
11
+ onEvent: (event: B24V2FetchEventItem) => Promise<void>;
12
+ abortSignal: AbortSignal;
13
+ logger: Logger;
14
+ }
15
+ export declare class PollingService {
16
+ private readonly api;
17
+ private readonly webhookUrl;
18
+ private readonly bot;
19
+ private readonly accountId;
20
+ private readonly pollingIntervalMs;
21
+ private readonly pollingFastIntervalMs;
22
+ private readonly withUserEvents;
23
+ private readonly onEvent;
24
+ private readonly abortSignal;
25
+ private readonly logger;
26
+ private readonly offsetPath;
27
+ private offset;
28
+ private rateLimitStreak;
29
+ constructor(opts: PollingServiceOptions);
30
+ /**
31
+ * Start the blocking polling loop.
32
+ * Uses imbot.v2.Event.get which returns { events, lastEventId, hasMore }.
33
+ */
34
+ start(): Promise<void>;
35
+ private loadOffset;
36
+ private persistOffset;
37
+ private sleepWithAbort;
38
+ }
39
+ //# sourceMappingURL=polling-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polling-service.d.ts","sourceRoot":"","sources":["../../src/polling-service.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAG9D,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,WAAW,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,UAAU,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,OAAO,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,WAAW,EAAE,WAAW,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AA6CD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAS;IAC/C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgD;IACxE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAEpC,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,eAAe,CAAK;gBAEhB,IAAI,EAAE,qBAAqB;IAevC;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAwFd,UAAU;YAgBV,aAAa;IAsB3B,OAAO,CAAC,cAAc;CAiBvB"}
@@ -0,0 +1,204 @@
1
+ import { writeFile, readFile, mkdir, rename } from 'node:fs/promises';
2
+ import { join, dirname } from 'node:path';
3
+ import { randomBytes } from 'node:crypto';
4
+ import { resolvePollingStateDir } from './state-paths.js';
5
+ import { Bitrix24ApiError } from './utils.js';
6
+ /** Exponential backoff with jitter for recoverable errors. */
7
+ class ExponentialBackoff {
8
+ initialMs;
9
+ maxMs;
10
+ factor;
11
+ jitter;
12
+ currentMs;
13
+ constructor(initialMs = 2000, maxMs = 30000, factor = 1.8, jitter = 0.25) {
14
+ this.initialMs = initialMs;
15
+ this.maxMs = maxMs;
16
+ this.factor = factor;
17
+ this.jitter = jitter;
18
+ this.currentMs = initialMs;
19
+ }
20
+ next() {
21
+ const base = this.currentMs;
22
+ const jitterRange = base * this.jitter;
23
+ const delay = base + (Math.random() * 2 - 1) * jitterRange;
24
+ this.currentMs = Math.min(this.currentMs * this.factor, this.maxMs);
25
+ return Math.max(delay, 0);
26
+ }
27
+ reset() {
28
+ this.currentMs = this.initialMs;
29
+ }
30
+ }
31
+ /** Fatal errors that should stop the polling loop. */
32
+ const FATAL_ERROR_CODES = new Set([
33
+ 'ACCESS_DENIED',
34
+ 'BOT_NOT_FOUND',
35
+ 'BOT_OWNERSHIP_ERROR',
36
+ 'AUTH_ERROR',
37
+ 'SCOPE_ERROR',
38
+ ]);
39
+ /** Errors that indicate B24 rate limiting. */
40
+ const RATE_LIMIT_CODE = 'QUERY_LIMIT_EXCEEDED';
41
+ export class PollingService {
42
+ api;
43
+ webhookUrl;
44
+ bot;
45
+ accountId;
46
+ pollingIntervalMs;
47
+ pollingFastIntervalMs;
48
+ withUserEvents;
49
+ onEvent;
50
+ abortSignal;
51
+ logger;
52
+ offsetPath;
53
+ offset = 0;
54
+ rateLimitStreak = 0;
55
+ constructor(opts) {
56
+ this.api = opts.api;
57
+ this.webhookUrl = opts.webhookUrl;
58
+ this.bot = opts.bot;
59
+ this.accountId = opts.accountId;
60
+ this.pollingIntervalMs = opts.pollingIntervalMs;
61
+ this.pollingFastIntervalMs = opts.pollingFastIntervalMs;
62
+ this.withUserEvents = Boolean(opts.withUserEvents);
63
+ this.onEvent = opts.onEvent;
64
+ this.abortSignal = opts.abortSignal;
65
+ this.logger = opts.logger;
66
+ this.offsetPath = join(resolvePollingStateDir(), `poll-offset-${this.accountId}.json`);
67
+ }
68
+ /**
69
+ * Start the blocking polling loop.
70
+ * Uses imbot.v2.Event.get which returns { events, lastEventId, hasMore }.
71
+ */
72
+ async start() {
73
+ this.offset = await this.loadOffset();
74
+ this.logger.info(`Polling started (botId=${this.bot.botId}, offset=${this.offset})`);
75
+ const backoff = new ExponentialBackoff();
76
+ while (!this.abortSignal.aborted) {
77
+ try {
78
+ const result = await this.api.fetchEvents(this.webhookUrl, this.bot, {
79
+ offset: this.offset,
80
+ limit: 100,
81
+ withUserEvents: this.withUserEvents,
82
+ });
83
+ // Successful fetch — reset backoff and rate limit streak
84
+ backoff.reset();
85
+ this.rateLimitStreak = 0;
86
+ const { events, lastEventId, hasMore } = result;
87
+ let nextOffset = this.offset;
88
+ // Process events sequentially
89
+ for (const event of events) {
90
+ if (this.abortSignal.aborted)
91
+ break;
92
+ try {
93
+ await this.onEvent(event);
94
+ nextOffset = Math.max(nextOffset, event.eventId + 1);
95
+ }
96
+ catch (err) {
97
+ if (nextOffset > this.offset) {
98
+ this.offset = nextOffset;
99
+ await this.persistOffset(this.offset);
100
+ }
101
+ throw err;
102
+ }
103
+ }
104
+ // Advance offset only for events that were processed successfully.
105
+ if (events.length > 0 && nextOffset > this.offset) {
106
+ this.offset = nextOffset;
107
+ await this.persistOffset(this.offset);
108
+ }
109
+ else if (events.length > 0 && lastEventId < this.offset) {
110
+ this.logger.warn('Polling: lastEventId regressed', {
111
+ lastEventId,
112
+ currentOffset: this.offset,
113
+ eventCount: events.length,
114
+ });
115
+ }
116
+ // If there are more events, fetch again quickly
117
+ if (hasMore) {
118
+ await this.sleepWithAbort(this.pollingFastIntervalMs);
119
+ }
120
+ else {
121
+ await this.sleepWithAbort(this.pollingIntervalMs);
122
+ }
123
+ }
124
+ catch (err) {
125
+ if (this.abortSignal.aborted)
126
+ break;
127
+ // Handle B24 API errors
128
+ if (err instanceof Bitrix24ApiError) {
129
+ // Fatal errors — stop polling
130
+ if (FATAL_ERROR_CODES.has(err.code)) {
131
+ this.logger.error(`Fatal polling error (${err.code}): ${err.description} — stopping`);
132
+ throw err;
133
+ }
134
+ // Rate limit — short pause without exponential backoff
135
+ if (err.code === RATE_LIMIT_CODE) {
136
+ this.rateLimitStreak++;
137
+ const pauseMs = this.rateLimitStreak >= 3 ? 5000 : 2000;
138
+ this.logger.warn(`Rate limited (streak=${this.rateLimitStreak}), pausing ${pauseMs}ms`);
139
+ await this.sleepWithAbort(pauseMs);
140
+ continue;
141
+ }
142
+ }
143
+ // HTTP errors and network errors — recoverable with backoff
144
+ const delayMs = backoff.next();
145
+ const errMsg = err instanceof Error ? err.message : String(err);
146
+ this.logger.warn(`Polling error, retrying in ${Math.round(delayMs)}ms: ${errMsg}`);
147
+ await this.sleepWithAbort(delayMs);
148
+ }
149
+ }
150
+ this.logger.info('Polling stopped');
151
+ }
152
+ // ─── Offset persistence ──────────────────────────────────────────────
153
+ async loadOffset() {
154
+ try {
155
+ const raw = await readFile(this.offsetPath, 'utf-8');
156
+ const state = JSON.parse(raw);
157
+ if (typeof state.offset === 'number' && state.offset >= 0) {
158
+ this.logger.info(`Loaded saved offset: ${state.offset}`);
159
+ return state.offset;
160
+ }
161
+ this.logger.debug('Saved offset invalid, starting from 0', { raw: state });
162
+ }
163
+ catch (err) {
164
+ // File doesn't exist or is corrupted — start from 0
165
+ this.logger.debug('Failed to load saved offset, starting from 0', err);
166
+ }
167
+ return 0;
168
+ }
169
+ async persistOffset(offset) {
170
+ try {
171
+ const dir = dirname(this.offsetPath);
172
+ await mkdir(dir, { recursive: true });
173
+ const state = {
174
+ offset,
175
+ updatedAt: new Date().toISOString(),
176
+ };
177
+ const data = JSON.stringify(state, null, 2);
178
+ // Atomic write: tmp file + rename
179
+ const tmpPath = `${this.offsetPath}.${randomBytes(4).toString('hex')}.tmp`;
180
+ await writeFile(tmpPath, data, 'utf-8');
181
+ await rename(tmpPath, this.offsetPath);
182
+ }
183
+ catch (err) {
184
+ this.logger.warn('Failed to persist offset', err);
185
+ }
186
+ }
187
+ // ─── Sleep with abort ────────────────────────────────────────────────
188
+ sleepWithAbort(ms) {
189
+ if (this.abortSignal.aborted)
190
+ return Promise.resolve();
191
+ return new Promise((resolve) => {
192
+ const timer = setTimeout(() => {
193
+ this.abortSignal.removeEventListener('abort', onAbort);
194
+ resolve();
195
+ }, ms);
196
+ const onAbort = () => {
197
+ clearTimeout(timer);
198
+ resolve();
199
+ };
200
+ this.abortSignal.addEventListener('abort', onAbort, { once: true });
201
+ });
202
+ }
203
+ }
204
+ //# sourceMappingURL=polling-service.js.map