@ihazz/bitrix24 1.1.12 → 1.1.13

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 (44) hide show
  1. package/README.md +77 -4
  2. package/dist/src/api.d.ts +10 -5
  3. package/dist/src/api.d.ts.map +1 -1
  4. package/dist/src/api.js +42 -8
  5. package/dist/src/api.js.map +1 -1
  6. package/dist/src/channel.d.ts +18 -1
  7. package/dist/src/channel.d.ts.map +1 -1
  8. package/dist/src/channel.js +1253 -42
  9. package/dist/src/channel.js.map +1 -1
  10. package/dist/src/i18n.js +68 -68
  11. package/dist/src/i18n.js.map +1 -1
  12. package/dist/src/inbound-handler.js +85 -7
  13. package/dist/src/inbound-handler.js.map +1 -1
  14. package/dist/src/media-service.d.ts +2 -0
  15. package/dist/src/media-service.d.ts.map +1 -1
  16. package/dist/src/media-service.js +117 -14
  17. package/dist/src/media-service.js.map +1 -1
  18. package/dist/src/message-utils.d.ts.map +1 -1
  19. package/dist/src/message-utils.js +73 -3
  20. package/dist/src/message-utils.js.map +1 -1
  21. package/dist/src/runtime.d.ts +1 -0
  22. package/dist/src/runtime.d.ts.map +1 -1
  23. package/dist/src/runtime.js.map +1 -1
  24. package/dist/src/send-service.d.ts +1 -0
  25. package/dist/src/send-service.d.ts.map +1 -1
  26. package/dist/src/send-service.js +26 -3
  27. package/dist/src/send-service.js.map +1 -1
  28. package/dist/src/state-paths.d.ts +1 -0
  29. package/dist/src/state-paths.d.ts.map +1 -1
  30. package/dist/src/state-paths.js +9 -0
  31. package/dist/src/state-paths.js.map +1 -1
  32. package/dist/src/types.d.ts +92 -0
  33. package/dist/src/types.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/src/api.ts +62 -13
  36. package/src/channel.ts +1734 -76
  37. package/src/i18n.ts +68 -68
  38. package/src/inbound-handler.ts +110 -7
  39. package/src/media-service.ts +146 -15
  40. package/src/message-utils.ts +90 -3
  41. package/src/runtime.ts +1 -0
  42. package/src/send-service.ts +40 -2
  43. package/src/state-paths.ts +11 -0
  44. package/src/types.ts +122 -0
@@ -149,6 +149,7 @@ export function markdownToBbCode(md: string): string {
149
149
  text = text.replace(/~~(.+?)~~/g, '[S]$1[/S]');
150
150
 
151
151
  // 3f. HTML inline formatting tags
152
+ text = text.replace(/<br\s*\/?>/gi, '[BR]');
152
153
  text = text.replace(/<u>([\s\S]*?)<\/u>/gi, '[U]$1[/U]');
153
154
  text = text.replace(/<b>([\s\S]*?)<\/b>/gi, '[B]$1[/B]');
154
155
  text = text.replace(/<strong>([\s\S]*?)<\/strong>/gi, '[B]$1[/B]');
@@ -158,13 +159,18 @@ export function markdownToBbCode(md: string): string {
158
159
  text = text.replace(/<del>([\s\S]*?)<\/del>/gi, '[S]$1[/S]');
159
160
  text = text.replace(/<strike>([\s\S]*?)<\/strike>/gi, '[S]$1[/S]');
160
161
 
161
- // 3g. Links: [text](url) → [URL=url]text[/URL]
162
- text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '[URL=$2]$1[/URL]');
162
+ // 3g. Links: [text](url) → Bitrix BB-code links and action links
163
+ text = text.replace(/\[([^\]]+)\]\(([\s\S]*?)\)/g, (_match, label: string, rawTarget: string) => {
164
+ return convertMarkdownLink(label, rawTarget);
165
+ });
163
166
 
164
167
  // 3h. Autolink URL: <https://...> → [URL]https://...[/URL]
165
168
  text = text.replace(/<(https?:\/\/[^>]+)>/g, '[URL]$1[/URL]');
166
169
 
167
- // 3i. Autolink email: <user@example.com> → [URL]mailto:user@example.com[/URL]
170
+ // 3i. Autolink tel URI: <tel:+79991234567> → [CALL]+79991234567[/CALL]
171
+ text = text.replace(/<tel:([^>]+)>/gi, '[CALL]$1[/CALL]');
172
+
173
+ // 3j. Autolink email: <user@example.com> → [URL]mailto:user@example.com[/URL]
168
174
  text = text.replace(/<([a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,})>/g, '[URL]mailto:$1[/URL]');
169
175
 
170
176
  // ── Phase 4: Restore placeholders ─────────────────────────────────────────
@@ -645,6 +651,87 @@ function extractMarkdownTarget(rawTarget: string): string {
645
651
  return trimmed;
646
652
  }
647
653
 
654
+ function convertMarkdownLink(label: string, rawTarget: string): string {
655
+ const target = extractMarkdownTarget(rawTarget);
656
+ const normalizedTarget = target.trim();
657
+
658
+ if (!normalizedTarget) {
659
+ return label;
660
+ }
661
+
662
+ const sendMatch = normalizedTarget.match(/^send:(.+)$/i);
663
+ if (sendMatch) {
664
+ const payload = normalizeActionLinkPayload(sendMatch[1]);
665
+ return payload ? `[SEND=${payload}]${label}[/SEND]` : `[SEND]${label}[/SEND]`;
666
+ }
667
+
668
+ const putMatch = normalizedTarget.match(/^put:(.+)$/i);
669
+ if (putMatch) {
670
+ const payload = normalizeActionLinkPayload(putMatch[1]);
671
+ return payload ? `[PUT=${payload}]${label}[/PUT]` : `[PUT]${label}[/PUT]`;
672
+ }
673
+
674
+ const callMatch = normalizedTarget.match(/^(?:call|tel):(.+)$/i);
675
+ if (callMatch) {
676
+ const phone = callMatch[1].trim();
677
+ return phone ? `[CALL=${phone}]${label}[/CALL]` : `[CALL]${label}[/CALL]`;
678
+ }
679
+
680
+ const userMatch = normalizedTarget.match(/^user:(.+)$/i);
681
+ if (userMatch) {
682
+ return `[USER=${userMatch[1].trim()}]${label}[/USER]`;
683
+ }
684
+
685
+ const chatMatch = normalizedTarget.match(/^chat:(.+)$/i);
686
+ if (chatMatch) {
687
+ return `[CHAT=${chatMatch[1].trim()}]${label}[/CHAT]`;
688
+ }
689
+
690
+ const contextMatch = normalizedTarget.match(/^context:(.+)$/i);
691
+ if (contextMatch) {
692
+ return `[context=${contextMatch[1].trim()}]${label}[/context]`;
693
+ }
694
+
695
+ const diskMatch = normalizedTarget.match(/^disk:(.+)$/i);
696
+ if (diskMatch) {
697
+ return `[disk=${diskMatch[1].trim()}]`;
698
+ }
699
+
700
+ const timestampMatch = normalizedTarget.match(/^timestamp:(.+)$/i);
701
+ if (timestampMatch) {
702
+ const timestampMarkup = buildTimestampBbCode(timestampMatch[1].trim());
703
+ if (timestampMarkup) {
704
+ return timestampMarkup;
705
+ }
706
+ }
707
+
708
+ return `[URL=${normalizedTarget}]${label}[/URL]`;
709
+ }
710
+
711
+ function normalizeActionLinkPayload(value: string): string {
712
+ return value.trim().replace(/^\/\//, '/');
713
+ }
714
+
715
+ function buildTimestampBbCode(value: string): string {
716
+ if (!value) {
717
+ return '';
718
+ }
719
+
720
+ const [timestamp, rawQuery = ''] = value.split('?', 2);
721
+ const normalizedTimestamp = timestamp.trim();
722
+ if (!normalizedTimestamp) {
723
+ return '';
724
+ }
725
+
726
+ const params = new URLSearchParams(rawQuery);
727
+ const format = params.get('format')?.trim();
728
+ if (format) {
729
+ return `[timestamp=${normalizedTimestamp} format=${format}]`;
730
+ }
731
+
732
+ return `[timestamp=${normalizedTimestamp}]`;
733
+ }
734
+
648
735
  function normalizeBitrixImageUrl(rawUrl: string): string {
649
736
  const trimmed = rawUrl.trim();
650
737
  if (!trimmed) return trimmed;
package/src/runtime.ts CHANGED
@@ -2,6 +2,7 @@ interface ReplyPayload {
2
2
  text?: string;
3
3
  mediaUrl?: string;
4
4
  mediaUrls?: string[];
5
+ replyToId?: string;
5
6
  isError?: boolean;
6
7
  channelData?: Record<string, unknown>;
7
8
  }
@@ -41,6 +41,7 @@ export class SendService {
41
41
  options?: {
42
42
  keyboard?: B24Keyboard;
43
43
  convertMarkdown?: boolean;
44
+ replyToMessageId?: number;
44
45
  forwardMessages?: number[];
45
46
  system?: boolean;
46
47
  },
@@ -49,8 +50,41 @@ export class SendService {
49
50
  ? markdownToBbCode(text)
50
51
  : text;
51
52
 
52
- const chunks = splitMessage(convertedText);
53
- if (chunks.length === 0) return { ok: true };
53
+ const chunks = convertedText.length > 0 ? splitMessage(convertedText) : [];
54
+ if (chunks.length === 0) {
55
+ if (!options?.forwardMessages?.length) {
56
+ return { ok: true };
57
+ }
58
+
59
+ const msgOptions: {
60
+ forwardMessages?: number[];
61
+ replyToMessageId?: number;
62
+ system?: boolean;
63
+ } = {
64
+ forwardMessages: options.forwardMessages,
65
+ };
66
+
67
+ if (options?.replyToMessageId) {
68
+ msgOptions.replyToMessageId = options.replyToMessageId;
69
+ }
70
+ if (options?.system !== undefined) {
71
+ msgOptions.system = options.system;
72
+ }
73
+
74
+ try {
75
+ const messageId = await this.api.sendMessage(
76
+ ctx.webhookUrl,
77
+ ctx.bot,
78
+ ctx.dialogId,
79
+ null,
80
+ msgOptions,
81
+ );
82
+ return { ok: true, messageId };
83
+ } catch (error) {
84
+ this.logger.error('Failed to send forwarded message without text', { error: serializeError(error) });
85
+ throw error;
86
+ }
87
+ }
54
88
  let lastMessageId: number | undefined;
55
89
 
56
90
  for (let i = 0; i < chunks.length; i++) {
@@ -58,6 +92,7 @@ export class SendService {
58
92
  const isLast = i === chunks.length - 1;
59
93
  const msgOptions: {
60
94
  keyboard?: B24Keyboard;
95
+ replyToMessageId?: number;
61
96
  forwardMessages?: number[];
62
97
  system?: boolean;
63
98
  } = {};
@@ -65,6 +100,9 @@ export class SendService {
65
100
  if (isLast && options?.keyboard) {
66
101
  msgOptions.keyboard = options.keyboard;
67
102
  }
103
+ if (isFirst && options?.replyToMessageId) {
104
+ msgOptions.replyToMessageId = options.replyToMessageId;
105
+ }
68
106
  if (isFirst && options?.forwardMessages?.length) {
69
107
  msgOptions.forwardMessages = options.forwardMessages;
70
108
  }
@@ -23,6 +23,17 @@ export function resolveManagedMediaDir(env: NodeJS.ProcessEnv = process.env): st
23
23
  return join(resolveOpenClawStateDir(env), 'media', 'bitrix24');
24
24
  }
25
25
 
26
+ export function resolveTrustedWorkspaceDirs(env: NodeJS.ProcessEnv = process.env): string[] {
27
+ const roots = [
28
+ env.OPENCLAW_WORKSPACE_DIR?.trim(),
29
+ env.CLAWDBOT_WORKSPACE_DIR?.trim(),
30
+ join(resolveOpenClawStateDir(env), 'workspace'),
31
+ join(resolveHomeDir(env), 'workspace'),
32
+ ].filter((value): value is string => Boolean(value));
33
+
34
+ return [...new Set(roots.map((root) => resolvePath(root)))];
35
+ }
36
+
26
37
  export function resolvePollingStateDir(env: NodeJS.ProcessEnv = process.env): string {
27
38
  return join(resolveOpenClawStateDir(env), 'state', 'bitrix24');
28
39
  }
package/src/types.ts CHANGED
@@ -346,12 +346,134 @@ export interface KeyboardNewline {
346
346
  /** B24 keyboard: flat array with NEWLINE separators between rows */
347
347
  export type B24Keyboard = (KeyboardButton | KeyboardNewline)[];
348
348
 
349
+ export type B24AttachColorToken = 'primary' | 'secondary' | 'alert' | 'base';
350
+
351
+ export interface B24AttachMessageBlock {
352
+ MESSAGE: string;
353
+ }
354
+
355
+ export interface B24AttachLinkValue {
356
+ LINK: string;
357
+ NAME?: string;
358
+ DESC?: string;
359
+ HTML?: string;
360
+ PREVIEW?: string;
361
+ WIDTH?: number;
362
+ HEIGHT?: number;
363
+ USER_ID?: number;
364
+ CHAT_ID?: number;
365
+ NETWORK_ID?: string;
366
+ }
367
+
368
+ export interface B24AttachLinkBlock {
369
+ LINK: B24AttachLinkValue;
370
+ }
371
+
372
+ export interface B24AttachImageItem {
373
+ LINK: string;
374
+ NAME?: string;
375
+ PREVIEW?: string;
376
+ WIDTH?: number;
377
+ HEIGHT?: number;
378
+ }
379
+
380
+ export interface B24AttachImageBlock {
381
+ IMAGE: B24AttachImageItem | B24AttachImageItem[];
382
+ }
383
+
384
+ export interface B24AttachFileItem {
385
+ LINK: string;
386
+ NAME?: string;
387
+ SIZE?: number;
388
+ }
389
+
390
+ export interface B24AttachFileBlock {
391
+ FILE: B24AttachFileItem | B24AttachFileItem[];
392
+ }
393
+
394
+ export interface B24AttachDelimiterValue {
395
+ SIZE?: number;
396
+ COLOR?: string;
397
+ }
398
+
399
+ export interface B24AttachDelimiterBlock {
400
+ DELIMITER: B24AttachDelimiterValue;
401
+ }
402
+
403
+ export type B24AttachGridDisplay = 'BLOCK' | 'LINE' | 'ROW' | 'TABLE';
404
+
405
+ export interface B24AttachGridItem {
406
+ DISPLAY: B24AttachGridDisplay;
407
+ NAME?: string;
408
+ VALUE?: string;
409
+ WIDTH?: number;
410
+ HEIGHT?: number;
411
+ COLOR_TOKEN?: B24AttachColorToken;
412
+ COLOR?: string;
413
+ LINK?: string;
414
+ USER_ID?: number;
415
+ CHAT_ID?: number;
416
+ }
417
+
418
+ export interface B24AttachGridBlock {
419
+ GRID: B24AttachGridItem[];
420
+ }
421
+
422
+ export interface B24AttachUserValue {
423
+ NAME?: string;
424
+ AVATAR?: string;
425
+ LINK?: string;
426
+ USER_ID?: number;
427
+ NETWORK_ID?: string;
428
+ }
429
+
430
+ export interface B24AttachUserBlock {
431
+ USER: B24AttachUserValue;
432
+ }
433
+
434
+ export type B24AttachBlock =
435
+ | B24AttachMessageBlock
436
+ | B24AttachLinkBlock
437
+ | B24AttachImageBlock
438
+ | B24AttachFileBlock
439
+ | B24AttachDelimiterBlock
440
+ | B24AttachGridBlock
441
+ | B24AttachUserBlock;
442
+
443
+ export interface B24AttachEnvelope {
444
+ ID?: number;
445
+ COLOR_TOKEN?: B24AttachColorToken;
446
+ COLOR?: string;
447
+ BLOCKS: B24AttachBlock[];
448
+ }
449
+
450
+ export type B24Attach = B24AttachEnvelope | B24AttachBlock[];
451
+
349
452
  export interface SendMessageResult {
350
453
  ok: boolean;
351
454
  messageId?: number;
352
455
  error?: string;
353
456
  }
354
457
 
458
+ // ─── Message Action Discovery ────────────────────────────────────────────────
459
+
460
+ export interface ChannelMessageToolSchemaContribution {
461
+ properties: Record<string, unknown>;
462
+ visibility?: 'current-channel' | 'all-configured';
463
+ }
464
+
465
+ export interface ChannelMessageToolDiscovery {
466
+ actions: string[];
467
+ capabilities: string[];
468
+ schema: ChannelMessageToolSchemaContribution | ChannelMessageToolSchemaContribution[] | null;
469
+ }
470
+
471
+ export interface ExtractedToolSendTarget {
472
+ to: string;
473
+ accountId?: string;
474
+ threadId?: string;
475
+ }
476
+
355
477
  // ─── FETCH Mode Context ─────────────────────────────────────────────────────
356
478
 
357
479
  /** Context passed from PollingService to InboundHandler for FETCH events */