@yanhaidao/wecom 2.4.120 → 2.5.110

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 (323) hide show
  1. package/README.md +4 -5
  2. package/dist/index.js +68 -0
  3. package/dist/src/accounts.js +20 -0
  4. package/dist/src/agent/handler.js +895 -0
  5. package/dist/src/agent/index.js +5 -0
  6. package/dist/src/app/account-runtime.js +216 -0
  7. package/dist/src/app/bootstrap.js +19 -0
  8. package/dist/src/app/index.js +118 -0
  9. package/dist/src/capability/agent/delivery-service.js +63 -0
  10. package/dist/src/capability/agent/fallback-policy.js +6 -0
  11. package/dist/src/capability/agent/ingress-service.js +33 -0
  12. package/dist/src/capability/agent/upstream-delivery-service.js +71 -0
  13. package/dist/src/capability/bot/dispatch-config.js +45 -0
  14. package/dist/src/capability/bot/fallback-delivery.js +147 -0
  15. package/dist/src/capability/bot/local-path-delivery.js +178 -0
  16. package/dist/src/capability/bot/sandbox-media.js +138 -0
  17. package/dist/src/capability/bot/service.js +49 -0
  18. package/dist/src/capability/bot/stream-delivery.js +321 -0
  19. package/dist/src/capability/bot/stream-finalizer.js +81 -0
  20. package/dist/src/capability/bot/stream-orchestrator.js +318 -0
  21. package/dist/src/capability/bot/types.js +1 -0
  22. package/{src/capability/calendar/client.ts → dist/src/capability/calendar/client.js} +118 -241
  23. package/{src/capability/calendar/schema.ts → dist/src/capability/calendar/schema.js} +0 -38
  24. package/dist/src/capability/calendar/tool.js +365 -0
  25. package/dist/src/capability/calendar/types.js +12 -0
  26. package/{src/capability/doc/client.ts → dist/src/capability/doc/client.js} +370 -605
  27. package/{src/capability/doc/schema.ts → dist/src/capability/doc/schema.js} +345 -394
  28. package/dist/src/capability/doc/tool.js +1556 -0
  29. package/dist/src/capability/doc/types.js +113 -0
  30. package/dist/src/capability/mcp/index.js +3 -0
  31. package/dist/src/capability/mcp/schema.js +102 -0
  32. package/dist/src/capability/mcp/tool.js +146 -0
  33. package/dist/src/capability/mcp/transport.js +293 -0
  34. package/dist/src/channel.js +224 -0
  35. package/dist/src/config/accounts.js +236 -0
  36. package/dist/src/config/derived-paths.js +31 -0
  37. package/dist/src/config/index.js +7 -0
  38. package/dist/src/config/media.js +110 -0
  39. package/dist/src/config/network.js +32 -0
  40. package/dist/src/config/routing.js +20 -0
  41. package/dist/src/config/runtime-config.js +25 -0
  42. package/dist/src/config/schema.js +4 -0
  43. package/{src/config-schema.ts → dist/src/config-schema.js} +1 -1
  44. package/dist/src/context-store.js +219 -0
  45. package/{src/crypto/aes.ts → dist/src/crypto/aes.js} +11 -28
  46. package/dist/src/crypto/index.js +9 -0
  47. package/{src/crypto/signature.ts → dist/src/crypto/signature.js} +3 -18
  48. package/{src/crypto/xml.ts → dist/src/crypto/xml.js} +3 -11
  49. package/dist/src/crypto.js +145 -0
  50. package/dist/src/domain/models.js +1 -0
  51. package/dist/src/domain/policies.js +32 -0
  52. package/{src/dynamic-agent.ts → dist/src/dynamic-agent.js} +36 -73
  53. package/dist/src/gateway-monitor.js +139 -0
  54. package/dist/src/http.js +114 -0
  55. package/{src/media.ts → dist/src/media.js} +21 -40
  56. package/dist/src/monitor/limits.js +7 -0
  57. package/dist/src/monitor/state.js +28 -0
  58. package/dist/src/monitor.js +84 -0
  59. package/dist/src/observability/audit-log.js +30 -0
  60. package/dist/src/observability/legacy-operational-event-store.js +22 -0
  61. package/dist/src/observability/raw-envelope-log.js +24 -0
  62. package/dist/src/observability/status-registry.js +9 -0
  63. package/dist/src/observability/transport-session-view.js +14 -0
  64. package/dist/src/onboarding.js +546 -0
  65. package/dist/src/outbound.js +557 -0
  66. package/dist/src/runtime/dispatcher.js +57 -0
  67. package/{src/runtime/index.ts → dist/src/runtime/index.js} +0 -1
  68. package/dist/src/runtime/outbound-intent.js +1 -0
  69. package/dist/src/runtime/reply-orchestrator.js +38 -0
  70. package/dist/src/runtime/routing-bridge.js +26 -0
  71. package/dist/src/runtime/session-manager.js +112 -0
  72. package/dist/src/runtime/source-registry.js +174 -0
  73. package/dist/src/runtime.js +1 -0
  74. package/dist/src/shared/command-auth.js +57 -0
  75. package/{src/shared/index.ts → dist/src/shared/index.js} +0 -1
  76. package/dist/src/shared/media-asset.js +65 -0
  77. package/dist/src/shared/media-service.js +59 -0
  78. package/dist/src/shared/media-types.js +1 -0
  79. package/{src/shared/xml-parser.ts → dist/src/shared/xml-parser.js} +72 -63
  80. package/dist/src/store/active-reply-store.js +41 -0
  81. package/dist/src/store/interfaces.js +1 -0
  82. package/dist/src/store/memory-store.js +33 -0
  83. package/dist/src/store/stream-batch-store.js +319 -0
  84. package/{src/target.ts → dist/src/target.js} +15 -48
  85. package/dist/src/transport/agent-api/client.js +168 -0
  86. package/dist/src/transport/agent-api/core.js +337 -0
  87. package/dist/src/transport/agent-api/delivery.js +28 -0
  88. package/dist/src/transport/agent-api/media-upload.js +4 -0
  89. package/dist/src/transport/agent-api/reply.js +24 -0
  90. package/dist/src/transport/agent-api/upstream-delivery.js +30 -0
  91. package/dist/src/transport/agent-api/upstream-media-upload.js +46 -0
  92. package/dist/src/transport/agent-api/upstream-reply.js +26 -0
  93. package/dist/src/transport/agent-callback/http-handler.js +30 -0
  94. package/dist/src/transport/agent-callback/inbound.js +4 -0
  95. package/dist/src/transport/agent-callback/reply.js +8 -0
  96. package/dist/src/transport/agent-callback/request-handler.js +189 -0
  97. package/dist/src/transport/agent-callback/session.js +15 -0
  98. package/dist/src/transport/bot-webhook/active-reply.js +27 -0
  99. package/dist/src/transport/bot-webhook/http-handler.js +31 -0
  100. package/dist/src/transport/bot-webhook/inbound-normalizer.js +496 -0
  101. package/dist/src/transport/bot-webhook/inbound.js +4 -0
  102. package/dist/src/transport/bot-webhook/message-shape.js +98 -0
  103. package/dist/src/transport/bot-webhook/protocol.js +124 -0
  104. package/dist/src/transport/bot-webhook/reply.js +9 -0
  105. package/dist/src/transport/bot-webhook/request-handler.js +285 -0
  106. package/dist/src/transport/bot-webhook/session.js +15 -0
  107. package/dist/src/transport/bot-ws/inbound.js +147 -0
  108. package/dist/src/transport/bot-ws/media.js +236 -0
  109. package/dist/src/transport/bot-ws/reply.js +310 -0
  110. package/dist/src/transport/bot-ws/sdk-adapter.js +257 -0
  111. package/dist/src/transport/bot-ws/session.js +15 -0
  112. package/dist/src/transport/http/common.js +78 -0
  113. package/dist/src/transport/http/registry.js +71 -0
  114. package/dist/src/transport/http/request-handler.js +51 -0
  115. package/{src/transport/index.ts → dist/src/transport/index.js} +2 -10
  116. package/dist/src/types/account.js +1 -0
  117. package/dist/src/types/config.js +1 -0
  118. package/dist/src/types/constants.js +28 -0
  119. package/dist/src/types/events.js +1 -0
  120. package/dist/src/types/index.js +1 -0
  121. package/dist/src/types/legacy-stream.js +1 -0
  122. package/dist/src/types/message.js +5 -0
  123. package/dist/src/types/runtime-context.js +1 -0
  124. package/dist/src/types/runtime.js +1 -0
  125. package/dist/src/types.js +1 -0
  126. package/dist/src/upstream/index.js +111 -0
  127. package/dist/src/wecom_msg_adapter/markdown_adapter.js +280 -0
  128. package/openclaw.plugin.json +15 -0
  129. package/package.json +18 -1
  130. package/.github/workflows/release.yml +0 -143
  131. package/GOVERNANCE.md +0 -26
  132. package/MENU_EVENT_CONF.md +0 -500
  133. package/MENU_EVENT_PLAN.md +0 -440
  134. package/SKILLS_CAL.md +0 -895
  135. package/SKILLS_DOC.md +0 -2288
  136. package/UPSTREAM_CONFIG.md +0 -170
  137. package/UPSTREAM_PLAN.md +0 -175
  138. package/assets/01.bot-add.png +0 -0
  139. package/assets/01.bot-setp2.png +0 -0
  140. package/assets/01.image.jpg +0 -0
  141. package/assets/02.agent.add.png +0 -0
  142. package/assets/02.agent.api-set.png +0 -0
  143. package/assets/02.image.jpg +0 -0
  144. package/assets/03.agent.page.png +0 -0
  145. package/assets/03.bot.page.png +0 -0
  146. package/assets/link-me.jpg +0 -0
  147. package/assets/register.png +0 -0
  148. package/changelog/v2.2.28.md +0 -70
  149. package/changelog/v2.3.10.md +0 -17
  150. package/changelog/v2.3.11.md +0 -19
  151. package/changelog/v2.3.12.md +0 -25
  152. package/changelog/v2.3.13.md +0 -19
  153. package/changelog/v2.3.14.md +0 -48
  154. package/changelog/v2.3.15.md +0 -15
  155. package/changelog/v2.3.16.md +0 -11
  156. package/changelog/v2.3.18.md +0 -22
  157. package/changelog/v2.3.19.md +0 -73
  158. package/changelog/v2.3.2.md +0 -28
  159. package/changelog/v2.3.26.md +0 -21
  160. package/changelog/v2.3.27.md +0 -33
  161. package/changelog/v2.3.4.md +0 -20
  162. package/changelog/v2.3.9.md +0 -22
  163. package/changelog/v2.4.12.md +0 -37
  164. package/compat-single-account.md +0 -148
  165. package/index.test.ts +0 -38
  166. package/scripts/test-proxy.ts +0 -70
  167. package/scripts/wecom/README.md +0 -123
  168. package/scripts/wecom/menu-click-help.js +0 -59
  169. package/scripts/wecom/menu-click-help.py +0 -55
  170. package/src/accounts.ts +0 -34
  171. package/src/agent/api-client.upload.test.ts +0 -109
  172. package/src/agent/event-router.test.ts +0 -421
  173. package/src/agent/event-router.ts +0 -272
  174. package/src/agent/handler.event-filter.test.ts +0 -135
  175. package/src/agent/handler.ts +0 -1250
  176. package/src/agent/index.ts +0 -12
  177. package/src/agent/script-runner.ts +0 -186
  178. package/src/agent/test-fixtures/invalid-json-script.mjs +0 -1
  179. package/src/agent/test-fixtures/reply-event-script.mjs +0 -29
  180. package/src/agent/test-fixtures/reply-event-script.py +0 -17
  181. package/src/app/account-runtime.ts +0 -276
  182. package/src/app/bootstrap.ts +0 -29
  183. package/src/app/index.ts +0 -192
  184. package/src/capability/agent/delivery-service.ts +0 -87
  185. package/src/capability/agent/fallback-policy.ts +0 -13
  186. package/src/capability/agent/ingress-service.ts +0 -38
  187. package/src/capability/agent/upstream-delivery-service.ts +0 -96
  188. package/src/capability/bot/dispatch-config.ts +0 -47
  189. package/src/capability/bot/fallback-delivery.ts +0 -178
  190. package/src/capability/bot/local-path-delivery.ts +0 -215
  191. package/src/capability/bot/sandbox-media.test.ts +0 -221
  192. package/src/capability/bot/sandbox-media.ts +0 -176
  193. package/src/capability/bot/service.ts +0 -56
  194. package/src/capability/bot/stream-delivery.ts +0 -379
  195. package/src/capability/bot/stream-finalizer.ts +0 -120
  196. package/src/capability/bot/stream-orchestrator.ts +0 -371
  197. package/src/capability/bot/types.ts +0 -8
  198. package/src/capability/calendar/SKILLS_CHECKLIST.md +0 -251
  199. package/src/capability/calendar/tool.ts +0 -417
  200. package/src/capability/calendar/types.ts +0 -309
  201. package/src/capability/doc/tool.ts +0 -1629
  202. package/src/capability/doc/types.ts +0 -792
  203. package/src/capability/mcp/index.ts +0 -10
  204. package/src/capability/mcp/schema.ts +0 -107
  205. package/src/capability/mcp/tool.ts +0 -174
  206. package/src/capability/mcp/transport.ts +0 -394
  207. package/src/channel.config.test.ts +0 -180
  208. package/src/channel.lifecycle.test.ts +0 -255
  209. package/src/channel.meta.test.ts +0 -26
  210. package/src/channel.ts +0 -256
  211. package/src/config/accounts.resolve.test.ts +0 -75
  212. package/src/config/accounts.ts +0 -312
  213. package/src/config/derived-paths.test.ts +0 -111
  214. package/src/config/derived-paths.ts +0 -41
  215. package/src/config/index.ts +0 -22
  216. package/src/config/media.test.ts +0 -113
  217. package/src/config/media.ts +0 -139
  218. package/src/config/network.ts +0 -20
  219. package/src/config/routing.test.ts +0 -88
  220. package/src/config/routing.ts +0 -26
  221. package/src/config/runtime-config.ts +0 -46
  222. package/src/config/schema.ts +0 -144
  223. package/src/context-store.ts +0 -297
  224. package/src/crypto/index.ts +0 -24
  225. package/src/crypto.test.ts +0 -32
  226. package/src/crypto.ts +0 -176
  227. package/src/domain/models.ts +0 -7
  228. package/src/domain/policies.ts +0 -36
  229. package/src/dynamic-agent.account-scope.test.ts +0 -17
  230. package/src/gateway-monitor.ts +0 -181
  231. package/src/http.ts +0 -137
  232. package/src/media.test.ts +0 -82
  233. package/src/monitor/limits.ts +0 -7
  234. package/src/monitor/state.queue.test.ts +0 -185
  235. package/src/monitor/state.ts +0 -34
  236. package/src/monitor.active.test.ts +0 -245
  237. package/src/monitor.inbound-filter.test.ts +0 -63
  238. package/src/monitor.integration.test.ts +0 -208
  239. package/src/monitor.ts +0 -121
  240. package/src/monitor.webhook.test.ts +0 -774
  241. package/src/observability/audit-log.ts +0 -48
  242. package/src/observability/legacy-operational-event-store.ts +0 -36
  243. package/src/observability/raw-envelope-log.ts +0 -28
  244. package/src/observability/status-registry.ts +0 -13
  245. package/src/observability/transport-session-view.ts +0 -14
  246. package/src/onboarding.test.ts +0 -336
  247. package/src/onboarding.ts +0 -704
  248. package/src/outbound.test.ts +0 -1271
  249. package/src/outbound.ts +0 -746
  250. package/src/runtime/dispatcher.ts +0 -71
  251. package/src/runtime/outbound-intent.ts +0 -4
  252. package/src/runtime/reply-orchestrator.test.ts +0 -71
  253. package/src/runtime/reply-orchestrator.ts +0 -67
  254. package/src/runtime/routing-bridge.test.ts +0 -115
  255. package/src/runtime/routing-bridge.ts +0 -44
  256. package/src/runtime/session-manager.test.ts +0 -174
  257. package/src/runtime/session-manager.ts +0 -139
  258. package/src/runtime/source-registry.ts +0 -249
  259. package/src/runtime.ts +0 -14
  260. package/src/shared/command-auth.ts +0 -87
  261. package/src/shared/media-asset.ts +0 -78
  262. package/src/shared/media-service.test.ts +0 -111
  263. package/src/shared/media-service.ts +0 -84
  264. package/src/shared/media-types.ts +0 -5
  265. package/src/shared/xml-parser.test.ts +0 -50
  266. package/src/store/active-reply-store.ts +0 -42
  267. package/src/store/interfaces.ts +0 -11
  268. package/src/store/memory-store.ts +0 -43
  269. package/src/store/stream-batch-store.ts +0 -350
  270. package/src/transport/agent-api/client.ts +0 -277
  271. package/src/transport/agent-api/core.ts +0 -463
  272. package/src/transport/agent-api/delivery.ts +0 -41
  273. package/src/transport/agent-api/media-upload.ts +0 -11
  274. package/src/transport/agent-api/reply.ts +0 -39
  275. package/src/transport/agent-api/upstream-delivery.ts +0 -45
  276. package/src/transport/agent-api/upstream-media-upload.ts +0 -70
  277. package/src/transport/agent-api/upstream-reply.ts +0 -43
  278. package/src/transport/agent-callback/http-handler.ts +0 -47
  279. package/src/transport/agent-callback/inbound.ts +0 -5
  280. package/src/transport/agent-callback/reply.ts +0 -13
  281. package/src/transport/agent-callback/request-handler.ts +0 -244
  282. package/src/transport/agent-callback/session.ts +0 -23
  283. package/src/transport/bot-webhook/active-reply.ts +0 -39
  284. package/src/transport/bot-webhook/http-handler.ts +0 -48
  285. package/src/transport/bot-webhook/inbound-normalizer.ts +0 -371
  286. package/src/transport/bot-webhook/inbound.ts +0 -5
  287. package/src/transport/bot-webhook/message-shape.ts +0 -89
  288. package/src/transport/bot-webhook/protocol.ts +0 -148
  289. package/src/transport/bot-webhook/reply.ts +0 -15
  290. package/src/transport/bot-webhook/request-handler.ts +0 -394
  291. package/src/transport/bot-webhook/session.ts +0 -23
  292. package/src/transport/bot-ws/inbound.test.ts +0 -96
  293. package/src/transport/bot-ws/inbound.ts +0 -116
  294. package/src/transport/bot-ws/media.test.ts +0 -44
  295. package/src/transport/bot-ws/media.ts +0 -321
  296. package/src/transport/bot-ws/reply.test.ts +0 -450
  297. package/src/transport/bot-ws/reply.ts +0 -365
  298. package/src/transport/bot-ws/sdk-adapter.test.ts +0 -187
  299. package/src/transport/bot-ws/sdk-adapter.ts +0 -314
  300. package/src/transport/bot-ws/session.ts +0 -28
  301. package/src/transport/http/common.ts +0 -109
  302. package/src/transport/http/registry.ts +0 -92
  303. package/src/transport/http/request-handler.ts +0 -84
  304. package/src/types/account.ts +0 -72
  305. package/src/types/config.ts +0 -166
  306. package/src/types/constants.ts +0 -31
  307. package/src/types/events.ts +0 -21
  308. package/src/types/global.d.ts +0 -9
  309. package/src/types/index.ts +0 -17
  310. package/src/types/legacy-stream.ts +0 -50
  311. package/src/types/message.ts +0 -187
  312. package/src/types/runtime-context.ts +0 -28
  313. package/src/types/runtime.ts +0 -165
  314. package/src/types.ts +0 -41
  315. package/src/upstream/index.ts +0 -150
  316. package/src/upstream.test.ts +0 -84
  317. package/src/wecom_msg_adapter/markdown_adapter.ts +0 -331
  318. package/tsconfig.json +0 -22
  319. package/vitest.config.ts +0 -26
  320. /package/{src/capability/agent/index.ts → dist/src/capability/agent/index.js} +0 -0
  321. /package/{src/capability/bot/index.ts → dist/src/capability/bot/index.js} +0 -0
  322. /package/{src/capability/calendar/index.ts → dist/src/capability/calendar/index.js} +0 -0
  323. /package/{src/capability/index.ts → dist/src/capability/index.js} +0 -0
@@ -1,371 +0,0 @@
1
- import { decryptWecomMediaWithMeta } from "../../media.js";
2
- import { resolveWecomEgressProxyUrl, resolveWecomMediaMaxBytes } from "../../config/index.js";
3
- import type { WecomBotInboundMessage as WecomInboundMessage } from "../../types/index.js";
4
- import type { WecomWebhookTarget } from "../../types/runtime-context.js";
5
- import { buildInboundBody } from "./message-shape.js";
6
-
7
- const MIME_BY_EXT: Record<string, string> = {
8
- txt: "text/plain",
9
- md: "text/markdown",
10
- json: "application/json",
11
- csv: "text/csv",
12
- html: "text/html",
13
- pdf: "application/pdf",
14
- doc: "application/msword",
15
- docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
16
- xls: "application/vnd.ms-excel",
17
- xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
18
- ppt: "application/vnd.ms-powerpoint",
19
- pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
20
- jpg: "image/jpeg",
21
- jpeg: "image/jpeg",
22
- png: "image/png",
23
- gif: "image/gif",
24
- webp: "image/webp",
25
- bmp: "image/bmp",
26
- ogg: "audio/ogg",
27
- wav: "audio/wav",
28
- mp3: "audio/mpeg",
29
- mp4: "video/mp4",
30
- zip: "application/zip",
31
- bin: "application/octet-stream",
32
- };
33
-
34
- const EXT_BY_MIME: Record<string, string> = {
35
- ...Object.fromEntries(Object.entries(MIME_BY_EXT).map(([ext, mime]) => [mime, ext])),
36
- "application/octet-stream": "bin",
37
- };
38
-
39
- const GENERIC_CONTENT_TYPES = new Set([
40
- "application/octet-stream",
41
- "binary/octet-stream",
42
- "application/download",
43
- ]);
44
-
45
- export type BotInboundMedia = {
46
- buffer: Buffer;
47
- contentType: string;
48
- filename: string;
49
- };
50
-
51
- export type BotInboundNormalizationResult = {
52
- body: string;
53
- media?: BotInboundMedia;
54
- };
55
-
56
- function normalizeContentType(raw?: string | null): string | undefined {
57
- const normalized = String(raw ?? "").trim().split(";")[0]?.trim().toLowerCase();
58
- return normalized || undefined;
59
- }
60
-
61
- function isGenericContentType(raw?: string | null): boolean {
62
- const normalized = normalizeContentType(raw);
63
- if (!normalized) return true;
64
- return GENERIC_CONTENT_TYPES.has(normalized);
65
- }
66
-
67
- export function guessContentTypeFromPath(filePath: string): string | undefined {
68
- const ext = filePath.split(".").pop()?.toLowerCase();
69
- if (!ext) return undefined;
70
- return MIME_BY_EXT[ext];
71
- }
72
-
73
- function guessExtensionFromContentType(contentType?: string): string | undefined {
74
- const normalized = normalizeContentType(contentType);
75
- if (!normalized) return undefined;
76
- if (normalized === "image/jpeg") return "jpg";
77
- return EXT_BY_MIME[normalized];
78
- }
79
-
80
- function extractFileNameFromUrl(rawUrl?: string): string | undefined {
81
- const s = String(rawUrl ?? "").trim();
82
- if (!s) return undefined;
83
- try {
84
- const u = new URL(s);
85
- const name = decodeURIComponent(u.pathname.split("/").pop() ?? "").trim();
86
- return name || undefined;
87
- } catch {
88
- return undefined;
89
- }
90
- }
91
-
92
- function sanitizeInboundFilename(raw?: string): string | undefined {
93
- const s = String(raw ?? "").trim();
94
- if (!s) return undefined;
95
- const base = s.split(/[\\/]/).pop()?.trim() ?? "";
96
- if (!base) return undefined;
97
- const sanitized = base.replace(/[\u0000-\u001f<>:"|?*]/g, "_").trim();
98
- return sanitized || undefined;
99
- }
100
-
101
- function hasLikelyExtension(name?: string): boolean {
102
- if (!name) return false;
103
- return /\.[a-z0-9]{1,16}$/i.test(name);
104
- }
105
-
106
- function detectMimeFromBuffer(buffer: Buffer): string | undefined {
107
- if (!buffer || buffer.length < 4) return undefined;
108
- if (
109
- buffer.length >= 8 &&
110
- buffer[0] === 0x89 &&
111
- buffer[1] === 0x50 &&
112
- buffer[2] === 0x4e &&
113
- buffer[3] === 0x47 &&
114
- buffer[4] === 0x0d &&
115
- buffer[5] === 0x0a &&
116
- buffer[6] === 0x1a &&
117
- buffer[7] === 0x0a
118
- ) {
119
- return "image/png";
120
- }
121
- if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
122
- return "image/jpeg";
123
- }
124
- if (buffer.subarray(0, 6).toString("ascii") === "GIF87a" || buffer.subarray(0, 6).toString("ascii") === "GIF89a") {
125
- return "image/gif";
126
- }
127
- if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") {
128
- return "image/webp";
129
- }
130
- if (buffer[0] === 0x42 && buffer[1] === 0x4d) {
131
- return "image/bmp";
132
- }
133
- if (buffer.subarray(0, 5).toString("ascii") === "%PDF-") {
134
- return "application/pdf";
135
- }
136
- if (buffer.subarray(0, 4).toString("ascii") === "OggS") {
137
- return "audio/ogg";
138
- }
139
- if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WAVE") {
140
- return "audio/wav";
141
- }
142
- if (buffer.subarray(0, 3).toString("ascii") === "ID3" || (buffer[0] === 0xff && (buffer[1] & 0xe0) === 0xe0)) {
143
- return "audio/mpeg";
144
- }
145
- if (buffer.length >= 12 && buffer.subarray(4, 8).toString("ascii") === "ftyp") {
146
- return "video/mp4";
147
- }
148
- if (
149
- buffer.length >= 8 &&
150
- buffer[0] === 0xd0 &&
151
- buffer[1] === 0xcf &&
152
- buffer[2] === 0x11 &&
153
- buffer[3] === 0xe0 &&
154
- buffer[4] === 0xa1 &&
155
- buffer[5] === 0xb1 &&
156
- buffer[6] === 0x1a &&
157
- buffer[7] === 0xe1
158
- ) {
159
- return "application/msword";
160
- }
161
- const zipMagic =
162
- (buffer[0] === 0x50 && buffer[1] === 0x4b && buffer[2] === 0x03 && buffer[3] === 0x04) ||
163
- (buffer[0] === 0x50 && buffer[1] === 0x4b && buffer[2] === 0x05 && buffer[3] === 0x06) ||
164
- (buffer[0] === 0x50 && buffer[1] === 0x4b && buffer[2] === 0x07 && buffer[3] === 0x08);
165
- if (zipMagic) {
166
- const probe = buffer.subarray(0, Math.min(buffer.length, 512 * 1024));
167
- if (probe.includes(Buffer.from("word/"))) return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
168
- if (probe.includes(Buffer.from("xl/"))) return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
169
- if (probe.includes(Buffer.from("ppt/"))) return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
170
- return "application/zip";
171
- }
172
- const sample = buffer.subarray(0, Math.min(buffer.length, 4096));
173
- let printable = 0;
174
- for (const b of sample) {
175
- if (b === 0x00) return undefined;
176
- if (b === 0x09 || b === 0x0a || b === 0x0d || (b >= 0x20 && b <= 0x7e)) {
177
- printable += 1;
178
- }
179
- }
180
- if (sample.length > 0 && printable / sample.length > 0.95) {
181
- return "text/plain";
182
- }
183
- return undefined;
184
- }
185
-
186
- function resolveInlineFileName(input: unknown): string | undefined {
187
- return sanitizeInboundFilename(String(input ?? "").trim());
188
- }
189
-
190
- function pickBotFileName(msg: WecomInboundMessage, item?: Record<string, any>): string | undefined {
191
- const fromItem = item
192
- ? resolveInlineFileName(item?.filename ?? item?.file_name ?? item?.fileName ?? item?.name ?? item?.title)
193
- : undefined;
194
- if (fromItem) return fromItem;
195
- return resolveInlineFileName(
196
- (msg as any)?.file?.filename ??
197
- (msg as any)?.file?.file_name ??
198
- (msg as any)?.file?.fileName ??
199
- (msg as any)?.file?.name ??
200
- (msg as any)?.file?.title ??
201
- (msg as any)?.filename ??
202
- (msg as any)?.fileName ??
203
- (msg as any)?.FileName,
204
- );
205
- }
206
-
207
- function inferInboundMediaMeta(params: {
208
- kind: "image" | "file";
209
- buffer: Buffer;
210
- sourceUrl?: string;
211
- sourceContentType?: string;
212
- sourceFilename?: string;
213
- explicitFilename?: string;
214
- }): { contentType: string; filename: string } {
215
- const headerType = normalizeContentType(params.sourceContentType);
216
- const magicType = detectMimeFromBuffer(params.buffer);
217
- const rawUrlName = sanitizeInboundFilename(extractFileNameFromUrl(params.sourceUrl));
218
- const guessedByUrl = hasLikelyExtension(rawUrlName) ? rawUrlName : undefined;
219
- const explicitName = sanitizeInboundFilename(params.explicitFilename);
220
- const sourceName = sanitizeInboundFilename(params.sourceFilename);
221
- const chosenName = explicitName || sourceName || guessedByUrl;
222
- const typeByName = chosenName ? guessContentTypeFromPath(chosenName) : undefined;
223
-
224
- let contentType: string;
225
- if (params.kind === "image") {
226
- if (magicType?.startsWith("image/")) contentType = magicType;
227
- else if (headerType?.startsWith("image/")) contentType = headerType;
228
- else if (typeByName?.startsWith("image/")) contentType = typeByName;
229
- else contentType = "image/jpeg";
230
- } else {
231
- contentType = magicType || (!isGenericContentType(headerType) ? headerType! : undefined) || typeByName || "application/octet-stream";
232
- }
233
-
234
- const hasExt = Boolean(chosenName && /\.[a-z0-9]{1,16}$/i.test(chosenName));
235
- const ext = guessExtensionFromContentType(contentType) || (params.kind === "image" ? "jpg" : "bin");
236
- const filename = chosenName ? (hasExt ? chosenName : `${chosenName}.${ext}`) : `${params.kind}.${ext}`;
237
- return { contentType, filename };
238
- }
239
-
240
- export function looksLikeSendLocalFileIntent(rawBody: string): boolean {
241
- const t = rawBody.trim();
242
- if (!t) return false;
243
- return /(发送|发给|发到|转发|把.*发|把.*发送|帮我发|给我发)/.test(t);
244
- }
245
-
246
- export async function processBotInboundMessage(params: {
247
- target: WecomWebhookTarget;
248
- msg: WecomInboundMessage;
249
- recordOperationalIssue: (event: {
250
- category: "media-decrypt-failed";
251
- messageId?: string;
252
- summary: string;
253
- raw: { transport: "bot-webhook"; envelopeType: "json"; body: WecomInboundMessage };
254
- error?: string;
255
- }) => void;
256
- }): Promise<BotInboundNormalizationResult> {
257
- const { target, msg, recordOperationalIssue } = params;
258
- const msgtype = String(msg.msgtype ?? "").toLowerCase();
259
- const aesKey = target.account.encodingAESKey;
260
- const maxBytes = resolveWecomMediaMaxBytes(target.config, target.account.accountId);
261
- const proxyUrl = resolveWecomEgressProxyUrl(target.config);
262
-
263
- if (msgtype === "image") {
264
- const url = String((msg as any).image?.url ?? "").trim();
265
- if (url && aesKey) {
266
- try {
267
- const decrypted = await decryptWecomMediaWithMeta(url, aesKey, { maxBytes, http: { proxyUrl } });
268
- const inferred = inferInboundMediaMeta({
269
- kind: "image",
270
- buffer: decrypted.buffer,
271
- sourceUrl: decrypted.sourceUrl || url,
272
- sourceContentType: decrypted.sourceContentType,
273
- sourceFilename: decrypted.sourceFilename,
274
- explicitFilename: pickBotFileName(msg),
275
- });
276
- return { body: "[image]", media: { buffer: decrypted.buffer, contentType: inferred.contentType, filename: inferred.filename } };
277
- } catch (err) {
278
- target.runtime.error?.(`图片解密失败: ${String(err)}; 可调大 channels.wecom.mediaMaxMb(当前=${Math.round(maxBytes / (1024 * 1024))}MB)例如:openclaw config set channels.wecom.mediaMaxMb 50`);
279
- recordOperationalIssue({
280
- category: "media-decrypt-failed",
281
- messageId: msg.msgid ? String(msg.msgid) : undefined,
282
- summary: `image decrypt failed url=${url}`,
283
- raw: { transport: "bot-webhook", envelopeType: "json", body: msg },
284
- error: err instanceof Error ? err.message : String(err),
285
- });
286
- const errorMessage = typeof err === "object" && err ? `${(err as any).message}${(err as any).cause ? ` (cause: ${String((err as any).cause)})` : ""}` : String(err);
287
- return { body: `[image] (decryption failed: ${errorMessage})` };
288
- }
289
- }
290
- }
291
-
292
- if (msgtype === "file") {
293
- const url = String((msg as any).file?.url ?? "").trim();
294
- if (url && aesKey) {
295
- try {
296
- const decrypted = await decryptWecomMediaWithMeta(url, aesKey, { maxBytes, http: { proxyUrl } });
297
- const inferred = inferInboundMediaMeta({
298
- kind: "file",
299
- buffer: decrypted.buffer,
300
- sourceUrl: decrypted.sourceUrl || url,
301
- sourceContentType: decrypted.sourceContentType,
302
- sourceFilename: decrypted.sourceFilename,
303
- explicitFilename: pickBotFileName(msg),
304
- });
305
- return { body: "[file]", media: { buffer: decrypted.buffer, contentType: inferred.contentType, filename: inferred.filename } };
306
- } catch (err) {
307
- target.runtime.error?.(`Failed to decrypt inbound file: ${String(err)}; 可调大 channels.wecom.mediaMaxMb(当前=${Math.round(maxBytes / (1024 * 1024))}MB)例如:openclaw config set channels.wecom.mediaMaxMb 50`);
308
- recordOperationalIssue({
309
- category: "media-decrypt-failed",
310
- messageId: msg.msgid ? String(msg.msgid) : undefined,
311
- summary: `file decrypt failed url=${url}`,
312
- raw: { transport: "bot-webhook", envelopeType: "json", body: msg },
313
- error: err instanceof Error ? err.message : String(err),
314
- });
315
- const errorMessage = typeof err === "object" && err ? `${(err as any).message}${(err as any).cause ? ` (cause: ${String((err as any).cause)})` : ""}` : String(err);
316
- return { body: `[file] (decryption failed: ${errorMessage})` };
317
- }
318
- }
319
- }
320
-
321
- if (msgtype === "mixed") {
322
- const items = (msg as any).mixed?.msg_item;
323
- if (Array.isArray(items)) {
324
- let foundMedia: BotInboundNormalizationResult["media"];
325
- const bodyParts: string[] = [];
326
- for (const item of items) {
327
- const t = String(item.msgtype ?? "").toLowerCase();
328
- if (t === "text") {
329
- const content = String(item.text?.content ?? "").trim();
330
- if (content) bodyParts.push(content);
331
- continue;
332
- }
333
- if ((t === "image" || t === "file") && !foundMedia && aesKey) {
334
- const url = String(item[t]?.url ?? "").trim();
335
- if (url) {
336
- try {
337
- const decrypted = await decryptWecomMediaWithMeta(url, aesKey, { maxBytes, http: { proxyUrl } });
338
- const inferred = inferInboundMediaMeta({
339
- kind: t,
340
- buffer: decrypted.buffer,
341
- sourceUrl: decrypted.sourceUrl || url,
342
- sourceContentType: decrypted.sourceContentType,
343
- sourceFilename: decrypted.sourceFilename,
344
- explicitFilename: pickBotFileName(msg, item?.[t]),
345
- });
346
- foundMedia = { buffer: decrypted.buffer, contentType: inferred.contentType, filename: inferred.filename };
347
- bodyParts.push(`[${t}]`);
348
- continue;
349
- } catch (err) {
350
- target.runtime.error?.(`Failed to decrypt mixed ${t}: ${String(err)}; 可调大 channels.wecom.mediaMaxMb(当前=${Math.round(maxBytes / (1024 * 1024))}MB)例如:openclaw config set channels.wecom.mediaMaxMb 50`);
351
- recordOperationalIssue({
352
- category: "media-decrypt-failed",
353
- messageId: msg.msgid ? String(msg.msgid) : undefined,
354
- summary: `mixed ${t} decrypt failed url=${url}`,
355
- raw: { transport: "bot-webhook", envelopeType: "json", body: msg },
356
- error: err instanceof Error ? err.message : String(err),
357
- });
358
- const errorMessage = typeof err === "object" && err ? `${(err as any).message}${(err as any).cause ? ` (cause: ${String((err as any).cause)})` : ""}` : String(err);
359
- bodyParts.push(`[${t}] (decryption failed: ${errorMessage})`);
360
- continue;
361
- }
362
- }
363
- }
364
- bodyParts.push(`[${t}]`);
365
- }
366
- return { body: bodyParts.join("\n"), media: foundMedia };
367
- }
368
- }
369
-
370
- return { body: buildInboundBody(msg) };
371
- }
@@ -1,5 +0,0 @@
1
- import { resolveDerivedPathSummary } from "../../config/index.js";
2
-
3
- export function resolveBotWebhookPaths(accountId: string): string[] {
4
- return resolveDerivedPathSummary(accountId).botWebhook;
5
- }
@@ -1,89 +0,0 @@
1
- import type { WecomBotInboundMessage as WecomInboundMessage, WecomInboundQuote } from "../../types/index.js";
2
-
3
- export type BotInboundProcessDecision = {
4
- shouldProcess: boolean;
5
- reason: string;
6
- senderUserId?: string;
7
- chatId?: string;
8
- };
9
-
10
- export function resolveWecomSenderUserId(msg: WecomInboundMessage): string | undefined {
11
- const direct = msg.from?.userid?.trim();
12
- if (direct) return direct;
13
- const legacy = String((msg as any).fromuserid ?? (msg as any).from_userid ?? (msg as any).fromUserId ?? "").trim();
14
- return legacy || undefined;
15
- }
16
-
17
- export function shouldProcessBotInboundMessage(msg: WecomInboundMessage): BotInboundProcessDecision {
18
- const senderUserId = resolveWecomSenderUserId(msg)?.trim();
19
- if (!senderUserId) {
20
- return { shouldProcess: false, reason: "missing_sender" };
21
- }
22
- if (senderUserId.toLowerCase() === "sys") {
23
- return { shouldProcess: false, reason: "system_sender" };
24
- }
25
-
26
- const chatType = String(msg.chattype ?? "").trim().toLowerCase();
27
- if (chatType === "group") {
28
- const chatId = msg.chatid?.trim();
29
- if (!chatId) {
30
- return { shouldProcess: false, reason: "missing_chatid", senderUserId };
31
- }
32
- return { shouldProcess: true, reason: "user_message", senderUserId, chatId };
33
- }
34
-
35
- return { shouldProcess: true, reason: "user_message", senderUserId, chatId: senderUserId };
36
- }
37
-
38
- function formatQuote(quote: WecomInboundQuote): string {
39
- const type = quote.msgtype ?? "";
40
- if (type === "text") return quote.text?.content || "";
41
- if (type === "image") return `[引用: 图片] ${quote.image?.url || ""}`;
42
- if (type === "mixed" && quote.mixed?.msg_item) {
43
- const items = quote.mixed.msg_item
44
- .map((item) => {
45
- if (item.msgtype === "text") return item.text?.content;
46
- if (item.msgtype === "image") return `[图片] ${item.image?.url || ""}`;
47
- return "";
48
- })
49
- .filter(Boolean)
50
- .join(" ");
51
- return `[引用: 图文] ${items}`;
52
- }
53
- if (type === "voice") return `[引用: 语音] ${quote.voice?.content || ""}`;
54
- if (type === "file") return `[引用: 文件] ${quote.file?.url || ""}`;
55
- return "";
56
- }
57
-
58
- export function buildInboundBody(msg: WecomInboundMessage): string {
59
- let body = "";
60
- const msgtype = String(msg.msgtype ?? "").toLowerCase();
61
-
62
- if (msgtype === "text") body = (msg as any).text?.content || "";
63
- else if (msgtype === "voice") body = (msg as any).voice?.content || "[voice]";
64
- else if (msgtype === "mixed") {
65
- const items = (msg as any).mixed?.msg_item;
66
- if (Array.isArray(items)) {
67
- body = items
68
- .map((item: any) => {
69
- const t = String(item?.msgtype ?? "").toLowerCase();
70
- if (t === "text") return item?.text?.content || "";
71
- if (t === "image") return `[image] ${item?.image?.url || ""}`;
72
- return `[${t || "item"}]`;
73
- })
74
- .filter(Boolean)
75
- .join("\n");
76
- } else body = "[mixed]";
77
- } else if (msgtype === "image") body = `[image] ${(msg as any).image?.url || ""}`;
78
- else if (msgtype === "file") body = `[file] ${(msg as any).file?.url || ""}`;
79
- else if (msgtype === "event") body = `[event] ${(msg as any).event?.eventtype || ""}`;
80
- else if (msgtype === "stream") body = `[stream_refresh] ${(msg as any).stream?.id || ""}`;
81
- else body = msgtype ? `[${msgtype}]` : "";
82
-
83
- const quote = (msg as any).quote;
84
- if (quote) {
85
- const quoteText = formatQuote(quote).trim();
86
- if (quoteText) body += `\n\n> ${quoteText}`;
87
- }
88
- return body;
89
- }
@@ -1,148 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from "node:http";
2
-
3
- import type { ResolvedBotAccount, WecomBotInboundMessage as WecomInboundMessage } from "../../types/index.js";
4
- import { LIMITS } from "../../monitor/state.js";
5
- import { computeWecomMsgSignature, encryptWecomPlaintext } from "../../crypto.js";
6
- import type { StreamState } from "../../types/legacy-stream.js";
7
- import type { WecomWebhookTarget } from "../../types/runtime-context.js";
8
-
9
- function truncateUtf8Bytes(text: string, maxBytes: number): string {
10
- const buf = Buffer.from(text, "utf8");
11
- if (buf.length <= maxBytes) return text;
12
- const slice = buf.subarray(buf.length - maxBytes);
13
- return slice.toString("utf8");
14
- }
15
-
16
- export function jsonOk(res: ServerResponse, body: unknown): void {
17
- res.statusCode = 200;
18
- res.setHeader("Content-Type", "text/plain; charset=utf-8");
19
- res.end(JSON.stringify(body));
20
- }
21
-
22
- export async function readBotWebhookJsonBody(req: IncomingMessage, maxBytes: number) {
23
- const chunks: Buffer[] = [];
24
- let total = 0;
25
- return await new Promise<{ ok: boolean; value?: unknown; error?: string }>((resolve) => {
26
- req.on("data", (chunk: Buffer) => {
27
- total += chunk.length;
28
- if (total > maxBytes) {
29
- resolve({ ok: false, error: "payload too large" });
30
- req.destroy();
31
- return;
32
- }
33
- chunks.push(chunk);
34
- });
35
- req.on("end", () => {
36
- try {
37
- const raw = Buffer.concat(chunks).toString("utf8");
38
- if (!raw.trim()) {
39
- resolve({ ok: false, error: "empty payload" });
40
- return;
41
- }
42
- resolve({ ok: true, value: JSON.parse(raw) as unknown });
43
- } catch (err) {
44
- resolve({ ok: false, error: err instanceof Error ? err.message : String(err) });
45
- }
46
- });
47
- req.on("error", (err) => {
48
- resolve({ ok: false, error: err instanceof Error ? err.message : String(err) });
49
- });
50
- });
51
- }
52
-
53
- export function buildEncryptedBotWebhookReply(params: {
54
- account: ResolvedBotAccount;
55
- plaintextJson: unknown;
56
- nonce: string;
57
- timestamp: string;
58
- }): { encrypt: string; msgsignature: string; timestamp: string; nonce: string } {
59
- const plaintext = JSON.stringify(params.plaintextJson ?? {});
60
- const encrypt = encryptWecomPlaintext({
61
- encodingAESKey: params.account.encodingAESKey ?? "",
62
- receiveId: params.account.receiveId ?? "",
63
- plaintext,
64
- });
65
- const msgsignature = computeWecomMsgSignature({
66
- token: params.account.token ?? "",
67
- timestamp: params.timestamp,
68
- nonce: params.nonce,
69
- encrypt,
70
- });
71
- return {
72
- encrypt,
73
- msgsignature,
74
- timestamp: params.timestamp,
75
- nonce: params.nonce,
76
- };
77
- }
78
-
79
- export function resolveBotIdentitySet(target: WecomWebhookTarget): Set<string> {
80
- const ids = new Set<string>();
81
- const single = target.account.config.aibotid?.trim();
82
- if (single) ids.add(single);
83
- for (const botId of target.account.config.botIds ?? []) {
84
- const normalized = String(botId ?? "").trim();
85
- if (normalized) ids.add(normalized);
86
- }
87
- return ids;
88
- }
89
-
90
- export function buildStreamPlaceholderReply(params: {
91
- streamId: string;
92
- placeholderContent?: string;
93
- }): { msgtype: "stream"; stream: { id: string; finish: boolean; content: string } } {
94
- const content = params.placeholderContent?.trim() || "1";
95
- return {
96
- msgtype: "stream",
97
- stream: {
98
- id: params.streamId,
99
- finish: false,
100
- content,
101
- },
102
- };
103
- }
104
-
105
- export function buildStreamTextPlaceholderReply(params: {
106
- streamId: string;
107
- content: string;
108
- }): { msgtype: "stream"; stream: { id: string; finish: boolean; content: string } } {
109
- return {
110
- msgtype: "stream",
111
- stream: {
112
- id: params.streamId,
113
- finish: false,
114
- content: params.content.trim() || "1",
115
- },
116
- };
117
- }
118
-
119
- export function buildStreamReplyFromState(state: StreamState): {
120
- msgtype: "stream";
121
- stream: { id: string; finish: boolean; content: string; msg_item?: Array<{ msgtype: string; image: { base64: string; md5: string } }> };
122
- } {
123
- const content = truncateUtf8Bytes(state.content, LIMITS.STREAM_MAX_BYTES);
124
- return {
125
- msgtype: "stream",
126
- stream: {
127
- id: state.streamId,
128
- finish: state.finished,
129
- content,
130
- ...(state.finished && state.images?.length
131
- ? {
132
- msg_item: state.images.map((img) => ({
133
- msgtype: "image",
134
- image: { base64: img.base64, md5: img.md5 },
135
- })),
136
- }
137
- : {}),
138
- },
139
- };
140
- }
141
-
142
- export function parseWecomPlainMessage(raw: string): WecomInboundMessage {
143
- const parsed = JSON.parse(raw) as unknown;
144
- if (!parsed || typeof parsed !== "object") {
145
- return {};
146
- }
147
- return parsed as WecomInboundMessage;
148
- }
@@ -1,15 +0,0 @@
1
- import type { ReplyContext } from "../../types/index.js";
2
-
3
- export function createBotWebhookReplyContext(params: {
4
- accountId: string;
5
- responseUrl?: string;
6
- raw: ReplyContext["raw"];
7
- }): ReplyContext {
8
- return {
9
- transport: "bot-webhook",
10
- accountId: params.accountId,
11
- responseUrl: params.responseUrl,
12
- passiveWindowMs: 5_000,
13
- raw: params.raw,
14
- };
15
- }