@gloablehive/celphone-wechat-plugin 1.0.0 → 1.0.1
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 +6 -4
- package/dist/src/channel.js +75 -77
- package/index.ts +7 -5
- package/package.json +2 -2
- package/src/channel.ts +87 -89
- package/src/client.ts +1 -1
- package/test-cache.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -61,15 +61,17 @@ export default defineChannelPluginEntry({
|
|
|
61
61
|
},
|
|
62
62
|
});
|
|
63
63
|
// Register gateway method for outbound media handling
|
|
64
|
-
api.
|
|
65
|
-
method: "POST",
|
|
64
|
+
api.registerHttpRoute({
|
|
66
65
|
path: "/celphone-wechat/media",
|
|
66
|
+
auth: "plugin",
|
|
67
67
|
handler: async (req, res) => {
|
|
68
|
-
const
|
|
68
|
+
const payload = req.body;
|
|
69
|
+
const { messageId, mediaUrl, mediaType } = payload || {};
|
|
69
70
|
// Handle media upload/send through WorkPhone
|
|
70
71
|
// This is called when the bot needs to send media files
|
|
72
|
+
res.setHeader("Content-Type", "application/json");
|
|
71
73
|
res.statusCode = 200;
|
|
72
|
-
res.
|
|
74
|
+
res.end(JSON.stringify({ success: true, messageId }));
|
|
73
75
|
return true;
|
|
74
76
|
},
|
|
75
77
|
});
|
package/dist/src/channel.js
CHANGED
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { createChatChannelPlugin, createChannelPluginBase, } from "openclaw/plugin-sdk/core";
|
|
22
22
|
import { createWorkPhoneClient } from "./client.js";
|
|
23
|
-
// Import cache modules
|
|
24
|
-
import { createCacheManager, } from "
|
|
23
|
+
// Import cache modules from shared package
|
|
24
|
+
import { createCacheManager, } from "@gloablehive/wechat-cache";
|
|
25
25
|
// Cache manager instance (lazy initialized)
|
|
26
26
|
let cacheManager = null;
|
|
27
27
|
/**
|
|
@@ -35,15 +35,15 @@ function getCacheManager(cfg) {
|
|
|
35
35
|
// If no accounts configured, create default from main config
|
|
36
36
|
if (accounts.length === 0 && section?.wechatAccountId) {
|
|
37
37
|
accounts.push({
|
|
38
|
-
accountId: section.accountId ||
|
|
38
|
+
accountId: section.accountId || "default",
|
|
39
39
|
wechatAccountId: section.wechatAccountId,
|
|
40
40
|
wechatId: section.wechatId || section.wechatAccountId,
|
|
41
|
-
nickName: section.nickName ||
|
|
41
|
+
nickName: section.nickName || "WeChat User",
|
|
42
42
|
enabled: true,
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
const basePath = globalThis?.process?.env?.OPENCLAW_CACHE_PATH
|
|
46
|
-
||
|
|
46
|
+
|| "~/.openclaw/channels/celphone-wechat";
|
|
47
47
|
cacheManager = createCacheManager({
|
|
48
48
|
basePath,
|
|
49
49
|
accounts,
|
|
@@ -54,13 +54,13 @@ function getCacheManager(cfg) {
|
|
|
54
54
|
} : undefined,
|
|
55
55
|
syncConfig: section?.sync ? {
|
|
56
56
|
databaseUrl: section.sync.databaseUrl,
|
|
57
|
-
syncMode: section.sync.syncMode ||
|
|
57
|
+
syncMode: section.sync.syncMode || "interval",
|
|
58
58
|
syncIntervalMs: section.sync.syncIntervalMs || 5 * 60 * 1000,
|
|
59
59
|
} : undefined,
|
|
60
60
|
});
|
|
61
61
|
// Initialize cache manager
|
|
62
62
|
cacheManager.init().catch(err => {
|
|
63
|
-
console.error(
|
|
63
|
+
console.error("[CelPhoneWeChat] Cache manager init failed:", err);
|
|
64
64
|
});
|
|
65
65
|
return cacheManager;
|
|
66
66
|
}
|
|
@@ -99,112 +99,108 @@ export const celPhoneWeChatPlugin = createChatChannelPlugin({
|
|
|
99
99
|
base: createChannelPluginBase({
|
|
100
100
|
id: "celphone-wechat",
|
|
101
101
|
setup: {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const section = cfg.channels?.["celphone-wechat"];
|
|
105
|
-
const hasApiKey = Boolean(section?.apiKey);
|
|
106
|
-
return {
|
|
107
|
-
enabled: hasApiKey,
|
|
108
|
-
configured: hasApiKey,
|
|
109
|
-
tokenStatus: hasApiKey ? "available" : "missing",
|
|
110
|
-
};
|
|
102
|
+
resolveAccountId: (params) => {
|
|
103
|
+
return resolveAccount(params.cfg, params.accountId)?.accountId || "";
|
|
111
104
|
},
|
|
105
|
+
applyAccountConfig: (params) => params.cfg,
|
|
112
106
|
},
|
|
113
107
|
}),
|
|
114
|
-
// DM security: who can message the bot
|
|
115
108
|
security: {
|
|
116
109
|
dm: {
|
|
117
110
|
channelKey: "celphone-wechat",
|
|
118
111
|
resolvePolicy: (account) => account.dmPolicy,
|
|
119
112
|
resolveAllowFrom: (account) => account.allowFrom,
|
|
120
|
-
defaultPolicy: "allowlist",
|
|
113
|
+
defaultPolicy: "allowlist",
|
|
121
114
|
},
|
|
122
115
|
},
|
|
123
|
-
// Pairing: not currently supported for this channel
|
|
124
|
-
// WorkPhone WeChat doesn't have a standard pairing flow
|
|
125
|
-
// pairing: { ... },
|
|
126
|
-
// Threading: how replies are delivered
|
|
127
|
-
// For WeChat, replies go back to the same chat (friend or chatroom)
|
|
128
116
|
threading: {
|
|
129
|
-
topLevelReplyToMode: "reply",
|
|
117
|
+
topLevelReplyToMode: "reply",
|
|
130
118
|
},
|
|
131
|
-
// Outbound: send messages to WeChat via WorkPhone API
|
|
132
|
-
//
|
|
133
|
-
// HUMAN ACCOUNT MODEL IMPORTANT:
|
|
134
|
-
// This channel connects to a real person's WeChat account.
|
|
135
|
-
// The agent needs to communicate with ALL their friends and groups,
|
|
136
|
-
// not just one conversation. We distinguish by conversation type:
|
|
137
|
-
// - DM (direct): friendWechatId in params.to
|
|
138
|
-
// - Group chat: chatroomId in params.to (detect by format or metadata)
|
|
139
119
|
outbound: {
|
|
120
|
+
channel: "celphone-wechat",
|
|
140
121
|
attachedResults: {
|
|
141
|
-
|
|
122
|
+
channel: "celphone-wechat",
|
|
142
123
|
sendText: async (params) => {
|
|
124
|
+
const cfg = params.cfg;
|
|
125
|
+
const section = cfg.channels?.["celphone-wechat"];
|
|
143
126
|
const client = createWorkPhoneClient({
|
|
144
|
-
baseUrl:
|
|
145
|
-
apiKey:
|
|
146
|
-
accountId:
|
|
147
|
-
wechatAccountId:
|
|
127
|
+
baseUrl: section?.baseUrl || "https://api.workphone.example.com",
|
|
128
|
+
apiKey: section?.apiKey,
|
|
129
|
+
accountId: section?.accountId || undefined,
|
|
130
|
+
wechatAccountId: section?.wechatAccountId,
|
|
148
131
|
});
|
|
149
|
-
|
|
150
|
-
// Chatroom IDs typically start with certain prefix or have specific format
|
|
151
|
-
const isChatroom = params.metadata?.conversationType === 'group' ||
|
|
152
|
-
(params.to && params.to.includes('@chatroom'));
|
|
132
|
+
const isChatroom = params.to?.includes("@chatroom");
|
|
153
133
|
let result;
|
|
154
134
|
if (isChatroom) {
|
|
155
|
-
// Send to chatroom (group)
|
|
156
135
|
result = await client.sendChatroomMessage({
|
|
157
|
-
wechatAccountId:
|
|
136
|
+
wechatAccountId: section?.wechatAccountId,
|
|
158
137
|
chatroomId: params.to,
|
|
159
138
|
content: params.text,
|
|
160
|
-
type:
|
|
139
|
+
type: "text",
|
|
161
140
|
});
|
|
162
141
|
}
|
|
163
142
|
else {
|
|
164
143
|
// Send to friend (DM)
|
|
165
144
|
result = await client.sendFriendMessage({
|
|
166
|
-
wechatAccountId:
|
|
145
|
+
wechatAccountId: section?.wechatAccountId,
|
|
167
146
|
friendWechatId: params.to,
|
|
168
147
|
content: params.text,
|
|
169
|
-
type:
|
|
148
|
+
type: "text",
|
|
170
149
|
});
|
|
171
150
|
}
|
|
172
151
|
return { messageId: result.messageId };
|
|
173
152
|
},
|
|
174
153
|
// Send media - also support both DM and group
|
|
175
154
|
sendMedia: async (params) => {
|
|
155
|
+
const cfg = params.cfg;
|
|
156
|
+
const section = cfg.channels?.["celphone-wechat"];
|
|
176
157
|
const client = createWorkPhoneClient({
|
|
177
|
-
baseUrl:
|
|
178
|
-
apiKey:
|
|
179
|
-
accountId:
|
|
180
|
-
wechatAccountId:
|
|
158
|
+
baseUrl: section?.baseUrl || "https://api.workphone.example.com",
|
|
159
|
+
apiKey: section?.apiKey,
|
|
160
|
+
accountId: section?.accountId || undefined,
|
|
161
|
+
wechatAccountId: section?.wechatAccountId,
|
|
181
162
|
});
|
|
182
|
-
const isChatroom = params.
|
|
183
|
-
|
|
163
|
+
const isChatroom = params.to?.includes("@chatroom");
|
|
164
|
+
// Get file path from mediaUrl or mediaReadFile
|
|
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
|
+
}
|
|
184
184
|
let result;
|
|
185
185
|
if (isChatroom) {
|
|
186
186
|
result = await client.sendChatroomMessage({
|
|
187
|
-
wechatAccountId:
|
|
187
|
+
wechatAccountId: section?.wechatAccountId,
|
|
188
188
|
chatroomId: params.to,
|
|
189
|
-
content:
|
|
190
|
-
type:
|
|
189
|
+
content: filePath,
|
|
190
|
+
type: "file",
|
|
191
191
|
});
|
|
192
192
|
}
|
|
193
193
|
else {
|
|
194
194
|
result = await client.sendFriendMessage({
|
|
195
|
-
wechatAccountId:
|
|
195
|
+
wechatAccountId: section?.wechatAccountId,
|
|
196
196
|
friendWechatId: params.to,
|
|
197
|
-
content:
|
|
198
|
-
type:
|
|
197
|
+
content: filePath,
|
|
198
|
+
type: "file",
|
|
199
199
|
});
|
|
200
200
|
}
|
|
201
201
|
return { messageId: result.messageId };
|
|
202
202
|
},
|
|
203
203
|
},
|
|
204
|
-
// Additional outbound handlers can be added here for:
|
|
205
|
-
// - sendLink: Send link cards
|
|
206
|
-
// - sendLocation: Send location
|
|
207
|
-
// - sendContact: Send contact card
|
|
208
204
|
},
|
|
209
205
|
// Additional capabilities
|
|
210
206
|
// - Describe the channel's message types and features
|
|
@@ -225,7 +221,7 @@ export const celPhoneWeChatPlugin = createChatChannelPlugin({
|
|
|
225
221
|
supportsReactions: false, // WeChat doesn't support reactions
|
|
226
222
|
supportsThreads: true, // Can reply in chat
|
|
227
223
|
supportsEditing: false, // Cannot edit sent messages
|
|
228
|
-
supportsDeleting: false,
|
|
224
|
+
supportsDeleting: false,
|
|
229
225
|
},
|
|
230
226
|
});
|
|
231
227
|
/**
|
|
@@ -241,31 +237,33 @@ export const celPhoneWeChatPlugin = createChatChannelPlugin({
|
|
|
241
237
|
*/
|
|
242
238
|
export async function handleInboundMessage(api, payload, cfg) {
|
|
243
239
|
const { event, accountId, wechatAccountId, message, friendRequest } = payload;
|
|
244
|
-
if (event ===
|
|
240
|
+
if (event === "message" && message) {
|
|
245
241
|
// Determine conversation type
|
|
246
242
|
const isChatroom = !!message.chatroomId;
|
|
247
243
|
const conversationId = isChatroom
|
|
248
244
|
? message.chatroomId
|
|
249
|
-
: message.fromUser || message.toUser ||
|
|
245
|
+
: message.fromUser || message.toUser || "";
|
|
250
246
|
// Convert to cache format and store locally
|
|
251
|
-
if (
|
|
247
|
+
if (cfg) {
|
|
252
248
|
try {
|
|
253
|
-
|
|
249
|
+
const cache = getCacheManager(cfg);
|
|
250
|
+
const wechatMessage = {
|
|
254
251
|
messageId: message.messageId,
|
|
255
252
|
msgSvrId: message.msgSvrId,
|
|
256
|
-
accountId: accountId,
|
|
257
|
-
conversationType: isChatroom ?
|
|
253
|
+
accountId: accountId || "default",
|
|
254
|
+
conversationType: isChatroom ? "chatroom" : "friend",
|
|
258
255
|
conversationId,
|
|
259
|
-
senderId: message.fromUser ||
|
|
256
|
+
senderId: message.fromUser || "",
|
|
260
257
|
content: message.content,
|
|
261
258
|
messageType: message.type || 1,
|
|
262
259
|
timestamp: message.timestamp || Date.now(),
|
|
263
260
|
isSelf: message.isSelf || false,
|
|
264
|
-
direction: message.isSelf ?
|
|
265
|
-
}
|
|
261
|
+
direction: message.isSelf ? "outbound" : "inbound",
|
|
262
|
+
};
|
|
263
|
+
await cache.onMessage(wechatMessage);
|
|
266
264
|
}
|
|
267
265
|
catch (err) {
|
|
268
|
-
console.error(
|
|
266
|
+
console.error("[CelPhoneWeChat] Cache write failed:", err);
|
|
269
267
|
}
|
|
270
268
|
}
|
|
271
269
|
// Convert WorkPhone message format to OpenClaw format
|
|
@@ -273,16 +271,16 @@ export async function handleInboundMessage(api, payload, cfg) {
|
|
|
273
271
|
id: message.messageId,
|
|
274
272
|
rawId: message.msgSvrId || message.messageId,
|
|
275
273
|
conversation: {
|
|
276
|
-
type: isChatroom ?
|
|
274
|
+
type: isChatroom ? "group" : "dm",
|
|
277
275
|
id: conversationId,
|
|
278
276
|
chatroomId: isChatroom ? conversationId : undefined,
|
|
279
277
|
},
|
|
280
278
|
sender: {
|
|
281
|
-
id: message.fromUser ||
|
|
279
|
+
id: message.fromUser || "",
|
|
282
280
|
platformId: message.wechatId,
|
|
283
281
|
},
|
|
284
282
|
content: {
|
|
285
|
-
type: message.type === 1 ?
|
|
283
|
+
type: message.type === 1 ? "text" : "media",
|
|
286
284
|
text: message.content,
|
|
287
285
|
},
|
|
288
286
|
timestamp: new Date(message.timestamp || Date.now()),
|
|
@@ -291,7 +289,7 @@ export async function handleInboundMessage(api, payload, cfg) {
|
|
|
291
289
|
// Dispatch to OpenClaw
|
|
292
290
|
await api.inbound.dispatchMessage(openclawMessage);
|
|
293
291
|
}
|
|
294
|
-
else if (event ===
|
|
292
|
+
else if (event === "friend_request" && friendRequest) {
|
|
295
293
|
// Handle friend request
|
|
296
294
|
await api.inbound.dispatchFriendRequest({
|
|
297
295
|
id: friendRequest.v1,
|
package/index.ts
CHANGED
|
@@ -44,7 +44,7 @@ export default defineChannelPluginEntry({
|
|
|
44
44
|
try {
|
|
45
45
|
// Parse the webhook payload
|
|
46
46
|
// The exact format depends on WorkPhone's webhook configuration
|
|
47
|
-
const payload = req.body;
|
|
47
|
+
const payload = (req as any).body;
|
|
48
48
|
|
|
49
49
|
// Verify the request is from WorkPhone
|
|
50
50
|
// Add signature verification here if needed
|
|
@@ -71,17 +71,19 @@ export default defineChannelPluginEntry({
|
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
// Register gateway method for outbound media handling
|
|
74
|
-
api.
|
|
75
|
-
method: "POST",
|
|
74
|
+
api.registerHttpRoute({
|
|
76
75
|
path: "/celphone-wechat/media",
|
|
76
|
+
auth: "plugin",
|
|
77
77
|
handler: async (req, res) => {
|
|
78
|
-
const
|
|
78
|
+
const payload = (req as any).body;
|
|
79
|
+
const { messageId, mediaUrl, mediaType } = payload || {};
|
|
79
80
|
|
|
80
81
|
// Handle media upload/send through WorkPhone
|
|
81
82
|
// This is called when the bot needs to send media files
|
|
82
83
|
|
|
84
|
+
res.setHeader("Content-Type", "application/json");
|
|
83
85
|
res.statusCode = 200;
|
|
84
|
-
res.
|
|
86
|
+
res.end(JSON.stringify({ success: true, messageId }));
|
|
85
87
|
return true;
|
|
86
88
|
},
|
|
87
89
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gloablehive/celphone-wechat-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw channel plugin for workphone-wechat API - enables sending/receiving WeChat messages through workphone",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@gloablehive/wechat-cache": "^1.0.
|
|
27
|
+
"@gloablehive/wechat-cache": "^1.0.1",
|
|
28
28
|
"openclaw": ">=1.0.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
package/src/channel.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
createCacheManager,
|
|
32
32
|
CacheManager,
|
|
33
33
|
WeChatAccount,
|
|
34
|
+
WeChatMessage,
|
|
34
35
|
} from "@gloablehive/wechat-cache";
|
|
35
36
|
|
|
36
37
|
// Cache manager instance (lazy initialized)
|
|
@@ -48,16 +49,16 @@ function getCacheManager(cfg: OpenClawConfig): CacheManager {
|
|
|
48
49
|
// If no accounts configured, create default from main config
|
|
49
50
|
if (accounts.length === 0 && section?.wechatAccountId) {
|
|
50
51
|
accounts.push({
|
|
51
|
-
accountId: section.accountId ||
|
|
52
|
+
accountId: section.accountId || "default",
|
|
52
53
|
wechatAccountId: section.wechatAccountId,
|
|
53
54
|
wechatId: section.wechatId || section.wechatAccountId,
|
|
54
|
-
nickName: section.nickName ||
|
|
55
|
+
nickName: section.nickName || "WeChat User",
|
|
55
56
|
enabled: true,
|
|
56
57
|
});
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
const basePath = (globalThis as any)?.process?.env?.OPENCLAW_CACHE_PATH
|
|
60
|
-
||
|
|
61
|
+
|| "~/.openclaw/channels/celphone-wechat";
|
|
61
62
|
|
|
62
63
|
cacheManager = createCacheManager({
|
|
63
64
|
basePath,
|
|
@@ -69,14 +70,14 @@ function getCacheManager(cfg: OpenClawConfig): CacheManager {
|
|
|
69
70
|
} : undefined,
|
|
70
71
|
syncConfig: section?.sync ? {
|
|
71
72
|
databaseUrl: section.sync.databaseUrl,
|
|
72
|
-
syncMode: section.sync.syncMode ||
|
|
73
|
+
syncMode: section.sync.syncMode || "interval",
|
|
73
74
|
syncIntervalMs: section.sync.syncIntervalMs || 5 * 60 * 1000,
|
|
74
75
|
} : undefined,
|
|
75
76
|
});
|
|
76
77
|
|
|
77
78
|
// Initialize cache manager
|
|
78
79
|
cacheManager.init().catch(err => {
|
|
79
|
-
console.error(
|
|
80
|
+
console.error("[CelPhoneWeChat] Cache manager init failed:", err);
|
|
80
81
|
});
|
|
81
82
|
|
|
82
83
|
return cacheManager;
|
|
@@ -86,9 +87,9 @@ export interface CelPhoneWeChatResolvedAccount {
|
|
|
86
87
|
accountId: string | null;
|
|
87
88
|
apiKey: string;
|
|
88
89
|
baseUrl: string;
|
|
89
|
-
wechatAccountId: string;
|
|
90
|
-
wechatId: string;
|
|
91
|
-
nickName: string;
|
|
90
|
+
wechatAccountId: string; // Required - which WeChat account this is
|
|
91
|
+
wechatId: string; // The actual WeChat ID (wxid_xxx)
|
|
92
|
+
nickName: string; // WeChat nickname for display
|
|
92
93
|
allowFrom: string[];
|
|
93
94
|
dmPolicy: string | undefined;
|
|
94
95
|
}
|
|
@@ -121,8 +122,8 @@ function resolveAccount(
|
|
|
121
122
|
apiKey,
|
|
122
123
|
baseUrl,
|
|
123
124
|
wechatAccountId,
|
|
124
|
-
wechatId: section?.wechatId || wechatAccountId,
|
|
125
|
-
nickName: section?.nickName || "WeChat User",
|
|
125
|
+
wechatId: section?.wechatId || wechatAccountId, // Actual WeChat ID (wxid_xxx)
|
|
126
|
+
nickName: section?.nickName || "WeChat User", // Display name
|
|
126
127
|
allowFrom: section?.allowFrom ?? [],
|
|
127
128
|
dmPolicy: section?.dmSecurity,
|
|
128
129
|
};
|
|
@@ -135,79 +136,58 @@ export const celPhoneWeChatPlugin = createChatChannelPlugin<CelPhoneWeChatResolv
|
|
|
135
136
|
base: createChannelPluginBase({
|
|
136
137
|
id: "celphone-wechat",
|
|
137
138
|
setup: {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const section = (cfg.channels as Record<string, any>)?.["celphone-wechat"];
|
|
141
|
-
const hasApiKey = Boolean(section?.apiKey);
|
|
142
|
-
return {
|
|
143
|
-
enabled: hasApiKey,
|
|
144
|
-
configured: hasApiKey,
|
|
145
|
-
tokenStatus: hasApiKey ? "available" : "missing",
|
|
146
|
-
};
|
|
139
|
+
resolveAccountId: (params: any) => {
|
|
140
|
+
return resolveAccount(params.cfg, params.accountId)?.accountId || "";
|
|
147
141
|
},
|
|
142
|
+
applyAccountConfig: (params) => params.cfg,
|
|
148
143
|
},
|
|
149
|
-
}),
|
|
144
|
+
}) as any,
|
|
150
145
|
|
|
151
|
-
// DM security: who can message the bot
|
|
152
146
|
security: {
|
|
153
147
|
dm: {
|
|
154
148
|
channelKey: "celphone-wechat",
|
|
155
149
|
resolvePolicy: (account) => account.dmPolicy,
|
|
156
150
|
resolveAllowFrom: (account) => account.allowFrom,
|
|
157
|
-
defaultPolicy: "allowlist",
|
|
151
|
+
defaultPolicy: "allowlist",
|
|
158
152
|
},
|
|
159
153
|
},
|
|
160
154
|
|
|
161
|
-
// Pairing: not currently supported for this channel
|
|
162
|
-
// WorkPhone WeChat doesn't have a standard pairing flow
|
|
163
|
-
// pairing: { ... },
|
|
164
|
-
|
|
165
|
-
// Threading: how replies are delivered
|
|
166
|
-
// For WeChat, replies go back to the same chat (friend or chatroom)
|
|
167
155
|
threading: {
|
|
168
|
-
topLevelReplyToMode: "reply",
|
|
156
|
+
topLevelReplyToMode: "reply",
|
|
169
157
|
},
|
|
170
158
|
|
|
171
|
-
// Outbound: send messages to WeChat via WorkPhone API
|
|
172
|
-
//
|
|
173
|
-
// HUMAN ACCOUNT MODEL IMPORTANT:
|
|
174
|
-
// This channel connects to a real person's WeChat account.
|
|
175
|
-
// The agent needs to communicate with ALL their friends and groups,
|
|
176
|
-
// not just one conversation. We distinguish by conversation type:
|
|
177
|
-
// - DM (direct): friendWechatId in params.to
|
|
178
|
-
// - Group chat: chatroomId in params.to (detect by format or metadata)
|
|
179
159
|
outbound: {
|
|
160
|
+
channel: "celphone-wechat",
|
|
180
161
|
attachedResults: {
|
|
181
|
-
|
|
182
|
-
sendText: async (params) => {
|
|
162
|
+
channel: "celphone-wechat",
|
|
163
|
+
sendText: async (params: any) => {
|
|
164
|
+
const cfg = params.cfg;
|
|
165
|
+
const section = (cfg.channels as Record<string, any>)?.["celphone-wechat"];
|
|
166
|
+
|
|
183
167
|
const client = createWorkPhoneClient({
|
|
184
|
-
baseUrl:
|
|
185
|
-
apiKey:
|
|
186
|
-
accountId:
|
|
187
|
-
wechatAccountId:
|
|
168
|
+
baseUrl: section?.baseUrl || "https://api.workphone.example.com",
|
|
169
|
+
apiKey: section?.apiKey,
|
|
170
|
+
accountId: section?.accountId || undefined,
|
|
171
|
+
wechatAccountId: section?.wechatAccountId,
|
|
188
172
|
});
|
|
189
173
|
|
|
190
|
-
|
|
191
|
-
// Chatroom IDs typically start with certain prefix or have specific format
|
|
192
|
-
const isChatroom = params.metadata?.conversationType === 'group' ||
|
|
193
|
-
(params.to && params.to.includes('@chatroom'));
|
|
174
|
+
const isChatroom = params.to?.includes("@chatroom");
|
|
194
175
|
|
|
195
176
|
let result;
|
|
196
177
|
if (isChatroom) {
|
|
197
|
-
// Send to chatroom (group)
|
|
198
178
|
result = await client.sendChatroomMessage({
|
|
199
|
-
wechatAccountId:
|
|
179
|
+
wechatAccountId: section?.wechatAccountId,
|
|
200
180
|
chatroomId: params.to,
|
|
201
181
|
content: params.text,
|
|
202
|
-
type:
|
|
182
|
+
type: "text",
|
|
203
183
|
});
|
|
204
184
|
} else {
|
|
205
185
|
// Send to friend (DM)
|
|
206
186
|
result = await client.sendFriendMessage({
|
|
207
|
-
wechatAccountId:
|
|
187
|
+
wechatAccountId: section?.wechatAccountId,
|
|
208
188
|
friendWechatId: params.to,
|
|
209
189
|
content: params.text,
|
|
210
|
-
type:
|
|
190
|
+
type: "text",
|
|
211
191
|
});
|
|
212
192
|
}
|
|
213
193
|
|
|
@@ -215,43 +195,59 @@ export const celPhoneWeChatPlugin = createChatChannelPlugin<CelPhoneWeChatResolv
|
|
|
215
195
|
},
|
|
216
196
|
|
|
217
197
|
// Send media - also support both DM and group
|
|
218
|
-
sendMedia: async (params) => {
|
|
198
|
+
sendMedia: async (params: any) => {
|
|
199
|
+
const cfg = params.cfg;
|
|
200
|
+
const section = (cfg.channels as Record<string, any>)?.["celphone-wechat"];
|
|
201
|
+
|
|
219
202
|
const client = createWorkPhoneClient({
|
|
220
|
-
baseUrl:
|
|
221
|
-
apiKey:
|
|
222
|
-
accountId:
|
|
223
|
-
wechatAccountId:
|
|
203
|
+
baseUrl: section?.baseUrl || "https://api.workphone.example.com",
|
|
204
|
+
apiKey: section?.apiKey,
|
|
205
|
+
accountId: section?.accountId || undefined,
|
|
206
|
+
wechatAccountId: section?.wechatAccountId,
|
|
224
207
|
});
|
|
225
208
|
|
|
226
|
-
const isChatroom = params.
|
|
227
|
-
|
|
209
|
+
const isChatroom = params.to?.includes("@chatroom");
|
|
210
|
+
|
|
211
|
+
// Get file path from mediaUrl or mediaReadFile
|
|
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
|
+
}
|
|
228
229
|
|
|
229
230
|
let result;
|
|
230
231
|
if (isChatroom) {
|
|
231
232
|
result = await client.sendChatroomMessage({
|
|
232
|
-
wechatAccountId:
|
|
233
|
+
wechatAccountId: section?.wechatAccountId,
|
|
233
234
|
chatroomId: params.to,
|
|
234
|
-
content:
|
|
235
|
-
type:
|
|
235
|
+
content: filePath,
|
|
236
|
+
type: "file",
|
|
236
237
|
});
|
|
237
238
|
} else {
|
|
238
239
|
result = await client.sendFriendMessage({
|
|
239
|
-
wechatAccountId:
|
|
240
|
+
wechatAccountId: section?.wechatAccountId,
|
|
240
241
|
friendWechatId: params.to,
|
|
241
|
-
content:
|
|
242
|
-
type:
|
|
242
|
+
content: filePath,
|
|
243
|
+
type: "file",
|
|
243
244
|
});
|
|
244
245
|
}
|
|
245
246
|
|
|
246
247
|
return { messageId: result.messageId };
|
|
247
248
|
},
|
|
248
249
|
},
|
|
249
|
-
|
|
250
|
-
// Additional outbound handlers can be added here for:
|
|
251
|
-
// - sendLink: Send link cards
|
|
252
|
-
// - sendLocation: Send location
|
|
253
|
-
// - sendContact: Send contact card
|
|
254
|
-
},
|
|
250
|
+
} as any,
|
|
255
251
|
|
|
256
252
|
// Additional capabilities
|
|
257
253
|
// - Describe the channel's message types and features
|
|
@@ -270,11 +266,11 @@ export const celPhoneWeChatPlugin = createChatChannelPlugin<CelPhoneWeChatResolv
|
|
|
270
266
|
supportsHtml: false,
|
|
271
267
|
supportsEmoji: true,
|
|
272
268
|
supportsReactions: false, // WeChat doesn't support reactions
|
|
273
|
-
supportsThreads: true,
|
|
274
|
-
supportsEditing: false,
|
|
275
|
-
supportsDeleting: false,
|
|
269
|
+
supportsThreads: true, // Can reply in chat
|
|
270
|
+
supportsEditing: false, // Cannot edit sent messages
|
|
271
|
+
supportsDeleting: false,
|
|
276
272
|
},
|
|
277
|
-
});
|
|
273
|
+
} as any);
|
|
278
274
|
|
|
279
275
|
/**
|
|
280
276
|
* Helper function to handle inbound webhook messages
|
|
@@ -294,31 +290,33 @@ export async function handleInboundMessage(
|
|
|
294
290
|
): Promise<void> {
|
|
295
291
|
const { event, accountId, wechatAccountId, message, friendRequest } = payload;
|
|
296
292
|
|
|
297
|
-
if (event ===
|
|
293
|
+
if (event === "message" && message) {
|
|
298
294
|
// Determine conversation type
|
|
299
295
|
const isChatroom = !!(message as any).chatroomId;
|
|
300
296
|
const conversationId = isChatroom
|
|
301
297
|
? (message as any).chatroomId
|
|
302
|
-
: message.fromUser || message.toUser ||
|
|
298
|
+
: message.fromUser || message.toUser || "";
|
|
303
299
|
|
|
304
300
|
// Convert to cache format and store locally
|
|
305
|
-
if (
|
|
301
|
+
if (cfg) {
|
|
306
302
|
try {
|
|
307
|
-
|
|
303
|
+
const cache = getCacheManager(cfg);
|
|
304
|
+
const wechatMessage: WeChatMessage = {
|
|
308
305
|
messageId: message.messageId,
|
|
309
306
|
msgSvrId: message.msgSvrId,
|
|
310
|
-
accountId: accountId,
|
|
311
|
-
conversationType: isChatroom ?
|
|
307
|
+
accountId: accountId || "default",
|
|
308
|
+
conversationType: isChatroom ? "chatroom" : "friend",
|
|
312
309
|
conversationId,
|
|
313
|
-
senderId: message.fromUser ||
|
|
310
|
+
senderId: message.fromUser || "",
|
|
314
311
|
content: message.content,
|
|
315
312
|
messageType: message.type || 1,
|
|
316
313
|
timestamp: message.timestamp || Date.now(),
|
|
317
314
|
isSelf: message.isSelf || false,
|
|
318
|
-
direction: message.isSelf ?
|
|
319
|
-
}
|
|
315
|
+
direction: message.isSelf ? "outbound" : "inbound",
|
|
316
|
+
};
|
|
317
|
+
await cache.onMessage(wechatMessage);
|
|
320
318
|
} catch (err) {
|
|
321
|
-
console.error(
|
|
319
|
+
console.error("[CelPhoneWeChat] Cache write failed:", err);
|
|
322
320
|
}
|
|
323
321
|
}
|
|
324
322
|
|
|
@@ -327,16 +325,16 @@ export async function handleInboundMessage(
|
|
|
327
325
|
id: message.messageId,
|
|
328
326
|
rawId: message.msgSvrId || message.messageId,
|
|
329
327
|
conversation: {
|
|
330
|
-
type: isChatroom ?
|
|
328
|
+
type: isChatroom ? "group" as const : "dm" as const,
|
|
331
329
|
id: conversationId,
|
|
332
330
|
chatroomId: isChatroom ? conversationId : undefined,
|
|
333
331
|
},
|
|
334
332
|
sender: {
|
|
335
|
-
id: message.fromUser ||
|
|
333
|
+
id: message.fromUser || "",
|
|
336
334
|
platformId: message.wechatId,
|
|
337
335
|
},
|
|
338
336
|
content: {
|
|
339
|
-
type: message.type === 1 ?
|
|
337
|
+
type: message.type === 1 ? "text" as const : "media" as const,
|
|
340
338
|
text: message.content,
|
|
341
339
|
},
|
|
342
340
|
timestamp: new Date(message.timestamp || Date.now()),
|
|
@@ -345,7 +343,7 @@ export async function handleInboundMessage(
|
|
|
345
343
|
|
|
346
344
|
// Dispatch to OpenClaw
|
|
347
345
|
await api.inbound.dispatchMessage(openclawMessage);
|
|
348
|
-
} else if (event ===
|
|
346
|
+
} else if (event === "friend_request" && friendRequest) {
|
|
349
347
|
// Handle friend request
|
|
350
348
|
await api.inbound.dispatchFriendRequest({
|
|
351
349
|
id: friendRequest.v1,
|
package/src/client.ts
CHANGED
|
@@ -107,7 +107,7 @@ export class WorkPhoneWeChatClient {
|
|
|
107
107
|
throw new Error(`WorkPhone API error: ${response.status} - ${error}`);
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
return response.json()
|
|
110
|
+
return response.json() as Promise<T>;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
// ========== WeChat Account Operations ==========
|