ylib-wecom-openclaw-plugin 2026.4.29

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 (180) hide show
  1. package/README.md +596 -0
  2. package/dist/index.d.ts +10 -0
  3. package/dist/index.js +99 -0
  4. package/dist/src/accounts.d.ts +57 -0
  5. package/dist/src/accounts.js +247 -0
  6. package/dist/src/agent/api-client.d.ts +95 -0
  7. package/dist/src/agent/api-client.js +425 -0
  8. package/dist/src/agent/handler.d.ts +64 -0
  9. package/dist/src/agent/handler.js +731 -0
  10. package/dist/src/agent/index.d.ts +5 -0
  11. package/dist/src/agent/index.js +21 -0
  12. package/dist/src/agent/webhook.d.ts +25 -0
  13. package/dist/src/agent/webhook.js +294 -0
  14. package/dist/src/agent/xml.d.ts +21 -0
  15. package/dist/src/agent/xml.js +43 -0
  16. package/dist/src/channel.d.ts +5 -0
  17. package/dist/src/channel.js +815 -0
  18. package/dist/src/chat-queue.d.ts +31 -0
  19. package/dist/src/chat-queue.js +53 -0
  20. package/dist/src/config-schema.d.ts +587 -0
  21. package/dist/src/config-schema.js +146 -0
  22. package/dist/src/const.d.ts +128 -0
  23. package/dist/src/const.js +168 -0
  24. package/dist/src/dm-policy.d.ts +29 -0
  25. package/dist/src/dm-policy.js +146 -0
  26. package/dist/src/dynamic-agent.d.ts +37 -0
  27. package/dist/src/dynamic-agent.js +67 -0
  28. package/dist/src/dynamic-routing.d.ts +65 -0
  29. package/dist/src/dynamic-routing.js +62 -0
  30. package/dist/src/endpoint-dispatch.d.ts +54 -0
  31. package/dist/src/endpoint-dispatch.js +967 -0
  32. package/dist/src/endpoint-event-adapter.d.ts +15 -0
  33. package/dist/src/endpoint-event-adapter.js +427 -0
  34. package/dist/src/group-policy.d.ts +30 -0
  35. package/dist/src/group-policy.js +126 -0
  36. package/dist/src/http.d.ts +27 -0
  37. package/dist/src/http.js +168 -0
  38. package/dist/src/im-runtime-telemetry.d.ts +25 -0
  39. package/dist/src/im-runtime-telemetry.js +68 -0
  40. package/dist/src/interface.d.ts +192 -0
  41. package/dist/src/interface.js +5 -0
  42. package/dist/src/markdown-chunk.d.ts +1 -0
  43. package/dist/src/markdown-chunk.js +396 -0
  44. package/dist/src/mcp/index.d.ts +6 -0
  45. package/dist/src/mcp/index.js +28 -0
  46. package/dist/src/mcp/interceptors/biz-error.d.ts +11 -0
  47. package/dist/src/mcp/interceptors/biz-error.js +73 -0
  48. package/dist/src/mcp/interceptors/doc-auth-error.d.ts +10 -0
  49. package/dist/src/mcp/interceptors/doc-auth-error.js +235 -0
  50. package/dist/src/mcp/interceptors/index.d.ts +35 -0
  51. package/dist/src/mcp/interceptors/index.js +143 -0
  52. package/dist/src/mcp/interceptors/msg-media.d.ts +11 -0
  53. package/dist/src/mcp/interceptors/msg-media.js +201 -0
  54. package/dist/src/mcp/interceptors/smartpage-create.d.ts +30 -0
  55. package/dist/src/mcp/interceptors/smartpage-create.js +252 -0
  56. package/dist/src/mcp/interceptors/smartpage-export.d.ts +17 -0
  57. package/dist/src/mcp/interceptors/smartpage-export.js +135 -0
  58. package/dist/src/mcp/interceptors/smartsheet-upload.d.ts +22 -0
  59. package/dist/src/mcp/interceptors/smartsheet-upload.js +388 -0
  60. package/dist/src/mcp/interceptors/types.d.ts +64 -0
  61. package/dist/src/mcp/interceptors/types.js +8 -0
  62. package/dist/src/mcp/schema.d.ts +11 -0
  63. package/dist/src/mcp/schema.js +115 -0
  64. package/dist/src/mcp/tool.d.ts +63 -0
  65. package/dist/src/mcp/tool.js +318 -0
  66. package/dist/src/mcp/transport.d.ts +94 -0
  67. package/dist/src/mcp/transport.js +702 -0
  68. package/dist/src/media-handler.d.ts +55 -0
  69. package/dist/src/media-handler.js +306 -0
  70. package/dist/src/media-uploader.d.ts +142 -0
  71. package/dist/src/media-uploader.js +446 -0
  72. package/dist/src/message-parser.d.ts +104 -0
  73. package/dist/src/message-parser.js +232 -0
  74. package/dist/src/message-sender.d.ts +54 -0
  75. package/dist/src/message-sender.js +210 -0
  76. package/dist/src/monitor.d.ts +69 -0
  77. package/dist/src/monitor.js +1846 -0
  78. package/dist/src/onboarding.d.ts +8 -0
  79. package/dist/src/onboarding.js +248 -0
  80. package/dist/src/openclaw-compat.d.ts +148 -0
  81. package/dist/src/openclaw-compat.js +839 -0
  82. package/dist/src/proactive-markdown-send.d.ts +14 -0
  83. package/dist/src/proactive-markdown-send.js +205 -0
  84. package/dist/src/reqid-store.d.ts +23 -0
  85. package/dist/src/reqid-store.js +136 -0
  86. package/dist/src/runtime.d.ts +2 -0
  87. package/dist/src/runtime.js +7 -0
  88. package/dist/src/shared/command-auth.d.ts +23 -0
  89. package/dist/src/shared/command-auth.js +112 -0
  90. package/dist/src/shared/xml-parser.d.ts +46 -0
  91. package/dist/src/shared/xml-parser.js +228 -0
  92. package/dist/src/state-dir-resolve.d.ts +2 -0
  93. package/dist/src/state-dir-resolve.js +33 -0
  94. package/dist/src/state-manager.d.ts +115 -0
  95. package/dist/src/state-manager.js +413 -0
  96. package/dist/src/target.d.ts +35 -0
  97. package/dist/src/target.js +71 -0
  98. package/dist/src/template-card-manager.d.ts +55 -0
  99. package/dist/src/template-card-manager.js +316 -0
  100. package/dist/src/template-card-parser.d.ts +37 -0
  101. package/dist/src/template-card-parser.js +672 -0
  102. package/dist/src/timeout.d.ts +20 -0
  103. package/dist/src/timeout.js +57 -0
  104. package/dist/src/types/account.d.ts +29 -0
  105. package/dist/src/types/account.js +5 -0
  106. package/dist/src/types/config.d.ts +98 -0
  107. package/dist/src/types/config.js +8 -0
  108. package/dist/src/types/constants.d.ts +42 -0
  109. package/dist/src/types/constants.js +45 -0
  110. package/dist/src/types/index.d.ts +7 -0
  111. package/dist/src/types/index.js +17 -0
  112. package/dist/src/types/message.d.ts +238 -0
  113. package/dist/src/types/message.js +6 -0
  114. package/dist/src/utils.d.ts +148 -0
  115. package/dist/src/utils.js +92 -0
  116. package/dist/src/version.d.ts +2 -0
  117. package/dist/src/version.js +28 -0
  118. package/dist/src/webhook/command-auth.d.ts +47 -0
  119. package/dist/src/webhook/command-auth.js +137 -0
  120. package/dist/src/webhook/gateway.d.ts +36 -0
  121. package/dist/src/webhook/gateway.js +297 -0
  122. package/dist/src/webhook/handler.d.ts +19 -0
  123. package/dist/src/webhook/handler.js +481 -0
  124. package/dist/src/webhook/helpers.d.ts +157 -0
  125. package/dist/src/webhook/helpers.js +936 -0
  126. package/dist/src/webhook/http.d.ts +27 -0
  127. package/dist/src/webhook/http.js +168 -0
  128. package/dist/src/webhook/index.d.ts +11 -0
  129. package/dist/src/webhook/index.js +43 -0
  130. package/dist/src/webhook/media.d.ts +30 -0
  131. package/dist/src/webhook/media.js +152 -0
  132. package/dist/src/webhook/monitor.d.ts +59 -0
  133. package/dist/src/webhook/monitor.js +1672 -0
  134. package/dist/src/webhook/state.d.ts +220 -0
  135. package/dist/src/webhook/state.js +568 -0
  136. package/dist/src/webhook/target.d.ts +41 -0
  137. package/dist/src/webhook/target.js +165 -0
  138. package/dist/src/webhook/types.d.ts +348 -0
  139. package/dist/src/webhook/types.js +36 -0
  140. package/dist/src/webhook/video-frame.d.ts +13 -0
  141. package/dist/src/webhook/video-frame.js +108 -0
  142. package/openclaw.plugin.json +19 -0
  143. package/package.json +96 -0
  144. package/schema.json +534 -0
  145. package/scripts/generate-schema.mjs +33 -0
  146. package/skills/wecom-contact/SKILL.md +162 -0
  147. package/skills/wecom-doc/SKILL.md +162 -0
  148. package/skills/wecom-doc/references/create-doc.md +56 -0
  149. package/skills/wecom-doc/references/edit-doc-content.md +68 -0
  150. package/skills/wecom-doc/references/get-doc-content.md +88 -0
  151. package/skills/wecom-doc/references/smartpage-create.md +125 -0
  152. package/skills/wecom-doc/references/smartpage-export.md +160 -0
  153. package/skills/wecom-meeting/SKILL.md +441 -0
  154. package/skills/wecom-meeting/references/example-full.md +30 -0
  155. package/skills/wecom-meeting/references/example-reminder.md +46 -0
  156. package/skills/wecom-meeting/references/example-security.md +22 -0
  157. package/skills/wecom-meeting/references/response-get-meeting-info.md +148 -0
  158. package/skills/wecom-msg/SKILL.md +157 -0
  159. package/skills/wecom-msg/references/api-get-messages.md +93 -0
  160. package/skills/wecom-msg/references/api-get-msg-chat-list.md +58 -0
  161. package/skills/wecom-msg/references/api-get-msg-media.md +44 -0
  162. package/skills/wecom-msg/references/api-send-message.md +39 -0
  163. package/skills/wecom-preflight/SKILL.md +141 -0
  164. package/skills/wecom-schedule/SKILL.md +161 -0
  165. package/skills/wecom-schedule/references/api-check-availability.md +56 -0
  166. package/skills/wecom-schedule/references/api-create-schedule.md +38 -0
  167. package/skills/wecom-schedule/references/api-get-schedule-detail.md +81 -0
  168. package/skills/wecom-schedule/references/api-update-schedule.md +32 -0
  169. package/skills/wecom-schedule/references/ref-reminders.md +24 -0
  170. package/skills/wecom-send-media/SKILL.md +68 -0
  171. package/skills/wecom-send-template-card/SKILL.md +157 -0
  172. package/skills/wecom-send-template-card/references/api-template-card-types.md +358 -0
  173. package/skills/wecom-smartsheet/SKILL.md +164 -0
  174. package/skills/wecom-smartsheet/references/smartsheet-cell-value-formats.md +163 -0
  175. package/skills/wecom-smartsheet/references/smartsheet-field-types.md +44 -0
  176. package/skills/wecom-smartsheet/references/smartsheet-get-records.md +96 -0
  177. package/skills/wecom-smartsheet/references/webhook-examples.md +185 -0
  178. package/skills/wecom-smartsheet/references/webhook-fallback.md +184 -0
  179. package/skills/wecom-todo/SKILL.md +392 -0
  180. package/skills/wecom-todo/examples/workflows.md +163 -0
@@ -0,0 +1,446 @@
1
+ "use strict";
2
+ /**
3
+ * 企业微信出站媒体上传工具模块
4
+ *
5
+ * 负责:
6
+ * - 从 mediaUrl 加载文件 buffer(远程 URL 或本地路径均支持)
7
+ * - 检测 MIME 类型并映射为企微媒体类型
8
+ * - 文件大小检查与降级策略
9
+ */
10
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
11
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
12
+ return new (P || (P = Promise))(function (resolve, reject) {
13
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
14
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
15
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
16
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
17
+ });
18
+ };
19
+ var __generator = (this && this.__generator) || function (thisArg, body) {
20
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
21
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
22
+ function verb(n) { return function (v) { return step([n, v]); }; }
23
+ function step(op) {
24
+ if (f) throw new TypeError("Generator is already executing.");
25
+ while (_) try {
26
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
27
+ if (y = 0, t) op = [op[0] & 2, t.value];
28
+ switch (op[0]) {
29
+ case 0: case 1: t = op; break;
30
+ case 4: _.label++; return { value: op[1], done: false };
31
+ case 5: _.label++; y = op[1]; op = [0]; continue;
32
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
33
+ default:
34
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
35
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
36
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
37
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
38
+ if (t[2]) _.ops.pop();
39
+ _.trys.pop(); continue;
40
+ }
41
+ op = body.call(thisArg, _);
42
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
43
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
44
+ }
45
+ };
46
+ exports.__esModule = true;
47
+ exports.uploadAndReplyMedia = exports.uploadAndSendMedia = exports.applyFileSizeLimits = exports.resolveMediaFile = exports.detectWeComMediaType = void 0;
48
+ var openclaw_compat_js_1 = require("./openclaw-compat.js");
49
+ var const_js_1 = require("./const.js");
50
+ // ============================================================================
51
+ // MIME → 企微媒体类型映射
52
+ // ============================================================================
53
+ /**
54
+ * 根据 MIME 类型检测企微媒体类型
55
+ *
56
+ * @param mimeType - MIME 类型字符串
57
+ * @returns 企微媒体类型
58
+ */
59
+ function detectWeComMediaType(mimeType) {
60
+ var mime = mimeType.toLowerCase();
61
+ // 图片类型
62
+ if (mime.startsWith("image/")) {
63
+ return "image";
64
+ }
65
+ // 视频类型
66
+ if (mime.startsWith("video/")) {
67
+ return "video";
68
+ }
69
+ // 语音类型
70
+ if (mime.startsWith("audio/") ||
71
+ mime === "application/ogg" // OGG 音频容器
72
+ ) {
73
+ return "voice";
74
+ }
75
+ // 其他类型默认为文件
76
+ return "file";
77
+ }
78
+ exports.detectWeComMediaType = detectWeComMediaType;
79
+ // ============================================================================
80
+ // 媒体文件加载
81
+ // ============================================================================
82
+ /**
83
+ * 从 mediaUrl 加载媒体文件
84
+ *
85
+ * 支持远程 URL(http/https)和本地路径(file:// 或绝对路径),
86
+ * 利用 openclaw plugin-sdk 的 loadOutboundMediaFromUrl 统一处理。
87
+ *
88
+ * @param mediaUrl - 媒体文件的 URL 或本地路径
89
+ * @param mediaLocalRoots - 允许读取本地文件的安全白名单目录
90
+ * @returns 解析后的媒体文件信息
91
+ */
92
+ function resolveMediaFile(mediaUrl, mediaLocalRoots, gatewayToken, gatewayBaseUrl) {
93
+ return __awaiter(this, void 0, void 0, function () {
94
+ var result, contentType, detected, fileName;
95
+ return __generator(this, function (_a) {
96
+ switch (_a.label) {
97
+ case 0: return [4 /*yield*/, openclaw_compat_js_1.loadOutboundMediaFromUrl(mediaUrl, {
98
+ maxBytes: const_js_1.ABSOLUTE_MAX_BYTES,
99
+ mediaLocalRoots: mediaLocalRoots,
100
+ gatewayToken: gatewayToken,
101
+ trustedGatewayBaseUrl: gatewayBaseUrl
102
+ })];
103
+ case 1:
104
+ result = _a.sent();
105
+ if (!result.buffer || result.buffer.length === 0) {
106
+ throw new Error("Failed to load media from " + mediaUrl + ": empty buffer");
107
+ }
108
+ contentType = result.contentType || "application/octet-stream";
109
+ if (!(contentType === "application/octet-stream" ||
110
+ contentType === "text/plain")) return [3 /*break*/, 3];
111
+ return [4 /*yield*/, openclaw_compat_js_1.detectMime(result.buffer)];
112
+ case 2:
113
+ detected = _a.sent();
114
+ if (detected) {
115
+ contentType = detected;
116
+ }
117
+ _a.label = 3;
118
+ case 3:
119
+ fileName = extractFileName(mediaUrl, result.fileName, contentType);
120
+ return [2 /*return*/, {
121
+ buffer: result.buffer,
122
+ contentType: contentType,
123
+ fileName: fileName
124
+ }];
125
+ }
126
+ });
127
+ });
128
+ }
129
+ exports.resolveMediaFile = resolveMediaFile;
130
+ // ============================================================================
131
+ // 文件大小检查与降级
132
+ // ============================================================================
133
+ /** 企微语音消息仅支持 AMR 格式 */
134
+ var VOICE_SUPPORTED_MIMES = new Set(["audio/amr"]);
135
+ /**
136
+ * 检查文件大小并执行降级策略
137
+ *
138
+ * 降级规则:
139
+ * - voice 非 AMR 格式 → 降级为 file(企微后台仅支持 AMR)
140
+ * - image 超过 10MB → 降级为 file
141
+ * - video 超过 10MB → 降级为 file
142
+ * - voice 超过 2MB → 降级为 file
143
+ * - file 超过 20MB → 拒绝发送
144
+ *
145
+ * @param fileSize - 文件大小(字节)
146
+ * @param detectedType - 检测到的企微媒体类型
147
+ * @param contentType - 文件的 MIME 类型(用于语音格式校验)
148
+ * @returns 大小检查结果
149
+ */
150
+ function applyFileSizeLimits(fileSize, detectedType, contentType) {
151
+ var fileSizeMB = (fileSize / (1024 * 1024)).toFixed(2);
152
+ // 先检查绝对上限(20MB)
153
+ if (fileSize > const_js_1.ABSOLUTE_MAX_BYTES) {
154
+ return {
155
+ finalType: detectedType,
156
+ shouldReject: true,
157
+ rejectReason: "\u6587\u4EF6\u5927\u5C0F " + fileSizeMB + "MB \u8D85\u8FC7\u4E86\u4F01\u4E1A\u5FAE\u4FE1\u5141\u8BB8\u7684\u6700\u5927\u9650\u5236 20MB\uFF0C\u65E0\u6CD5\u53D1\u9001\u3002\u8BF7\u5C1D\u8BD5\u538B\u7F29\u6587\u4EF6\u6216\u51CF\u5C0F\u6587\u4EF6\u5927\u5C0F\u3002",
158
+ downgraded: false
159
+ };
160
+ }
161
+ // 按类型检查大小限制
162
+ switch (detectedType) {
163
+ case "image":
164
+ if (fileSize > const_js_1.IMAGE_MAX_BYTES) {
165
+ return {
166
+ finalType: "file",
167
+ shouldReject: false,
168
+ downgraded: true,
169
+ downgradeNote: "\u56FE\u7247\u5927\u5C0F " + fileSizeMB + "MB \u8D85\u8FC7 10MB \u9650\u5236\uFF0C\u5DF2\u8F6C\u4E3A\u6587\u4EF6\u683C\u5F0F\u53D1\u9001"
170
+ };
171
+ }
172
+ break;
173
+ case "video":
174
+ if (fileSize > const_js_1.VIDEO_MAX_BYTES) {
175
+ return {
176
+ finalType: "file",
177
+ shouldReject: false,
178
+ downgraded: true,
179
+ downgradeNote: "\u89C6\u9891\u5927\u5C0F " + fileSizeMB + "MB \u8D85\u8FC7 10MB \u9650\u5236\uFF0C\u5DF2\u8F6C\u4E3A\u6587\u4EF6\u683C\u5F0F\u53D1\u9001"
180
+ };
181
+ }
182
+ break;
183
+ case "voice":
184
+ // 企微语音消息仅支持 AMR 格式,非 AMR 一律降级为文件
185
+ if (contentType && !VOICE_SUPPORTED_MIMES.has(contentType.toLowerCase())) {
186
+ return {
187
+ finalType: "file",
188
+ shouldReject: false,
189
+ downgraded: true,
190
+ downgradeNote: "\u8BED\u97F3\u683C\u5F0F " + contentType + " \u4E0D\u652F\u6301\uFF0C\u4F01\u5FAE\u4EC5\u652F\u6301 AMR \u683C\u5F0F\uFF0C\u5DF2\u8F6C\u4E3A\u6587\u4EF6\u683C\u5F0F\u53D1\u9001"
191
+ };
192
+ }
193
+ if (fileSize > const_js_1.VOICE_MAX_BYTES) {
194
+ return {
195
+ finalType: "file",
196
+ shouldReject: false,
197
+ downgraded: true,
198
+ downgradeNote: "\u8BED\u97F3\u5927\u5C0F " + fileSizeMB + "MB \u8D85\u8FC7 2MB \u9650\u5236\uFF0C\u5DF2\u8F6C\u4E3A\u6587\u4EF6\u683C\u5F0F\u53D1\u9001"
199
+ };
200
+ }
201
+ break;
202
+ case "file":
203
+ // file 类型在绝对上限内即可
204
+ break;
205
+ }
206
+ // 无需降级
207
+ return {
208
+ finalType: detectedType,
209
+ shouldReject: false,
210
+ downgraded: false
211
+ };
212
+ }
213
+ exports.applyFileSizeLimits = applyFileSizeLimits;
214
+ // ============================================================================
215
+ // 辅助函数
216
+ // ============================================================================
217
+ /**
218
+ * 从 URL/路径中提取文件名
219
+ */
220
+ function extractFileName(mediaUrl, providedFileName, contentType) {
221
+ // 优先使用提供的文件名
222
+ if (providedFileName) {
223
+ return sanitizeFileName(providedFileName);
224
+ }
225
+ // 尝试从 URL 中提取
226
+ try {
227
+ var urlObj = new URL(mediaUrl, "file://");
228
+ for (var _i = 0, _a = [
229
+ "relative_path",
230
+ "path",
231
+ "file_path",
232
+ "filepath",
233
+ "target_path",
234
+ "source_path",
235
+ ]; _i < _a.length; _i++) {
236
+ var key = _a[_i];
237
+ var rawPath = urlObj.searchParams.get(key);
238
+ if (!rawPath)
239
+ continue;
240
+ var fromQueryPath = sanitizeFileName(decodeURIComponent(rawPath));
241
+ if (fromQueryPath)
242
+ return appendExtensionIfMissing(fromQueryPath, contentType);
243
+ }
244
+ var fromPath = sanitizeFileName(decodeURIComponent(urlObj.pathname));
245
+ if (fromPath && fromPath.includes(".")) {
246
+ return appendExtensionIfMissing(fromPath, contentType);
247
+ }
248
+ }
249
+ catch (_b) {
250
+ // 尝试作为普通路径处理
251
+ var fromPath = sanitizeFileName(mediaUrl);
252
+ if (fromPath && fromPath.includes(".")) {
253
+ return appendExtensionIfMissing(fromPath, contentType);
254
+ }
255
+ }
256
+ // 使用 MIME 类型生成默认文件名
257
+ var ext = mimeToExtension(contentType || "application/octet-stream");
258
+ return "media_" + Date.now() + ext;
259
+ }
260
+ function sanitizeFileName(name) {
261
+ var trimmed = String(name || "").trim();
262
+ if (!trimmed)
263
+ return "";
264
+ var base = trimmed.replace(/\\/g, "/").split("/").filter(Boolean).pop() || "";
265
+ var safe = base.replace(/[\\/:*?"<>|\u0000-\u001f]/g, "_");
266
+ return safe === "." || safe === ".." ? "" : safe;
267
+ }
268
+ function appendExtensionIfMissing(name, contentType) {
269
+ if (!name)
270
+ return "";
271
+ if (name.includes("."))
272
+ return name;
273
+ var ext = mimeToExtension(contentType || "application/octet-stream");
274
+ return ext ? "" + name + ext : name;
275
+ }
276
+ /**
277
+ * MIME 类型转文件扩展名
278
+ */
279
+ function mimeToExtension(mime) {
280
+ var map = {
281
+ "image/jpeg": ".jpg",
282
+ "image/png": ".png",
283
+ "image/gif": ".gif",
284
+ "image/webp": ".webp",
285
+ "image/bmp": ".bmp",
286
+ "image/svg+xml": ".svg",
287
+ "video/mp4": ".mp4",
288
+ "video/quicktime": ".mov",
289
+ "video/x-msvideo": ".avi",
290
+ "video/webm": ".webm",
291
+ "audio/mpeg": ".mp3",
292
+ "audio/ogg": ".ogg",
293
+ "audio/wav": ".wav",
294
+ "audio/amr": ".amr",
295
+ "audio/aac": ".aac",
296
+ "application/pdf": ".pdf",
297
+ "application/zip": ".zip",
298
+ "application/msword": ".doc",
299
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": ".docx",
300
+ "application/vnd.ms-excel": ".xls",
301
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx",
302
+ "text/plain": ".txt"
303
+ };
304
+ return map[mime] || ".bin";
305
+ }
306
+ /**
307
+ * 公共媒体上传+发送流程
308
+ *
309
+ * 统一处理:resolveMediaFile → detectType → sizeCheck → uploadMedia → sendMediaMessage
310
+ * 媒体消息统一走 aibot_send_msg 主动发送,避免多文件场景下 reqId 只能用一次的问题。
311
+ * channel.ts 的 sendMedia 和 monitor.ts 的 deliver 回调都使用此函数。
312
+ */
313
+ function uploadAndSendMedia(options) {
314
+ var _a, _b;
315
+ return __awaiter(this, void 0, void 0, function () {
316
+ var wsClient, mediaUrl, chatId, mediaLocalRoots, gatewayToken, gatewayBaseUrl, log, errorLog, media, detectedType, sizeCheck, finalType, uploadResult, result, messageId, err_1, errMsg;
317
+ return __generator(this, function (_c) {
318
+ switch (_c.label) {
319
+ case 0:
320
+ wsClient = options.wsClient, mediaUrl = options.mediaUrl, chatId = options.chatId, mediaLocalRoots = options.mediaLocalRoots, gatewayToken = options.gatewayToken, gatewayBaseUrl = options.gatewayBaseUrl, log = options.log, errorLog = options.errorLog;
321
+ _c.label = 1;
322
+ case 1:
323
+ _c.trys.push([1, 5, , 6]);
324
+ // 1. 加载媒体文件
325
+ log === null || log === void 0 ? void 0 : log("[wecom] Uploading media: url=" + mediaUrl);
326
+ return [4 /*yield*/, resolveMediaFile(mediaUrl, mediaLocalRoots, gatewayToken, gatewayBaseUrl)];
327
+ case 2:
328
+ media = _c.sent();
329
+ detectedType = detectWeComMediaType(media.contentType);
330
+ sizeCheck = applyFileSizeLimits(media.buffer.length, detectedType, media.contentType);
331
+ if (sizeCheck.shouldReject) {
332
+ errorLog === null || errorLog === void 0 ? void 0 : errorLog("[wecom] Media rejected: " + sizeCheck.rejectReason);
333
+ return [2 /*return*/, {
334
+ ok: false,
335
+ rejected: true,
336
+ rejectReason: sizeCheck.rejectReason,
337
+ finalType: sizeCheck.finalType,
338
+ fileName: media.fileName
339
+ }];
340
+ }
341
+ finalType = sizeCheck.finalType;
342
+ return [4 /*yield*/, wsClient.uploadMedia(media.buffer, {
343
+ type: finalType,
344
+ filename: media.fileName
345
+ })];
346
+ case 3:
347
+ uploadResult = _c.sent();
348
+ log === null || log === void 0 ? void 0 : log("[wecom] Media uploaded: media_id=" + uploadResult.media_id + ", type=" + finalType);
349
+ return [4 /*yield*/, wsClient.sendMediaMessage(chatId, finalType, uploadResult.media_id)];
350
+ case 4:
351
+ result = _c.sent();
352
+ messageId = (_b = (_a = result === null || result === void 0 ? void 0 : result.headers) === null || _a === void 0 ? void 0 : _a.req_id) !== null && _b !== void 0 ? _b : "wecom-media-" + Date.now();
353
+ log === null || log === void 0 ? void 0 : log("[wecom] Media sent via sendMediaMessage: chatId=" + chatId + ", type=" + finalType);
354
+ return [2 /*return*/, {
355
+ ok: true,
356
+ messageId: messageId,
357
+ fileName: media.fileName,
358
+ finalType: finalType,
359
+ downgraded: sizeCheck.downgraded,
360
+ downgradeNote: sizeCheck.downgradeNote
361
+ }];
362
+ case 5:
363
+ err_1 = _c.sent();
364
+ errMsg = String(err_1);
365
+ errorLog === null || errorLog === void 0 ? void 0 : errorLog("[wecom] Failed to upload/send media: url=" + mediaUrl + ", error=" + errMsg);
366
+ return [2 /*return*/, {
367
+ ok: false,
368
+ error: errMsg
369
+ }];
370
+ case 6: return [2 /*return*/];
371
+ }
372
+ });
373
+ });
374
+ }
375
+ exports.uploadAndSendMedia = uploadAndSendMedia;
376
+ /**
377
+ * 被动回复媒体上传+发送流程
378
+ *
379
+ * 统一处理:resolveMediaFile → detectType → sizeCheck → uploadMedia → replyMedia
380
+ * 通过 aibot_respond_msg 被动回复通道发送媒体消息,可以覆盖之前的 THINKING_MESSAGE。
381
+ *
382
+ * 适用场景:回包只有媒体没有文本时,第一个媒体文件用此方法发送以清理 thinking 状态。
383
+ */
384
+ function uploadAndReplyMedia(options) {
385
+ var _a, _b;
386
+ return __awaiter(this, void 0, void 0, function () {
387
+ var wsClient, mediaUrl, frame, mediaLocalRoots, gatewayToken, gatewayBaseUrl, log, errorLog, media, detectedType, sizeCheck, finalType, uploadResult, result, messageId, err_2, errMsg;
388
+ return __generator(this, function (_c) {
389
+ switch (_c.label) {
390
+ case 0:
391
+ wsClient = options.wsClient, mediaUrl = options.mediaUrl, frame = options.frame, mediaLocalRoots = options.mediaLocalRoots, gatewayToken = options.gatewayToken, gatewayBaseUrl = options.gatewayBaseUrl, log = options.log, errorLog = options.errorLog;
392
+ _c.label = 1;
393
+ case 1:
394
+ _c.trys.push([1, 5, , 6]);
395
+ // 1. 加载媒体文件
396
+ log === null || log === void 0 ? void 0 : log("[wecom] Uploading media (reply mode): url=" + mediaUrl);
397
+ return [4 /*yield*/, resolveMediaFile(mediaUrl, mediaLocalRoots, gatewayToken, gatewayBaseUrl)];
398
+ case 2:
399
+ media = _c.sent();
400
+ detectedType = detectWeComMediaType(media.contentType);
401
+ sizeCheck = applyFileSizeLimits(media.buffer.length, detectedType, media.contentType);
402
+ if (sizeCheck.shouldReject) {
403
+ errorLog === null || errorLog === void 0 ? void 0 : errorLog("[wecom] Media rejected: " + sizeCheck.rejectReason);
404
+ return [2 /*return*/, {
405
+ ok: false,
406
+ rejected: true,
407
+ rejectReason: sizeCheck.rejectReason,
408
+ finalType: sizeCheck.finalType,
409
+ fileName: media.fileName
410
+ }];
411
+ }
412
+ finalType = sizeCheck.finalType;
413
+ return [4 /*yield*/, wsClient.uploadMedia(media.buffer, {
414
+ type: finalType,
415
+ filename: media.fileName
416
+ })];
417
+ case 3:
418
+ uploadResult = _c.sent();
419
+ log === null || log === void 0 ? void 0 : log("[wecom] Media uploaded: media_id=" + uploadResult.media_id + ", type=" + finalType);
420
+ return [4 /*yield*/, wsClient.replyMedia(frame, finalType, uploadResult.media_id)];
421
+ case 4:
422
+ result = _c.sent();
423
+ messageId = (_b = (_a = result === null || result === void 0 ? void 0 : result.headers) === null || _a === void 0 ? void 0 : _a.req_id) !== null && _b !== void 0 ? _b : "wecom-reply-media-" + Date.now();
424
+ log === null || log === void 0 ? void 0 : log("[wecom] Media sent via replyMedia (passive reply): type=" + finalType);
425
+ return [2 /*return*/, {
426
+ ok: true,
427
+ messageId: messageId,
428
+ fileName: media.fileName,
429
+ finalType: finalType,
430
+ downgraded: sizeCheck.downgraded,
431
+ downgradeNote: sizeCheck.downgradeNote
432
+ }];
433
+ case 5:
434
+ err_2 = _c.sent();
435
+ errMsg = String(err_2);
436
+ errorLog === null || errorLog === void 0 ? void 0 : errorLog("[wecom] Failed to upload/reply media: url=" + mediaUrl + ", error=" + errMsg);
437
+ return [2 /*return*/, {
438
+ ok: false,
439
+ error: errMsg
440
+ }];
441
+ case 6: return [2 /*return*/];
442
+ }
443
+ });
444
+ });
445
+ }
446
+ exports.uploadAndReplyMedia = uploadAndReplyMedia;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * 企业微信消息内容解析模块
3
+ *
4
+ * 负责从 WsFrame 中提取文本、图片、引用等内容
5
+ */
6
+ export interface MessageBody {
7
+ msgid: string;
8
+ aibotid?: string;
9
+ chatid?: string;
10
+ chattype: "single" | "group";
11
+ from: {
12
+ corpid?: string;
13
+ userid: string;
14
+ /** 会话 ID(权限变更事件回调中携带) */
15
+ chat_id?: string;
16
+ };
17
+ response_url?: string;
18
+ msgtype: string;
19
+ text?: {
20
+ content: string;
21
+ };
22
+ image?: {
23
+ url?: string;
24
+ aeskey?: string;
25
+ };
26
+ voice?: {
27
+ content?: string;
28
+ };
29
+ mixed?: {
30
+ msg_item: Array<{
31
+ msgtype: "text" | "image";
32
+ text?: {
33
+ content: string;
34
+ };
35
+ image?: {
36
+ url?: string;
37
+ aeskey?: string;
38
+ };
39
+ }>;
40
+ };
41
+ file?: {
42
+ url?: string;
43
+ aeskey?: string;
44
+ };
45
+ video?: {
46
+ url?: string;
47
+ aeskey?: string;
48
+ };
49
+ quote?: {
50
+ msgtype: string;
51
+ text?: {
52
+ content: string;
53
+ };
54
+ voice?: {
55
+ content: string;
56
+ };
57
+ image?: {
58
+ url?: string;
59
+ aeskey?: string;
60
+ };
61
+ file?: {
62
+ url?: string;
63
+ aeskey?: string;
64
+ };
65
+ video?: {
66
+ url?: string;
67
+ aeskey?: string;
68
+ };
69
+ };
70
+ event?: {
71
+ eventtype?: string;
72
+ template_card_event?: {
73
+ card_type?: string;
74
+ event_key?: string;
75
+ task_id?: string;
76
+ selected_items?: {
77
+ selected_item?: Array<{
78
+ question_key?: string;
79
+ option_ids?: {
80
+ option_id?: string[];
81
+ };
82
+ }>;
83
+ };
84
+ };
85
+ /** 权限变更事件回调(如文档授权) */
86
+ auth_change_event?: {
87
+ /** 当前权限列表:1-新建和编辑文档;2-获取成员文档内容 */
88
+ auth_list?: number[];
89
+ };
90
+ };
91
+ }
92
+ export interface ParsedMessageContent {
93
+ textParts: string[];
94
+ imageUrls: string[];
95
+ imageAesKeys: Map<string, string>;
96
+ fileUrls: string[];
97
+ fileAesKeys: Map<string, string>;
98
+ quoteContent: string | undefined;
99
+ }
100
+ /**
101
+ * 解析消息内容(支持单条消息、图文混排、事件回调和引用消息)
102
+ * @returns 提取的文本数组、图片URL数组和引用消息内容
103
+ */
104
+ export declare function parseMessageContent(body: MessageBody): ParsedMessageContent;