@gloablehive/ipad-wechat-plugin 1.0.0 → 1.0.2

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 ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * OpenClaw Channel Plugin Entry Point
3
+ *
4
+ * iPad WeChat Plugin - enables OpenClaw to send/receive WeChat messages
5
+ * through the iPad protocol (different from WorkPhone/phone protocol)
6
+ */
7
+ import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
8
+ import { ipadWeChatPlugin, handleInboundMessage } from "./src/channel.js";
9
+ export default defineChannelPluginEntry({
10
+ id: "ipad-wechat",
11
+ name: "iPad WeChat",
12
+ description: "Connect OpenClaw to iPad WeChat protocol for sending and receiving WeChat messages",
13
+ plugin: ipadWeChatPlugin,
14
+ registerFull(api) {
15
+ // Register webhook endpoint for receiving messages
16
+ api.registerHttpRoute({
17
+ path: "/ipad-wechat/webhook",
18
+ auth: "plugin",
19
+ handler: async (req, res) => {
20
+ try {
21
+ const payload = req.body;
22
+ // Handle inbound message
23
+ console.log("[iPad WeChat] Received webhook:", payload);
24
+ if (payload) {
25
+ await handleInboundMessage(api, payload);
26
+ }
27
+ res.statusCode = 200;
28
+ res.end("ok");
29
+ return true;
30
+ }
31
+ catch (error) {
32
+ console.error("[iPad WeChat] Webhook error:", error);
33
+ res.statusCode = 500;
34
+ res.end("Internal error");
35
+ return true;
36
+ }
37
+ },
38
+ });
39
+ },
40
+ });
@@ -0,0 +1,98 @@
1
+ {
2
+ "$schema": "https://openclaw.ai/schema/plugin.json",
3
+ "id": "ipad-wechat",
4
+ "name": "iPad WeChat",
5
+ "version": "1.0.0",
6
+ "description": "Connect OpenClaw to iPad WeChat protocol 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/ipad-wechat-plugin"
14
+ },
15
+ "keywords": ["openclaw", "channel", "wechat", "ipad", "messaging"],
16
+ "license": "MIT",
17
+ "type": "channel",
18
+ "capabilities": {
19
+ "inbound": {
20
+ "message": true,
21
+ "friend_request": true,
22
+ "group_invite": true
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
+ "configSchema": {
40
+ "type": "object",
41
+ "properties": {
42
+ "appKey": {
43
+ "type": "string",
44
+ "description": "JuHeBot App Key (聚合聊天开放平台)",
45
+ "secret": true,
46
+ "required": true
47
+ },
48
+ "appSecret": {
49
+ "type": "string",
50
+ "description": "JuHeBot App Secret",
51
+ "secret": true,
52
+ "required": true
53
+ },
54
+ "guid": {
55
+ "type": "string",
56
+ "description": "实例/设备 ID (Guid)",
57
+ "required": true
58
+ },
59
+ "accountId": {
60
+ "type": "string",
61
+ "description": "Internal account ID for routing"
62
+ },
63
+ "wechatAccountId": {
64
+ "type": "string",
65
+ "description": "WeChat account ID in the system"
66
+ },
67
+ "wechatId": {
68
+ "type": "string",
69
+ "description": "WeChat ID (wxid_xxx)"
70
+ },
71
+ "nickName": {
72
+ "type": "string",
73
+ "description": "Display name for this WeChat account"
74
+ },
75
+ "allowFrom": {
76
+ "type": "array",
77
+ "items": { "type": "string" },
78
+ "description": "List of WeChat IDs that can DM the agent",
79
+ "default": []
80
+ },
81
+ "dmSecurity": {
82
+ "type": "string",
83
+ "enum": ["allowlist", "blocklist", "allowall"],
84
+ "description": "DM security policy",
85
+ "default": "allowlist"
86
+ }
87
+ },
88
+ "required": ["appKey", "appSecret", "guid"]
89
+ },
90
+ "permissions": {
91
+ "channels": ["ipad-wechat"],
92
+ "http": {
93
+ "outbound": ["*"],
94
+ "inbound": ["/ipad-wechat/webhook"]
95
+ }
96
+ },
97
+ "apiVersion": "1.0.0"
98
+ }
@@ -0,0 +1,243 @@
1
+ /**
2
+ * iPad WeChat Channel Plugin for OpenClaw
3
+ *
4
+ * Uses iPad protocol (different from phone/WorkPhone protocol)
5
+ * API documentation: wechat/api.md
6
+ *
7
+ * Includes Local Cache (shared with celphone-wechat-plugin):
8
+ * - Per-account, per-user/conversation MD files
9
+ * - YAML frontmatter (aligned with Claude Code)
10
+ * - MEMORY.md indexing
11
+ * - 4-layer compression
12
+ * - AI summary extraction
13
+ * - SAAS connectivity + offline fallback
14
+ * - Cloud sync
15
+ */
16
+ import { createChatChannelPlugin, createChannelPluginBase, } from "openclaw/plugin-sdk/core";
17
+ import { createIPadClient } from "./client.js";
18
+ // Import cache modules from shared package
19
+ import { createCacheManager, } from "@gloablehive/wechat-cache";
20
+ // Cache manager instance (lazy initialized)
21
+ let cacheManager = null;
22
+ /**
23
+ * Get or create cache manager
24
+ */
25
+ function getCacheManager(cfg) {
26
+ if (cacheManager)
27
+ return cacheManager;
28
+ const section = cfg.channels?.["ipad-wechat"];
29
+ const accounts = (section?.accounts || []);
30
+ // If no accounts configured, create default from main config
31
+ if (accounts.length === 0 && section?.wechatAccountId) {
32
+ accounts.push({
33
+ accountId: section.accountId || "default",
34
+ wechatAccountId: section.wechatAccountId,
35
+ wechatId: section.wechatId || "",
36
+ nickName: section.nickName || "WeChat User",
37
+ enabled: true,
38
+ });
39
+ }
40
+ const basePath = section?.cachePath || "./cache/wechat-ipad";
41
+ cacheManager = createCacheManager({
42
+ basePath,
43
+ accounts,
44
+ });
45
+ return cacheManager;
46
+ }
47
+ function resolveAccount(cfg, accountId) {
48
+ const section = cfg.channels?.["ipad-wechat"];
49
+ const appKey = section?.appKey;
50
+ const appSecret = section?.appSecret;
51
+ const guid = section?.guid;
52
+ if (!appKey || !appSecret || !guid) {
53
+ throw new Error("ipad-wechat: appKey, appSecret, and guid are required");
54
+ }
55
+ return {
56
+ accountId: accountId ?? null,
57
+ appKey,
58
+ appSecret,
59
+ guid,
60
+ wechatAccountId: section?.wechatAccountId || "",
61
+ wechatId: section?.wechatId || "",
62
+ nickName: section?.nickName || "WeChat User",
63
+ allowFrom: section?.allowFrom ?? [],
64
+ dmPolicy: section?.dmSecurity,
65
+ };
66
+ }
67
+ export const ipadWeChatPlugin = createChatChannelPlugin({
68
+ base: createChannelPluginBase({
69
+ id: "ipad-wechat",
70
+ capabilities: {
71
+ supportedMessageTypes: ["text", "image", "video", "file", "link", "location", "contact"],
72
+ maxAttachmentSize: 25 * 1024 * 1024,
73
+ supportsMarkdown: false,
74
+ supportsHtml: false,
75
+ supportsEmoji: true,
76
+ supportsReactions: false,
77
+ supportsThreads: true,
78
+ supportsEditing: false,
79
+ supportsDeleting: false,
80
+ },
81
+ setup: {
82
+ resolveAccountId: (params) => {
83
+ return resolveAccount(params.cfg, params.accountId)?.accountId || "";
84
+ },
85
+ inspectAccount(cfg, accountId) {
86
+ const section = cfg.channels?.["ipad-wechat"];
87
+ const hasCredentials = Boolean(section?.appKey && section?.appSecret && section?.guid);
88
+ return {
89
+ enabled: hasCredentials,
90
+ configured: hasCredentials,
91
+ tokenStatus: hasCredentials ? "available" : "missing",
92
+ };
93
+ },
94
+ },
95
+ }),
96
+ security: {
97
+ dm: {
98
+ channelKey: "ipad-wechat",
99
+ resolvePolicy: (account) => account.dmPolicy,
100
+ resolveAllowFrom: (account) => account.allowFrom,
101
+ defaultPolicy: "allowlist",
102
+ },
103
+ },
104
+ threading: {
105
+ topLevelReplyToMode: "reply",
106
+ },
107
+ outbound: {
108
+ channel: "ipad-wechat",
109
+ attachedResults: {
110
+ sendText: async (params) => {
111
+ // Get config from params.cfg
112
+ const cfg = params.cfg;
113
+ const section = cfg.channels?.["ipad-wechat"];
114
+ const client = createIPadClient({
115
+ appKey: section?.appKey,
116
+ appSecret: section?.appSecret,
117
+ guid: section?.guid,
118
+ });
119
+ // Check if DM or group
120
+ const isChatroom = params.to?.includes("@chatroom");
121
+ if (isChatroom) {
122
+ const result = await client.sendRoomMessage({
123
+ roomId: params.to,
124
+ content: params.text,
125
+ });
126
+ return { messageId: result.messageId };
127
+ }
128
+ else {
129
+ const result = await client.sendFriendMessage({
130
+ friendWechatId: params.to,
131
+ content: params.text,
132
+ });
133
+ return { messageId: result.messageId };
134
+ }
135
+ },
136
+ sendMedia: async (params) => {
137
+ const cfg = params.cfg;
138
+ const section = cfg.channels?.["ipad-wechat"];
139
+ const client = createIPadClient({
140
+ appKey: section?.appKey,
141
+ appSecret: section?.appSecret,
142
+ guid: section?.guid,
143
+ });
144
+ const isChatroom = params.to?.includes("@chatroom");
145
+ // Get file path from mediaUrl or mediaReadFile
146
+ let filePath = "";
147
+ if (params.mediaUrl) {
148
+ filePath = params.mediaUrl;
149
+ }
150
+ else if (params.mediaReadFile) {
151
+ // Read file from local roots
152
+ const roots = params.mediaLocalRoots || ["./"];
153
+ for (const root of roots) {
154
+ try {
155
+ const fullPath = `${root}/${filePath}`;
156
+ const buffer = await params.mediaReadFile(fullPath);
157
+ // In a real implementation, we'd upload the file first
158
+ filePath = fullPath;
159
+ break;
160
+ }
161
+ catch {
162
+ // Try next root
163
+ }
164
+ }
165
+ }
166
+ if (isChatroom) {
167
+ const result = await client.sendRoomMedia({
168
+ roomId: params.to,
169
+ filePath: filePath,
170
+ });
171
+ return { messageId: result.messageId };
172
+ }
173
+ else {
174
+ const result = await client.sendFriendMedia({
175
+ friendWechatId: params.to,
176
+ filePath: filePath,
177
+ });
178
+ return { messageId: result.messageId };
179
+ }
180
+ },
181
+ },
182
+ },
183
+ capabilities: {
184
+ supportedMessageTypes: ["text", "image", "video", "file", "link", "location", "contact"],
185
+ maxAttachmentSize: 25 * 1024 * 1024,
186
+ supportsMarkdown: false,
187
+ supportsHtml: false,
188
+ supportsEmoji: true,
189
+ supportsReactions: false,
190
+ supportsThreads: true,
191
+ supportsEditing: false,
192
+ supportsDeleting: false,
193
+ },
194
+ });
195
+ export async function handleInboundMessage(api, payload, cfg) {
196
+ const { event, message } = payload;
197
+ if (event === "message" && message) {
198
+ const isChatroom = !!message.roomId;
199
+ const conversationId = isChatroom
200
+ ? message.roomId
201
+ : message.fromUser || message.toUser || "";
202
+ const openclawMessage = {
203
+ id: message.messageId,
204
+ conversation: {
205
+ type: isChatroom ? "group" : "dm",
206
+ id: conversationId,
207
+ },
208
+ sender: {
209
+ id: message.fromUser || "",
210
+ },
211
+ content: {
212
+ type: "text",
213
+ text: message.content,
214
+ },
215
+ timestamp: new Date(message.timestamp || Date.now()),
216
+ isSelf: message.isSelf || false,
217
+ };
218
+ // Write to cache
219
+ if (cfg) {
220
+ try {
221
+ const cache = getCacheManager(cfg);
222
+ const wechatMessage = {
223
+ messageId: message.messageId,
224
+ accountId: cfg.channels?.["ipad-wechat"]?.accountId || "default",
225
+ conversationType: isChatroom ? "chatroom" : "friend",
226
+ conversationId,
227
+ senderId: message.fromUser || "",
228
+ content: message.content,
229
+ messageType: message.type || 1,
230
+ timestamp: message.timestamp || Date.now(),
231
+ isSelf: message.isSelf || false,
232
+ direction: "inbound",
233
+ };
234
+ await cache.onMessage(wechatMessage);
235
+ }
236
+ catch (error) {
237
+ console.error("[iPad WeChat] Cache error:", error);
238
+ }
239
+ }
240
+ await api.inbound.dispatchMessage(openclawMessage);
241
+ }
242
+ }
243
+ export default ipadWeChatPlugin;