@jeik/dingtalk-connector 0.8.21-fix45 → 0.8.21-fix46
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 +17 -0
- package/README.en.md +9 -7
- package/README.md +10 -8
- package/dist/entry-bundled.mjs +1 -1
- package/dist/{gateway-methods-b4qQpdOB.mjs → gateway-methods-BTByJFxo.mjs} +1 -1
- package/dist/{gateway-methods-CCh-LutW.mjs → gateway-methods-CkBI4__L.mjs} +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{media-ClfWY5Cw.mjs → media-9LvaywGZ.mjs} +1 -1
- package/dist/{media-_qA1Qjt_.mjs → media-BMNRjseJ.mjs} +123 -28
- package/dist/{message-handler-BKmyuozN.mjs → message-handler-CytB34ZJ.mjs} +7 -7
- package/dist/{messaging-CpOPEOI8.mjs → messaging-ngF20yqM.mjs} +14 -6
- package/dist/{runtime-Dj8jSHjE.mjs → runtime-I36vhOzx.mjs} +3 -3
- package/docs/RELEASE_NOTES_V0.8.21-fix46.md +45 -0
- package/package.json +1 -1
- package/src/services/media.ts +199 -31
- package/src/services/messaging.ts +18 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ 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.21-fix46] - 2026-07-20
|
|
9
|
+
|
|
10
|
+
### 诊断 / Diagnostics
|
|
11
|
+
- ✨ **message 本地 MD 图灰图排查日志** — 恢复并加强 always-on `[DingTalk][LocalImage]` / `[MediaIdTrace]`:
|
|
12
|
+
- `sendTextToDingTalk` 入口 / 处理后 / API前
|
|
13
|
+
- `processImagesForOutbound` 扫描每张 `![]`(kind=local|http|mediaId、inCode、exists、size)
|
|
14
|
+
- 本地上传成败 / 远程下载上传 / `residualLocal` 残留告警
|
|
15
|
+
- MediaIdTrace 固定字段:`mediaIdCount` / `mdImgs` / `residualLocal` / preview
|
|
16
|
+
|
|
17
|
+
逻辑仍基于 fix45(图+下载同泡、只认 `![]`、代码块跳过)。
|
|
18
|
+
|
|
19
|
+
### 安装 / Install
|
|
20
|
+
```bash
|
|
21
|
+
npx -y @jeik/dingtalk-connector install --force
|
|
22
|
+
openclaw gateway restart
|
|
23
|
+
```
|
|
24
|
+
|
|
8
25
|
## [0.8.21-fix45] - 2026-07-20
|
|
9
26
|
|
|
10
27
|
生产稳定版 / Production-stable community release。
|
package/README.en.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<p>Community maintained fork of the official <strong>v0.8.20</strong> release, tracking and fixing bugs the official team hasn't addressed.<br/>
|
|
5
5
|
Identical to the official release in functionality — only community-critical fixes applied.</p>
|
|
6
6
|
|
|
7
|
-
<p><strong>Current published release: <a href="https://www.npmjs.com/package/@jeik/dingtalk-connector">@jeik/dingtalk-connector</a> v0.8.21-
|
|
7
|
+
<p><strong>Current published release: <a href="https://www.npmjs.com/package/@jeik/dingtalk-connector">@jeik/dingtalk-connector</a> v0.8.21-fix46</strong> (production-stable; install: <code>npx -y @jeik/dingtalk-connector install</code>).</p>
|
|
8
8
|
|
|
9
9
|
<p>
|
|
10
10
|
<a href="https://www.npmjs.com/package/@jeik/dingtalk-connector"><img src="https://img.shields.io/npm/v/@jeik/dingtalk-connector.svg?style=flat&colorA=18181B&colorB=28CF8D" alt="npm version" /></a>
|
|
@@ -23,21 +23,23 @@
|
|
|
23
23
|
|
|
24
24
|
## 🔧 Recent Updates
|
|
25
25
|
|
|
26
|
-
### 🚀 v0.8.21-
|
|
26
|
+
### 🚀 v0.8.21-fix46 · 2026-07-20 (current)
|
|
27
27
|
|
|
28
|
-
**Theme:
|
|
28
|
+
**Theme: always-on local MD image diagnostics (behavior same as fix45)**
|
|
29
29
|
|
|
30
30
|
| | Change |
|
|
31
31
|
|--|--------|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
| 📎 **Quoted AI cards** | Cache final card text for quote backfill |
|
|
35
|
-
| 🌐 **message media** | `media: "https://..."` download-then-upload |
|
|
32
|
+
| 🔍 **LocalImage logs** | message path: scan / exists / upload / residual |
|
|
33
|
+
| 🔍 **MediaIdTrace** | `mdImgs`, `residualLocal`, API-before preview |
|
|
36
34
|
|
|
37
35
|
```bash
|
|
38
36
|
npx -y @jeik/dingtalk-connector install --force && openclaw gateway restart
|
|
39
37
|
```
|
|
40
38
|
|
|
39
|
+
### 📦 v0.8.21-fix45 · 2026-07-20
|
|
40
|
+
|
|
41
|
+
Image + download link one bubble · only `![]` · quote cache · residual false-positive fix.
|
|
42
|
+
|
|
41
43
|
### 📦 v0.8.21-fix38 · 2026-07-20
|
|
42
44
|
|
|
43
45
|
Remote `media` download-then-upload.
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<p>基于官方 <strong>v0.8.20</strong> 的社区维护版本,由社区持续跟进修复官方无暇处理的 Bug。<br/>
|
|
5
5
|
功能与官方完全一致,拥有最快的修复速度,及时合并官方pr和个人发现的bug和社区急需的 Bug。</p>
|
|
6
6
|
|
|
7
|
-
<p><strong>当前发布版:<a href="https://www.npmjs.com/package/@jeik/dingtalk-connector">@jeik/dingtalk-connector</a> v0.8.21-
|
|
7
|
+
<p><strong>当前发布版:<a href="https://www.npmjs.com/package/@jeik/dingtalk-connector">@jeik/dingtalk-connector</a> v0.8.21-fix46</strong>(稳定生产可用;一键安装:`npx -y @jeik/dingtalk-connector install`;本地 tgz:`openclaw plugins install ./jeik-dingtalk-connector-0.8.21-fix46.tgz --force`)</p>
|
|
8
8
|
|
|
9
9
|
<p>
|
|
10
10
|
<a href="https://www.npmjs.com/package/@jeik/dingtalk-connector"><img src="https://img.shields.io/npm/v/@jeik/dingtalk-connector.svg?style=flat&colorA=18181B&colorB=28CF8D" alt="npm version" /></a>
|
|
@@ -23,21 +23,23 @@
|
|
|
23
23
|
|
|
24
24
|
## 🔧 最近更新
|
|
25
25
|
|
|
26
|
-
### 🚀 v0.8.21-
|
|
26
|
+
### 🚀 v0.8.21-fix46 · 2026-07-20(当前)
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
**主题:本地 MD 图灰图诊断日志(逻辑同 fix45)**
|
|
29
29
|
|
|
30
30
|
| | 改动 |
|
|
31
31
|
|--|------|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
| 📎 **引用 AI 卡** | 定稿缓存正文,引用时回填(`CardCache`) |
|
|
35
|
-
| 🌐 **message media** | `media: "https://..."` 下载再上传 |
|
|
32
|
+
| 🔍 **LocalImage 日志** | message 路径 always-on:扫描 / exists / 上传成败 / residual |
|
|
33
|
+
| 🔍 **MediaIdTrace** | `mdImgs`、`residualLocal`、API前 preview,方便对灰图 |
|
|
36
34
|
|
|
37
35
|
```bash
|
|
38
36
|
npx -y @jeik/dingtalk-connector install --force && openclaw gateway restart
|
|
39
37
|
```
|
|
40
38
|
|
|
39
|
+
### 📦 v0.8.21-fix45 · 2026-07-20
|
|
40
|
+
|
|
41
|
+
图 + 下载链接单气泡 · 只认 `![]` · 引用卡片缓存 · residual 误报修复。
|
|
42
|
+
|
|
41
43
|
### 📦 v0.8.21-fix38 · 2026-07-20
|
|
42
44
|
|
|
43
45
|
message 远程 `media` 下载上传。
|
|
@@ -64,7 +66,7 @@ message 远程 `media` 下载上传。
|
|
|
64
66
|
| 2026-06-28 | 上线 npm `@jeik/dingtalk-connector`;过程消息提前定稿修复 |
|
|
65
67
|
| 2026-05 | MD 直链/本地图;多轮刷屏;4.29+ 无文本输出;WebSocket 幻影重连 |
|
|
66
68
|
|
|
67
|
-
完整说明:[CHANGELOG.md](CHANGELOG.md) · [FIXES.md](FIXES.md) · [Release
|
|
69
|
+
完整说明:[CHANGELOG.md](CHANGELOG.md) · [FIXES.md](FIXES.md) · [Release fix46](docs/RELEASE_NOTES_V0.8.21-fix46.md)
|
|
68
70
|
|
|
69
71
|
---
|
|
70
72
|
|
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-BTByJFxo.mjs");
|
|
27
27
|
registerGatewayMethods(api);
|
|
28
28
|
}
|
|
29
29
|
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as registerGatewayMethods } from "./gateway-methods-
|
|
1
|
+
import { t as registerGatewayMethods } from "./gateway-methods-CkBI4__L.mjs";
|
|
2
2
|
export { registerGatewayMethods };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as resolveDingtalkAccount, t as listDingtalkAccountIds } from "./accounts-BAzdqkAV.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, d as finishAICard, i as sendProactive, s as buildBotMentionTable } from "./messaging-
|
|
4
|
+
import { c as prepareMultiBotMentions, d as finishAICard, i as sendProactive, s as buildBotMentionTable } from "./messaging-ngF20yqM.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.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as initDingtalkPluginConfigSchema, 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-I36vhOzx.mjs";
|
|
2
|
+
import { t as registerGatewayMethods } from "./gateway-methods-CkBI4__L.mjs";
|
|
3
3
|
//#region index.ts
|
|
4
4
|
/**
|
|
5
5
|
* 检测同一 plugin id 在多个路径被加载的情况。
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as toLocalPath, d as processLocalImages, f as processRawMediaPaths, h as sendFileProactive, o as hasResidualLocalMdImagesOutsideCode, p as processVideoMarkers } from "./media-
|
|
1
|
+
import { _ as toLocalPath, d as processLocalImages, f as processRawMediaPaths, h as sendFileProactive, o as hasResidualLocalMdImagesOutsideCode, p as processVideoMarkers } from "./media-BMNRjseJ.mjs";
|
|
2
2
|
export { hasResidualLocalMdImagesOutsideCode, processLocalImages, processRawMediaPaths, processVideoMarkers, sendFileProactive, toLocalPath };
|
|
@@ -68,8 +68,9 @@ function toLocalPath(raw) {
|
|
|
68
68
|
return filePath;
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
|
-
*
|
|
72
|
-
* verbose
|
|
71
|
+
* 本地图诊断:默认 console.log 一行(不走 log.warn,避免 [sendTextToDingTalk] 双前缀)。
|
|
72
|
+
* verbose=true 的细节(realpath 等)仅在 debug 时输出。
|
|
73
|
+
* 决策路径(匹配/exists/上传成败/残留)务必 always-on,方便查灰图。
|
|
73
74
|
*/
|
|
74
75
|
function logLocalImage(log, step, detail, verbose = false) {
|
|
75
76
|
const line = detail ? `[DingTalk][LocalImage] ${step} | ${detail}` : `[DingTalk][LocalImage] ${step}`;
|
|
@@ -81,21 +82,36 @@ function logLocalImage(log, step, detail, verbose = false) {
|
|
|
81
82
|
}
|
|
82
83
|
console.log(line);
|
|
83
84
|
}
|
|
84
|
-
/** 钉钉 mediaId
|
|
85
|
+
/** 钉钉 mediaId 形态(oapi 返回常带 @) */
|
|
85
86
|
const MEDIA_ID_RE = /@[A-Za-z0-9_-]{8,}/g;
|
|
87
|
+
function classifyMdImgTarget(target) {
|
|
88
|
+
const t = (target || "").trim();
|
|
89
|
+
if (!t) return "other";
|
|
90
|
+
if (t.startsWith("@") && !t.includes("/") && !t.includes("\\")) return "mediaId";
|
|
91
|
+
if (looksLikeRemoteUrl(t) || /^https?:\/\//i.test(t)) return "http";
|
|
92
|
+
if (isLocalImageRef(t)) return "local";
|
|
93
|
+
return "other";
|
|
94
|
+
}
|
|
86
95
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
96
|
+
* MediaId 追踪:关键节点 / force / 裸 mediaId 时输出。
|
|
97
|
+
* 固定字段:mediaIdCount / ids / BARE_ids / mdImgs / httpCount / residualLocal / preview
|
|
98
|
+
* (灰图排查:看 mdImgs 是 path:本地 还是 mediaId:@xxx,以及 residualLocal)
|
|
89
99
|
*/
|
|
90
100
|
function logMediaIdTrace(stage, content, extra, force = false) {
|
|
91
101
|
const text = typeof content === "string" ? content : "";
|
|
92
102
|
const ids = [...new Set([...text.matchAll(MEDIA_ID_RE)].map((m) => m[0]))];
|
|
93
|
-
const
|
|
103
|
+
const mdTargets = [...text.matchAll(LOCAL_IMAGE_RE)].map((m) => (m[2] || "").trim());
|
|
104
|
+
const inMd = new Set(mdTargets);
|
|
94
105
|
const bareIds = ids.filter((id) => !inMd.has(id));
|
|
106
|
+
const mdImgs = mdTargets.slice(0, 8).map((t) => {
|
|
107
|
+
return `${classifyMdImgTarget(t)}:${t.length > 100 ? `${t.slice(0, 100)}…` : t}`;
|
|
108
|
+
});
|
|
109
|
+
const residualLocal = mdTargets.filter((t) => isLocalImageRef(t));
|
|
110
|
+
const httpCount = (text.match(/https?:\/\//gi) || []).length;
|
|
95
111
|
const suspiciousBare = /type=image|sampleImage|photoURL/i.test(`${stage} ${extra || ""}`) ? [] : bareIds;
|
|
96
|
-
if (!force
|
|
97
|
-
const preview = text.replace(/\n/g, "\\n").slice(0,
|
|
98
|
-
console.log(`[DingTalk][MediaIdTrace] ${stage} | len=${text.length}` + (ids.length ? `
|
|
112
|
+
if (!(force || suspiciousBare.length > 0 || residualLocal.length > 0 || /API前|完成|:out|:in|after-process|processImages/i.test(stage))) return;
|
|
113
|
+
const preview = text.replace(/\n/g, "\\n").slice(0, 200);
|
|
114
|
+
console.log(`[DingTalk][MediaIdTrace] ${stage} | len=${text.length} mediaIdCount=${ids.length}` + (ids.length ? ` ids=[${ids.slice(0, 4).join(",")}]` : "") + ` BARE_ids=${suspiciousBare.length ? suspiciousBare.join(",") : "无"} mdImgs=[${mdImgs.length ? mdImgs.join(" || ") : "-"}] httpCount=${httpCount}` + (residualLocal.length ? ` residualLocal=${residualLocal.length}:${residualLocal.slice(0, 3).map((p) => p.slice(0, 80)).join("|")}` : " residualLocal=0") + (extra ? ` ${extra}` : "") + ` preview="${preview}"`);
|
|
99
115
|
}
|
|
100
116
|
/** Markdown 代码区域(围栏 ``` 与行内 `...`),其中的路径不应上传 */
|
|
101
117
|
function getMarkdownCodeRanges(content) {
|
|
@@ -124,21 +140,21 @@ async function uploadMediaToDingTalk(filePath, mediaType, oapiToken, maxSize = 2
|
|
|
124
140
|
const tag = `uploadMedia type=${mediaType}`;
|
|
125
141
|
try {
|
|
126
142
|
let absPath = toLocalPath(filePath);
|
|
127
|
-
logLocalImage(log, `${tag} 开始`, `path=${absPath}
|
|
143
|
+
logLocalImage(log, `${tag} 开始`, `path=${absPath}`);
|
|
128
144
|
if (!fs.existsSync(absPath)) {
|
|
129
145
|
logLocalImage(log, `${tag} 失败:文件不存在`, absPath);
|
|
130
146
|
return null;
|
|
131
147
|
}
|
|
132
148
|
try {
|
|
133
149
|
const real = fs.realpathSync(absPath);
|
|
134
|
-
if (real !== absPath) logLocalImage(log, `${tag} realpath`, `${absPath} → ${real}
|
|
150
|
+
if (real !== absPath) logLocalImage(log, `${tag} realpath`, `${absPath} → ${real}`);
|
|
135
151
|
absPath = real;
|
|
136
152
|
} catch (e) {
|
|
137
153
|
logLocalImage(log, `${tag} realpath跳过`, e?.message || String(e), true);
|
|
138
154
|
}
|
|
139
155
|
const stats = fs.statSync(absPath);
|
|
140
156
|
const fileSizeMB = (stats.size / (1024 * 1024)).toFixed(2);
|
|
141
|
-
logLocalImage(log, `${tag} 文件可读`, `size=${fileSizeMB}MB
|
|
157
|
+
logLocalImage(log, `${tag} 文件可读`, `size=${fileSizeMB}MB mode=${stats.mode}`);
|
|
142
158
|
const effectiveMax = mediaType === "image" ? Math.min(maxSize, 10 * 1024 * 1024) : mediaType === "voice" ? Math.min(maxSize, 2 * 1024 * 1024) : maxSize;
|
|
143
159
|
if (stats.size > effectiveMax) {
|
|
144
160
|
const maxSizeMB = (effectiveMax / (1024 * 1024)).toFixed(0);
|
|
@@ -197,17 +213,26 @@ async function uploadMediaToDingTalk(filePath, mediaType, oapiToken, maxSize = 2
|
|
|
197
213
|
*/
|
|
198
214
|
async function uploadLocalImageWithFallback(filePath, oapiToken, log) {
|
|
199
215
|
const absPath = toLocalPath(filePath);
|
|
200
|
-
|
|
216
|
+
const exists = fs.existsSync(absPath);
|
|
217
|
+
logLocalImage(log, "fallback 入口", `path=${absPath} exists=${exists}`);
|
|
218
|
+
if (!exists) {
|
|
201
219
|
logLocalImage(log, "fallback 失败:文件不存在", absPath);
|
|
202
220
|
return null;
|
|
203
221
|
}
|
|
222
|
+
logLocalImage(log, "fallback 第1次:直接上传", absPath);
|
|
204
223
|
let result = await uploadMediaToDingTalk(absPath, "image", oapiToken, 10 * 1024 * 1024, log);
|
|
205
|
-
if (result?.mediaId)
|
|
224
|
+
if (result?.mediaId) {
|
|
225
|
+
logLocalImage(log, "fallback 第1次成功", result.mediaId);
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
206
228
|
const ext = path.extname(absPath) || ".jpg";
|
|
207
229
|
const tmp = path.join(os.tmpdir(), `dingtalk-img-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext.toLowerCase()}`);
|
|
208
230
|
try {
|
|
209
231
|
fs.copyFileSync(absPath, tmp);
|
|
232
|
+
logLocalImage(log, "fallback 第2次:tmp重试", `${absPath} → ${tmp}`);
|
|
210
233
|
result = await uploadMediaToDingTalk(tmp, "image", oapiToken, 10 * 1024 * 1024, log);
|
|
234
|
+
if (result?.mediaId) logLocalImage(log, "fallback 第2次成功", result.mediaId);
|
|
235
|
+
else logLocalImage(log, "fallback 第2次仍失败", tmp);
|
|
211
236
|
return result;
|
|
212
237
|
} catch (err) {
|
|
213
238
|
logLocalImage(log, "fallback 拷贝/重试异常", `${absPath} err=${err?.message || err}`);
|
|
@@ -278,51 +303,109 @@ function replaceOutsideImageTargets(content, original, replacement) {
|
|
|
278
303
|
* (钉钉改写发生在「![] 与正文共用同一 http」时;![] 改成 mediaId 后即可同泡共存)
|
|
279
304
|
* - 本地:正文重复的本地路径换成 down.dingtalk.com 可点链接(仍同一条消息)
|
|
280
305
|
* - **不再拆第二条 text 消息**
|
|
306
|
+
*
|
|
307
|
+
* 日志约定(always-on,查灰图必看):
|
|
308
|
+
* - 开始 / 扫描每张 ![] / 本地 exists / 上传成败 / 远程下载 / 残留 / out
|
|
281
309
|
*/
|
|
282
310
|
async function processImagesForOutbound(content, oapiToken, log) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
311
|
+
logLocalImage(log, "processImagesForOutbound 开始", `contentLen=${content?.length ?? 0} hasToken=${!!oapiToken}`);
|
|
312
|
+
if (!oapiToken) {
|
|
313
|
+
logLocalImage(log, "processImagesForOutbound 跳过", "无 oapiToken(本地路径无法上传→必灰图)");
|
|
314
|
+
logMediaIdTrace("processImagesForOutbound:skip-no-token", content, void 0, true);
|
|
315
|
+
return {
|
|
316
|
+
text: content || "",
|
|
317
|
+
followUpUrls: []
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
if (!content || typeof content !== "string") {
|
|
321
|
+
logLocalImage(log, "processImagesForOutbound 跳过", "content 为空");
|
|
322
|
+
return {
|
|
323
|
+
text: content || "",
|
|
324
|
+
followUpUrls: []
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
logMediaIdTrace("processImagesForOutbound:in", content, void 0, true);
|
|
287
328
|
const codeRanges = getMarkdownCodeRanges(content);
|
|
329
|
+
if (codeRanges.length > 0) logLocalImage(log, "processImagesForOutbound 代码区域", `count=${codeRanges.length}(围栏/行内 code 内的图路径将跳过)`);
|
|
288
330
|
const mdMatches = [...content.matchAll(LOCAL_IMAGE_RE)];
|
|
331
|
+
const scanPreview = mdMatches.map((m, i) => {
|
|
332
|
+
const p = (m[2] || "").trim();
|
|
333
|
+
const inCode = m.index !== void 0 && isIndexInCodeRange(m.index, codeRanges);
|
|
334
|
+
const kind = classifyMdImgTarget(p);
|
|
335
|
+
return `#${i + 1} kind=${kind} inCode=${inCode} path=${p.slice(0, 120)}`;
|
|
336
|
+
});
|
|
337
|
+
logLocalImage(log, "processImagesForOutbound 扫描 ![]()", scanPreview.length ? scanPreview.join(" || ") : "正文中没有任何 markdown 图片语法");
|
|
289
338
|
let result = content;
|
|
290
339
|
/** 本地路径 → 可点 downloadUrl(仅替换正文中的裸路径,不拆消息) */
|
|
291
340
|
const localPathToDl = /* @__PURE__ */ new Map();
|
|
292
341
|
let uploadCount = 0;
|
|
342
|
+
let localTried = 0;
|
|
343
|
+
let remoteTried = 0;
|
|
344
|
+
let skippedCode = 0;
|
|
345
|
+
let skippedOther = 0;
|
|
293
346
|
for (const match of mdMatches) {
|
|
294
|
-
if (match.index !== void 0 && isIndexInCodeRange(match.index, codeRanges))
|
|
347
|
+
if (match.index !== void 0 && isIndexInCodeRange(match.index, codeRanges)) {
|
|
348
|
+
skippedCode++;
|
|
349
|
+
logLocalImage(log, "processImagesForOutbound 跳过代码块内", (match[2] || "").trim().slice(0, 120));
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
295
352
|
const [fullMatch, alt, rawPath] = match;
|
|
296
353
|
const cleanPath = (rawPath || "").replace(/\\ /g, " ").trim();
|
|
297
354
|
if (!cleanPath) continue;
|
|
298
355
|
if (isLocalImageRef(cleanPath)) {
|
|
356
|
+
localTried++;
|
|
299
357
|
const absPath = toLocalPath(cleanPath);
|
|
358
|
+
let exists = false;
|
|
359
|
+
let sizeMB = "-";
|
|
360
|
+
try {
|
|
361
|
+
exists = fs.existsSync(absPath);
|
|
362
|
+
if (exists) sizeMB = (fs.statSync(absPath).size / (1024 * 1024)).toFixed(2);
|
|
363
|
+
} catch (e) {
|
|
364
|
+
logLocalImage(log, "processImagesForOutbound 本地 stat 异常", e?.message || String(e));
|
|
365
|
+
}
|
|
366
|
+
logLocalImage(log, "processImagesForOutbound 处理本地图", `alt=${alt || "-"} raw=${cleanPath.slice(0, 120)} abs=${absPath} exists=${exists} sizeMB=${sizeMB}`);
|
|
367
|
+
if (!exists) {
|
|
368
|
+
result = result.split(fullMatch).join(`📷 *图片文件不存在(${path.basename(absPath)})*`);
|
|
369
|
+
logLocalImage(log, "processImagesForOutbound 本地文件不存在→失败提示", absPath);
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
300
372
|
try {
|
|
301
373
|
const uploadResult = await uploadLocalImageWithFallback(absPath, oapiToken, log);
|
|
302
374
|
if (uploadResult?.mediaId) {
|
|
303
|
-
|
|
375
|
+
const replacement = ``;
|
|
376
|
+
result = result.split(fullMatch).join(replacement);
|
|
304
377
|
uploadCount++;
|
|
305
378
|
if (uploadResult.downloadUrl) {
|
|
306
379
|
localPathToDl.set(cleanPath, uploadResult.downloadUrl);
|
|
307
380
|
localPathToDl.set(absPath, uploadResult.downloadUrl);
|
|
308
381
|
}
|
|
309
|
-
|
|
310
|
-
|
|
382
|
+
logLocalImage(log, "processImagesForOutbound 本地→mediaId 成功", `${absPath} → ${uploadResult.mediaId}`);
|
|
383
|
+
} else {
|
|
384
|
+
result = result.split(fullMatch).join(`📷 *图片上传失败(${path.basename(absPath)})*`);
|
|
385
|
+
logLocalImage(log, "processImagesForOutbound 本地上传失败→失败提示", absPath);
|
|
386
|
+
}
|
|
387
|
+
} catch (err) {
|
|
311
388
|
result = result.split(fullMatch).join(`📷 *图片上传异常(${path.basename(toLocalPath(cleanPath))})*`);
|
|
389
|
+
logLocalImage(log, "processImagesForOutbound 本地上传异常", `${absPath} err=${err?.message || err}`);
|
|
312
390
|
}
|
|
313
391
|
continue;
|
|
314
392
|
}
|
|
315
393
|
if (looksLikeRemoteUrl(cleanPath)) {
|
|
394
|
+
remoteTried++;
|
|
395
|
+
logLocalImage(log, "processImagesForOutbound 远程图", cleanPath.slice(0, 160));
|
|
316
396
|
let tmp = null;
|
|
317
397
|
try {
|
|
318
398
|
tmp = await downloadRemoteUrlToTemp(cleanPath);
|
|
319
|
-
if (!tmp)
|
|
399
|
+
if (!tmp) {
|
|
400
|
+
logLocalImage(log, "processImagesForOutbound 远程下载失败(保留原![]URL)", cleanPath.slice(0, 120));
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
320
403
|
const uploadResult = await uploadLocalImageWithFallback(tmp, oapiToken, log);
|
|
321
404
|
if (uploadResult?.mediaId) {
|
|
322
405
|
result = result.split(fullMatch).join(``);
|
|
323
406
|
uploadCount++;
|
|
324
|
-
|
|
325
|
-
}
|
|
407
|
+
logLocalImage(log, "processImagesForOutbound 远程→mediaId 下载链保留原URL", `${cleanPath.slice(0, 80)} → ${uploadResult.mediaId}`);
|
|
408
|
+
} else logLocalImage(log, "processImagesForOutbound 远程上传失败(保留原![]URL)", cleanPath.slice(0, 120));
|
|
326
409
|
} catch (err) {
|
|
327
410
|
logLocalImage(log, "远程图处理失败", err?.message || String(err));
|
|
328
411
|
} finally {
|
|
@@ -330,11 +413,23 @@ async function processImagesForOutbound(content, oapiToken, log) {
|
|
|
330
413
|
fs.unlinkSync(tmp);
|
|
331
414
|
} catch {}
|
|
332
415
|
}
|
|
416
|
+
continue;
|
|
333
417
|
}
|
|
418
|
+
skippedOther++;
|
|
419
|
+
logLocalImage(log, "processImagesForOutbound 跳过非本地非远程", `kind=${classifyMdImgTarget(cleanPath)} path=${cleanPath.slice(0, 120)}`);
|
|
420
|
+
}
|
|
421
|
+
for (const [p, dl] of localPathToDl) {
|
|
422
|
+
const before = result;
|
|
423
|
+
result = replaceOutsideImageTargets(result, p, dl);
|
|
424
|
+
if (before !== result) logLocalImage(log, "processImagesForOutbound 正文裸本地路径→downloadUrl", `${p.slice(0, 80)} → ${dl}`);
|
|
334
425
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
426
|
+
const residual = [...result.matchAll(LOCAL_IMAGE_RE)].filter((m) => {
|
|
427
|
+
if (m.index !== void 0 && isIndexInCodeRange(m.index, getMarkdownCodeRanges(result))) return false;
|
|
428
|
+
return isLocalImageRef((m[2] || "").trim());
|
|
429
|
+
}).map((m) => (m[2] || "").trim());
|
|
430
|
+
logLocalImage(log, "processImagesForOutbound 完成", `uploads=${uploadCount} localTried=${localTried} remoteTried=${remoteTried} skippedCode=${skippedCode} skippedOther=${skippedOther} outLen=${result.length}` + (residual.length ? ` ⚠️residualLocal=${residual.length}:${residual.slice(0, 3).join("|")}` : " residualLocal=0"));
|
|
431
|
+
if (residual.length) logLocalImage(log, "processImagesForOutbound ⚠️仍有代码块外本地路径未替换(钉钉必灰图)", residual.join(" | "));
|
|
432
|
+
logMediaIdTrace("processImagesForOutbound:out", result, void 0, true);
|
|
338
433
|
return {
|
|
339
434
|
text: result,
|
|
340
435
|
followUpUrls: []
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { a as resolveDingtalkAccount } from "./accounts-BAzdqkAV.mjs";
|
|
2
|
-
import { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-
|
|
2
|
+
import { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-I36vhOzx.mjs";
|
|
3
3
|
import { n as createLoggerFromConfig } from "./logger-mZ9OSbmD.mjs";
|
|
4
4
|
import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
|
|
5
5
|
import { i as getOapiAccessToken } from "./utils-CIfI_3Jh.mjs";
|
|
6
|
-
import { S as finalClean, _ as FINAL_TAG, a as sendTextMessage, b as estimateTokens, d as finishAICard, g as unregisterActiveCard, h as streamAICard, i as sendProactive, m as registerActiveCard, p as isQpsLimitError, r as sendMessage, t as sendMarkdownMessage, u as createAICardForTarget, v as PROCESS_TAG, x as extractFinal, y as displayClean } from "./messaging-
|
|
6
|
+
import { S as finalClean, _ as FINAL_TAG, a as sendTextMessage, b as estimateTokens, d as finishAICard, g as unregisterActiveCard, h as streamAICard, i as sendProactive, m as registerActiveCard, p as isQpsLimitError, r as sendMessage, t as sendMarkdownMessage, u as createAICardForTarget, v as PROCESS_TAG, x as extractFinal, y as displayClean } from "./messaging-ngF20yqM.mjs";
|
|
7
7
|
import { a as QUEUE_BUSY_ACK_PHRASES, n as normalizeSlashCommand, t as buildSessionContext } from "./session-DJ4jYqPv.mjs";
|
|
8
8
|
import { d as recallEmotionReply, o as getAccessToken, r as addEmotionReply, s as getOapiAccessToken$1, t as DINGTALK_API } from "./utils-legacy-CALCPP1t.mjs";
|
|
9
9
|
import "./chunk-upload-BBQgGtcZ.mjs";
|
|
@@ -62,7 +62,7 @@ function resolveAgentWorkspaceDir(cfg, agentId) {
|
|
|
62
62
|
* 直接复用 media.ts 权威实现,避免 reply 路径与 message 路径两套逻辑分叉。
|
|
63
63
|
*/
|
|
64
64
|
async function processLocalImages(content, oapiToken, log) {
|
|
65
|
-
const { processLocalImages: impl } = await import("./media-
|
|
65
|
+
const { processLocalImages: impl } = await import("./media-9LvaywGZ.mjs");
|
|
66
66
|
return impl(content, oapiToken, log);
|
|
67
67
|
}
|
|
68
68
|
//#endregion
|
|
@@ -581,14 +581,14 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
581
581
|
finalText = await processLocalImages(finalText, oapiToken, log);
|
|
582
582
|
if (beforeImg !== finalText) console.log(`[DingTalk][LocalImage] closeStreaming 图片处理 | ${beforeImg.length}→${finalText.length}`);
|
|
583
583
|
try {
|
|
584
|
-
const { hasResidualLocalMdImagesOutsideCode } = await import("./media-
|
|
584
|
+
const { hasResidualLocalMdImagesOutsideCode } = await import("./media-9LvaywGZ.mjs");
|
|
585
585
|
if (hasResidualLocalMdImagesOutsideCode(finalText)) console.warn(`[DingTalk][LocalImage] closeStreaming 定稿后仍有代码块外的本地 MD 图未上传,钉钉可能灰图`);
|
|
586
586
|
} catch {}
|
|
587
587
|
finalText = await processVideoMarkers(finalText, "", account.config, oapiToken, log, true, target);
|
|
588
588
|
finalText = await processAudioMarkers(finalText, "", account.config, oapiToken, log, true, target);
|
|
589
589
|
finalText = await uploadAndReplaceFileMarkers(finalText, "", account.config, oapiToken, log, true, target);
|
|
590
590
|
log.info(`[DingTalk][closeStreaming] 准备调用 processRawMediaPaths`);
|
|
591
|
-
const { processRawMediaPaths } = await import("./media-
|
|
591
|
+
const { processRawMediaPaths } = await import("./media-9LvaywGZ.mjs");
|
|
592
592
|
finalText = await processRawMediaPaths(finalText, account.config, oapiToken, log, target);
|
|
593
593
|
log.info(`[DingTalk][closeStreaming] processRawMediaPaths 处理完成`);
|
|
594
594
|
} else log.warn(`[DingTalk][closeStreaming] oapiToken 为空,跳过媒体处理`);
|
|
@@ -744,7 +744,7 @@ function createDingtalkReplyDispatcher(params) {
|
|
|
744
744
|
const oapiToken = await getOapiAccessToken(account.config);
|
|
745
745
|
if (oapiToken) {
|
|
746
746
|
log.info(`[DingTalk][deliver] 检测到 final 响应,准备处理裸露文件路径`);
|
|
747
|
-
const { processRawMediaPaths } = await import("./media-
|
|
747
|
+
const { processRawMediaPaths } = await import("./media-9LvaywGZ.mjs");
|
|
748
748
|
text = await processRawMediaPaths(text, account.config, oapiToken, log, target);
|
|
749
749
|
log.info(`[DingTalk][deliver] 裸露文件路径处理完成`);
|
|
750
750
|
}
|
|
@@ -2184,7 +2184,7 @@ async function handleDingTalkMessageInternal(params) {
|
|
|
2184
2184
|
finalText = await processVideoMarkers(finalText, "", config, oapiToken, log, true, mediaTarget);
|
|
2185
2185
|
finalText = await processAudioMarkers(finalText, "", config, oapiToken, log, true, mediaTarget);
|
|
2186
2186
|
finalText = await uploadAndReplaceFileMarkers(finalText, "", config, oapiToken, log, true, mediaTarget);
|
|
2187
|
-
const { processRawMediaPaths } = await import("./media-
|
|
2187
|
+
const { processRawMediaPaths } = await import("./media-9LvaywGZ.mjs");
|
|
2188
2188
|
finalText = await processRawMediaPaths(finalText, config, oapiToken, log, mediaTarget);
|
|
2189
2189
|
}
|
|
2190
2190
|
const textToSend = finalText.trim() || "✅ 任务执行完成(无文本输出)";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as logMediaIdTrace, d as processLocalImages, u as processImagesForOutbound, y as uploadMediaToDingTalk } from "./media-
|
|
1
|
+
import { c as logMediaIdTrace, d as processLocalImages, u as processImagesForOutbound, y as uploadMediaToDingTalk } from "./media-BMNRjseJ.mjs";
|
|
2
2
|
import { n as createLoggerFromConfig } from "./logger-mZ9OSbmD.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";
|
|
@@ -848,7 +848,7 @@ async function resolveMediaSourceToLocalFile(mediaUrl, mediaLocalRoots, log) {
|
|
|
848
848
|
cleanup: tmp
|
|
849
849
|
};
|
|
850
850
|
}
|
|
851
|
-
const { toLocalPath } = await import("./media-
|
|
851
|
+
const { toLocalPath } = await import("./media-9LvaywGZ.mjs");
|
|
852
852
|
const _fs = await import("fs");
|
|
853
853
|
const _path = await import("path");
|
|
854
854
|
let resolved = toLocalPath(mediaUrl);
|
|
@@ -883,14 +883,22 @@ async function sendTextToDingTalk(params) {
|
|
|
883
883
|
};
|
|
884
884
|
}
|
|
885
885
|
const targetParam = resolveOutboundTarget(target);
|
|
886
|
+
console.log(`[DingTalk][LocalImage] sendTextToDingTalk 入口 | target=${target} textLen=${text?.length ?? 0}`);
|
|
887
|
+
logMediaIdTrace("sendTextToDingTalk:in", text, `target=${target}`, true);
|
|
888
|
+
const beforeLen = text?.length ?? 0;
|
|
886
889
|
try {
|
|
887
890
|
const oapiToken = await getOapiAccessToken(config);
|
|
888
|
-
|
|
891
|
+
console.log(`[DingTalk][LocalImage] sendTextToDingTalk 入口 | target=${target} textLen=${beforeLen} hasToken=${!!oapiToken}`);
|
|
892
|
+
if (oapiToken && text) {
|
|
893
|
+
text = (await processImagesForOutbound(text, oapiToken, log)).text;
|
|
894
|
+
console.log(`[DingTalk][LocalImage] sendTextToDingTalk 处理后 | ${beforeLen}→${text.length} 字`);
|
|
895
|
+
logMediaIdTrace("sendTextToDingTalk:after-process", text, void 0, true);
|
|
896
|
+
} else if (!oapiToken) console.warn(`[DingTalk][LocalImage] sendTextToDingTalk 无 oapiToken,跳过图片上传(本地 MD 图会灰)`);
|
|
889
897
|
} catch (err) {
|
|
890
898
|
console.warn(`[DingTalk][LocalImage] sendTextToDingTalk 图片处理失败: ${err?.message || err}`);
|
|
891
899
|
}
|
|
892
900
|
const msgType = shouldSendAsMarkdown(text) ? "markdown" : "text";
|
|
893
|
-
logMediaIdTrace("sendTextToDingTalk:API前", text, `msgType=${msgType}
|
|
901
|
+
logMediaIdTrace("sendTextToDingTalk:API前", text, `msgType=${msgType}`, true);
|
|
894
902
|
return sendProactive(config, targetParam, text, {
|
|
895
903
|
msgType,
|
|
896
904
|
replyToId,
|
|
@@ -1113,7 +1121,7 @@ async function sendMediaToDingTalk(params) {
|
|
|
1113
1121
|
const fileName = mediaUrl.split("/").filter(Boolean).pop()?.split("?")[0] || "file";
|
|
1114
1122
|
if (mediaType === "video") {
|
|
1115
1123
|
const videoMarker = `[DINGTALK_VIDEO]{"path":"${resolvedMediaUrl}"}[/DINGTALK_VIDEO]`;
|
|
1116
|
-
const { processVideoMarkers } = await import("./media-
|
|
1124
|
+
const { processVideoMarkers } = await import("./media-9LvaywGZ.mjs");
|
|
1117
1125
|
await processVideoMarkers(videoMarker, "", config, oapiToken, console, true, targetParam);
|
|
1118
1126
|
return {
|
|
1119
1127
|
ok: true,
|
|
@@ -1132,7 +1140,7 @@ async function sendMediaToDingTalk(params) {
|
|
|
1132
1140
|
fileName,
|
|
1133
1141
|
fileType: ext && ext.length < 12 ? ext : "file"
|
|
1134
1142
|
};
|
|
1135
|
-
const { sendFileProactive } = await import("./media-
|
|
1143
|
+
const { sendFileProactive } = await import("./media-9LvaywGZ.mjs");
|
|
1136
1144
|
await sendFileProactive(config, targetParam, fileInfo, uploadResult.mediaId, log);
|
|
1137
1145
|
return {
|
|
1138
1146
|
ok: true,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { b as __exportAll } from "./media-
|
|
1
|
+
import { b as __exportAll } from "./media-BMNRjseJ.mjs";
|
|
2
2
|
import { a as resolveDingtalkAccount, c as addWildcardAllowFrom, d as hasConfiguredSecretInput, f as normalizeAccountId, l as createDefaultChannelRuntimeState, m as resolveDefaultGroupPolicy, o as resolveDingtalkCredentials, p as resolveAllowlistProviderRuntimeGroupPolicy, r as resolveDefaultDingtalkAccountId, s as DEFAULT_ACCOUNT_ID, t as listDingtalkAccountIds, u as formatDocsLink } from "./accounts-BAzdqkAV.mjs";
|
|
3
3
|
import { t as createLogger } from "./logger-mZ9OSbmD.mjs";
|
|
4
4
|
import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
|
|
5
5
|
import "./utils-CIfI_3Jh.mjs";
|
|
6
|
-
import { S as finalClean, f as getActiveCardForConversation, n as sendMediaToDingTalk, o as sendTextToDingTalk } from "./messaging-
|
|
6
|
+
import { S as finalClean, f as getActiveCardForConversation, n as sendMediaToDingTalk, o as sendTextToDingTalk } from "./messaging-ngF20yqM.mjs";
|
|
7
7
|
import { createRequire } from "node:module";
|
|
8
8
|
import { z, z as z$1 } from "zod";
|
|
9
9
|
//#region src/secret-input.ts
|
|
@@ -930,7 +930,7 @@ async function monitorDingtalkProvider(opts = {}) {
|
|
|
930
930
|
const log = createLogger(cfg.channels?.["dingtalk-connector"]?.debug ?? false);
|
|
931
931
|
const [accountsModule, monitorAccountModule, monitorSingleModule] = await Promise.all([
|
|
932
932
|
import("./accounts-BQptOmgB.mjs"),
|
|
933
|
-
import("./message-handler-
|
|
933
|
+
import("./message-handler-CytB34ZJ.mjs"),
|
|
934
934
|
import("./connection-DHHFFNQJ.mjs")
|
|
935
935
|
]);
|
|
936
936
|
const { resolveDingtalkAccount, listEnabledDingtalkAccounts } = accountsModule;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Release Notes — v0.8.21-fix46(2026-07-20)
|
|
2
|
+
|
|
3
|
+
生产稳定版 / Production-stable community build of `@jeik/dingtalk-connector`。
|
|
4
|
+
|
|
5
|
+
在 **fix45** 行为不变的基础上,补强 **message 本地 MD 图** 诊断日志,方便排查灰图。
|
|
6
|
+
|
|
7
|
+
## 安装 / Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx -y @jeik/dingtalk-connector install --force
|
|
11
|
+
openclaw gateway restart
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
本地 tgz:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
openclaw plugins install ./jeik-dingtalk-connector-0.8.21-fix46.tgz --force
|
|
18
|
+
openclaw gateway restart
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 本版要点 / Highlights
|
|
22
|
+
|
|
23
|
+
| 领域 | 说明 |
|
|
24
|
+
|------|------|
|
|
25
|
+
| 诊断日志 | always-on 本地图流水线:匹配 → exists → 上传 → API 前 payload → residual |
|
|
26
|
+
| MediaIdTrace | `mdImgs=[local:…\|mediaId:…]`、`residualLocal`、preview |
|
|
27
|
+
| 业务逻辑 | **与 fix45 相同**(图+下载同泡、只认 `![]`、代码块跳过、CardCache) |
|
|
28
|
+
|
|
29
|
+
## 查灰图时看哪些行
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
[DingTalk][LocalImage] sendTextToDingTalk 入口 | ...
|
|
33
|
+
[DingTalk][LocalImage] processImagesForOutbound 扫描 ![]() | #1 kind=local exists=...
|
|
34
|
+
[DingTalk][LocalImage] processImagesForOutbound 本地→mediaId 成功 | ... → @lADP...
|
|
35
|
+
[DingTalk][MediaIdTrace] sendTextToDingTalk:API前 | mdImgs=[mediaId:@lADP...] residualLocal=0
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- `kind=local` 但 `exists=false` → 路径在 gateway 机上不存在
|
|
39
|
+
- 上传失败 → 会看到失败提示 / fallback 第2次
|
|
40
|
+
- API前仍是 `local:/tmp/...` 或 `residualLocal>0` → 未换成 mediaId
|
|
41
|
+
- API前已是 `mediaId:@lADP...` 仍灰 → 偏钉钉渲染/账号侧
|
|
42
|
+
|
|
43
|
+
## 升级
|
|
44
|
+
|
|
45
|
+
从 fix45 直接 `--force` 安装即可。
|
package/package.json
CHANGED
package/src/services/media.ts
CHANGED
|
@@ -211,8 +211,9 @@ export interface UploadResult {
|
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
/**
|
|
214
|
-
*
|
|
215
|
-
* verbose
|
|
214
|
+
* 本地图诊断:默认 console.log 一行(不走 log.warn,避免 [sendTextToDingTalk] 双前缀)。
|
|
215
|
+
* verbose=true 的细节(realpath 等)仅在 debug 时输出。
|
|
216
|
+
* 决策路径(匹配/exists/上传成败/残留)务必 always-on,方便查灰图。
|
|
216
217
|
*/
|
|
217
218
|
function logLocalImage(log: any, step: string, detail?: string, verbose = false) {
|
|
218
219
|
const line = detail
|
|
@@ -229,12 +230,22 @@ function logLocalImage(log: any, step: string, detail?: string, verbose = false)
|
|
|
229
230
|
console.log(line);
|
|
230
231
|
}
|
|
231
232
|
|
|
232
|
-
/** 钉钉 mediaId
|
|
233
|
+
/** 钉钉 mediaId 形态(oapi 返回常带 @) */
|
|
233
234
|
const MEDIA_ID_RE = /@[A-Za-z0-9_-]{8,}/g;
|
|
234
235
|
|
|
236
|
+
function classifyMdImgTarget(target: string): "mediaId" | "http" | "local" | "other" {
|
|
237
|
+
const t = (target || "").trim();
|
|
238
|
+
if (!t) return "other";
|
|
239
|
+
if (t.startsWith("@") && !t.includes("/") && !t.includes("\\")) return "mediaId";
|
|
240
|
+
if (looksLikeRemoteUrl(t) || /^https?:\/\//i.test(t)) return "http";
|
|
241
|
+
if (isLocalImageRef(t)) return "local";
|
|
242
|
+
return "other";
|
|
243
|
+
}
|
|
244
|
+
|
|
235
245
|
/**
|
|
236
|
-
*
|
|
237
|
-
*
|
|
246
|
+
* MediaId 追踪:关键节点 / force / 裸 mediaId 时输出。
|
|
247
|
+
* 固定字段:mediaIdCount / ids / BARE_ids / mdImgs / httpCount / residualLocal / preview
|
|
248
|
+
* (灰图排查:看 mdImgs 是 path:本地 还是 mediaId:@xxx,以及 residualLocal)
|
|
238
249
|
*/
|
|
239
250
|
export function logMediaIdTrace(
|
|
240
251
|
stage: string,
|
|
@@ -244,23 +255,42 @@ export function logMediaIdTrace(
|
|
|
244
255
|
): void {
|
|
245
256
|
const text = typeof content === "string" ? content : "";
|
|
246
257
|
const ids = [...new Set([...text.matchAll(MEDIA_ID_RE)].map((m) => m[0]))];
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
);
|
|
258
|
+
const mdTargets = [...text.matchAll(LOCAL_IMAGE_RE)].map((m) => (m[2] || "").trim());
|
|
259
|
+
const inMd = new Set(mdTargets);
|
|
250
260
|
const bareIds = ids.filter((id) => !inMd.has(id));
|
|
261
|
+
const mdImgs = mdTargets.slice(0, 8).map((t) => {
|
|
262
|
+
const kind = classifyMdImgTarget(t);
|
|
263
|
+
const short = t.length > 100 ? `${t.slice(0, 100)}…` : t;
|
|
264
|
+
return `${kind}:${short}`;
|
|
265
|
+
});
|
|
266
|
+
const residualLocal = mdTargets.filter((t) => isLocalImageRef(t));
|
|
267
|
+
const httpCount = (text.match(/https?:\/\//gi) || []).length;
|
|
251
268
|
// sampleImageMsg 的 photoURL 就是裸 mediaId,不算异常
|
|
252
269
|
const isImageMsg = /type=image|sampleImage|photoURL/i.test(`${stage} ${extra || ""}`);
|
|
253
270
|
const suspiciousBare = isImageMsg ? [] : bareIds;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
271
|
+
const always =
|
|
272
|
+
force ||
|
|
273
|
+
suspiciousBare.length > 0 ||
|
|
274
|
+
residualLocal.length > 0 ||
|
|
275
|
+
/API前|完成|:out|:in|after-process|processImages/i.test(stage);
|
|
276
|
+
if (!always) return;
|
|
277
|
+
|
|
278
|
+
const preview = text.replace(/\n/g, "\\n").slice(0, 200);
|
|
258
279
|
console.log(
|
|
259
280
|
`[DingTalk][MediaIdTrace] ${stage} | len=${text.length}` +
|
|
260
|
-
|
|
261
|
-
(
|
|
281
|
+
` mediaIdCount=${ids.length}` +
|
|
282
|
+
(ids.length ? ` ids=[${ids.slice(0, 4).join(",")}]` : "") +
|
|
283
|
+
` BARE_ids=${suspiciousBare.length ? suspiciousBare.join(",") : "无"}` +
|
|
284
|
+
` mdImgs=[${mdImgs.length ? mdImgs.join(" || ") : "-"}]` +
|
|
285
|
+
` httpCount=${httpCount}` +
|
|
286
|
+
(residualLocal.length
|
|
287
|
+
? ` residualLocal=${residualLocal.length}:${residualLocal
|
|
288
|
+
.slice(0, 3)
|
|
289
|
+
.map((p) => p.slice(0, 80))
|
|
290
|
+
.join("|")}`
|
|
291
|
+
: " residualLocal=0") +
|
|
262
292
|
(extra ? ` ${extra}` : "") +
|
|
263
|
-
`
|
|
293
|
+
` preview="${preview}"`,
|
|
264
294
|
);
|
|
265
295
|
}
|
|
266
296
|
|
|
@@ -298,7 +328,8 @@ export async function uploadMediaToDingTalk(
|
|
|
298
328
|
const tag = `uploadMedia type=${mediaType}`;
|
|
299
329
|
try {
|
|
300
330
|
let absPath = toLocalPath(filePath);
|
|
301
|
-
|
|
331
|
+
// 上传起止 always-on:灰图时必须能看到是否走到 OAPI、文件是否可读
|
|
332
|
+
logLocalImage(log, `${tag} 开始`, `path=${absPath}`);
|
|
302
333
|
|
|
303
334
|
if (!fs.existsSync(absPath)) {
|
|
304
335
|
logLocalImage(log, `${tag} 失败:文件不存在`, absPath);
|
|
@@ -309,7 +340,7 @@ export async function uploadMediaToDingTalk(
|
|
|
309
340
|
try {
|
|
310
341
|
const real = fs.realpathSync(absPath);
|
|
311
342
|
if (real !== absPath) {
|
|
312
|
-
logLocalImage(log, `${tag} realpath`, `${absPath} → ${real}
|
|
343
|
+
logLocalImage(log, `${tag} realpath`, `${absPath} → ${real}`);
|
|
313
344
|
}
|
|
314
345
|
absPath = real;
|
|
315
346
|
} catch (e: any) {
|
|
@@ -319,7 +350,7 @@ export async function uploadMediaToDingTalk(
|
|
|
319
350
|
// 检查文件大小
|
|
320
351
|
const stats = fs.statSync(absPath);
|
|
321
352
|
const fileSizeMB = (stats.size / (1024 * 1024)).toFixed(2);
|
|
322
|
-
logLocalImage(log, `${tag} 文件可读`, `size=${fileSizeMB}MB
|
|
353
|
+
logLocalImage(log, `${tag} 文件可读`, `size=${fileSizeMB}MB mode=${stats.mode}`);
|
|
323
354
|
|
|
324
355
|
// 钉钉 image 类型官方上限约 10MB;调用方若传入更大 limit 也按类型收紧
|
|
325
356
|
const effectiveMax =
|
|
@@ -429,13 +460,17 @@ export async function uploadLocalImageWithFallback(
|
|
|
429
460
|
log?: any,
|
|
430
461
|
): Promise<UploadResult | null> {
|
|
431
462
|
const absPath = toLocalPath(filePath);
|
|
432
|
-
|
|
463
|
+
const exists = fs.existsSync(absPath);
|
|
464
|
+
logLocalImage(log, "fallback 入口", `path=${absPath} exists=${exists}`);
|
|
465
|
+
if (!exists) {
|
|
433
466
|
logLocalImage(log, "fallback 失败:文件不存在", absPath);
|
|
434
467
|
return null;
|
|
435
468
|
}
|
|
436
469
|
|
|
470
|
+
logLocalImage(log, "fallback 第1次:直接上传", absPath);
|
|
437
471
|
let result = await uploadMediaToDingTalk(absPath, "image", oapiToken, 10 * 1024 * 1024, log);
|
|
438
472
|
if (result?.mediaId) {
|
|
473
|
+
logLocalImage(log, "fallback 第1次成功", result.mediaId);
|
|
439
474
|
return result;
|
|
440
475
|
}
|
|
441
476
|
|
|
@@ -447,7 +482,13 @@ export async function uploadLocalImageWithFallback(
|
|
|
447
482
|
);
|
|
448
483
|
try {
|
|
449
484
|
fs.copyFileSync(absPath, tmp);
|
|
485
|
+
logLocalImage(log, "fallback 第2次:tmp重试", `${absPath} → ${tmp}`);
|
|
450
486
|
result = await uploadMediaToDingTalk(tmp, "image", oapiToken, 10 * 1024 * 1024, log);
|
|
487
|
+
if (result?.mediaId) {
|
|
488
|
+
logLocalImage(log, "fallback 第2次成功", result.mediaId);
|
|
489
|
+
} else {
|
|
490
|
+
logLocalImage(log, "fallback 第2次仍失败", tmp);
|
|
491
|
+
}
|
|
451
492
|
return result;
|
|
452
493
|
} catch (err: any) {
|
|
453
494
|
logLocalImage(
|
|
@@ -543,68 +584,158 @@ function replaceOutsideImageTargets(
|
|
|
543
584
|
* (钉钉改写发生在「![] 与正文共用同一 http」时;![] 改成 mediaId 后即可同泡共存)
|
|
544
585
|
* - 本地:正文重复的本地路径换成 down.dingtalk.com 可点链接(仍同一条消息)
|
|
545
586
|
* - **不再拆第二条 text 消息**
|
|
587
|
+
*
|
|
588
|
+
* 日志约定(always-on,查灰图必看):
|
|
589
|
+
* - 开始 / 扫描每张 ![] / 本地 exists / 上传成败 / 远程下载 / 残留 / out
|
|
546
590
|
*/
|
|
547
591
|
export async function processImagesForOutbound(
|
|
548
592
|
content: string,
|
|
549
593
|
oapiToken: string | null,
|
|
550
594
|
log?: any,
|
|
551
595
|
): Promise<ProcessImagesOutboundResult> {
|
|
552
|
-
|
|
596
|
+
logLocalImage(
|
|
597
|
+
log,
|
|
598
|
+
"processImagesForOutbound 开始",
|
|
599
|
+
`contentLen=${content?.length ?? 0} hasToken=${!!oapiToken}`,
|
|
600
|
+
);
|
|
601
|
+
if (!oapiToken) {
|
|
602
|
+
logLocalImage(log, "processImagesForOutbound 跳过", "无 oapiToken(本地路径无法上传→必灰图)");
|
|
603
|
+
logMediaIdTrace("processImagesForOutbound:skip-no-token", content, undefined, true);
|
|
604
|
+
return { text: content || "", followUpUrls: [] };
|
|
605
|
+
}
|
|
606
|
+
if (!content || typeof content !== "string") {
|
|
607
|
+
logLocalImage(log, "processImagesForOutbound 跳过", "content 为空");
|
|
553
608
|
return { text: content || "", followUpUrls: [] };
|
|
554
609
|
}
|
|
555
610
|
|
|
611
|
+
logMediaIdTrace("processImagesForOutbound:in", content, undefined, true);
|
|
612
|
+
|
|
556
613
|
const codeRanges = getMarkdownCodeRanges(content);
|
|
614
|
+
if (codeRanges.length > 0) {
|
|
615
|
+
logLocalImage(
|
|
616
|
+
log,
|
|
617
|
+
"processImagesForOutbound 代码区域",
|
|
618
|
+
`count=${codeRanges.length}(围栏/行内 code 内的图路径将跳过)`,
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
|
|
557
622
|
const mdMatches = [...content.matchAll(LOCAL_IMAGE_RE)];
|
|
623
|
+
const scanPreview = mdMatches.map((m, i) => {
|
|
624
|
+
const p = (m[2] || "").trim();
|
|
625
|
+
const inCode = m.index !== undefined && isIndexInCodeRange(m.index, codeRanges);
|
|
626
|
+
const kind = classifyMdImgTarget(p);
|
|
627
|
+
return `#${i + 1} kind=${kind} inCode=${inCode} path=${p.slice(0, 120)}`;
|
|
628
|
+
});
|
|
629
|
+
logLocalImage(
|
|
630
|
+
log,
|
|
631
|
+
"processImagesForOutbound 扫描 ![]()",
|
|
632
|
+
scanPreview.length ? scanPreview.join(" || ") : "正文中没有任何 markdown 图片语法",
|
|
633
|
+
);
|
|
634
|
+
|
|
558
635
|
let result = content;
|
|
559
636
|
/** 本地路径 → 可点 downloadUrl(仅替换正文中的裸路径,不拆消息) */
|
|
560
637
|
const localPathToDl = new Map<string, string>();
|
|
561
638
|
let uploadCount = 0;
|
|
639
|
+
let localTried = 0;
|
|
640
|
+
let remoteTried = 0;
|
|
641
|
+
let skippedCode = 0;
|
|
642
|
+
let skippedOther = 0;
|
|
562
643
|
|
|
563
644
|
for (const match of mdMatches) {
|
|
564
|
-
if (match.index !== undefined && isIndexInCodeRange(match.index, codeRanges))
|
|
645
|
+
if (match.index !== undefined && isIndexInCodeRange(match.index, codeRanges)) {
|
|
646
|
+
skippedCode++;
|
|
647
|
+
logLocalImage(
|
|
648
|
+
log,
|
|
649
|
+
"processImagesForOutbound 跳过代码块内",
|
|
650
|
+
(match[2] || "").trim().slice(0, 120),
|
|
651
|
+
);
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
565
654
|
const [fullMatch, alt, rawPath] = match;
|
|
566
655
|
const cleanPath = (rawPath || "").replace(/\\ /g, " ").trim();
|
|
567
656
|
if (!cleanPath) continue;
|
|
568
657
|
|
|
569
658
|
if (isLocalImageRef(cleanPath)) {
|
|
659
|
+
localTried++;
|
|
570
660
|
const absPath = toLocalPath(cleanPath);
|
|
661
|
+
let exists = false;
|
|
662
|
+
let sizeMB = "-";
|
|
663
|
+
try {
|
|
664
|
+
exists = fs.existsSync(absPath);
|
|
665
|
+
if (exists) {
|
|
666
|
+
sizeMB = (fs.statSync(absPath).size / (1024 * 1024)).toFixed(2);
|
|
667
|
+
}
|
|
668
|
+
} catch (e: any) {
|
|
669
|
+
logLocalImage(log, "processImagesForOutbound 本地 stat 异常", e?.message || String(e));
|
|
670
|
+
}
|
|
671
|
+
logLocalImage(
|
|
672
|
+
log,
|
|
673
|
+
"processImagesForOutbound 处理本地图",
|
|
674
|
+
`alt=${alt || "-"} raw=${cleanPath.slice(0, 120)} abs=${absPath} exists=${exists} sizeMB=${sizeMB}`,
|
|
675
|
+
);
|
|
676
|
+
if (!exists) {
|
|
677
|
+
result = result
|
|
678
|
+
.split(fullMatch)
|
|
679
|
+
.join(`📷 *图片文件不存在(${path.basename(absPath)})*`);
|
|
680
|
+
logLocalImage(log, "processImagesForOutbound 本地文件不存在→失败提示", absPath);
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
571
683
|
try {
|
|
572
684
|
const uploadResult = await uploadLocalImageWithFallback(absPath, oapiToken, log);
|
|
573
685
|
if (uploadResult?.mediaId) {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
.join(``);
|
|
686
|
+
const replacement = ``;
|
|
687
|
+
result = result.split(fullMatch).join(replacement);
|
|
577
688
|
uploadCount++;
|
|
578
689
|
if (uploadResult.downloadUrl) {
|
|
579
690
|
localPathToDl.set(cleanPath, uploadResult.downloadUrl);
|
|
580
691
|
localPathToDl.set(absPath, uploadResult.downloadUrl);
|
|
581
692
|
}
|
|
693
|
+
logLocalImage(
|
|
694
|
+
log,
|
|
695
|
+
"processImagesForOutbound 本地→mediaId 成功",
|
|
696
|
+
`${absPath} → ${uploadResult.mediaId}`,
|
|
697
|
+
);
|
|
582
698
|
} else {
|
|
583
699
|
result = result
|
|
584
700
|
.split(fullMatch)
|
|
585
701
|
.join(`📷 *图片上传失败(${path.basename(absPath)})*`);
|
|
702
|
+
logLocalImage(log, "processImagesForOutbound 本地上传失败→失败提示", absPath);
|
|
586
703
|
}
|
|
587
|
-
} catch {
|
|
704
|
+
} catch (err: any) {
|
|
588
705
|
result = result
|
|
589
706
|
.split(fullMatch)
|
|
590
707
|
.join(`📷 *图片上传异常(${path.basename(toLocalPath(cleanPath))})*`);
|
|
708
|
+
logLocalImage(
|
|
709
|
+
log,
|
|
710
|
+
"processImagesForOutbound 本地上传异常",
|
|
711
|
+
`${absPath} err=${err?.message || err}`,
|
|
712
|
+
);
|
|
591
713
|
}
|
|
592
714
|
continue;
|
|
593
715
|
}
|
|
594
716
|
|
|
595
717
|
if (looksLikeRemoteUrl(cleanPath)) {
|
|
718
|
+
remoteTried++;
|
|
719
|
+
logLocalImage(log, "processImagesForOutbound 远程图", cleanPath.slice(0, 160));
|
|
596
720
|
let tmp: string | null = null;
|
|
597
721
|
try {
|
|
598
722
|
tmp = await downloadRemoteUrlToTemp(cleanPath);
|
|
599
|
-
if (!tmp)
|
|
723
|
+
if (!tmp) {
|
|
724
|
+
logLocalImage(log, "processImagesForOutbound 远程下载失败(保留原![]URL)", cleanPath.slice(0, 120));
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
600
727
|
const uploadResult = await uploadLocalImageWithFallback(tmp, oapiToken, log);
|
|
601
728
|
if (uploadResult?.mediaId) {
|
|
602
729
|
// 只改 ![] 为 mediaId;正文「下载链接:http://…」保持原 URL,同一条消息
|
|
603
730
|
result = result.split(fullMatch).join(``);
|
|
604
731
|
uploadCount++;
|
|
605
|
-
|
|
606
|
-
|
|
732
|
+
logLocalImage(
|
|
733
|
+
log,
|
|
734
|
+
"processImagesForOutbound 远程→mediaId 下载链保留原URL",
|
|
735
|
+
`${cleanPath.slice(0, 80)} → ${uploadResult.mediaId}`,
|
|
607
736
|
);
|
|
737
|
+
} else {
|
|
738
|
+
logLocalImage(log, "processImagesForOutbound 远程上传失败(保留原![]URL)", cleanPath.slice(0, 120));
|
|
608
739
|
}
|
|
609
740
|
} catch (err: any) {
|
|
610
741
|
logLocalImage(log, "远程图处理失败", err?.message || String(err));
|
|
@@ -617,18 +748,55 @@ export async function processImagesForOutbound(
|
|
|
617
748
|
}
|
|
618
749
|
}
|
|
619
750
|
}
|
|
751
|
+
continue;
|
|
620
752
|
}
|
|
753
|
+
|
|
754
|
+
skippedOther++;
|
|
755
|
+
logLocalImage(
|
|
756
|
+
log,
|
|
757
|
+
"processImagesForOutbound 跳过非本地非远程",
|
|
758
|
+
`kind=${classifyMdImgTarget(cleanPath)} path=${cleanPath.slice(0, 120)}`,
|
|
759
|
+
);
|
|
621
760
|
}
|
|
622
761
|
|
|
623
762
|
// 本地路径若在「下载链接」等处再出现:换成可点 downloadUrl(仍同一气泡)
|
|
624
763
|
for (const [p, dl] of localPathToDl) {
|
|
764
|
+
const before = result;
|
|
625
765
|
result = replaceOutsideImageTargets(result, p, dl);
|
|
766
|
+
if (before !== result) {
|
|
767
|
+
logLocalImage(
|
|
768
|
+
log,
|
|
769
|
+
"processImagesForOutbound 正文裸本地路径→downloadUrl",
|
|
770
|
+
`${p.slice(0, 80)} → ${dl}`,
|
|
771
|
+
);
|
|
772
|
+
}
|
|
626
773
|
}
|
|
627
774
|
|
|
628
|
-
|
|
629
|
-
|
|
775
|
+
const residual = [...result.matchAll(LOCAL_IMAGE_RE)]
|
|
776
|
+
.filter((m) => {
|
|
777
|
+
if (m.index !== undefined && isIndexInCodeRange(m.index, getMarkdownCodeRanges(result))) {
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
return isLocalImageRef((m[2] || "").trim());
|
|
781
|
+
})
|
|
782
|
+
.map((m) => (m[2] || "").trim());
|
|
783
|
+
|
|
784
|
+
logLocalImage(
|
|
785
|
+
log,
|
|
786
|
+
"processImagesForOutbound 完成",
|
|
787
|
+
`uploads=${uploadCount} localTried=${localTried} remoteTried=${remoteTried} skippedCode=${skippedCode} skippedOther=${skippedOther} outLen=${result.length}` +
|
|
788
|
+
(residual.length
|
|
789
|
+
? ` ⚠️residualLocal=${residual.length}:${residual.slice(0, 3).join("|")}`
|
|
790
|
+
: " residualLocal=0"),
|
|
630
791
|
);
|
|
631
|
-
|
|
792
|
+
if (residual.length) {
|
|
793
|
+
logLocalImage(
|
|
794
|
+
log,
|
|
795
|
+
"processImagesForOutbound ⚠️仍有代码块外本地路径未替换(钉钉必灰图)",
|
|
796
|
+
residual.join(" | "),
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
logMediaIdTrace("processImagesForOutbound:out", result, undefined, true);
|
|
632
800
|
|
|
633
801
|
return { text: result, followUpUrls: [] };
|
|
634
802
|
}
|
|
@@ -842,12 +842,29 @@ export async function sendTextToDingTalk(params: {
|
|
|
842
842
|
|
|
843
843
|
const targetParam = resolveOutboundTarget(target);
|
|
844
844
|
|
|
845
|
+
console.log(
|
|
846
|
+
`[DingTalk][LocalImage] sendTextToDingTalk 入口 | target=${target} textLen=${text?.length ?? 0}`,
|
|
847
|
+
);
|
|
848
|
+
logMediaIdTrace("sendTextToDingTalk:in", text, `target=${target}`, true);
|
|
849
|
+
|
|
845
850
|
// 图片:![] 上传为 mediaId;下载链接原 URL 留在同一条 markdown(不拆消息)
|
|
851
|
+
const beforeLen = text?.length ?? 0;
|
|
846
852
|
try {
|
|
847
853
|
const oapiToken = await getOapiAccessToken(config);
|
|
854
|
+
console.log(
|
|
855
|
+
`[DingTalk][LocalImage] sendTextToDingTalk 入口 | target=${target} textLen=${beforeLen} hasToken=${!!oapiToken}`,
|
|
856
|
+
);
|
|
848
857
|
if (oapiToken && text) {
|
|
849
858
|
const processed = await processImagesForOutbound(text, oapiToken, log);
|
|
850
859
|
text = processed.text;
|
|
860
|
+
console.log(
|
|
861
|
+
`[DingTalk][LocalImage] sendTextToDingTalk 处理后 | ${beforeLen}→${text.length} 字`,
|
|
862
|
+
);
|
|
863
|
+
logMediaIdTrace("sendTextToDingTalk:after-process", text, undefined, true);
|
|
864
|
+
} else if (!oapiToken) {
|
|
865
|
+
console.warn(
|
|
866
|
+
`[DingTalk][LocalImage] sendTextToDingTalk 无 oapiToken,跳过图片上传(本地 MD 图会灰)`,
|
|
867
|
+
);
|
|
851
868
|
}
|
|
852
869
|
} catch (err: any) {
|
|
853
870
|
console.warn(
|
|
@@ -856,7 +873,7 @@ export async function sendTextToDingTalk(params: {
|
|
|
856
873
|
}
|
|
857
874
|
|
|
858
875
|
const msgType: DingTalkMsgType = shouldSendAsMarkdown(text) ? "markdown" : "text";
|
|
859
|
-
logMediaIdTrace("sendTextToDingTalk:API前", text, `msgType=${msgType}
|
|
876
|
+
logMediaIdTrace("sendTextToDingTalk:API前", text, `msgType=${msgType}`, true);
|
|
860
877
|
|
|
861
878
|
return sendProactive(config, targetParam, text, {
|
|
862
879
|
msgType,
|