@dingtalk-real-ai/dingtalk-connector 0.8.20-beta.8 → 0.8.20
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/CHANGELOG.md +13 -0
- package/bin/dingtalk-connector.js +50 -13
- package/dist/{common-PeRcixHR.mjs → common-BGJlWkEp.mjs} +0 -1
- package/dist/{common-Dv3coHhJ.mjs → common-CGPC5bYt.mjs} +1 -1
- package/dist/{connection-DukhmwuY.mjs → connection-BZd5NXuh.mjs} +7 -23
- package/dist/entry-bundled.mjs +1 -1
- package/dist/{gateway-methods-BZSmkOsG.mjs → gateway-methods-DI8lkjSd.mjs} +1 -1
- package/dist/gateway-methods-DtdiDpYK.mjs +2 -0
- package/dist/index.d.mts +14 -1
- package/dist/index.mjs +4 -3
- package/dist/media-DEuF7r3G.mjs +2 -0
- package/dist/{media-CqiIK9ZD.mjs → media-DUMfXnwJ.mjs} +11 -59
- package/dist/{message-handler-BdHjL9Mi.mjs → message-handler-_vk6QsWo.mjs} +24 -29
- package/dist/{messaging-BNycOrEX.mjs → messaging-CyIJY4h2.mjs} +4 -4
- package/dist/{runtime-CJCdIYOh.mjs → runtime-b4xvqwW6.mjs} +26 -11
- package/docs/RELEASE_NOTES_V0.8.20.md +49 -0
- package/index.ts +3 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
- package/src/channel.ts +22 -7
- package/src/core/connection.ts +9 -54
- package/src/onboarding.ts +3 -7
- package/src/reply-dispatcher.ts +21 -29
- package/src/services/media/common.ts +0 -3
- package/src/services/media.ts +15 -76
- package/dist/gateway-methods-DVDpyryu.mjs +0 -2
- package/dist/media-F5D9__w3.mjs +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.8.20] - 2026-04-28
|
|
9
|
+
|
|
10
|
+
### 修复 / Fixes
|
|
11
|
+
- 🐛 **OpenClaw 插件加载兼容性 (Issue #527)** — `configSchema` 改为延迟初始化,通过 `createRequire` 解析 `openclaw/plugin-sdk/core`,修复插件安装到 `~/.openclaw/extensions/` 时 ESM 裸说明符解析失败导致的 "Cannot find package 'openclaw'" 崩溃
|
|
12
|
+
**OpenClaw plugin load compatibility (Issue #527)** — `configSchema` deferred to lazy init via `createRequire`, fixing "Cannot find package 'openclaw'" crash when plugin is installed to `~/.openclaw/extensions/`
|
|
13
|
+
|
|
14
|
+
- 🐛 **Onboarding 动态导入** — `promptSingleChannelSecretInput` 从静态 import 改为动态 `import()`,避免在 ESM 加载阶段触发同样的裸说明符解析错误
|
|
15
|
+
**Onboarding dynamic import** — `promptSingleChannelSecretInput` switched from static to dynamic `import()` to avoid bare specifier resolution error during ESM loading
|
|
16
|
+
|
|
17
|
+
### 改进 / Improvements
|
|
18
|
+
- ✅ **DWS CLI 版本管理重构** — `ensureDwsCli()` 新增 `compareVersions()` 语义版本比较,支持四种场景:目标版本更高时自动升级、本地版本更高时询问是否覆盖、版本一致时跳过、全新安装时显示已安装版本号
|
|
19
|
+
**DWS CLI version management refactor** — `ensureDwsCli()` now uses `compareVersions()` for semver comparison with four scenarios: auto-upgrade when target is newer, prompt before downgrade, skip when equal, show version on fresh install
|
|
20
|
+
|
|
8
21
|
## [0.8.19] - 2026-04-25
|
|
9
22
|
|
|
10
23
|
### 新增 / Added
|
|
@@ -428,6 +428,22 @@ function getTargetDwsVersion() {
|
|
|
428
428
|
return versionMatch ? versionMatch[1] : null;
|
|
429
429
|
}
|
|
430
430
|
|
|
431
|
+
/**
|
|
432
|
+
* Compare two semver version strings.
|
|
433
|
+
* Returns: positive if a > b, negative if a < b, 0 if equal.
|
|
434
|
+
*/
|
|
435
|
+
function compareVersions(versionA, versionB) {
|
|
436
|
+
const partsA = versionA.split('.').map(Number);
|
|
437
|
+
const partsB = versionB.split('.').map(Number);
|
|
438
|
+
const maxLength = Math.max(partsA.length, partsB.length);
|
|
439
|
+
for (let i = 0; i < maxLength; i++) {
|
|
440
|
+
const partA = partsA[i] || 0;
|
|
441
|
+
const partB = partsB[i] || 0;
|
|
442
|
+
if (partA !== partB) return partA - partB;
|
|
443
|
+
}
|
|
444
|
+
return 0;
|
|
445
|
+
}
|
|
446
|
+
|
|
431
447
|
function askUserConfirmation(question) {
|
|
432
448
|
const { createInterface } = createRequire(import.meta.url)('node:readline');
|
|
433
449
|
const rl = createInterface({
|
|
@@ -498,33 +514,50 @@ function isDwsAuthenticated() {
|
|
|
498
514
|
}
|
|
499
515
|
|
|
500
516
|
async function ensureDwsCli() {
|
|
517
|
+
const targetVersion = getTargetDwsVersion();
|
|
518
|
+
|
|
501
519
|
if (isDwsInstalled()) {
|
|
502
520
|
const installedVersion = getInstalledDwsVersion();
|
|
503
|
-
const targetVersion = getTargetDwsVersion();
|
|
504
521
|
const versionDisplay = installedVersion ? `v${installedVersion}` : 'unknown version';
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
console.log(
|
|
522
|
+
const comparison = (installedVersion && targetVersion) ? compareVersions(targetVersion, installedVersion) : 0;
|
|
523
|
+
|
|
524
|
+
if (comparison > 0) {
|
|
525
|
+
// Scenario 1: target > local → upgrade directly
|
|
526
|
+
console.log(dim(` ℹ dws CLI detected (${versionDisplay}), upgrading to v${targetVersion}...`) + '\n');
|
|
527
|
+
console.log(dim(` v${installedVersion} → v${targetVersion}`) + '\n');
|
|
528
|
+
const upgraded = installDwsCli();
|
|
529
|
+
if (upgraded) {
|
|
530
|
+
const newVersion = getInstalledDwsVersion();
|
|
531
|
+
console.log(green(` ✔ dws CLI upgraded to v${newVersion || targetVersion}`) + '\n');
|
|
532
|
+
} else {
|
|
533
|
+
console.log(red(' ⚠ Upgrade failed. Continuing with current version.') + '\n');
|
|
534
|
+
}
|
|
535
|
+
} else if (comparison < 0) {
|
|
536
|
+
// Scenario 2: target < local → ask user before downgrading
|
|
537
|
+
console.log(dim(` ℹ dws CLI detected (${versionDisplay})`) + '\n');
|
|
538
|
+
console.log(orange(` ⚠ Your local dws CLI (v${installedVersion}) is newer than the bundled version (v${targetVersion}).`) + '\n');
|
|
539
|
+
console.log(dim(` Overwriting would downgrade: v${installedVersion} → v${targetVersion}`) + '\n');
|
|
511
540
|
const answer = await askUserConfirmation(
|
|
512
|
-
` Do you want to
|
|
541
|
+
` Do you want to overwrite with v${targetVersion}? (是否覆盖为旧版本?) [y/N] `
|
|
513
542
|
);
|
|
514
543
|
if (answer === 'y' || answer === 'yes') {
|
|
515
544
|
console.log('');
|
|
516
|
-
const
|
|
517
|
-
if (
|
|
545
|
+
const downgraded = installDwsCli();
|
|
546
|
+
if (downgraded) {
|
|
518
547
|
const newVersion = getInstalledDwsVersion();
|
|
519
|
-
console.log(green(` ✔ dws CLI
|
|
548
|
+
console.log(green(` ✔ dws CLI replaced with v${newVersion || targetVersion}`) + '\n');
|
|
520
549
|
} else {
|
|
521
|
-
console.log(red(' ⚠
|
|
550
|
+
console.log(red(' ⚠ Overwrite failed. Continuing with current version.') + '\n');
|
|
522
551
|
}
|
|
523
552
|
} else {
|
|
524
553
|
console.log('\n' + dim(` Keeping current dws CLI v${installedVersion}`) + '\n');
|
|
525
554
|
}
|
|
555
|
+
} else {
|
|
556
|
+
// Scenario 3: versions are equal → skip
|
|
557
|
+
console.log(dim(` ✔ dws CLI already installed (${versionDisplay}), version is up to date`) + '\n');
|
|
526
558
|
}
|
|
527
559
|
|
|
560
|
+
// Check authentication status regardless of version scenario
|
|
528
561
|
if (isDwsAuthenticated()) {
|
|
529
562
|
console.log(dim(' ✔ dws CLI authenticated') + '\n');
|
|
530
563
|
} else {
|
|
@@ -534,6 +567,7 @@ async function ensureDwsCli() {
|
|
|
534
567
|
return;
|
|
535
568
|
}
|
|
536
569
|
|
|
570
|
+
// Scenario 4: dws not installed → install and show version
|
|
537
571
|
const installed = installDwsCli();
|
|
538
572
|
if (!installed) {
|
|
539
573
|
console.log(red(' ⚠ Could not install dws CLI automatically.') + '\n');
|
|
@@ -544,7 +578,10 @@ async function ensureDwsCli() {
|
|
|
544
578
|
return;
|
|
545
579
|
}
|
|
546
580
|
|
|
547
|
-
|
|
581
|
+
const freshVersion = getInstalledDwsVersion();
|
|
582
|
+
const freshDisplay = freshVersion ? `v${freshVersion}` : (targetVersion ? `v${targetVersion}` : '');
|
|
583
|
+
console.log(green(` ✔ dws CLI installed (${freshDisplay})`) + '\n');
|
|
584
|
+
console.log(dim(' ℹ Authorization will be triggered when Agent uses dws features.') + '\n');
|
|
548
585
|
console.log(dim(' You can also authorize manually anytime: ') + cyan('dws auth login') + '\n');
|
|
549
586
|
}
|
|
550
587
|
|
|
@@ -24,7 +24,6 @@ function toLocalPath(raw) {
|
|
|
24
24
|
if (filePath.startsWith("file://")) filePath = filePath.replace("file://", "");
|
|
25
25
|
else if (filePath.startsWith("MEDIA:")) filePath = filePath.replace("MEDIA:", "");
|
|
26
26
|
else if (filePath.startsWith("attachment://")) filePath = filePath.replace("attachment://", "");
|
|
27
|
-
filePath = filePath.trim();
|
|
28
27
|
try {
|
|
29
28
|
filePath = decodeURIComponent(filePath);
|
|
30
29
|
} catch {}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as VIDEO_MARKER_PATTERN, r as FILE_MARKER_PATTERN, t as AUDIO_MARKER_PATTERN } from "./common-
|
|
1
|
+
import { a as VIDEO_MARKER_PATTERN, r as FILE_MARKER_PATTERN, t as AUDIO_MARKER_PATTERN } from "./common-BGJlWkEp.mjs";
|
|
2
2
|
export { AUDIO_MARKER_PATTERN, FILE_MARKER_PATTERN, VIDEO_MARKER_PATTERN };
|
|
@@ -6,7 +6,7 @@ import * as fs from "fs";
|
|
|
6
6
|
*
|
|
7
7
|
* 职责:
|
|
8
8
|
* - 管理单个钉钉账号的 WebSocket 连接
|
|
9
|
-
* - 实现应用层心跳检测(10
|
|
9
|
+
* - 实现应用层心跳检测(10 秒间隔,90 秒超时)
|
|
10
10
|
* - 处理连接重连逻辑,带指数退避
|
|
11
11
|
* - 消息去重(内置 Map,5 分钟 TTL)
|
|
12
12
|
*
|
|
@@ -18,7 +18,7 @@ import * as fs from "fs";
|
|
|
18
18
|
/** 心跳间隔(毫秒) */
|
|
19
19
|
const HEARTBEAT_INTERVAL = 10 * 1e3;
|
|
20
20
|
/** 超时阈值(毫秒) */
|
|
21
|
-
const TIMEOUT_THRESHOLD =
|
|
21
|
+
const TIMEOUT_THRESHOLD = 20 * 1e3;
|
|
22
22
|
/** 基础退避时间(毫秒) */
|
|
23
23
|
const BASE_BACKOFF_DELAY = 1e3;
|
|
24
24
|
/** 最大退避时间(毫秒) */
|
|
@@ -30,11 +30,6 @@ async function monitorSingleAccount(opts) {
|
|
|
30
30
|
const log = runtime?.log;
|
|
31
31
|
const { createLoggerFromConfig } = await import("./logger-BeHWErmX.mjs");
|
|
32
32
|
const logger = createLoggerFromConfig(account.config, `DingTalk:${accountId}`);
|
|
33
|
-
const resolvedConfig = {
|
|
34
|
-
...account.config,
|
|
35
|
-
...account.clientId != null ? { clientId: account.clientId } : {},
|
|
36
|
-
...account.clientSecret != null ? { clientSecret: account.clientSecret } : {}
|
|
37
|
-
};
|
|
38
33
|
if (!account.clientId || !account.clientSecret) throw new Error(`[DingTalk][${accountId}] Missing credentials: clientId=${account.clientId ? "present" : "MISSING"}, clientSecret=${account.clientSecret ? "present" : "MISSING"}. Please check your configuration in channels.dingtalk-connector.`);
|
|
39
34
|
const clientIdStr = String(account.clientId);
|
|
40
35
|
const clientSecretStr = String(account.clientSecret);
|
|
@@ -156,9 +151,6 @@ async function monitorSingleAccount(opts) {
|
|
|
156
151
|
client.socket?.once("open", onOpen);
|
|
157
152
|
client.socket?.once("error", onError);
|
|
158
153
|
})) throw new Error(`连接建立超时或失败`);
|
|
159
|
-
setupPongListener();
|
|
160
|
-
setupMessageListener();
|
|
161
|
-
setupCloseListener();
|
|
162
154
|
lastSocketAvailableTime = Date.now();
|
|
163
155
|
connectionEstablishedTime = Date.now();
|
|
164
156
|
reconnectAttempts = 0;
|
|
@@ -271,6 +263,9 @@ async function monitorSingleAccount(opts) {
|
|
|
271
263
|
if (client.socket) client.socket.removeAllListeners();
|
|
272
264
|
logger.debug(`Connection 已停止`);
|
|
273
265
|
}
|
|
266
|
+
setupPongListener();
|
|
267
|
+
setupMessageListener();
|
|
268
|
+
setupCloseListener();
|
|
274
269
|
return new Promise(async (resolve, reject) => {
|
|
275
270
|
if (abortSignal) {
|
|
276
271
|
const onAbort = async () => {
|
|
@@ -345,7 +340,7 @@ async function monitorSingleAccount(opts) {
|
|
|
345
340
|
logger.info(`🚀 开始处理消息...`);
|
|
346
341
|
await messageHandler({
|
|
347
342
|
accountId,
|
|
348
|
-
config:
|
|
343
|
+
config: account.config,
|
|
349
344
|
data,
|
|
350
345
|
sessionWebhook: data.sessionWebhook,
|
|
351
346
|
runtime,
|
|
@@ -374,10 +369,7 @@ async function monitorSingleAccount(opts) {
|
|
|
374
369
|
await client.connect();
|
|
375
370
|
logger.info(`Connected to DingTalk Stream successfully`);
|
|
376
371
|
logger.info(`PID: ${process.pid}`);
|
|
377
|
-
logger.info(`✅ 自定义 keepAlive: true (
|
|
378
|
-
setupPongListener();
|
|
379
|
-
setupMessageListener();
|
|
380
|
-
setupCloseListener();
|
|
372
|
+
logger.info(`✅ 自定义 keepAlive: true (10 秒心跳,90 秒超时), 指数退避重连`);
|
|
381
373
|
onStatusChange?.({
|
|
382
374
|
connected: true,
|
|
383
375
|
lastConnectedAt: Date.now()
|
|
@@ -407,14 +399,6 @@ async function monitorSingleAccount(opts) {
|
|
|
407
399
|
reject(/* @__PURE__ */ new Error(`[DingTalk][${accountId}] Authentication failed (401 Unauthorized):\n - Your clientId or clientSecret is invalid, expired, or revoked\n - clientId: ${clientIdStr.substring(0, 8)}...\n - Please verify your credentials at DingTalk Developer Console\n - Error details: ${error.message}`));
|
|
408
400
|
return;
|
|
409
401
|
}
|
|
410
|
-
if (error.response?.status === 403 || error.message?.includes("status code 403") || error.message?.includes("403")) {
|
|
411
|
-
reject(/* @__PURE__ */ new Error(`[DingTalk][${accountId}] Forbidden (403):\n - 可能原因 / Possible causes:\n 1. 机器人未开启 Stream 模式(需在钉钉开放平台 → 消息接收模式中设置)\n 2. 机器人处于"开发中"状态,尚未发布上线\n 3. API 调用频率超限(QPS Limit)\n - Error details: ${error.message}\n - Response data: ${JSON.stringify(error.response?.data || {})}`));
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
if (error.message?.includes("protocol") || error.message?.includes("mismatch") || error.message?.includes("upgrade")) {
|
|
415
|
-
reject(/* @__PURE__ */ new Error(`[DingTalk][${accountId}] Failed to connect to DingTalk Stream: ${error.message}\n - 可能原因 / Possible causes:\n 1. dingtalk-stream SDK 版本过旧,请升级插件: npx openclaw@latest add @dingtalk-real-ai/dingtalk-connector\n 2. 网络代理或防火墙拦截了 WebSocket 连接\n 3. 钉钉服务端协议已更新,当前版本不兼容\n - 建议 / Suggestions:\n 1. 升级到最新版本的 dingtalk-connector 插件\n 2. 检查网络环境是否允许 WebSocket 连接\n 3. 如仍有问题,请提 Issue 并附上完整日志`));
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
402
|
reject(/* @__PURE__ */ new Error(`[DingTalk][${accountId}] Failed to connect to DingTalk Stream: ${error.message}`));
|
|
419
403
|
return;
|
|
420
404
|
}
|
package/dist/entry-bundled.mjs
CHANGED
|
@@ -23,7 +23,7 @@ var entry_bundled_default = defineBundledChannelEntry({
|
|
|
23
23
|
exportName: "setDingtalkRuntime"
|
|
24
24
|
},
|
|
25
25
|
async registerFull(api) {
|
|
26
|
-
const { registerGatewayMethods } = await import("./gateway-methods-
|
|
26
|
+
const { registerGatewayMethods } = await import("./gateway-methods-DtdiDpYK.mjs");
|
|
27
27
|
registerGatewayMethods(api);
|
|
28
28
|
}
|
|
29
29
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as resolveDingtalkAccount, t as listDingtalkAccountIds } from "./accounts-CF4oK_HZ.mjs";
|
|
2
2
|
import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
|
|
3
3
|
import { r as getAccessToken, t as DINGTALK_API } from "./utils-CIfI_3Jh.mjs";
|
|
4
|
-
import { c as prepareMultiBotMentions, i as sendProactive, s as buildBotMentionTable, u as finishAICard } from "./messaging-
|
|
4
|
+
import { c as prepareMultiBotMentions, i as sendProactive, s as buildBotMentionTable, u as finishAICard } from "./messaging-CyIJY4h2.mjs";
|
|
5
5
|
import { c as getUnionId, d as recallEmotionReply } from "./utils-legacy-CALCPP1t.mjs";
|
|
6
6
|
//#region src/docs.ts
|
|
7
7
|
var DingtalkDocsClient = class {
|
package/dist/index.d.mts
CHANGED
|
@@ -156,6 +156,19 @@ type ResolvedDingtalkAccount = {
|
|
|
156
156
|
//#endregion
|
|
157
157
|
//#region src/channel.d.ts
|
|
158
158
|
declare const dingtalkPlugin: ChannelPlugin<ResolvedDingtalkAccount>;
|
|
159
|
+
/**
|
|
160
|
+
* Synchronously initializes `dingtalkPlugin.configSchema` using `createRequire`.
|
|
161
|
+
*
|
|
162
|
+
* Static `import ... from "openclaw/plugin-sdk/core"` causes
|
|
163
|
+
* "Cannot find package 'openclaw'" when the plugin is installed to
|
|
164
|
+
* `~/.openclaw/extensions/` (Issue #527) because the ESM loader resolves
|
|
165
|
+
* bare specifiers at parse time before the gateway's jiti alias map is active.
|
|
166
|
+
*
|
|
167
|
+
* By deferring the resolve to `register()` time and using `createRequire`
|
|
168
|
+
* (which searches the gateway's own `node_modules`), we avoid the crash
|
|
169
|
+
* while keeping the call synchronous as required by the plugin API.
|
|
170
|
+
*/
|
|
171
|
+
declare function initDingtalkPluginConfigSchema(): void;
|
|
159
172
|
//#endregion
|
|
160
173
|
//#region src/runtime.d.ts
|
|
161
174
|
declare const setDingtalkRuntime: (next: PluginRuntime) => void, getDingtalkRuntime: () => PluginRuntime;
|
|
@@ -169,4 +182,4 @@ declare function registerGatewayMethods(api: OpenClawPluginApi): void;
|
|
|
169
182
|
//#region index.d.ts
|
|
170
183
|
declare function register(api: OpenClawPluginApi): void;
|
|
171
184
|
//#endregion
|
|
172
|
-
export { register as default, dingtalkPlugin, registerGatewayMethods, setDingtalkRuntime };
|
|
185
|
+
export { register as default, dingtalkPlugin, initDingtalkPluginConfigSchema, registerGatewayMethods, setDingtalkRuntime };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-
|
|
2
|
-
import { t as registerGatewayMethods } from "./gateway-methods-
|
|
1
|
+
import { a as initDingtalkPluginConfigSchema, i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-b4xvqwW6.mjs";
|
|
2
|
+
import { t as registerGatewayMethods } from "./gateway-methods-DI8lkjSd.mjs";
|
|
3
3
|
//#region index.ts
|
|
4
4
|
/**
|
|
5
5
|
* 检测同一 plugin id 在多个路径被加载的情况。
|
|
@@ -37,8 +37,9 @@ function recordAndCheckLoadPath(api) {
|
|
|
37
37
|
function register(api) {
|
|
38
38
|
recordAndCheckLoadPath(api);
|
|
39
39
|
setDingtalkRuntime(api.runtime);
|
|
40
|
+
initDingtalkPluginConfigSchema();
|
|
40
41
|
api.registerChannel({ plugin: dingtalkPlugin });
|
|
41
42
|
registerGatewayMethods(api);
|
|
42
43
|
}
|
|
43
44
|
//#endregion
|
|
44
|
-
export { register as default, dingtalkPlugin, registerGatewayMethods, setDingtalkRuntime };
|
|
45
|
+
export { register as default, dingtalkPlugin, initDingtalkPluginConfigSchema, registerGatewayMethods, setDingtalkRuntime };
|
|
@@ -32,7 +32,6 @@ function toLocalPath(raw) {
|
|
|
32
32
|
if (filePath.startsWith("file://")) filePath = filePath.replace("file://", "");
|
|
33
33
|
else if (filePath.startsWith("MEDIA:")) filePath = filePath.replace("MEDIA:", "");
|
|
34
34
|
else if (filePath.startsWith("attachment://")) filePath = filePath.replace("attachment://", "");
|
|
35
|
-
filePath = filePath.trim();
|
|
36
35
|
try {
|
|
37
36
|
filePath = decodeURIComponent(filePath);
|
|
38
37
|
} catch {}
|
|
@@ -436,44 +435,9 @@ async function sendFileProactive(config, target, fileInfo, mediaId, log) {
|
|
|
436
435
|
throw err;
|
|
437
436
|
}
|
|
438
437
|
}
|
|
439
|
-
/**
|
|
440
|
-
* 发送图片消息(主动 API 模式)
|
|
441
|
-
*/
|
|
442
|
-
async function sendImageProactive(config, target, photoURL, log) {
|
|
443
|
-
try {
|
|
444
|
-
const token = await (await import("./utils-DY1gFCdU.mjs")).getAccessToken(config);
|
|
445
|
-
const { DINGTALK_API } = await import("./utils-DY1gFCdU.mjs");
|
|
446
|
-
const msgParam = { photoURL };
|
|
447
|
-
const body = {
|
|
448
|
-
robotCode: String(config.clientId),
|
|
449
|
-
msgKey: "sampleImageMsg",
|
|
450
|
-
msgParam: JSON.stringify(msgParam)
|
|
451
|
-
};
|
|
452
|
-
let endpoint;
|
|
453
|
-
if (target.type === "group") {
|
|
454
|
-
body.openConversationId = target.openConversationId;
|
|
455
|
-
endpoint = `${DINGTALK_API}/v1.0/robot/groupMessages/send`;
|
|
456
|
-
} else {
|
|
457
|
-
body.userIds = [target.userId];
|
|
458
|
-
endpoint = `${DINGTALK_API}/v1.0/robot/oToMessages/batchSend`;
|
|
459
|
-
}
|
|
460
|
-
log?.info?.(`Image[Proactive] 发送图片消息: ${photoURL}`);
|
|
461
|
-
const resp = await dingtalkHttp.post(endpoint, body, {
|
|
462
|
-
headers: {
|
|
463
|
-
"x-acs-dingtalk-access-token": token,
|
|
464
|
-
"Content-Type": "application/json"
|
|
465
|
-
},
|
|
466
|
-
timeout: 1e4
|
|
467
|
-
});
|
|
468
|
-
if (resp.data?.processQueryKey) log?.info?.(`Image[Proactive] 图片消息发送成功`);
|
|
469
|
-
else log?.warn?.(`Image[Proactive] 图片消息发送响应异常: ${JSON.stringify(resp.data)}`);
|
|
470
|
-
} catch (err) {
|
|
471
|
-
log?.error?.(`Image[Proactive] 发送图片消息失败: ${err.message}`);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
438
|
async function processRawMediaPaths(content, config, oapiToken, log, target) {
|
|
475
439
|
const logPrefix = "RawMedia";
|
|
476
|
-
const matches = Array.from(content.matchAll(/(?:^|\s)((?:
|
|
440
|
+
const matches = Array.from(content.matchAll(/(?:^|\s)((?:[A-Za-z]:)?[\/\\](?:[^\/\\:\*\?"<>\|\s]+[\/\\])*[^\/\\:\*\?"<>\|\s]+\.(?:mp4|avi|mov|wmv|flv|mkv|webm|mp3|wav|flac|aac|ogg|m4a|wma|pdf|doc|docx|xls|xlsx|ppt|pptx|txt|zip|rar|7z|tar|gz))(?:\s|$)/gi));
|
|
477
441
|
if (matches.length === 0) return content;
|
|
478
442
|
log?.info?.(`${logPrefix} 检测到 ${matches.length} 个裸露的本地文件路径`);
|
|
479
443
|
let processedContent = content;
|
|
@@ -483,18 +447,9 @@ async function processRawMediaPaths(content, config, oapiToken, log, target) {
|
|
|
483
447
|
const filePath = match[1].trim();
|
|
484
448
|
try {
|
|
485
449
|
log?.info?.(`${logPrefix} 开始处理文件: ${filePath}`);
|
|
486
|
-
const
|
|
487
|
-
const ext = localFilePath.toLowerCase().split(".").pop() || "";
|
|
450
|
+
const ext = filePath.toLowerCase().split(".").pop() || "";
|
|
488
451
|
let mediaType;
|
|
489
452
|
if ([
|
|
490
|
-
"png",
|
|
491
|
-
"jpg",
|
|
492
|
-
"jpeg",
|
|
493
|
-
"gif",
|
|
494
|
-
"bmp",
|
|
495
|
-
"webp"
|
|
496
|
-
].includes(ext)) mediaType = "image";
|
|
497
|
-
else if ([
|
|
498
453
|
"mp4",
|
|
499
454
|
"avi",
|
|
500
455
|
"mov",
|
|
@@ -513,27 +468,24 @@ async function processRawMediaPaths(content, config, oapiToken, log, target) {
|
|
|
513
468
|
"wma"
|
|
514
469
|
].includes(ext)) mediaType = "voice";
|
|
515
470
|
else mediaType = "file";
|
|
516
|
-
const uploadResult = await uploadMediaToDingTalk(
|
|
471
|
+
const uploadResult = await uploadMediaToDingTalk(filePath, mediaType, oapiToken, 20 * 1024 * 1024, log);
|
|
517
472
|
if (!uploadResult) {
|
|
518
|
-
log?.error?.(`${logPrefix} 文件上传失败: ${
|
|
519
|
-
statusMessages.push(`⚠️ 文件上传失败: ${
|
|
473
|
+
log?.error?.(`${logPrefix} 文件上传失败: ${filePath}`);
|
|
474
|
+
statusMessages.push(`⚠️ 文件上传失败: ${filePath}`);
|
|
520
475
|
continue;
|
|
521
476
|
}
|
|
522
|
-
const fileName =
|
|
523
|
-
if (mediaType === "
|
|
524
|
-
|
|
525
|
-
statusMessages.push(`✅ 图片已发送: ${fileName}`);
|
|
526
|
-
} else if (mediaType === "video") {
|
|
527
|
-
const metadata = await extractVideoMetadata(localFilePath, log);
|
|
477
|
+
const fileName = filePath.split(/[\/\\]/).pop() || "unknown";
|
|
478
|
+
if (mediaType === "video") {
|
|
479
|
+
const metadata = await extractVideoMetadata(filePath, log);
|
|
528
480
|
if (target) await sendVideoProactive(config, target, uploadResult.mediaId, fileName, log, metadata);
|
|
529
481
|
statusMessages.push(`✅ 视频已发送: ${fileName}`);
|
|
530
482
|
} else if (mediaType === "voice") {
|
|
531
|
-
const durationMs = await extractAudioDuration(
|
|
483
|
+
const durationMs = await extractAudioDuration(filePath, log);
|
|
532
484
|
if (target) await sendAudioProactive(config, target, fileName, uploadResult.downloadUrl, log, durationMs ?? void 0);
|
|
533
485
|
statusMessages.push(`✅ 音频已发送: ${fileName}`);
|
|
534
486
|
} else {
|
|
535
487
|
const fileInfo = {
|
|
536
|
-
path:
|
|
488
|
+
path: filePath,
|
|
537
489
|
fileName,
|
|
538
490
|
fileType: ext
|
|
539
491
|
};
|
|
@@ -554,4 +506,4 @@ async function processRawMediaPaths(content, config, oapiToken, log, target) {
|
|
|
554
506
|
return processedContent;
|
|
555
507
|
}
|
|
556
508
|
//#endregion
|
|
557
|
-
export { processVideoMarkers as a,
|
|
509
|
+
export { processVideoMarkers as a, sendVideoProactive as c, __exportAll as d, processRawMediaPaths as i, toLocalPath as l, extractVideoMetadata as n, sendAudioProactive as o, extractVideoThumbnail as r, sendFileProactive as s, VIDEO_MARKER_PATTERN as t, uploadMediaToDingTalk as u };
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { u as uploadMediaToDingTalk } from "./media-DUMfXnwJ.mjs";
|
|
2
2
|
import { a as resolveDingtalkAccount } from "./accounts-CF4oK_HZ.mjs";
|
|
3
|
-
import { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-
|
|
3
|
+
import { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-b4xvqwW6.mjs";
|
|
4
4
|
import { n as createLoggerFromConfig } from "./logger-BDWwViGT.mjs";
|
|
5
5
|
import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
|
|
6
6
|
import { i as getOapiAccessToken } from "./utils-CIfI_3Jh.mjs";
|
|
7
|
-
import { a as sendTextMessage, d as isQpsLimitError, f as streamAICard, i as sendProactive, l as createAICardForTarget, r as sendMessage, t as sendMarkdownMessage, u as finishAICard } from "./messaging-
|
|
7
|
+
import { a as sendTextMessage, d as isQpsLimitError, f as streamAICard, i as sendProactive, l as createAICardForTarget, r as sendMessage, t as sendMarkdownMessage, u as finishAICard } from "./messaging-CyIJY4h2.mjs";
|
|
8
8
|
import { a as QUEUE_BUSY_ACK_PHRASES, n as normalizeSlashCommand, t as buildSessionContext } from "./session-DJ4jYqPv.mjs";
|
|
9
9
|
import { d as recallEmotionReply, o as getAccessToken, r as addEmotionReply, s as getOapiAccessToken$1, t as DINGTALK_API } from "./utils-legacy-CALCPP1t.mjs";
|
|
10
10
|
import "./chunk-upload-6p9cf3UB.mjs";
|
|
11
|
-
import { a as VIDEO_MARKER_PATTERN, i as LOCAL_IMAGE_RE, o as toLocalPath, r as FILE_MARKER_PATTERN, s as uploadMediaToDingTalk$1, t as AUDIO_MARKER_PATTERN } from "./common-
|
|
11
|
+
import { a as VIDEO_MARKER_PATTERN, i as LOCAL_IMAGE_RE, o as toLocalPath, r as FILE_MARKER_PATTERN, s as uploadMediaToDingTalk$1, t as AUDIO_MARKER_PATTERN } from "./common-BGJlWkEp.mjs";
|
|
12
12
|
import * as fs from "fs";
|
|
13
13
|
import * as path from "path";
|
|
14
14
|
import * as os from "node:os";
|
|
@@ -204,7 +204,7 @@ async function uploadAndReplaceFileMarkers(content, sessionWebhook, config, oapi
|
|
|
204
204
|
}
|
|
205
205
|
//#endregion
|
|
206
206
|
//#region src/reply-dispatcher.ts
|
|
207
|
-
const { createReplyPrefixOptions
|
|
207
|
+
const { createReplyPrefixOptions, createTypingCallbacks, logTypingFailure } = await import("openclaw/plugin-sdk/channel-runtime");
|
|
208
208
|
function createDingtalkReplyDispatcher(params) {
|
|
209
209
|
const core = getDingtalkRuntime();
|
|
210
210
|
const { cfg, agentId, conversationId, senderId, isDirect, accountId, sessionWebhook, asyncMode = false, preCreatedCard } = params;
|
|
@@ -212,18 +212,13 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
212
212
|
cfg,
|
|
213
213
|
accountId
|
|
214
214
|
});
|
|
215
|
-
const resolvedConfig = {
|
|
216
|
-
...account.config,
|
|
217
|
-
...account.clientId != null ? { clientId: account.clientId } : {},
|
|
218
|
-
...account.clientSecret != null ? { clientSecret: account.clientSecret } : {}
|
|
219
|
-
};
|
|
220
215
|
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
|
|
221
216
|
cfg,
|
|
222
217
|
agentId,
|
|
223
218
|
channel: CHANNEL_ID,
|
|
224
219
|
accountId
|
|
225
220
|
});
|
|
226
|
-
const log = createLoggerFromConfig(
|
|
221
|
+
const log = createLoggerFromConfig(account.config, `DingTalk:${accountId}`);
|
|
227
222
|
let currentCardTarget = null;
|
|
228
223
|
let accumulatedText = "";
|
|
229
224
|
const deliveredFinalTexts = /* @__PURE__ */ new Set();
|
|
@@ -256,7 +251,7 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
256
251
|
}[errorType];
|
|
257
252
|
log.warn(`[DingTalk][Fallback] ${errorMessage}, error: ${originalError}`);
|
|
258
253
|
try {
|
|
259
|
-
await sendMessage(
|
|
254
|
+
await sendMessage(account.config, sessionWebhook, errorMessage, {
|
|
260
255
|
useMarkdown: false,
|
|
261
256
|
log: params.runtime.log
|
|
262
257
|
});
|
|
@@ -318,7 +313,7 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
318
313
|
openConversationId: conversationId
|
|
319
314
|
};
|
|
320
315
|
log.info(`[DingTalk][startStreaming] 目标:${JSON.stringify(target)}`);
|
|
321
|
-
const card = await createAICardForTarget(
|
|
316
|
+
const card = await createAICardForTarget(account.config, target, log);
|
|
322
317
|
currentCardTarget = card;
|
|
323
318
|
accumulatedText = "";
|
|
324
319
|
if (card) log.info(`[DingTalk][startStreaming] ✅ AI Card 创建成功`);
|
|
@@ -357,12 +352,12 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
357
352
|
log.info(`[DingTalk][closeStreaming] 开始处理媒体文件,target=${JSON.stringify(target)}`);
|
|
358
353
|
if (oapiToken) {
|
|
359
354
|
finalText = await processLocalImages(finalText, oapiToken, log);
|
|
360
|
-
finalText = await processVideoMarkers(finalText, "",
|
|
361
|
-
finalText = await processAudioMarkers(finalText, "",
|
|
362
|
-
finalText = await uploadAndReplaceFileMarkers(finalText, "",
|
|
355
|
+
finalText = await processVideoMarkers(finalText, "", account.config, oapiToken, log, true, target);
|
|
356
|
+
finalText = await processAudioMarkers(finalText, "", account.config, oapiToken, log, true, target);
|
|
357
|
+
finalText = await uploadAndReplaceFileMarkers(finalText, "", account.config, oapiToken, log, true, target);
|
|
363
358
|
log.info(`[DingTalk][closeStreaming] 准备调用 processRawMediaPaths`);
|
|
364
|
-
const { processRawMediaPaths } = await import("./media-
|
|
365
|
-
finalText = await processRawMediaPaths(finalText,
|
|
359
|
+
const { processRawMediaPaths } = await import("./media-DEuF7r3G.mjs");
|
|
360
|
+
finalText = await processRawMediaPaths(finalText, account.config, oapiToken, log, target);
|
|
366
361
|
log.info(`[DingTalk][closeStreaming] processRawMediaPaths 处理完成`);
|
|
367
362
|
} else log.warn(`[DingTalk][closeStreaming] oapiToken 为空,跳过媒体处理`);
|
|
368
363
|
try {
|
|
@@ -393,14 +388,14 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
393
388
|
}
|
|
394
389
|
log.info(`[DingTalk][closeStreaming] 准备调用 finishAICard,文本长度=${finalText.length}`);
|
|
395
390
|
log.debug(`[DingTalk][closeStreaming] 最终发送内容长度=${finalText.length}`);
|
|
396
|
-
await finishAICard(cardSnapshot, finalText,
|
|
391
|
+
await finishAICard(cardSnapshot, finalText, account.config, log);
|
|
397
392
|
log.info(`[DingTalk][closeStreaming] ✅ AI Card 关闭成功`);
|
|
398
393
|
} catch (error) {
|
|
399
394
|
log.error(`[DingTalk][closeStreaming] ❌ AI Card 关闭失败:${error?.message || String(error)}`);
|
|
400
395
|
await sendFallbackErrorMessage("mediaProcess", error?.message || String(error));
|
|
401
396
|
if (accumulatedText.trim()) try {
|
|
402
397
|
log.info(`[DingTalk][closeStreaming] 降级发送普通消息`);
|
|
403
|
-
await sendMessage(
|
|
398
|
+
await sendMessage(account.config, sessionWebhook, accumulatedText, {
|
|
404
399
|
useMarkdown: true,
|
|
405
400
|
log: params.runtime.log
|
|
406
401
|
});
|
|
@@ -437,8 +432,8 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
437
432
|
const oapiToken = await getOapiAccessToken(account.config);
|
|
438
433
|
if (oapiToken) {
|
|
439
434
|
log.info(`[DingTalk][deliver] 检测到 final 响应,准备处理裸露文件路径`);
|
|
440
|
-
const { processRawMediaPaths } = await import("./media-
|
|
441
|
-
text = await processRawMediaPaths(text,
|
|
435
|
+
const { processRawMediaPaths } = await import("./media-DEuF7r3G.mjs");
|
|
436
|
+
text = await processRawMediaPaths(text, account.config, oapiToken, log, target);
|
|
442
437
|
log.info(`[DingTalk][deliver] 裸露文件路径处理完成`);
|
|
443
438
|
}
|
|
444
439
|
} catch (err) {
|
|
@@ -472,7 +467,7 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
472
467
|
if (now - lastUpdateTime >= updateInterval) {
|
|
473
468
|
lastUpdateTime = now;
|
|
474
469
|
try {
|
|
475
|
-
await streamAICard(currentCardTarget, text, false,
|
|
470
|
+
await streamAICard(currentCardTarget, text, false, account.config, log);
|
|
476
471
|
log.info(`[DingTalk][deliver] ✅ block 更新到 AI Card 成功`);
|
|
477
472
|
} catch (streamErr) {
|
|
478
473
|
log.error(`[DingTalk][deliver] ❌ block 更新 AI Card 失败:${streamErr.message}`);
|
|
@@ -495,15 +490,15 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
495
490
|
if (info?.kind === "final") {
|
|
496
491
|
log.info(`[DingTalk][deliver] 降级到非流式发送,文本长度=${text.length}, isTextMode=${isTextMode}, groupReplyMode=${groupReplyMode}`);
|
|
497
492
|
try {
|
|
498
|
-
for (const chunk of core.channel.text.chunkTextWithMode(text, textChunkLimit, chunkMode)) if (isTextMode) if (groupReplyMode === "markdown") await sendMarkdownMessage(
|
|
493
|
+
for (const chunk of core.channel.text.chunkTextWithMode(text, textChunkLimit, chunkMode)) if (isTextMode) if (groupReplyMode === "markdown") await sendMarkdownMessage(account.config, sessionWebhook, chunk.split("\n")[0]?.replace(/^[#*\s\->]+/, "").slice(0, 20) || "Message", chunk, {
|
|
499
494
|
cfg,
|
|
500
495
|
detectBareAliases: true
|
|
501
496
|
});
|
|
502
|
-
else await sendTextMessage(
|
|
497
|
+
else await sendTextMessage(account.config, sessionWebhook, chunk, {
|
|
503
498
|
cfg,
|
|
504
499
|
detectBareAliases: true
|
|
505
500
|
});
|
|
506
|
-
else await sendMessage(
|
|
501
|
+
else await sendMessage(account.config, sessionWebhook, chunk, {
|
|
507
502
|
useMarkdown: true,
|
|
508
503
|
log: params.runtime.log,
|
|
509
504
|
cfg,
|
|
@@ -558,12 +553,12 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
558
553
|
accumulatedText = payload.text;
|
|
559
554
|
const now = Date.now();
|
|
560
555
|
if (now - lastUpdateTime >= updateInterval) {
|
|
561
|
-
const { FILE_MARKER_PATTERN, VIDEO_MARKER_PATTERN, AUDIO_MARKER_PATTERN } = await import("./common-
|
|
556
|
+
const { FILE_MARKER_PATTERN, VIDEO_MARKER_PATTERN, AUDIO_MARKER_PATTERN } = await import("./common-CGPC5bYt.mjs");
|
|
562
557
|
const displayContent = accumulatedText.replace(FILE_MARKER_PATTERN, "").replace(VIDEO_MARKER_PATTERN, "").replace(AUDIO_MARKER_PATTERN, "").trim();
|
|
563
558
|
log.debug(`[DingTalk][onPartialReply] 更新 AI Card,显示文本长度=${displayContent.length}`);
|
|
564
559
|
lastUpdateTime = now;
|
|
565
560
|
try {
|
|
566
|
-
await streamAICard(currentCardTarget, displayContent, false,
|
|
561
|
+
await streamAICard(currentCardTarget, displayContent, false, account.config, log);
|
|
567
562
|
log.debug(`[DingTalk][onPartialReply] ✅ AI Card 更新成功`);
|
|
568
563
|
} catch (err) {
|
|
569
564
|
if (isQpsLimitError(err)) log.warn(`[DingTalk][onPartialReply] AI Card 流式更新遇到 QPS 限流,已在内部退避重试;本次跳过,等待下一次 partial 更新补齐内容`);
|
|
@@ -1701,7 +1696,7 @@ async function handleDingTalkMessageInternal(params) {
|
|
|
1701
1696
|
finalText = await processVideoMarkers(finalText, "", config, oapiToken, log, true, mediaTarget);
|
|
1702
1697
|
finalText = await processAudioMarkers(finalText, "", config, oapiToken, log, true, mediaTarget);
|
|
1703
1698
|
finalText = await uploadAndReplaceFileMarkers(finalText, "", config, oapiToken, log, true, mediaTarget);
|
|
1704
|
-
const { processRawMediaPaths } = await import("./media-
|
|
1699
|
+
const { processRawMediaPaths } = await import("./media-DEuF7r3G.mjs");
|
|
1705
1700
|
finalText = await processRawMediaPaths(finalText, config, oapiToken, log, mediaTarget);
|
|
1706
1701
|
}
|
|
1707
1702
|
const textToSend = finalText.trim() || "✅ 任务执行完成(无文本输出)";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { u as uploadMediaToDingTalk } from "./media-DUMfXnwJ.mjs";
|
|
2
2
|
import { n as createLoggerFromConfig } from "./logger-BDWwViGT.mjs";
|
|
3
3
|
import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
|
|
4
4
|
import { i as getOapiAccessToken, r as getAccessToken, t as DINGTALK_API } from "./utils-CIfI_3Jh.mjs";
|
|
@@ -771,7 +771,7 @@ async function sendMediaToDingTalk(params) {
|
|
|
771
771
|
});
|
|
772
772
|
}
|
|
773
773
|
let resolvedMediaUrl = mediaUrl;
|
|
774
|
-
const { toLocalPath } = await import("./media-
|
|
774
|
+
const { toLocalPath } = await import("./media-DEuF7r3G.mjs");
|
|
775
775
|
const _fs = await import("fs");
|
|
776
776
|
const _path = await import("path");
|
|
777
777
|
const directPath = toLocalPath(mediaUrl);
|
|
@@ -806,7 +806,7 @@ async function sendMediaToDingTalk(params) {
|
|
|
806
806
|
}
|
|
807
807
|
if (mediaType === "video") {
|
|
808
808
|
const videoMarker = `[DINGTALK_VIDEO]{"path":"${mediaUrl}"}[/DINGTALK_VIDEO]`;
|
|
809
|
-
const { processVideoMarkers } = await import("./media-
|
|
809
|
+
const { processVideoMarkers } = await import("./media-DEuF7r3G.mjs");
|
|
810
810
|
await processVideoMarkers(videoMarker, "", config, oapiToken, console, true, targetParam);
|
|
811
811
|
if (text?.trim()) {
|
|
812
812
|
const result = await sendProactive(config, targetParam, text, {
|
|
@@ -830,7 +830,7 @@ async function sendMediaToDingTalk(params) {
|
|
|
830
830
|
fileName,
|
|
831
831
|
fileType: ext || "file"
|
|
832
832
|
};
|
|
833
|
-
const { sendFileProactive } = await import("./media-
|
|
833
|
+
const { sendFileProactive } = await import("./media-DEuF7r3G.mjs");
|
|
834
834
|
await sendFileProactive(config, targetParam, fileInfo, uploadResult.mediaId, log);
|
|
835
835
|
return {
|
|
836
836
|
ok: true,
|