@ihazz/bitrix24 0.2.5 → 1.0.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/README.md +118 -164
- package/index.ts +46 -11
- package/openclaw.plugin.json +1 -0
- package/package.json +1 -1
- package/skills/bitrix24/SKILL.md +70 -0
- package/src/access-control.ts +102 -48
- package/src/api.ts +434 -232
- package/src/channel.ts +1441 -365
- package/src/commands.ts +169 -31
- package/src/config-schema.ts +8 -3
- package/src/config.ts +11 -0
- package/src/dedup.ts +4 -0
- package/src/i18n.ts +127 -0
- package/src/inbound-handler.ts +306 -110
- package/src/media-service.ts +218 -65
- package/src/message-utils.ts +252 -10
- package/src/polling-service.ts +240 -0
- package/src/rate-limiter.ts +11 -6
- package/src/send-service.ts +140 -60
- package/src/types.ts +279 -185
- package/src/utils.ts +54 -3
- package/tests/access-control.test.ts +174 -58
- package/tests/api.test.ts +95 -0
- package/tests/channel.test.ts +230 -9
- package/tests/commands.test.ts +57 -0
- package/tests/config.test.ts +5 -1
- package/tests/i18n.test.ts +47 -0
- package/tests/inbound-handler.test.ts +554 -69
- package/tests/index.test.ts +94 -0
- package/tests/media-service.test.ts +146 -51
- package/tests/message-utils.test.ts +64 -0
- package/tests/polling-service.test.ts +77 -0
- package/tests/rate-limiter.test.ts +2 -2
- package/tests/send-service.test.ts +145 -0
package/src/commands.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenClaw bot commands to register with Bitrix24.
|
|
3
3
|
*
|
|
4
|
-
* Standard OpenClaw bot commands registered via imbot.
|
|
4
|
+
* Standard OpenClaw bot commands registered via imbot.v2.Command.register.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
export interface BotCommandDef {
|
|
@@ -9,52 +9,190 @@ export interface BotCommandDef {
|
|
|
9
9
|
en: string;
|
|
10
10
|
ru: string;
|
|
11
11
|
params?: string;
|
|
12
|
+
group: 'status' | 'session' | 'options' | 'management' | 'tools' | 'export';
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
const SHORT_HELP_COMMANDS = new Set(['help', 'status', 'commands', 'new', 'models']);
|
|
16
|
+
const COMMAND_META_COLOR = '#6C788A';
|
|
17
|
+
const COMMAND_GROUP_ORDER = ['status', 'session', 'options', 'management', 'tools', 'export'] as const;
|
|
18
|
+
const MODELS_PROVIDER_LINE_RE = /^[•*-]\s+([^()\s]+)\s+\((\d+)\)$/i;
|
|
19
|
+
|
|
20
|
+
const COMMAND_GROUP_LABELS = {
|
|
21
|
+
ru: {
|
|
22
|
+
status: 'Справка и статус',
|
|
23
|
+
session: 'Сессия',
|
|
24
|
+
options: 'Параметры',
|
|
25
|
+
management: 'Управление',
|
|
26
|
+
tools: 'Инструменты',
|
|
27
|
+
export: 'Экспорт',
|
|
28
|
+
},
|
|
29
|
+
en: {
|
|
30
|
+
status: 'Help and status',
|
|
31
|
+
session: 'Session',
|
|
32
|
+
options: 'Options',
|
|
33
|
+
management: 'Management',
|
|
34
|
+
tools: 'Tools',
|
|
35
|
+
export: 'Export',
|
|
36
|
+
},
|
|
37
|
+
} as const;
|
|
38
|
+
|
|
14
39
|
/**
|
|
15
40
|
* Standard OpenClaw commands.
|
|
16
41
|
* Excludes: focus/unfocus/agents (Discord-specific), allowlist/bash (text-only scope).
|
|
17
42
|
*/
|
|
18
43
|
export const OPENCLAW_COMMANDS: BotCommandDef[] = [
|
|
19
44
|
// ── Status ──
|
|
20
|
-
{ command: 'help', en: 'Show available commands', ru: 'Показать доступные команды' },
|
|
21
|
-
{ command: 'commands', en: 'List all slash commands', ru: 'Список всех команд' },
|
|
22
|
-
{ command: 'status', en: 'Show current status', ru: 'Показать текущий статус' },
|
|
23
|
-
{ command: 'context', en: 'Explain how context is built', ru: 'Объяснить построение контекста' },
|
|
24
|
-
{ command: 'whoami', en: 'Show your sender ID', ru: 'Показать ваш ID' },
|
|
25
|
-
{ command: 'usage', en: 'Usage and cost summary', ru: 'Использование и стоимость', params: 'off|tokens|full|cost' },
|
|
45
|
+
{ command: 'help', en: 'Show available commands', ru: 'Показать доступные команды', group: 'status' },
|
|
46
|
+
{ command: 'commands', en: 'List all slash commands', ru: 'Список всех команд', group: 'status' },
|
|
47
|
+
{ command: 'status', en: 'Show current status', ru: 'Показать текущий статус', group: 'status' },
|
|
48
|
+
{ command: 'context', en: 'Explain how context is built', ru: 'Объяснить построение контекста', group: 'status' },
|
|
49
|
+
{ command: 'whoami', en: 'Show your sender ID', ru: 'Показать ваш ID', group: 'status' },
|
|
50
|
+
{ command: 'usage', en: 'Usage and cost summary', ru: 'Использование и стоимость', params: 'off | tokens | full | cost', group: 'status' },
|
|
26
51
|
|
|
27
52
|
// ── Session ──
|
|
28
|
-
{ command: 'new', en: 'Start a new session', ru: 'Начать новую сессию' },
|
|
29
|
-
{ command: 'reset', en: 'Reset the current session', ru: 'Сбросить текущую сессию' },
|
|
30
|
-
{ command: 'stop', en: 'Stop the current run', ru: 'Остановить текущий запуск' },
|
|
31
|
-
{ command: 'compact', en: 'Compact the session context', ru: 'Сжать контекст сессии', params: 'instructions' },
|
|
32
|
-
{ command: 'session', en: 'Manage session settings', ru: 'Настройки сессии', params: 'ttl
|
|
53
|
+
{ command: 'new', en: 'Start a new session', ru: 'Начать новую сессию', group: 'session' },
|
|
54
|
+
{ command: 'reset', en: 'Reset the current session', ru: 'Сбросить текущую сессию', group: 'session' },
|
|
55
|
+
{ command: 'stop', en: 'Stop the current run', ru: 'Остановить текущий запуск', group: 'session' },
|
|
56
|
+
{ command: 'compact', en: 'Compact the session context', ru: 'Сжать контекст сессии', params: 'instructions', group: 'session' },
|
|
57
|
+
{ command: 'session', en: 'Manage session settings', ru: 'Настройки сессии', params: 'ttl | ...', group: 'session' },
|
|
33
58
|
|
|
34
59
|
// ── Options ──
|
|
35
|
-
{ command: 'model', en: 'Show or set the model', ru: 'Показать/сменить модель', params: 'model name' },
|
|
36
|
-
{ command: 'models', en: 'List available models', ru: 'Список моделей', params: 'provider' },
|
|
37
|
-
{ command: 'think', en: 'Set thinking level', ru: 'Уровень размышлений', params: 'off|low|medium|high' },
|
|
38
|
-
{ command: 'verbose', en: 'Toggle verbose mode', ru: 'Подробный режим', params: 'on|off' },
|
|
39
|
-
{ command: 'reasoning', en: 'Toggle reasoning visibility', ru: 'Видимость рассуждений', params: 'on|off|stream' },
|
|
40
|
-
{ command: 'elevated', en: 'Toggle elevated mode', ru: 'Режим с расширенными правами', params: 'on|off|ask|full' },
|
|
41
|
-
{ command: 'exec', en: 'Set exec defaults', ru: 'Настройки выполнения', params: 'host|security|ask|node' },
|
|
42
|
-
{ command: 'queue', en: 'Adjust queue settings', ru: 'Настройки очереди', params: 'mode|debounce|cap|drop' },
|
|
60
|
+
{ command: 'model', en: 'Show or set the model', ru: 'Показать/сменить модель', params: 'model name', group: 'options' },
|
|
61
|
+
{ command: 'models', en: 'List available models', ru: 'Список моделей', params: 'provider', group: 'options' },
|
|
62
|
+
{ command: 'think', en: 'Set thinking level', ru: 'Уровень размышлений', params: 'off | low | medium | high', group: 'options' },
|
|
63
|
+
{ command: 'verbose', en: 'Toggle verbose mode', ru: 'Подробный режим', params: 'on | off', group: 'options' },
|
|
64
|
+
{ command: 'reasoning', en: 'Toggle reasoning visibility', ru: 'Видимость рассуждений', params: 'on | off | stream', group: 'options' },
|
|
65
|
+
{ command: 'elevated', en: 'Toggle elevated mode', ru: 'Режим с расширенными правами', params: 'on | off | ask | full', group: 'options' },
|
|
66
|
+
{ command: 'exec', en: 'Set exec defaults', ru: 'Настройки выполнения', params: 'host | security | ask | node', group: 'options' },
|
|
67
|
+
{ command: 'queue', en: 'Adjust queue settings', ru: 'Настройки очереди', params: 'mode | debounce | cap | drop', group: 'options' },
|
|
43
68
|
|
|
44
69
|
// ── Management ──
|
|
45
|
-
{ command: 'config', en: 'Show or set config values', ru: 'Показать/задать конфигурацию', params: 'show|get|set|unset' },
|
|
46
|
-
{ command: 'debug', en: 'Set runtime debug overrides', ru: 'Отладочные настройки', params: 'show|reset|set|unset' },
|
|
47
|
-
{ command: 'approve', en: 'Approve or deny exec requests', ru: 'Одобрить/отклонить запросы' },
|
|
48
|
-
{ command: 'activation', en: 'Set group activation mode', ru: 'Режим активации в группах', params: 'mention|always' },
|
|
49
|
-
{ command: 'send', en: 'Set send policy', ru: 'Политика отправки', params: 'on|off|inherit' },
|
|
50
|
-
{ command: 'subagents', en: 'Manage subagent runs', ru: 'Управление субагентами' },
|
|
51
|
-
{ command: 'kill', en: 'Kill a running subagent', ru: 'Остановить субагента', params: 'id|all' },
|
|
52
|
-
{ command: 'steer', en: 'Send guidance to a subagent', ru: 'Направить субагента', params: 'message' },
|
|
70
|
+
{ command: 'config', en: 'Show or set config values', ru: 'Показать/задать конфигурацию', params: 'show | get | set | unset', group: 'management' },
|
|
71
|
+
{ command: 'debug', en: 'Set runtime debug overrides', ru: 'Отладочные настройки', params: 'show | reset | set | unset', group: 'management' },
|
|
72
|
+
{ command: 'approve', en: 'Approve or deny exec requests', ru: 'Одобрить/отклонить запросы', group: 'management' },
|
|
73
|
+
{ command: 'activation', en: 'Set group activation mode', ru: 'Режим активации в группах', params: 'mention | always', group: 'management' },
|
|
74
|
+
{ command: 'send', en: 'Set send policy', ru: 'Политика отправки', params: 'on | off | inherit', group: 'management' },
|
|
75
|
+
{ command: 'subagents', en: 'Manage subagent runs', ru: 'Управление субагентами', group: 'management' },
|
|
76
|
+
{ command: 'kill', en: 'Kill a running subagent', ru: 'Остановить субагента', params: 'id | all', group: 'management' },
|
|
77
|
+
{ command: 'steer', en: 'Send guidance to a subagent', ru: 'Направить субагента', params: 'message', group: 'management' },
|
|
53
78
|
|
|
54
79
|
// ── Tools ──
|
|
55
|
-
{ command: 'skill', en: 'Run a skill by name', ru: 'Запустить навык', params: 'name' },
|
|
56
|
-
{ command: 'restart', en: 'Restart OpenClaw', ru: 'Перезапустить OpenClaw' },
|
|
80
|
+
{ command: 'skill', en: 'Run a skill by name', ru: 'Запустить навык', params: 'name', group: 'tools' },
|
|
81
|
+
{ command: 'restart', en: 'Restart OpenClaw', ru: 'Перезапустить OpenClaw', group: 'tools' },
|
|
57
82
|
|
|
58
83
|
// ── Export ──
|
|
59
|
-
{ command: 'export-session', en: 'Export session to HTML', ru: 'Экспорт сессии в HTML' },
|
|
84
|
+
{ command: 'export-session', en: 'Export session to HTML', ru: 'Экспорт сессии в HTML', group: 'export' },
|
|
60
85
|
];
|
|
86
|
+
|
|
87
|
+
export function buildCommandsHelpText(
|
|
88
|
+
lang: string | undefined,
|
|
89
|
+
options?: { concise?: boolean },
|
|
90
|
+
): string {
|
|
91
|
+
const isRu = lang?.toLowerCase().startsWith('ru') ?? false;
|
|
92
|
+
const concise = options?.concise === true;
|
|
93
|
+
const defs = concise
|
|
94
|
+
? OPENCLAW_COMMANDS.filter((def) => SHORT_HELP_COMMANDS.has(def.command))
|
|
95
|
+
: OPENCLAW_COMMANDS;
|
|
96
|
+
const labels = isRu ? COMMAND_GROUP_LABELS.ru : COMMAND_GROUP_LABELS.en;
|
|
97
|
+
const header = concise
|
|
98
|
+
? (isRu ? '[B]Основные команды[/B]' : '[B]Key commands[/B]')
|
|
99
|
+
: (isRu ? '[B]Доступные команды[/B]' : '[B]Available commands[/B]');
|
|
100
|
+
const footer = concise
|
|
101
|
+
? (isRu
|
|
102
|
+
? `[COLOR=${COMMAND_META_COLOR}]Полный список:[/COLOR] ${formatCommandTrigger({
|
|
103
|
+
command: 'commands',
|
|
104
|
+
})}`
|
|
105
|
+
: `[COLOR=${COMMAND_META_COLOR}]Full list:[/COLOR] ${formatCommandTrigger({
|
|
106
|
+
command: 'commands',
|
|
107
|
+
})}`)
|
|
108
|
+
: '';
|
|
109
|
+
|
|
110
|
+
const sections = COMMAND_GROUP_ORDER
|
|
111
|
+
.map((group) => {
|
|
112
|
+
const groupDefs = defs.filter((def) => def.group === group);
|
|
113
|
+
if (groupDefs.length === 0) {
|
|
114
|
+
return '';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const lines = groupDefs.map((def) => formatCommandEntry(def, isRu));
|
|
118
|
+
if (concise) {
|
|
119
|
+
return lines.join('\n');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return [`[B]${labels[group]}[/B]`, ...lines].join('\n');
|
|
123
|
+
})
|
|
124
|
+
.filter(Boolean);
|
|
125
|
+
|
|
126
|
+
return [header, ...sections, footer]
|
|
127
|
+
.filter(Boolean)
|
|
128
|
+
.join('\n\n');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function formatModelsCommandReply(
|
|
132
|
+
text: string,
|
|
133
|
+
lang?: string,
|
|
134
|
+
): string | null {
|
|
135
|
+
const lines = text
|
|
136
|
+
.split(/\r?\n/)
|
|
137
|
+
.map((line) => line.trim())
|
|
138
|
+
.filter(Boolean);
|
|
139
|
+
const providers: Array<{ name: string; count: string }> = [];
|
|
140
|
+
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
const match = line.match(MODELS_PROVIDER_LINE_RE);
|
|
143
|
+
if (!match) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
providers.push({ name: match[1], count: match[2] });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (providers.length === 0) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const isRu = lang?.toLowerCase().startsWith('ru') ?? false;
|
|
154
|
+
const header = isRu ? '[B]Провайдеры[/B]' : '[B]Providers[/B]';
|
|
155
|
+
const showModelsLabel = isRu
|
|
156
|
+
? `[COLOR=${COMMAND_META_COLOR}]Посмотреть модели провайдера:[/COLOR]`
|
|
157
|
+
: `[COLOR=${COMMAND_META_COLOR}]Show provider models:[/COLOR]`;
|
|
158
|
+
const switchModelLabel = isRu
|
|
159
|
+
? `[COLOR=${COMMAND_META_COLOR}]Сменить модель:[/COLOR]`
|
|
160
|
+
: `[COLOR=${COMMAND_META_COLOR}]Switch model:[/COLOR]`;
|
|
161
|
+
|
|
162
|
+
const providerLines = providers.map((provider) => {
|
|
163
|
+
return `• [send=/models ${provider.name}]${provider.name}[/send] [COLOR=${COMMAND_META_COLOR}](${provider.count})[/COLOR]`;
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return [
|
|
167
|
+
header,
|
|
168
|
+
...providerLines,
|
|
169
|
+
'',
|
|
170
|
+
`${showModelsLabel} [put=/models ]/models <provider>[/put]`,
|
|
171
|
+
`${switchModelLabel} [put=/model ]/model <provider/model>[/put]`,
|
|
172
|
+
].join('\n');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function formatCommandEntry(def: BotCommandDef, isRu: boolean): string {
|
|
176
|
+
const description = isRu ? def.ru : def.en;
|
|
177
|
+
const paramsLabel = isRu ? 'Параметры' : 'Params';
|
|
178
|
+
const command = formatCommandTrigger(def);
|
|
179
|
+
|
|
180
|
+
if (!def.params) {
|
|
181
|
+
return `${command} — ${description}`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return [
|
|
185
|
+
`${command} — ${description}`,
|
|
186
|
+
`[COLOR=${COMMAND_META_COLOR}]${paramsLabel}: ${def.params}[/COLOR]`,
|
|
187
|
+
].join('\n');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function formatCommandTrigger(def: Pick<BotCommandDef, 'command' | 'params'>): string {
|
|
191
|
+
const visibleCommand = `/${def.command}`;
|
|
192
|
+
|
|
193
|
+
if (!def.params) {
|
|
194
|
+
return `[send=/${def.command}]${visibleCommand}[/send]`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return `[put=/${def.command} ]${visibleCommand}[/put]`;
|
|
198
|
+
}
|
package/src/config-schema.ts
CHANGED
|
@@ -3,12 +3,17 @@ import { z } from 'zod';
|
|
|
3
3
|
const AccountSchema = z.object({
|
|
4
4
|
enabled: z.boolean().optional().default(true),
|
|
5
5
|
webhookUrl: z.string().url().optional(),
|
|
6
|
+
botToken: z.string().optional(),
|
|
6
7
|
botName: z.string().optional().default('OpenClaw'),
|
|
7
|
-
botCode: z.string().optional()
|
|
8
|
+
botCode: z.string().optional(),
|
|
8
9
|
botAvatar: z.string().optional(),
|
|
9
|
-
callbackUrl: z.string().url().optional(),
|
|
10
|
-
dmPolicy: z.enum(['open', 'allowlist', 'pairing']).optional().default('pairing'),
|
|
11
10
|
allowFrom: z.array(z.string()).optional(),
|
|
11
|
+
callbackUrl: z.string().url().optional(),
|
|
12
|
+
eventMode: z.enum(['fetch', 'webhook']).optional(),
|
|
13
|
+
agentMode: z.boolean().optional().default(false),
|
|
14
|
+
pollingIntervalMs: z.number().int().min(500).optional().default(3000),
|
|
15
|
+
pollingFastIntervalMs: z.number().int().min(50).optional().default(100),
|
|
16
|
+
dmPolicy: z.enum(['pairing', 'webhookUser']).optional().default('webhookUser'),
|
|
12
17
|
showTyping: z.boolean().optional().default(true),
|
|
13
18
|
streamUpdates: z.boolean().optional().default(false),
|
|
14
19
|
updateIntervalMs: z.number().int().min(500).optional().default(10000),
|
package/src/config.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Bitrix24PluginConfig, Bitrix24AccountConfig } from './types.js';
|
|
2
|
+
import { Bitrix24ConfigSchema } from './config-schema.js';
|
|
2
3
|
|
|
3
4
|
const DEFAULT_ACCOUNT_ID = 'default';
|
|
4
5
|
|
|
@@ -51,6 +52,16 @@ export function resolveAccount(
|
|
|
51
52
|
} {
|
|
52
53
|
const id = accountId || DEFAULT_ACCOUNT_ID;
|
|
53
54
|
const config = getConfig(cfg, id);
|
|
55
|
+
|
|
56
|
+
// Validate config against Zod schema (validation only, no transform)
|
|
57
|
+
if (config.webhookUrl) {
|
|
58
|
+
const result = Bitrix24ConfigSchema.safeParse(config);
|
|
59
|
+
if (!result.success) {
|
|
60
|
+
const issues = result.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ');
|
|
61
|
+
throw new Error(`[bitrix24] Invalid config for account "${id}": ${issues}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
54
65
|
return {
|
|
55
66
|
accountId: id,
|
|
56
67
|
config,
|
package/src/dedup.ts
CHANGED
|
@@ -10,6 +10,10 @@ export class Dedup {
|
|
|
10
10
|
constructor(opts: { ttlMs?: number } = {}) {
|
|
11
11
|
this.ttlMs = opts.ttlMs ?? 5 * 60 * 1000; // 5 minutes default
|
|
12
12
|
this.cleanupTimer = setInterval(() => this.cleanup(), this.ttlMs);
|
|
13
|
+
// Don't prevent process exit if destroy() isn't called
|
|
14
|
+
if (this.cleanupTimer && typeof this.cleanupTimer === 'object' && 'unref' in this.cleanupTimer) {
|
|
15
|
+
this.cleanupTimer.unref();
|
|
16
|
+
}
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
/**
|
package/src/i18n.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internationalization module for user-facing strings.
|
|
3
|
+
* Supported languages: en, ru, de, es, fr, pt.
|
|
4
|
+
* Language code is resolved from B24 LANGUAGE field in event PARAMS.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ─── Locale resolver ─────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
export function resolveLocale(lang: string | undefined): string {
|
|
10
|
+
return (lang ?? 'en').toLowerCase().slice(0, 2);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function resolve<T>(dict: Record<string, T>, lang: string | undefined): T {
|
|
14
|
+
const code = resolveLocale(lang);
|
|
15
|
+
return dict[code] ?? dict.en;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ─── Media download failed ───────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
const I18N_MEDIA_DOWNLOAD_FAILED: Record<string, (files: string) => string> = {
|
|
21
|
+
en: (f) => `\u26a0\ufe0f 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.`,
|
|
22
|
+
ru: (f) => `\u26a0\ufe0f \u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0444\u0430\u0439\u043b(\u044b): ${f}.\n\n\u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435, \u0447\u0442\u043e \u0431\u043e\u0442 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0432 \u043d\u0443\u0436\u043d\u043e\u043c \u0434\u0438\u0430\u043b\u043e\u0433\u0435 \u0438 \u0443 \u043d\u0435\u0433\u043e \u0435\u0441\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u044d\u0442\u043e\u043c\u0443 \u0444\u0430\u0439\u043b\u0443.`,
|
|
23
|
+
de: (f) => `\u26a0\ufe0f Datei(en) konnten nicht heruntergeladen werden: ${f}.\n\nDateiverarbeitung ist derzeit nur f\u00fcr den Hauptbenutzer (Webhook-Besitzer) verf\u00fcgbar. Diese Einschr\u00e4nkung wird in einer zuk\u00fcnftigen Version behoben.`,
|
|
24
|
+
es: (f) => `\u26a0\ufe0f No se pudo descargar el/los archivo(s): ${f}.\n\nEl procesamiento de archivos actualmente solo est\u00e1 disponible para el usuario principal (propietario del webhook). Esta limitaci\u00f3n se eliminar\u00e1 en una versi\u00f3n futura.`,
|
|
25
|
+
fr: (f) => `\u26a0\ufe0f Impossible de t\u00e9l\u00e9charger le(s) fichier(s) : ${f}.\n\nLe traitement des fichiers est actuellement r\u00e9serv\u00e9 \u00e0 l'utilisateur principal (propri\u00e9taire du webhook). Cette limitation sera lev\u00e9e dans une prochaine version.`,
|
|
26
|
+
pt: (f) => `\u26a0\ufe0f N\u00e3o foi poss\u00edvel baixar o(s) arquivo(s): ${f}.\n\nO processamento de arquivos est\u00e1 dispon\u00edvel apenas para o usu\u00e1rio principal (dono do webhook). Essa limita\u00e7\u00e3o ser\u00e1 removida em uma vers\u00e3o futura.`,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function mediaDownloadFailed(lang: string | undefined, fileNames: string): string {
|
|
30
|
+
return resolve(I18N_MEDIA_DOWNLOAD_FAILED, lang)(fileNames);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ─── Group chat unsupported ──────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
const I18N_GROUP_CHAT_UNSUPPORTED: Record<string, string> = {
|
|
36
|
+
en: 'Sorry, I can only work in private messages. Please message me directly.',
|
|
37
|
+
ru: '\u0418\u0437\u0432\u0438\u043d\u0438\u0442\u0435, \u044f \u0440\u0430\u0431\u043e\u0442\u0430\u044e \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u043b\u0438\u0447\u043d\u044b\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u0445. \u041d\u0430\u043f\u0438\u0448\u0438\u0442\u0435 \u043c\u043d\u0435 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e.',
|
|
38
|
+
de: 'Entschuldigung, ich arbeite nur in privaten Nachrichten. Bitte schreiben Sie mir direkt.',
|
|
39
|
+
es: 'Lo siento, solo funciono en mensajes privados. Por favor, escr\u00edbeme directamente.',
|
|
40
|
+
fr: 'D\u00e9sol\u00e9, je ne fonctionne qu\'en messages priv\u00e9s. Veuillez m\'\u00e9crire directement.',
|
|
41
|
+
pt: 'Desculpe, s\u00f3 funciono em mensagens privadas. Por favor, escreva-me diretamente.',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function groupChatUnsupported(lang: string | undefined): string {
|
|
45
|
+
return resolve(I18N_GROUP_CHAT_UNSUPPORTED, lang);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── Personal bot access denied ──────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
const I18N_PERSONAL_BOT_OWNER_ONLY: Record<string, string> = {
|
|
51
|
+
en: 'This bot is personal and accepts messages only from the bot owner.',
|
|
52
|
+
ru: 'Этот бот персональный и принимает сообщения только от владельца бота.',
|
|
53
|
+
de: 'Dieser Bot ist persoenlich und akzeptiert Nachrichten nur vom Bot-Besitzer.',
|
|
54
|
+
es: 'Este bot es personal y solo acepta mensajes del propietario del bot.',
|
|
55
|
+
fr: 'Ce bot est personnel et accepte uniquement les messages du proprietaire du bot.',
|
|
56
|
+
pt: 'Este bot e pessoal e aceita mensagens apenas do proprietario do bot.',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export function personalBotOwnerOnly(lang: string | undefined): string {
|
|
60
|
+
return resolve(I18N_PERSONAL_BOT_OWNER_ONLY, lang);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ─── Reply-to-message unsupported ───────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
const I18N_REPLY_MESSAGE_UNSUPPORTED: Record<string, string> = {
|
|
66
|
+
en: 'Replies to specific messages are not supported in the Bitrix24 integration yet. Please send your request as a new message for now. Support will appear in a future version.',
|
|
67
|
+
ru: 'Ответы на конкретные сообщения в интеграции Bitrix24 пока не поддерживаются. Пока что отправьте запрос отдельным новым сообщением. Поддержка появится в будущих версиях.',
|
|
68
|
+
de: 'Antworten auf bestimmte Nachrichten werden in der Bitrix24-Integration derzeit noch nicht unterstuetzt. Bitte senden Sie Ihre Anfrage vorerst als neue Nachricht. Die Unterstuetzung kommt in einer zukuenftigen Version.',
|
|
69
|
+
es: 'Las respuestas a mensajes concretos todavia no son compatibles en la integracion con Bitrix24. Por ahora, envia tu solicitud como un mensaje nuevo. Esta compatibilidad llegara en una version futura.',
|
|
70
|
+
fr: 'Les reponses a des messages precis ne sont pas encore prises en charge dans l integration Bitrix24. Pour le moment, envoyez votre demande comme un nouveau message. Cette prise en charge arrivera dans une prochaine version.',
|
|
71
|
+
pt: 'Respostas a mensagens especificas ainda nao sao suportadas na integracao com o Bitrix24. Por enquanto, envie sua solicitacao como uma nova mensagem. Esse suporte chegara em uma versao futura.',
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export function replyMessageUnsupported(lang: string | undefined): string {
|
|
75
|
+
return resolve(I18N_REPLY_MESSAGE_UNSUPPORTED, lang);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const I18N_FORWARDED_MESSAGE_UNSUPPORTED: Record<string, string> = {
|
|
79
|
+
en: 'Forwarded messages are not supported in the Bitrix24 integration yet. Please send the text directly or upload the file/image as a regular message. Support will appear in a future version.',
|
|
80
|
+
ru: 'Пересланные сообщения в интеграции Bitrix24 пока не поддерживаются. Пока что отправьте текст напрямую или загрузите файл либо изображение обычным сообщением. Поддержка появится в будущих версиях.',
|
|
81
|
+
de: 'Weitergeleitete Nachrichten werden in der Bitrix24-Integration derzeit noch nicht unterstuetzt. Bitte senden Sie den Text direkt oder laden Sie die Datei bzw. das Bild als normale Nachricht hoch. Die Unterstuetzung kommt in einer zukuenftigen Version.',
|
|
82
|
+
es: 'Los mensajes reenviados todavia no son compatibles en la integracion con Bitrix24. Por ahora, envia el texto directamente o sube el archivo o la imagen como un mensaje normal. Esta compatibilidad llegara en una version futura.',
|
|
83
|
+
fr: 'Les messages transferes ne sont pas encore pris en charge dans l integration Bitrix24. Pour le moment, envoyez le texte directement ou telechargez le fichier ou l image comme un message normal. Cette prise en charge arrivera dans une prochaine version.',
|
|
84
|
+
pt: 'Mensagens encaminhadas ainda nao sao suportadas na integracao com o Bitrix24. Por enquanto, envie o texto diretamente ou carregue o arquivo ou a imagem como uma mensagem normal. Esse suporte chegara em uma versao futura.',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export function forwardedMessageUnsupported(lang: string | undefined): string {
|
|
88
|
+
return resolve(I18N_FORWARDED_MESSAGE_UNSUPPORTED, lang);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ─── Onboarding messages ─────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
const I18N_WELCOME: Record<string, (botName: string) => string> = {
|
|
94
|
+
en: (n) => `${n} ready. Send me a message or pick a command below.`,
|
|
95
|
+
ru: (n) => `${n} \u0433\u043e\u0442\u043e\u0432. \u041d\u0430\u043f\u0438\u0448\u0438\u0442\u0435 \u043c\u043d\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0438\u043b\u0438 \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u043d\u0438\u0436\u0435.`,
|
|
96
|
+
de: (n) => `${n} bereit. Senden Sie mir eine Nachricht oder w\u00e4hlen Sie einen Befehl unten.`,
|
|
97
|
+
es: (n) => `${n} listo. Env\u00edame un mensaje o elige un comando abajo.`,
|
|
98
|
+
fr: (n) => `${n} pr\u00eat. Envoyez-moi un message ou choisissez une commande ci-dessous.`,
|
|
99
|
+
pt: (n) => `${n} pronto. Envie-me uma mensagem ou escolha um comando abaixo.`,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export function welcomeMessage(lang: string | undefined, botName: string): string {
|
|
103
|
+
return resolve(I18N_WELCOME, lang)(botName);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const I18N_PAIRING_WELCOME: Record<string, (botName: string) => string> = {
|
|
107
|
+
en: (n) => `${n} ready. To start, send me any message and I will return a pairing code. Then approve it in OpenClaw with: openclaw pairing approve bitrix24 <CODE>`,
|
|
108
|
+
ru: (n) => `${n} \u0433\u043e\u0442\u043e\u0432. \u0427\u0442\u043e\u0431\u044b \u043d\u0430\u0447\u0430\u0442\u044c, \u043e\u0442\u043f\u0440\u0430\u0432\u044c\u0442\u0435 \u043c\u043d\u0435 \u043b\u044e\u0431\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u0438 \u044f \u0432\u0435\u0440\u043d\u0443 \u043a\u043e\u0434 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438. \u0417\u0430\u0442\u0435\u043c \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435 \u0435\u0433\u043e \u0432 OpenClaw \u043a\u043e\u043c\u0430\u043d\u0434\u043e\u0439: openclaw pairing approve bitrix24 <CODE>`,
|
|
109
|
+
de: (n) => `${n} ist bereit. Senden Sie mir eine beliebige Nachricht, dann gebe ich einen Pairing-Code zurueck. Bestaetigen Sie ihn danach in OpenClaw mit: openclaw pairing approve bitrix24 <CODE>`,
|
|
110
|
+
es: (n) => `${n} esta listo. Enviame cualquier mensaje y te devolvere un codigo de vinculacion. Luego apruebalo en OpenClaw con: openclaw pairing approve bitrix24 <CODE>`,
|
|
111
|
+
fr: (n) => `${n} est pret. Envoyez-moi n'importe quel message et je vous renverrai un code d'association. Ensuite, validez-le dans OpenClaw avec : openclaw pairing approve bitrix24 <CODE>`,
|
|
112
|
+
pt: (n) => `${n} esta pronto. Envie-me qualquer mensagem e eu retornarei um codigo de pareamento. Depois, aprove-o no OpenClaw com: openclaw pairing approve bitrix24 <CODE>`,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export function pairingWelcomeMessage(lang: string | undefined, botName: string): string {
|
|
116
|
+
return resolve(I18N_PAIRING_WELCOME, lang)(botName);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function onboardingMessage(
|
|
120
|
+
lang: string | undefined,
|
|
121
|
+
botName: string,
|
|
122
|
+
dmPolicy: 'pairing' | 'webhookUser' | undefined,
|
|
123
|
+
): string {
|
|
124
|
+
return dmPolicy === 'pairing'
|
|
125
|
+
? pairingWelcomeMessage(lang, botName)
|
|
126
|
+
: welcomeMessage(lang, botName);
|
|
127
|
+
}
|