@gloablehive/celphone-wechat-plugin 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.
- package/INSTALL.md +231 -0
- package/README.md +259 -0
- package/dist/index-simple.js +9 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +77 -0
- package/dist/mock-server.d.ts +6 -0
- package/dist/mock-server.js +203 -0
- package/dist/openclaw.plugin.json +96 -0
- package/dist/setup-entry.d.ts +9 -0
- package/dist/setup-entry.js +8 -0
- package/dist/src/cache/compactor.d.ts +36 -0
- package/dist/src/cache/compactor.js +154 -0
- package/dist/src/cache/extractor.d.ts +48 -0
- package/dist/src/cache/extractor.js +120 -0
- package/dist/src/cache/index.d.ts +15 -0
- package/dist/src/cache/index.js +16 -0
- package/dist/src/cache/indexer.d.ts +41 -0
- package/dist/src/cache/indexer.js +262 -0
- package/dist/src/cache/manager.d.ts +113 -0
- package/dist/src/cache/manager.js +271 -0
- package/dist/src/cache/message-queue.d.ts +59 -0
- package/dist/src/cache/message-queue.js +147 -0
- package/dist/src/cache/saas-connector.d.ts +94 -0
- package/dist/src/cache/saas-connector.js +289 -0
- package/dist/src/cache/syncer.d.ts +60 -0
- package/dist/src/cache/syncer.js +177 -0
- package/dist/src/cache/types.d.ts +198 -0
- package/dist/src/cache/types.js +43 -0
- package/dist/src/cache/writer.d.ts +81 -0
- package/dist/src/cache/writer.js +461 -0
- package/dist/src/channel.d.ts +65 -0
- package/dist/src/channel.js +334 -0
- package/dist/src/client.d.ts +280 -0
- package/dist/src/client.js +248 -0
- package/index-simple.ts +11 -0
- package/index.ts +89 -0
- package/mock-server.ts +237 -0
- package/openclaw.plugin.json +98 -0
- package/package.json +37 -0
- package/setup-entry.ts +10 -0
- package/src/channel.ts +398 -0
- package/src/client.ts +412 -0
- package/test-cache.ts +260 -0
- package/test-integration.ts +319 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WorkPhone WeChat API Client
|
|
3
|
+
*
|
|
4
|
+
* Interfaces with the WorkPhone API for sending/receiving WeChat messages
|
|
5
|
+
* Based on the Apifox API manual: data/apifox-workphone-3/
|
|
6
|
+
*/
|
|
7
|
+
export class WorkPhoneWeChatClient {
|
|
8
|
+
config;
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this.config = config;
|
|
11
|
+
}
|
|
12
|
+
async request(method, path, body, query) {
|
|
13
|
+
const url = new URL(`${this.config.baseUrl}${path}`);
|
|
14
|
+
if (query) {
|
|
15
|
+
Object.entries(query).forEach(([k, v]) => url.searchParams.append(k, v));
|
|
16
|
+
}
|
|
17
|
+
const headers = {
|
|
18
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
19
|
+
'Content-Type': 'application/json',
|
|
20
|
+
};
|
|
21
|
+
if (this.config.accountId) {
|
|
22
|
+
headers['X-Account-Id'] = this.config.accountId;
|
|
23
|
+
}
|
|
24
|
+
const response = await fetch(url.toString(), {
|
|
25
|
+
method,
|
|
26
|
+
headers,
|
|
27
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
28
|
+
});
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
const error = await response.text();
|
|
31
|
+
throw new Error(`WorkPhone API error: ${response.status} - ${error}`);
|
|
32
|
+
}
|
|
33
|
+
return response.json();
|
|
34
|
+
}
|
|
35
|
+
// ========== WeChat Account Operations ==========
|
|
36
|
+
/**
|
|
37
|
+
* Get all WeChat accounts for an account
|
|
38
|
+
* GET /api/v1/accounts/{accountId}/wechatAccounts
|
|
39
|
+
*/
|
|
40
|
+
async getWeChatAccounts(accountId) {
|
|
41
|
+
return this.request('GET', `/api/v1/accounts/${accountId}/wechatAccounts`);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get WeChat account info by wechatId
|
|
45
|
+
* GET /wp/api/v1/wechatAccounts/{wechatId}/byWechatId
|
|
46
|
+
*/
|
|
47
|
+
async getWeChatAccountById(wechatId) {
|
|
48
|
+
return this.request('GET', `/wp/api/v1/wechatAccounts/${wechatId}/byWechatId`);
|
|
49
|
+
}
|
|
50
|
+
// ========== Friend Operations ==========
|
|
51
|
+
/**
|
|
52
|
+
* Get all friends for a WeChat account
|
|
53
|
+
* GET /wp/api/v1/wechatAccounts/{wechatAccountId}/friends
|
|
54
|
+
*/
|
|
55
|
+
async getFriends(wechatAccountId, options) {
|
|
56
|
+
return this.request('GET', `/wp/api/v1/wechatAccounts/${wechatAccountId}/friends`, undefined, options);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Search friends
|
|
60
|
+
* GET /wp/api/v1/wechatFriends
|
|
61
|
+
*/
|
|
62
|
+
async searchFriends(params) {
|
|
63
|
+
return this.request('GET', '/wp/api/v1/wechatFriends', undefined, params);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get friend by ID
|
|
67
|
+
* GET /wp/api/v1/wechatFriends/{wechatFriendId}
|
|
68
|
+
*/
|
|
69
|
+
async getFriend(wechatFriendId) {
|
|
70
|
+
return this.request('GET', `/wp/api/v1/wechatFriends/${wechatFriendId}`);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Add friend (send friend request)
|
|
74
|
+
* POST /wp/api/v1/wechatFriends/addFriend
|
|
75
|
+
*/
|
|
76
|
+
async addFriend(params) {
|
|
77
|
+
return this.request('POST', '/wp/api/v1/wechatFriends/addFriend', params);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Accept friend request
|
|
81
|
+
* POST /wp/api/v1/wechatFriends/{wechatFriendId}/acceptFriend
|
|
82
|
+
*/
|
|
83
|
+
async acceptFriend(wechatFriendId) {
|
|
84
|
+
return this.request('POST', `/wp/api/v1/wechatFriends/${wechatFriendId}/acceptFriend`);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Update friend remark
|
|
88
|
+
* PUT /wp/api/v1/wechatFriends/{wechatFriendId}/remark
|
|
89
|
+
*/
|
|
90
|
+
async updateFriendRemark(wechatFriendId, remark) {
|
|
91
|
+
return this.request('PUT', `/wp/api/v1/wechatFriends/${wechatFriendId}/remark`, { remark });
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Update friend labels
|
|
95
|
+
* PUT /wp/api/v1/wechatFriends/{wechatFriendId}/label
|
|
96
|
+
*/
|
|
97
|
+
async updateFriendLabels(wechatFriendId, labelIds) {
|
|
98
|
+
return this.request('PUT', `/wp/api/v1/wechatFriends/${wechatFriendId}/label`, { labelIds });
|
|
99
|
+
}
|
|
100
|
+
// ========== Chatroom (Group) Operations ==========
|
|
101
|
+
/**
|
|
102
|
+
* Get all chatrooms for a WeChat account
|
|
103
|
+
* GET /wp/api/v1/wechatAccounts/{wechatAccountId}/chatrooms
|
|
104
|
+
*/
|
|
105
|
+
async getChatrooms(wechatAccountId) {
|
|
106
|
+
return this.request('GET', `/wp/api/v1/wechatAccounts/${wechatAccountId}/chatrooms`);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Get chatroom info by chatroomId
|
|
110
|
+
* GET /wp/api/v1/wechatChatrooms/{wechatChatroomId}
|
|
111
|
+
*/
|
|
112
|
+
async getChatroom(wechatChatroomId) {
|
|
113
|
+
return this.request('GET', `/wp/api/v1/wechatChatrooms/${wechatChatroomId}`);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Update chatroom name (announcement)
|
|
117
|
+
* PUT /wp/api/v1/wechatChatrooms/{wechatChatroomId}/chatroomName
|
|
118
|
+
*/
|
|
119
|
+
async updateChatroomName(wechatChatroomId, name) {
|
|
120
|
+
return this.request('PUT', `/wp/api/v1/wechatChatrooms/${wechatChatroomId}/chatroomName`, { name });
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Publish chatroom announcement
|
|
124
|
+
* PUT /api/v1/wechat/chatroom/announcement
|
|
125
|
+
*/
|
|
126
|
+
async publishChatroomAnnouncement(wechatChatroomId, content) {
|
|
127
|
+
return this.request('PUT', '/api/v1/wechat/chatroom/announcement', {
|
|
128
|
+
wechatChatroomId,
|
|
129
|
+
content,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
// ========== Message Operations ==========
|
|
133
|
+
/**
|
|
134
|
+
* Get friend messages (1-on-1 chat)
|
|
135
|
+
* GET /wp/api/v1/wechat/friendMessages
|
|
136
|
+
*/
|
|
137
|
+
async getFriendMessages(params) {
|
|
138
|
+
return this.request('GET', '/wp/api/v1/wechat/friendMessages', undefined, params);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get chatroom messages (group chat)
|
|
142
|
+
* GET /wp/api/v1/wechat/chatroomMessages
|
|
143
|
+
*/
|
|
144
|
+
async getChatroomMessages(params) {
|
|
145
|
+
return this.request('GET', '/wp/api/v1/wechat/chatroomMessages', undefined, params);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Send friend message (1-on-1)
|
|
149
|
+
* POST /wp/api/v1/wechat/friendMessages
|
|
150
|
+
*/
|
|
151
|
+
async sendFriendMessage(params) {
|
|
152
|
+
return this.request('POST', '/wp/api/v1/wechat/friendMessages', params);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Send chatroom message (group)
|
|
156
|
+
* POST /wp/api/v1/wechat/chatroomMessages
|
|
157
|
+
*/
|
|
158
|
+
async sendChatroomMessage(params) {
|
|
159
|
+
return this.request('POST', '/wp/api/v1/wechat/chatroomMessages', params);
|
|
160
|
+
}
|
|
161
|
+
// ========== Moments (朋友圈) Operations ==========
|
|
162
|
+
/**
|
|
163
|
+
* Get moments
|
|
164
|
+
* GET /wp/api/v1/moments
|
|
165
|
+
*/
|
|
166
|
+
async getMoments(params) {
|
|
167
|
+
return this.request('GET', '/wp/api/v1/moments', undefined, params);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Publish moment (post to Moments)
|
|
171
|
+
* POST /wp/api/v1/moments
|
|
172
|
+
*/
|
|
173
|
+
async publishMoment(params) {
|
|
174
|
+
return this.request('POST', '/wp/api/v1/moments', params);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Comment on a moment
|
|
178
|
+
* POST /wp/api/v1/moments/{momentId}/comment
|
|
179
|
+
*/
|
|
180
|
+
async commentMoment(momentId, content, replyCommentId) {
|
|
181
|
+
return this.request('POST', `/wp/api/v1/moments/${momentId}/comment`, {
|
|
182
|
+
content,
|
|
183
|
+
replyCommentId,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Like a moment
|
|
188
|
+
* POST /wp/api/v1/moments/{momentId}/like
|
|
189
|
+
*/
|
|
190
|
+
async likeMoment(momentId) {
|
|
191
|
+
return this.request('POST', `/wp/api/v1/moments/${momentId}/like`);
|
|
192
|
+
}
|
|
193
|
+
// ========== Media Download ==========
|
|
194
|
+
/**
|
|
195
|
+
* Download friend message file
|
|
196
|
+
*/
|
|
197
|
+
async downloadFriendFile(messageId, wechatTime) {
|
|
198
|
+
const url = `${this.config.baseUrl}/wp/api/v1/wechat/friendMessage/downloadFile/${messageId}/${wechatTime}`;
|
|
199
|
+
const response = await fetch(url, {
|
|
200
|
+
headers: { 'Authorization': `Bearer ${this.config.apiKey}` },
|
|
201
|
+
});
|
|
202
|
+
if (!response.ok) {
|
|
203
|
+
throw new Error(`Download failed: ${response.status}`);
|
|
204
|
+
}
|
|
205
|
+
return response.blob();
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Download chatroom message file
|
|
209
|
+
*/
|
|
210
|
+
async downloadChatroomFile(messageId, wechatTime) {
|
|
211
|
+
const url = `${this.config.baseUrl}/wp/api/v1/wechat/chatroomMessage/downloadFile/${messageId}/${wechatTime}`;
|
|
212
|
+
const response = await fetch(url, {
|
|
213
|
+
headers: { 'Authorization': `Bearer ${this.config.apiKey}` },
|
|
214
|
+
});
|
|
215
|
+
if (!response.ok) {
|
|
216
|
+
throw new Error(`Download failed: ${response.status}`);
|
|
217
|
+
}
|
|
218
|
+
return response.blob();
|
|
219
|
+
}
|
|
220
|
+
// ========== Webhook Parsing ==========
|
|
221
|
+
/**
|
|
222
|
+
* Parse webhook payload from WorkPhone callback
|
|
223
|
+
* This should be called by your webhook endpoint handler
|
|
224
|
+
*/
|
|
225
|
+
parseWebhookPayload(payload) {
|
|
226
|
+
// WorkPhone webhook format - adjust based on actual webhook docs
|
|
227
|
+
const eventMap = {
|
|
228
|
+
'msg': 'message',
|
|
229
|
+
'friend_request': 'friend_request',
|
|
230
|
+
'group_invite': 'group_invite',
|
|
231
|
+
'system': 'system',
|
|
232
|
+
};
|
|
233
|
+
return {
|
|
234
|
+
event: eventMap[payload.event] || 'message',
|
|
235
|
+
accountId: payload.accountId || '',
|
|
236
|
+
wechatAccountId: payload.wechatAccountId || '',
|
|
237
|
+
message: payload.message,
|
|
238
|
+
friendRequest: payload.friendRequest,
|
|
239
|
+
timestamp: payload.timestamp || Date.now(),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Factory function to create a WorkPhone client from OpenClaw config
|
|
245
|
+
*/
|
|
246
|
+
export function createWorkPhoneClient(config) {
|
|
247
|
+
return new WorkPhoneWeChatClient(config);
|
|
248
|
+
}
|
package/index-simple.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw Channel Plugin Entry Point - Simplified Version
|
|
3
|
+
*
|
|
4
|
+
* WorkPhone WeChat Plugin - 最小可运行版本
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { celPhoneWeChatPlugin } from "./src/channel.js";
|
|
8
|
+
|
|
9
|
+
// 直接导出插件,不使用复杂包装
|
|
10
|
+
export default celPhoneWeChatPlugin;
|
|
11
|
+
export { celPhoneWeChatPlugin };
|
package/index.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw Channel Plugin Entry Point
|
|
3
|
+
*
|
|
4
|
+
* WorkPhone WeChat Plugin - enables OpenClaw to send/receive WeChat messages
|
|
5
|
+
* through the WorkPhone API platform.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
|
|
9
|
+
import { celPhoneWeChatPlugin, handleInboundMessage } from "./src/channel.js";
|
|
10
|
+
|
|
11
|
+
export default defineChannelPluginEntry({
|
|
12
|
+
id: "celphone-wechat",
|
|
13
|
+
name: "WorkPhone WeChat",
|
|
14
|
+
description: "Connect OpenClaw to WorkPhone WeChat API for sending and receiving WeChat messages",
|
|
15
|
+
plugin: celPhoneWeChatPlugin,
|
|
16
|
+
|
|
17
|
+
registerCliMetadata(api) {
|
|
18
|
+
api.registerCli(
|
|
19
|
+
({ program }) => {
|
|
20
|
+
program
|
|
21
|
+
.command("celphone-wechat")
|
|
22
|
+
.description("WorkPhone WeChat channel management")
|
|
23
|
+
.option("-a, --account <id>", "WeChat account ID")
|
|
24
|
+
.option("-l, --list", "List configured WeChat accounts");
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
descriptors: [
|
|
28
|
+
{
|
|
29
|
+
name: "celphone-wechat",
|
|
30
|
+
description: "WorkPhone WeChat channel",
|
|
31
|
+
hasSubcommands: true,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
registerFull(api) {
|
|
39
|
+
// Register webhook endpoint for receiving messages from WorkPhone
|
|
40
|
+
api.registerHttpRoute({
|
|
41
|
+
path: "/celphone-wechat/webhook",
|
|
42
|
+
auth: "plugin", // Plugin-managed auth - verify signatures yourself
|
|
43
|
+
handler: async (req, res) => {
|
|
44
|
+
try {
|
|
45
|
+
// Parse the webhook payload
|
|
46
|
+
// The exact format depends on WorkPhone's webhook configuration
|
|
47
|
+
const payload = req.body;
|
|
48
|
+
|
|
49
|
+
// Verify the request is from WorkPhone
|
|
50
|
+
// Add signature verification here if needed
|
|
51
|
+
const signature = req.headers["x-workphone-signature"];
|
|
52
|
+
if (!signature) {
|
|
53
|
+
res.statusCode = 401;
|
|
54
|
+
res.end("Missing signature");
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Handle the inbound message
|
|
59
|
+
await handleInboundMessage(api, payload);
|
|
60
|
+
|
|
61
|
+
res.statusCode = 200;
|
|
62
|
+
res.end("ok");
|
|
63
|
+
return true;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error("Webhook error:", error);
|
|
66
|
+
res.statusCode = 500;
|
|
67
|
+
res.end("Internal error");
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Register gateway method for outbound media handling
|
|
74
|
+
api.registerGatewayMethod({
|
|
75
|
+
method: "POST",
|
|
76
|
+
path: "/celphone-wechat/media",
|
|
77
|
+
handler: async (req, res) => {
|
|
78
|
+
const { messageId, mediaUrl, mediaType } = req.body;
|
|
79
|
+
|
|
80
|
+
// Handle media upload/send through WorkPhone
|
|
81
|
+
// This is called when the bot needs to send media files
|
|
82
|
+
|
|
83
|
+
res.statusCode = 200;
|
|
84
|
+
res.json({ success: true, messageId });
|
|
85
|
+
return true;
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
});
|
package/mock-server.ts
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock WorkPhone API Server for Testing
|
|
3
|
+
*
|
|
4
|
+
* 模拟 WorkPhone 的 API 端点,用于本地测试
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import express from 'express';
|
|
8
|
+
import { createServer } from 'http';
|
|
9
|
+
|
|
10
|
+
const app = express();
|
|
11
|
+
app.use(express.json());
|
|
12
|
+
|
|
13
|
+
// 配置
|
|
14
|
+
const MOCK_CONFIG = {
|
|
15
|
+
apiKey: 'test-api-key-12345',
|
|
16
|
+
wechatAccountId: 'mock-wechat-001',
|
|
17
|
+
wechatId: 'wxid_mock001',
|
|
18
|
+
baseUrl: 'http://localhost:18999',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// 存储消息
|
|
22
|
+
const messages: any[] = [];
|
|
23
|
+
const friends: any[] = [
|
|
24
|
+
{ wechatId: 'wxid_customer001', remark: '客户A', nickname: '张三' },
|
|
25
|
+
{ wechatId: 'wxid_customer002', remark: '客户B', nickname: '李四' },
|
|
26
|
+
{ wechatId: 'wxid_customer003', remark: '潜在客户', nickname: '王五' },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
// ============ API 端点 ============
|
|
30
|
+
|
|
31
|
+
// 健康检查
|
|
32
|
+
app.get('/health', (req, res) => {
|
|
33
|
+
res.json({ status: 'ok' });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// 获取账号列表
|
|
37
|
+
app.get('/api/v1/wechat/accounts', (req, res) => {
|
|
38
|
+
const auth = req.headers.authorization;
|
|
39
|
+
if (auth !== `Bearer ${MOCK_CONFIG.apiKey}`) {
|
|
40
|
+
return res.status(401).json({ error: 'unauthorized' });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
res.json({
|
|
44
|
+
data: [
|
|
45
|
+
{
|
|
46
|
+
accountId: MOCK_CONFIG.wechatAccountId,
|
|
47
|
+
wechatId: MOCK_CONFIG.wechatId,
|
|
48
|
+
nickname: '测试客服',
|
|
49
|
+
status: 'online',
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// 获取好友列表
|
|
56
|
+
app.get(`/api/v1/wechat/accounts/${MOCK_CONFIG.wechatAccountId}/friends`, (req, res) => {
|
|
57
|
+
const auth = req.headers.authorization;
|
|
58
|
+
if (auth !== `Bearer ${MOCK_CONFIG.apiKey}`) {
|
|
59
|
+
return res.status(401).json({ error: 'unauthorized' });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
res.json({ data: friends });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// 发送好友消息
|
|
66
|
+
app.post(`/api/v1/wechat/accounts/${MOCK_CONFIG.wechatAccountId}/messages/friend`, (req, res) => {
|
|
67
|
+
const auth = req.headers.authorization;
|
|
68
|
+
if (auth !== `Bearer ${MOCK_CONFIG.apiKey}`) {
|
|
69
|
+
return res.status(401).json({ error: 'unauthorized' });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const { friendWechatId, content, type } = req.body;
|
|
73
|
+
|
|
74
|
+
console.log(`[Mock] 发送消息给 ${friendWechatId}: ${content}`);
|
|
75
|
+
|
|
76
|
+
const messageId = `msg_${Date.now()}`;
|
|
77
|
+
messages.push({
|
|
78
|
+
messageId,
|
|
79
|
+
friendWechatId,
|
|
80
|
+
content,
|
|
81
|
+
type,
|
|
82
|
+
timestamp: Date.now(),
|
|
83
|
+
direction: 'outbound',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
res.json({
|
|
87
|
+
code: 0,
|
|
88
|
+
message: 'success',
|
|
89
|
+
data: {
|
|
90
|
+
messageId,
|
|
91
|
+
msgSvrId: `svr_${Date.now()}`,
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// 发送群消息
|
|
97
|
+
app.post(`/api/v1/wechat/accounts/${MOCK_CONFIG.wechatAccountId}/messages/chatroom`, (req, res) => {
|
|
98
|
+
const auth = req.headers.authorization;
|
|
99
|
+
if (auth !== `Bearer ${MOCK_CONFIG.apiKey}`) {
|
|
100
|
+
return res.status(401).json({ error: 'unauthorized' });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const { chatroomId, content, type, atUsers } = req.body;
|
|
104
|
+
|
|
105
|
+
console.log(`[Mock] 发送消息到群 ${chatroomId}: ${content}`);
|
|
106
|
+
|
|
107
|
+
const messageId = `msg_${Date.now()}`;
|
|
108
|
+
messages.push({
|
|
109
|
+
messageId,
|
|
110
|
+
chatroomId,
|
|
111
|
+
content,
|
|
112
|
+
type,
|
|
113
|
+
atUsers,
|
|
114
|
+
timestamp: Date.now(),
|
|
115
|
+
direction: 'outbound',
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
res.json({
|
|
119
|
+
code: 0,
|
|
120
|
+
message: 'success',
|
|
121
|
+
data: {
|
|
122
|
+
messageId,
|
|
123
|
+
msgSvrId: `svr_${Date.now()}`,
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Webhook 配置(返回当前 webhook 地址)
|
|
129
|
+
app.get(`/api/v1/wechat/accounts/${MOCK_CONFIG.wechatAccountId}/webhook`, (req, res) => {
|
|
130
|
+
res.json({
|
|
131
|
+
data: {
|
|
132
|
+
url: 'http://localhost:18999/webhook',
|
|
133
|
+
enabled: true,
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// 设置 webhook
|
|
139
|
+
app.put(`/api/v1/wechat/accounts/${MOCK_CONFIG.wechatAccountId}/webhook`, (req, res) => {
|
|
140
|
+
const { url } = req.body;
|
|
141
|
+
console.log(`[Mock] Webhook 设置为: ${url}`);
|
|
142
|
+
res.json({ code: 0, message: 'success' });
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// ============ Webhook 模拟触发 ============
|
|
146
|
+
|
|
147
|
+
// 模拟收到好友消息
|
|
148
|
+
app.post('/mock/trigger/friend-message', (req, res) => {
|
|
149
|
+
const { wechatId, content } = req.body;
|
|
150
|
+
|
|
151
|
+
const payload = {
|
|
152
|
+
event: 'message',
|
|
153
|
+
accountId: MOCK_CONFIG.wechatAccountId,
|
|
154
|
+
wechatAccountId: MOCK_CONFIG.wechatAccountId,
|
|
155
|
+
message: {
|
|
156
|
+
messageId: `in_msg_${Date.now()}`,
|
|
157
|
+
msgSvrId: `svr_${Date.now()}`,
|
|
158
|
+
fromUser: wechatId || 'wxid_customer001',
|
|
159
|
+
toUser: MOCK_CONFIG.wechatId,
|
|
160
|
+
content: content || '你好,这是测试消息',
|
|
161
|
+
type: 1,
|
|
162
|
+
timestamp: Date.now(),
|
|
163
|
+
isSelf: false,
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// 回调(如果配置了 webhook)
|
|
168
|
+
// 实际使用时会让用户配置真实的 webhook URL
|
|
169
|
+
|
|
170
|
+
res.json({
|
|
171
|
+
code: 0,
|
|
172
|
+
message: 'triggered',
|
|
173
|
+
payload
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// 模拟收到群消息
|
|
178
|
+
app.post('/mock/trigger/chatroom-message', (req, res) => {
|
|
179
|
+
const { chatroomId, content } = req.body;
|
|
180
|
+
|
|
181
|
+
const payload = {
|
|
182
|
+
event: 'message',
|
|
183
|
+
accountId: MOCK_CONFIG.wechatAccountId,
|
|
184
|
+
wechatAccountId: MOCK_CONFIG.wechatAccountId,
|
|
185
|
+
message: {
|
|
186
|
+
messageId: `in_msg_${Date.now()}`,
|
|
187
|
+
msgSvrId: `svr_${Date.now()}`,
|
|
188
|
+
fromUser: 'wxid_customer001',
|
|
189
|
+
chatroomId: chatroomId || '123456789@chatroom',
|
|
190
|
+
content: content || '大家好',
|
|
191
|
+
type: 1,
|
|
192
|
+
timestamp: Date.now(),
|
|
193
|
+
isSelf: false,
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
res.json({
|
|
198
|
+
code: 0,
|
|
199
|
+
message: 'triggered',
|
|
200
|
+
payload
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// 获取消息历史
|
|
205
|
+
app.get('/mock/messages', (req, res) => {
|
|
206
|
+
res.json({ data: messages });
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// 启动服务器
|
|
210
|
+
const PORT = 18999;
|
|
211
|
+
const server = createServer(app);
|
|
212
|
+
|
|
213
|
+
server.listen(PORT, () => {
|
|
214
|
+
console.log(`
|
|
215
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
216
|
+
║ Mock WorkPhone API Server ║
|
|
217
|
+
╠═══════════════════════════════════════════════════════════╣
|
|
218
|
+
║ Base URL: http://localhost:${PORT} ║
|
|
219
|
+
║ API Key: ${MOCK_CONFIG.apiKey} ║
|
|
220
|
+
║ WeChat Account: ${MOCK_CONFIG.wechatAccountId} ║
|
|
221
|
+
╠═══════════════════════════════════════════════════════════╣
|
|
222
|
+
║ 测试端点: ║
|
|
223
|
+
║ - POST /mock/trigger/friend-message 模拟好友消息 ║
|
|
224
|
+
║ - POST /mock/trigger/chatroom-message 模拟群消息 ║
|
|
225
|
+
║ - GET /mock/messages 查看消息历史 ║
|
|
226
|
+
╚═══════════════════════════════════════════════════════════╝
|
|
227
|
+
`);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// 优雅关闭
|
|
231
|
+
process.on('SIGINT', () => {
|
|
232
|
+
console.log('\n[Mock] 关闭服务器...');
|
|
233
|
+
server.close(() => {
|
|
234
|
+
console.log('[Mock] 服务器已关闭');
|
|
235
|
+
process.exit(0);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://openclaw.ai/schema/plugin.json",
|
|
3
|
+
"id": "celphone-wechat",
|
|
4
|
+
"name": "WorkPhone WeChat",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"description": "Connect OpenClaw to WorkPhone WeChat API for sending and receiving WeChat messages",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "gloablehive",
|
|
9
|
+
"url": "https://github.com/gloablehive"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/gloablehive/channels/celphone-wechat-plugin"
|
|
14
|
+
},
|
|
15
|
+
"keywords": ["openclaw", "channel", "wechat", "workphone", "messaging"],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"type": "channel",
|
|
18
|
+
"capabilities": {
|
|
19
|
+
"inbound": {
|
|
20
|
+
"message": true,
|
|
21
|
+
"friend_request": true,
|
|
22
|
+
"group_invite": false
|
|
23
|
+
},
|
|
24
|
+
"outbound": {
|
|
25
|
+
"text": true,
|
|
26
|
+
"media": true,
|
|
27
|
+
"link": true,
|
|
28
|
+
"location": true,
|
|
29
|
+
"contact": true
|
|
30
|
+
},
|
|
31
|
+
"features": {
|
|
32
|
+
"pairing": false,
|
|
33
|
+
"reactions": false,
|
|
34
|
+
"editing": false,
|
|
35
|
+
"deleting": false,
|
|
36
|
+
"threads": true
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"config": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"properties": {
|
|
42
|
+
"apiKey": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "WorkPhone API key for authentication",
|
|
45
|
+
"secret": true,
|
|
46
|
+
"required": true
|
|
47
|
+
},
|
|
48
|
+
"baseUrl": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": "WorkPhone API base URL",
|
|
51
|
+
"default": "https://api.workphone.example.com"
|
|
52
|
+
},
|
|
53
|
+
"accountId": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"description": "WorkPhone account ID"
|
|
56
|
+
},
|
|
57
|
+
"wechatAccountId": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"description": "WeChat account ID to use (from WorkPhone) - REQUIRED",
|
|
60
|
+
"required": true
|
|
61
|
+
},
|
|
62
|
+
"wechatId": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"description": "The actual WeChat ID (wxid_xxx) for this account"
|
|
65
|
+
},
|
|
66
|
+
"nickName": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"description": "Display name for this WeChat account"
|
|
69
|
+
},
|
|
70
|
+
"allowFrom": {
|
|
71
|
+
"type": "array",
|
|
72
|
+
"items": { "type": "string" },
|
|
73
|
+
"description": "List of WeChat IDs that can DM the agent",
|
|
74
|
+
"default": []
|
|
75
|
+
},
|
|
76
|
+
"dmSecurity": {
|
|
77
|
+
"type": "string",
|
|
78
|
+
"enum": ["allowlist", "blocklist", "allowall"],
|
|
79
|
+
"description": "DM security policy",
|
|
80
|
+
"default": "allowlist"
|
|
81
|
+
},
|
|
82
|
+
"webhookSecret": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"description": "Secret for verifying webhook requests",
|
|
85
|
+
"secret": true
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"required": ["apiKey", "wechatAccountId"]
|
|
89
|
+
},
|
|
90
|
+
"permissions": {
|
|
91
|
+
"channels": ["celphone-wechat"],
|
|
92
|
+
"http": {
|
|
93
|
+
"outbound": ["*"],
|
|
94
|
+
"inbound": ["/celphone-wechat/webhook"]
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"apiVersion": "1.0.0"
|
|
98
|
+
}
|