@gloablehive/ipad-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 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,229 @@
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
+ setup: {
71
+ resolveAccount,
72
+ inspectAccount(cfg, accountId) {
73
+ const section = cfg.channels?.["ipad-wechat"];
74
+ const hasCredentials = Boolean(section?.appKey && section?.appSecret && section?.guid);
75
+ return {
76
+ enabled: hasCredentials,
77
+ configured: hasCredentials,
78
+ tokenStatus: hasCredentials ? "available" : "missing",
79
+ };
80
+ },
81
+ },
82
+ }),
83
+ security: {
84
+ dm: {
85
+ channelKey: "ipad-wechat",
86
+ resolvePolicy: (account) => account.dmPolicy,
87
+ resolveAllowFrom: (account) => account.allowFrom,
88
+ defaultPolicy: "allowlist",
89
+ },
90
+ },
91
+ threading: {
92
+ topLevelReplyToMode: "reply",
93
+ },
94
+ outbound: {
95
+ attachedResults: {
96
+ sendText: async (params) => {
97
+ // Get the resolved account from params
98
+ const cfg = params.cfg;
99
+ const section = cfg.channels?.["ipad-wechat"];
100
+ const client = createIPadClient({
101
+ appKey: section?.appKey,
102
+ appSecret: section?.appSecret,
103
+ guid: section?.guid,
104
+ });
105
+ // Check if DM or group
106
+ const isChatroom = params.to?.includes("@chatroom");
107
+ if (isChatroom) {
108
+ const result = await client.sendRoomMessage({
109
+ roomId: params.to,
110
+ content: params.text,
111
+ });
112
+ return { messageId: result.messageId };
113
+ }
114
+ else {
115
+ const result = await client.sendFriendMessage({
116
+ friendWechatId: params.to,
117
+ content: params.text,
118
+ });
119
+ return { messageId: result.messageId };
120
+ }
121
+ },
122
+ sendMedia: async (params) => {
123
+ const cfg = params.cfg;
124
+ const section = cfg.channels?.["ipad-wechat"];
125
+ const client = createIPadClient({
126
+ appKey: section?.appKey,
127
+ appSecret: section?.appSecret,
128
+ guid: section?.guid,
129
+ });
130
+ const isChatroom = params.to?.includes("@chatroom");
131
+ // Get file path from mediaUrl or mediaReadFile
132
+ let filePath = "";
133
+ if (params.mediaUrl) {
134
+ filePath = params.mediaUrl;
135
+ }
136
+ else if (params.mediaReadFile) {
137
+ // Read file from local roots
138
+ const roots = params.mediaLocalRoots || ["./"];
139
+ for (const root of roots) {
140
+ try {
141
+ const fullPath = `${root}/${filePath}`;
142
+ const buffer = await params.mediaReadFile(fullPath);
143
+ // In a real implementation, we'd upload the file first
144
+ filePath = fullPath;
145
+ break;
146
+ }
147
+ catch {
148
+ // Try next root
149
+ }
150
+ }
151
+ }
152
+ if (isChatroom) {
153
+ const result = await client.sendRoomMedia({
154
+ roomId: params.to,
155
+ filePath: filePath,
156
+ });
157
+ return { messageId: result.messageId };
158
+ }
159
+ else {
160
+ const result = await client.sendFriendMedia({
161
+ friendWechatId: params.to,
162
+ filePath: filePath,
163
+ });
164
+ return { messageId: result.messageId };
165
+ }
166
+ },
167
+ },
168
+ },
169
+ capabilities: {
170
+ supportedMessageTypes: ["text", "image", "video", "file", "link", "location", "contact"],
171
+ maxAttachmentSize: 25 * 1024 * 1024,
172
+ supportsMarkdown: false,
173
+ supportsHtml: false,
174
+ supportsEmoji: true,
175
+ supportsReactions: false,
176
+ supportsThreads: true,
177
+ supportsEditing: false,
178
+ supportsDeleting: false,
179
+ },
180
+ });
181
+ export async function handleInboundMessage(api, payload, cfg) {
182
+ const { event, message } = payload;
183
+ if (event === "message" && message) {
184
+ const isChatroom = !!message.roomId;
185
+ const conversationId = isChatroom
186
+ ? message.roomId
187
+ : message.fromUser || message.toUser || "";
188
+ const openclawMessage = {
189
+ id: message.messageId,
190
+ conversation: {
191
+ type: isChatroom ? "group" : "dm",
192
+ id: conversationId,
193
+ },
194
+ sender: {
195
+ id: message.fromUser || "",
196
+ },
197
+ content: {
198
+ type: "text",
199
+ text: message.content,
200
+ },
201
+ timestamp: new Date(message.timestamp || Date.now()),
202
+ isSelf: message.isSelf || false,
203
+ };
204
+ // Write to cache
205
+ if (cfg) {
206
+ try {
207
+ const cache = getCacheManager(cfg);
208
+ const wechatMessage = {
209
+ messageId: message.messageId,
210
+ accountId: cfg.channels?.["ipad-wechat"]?.accountId || "default",
211
+ conversationType: isChatroom ? "chatroom" : "friend",
212
+ conversationId,
213
+ senderId: message.fromUser || "",
214
+ content: message.content,
215
+ messageType: message.type || 1,
216
+ timestamp: message.timestamp || Date.now(),
217
+ isSelf: message.isSelf || false,
218
+ direction: "inbound",
219
+ };
220
+ await cache.onMessage(wechatMessage);
221
+ }
222
+ catch (error) {
223
+ console.error("[iPad WeChat] Cache error:", error);
224
+ }
225
+ }
226
+ await api.inbound.dispatchMessage(openclawMessage);
227
+ }
228
+ }
229
+ export default ipadWeChatPlugin;