@larksuite/openclaw-lark 2026.3.9

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 (689) hide show
  1. package/LICENSE +10 -0
  2. package/README.md +283 -0
  3. package/index.d.ts +37 -0
  4. package/index.d.ts.map +1 -0
  5. package/index.js +119 -0
  6. package/index.js.map +1 -0
  7. package/openclaw.plugin.json +10 -0
  8. package/package.json +47 -0
  9. package/skills/feishu-bitable/SKILL.md +248 -0
  10. package/skills/feishu-bitable/references/examples.md +813 -0
  11. package/skills/feishu-bitable/references/field-properties.md +763 -0
  12. package/skills/feishu-bitable/references/record-values.md +911 -0
  13. package/skills/feishu-calendar/SKILL.md +244 -0
  14. package/skills/feishu-channel-rules/SKILL.md +18 -0
  15. package/skills/feishu-channel-rules/references/markdown-syntax.md +138 -0
  16. package/skills/feishu-create-doc/SKILL.md +719 -0
  17. package/skills/feishu-fetch-doc/SKILL.md +93 -0
  18. package/skills/feishu-im-read/SKILL.md +163 -0
  19. package/skills/feishu-task/SKILL.md +293 -0
  20. package/skills/feishu-troubleshoot/SKILL.md +70 -0
  21. package/skills/feishu-update-doc/SKILL.md +285 -0
  22. package/src/card/builder.d.ts +100 -0
  23. package/src/card/builder.d.ts.map +1 -0
  24. package/src/card/builder.js +381 -0
  25. package/src/card/builder.js.map +1 -0
  26. package/src/card/cardkit.d.ts +91 -0
  27. package/src/card/cardkit.d.ts.map +1 -0
  28. package/src/card/cardkit.js +182 -0
  29. package/src/card/cardkit.js.map +1 -0
  30. package/src/card/flush-controller.d.ts +46 -0
  31. package/src/card/flush-controller.d.ts.map +1 -0
  32. package/src/card/flush-controller.js +135 -0
  33. package/src/card/flush-controller.js.map +1 -0
  34. package/src/card/markdown-style.d.ts +17 -0
  35. package/src/card/markdown-style.d.ts.map +1 -0
  36. package/src/card/markdown-style.js +98 -0
  37. package/src/card/markdown-style.js.map +1 -0
  38. package/src/card/reply-dispatcher-types.d.ts +121 -0
  39. package/src/card/reply-dispatcher-types.d.ts.map +1 -0
  40. package/src/card/reply-dispatcher-types.js +58 -0
  41. package/src/card/reply-dispatcher-types.js.map +1 -0
  42. package/src/card/reply-dispatcher.d.ts +16 -0
  43. package/src/card/reply-dispatcher.d.ts.map +1 -0
  44. package/src/card/reply-dispatcher.js +293 -0
  45. package/src/card/reply-dispatcher.js.map +1 -0
  46. package/src/card/reply-mode.d.ts +39 -0
  47. package/src/card/reply-mode.d.ts.map +1 -0
  48. package/src/card/reply-mode.js +66 -0
  49. package/src/card/reply-mode.js.map +1 -0
  50. package/src/card/streaming-card-controller.d.ts +88 -0
  51. package/src/card/streaming-card-controller.d.ts.map +1 -0
  52. package/src/card/streaming-card-controller.js +717 -0
  53. package/src/card/streaming-card-controller.js.map +1 -0
  54. package/src/card/unavailable-guard.d.ts +36 -0
  55. package/src/card/unavailable-guard.d.ts.map +1 -0
  56. package/src/card/unavailable-guard.js +84 -0
  57. package/src/card/unavailable-guard.js.map +1 -0
  58. package/src/channel/abort-detect.d.ts +35 -0
  59. package/src/channel/abort-detect.d.ts.map +1 -0
  60. package/src/channel/abort-detect.js +125 -0
  61. package/src/channel/abort-detect.js.map +1 -0
  62. package/src/channel/chat-queue.d.ts +42 -0
  63. package/src/channel/chat-queue.d.ts.map +1 -0
  64. package/src/channel/chat-queue.js +59 -0
  65. package/src/channel/chat-queue.js.map +1 -0
  66. package/src/channel/config-adapter.d.ts +24 -0
  67. package/src/channel/config-adapter.d.ts.map +1 -0
  68. package/src/channel/config-adapter.js +102 -0
  69. package/src/channel/config-adapter.js.map +1 -0
  70. package/src/channel/directory.d.ts +58 -0
  71. package/src/channel/directory.d.ts.map +1 -0
  72. package/src/channel/directory.js +192 -0
  73. package/src/channel/directory.js.map +1 -0
  74. package/src/channel/event-handlers.d.ts +16 -0
  75. package/src/channel/event-handlers.d.ts.map +1 -0
  76. package/src/channel/event-handlers.js +222 -0
  77. package/src/channel/event-handlers.js.map +1 -0
  78. package/src/channel/monitor.d.ts +18 -0
  79. package/src/channel/monitor.d.ts.map +1 -0
  80. package/src/channel/monitor.js +128 -0
  81. package/src/channel/monitor.js.map +1 -0
  82. package/src/channel/onboarding-config.d.ts +18 -0
  83. package/src/channel/onboarding-config.d.ts.map +1 -0
  84. package/src/channel/onboarding-config.js +89 -0
  85. package/src/channel/onboarding-config.js.map +1 -0
  86. package/src/channel/onboarding-migrate.d.ts +26 -0
  87. package/src/channel/onboarding-migrate.d.ts.map +1 -0
  88. package/src/channel/onboarding-migrate.js +68 -0
  89. package/src/channel/onboarding-migrate.js.map +1 -0
  90. package/src/channel/onboarding.d.ts +13 -0
  91. package/src/channel/onboarding.d.ts.map +1 -0
  92. package/src/channel/onboarding.js +297 -0
  93. package/src/channel/onboarding.js.map +1 -0
  94. package/src/channel/plugin.d.ts +14 -0
  95. package/src/channel/plugin.d.ts.map +1 -0
  96. package/src/channel/plugin.js +279 -0
  97. package/src/channel/plugin.js.map +1 -0
  98. package/src/channel/probe.d.ts +15 -0
  99. package/src/channel/probe.d.ts.map +1 -0
  100. package/src/channel/probe.js +22 -0
  101. package/src/channel/probe.js.map +1 -0
  102. package/src/channel/types.d.ts +37 -0
  103. package/src/channel/types.d.ts.map +1 -0
  104. package/src/channel/types.js +8 -0
  105. package/src/channel/types.js.map +1 -0
  106. package/src/commands/auth.d.ts +16 -0
  107. package/src/commands/auth.d.ts.map +1 -0
  108. package/src/commands/auth.js +85 -0
  109. package/src/commands/auth.js.map +1 -0
  110. package/src/commands/diagnose.d.ts +70 -0
  111. package/src/commands/diagnose.d.ts.map +1 -0
  112. package/src/commands/diagnose.js +808 -0
  113. package/src/commands/diagnose.js.map +1 -0
  114. package/src/commands/doctor.d.ts +18 -0
  115. package/src/commands/doctor.d.ts.map +1 -0
  116. package/src/commands/doctor.js +439 -0
  117. package/src/commands/doctor.js.map +1 -0
  118. package/src/commands/index.d.ts +9 -0
  119. package/src/commands/index.d.ts.map +1 -0
  120. package/src/commands/index.js +208 -0
  121. package/src/commands/index.js.map +1 -0
  122. package/src/core/accounts.d.ts +38 -0
  123. package/src/core/accounts.d.ts.map +1 -0
  124. package/src/core/accounts.js +179 -0
  125. package/src/core/accounts.js.map +1 -0
  126. package/src/core/agent-config.d.ts +101 -0
  127. package/src/core/agent-config.d.ts.map +1 -0
  128. package/src/core/agent-config.js +140 -0
  129. package/src/core/agent-config.js.map +1 -0
  130. package/src/core/api-error.d.ts +49 -0
  131. package/src/core/api-error.d.ts.map +1 -0
  132. package/src/core/api-error.js +113 -0
  133. package/src/core/api-error.js.map +1 -0
  134. package/src/core/app-owner-fallback.d.ts +22 -0
  135. package/src/core/app-owner-fallback.d.ts.map +1 -0
  136. package/src/core/app-owner-fallback.js +39 -0
  137. package/src/core/app-owner-fallback.js.map +1 -0
  138. package/src/core/app-scope-checker.d.ts +88 -0
  139. package/src/core/app-scope-checker.d.ts.map +1 -0
  140. package/src/core/app-scope-checker.js +191 -0
  141. package/src/core/app-scope-checker.js.map +1 -0
  142. package/src/core/auth-errors.d.ts +143 -0
  143. package/src/core/auth-errors.d.ts.map +1 -0
  144. package/src/core/auth-errors.js +156 -0
  145. package/src/core/auth-errors.js.map +1 -0
  146. package/src/core/chat-info-cache.d.ts +58 -0
  147. package/src/core/chat-info-cache.d.ts.map +1 -0
  148. package/src/core/chat-info-cache.js +153 -0
  149. package/src/core/chat-info-cache.js.map +1 -0
  150. package/src/core/config-schema.d.ts +449 -0
  151. package/src/core/config-schema.d.ts.map +1 -0
  152. package/src/core/config-schema.js +201 -0
  153. package/src/core/config-schema.js.map +1 -0
  154. package/src/core/device-flow.d.ts +78 -0
  155. package/src/core/device-flow.d.ts.map +1 -0
  156. package/src/core/device-flow.js +213 -0
  157. package/src/core/device-flow.js.map +1 -0
  158. package/src/core/feishu-fetch.d.ts +19 -0
  159. package/src/core/feishu-fetch.d.ts.map +1 -0
  160. package/src/core/feishu-fetch.js +26 -0
  161. package/src/core/feishu-fetch.js.map +1 -0
  162. package/src/core/footer-config.d.ts +25 -0
  163. package/src/core/footer-config.d.ts.map +1 -0
  164. package/src/core/footer-config.js +40 -0
  165. package/src/core/footer-config.js.map +1 -0
  166. package/src/core/lark-client.d.ts +109 -0
  167. package/src/core/lark-client.d.ts.map +1 -0
  168. package/src/core/lark-client.js +354 -0
  169. package/src/core/lark-client.js.map +1 -0
  170. package/src/core/lark-logger.d.ts +24 -0
  171. package/src/core/lark-logger.d.ts.map +1 -0
  172. package/src/core/lark-logger.js +155 -0
  173. package/src/core/lark-logger.js.map +1 -0
  174. package/src/core/lark-ticket.d.ts +30 -0
  175. package/src/core/lark-ticket.d.ts.map +1 -0
  176. package/src/core/lark-ticket.js +36 -0
  177. package/src/core/lark-ticket.js.map +1 -0
  178. package/src/core/message-unavailable.d.ts +54 -0
  179. package/src/core/message-unavailable.d.ts.map +1 -0
  180. package/src/core/message-unavailable.js +131 -0
  181. package/src/core/message-unavailable.js.map +1 -0
  182. package/src/core/owner-policy.d.ts +32 -0
  183. package/src/core/owner-policy.d.ts.map +1 -0
  184. package/src/core/owner-policy.js +53 -0
  185. package/src/core/owner-policy.js.map +1 -0
  186. package/src/core/permission-url.d.ts +23 -0
  187. package/src/core/permission-url.d.ts.map +1 -0
  188. package/src/core/permission-url.js +73 -0
  189. package/src/core/permission-url.js.map +1 -0
  190. package/src/core/raw-request.d.ts +28 -0
  191. package/src/core/raw-request.d.ts.map +1 -0
  192. package/src/core/raw-request.js +63 -0
  193. package/src/core/raw-request.js.map +1 -0
  194. package/src/core/scope-manager.d.ts +169 -0
  195. package/src/core/scope-manager.d.ts.map +1 -0
  196. package/src/core/scope-manager.js +214 -0
  197. package/src/core/scope-manager.js.map +1 -0
  198. package/src/core/security-check.d.ts +73 -0
  199. package/src/core/security-check.d.ts.map +1 -0
  200. package/src/core/security-check.js +175 -0
  201. package/src/core/security-check.js.map +1 -0
  202. package/src/core/shutdown-hooks.d.ts +23 -0
  203. package/src/core/shutdown-hooks.d.ts.map +1 -0
  204. package/src/core/shutdown-hooks.js +57 -0
  205. package/src/core/shutdown-hooks.js.map +1 -0
  206. package/src/core/targets.d.ts +50 -0
  207. package/src/core/targets.d.ts.map +1 -0
  208. package/src/core/targets.js +128 -0
  209. package/src/core/targets.js.map +1 -0
  210. package/src/core/token-store.d.ts +55 -0
  211. package/src/core/token-store.d.ts.map +1 -0
  212. package/src/core/token-store.js +315 -0
  213. package/src/core/token-store.js.map +1 -0
  214. package/src/core/tool-client.d.ts +177 -0
  215. package/src/core/tool-client.d.ts.map +1 -0
  216. package/src/core/tool-client.js +381 -0
  217. package/src/core/tool-client.js.map +1 -0
  218. package/src/core/tool-scopes.d.ts +154 -0
  219. package/src/core/tool-scopes.d.ts.map +1 -0
  220. package/src/core/tool-scopes.js +326 -0
  221. package/src/core/tool-scopes.js.map +1 -0
  222. package/src/core/tools-config.d.ts +35 -0
  223. package/src/core/tools-config.d.ts.map +1 -0
  224. package/src/core/tools-config.js +88 -0
  225. package/src/core/tools-config.js.map +1 -0
  226. package/src/core/types.d.ts +88 -0
  227. package/src/core/types.d.ts.map +1 -0
  228. package/src/core/types.js +12 -0
  229. package/src/core/types.js.map +1 -0
  230. package/src/core/uat-client.d.ts +47 -0
  231. package/src/core/uat-client.d.ts.map +1 -0
  232. package/src/core/uat-client.js +173 -0
  233. package/src/core/uat-client.js.map +1 -0
  234. package/src/core/version.d.ts +26 -0
  235. package/src/core/version.d.ts.map +1 -0
  236. package/src/core/version.js +50 -0
  237. package/src/core/version.js.map +1 -0
  238. package/src/messaging/converters/audio.d.ts +9 -0
  239. package/src/messaging/converters/audio.d.ts.map +1 -0
  240. package/src/messaging/converters/audio.js +22 -0
  241. package/src/messaging/converters/audio.js.map +1 -0
  242. package/src/messaging/converters/calendar.d.ts +14 -0
  243. package/src/messaging/converters/calendar.d.ts.map +1 -0
  244. package/src/messaging/converters/calendar.js +51 -0
  245. package/src/messaging/converters/calendar.js.map +1 -0
  246. package/src/messaging/converters/content-converter.d.ts +42 -0
  247. package/src/messaging/converters/content-converter.d.ts.map +1 -0
  248. package/src/messaging/converters/content-converter.js +107 -0
  249. package/src/messaging/converters/content-converter.js.map +1 -0
  250. package/src/messaging/converters/file.d.ts +9 -0
  251. package/src/messaging/converters/file.d.ts.map +1 -0
  252. package/src/messaging/converters/file.js +21 -0
  253. package/src/messaging/converters/file.js.map +1 -0
  254. package/src/messaging/converters/folder.d.ts +9 -0
  255. package/src/messaging/converters/folder.d.ts.map +1 -0
  256. package/src/messaging/converters/folder.js +21 -0
  257. package/src/messaging/converters/folder.js.map +1 -0
  258. package/src/messaging/converters/hongbao.d.ts +9 -0
  259. package/src/messaging/converters/hongbao.d.ts.map +1 -0
  260. package/src/messaging/converters/hongbao.js +17 -0
  261. package/src/messaging/converters/hongbao.js.map +1 -0
  262. package/src/messaging/converters/image.d.ts +9 -0
  263. package/src/messaging/converters/image.d.ts.map +1 -0
  264. package/src/messaging/converters/image.js +19 -0
  265. package/src/messaging/converters/image.js.map +1 -0
  266. package/src/messaging/converters/index.d.ts +9 -0
  267. package/src/messaging/converters/index.d.ts.map +1 -0
  268. package/src/messaging/converters/index.js +51 -0
  269. package/src/messaging/converters/index.js.map +1 -0
  270. package/src/messaging/converters/interactive/card-converter.d.ts +77 -0
  271. package/src/messaging/converters/interactive/card-converter.d.ts.map +1 -0
  272. package/src/messaging/converters/interactive/card-converter.js +1174 -0
  273. package/src/messaging/converters/interactive/card-converter.js.map +1 -0
  274. package/src/messaging/converters/interactive/card-utils.d.ts +10 -0
  275. package/src/messaging/converters/interactive/card-utils.d.ts.map +1 -0
  276. package/src/messaging/converters/interactive/card-utils.js +43 -0
  277. package/src/messaging/converters/interactive/card-utils.js.map +1 -0
  278. package/src/messaging/converters/interactive/index.d.ts +9 -0
  279. package/src/messaging/converters/interactive/index.d.ts.map +1 -0
  280. package/src/messaging/converters/interactive/index.js +22 -0
  281. package/src/messaging/converters/interactive/index.js.map +1 -0
  282. package/src/messaging/converters/interactive/legacy.d.ts +12 -0
  283. package/src/messaging/converters/interactive/legacy.d.ts.map +1 -0
  284. package/src/messaging/converters/interactive/legacy.js +58 -0
  285. package/src/messaging/converters/interactive/legacy.js.map +1 -0
  286. package/src/messaging/converters/interactive/types.d.ts +24 -0
  287. package/src/messaging/converters/interactive/types.d.ts.map +1 -0
  288. package/src/messaging/converters/interactive/types.js +25 -0
  289. package/src/messaging/converters/interactive/types.js.map +1 -0
  290. package/src/messaging/converters/location.d.ts +9 -0
  291. package/src/messaging/converters/location.d.ts.map +1 -0
  292. package/src/messaging/converters/location.js +20 -0
  293. package/src/messaging/converters/location.js.map +1 -0
  294. package/src/messaging/converters/merge-forward.d.ts +33 -0
  295. package/src/messaging/converters/merge-forward.d.ts.map +1 -0
  296. package/src/messaging/converters/merge-forward.js +226 -0
  297. package/src/messaging/converters/merge-forward.js.map +1 -0
  298. package/src/messaging/converters/post.d.ts +12 -0
  299. package/src/messaging/converters/post.d.ts.map +1 -0
  300. package/src/messaging/converters/post.js +136 -0
  301. package/src/messaging/converters/post.js.map +1 -0
  302. package/src/messaging/converters/share.d.ts +10 -0
  303. package/src/messaging/converters/share.d.ts.map +1 -0
  304. package/src/messaging/converters/share.js +24 -0
  305. package/src/messaging/converters/share.js.map +1 -0
  306. package/src/messaging/converters/sticker.d.ts +9 -0
  307. package/src/messaging/converters/sticker.d.ts.map +1 -0
  308. package/src/messaging/converters/sticker.js +19 -0
  309. package/src/messaging/converters/sticker.js.map +1 -0
  310. package/src/messaging/converters/system.d.ts +13 -0
  311. package/src/messaging/converters/system.d.ts.map +1 -0
  312. package/src/messaging/converters/system.js +33 -0
  313. package/src/messaging/converters/system.js.map +1 -0
  314. package/src/messaging/converters/text.d.ts +9 -0
  315. package/src/messaging/converters/text.d.ts.map +1 -0
  316. package/src/messaging/converters/text.js +15 -0
  317. package/src/messaging/converters/text.js.map +1 -0
  318. package/src/messaging/converters/todo.d.ts +9 -0
  319. package/src/messaging/converters/todo.d.ts.map +1 -0
  320. package/src/messaging/converters/todo.js +42 -0
  321. package/src/messaging/converters/todo.js.map +1 -0
  322. package/src/messaging/converters/types.d.ts +108 -0
  323. package/src/messaging/converters/types.d.ts.map +1 -0
  324. package/src/messaging/converters/types.js +8 -0
  325. package/src/messaging/converters/types.js.map +1 -0
  326. package/src/messaging/converters/unknown.d.ts +9 -0
  327. package/src/messaging/converters/unknown.d.ts.map +1 -0
  328. package/src/messaging/converters/unknown.js +17 -0
  329. package/src/messaging/converters/unknown.js.map +1 -0
  330. package/src/messaging/converters/utils.d.ts +23 -0
  331. package/src/messaging/converters/utils.d.ts.map +1 -0
  332. package/src/messaging/converters/utils.js +52 -0
  333. package/src/messaging/converters/utils.js.map +1 -0
  334. package/src/messaging/converters/video-chat.d.ts +9 -0
  335. package/src/messaging/converters/video-chat.d.ts.map +1 -0
  336. package/src/messaging/converters/video-chat.js +24 -0
  337. package/src/messaging/converters/video-chat.js.map +1 -0
  338. package/src/messaging/converters/video.d.ts +9 -0
  339. package/src/messaging/converters/video.d.ts.map +1 -0
  340. package/src/messaging/converters/video.js +33 -0
  341. package/src/messaging/converters/video.js.map +1 -0
  342. package/src/messaging/converters/vote.d.ts +9 -0
  343. package/src/messaging/converters/vote.d.ts.map +1 -0
  344. package/src/messaging/converters/vote.js +25 -0
  345. package/src/messaging/converters/vote.js.map +1 -0
  346. package/src/messaging/inbound/dedup.d.ts +60 -0
  347. package/src/messaging/inbound/dedup.d.ts.map +1 -0
  348. package/src/messaging/inbound/dedup.js +117 -0
  349. package/src/messaging/inbound/dedup.js.map +1 -0
  350. package/src/messaging/inbound/dispatch-builders.d.ts +84 -0
  351. package/src/messaging/inbound/dispatch-builders.d.ts.map +1 -0
  352. package/src/messaging/inbound/dispatch-builders.js +153 -0
  353. package/src/messaging/inbound/dispatch-builders.js.map +1 -0
  354. package/src/messaging/inbound/dispatch-commands.d.ts +28 -0
  355. package/src/messaging/inbound/dispatch-commands.d.ts.map +1 -0
  356. package/src/messaging/inbound/dispatch-commands.js +113 -0
  357. package/src/messaging/inbound/dispatch-commands.js.map +1 -0
  358. package/src/messaging/inbound/dispatch-context.d.ts +68 -0
  359. package/src/messaging/inbound/dispatch-context.d.ts.map +1 -0
  360. package/src/messaging/inbound/dispatch-context.js +137 -0
  361. package/src/messaging/inbound/dispatch-context.js.map +1 -0
  362. package/src/messaging/inbound/dispatch.d.ts +48 -0
  363. package/src/messaging/inbound/dispatch.d.ts.map +1 -0
  364. package/src/messaging/inbound/dispatch.js +193 -0
  365. package/src/messaging/inbound/dispatch.js.map +1 -0
  366. package/src/messaging/inbound/enrich.d.ts +103 -0
  367. package/src/messaging/inbound/enrich.d.ts.map +1 -0
  368. package/src/messaging/inbound/enrich.js +228 -0
  369. package/src/messaging/inbound/enrich.js.map +1 -0
  370. package/src/messaging/inbound/gate-effects.d.ts +24 -0
  371. package/src/messaging/inbound/gate-effects.d.ts.map +1 -0
  372. package/src/messaging/inbound/gate-effects.js +44 -0
  373. package/src/messaging/inbound/gate-effects.js.map +1 -0
  374. package/src/messaging/inbound/gate.d.ts +61 -0
  375. package/src/messaging/inbound/gate.d.ts.map +1 -0
  376. package/src/messaging/inbound/gate.js +234 -0
  377. package/src/messaging/inbound/gate.js.map +1 -0
  378. package/src/messaging/inbound/handler.d.ts +36 -0
  379. package/src/messaging/inbound/handler.d.ts.map +1 -0
  380. package/src/messaging/inbound/handler.js +174 -0
  381. package/src/messaging/inbound/handler.js.map +1 -0
  382. package/src/messaging/inbound/media-resolver.d.ts +33 -0
  383. package/src/messaging/inbound/media-resolver.d.ts.map +1 -0
  384. package/src/messaging/inbound/media-resolver.js +88 -0
  385. package/src/messaging/inbound/media-resolver.js.map +1 -0
  386. package/src/messaging/inbound/mention.d.ts +40 -0
  387. package/src/messaging/inbound/mention.d.ts.map +1 -0
  388. package/src/messaging/inbound/mention.js +82 -0
  389. package/src/messaging/inbound/mention.js.map +1 -0
  390. package/src/messaging/inbound/parse-io.d.ts +51 -0
  391. package/src/messaging/inbound/parse-io.d.ts.map +1 -0
  392. package/src/messaging/inbound/parse-io.js +82 -0
  393. package/src/messaging/inbound/parse-io.js.map +1 -0
  394. package/src/messaging/inbound/parse.d.ts +29 -0
  395. package/src/messaging/inbound/parse.d.ts.map +1 -0
  396. package/src/messaging/inbound/parse.js +107 -0
  397. package/src/messaging/inbound/parse.js.map +1 -0
  398. package/src/messaging/inbound/permission.d.ts +18 -0
  399. package/src/messaging/inbound/permission.d.ts.map +1 -0
  400. package/src/messaging/inbound/permission.js +41 -0
  401. package/src/messaging/inbound/permission.js.map +1 -0
  402. package/src/messaging/inbound/policy.d.ts +95 -0
  403. package/src/messaging/inbound/policy.d.ts.map +1 -0
  404. package/src/messaging/inbound/policy.js +161 -0
  405. package/src/messaging/inbound/policy.js.map +1 -0
  406. package/src/messaging/inbound/reaction-handler.d.ts +62 -0
  407. package/src/messaging/inbound/reaction-handler.d.ts.map +1 -0
  408. package/src/messaging/inbound/reaction-handler.js +221 -0
  409. package/src/messaging/inbound/reaction-handler.js.map +1 -0
  410. package/src/messaging/inbound/user-name-cache.d.ts +83 -0
  411. package/src/messaging/inbound/user-name-cache.d.ts.map +1 -0
  412. package/src/messaging/inbound/user-name-cache.js +242 -0
  413. package/src/messaging/inbound/user-name-cache.js.map +1 -0
  414. package/src/messaging/outbound/actions.d.ts +17 -0
  415. package/src/messaging/outbound/actions.d.ts.map +1 -0
  416. package/src/messaging/outbound/actions.js +310 -0
  417. package/src/messaging/outbound/actions.js.map +1 -0
  418. package/src/messaging/outbound/chat-manage.d.ts +65 -0
  419. package/src/messaging/outbound/chat-manage.d.ts.map +1 -0
  420. package/src/messaging/outbound/chat-manage.js +112 -0
  421. package/src/messaging/outbound/chat-manage.js.map +1 -0
  422. package/src/messaging/outbound/deliver.d.ts +156 -0
  423. package/src/messaging/outbound/deliver.d.ts.map +1 -0
  424. package/src/messaging/outbound/deliver.js +299 -0
  425. package/src/messaging/outbound/deliver.js.map +1 -0
  426. package/src/messaging/outbound/fetch.d.ts +13 -0
  427. package/src/messaging/outbound/fetch.d.ts.map +1 -0
  428. package/src/messaging/outbound/fetch.js +13 -0
  429. package/src/messaging/outbound/fetch.js.map +1 -0
  430. package/src/messaging/outbound/forward.d.ts +27 -0
  431. package/src/messaging/outbound/forward.d.ts.map +1 -0
  432. package/src/messaging/outbound/forward.js +49 -0
  433. package/src/messaging/outbound/forward.js.map +1 -0
  434. package/src/messaging/outbound/media-url-utils.d.ts +30 -0
  435. package/src/messaging/outbound/media-url-utils.d.ts.map +1 -0
  436. package/src/messaging/outbound/media-url-utils.js +131 -0
  437. package/src/messaging/outbound/media-url-utils.js.map +1 -0
  438. package/src/messaging/outbound/media.d.ts +256 -0
  439. package/src/messaging/outbound/media.d.ts.map +1 -0
  440. package/src/messaging/outbound/media.js +749 -0
  441. package/src/messaging/outbound/media.js.map +1 -0
  442. package/src/messaging/outbound/outbound.d.ts +90 -0
  443. package/src/messaging/outbound/outbound.d.ts.map +1 -0
  444. package/src/messaging/outbound/outbound.js +112 -0
  445. package/src/messaging/outbound/outbound.js.map +1 -0
  446. package/src/messaging/outbound/reactions.d.ts +125 -0
  447. package/src/messaging/outbound/reactions.d.ts.map +1 -0
  448. package/src/messaging/outbound/reactions.js +379 -0
  449. package/src/messaging/outbound/reactions.js.map +1 -0
  450. package/src/messaging/outbound/send.d.ts +136 -0
  451. package/src/messaging/outbound/send.d.ts.map +1 -0
  452. package/src/messaging/outbound/send.js +289 -0
  453. package/src/messaging/outbound/send.js.map +1 -0
  454. package/src/messaging/outbound/typing.d.ts +61 -0
  455. package/src/messaging/outbound/typing.d.ts.map +1 -0
  456. package/src/messaging/outbound/typing.js +136 -0
  457. package/src/messaging/outbound/typing.js.map +1 -0
  458. package/src/messaging/shared/message-lookup.d.ts +55 -0
  459. package/src/messaging/shared/message-lookup.d.ts.map +1 -0
  460. package/src/messaging/shared/message-lookup.js +118 -0
  461. package/src/messaging/shared/message-lookup.js.map +1 -0
  462. package/src/messaging/types.d.ts +177 -0
  463. package/src/messaging/types.d.ts.map +1 -0
  464. package/src/messaging/types.js +11 -0
  465. package/src/messaging/types.js.map +1 -0
  466. package/src/tools/auto-auth.d.ts +57 -0
  467. package/src/tools/auto-auth.d.ts.map +1 -0
  468. package/src/tools/auto-auth.js +925 -0
  469. package/src/tools/auto-auth.js.map +1 -0
  470. package/src/tools/helpers.d.ts +220 -0
  471. package/src/tools/helpers.d.ts.map +1 -0
  472. package/src/tools/helpers.js +298 -0
  473. package/src/tools/helpers.js.map +1 -0
  474. package/src/tools/mcp/doc/create.d.ts +13 -0
  475. package/src/tools/mcp/doc/create.d.ts.map +1 -0
  476. package/src/tools/mcp/doc/create.js +45 -0
  477. package/src/tools/mcp/doc/create.js.map +1 -0
  478. package/src/tools/mcp/doc/fetch.d.ts +13 -0
  479. package/src/tools/mcp/doc/fetch.d.ts.map +1 -0
  480. package/src/tools/mcp/doc/fetch.js +37 -0
  481. package/src/tools/mcp/doc/fetch.js.map +1 -0
  482. package/src/tools/mcp/doc/index.d.ts +13 -0
  483. package/src/tools/mcp/doc/index.d.ts.map +1 -0
  484. package/src/tools/mcp/doc/index.js +42 -0
  485. package/src/tools/mcp/doc/index.js.map +1 -0
  486. package/src/tools/mcp/doc/update.d.ts +13 -0
  487. package/src/tools/mcp/doc/update.d.ts.map +1 -0
  488. package/src/tools/mcp/doc/update.js +62 -0
  489. package/src/tools/mcp/doc/update.js.map +1 -0
  490. package/src/tools/mcp/shared.d.ts +58 -0
  491. package/src/tools/mcp/shared.d.ts.map +1 -0
  492. package/src/tools/mcp/shared.js +224 -0
  493. package/src/tools/mcp/shared.js.map +1 -0
  494. package/src/tools/oapi/bitable/app-table-field.d.ts +17 -0
  495. package/src/tools/oapi/bitable/app-table-field.d.ts.map +1 -0
  496. package/src/tools/oapi/bitable/app-table-field.js +224 -0
  497. package/src/tools/oapi/bitable/app-table-field.js.map +1 -0
  498. package/src/tools/oapi/bitable/app-table-record.d.ts +21 -0
  499. package/src/tools/oapi/bitable/app-table-record.d.ts.map +1 -0
  500. package/src/tools/oapi/bitable/app-table-record.js +449 -0
  501. package/src/tools/oapi/bitable/app-table-record.js.map +1 -0
  502. package/src/tools/oapi/bitable/app-table-view.d.ts +18 -0
  503. package/src/tools/oapi/bitable/app-table-view.d.ts.map +1 -0
  504. package/src/tools/oapi/bitable/app-table-view.js +197 -0
  505. package/src/tools/oapi/bitable/app-table-view.js.map +1 -0
  506. package/src/tools/oapi/bitable/app-table.d.ts +20 -0
  507. package/src/tools/oapi/bitable/app-table.d.ts.map +1 -0
  508. package/src/tools/oapi/bitable/app-table.js +249 -0
  509. package/src/tools/oapi/bitable/app-table.js.map +1 -0
  510. package/src/tools/oapi/bitable/app.d.ts +19 -0
  511. package/src/tools/oapi/bitable/app.d.ts.map +1 -0
  512. package/src/tools/oapi/bitable/app.js +188 -0
  513. package/src/tools/oapi/bitable/app.js.map +1 -0
  514. package/src/tools/oapi/bitable/index.d.ts +10 -0
  515. package/src/tools/oapi/bitable/index.d.ts.map +1 -0
  516. package/src/tools/oapi/bitable/index.js +10 -0
  517. package/src/tools/oapi/bitable/index.js.map +1 -0
  518. package/src/tools/oapi/calendar/calendar.d.ts +16 -0
  519. package/src/tools/oapi/calendar/calendar.d.ts.map +1 -0
  520. package/src/tools/oapi/calendar/calendar.js +124 -0
  521. package/src/tools/oapi/calendar/calendar.js.map +1 -0
  522. package/src/tools/oapi/calendar/event-attendee.d.ts +17 -0
  523. package/src/tools/oapi/calendar/event-attendee.d.ts.map +1 -0
  524. package/src/tools/oapi/calendar/event-attendee.js +275 -0
  525. package/src/tools/oapi/calendar/event-attendee.js.map +1 -0
  526. package/src/tools/oapi/calendar/event.d.ts +17 -0
  527. package/src/tools/oapi/calendar/event.d.ts.map +1 -0
  528. package/src/tools/oapi/calendar/event.js +721 -0
  529. package/src/tools/oapi/calendar/event.js.map +1 -0
  530. package/src/tools/oapi/calendar/freebusy.d.ts +14 -0
  531. package/src/tools/oapi/calendar/freebusy.d.ts.map +1 -0
  532. package/src/tools/oapi/calendar/freebusy.js +113 -0
  533. package/src/tools/oapi/calendar/freebusy.js.map +1 -0
  534. package/src/tools/oapi/calendar/index.d.ts +9 -0
  535. package/src/tools/oapi/calendar/index.d.ts.map +1 -0
  536. package/src/tools/oapi/calendar/index.js +9 -0
  537. package/src/tools/oapi/calendar/index.js.map +1 -0
  538. package/src/tools/oapi/chat/chat.d.ts +17 -0
  539. package/src/tools/oapi/chat/chat.d.ts.map +1 -0
  540. package/src/tools/oapi/chat/chat.js +126 -0
  541. package/src/tools/oapi/chat/chat.js.map +1 -0
  542. package/src/tools/oapi/chat/index.d.ts +11 -0
  543. package/src/tools/oapi/chat/index.d.ts.map +1 -0
  544. package/src/tools/oapi/chat/index.js +16 -0
  545. package/src/tools/oapi/chat/index.js.map +1 -0
  546. package/src/tools/oapi/chat/members.d.ts +12 -0
  547. package/src/tools/oapi/chat/members.d.ts.map +1 -0
  548. package/src/tools/oapi/chat/members.js +83 -0
  549. package/src/tools/oapi/chat/members.js.map +1 -0
  550. package/src/tools/oapi/common/get-user.d.ts +13 -0
  551. package/src/tools/oapi/common/get-user.d.ts.map +1 -0
  552. package/src/tools/oapi/common/get-user.js +108 -0
  553. package/src/tools/oapi/common/get-user.js.map +1 -0
  554. package/src/tools/oapi/common/index.d.ts +7 -0
  555. package/src/tools/oapi/common/index.d.ts.map +1 -0
  556. package/src/tools/oapi/common/index.js +7 -0
  557. package/src/tools/oapi/common/index.js.map +1 -0
  558. package/src/tools/oapi/common/search-user.d.ts +12 -0
  559. package/src/tools/oapi/common/search-user.d.ts.map +1 -0
  560. package/src/tools/oapi/common/search-user.js +75 -0
  561. package/src/tools/oapi/common/search-user.js.map +1 -0
  562. package/src/tools/oapi/drive/doc-comments.d.ts +16 -0
  563. package/src/tools/oapi/drive/doc-comments.d.ts.map +1 -0
  564. package/src/tools/oapi/drive/doc-comments.js +288 -0
  565. package/src/tools/oapi/drive/doc-comments.js.map +1 -0
  566. package/src/tools/oapi/drive/doc-media.d.ts +20 -0
  567. package/src/tools/oapi/drive/doc-media.d.ts.map +1 -0
  568. package/src/tools/oapi/drive/doc-media.js +337 -0
  569. package/src/tools/oapi/drive/doc-media.js.map +1 -0
  570. package/src/tools/oapi/drive/file.d.ts +20 -0
  571. package/src/tools/oapi/drive/file.d.ts.map +1 -0
  572. package/src/tools/oapi/drive/file.js +485 -0
  573. package/src/tools/oapi/drive/file.js.map +1 -0
  574. package/src/tools/oapi/drive/index.d.ts +13 -0
  575. package/src/tools/oapi/drive/index.d.ts.map +1 -0
  576. package/src/tools/oapi/drive/index.js +37 -0
  577. package/src/tools/oapi/drive/index.js.map +1 -0
  578. package/src/tools/oapi/helpers.d.ts +174 -0
  579. package/src/tools/oapi/helpers.d.ts.map +1 -0
  580. package/src/tools/oapi/helpers.js +341 -0
  581. package/src/tools/oapi/helpers.js.map +1 -0
  582. package/src/tools/oapi/im/format-messages.d.ts +51 -0
  583. package/src/tools/oapi/im/format-messages.d.ts.map +1 -0
  584. package/src/tools/oapi/im/format-messages.js +166 -0
  585. package/src/tools/oapi/im/format-messages.js.map +1 -0
  586. package/src/tools/oapi/im/index.d.ts +11 -0
  587. package/src/tools/oapi/im/index.d.ts.map +1 -0
  588. package/src/tools/oapi/im/index.js +18 -0
  589. package/src/tools/oapi/im/index.js.map +1 -0
  590. package/src/tools/oapi/im/message-read.d.ts +14 -0
  591. package/src/tools/oapi/im/message-read.d.ts.map +1 -0
  592. package/src/tools/oapi/im/message-read.js +412 -0
  593. package/src/tools/oapi/im/message-read.js.map +1 -0
  594. package/src/tools/oapi/im/message.d.ts +17 -0
  595. package/src/tools/oapi/im/message.d.ts.map +1 -0
  596. package/src/tools/oapi/im/message.js +171 -0
  597. package/src/tools/oapi/im/message.js.map +1 -0
  598. package/src/tools/oapi/im/resource.d.ts +14 -0
  599. package/src/tools/oapi/im/resource.d.ts.map +1 -0
  600. package/src/tools/oapi/im/resource.js +152 -0
  601. package/src/tools/oapi/im/resource.js.map +1 -0
  602. package/src/tools/oapi/im/time-utils.d.ts +47 -0
  603. package/src/tools/oapi/im/time-utils.d.ts.map +1 -0
  604. package/src/tools/oapi/im/time-utils.js +202 -0
  605. package/src/tools/oapi/im/time-utils.js.map +1 -0
  606. package/src/tools/oapi/im/user-name-uat.d.ts +24 -0
  607. package/src/tools/oapi/im/user-name-uat.d.ts.map +1 -0
  608. package/src/tools/oapi/im/user-name-uat.js +128 -0
  609. package/src/tools/oapi/im/user-name-uat.js.map +1 -0
  610. package/src/tools/oapi/index.d.ts +12 -0
  611. package/src/tools/oapi/index.d.ts.map +1 -0
  612. package/src/tools/oapi/index.js +59 -0
  613. package/src/tools/oapi/index.js.map +1 -0
  614. package/src/tools/oapi/sdk-types.d.ts +97 -0
  615. package/src/tools/oapi/sdk-types.d.ts.map +1 -0
  616. package/src/tools/oapi/sdk-types.js +13 -0
  617. package/src/tools/oapi/sdk-types.js.map +1 -0
  618. package/src/tools/oapi/search/doc-search.d.ts +14 -0
  619. package/src/tools/oapi/search/doc-search.d.ts.map +1 -0
  620. package/src/tools/oapi/search/doc-search.js +203 -0
  621. package/src/tools/oapi/search/doc-search.js.map +1 -0
  622. package/src/tools/oapi/search/index.d.ts +13 -0
  623. package/src/tools/oapi/search/index.d.ts.map +1 -0
  624. package/src/tools/oapi/search/index.js +34 -0
  625. package/src/tools/oapi/search/index.js.map +1 -0
  626. package/src/tools/oapi/sheets/index.d.ts +13 -0
  627. package/src/tools/oapi/sheets/index.d.ts.map +1 -0
  628. package/src/tools/oapi/sheets/index.js +32 -0
  629. package/src/tools/oapi/sheets/index.js.map +1 -0
  630. package/src/tools/oapi/sheets/sheet.d.ts +17 -0
  631. package/src/tools/oapi/sheets/sheet.d.ts.map +1 -0
  632. package/src/tools/oapi/sheets/sheet.js +652 -0
  633. package/src/tools/oapi/sheets/sheet.js.map +1 -0
  634. package/src/tools/oapi/task/comment.d.ts +16 -0
  635. package/src/tools/oapi/task/comment.d.ts.map +1 -0
  636. package/src/tools/oapi/task/comment.js +142 -0
  637. package/src/tools/oapi/task/comment.js.map +1 -0
  638. package/src/tools/oapi/task/index.d.ts +9 -0
  639. package/src/tools/oapi/task/index.d.ts.map +1 -0
  640. package/src/tools/oapi/task/index.js +9 -0
  641. package/src/tools/oapi/task/index.js.map +1 -0
  642. package/src/tools/oapi/task/subtask.d.ts +15 -0
  643. package/src/tools/oapi/task/subtask.d.ts.map +1 -0
  644. package/src/tools/oapi/task/subtask.js +164 -0
  645. package/src/tools/oapi/task/subtask.js.map +1 -0
  646. package/src/tools/oapi/task/task.d.ts +17 -0
  647. package/src/tools/oapi/task/task.d.ts.map +1 -0
  648. package/src/tools/oapi/task/task.js +346 -0
  649. package/src/tools/oapi/task/task.js.map +1 -0
  650. package/src/tools/oapi/task/tasklist.d.ts +22 -0
  651. package/src/tools/oapi/task/tasklist.d.ts.map +1 -0
  652. package/src/tools/oapi/task/tasklist.js +323 -0
  653. package/src/tools/oapi/task/tasklist.js.map +1 -0
  654. package/src/tools/oapi/wiki/index.d.ts +13 -0
  655. package/src/tools/oapi/wiki/index.d.ts.map +1 -0
  656. package/src/tools/oapi/wiki/index.js +35 -0
  657. package/src/tools/oapi/wiki/index.js.map +1 -0
  658. package/src/tools/oapi/wiki/space-node.d.ts +18 -0
  659. package/src/tools/oapi/wiki/space-node.d.ts.map +1 -0
  660. package/src/tools/oapi/wiki/space-node.js +253 -0
  661. package/src/tools/oapi/wiki/space-node.js.map +1 -0
  662. package/src/tools/oapi/wiki/space.d.ts +16 -0
  663. package/src/tools/oapi/wiki/space.d.ts.map +1 -0
  664. package/src/tools/oapi/wiki/space.js +132 -0
  665. package/src/tools/oapi/wiki/space.js.map +1 -0
  666. package/src/tools/oauth-batch-auth.d.ts +12 -0
  667. package/src/tools/oauth-batch-auth.d.ts.map +1 -0
  668. package/src/tools/oauth-batch-auth.js +142 -0
  669. package/src/tools/oauth-batch-auth.js.map +1 -0
  670. package/src/tools/oauth-cards.d.ts +27 -0
  671. package/src/tools/oauth-cards.d.ts.map +1 -0
  672. package/src/tools/oauth-cards.js +251 -0
  673. package/src/tools/oauth-cards.js.map +1 -0
  674. package/src/tools/oauth.d.ts +48 -0
  675. package/src/tools/oauth.d.ts.map +1 -0
  676. package/src/tools/oauth.js +619 -0
  677. package/src/tools/oauth.js.map +1 -0
  678. package/src/tools/onboarding-auth.d.ts +28 -0
  679. package/src/tools/onboarding-auth.d.ts.map +1 -0
  680. package/src/tools/onboarding-auth.js +131 -0
  681. package/src/tools/onboarding-auth.js.map +1 -0
  682. package/src/tools/tat/im/index.d.ts +16 -0
  683. package/src/tools/tat/im/index.d.ts.map +1 -0
  684. package/src/tools/tat/im/index.js +19 -0
  685. package/src/tools/tat/im/index.js.map +1 -0
  686. package/src/tools/tat/im/resource.d.ts +16 -0
  687. package/src/tools/tat/im/resource.d.ts.map +1 -0
  688. package/src/tools/tat/im/resource.js +158 -0
  689. package/src/tools/tat/im/resource.js.map +1 -0
@@ -0,0 +1,749 @@
1
+ /**
2
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * Media handling for the Feishu/Lark channel plugin.
6
+ *
7
+ * Provides functions for downloading images and file resources from
8
+ * Feishu messages, uploading media to the Feishu IM storage, and
9
+ * sending image / file messages to chats.
10
+ */
11
+ /* eslint-disable @typescript-eslint/no-explicit-any */
12
+ import * as dns from 'node:dns/promises';
13
+ import * as fs from 'node:fs';
14
+ import * as net from 'node:net';
15
+ import * as os from 'node:os';
16
+ import * as path from 'node:path';
17
+ import { Readable } from 'node:stream';
18
+ import { LarkClient } from '../../core/lark-client';
19
+ import { normalizeFeishuTarget, resolveReceiveIdType } from '../../core/targets';
20
+ import { isLocalMediaPath, normalizeMediaUrlInput, resolveFileNameFromMediaUrl, safeFileUrlToPath, validateLocalMediaRoots, } from './media-url-utils';
21
+ import { larkLogger } from '../../core/lark-logger';
22
+ const log = larkLogger('outbound/media');
23
+ // ---------------------------------------------------------------------------
24
+ // Response extraction helpers
25
+ // ---------------------------------------------------------------------------
26
+ /**
27
+ * Extract a Buffer from various SDK response formats.
28
+ *
29
+ * The Feishu Node SDK can return binary data in several shapes depending
30
+ * on the runtime environment and SDK version:
31
+ * - A Buffer directly
32
+ * - An ArrayBuffer
33
+ * - A response object with a `.data` property
34
+ * - A response object with `.getReadableStream()`
35
+ * - A response object with `.writeFile(path)`
36
+ * - An async iterable / iterator
37
+ * - A Node.js Readable stream
38
+ *
39
+ * This helper normalises all of those into a single Buffer.
40
+ */
41
+ async function extractBufferFromResponse(response) {
42
+ // Direct Buffer
43
+ if (Buffer.isBuffer(response)) {
44
+ return { buffer: response };
45
+ }
46
+ // ArrayBuffer
47
+ if (response instanceof ArrayBuffer) {
48
+ return { buffer: Buffer.from(response) };
49
+ }
50
+ // Null / undefined guard
51
+ if (response == null) {
52
+ throw new Error('[feishu-media] Received null/undefined response');
53
+ }
54
+ const resp = response;
55
+ const contentType = resp.headers?.['content-type'] ?? resp.contentType ?? undefined;
56
+ // Response with .data as Buffer or ArrayBuffer
57
+ if (resp.data != null) {
58
+ if (Buffer.isBuffer(resp.data)) {
59
+ return { buffer: resp.data, contentType };
60
+ }
61
+ if (resp.data instanceof ArrayBuffer) {
62
+ return { buffer: Buffer.from(resp.data), contentType };
63
+ }
64
+ // .data might itself be a readable stream
65
+ if (typeof resp.data.pipe === 'function') {
66
+ const buf = await streamToBuffer(resp.data);
67
+ return { buffer: buf, contentType };
68
+ }
69
+ }
70
+ // Response with .getReadableStream()
71
+ if (typeof resp.getReadableStream === 'function') {
72
+ const stream = await resp.getReadableStream();
73
+ const buf = await streamToBuffer(stream);
74
+ return { buffer: buf, contentType };
75
+ }
76
+ // Response with .writeFile(path) -- write to a temp file and read back.
77
+ if (typeof resp.writeFile === 'function') {
78
+ const tmpDir = os.tmpdir();
79
+ const tmpFile = path.join(tmpDir, `feishu-media-${Date.now()}`);
80
+ try {
81
+ await resp.writeFile(tmpFile);
82
+ const buf = fs.readFileSync(tmpFile);
83
+ return { buffer: buf, contentType };
84
+ }
85
+ finally {
86
+ // Clean up the temp file.
87
+ try {
88
+ fs.unlinkSync(tmpFile);
89
+ }
90
+ catch {
91
+ // Ignore cleanup errors.
92
+ }
93
+ }
94
+ }
95
+ // Async iterable / iterator (e.g. response body chunks)
96
+ if (typeof resp[Symbol.asyncIterator] === 'function' || typeof resp.next === 'function') {
97
+ const chunks = [];
98
+ const iterable = typeof resp[Symbol.asyncIterator] === 'function'
99
+ ? resp
100
+ : asyncIteratorToIterable(resp);
101
+ for await (const chunk of iterable) {
102
+ chunks.push(Buffer.from(chunk));
103
+ }
104
+ return { buffer: Buffer.concat(chunks), contentType };
105
+ }
106
+ // Node.js Readable stream
107
+ if (typeof resp.pipe === 'function') {
108
+ const buf = await streamToBuffer(resp);
109
+ return { buffer: buf, contentType };
110
+ }
111
+ throw new Error('[feishu-media] Unable to extract binary data from response: unrecognised format');
112
+ }
113
+ /**
114
+ * Consume a Readable stream into a Buffer.
115
+ */
116
+ function streamToBuffer(stream) {
117
+ return new Promise((resolve, reject) => {
118
+ const chunks = [];
119
+ stream.on('data', (chunk) => {
120
+ chunks.push(Buffer.from(chunk));
121
+ });
122
+ stream.on('end', () => resolve(Buffer.concat(chunks)));
123
+ stream.on('error', reject);
124
+ });
125
+ }
126
+ /**
127
+ * Wrap an AsyncIterator into an AsyncIterable.
128
+ */
129
+ async function* asyncIteratorToIterable(iterator) {
130
+ while (true) {
131
+ const { value, done } = await iterator.next();
132
+ if (done)
133
+ break;
134
+ yield value;
135
+ }
136
+ }
137
+ // ---------------------------------------------------------------------------
138
+ // downloadMessageResourceFeishu
139
+ // ---------------------------------------------------------------------------
140
+ /**
141
+ * Download a resource (image or file) attached to a specific message.
142
+ *
143
+ * @param params.cfg - Plugin configuration.
144
+ * @param params.messageId - The message the resource belongs to.
145
+ * @param params.fileKey - The file_key or image_key of the resource.
146
+ * @param params.type - Whether the resource is an "image" or "file".
147
+ * @param params.accountId - Optional account identifier.
148
+ * @returns The resource buffer, content type, and file name.
149
+ */
150
+ export async function downloadMessageResourceFeishu(params) {
151
+ const { cfg, messageId, fileKey, type, accountId } = params;
152
+ const client = LarkClient.fromCfg(cfg, accountId).sdk;
153
+ const response = await client.im.messageResource.get({
154
+ path: {
155
+ message_id: messageId,
156
+ file_key: fileKey,
157
+ },
158
+ params: {
159
+ type,
160
+ },
161
+ });
162
+ const { buffer, contentType } = await extractBufferFromResponse(response);
163
+ // Attempt to extract file name from response headers.
164
+ let fileName;
165
+ if (response && typeof response === 'object') {
166
+ const resp = response;
167
+ const disposition = resp.headers?.['content-disposition'] ?? resp.headers?.['Content-Disposition'];
168
+ if (typeof disposition === 'string') {
169
+ const match = disposition.match(/filename[*]?=(?:UTF-8'')?["']?([^"';\n]+)/i);
170
+ if (match) {
171
+ fileName = decodeURIComponent(match[1].trim());
172
+ }
173
+ }
174
+ }
175
+ return { buffer, contentType, fileName };
176
+ }
177
+ // ---------------------------------------------------------------------------
178
+ // uploadImageLark
179
+ // ---------------------------------------------------------------------------
180
+ /**
181
+ * Upload an image to Feishu IM storage.
182
+ *
183
+ * Accepts either a Buffer containing the raw image bytes or a file
184
+ * system path to read from.
185
+ *
186
+ * @param params.cfg - Plugin configuration.
187
+ * @param params.image - A Buffer or local file path for the image.
188
+ * @param params.imageType - The image usage type: "message" (default) or "avatar".
189
+ * @param params.accountId - Optional account identifier.
190
+ * @returns The assigned image_key.
191
+ */
192
+ export async function uploadImageLark(params) {
193
+ const { cfg, image, imageType = 'message', accountId } = params;
194
+ const client = LarkClient.fromCfg(cfg, accountId).sdk;
195
+ const imageStream = Buffer.isBuffer(image) ? Readable.from(image) : fs.createReadStream(image);
196
+ const response = await client.im.image.create({
197
+ data: { image_type: imageType, image: imageStream },
198
+ });
199
+ const imageKey = response?.data?.image_key ?? response?.image_key;
200
+ if (!imageKey) {
201
+ throw new Error('[feishu-media] Image upload failed: no image_key in response. ' +
202
+ `Check that the image is a valid format (JPEG/PNG/GIF/BMP/WEBP). ` +
203
+ `Response: ${JSON.stringify(response).slice(0, 200)}`);
204
+ }
205
+ return { imageKey };
206
+ }
207
+ // ---------------------------------------------------------------------------
208
+ // uploadFileLark
209
+ // ---------------------------------------------------------------------------
210
+ /**
211
+ * Upload a file to Feishu IM storage.
212
+ *
213
+ * @param params.cfg - Plugin configuration.
214
+ * @param params.file - A Buffer or local file path.
215
+ * @param params.fileName - The display name of the file.
216
+ * @param params.fileType - Feishu file type: "opus" | "mp4" | "pdf" | "doc" | "xls" | "ppt" | "stream".
217
+ * @param params.duration - Duration in milliseconds (for audio/video files).
218
+ * @param params.accountId - Optional account identifier.
219
+ * @returns The assigned file_key.
220
+ */
221
+ export async function uploadFileLark(params) {
222
+ const { cfg, file, fileName, fileType, duration, accountId } = params;
223
+ const client = LarkClient.fromCfg(cfg, accountId).sdk;
224
+ const fileStream = Buffer.isBuffer(file) ? Readable.from(file) : fs.createReadStream(file);
225
+ const response = await client.im.file.create({
226
+ data: {
227
+ file_type: fileType,
228
+ file_name: fileName,
229
+ file: fileStream,
230
+ ...(duration !== undefined ? { duration: String(duration) } : {}),
231
+ },
232
+ });
233
+ const fileKey = response?.data?.file_key ?? response?.file_key;
234
+ if (!fileKey) {
235
+ throw new Error(`[feishu-media] File upload failed: no file_key in response for "${fileName}" (type=${fileType}). ` +
236
+ `Response: ${JSON.stringify(response).slice(0, 200)}`);
237
+ }
238
+ return { fileKey };
239
+ }
240
+ // ---------------------------------------------------------------------------
241
+ // Shared media message sender
242
+ // ---------------------------------------------------------------------------
243
+ /**
244
+ * Unified media message sender — handles both reply and create paths for
245
+ * image / file / audio `msg_type` values.
246
+ *
247
+ * Mirrors {@link sendImMessage} in `deliver.ts` (which covers "post" and
248
+ * "interactive"), extracted here to avoid a cross-module dependency.
249
+ */
250
+ async function sendMediaMessage(params) {
251
+ const { client, to, content, msgType, replyToMessageId, replyInThread } = params;
252
+ if (replyToMessageId) {
253
+ const response = await client.im.message.reply({
254
+ path: { message_id: replyToMessageId },
255
+ data: { content, msg_type: msgType, reply_in_thread: replyInThread },
256
+ });
257
+ return {
258
+ messageId: response?.data?.message_id ?? '',
259
+ chatId: response?.data?.chat_id ?? '',
260
+ };
261
+ }
262
+ const target = normalizeFeishuTarget(to);
263
+ if (!target) {
264
+ throw new Error(`[feishu-media] Cannot send ${msgType} message: "${to}" is not a valid target. ` +
265
+ `Expected a chat_id (oc_*), open_id (ou_*), or user_id.`);
266
+ }
267
+ const receiveIdType = resolveReceiveIdType(target);
268
+ const response = await client.im.message.create({
269
+ params: { receive_id_type: receiveIdType },
270
+ data: { receive_id: target, msg_type: msgType, content },
271
+ });
272
+ return {
273
+ messageId: response?.data?.message_id ?? '',
274
+ chatId: response?.data?.chat_id ?? '',
275
+ };
276
+ }
277
+ // ---------------------------------------------------------------------------
278
+ // sendImageLark
279
+ // ---------------------------------------------------------------------------
280
+ /**
281
+ * Send an image message to a chat or user.
282
+ *
283
+ * @param params.cfg - Plugin configuration.
284
+ * @param params.to - Target identifier.
285
+ * @param params.imageKey - The image_key from a previous upload.
286
+ * @param params.replyToMessageId - Optional message ID for threaded reply.
287
+ * @param params.replyInThread - When true, reply appears in thread.
288
+ * @param params.accountId - Optional account identifier.
289
+ * @returns The send result.
290
+ */
291
+ export async function sendImageLark(params) {
292
+ const { cfg, to, imageKey, replyToMessageId, replyInThread, accountId } = params;
293
+ log.info(`sendImageLark: target=${to}, imageKey=${imageKey}`);
294
+ const client = LarkClient.fromCfg(cfg, accountId).sdk;
295
+ const content = JSON.stringify({ image_key: imageKey });
296
+ return sendMediaMessage({ client, to, content, msgType: 'image', replyToMessageId, replyInThread });
297
+ }
298
+ // ---------------------------------------------------------------------------
299
+ // sendFileLark
300
+ // ---------------------------------------------------------------------------
301
+ /**
302
+ * Send a file message to a chat or user.
303
+ *
304
+ * @param params.cfg - Plugin configuration.
305
+ * @param params.to - Target identifier.
306
+ * @param params.fileKey - The file_key from a previous upload.
307
+ * @param params.replyToMessageId - Optional message ID for threaded reply.
308
+ * @param params.replyInThread - When true, reply appears in thread.
309
+ * @param params.accountId - Optional account identifier.
310
+ * @returns The send result.
311
+ */
312
+ export async function sendFileLark(params) {
313
+ const { cfg, to, fileKey, replyToMessageId, replyInThread, accountId } = params;
314
+ log.info(`sendFileLark: target=${to}, fileKey=${fileKey}`);
315
+ const client = LarkClient.fromCfg(cfg, accountId).sdk;
316
+ const content = JSON.stringify({ file_key: fileKey });
317
+ return sendMediaMessage({ client, to, content, msgType: 'file', replyToMessageId, replyInThread });
318
+ }
319
+ // ---------------------------------------------------------------------------
320
+ // sendVideoLark
321
+ // ---------------------------------------------------------------------------
322
+ /**
323
+ * Send a video message to a chat or user.
324
+ *
325
+ * Uses `msg_type: "media"` so Feishu renders the message as a playable
326
+ * video instead of a file attachment.
327
+ *
328
+ * @param params.cfg - Plugin configuration.
329
+ * @param params.to - Target identifier.
330
+ * @param params.fileKey - The file_key from a previous upload.
331
+ * @param params.replyToMessageId - Optional message ID for threaded reply.
332
+ * @param params.replyInThread - When true, reply appears in thread.
333
+ * @param params.accountId - Optional account identifier.
334
+ * @returns The send result.
335
+ */
336
+ export async function sendVideoLark(params) {
337
+ const { cfg, to, fileKey, replyToMessageId, replyInThread, accountId } = params;
338
+ log.info(`sendVideoLark: target=${to}, fileKey=${fileKey}`);
339
+ const client = LarkClient.fromCfg(cfg, accountId).sdk;
340
+ const content = JSON.stringify({ file_key: fileKey });
341
+ return sendMediaMessage({ client, to, content, msgType: 'media', replyToMessageId, replyInThread });
342
+ }
343
+ // ---------------------------------------------------------------------------
344
+ // sendAudioLark
345
+ // ---------------------------------------------------------------------------
346
+ /**
347
+ * Send an audio message to a chat or user.
348
+ *
349
+ * Uses `msg_type: "audio"` so Feishu renders the message as a playable
350
+ * voice bubble instead of a file attachment.
351
+ *
352
+ * @param params.cfg - Plugin configuration.
353
+ * @param params.to - Target identifier.
354
+ * @param params.fileKey - The file_key from a previous upload.
355
+ * @param params.replyToMessageId - Optional message ID for threaded reply.
356
+ * @param params.replyInThread - When true, reply appears in thread.
357
+ * @param params.accountId - Optional account identifier.
358
+ * @returns The send result.
359
+ */
360
+ export async function sendAudioLark(params) {
361
+ const { cfg, to, fileKey, replyToMessageId, replyInThread, accountId } = params;
362
+ log.info(`sendAudioLark: target=${to}, fileKey=${fileKey}`);
363
+ const client = LarkClient.fromCfg(cfg, accountId).sdk;
364
+ const content = JSON.stringify({ file_key: fileKey });
365
+ return sendMediaMessage({ client, to, content, msgType: 'audio', replyToMessageId, replyInThread });
366
+ }
367
+ // ---------------------------------------------------------------------------
368
+ // detectFileType
369
+ // ---------------------------------------------------------------------------
370
+ /** Known image extensions. */
371
+ const IMAGE_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.ico', '.tiff', '.tif', '.heic']);
372
+ /** Extension-to-Feishu-file-type mapping. */
373
+ const EXTENSION_TYPE_MAP = {
374
+ '.opus': 'opus',
375
+ '.ogg': 'opus',
376
+ '.mp4': 'mp4',
377
+ '.mov': 'mp4',
378
+ '.avi': 'mp4',
379
+ '.mkv': 'mp4',
380
+ '.webm': 'mp4',
381
+ '.pdf': 'pdf',
382
+ '.doc': 'doc',
383
+ '.docx': 'doc',
384
+ '.xls': 'xls',
385
+ '.xlsx': 'xls',
386
+ '.csv': 'xls',
387
+ '.ppt': 'ppt',
388
+ '.pptx': 'ppt',
389
+ };
390
+ /**
391
+ * Detect the Feishu file type from a file name extension.
392
+ *
393
+ * Returns one of the Feishu-supported file type strings, or "stream"
394
+ * as a catch-all for unrecognised extensions.
395
+ *
396
+ * @param fileName - The file name (with extension).
397
+ * @returns The detected file type.
398
+ */
399
+ export function detectFileType(fileName) {
400
+ const ext = path.extname(fileName).toLowerCase();
401
+ return EXTENSION_TYPE_MAP[ext] ?? 'stream';
402
+ }
403
+ /**
404
+ * Parse the duration (in milliseconds) from an OGG/Opus audio buffer.
405
+ *
406
+ * Scans backward from the end of the buffer to find the last OggS page
407
+ * header, reads the granule position (absolute sample count), and divides
408
+ * by 48 000 (the Opus standard sample rate) then converts to milliseconds.
409
+ *
410
+ * Returns `undefined` when the buffer cannot be parsed (e.g. truncated or
411
+ * not actually OGG). This is intentionally lenient so callers can fall
412
+ * back gracefully.
413
+ */
414
+ export function parseOggOpusDuration(buffer) {
415
+ // OggS magic bytes: 0x4f 0x67 0x67 0x53
416
+ const OGGS = Buffer.from('OggS');
417
+ // Scan backwards for the last OggS sync word.
418
+ let offset = -1;
419
+ for (let i = buffer.length - OGGS.length; i >= 0; i--) {
420
+ if (buffer[i] === 0x4f && buffer.compare(OGGS, 0, 4, i, i + 4) === 0) {
421
+ offset = i;
422
+ break;
423
+ }
424
+ }
425
+ if (offset < 0)
426
+ return undefined;
427
+ // Granule position is at bytes 6..13 of the page header (8 bytes, little-endian).
428
+ const granuleOffset = offset + 6;
429
+ if (granuleOffset + 8 > buffer.length)
430
+ return undefined;
431
+ // Read as two 32-bit LE values and combine (avoids BigInt for portability).
432
+ const lo = buffer.readUInt32LE(granuleOffset);
433
+ const hi = buffer.readUInt32LE(granuleOffset + 4);
434
+ const granule = hi * 0x1_0000_0000 + lo;
435
+ if (granule <= 0)
436
+ return undefined;
437
+ return Math.ceil(granule / 48_000) * 1000;
438
+ }
439
+ /**
440
+ * Parse the duration (in milliseconds) from an MP4 video buffer.
441
+ *
442
+ * Scans top-level boxes to locate the `moov` container, then finds the
443
+ * `mvhd` (Movie Header) box inside it. The `mvhd` box stores:
444
+ * - **timescale**: number of time-units per second
445
+ * - **duration**: total duration in those time-units
446
+ *
447
+ * Supports both version-0 (32-bit fields) and version-1 (64-bit fields)
448
+ * of the `mvhd` box.
449
+ *
450
+ * Returns `undefined` when the buffer cannot be parsed (e.g. truncated,
451
+ * `moov` at end of a huge file not fully buffered, or not actually MP4).
452
+ */
453
+ export function parseMp4Duration(buffer) {
454
+ // Locate `moov` among top-level boxes.
455
+ const moovData = findBox(buffer, 0, buffer.length, 'moov');
456
+ if (!moovData)
457
+ return undefined;
458
+ // Locate `mvhd` inside `moov`.
459
+ const mvhdData = findBox(buffer, moovData.dataStart, moovData.dataEnd, 'mvhd');
460
+ if (!mvhdData)
461
+ return undefined;
462
+ const off = mvhdData.dataStart;
463
+ if (off + 1 > buffer.length)
464
+ return undefined;
465
+ const version = buffer.readUInt8(off);
466
+ let timescale;
467
+ let duration;
468
+ if (version === 0) {
469
+ // version(1) + flags(3) + creation(4) + modification(4) + timescale(4) + duration(4) = 20 bytes
470
+ if (off + 20 > buffer.length)
471
+ return undefined;
472
+ timescale = buffer.readUInt32BE(off + 12);
473
+ duration = buffer.readUInt32BE(off + 16);
474
+ }
475
+ else {
476
+ // version(1) + flags(3) + creation(8) + modification(8) + timescale(4) + duration(8) = 32 bytes
477
+ if (off + 32 > buffer.length)
478
+ return undefined;
479
+ timescale = buffer.readUInt32BE(off + 20);
480
+ // Read 64-bit duration as two 32-bit halves (avoids BigInt).
481
+ const hi = buffer.readUInt32BE(off + 24);
482
+ const lo = buffer.readUInt32BE(off + 28);
483
+ duration = hi * 0x1_0000_0000 + lo;
484
+ }
485
+ if (timescale <= 0 || duration <= 0)
486
+ return undefined;
487
+ return Math.round((duration / timescale) * 1000);
488
+ }
489
+ /**
490
+ * Find a box (atom) by its 4-character type within a range of the buffer.
491
+ * Returns the data start/end offsets (after the 8-byte box header), or
492
+ * `undefined` if not found.
493
+ */
494
+ function findBox(buffer, start, end, type) {
495
+ let offset = start;
496
+ while (offset + 8 <= end) {
497
+ const size = buffer.readUInt32BE(offset);
498
+ const boxType = buffer.toString('ascii', offset + 4, offset + 8);
499
+ // size == 0 means box extends to the end; size == 1 means 64-bit extended size.
500
+ let boxEnd;
501
+ let dataStart;
502
+ if (size === 0) {
503
+ boxEnd = end;
504
+ dataStart = offset + 8;
505
+ }
506
+ else if (size === 1) {
507
+ if (offset + 16 > end)
508
+ break;
509
+ const hi = buffer.readUInt32BE(offset + 8);
510
+ const lo = buffer.readUInt32BE(offset + 12);
511
+ boxEnd = offset + hi * 0x1_0000_0000 + lo;
512
+ dataStart = offset + 16;
513
+ }
514
+ else {
515
+ if (size < 8)
516
+ break; // invalid
517
+ boxEnd = offset + size;
518
+ dataStart = offset + 8;
519
+ }
520
+ if (boxType === type) {
521
+ return { dataStart, dataEnd: Math.min(boxEnd, end) };
522
+ }
523
+ offset = boxEnd;
524
+ }
525
+ return undefined;
526
+ }
527
+ /**
528
+ * Check whether a file name has an image extension.
529
+ */
530
+ function isImageFileName(fileName) {
531
+ const ext = path.extname(fileName).toLowerCase();
532
+ return IMAGE_EXTENSIONS.has(ext);
533
+ }
534
+ // ---------------------------------------------------------------------------
535
+ // uploadAndSendMediaLark
536
+ // ---------------------------------------------------------------------------
537
+ /**
538
+ * Upload and send a media file (image or general file) in one step.
539
+ *
540
+ * Accepts either a URL (remote or local `file://`) or a raw Buffer.
541
+ * The function determines whether the media is an image (by extension)
542
+ * and uses the appropriate upload/send path.
543
+ *
544
+ * @param params.cfg - Plugin configuration.
545
+ * @param params.to - Target identifier.
546
+ * @param params.mediaUrl - URL of the media (http/https or local path).
547
+ * @param params.mediaBuffer - Raw bytes of the media (alternative to URL).
548
+ * @param params.fileName - File name (used for type detection and display).
549
+ * @param params.replyToMessageId - Optional message ID for threaded reply.
550
+ * @param params.accountId - Optional account identifier.
551
+ * @returns The send result.
552
+ */
553
+ export async function uploadAndSendMediaLark(params) {
554
+ const { cfg, to, mediaUrl, mediaBuffer, fileName, replyToMessageId, replyInThread, accountId, mediaLocalRoots } = params;
555
+ log.info(`uploadAndSendMediaLark: target=${to}, ` +
556
+ `source=${mediaBuffer ? 'buffer' : (mediaUrl ?? '(none)')}, fileName=${fileName ?? '(auto)'}`);
557
+ // Resolve the media to a Buffer.
558
+ let buffer;
559
+ let resolvedFileName = fileName ?? 'file';
560
+ if (mediaBuffer) {
561
+ buffer = mediaBuffer;
562
+ log.debug(`using provided buffer: ${buffer.length} bytes`);
563
+ }
564
+ else if (mediaUrl) {
565
+ buffer = await fetchMediaBuffer(mediaUrl, mediaLocalRoots);
566
+ log.debug(`fetched media: ${buffer.length} bytes from "${mediaUrl}"`);
567
+ // Derive a file name from the URL if none was provided.
568
+ if (!fileName) {
569
+ const derivedFileName = resolveFileNameFromMediaUrl(mediaUrl);
570
+ if (derivedFileName) {
571
+ resolvedFileName = derivedFileName;
572
+ }
573
+ }
574
+ }
575
+ else {
576
+ throw new Error('[feishu-media] uploadAndSendMediaLark requires either mediaUrl or mediaBuffer. ' +
577
+ 'Provide a URL (http/https/file://) or a raw Buffer to send media.');
578
+ }
579
+ // Decide whether to send as image or file based on the extension.
580
+ const isImage = isImageFileName(resolvedFileName);
581
+ log.info(`resolved: fileName="${resolvedFileName}", ` + `type=${isImage ? 'image' : 'file'}, size=${buffer.length}`);
582
+ if (isImage) {
583
+ // Upload as image, then send image message.
584
+ const { imageKey } = await uploadImageLark({
585
+ cfg,
586
+ image: buffer,
587
+ imageType: 'message',
588
+ accountId,
589
+ });
590
+ log.debug(`image uploaded: imageKey=${imageKey}`);
591
+ return sendImageLark({
592
+ cfg,
593
+ to,
594
+ imageKey,
595
+ replyToMessageId,
596
+ replyInThread,
597
+ accountId,
598
+ });
599
+ }
600
+ // Upload as file, then send as file or audio message.
601
+ const fileType = detectFileType(resolvedFileName);
602
+ const isAudio = fileType === 'opus';
603
+ const isVideo = fileType === 'mp4';
604
+ const duration = isAudio ? parseOggOpusDuration(buffer) : isVideo ? parseMp4Duration(buffer) : undefined;
605
+ const { fileKey } = await uploadFileLark({
606
+ cfg,
607
+ file: buffer,
608
+ fileName: resolvedFileName,
609
+ fileType,
610
+ duration,
611
+ accountId,
612
+ });
613
+ log.debug(`file uploaded: fileKey=${fileKey}, ` +
614
+ `fileType=${fileType}${isAudio || isVideo ? `, duration=${duration ?? 'unknown'}ms` : ''}`);
615
+ if (isAudio) {
616
+ return sendAudioLark({ cfg, to, fileKey, replyToMessageId, replyInThread, accountId });
617
+ }
618
+ if (isVideo) {
619
+ return sendVideoLark({ cfg, to, fileKey, replyToMessageId, replyInThread, accountId });
620
+ }
621
+ return sendFileLark({
622
+ cfg,
623
+ to,
624
+ fileKey,
625
+ replyToMessageId,
626
+ replyInThread,
627
+ accountId,
628
+ });
629
+ }
630
+ // ---------------------------------------------------------------------------
631
+ // SSRF protection — private/reserved IP filtering
632
+ // ---------------------------------------------------------------------------
633
+ /**
634
+ * Check whether an IP address belongs to a private or reserved range.
635
+ *
636
+ * Blocks: 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16,
637
+ * 169.254.0.0/16 (link-local / cloud metadata), 0.0.0.0,
638
+ * IPv6 loopback (::1), link-local (fe80::), ULA (fc/fd).
639
+ */
640
+ function isPrivateIP(ip) {
641
+ // IPv4 private / reserved ranges
642
+ if (ip.startsWith('127.'))
643
+ return true;
644
+ if (ip.startsWith('10.'))
645
+ return true;
646
+ if (ip.startsWith('192.168.'))
647
+ return true;
648
+ if (ip.startsWith('169.254.'))
649
+ return true;
650
+ if (ip === '0.0.0.0')
651
+ return true;
652
+ if (/^172\.(1[6-9]|2[0-9]|3[01])\./.test(ip))
653
+ return true;
654
+ // IPv6 private / reserved ranges
655
+ if (ip === '::1' || ip === '::')
656
+ return true;
657
+ if (ip.startsWith('fe80:'))
658
+ return true; // link-local
659
+ if (ip.startsWith('fc') || ip.startsWith('fd'))
660
+ return true; // ULA
661
+ return false;
662
+ }
663
+ /**
664
+ * Validate that a remote URL does not target private/reserved IP addresses.
665
+ *
666
+ * Resolves the hostname via DNS and checks all returned addresses.
667
+ * Rejects URLs with non-http(s) protocols.
668
+ */
669
+ async function validateRemoteUrl(raw) {
670
+ const parsed = new URL(raw);
671
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
672
+ throw new Error(`[feishu-media] Unsupported protocol "${parsed.protocol}" in URL "${raw}". ` +
673
+ `Only http:// and https:// are allowed for remote media.`);
674
+ }
675
+ const hostname = parsed.hostname.replace(/^\[|\]$/g, '');
676
+ if (net.isIP(hostname)) {
677
+ // URL contains a literal IP address — check it directly.
678
+ if (isPrivateIP(hostname)) {
679
+ throw new Error(`[feishu-media] Access to private/reserved IP "${hostname}" is denied (SSRF protection). ` +
680
+ `URL: "${raw}"`);
681
+ }
682
+ }
683
+ else {
684
+ // Resolve the domain and check every address it points to.
685
+ try {
686
+ const addresses = await dns.resolve(hostname);
687
+ for (const addr of addresses) {
688
+ if (isPrivateIP(addr)) {
689
+ throw new Error(`[feishu-media] Domain "${hostname}" resolves to private/reserved IP "${addr}" (SSRF protection). ` +
690
+ `URL: "${raw}"`);
691
+ }
692
+ }
693
+ }
694
+ catch (err) {
695
+ if (err instanceof Error && err.message.includes('SSRF protection')) {
696
+ throw err;
697
+ }
698
+ // DNS failure is logged but not blocking — the subsequent fetch will
699
+ // produce a clear network error if the host is truly unreachable.
700
+ log.warn(`[feishu-media] DNS resolution failed for "${hostname}": ${err}`);
701
+ }
702
+ }
703
+ }
704
+ // ---------------------------------------------------------------------------
705
+ // fetchMediaBuffer
706
+ // ---------------------------------------------------------------------------
707
+ /**
708
+ * Fetch media bytes from a URL or local file path.
709
+ *
710
+ * Supports:
711
+ * - `http://` and `https://` URLs (fetched via the global `fetch` API)
712
+ * - `file://` URLs and bare file system paths (read from disk, gated
713
+ * by `localRoots` for path-traversal prevention)
714
+ */
715
+ async function fetchMediaBuffer(urlOrPath, localRoots) {
716
+ const raw = normalizeMediaUrlInput(urlOrPath);
717
+ // Local file path (absolute or relative, or file:// URL).
718
+ if (isLocalMediaPath(raw)) {
719
+ const filePath = raw.startsWith('file://') ? safeFileUrlToPath(raw) : raw;
720
+ if (localRoots !== undefined) {
721
+ // Explicit allowlist configured — enforce path restriction.
722
+ validateLocalMediaRoots(filePath, localRoots);
723
+ }
724
+ else {
725
+ // Deny by default: unconfigured mediaLocalRoots must not allow
726
+ // arbitrary local file reads.
727
+ throw new Error(`[feishu-media] Local file access denied for "${filePath}": ` +
728
+ `mediaLocalRoots is not configured. ` +
729
+ `Configure mediaLocalRoots to explicitly allow local file access.`);
730
+ }
731
+ const buf = fs.readFileSync(filePath);
732
+ log.debug(`local file read: "${filePath}", ${buf.length} bytes`);
733
+ return buf;
734
+ }
735
+ // Remote URL — validate against SSRF before fetching.
736
+ await validateRemoteUrl(raw);
737
+ const FETCH_TIMEOUT_MS = 30_000;
738
+ log.info(`fetching remote media: ${raw}`);
739
+ const response = await fetch(raw, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
740
+ if (!response.ok) {
741
+ throw new Error(`[feishu-media] Failed to fetch media from "${raw}": ` +
742
+ `HTTP ${response.status} ${response.statusText}. ` +
743
+ `Verify the URL is accessible and returns a valid media resource.`);
744
+ }
745
+ const arrayBuffer = await response.arrayBuffer();
746
+ log.debug(`remote media fetched: ${raw}, ${arrayBuffer.byteLength} bytes`);
747
+ return Buffer.from(arrayBuffer);
748
+ }
749
+ //# sourceMappingURL=media.js.map