@qqvu/openclaw-channel 1.0.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.
@@ -0,0 +1,77 @@
1
+ import type { PufferfishAccount, SendMessageRequest, SendMessageResponse } from './types.js';
2
+ /**
3
+ * Pufferfish API 客户端
4
+ * 负责与 Pufferfish 服务器的 HTTP API 通信
5
+ */
6
+ export declare class PufferfishAPIClient {
7
+ private baseURL;
8
+ private token;
9
+ private privateKey?;
10
+ private botUserId;
11
+ constructor(account: PufferfishAccount);
12
+ /**
13
+ * 请求全局 Token(用于 Discovery API)
14
+ * @param workerId OpenClaw 实例唯一标识
15
+ * @param apiKey Operator API Key
16
+ * @returns 返回 token 和过期时间
17
+ */
18
+ requestGlobalToken(workerId: string, apiKey: string): Promise<{
19
+ token: string;
20
+ expiresAt: number;
21
+ }>;
22
+ /**
23
+ * 刷新全局 Token
24
+ * @param workerId OpenClaw 实例唯一标识
25
+ * @param accessToken 旧的 token
26
+ * @returns 返回新 token 和过期时间
27
+ */
28
+ refreshGlobalToken(workerId: string, accessToken: string): Promise<{
29
+ token: string;
30
+ expiresAt: number;
31
+ }>;
32
+ /**
33
+ * 自动发现当前用户可用的机器人账号配置
34
+ * @param globalToken 全局 Token(从 requestGlobalToken 获取)
35
+ * @param options 可选参数
36
+ */
37
+ discoverAccounts(globalToken: string, options?: {
38
+ workerId?: string;
39
+ leaseSeconds?: number;
40
+ }): Promise<PufferfishAccount[]>;
41
+ /**
42
+ * 发送消息到 Pufferfish
43
+ */
44
+ sendMessage(params: SendMessageRequest): Promise<SendMessageResponse>;
45
+ /**
46
+ * 发送流式消息到 Pufferfish(支持打字效果)
47
+ * @param params 流式消息参数
48
+ */
49
+ sendStreamMessage(params: {
50
+ chatId: string;
51
+ content: string;
52
+ isStream: boolean;
53
+ streamEnd: boolean;
54
+ }): Promise<SendMessageResponse>;
55
+ /**
56
+ * 上传文件到 Pufferfish OSS
57
+ */
58
+ uploadFile(fileData: Buffer, fileName: string): Promise<string>;
59
+ /**
60
+ * 下载文件
61
+ */
62
+ downloadFile(url: string): Promise<Buffer>;
63
+ /**
64
+ * 请求签名(ECDSA)
65
+ */
66
+ private signRequest;
67
+ private accountBotId;
68
+ /**
69
+ * 将 OpenClaw 的出站消息封装为 Pufferfish 客户端可解析的 MessageContent(base64(json))。
70
+ *
71
+ * Pufferfish 客户端在 encryptVersion=0 时仍会对 content 做 base64Decode + jsonDecode,
72
+ * 所以这里必须发送 base64(JSON.stringify(MessageContent)).
73
+ */
74
+ private encodeMessageContent;
75
+ private toStringMap;
76
+ }
77
+ //# sourceMappingURL=api-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAE7F;;;GAGG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAS;gBAEd,OAAO,EAAE,iBAAiB;IAWtC;;;;;OAKG;IACG,kBAAkB,CACtB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAwBhD;;;;;OAKG;IACG,kBAAkB,CACtB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAwBhD;;;;OAIG;IACG,gBAAgB,CACpB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,GACrD,OAAO,CAAC,iBAAiB,EAAE,CAAC;IA4C/B;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAmC3E;;;OAGG;IACG,iBAAiB,CAAC,MAAM,EAAE;QAC9B,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,OAAO,CAAC;QAClB,SAAS,EAAE,OAAO,CAAC;KACpB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAmChC;;OAEG;IACG,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA8BrE;;OAEG;IACG,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAShD;;OAEG;IACH,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,YAAY;IAIpB;;;;;OAKG;IACH,OAAO,CAAC,oBAAoB;IA6B5B,OAAO,CAAC,WAAW;CAQpB"}
@@ -0,0 +1,265 @@
1
+ import crypto from 'crypto';
2
+ /**
3
+ * Pufferfish API 客户端
4
+ * 负责与 Pufferfish 服务器的 HTTP API 通信
5
+ */
6
+ export class PufferfishAPIClient {
7
+ baseURL; // API 基础URL
8
+ token; // JWT 认证令牌
9
+ privateKey; // ECDSA 私钥(用于签名)
10
+ botUserId; // 机器人用户ID
11
+ constructor(account) {
12
+ // 兼容用户把 apiUrl 配成 https://host/v1 或 https://host/v1/
13
+ // 统一把 baseURL 规范化为不带尾部斜杠、且不带末尾 /v1
14
+ let url = String(account.apiUrl ?? '').trim().replace(/\/+$/, '');
15
+ if (url.endsWith('/v1'))
16
+ url = url.slice(0, -3);
17
+ this.baseURL = url;
18
+ this.token = account.token;
19
+ this.privateKey = account.privateKey;
20
+ this.botUserId = account.botUserId ?? 0;
21
+ }
22
+ /**
23
+ * 请求全局 Token(用于 Discovery API)
24
+ * @param workerId OpenClaw 实例唯一标识
25
+ * @param apiKey Operator API Key
26
+ * @returns 返回 token 和过期时间
27
+ */
28
+ async requestGlobalToken(workerId, apiKey) {
29
+ const response = await fetch(`${this.baseURL}/v1/ai-bot/auth/request-token`, {
30
+ method: 'POST',
31
+ headers: {
32
+ 'Content-Type': 'application/json',
33
+ },
34
+ body: JSON.stringify({ workerId, apiKey }),
35
+ });
36
+ if (!response.ok) {
37
+ const text = await response.text();
38
+ throw new Error(`请求全局 Token 失败: ${response.status} ${text}`);
39
+ }
40
+ const data = await response.json();
41
+ return {
42
+ token: data.accessToken,
43
+ expiresAt: data.expiresAt,
44
+ };
45
+ }
46
+ /**
47
+ * 刷新全局 Token
48
+ * @param workerId OpenClaw 实例唯一标识
49
+ * @param accessToken 旧的 token
50
+ * @returns 返回新 token 和过期时间
51
+ */
52
+ async refreshGlobalToken(workerId, accessToken) {
53
+ const response = await fetch(`${this.baseURL}/v1/ai-bot/auth/refresh-token`, {
54
+ method: 'POST',
55
+ headers: {
56
+ 'Content-Type': 'application/json',
57
+ },
58
+ body: JSON.stringify({ workerId, accessToken }),
59
+ });
60
+ if (!response.ok) {
61
+ const text = await response.text();
62
+ throw new Error(`刷新全局 Token 失败: ${response.status} ${text}`);
63
+ }
64
+ const data = await response.json();
65
+ return {
66
+ token: data.accessToken,
67
+ expiresAt: data.expiresAt,
68
+ };
69
+ }
70
+ /**
71
+ * 自动发现当前用户可用的机器人账号配置
72
+ * @param globalToken 全局 Token(从 requestGlobalToken 获取)
73
+ * @param options 可选参数
74
+ */
75
+ async discoverAccounts(globalToken, options) {
76
+ const params = new URLSearchParams();
77
+ if (options?.workerId)
78
+ params.set('worker_id', options.workerId);
79
+ if (options?.leaseSeconds && options.leaseSeconds > 0) {
80
+ params.set('lease_seconds', String(options.leaseSeconds));
81
+ }
82
+ const query = params.toString();
83
+ const url = `${this.baseURL}/v1/ai-bot/discovery${query ? `?${query}` : ''}`;
84
+ const response = await fetch(url, {
85
+ method: 'GET',
86
+ headers: {
87
+ 'X-Global-Token': globalToken, // 使用全局 Token
88
+ },
89
+ });
90
+ if (!response.ok) {
91
+ const text = await response.text().catch(() => '');
92
+ throw new Error(`Failed to discover accounts: ${response.status} ${response.statusText}${text ? ` - ${text}` : ''}`);
93
+ }
94
+ const data = await response.json();
95
+ return (data.items ?? [])
96
+ .filter((item) => item.enabled && item.apiUrl && item.wsUrl && item.botUserId && item.token)
97
+ .map((item) => ({
98
+ accountId: item.accountId ?? String(item.botUserId),
99
+ enabled: true,
100
+ apiUrl: item.apiUrl,
101
+ wsUrl: item.wsUrl,
102
+ botUserId: Number(item.botUserId),
103
+ token: item.token,
104
+ }));
105
+ }
106
+ /**
107
+ * 发送消息到 Pufferfish
108
+ */
109
+ async sendMessage(params) {
110
+ const encodedContent = this.encodeMessageContent(params.type, params.content, params.metadata);
111
+ console.log(`[Pufferfish API] BotSendMessage content encoded type=${params.type} ` +
112
+ `rawLen=${(params.content ?? '').length} ` +
113
+ `encodedLen=${encodedContent.length} ` +
114
+ `encodedPrefix=${encodedContent.slice(0, 12)}`);
115
+ const body = JSON.stringify({
116
+ botId: this.accountBotId(),
117
+ chatId: params.chatId,
118
+ messageType: params.type,
119
+ content: encodedContent,
120
+ metadata: this.toStringMap(params.metadata),
121
+ });
122
+ const headers = { 'Content-Type': 'application/json' };
123
+ // 如果配置了私钥,添加签名
124
+ if (this.privateKey) {
125
+ headers['X-Signature'] = this.signRequest(body);
126
+ }
127
+ const response = await fetch(`${this.baseURL}/v1.AIBot/BotSendMessage`, {
128
+ method: 'POST',
129
+ headers,
130
+ body,
131
+ });
132
+ if (!response.ok) {
133
+ throw new Error(`Failed to send message: ${response.status} ${response.statusText}`);
134
+ }
135
+ return response.json();
136
+ }
137
+ /**
138
+ * 发送流式消息到 Pufferfish(支持打字效果)
139
+ * @param params 流式消息参数
140
+ */
141
+ async sendStreamMessage(params) {
142
+ const encodedContent = this.encodeMessageContent('text', params.content, {
143
+ isStream: params.isStream,
144
+ streamEnd: params.streamEnd,
145
+ });
146
+ const body = JSON.stringify({
147
+ botId: this.accountBotId(),
148
+ chatId: params.chatId,
149
+ messageType: 'text',
150
+ content: encodedContent,
151
+ metadata: {
152
+ isStream: params.isStream,
153
+ streamEnd: params.streamEnd,
154
+ },
155
+ });
156
+ const headers = { 'Content-Type': 'application/json' };
157
+ if (this.privateKey) {
158
+ headers['X-Signature'] = this.signRequest(body);
159
+ }
160
+ const response = await fetch(`${this.baseURL}/v1.AIBot/BotSendMessage`, {
161
+ method: 'POST',
162
+ headers,
163
+ body,
164
+ });
165
+ if (!response.ok) {
166
+ throw new Error(`Failed to send stream message: ${response.status} ${response.statusText}`);
167
+ }
168
+ return response.json();
169
+ }
170
+ /**
171
+ * 上传文件到 Pufferfish OSS
172
+ */
173
+ async uploadFile(fileData, fileName) {
174
+ // 1. 获取预签名 URL
175
+ const presignedResp = await fetch(`${this.baseURL}/v1/file/presigned`, {
176
+ method: 'POST',
177
+ headers: {
178
+ 'Authorization': `Bearer ${this.token}`,
179
+ 'Content-Type': 'application/json',
180
+ },
181
+ body: JSON.stringify({ fileName }),
182
+ });
183
+ if (!presignedResp.ok) {
184
+ throw new Error('Failed to get presigned URL');
185
+ }
186
+ const { uploadUrl, publicUrl } = await presignedResp.json();
187
+ // 2. 上传文件
188
+ const uploadResp = await fetch(uploadUrl, {
189
+ method: 'PUT',
190
+ body: new Uint8Array(fileData),
191
+ });
192
+ if (!uploadResp.ok) {
193
+ throw new Error('Failed to upload file');
194
+ }
195
+ return publicUrl;
196
+ }
197
+ /**
198
+ * 下载文件
199
+ */
200
+ async downloadFile(url) {
201
+ const response = await fetch(url);
202
+ if (!response.ok) {
203
+ throw new Error(`Failed to download file: ${response.status}`);
204
+ }
205
+ const arrayBuffer = await response.arrayBuffer();
206
+ return Buffer.from(arrayBuffer);
207
+ }
208
+ /**
209
+ * 请求签名(ECDSA)
210
+ */
211
+ signRequest(body) {
212
+ if (!this.privateKey) {
213
+ return '';
214
+ }
215
+ const sign = crypto.createSign('SHA256');
216
+ sign.update(body);
217
+ return sign.sign(this.privateKey, 'base64');
218
+ }
219
+ accountBotId() {
220
+ return this.botUserId;
221
+ }
222
+ /**
223
+ * 将 OpenClaw 的出站消息封装为 Pufferfish 客户端可解析的 MessageContent(base64(json))。
224
+ *
225
+ * Pufferfish 客户端在 encryptVersion=0 时仍会对 content 做 base64Decode + jsonDecode,
226
+ * 所以这里必须发送 base64(JSON.stringify(MessageContent)).
227
+ */
228
+ encodeMessageContent(type, content, metadata) {
229
+ const kind = type;
230
+ const msg = { kind };
231
+ if (type === 'text') {
232
+ msg.text = content;
233
+ }
234
+ else if (type === 'image') {
235
+ msg.mediaList = [{ url: content }];
236
+ if (metadata?.caption) {
237
+ msg.mediaList[0].caption = String(metadata.caption);
238
+ }
239
+ }
240
+ else if (type === 'file') {
241
+ msg.file = {
242
+ url: content,
243
+ name: String(metadata?.fileName ?? 'file'),
244
+ };
245
+ if (metadata?.caption) {
246
+ msg.file.caption = String(metadata.caption);
247
+ }
248
+ }
249
+ else {
250
+ msg.text = content;
251
+ }
252
+ const json = JSON.stringify(msg);
253
+ return Buffer.from(json, 'utf8').toString('base64');
254
+ }
255
+ toStringMap(input) {
256
+ const out = {};
257
+ if (!input)
258
+ return out;
259
+ for (const [k, v] of Object.entries(input)) {
260
+ out[k] = typeof v === 'string' ? v : JSON.stringify(v);
261
+ }
262
+ return out;
263
+ }
264
+ }
265
+ //# sourceMappingURL=api-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ import type { PufferfishAccount } from './types.js';
2
+ export declare function setRuntimeAccount(account: PufferfishAccount): void;
3
+ export declare function removeRuntimeAccount(accountId: string): void;
4
+ /**
5
+ * Pufferfish Channel 定义
6
+ * 实现 OpenClaw Channel 接口,连接到 Pufferfish IM 平台
7
+ */
8
+ export declare const pufferfishChannel: {
9
+ id: string;
10
+ meta: {
11
+ id: string;
12
+ label: string;
13
+ selectionLabel: string;
14
+ docsPath: string;
15
+ blurb: string;
16
+ aliases: string[];
17
+ };
18
+ capabilities: {
19
+ chatTypes: readonly ["direct", "group"];
20
+ media: readonly ["text", "image", "file"];
21
+ mentions: boolean;
22
+ threads: boolean;
23
+ };
24
+ config: {
25
+ listAccountIds: (cfg: any) => string[];
26
+ resolveAccount: (cfg: any, accountId?: string) => PufferfishAccount;
27
+ };
28
+ /**
29
+ * OpenClaw 工具「发消息」解析 to:纯数字或 user: 等形式需映射为服务端 chatId;
30
+ * 否则会走「通讯录」目录匹配,本通道未实现 directory 时会报 Unknown target。
31
+ */
32
+ messaging: {
33
+ normalizeTarget: (raw: string) => string;
34
+ inferTargetChatType: ({ to }: {
35
+ to: string;
36
+ }) => "direct" | "group" | undefined;
37
+ targetResolver: {
38
+ hint: string;
39
+ looksLikeId: (trimmed: string) => boolean;
40
+ resolveTarget: (params: {
41
+ input: string;
42
+ normalized: string;
43
+ preferredKind?: string;
44
+ }) => Promise<{
45
+ to: string;
46
+ kind: string;
47
+ display: string;
48
+ source: "normalized";
49
+ }>;
50
+ };
51
+ };
52
+ outbound: {
53
+ deliveryMode: "direct";
54
+ /**
55
+ * OpenClaw 传入 ctx:`to`(投递目标)、`accountId`、`cfg`;与自定义 chatId / account 兼容。
56
+ */
57
+ sendText: (ctx: any) => Promise<{
58
+ ok: boolean;
59
+ error?: undefined;
60
+ } | {
61
+ ok: boolean;
62
+ error: any;
63
+ }>;
64
+ sendImage: (ctx: any) => Promise<{
65
+ ok: boolean;
66
+ error?: undefined;
67
+ } | {
68
+ ok: boolean;
69
+ error: any;
70
+ }>;
71
+ sendFile: (ctx: any) => Promise<{
72
+ ok: boolean;
73
+ error?: undefined;
74
+ } | {
75
+ ok: boolean;
76
+ error: any;
77
+ }>;
78
+ };
79
+ };
80
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAIpD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAElE;AAED,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE5D;AAkDD;;;GAGG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;8BAwBJ,GAAG;8BAIH,GAAG,cAAc,MAAM,KAAG,iBAAiB;;IAInE;;;OAGG;;+BAEsB,MAAM;sCACC;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE;;;mCAanB,MAAM;oCAOC;gBAC5B,KAAK,EAAE,MAAM,CAAC;gBACd,UAAU,EAAE,MAAM,CAAC;gBACnB,aAAa,CAAC,EAAE,MAAM,CAAC;aACxB;;;;;;;;;;QAwBH;;WAEG;wBACmB,GAAG;;;;;;;yBAwCF,GAAG;;;;;;;wBAwBJ,GAAG;;;;;;;;CA2B5B,CAAC"}
@@ -0,0 +1,226 @@
1
+ import { PufferfishAPIClient } from './api-client.js';
2
+ const runtimeAccounts = new Map();
3
+ export function setRuntimeAccount(account) {
4
+ runtimeAccounts.set(account.accountId, account);
5
+ }
6
+ export function removeRuntimeAccount(accountId) {
7
+ runtimeAccounts.delete(accountId);
8
+ }
9
+ /**
10
+ * 与 Pufferfish 服务端 parseChatId 一致:私聊 user_{id}、群聊 group_{id}
11
+ */
12
+ function normalizePufferfishChatId(raw) {
13
+ const t = raw.trim();
14
+ if (!t)
15
+ return t;
16
+ const userColon = /^user:(\d+)$/i.exec(t);
17
+ if (userColon)
18
+ return `user_${userColon[1]}`;
19
+ if (/^user_\d+$/i.test(t))
20
+ return t;
21
+ const groupColon = /^group:(\d+)$/i.exec(t);
22
+ if (groupColon)
23
+ return `group_${groupColon[1]}`;
24
+ if (/^group_\d+$/i.test(t))
25
+ return t;
26
+ const channelNum = t.match(/^channel:(\d+)$/i);
27
+ if (channelNum)
28
+ return `group_${channelNum[1]}`;
29
+ if (/^\d+$/.test(t))
30
+ return `user_${t}`;
31
+ return t;
32
+ }
33
+ function resolvePufferfishAccountFromConfig(cfg, accountId) {
34
+ const runtime = runtimeAccounts.get(accountId ?? 'default');
35
+ if (runtime) {
36
+ return runtime;
37
+ }
38
+ const account = cfg.channels?.pufferfish?.accounts?.[accountId ?? 'default'];
39
+ return {
40
+ accountId: accountId ?? 'default',
41
+ enabled: account?.enabled ?? true,
42
+ apiUrl: account?.apiUrl ?? 'http://localhost:8080',
43
+ wsUrl: account?.wsUrl ?? 'ws://localhost:8080/v1/ai-bot/ws',
44
+ botUserId: account?.botUserId ?? 0,
45
+ token: account?.token ?? '',
46
+ privateKey: account?.privateKey,
47
+ };
48
+ }
49
+ function resolveOutboundCtx(ctx) {
50
+ const rawTarget = String(ctx.chatId ?? ctx.to ?? '').trim();
51
+ const chatId = normalizePufferfishChatId(rawTarget);
52
+ const account = ctx.account ?? resolvePufferfishAccountFromConfig(ctx.cfg, ctx.accountId);
53
+ return { chatId, account };
54
+ }
55
+ /**
56
+ * Pufferfish Channel 定义
57
+ * 实现 OpenClaw Channel 接口,连接到 Pufferfish IM 平台
58
+ */
59
+ export const pufferfishChannel = {
60
+ id: 'pufferfish',
61
+ // 元数据:Channel 的基本信息
62
+ meta: {
63
+ id: 'pufferfish',
64
+ label: 'Pufferfish IM',
65
+ selectionLabel: 'Pufferfish (即时通讯平台)',
66
+ docsPath: '/channels/pufferfish',
67
+ blurb: 'Connect to Pufferfish instant messaging platform with AI capabilities',
68
+ aliases: ['pf', 'pufferfish-im'],
69
+ },
70
+ // 能力声明:支持的功能
71
+ capabilities: {
72
+ chatTypes: ['direct', 'group'], // 支持私聊和群聊
73
+ media: ['text', 'image', 'file'], // 支持文本、图片、文件
74
+ mentions: true, // 支持 @提及
75
+ threads: false, // 不支持消息线程
76
+ },
77
+ // 配置解析:从 OpenClaw 配置文件读取账号信息
78
+ config: {
79
+ // 列出所有配置的账号ID
80
+ listAccountIds: (cfg) => Object.keys(cfg.channels?.pufferfish?.accounts ?? {}),
81
+ // 解析指定账号的配置
82
+ resolveAccount: (cfg, accountId) => resolvePufferfishAccountFromConfig(cfg, accountId),
83
+ },
84
+ /**
85
+ * OpenClaw 工具「发消息」解析 to:纯数字或 user: 等形式需映射为服务端 chatId;
86
+ * 否则会走「通讯录」目录匹配,本通道未实现 directory 时会报 Unknown target。
87
+ */
88
+ messaging: {
89
+ normalizeTarget: (raw) => normalizePufferfishChatId(raw),
90
+ inferTargetChatType: ({ to }) => {
91
+ const t = String(to ?? '').trim();
92
+ if (/^group_\d+$/i.test(t))
93
+ return 'group';
94
+ if (/^group:\d+$/i.test(t))
95
+ return 'group';
96
+ if (/^channel:\d+$/i.test(t))
97
+ return 'group';
98
+ if (/^user_\d+$/i.test(t))
99
+ return 'direct';
100
+ if (/^user:\d+$/i.test(t))
101
+ return 'direct';
102
+ if (/^\d+$/.test(t))
103
+ return 'direct';
104
+ return undefined;
105
+ },
106
+ targetResolver: {
107
+ hint: '私聊:`user_<用户数字ID>`、`user:<ID>` 或直接写用户数字 ID;群:`group_<群ID>` 或 `group:<ID>`。',
108
+ looksLikeId: (trimmed) => {
109
+ if (/^(user|group)_\d+$/i.test(trimmed))
110
+ return true;
111
+ if (/^(user|group):\d+$/i.test(trimmed))
112
+ return true;
113
+ if (/^channel:\d+$/i.test(trimmed))
114
+ return true;
115
+ if (/^\d+$/.test(trimmed))
116
+ return true;
117
+ return false;
118
+ },
119
+ resolveTarget: async (params) => {
120
+ const raw = (params.normalized?.trim() ? params.normalized : params.input).trim();
121
+ let chatId = normalizePufferfishChatId(raw);
122
+ if (params.preferredKind === 'group' && /^\d+$/.test(raw)) {
123
+ chatId = `group_${raw}`;
124
+ }
125
+ else if (params.preferredKind === 'user' && /^\d+$/.test(raw)) {
126
+ chatId = `user_${raw}`;
127
+ }
128
+ const kind = /^group_/i.test(chatId) ? 'group' : 'user';
129
+ const display = chatId.replace(/^(user|group)_/i, '');
130
+ return {
131
+ to: chatId,
132
+ kind,
133
+ display,
134
+ source: 'normalized',
135
+ };
136
+ },
137
+ },
138
+ },
139
+ // 出站消息处理:AI 发送消息给用户
140
+ outbound: {
141
+ deliveryMode: 'direct', // 直接发送模式
142
+ /**
143
+ * OpenClaw 传入 ctx:`to`(投递目标)、`accountId`、`cfg`;与自定义 chatId / account 兼容。
144
+ */
145
+ sendText: async (ctx) => {
146
+ const { text, streaming } = ctx;
147
+ const { chatId, account } = resolveOutboundCtx(ctx);
148
+ const client = new PufferfishAPIClient(account);
149
+ try {
150
+ if (streaming) {
151
+ const chunkSize = 10;
152
+ const chunks = [];
153
+ for (let i = 0; i < text.length; i += chunkSize) {
154
+ chunks.push(text.substring(0, i + chunkSize));
155
+ }
156
+ for (let i = 0; i < chunks.length; i++) {
157
+ const isLast = i === chunks.length - 1;
158
+ await client.sendStreamMessage({
159
+ chatId,
160
+ content: chunks[i],
161
+ isStream: true,
162
+ streamEnd: isLast,
163
+ });
164
+ if (!isLast) {
165
+ await new Promise(resolve => setTimeout(resolve, 50));
166
+ }
167
+ }
168
+ }
169
+ else {
170
+ await client.sendMessage({
171
+ chatId,
172
+ type: 'text',
173
+ content: text,
174
+ });
175
+ }
176
+ return { ok: true };
177
+ }
178
+ catch (error) {
179
+ console.error('[Pufferfish Channel] Failed to send text:', error);
180
+ return { ok: false, error: error.message };
181
+ }
182
+ },
183
+ sendImage: async (ctx) => {
184
+ const imageUrl = ctx.imageUrl ?? ctx.mediaUrl;
185
+ const { chatId, account } = resolveOutboundCtx(ctx);
186
+ const client = new PufferfishAPIClient(account);
187
+ try {
188
+ const imageData = await client.downloadFile(imageUrl);
189
+ const ossUrl = await client.uploadFile(imageData, 'image.jpg');
190
+ await client.sendMessage({
191
+ chatId,
192
+ type: 'image',
193
+ content: ossUrl,
194
+ });
195
+ return { ok: true };
196
+ }
197
+ catch (error) {
198
+ console.error('[Pufferfish Channel] Failed to send image:', error);
199
+ return { ok: false, error: error.message };
200
+ }
201
+ },
202
+ sendFile: async (ctx) => {
203
+ const { fileUrl, fileName, mediaUrl } = ctx;
204
+ const url = fileUrl ?? mediaUrl;
205
+ const { chatId, account } = resolveOutboundCtx(ctx);
206
+ const client = new PufferfishAPIClient(account);
207
+ try {
208
+ const fileData = await client.downloadFile(url);
209
+ const name = fileName ?? 'file.bin';
210
+ const ossUrl = await client.uploadFile(fileData, name);
211
+ await client.sendMessage({
212
+ chatId,
213
+ type: 'file',
214
+ content: ossUrl,
215
+ metadata: { fileName: name },
216
+ });
217
+ return { ok: true };
218
+ }
219
+ catch (error) {
220
+ console.error('[Pufferfish Channel] Failed to send file:', error);
221
+ return { ok: false, error: error.message };
222
+ }
223
+ },
224
+ },
225
+ };
226
+ //# sourceMappingURL=channel.js.map