chatccc 0.1.6 → 0.2.0
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/README.md +10 -12
- package/bin/chatccc.mjs +24 -6
- package/package.json +2 -1
- package/src/cardkit.ts +157 -157
- package/src/cards.ts +214 -169
- package/src/config.ts +223 -80
- package/src/exit-banner.ts +23 -0
- package/src/feishu-api.ts +248 -243
- package/src/index.ts +708 -599
- package/src/session.ts +455 -409
- package/src/shared.ts +285 -185
package/README.md
CHANGED
|
@@ -96,25 +96,23 @@ cp .env.example .env
|
|
|
96
96
|
编辑 `.env`,填入上一步拿到的凭证:
|
|
97
97
|
|
|
98
98
|
```env
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
CHATCCC_APP_ID=cli_xxxxxxxxxxxx
|
|
100
|
+
CHATCCC_APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
若你曾使用旧版变量名 `FEISHU_CLAUDER_APP_ID` / `FEISHU_CLAUDER_APP_SECRET`,请改为上表中的 `CHATCCC_APP_ID` / `CHATCCC_APP_SECRET`。
|
|
104
|
+
|
|
105
|
+
可选:Claude 模型与思考深度(**默认均为 `default`**):
|
|
104
106
|
|
|
105
107
|
```env
|
|
106
|
-
#
|
|
107
|
-
CHATCCC_ANTHROPIC_MODEL=
|
|
108
|
+
# 网关或服务商文档中的 model;设为 default(任意大小写)或不写则交给 SDK/CLI 默认,且 /status 中显示为 default
|
|
109
|
+
CHATCCC_ANTHROPIC_MODEL=default
|
|
108
110
|
|
|
109
|
-
#
|
|
110
|
-
CHATCCC_ANTHROPIC_EFFORT=
|
|
111
|
+
# 如 low / medium / high / max;设为 default(任意大小写)或不写则不向 SDK 传入 effort,/status 显示 default
|
|
112
|
+
CHATCCC_ANTHROPIC_EFFORT=default
|
|
111
113
|
```
|
|
112
114
|
|
|
113
|
-
`**
|
|
114
|
-
|
|
115
|
-
`**CHATCCC_ANTHROPIC_EFFORT` 优先级**:环境变量设置的值 > 代码内默认值 `max`。
|
|
116
|
-
|
|
117
|
-
`**CHATCCC_PORT` 端口配置**:默认端口为 `18080`。如果你需要在一台机器上同时运行多个 ChatCCC 实例(例如用不同飞书应用分别接入不同项目),可以在各自的 `.env` 中设置不同的端口:
|
|
115
|
+
`**CHATCCC_PORT`(可选)**:默认端口为 `18080`。若在一台机器上同时运行多个 ChatCCC 实例,可在各自的 `.env` 中设置不同端口:
|
|
118
116
|
|
|
119
117
|
```env
|
|
120
118
|
# 实例 A(项目1)的 .env
|
package/bin/chatccc.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
5
|
import { dirname, join } from "node:path";
|
|
5
6
|
|
|
@@ -9,10 +10,27 @@ const pkgRoot = dirname(require.resolve("../package.json"));
|
|
|
9
10
|
const indexTs = join(pkgRoot, "src", "index.ts");
|
|
10
11
|
const tsxCli = require.resolve("tsx/cli");
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
);
|
|
13
|
+
// 与 npm run dev 一致:当前工作目录下的 .env 会被加载(全局安装时 cwd 一般为你的项目根)
|
|
14
|
+
const envFile = join(process.cwd(), ".env");
|
|
15
|
+
if (!existsSync(envFile)) {
|
|
16
|
+
console.error(`[chatccc] 当前目录下未找到 .env,不会自动加载环境变量: ${envFile}`);
|
|
17
|
+
console.error(" 将只使用系统/用户环境变量。若未设置 CHATCCC_APP_ID / CHATCCC_APP_SECRET,启动会失败。");
|
|
18
|
+
console.error(" 建议: cd 到项目根目录,复制 .env.example 为 .env 并填写后再运行 chatccc。\n");
|
|
19
|
+
}
|
|
20
|
+
const tsxArgs = existsSync(envFile)
|
|
21
|
+
? [tsxCli, "--env-file", envFile, indexTs, ...process.argv.slice(2)]
|
|
22
|
+
: [tsxCli, indexTs, ...process.argv.slice(2)];
|
|
17
23
|
|
|
18
|
-
|
|
24
|
+
const result = spawnSync(process.execPath, tsxArgs, {
|
|
25
|
+
stdio: "inherit",
|
|
26
|
+
cwd: process.cwd(),
|
|
27
|
+
env: process.env,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const code = result.status === null ? 1 : result.status;
|
|
31
|
+
if (code !== 0) {
|
|
32
|
+
console.error("[ 未启动 ]");
|
|
33
|
+
console.error("chatccc 子进程已结束,服务没有在后台运行。");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
process.exit(code);
|
package/package.json
CHANGED
package/src/cardkit.ts
CHANGED
|
@@ -1,158 +1,158 @@
|
|
|
1
|
-
import { BASE_URL, fileLog, ts } from "./config.ts";
|
|
2
|
-
|
|
3
|
-
// CardKit 是飞书的卡片流式更新 API,通过 cardElement.content 端点
|
|
4
|
-
// 对卡片上指定 element_id 的元素进行增量内容更新,实现打字机效果。
|
|
5
|
-
// 与普通卡片 PATCH 的区别:PATCH 是全量替换卡片 JSON,CardKit 是
|
|
6
|
-
// 由飞书客户端自行渲染增量文本,用户看到的是逐字出现的效果。
|
|
7
|
-
// 参考: https://open.feishu.cn/document/uYjLw4iNukDO6YDN4ITM4MjM
|
|
8
|
-
|
|
9
|
-
export async function createCardKitCard(token: string, cardJson: string): Promise<string> {
|
|
10
|
-
const body = JSON.stringify({ type: "card_json", data: cardJson });
|
|
11
|
-
const resp = await fetch(`${BASE_URL}/cardkit/v1/cards`, {
|
|
12
|
-
method: "POST",
|
|
13
|
-
headers: {
|
|
14
|
-
Authorization: `Bearer ${token}`,
|
|
15
|
-
"Content-Type": "application/json",
|
|
16
|
-
},
|
|
17
|
-
body,
|
|
18
|
-
});
|
|
19
|
-
const respText = await resp.text();
|
|
20
|
-
let data: { code: number; msg?: string; data?: { card_id?: string } };
|
|
21
|
-
try {
|
|
22
|
-
data = JSON.parse(respText);
|
|
23
|
-
} catch {
|
|
24
|
-
console.error(`[${ts()}] [CARDIKT] createCard: invalid JSON response (status=${resp.status}) body=${respText.slice(0, 500)}`);
|
|
25
|
-
fileLog.flush();
|
|
26
|
-
throw new Error(`CardKit create: invalid JSON response (status=${resp.status})`);
|
|
27
|
-
}
|
|
28
|
-
if (data.code !== 0) {
|
|
29
|
-
console.error(`[${ts()}] [CARDIKT] createCard FAIL: status=${resp.status} code=${data.code} msg="${data.msg}" body=${respText.slice(0, 500)}`);
|
|
30
|
-
fileLog.flush();
|
|
31
|
-
throw new Error(`CardKit create: [${data.code}] ${data.msg}`);
|
|
32
|
-
}
|
|
33
|
-
console.log(`[${ts()}] [CARDIKT] createCard OK cardId=${data.data?.card_id ?? "(none)"}`);
|
|
34
|
-
return data.data?.card_id ?? "";
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export async function setCardKitSettings(
|
|
38
|
-
token: string,
|
|
39
|
-
cardId: string,
|
|
40
|
-
settings: Record<string, unknown>,
|
|
41
|
-
sequence: number
|
|
42
|
-
): Promise<void> {
|
|
43
|
-
const resp = await fetch(`${BASE_URL}/cardkit/v1/cards/${cardId}/settings`, {
|
|
44
|
-
method: "PATCH",
|
|
45
|
-
headers: {
|
|
46
|
-
Authorization: `Bearer ${token}`,
|
|
47
|
-
"Content-Type": "application/json",
|
|
48
|
-
},
|
|
49
|
-
body: JSON.stringify({ settings: JSON.stringify(settings), sequence }),
|
|
50
|
-
});
|
|
51
|
-
const respText = await resp.text();
|
|
52
|
-
let data: { code: number; msg?: string };
|
|
53
|
-
try { data = JSON.parse(respText); } catch {
|
|
54
|
-
console.error(`[${ts()}] [CARDIKT] settings FAIL: invalid JSON cardId=${cardId} seq=${sequence} status=${resp.status}`);
|
|
55
|
-
fileLog.flush();
|
|
56
|
-
throw new Error(`CardKit settings: invalid JSON (status=${resp.status})`);
|
|
57
|
-
}
|
|
58
|
-
if (data.code !== 0) {
|
|
59
|
-
console.error(`[${ts()}] [CARDIKT] settings FAIL: cardId=${cardId} seq=${sequence} status=${resp.status} code=${data.code} msg="${data.msg}"`);
|
|
60
|
-
fileLog.flush();
|
|
61
|
-
throw new Error(`CardKit settings: [${data.code}] ${data.msg}`);
|
|
62
|
-
}
|
|
63
|
-
console.log(`[${ts()}] [CARDIKT] settings OK cardId=${cardId} seq=${sequence}`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export async function streamCardKitElement(
|
|
67
|
-
token: string,
|
|
68
|
-
cardId: string,
|
|
69
|
-
elementId: string,
|
|
70
|
-
content: string,
|
|
71
|
-
sequence: number
|
|
72
|
-
): Promise<void> {
|
|
73
|
-
const resp = await fetch(
|
|
74
|
-
`${BASE_URL}/cardkit/v1/cards/${cardId}/elements/${elementId}/content`,
|
|
75
|
-
{
|
|
76
|
-
method: "PUT",
|
|
77
|
-
headers: {
|
|
78
|
-
Authorization: `Bearer ${token}`,
|
|
79
|
-
"Content-Type": "application/json",
|
|
80
|
-
},
|
|
81
|
-
body: JSON.stringify({ content, sequence }),
|
|
82
|
-
}
|
|
83
|
-
);
|
|
84
|
-
const respText = await resp.text();
|
|
85
|
-
let data: { code: number; msg?: string };
|
|
86
|
-
try { data = JSON.parse(respText); } catch {
|
|
87
|
-
console.error(`[${ts()}] [CARDIKT] streamElement FAIL: invalid JSON cardId=${cardId} element=${elementId} seq=${sequence} status=${resp.status}`);
|
|
88
|
-
fileLog.flush();
|
|
89
|
-
throw new Error(`CardKit stream: invalid JSON (status=${resp.status})`);
|
|
90
|
-
}
|
|
91
|
-
if (data.code !== 0) {
|
|
92
|
-
console.error(`[${ts()}] [CARDIKT] streamElement FAIL: cardId=${cardId} element=${elementId} seq=${sequence} status=${resp.status} code=${data.code} msg="${data.msg}"`);
|
|
93
|
-
fileLog.flush();
|
|
94
|
-
throw new Error(`CardKit stream: [${data.code}] ${data.msg}`);
|
|
95
|
-
}
|
|
96
|
-
// success log is intentionally sparse — uncomment to debug streaming throughput
|
|
97
|
-
// console.log(`[${ts()}] [CARDIKT] streamElement OK cardId=${cardId} seq=${sequence}`);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export async function updateCardKitCard(
|
|
101
|
-
token: string,
|
|
102
|
-
cardId: string,
|
|
103
|
-
cardJson: string,
|
|
104
|
-
sequence: number
|
|
105
|
-
): Promise<void> {
|
|
106
|
-
const resp = await fetch(`${BASE_URL}/cardkit/v1/cards/${cardId}`, {
|
|
107
|
-
method: "PUT",
|
|
108
|
-
headers: {
|
|
109
|
-
Authorization: `Bearer ${token}`,
|
|
110
|
-
"Content-Type": "application/json",
|
|
111
|
-
},
|
|
112
|
-
body: JSON.stringify({ card: { type: "card_json", data: cardJson }, sequence }),
|
|
113
|
-
});
|
|
114
|
-
const respText = await resp.text();
|
|
115
|
-
let data: { code: number; msg?: string };
|
|
116
|
-
try { data = JSON.parse(respText); } catch {
|
|
117
|
-
console.error(`[${ts()}] [CARDIKT] updateCard FAIL: invalid JSON cardId=${cardId} seq=${sequence} status=${resp.status}`);
|
|
118
|
-
fileLog.flush();
|
|
119
|
-
throw new Error(`CardKit update: invalid JSON (status=${resp.status})`);
|
|
120
|
-
}
|
|
121
|
-
if (data.code !== 0) {
|
|
122
|
-
console.error(`[${ts()}] [CARDIKT] updateCard FAIL: cardId=${cardId} seq=${sequence} status=${resp.status} code=${data.code} msg="${data.msg}"`);
|
|
123
|
-
fileLog.flush();
|
|
124
|
-
throw new Error(`CardKit update: [${data.code}] ${data.msg}`);
|
|
125
|
-
}
|
|
126
|
-
console.log(`[${ts()}] [CARDIKT] updateCard OK cardId=${cardId} seq=${sequence}`);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export async function sendCardKitMessage(
|
|
130
|
-
token: string,
|
|
131
|
-
chatId: string,
|
|
132
|
-
cardId: string
|
|
133
|
-
): Promise<string> {
|
|
134
|
-
const payload = { receive_id: chatId, msg_type: "interactive", content: JSON.stringify({ type: "card", data: { card_id: cardId } }) };
|
|
135
|
-
const url = `${BASE_URL}/im/v1/messages?receive_id_type=chat_id`;
|
|
136
|
-
const resp = await fetch(url, {
|
|
137
|
-
method: "POST",
|
|
138
|
-
headers: {
|
|
139
|
-
Authorization: `Bearer ${token}`,
|
|
140
|
-
"Content-Type": "application/json",
|
|
141
|
-
},
|
|
142
|
-
body: JSON.stringify(payload),
|
|
143
|
-
});
|
|
144
|
-
const respText = await resp.text();
|
|
145
|
-
let data: { code: number; msg?: string; data?: { message_id?: string } };
|
|
146
|
-
try { data = JSON.parse(respText); } catch {
|
|
147
|
-
console.error(`[${ts()}] [CARDIKT] sendMessage FAIL: invalid JSON cardId=${cardId} chatId=${chatId} status=${resp.status}`);
|
|
148
|
-
fileLog.flush();
|
|
149
|
-
throw new Error(`sendCardKitMessage: invalid JSON (status=${resp.status})`);
|
|
150
|
-
}
|
|
151
|
-
if (data.code !== 0) {
|
|
152
|
-
console.error(`[${ts()}] [CARDIKT] sendMessage FAIL: status=${resp.status} code=${data.code} msg="${data.msg}" cardId=${cardId} chatId=${chatId}`);
|
|
153
|
-
fileLog.flush();
|
|
154
|
-
throw new Error(`[${data.code}] ${data.msg}`);
|
|
155
|
-
}
|
|
156
|
-
console.log(`[${ts()}] [CARDIKT] sendMessage OK cardId=${cardId} chatId=${chatId} msgId=${data.data?.message_id ?? "(none)"}`);
|
|
157
|
-
return data.data?.message_id ?? "";
|
|
1
|
+
import { BASE_URL, fileLog, ts } from "./config.ts";
|
|
2
|
+
|
|
3
|
+
// CardKit 是飞书的卡片流式更新 API,通过 cardElement.content 端点
|
|
4
|
+
// 对卡片上指定 element_id 的元素进行增量内容更新,实现打字机效果。
|
|
5
|
+
// 与普通卡片 PATCH 的区别:PATCH 是全量替换卡片 JSON,CardKit 是
|
|
6
|
+
// 由飞书客户端自行渲染增量文本,用户看到的是逐字出现的效果。
|
|
7
|
+
// 参考: https://open.feishu.cn/document/uYjLw4iNukDO6YDN4ITM4MjM
|
|
8
|
+
|
|
9
|
+
export async function createCardKitCard(token: string, cardJson: string): Promise<string> {
|
|
10
|
+
const body = JSON.stringify({ type: "card_json", data: cardJson });
|
|
11
|
+
const resp = await fetch(`${BASE_URL}/cardkit/v1/cards`, {
|
|
12
|
+
method: "POST",
|
|
13
|
+
headers: {
|
|
14
|
+
Authorization: `Bearer ${token}`,
|
|
15
|
+
"Content-Type": "application/json",
|
|
16
|
+
},
|
|
17
|
+
body,
|
|
18
|
+
});
|
|
19
|
+
const respText = await resp.text();
|
|
20
|
+
let data: { code: number; msg?: string; data?: { card_id?: string } };
|
|
21
|
+
try {
|
|
22
|
+
data = JSON.parse(respText);
|
|
23
|
+
} catch {
|
|
24
|
+
console.error(`[${ts()}] [CARDIKT] createCard: invalid JSON response (status=${resp.status}) body=${respText.slice(0, 500)}`);
|
|
25
|
+
fileLog.flush();
|
|
26
|
+
throw new Error(`CardKit create: invalid JSON response (status=${resp.status})`);
|
|
27
|
+
}
|
|
28
|
+
if (data.code !== 0) {
|
|
29
|
+
console.error(`[${ts()}] [CARDIKT] createCard FAIL: status=${resp.status} code=${data.code} msg="${data.msg}" body=${respText.slice(0, 500)}`);
|
|
30
|
+
fileLog.flush();
|
|
31
|
+
throw new Error(`CardKit create: [${data.code}] ${data.msg}`);
|
|
32
|
+
}
|
|
33
|
+
console.log(`[${ts()}] [CARDIKT] createCard OK cardId=${data.data?.card_id ?? "(none)"}`);
|
|
34
|
+
return data.data?.card_id ?? "";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function setCardKitSettings(
|
|
38
|
+
token: string,
|
|
39
|
+
cardId: string,
|
|
40
|
+
settings: Record<string, unknown>,
|
|
41
|
+
sequence: number
|
|
42
|
+
): Promise<void> {
|
|
43
|
+
const resp = await fetch(`${BASE_URL}/cardkit/v1/cards/${cardId}/settings`, {
|
|
44
|
+
method: "PATCH",
|
|
45
|
+
headers: {
|
|
46
|
+
Authorization: `Bearer ${token}`,
|
|
47
|
+
"Content-Type": "application/json",
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify({ settings: JSON.stringify(settings), sequence }),
|
|
50
|
+
});
|
|
51
|
+
const respText = await resp.text();
|
|
52
|
+
let data: { code: number; msg?: string };
|
|
53
|
+
try { data = JSON.parse(respText); } catch {
|
|
54
|
+
console.error(`[${ts()}] [CARDIKT] settings FAIL: invalid JSON cardId=${cardId} seq=${sequence} status=${resp.status}`);
|
|
55
|
+
fileLog.flush();
|
|
56
|
+
throw new Error(`CardKit settings: invalid JSON (status=${resp.status})`);
|
|
57
|
+
}
|
|
58
|
+
if (data.code !== 0) {
|
|
59
|
+
console.error(`[${ts()}] [CARDIKT] settings FAIL: cardId=${cardId} seq=${sequence} status=${resp.status} code=${data.code} msg="${data.msg}"`);
|
|
60
|
+
fileLog.flush();
|
|
61
|
+
throw new Error(`CardKit settings: [${data.code}] ${data.msg}`);
|
|
62
|
+
}
|
|
63
|
+
console.log(`[${ts()}] [CARDIKT] settings OK cardId=${cardId} seq=${sequence}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function streamCardKitElement(
|
|
67
|
+
token: string,
|
|
68
|
+
cardId: string,
|
|
69
|
+
elementId: string,
|
|
70
|
+
content: string,
|
|
71
|
+
sequence: number
|
|
72
|
+
): Promise<void> {
|
|
73
|
+
const resp = await fetch(
|
|
74
|
+
`${BASE_URL}/cardkit/v1/cards/${cardId}/elements/${elementId}/content`,
|
|
75
|
+
{
|
|
76
|
+
method: "PUT",
|
|
77
|
+
headers: {
|
|
78
|
+
Authorization: `Bearer ${token}`,
|
|
79
|
+
"Content-Type": "application/json",
|
|
80
|
+
},
|
|
81
|
+
body: JSON.stringify({ content, sequence }),
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
const respText = await resp.text();
|
|
85
|
+
let data: { code: number; msg?: string };
|
|
86
|
+
try { data = JSON.parse(respText); } catch {
|
|
87
|
+
console.error(`[${ts()}] [CARDIKT] streamElement FAIL: invalid JSON cardId=${cardId} element=${elementId} seq=${sequence} status=${resp.status}`);
|
|
88
|
+
fileLog.flush();
|
|
89
|
+
throw new Error(`CardKit stream: invalid JSON (status=${resp.status})`);
|
|
90
|
+
}
|
|
91
|
+
if (data.code !== 0) {
|
|
92
|
+
console.error(`[${ts()}] [CARDIKT] streamElement FAIL: cardId=${cardId} element=${elementId} seq=${sequence} status=${resp.status} code=${data.code} msg="${data.msg}"`);
|
|
93
|
+
fileLog.flush();
|
|
94
|
+
throw new Error(`CardKit stream: [${data.code}] ${data.msg}`);
|
|
95
|
+
}
|
|
96
|
+
// success log is intentionally sparse — uncomment to debug streaming throughput
|
|
97
|
+
// console.log(`[${ts()}] [CARDIKT] streamElement OK cardId=${cardId} seq=${sequence}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function updateCardKitCard(
|
|
101
|
+
token: string,
|
|
102
|
+
cardId: string,
|
|
103
|
+
cardJson: string,
|
|
104
|
+
sequence: number
|
|
105
|
+
): Promise<void> {
|
|
106
|
+
const resp = await fetch(`${BASE_URL}/cardkit/v1/cards/${cardId}`, {
|
|
107
|
+
method: "PUT",
|
|
108
|
+
headers: {
|
|
109
|
+
Authorization: `Bearer ${token}`,
|
|
110
|
+
"Content-Type": "application/json",
|
|
111
|
+
},
|
|
112
|
+
body: JSON.stringify({ card: { type: "card_json", data: cardJson }, sequence }),
|
|
113
|
+
});
|
|
114
|
+
const respText = await resp.text();
|
|
115
|
+
let data: { code: number; msg?: string };
|
|
116
|
+
try { data = JSON.parse(respText); } catch {
|
|
117
|
+
console.error(`[${ts()}] [CARDIKT] updateCard FAIL: invalid JSON cardId=${cardId} seq=${sequence} status=${resp.status}`);
|
|
118
|
+
fileLog.flush();
|
|
119
|
+
throw new Error(`CardKit update: invalid JSON (status=${resp.status})`);
|
|
120
|
+
}
|
|
121
|
+
if (data.code !== 0) {
|
|
122
|
+
console.error(`[${ts()}] [CARDIKT] updateCard FAIL: cardId=${cardId} seq=${sequence} status=${resp.status} code=${data.code} msg="${data.msg}"`);
|
|
123
|
+
fileLog.flush();
|
|
124
|
+
throw new Error(`CardKit update: [${data.code}] ${data.msg}`);
|
|
125
|
+
}
|
|
126
|
+
console.log(`[${ts()}] [CARDIKT] updateCard OK cardId=${cardId} seq=${sequence}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function sendCardKitMessage(
|
|
130
|
+
token: string,
|
|
131
|
+
chatId: string,
|
|
132
|
+
cardId: string
|
|
133
|
+
): Promise<string> {
|
|
134
|
+
const payload = { receive_id: chatId, msg_type: "interactive", content: JSON.stringify({ type: "card", data: { card_id: cardId } }) };
|
|
135
|
+
const url = `${BASE_URL}/im/v1/messages?receive_id_type=chat_id`;
|
|
136
|
+
const resp = await fetch(url, {
|
|
137
|
+
method: "POST",
|
|
138
|
+
headers: {
|
|
139
|
+
Authorization: `Bearer ${token}`,
|
|
140
|
+
"Content-Type": "application/json",
|
|
141
|
+
},
|
|
142
|
+
body: JSON.stringify(payload),
|
|
143
|
+
});
|
|
144
|
+
const respText = await resp.text();
|
|
145
|
+
let data: { code: number; msg?: string; data?: { message_id?: string } };
|
|
146
|
+
try { data = JSON.parse(respText); } catch {
|
|
147
|
+
console.error(`[${ts()}] [CARDIKT] sendMessage FAIL: invalid JSON cardId=${cardId} chatId=${chatId} status=${resp.status}`);
|
|
148
|
+
fileLog.flush();
|
|
149
|
+
throw new Error(`sendCardKitMessage: invalid JSON (status=${resp.status})`);
|
|
150
|
+
}
|
|
151
|
+
if (data.code !== 0) {
|
|
152
|
+
console.error(`[${ts()}] [CARDIKT] sendMessage FAIL: status=${resp.status} code=${data.code} msg="${data.msg}" cardId=${cardId} chatId=${chatId}`);
|
|
153
|
+
fileLog.flush();
|
|
154
|
+
throw new Error(`[${data.code}] ${data.msg}`);
|
|
155
|
+
}
|
|
156
|
+
console.log(`[${ts()}] [CARDIKT] sendMessage OK cardId=${cardId} chatId=${chatId} msgId=${data.data?.message_id ?? "(none)"}`);
|
|
157
|
+
return data.data?.message_id ?? "";
|
|
158
158
|
}
|