@amaster.ai/pi-channels 0.1.2-beta.2 → 0.1.2-beta.21

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 (59) hide show
  1. package/README.md +77 -26
  2. package/dist/adapters/dingtalk.d.ts +5 -0
  3. package/dist/adapters/dingtalk.d.ts.map +1 -0
  4. package/dist/adapters/dingtalk.js +269 -0
  5. package/dist/adapters/dingtalk.js.map +1 -0
  6. package/dist/adapters/feishu.d.ts.map +1 -1
  7. package/dist/adapters/feishu.js +122 -5
  8. package/dist/adapters/feishu.js.map +1 -1
  9. package/dist/adapters/wecom/adapter.d.ts +3 -1
  10. package/dist/adapters/wecom/adapter.d.ts.map +1 -1
  11. package/dist/adapters/wecom/adapter.js +194 -157
  12. package/dist/adapters/wecom/adapter.js.map +1 -1
  13. package/dist/adapters/wecom.d.ts +0 -2
  14. package/dist/adapters/wecom.d.ts.map +1 -1
  15. package/dist/adapters/wecom.js +0 -1
  16. package/dist/adapters/wecom.js.map +1 -1
  17. package/dist/bridge-provider.d.ts +3 -0
  18. package/dist/bridge-provider.d.ts.map +1 -0
  19. package/dist/bridge-provider.js +79 -0
  20. package/dist/bridge-provider.js.map +1 -0
  21. package/dist/bridge.d.ts +1 -0
  22. package/dist/bridge.d.ts.map +1 -1
  23. package/dist/bridge.js +316 -8
  24. package/dist/bridge.js.map +1 -1
  25. package/dist/config.d.ts +1 -0
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +94 -18
  28. package/dist/config.js.map +1 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +557 -25
  31. package/dist/index.js.map +1 -1
  32. package/dist/registry.d.ts +12 -2
  33. package/dist/registry.d.ts.map +1 -1
  34. package/dist/registry.js +93 -22
  35. package/dist/registry.js.map +1 -1
  36. package/dist/types.d.ts +38 -12
  37. package/dist/types.d.ts.map +1 -1
  38. package/package.json +17 -3
  39. package/preview.png +0 -0
  40. package/dist/adapters/wecom/client.d.ts +0 -13
  41. package/dist/adapters/wecom/client.d.ts.map +0 -1
  42. package/dist/adapters/wecom/client.js +0 -116
  43. package/dist/adapters/wecom/client.js.map +0 -1
  44. package/dist/adapters/wecom/crypto.d.ts +0 -14
  45. package/dist/adapters/wecom/crypto.d.ts.map +0 -1
  46. package/dist/adapters/wecom/crypto.js +0 -37
  47. package/dist/adapters/wecom/crypto.js.map +0 -1
  48. package/dist/adapters/wecom/errors.d.ts +0 -25
  49. package/dist/adapters/wecom/errors.d.ts.map +0 -1
  50. package/dist/adapters/wecom/errors.js +0 -56
  51. package/dist/adapters/wecom/errors.js.map +0 -1
  52. package/dist/adapters/wecom/types.d.ts +0 -52
  53. package/dist/adapters/wecom/types.d.ts.map +0 -1
  54. package/dist/adapters/wecom/types.js +0 -2
  55. package/dist/adapters/wecom/types.js.map +0 -1
  56. package/dist/adapters/wecom/xml.d.ts +0 -4
  57. package/dist/adapters/wecom/xml.d.ts.map +0 -1
  58. package/dist/adapters/wecom/xml.js +0 -16
  59. package/dist/adapters/wecom/xml.js.map +0 -1
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @amaster.ai/pi-channels
2
2
 
3
+ ![pi-channels preview](https://raw.githubusercontent.com/TGYD-helige/pi/master/packages/pi-channels/preview.png)
4
+
3
5
  Pi extension for native messaging channels.
4
6
 
5
7
  This package follows the same extension shape as the open-source `@e9n/pi-channels`
@@ -10,17 +12,20 @@ It does not depend on `@e9n/pi-channels` at runtime. That package is published a
10
12
  a standalone extension source package with the older `@mariozechner/*` peer
11
13
  namespace and Slack/Telegram dependencies. This package keeps the same channel
12
14
  contract while targeting this repo's `@earendil-works/*` API surface and native
13
- Feishu/WeCom adapters.
15
+ Feishu/DingTalk/WeCom adapters.
14
16
 
15
17
  ## Channels
16
18
 
17
19
  - `feishu` - Native Feishu/Lark app messaging backed by the official
18
20
  `@larksuiteoapi/node-sdk`. Supports outgoing text messages, WebSocket events
19
21
  by default, and HTTP event callbacks as a fallback.
20
- - `wecom` - Native WeCom self-built app messaging. Supports outgoing text
21
- messages, appchat messages, encrypted HTTP callbacks for text messages, token
22
- caching, request timeouts, and actionable API errors such as trusted-IP
23
- failures.
22
+ - `wecom` - Native WeCom intelligent bot messaging backed by the official
23
+ `@wecom/aibot-node-sdk`. Supports WebSocket long connection events and active
24
+ Markdown pushes with Bot ID / Secret credentials.
25
+ - `dingtalk` - Native DingTalk app bot messaging backed by the official
26
+ `dingtalk-stream` SDK. Supports Stream mode incoming bot messages, reply
27
+ messages through `sessionWebhook`, and active group text pushes through the
28
+ DingTalk OpenAPI.
24
29
  - `webhook` - Generic outgoing HTTP requests.
25
30
 
26
31
  ## Example
@@ -38,17 +43,18 @@ Feishu/WeCom adapters.
38
43
  },
39
44
  "wecom": {
40
45
  "type": "wecom",
41
- "corpId": "${WECOM_CORP_ID}",
42
- "agentId": "${WECOM_AGENT_ID}",
43
- "secret": "${WECOM_SECRET}",
44
- "timeoutMs": 15000,
45
- "token": "${WECOM_CALLBACK_TOKEN}",
46
- "encodingAesKey": "${WECOM_ENCODING_AES_KEY}",
47
- "incoming": {
48
- "enabled": true,
49
- "port": 8788,
50
- "path": "/wecom/events"
51
- }
46
+ "botId": "${WECOM_BOT_ID}",
47
+ "secret": "${WECOM_BOT_SECRET}",
48
+ "eventMode": "websocket",
49
+ "respondToMentionsOnly": true,
50
+ "timeoutMs": 15000
51
+ },
52
+ "dingtalk": {
53
+ "type": "dingtalk",
54
+ "clientId": "${DINGTALK_CLIENT_ID}",
55
+ "clientSecret": "${DINGTALK_CLIENT_SECRET}",
56
+ "eventMode": "stream",
57
+ "respondToMentionsOnly": true
52
58
  }
53
59
  },
54
60
  "routes": {
@@ -102,6 +108,46 @@ payloads:
102
108
 
103
109
  Set `"eventMode": "off"` for outgoing-only usage.
104
110
 
111
+ ### DingTalk modes
112
+
113
+ Create a DingTalk internal app, add the bot capability, select Stream mode, and
114
+ publish it. Copy the Client ID and Client Secret into the adapter config:
115
+
116
+ ```json
117
+ {
118
+ "type": "dingtalk",
119
+ "clientId": "${DINGTALK_CLIENT_ID}",
120
+ "clientSecret": "${DINGTALK_CLIENT_SECRET}",
121
+ "robotCode": "${DINGTALK_ROBOT_CODE}",
122
+ "eventMode": "stream",
123
+ "respondToMentionsOnly": true,
124
+ "allowedConversationIds": ["cid_xxx"]
125
+ }
126
+ ```
127
+
128
+ Incoming replies use the per-message `sessionWebhook` when available. Active
129
+ route sends use DingTalk's group message OpenAPI with the route recipient as the
130
+ conversation/openConversationId. `robotCode` defaults to `clientId` when omitted.
131
+
132
+ Set `"eventMode": "off"` when the bot should only send messages.
133
+
134
+ ### WeCom modes
135
+
136
+ Create an intelligent bot in the WeCom admin console, choose API mode, and select
137
+ the long connection option. Copy the Bot ID and Secret into the adapter config:
138
+
139
+ ```json
140
+ {
141
+ "type": "wecom",
142
+ "botId": "${WECOM_BOT_ID}",
143
+ "secret": "${WECOM_BOT_SECRET}",
144
+ "eventMode": "websocket",
145
+ "allowedChatIds": ["wr_xxx"]
146
+ }
147
+ ```
148
+
149
+ Set `"eventMode": "off"` when the bot should only send messages.
150
+
105
151
  ## Tool
106
152
 
107
153
  ```text
@@ -120,20 +166,24 @@ user_id:abc123
120
166
  email:name@example.com
121
167
  ```
122
168
 
123
- For WeCom, recipients default to `touser`. Prefix the recipient to send to parties
124
- or tags. Prefix with `chat:` or `appchat:` to use the WeCom appchat API:
169
+ For WeCom intelligent bots, recipients are the conversation ids used by the Bot
170
+ API. In a group, send one message to the bot and use route capture to fill the
171
+ group `chatid`; for one-to-one pushes, use the target user's userid:
125
172
 
126
173
  ```text
127
- user:zhangsan
128
- party:2
129
- tag:7
130
- appchat:chat-id
174
+ wr_xxx
175
+ zhangsan
131
176
  ```
132
177
 
133
- If WeCom returns `60020 not allow to access from your ip`, the adapter raises a
134
- typed `WeComApiError` with category `ip_whitelist`. Configure the trusted IP in
135
- the WeCom console, or run pi behind a fixed egress proxy that is allowed by the
136
- enterprise.
178
+ The intelligent bot adapter uses the WebSocket Bot API, so long connection mode
179
+ does not require a public callback URL.
180
+
181
+ For DingTalk app bots, send one message to the bot and use route capture to fill
182
+ the group conversation id:
183
+
184
+ ```text
185
+ cid_xxx
186
+ ```
137
187
 
138
188
  ## Events
139
189
 
@@ -147,6 +197,7 @@ enterprise.
147
197
 
148
198
  ```text
149
199
  /channel list
200
+ /channel reload
150
201
  /channel bridge on
151
202
  /channel bridge off
152
203
  /channel bridge status
@@ -0,0 +1,5 @@
1
+ import type { AdapterConfig, ChannelAdapter } from '../types.js';
2
+ export declare function createDingTalkAdapter(config: AdapterConfig, context?: {
3
+ log?: (event: string, data?: Record<string, unknown>, level?: string) => void;
4
+ }): ChannelAdapter;
5
+ //# sourceMappingURL=dingtalk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dingtalk.d.ts","sourceRoot":"","sources":["../../src/adapters/dingtalk.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EAIf,MAAM,aAAa,CAAC;AAsMrB,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,aAAa,EACrB,OAAO,GAAE;IACP,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1E,GACL,cAAc,CA+IhB"}
@@ -0,0 +1,269 @@
1
+ const DEFAULT_OPEN_API_BASE_URL = 'https://api.dingtalk.com';
2
+ const DEFAULT_TOKEN_URL = 'https://oapi.dingtalk.com/gettoken';
3
+ const DINGTALK_TEXT_MSG_KEY = 'sampleText';
4
+ function asConfig(config) {
5
+ return config;
6
+ }
7
+ function requireString(value, name) {
8
+ if (typeof value === 'string' && value.trim())
9
+ return value.trim();
10
+ if (typeof value === 'number' && Number.isFinite(value))
11
+ return String(value);
12
+ throw new Error(`DingTalk adapter requires ${name}`);
13
+ }
14
+ function optionalString(value) {
15
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
16
+ }
17
+ function normalizeConfig(config) {
18
+ const cfg = asConfig(config);
19
+ const clientId = requireString(cfg.clientId, 'clientId');
20
+ return {
21
+ ...cfg,
22
+ clientId,
23
+ clientSecret: requireString(cfg.clientSecret, 'clientSecret'),
24
+ robotCode: optionalString(cfg.robotCode) ?? clientId,
25
+ eventMode: cfg.eventMode === 'off' ? 'off' : 'stream',
26
+ openApiBaseUrl: optionalString(cfg.openApiBaseUrl) ?? DEFAULT_OPEN_API_BASE_URL,
27
+ tokenUrl: optionalString(cfg.tokenUrl) ?? DEFAULT_TOKEN_URL,
28
+ };
29
+ }
30
+ function textFromMessage(message) {
31
+ if (!message.text)
32
+ throw new Error('DingTalk adapter requires text');
33
+ return message.source ? `[${message.source}]\n${message.text}` : message.text;
34
+ }
35
+ function shouldAcceptMessage(body, cfg) {
36
+ const conversationId = body.conversationId ?? '';
37
+ const allowedConversationIds = cfg.allowedConversationIds;
38
+ if (conversationId &&
39
+ Array.isArray(allowedConversationIds) &&
40
+ allowedConversationIds.length > 0 &&
41
+ !allowedConversationIds.includes(conversationId)) {
42
+ return false;
43
+ }
44
+ const senderId = body.senderId ?? body.senderStaffId ?? '';
45
+ const allowedSenderIds = cfg.allowedSenderIds;
46
+ if (senderId &&
47
+ Array.isArray(allowedSenderIds) &&
48
+ allowedSenderIds.length > 0 &&
49
+ !allowedSenderIds.includes(senderId)) {
50
+ return false;
51
+ }
52
+ if ((cfg.respondToMentionsOnly ?? true) &&
53
+ body.conversationType !== '1' &&
54
+ body.isInAtList === false) {
55
+ return false;
56
+ }
57
+ return true;
58
+ }
59
+ function senderForMessage(body) {
60
+ return body.conversationId ?? body.senderId ?? body.senderStaffId ?? '';
61
+ }
62
+ function metadataFromMessage(body) {
63
+ return {
64
+ messageId: body.msgId,
65
+ replyToMessageId: body.msgId,
66
+ conversationId: body.conversationId,
67
+ chatId: body.conversationId,
68
+ groupId: body.conversationId,
69
+ conversationTitle: body.conversationTitle,
70
+ chatName: body.conversationTitle,
71
+ groupName: body.conversationTitle,
72
+ conversationType: body.conversationType,
73
+ senderId: body.senderId,
74
+ senderStaffId: body.senderStaffId,
75
+ senderNick: body.senderNick,
76
+ senderName: body.senderNick,
77
+ sessionWebhook: body.sessionWebhook,
78
+ sessionWebhookExpiredTime: body.sessionWebhookExpiredTime,
79
+ robotCode: body.robotCode,
80
+ chatbotCorpId: body.chatbotCorpId,
81
+ chatbotUserId: body.chatbotUserId,
82
+ createAt: body.createAt,
83
+ rawMsgtype: body.msgtype,
84
+ };
85
+ }
86
+ function parseRobotMessage(downstream) {
87
+ if (downstream.headers.topic !== '/v1.0/im/bot/messages/get')
88
+ return undefined;
89
+ try {
90
+ const parsed = JSON.parse(downstream.data);
91
+ return parsed && typeof parsed === 'object' ? parsed : undefined;
92
+ }
93
+ catch {
94
+ return undefined;
95
+ }
96
+ }
97
+ function sessionWebhookFromMetadata(metadata) {
98
+ const value = metadata?.sessionWebhook;
99
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
100
+ }
101
+ function sessionWebhookIsFresh(metadata) {
102
+ const value = metadata?.sessionWebhookExpiredTime;
103
+ if (typeof value !== 'number' || !Number.isFinite(value))
104
+ return true;
105
+ return value > Date.now();
106
+ }
107
+ function normalizeApiBaseUrl(value) {
108
+ return value.replace(/\/+$/, '');
109
+ }
110
+ async function readJsonResponse(response) {
111
+ const text = await response.text();
112
+ if (!text)
113
+ return undefined;
114
+ try {
115
+ return JSON.parse(text);
116
+ }
117
+ catch {
118
+ return text;
119
+ }
120
+ }
121
+ function formatDingTalkApiError(body) {
122
+ if (!body || typeof body !== 'object' || Array.isArray(body))
123
+ return undefined;
124
+ const record = body;
125
+ const code = record.code ?? record.errcode;
126
+ if (code === undefined || code === 0 || code === '0')
127
+ return undefined;
128
+ const message = record.message ?? record.errmsg;
129
+ return `DingTalk API error ${String(code)}${message ? `: ${String(message)}` : ''}`;
130
+ }
131
+ export function createDingTalkAdapter(config, context = {}) {
132
+ const cfg = normalizeConfig(config);
133
+ let client = null;
134
+ let streamModulePromise = null;
135
+ let accessToken;
136
+ async function loadStreamModule() {
137
+ streamModulePromise ??= import('dingtalk-stream');
138
+ return streamModulePromise;
139
+ }
140
+ async function getAccessToken() {
141
+ if (accessToken && accessToken.expiresAt > Date.now())
142
+ return accessToken.token;
143
+ const url = new URL(cfg.tokenUrl);
144
+ url.searchParams.set('appkey', cfg.clientId);
145
+ url.searchParams.set('appsecret', cfg.clientSecret);
146
+ const response = await fetch(url);
147
+ const body = (await readJsonResponse(response));
148
+ if (!response.ok) {
149
+ throw new Error(`DingTalk token error ${response.status}: ${JSON.stringify(body)}`);
150
+ }
151
+ if (body?.errcode && body.errcode !== 0) {
152
+ throw new Error(`DingTalk token error ${body.errcode}: ${body.errmsg ?? 'unknown'}`);
153
+ }
154
+ if (!body?.access_token)
155
+ throw new Error('DingTalk token response missing access_token');
156
+ const expiresIn = Number.isFinite(body.expires_in) ? Number(body.expires_in) : 7200;
157
+ accessToken = {
158
+ token: body.access_token,
159
+ expiresAt: Date.now() + Math.max(60, expiresIn - 300) * 1000,
160
+ };
161
+ return accessToken.token;
162
+ }
163
+ async function sendViaSessionWebhook(url, text) {
164
+ const response = await fetch(url, {
165
+ method: 'POST',
166
+ headers: { 'Content-Type': 'application/json; charset=utf-8' },
167
+ body: JSON.stringify({
168
+ msgtype: 'text',
169
+ text: { content: text },
170
+ }),
171
+ });
172
+ const body = await readJsonResponse(response);
173
+ if (!response.ok) {
174
+ throw new Error(`DingTalk session webhook error ${response.status}: ${JSON.stringify(body)}`);
175
+ }
176
+ const apiError = formatDingTalkApiError(body);
177
+ if (apiError)
178
+ throw new Error(apiError);
179
+ }
180
+ async function sendViaOpenApi(recipient, text) {
181
+ const conversationId = recipient.trim();
182
+ if (!conversationId)
183
+ throw new Error('DingTalk adapter requires recipient');
184
+ const token = await getAccessToken();
185
+ const response = await fetch(`${normalizeApiBaseUrl(cfg.openApiBaseUrl)}/v1.0/robot/groupMessages/send`, {
186
+ method: 'POST',
187
+ headers: {
188
+ 'Content-Type': 'application/json; charset=utf-8',
189
+ 'x-acs-dingtalk-access-token': token,
190
+ },
191
+ body: JSON.stringify({
192
+ robotCode: cfg.robotCode,
193
+ openConversationId: conversationId,
194
+ msgKey: DINGTALK_TEXT_MSG_KEY,
195
+ msgParam: JSON.stringify({ content: text }),
196
+ }),
197
+ });
198
+ const body = await readJsonResponse(response);
199
+ if (!response.ok) {
200
+ throw new Error(`DingTalk send error ${response.status}: ${JSON.stringify(body)}`);
201
+ }
202
+ const apiError = formatDingTalkApiError(body);
203
+ if (apiError)
204
+ throw new Error(apiError);
205
+ }
206
+ async function sendText(message) {
207
+ const text = textFromMessage(message);
208
+ const sessionWebhook = sessionWebhookFromMetadata(message.metadata);
209
+ if (sessionWebhook && sessionWebhookIsFresh(message.metadata)) {
210
+ await sendViaSessionWebhook(sessionWebhook, text);
211
+ return;
212
+ }
213
+ await sendViaOpenApi(message.recipient, text);
214
+ }
215
+ async function start(onMessage) {
216
+ if (cfg.eventMode === 'off')
217
+ return;
218
+ if (client)
219
+ return;
220
+ const stream = await loadStreamModule();
221
+ client = new stream.DWClient({
222
+ clientId: cfg.clientId,
223
+ clientSecret: cfg.clientSecret,
224
+ ...(cfg.ua ? { ua: cfg.ua } : { ua: 'amaster-pi-channels' }),
225
+ ...(cfg.keepAlive !== undefined ? { keepAlive: cfg.keepAlive } : {}),
226
+ ...(cfg.debug !== undefined ? { debug: cfg.debug } : {}),
227
+ });
228
+ client.registerCallbackListener(stream.TOPIC_ROBOT, (downstream) => {
229
+ try {
230
+ const body = parseRobotMessage(downstream);
231
+ if (!body)
232
+ return;
233
+ if (!shouldAcceptMessage(body, cfg))
234
+ return;
235
+ if (body.msgtype !== 'text')
236
+ return;
237
+ const text = body.text?.content?.trim();
238
+ const sender = senderForMessage(body);
239
+ if (!text || !sender)
240
+ return;
241
+ void onMessage({
242
+ adapter: 'dingtalk',
243
+ sender,
244
+ text,
245
+ metadata: metadataFromMessage(body),
246
+ });
247
+ }
248
+ finally {
249
+ try {
250
+ client?.socketCallBackResponse(downstream.headers.messageId, null);
251
+ }
252
+ catch (error) {
253
+ context.log?.('dingtalk-stream-ack-failed', { error: error instanceof Error ? error.message : String(error) }, 'WARN');
254
+ }
255
+ }
256
+ });
257
+ await client.connect();
258
+ }
259
+ return {
260
+ direction: cfg.eventMode === 'off' ? 'outgoing' : 'bidirectional',
261
+ send: sendText,
262
+ start,
263
+ async stop() {
264
+ client?.disconnect();
265
+ client = null;
266
+ },
267
+ };
268
+ }
269
+ //# sourceMappingURL=dingtalk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dingtalk.js","sourceRoot":"","sources":["../../src/adapters/dingtalk.ts"],"names":[],"mappings":"AA2DA,MAAM,yBAAyB,GAAG,0BAA0B,CAAC;AAC7D,MAAM,iBAAiB,GAAG,oCAAoC,CAAC;AAC/D,MAAM,qBAAqB,GAAG,YAAY,CAAC;AAE3C,SAAS,QAAQ,CAAC,MAAqB;IACrC,OAAO,MAA+B,CAAC;AACzC,CAAC;AAED,SAAS,aAAa,CAAC,KAAc,EAAE,IAAY;IACjD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE;QAAE,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;IACnE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9E,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9E,CAAC;AAED,SAAS,eAAe,CAAC,MAAqB;IAC5C,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC7B,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IACzD,OAAO;QACL,GAAG,GAAG;QACN,QAAQ;QACR,YAAY,EAAE,aAAa,CAAC,GAAG,CAAC,YAAY,EAAE,cAAc,CAAC;QAC7D,SAAS,EAAE,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,QAAQ;QACpD,SAAS,EAAE,GAAG,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ;QACrD,cAAc,EAAE,cAAc,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,yBAAyB;QAC/E,QAAQ,EAAE,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,iBAAiB;KAC5D,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,OAAuB;IAC9C,IAAI,CAAC,OAAO,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACrE,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;AAChF,CAAC;AAED,SAAS,mBAAmB,CAAC,IAA0B,EAAE,GAA6B;IACpF,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,IAAI,EAAE,CAAC;IACjD,MAAM,sBAAsB,GAAG,GAAG,CAAC,sBAAsB,CAAC;IAC1D,IACE,cAAc;QACd,KAAK,CAAC,OAAO,CAAC,sBAAsB,CAAC;QACrC,sBAAsB,CAAC,MAAM,GAAG,CAAC;QACjC,CAAC,sBAAsB,CAAC,QAAQ,CAAC,cAAc,CAAC,EAChD,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,aAAa,IAAI,EAAE,CAAC;IAC3D,MAAM,gBAAgB,GAAG,GAAG,CAAC,gBAAgB,CAAC;IAC9C,IACE,QAAQ;QACR,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC;QAC/B,gBAAgB,CAAC,MAAM,GAAG,CAAC;QAC3B,CAAC,gBAAgB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EACpC,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IACE,CAAC,GAAG,CAAC,qBAAqB,IAAI,IAAI,CAAC;QACnC,IAAI,CAAC,gBAAgB,KAAK,GAAG;QAC7B,IAAI,CAAC,UAAU,KAAK,KAAK,EACzB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,gBAAgB,CAAC,IAA0B;IAClD,OAAO,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,aAAa,IAAI,EAAE,CAAC;AAC1E,CAAC;AAED,SAAS,mBAAmB,CAAC,IAA0B;IACrD,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,KAAK;QACrB,gBAAgB,EAAE,IAAI,CAAC,KAAK;QAC5B,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,MAAM,EAAE,IAAI,CAAC,cAAc;QAC3B,OAAO,EAAE,IAAI,CAAC,cAAc;QAC5B,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;QACzC,QAAQ,EAAE,IAAI,CAAC,iBAAiB;QAChC,SAAS,EAAE,IAAI,CAAC,iBAAiB;QACjC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;QACvC,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,yBAAyB,EAAE,IAAI,CAAC,yBAAyB;QACzD,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,UAAU,EAAE,IAAI,CAAC,OAAO;KACzB,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,UAA8B;IACvD,IAAI,UAAU,CAAC,OAAO,CAAC,KAAK,KAAK,2BAA2B;QAAE,OAAO,SAAS,CAAC;IAC/E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAyB,CAAC;QACnE,OAAO,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,0BAA0B,CACjC,QAA6C;IAE7C,MAAM,KAAK,GAAG,QAAQ,EAAE,cAAc,CAAC;IACvC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9E,CAAC;AAED,SAAS,qBAAqB,CAAC,QAA6C;IAC1E,MAAM,KAAK,GAAG,QAAQ,EAAE,yBAAyB,CAAC;IAClD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtE,OAAO,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;AAC5B,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAa;IACxC,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACnC,CAAC;AAED,KAAK,UAAU,gBAAgB,CAAC,QAAkB;IAChD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACnC,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,sBAAsB,CAAC,IAAa;IAC3C,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAC/E,MAAM,MAAM,GAAG,IAAgC,CAAC;IAChD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,OAAO,CAAC;IAC3C,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,GAAG;QAAE,OAAO,SAAS,CAAC;IACvE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC;IAChD,OAAO,sBAAsB,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AACtF,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,MAAqB,EACrB,UAEI,EAAE;IAEN,MAAM,GAAG,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,MAAM,GAAoB,IAAI,CAAC;IACnC,IAAI,mBAAmB,GAAyC,IAAI,CAAC;IACrE,IAAI,WAKS,CAAC;IAEd,KAAK,UAAU,gBAAgB;QAC7B,mBAAmB,KAAK,MAAM,CAAC,iBAAiB,CAAC,CAAC;QAClD,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IAED,KAAK,UAAU,cAAc;QAC3B,IAAI,WAAW,IAAI,WAAW,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE;YAAE,OAAO,WAAW,CAAC,KAAK,CAAC;QAChF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAClC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7C,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC;QACpD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,IAAI,GAAG,CAAC,MAAM,gBAAgB,CAAC,QAAQ,CAAC,CAA4C,CAAC;QAC3F,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,wBAAwB,QAAQ,CAAC,MAAM,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACtF,CAAC;QACD,IAAI,IAAI,EAAE,OAAO,IAAI,IAAI,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC,CAAC;QACvF,CAAC;QACD,IAAI,CAAC,IAAI,EAAE,YAAY;YAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;QACzF,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACpF,WAAW,GAAG;YACZ,KAAK,EAAE,IAAI,CAAC,YAAY;YACxB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,GAAG,IAAI;SAC7D,CAAC;QACF,OAAO,WAAW,CAAC,KAAK,CAAC;IAC3B,CAAC;IAED,KAAK,UAAU,qBAAqB,CAAC,GAAW,EAAE,IAAY;QAC5D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,iCAAiC,EAAE;YAC9D,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,OAAO,EAAE,MAAM;gBACf,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;aACxB,CAAC;SACH,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAC9C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,kCAAkC,QAAQ,CAAC,MAAM,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChG,CAAC;QACD,MAAM,QAAQ,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC1C,CAAC;IAED,KAAK,UAAU,cAAc,CAAC,SAAiB,EAAE,IAAY;QAC3D,MAAM,cAAc,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC,cAAc;YAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;QAC5E,MAAM,KAAK,GAAG,MAAM,cAAc,EAAE,CAAC;QACrC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAC1B,GAAG,mBAAmB,CAAC,GAAG,CAAC,cAAc,CAAC,gCAAgC,EAC1E;YACE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,iCAAiC;gBACjD,6BAA6B,EAAE,KAAK;aACrC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,kBAAkB,EAAE,cAAc;gBAClC,MAAM,EAAE,qBAAqB;gBAC7B,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;aAC5C,CAAC;SACH,CACF,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAC9C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,uBAAuB,QAAQ,CAAC,MAAM,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrF,CAAC;QACD,MAAM,QAAQ,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC1C,CAAC;IAED,KAAK,UAAU,QAAQ,CAAC,OAAuB;QAC7C,MAAM,IAAI,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QACtC,MAAM,cAAc,GAAG,0BAA0B,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACpE,IAAI,cAAc,IAAI,qBAAqB,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9D,MAAM,qBAAqB,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;YAClD,OAAO;QACT,CAAC;QACD,MAAM,cAAc,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,UAAU,KAAK,CAAC,SAA4B;QAC/C,IAAI,GAAG,CAAC,SAAS,KAAK,KAAK;YAAE,OAAO;QACpC,IAAI,MAAM;YAAE,OAAO;QACnB,MAAM,MAAM,GAAG,MAAM,gBAAgB,EAAE,CAAC;QACxC,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC;YAC3B,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,YAAY,EAAE,GAAG,CAAC,YAAY;YAC9B,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,qBAAqB,EAAE,CAAC;YAC5D,GAAG,CAAC,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpE,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACzD,CAAC,CAAC;QACH,MAAM,CAAC,wBAAwB,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,UAA8B,EAAE,EAAE;YACrF,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAC;gBAC3C,IAAI,CAAC,IAAI;oBAAE,OAAO;gBAClB,IAAI,CAAC,mBAAmB,CAAC,IAAI,EAAE,GAAG,CAAC;oBAAE,OAAO;gBAC5C,IAAI,IAAI,CAAC,OAAO,KAAK,MAAM;oBAAE,OAAO;gBACpC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;gBACxC,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;gBACtC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM;oBAAE,OAAO;gBAC7B,KAAK,SAAS,CAAC;oBACb,OAAO,EAAE,UAAU;oBACnB,MAAM;oBACN,IAAI;oBACJ,QAAQ,EAAE,mBAAmB,CAAC,IAAI,CAAC;iBACpC,CAAC,CAAC;YACL,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC;oBACH,MAAM,EAAE,sBAAsB,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;gBACrE,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,OAAO,CAAC,GAAG,EAAE,CACX,4BAA4B,EAC5B,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EACjE,MAAM,CACP,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;QACH,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;IACzB,CAAC;IAED,OAAO;QACL,SAAS,EAAE,GAAG,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,eAAe;QACjE,IAAI,EAAE,QAAQ;QACd,KAAK;QACL,KAAK,CAAC,IAAI;YACR,MAAM,EAAE,UAAU,EAAE,CAAC;YACrB,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"feishu.d.ts","sourceRoot":"","sources":["../../src/adapters/feishu.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EAIf,MAAM,aAAa,CAAC;AA2JrB,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,EACrB,OAAO,GAAE;IACP,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1E,GACL,cAAc,CA8OhB"}
1
+ {"version":3,"file":"feishu.d.ts","sourceRoot":"","sources":["../../src/adapters/feishu.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EAIf,MAAM,aAAa,CAAC;AAyPrB,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,EACrB,OAAO,GAAE;IACP,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1E,GACL,cAAc,CAoThB"}
@@ -20,6 +20,61 @@ function parseTextContent(content) {
20
20
  return { text: content };
21
21
  }
22
22
  }
23
+ function normalizeFeishuIncomingText(text, mentions, cfg, mentionedBot) {
24
+ let cleaned = text.replace(/\u00a0/g, ' ').trim();
25
+ cleaned = cleaned.replace(/^回复\s+[^::]{1,80}[::]\s*/u, '').trim();
26
+ if (mentions?.length) {
27
+ const botMentions = mentions.filter((mention) => isBotMention(mention, cfg));
28
+ const leadingMentions = botMentions.length > 0 ? botMentions : mentionedBot ? mentions : [];
29
+ cleaned = stripLeadingMentionTokens(cleaned, mentionTokens(leadingMentions));
30
+ }
31
+ if (mentionedBot) {
32
+ cleaned = cleaned.replace(/^@[\p{L}\p{N}_\-.·]+(?:\s+|$)/u, '').trim();
33
+ }
34
+ return cleaned;
35
+ }
36
+ function isBotMention(mention, cfg) {
37
+ if (mention.isBot)
38
+ return true;
39
+ const botOpenId = optionalNonEmptyString(cfg.botOpenId);
40
+ if (!botOpenId)
41
+ return false;
42
+ return (mention.openId === botOpenId ||
43
+ mention.userId === botOpenId ||
44
+ mention.id?.open_id === botOpenId ||
45
+ mention.id?.user_id === botOpenId ||
46
+ mention.id?.union_id === botOpenId);
47
+ }
48
+ function mentionTokens(mentions) {
49
+ const tokens = new Set();
50
+ for (const mention of mentions) {
51
+ if (mention.key?.trim())
52
+ tokens.add(mention.key.trim());
53
+ if (mention.name?.trim())
54
+ tokens.add(`@${mention.name.trim()}`);
55
+ }
56
+ return [...tokens].sort((a, b) => b.length - a.length);
57
+ }
58
+ function stripLeadingMentionTokens(text, tokens) {
59
+ let cleaned = text.trimStart();
60
+ for (let i = 0; i < 8; i++) {
61
+ const next = stripOneLeadingMentionToken(cleaned, tokens);
62
+ if (next === cleaned)
63
+ break;
64
+ cleaned = next.trimStart();
65
+ }
66
+ return cleaned.trim();
67
+ }
68
+ function stripOneLeadingMentionToken(text, tokens) {
69
+ for (const token of tokens) {
70
+ if (text === token)
71
+ return '';
72
+ if (text.startsWith(token) && /\s/u.test(text[token.length] ?? '')) {
73
+ return text.slice(token.length);
74
+ }
75
+ }
76
+ return text;
77
+ }
23
78
  function resolveReceiveId(message, defaultType) {
24
79
  const match = message.recipient.match(/^(chat_id|open_id|user_id|union_id|email):(.+)$/);
25
80
  if (!match)
@@ -89,6 +144,17 @@ function shouldAcceptMessage(msg, cfg) {
89
144
  function senderForMessage(chatId, threadId) {
90
145
  return threadId ? `${chatId}:${threadId}` : chatId;
91
146
  }
147
+ function objectStringField(value, key) {
148
+ if (!value || typeof value !== 'object')
149
+ return undefined;
150
+ const field = value[key];
151
+ return typeof field === 'string' && field.trim() ? field : undefined;
152
+ }
153
+ function ackReactionEmoji(cfg) {
154
+ if (cfg.ackReactionEmoji === false)
155
+ return null;
156
+ return optionalNonEmptyString(cfg.ackReactionEmoji) ?? 'OK';
157
+ }
92
158
  export function createFeishuAdapter(config, context = {}) {
93
159
  const cfg = asConfig(config);
94
160
  const appId = requireString(cfg.appId, 'appId');
@@ -110,6 +176,50 @@ export function createFeishuAdapter(config, context = {}) {
110
176
  });
111
177
  let channel = null;
112
178
  let server = null;
179
+ async function resolveChatName(chatId) {
180
+ const chatApi = client.im.chat;
181
+ if (!chatApi?.get)
182
+ return undefined;
183
+ try {
184
+ const response = await chatApi.get({ path: { chat_id: chatId } });
185
+ const code = response.code;
186
+ if (code !== undefined && code !== 0)
187
+ return undefined;
188
+ const data = response.data;
189
+ const chat = data && typeof data === 'object' ? data.chat : undefined;
190
+ return (objectStringField(data, 'name') ??
191
+ objectStringField(chat, 'name') ??
192
+ objectStringField(data, 'chat_name') ??
193
+ objectStringField(chat, 'chat_name'));
194
+ }
195
+ catch (error) {
196
+ context.log?.('feishu-chat-name-fetch-failed', { chatId, error: error instanceof Error ? error.message : String(error) }, 'WARN');
197
+ return undefined;
198
+ }
199
+ }
200
+ async function addAckReaction(messageId) {
201
+ const emojiType = ackReactionEmoji(cfg);
202
+ if (!emojiType)
203
+ return;
204
+ try {
205
+ const response = (await client.request({
206
+ url: `/open-apis/im/v1/messages/${encodeURIComponent(messageId)}/reactions`,
207
+ method: 'POST',
208
+ data: {
209
+ reaction_type: {
210
+ emoji_type: emojiType,
211
+ },
212
+ },
213
+ }));
214
+ const body = response.data;
215
+ if (body?.code !== undefined && body.code !== 0) {
216
+ context.log?.('feishu-ack-reaction-failed', { messageId, emojiType, code: body.code, msg: body.msg }, 'WARN');
217
+ }
218
+ }
219
+ catch (error) {
220
+ context.log?.('feishu-ack-reaction-failed', { messageId, emojiType, error: error instanceof Error ? error.message : String(error) }, 'WARN');
221
+ }
222
+ }
113
223
  async function sendText(message) {
114
224
  if (!message.text)
115
225
  throw new Error('Feishu adapter requires text');
@@ -146,7 +256,7 @@ export function createFeishuAdapter(config, context = {}) {
146
256
  throw new Error(`Feishu send error: ${response.msg ?? response.code ?? 'unknown'}`);
147
257
  }
148
258
  }
149
- function handleNormalizedMessage(msg, onMessage) {
259
+ async function handleNormalizedMessage(msg, onMessage) {
150
260
  if (!shouldAcceptMessage({
151
261
  chatId: msg.chatId,
152
262
  senderId: msg.senderId,
@@ -157,15 +267,19 @@ export function createFeishuAdapter(config, context = {}) {
157
267
  }
158
268
  if (msg.rawContentType !== 'text' && msg.rawContentType !== 'post')
159
269
  return;
160
- if (!msg.content.trim())
270
+ const text = normalizeFeishuIncomingText(msg.content, msg.mentions, cfg, msg.mentionedBot);
271
+ if (!text)
161
272
  return;
273
+ void addAckReaction(msg.messageId);
274
+ const chatName = await resolveChatName(msg.chatId);
162
275
  void onMessage({
163
276
  adapter: 'feishu',
164
277
  sender: senderForMessage(msg.chatId, msg.threadId),
165
- text: msg.content,
278
+ text,
166
279
  metadata: {
167
280
  messageId: msg.messageId,
168
281
  chatId: msg.chatId,
282
+ ...(chatName ? { chatName } : {}),
169
283
  chatType: msg.chatType,
170
284
  senderId: msg.senderId,
171
285
  senderName: msg.senderName,
@@ -197,9 +311,10 @@ export function createFeishuAdapter(config, context = {}) {
197
311
  }, cfg)) {
198
312
  return;
199
313
  }
200
- const text = parsed.text.trim();
314
+ const text = normalizeFeishuIncomingText(parsed.text, data.message.mentions, cfg, mentionedBot);
201
315
  if (!text)
202
316
  return;
317
+ void addAckReaction(data.message.message_id);
203
318
  void onMessage({
204
319
  adapter: 'feishu',
205
320
  sender: senderForMessage(data.message.chat_id, data.message.thread_id),
@@ -248,7 +363,9 @@ export function createFeishuAdapter(config, context = {}) {
248
363
  ? { wsConfig: { pingTimeout: cfg.wsPingTimeoutSeconds } }
249
364
  : {}),
250
365
  });
251
- channel.on('message', (msg) => handleNormalizedMessage(msg, onMessage));
366
+ channel.on('message', (msg) => {
367
+ void handleNormalizedMessage(msg, onMessage);
368
+ });
252
369
  channel.on('error', (error) => {
253
370
  context.log?.('feishu-channel-error', { error: error.message, code: error.code }, 'ERROR');
254
371
  });