opencode-bridge 2.9.0-beta

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 (237) hide show
  1. package/.env.example +131 -0
  2. package/LICENSE +674 -0
  3. package/README.md +1195 -0
  4. package/bin/opencode-bridge.js +31 -0
  5. package/dist/commands/effort.d.ts +9 -0
  6. package/dist/commands/effort.d.ts.map +1 -0
  7. package/dist/commands/effort.js +56 -0
  8. package/dist/commands/effort.js.map +1 -0
  9. package/dist/commands/parser.d.ts +37 -0
  10. package/dist/commands/parser.d.ts.map +1 -0
  11. package/dist/commands/parser.js +355 -0
  12. package/dist/commands/parser.js.map +1 -0
  13. package/dist/config.d.ts +91 -0
  14. package/dist/config.d.ts.map +1 -0
  15. package/dist/config.js +340 -0
  16. package/dist/config.js.map +1 -0
  17. package/dist/feishu/cards-stream.d.ts +65 -0
  18. package/dist/feishu/cards-stream.d.ts.map +1 -0
  19. package/dist/feishu/cards-stream.js +448 -0
  20. package/dist/feishu/cards-stream.js.map +1 -0
  21. package/dist/feishu/cards.d.ts +81 -0
  22. package/dist/feishu/cards.d.ts.map +1 -0
  23. package/dist/feishu/cards.js +560 -0
  24. package/dist/feishu/cards.js.map +1 -0
  25. package/dist/feishu/client.d.ts +132 -0
  26. package/dist/feishu/client.d.ts.map +1 -0
  27. package/dist/feishu/client.js +952 -0
  28. package/dist/feishu/client.js.map +1 -0
  29. package/dist/feishu/streamer.d.ts +30 -0
  30. package/dist/feishu/streamer.d.ts.map +1 -0
  31. package/dist/feishu/streamer.js +95 -0
  32. package/dist/feishu/streamer.js.map +1 -0
  33. package/dist/handlers/card-action.d.ts +12 -0
  34. package/dist/handlers/card-action.d.ts.map +1 -0
  35. package/dist/handlers/card-action.js +154 -0
  36. package/dist/handlers/card-action.js.map +1 -0
  37. package/dist/handlers/command.d.ts +76 -0
  38. package/dist/handlers/command.d.ts.map +1 -0
  39. package/dist/handlers/command.js +1773 -0
  40. package/dist/handlers/command.js.map +1 -0
  41. package/dist/handlers/discord.d.ts +78 -0
  42. package/dist/handlers/discord.d.ts.map +1 -0
  43. package/dist/handlers/discord.js +1832 -0
  44. package/dist/handlers/discord.js.map +1 -0
  45. package/dist/handlers/file-sender.d.ts +22 -0
  46. package/dist/handlers/file-sender.d.ts.map +1 -0
  47. package/dist/handlers/file-sender.js +183 -0
  48. package/dist/handlers/file-sender.js.map +1 -0
  49. package/dist/handlers/group.d.ts +21 -0
  50. package/dist/handlers/group.d.ts.map +1 -0
  51. package/dist/handlers/group.js +414 -0
  52. package/dist/handlers/group.js.map +1 -0
  53. package/dist/handlers/lifecycle.d.ts +17 -0
  54. package/dist/handlers/lifecycle.d.ts.map +1 -0
  55. package/dist/handlers/lifecycle.js +129 -0
  56. package/dist/handlers/lifecycle.js.map +1 -0
  57. package/dist/handlers/p2p.d.ts +44 -0
  58. package/dist/handlers/p2p.d.ts.map +1 -0
  59. package/dist/handlers/p2p.js +625 -0
  60. package/dist/handlers/p2p.js.map +1 -0
  61. package/dist/index.d.ts +33 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +1562 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/opencode/client.d.ts +176 -0
  66. package/dist/opencode/client.d.ts.map +1 -0
  67. package/dist/opencode/client.js +1126 -0
  68. package/dist/opencode/client.js.map +1 -0
  69. package/dist/opencode/delayed-handler.d.ts +33 -0
  70. package/dist/opencode/delayed-handler.d.ts.map +1 -0
  71. package/dist/opencode/delayed-handler.js +74 -0
  72. package/dist/opencode/delayed-handler.js.map +1 -0
  73. package/dist/opencode/output-buffer.d.ts +56 -0
  74. package/dist/opencode/output-buffer.d.ts.map +1 -0
  75. package/dist/opencode/output-buffer.js +202 -0
  76. package/dist/opencode/output-buffer.js.map +1 -0
  77. package/dist/opencode/question-handler.d.ts +61 -0
  78. package/dist/opencode/question-handler.d.ts.map +1 -0
  79. package/dist/opencode/question-handler.js +183 -0
  80. package/dist/opencode/question-handler.js.map +1 -0
  81. package/dist/opencode/question-parser.d.ts +9 -0
  82. package/dist/opencode/question-parser.d.ts.map +1 -0
  83. package/dist/opencode/question-parser.js +69 -0
  84. package/dist/opencode/question-parser.js.map +1 -0
  85. package/dist/opencode/session-queue.d.ts +16 -0
  86. package/dist/opencode/session-queue.d.ts.map +1 -0
  87. package/dist/opencode/session-queue.js +41 -0
  88. package/dist/opencode/session-queue.js.map +1 -0
  89. package/dist/permissions/handler.d.ts +36 -0
  90. package/dist/permissions/handler.d.ts.map +1 -0
  91. package/dist/permissions/handler.js +141 -0
  92. package/dist/permissions/handler.js.map +1 -0
  93. package/dist/platform/adapters/discord-adapter.d.ts +45 -0
  94. package/dist/platform/adapters/discord-adapter.d.ts.map +1 -0
  95. package/dist/platform/adapters/discord-adapter.js +497 -0
  96. package/dist/platform/adapters/discord-adapter.js.map +1 -0
  97. package/dist/platform/adapters/feishu-adapter.d.ts +29 -0
  98. package/dist/platform/adapters/feishu-adapter.d.ts.map +1 -0
  99. package/dist/platform/adapters/feishu-adapter.js +150 -0
  100. package/dist/platform/adapters/feishu-adapter.js.map +1 -0
  101. package/dist/platform/registry.d.ts +41 -0
  102. package/dist/platform/registry.d.ts.map +1 -0
  103. package/dist/platform/registry.js +87 -0
  104. package/dist/platform/registry.js.map +1 -0
  105. package/dist/platform/types.d.ts +92 -0
  106. package/dist/platform/types.d.ts.map +1 -0
  107. package/dist/platform/types.js +4 -0
  108. package/dist/platform/types.js.map +1 -0
  109. package/dist/reliability/audit-log.d.ts +93 -0
  110. package/dist/reliability/audit-log.d.ts.map +1 -0
  111. package/dist/reliability/audit-log.js +248 -0
  112. package/dist/reliability/audit-log.js.map +1 -0
  113. package/dist/reliability/config-guard.d.ts +42 -0
  114. package/dist/reliability/config-guard.d.ts.map +1 -0
  115. package/dist/reliability/config-guard.js +264 -0
  116. package/dist/reliability/config-guard.js.map +1 -0
  117. package/dist/reliability/conversation-heartbeat.d.ts +37 -0
  118. package/dist/reliability/conversation-heartbeat.d.ts.map +1 -0
  119. package/dist/reliability/conversation-heartbeat.js +179 -0
  120. package/dist/reliability/conversation-heartbeat.js.map +1 -0
  121. package/dist/reliability/cron-api-server.d.ts +13 -0
  122. package/dist/reliability/cron-api-server.d.ts.map +1 -0
  123. package/dist/reliability/cron-api-server.js +247 -0
  124. package/dist/reliability/cron-api-server.js.map +1 -0
  125. package/dist/reliability/cron-control.d.ts +34 -0
  126. package/dist/reliability/cron-control.d.ts.map +1 -0
  127. package/dist/reliability/cron-control.js +864 -0
  128. package/dist/reliability/cron-control.js.map +1 -0
  129. package/dist/reliability/cron-semantic.d.ts +9 -0
  130. package/dist/reliability/cron-semantic.d.ts.map +1 -0
  131. package/dist/reliability/cron-semantic.js +208 -0
  132. package/dist/reliability/cron-semantic.js.map +1 -0
  133. package/dist/reliability/environment-doctor.d.ts +56 -0
  134. package/dist/reliability/environment-doctor.d.ts.map +1 -0
  135. package/dist/reliability/environment-doctor.js +213 -0
  136. package/dist/reliability/environment-doctor.js.map +1 -0
  137. package/dist/reliability/job-registry.d.ts +26 -0
  138. package/dist/reliability/job-registry.d.ts.map +1 -0
  139. package/dist/reliability/job-registry.js +77 -0
  140. package/dist/reliability/job-registry.js.map +1 -0
  141. package/dist/reliability/opencode-probe.d.ts +37 -0
  142. package/dist/reliability/opencode-probe.d.ts.map +1 -0
  143. package/dist/reliability/opencode-probe.js +195 -0
  144. package/dist/reliability/opencode-probe.js.map +1 -0
  145. package/dist/reliability/opencode-restart.d.ts +42 -0
  146. package/dist/reliability/opencode-restart.d.ts.map +1 -0
  147. package/dist/reliability/opencode-restart.js +155 -0
  148. package/dist/reliability/opencode-restart.js.map +1 -0
  149. package/dist/reliability/proactive-heartbeat.d.ts +39 -0
  150. package/dist/reliability/proactive-heartbeat.d.ts.map +1 -0
  151. package/dist/reliability/proactive-heartbeat.js +147 -0
  152. package/dist/reliability/proactive-heartbeat.js.map +1 -0
  153. package/dist/reliability/process-check-job.d.ts +73 -0
  154. package/dist/reliability/process-check-job.d.ts.map +1 -0
  155. package/dist/reliability/process-check-job.js +254 -0
  156. package/dist/reliability/process-check-job.js.map +1 -0
  157. package/dist/reliability/process-guard.d.ts +53 -0
  158. package/dist/reliability/process-guard.d.ts.map +1 -0
  159. package/dist/reliability/process-guard.js +344 -0
  160. package/dist/reliability/process-guard.js.map +1 -0
  161. package/dist/reliability/recovery-reporter.d.ts +37 -0
  162. package/dist/reliability/recovery-reporter.d.ts.map +1 -0
  163. package/dist/reliability/recovery-reporter.js +161 -0
  164. package/dist/reliability/recovery-reporter.js.map +1 -0
  165. package/dist/reliability/rescue-executor.d.ts +52 -0
  166. package/dist/reliability/rescue-executor.d.ts.map +1 -0
  167. package/dist/reliability/rescue-executor.js +244 -0
  168. package/dist/reliability/rescue-executor.js.map +1 -0
  169. package/dist/reliability/rescue-policy.d.ts +39 -0
  170. package/dist/reliability/rescue-policy.d.ts.map +1 -0
  171. package/dist/reliability/rescue-policy.js +85 -0
  172. package/dist/reliability/rescue-policy.js.map +1 -0
  173. package/dist/reliability/runtime-cron-dispatcher.d.ts +30 -0
  174. package/dist/reliability/runtime-cron-dispatcher.d.ts.map +1 -0
  175. package/dist/reliability/runtime-cron-dispatcher.js +100 -0
  176. package/dist/reliability/runtime-cron-dispatcher.js.map +1 -0
  177. package/dist/reliability/runtime-cron-orphan.d.ts +18 -0
  178. package/dist/reliability/runtime-cron-orphan.d.ts.map +1 -0
  179. package/dist/reliability/runtime-cron-orphan.js +87 -0
  180. package/dist/reliability/runtime-cron-orphan.js.map +1 -0
  181. package/dist/reliability/runtime-cron-registry.d.ts +4 -0
  182. package/dist/reliability/runtime-cron-registry.d.ts.map +1 -0
  183. package/dist/reliability/runtime-cron-registry.js +8 -0
  184. package/dist/reliability/runtime-cron-registry.js.map +1 -0
  185. package/dist/reliability/runtime-cron.d.ts +75 -0
  186. package/dist/reliability/runtime-cron.d.ts.map +1 -0
  187. package/dist/reliability/runtime-cron.js +309 -0
  188. package/dist/reliability/runtime-cron.js.map +1 -0
  189. package/dist/reliability/scheduler.d.ts +38 -0
  190. package/dist/reliability/scheduler.d.ts.map +1 -0
  191. package/dist/reliability/scheduler.js +174 -0
  192. package/dist/reliability/scheduler.js.map +1 -0
  193. package/dist/reliability/types.d.ts +151 -0
  194. package/dist/reliability/types.d.ts.map +1 -0
  195. package/dist/reliability/types.js +178 -0
  196. package/dist/reliability/types.js.map +1 -0
  197. package/dist/router/action-handlers.d.ts +27 -0
  198. package/dist/router/action-handlers.d.ts.map +1 -0
  199. package/dist/router/action-handlers.js +226 -0
  200. package/dist/router/action-handlers.js.map +1 -0
  201. package/dist/router/opencode-event-hub.d.ts +159 -0
  202. package/dist/router/opencode-event-hub.d.ts.map +1 -0
  203. package/dist/router/opencode-event-hub.js +589 -0
  204. package/dist/router/opencode-event-hub.js.map +1 -0
  205. package/dist/router/root-router.d.ts +94 -0
  206. package/dist/router/root-router.d.ts.map +1 -0
  207. package/dist/router/root-router.js +214 -0
  208. package/dist/router/root-router.js.map +1 -0
  209. package/dist/store/chat-session.d.ts +150 -0
  210. package/dist/store/chat-session.d.ts.map +1 -0
  211. package/dist/store/chat-session.js +640 -0
  212. package/dist/store/chat-session.js.map +1 -0
  213. package/dist/store/session-directory.d.ts +12 -0
  214. package/dist/store/session-directory.d.ts.map +1 -0
  215. package/dist/store/session-directory.js +47 -0
  216. package/dist/store/session-directory.js.map +1 -0
  217. package/dist/store/session-group.d.ts +19 -0
  218. package/dist/store/session-group.d.ts.map +1 -0
  219. package/dist/store/session-group.js +92 -0
  220. package/dist/store/session-group.js.map +1 -0
  221. package/dist/store/user-session.d.ts +19 -0
  222. package/dist/store/user-session.d.ts.map +1 -0
  223. package/dist/store/user-session.js +112 -0
  224. package/dist/store/user-session.js.map +1 -0
  225. package/dist/utils/async-queue.d.ts +12 -0
  226. package/dist/utils/async-queue.d.ts.map +1 -0
  227. package/dist/utils/async-queue.js +51 -0
  228. package/dist/utils/async-queue.js.map +1 -0
  229. package/dist/utils/directory-policy.d.ts +50 -0
  230. package/dist/utils/directory-policy.d.ts.map +1 -0
  231. package/dist/utils/directory-policy.js +379 -0
  232. package/dist/utils/directory-policy.js.map +1 -0
  233. package/dist/utils/session-title.d.ts +2 -0
  234. package/dist/utils/session-title.d.ts.map +1 -0
  235. package/dist/utils/session-title.js +10 -0
  236. package/dist/utils/session-title.js.map +1 -0
  237. package/package.json +73 -0
@@ -0,0 +1,952 @@
1
+ import * as lark from '@larksuiteoapi/node-sdk';
2
+ import { feishuConfig } from '../config.js';
3
+ import { EventEmitter } from 'events';
4
+ function formatError(error) {
5
+ if (error instanceof Error) {
6
+ const responseData = typeof error === 'object' && error !== null && 'response' in error
7
+ ? error.response?.data
8
+ : undefined;
9
+ return { message: `${error.name}: ${error.message}`, responseData };
10
+ }
11
+ const responseData = typeof error === 'object' && error !== null && 'response' in error
12
+ ? error.response?.data
13
+ : undefined;
14
+ let message = '';
15
+ try {
16
+ message = JSON.stringify(error);
17
+ }
18
+ catch {
19
+ message = String(error);
20
+ }
21
+ return { message, responseData };
22
+ }
23
+ function extractApiCode(responseData) {
24
+ if (!responseData || typeof responseData !== 'object')
25
+ return undefined;
26
+ const value = responseData.code;
27
+ if (typeof value === 'number')
28
+ return value;
29
+ if (typeof value === 'string') {
30
+ const parsed = Number(value);
31
+ return Number.isNaN(parsed) ? undefined : parsed;
32
+ }
33
+ return undefined;
34
+ }
35
+ function stringifyErrorPayload(payload) {
36
+ if (typeof payload === 'string') {
37
+ return payload;
38
+ }
39
+ try {
40
+ return JSON.stringify(payload);
41
+ }
42
+ catch {
43
+ return String(payload);
44
+ }
45
+ }
46
+ function isUniversalCardBuildFailure(responseData) {
47
+ const apiCode = extractApiCode(responseData);
48
+ if (apiCode === 230099) {
49
+ return true;
50
+ }
51
+ const text = stringifyErrorPayload(responseData).toLowerCase();
52
+ return text.includes('230099')
53
+ || text.includes('200800')
54
+ || text.includes('create universal card fail');
55
+ }
56
+ function buildFallbackInteractiveCard(sourceCard) {
57
+ const cardRecord = sourceCard;
58
+ const rawTitle = cardRecord.header?.title?.content;
59
+ const title = typeof rawTitle === 'string' && rawTitle.trim()
60
+ ? rawTitle.trim().slice(0, 60)
61
+ : 'OpenCode 输出(已精简)';
62
+ const rawTemplate = cardRecord.header?.template;
63
+ const template = typeof rawTemplate === 'string' && rawTemplate.trim()
64
+ ? rawTemplate.trim()
65
+ : 'blue';
66
+ return {
67
+ schema: '2.0',
68
+ config: {
69
+ wide_screen_mode: true,
70
+ },
71
+ header: {
72
+ title: {
73
+ tag: 'plain_text',
74
+ content: title,
75
+ },
76
+ template,
77
+ },
78
+ body: {
79
+ elements: [
80
+ {
81
+ tag: 'markdown',
82
+ content: '⚠️ 卡片内容过长或结构超限,已自动精简显示。\n请在 OpenCode Web 查看完整输出。',
83
+ },
84
+ ],
85
+ },
86
+ };
87
+ }
88
+ function getString(value) {
89
+ return typeof value === 'string' ? value : undefined;
90
+ }
91
+ function getNumber(value) {
92
+ if (typeof value === 'number')
93
+ return value;
94
+ if (typeof value === 'string') {
95
+ const parsed = Number(value);
96
+ return Number.isNaN(parsed) ? undefined : parsed;
97
+ }
98
+ return undefined;
99
+ }
100
+ function collectAttachmentsFromContent(content) {
101
+ if (!content || typeof content !== 'object')
102
+ return [];
103
+ const attachments = [];
104
+ const visited = new Set();
105
+ const stack = [content];
106
+ while (stack.length > 0) {
107
+ const current = stack.pop();
108
+ if (!current || typeof current !== 'object')
109
+ continue;
110
+ if (visited.has(current))
111
+ continue;
112
+ visited.add(current);
113
+ if (Array.isArray(current)) {
114
+ for (const item of current)
115
+ stack.push(item);
116
+ continue;
117
+ }
118
+ const record = current;
119
+ const imageKey = getString(record.image_key) || getString(record.imageKey);
120
+ if (imageKey) {
121
+ attachments.push({ type: 'image', fileKey: imageKey });
122
+ }
123
+ const fileKey = getString(record.file_key) || getString(record.fileKey);
124
+ if (fileKey) {
125
+ attachments.push({
126
+ type: 'file',
127
+ fileKey,
128
+ fileName: getString(record.file_name) || getString(record.fileName),
129
+ fileType: getString(record.file_type) || getString(record.fileType),
130
+ fileSize: getNumber(record.file_size) || getNumber(record.fileSize),
131
+ });
132
+ }
133
+ for (const value of Object.values(record)) {
134
+ stack.push(value);
135
+ }
136
+ }
137
+ return attachments;
138
+ }
139
+ function extractTextFromPost(content) {
140
+ if (!content || typeof content !== 'object')
141
+ return '';
142
+ const record = content;
143
+ const parts = [];
144
+ const root = record.content;
145
+ if (!root)
146
+ return '';
147
+ const stack = [root];
148
+ const visited = new Set();
149
+ while (stack.length > 0) {
150
+ const current = stack.pop();
151
+ if (!current || typeof current !== 'object')
152
+ continue;
153
+ if (visited.has(current))
154
+ continue;
155
+ visited.add(current);
156
+ if (Array.isArray(current)) {
157
+ for (const item of current)
158
+ stack.push(item);
159
+ continue;
160
+ }
161
+ const node = current;
162
+ const tag = getString(node.tag);
163
+ if ((tag === 'text' || tag === 'a') && typeof node.text === 'string') {
164
+ parts.push(node.text);
165
+ }
166
+ for (const value of Object.values(node)) {
167
+ stack.push(value);
168
+ }
169
+ }
170
+ return parts.join('');
171
+ }
172
+ class FeishuClient extends EventEmitter {
173
+ client;
174
+ wsClient = null;
175
+ eventDispatcher;
176
+ cardActionHandler;
177
+ cardUpdateQueue = new Map();
178
+ constructor() {
179
+ super();
180
+ this.client = new lark.Client({
181
+ appId: feishuConfig.appId,
182
+ appSecret: feishuConfig.appSecret,
183
+ disableTokenCache: false,
184
+ });
185
+ // 创建事件分发器
186
+ this.eventDispatcher = new lark.EventDispatcher({
187
+ encryptKey: feishuConfig.encryptKey,
188
+ verificationToken: feishuConfig.verificationToken,
189
+ });
190
+ }
191
+ // 启动长连接
192
+ async start() {
193
+ console.log('[飞书] 正在启动长连接...');
194
+ // 注册消息接收事件
195
+ this.eventDispatcher.register({
196
+ 'im.message.receive_v1': (data) => {
197
+ this.handleMessage(data);
198
+ return { msg: 'ok' };
199
+ },
200
+ // 注册消息已读事件(消除警告)
201
+ 'im.message.message_read_v1': (data) => {
202
+ return { msg: 'ok' };
203
+ },
204
+ });
205
+ // 注册卡片回调事件
206
+ this.eventDispatcher.register({
207
+ 'card.action.trigger': async (data) => {
208
+ return await this.handleCardAction(data);
209
+ },
210
+ });
211
+ // 监听消息撤回事件
212
+ // 本地不再重复注册撤回事件,避免与 onMessageRecalled 冲突
213
+ this.wsClient = new lark.WSClient({
214
+ appId: feishuConfig.appId,
215
+ appSecret: feishuConfig.appSecret,
216
+ });
217
+ // 启动连接
218
+ await this.wsClient.start({ eventDispatcher: this.eventDispatcher });
219
+ console.log('[飞书] 长连接已建立');
220
+ }
221
+ // 监听群成员退群事件
222
+ onMemberLeft(callback) {
223
+ // @ts-ignore: using loose types for dynamic registration
224
+ this.eventDispatcher.register({
225
+ 'im.chat.member.user.deleted_v1': (data) => {
226
+ const chatId = data.chat_id;
227
+ const users = data.users || [];
228
+ for (const user of users) {
229
+ const openId = user.user_id?.open_id;
230
+ if (openId)
231
+ callback(chatId, openId);
232
+ }
233
+ return { msg: 'ok' };
234
+ }
235
+ });
236
+ }
237
+ // 监听群解散事件
238
+ onChatDisbanded(callback) {
239
+ // @ts-ignore
240
+ this.eventDispatcher.register({
241
+ 'im.chat.disbanded_v1': (data) => {
242
+ if (data.chat_id)
243
+ callback(data.chat_id);
244
+ return { msg: 'ok' };
245
+ }
246
+ });
247
+ }
248
+ // 监听消息撤回事件
249
+ onMessageRecalled(callback) {
250
+ // @ts-ignore
251
+ this.eventDispatcher.register({
252
+ 'im.message.recalled_v1': (data) => {
253
+ callback(data);
254
+ return { msg: 'ok' };
255
+ }
256
+ });
257
+ }
258
+ // 处理接收到的消息
259
+ handleMessage(data) {
260
+ try {
261
+ const message = data.message;
262
+ const sender = data.sender;
263
+ // 忽略机器人自己发的消息
264
+ if (sender.sender_type === 'bot') {
265
+ return;
266
+ }
267
+ const msgType = message.message_type;
268
+ let content = '';
269
+ let parsedContent = null;
270
+ try {
271
+ parsedContent = JSON.parse(message.content);
272
+ if (parsedContent && typeof parsedContent.text === 'string') {
273
+ content = parsedContent.text;
274
+ }
275
+ }
276
+ catch {
277
+ content = message.content;
278
+ }
279
+ if (!content && parsedContent && msgType === 'post') {
280
+ const postText = extractTextFromPost(parsedContent);
281
+ if (postText)
282
+ content = postText;
283
+ }
284
+ const attachments = [];
285
+ const attachmentMap = new Map();
286
+ const addAttachment = (item) => {
287
+ const key = `${item.type}:${item.fileKey}`;
288
+ const existing = attachmentMap.get(key);
289
+ if (!existing) {
290
+ attachmentMap.set(key, item);
291
+ return;
292
+ }
293
+ attachmentMap.set(key, {
294
+ type: existing.type,
295
+ fileKey: existing.fileKey,
296
+ fileName: existing.fileName || item.fileName,
297
+ fileType: existing.fileType || item.fileType,
298
+ fileSize: existing.fileSize ?? item.fileSize,
299
+ });
300
+ };
301
+ if (parsedContent && msgType === 'image') {
302
+ const imageKey = getString(parsedContent.image_key) || getString(parsedContent.imageKey);
303
+ if (imageKey) {
304
+ addAttachment({ type: 'image', fileKey: imageKey });
305
+ }
306
+ }
307
+ if (parsedContent && msgType === 'file') {
308
+ const fileKey = getString(parsedContent.file_key) || getString(parsedContent.fileKey);
309
+ if (fileKey) {
310
+ addAttachment({
311
+ type: 'file',
312
+ fileKey,
313
+ fileName: getString(parsedContent.file_name) || getString(parsedContent.fileName),
314
+ fileType: getString(parsedContent.file_type) || getString(parsedContent.fileType),
315
+ fileSize: getNumber(parsedContent.file_size) || getNumber(parsedContent.fileSize),
316
+ });
317
+ }
318
+ }
319
+ if (parsedContent) {
320
+ const collected = collectAttachmentsFromContent(parsedContent);
321
+ for (const item of collected) {
322
+ addAttachment(item);
323
+ }
324
+ }
325
+ attachments.push(...attachmentMap.values());
326
+ // 移除@机器人的部分
327
+ if (message.mentions) {
328
+ for (const mention of message.mentions) {
329
+ content = content.replace(mention.key, '').trim();
330
+ }
331
+ }
332
+ const messageEvent = {
333
+ messageId: message.message_id,
334
+ chatId: message.chat_id,
335
+ threadId: message.thread_id,
336
+ chatType: message.chat_type,
337
+ senderId: sender.sender_id?.open_id || '',
338
+ senderType: sender.sender_type,
339
+ content: content.trim(),
340
+ msgType,
341
+ attachments: attachments.length > 0 ? attachments : undefined,
342
+ mentions: message.mentions?.map(m => ({
343
+ key: m.key,
344
+ id: { open_id: m.id.open_id || '' },
345
+ name: m.name,
346
+ })),
347
+ rawEvent: data,
348
+ };
349
+ this.emit('message', messageEvent);
350
+ }
351
+ catch (error) {
352
+ console.error('[飞书] 解析消息失败:', error);
353
+ }
354
+ }
355
+ // 设置卡片动作处理器(支持直接返回新卡片)
356
+ setCardActionHandler(handler) {
357
+ this.cardActionHandler = handler;
358
+ }
359
+ // 处理卡片按钮点击(通过 CardActionHandler 处理,需要单独设置)
360
+ async handleCardAction(data) {
361
+ try {
362
+ const event = data;
363
+ const messageId = event.open_message_id ||
364
+ event.message_id ||
365
+ event.context?.open_message_id ||
366
+ event.context?.message_id;
367
+ const chatId = event.open_chat_id ||
368
+ event.chat_id ||
369
+ event.context?.open_chat_id ||
370
+ event.context?.chat_id;
371
+ const threadId = event.open_thread_id ||
372
+ event.thread_id ||
373
+ event.context?.open_thread_id ||
374
+ event.context?.thread_id;
375
+ const cardEvent = {
376
+ openId: event.operator.open_id,
377
+ action: event.action,
378
+ token: event.token,
379
+ messageId,
380
+ chatId,
381
+ threadId,
382
+ rawEvent: data,
383
+ };
384
+ if (this.cardActionHandler) {
385
+ const response = await this.cardActionHandler(cardEvent);
386
+ if (response !== undefined) {
387
+ return response;
388
+ }
389
+ return { msg: 'ok' };
390
+ }
391
+ this.emit('cardAction', cardEvent);
392
+ return { msg: 'ok' };
393
+ }
394
+ catch (error) {
395
+ console.error('[飞书] 解析卡片事件失败:', error);
396
+ return { msg: 'ok' };
397
+ }
398
+ }
399
+ // 下载消息中的资源文件
400
+ async downloadMessageResource(messageId, fileKey, type) {
401
+ try {
402
+ const response = await this.client.im.messageResource.get({
403
+ path: { message_id: messageId, file_key: fileKey },
404
+ params: { type },
405
+ });
406
+ return {
407
+ writeFile: response.writeFile,
408
+ headers: response.headers,
409
+ };
410
+ }
411
+ catch (error) {
412
+ const formatted = formatError(error);
413
+ console.error('[飞书] 下载消息资源失败:', formatted.message, formatted.responseData ?? '');
414
+ return null;
415
+ }
416
+ }
417
+ // 发送文本消息
418
+ async sendText(chatId, text) {
419
+ try {
420
+ const response = await this.client.im.message.create({
421
+ params: { receive_id_type: 'chat_id' },
422
+ data: {
423
+ receive_id: chatId,
424
+ msg_type: 'text',
425
+ content: JSON.stringify({ text }),
426
+ },
427
+ });
428
+ const msgId = response.data?.message_id || null;
429
+ if (msgId) {
430
+ console.log(`[飞书] 发送文字成功: msgId=${msgId.slice(0, 16)}...`);
431
+ }
432
+ else {
433
+ console.log('[飞书] 发送文字返回空消息ID');
434
+ }
435
+ return msgId;
436
+ }
437
+ catch (error) {
438
+ const formatted = formatError(error);
439
+ const errCode = typeof error === 'object' && error !== null && 'code' in error ? error.code : undefined;
440
+ const apiCode = extractApiCode(formatted.responseData);
441
+ if (apiCode === 230002) {
442
+ console.warn(`[飞书] 群不可用,发送文字失败: chatId=${chatId}`);
443
+ this.emit('chatUnavailable', chatId);
444
+ return null;
445
+ }
446
+ console.error(`[飞书] 发送文字失败: code=${errCode}, ${formatted.message}`);
447
+ return null;
448
+ }
449
+ }
450
+ // 回复消息
451
+ async reply(messageId, text) {
452
+ try {
453
+ const response = await this.client.im.message.reply({
454
+ path: { message_id: messageId },
455
+ data: {
456
+ msg_type: 'text',
457
+ content: JSON.stringify({ text }),
458
+ },
459
+ });
460
+ const msgId = response.data?.message_id || null;
461
+ if (msgId) {
462
+ console.log(`[飞书] 回复成功: msgId=${msgId.slice(0, 16)}...`);
463
+ }
464
+ else {
465
+ console.log('[飞书] 回复返回空消息ID');
466
+ }
467
+ return msgId;
468
+ }
469
+ catch (error) {
470
+ const formatted = formatError(error);
471
+ const errCode = typeof error === 'object' && error !== null && 'code' in error ? error.code : undefined;
472
+ console.error(`[飞书] 回复失败: code=${errCode}, ${formatted.message}`);
473
+ return null;
474
+ }
475
+ }
476
+ // 回复卡片
477
+ async replyCard(messageId, card) {
478
+ try {
479
+ const response = await this.client.im.message.reply({
480
+ path: { message_id: messageId },
481
+ data: {
482
+ msg_type: 'interactive',
483
+ content: JSON.stringify(card),
484
+ },
485
+ });
486
+ const msgId = response.data?.message_id || null;
487
+ if (msgId) {
488
+ console.log(`[飞书] 回复卡片成功: msgId=${msgId.slice(0, 16)}...`);
489
+ }
490
+ else {
491
+ console.log('[飞书] 回复卡片返回空消息ID');
492
+ }
493
+ return msgId;
494
+ }
495
+ catch (error) {
496
+ const formatted = formatError(error);
497
+ const errCode = typeof error === 'object' && error !== null && 'code' in error ? error.code : undefined;
498
+ console.error(`[飞书] 回复卡片失败: code=${errCode}, ${formatted.message}`);
499
+ return null;
500
+ }
501
+ }
502
+ // 更新卡片
503
+ async updateCard(messageId, card) {
504
+ const prev = this.cardUpdateQueue.get(messageId) || Promise.resolve(true);
505
+ const next = prev
506
+ .catch(() => true)
507
+ .then(async () => {
508
+ return await this.doUpdateCard(messageId, card);
509
+ })
510
+ .finally(() => {
511
+ if (this.cardUpdateQueue.get(messageId) === next) {
512
+ this.cardUpdateQueue.delete(messageId);
513
+ }
514
+ });
515
+ this.cardUpdateQueue.set(messageId, next);
516
+ return await next;
517
+ }
518
+ async doUpdateCard(messageId, card) {
519
+ try {
520
+ const data = {
521
+ msg_type: 'interactive',
522
+ content: JSON.stringify(card),
523
+ };
524
+ await this.client.im.message.patch({
525
+ path: { message_id: messageId },
526
+ data,
527
+ });
528
+ console.log(`[飞书] 更新卡片成功: msgId=${messageId.slice(0, 16)}...`);
529
+ return true;
530
+ }
531
+ catch (error) {
532
+ const formatted = formatError(error);
533
+ const errCode = typeof error === 'object' && error !== null && 'code' in error ? error.code : undefined;
534
+ const errMsg = typeof error === 'object' && error !== null && 'msg' in error ? error.msg : undefined;
535
+ console.error(`[飞书] 更新卡片失败: code=${errCode}, msg=${errMsg}, msgId=${messageId}`);
536
+ console.error(`[飞书] 更新卡片错误详情: ${formatted.message}`);
537
+ if (formatted.responseData) {
538
+ try {
539
+ console.error(`[飞书] 响应数据: ${JSON.stringify(formatted.responseData).slice(0, 500)}`);
540
+ }
541
+ catch {
542
+ // ignore
543
+ }
544
+ }
545
+ if (isUniversalCardBuildFailure(formatted.responseData)) {
546
+ console.warn(`[飞书] 更新卡片触发 230099/200800,尝试发送精简卡片: msgId=${messageId}`);
547
+ try {
548
+ const fallbackData = {
549
+ msg_type: 'interactive',
550
+ content: JSON.stringify(buildFallbackInteractiveCard(card)),
551
+ };
552
+ await this.client.im.message.patch({
553
+ path: { message_id: messageId },
554
+ data: fallbackData,
555
+ });
556
+ console.log(`[飞书] 精简卡片更新成功: msgId=${messageId.slice(0, 16)}...`);
557
+ return true;
558
+ }
559
+ catch (fallbackError) {
560
+ const fallbackFormatted = formatError(fallbackError);
561
+ console.error(`[飞书] 精简卡片更新失败: ${fallbackFormatted.message}`);
562
+ }
563
+ }
564
+ return false;
565
+ }
566
+ }
567
+ // 更新消息(用于定时刷新输出)
568
+ async updateMessage(messageId, text) {
569
+ try {
570
+ await this.client.im.message.patch({
571
+ path: { message_id: messageId },
572
+ data: {
573
+ content: JSON.stringify({ text }),
574
+ },
575
+ });
576
+ return true;
577
+ }
578
+ catch (error) {
579
+ const formatted = formatError(error);
580
+ console.error('[飞书] 更新消息失败:', formatted.message, formatted.responseData ?? '');
581
+ return false;
582
+ }
583
+ }
584
+ // 发送消息卡片
585
+ async sendCard(chatId, card) {
586
+ try {
587
+ const response = await this.client.im.message.create({
588
+ params: { receive_id_type: 'chat_id' },
589
+ data: {
590
+ receive_id: chatId,
591
+ msg_type: 'interactive',
592
+ content: JSON.stringify(card),
593
+ },
594
+ });
595
+ const msgId = response.data?.message_id || null;
596
+ if (msgId) {
597
+ console.log(`[飞书] 发送卡片成功: msgId=${msgId.slice(0, 16)}...`);
598
+ }
599
+ else {
600
+ console.log('[飞书] 发送卡片返回空消息ID');
601
+ }
602
+ return msgId;
603
+ }
604
+ catch (error) {
605
+ const formatted = formatError(error);
606
+ const errCode = typeof error === 'object' && error !== null && 'code' in error ? error.code : undefined;
607
+ const apiCode = extractApiCode(formatted.responseData);
608
+ if (apiCode === 230002) {
609
+ console.warn(`[飞书] 群不可用,发送卡片失败: chatId=${chatId}`);
610
+ this.emit('chatUnavailable', chatId);
611
+ return null;
612
+ }
613
+ if (isUniversalCardBuildFailure(formatted.responseData)) {
614
+ console.warn(`[飞书] 发送卡片触发 230099/200800,尝试发送精简卡片: chatId=${chatId}`);
615
+ try {
616
+ const fallbackResponse = await this.client.im.message.create({
617
+ params: { receive_id_type: 'chat_id' },
618
+ data: {
619
+ receive_id: chatId,
620
+ msg_type: 'interactive',
621
+ content: JSON.stringify(buildFallbackInteractiveCard(card)),
622
+ },
623
+ });
624
+ const fallbackMsgId = fallbackResponse.data?.message_id || null;
625
+ if (fallbackMsgId) {
626
+ console.log(`[飞书] 精简卡片发送成功: msgId=${fallbackMsgId.slice(0, 16)}...`);
627
+ }
628
+ return fallbackMsgId;
629
+ }
630
+ catch (fallbackError) {
631
+ const fallbackFormatted = formatError(fallbackError);
632
+ console.error(`[飞书] 精简卡片发送失败: ${fallbackFormatted.message}`);
633
+ }
634
+ }
635
+ console.error(`[飞书] 发送卡片失败: code=${errCode}, ${formatted.message}`);
636
+ return null;
637
+ }
638
+ }
639
+ // 撤回消息
640
+ async deleteMessage(messageId) {
641
+ try {
642
+ await this.client.im.message.delete({
643
+ path: { message_id: messageId },
644
+ });
645
+ return true;
646
+ }
647
+ catch (error) {
648
+ const formatted = formatError(error);
649
+ console.error('[飞书] 撤回消息失败:', formatted.message, formatted.responseData ?? '');
650
+ return false;
651
+ }
652
+ }
653
+ // 指定群管理员
654
+ async addChatManager(chatId, managerId, idType) {
655
+ try {
656
+ const response = await this.client.im.chatManagers.addManagers({
657
+ path: { chat_id: chatId },
658
+ params: { member_id_type: idType },
659
+ data: { manager_ids: [managerId] },
660
+ });
661
+ return response.code === 0;
662
+ }
663
+ catch (error) {
664
+ const formatted = formatError(error);
665
+ console.error('[飞书] 设置群管理员失败:', formatted.message, formatted.responseData ?? '');
666
+ return false;
667
+ }
668
+ }
669
+ // 创建群聊
670
+ async createChat(name, userIds, description) {
671
+ try {
672
+ const response = await this.client.im.chat.create({
673
+ params: {
674
+ user_id_type: 'open_id',
675
+ set_bot_manager: true, // 设置机器人为管理员
676
+ },
677
+ data: {
678
+ name,
679
+ description,
680
+ user_id_list: userIds,
681
+ },
682
+ });
683
+ const chatId = response.data?.chat_id || null;
684
+ // 飞书 API 返回的 invalid_id_list 包含无法添加的用户 ID
685
+ const invalidUserIds = response.data?.invalid_id_list || [];
686
+ if (response.code === 0 && chatId) {
687
+ console.log(`[飞书] 创建群聊成功: chatId=${chatId}, name=${name}, userIds=${userIds.join(',')}`);
688
+ if (invalidUserIds.length > 0) {
689
+ console.warn(`[飞书] 创建群聊时部分用户添加失败: invalidIds=${invalidUserIds.join(',')}`);
690
+ }
691
+ }
692
+ else {
693
+ console.error(`[飞书] 创建群聊失败: code=${response.code}, msg=${response.msg}, name=${name}, userIds=${userIds.join(',')}`);
694
+ if (response.data) {
695
+ console.error(`[飞书] 创建群聊错误详情: ${JSON.stringify(response.data)}`);
696
+ }
697
+ }
698
+ return { chatId, invalidUserIds };
699
+ }
700
+ catch (error) {
701
+ const formatted = formatError(error);
702
+ console.error('[飞书] 创建群聊失败:', formatted.message, formatted.responseData ?? '');
703
+ return { chatId: null, invalidUserIds: [] };
704
+ }
705
+ }
706
+ // 解散群聊
707
+ async disbandChat(chatId) {
708
+ try {
709
+ await this.client.im.chat.delete({
710
+ path: { chat_id: chatId },
711
+ });
712
+ console.log(`[飞书] 解散群聊成功: chatId=${chatId}`);
713
+ return true;
714
+ }
715
+ catch (error) {
716
+ const formatted = formatError(error);
717
+ console.error('[飞书] 解散群聊失败:', formatted.message, formatted.responseData ?? '');
718
+ return false;
719
+ }
720
+ }
721
+ // 获取群成员列表 (返回 open_id 列表)
722
+ async getChatMembers(chatId) {
723
+ try {
724
+ // 获取所有成员,支持分页
725
+ const memberIds = [];
726
+ let pageToken;
727
+ do {
728
+ const response = await this.client.im.chatMembers.get({
729
+ path: { chat_id: chatId },
730
+ params: {
731
+ member_id_type: 'open_id',
732
+ page_size: 100,
733
+ page_token: pageToken,
734
+ },
735
+ });
736
+ if (response.data?.items) {
737
+ for (const item of response.data.items) {
738
+ if (item.member_id) {
739
+ memberIds.push(item.member_id);
740
+ }
741
+ }
742
+ }
743
+ pageToken = response.data?.page_token;
744
+ } while (pageToken);
745
+ return memberIds;
746
+ }
747
+ catch (error) {
748
+ const formatted = formatError(error);
749
+ console.error('[飞书] 获取群成员失败:', formatted.message, formatted.responseData ?? '');
750
+ return [];
751
+ }
752
+ }
753
+ // 获取机器人所在的群列表
754
+ async getUserChats() {
755
+ try {
756
+ const chatIds = [];
757
+ let pageToken;
758
+ do {
759
+ const response = await this.client.im.chat.list({
760
+ params: {
761
+ page_size: 100,
762
+ page_token: pageToken,
763
+ },
764
+ });
765
+ if (response.data?.items) {
766
+ for (const item of response.data.items) {
767
+ if (item.chat_id) {
768
+ chatIds.push(item.chat_id);
769
+ }
770
+ }
771
+ }
772
+ pageToken = response.data?.page_token;
773
+ } while (pageToken);
774
+ return chatIds;
775
+ }
776
+ catch (error) {
777
+ const formatted = formatError(error);
778
+ console.error('[飞书] 获取群列表失败:', formatted.message, formatted.responseData ?? '');
779
+ return [];
780
+ }
781
+ }
782
+ // 获取群信息
783
+ async getChat(chatId) {
784
+ try {
785
+ const response = await this.client.im.chat.get({
786
+ path: { chat_id: chatId },
787
+ params: { user_id_type: 'open_id' },
788
+ });
789
+ if (response.code === 0 && response.data) {
790
+ return {
791
+ ownerId: response.data.owner_id || '',
792
+ name: response.data.name || '',
793
+ };
794
+ }
795
+ return null;
796
+ }
797
+ catch (error) {
798
+ const formatted = formatError(error);
799
+ console.error('[飞书] 获取群信息失败:', formatted.message, formatted.responseData ?? '');
800
+ return null;
801
+ }
802
+ }
803
+ // 邀请用户进群
804
+ async addChatMembers(chatId, userIds) {
805
+ try {
806
+ const response = await this.client.im.chatMembers.create({
807
+ path: { chat_id: chatId },
808
+ params: { member_id_type: 'open_id' },
809
+ data: { id_list: userIds },
810
+ });
811
+ if (response.code === 0) {
812
+ console.log(`[飞书] 邀请用户 ${userIds.join(', ')} 进群 ${chatId} 成功`);
813
+ }
814
+ else {
815
+ console.error(`[飞书] 邀请用户进群 ${chatId} 失败: code=${response.code}, msg=${response.msg}, userIds=${userIds.join(', ')}`);
816
+ if (response.data) {
817
+ console.error(`[飞书] 邀请用户进群错误详情: ${JSON.stringify(response.data)}`);
818
+ }
819
+ }
820
+ return response.code === 0;
821
+ }
822
+ catch (error) {
823
+ const formatted = formatError(error);
824
+ console.error('[飞书] 邀请进群失败:', formatted.message, formatted.responseData ?? '');
825
+ return false;
826
+ }
827
+ }
828
+ // 上传图片,返回 image_key
829
+ async uploadImage(imageData) {
830
+ try {
831
+ const response = await this.client.im.image.create({
832
+ data: {
833
+ image_type: 'message',
834
+ image: imageData,
835
+ },
836
+ });
837
+ const imageKey = response?.image_key || null;
838
+ if (imageKey) {
839
+ console.log(`[飞书] 上传图片成功: imageKey=${imageKey.slice(0, 16)}...`);
840
+ }
841
+ else {
842
+ console.log('[飞书] 上传图片返回空 image_key');
843
+ }
844
+ return imageKey;
845
+ }
846
+ catch (error) {
847
+ const formatted = formatError(error);
848
+ console.error(`[飞书] 上传图片失败: ${formatted.message}`);
849
+ return null;
850
+ }
851
+ }
852
+ // 上传文件,返回 file_key
853
+ async uploadFile(fileData, fileName, fileType) {
854
+ try {
855
+ const response = await this.client.im.file.create({
856
+ data: {
857
+ file_type: fileType,
858
+ file_name: fileName,
859
+ file: fileData,
860
+ },
861
+ });
862
+ const fileKey = response?.file_key || null;
863
+ if (fileKey) {
864
+ console.log(`[飞书] 上传文件成功: fileKey=${fileKey.slice(0, 16)}..., name=${fileName}`);
865
+ }
866
+ else {
867
+ console.log('[飞书] 上传文件返回空 file_key');
868
+ }
869
+ return fileKey;
870
+ }
871
+ catch (error) {
872
+ const formatted = formatError(error);
873
+ console.error(`[飞书] 上传文件失败: ${formatted.message}`);
874
+ return null;
875
+ }
876
+ }
877
+ // 发送图片消息
878
+ async sendImageMessage(chatId, imageKey) {
879
+ try {
880
+ const response = await this.client.im.message.create({
881
+ params: { receive_id_type: 'chat_id' },
882
+ data: {
883
+ receive_id: chatId,
884
+ msg_type: 'image',
885
+ content: JSON.stringify({ image_key: imageKey }),
886
+ },
887
+ });
888
+ const msgId = response.data?.message_id || null;
889
+ if (msgId) {
890
+ console.log(`[飞书] 发送图片消息成功: msgId=${msgId.slice(0, 16)}...`);
891
+ }
892
+ else {
893
+ console.log('[飞书] 发送图片消息返回空消息ID');
894
+ }
895
+ return msgId;
896
+ }
897
+ catch (error) {
898
+ const formatted = formatError(error);
899
+ const apiCode = extractApiCode(formatted.responseData);
900
+ if (apiCode === 230002) {
901
+ console.warn(`[飞书] 群不可用,发送图片消息失败: chatId=${chatId}`);
902
+ this.emit('chatUnavailable', chatId);
903
+ return null;
904
+ }
905
+ console.error(`[飞书] 发送图片消息失败: ${formatted.message}`);
906
+ return null;
907
+ }
908
+ }
909
+ // 发送文件消息
910
+ async sendFileMessage(chatId, fileKey) {
911
+ try {
912
+ const response = await this.client.im.message.create({
913
+ params: { receive_id_type: 'chat_id' },
914
+ data: {
915
+ receive_id: chatId,
916
+ msg_type: 'file',
917
+ content: JSON.stringify({ file_key: fileKey }),
918
+ },
919
+ });
920
+ const msgId = response.data?.message_id || null;
921
+ if (msgId) {
922
+ console.log(`[飞书] 发送文件消息成功: msgId=${msgId.slice(0, 16)}...`);
923
+ }
924
+ else {
925
+ console.log('[飞书] 发送文件消息返回空消息ID');
926
+ }
927
+ return msgId;
928
+ }
929
+ catch (error) {
930
+ const formatted = formatError(error);
931
+ const apiCode = extractApiCode(formatted.responseData);
932
+ if (apiCode === 230002) {
933
+ console.warn(`[飞书] 群不可用,发送文件消息失败: chatId=${chatId}`);
934
+ this.emit('chatUnavailable', chatId);
935
+ return null;
936
+ }
937
+ console.error(`[飞书] 发送文件消息失败: ${formatted.message}`);
938
+ return null;
939
+ }
940
+ }
941
+ // 停止长连接
942
+ stop() {
943
+ if (this.wsClient) {
944
+ this.wsClient.close();
945
+ this.wsClient = null;
946
+ }
947
+ console.log('[飞书] 已断开连接');
948
+ }
949
+ }
950
+ // 单例导出
951
+ export const feishuClient = new FeishuClient();
952
+ //# sourceMappingURL=client.js.map