@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,112 @@
1
+ /**
2
+ * 消息格式适配器
3
+ * 负责在 Pufferfish 消息格式和 OpenClaw 消息格式之间转换
4
+ */
5
+ export class MessageAdapter {
6
+ /**
7
+ * 尝试从 Pufferfish 侧透传的 content 中解码 MessageContent(base64(JSON))。
8
+ *
9
+ * Pufferfish 服务端在 WS 转发时会原样透传 `message.Content`,
10
+ * 而该字段在 encryptVersion=0 时是 base64(JSON)。
11
+ *
12
+ * 解码失败则返回 null,调用方应回退使用原始 msg.content。
13
+ */
14
+ tryDecodeMessageContent(content) {
15
+ if (!content)
16
+ return null;
17
+ try {
18
+ const jsonStr = Buffer.from(content, 'base64').toString('utf8');
19
+ return JSON.parse(jsonStr);
20
+ }
21
+ catch (_) {
22
+ return null;
23
+ }
24
+ }
25
+ /**
26
+ * 将 Pufferfish 消息转换为 OpenClaw 格式
27
+ * @param msg Pufferfish 消息对象
28
+ * @returns OpenClaw 消息对象
29
+ */
30
+ toOpenClawMessage(msg) {
31
+ const result = {
32
+ metadata: {
33
+ messageId: msg.messageId,
34
+ chatId: msg.chatId,
35
+ userId: msg.userId,
36
+ timestamp: msg.timestamp,
37
+ ...msg.metadata,
38
+ },
39
+ };
40
+ // 入站 WS 的 msg.content 在 Pufferfish 里通常是 MessageContent(base64(JSON)),
41
+ // 这里先尝试解码,拿到真实 text/url 再映射到 OpenClaw。
42
+ const decoded = this.tryDecodeMessageContent(msg.content);
43
+ switch (msg.type) {
44
+ case 'text':
45
+ // MessageContent: { kind: 'text', text: '...' }
46
+ if (decoded?.kind === 'text' && typeof decoded?.text === 'string') {
47
+ result.text = decoded.text;
48
+ }
49
+ else {
50
+ result.text = msg.content;
51
+ }
52
+ break;
53
+ case 'image':
54
+ // MessageContent: { kind: 'image', mediaList: [{ url: '...' }] }
55
+ const imgUrl = decoded?.kind === 'image'
56
+ ? decoded?.mediaList?.[0]?.url
57
+ : undefined;
58
+ result.imageUrl = imgUrl || msg.content;
59
+ result.text = '[图片]';
60
+ break;
61
+ case 'file':
62
+ // MessageContent: { kind: 'file', file: { url: '...', name: '...' } }
63
+ const fileUrl = decoded?.kind === 'file' ? decoded?.file?.url : undefined;
64
+ const fileNameFromContent = decoded?.kind === 'file' ? decoded?.file?.name : undefined;
65
+ result.fileUrl = fileUrl || msg.content;
66
+ result.fileName =
67
+ fileNameFromContent || msg.metadata?.fileName || 'file';
68
+ result.text = `[文件: ${result.fileName}]`;
69
+ break;
70
+ case 'audio':
71
+ // MessageContent: { kind: 'audio', mediaList: [{ url: '...' }] }
72
+ const audioUrl = decoded?.kind === 'audio' ? decoded?.mediaList?.[0]?.url : undefined;
73
+ result.fileUrl = audioUrl || msg.content;
74
+ result.text = '[语音消息]';
75
+ break;
76
+ case 'video':
77
+ // MessageContent: { kind: 'video', mediaList: [{ url: '...' }] }
78
+ const videoUrl = decoded?.kind === 'video'
79
+ ? decoded?.mediaList?.[0]?.url
80
+ : undefined;
81
+ result.fileUrl = videoUrl || msg.content;
82
+ result.text = '[视频]';
83
+ break;
84
+ default:
85
+ // 兜底:尽量还是给文本,便于 Agent 继续工作
86
+ result.text =
87
+ (decoded?.kind === 'text' && typeof decoded?.text === 'string'
88
+ ? decoded.text
89
+ : msg.content);
90
+ }
91
+ return result;
92
+ }
93
+ /**
94
+ * 从 OpenClaw 消息提取文本内容
95
+ */
96
+ extractText(msg) {
97
+ return msg.text || '';
98
+ }
99
+ /**
100
+ * 检查消息是否包含图片
101
+ */
102
+ hasImage(msg) {
103
+ return !!msg.imageUrl;
104
+ }
105
+ /**
106
+ * 检查消息是否包含文件
107
+ */
108
+ hasFile(msg) {
109
+ return !!msg.fileUrl;
110
+ }
111
+ }
112
+ //# sourceMappingURL=message-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-adapter.js","sourceRoot":"","sources":["../src/message-adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Pufferfish 消息类型定义
3
+ * 从 Pufferfish 服务器接收的消息格式
4
+ */
5
+ export interface PufferfishMessage {
6
+ messageId: string;
7
+ chatId: string;
8
+ userId: number;
9
+ type: 'text' | 'image' | 'file' | 'audio' | 'video';
10
+ content: string;
11
+ metadata?: Record<string, any>;
12
+ timestamp: number;
13
+ isStream?: boolean;
14
+ streamEnd?: boolean;
15
+ }
16
+ /**
17
+ * Pufferfish 账号配置
18
+ * OpenClaw 配置文件中的账号配置项
19
+ */
20
+ export interface PufferfishAccount {
21
+ accountId: string;
22
+ enabled: boolean;
23
+ apiUrl: string;
24
+ wsUrl: string;
25
+ botUserId: number;
26
+ token: string;
27
+ privateKey?: string;
28
+ }
29
+ /**
30
+ * 自动发现配置
31
+ */
32
+ export interface PufferfishDiscoveryConfig {
33
+ enabled: boolean;
34
+ apiUrl: string;
35
+ operatorApiKey: string;
36
+ intervalSeconds?: number;
37
+ /**
38
+ * 用于 discovery 负载均衡的 workerId。
39
+ * 如果不指定,将回退到环境变量 PUFFERFISH_WORKER_ID 或默认值。
40
+ */
41
+ workerId?: string;
42
+ }
43
+ /**
44
+ * OpenClaw 消息格式
45
+ * 传递给 OpenClaw Agent 的消息格式
46
+ */
47
+ export interface OpenClawMessage {
48
+ text?: string;
49
+ imageUrl?: string;
50
+ fileUrl?: string;
51
+ fileName?: string;
52
+ metadata?: Record<string, any>;
53
+ }
54
+ /**
55
+ * 发送消息请求
56
+ * 调用 Pufferfish API 发送消息的请求格式
57
+ */
58
+ export interface SendMessageRequest {
59
+ chatId: string;
60
+ type: 'text' | 'image' | 'file';
61
+ content: string;
62
+ metadata?: Record<string, any>;
63
+ }
64
+ /**
65
+ * 发送消息响应
66
+ * Pufferfish API 返回的响应格式
67
+ */
68
+ export interface SendMessageResponse {
69
+ messageId: string;
70
+ timestamp: number;
71
+ }
72
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,50 @@
1
+ import { EventEmitter } from 'events';
2
+ import type { PufferfishAccount } from './types.js';
3
+ /**
4
+ * Pufferfish WebSocket 客户端
5
+ * 负责与 Pufferfish 服务器建立 WebSocket 长连接,接收实时消息
6
+ *
7
+ * 事件:
8
+ * - 'connected': WebSocket 连接成功
9
+ * - 'disconnected': WebSocket 断开连接
10
+ * - 'message': 收到新消息 (PufferfishMessage)
11
+ * - 'error': 发生错误
12
+ */
13
+ export declare class PufferfishWebSocketClient extends EventEmitter {
14
+ private ws;
15
+ private account;
16
+ private reconnectTimer;
17
+ private heartbeatTimer;
18
+ private isConnecting;
19
+ private shouldReconnect;
20
+ constructor(account: PufferfishAccount);
21
+ private prefix;
22
+ /** discovery 下发新 token 时同步,避免重连仍用旧 token */
23
+ updateAccount(account: PufferfishAccount): void;
24
+ isOpen(): boolean;
25
+ /**
26
+ * 连接到 Pufferfish WebSocket 服务器
27
+ */
28
+ connect(): Promise<void>;
29
+ /**
30
+ * 断开连接
31
+ */
32
+ disconnect(): void;
33
+ /**
34
+ * 发送消息(用于心跳等)
35
+ */
36
+ send(data: any): void;
37
+ /**
38
+ * 启动心跳
39
+ */
40
+ private startHeartbeat;
41
+ /**
42
+ * 停止心跳
43
+ */
44
+ private stopHeartbeat;
45
+ /**
46
+ * 计划重连
47
+ */
48
+ private scheduleReconnect;
49
+ }
50
+ //# sourceMappingURL=websocket-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket-client.d.ts","sourceRoot":"","sources":["../src/websocket-client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,iBAAiB,EAAqB,MAAM,YAAY,CAAC;AAEvE;;;;;;;;;GASG;AACH,qBAAa,yBAA0B,SAAQ,YAAY;IACzD,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,eAAe,CAAQ;gBAEnB,OAAO,EAAE,iBAAiB;IAKtC,OAAO,CAAC,MAAM;IAMd,4CAA4C;IAC5C,aAAa,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI;IAI/C,MAAM,IAAI,OAAO;IAIjB;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAoE9B;;OAEG;IACH,UAAU,IAAI,IAAI;IAelB;;OAEG;IACH,IAAI,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI;IAMrB;;OAEG;IACH,OAAO,CAAC,cAAc;IAStB;;OAEG;IACH,OAAO,CAAC,aAAa;IAOrB;;OAEG;IACH,OAAO,CAAC,iBAAiB;CAa1B"}
@@ -0,0 +1,157 @@
1
+ import WebSocket from 'ws';
2
+ import { EventEmitter } from 'events';
3
+ /**
4
+ * Pufferfish WebSocket 客户端
5
+ * 负责与 Pufferfish 服务器建立 WebSocket 长连接,接收实时消息
6
+ *
7
+ * 事件:
8
+ * - 'connected': WebSocket 连接成功
9
+ * - 'disconnected': WebSocket 断开连接
10
+ * - 'message': 收到新消息 (PufferfishMessage)
11
+ * - 'error': 发生错误
12
+ */
13
+ export class PufferfishWebSocketClient extends EventEmitter {
14
+ ws = null; // WebSocket 连接实例
15
+ account; // 账号配置
16
+ reconnectTimer = null; // 重连定时器
17
+ heartbeatTimer = null; // 心跳定时器
18
+ isConnecting = false; // 是否正在连接中
19
+ shouldReconnect = true; // 是否应该自动重连
20
+ constructor(account) {
21
+ super();
22
+ this.account = account;
23
+ }
24
+ prefix() {
25
+ const botUserId = this.account?.botUserId ?? 0;
26
+ const accountId = this.account?.accountId ?? 'unknown';
27
+ return `[Pufferfish WS] [accountId=${accountId}] [botUserId=${botUserId}]`;
28
+ }
29
+ /** discovery 下发新 token 时同步,避免重连仍用旧 token */
30
+ updateAccount(account) {
31
+ this.account = account;
32
+ }
33
+ isOpen() {
34
+ return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
35
+ }
36
+ /**
37
+ * 连接到 Pufferfish WebSocket 服务器
38
+ */
39
+ async connect() {
40
+ if (this.isConnecting || (this.ws && this.ws.readyState === WebSocket.OPEN)) {
41
+ return;
42
+ }
43
+ this.isConnecting = true;
44
+ const tokenQ = encodeURIComponent(this.account.token);
45
+ const botIdQ = encodeURIComponent(String(this.account.botUserId));
46
+ const wsUrl = `${this.account.wsUrl}?token=${tokenQ}&botId=${botIdQ}`;
47
+ try {
48
+ console.log(`${this.prefix()} Connecting... wsUrl=${this.account.wsUrl}`);
49
+ this.ws = new WebSocket(wsUrl);
50
+ this.ws.on('open', () => {
51
+ this.isConnecting = false;
52
+ this.emit('connected');
53
+ this.startHeartbeat();
54
+ console.log(`${this.prefix()} Connected`);
55
+ });
56
+ this.ws.on('message', (data) => {
57
+ try {
58
+ const raw = data.toString();
59
+ const message = JSON.parse(raw);
60
+ console.log(`${this.prefix()} Inbound message received` +
61
+ ` messageId=${message?.messageId ?? 'unknown'}` +
62
+ ` chatId=${message?.chatId ?? 'unknown'}` +
63
+ ` userId=${message?.userId ?? 'unknown'}` +
64
+ ` type=${message?.type ?? 'unknown'}` +
65
+ ` isStream=${message?.isStream ?? false}` +
66
+ ` streamEnd=${message?.streamEnd ?? false}` +
67
+ ` rawLen=${raw.length}`);
68
+ this.emit('message', message);
69
+ }
70
+ catch (error) {
71
+ const raw = data?.toString?.() ?? '';
72
+ console.error(`${this.prefix()} Failed to parse message:`, error, `rawLen=${raw.length}`, raw);
73
+ }
74
+ });
75
+ this.ws.on('close', () => {
76
+ this.isConnecting = false;
77
+ this.stopHeartbeat();
78
+ this.emit('disconnected');
79
+ console.log(`${this.prefix()} Disconnected`);
80
+ if (this.shouldReconnect) {
81
+ this.scheduleReconnect();
82
+ }
83
+ });
84
+ this.ws.on('error', (error) => {
85
+ this.isConnecting = false;
86
+ console.error(`${this.prefix()} Error:`, error);
87
+ this.emit('error', error);
88
+ });
89
+ this.ws.on('pong', () => {
90
+ // 收到 pong 响应,连接正常
91
+ });
92
+ }
93
+ catch (error) {
94
+ this.isConnecting = false;
95
+ throw error;
96
+ }
97
+ }
98
+ /**
99
+ * 断开连接
100
+ */
101
+ disconnect() {
102
+ this.shouldReconnect = false;
103
+ this.stopHeartbeat();
104
+ if (this.reconnectTimer) {
105
+ clearTimeout(this.reconnectTimer);
106
+ this.reconnectTimer = null;
107
+ }
108
+ if (this.ws) {
109
+ this.ws.close();
110
+ this.ws = null;
111
+ }
112
+ }
113
+ /**
114
+ * 发送消息(用于心跳等)
115
+ */
116
+ send(data) {
117
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
118
+ this.ws.send(JSON.stringify(data));
119
+ }
120
+ }
121
+ /**
122
+ * 启动心跳
123
+ */
124
+ startHeartbeat() {
125
+ this.stopHeartbeat();
126
+ this.heartbeatTimer = setInterval(() => {
127
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
128
+ this.ws.ping();
129
+ }
130
+ }, 30000); // 30秒心跳
131
+ }
132
+ /**
133
+ * 停止心跳
134
+ */
135
+ stopHeartbeat() {
136
+ if (this.heartbeatTimer) {
137
+ clearInterval(this.heartbeatTimer);
138
+ this.heartbeatTimer = null;
139
+ }
140
+ }
141
+ /**
142
+ * 计划重连
143
+ */
144
+ scheduleReconnect() {
145
+ if (this.reconnectTimer) {
146
+ return;
147
+ }
148
+ console.log('[Pufferfish WS] Reconnecting in 3 seconds...');
149
+ this.reconnectTimer = setTimeout(() => {
150
+ this.reconnectTimer = null;
151
+ this.connect().catch((error) => {
152
+ console.error('[Pufferfish WS] Reconnect failed:', error);
153
+ });
154
+ }, 3000);
155
+ }
156
+ }
157
+ //# sourceMappingURL=websocket-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket-client.js","sourceRoot":"","sources":["../src/websocket-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ {
2
+ "id": "pufferfish-channel",
3
+ "name": "Pufferfish IM Channel",
4
+ "description": "OpenClaw channel plugin for Pufferfish IM platform",
5
+ "channels": ["pufferfish"],
6
+ "configSchema": {
7
+ "type": "object",
8
+ "additionalProperties": true,
9
+ "properties": {
10
+ "discovery": {
11
+ "type": "object",
12
+ "additionalProperties": true
13
+ },
14
+ "accounts": {
15
+ "type": "object",
16
+ "additionalProperties": true
17
+ }
18
+ }
19
+ }
20
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@qqvu/openclaw-channel",
3
+ "version": "1.0.0",
4
+ "description": "OpenClaw channel plugin for Pufferfish IM platform",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "author": "Pufferfish Team",
9
+ "license": "MIT",
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch",
13
+ "test": "vitest run"
14
+ },
15
+ "openclaw": {
16
+ "extensions": [
17
+ "./src/index.ts"
18
+ ],
19
+ "channel": {
20
+ "id": "pufferfish",
21
+ "label": "Pufferfish IM",
22
+ "blurb": "Connect to Pufferfish instant messaging platform with AI capabilities"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "package.json",
28
+ "README.md",
29
+ "LICENSE",
30
+ "openclaw.plugin.json"
31
+ ],
32
+ "dependencies": {
33
+ "crypto": "^1.0.1",
34
+ "node-fetch": "^3.3.2",
35
+ "ws": "^8.18.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^22.19.15",
39
+ "@types/ws": "^8.5.12",
40
+ "ts-node": "^10.9.2",
41
+ "typescript": "^5.9.3",
42
+ "vitest": "^2.1.0"
43
+ }
44
+ }