@zhin.js/adapter-lark 1.1.4 → 1.1.6
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.
- package/CHANGELOG.md +49 -0
- package/README.md +58 -2
- package/adapters/lark/index.js +3 -3
- package/adapters/lark/index.ts +3 -3
- package/lib/cards.d.ts +23 -0
- package/lib/cards.js +77 -0
- package/lib/endpoint.d.ts +10 -5
- package/lib/endpoint.js +187 -53
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/long-connection.d.ts +16 -0
- package/lib/long-connection.js +88 -0
- package/lib/protocol.d.ts +7 -4
- package/lib/protocol.js +63 -2
- package/lib/stream-proxy.d.ts +7 -0
- package/lib/stream-proxy.js +17 -0
- package/lib/web-api-proxy.d.ts +11 -0
- package/lib/web-api-proxy.js +48 -0
- package/lib/webhook.d.ts +2 -0
- package/lib/webhook.js +13 -10
- package/package.json +19 -17
- package/schema.json +67 -0
- package/src/cards.ts +69 -0
- package/src/endpoint.ts +168 -53
- package/src/index.ts +7 -0
- package/src/long-connection.ts +87 -0
- package/src/protocol.ts +56 -2
- package/src/stream-proxy.ts +20 -0
- package/src/web-api-proxy.ts +51 -0
- package/src/webhook.ts +15 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
# @zhin.js/adapter-lark
|
|
2
2
|
|
|
3
|
+
## 1.1.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [badcc1f]
|
|
8
|
+
- @zhin.js/core@1.1.41
|
|
9
|
+
- @zhin.js/feature-kit@1.1.3
|
|
10
|
+
- @zhin.js/adapter@1.1.17
|
|
11
|
+
- @zhin.js/agent-feature@1.1.4
|
|
12
|
+
- @zhin.js/skill@1.1.4
|
|
13
|
+
- @zhin.js/tool@1.1.4
|
|
14
|
+
- @zhin.js/host-http@1.1.4
|
|
15
|
+
|
|
16
|
+
## 1.1.5
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- b8c0f46: Keep native TypeScript process supervision at the CLI entry point so programmatic runtime startup does not respawn its caller. Pair the Lark proxy dispatcher with its Undici fetch implementation to support newer Node versions while retaining TLS verification and delivery uncertainty handling.
|
|
21
|
+
- b9b3953: Stop synthesizing message IDs after outbound API calls. Missing real platform IDs now report an unknown delivery outcome and must not be retried automatically. DingTalk webhook success responses without a message ID therefore remain unconfirmed under the current receipt contract.
|
|
22
|
+
|
|
23
|
+
LINE reply and push use their actual sentMessages IDs. Reply HTTP 400 errors no longer trigger automatic push fallback: rejected or partially successful replies could otherwise duplicate a message. Locally expired cached reply tokens still select push before a reply request is made.
|
|
24
|
+
|
|
25
|
+
- b9b3953: 新增飞书 WebSocket 长连接模式,保留默认 HTTP webhook。使用官方 SDK 处理协议帧与 ACK,共享端点生命周期管理重连和停止;等待实际握手、观测 SDK 连接状态,增加消息重推去重及启动缓冲。
|
|
26
|
+
|
|
27
|
+
发送失败仅记录数值 HTTP status 和平台 code;明确 4xx/业务拒绝保留 rejected,网络与服务端不确定响应保留 unknown,不再把权限拒绝统一包装成未知,也不输出原始响应体。
|
|
28
|
+
|
|
29
|
+
将 canonical reply 映射到原消息回复接口,修复字面量 [reply]。图片上传拒绝不再静默降级并确认丢失媒体的消息;保留实际错误分类及安全诊断。
|
|
30
|
+
|
|
31
|
+
图文复合消息及多图片使用原生 post 保留文本和图片顺序,修复只发送首图导致文本丢失的问题。
|
|
32
|
+
|
|
33
|
+
- Updated dependencies [b9b3953]
|
|
34
|
+
- Updated dependencies [b9b3953]
|
|
35
|
+
- Updated dependencies [b9b3953]
|
|
36
|
+
- Updated dependencies [b9b3953]
|
|
37
|
+
- Updated dependencies [b9b3953]
|
|
38
|
+
- Updated dependencies [b9b3953]
|
|
39
|
+
- Updated dependencies [b9b3953]
|
|
40
|
+
- Updated dependencies [b9b3953]
|
|
41
|
+
- Updated dependencies [b9b3953]
|
|
42
|
+
- Updated dependencies [b9b3953]
|
|
43
|
+
- Updated dependencies [b9b3953]
|
|
44
|
+
- @zhin.js/adapter@1.1.16
|
|
45
|
+
- @zhin.js/im-contract@1.1.3
|
|
46
|
+
- @zhin.js/core@1.1.40
|
|
47
|
+
- @zhin.js/host-http@1.1.3
|
|
48
|
+
- @zhin.js/tool@1.1.3
|
|
49
|
+
- @zhin.js/skill@1.1.3
|
|
50
|
+
- @zhin.js/agent-feature@1.1.3
|
|
51
|
+
|
|
3
52
|
## 1.1.4
|
|
4
53
|
|
|
5
54
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Zhin.js 飞书 / Lark 适配器(Plugin Runtime),通过 Runtime Host HTTP W
|
|
|
8
8
|
- URL 验证挑战(`url_verification`)
|
|
9
9
|
- Tenant Access Token 自动刷新
|
|
10
10
|
- 支持飞书与 Lark 国际版 API 基址
|
|
11
|
-
- canonical `markdown` 段编码为 `interactive` 卡片中的 `
|
|
11
|
+
- canonical `markdown` 段编码为 `interactive` JSON 2.0 卡片中的 `markdown`
|
|
12
12
|
- 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
|
|
13
13
|
|
|
14
14
|
## 安装
|
|
@@ -70,7 +70,7 @@ plugins:
|
|
|
70
70
|
| 飞书类型 | 入站 content(文本摘要) | 出站 wire |
|
|
71
71
|
|----------|--------------------------|-----------|
|
|
72
72
|
| text | 原文 | text |
|
|
73
|
-
| markdown | 原文 | interactive
|
|
73
|
+
| markdown | 原文 | interactive(JSON 2.0 `markdown`) |
|
|
74
74
|
| image | `[image]` | image(需 `file_key`) |
|
|
75
75
|
| file | `[file: name]` | file |
|
|
76
76
|
| audio / video / sticker | `[audio]` / `[video]` / `[sticker]` | — |
|
|
@@ -99,3 +99,59 @@ pnpm --filter @zhin.js/adapter-lark test
|
|
|
99
99
|
| Tenant Token 获取失败 | 检查 `appId`、`appSecret` 与应用版本是否已发布 |
|
|
100
100
|
| 群里 @机器人不触发 AI | 当前无法可靠判定 bot open_id;使用显式 AI 前缀 |
|
|
101
101
|
| 能收不能发 | 检查机器人消息权限、可见范围与应用是否已加入群聊 |
|
|
102
|
+
|
|
103
|
+
## 飞书长连接接收
|
|
104
|
+
|
|
105
|
+
`endpoints[].mode` 支持 `webhook`(兼容默认)和 `websocket`。长连接适用于企业自建飞书应用;开发者后台「事件与回调」选择「使用长连接接收事件」,订阅 `im.message.receive_v1`,启用机器人并发布到测试范围。
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
mode: websocket
|
|
109
|
+
endpoints:
|
|
110
|
+
- id: feishu-bot
|
|
111
|
+
appId: ${LARK_APP_ID}
|
|
112
|
+
appSecret: ${LARK_APP_SECRET}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
长连接不注册 HTTP 路由,也不需要公网域名、`verificationToken` 或 `encryptKey`。出站仍走 OpenAPI。SDK 处理协议 ACK 和心跳;共享 `createEndpointLifecycle` 负责停止与重连,SDK 内建重连关闭。连接启动只在 SDK `onReady` 握手完成后成功,30 秒未完成则失败;运行中每秒读取 SDK 连接状态。每个 endpoint 对消息 ID 做 5 分钟去重(最多 10,000 项);这是进程内平台重推防护,跨重启业务去重由应用自己的持久化记录负责。
|
|
116
|
+
|
|
117
|
+
启动到 generation 开放之间最多缓存 100 条消息,停止清空;超过上限会明确告警。事件处理不等待下游回复,保证 SDK ACK 不被回复链路阻塞。同应用多客户端是集群分发,不能用于验证两个 Bot 的账号隔离,应使用不同应用。
|
|
118
|
+
|
|
119
|
+
官方说明:[Node SDK 长连接](https://github.com/larksuite/node-sdk#long-connection)。长连接实现及发布 SDK 合同有本地回归;实机接收、断线恢复与平台权限仍需实际应用验收。
|
|
120
|
+
|
|
121
|
+
出站 canonical `reply` 段使用飞书原消息 `message_id` 调用正式回复接口,引用标记不作为正文发送。图片需授予 `im:resource:upload`(或平台兼容的 `im:resource`)并完成发布。上传被拒绝或缺失真实 `image_key` 时整次发送失败,不发送文字替代品以冒充图片成功;诊断仅输出数值 status/code。撤回仍走 DELETE message API,平台权限或业务拒绝会向调用方透传。
|
|
122
|
+
|
|
123
|
+
文本与图片混排、多图片使用原生 `post`,保留文本和图片的原始顺序;单图片仍使用原生图片消息。回复元数据可与 post 组合。
|
|
124
|
+
|
|
125
|
+
### 原生 Markdown、分享与按钮
|
|
126
|
+
|
|
127
|
+
Canonical `markdown` 使用飞书 `interactive` JSON 2.0 消息中的 `markdown` 组件;普通文字保留为 `plain_text`,图片上传后可放入同一张卡片。`share` 映射为标题、描述和打开 URL 的原生卡片按钮,不声称提供平台网页预览抓取。Canonical `keyboard` 按行映射为原生 action/button,回传 `value.zhin_payload`。
|
|
128
|
+
|
|
129
|
+
应用后台须启用 **card.action.trigger** 卡片回调,与消息接收使用同样的长连接或 HTTP 回调入口。适配器立即返回 ACK,不等待业务回复。回调映射真实 operator.open_id、context.open_chat_id 和 sourceMessageId,且只关联当前端点近 5 分钟内成功发送的卡片,保留原会话/线程信息。重启后未关联的旧卡片点击会丢弃;重复回调去重。仅本地 SDK/HTTP 合同与回归已验证,实机点击和权限配置仍需验收。
|
|
130
|
+
|
|
131
|
+
参考:[飞书卡片回调结构](https://open.feishu.cn/document/uAjLw4CM/ukzMukzMukzM/feishu-cards/card-callback-communication)、[按钮组件](https://open.feishu.cn/document/common-capabilities/message-card/add-card-interaction/interactive-components/button)。
|
|
132
|
+
|
|
133
|
+
Markdown 单独卡片使用 JSON 2.0,以支持行内代码;v1 `lark_md` 的反引号实机显示为字面量,不能作为完整 Markdown 通过证据。当前 Markdown 与 share/keyboard 混排明确 unsupported,避免退回 v1 丢失代码样式。JSON2 行内代码仍须新的客户端实机验收,不因本地结构回归算通过。
|
|
134
|
+
# 长连接 TCP 故障验收
|
|
135
|
+
|
|
136
|
+
可选 `streamProxy: { port: 18443, serverName: "实际网关域名" }` 仅将 WSS TCP 路由到 `127.0.0.1`;已安装 SDK 的 WSClient `agent` 只传给 WebSocket,discovery/OpenAPI 不使用它。HTTP Host、私有 URL/query、TLS SNI 和证书链/域名校验仍为真实平台,不关闭验证;域名变化明确拒绝且只输出安全 hostname,不回退直连。Agent 随连接关闭销毁,重连仍由统一 Endpoint lifecycle 管理。
|
|
137
|
+
|
|
138
|
+
验收 example 支持进程覆盖 `LARK_STREAM_PROXY_PORT` / `LARK_STREAM_PROXY_SERVER_NAME`,需同时填写,留空正常直连。先用占位域名获取拒绝日志中的实际 gateway hostname,再启动 `scripts/platform-acceptance/tcp-fault-proxy.mjs --upstream-host 实际网关域名 --upstream-port 443 --port 18443 --control-port 18444` 并将 serverName 同步为此域名。保留已有 `.env`,只在启动命令临时覆盖变量。
|
|
139
|
+
|
|
140
|
+
确认正常入站后 POST 代理 `/cut`,观察 WSS 关闭及 transportState 进入重连;POST `/recover` 后等待重新 open,再发送唯一新 probe并确认入站/可见回包。切断的仅 WSS,OpenAPI出站仍直连,不足以证明所有网络恢复、发送重试或离线消息无损。网关变化应停止并更新固定上游;完成后停止代理、移除临时覆盖。实机结果单独记录,本地 SDK/TLS 回归不当作平台通过。
|
|
141
|
+
|
|
142
|
+
### 出站 HTTP 故障边界
|
|
143
|
+
|
|
144
|
+
本地真实 HTTP 服务回归覆盖服务器收到请求后断连接、500、408、429、403 与成功 HTTP 中明确平台拒绝;每个样本只有一个消息 POST。408/5xx/丢失结果保持 unknown,平台拒绝为 rejected;不自动重发。现有 Stream 代理只用于 WSS,不能证明 API 故障。`apiBaseUrl` 能用于本地无凭据 HTTP fixture,但把真实 HTTPS 换成本地 HTTP 会改变 TLS 身份,不应这样做实机验收;保留真实身份的 API 专用 TLS 路由入口仍需独立实现。
|
|
145
|
+
|
|
146
|
+
### 飞书出站 API 受控故障代理(默认关闭)
|
|
147
|
+
|
|
148
|
+
可选 `webApiProxy: { port: 18094 }` 仅改变出站 API 的 TCP 连接地址为 `127.0.0.1`。API URL、HTTP Host 和 TLS SNI 仍为 `open.feishu.cn`,始终验证证书链与该域名;不支持配置 hostname、CA 或关闭 TLS 校验。仅允许官方 `https://open.feishu.cn/open-apis`,不改变 `apiBaseUrl`,不兼容 Larksuite origin。与长连接 `streamProxy` 独立。Endpoint 停止会销毁自身 dispatcher/连接,重启可建立新连接;没有自动发送重试。
|
|
149
|
+
|
|
150
|
+
隔离验收启动方式(不改 `.env`):
|
|
151
|
+
|
|
152
|
+
```sh
|
|
153
|
+
node scripts/platform-acceptance/tcp-fault-proxy.mjs --upstream-host open.feishu.cn --upstream-port 443 --port 18094 --control-port 18095
|
|
154
|
+
LARK_WEB_API_PROXY_PORT=18094 pnpm --filter platform-acceptance-bot start:lark
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
控制端使用工具指南的 cut/recover。恢复后发送新 probe;断线发生在 POST 接收后时结果保持 unknown,不自动重投旧样本。此入口适用于专用测试实例,不能用本地 TLS fixture 冒充真实飞书恢复。默认未配置时仍使用原有 global fetch 直连。代理使用同一依赖版本的 [undici fetch](https://github.com/nodejs/undici/blob/v6.28.0/docs/docs/api/Fetch.md) 与 dispatcher,避免 Node 内置版本的 handler 合同差异;采用 [undici 6.x Agent connect 合同](https://github.com/nodejs/undici/blob/v6.28.0/docs/docs/api/Agent.md)。
|
package/adapters/lark/index.js
CHANGED
|
@@ -17,7 +17,7 @@ export default defineAdapter({
|
|
|
17
17
|
// 卡片交互未接入出站通道,交互段降级纯文本。
|
|
18
18
|
segments: {
|
|
19
19
|
outboundMedia: ['url', 'upload'],
|
|
20
|
-
interactive: '
|
|
20
|
+
interactive: 'native',
|
|
21
21
|
markdown: 'native',
|
|
22
22
|
},
|
|
23
23
|
create(context) {
|
|
@@ -25,11 +25,11 @@ export default defineAdapter({
|
|
|
25
25
|
// 注册到插件运行时状态(lark endpoint list 的"运行中"数据源)
|
|
26
26
|
context.use(larkRuntimeStateToken).endpoints.set(config.id, {
|
|
27
27
|
id: config.id,
|
|
28
|
-
mode:
|
|
28
|
+
mode: config.mode,
|
|
29
29
|
});
|
|
30
30
|
return new LarkEndpoint({
|
|
31
31
|
id: context.id,
|
|
32
|
-
http: context.use(httpHostToken),
|
|
32
|
+
...(config.mode === 'webhook' ? { http: context.use(httpHostToken) } : {}),
|
|
33
33
|
config,
|
|
34
34
|
});
|
|
35
35
|
},
|
package/adapters/lark/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ export default defineAdapter<LarkEndpointConfig>({
|
|
|
22
22
|
// 卡片交互未接入出站通道,交互段降级纯文本。
|
|
23
23
|
segments: {
|
|
24
24
|
outboundMedia: ['url', 'upload'],
|
|
25
|
-
interactive: '
|
|
25
|
+
interactive: 'native',
|
|
26
26
|
markdown: 'native',
|
|
27
27
|
},
|
|
28
28
|
create(context) {
|
|
@@ -30,11 +30,11 @@ export default defineAdapter<LarkEndpointConfig>({
|
|
|
30
30
|
// 注册到插件运行时状态(lark endpoint list 的"运行中"数据源)
|
|
31
31
|
context.use(larkRuntimeStateToken).endpoints.set(config.id, {
|
|
32
32
|
id: config.id,
|
|
33
|
-
mode:
|
|
33
|
+
mode: config.mode,
|
|
34
34
|
});
|
|
35
35
|
return new LarkEndpoint({
|
|
36
36
|
id: context.id,
|
|
37
|
-
http: context.use(httpHostToken),
|
|
37
|
+
...(config.mode === 'webhook' ? { http: context.use(httpHostToken) } : {}),
|
|
38
38
|
config,
|
|
39
39
|
});
|
|
40
40
|
},
|
package/lib/cards.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { LarkSendBody, LarkWireSegment } from './protocol.js';
|
|
2
|
+
/** Feishu JSON card v1: ordered native rich text, link and button components. */
|
|
3
|
+
export declare function formatLarkCard(items: Array<string | LarkWireSegment>): LarkSendBody;
|
|
4
|
+
export interface LarkCardAction {
|
|
5
|
+
readonly token?: string;
|
|
6
|
+
readonly context?: {
|
|
7
|
+
readonly open_message_id?: string;
|
|
8
|
+
readonly open_chat_id?: string;
|
|
9
|
+
};
|
|
10
|
+
readonly open_message_id?: string;
|
|
11
|
+
readonly open_chat_id?: string;
|
|
12
|
+
readonly operator?: {
|
|
13
|
+
readonly open_id?: string;
|
|
14
|
+
readonly user_id?: string;
|
|
15
|
+
};
|
|
16
|
+
readonly action?: {
|
|
17
|
+
readonly tag?: string;
|
|
18
|
+
readonly value?: {
|
|
19
|
+
readonly zhin_payload?: unknown;
|
|
20
|
+
readonly zhin_button_id?: unknown;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
}
|
package/lib/cards.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
2
|
+
/** Feishu JSON card v1: ordered native rich text, link and button components. */
|
|
3
|
+
export function formatLarkCard(items) {
|
|
4
|
+
const elements = [];
|
|
5
|
+
let replyTo;
|
|
6
|
+
for (const item of items) {
|
|
7
|
+
if (typeof item === 'string') {
|
|
8
|
+
elements.push({ tag: 'div', text: { tag: 'plain_text', content: item } });
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
const data = item.data ?? {};
|
|
12
|
+
switch (item.type) {
|
|
13
|
+
case 'reply':
|
|
14
|
+
replyTo = String(data.message_id ?? '') || undefined;
|
|
15
|
+
break;
|
|
16
|
+
case 'text':
|
|
17
|
+
elements.push({ tag: 'div', text: { tag: 'plain_text', content: String(data.text ?? data.content ?? '') } });
|
|
18
|
+
break;
|
|
19
|
+
case 'markdown':
|
|
20
|
+
elements.push({ tag: 'div', text: { tag: 'lark_md', content: String(data.content ?? data.text ?? '') } });
|
|
21
|
+
break;
|
|
22
|
+
case 'image': {
|
|
23
|
+
const media = data.media;
|
|
24
|
+
if (media?.kind !== 'file' || !media.value)
|
|
25
|
+
throw unsupported();
|
|
26
|
+
elements.push({ tag: 'img', img_key: media.value, alt: { tag: 'plain_text', content: 'Image' } });
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
case 'share': {
|
|
30
|
+
const url = String(data.url ?? '');
|
|
31
|
+
if (!/^https?:\/\//.test(url))
|
|
32
|
+
throw unsupported();
|
|
33
|
+
elements.push({ tag: 'div', text: { tag: 'plain_text', content: String(data.title ?? url) } });
|
|
34
|
+
if (data.description)
|
|
35
|
+
elements.push({ tag: 'div', text: { tag: 'plain_text', content: String(data.description) } });
|
|
36
|
+
elements.push({ tag: 'action', actions: [{ tag: 'button', text: { tag: 'plain_text', content: '打开链接' }, url }] });
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case 'keyboard': {
|
|
40
|
+
const rows = data.rows;
|
|
41
|
+
if (!Array.isArray(rows) || rows.length === 0)
|
|
42
|
+
throw unsupported();
|
|
43
|
+
for (const row of rows) {
|
|
44
|
+
if (!Array.isArray(row) || !row.length || row.length > 5)
|
|
45
|
+
throw unsupported();
|
|
46
|
+
const actions = row.map(button => {
|
|
47
|
+
if (!button.label || (!button.payload && !button.url))
|
|
48
|
+
throw unsupported();
|
|
49
|
+
return { tag: 'button', text: { tag: 'plain_text', content: button.label },
|
|
50
|
+
...(button.url ? { url: button.url } : { value: { zhin_payload: button.payload, zhin_button_id: button.id } }) };
|
|
51
|
+
});
|
|
52
|
+
elements.push({ tag: 'action', actions });
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
default: throw unsupported();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// JSON 1.0 lark_md does not render inline code. Use the SDK's JSON 2.0
|
|
60
|
+
// markdown component for Markdown cards; keep validated v1 action cards.
|
|
61
|
+
const hasMarkdown = items.some(item => typeof item !== 'string' && item.type === 'markdown');
|
|
62
|
+
const hasActions = items.some(item => typeof item !== 'string' && ['share', 'keyboard'].includes(item.type));
|
|
63
|
+
if (hasMarkdown && hasActions)
|
|
64
|
+
throw new EndpointDeliveryError('unsupported_operation', 'Mixed Markdown and action cards require JSON 2.0 action support', 'not_sent');
|
|
65
|
+
if (hasMarkdown) {
|
|
66
|
+
const bodyElements = elements.map(element => {
|
|
67
|
+
const row = element;
|
|
68
|
+
if (row.tag !== 'div')
|
|
69
|
+
return element;
|
|
70
|
+
const text = row.text;
|
|
71
|
+
return { tag: 'markdown', content: text.tag === 'lark_md' ? text.content : text.content.replace(/[\\`*_{}[\]()#+.!|>~-]/g, '\\$&') };
|
|
72
|
+
});
|
|
73
|
+
return { msg_type: 'interactive', content: JSON.stringify({ schema: '2.0', body: { elements: bodyElements } }), ...(replyTo ? { replyTo } : {}) };
|
|
74
|
+
}
|
|
75
|
+
return { msg_type: 'interactive', content: JSON.stringify({ config: { wide_screen_mode: true }, elements }), ...(replyTo ? { replyTo } : {}) };
|
|
76
|
+
}
|
|
77
|
+
function unsupported() { return new EndpointDeliveryError('unsupported_operation', 'Unsupported or malformed Lark card segment', 'not_sent'); }
|
package/lib/endpoint.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type EndpointControl, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
|
|
1
|
+
import type { LarkCardAction } from './cards.js';
|
|
2
|
+
import { Endpoint, type EndpointTransportState, type EndpointControl, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
|
|
3
3
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
4
4
|
import type { CapabilityId } from 'zhin.js';
|
|
5
5
|
import { resolveMediaBinary } from './media-upload.js';
|
|
6
6
|
import { type LarkMessage, type ResolvedLarkConfig } from './protocol.js';
|
|
7
|
+
import { type LarkLongConnectionFactory } from './long-connection.js';
|
|
7
8
|
export type LarkFetch = (url: string, init?: {
|
|
8
9
|
readonly method?: string;
|
|
9
10
|
readonly headers?: Record<string, string>;
|
|
@@ -17,7 +18,8 @@ export type LarkFetch = (url: string, init?: {
|
|
|
17
18
|
}>;
|
|
18
19
|
export interface LarkEndpointOptions {
|
|
19
20
|
readonly id: CapabilityId;
|
|
20
|
-
readonly http
|
|
21
|
+
readonly http?: HttpHost;
|
|
22
|
+
readonly longConnectionFactory?: LarkLongConnectionFactory;
|
|
21
23
|
readonly config: ResolvedLarkConfig;
|
|
22
24
|
readonly fetch?: LarkFetch;
|
|
23
25
|
}
|
|
@@ -72,16 +74,19 @@ export declare class LarkEndpoint extends Endpoint<LarkClient> {
|
|
|
72
74
|
/** Used by webhook handler. */
|
|
73
75
|
get isOpen(): boolean;
|
|
74
76
|
get config(): ResolvedLarkConfig;
|
|
77
|
+
get transportState(): EndpointTransportState | undefined;
|
|
75
78
|
start(): Promise<void>;
|
|
76
79
|
open(): void;
|
|
77
80
|
close(): void;
|
|
78
81
|
stop(): Promise<void>;
|
|
79
82
|
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
80
83
|
recallMessage(messageId: string): Promise<void>;
|
|
81
|
-
/**
|
|
82
|
-
uploadImage(media: Parameters<typeof resolveMediaBinary>[0]): Promise<string
|
|
84
|
+
/** Upload must succeed before issuing a message; dropped media is never confirmed. */
|
|
85
|
+
uploadImage(media: Parameters<typeof resolveMediaBinary>[0]): Promise<string>;
|
|
83
86
|
/** Test / internal: admit a parsed message when open (non-webhook path). */
|
|
84
87
|
admit(msg: LarkMessage): void;
|
|
88
|
+
/** Only correlate callbacks to a card actually sent by this endpoint. */
|
|
89
|
+
admitCardAction(event: LarkCardAction): void;
|
|
85
90
|
admitPlatform(name: string, event: unknown): void;
|
|
86
91
|
getUserInfo(userId: string, userIdType?: 'open_id' | 'user_id' | 'union_id'): Promise<unknown>;
|
|
87
92
|
getChatInfo(chatId: string): Promise<unknown>;
|