@gloablehive/celphone-wechat-plugin 1.0.1 → 1.1.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/dist/index.js +9 -2
- package/dist/src/channel.js +126 -64
- package/dist/src/client-pool.js +82 -0
- package/index.ts +10 -2
- package/package.json +3 -3
- package/src/channel.ts +162 -64
- package/src/client-pool.ts +103 -0
package/dist/index.js
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* WorkPhone WeChat Plugin - enables OpenClaw to send/receive WeChat messages
|
|
5
5
|
* through the WorkPhone API platform.
|
|
6
|
+
*
|
|
7
|
+
* Multi-account support:
|
|
8
|
+
* - Each account in accounts[] can bind to a different agent
|
|
9
|
+
* - Webhook routing uses AccountRegistry to find agentId
|
|
10
|
+
* - cfg is passed to handleInboundMessage for cache isolation
|
|
6
11
|
*/
|
|
7
12
|
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
|
|
8
13
|
import { celPhoneWeChatPlugin, handleInboundMessage } from "./src/channel.js";
|
|
@@ -35,6 +40,8 @@ export default defineChannelPluginEntry({
|
|
|
35
40
|
auth: "plugin", // Plugin-managed auth - verify signatures yourself
|
|
36
41
|
handler: async (req, res) => {
|
|
37
42
|
try {
|
|
43
|
+
// Get config from request (provided by OpenClaw framework)
|
|
44
|
+
const cfg = req.cfg;
|
|
38
45
|
// Parse the webhook payload
|
|
39
46
|
// The exact format depends on WorkPhone's webhook configuration
|
|
40
47
|
const payload = req.body;
|
|
@@ -46,8 +53,8 @@ export default defineChannelPluginEntry({
|
|
|
46
53
|
res.end("Missing signature");
|
|
47
54
|
return true;
|
|
48
55
|
}
|
|
49
|
-
// Handle the inbound message
|
|
50
|
-
await handleInboundMessage(api, payload);
|
|
56
|
+
// Handle the inbound message with cfg for multi-account routing
|
|
57
|
+
await handleInboundMessage(api, payload, cfg);
|
|
51
58
|
res.statusCode = 200;
|
|
52
59
|
res.end("ok");
|
|
53
60
|
return true;
|
package/dist/src/channel.js
CHANGED
|
@@ -9,21 +9,49 @@
|
|
|
9
9
|
* The agent communicates with ALL friends and groups under that WeChat account,
|
|
10
10
|
* not just one DM context. This is "human mode" vs "bot mode".
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
* -
|
|
14
|
-
* -
|
|
15
|
-
* -
|
|
16
|
-
* - 4-layer compression
|
|
17
|
-
* - AI summary extraction
|
|
18
|
-
* - SAAS connectivity + offline fallback
|
|
19
|
-
* - Cloud sync
|
|
12
|
+
* Multi-account support:
|
|
13
|
+
* - Config supports accounts array with per-account credentials
|
|
14
|
+
* - Each account can be bound to a different agent
|
|
15
|
+
* - Security policies are per-account isolated
|
|
20
16
|
*/
|
|
21
17
|
import { createChatChannelPlugin, createChannelPluginBase, } from "openclaw/plugin-sdk/core";
|
|
22
|
-
import { createWorkPhoneClient } from "./client.js";
|
|
23
18
|
// Import cache modules from shared package
|
|
24
|
-
import { createCacheManager, } from "@gloablehive/wechat-cache";
|
|
19
|
+
import { createCacheManager, createAccountRegistry, } from "@gloablehive/wechat-cache";
|
|
20
|
+
// Import client pool
|
|
21
|
+
import { getWorkPhoneClient, createClientConfig } from "./client-pool.js";
|
|
25
22
|
// Cache manager instance (lazy initialized)
|
|
26
23
|
let cacheManager = null;
|
|
24
|
+
// Account registry for routing
|
|
25
|
+
let accountRegistry = null;
|
|
26
|
+
/**
|
|
27
|
+
* Get or create account registry
|
|
28
|
+
*/
|
|
29
|
+
function getAccountRegistry() {
|
|
30
|
+
if (!accountRegistry) {
|
|
31
|
+
accountRegistry = createAccountRegistry();
|
|
32
|
+
}
|
|
33
|
+
return accountRegistry;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Initialize account registry from config
|
|
37
|
+
*/
|
|
38
|
+
function initializeRegistry(cfg) {
|
|
39
|
+
const registry = getAccountRegistry();
|
|
40
|
+
registry.clear();
|
|
41
|
+
const section = cfg.channels?.["celphone-wechat"];
|
|
42
|
+
const accounts = (section?.accounts || []);
|
|
43
|
+
// Register each account
|
|
44
|
+
for (const account of accounts) {
|
|
45
|
+
if (account.enabled !== false) {
|
|
46
|
+
const registeredAccount = {
|
|
47
|
+
...account,
|
|
48
|
+
channelType: "celphone-wechat",
|
|
49
|
+
registeredAt: Date.now(),
|
|
50
|
+
};
|
|
51
|
+
registry.register(registeredAccount);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
27
55
|
/**
|
|
28
56
|
* Get or create cache manager
|
|
29
57
|
*/
|
|
@@ -42,6 +70,8 @@ function getCacheManager(cfg) {
|
|
|
42
70
|
enabled: true,
|
|
43
71
|
});
|
|
44
72
|
}
|
|
73
|
+
// Initialize registry with accounts
|
|
74
|
+
initializeRegistry(cfg);
|
|
45
75
|
const basePath = globalThis?.process?.env?.OPENCLAW_CACHE_PATH
|
|
46
76
|
|| "~/.openclaw/channels/celphone-wechat";
|
|
47
77
|
cacheManager = createCacheManager({
|
|
@@ -58,20 +88,55 @@ function getCacheManager(cfg) {
|
|
|
58
88
|
syncIntervalMs: section.sync.syncIntervalMs || 5 * 60 * 1000,
|
|
59
89
|
} : undefined,
|
|
60
90
|
});
|
|
61
|
-
// Initialize cache manager
|
|
91
|
+
// Initialize cache manager - must await for ready state
|
|
62
92
|
cacheManager.init().catch(err => {
|
|
63
93
|
console.error("[CelPhoneWeChat] Cache manager init failed:", err);
|
|
64
94
|
});
|
|
65
95
|
return cacheManager;
|
|
66
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* List all account IDs from config
|
|
99
|
+
*/
|
|
100
|
+
function listAccountIds(cfg) {
|
|
101
|
+
const section = cfg.channels?.["celphone-wechat"];
|
|
102
|
+
const accounts = (section?.accounts || []);
|
|
103
|
+
if (accounts.length > 0) {
|
|
104
|
+
return accounts.map(a => a.accountId).filter(Boolean);
|
|
105
|
+
}
|
|
106
|
+
// Backward compatibility: single account
|
|
107
|
+
if (section?.wechatAccountId) {
|
|
108
|
+
return [section.accountId || "default"];
|
|
109
|
+
}
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
67
112
|
/**
|
|
68
113
|
* Resolve account from OpenClaw config
|
|
69
114
|
*
|
|
70
|
-
*
|
|
71
|
-
* a real person with all their friends and groups, not a bot.
|
|
115
|
+
* Supports both multi-account (accounts array) and legacy (single) formats.
|
|
72
116
|
*/
|
|
73
117
|
function resolveAccount(cfg, accountId) {
|
|
74
118
|
const section = cfg.channels?.["celphone-wechat"];
|
|
119
|
+
const accounts = (section?.accounts || []);
|
|
120
|
+
// Multi-account mode: look up from accounts array
|
|
121
|
+
if (accounts.length > 0) {
|
|
122
|
+
const targetAccountId = accountId || accounts[0]?.accountId;
|
|
123
|
+
const account = accounts.find(a => a.accountId === targetAccountId);
|
|
124
|
+
if (!account) {
|
|
125
|
+
throw new Error(`celphone-wechat: account not found - ${targetAccountId}`);
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
accountId: account.accountId,
|
|
129
|
+
agentId: account.agentId || null,
|
|
130
|
+
apiKey: account.apiKey || section?.apiKey || "",
|
|
131
|
+
baseUrl: account.baseUrl || section?.baseUrl || "https://api.workphone.example.com",
|
|
132
|
+
wechatAccountId: account.wechatAccountId,
|
|
133
|
+
wechatId: account.wechatId || account.wechatAccountId,
|
|
134
|
+
nickName: account.nickName || "WeChat User",
|
|
135
|
+
allowFrom: account.allowFrom ?? [],
|
|
136
|
+
dmPolicy: account.dmPolicy,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// Legacy single-account mode
|
|
75
140
|
const apiKey = section?.apiKey;
|
|
76
141
|
const baseUrl = section?.baseUrl || "https://api.workphone.example.com";
|
|
77
142
|
const wechatAccountId = section?.wechatAccountId;
|
|
@@ -83,11 +148,12 @@ function resolveAccount(cfg, accountId) {
|
|
|
83
148
|
}
|
|
84
149
|
return {
|
|
85
150
|
accountId: accountId ?? null,
|
|
151
|
+
agentId: null,
|
|
86
152
|
apiKey,
|
|
87
153
|
baseUrl,
|
|
88
154
|
wechatAccountId,
|
|
89
|
-
wechatId: section?.wechatId || wechatAccountId,
|
|
90
|
-
nickName: section?.nickName || "WeChat User",
|
|
155
|
+
wechatId: section?.wechatId || wechatAccountId,
|
|
156
|
+
nickName: section?.nickName || "WeChat User",
|
|
91
157
|
allowFrom: section?.allowFrom ?? [],
|
|
92
158
|
dmPolicy: section?.dmSecurity,
|
|
93
159
|
};
|
|
@@ -122,27 +188,25 @@ export const celPhoneWeChatPlugin = createChatChannelPlugin({
|
|
|
122
188
|
channel: "celphone-wechat",
|
|
123
189
|
sendText: async (params) => {
|
|
124
190
|
const cfg = params.cfg;
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
});
|
|
191
|
+
const accountId = params.accountId;
|
|
192
|
+
// Resolve account to get credentials
|
|
193
|
+
const account = resolveAccount(cfg, accountId);
|
|
194
|
+
// Use client pool for connection reuse
|
|
195
|
+
const clientConfig = createClientConfig(account.baseUrl, account.apiKey, account.accountId || undefined, account.wechatAccountId);
|
|
196
|
+
const client = getWorkPhoneClient(account.accountId || "default", clientConfig);
|
|
132
197
|
const isChatroom = params.to?.includes("@chatroom");
|
|
133
198
|
let result;
|
|
134
199
|
if (isChatroom) {
|
|
135
200
|
result = await client.sendChatroomMessage({
|
|
136
|
-
wechatAccountId:
|
|
201
|
+
wechatAccountId: account.wechatAccountId,
|
|
137
202
|
chatroomId: params.to,
|
|
138
203
|
content: params.text,
|
|
139
204
|
type: "text",
|
|
140
205
|
});
|
|
141
206
|
}
|
|
142
207
|
else {
|
|
143
|
-
// Send to friend (DM)
|
|
144
208
|
result = await client.sendFriendMessage({
|
|
145
|
-
wechatAccountId:
|
|
209
|
+
wechatAccountId: account.wechatAccountId,
|
|
146
210
|
friendWechatId: params.to,
|
|
147
211
|
content: params.text,
|
|
148
212
|
type: "text",
|
|
@@ -150,41 +214,20 @@ export const celPhoneWeChatPlugin = createChatChannelPlugin({
|
|
|
150
214
|
}
|
|
151
215
|
return { messageId: result.messageId };
|
|
152
216
|
},
|
|
153
|
-
// Send media - also support both DM and group
|
|
154
217
|
sendMedia: async (params) => {
|
|
155
218
|
const cfg = params.cfg;
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
});
|
|
219
|
+
const accountId = params.accountId;
|
|
220
|
+
// Resolve account to get credentials
|
|
221
|
+
const account = resolveAccount(cfg, accountId);
|
|
222
|
+
// Use client pool
|
|
223
|
+
const clientConfig = createClientConfig(account.baseUrl, account.apiKey, account.accountId || undefined, account.wechatAccountId);
|
|
224
|
+
const client = getWorkPhoneClient(account.accountId || "default", clientConfig);
|
|
163
225
|
const isChatroom = params.to?.includes("@chatroom");
|
|
164
|
-
|
|
165
|
-
let filePath = "";
|
|
166
|
-
if (params.mediaUrl) {
|
|
167
|
-
filePath = params.mediaUrl;
|
|
168
|
-
}
|
|
169
|
-
else if (params.mediaReadFile) {
|
|
170
|
-
// Read file from local roots
|
|
171
|
-
const roots = params.mediaLocalRoots || ["./"];
|
|
172
|
-
for (const root of roots) {
|
|
173
|
-
try {
|
|
174
|
-
// Need to find the file path from the message
|
|
175
|
-
// In practice, we'd need to look at the attachment info
|
|
176
|
-
filePath = root;
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
catch {
|
|
180
|
-
// Try next root
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
226
|
+
let filePath = params.mediaUrl || "";
|
|
184
227
|
let result;
|
|
185
228
|
if (isChatroom) {
|
|
186
229
|
result = await client.sendChatroomMessage({
|
|
187
|
-
wechatAccountId:
|
|
230
|
+
wechatAccountId: account.wechatAccountId,
|
|
188
231
|
chatroomId: params.to,
|
|
189
232
|
content: filePath,
|
|
190
233
|
type: "file",
|
|
@@ -192,7 +235,7 @@ export const celPhoneWeChatPlugin = createChatChannelPlugin({
|
|
|
192
235
|
}
|
|
193
236
|
else {
|
|
194
237
|
result = await client.sendFriendMessage({
|
|
195
|
-
wechatAccountId:
|
|
238
|
+
wechatAccountId: account.wechatAccountId,
|
|
196
239
|
friendWechatId: params.to,
|
|
197
240
|
content: filePath,
|
|
198
241
|
type: "file",
|
|
@@ -202,8 +245,6 @@ export const celPhoneWeChatPlugin = createChatChannelPlugin({
|
|
|
202
245
|
},
|
|
203
246
|
},
|
|
204
247
|
},
|
|
205
|
-
// Additional capabilities
|
|
206
|
-
// - Describe the channel's message types and features
|
|
207
248
|
capabilities: {
|
|
208
249
|
supportedMessageTypes: [
|
|
209
250
|
"text",
|
|
@@ -214,13 +255,13 @@ export const celPhoneWeChatPlugin = createChatChannelPlugin({
|
|
|
214
255
|
"location",
|
|
215
256
|
"contact",
|
|
216
257
|
],
|
|
217
|
-
maxAttachmentSize: 25 * 1024 * 1024,
|
|
218
|
-
supportsMarkdown: false,
|
|
258
|
+
maxAttachmentSize: 25 * 1024 * 1024,
|
|
259
|
+
supportsMarkdown: false,
|
|
219
260
|
supportsHtml: false,
|
|
220
261
|
supportsEmoji: true,
|
|
221
|
-
supportsReactions: false,
|
|
222
|
-
supportsThreads: true,
|
|
223
|
-
supportsEditing: false,
|
|
262
|
+
supportsReactions: false,
|
|
263
|
+
supportsThreads: true,
|
|
264
|
+
supportsEditing: false,
|
|
224
265
|
supportsDeleting: false,
|
|
225
266
|
},
|
|
226
267
|
});
|
|
@@ -228,6 +269,10 @@ export const celPhoneWeChatPlugin = createChatChannelPlugin({
|
|
|
228
269
|
* Helper function to handle inbound webhook messages
|
|
229
270
|
* This should be called from your HTTP route handler
|
|
230
271
|
*
|
|
272
|
+
* Supports multi-account routing via AccountRegistry:
|
|
273
|
+
* - Looks up agentId from wechatAccountId
|
|
274
|
+
* - Dispatches to correct agent
|
|
275
|
+
*
|
|
231
276
|
* Integrates with Cache Manager for:
|
|
232
277
|
* - Local MD file caching
|
|
233
278
|
* - User profile storage
|
|
@@ -243,6 +288,21 @@ export async function handleInboundMessage(api, payload, cfg) {
|
|
|
243
288
|
const conversationId = isChatroom
|
|
244
289
|
? message.chatroomId
|
|
245
290
|
: message.fromUser || message.toUser || "";
|
|
291
|
+
// Determine the account ID for this message
|
|
292
|
+
// Priority: payload.accountId > wechatAccountId lookup > "default"
|
|
293
|
+
const resolvedAccountId = accountId || wechatAccountId || "default";
|
|
294
|
+
// Look up agentId for routing
|
|
295
|
+
let agentId = null;
|
|
296
|
+
if (cfg) {
|
|
297
|
+
const registry = getAccountRegistry();
|
|
298
|
+
// Try by wechatAccountId first
|
|
299
|
+
agentId = registry.getAgentId(wechatAccountId || "");
|
|
300
|
+
// Fallback to accountId
|
|
301
|
+
if (!agentId && accountId) {
|
|
302
|
+
const regAccount = registry.getByAccountId(accountId);
|
|
303
|
+
agentId = regAccount?.agentId || null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
246
306
|
// Convert to cache format and store locally
|
|
247
307
|
if (cfg) {
|
|
248
308
|
try {
|
|
@@ -250,7 +310,7 @@ export async function handleInboundMessage(api, payload, cfg) {
|
|
|
250
310
|
const wechatMessage = {
|
|
251
311
|
messageId: message.messageId,
|
|
252
312
|
msgSvrId: message.msgSvrId,
|
|
253
|
-
accountId:
|
|
313
|
+
accountId: resolvedAccountId,
|
|
254
314
|
conversationType: isChatroom ? "chatroom" : "friend",
|
|
255
315
|
conversationId,
|
|
256
316
|
senderId: message.fromUser || "",
|
|
@@ -285,6 +345,8 @@ export async function handleInboundMessage(api, payload, cfg) {
|
|
|
285
345
|
},
|
|
286
346
|
timestamp: new Date(message.timestamp || Date.now()),
|
|
287
347
|
isSelf: message.isSelf || false,
|
|
348
|
+
agentId, // Include agentId for routing
|
|
349
|
+
accountId: resolvedAccountId,
|
|
288
350
|
};
|
|
289
351
|
// Dispatch to OpenClaw
|
|
290
352
|
await api.inbound.dispatchMessage(openclawMessage);
|
|
@@ -296,7 +358,7 @@ export async function handleInboundMessage(api, payload, cfg) {
|
|
|
296
358
|
platformId: friendRequest.fromUser,
|
|
297
359
|
scene: friendRequest.scene,
|
|
298
360
|
ticket: friendRequest.ticket,
|
|
299
|
-
accountId,
|
|
361
|
+
accountId: accountId || wechatAccountId,
|
|
300
362
|
wechatAccountId,
|
|
301
363
|
});
|
|
302
364
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client Pool - Per-account client management
|
|
3
|
+
*
|
|
4
|
+
* Manages WorkPhone API clients per account to avoid creating
|
|
5
|
+
* new clients for each message and ensure credential isolation.
|
|
6
|
+
*/
|
|
7
|
+
import { WorkPhoneWeChatClient } from "./client.js";
|
|
8
|
+
/**
|
|
9
|
+
* Client pool for WorkPhone WeChat clients
|
|
10
|
+
*/
|
|
11
|
+
class ClientPool {
|
|
12
|
+
clients = new Map();
|
|
13
|
+
/**
|
|
14
|
+
* Get or create a client for the given account
|
|
15
|
+
*/
|
|
16
|
+
getClient(accountId, config) {
|
|
17
|
+
if (this.clients.has(accountId)) {
|
|
18
|
+
return this.clients.get(accountId);
|
|
19
|
+
}
|
|
20
|
+
const client = new WorkPhoneWeChatClient(config);
|
|
21
|
+
this.clients.set(accountId, client);
|
|
22
|
+
return client;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Check if client exists
|
|
26
|
+
*/
|
|
27
|
+
hasClient(accountId) {
|
|
28
|
+
return this.clients.has(accountId);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Remove a client by accountId
|
|
32
|
+
*/
|
|
33
|
+
removeClient(accountId) {
|
|
34
|
+
return this.clients.delete(accountId);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Clear all clients
|
|
38
|
+
*/
|
|
39
|
+
clear() {
|
|
40
|
+
this.clients.clear();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get all account IDs with active clients
|
|
44
|
+
*/
|
|
45
|
+
getActiveAccountIds() {
|
|
46
|
+
return Array.from(this.clients.keys());
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get pool size
|
|
50
|
+
*/
|
|
51
|
+
get size() {
|
|
52
|
+
return this.clients.size;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Singleton instance
|
|
56
|
+
let clientPool = null;
|
|
57
|
+
/**
|
|
58
|
+
* Get or create the client pool singleton
|
|
59
|
+
*/
|
|
60
|
+
export function getClientPool() {
|
|
61
|
+
if (!clientPool) {
|
|
62
|
+
clientPool = new ClientPool();
|
|
63
|
+
}
|
|
64
|
+
return clientPool;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get a client for a specific account
|
|
68
|
+
*/
|
|
69
|
+
export function getWorkPhoneClient(accountId, config) {
|
|
70
|
+
return getClientPool().getClient(accountId, config);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Create a client config from resolved account
|
|
74
|
+
*/
|
|
75
|
+
export function createClientConfig(baseUrl, apiKey, accountId, wechatAccountId) {
|
|
76
|
+
return {
|
|
77
|
+
baseUrl,
|
|
78
|
+
apiKey,
|
|
79
|
+
accountId,
|
|
80
|
+
wechatAccountId,
|
|
81
|
+
};
|
|
82
|
+
}
|
package/index.ts
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* WorkPhone WeChat Plugin - enables OpenClaw to send/receive WeChat messages
|
|
5
5
|
* through the WorkPhone API platform.
|
|
6
|
+
*
|
|
7
|
+
* Multi-account support:
|
|
8
|
+
* - Each account in accounts[] can bind to a different agent
|
|
9
|
+
* - Webhook routing uses AccountRegistry to find agentId
|
|
10
|
+
* - cfg is passed to handleInboundMessage for cache isolation
|
|
6
11
|
*/
|
|
7
12
|
|
|
8
13
|
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
|
|
@@ -42,6 +47,9 @@ export default defineChannelPluginEntry({
|
|
|
42
47
|
auth: "plugin", // Plugin-managed auth - verify signatures yourself
|
|
43
48
|
handler: async (req, res) => {
|
|
44
49
|
try {
|
|
50
|
+
// Get config from request (provided by OpenClaw framework)
|
|
51
|
+
const cfg = (req as any).cfg;
|
|
52
|
+
|
|
45
53
|
// Parse the webhook payload
|
|
46
54
|
// The exact format depends on WorkPhone's webhook configuration
|
|
47
55
|
const payload = (req as any).body;
|
|
@@ -55,8 +63,8 @@ export default defineChannelPluginEntry({
|
|
|
55
63
|
return true;
|
|
56
64
|
}
|
|
57
65
|
|
|
58
|
-
// Handle the inbound message
|
|
59
|
-
await handleInboundMessage(api, payload);
|
|
66
|
+
// Handle the inbound message with cfg for multi-account routing
|
|
67
|
+
await handleInboundMessage(api, payload, cfg);
|
|
60
68
|
|
|
61
69
|
res.statusCode = 200;
|
|
62
70
|
res.end("ok");
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gloablehive/celphone-wechat-plugin",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "OpenClaw channel plugin for workphone-wechat API - enables sending/receiving WeChat messages through workphone",
|
|
5
|
+
"description": "OpenClaw channel plugin for workphone-wechat API - enables sending/receiving WeChat messages through workphone. Supports multi-account with per-account agent binding.",
|
|
6
6
|
"main": "index.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test": "npx tsx test-cache.ts",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@gloablehive/wechat-cache": "^1.0.
|
|
27
|
+
"@gloablehive/wechat-cache": "^1.0.2",
|
|
28
28
|
"openclaw": ">=1.0.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
package/src/channel.ts
CHANGED
|
@@ -9,14 +9,10 @@
|
|
|
9
9
|
* The agent communicates with ALL friends and groups under that WeChat account,
|
|
10
10
|
* not just one DM context. This is "human mode" vs "bot mode".
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
* -
|
|
14
|
-
* -
|
|
15
|
-
* -
|
|
16
|
-
* - 4-layer compression
|
|
17
|
-
* - AI summary extraction
|
|
18
|
-
* - SAAS connectivity + offline fallback
|
|
19
|
-
* - Cloud sync
|
|
12
|
+
* Multi-account support:
|
|
13
|
+
* - Config supports accounts array with per-account credentials
|
|
14
|
+
* - Each account can be bound to a different agent
|
|
15
|
+
* - Security policies are per-account isolated
|
|
20
16
|
*/
|
|
21
17
|
|
|
22
18
|
import {
|
|
@@ -24,7 +20,7 @@ import {
|
|
|
24
20
|
createChannelPluginBase,
|
|
25
21
|
} from "openclaw/plugin-sdk/core";
|
|
26
22
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
|
|
27
|
-
import {
|
|
23
|
+
import type { WebhookPayload } from "./client.js";
|
|
28
24
|
|
|
29
25
|
// Import cache modules from shared package
|
|
30
26
|
import {
|
|
@@ -32,11 +28,53 @@ import {
|
|
|
32
28
|
CacheManager,
|
|
33
29
|
WeChatAccount,
|
|
34
30
|
WeChatMessage,
|
|
31
|
+
AccountRegistry,
|
|
32
|
+
createAccountRegistry,
|
|
33
|
+
type RegisteredAccount,
|
|
35
34
|
} from "@gloablehive/wechat-cache";
|
|
36
35
|
|
|
36
|
+
// Import client pool
|
|
37
|
+
import { getWorkPhoneClient, createClientConfig, getClientPool } from "./client-pool.js";
|
|
38
|
+
|
|
37
39
|
// Cache manager instance (lazy initialized)
|
|
38
40
|
let cacheManager: CacheManager | null = null;
|
|
39
41
|
|
|
42
|
+
// Account registry for routing
|
|
43
|
+
let accountRegistry: AccountRegistry | null = null;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get or create account registry
|
|
47
|
+
*/
|
|
48
|
+
function getAccountRegistry(): AccountRegistry {
|
|
49
|
+
if (!accountRegistry) {
|
|
50
|
+
accountRegistry = createAccountRegistry();
|
|
51
|
+
}
|
|
52
|
+
return accountRegistry;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Initialize account registry from config
|
|
57
|
+
*/
|
|
58
|
+
function initializeRegistry(cfg: OpenClawConfig): void {
|
|
59
|
+
const registry = getAccountRegistry();
|
|
60
|
+
registry.clear();
|
|
61
|
+
|
|
62
|
+
const section = (cfg.channels as Record<string, any>)?.["celphone-wechat"];
|
|
63
|
+
const accounts = (section?.accounts || []) as WeChatAccount[];
|
|
64
|
+
|
|
65
|
+
// Register each account
|
|
66
|
+
for (const account of accounts) {
|
|
67
|
+
if (account.enabled !== false) {
|
|
68
|
+
const registeredAccount: RegisteredAccount = {
|
|
69
|
+
...account,
|
|
70
|
+
channelType: "celphone-wechat",
|
|
71
|
+
registeredAt: Date.now(),
|
|
72
|
+
};
|
|
73
|
+
registry.register(registeredAccount);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
40
78
|
/**
|
|
41
79
|
* Get or create cache manager
|
|
42
80
|
*/
|
|
@@ -57,6 +95,9 @@ function getCacheManager(cfg: OpenClawConfig): CacheManager {
|
|
|
57
95
|
});
|
|
58
96
|
}
|
|
59
97
|
|
|
98
|
+
// Initialize registry with accounts
|
|
99
|
+
initializeRegistry(cfg);
|
|
100
|
+
|
|
60
101
|
const basePath = (globalThis as any)?.process?.env?.OPENCLAW_CACHE_PATH
|
|
61
102
|
|| "~/.openclaw/channels/celphone-wechat";
|
|
62
103
|
|
|
@@ -75,7 +116,7 @@ function getCacheManager(cfg: OpenClawConfig): CacheManager {
|
|
|
75
116
|
} : undefined,
|
|
76
117
|
});
|
|
77
118
|
|
|
78
|
-
// Initialize cache manager
|
|
119
|
+
// Initialize cache manager - must await for ready state
|
|
79
120
|
cacheManager.init().catch(err => {
|
|
80
121
|
console.error("[CelPhoneWeChat] Cache manager init failed:", err);
|
|
81
122
|
});
|
|
@@ -85,26 +126,70 @@ function getCacheManager(cfg: OpenClawConfig): CacheManager {
|
|
|
85
126
|
|
|
86
127
|
export interface CelPhoneWeChatResolvedAccount {
|
|
87
128
|
accountId: string | null;
|
|
129
|
+
agentId: string | null;
|
|
88
130
|
apiKey: string;
|
|
89
131
|
baseUrl: string;
|
|
90
|
-
wechatAccountId: string;
|
|
91
|
-
wechatId: string;
|
|
92
|
-
nickName: string;
|
|
132
|
+
wechatAccountId: string;
|
|
133
|
+
wechatId: string;
|
|
134
|
+
nickName: string;
|
|
93
135
|
allowFrom: string[];
|
|
94
136
|
dmPolicy: string | undefined;
|
|
95
137
|
}
|
|
96
138
|
|
|
139
|
+
/**
|
|
140
|
+
* List all account IDs from config
|
|
141
|
+
*/
|
|
142
|
+
function listAccountIds(cfg: OpenClawConfig): string[] {
|
|
143
|
+
const section = (cfg.channels as Record<string, any>)?.["celphone-wechat"];
|
|
144
|
+
const accounts = (section?.accounts || []) as WeChatAccount[];
|
|
145
|
+
|
|
146
|
+
if (accounts.length > 0) {
|
|
147
|
+
return accounts.map(a => a.accountId).filter(Boolean);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Backward compatibility: single account
|
|
151
|
+
if (section?.wechatAccountId) {
|
|
152
|
+
return [section.accountId || "default"];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
|
|
97
158
|
/**
|
|
98
159
|
* Resolve account from OpenClaw config
|
|
99
160
|
*
|
|
100
|
-
*
|
|
101
|
-
* a real person with all their friends and groups, not a bot.
|
|
161
|
+
* Supports both multi-account (accounts array) and legacy (single) formats.
|
|
102
162
|
*/
|
|
103
163
|
function resolveAccount(
|
|
104
164
|
cfg: OpenClawConfig,
|
|
105
165
|
accountId?: string | null
|
|
106
166
|
): CelPhoneWeChatResolvedAccount {
|
|
107
167
|
const section = (cfg.channels as Record<string, any>)?.["celphone-wechat"];
|
|
168
|
+
const accounts = (section?.accounts || []) as WeChatAccount[];
|
|
169
|
+
|
|
170
|
+
// Multi-account mode: look up from accounts array
|
|
171
|
+
if (accounts.length > 0) {
|
|
172
|
+
const targetAccountId = accountId || accounts[0]?.accountId;
|
|
173
|
+
const account = accounts.find(a => a.accountId === targetAccountId);
|
|
174
|
+
|
|
175
|
+
if (!account) {
|
|
176
|
+
throw new Error(`celphone-wechat: account not found - ${targetAccountId}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
accountId: account.accountId,
|
|
181
|
+
agentId: account.agentId || null,
|
|
182
|
+
apiKey: account.apiKey || section?.apiKey || "",
|
|
183
|
+
baseUrl: account.baseUrl || section?.baseUrl || "https://api.workphone.example.com",
|
|
184
|
+
wechatAccountId: account.wechatAccountId,
|
|
185
|
+
wechatId: account.wechatId || account.wechatAccountId,
|
|
186
|
+
nickName: account.nickName || "WeChat User",
|
|
187
|
+
allowFrom: account.allowFrom ?? [],
|
|
188
|
+
dmPolicy: account.dmPolicy,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Legacy single-account mode
|
|
108
193
|
const apiKey = section?.apiKey;
|
|
109
194
|
const baseUrl = section?.baseUrl || "https://api.workphone.example.com";
|
|
110
195
|
const wechatAccountId = section?.wechatAccountId;
|
|
@@ -119,11 +204,12 @@ function resolveAccount(
|
|
|
119
204
|
|
|
120
205
|
return {
|
|
121
206
|
accountId: accountId ?? null,
|
|
207
|
+
agentId: null,
|
|
122
208
|
apiKey,
|
|
123
209
|
baseUrl,
|
|
124
210
|
wechatAccountId,
|
|
125
|
-
wechatId: section?.wechatId || wechatAccountId,
|
|
126
|
-
nickName: section?.nickName || "WeChat User",
|
|
211
|
+
wechatId: section?.wechatId || wechatAccountId,
|
|
212
|
+
nickName: section?.nickName || "WeChat User",
|
|
127
213
|
allowFrom: section?.allowFrom ?? [],
|
|
128
214
|
dmPolicy: section?.dmSecurity,
|
|
129
215
|
};
|
|
@@ -162,29 +248,33 @@ export const celPhoneWeChatPlugin = createChatChannelPlugin<CelPhoneWeChatResolv
|
|
|
162
248
|
channel: "celphone-wechat",
|
|
163
249
|
sendText: async (params: any) => {
|
|
164
250
|
const cfg = params.cfg;
|
|
165
|
-
const
|
|
251
|
+
const accountId = params.accountId;
|
|
252
|
+
|
|
253
|
+
// Resolve account to get credentials
|
|
254
|
+
const account = resolveAccount(cfg, accountId);
|
|
166
255
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
256
|
+
// Use client pool for connection reuse
|
|
257
|
+
const clientConfig = createClientConfig(
|
|
258
|
+
account.baseUrl,
|
|
259
|
+
account.apiKey,
|
|
260
|
+
account.accountId || undefined,
|
|
261
|
+
account.wechatAccountId
|
|
262
|
+
);
|
|
263
|
+
const client = getWorkPhoneClient(account.accountId || "default", clientConfig);
|
|
173
264
|
|
|
174
265
|
const isChatroom = params.to?.includes("@chatroom");
|
|
175
266
|
|
|
176
267
|
let result;
|
|
177
268
|
if (isChatroom) {
|
|
178
269
|
result = await client.sendChatroomMessage({
|
|
179
|
-
wechatAccountId:
|
|
270
|
+
wechatAccountId: account.wechatAccountId,
|
|
180
271
|
chatroomId: params.to,
|
|
181
272
|
content: params.text,
|
|
182
273
|
type: "text",
|
|
183
274
|
});
|
|
184
275
|
} else {
|
|
185
|
-
// Send to friend (DM)
|
|
186
276
|
result = await client.sendFriendMessage({
|
|
187
|
-
wechatAccountId:
|
|
277
|
+
wechatAccountId: account.wechatAccountId,
|
|
188
278
|
friendWechatId: params.to,
|
|
189
279
|
content: params.text,
|
|
190
280
|
type: "text",
|
|
@@ -194,50 +284,37 @@ export const celPhoneWeChatPlugin = createChatChannelPlugin<CelPhoneWeChatResolv
|
|
|
194
284
|
return { messageId: result.messageId };
|
|
195
285
|
},
|
|
196
286
|
|
|
197
|
-
// Send media - also support both DM and group
|
|
198
287
|
sendMedia: async (params: any) => {
|
|
199
288
|
const cfg = params.cfg;
|
|
200
|
-
const
|
|
289
|
+
const accountId = params.accountId;
|
|
201
290
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
291
|
+
// Resolve account to get credentials
|
|
292
|
+
const account = resolveAccount(cfg, accountId);
|
|
293
|
+
|
|
294
|
+
// Use client pool
|
|
295
|
+
const clientConfig = createClientConfig(
|
|
296
|
+
account.baseUrl,
|
|
297
|
+
account.apiKey,
|
|
298
|
+
account.accountId || undefined,
|
|
299
|
+
account.wechatAccountId
|
|
300
|
+
);
|
|
301
|
+
const client = getWorkPhoneClient(account.accountId || "default", clientConfig);
|
|
208
302
|
|
|
209
303
|
const isChatroom = params.to?.includes("@chatroom");
|
|
210
304
|
|
|
211
|
-
|
|
212
|
-
let filePath = "";
|
|
213
|
-
if (params.mediaUrl) {
|
|
214
|
-
filePath = params.mediaUrl;
|
|
215
|
-
} else if (params.mediaReadFile) {
|
|
216
|
-
// Read file from local roots
|
|
217
|
-
const roots = params.mediaLocalRoots || ["./"];
|
|
218
|
-
for (const root of roots) {
|
|
219
|
-
try {
|
|
220
|
-
// Need to find the file path from the message
|
|
221
|
-
// In practice, we'd need to look at the attachment info
|
|
222
|
-
filePath = root;
|
|
223
|
-
break;
|
|
224
|
-
} catch {
|
|
225
|
-
// Try next root
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
305
|
+
let filePath = params.mediaUrl || "";
|
|
229
306
|
|
|
230
307
|
let result;
|
|
231
308
|
if (isChatroom) {
|
|
232
309
|
result = await client.sendChatroomMessage({
|
|
233
|
-
wechatAccountId:
|
|
310
|
+
wechatAccountId: account.wechatAccountId,
|
|
234
311
|
chatroomId: params.to,
|
|
235
312
|
content: filePath,
|
|
236
313
|
type: "file",
|
|
237
314
|
});
|
|
238
315
|
} else {
|
|
239
316
|
result = await client.sendFriendMessage({
|
|
240
|
-
wechatAccountId:
|
|
317
|
+
wechatAccountId: account.wechatAccountId,
|
|
241
318
|
friendWechatId: params.to,
|
|
242
319
|
content: filePath,
|
|
243
320
|
type: "file",
|
|
@@ -249,8 +326,6 @@ export const celPhoneWeChatPlugin = createChatChannelPlugin<CelPhoneWeChatResolv
|
|
|
249
326
|
},
|
|
250
327
|
} as any,
|
|
251
328
|
|
|
252
|
-
// Additional capabilities
|
|
253
|
-
// - Describe the channel's message types and features
|
|
254
329
|
capabilities: {
|
|
255
330
|
supportedMessageTypes: [
|
|
256
331
|
"text",
|
|
@@ -261,13 +336,13 @@ export const celPhoneWeChatPlugin = createChatChannelPlugin<CelPhoneWeChatResolv
|
|
|
261
336
|
"location",
|
|
262
337
|
"contact",
|
|
263
338
|
],
|
|
264
|
-
maxAttachmentSize: 25 * 1024 * 1024,
|
|
265
|
-
supportsMarkdown: false,
|
|
339
|
+
maxAttachmentSize: 25 * 1024 * 1024,
|
|
340
|
+
supportsMarkdown: false,
|
|
266
341
|
supportsHtml: false,
|
|
267
342
|
supportsEmoji: true,
|
|
268
|
-
supportsReactions: false,
|
|
269
|
-
supportsThreads: true,
|
|
270
|
-
supportsEditing: false,
|
|
343
|
+
supportsReactions: false,
|
|
344
|
+
supportsThreads: true,
|
|
345
|
+
supportsEditing: false,
|
|
271
346
|
supportsDeleting: false,
|
|
272
347
|
},
|
|
273
348
|
} as any);
|
|
@@ -276,6 +351,10 @@ export const celPhoneWeChatPlugin = createChatChannelPlugin<CelPhoneWeChatResolv
|
|
|
276
351
|
* Helper function to handle inbound webhook messages
|
|
277
352
|
* This should be called from your HTTP route handler
|
|
278
353
|
*
|
|
354
|
+
* Supports multi-account routing via AccountRegistry:
|
|
355
|
+
* - Looks up agentId from wechatAccountId
|
|
356
|
+
* - Dispatches to correct agent
|
|
357
|
+
*
|
|
279
358
|
* Integrates with Cache Manager for:
|
|
280
359
|
* - Local MD file caching
|
|
281
360
|
* - User profile storage
|
|
@@ -297,6 +376,23 @@ export async function handleInboundMessage(
|
|
|
297
376
|
? (message as any).chatroomId
|
|
298
377
|
: message.fromUser || message.toUser || "";
|
|
299
378
|
|
|
379
|
+
// Determine the account ID for this message
|
|
380
|
+
// Priority: payload.accountId > wechatAccountId lookup > "default"
|
|
381
|
+
const resolvedAccountId = accountId || wechatAccountId || "default";
|
|
382
|
+
|
|
383
|
+
// Look up agentId for routing
|
|
384
|
+
let agentId: string | null = null;
|
|
385
|
+
if (cfg) {
|
|
386
|
+
const registry = getAccountRegistry();
|
|
387
|
+
// Try by wechatAccountId first
|
|
388
|
+
agentId = registry.getAgentId(wechatAccountId || "");
|
|
389
|
+
// Fallback to accountId
|
|
390
|
+
if (!agentId && accountId) {
|
|
391
|
+
const regAccount = registry.getByAccountId(accountId);
|
|
392
|
+
agentId = regAccount?.agentId || null;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
300
396
|
// Convert to cache format and store locally
|
|
301
397
|
if (cfg) {
|
|
302
398
|
try {
|
|
@@ -304,7 +400,7 @@ export async function handleInboundMessage(
|
|
|
304
400
|
const wechatMessage: WeChatMessage = {
|
|
305
401
|
messageId: message.messageId,
|
|
306
402
|
msgSvrId: message.msgSvrId,
|
|
307
|
-
accountId:
|
|
403
|
+
accountId: resolvedAccountId,
|
|
308
404
|
conversationType: isChatroom ? "chatroom" : "friend",
|
|
309
405
|
conversationId,
|
|
310
406
|
senderId: message.fromUser || "",
|
|
@@ -339,6 +435,8 @@ export async function handleInboundMessage(
|
|
|
339
435
|
},
|
|
340
436
|
timestamp: new Date(message.timestamp || Date.now()),
|
|
341
437
|
isSelf: message.isSelf || false,
|
|
438
|
+
agentId, // Include agentId for routing
|
|
439
|
+
accountId: resolvedAccountId,
|
|
342
440
|
};
|
|
343
441
|
|
|
344
442
|
// Dispatch to OpenClaw
|
|
@@ -350,7 +448,7 @@ export async function handleInboundMessage(
|
|
|
350
448
|
platformId: friendRequest.fromUser,
|
|
351
449
|
scene: friendRequest.scene,
|
|
352
450
|
ticket: friendRequest.ticket,
|
|
353
|
-
accountId,
|
|
451
|
+
accountId: accountId || wechatAccountId,
|
|
354
452
|
wechatAccountId,
|
|
355
453
|
});
|
|
356
454
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client Pool - Per-account client management
|
|
3
|
+
*
|
|
4
|
+
* Manages WorkPhone API clients per account to avoid creating
|
|
5
|
+
* new clients for each message and ensure credential isolation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { WorkPhoneWeChatClient, type WorkPhoneConfig } from "./client.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Client pool for WorkPhone WeChat clients
|
|
12
|
+
*/
|
|
13
|
+
class ClientPool {
|
|
14
|
+
private clients: Map<string, WorkPhoneWeChatClient> = new Map();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get or create a client for the given account
|
|
18
|
+
*/
|
|
19
|
+
getClient(accountId: string, config: WorkPhoneConfig): WorkPhoneWeChatClient {
|
|
20
|
+
if (this.clients.has(accountId)) {
|
|
21
|
+
return this.clients.get(accountId)!;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const client = new WorkPhoneWeChatClient(config);
|
|
25
|
+
this.clients.set(accountId, client);
|
|
26
|
+
return client;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check if client exists
|
|
31
|
+
*/
|
|
32
|
+
hasClient(accountId: string): boolean {
|
|
33
|
+
return this.clients.has(accountId);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Remove a client by accountId
|
|
38
|
+
*/
|
|
39
|
+
removeClient(accountId: string): boolean {
|
|
40
|
+
return this.clients.delete(accountId);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Clear all clients
|
|
45
|
+
*/
|
|
46
|
+
clear(): void {
|
|
47
|
+
this.clients.clear();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get all account IDs with active clients
|
|
52
|
+
*/
|
|
53
|
+
getActiveAccountIds(): string[] {
|
|
54
|
+
return Array.from(this.clients.keys());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get pool size
|
|
59
|
+
*/
|
|
60
|
+
get size(): number {
|
|
61
|
+
return this.clients.size;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Singleton instance
|
|
66
|
+
let clientPool: ClientPool | null = null;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get or create the client pool singleton
|
|
70
|
+
*/
|
|
71
|
+
export function getClientPool(): ClientPool {
|
|
72
|
+
if (!clientPool) {
|
|
73
|
+
clientPool = new ClientPool();
|
|
74
|
+
}
|
|
75
|
+
return clientPool;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get a client for a specific account
|
|
80
|
+
*/
|
|
81
|
+
export function getWorkPhoneClient(
|
|
82
|
+
accountId: string,
|
|
83
|
+
config: WorkPhoneConfig
|
|
84
|
+
): WorkPhoneWeChatClient {
|
|
85
|
+
return getClientPool().getClient(accountId, config);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Create a client config from resolved account
|
|
90
|
+
*/
|
|
91
|
+
export function createClientConfig(
|
|
92
|
+
baseUrl: string,
|
|
93
|
+
apiKey: string,
|
|
94
|
+
accountId: string | undefined,
|
|
95
|
+
wechatAccountId: string | undefined
|
|
96
|
+
): WorkPhoneConfig {
|
|
97
|
+
return {
|
|
98
|
+
baseUrl,
|
|
99
|
+
apiKey,
|
|
100
|
+
accountId,
|
|
101
|
+
wechatAccountId,
|
|
102
|
+
};
|
|
103
|
+
}
|