@ihazz/bitrix24 0.1.6 → 0.2.0
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/package.json +1 -1
- package/src/api.ts +12 -0
- package/src/channel.ts +57 -16
- package/src/media-service.ts +15 -6
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -307,6 +307,18 @@ export class Bitrix24Api {
|
|
|
307
307
|
return result.result;
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
+
async getFileInfoViaWebhook(
|
|
311
|
+
webhookUrl: string,
|
|
312
|
+
fileId: number,
|
|
313
|
+
): Promise<{ DOWNLOAD_URL: string; [key: string]: unknown }> {
|
|
314
|
+
const result = await this.callWebhook<{ DOWNLOAD_URL: string; [key: string]: unknown }>(
|
|
315
|
+
webhookUrl,
|
|
316
|
+
'disk.file.get',
|
|
317
|
+
{ id: fileId },
|
|
318
|
+
);
|
|
319
|
+
return result.result;
|
|
320
|
+
}
|
|
321
|
+
|
|
310
322
|
/**
|
|
311
323
|
* Get the disk folder ID for a chat (needed for file uploads).
|
|
312
324
|
*/
|
package/src/channel.ts
CHANGED
|
@@ -38,6 +38,18 @@ interface GatewayState {
|
|
|
38
38
|
|
|
39
39
|
let gatewayState: GatewayState | null = null;
|
|
40
40
|
|
|
41
|
+
// ─── Default command keyboard ────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
/** Default keyboard shown with command responses and welcome messages. */
|
|
44
|
+
export const DEFAULT_COMMAND_KEYBOARD: B24Keyboard = [
|
|
45
|
+
{ TEXT: 'Help', COMMAND: 'help', BG_COLOR_TOKEN: 'primary', DISPLAY: 'LINE' },
|
|
46
|
+
{ TEXT: 'Status', COMMAND: 'status', DISPLAY: 'LINE' },
|
|
47
|
+
{ TEXT: 'Commands', COMMAND: 'commands', DISPLAY: 'LINE' },
|
|
48
|
+
{ TYPE: 'NEWLINE' },
|
|
49
|
+
{ TEXT: 'New session', COMMAND: 'new', DISPLAY: 'LINE' },
|
|
50
|
+
{ TEXT: 'Models', COMMAND: 'models', DISPLAY: 'LINE' },
|
|
51
|
+
];
|
|
52
|
+
|
|
41
53
|
// ─── Keyboard / Button conversion ────────────────────────────────────────────
|
|
42
54
|
|
|
43
55
|
/** Generic button format used by OpenClaw channelData. */
|
|
@@ -498,6 +510,7 @@ export const bitrix24Plugin = {
|
|
|
498
510
|
extension: m.extension,
|
|
499
511
|
clientEndpoint: msgCtx.clientEndpoint,
|
|
500
512
|
userToken: msgCtx.userToken,
|
|
513
|
+
webhookUrl: config.webhookUrl,
|
|
501
514
|
}),
|
|
502
515
|
),
|
|
503
516
|
)).filter(Boolean) as DownloadedMedia[];
|
|
@@ -628,25 +641,40 @@ export const bitrix24Plugin = {
|
|
|
628
641
|
|
|
629
642
|
logger.info('Inbound command', { commandName, commandParams, senderId, dialogId });
|
|
630
643
|
|
|
631
|
-
|
|
632
|
-
|
|
644
|
+
let runtime;
|
|
645
|
+
let cfg;
|
|
646
|
+
try {
|
|
647
|
+
runtime = getBitrix24Runtime();
|
|
648
|
+
cfg = runtime.config.loadConfig();
|
|
649
|
+
} catch (err) {
|
|
650
|
+
logger.error('Failed to get runtime/config for command', err);
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
633
653
|
|
|
634
654
|
// Pairing-aware access control (commands don't send pairing replies)
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
655
|
+
let accessResult;
|
|
656
|
+
try {
|
|
657
|
+
accessResult = await checkAccessWithPairing({
|
|
658
|
+
senderId,
|
|
659
|
+
config,
|
|
660
|
+
runtime,
|
|
661
|
+
accountId: ctx.accountId,
|
|
662
|
+
pairingAdapter: bitrix24Plugin.pairing,
|
|
663
|
+
sendReply: async () => {},
|
|
664
|
+
logger,
|
|
665
|
+
});
|
|
666
|
+
} catch (err) {
|
|
667
|
+
logger.error('Access check failed for command', err);
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
644
670
|
|
|
645
671
|
if (accessResult !== 'allow') {
|
|
646
672
|
logger.debug(`Command blocked (${accessResult})`, { senderId });
|
|
647
673
|
return;
|
|
648
674
|
}
|
|
649
675
|
|
|
676
|
+
logger.debug('Command access allowed, resolving route', { commandText });
|
|
677
|
+
|
|
650
678
|
const route = runtime.channel.routing.resolveAgentRoute({
|
|
651
679
|
cfg,
|
|
652
680
|
channel: 'bitrix24',
|
|
@@ -654,8 +682,11 @@ export const bitrix24Plugin = {
|
|
|
654
682
|
peer: { kind: isDm ? 'direct' : 'group', id: dialogId },
|
|
655
683
|
});
|
|
656
684
|
|
|
657
|
-
|
|
658
|
-
|
|
685
|
+
logger.debug('Command route resolved', { sessionKey: route.sessionKey });
|
|
686
|
+
|
|
687
|
+
// Each command invocation gets a unique ephemeral session
|
|
688
|
+
// so the gateway doesn't treat it as "already handled".
|
|
689
|
+
const slashSessionKey = `bitrix24:slash:${senderId}:${Date.now()}`;
|
|
659
690
|
|
|
660
691
|
const inboundCtx = runtime.channel.reply.finalizeInboundContext({
|
|
661
692
|
Body: commandText,
|
|
@@ -689,15 +720,23 @@ export const bitrix24Plugin = {
|
|
|
689
720
|
dialogId,
|
|
690
721
|
};
|
|
691
722
|
|
|
723
|
+
logger.debug('Dispatching command to agent', { commandText, slashSessionKey });
|
|
724
|
+
|
|
692
725
|
try {
|
|
693
726
|
await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
694
727
|
ctx: inboundCtx,
|
|
695
728
|
cfg,
|
|
696
729
|
dispatcherOptions: {
|
|
697
730
|
deliver: async (payload) => {
|
|
731
|
+
logger.debug('Command deliver callback', {
|
|
732
|
+
hasText: !!payload.text,
|
|
733
|
+
textLen: payload.text?.length ?? 0,
|
|
734
|
+
hasMedia: !!(payload.mediaUrl || payload.mediaUrls?.length),
|
|
735
|
+
});
|
|
698
736
|
if (payload.text) {
|
|
699
|
-
|
|
700
|
-
|
|
737
|
+
// Use agent-provided keyboard if any, otherwise re-attach default command keyboard
|
|
738
|
+
const keyboard = extractKeyboardFromPayload(payload) ?? DEFAULT_COMMAND_KEYBOARD;
|
|
739
|
+
await sendService.sendText(sendCtx, payload.text, { keyboard });
|
|
701
740
|
}
|
|
702
741
|
},
|
|
703
742
|
onReplyStart: async () => {
|
|
@@ -710,6 +749,7 @@ export const bitrix24Plugin = {
|
|
|
710
749
|
},
|
|
711
750
|
},
|
|
712
751
|
});
|
|
752
|
+
logger.debug('Command dispatch completed', { commandText });
|
|
713
753
|
} catch (err) {
|
|
714
754
|
logger.error('Error dispatching command to agent', err);
|
|
715
755
|
}
|
|
@@ -728,7 +768,8 @@ export const bitrix24Plugin = {
|
|
|
728
768
|
botEntry.client_endpoint,
|
|
729
769
|
botEntry.access_token,
|
|
730
770
|
dialogId,
|
|
731
|
-
`${config.botName ?? 'OpenClaw'} ready. Send me a message
|
|
771
|
+
`${config.botName ?? 'OpenClaw'} ready. Send me a message or pick a command below.`,
|
|
772
|
+
{ KEYBOARD: DEFAULT_COMMAND_KEYBOARD },
|
|
732
773
|
);
|
|
733
774
|
} catch (err) {
|
|
734
775
|
logger.error('Failed to send welcome message', err);
|
package/src/media-service.ts
CHANGED
|
@@ -90,16 +90,25 @@ export class MediaService {
|
|
|
90
90
|
extension: string;
|
|
91
91
|
clientEndpoint: string;
|
|
92
92
|
userToken: string;
|
|
93
|
+
webhookUrl?: string;
|
|
93
94
|
}): Promise<DownloadedMedia | null> {
|
|
94
|
-
const { fileId, fileName, extension, clientEndpoint, userToken } = params;
|
|
95
|
+
const { fileId, fileName, extension, clientEndpoint, userToken, webhookUrl } = params;
|
|
95
96
|
|
|
96
97
|
try {
|
|
97
98
|
// Get download URL from B24 REST API
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
// Try webhook URL first (more reliable — event tokens often lack disk scope),
|
|
100
|
+
// fall back to event access token.
|
|
101
|
+
let fileInfo: { DOWNLOAD_URL: string; [key: string]: unknown };
|
|
102
|
+
if (webhookUrl) {
|
|
103
|
+
try {
|
|
104
|
+
fileInfo = await this.api.getFileInfoViaWebhook(webhookUrl, Number(fileId));
|
|
105
|
+
} catch {
|
|
106
|
+
this.logger.debug('Webhook disk.file.get failed, falling back to token', { fileId });
|
|
107
|
+
fileInfo = await this.api.getFileInfo(clientEndpoint, userToken, Number(fileId));
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
fileInfo = await this.api.getFileInfo(clientEndpoint, userToken, Number(fileId));
|
|
111
|
+
}
|
|
103
112
|
|
|
104
113
|
const downloadUrl = fileInfo.DOWNLOAD_URL;
|
|
105
114
|
if (!downloadUrl) {
|