@jeik/dingtalk-connector 0.8.21-fix1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/CHANGELOG.md +686 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +181 -0
  4. package/README.md +221 -0
  5. package/bin/dingtalk-connector.js +858 -0
  6. package/bin/wizard-config.mjs +110 -0
  7. package/dist/accounts-BAzdqkAV.mjs +268 -0
  8. package/dist/accounts-BQptOmgB.mjs +2 -0
  9. package/dist/chunk-upload-BBQgGtcZ.mjs +193 -0
  10. package/dist/chunk-upload-DaLXXZH3.mjs +2 -0
  11. package/dist/common-C8pYKU_y.mjs +2 -0
  12. package/dist/common-Dt9n6fQN.mjs +101 -0
  13. package/dist/connection-DHHFFNQJ.mjs +423 -0
  14. package/dist/entry-bundled.d.mts +16 -0
  15. package/dist/entry-bundled.mjs +31 -0
  16. package/dist/game-xiyou-CqHt-6Q1.mjs +4271 -0
  17. package/dist/gateway-methods-C4tcgI7P.mjs +771 -0
  18. package/dist/gateway-methods-Ci31A3vg.mjs +2 -0
  19. package/dist/http-client-CpnJHB89.mjs +2 -0
  20. package/dist/http-client-DFWZgO1n.mjs +33 -0
  21. package/dist/index.d.mts +193 -0
  22. package/dist/index.mjs +45 -0
  23. package/dist/logger-BmJkQkm1.mjs +2 -0
  24. package/dist/logger-mZ9OSbmD.mjs +58 -0
  25. package/dist/media-C_SVin7s.mjs +2 -0
  26. package/dist/media-cz72EVS3.mjs +509 -0
  27. package/dist/message-handler-DESzFFDc.mjs +1971 -0
  28. package/dist/messaging-B6l1sRvX.mjs +1044 -0
  29. package/dist/runtime-DUgpo5zC.mjs +1422 -0
  30. package/dist/session-DJ4jYqPv.mjs +114 -0
  31. package/dist/utils-Bjh4r_qS.mjs +4 -0
  32. package/dist/utils-CIfI_3Jh.mjs +63 -0
  33. package/dist/utils-legacy-CALCPP1t.mjs +230 -0
  34. package/dist/utils-legacy-CFYDBM4r.mjs +3 -0
  35. package/docs/DEAP_AGENT_GUIDE.en.md +115 -0
  36. package/docs/DEAP_AGENT_GUIDE.md +115 -0
  37. package/docs/DINGTALK_MANUAL_SETUP.md +50 -0
  38. package/docs/MULTI_AGENT_SETUP.md +306 -0
  39. package/docs/RELEASE_NOTES_V0.7.10.md +40 -0
  40. package/docs/RELEASE_NOTES_V0.7.2.md +143 -0
  41. package/docs/RELEASE_NOTES_V0.7.3.md +149 -0
  42. package/docs/RELEASE_NOTES_V0.7.4.md +206 -0
  43. package/docs/RELEASE_NOTES_V0.7.5.md +267 -0
  44. package/docs/RELEASE_NOTES_V0.7.6.md +219 -0
  45. package/docs/RELEASE_NOTES_V0.7.7.md +122 -0
  46. package/docs/RELEASE_NOTES_V0.7.8.md +101 -0
  47. package/docs/RELEASE_NOTES_V0.7.9.md +65 -0
  48. package/docs/RELEASE_NOTES_V0.8.0.md +53 -0
  49. package/docs/RELEASE_NOTES_V0.8.1.md +47 -0
  50. package/docs/RELEASE_NOTES_V0.8.10.md +49 -0
  51. package/docs/RELEASE_NOTES_V0.8.11.md +51 -0
  52. package/docs/RELEASE_NOTES_V0.8.12.md +63 -0
  53. package/docs/RELEASE_NOTES_V0.8.13-beta.0.md +69 -0
  54. package/docs/RELEASE_NOTES_V0.8.13.md +62 -0
  55. package/docs/RELEASE_NOTES_V0.8.14.md +86 -0
  56. package/docs/RELEASE_NOTES_V0.8.16.md +40 -0
  57. package/docs/RELEASE_NOTES_V0.8.17.md +87 -0
  58. package/docs/RELEASE_NOTES_V0.8.18.md +64 -0
  59. package/docs/RELEASE_NOTES_V0.8.19.md +62 -0
  60. package/docs/RELEASE_NOTES_V0.8.2.md +55 -0
  61. package/docs/RELEASE_NOTES_V0.8.20.md +49 -0
  62. package/docs/RELEASE_NOTES_V0.8.3.md +63 -0
  63. package/docs/RELEASE_NOTES_V0.8.4.md +45 -0
  64. package/docs/RELEASE_NOTES_V0.8.7.md +49 -0
  65. package/docs/RELEASE_NOTES_V0.8.8.md +63 -0
  66. package/docs/RELEASE_NOTES_V0.8.9.md +81 -0
  67. package/docs/RELEASE_NOTES_v0.7.0.md +142 -0
  68. package/docs/RELEASE_NOTES_v0.7.1.md +74 -0
  69. package/docs/TROUBLESHOOTING.md +122 -0
  70. package/index.ts +77 -0
  71. package/openclaw.plugin.json +551 -0
  72. package/package.json +147 -0
  73. package/skills/dingtalk-channel-rules/SKILL.md +91 -0
  74. package/skills/dingtalk-troubleshoot/SKILL.md +93 -0
  75. package/skills/dws-cli/SKILL.md +129 -0
  76. package/skills/dws-cli/references/error-codes.md +95 -0
  77. package/skills/dws-cli/references/field-rules.md +105 -0
  78. package/skills/dws-cli/references/global-reference.md +104 -0
  79. package/skills/dws-cli/references/intent-guide.md +114 -0
  80. package/skills/dws-cli/references/products/aitable.md +452 -0
  81. package/skills/dws-cli/references/products/attendance.md +93 -0
  82. package/skills/dws-cli/references/products/calendar.md +217 -0
  83. package/skills/dws-cli/references/products/chat.md +292 -0
  84. package/skills/dws-cli/references/products/contact.md +108 -0
  85. package/skills/dws-cli/references/products/ding.md +57 -0
  86. package/skills/dws-cli/references/products/report.md +162 -0
  87. package/skills/dws-cli/references/products/simple.md +128 -0
  88. package/skills/dws-cli/references/products/todo.md +138 -0
  89. package/skills/dws-cli/references/products/workbench.md +39 -0
  90. package/skills/dws-cli/references/recovery-guide.md +94 -0
  91. package/src/channel.ts +588 -0
  92. package/src/config/accounts.ts +242 -0
  93. package/src/config/schema.ts +180 -0
  94. package/src/core/connection.ts +741 -0
  95. package/src/core/message-handler.ts +1788 -0
  96. package/src/core/provider.ts +111 -0
  97. package/src/core/state.ts +54 -0
  98. package/src/device-auth-config.ts +14 -0
  99. package/src/device-auth.ts +197 -0
  100. package/src/directory.ts +95 -0
  101. package/src/docs.ts +293 -0
  102. package/src/game-xiyou/achievement-engine.ts +252 -0
  103. package/src/game-xiyou/bounty-system.ts +315 -0
  104. package/src/game-xiyou/commands.ts +223 -0
  105. package/src/game-xiyou/drop-engine.ts +241 -0
  106. package/src/game-xiyou/encounter-system.ts +135 -0
  107. package/src/game-xiyou/escape-engine.ts +164 -0
  108. package/src/game-xiyou/exp-calculator.ts +139 -0
  109. package/src/game-xiyou/index.ts +479 -0
  110. package/src/game-xiyou/level-system.ts +91 -0
  111. package/src/game-xiyou/monster-pool.ts +180 -0
  112. package/src/game-xiyou/pity-counter.ts +114 -0
  113. package/src/game-xiyou/random-event-engine.ts +648 -0
  114. package/src/game-xiyou/renderer.ts +679 -0
  115. package/src/game-xiyou/storage.ts +218 -0
  116. package/src/game-xiyou/treasure-system.ts +105 -0
  117. package/src/game-xiyou/types.ts +582 -0
  118. package/src/game-xiyou/uid-resolver.ts +49 -0
  119. package/src/gateway-methods.ts +740 -0
  120. package/src/onboarding.ts +553 -0
  121. package/src/policy.ts +32 -0
  122. package/src/probe.ts +210 -0
  123. package/src/reply-dispatcher.ts +874 -0
  124. package/src/runtime.ts +32 -0
  125. package/src/sdk/helpers.ts +322 -0
  126. package/src/sdk/types.ts +519 -0
  127. package/src/secret-input.ts +19 -0
  128. package/src/services/media/audio.ts +54 -0
  129. package/src/services/media/chunk-upload.ts +296 -0
  130. package/src/services/media/common.ts +155 -0
  131. package/src/services/media/file.ts +75 -0
  132. package/src/services/media/image.ts +81 -0
  133. package/src/services/media/index.ts +10 -0
  134. package/src/services/media/video.ts +162 -0
  135. package/src/services/media.ts +1143 -0
  136. package/src/services/messaging/card.ts +604 -0
  137. package/src/services/messaging/index.ts +18 -0
  138. package/src/services/messaging/mentions.ts +267 -0
  139. package/src/services/messaging/send.ts +141 -0
  140. package/src/services/messaging.ts +1191 -0
  141. package/src/services/reply-markers.ts +55 -0
  142. package/src/targets.ts +45 -0
  143. package/src/types/index.ts +59 -0
  144. package/src/types/pdf-parse.d.ts +3 -0
  145. package/src/utils/agent.ts +63 -0
  146. package/src/utils/async.ts +51 -0
  147. package/src/utils/constants.ts +27 -0
  148. package/src/utils/http-client.ts +38 -0
  149. package/src/utils/index.ts +8 -0
  150. package/src/utils/logger.ts +78 -0
  151. package/src/utils/session.ts +147 -0
  152. package/src/utils/token.ts +93 -0
  153. package/src/utils/utils-legacy.ts +454 -0
  154. package/tsconfig.json +20 -0
@@ -0,0 +1,509 @@
1
+ import { n as dingtalkOapiHttp, t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
2
+ import { n as DINGTALK_OAPI } from "./utils-CIfI_3Jh.mjs";
3
+ import { createRequire } from "node:module";
4
+ import * as fs from "fs";
5
+ import * as path from "path";
6
+ import FormData from "form-data";
7
+ //#region \0rolldown/runtime.js
8
+ var __defProp = Object.defineProperty;
9
+ var __exportAll = (all, no_symbols) => {
10
+ let target = {};
11
+ for (var name in all) __defProp(target, name, {
12
+ get: all[name],
13
+ enumerable: true
14
+ });
15
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
16
+ return target;
17
+ };
18
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
+ //#endregion
20
+ //#region src/services/media.ts
21
+ /**
22
+ * 钉钉媒体处理
23
+ * 支持图片、视频、音频、文件的上传和下载
24
+ */
25
+ /** 视频标记正则表达式 */
26
+ const VIDEO_MARKER_PATTERN = /\[DINGTALK_VIDEO\](.*?)\[\/DINGTALK_VIDEO\]/gs;
27
+ /**
28
+ * 去掉 file:// / MEDIA: / attachment:// 前缀,得到实际的绝对路径
29
+ */
30
+ function toLocalPath(raw) {
31
+ let filePath = raw;
32
+ if (filePath.startsWith("file://")) filePath = filePath.replace("file://", "");
33
+ else if (filePath.startsWith("MEDIA:")) filePath = filePath.replace("MEDIA:", "");
34
+ else if (filePath.startsWith("attachment://")) filePath = filePath.replace("attachment://", "");
35
+ try {
36
+ filePath = decodeURIComponent(filePath);
37
+ } catch {}
38
+ return filePath;
39
+ }
40
+ async function uploadMediaToDingTalk(filePath, mediaType, oapiToken, maxSize = 20 * 1024 * 1024, log) {
41
+ try {
42
+ const absPath = toLocalPath(filePath);
43
+ log?.info?.(`开始上传,文件路径:${absPath}`);
44
+ if (!fs.existsSync(absPath)) {
45
+ log?.warn?.(`文件不存在:${absPath}`);
46
+ return null;
47
+ }
48
+ const stats = fs.statSync(absPath);
49
+ const fileSizeMB = (stats.size / (1024 * 1024)).toFixed(2);
50
+ stats.size;
51
+ log?.info?.(`文件大小:${fileSizeMB}MB`);
52
+ if (stats.size > maxSize) {
53
+ const maxSizeMB = (maxSize / (1024 * 1024)).toFixed(0);
54
+ log?.warn?.(`文件过大:${absPath}, 大小:${fileSizeMB}MB, 超过限制 ${maxSizeMB}MB`);
55
+ return null;
56
+ }
57
+ const getContentType = () => {
58
+ const ext = path.extname(absPath).toLowerCase();
59
+ if (mediaType === "image") return ext === ".png" ? "image/png" : "image/jpeg";
60
+ else if (mediaType === "video") return ext === ".mp4" ? "video/mp4" : "video/quicktime";
61
+ else if (mediaType === "voice") return ext === ".mp3" ? "audio/mpeg" : "audio/amr";
62
+ else return "application/octet-stream";
63
+ };
64
+ const form = new FormData();
65
+ form.append("media", fs.createReadStream(absPath), {
66
+ filename: path.basename(absPath),
67
+ contentType: getContentType()
68
+ });
69
+ log?.info?.(`上传文件: ${absPath} (${fileSizeMB}MB)`);
70
+ const resp = await dingtalkOapiHttp.post(`${DINGTALK_OAPI}/media/upload?access_token=${oapiToken}&type=${mediaType === "video" ? "file" : mediaType}`, form, {
71
+ headers: form.getHeaders(),
72
+ timeout: 6e4
73
+ });
74
+ const mediaId = resp.data?.media_id;
75
+ if (mediaId) {
76
+ const cleanMediaId = mediaId.startsWith("@") ? mediaId.substring(1) : mediaId;
77
+ const downloadUrl = `https://down.dingtalk.com/media/${cleanMediaId}`;
78
+ log?.info?.(`上传成功: media_id=${mediaId}, cleanMediaId=${cleanMediaId}, downloadUrl=${downloadUrl}`);
79
+ return {
80
+ mediaId,
81
+ cleanMediaId,
82
+ downloadUrl
83
+ };
84
+ }
85
+ log?.warn?.(`上传返回无 media_id: ${JSON.stringify(resp.data)}`);
86
+ return null;
87
+ } catch (err) {
88
+ log?.error?.(`上传失败: ${err.message}`);
89
+ return null;
90
+ }
91
+ }
92
+ /**
93
+ * 提取视频元数据(时长、分辨率)
94
+ */
95
+ async function extractVideoMetadata(filePath, log) {
96
+ try {
97
+ const ffmpeg = __require("fluent-ffmpeg");
98
+ const ffmpegPath = __require("@ffmpeg-installer/ffmpeg").path;
99
+ const ffprobePath = __require("@ffprobe-installer/ffprobe").path;
100
+ ffmpeg.setFfmpegPath(ffmpegPath);
101
+ ffmpeg.setFfprobePath(ffprobePath);
102
+ return new Promise((resolve) => {
103
+ ffmpeg.ffprobe(filePath, (err, metadata) => {
104
+ if (err) {
105
+ log?.warn?.(`ffprobe 执行失败: ${err.message}`);
106
+ resolve(null);
107
+ return;
108
+ }
109
+ try {
110
+ const duration = metadata.format?.duration ? Math.round(parseFloat(metadata.format.duration) * 1e3) : 0;
111
+ const videoStream = metadata.streams?.find((s) => s.codec_type === "video");
112
+ resolve({
113
+ duration,
114
+ width: videoStream?.width || 0,
115
+ height: videoStream?.height || 0
116
+ });
117
+ } catch (err) {
118
+ log?.warn?.(`解析 ffprobe 输出失败`);
119
+ resolve(null);
120
+ }
121
+ });
122
+ });
123
+ } catch (err) {
124
+ log?.warn?.(`提取视频元数据失败: ${err.message}`);
125
+ return null;
126
+ }
127
+ }
128
+ /**
129
+ * 生成视频封面图(第1秒截图)
130
+ */
131
+ async function extractVideoThumbnail(videoPath, outputPath, log) {
132
+ try {
133
+ const ffmpeg = __require("fluent-ffmpeg");
134
+ const ffmpegPath = __require("@ffmpeg-installer/ffmpeg").path;
135
+ const path = await import("path");
136
+ ffmpeg.setFfmpegPath(ffmpegPath);
137
+ return new Promise((resolve) => {
138
+ ffmpeg(videoPath).screenshots({
139
+ count: 1,
140
+ folder: path.dirname(outputPath),
141
+ filename: path.basename(outputPath),
142
+ timemarks: ["1"],
143
+ size: "?x360"
144
+ }).on("end", () => {
145
+ log?.info?.(`封面生成成功: ${outputPath}`);
146
+ resolve(outputPath);
147
+ }).on("error", (err) => {
148
+ log?.error?.(`封面生成失败: ${err.message}`);
149
+ resolve(null);
150
+ });
151
+ });
152
+ } catch (err) {
153
+ log?.error?.(`ffmpeg 失败: ${err.message}`);
154
+ return null;
155
+ }
156
+ }
157
+ /**
158
+ * 提取视频标记并发送视频消息
159
+ */
160
+ async function processVideoMarkers(content, sessionWebhook, config, oapiToken, log, useProactiveApi = false, target) {
161
+ const logPrefix = useProactiveApi ? "Video[Proactive]" : "Video";
162
+ if (!oapiToken) {
163
+ log?.warn?.(`${logPrefix} 无 oapiToken,跳过视频处理`);
164
+ return content;
165
+ }
166
+ const matches = [...content.matchAll(VIDEO_MARKER_PATTERN)];
167
+ const videoInfos = [];
168
+ const invalidVideos = [];
169
+ const os = await import("os");
170
+ for (const match of matches) try {
171
+ const videoInfo = JSON.parse(match[1]);
172
+ if (videoInfo.path && fs.existsSync(videoInfo.path)) {
173
+ videoInfos.push(videoInfo);
174
+ log?.info?.(`${logPrefix} 提取到视频: ${videoInfo.path}`);
175
+ } else {
176
+ invalidVideos.push(videoInfo.path || "未知路径");
177
+ log?.warn?.(`${logPrefix} 视频文件不存在: ${videoInfo.path}`);
178
+ }
179
+ } catch (err) {
180
+ log?.warn?.(`${logPrefix} 解析标记失败: ${err.message}`);
181
+ }
182
+ if (videoInfos.length === 0 && invalidVideos.length === 0) {
183
+ log?.info?.(`${logPrefix} 未检测到视频标记`);
184
+ return content.replace(VIDEO_MARKER_PATTERN, "").trim();
185
+ }
186
+ let cleanedContent = content.replace(VIDEO_MARKER_PATTERN, "").trim();
187
+ const statusMessages = [];
188
+ for (const invalidPath of invalidVideos) statusMessages.push(`⚠️ 视频文件不存在: ${path.basename(invalidPath)}`);
189
+ if (videoInfos.length > 0) log?.info?.(`${logPrefix} 检测到 ${videoInfos.length} 个视频,开始处理...`);
190
+ for (const videoInfo of videoInfos) {
191
+ const fileName = path.basename(videoInfo.path);
192
+ let thumbnailPath = "";
193
+ try {
194
+ const metadata = await extractVideoMetadata(videoInfo.path, log);
195
+ if (!metadata) {
196
+ log?.warn?.(`${logPrefix} 无法提取元数据: ${videoInfo.path}`);
197
+ statusMessages.push(`⚠️ 视频处理失败: ${fileName}(无法读取视频信息)`);
198
+ continue;
199
+ }
200
+ thumbnailPath = path.join(os.tmpdir(), `thumbnail_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.jpg`);
201
+ log?.info?.(`${logPrefix} 准备生成封面: ${thumbnailPath}`);
202
+ if (!await extractVideoThumbnail(videoInfo.path, thumbnailPath, log)) {
203
+ log?.warn?.(`${logPrefix} 无法生成封面: ${videoInfo.path}`);
204
+ statusMessages.push(`⚠️ 视频处理失败: ${fileName}(无法生成封面)`);
205
+ continue;
206
+ }
207
+ if (fs.existsSync(thumbnailPath)) {
208
+ const stats = fs.statSync(thumbnailPath);
209
+ log?.info?.(`${logPrefix} 封面文件生成完成: ${thumbnailPath}, 大小: ${(stats.size / 1024).toFixed(2)}KB`);
210
+ if (stats.size < 1024) log?.warn?.(`${logPrefix} 封面文件过小,可能存在质量问题`);
211
+ } else {
212
+ log?.error?.(`${logPrefix} 封面文件未生成: ${thumbnailPath}`);
213
+ statusMessages.push(`⚠️ 视频处理失败: ${fileName}(封面文件未生成)`);
214
+ continue;
215
+ }
216
+ const videoUploadResult = await uploadMediaToDingTalk(videoInfo.path, "video", oapiToken, 20 * 1024 * 1024, log);
217
+ if (!videoUploadResult) {
218
+ log?.warn?.(`${logPrefix} 视频上传失败: ${videoInfo.path}`);
219
+ statusMessages.push(`⚠️ 视频上传失败: ${fileName}(文件可能超过 20MB 限制)`);
220
+ continue;
221
+ }
222
+ const videoMediaId = videoUploadResult.mediaId;
223
+ const picUploadResult = await uploadMediaToDingTalk(thumbnailPath, "image", oapiToken, 20 * 1024 * 1024, log);
224
+ if (!picUploadResult) {
225
+ log?.warn?.(`${logPrefix} 封面上传失败: ${thumbnailPath}`);
226
+ statusMessages.push(`⚠️ 视频封面上传失败: ${fileName}`);
227
+ continue;
228
+ }
229
+ const picMediaId = picUploadResult.mediaId;
230
+ if (useProactiveApi && target) await sendVideoProactive(config, target, videoMediaId, picMediaId, metadata, log);
231
+ else await sendVideoMessage(config, sessionWebhook, fileName, videoUploadResult.downloadUrl, log, metadata);
232
+ statusMessages.push(`✅ 视频已发送: ${fileName}`);
233
+ log?.info?.(`${logPrefix} 视频处理完成: ${fileName}`);
234
+ } catch (err) {
235
+ log?.error?.(`${logPrefix} 处理视频失败: ${err.message}`);
236
+ statusMessages.push(`⚠️ 视频处理异常: ${fileName}(${err.message})`);
237
+ } finally {
238
+ if (thumbnailPath && fs.existsSync(thumbnailPath)) try {
239
+ fs.unlinkSync(thumbnailPath);
240
+ log?.info?.(`${logPrefix} 临时封面已清理: ${thumbnailPath}`);
241
+ } catch (cleanupErr) {
242
+ log?.warn?.(`${logPrefix} 清理临时文件失败: ${cleanupErr?.message || cleanupErr}`);
243
+ }
244
+ }
245
+ }
246
+ if (statusMessages.length > 0) {
247
+ const statusText = statusMessages.join("\n");
248
+ cleanedContent = cleanedContent ? `${cleanedContent}\n\n${statusText}` : statusText;
249
+ }
250
+ return cleanedContent;
251
+ }
252
+ /**
253
+ * 提取音频时长
254
+ *
255
+ * 使用 fluent-ffmpeg 的 ffprobe API,与 extractVideoMetadata 保持一致,
256
+ * 完全避免直接调用 child_process,消除安全扫描误报。
257
+ */
258
+ async function extractAudioDuration(filePath, log) {
259
+ try {
260
+ const ffmpeg = __require("fluent-ffmpeg");
261
+ try {
262
+ const ffprobeInstaller = __require("@ffprobe-installer/ffprobe");
263
+ if (ffprobeInstaller?.path) ffmpeg.setFfprobePath(ffprobeInstaller.path);
264
+ } catch {}
265
+ return new Promise((resolve) => {
266
+ ffmpeg.ffprobe(filePath, (err, metadata) => {
267
+ if (err) {
268
+ log?.warn?.(`ffprobe 执行失败: ${err.message}`);
269
+ resolve(null);
270
+ return;
271
+ }
272
+ try {
273
+ resolve(metadata.format?.duration ? Math.round(parseFloat(metadata.format.duration) * 1e3) : 0);
274
+ } catch (parseErr) {
275
+ log?.warn?.(`解析 ffprobe 输出失败`);
276
+ resolve(null);
277
+ }
278
+ });
279
+ });
280
+ } catch (err) {
281
+ log?.warn?.(`提取音频时长失败: ${err.message}`);
282
+ return null;
283
+ }
284
+ }
285
+ /**
286
+ * 发送视频消息(sessionWebhook 模式)
287
+ */
288
+ async function sendVideoMessage(config, sessionWebhook, fileName, mediaId, log, metadata) {
289
+ try {
290
+ const token = await (await import("./utils-Bjh4r_qS.mjs")).getAccessToken(config);
291
+ const videoMessage = {
292
+ msgtype: "video",
293
+ video: {
294
+ mediaId,
295
+ duration: metadata?.duration.toString() || "60000",
296
+ type: "mp4"
297
+ }
298
+ };
299
+ log?.info?.(`发送视频消息: ${fileName}`);
300
+ const resp = await dingtalkHttp.post(sessionWebhook, videoMessage, {
301
+ headers: {
302
+ "x-acs-dingtalk-access-token": token,
303
+ "Content-Type": "application/json"
304
+ },
305
+ timeout: 1e4
306
+ });
307
+ if (resp.data?.success !== false) log?.info?.(`视频消息发送成功: ${fileName}`);
308
+ else log?.error?.(`视频消息发送失败: ${JSON.stringify(resp.data)}`);
309
+ } catch (err) {
310
+ log?.error?.(`发送视频消息异常: ${fileName}, 错误: ${err.message}`);
311
+ }
312
+ }
313
+ /**
314
+ * 发送视频消息(主动 API 模式)
315
+ */
316
+ async function sendVideoProactive(config, target, videoMediaId, picMediaId, metadata, log) {
317
+ try {
318
+ const token = await (await import("./utils-Bjh4r_qS.mjs")).getAccessToken(config);
319
+ const { DINGTALK_API } = await import("./utils-Bjh4r_qS.mjs");
320
+ const msgParam = {
321
+ duration: metadata?.duration.toString() || "60000",
322
+ videoMediaId,
323
+ videoType: "mp4",
324
+ picMediaId: picMediaId || ""
325
+ };
326
+ const body = {
327
+ robotCode: String(config.clientId),
328
+ msgKey: "sampleVideo",
329
+ msgParam: JSON.stringify(msgParam)
330
+ };
331
+ let endpoint;
332
+ if (target.type === "group") {
333
+ body.openConversationId = target.openConversationId;
334
+ endpoint = `${DINGTALK_API}/v1.0/robot/groupMessages/send`;
335
+ } else {
336
+ body.userIds = [target.userId];
337
+ endpoint = `${DINGTALK_API}/v1.0/robot/oToMessages/batchSend`;
338
+ }
339
+ log?.info?.(`Video[Proactive] 发送视频消息`);
340
+ log?.info?.(`Video[Proactive] 请求体: ${JSON.stringify(body, null, 2)}`);
341
+ log?.info?.(`Video[Proactive] endpoint: ${endpoint}`);
342
+ const resp = await dingtalkHttp.post(endpoint, body, {
343
+ headers: {
344
+ "x-acs-dingtalk-access-token": token,
345
+ "Content-Type": "application/json"
346
+ },
347
+ timeout: 1e4
348
+ });
349
+ log?.info?.(`Video[Proactive] 钉钉 API 响应: ${JSON.stringify(resp.data, null, 2)}`);
350
+ if (resp.data?.processQueryKey) log?.info?.(`Video[Proactive] 视频消息发送成功`);
351
+ else {
352
+ log?.error?.(`Video[Proactive] 视频消息发送失败: ${JSON.stringify(resp.data)}`);
353
+ throw new Error(`视频消息发送失败: ${JSON.stringify(resp.data)}`);
354
+ }
355
+ } catch (err) {
356
+ log?.error?.(`Video[Proactive] 发送视频消息失败, 错误: ${err.message}`);
357
+ }
358
+ }
359
+ /**
360
+ * 发送音频消息(主动 API 模式)
361
+ */
362
+ async function sendAudioProactive(config, target, fileName, mediaId, log, durationMs) {
363
+ try {
364
+ const token = await (await import("./utils-Bjh4r_qS.mjs")).getAccessToken(config);
365
+ const { DINGTALK_API } = await import("./utils-Bjh4r_qS.mjs");
366
+ const msgParam = {
367
+ mediaId,
368
+ duration: durationMs && durationMs > 0 ? durationMs.toString() : "60000"
369
+ };
370
+ const body = {
371
+ robotCode: String(config.clientId),
372
+ msgKey: "sampleAudio",
373
+ msgParam: JSON.stringify(msgParam)
374
+ };
375
+ let endpoint;
376
+ if (target.type === "group") {
377
+ body.openConversationId = target.openConversationId;
378
+ endpoint = `${DINGTALK_API}/v1.0/robot/groupMessages/send`;
379
+ } else {
380
+ body.userIds = [target.userId];
381
+ endpoint = `${DINGTALK_API}/v1.0/robot/oToMessages/batchSend`;
382
+ }
383
+ log?.info?.(`Audio[Proactive] 发送音频消息: ${fileName}`);
384
+ const resp = await dingtalkHttp.post(endpoint, body, {
385
+ headers: {
386
+ "x-acs-dingtalk-access-token": token,
387
+ "Content-Type": "application/json"
388
+ },
389
+ timeout: 1e4
390
+ });
391
+ if (resp.data?.processQueryKey) log?.info?.(`Audio[Proactive] 音频消息发送成功: ${fileName}`);
392
+ else log?.warn?.(`Audio[Proactive] 音频消息发送响应异常: ${JSON.stringify(resp.data)}`);
393
+ } catch (err) {
394
+ log?.error?.(`Audio[Proactive] 发送音频消息失败: ${fileName}, 错误: ${err.message}`);
395
+ }
396
+ }
397
+ /**
398
+ * 发送文件消息(主动 API 模式)
399
+ */
400
+ async function sendFileProactive(config, target, fileInfo, mediaId, log) {
401
+ try {
402
+ const token = await (await import("./utils-Bjh4r_qS.mjs")).getAccessToken(config);
403
+ const { DINGTALK_API } = await import("./utils-Bjh4r_qS.mjs");
404
+ const resolvedFileName = fileInfo.fileName || path.basename(fileInfo.path);
405
+ const msgParam = {
406
+ mediaId,
407
+ fileName: resolvedFileName,
408
+ fileType: fileInfo.fileType || resolvedFileName.split(".").pop() || "file"
409
+ };
410
+ const body = {
411
+ robotCode: String(config.clientId),
412
+ msgKey: "sampleFile",
413
+ msgParam: JSON.stringify(msgParam)
414
+ };
415
+ let endpoint;
416
+ if (target.type === "group") {
417
+ body.openConversationId = target.openConversationId;
418
+ endpoint = `${DINGTALK_API}/v1.0/robot/groupMessages/send`;
419
+ } else {
420
+ body.userIds = [target.userId];
421
+ endpoint = `${DINGTALK_API}/v1.0/robot/oToMessages/batchSend`;
422
+ }
423
+ log?.info?.(`File[Proactive] 发送文件消息: ${fileInfo.fileName}`);
424
+ const resp = await dingtalkHttp.post(endpoint, body, {
425
+ headers: {
426
+ "x-acs-dingtalk-access-token": token,
427
+ "Content-Type": "application/json"
428
+ },
429
+ timeout: 1e4
430
+ });
431
+ if (resp.data?.processQueryKey) log?.info?.(`File[Proactive] 发送成功: processQueryKey=${resp.data.processQueryKey}`);
432
+ else log?.warn?.(`File[Proactive] 发送失败: ${JSON.stringify(resp.data)}`);
433
+ } catch (err) {
434
+ log?.error?.(`File[Proactive] 发送文件消息失败: ${fileInfo.fileName}, 错误: ${err.message}`);
435
+ throw err;
436
+ }
437
+ }
438
+ async function processRawMediaPaths(content, config, oapiToken, log, target) {
439
+ const logPrefix = "RawMedia";
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));
441
+ if (matches.length === 0) return content;
442
+ log?.info?.(`${logPrefix} 检测到 ${matches.length} 个裸露的本地文件路径`);
443
+ let processedContent = content;
444
+ const statusMessages = [];
445
+ for (const match of matches) {
446
+ const fullMatch = match[0];
447
+ const filePath = match[1].trim();
448
+ try {
449
+ log?.info?.(`${logPrefix} 开始处理文件: ${filePath}`);
450
+ const ext = filePath.toLowerCase().split(".").pop() || "";
451
+ let mediaType;
452
+ if ([
453
+ "mp4",
454
+ "avi",
455
+ "mov",
456
+ "wmv",
457
+ "flv",
458
+ "mkv",
459
+ "webm"
460
+ ].includes(ext)) mediaType = "video";
461
+ else if ([
462
+ "mp3",
463
+ "wav",
464
+ "flac",
465
+ "aac",
466
+ "ogg",
467
+ "m4a",
468
+ "wma"
469
+ ].includes(ext)) mediaType = "voice";
470
+ else mediaType = "file";
471
+ const uploadResult = await uploadMediaToDingTalk(filePath, mediaType, oapiToken, 20 * 1024 * 1024, log);
472
+ if (!uploadResult) {
473
+ log?.error?.(`${logPrefix} 文件上传失败: ${filePath}`);
474
+ statusMessages.push(`⚠️ 文件上传失败: ${filePath}`);
475
+ continue;
476
+ }
477
+ const fileName = filePath.split(/[\/\\]/).pop() || "unknown";
478
+ if (mediaType === "video") {
479
+ const metadata = await extractVideoMetadata(filePath, log);
480
+ if (target) await sendVideoProactive(config, target, uploadResult.mediaId, fileName, log, metadata);
481
+ statusMessages.push(`✅ 视频已发送: ${fileName}`);
482
+ } else if (mediaType === "voice") {
483
+ const durationMs = await extractAudioDuration(filePath, log);
484
+ if (target) await sendAudioProactive(config, target, fileName, uploadResult.downloadUrl, log, durationMs ?? void 0);
485
+ statusMessages.push(`✅ 音频已发送: ${fileName}`);
486
+ } else {
487
+ const fileInfo = {
488
+ path: filePath,
489
+ fileName,
490
+ fileType: ext
491
+ };
492
+ if (target) await sendFileProactive(config, target, fileInfo, uploadResult.mediaId, log);
493
+ statusMessages.push(`✅ 文件已发送: ${fileName}`);
494
+ }
495
+ processedContent = processedContent.replace(fullMatch, fullMatch.replace(filePath, ""));
496
+ log?.info?.(`${logPrefix} 文件处理完成: ${fileName}`);
497
+ } catch (err) {
498
+ log?.error?.(`${logPrefix} 处理文件失败: ${filePath}, 错误: ${err.message}`);
499
+ statusMessages.push(`⚠️ 处理失败: ${filePath}`);
500
+ }
501
+ }
502
+ if (statusMessages.length > 0) {
503
+ const statusText = "\n\n" + statusMessages.join("\n");
504
+ processedContent = processedContent.trim() + statusText;
505
+ }
506
+ return processedContent;
507
+ }
508
+ //#endregion
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 };