@zhin.js/adapter-weixin-ilink 0.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.
Files changed (163) hide show
  1. package/README.md +96 -0
  2. package/client/LoginAssistPanel.tsx +200 -0
  3. package/client/WeixinIlinkManagement.tsx +139 -0
  4. package/client/index.tsx +11 -0
  5. package/client/tsconfig.json +13 -0
  6. package/client/utils/api.ts +30 -0
  7. package/dist/index.js +29 -0
  8. package/lib/adapter.d.ts +11 -0
  9. package/lib/adapter.d.ts.map +1 -0
  10. package/lib/adapter.js +14 -0
  11. package/lib/adapter.js.map +1 -0
  12. package/lib/aes-ecb.d.ts +7 -0
  13. package/lib/aes-ecb.d.ts.map +1 -0
  14. package/lib/aes-ecb.js +19 -0
  15. package/lib/aes-ecb.js.map +1 -0
  16. package/lib/bot.d.ts +37 -0
  17. package/lib/bot.d.ts.map +1 -0
  18. package/lib/bot.js +383 -0
  19. package/lib/bot.js.map +1 -0
  20. package/lib/cdn-upload.d.ts +18 -0
  21. package/lib/cdn-upload.d.ts.map +1 -0
  22. package/lib/cdn-upload.js +73 -0
  23. package/lib/cdn-upload.js.map +1 -0
  24. package/lib/cdn-url.d.ts +14 -0
  25. package/lib/cdn-url.d.ts.map +1 -0
  26. package/lib/cdn-url.js +14 -0
  27. package/lib/cdn-url.js.map +1 -0
  28. package/lib/context-store.d.ts +2 -0
  29. package/lib/context-store.d.ts.map +1 -0
  30. package/lib/context-store.js +2 -0
  31. package/lib/context-store.js.map +1 -0
  32. package/lib/credentials.d.ts +15 -0
  33. package/lib/credentials.d.ts.map +1 -0
  34. package/lib/credentials.js +49 -0
  35. package/lib/credentials.js.map +1 -0
  36. package/lib/ilink-api.d.ts +81 -0
  37. package/lib/ilink-api.d.ts.map +1 -0
  38. package/lib/ilink-api.js +273 -0
  39. package/lib/ilink-api.js.map +1 -0
  40. package/lib/ilink-config-cache.d.ts +19 -0
  41. package/lib/ilink-config-cache.d.ts.map +1 -0
  42. package/lib/ilink-config-cache.js +64 -0
  43. package/lib/ilink-config-cache.js.map +1 -0
  44. package/lib/ilink-logger.d.ts +10 -0
  45. package/lib/ilink-logger.d.ts.map +1 -0
  46. package/lib/ilink-logger.js +27 -0
  47. package/lib/ilink-logger.js.map +1 -0
  48. package/lib/ilink-meta.d.ts +13 -0
  49. package/lib/ilink-meta.d.ts.map +1 -0
  50. package/lib/ilink-meta.js +57 -0
  51. package/lib/ilink-meta.js.map +1 -0
  52. package/lib/ilink-random.d.ts +11 -0
  53. package/lib/ilink-random.d.ts.map +1 -0
  54. package/lib/ilink-random.js +16 -0
  55. package/lib/ilink-random.js.map +1 -0
  56. package/lib/ilink-redact.d.ts +21 -0
  57. package/lib/ilink-redact.d.ts.map +1 -0
  58. package/lib/ilink-redact.js +54 -0
  59. package/lib/ilink-redact.js.map +1 -0
  60. package/lib/ilink-session-guard.d.ts +16 -0
  61. package/lib/ilink-session-guard.d.ts.map +1 -0
  62. package/lib/ilink-session-guard.js +49 -0
  63. package/lib/ilink-session-guard.js.map +1 -0
  64. package/lib/ilink-types.d.ts +233 -0
  65. package/lib/ilink-types.d.ts.map +1 -0
  66. package/lib/ilink-types.js +35 -0
  67. package/lib/ilink-types.js.map +1 -0
  68. package/lib/index.d.ts +20 -0
  69. package/lib/index.d.ts.map +1 -0
  70. package/lib/index.js +38 -0
  71. package/lib/index.js.map +1 -0
  72. package/lib/login-assist-routes.d.ts +7 -0
  73. package/lib/login-assist-routes.d.ts.map +1 -0
  74. package/lib/login-assist-routes.js +29 -0
  75. package/lib/login-assist-routes.js.map +1 -0
  76. package/lib/login.d.ts +6 -0
  77. package/lib/login.d.ts.map +1 -0
  78. package/lib/login.js +121 -0
  79. package/lib/login.js.map +1 -0
  80. package/lib/media-download.d.ts +19 -0
  81. package/lib/media-download.d.ts.map +1 -0
  82. package/lib/media-download.js +95 -0
  83. package/lib/media-download.js.map +1 -0
  84. package/lib/mime.d.ts +9 -0
  85. package/lib/mime.d.ts.map +1 -0
  86. package/lib/mime.js +99 -0
  87. package/lib/mime.js.map +1 -0
  88. package/lib/outbound-media.d.ts +5 -0
  89. package/lib/outbound-media.d.ts.map +1 -0
  90. package/lib/outbound-media.js +122 -0
  91. package/lib/outbound-media.js.map +1 -0
  92. package/lib/pic-decrypt.d.ts +10 -0
  93. package/lib/pic-decrypt.d.ts.map +1 -0
  94. package/lib/pic-decrypt.js +89 -0
  95. package/lib/pic-decrypt.js.map +1 -0
  96. package/lib/routes.d.ts +8 -0
  97. package/lib/routes.d.ts.map +1 -0
  98. package/lib/routes.js +29 -0
  99. package/lib/routes.js.map +1 -0
  100. package/lib/silk-transcode.d.ts +9 -0
  101. package/lib/silk-transcode.d.ts.map +1 -0
  102. package/lib/silk-transcode.js +64 -0
  103. package/lib/silk-transcode.js.map +1 -0
  104. package/lib/types.d.ts +26 -0
  105. package/lib/types.d.ts.map +1 -0
  106. package/lib/types.js +2 -0
  107. package/lib/types.js.map +1 -0
  108. package/lib/typing-indicator.d.ts +32 -0
  109. package/lib/typing-indicator.d.ts.map +1 -0
  110. package/lib/typing-indicator.js +95 -0
  111. package/lib/typing-indicator.js.map +1 -0
  112. package/lib/upload.d.ts +43 -0
  113. package/lib/upload.d.ts.map +1 -0
  114. package/lib/upload.js +106 -0
  115. package/lib/upload.js.map +1 -0
  116. package/lib/weixin-inbound.d.ts +71 -0
  117. package/lib/weixin-inbound.d.ts.map +1 -0
  118. package/lib/weixin-inbound.js +201 -0
  119. package/lib/weixin-inbound.js.map +1 -0
  120. package/lib/weixin-send-media.d.ts +22 -0
  121. package/lib/weixin-send-media.d.ts.map +1 -0
  122. package/lib/weixin-send-media.js +54 -0
  123. package/lib/weixin-send-media.js.map +1 -0
  124. package/lib/weixin-send.d.ts +65 -0
  125. package/lib/weixin-send.d.ts.map +1 -0
  126. package/lib/weixin-send.js +181 -0
  127. package/lib/weixin-send.js.map +1 -0
  128. package/package.json +91 -0
  129. package/src/adapter.ts +16 -0
  130. package/src/aes-ecb.ts +21 -0
  131. package/src/bot.ts +483 -0
  132. package/src/cdn-upload.test.ts +177 -0
  133. package/src/cdn-upload.ts +87 -0
  134. package/src/cdn-url.ts +20 -0
  135. package/src/context-store.ts +8 -0
  136. package/src/credentials.ts +64 -0
  137. package/src/ilink-api.ts +349 -0
  138. package/src/ilink-config-cache.ts +79 -0
  139. package/src/ilink-logger.ts +36 -0
  140. package/src/ilink-meta.ts +60 -0
  141. package/src/ilink-random.ts +17 -0
  142. package/src/ilink-redact.ts +54 -0
  143. package/src/ilink-session-guard.ts +58 -0
  144. package/src/ilink-types.ts +261 -0
  145. package/src/index.ts +60 -0
  146. package/src/login-assist-routes.ts +36 -0
  147. package/src/login.ts +166 -0
  148. package/src/media-download.ts +149 -0
  149. package/src/mime.test.ts +74 -0
  150. package/src/mime.ts +105 -0
  151. package/src/outbound-media.ts +141 -0
  152. package/src/pic-decrypt.ts +101 -0
  153. package/src/routes.ts +39 -0
  154. package/src/silk-transcode.test.ts +58 -0
  155. package/src/silk-transcode.ts +74 -0
  156. package/src/silk-wasm.d.ts +6 -0
  157. package/src/types.ts +26 -0
  158. package/src/typing-indicator.ts +139 -0
  159. package/src/upload.test.ts +305 -0
  160. package/src/upload.ts +157 -0
  161. package/src/weixin-inbound.ts +257 -0
  162. package/src/weixin-send-media.ts +72 -0
  163. package/src/weixin-send.ts +237 -0
@@ -0,0 +1,201 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { logger } from "./ilink-logger.js";
4
+ import { generateId } from "./ilink-random.js";
5
+ import { MessageItemType } from "./ilink-types.js";
6
+ import { resolveStateDir } from "./credentials.js";
7
+ // ---------------------------------------------------------------------------
8
+ // Context token store (in-process cache + disk persistence)
9
+ // ---------------------------------------------------------------------------
10
+ /**
11
+ * contextToken is issued per-message by the Weixin getupdates API and must
12
+ * be echoed verbatim in every outbound send. The in-memory map is the primary
13
+ * lookup; a disk-backed file per account ensures tokens survive gateway restarts.
14
+ */
15
+ const contextTokenStore = new Map();
16
+ function contextTokenKey(accountId, userId) {
17
+ return `${accountId}:${userId}`;
18
+ }
19
+ // ---------------------------------------------------------------------------
20
+ // Disk persistence helpers
21
+ // ---------------------------------------------------------------------------
22
+ function resolveContextTokenFilePath(accountId) {
23
+ return path.join(resolveStateDir(), "context-tokens", `${accountId}.context-tokens.json`);
24
+ }
25
+ /** Persist all context tokens for a given account to disk. */
26
+ function persistContextTokens(accountId) {
27
+ const prefix = `${accountId}:`;
28
+ const tokens = {};
29
+ for (const [k, v] of contextTokenStore) {
30
+ if (k.startsWith(prefix)) {
31
+ tokens[k.slice(prefix.length)] = v;
32
+ }
33
+ }
34
+ const filePath = resolveContextTokenFilePath(accountId);
35
+ try {
36
+ const dir = path.dirname(filePath);
37
+ fs.mkdirSync(dir, { recursive: true });
38
+ fs.writeFileSync(filePath, JSON.stringify(tokens, null, 0), "utf-8");
39
+ }
40
+ catch (err) {
41
+ logger.warn(`persistContextTokens: failed to write ${filePath}: ${String(err)}`);
42
+ }
43
+ }
44
+ /**
45
+ * Restore persisted context tokens for an account into the in-memory map.
46
+ * Called once during gateway startAccount to survive restarts.
47
+ */
48
+ export function restoreContextTokens(accountId) {
49
+ const filePath = resolveContextTokenFilePath(accountId);
50
+ try {
51
+ if (!fs.existsSync(filePath))
52
+ return;
53
+ const raw = fs.readFileSync(filePath, "utf-8");
54
+ const tokens = JSON.parse(raw);
55
+ let count = 0;
56
+ for (const [userId, token] of Object.entries(tokens)) {
57
+ if (typeof token === "string" && token) {
58
+ contextTokenStore.set(contextTokenKey(accountId, userId), token);
59
+ count++;
60
+ }
61
+ }
62
+ logger.info(`restoreContextTokens: restored ${count} tokens for account=${accountId}`);
63
+ }
64
+ catch (err) {
65
+ logger.warn(`restoreContextTokens: failed to read ${filePath}: ${String(err)}`);
66
+ }
67
+ }
68
+ /** Remove all context tokens for a given account (memory + disk). */
69
+ export function clearContextTokensForAccount(accountId) {
70
+ const prefix = `${accountId}:`;
71
+ for (const k of [...contextTokenStore.keys()]) {
72
+ if (k.startsWith(prefix)) {
73
+ contextTokenStore.delete(k);
74
+ }
75
+ }
76
+ const filePath = resolveContextTokenFilePath(accountId);
77
+ try {
78
+ if (fs.existsSync(filePath))
79
+ fs.unlinkSync(filePath);
80
+ }
81
+ catch (err) {
82
+ logger.warn(`clearContextTokensForAccount: failed to remove ${filePath}: ${String(err)}`);
83
+ }
84
+ logger.info(`clearContextTokensForAccount: cleared tokens for account=${accountId}`);
85
+ }
86
+ /** Store a context token for a given account+user pair (memory + disk). */
87
+ export function setContextToken(accountId, userId, token) {
88
+ const k = contextTokenKey(accountId, userId);
89
+ logger.debug(`setContextToken: key=${k}`);
90
+ contextTokenStore.set(k, token);
91
+ persistContextTokens(accountId);
92
+ }
93
+ /** Retrieve the cached context token for a given account+user pair. */
94
+ export function getContextToken(accountId, userId) {
95
+ const k = contextTokenKey(accountId, userId);
96
+ const val = contextTokenStore.get(k);
97
+ logger.debug(`getContextToken: key=${k} found=${val !== undefined} storeSize=${contextTokenStore.size}`);
98
+ return val;
99
+ }
100
+ /**
101
+ * Find all accountIds that have an active contextToken for the given userId.
102
+ * Used to infer the sending bot account from the recipient address when
103
+ * accountId is not explicitly provided (e.g. cron delivery).
104
+ *
105
+ * Returns all matching accountIds (not just the first) so the caller can
106
+ * detect ambiguity when multiple accounts have sessions with the same user.
107
+ */
108
+ export function findAccountIdsByContextToken(accountIds, userId) {
109
+ return accountIds.filter((id) => contextTokenStore.has(contextTokenKey(id, userId)));
110
+ }
111
+ // ---------------------------------------------------------------------------
112
+ // Message ID generation
113
+ // ---------------------------------------------------------------------------
114
+ function generateMessageSid() {
115
+ return generateId("openclaw-weixin");
116
+ }
117
+ /** Returns true if the message item is a media type (image, video, file, or voice). */
118
+ export function isMediaItem(item) {
119
+ return (item.type === MessageItemType.IMAGE ||
120
+ item.type === MessageItemType.VIDEO ||
121
+ item.type === MessageItemType.FILE ||
122
+ item.type === MessageItemType.VOICE);
123
+ }
124
+ export function bodyFromItemList(itemList) {
125
+ if (!itemList?.length)
126
+ return "";
127
+ for (const item of itemList) {
128
+ if (item.type === MessageItemType.TEXT && item.text_item?.text != null) {
129
+ const text = String(item.text_item.text);
130
+ const ref = item.ref_msg;
131
+ if (!ref)
132
+ return text;
133
+ // Quoted media is passed as MediaPath; only include the current text as body.
134
+ if (ref.message_item && isMediaItem(ref.message_item))
135
+ return text;
136
+ // Build quoted context from both title and message_item content.
137
+ const parts = [];
138
+ if (ref.title)
139
+ parts.push(ref.title);
140
+ if (ref.message_item) {
141
+ const refBody = bodyFromItemList([ref.message_item]);
142
+ if (refBody)
143
+ parts.push(refBody);
144
+ }
145
+ if (!parts.length)
146
+ return text;
147
+ return `[引用: ${parts.join(" | ")}]\n${text}`;
148
+ }
149
+ // 语音转文字:如果语音消息有 text 字段,直接使用文字内容
150
+ if (item.type === MessageItemType.VOICE && item.voice_item?.text) {
151
+ return item.voice_item.text;
152
+ }
153
+ }
154
+ return "";
155
+ }
156
+ /**
157
+ * Convert a WeixinMessage from getUpdates to the inbound MsgContext for the core pipeline.
158
+ * Media: only pass MediaPath (local file, after CDN download + decrypt).
159
+ * We never pass MediaUrl — the upstream CDN URL is encrypted/auth-only.
160
+ * Priority when multiple media types present: image > video > file > voice.
161
+ */
162
+ export function weixinMessageToMsgContext(msg, accountId, opts) {
163
+ const from_user_id = msg.from_user_id ?? "";
164
+ const ctx = {
165
+ Body: bodyFromItemList(msg.item_list),
166
+ From: from_user_id,
167
+ To: from_user_id,
168
+ AccountId: accountId,
169
+ OriginatingChannel: "openclaw-weixin",
170
+ OriginatingTo: from_user_id,
171
+ MessageSid: generateMessageSid(),
172
+ Timestamp: msg.create_time_ms,
173
+ Provider: "openclaw-weixin",
174
+ ChatType: "direct",
175
+ };
176
+ if (msg.context_token) {
177
+ ctx.context_token = msg.context_token;
178
+ }
179
+ if (opts?.decryptedPicPath) {
180
+ ctx.MediaPath = opts.decryptedPicPath;
181
+ ctx.MediaType = "image/*";
182
+ }
183
+ else if (opts?.decryptedVideoPath) {
184
+ ctx.MediaPath = opts.decryptedVideoPath;
185
+ ctx.MediaType = "video/mp4";
186
+ }
187
+ else if (opts?.decryptedFilePath) {
188
+ ctx.MediaPath = opts.decryptedFilePath;
189
+ ctx.MediaType = opts.fileMediaType ?? "application/octet-stream";
190
+ }
191
+ else if (opts?.decryptedVoicePath) {
192
+ ctx.MediaPath = opts.decryptedVoicePath;
193
+ ctx.MediaType = opts.voiceMediaType ?? "audio/wav";
194
+ }
195
+ return ctx;
196
+ }
197
+ /** Extract the context_token from an inbound WeixinMsgContext. */
198
+ export function getContextTokenFromMsgContext(ctx) {
199
+ return ctx.context_token;
200
+ }
201
+ //# sourceMappingURL=weixin-inbound.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weixin-inbound.js","sourceRoot":"","sources":["../src/weixin-inbound.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { WeixinApiOptions } from "./ilink-api.js";
2
+ /**
3
+ * Upload a local file and send it as a weixin message, routing by MIME type:
4
+ * video/* → uploadVideoToWeixin + sendVideoMessageWeixin
5
+ * image/* → uploadFileToWeixin + sendImageMessageWeixin
6
+ * else → uploadFileAttachmentToWeixin + sendFileMessageWeixin
7
+ *
8
+ * Used by both the auto-reply deliver path (monitor.ts) and the outbound
9
+ * sendMedia path (channel.ts) so they stay in sync.
10
+ */
11
+ export declare function sendWeixinMediaFile(params: {
12
+ filePath: string;
13
+ to: string;
14
+ text: string;
15
+ opts: WeixinApiOptions & {
16
+ contextToken?: string;
17
+ };
18
+ cdnBaseUrl: string;
19
+ }): Promise<{
20
+ messageId: string;
21
+ }>;
22
+ //# sourceMappingURL=weixin-send-media.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weixin-send-media.d.ts","sourceRoot":"","sources":["../src/weixin-send-media.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAMvD;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,gBAAgB,GAAG;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAiDjC"}
@@ -0,0 +1,54 @@
1
+ import path from "node:path";
2
+ import { logger } from "./ilink-logger.js";
3
+ import { getMimeFromFilename } from "./mime.js";
4
+ import { sendFileMessageWeixin, sendImageMessageWeixin, sendVideoMessageWeixin } from "./weixin-send.js";
5
+ import { uploadFileAttachmentToWeixin, uploadFileToWeixin, uploadVideoToWeixin } from "./upload.js";
6
+ /**
7
+ * Upload a local file and send it as a weixin message, routing by MIME type:
8
+ * video/* → uploadVideoToWeixin + sendVideoMessageWeixin
9
+ * image/* → uploadFileToWeixin + sendImageMessageWeixin
10
+ * else → uploadFileAttachmentToWeixin + sendFileMessageWeixin
11
+ *
12
+ * Used by both the auto-reply deliver path (monitor.ts) and the outbound
13
+ * sendMedia path (channel.ts) so they stay in sync.
14
+ */
15
+ export async function sendWeixinMediaFile(params) {
16
+ const { filePath, to, text, opts, cdnBaseUrl } = params;
17
+ const mime = getMimeFromFilename(filePath);
18
+ const uploadOpts = { baseUrl: opts.baseUrl, token: opts.token };
19
+ if (mime.startsWith("video/")) {
20
+ logger.info(`[weixin] sendWeixinMediaFile: uploading video filePath=${filePath} to=${to}`);
21
+ const uploaded = await uploadVideoToWeixin({
22
+ filePath,
23
+ toUserId: to,
24
+ opts: uploadOpts,
25
+ cdnBaseUrl,
26
+ });
27
+ logger.info(`[weixin] sendWeixinMediaFile: video upload done filekey=${uploaded.filekey} size=${uploaded.fileSize}`);
28
+ return sendVideoMessageWeixin({ to, text, uploaded, opts });
29
+ }
30
+ if (mime.startsWith("image/")) {
31
+ logger.info(`[weixin] sendWeixinMediaFile: uploading image filePath=${filePath} to=${to}`);
32
+ const uploaded = await uploadFileToWeixin({
33
+ filePath,
34
+ toUserId: to,
35
+ opts: uploadOpts,
36
+ cdnBaseUrl,
37
+ });
38
+ logger.info(`[weixin] sendWeixinMediaFile: image upload done filekey=${uploaded.filekey} size=${uploaded.fileSize}`);
39
+ return sendImageMessageWeixin({ to, text, uploaded, opts });
40
+ }
41
+ // File attachment: pdf, doc, zip, etc.
42
+ const fileName = path.basename(filePath);
43
+ logger.info(`[weixin] sendWeixinMediaFile: uploading file attachment filePath=${filePath} name=${fileName} to=${to}`);
44
+ const uploaded = await uploadFileAttachmentToWeixin({
45
+ filePath,
46
+ fileName,
47
+ toUserId: to,
48
+ opts: uploadOpts,
49
+ cdnBaseUrl,
50
+ });
51
+ logger.info(`[weixin] sendWeixinMediaFile: file upload done filekey=${uploaded.filekey} size=${uploaded.fileSize}`);
52
+ return sendFileMessageWeixin({ to, text, fileName, uploaded, opts });
53
+ }
54
+ //# sourceMappingURL=weixin-send-media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weixin-send-media.js","sourceRoot":"","sources":["../src/weixin-send-media.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AACzG,OAAO,EAAE,4BAA4B,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEpG;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,MAMzC;IACC,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;IACxD,MAAM,IAAI,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAqB,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;IAElF,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,0DAA0D,QAAQ,OAAO,EAAE,EAAE,CAAC,CAAC;QAC3F,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC;YACzC,QAAQ;YACR,QAAQ,EAAE,EAAE;YACZ,IAAI,EAAE,UAAU;YAChB,UAAU;SACX,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CACT,2DAA2D,QAAQ,CAAC,OAAO,SAAS,QAAQ,CAAC,QAAQ,EAAE,CACxG,CAAC;QACF,OAAO,sBAAsB,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,0DAA0D,QAAQ,OAAO,EAAE,EAAE,CAAC,CAAC;QAC3F,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC;YACxC,QAAQ;YACR,QAAQ,EAAE,EAAE;YACZ,IAAI,EAAE,UAAU;YAChB,UAAU;SACX,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CACT,2DAA2D,QAAQ,CAAC,OAAO,SAAS,QAAQ,CAAC,QAAQ,EAAE,CACxG,CAAC;QACF,OAAO,sBAAsB,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,uCAAuC;IACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACzC,MAAM,CAAC,IAAI,CACT,oEAAoE,QAAQ,SAAS,QAAQ,OAAO,EAAE,EAAE,CACzG,CAAC;IACF,MAAM,QAAQ,GAAG,MAAM,4BAA4B,CAAC;QAClD,QAAQ;QACR,QAAQ;QACR,QAAQ,EAAE,EAAE;QACZ,IAAI,EAAE,UAAU;QAChB,UAAU;KACX,CAAC,CAAC;IACH,MAAM,CAAC,IAAI,CACT,0DAA0D,QAAQ,CAAC,OAAO,SAAS,QAAQ,CAAC,QAAQ,EAAE,CACvG,CAAC;IACF,OAAO,qBAAqB,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;AACvE,CAAC"}
@@ -0,0 +1,65 @@
1
+ import type { WeixinApiOptions } from "./ilink-api.js";
2
+ import type { UploadedFileInfo } from "./upload.js";
3
+ /**
4
+ * Send a plain text message downstream.
5
+ */
6
+ export declare function sendMessageWeixin(params: {
7
+ to: string;
8
+ text: string;
9
+ opts: WeixinApiOptions & {
10
+ contextToken?: string;
11
+ };
12
+ }): Promise<{
13
+ messageId: string;
14
+ }>;
15
+ /**
16
+ * Send an image message downstream using a previously uploaded file.
17
+ * Optionally include a text caption as a separate TEXT item before the image.
18
+ *
19
+ * ImageItem fields:
20
+ * - media.encrypt_query_param: CDN download param
21
+ * - media.aes_key: AES key, base64-encoded
22
+ * - mid_size: original ciphertext file size
23
+ */
24
+ export declare function sendImageMessageWeixin(params: {
25
+ to: string;
26
+ text: string;
27
+ uploaded: UploadedFileInfo;
28
+ opts: WeixinApiOptions & {
29
+ contextToken?: string;
30
+ };
31
+ }): Promise<{
32
+ messageId: string;
33
+ }>;
34
+ /**
35
+ * Send a video message downstream using a previously uploaded file.
36
+ * VideoItem: media (CDN ref), video_size (ciphertext bytes).
37
+ * Includes an optional text caption sent as a separate TEXT item first.
38
+ */
39
+ export declare function sendVideoMessageWeixin(params: {
40
+ to: string;
41
+ text: string;
42
+ uploaded: UploadedFileInfo;
43
+ opts: WeixinApiOptions & {
44
+ contextToken?: string;
45
+ };
46
+ }): Promise<{
47
+ messageId: string;
48
+ }>;
49
+ /**
50
+ * Send a file attachment downstream using a previously uploaded file.
51
+ * FileItem: media (CDN ref), file_name, len (plaintext bytes as string).
52
+ * Includes an optional text caption sent as a separate TEXT item first.
53
+ */
54
+ export declare function sendFileMessageWeixin(params: {
55
+ to: string;
56
+ text: string;
57
+ fileName: string;
58
+ uploaded: UploadedFileInfo;
59
+ opts: WeixinApiOptions & {
60
+ contextToken?: string;
61
+ };
62
+ }): Promise<{
63
+ messageId: string;
64
+ }>;
65
+ //# sourceMappingURL=weixin-send.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weixin-send.d.ts","sourceRoot":"","sources":["../src/weixin-send.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAKvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA8CpD;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE;IAC9C,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,gBAAgB,GAAG;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACpD,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAwBjC;AAsDD;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,IAAI,EAAE,gBAAgB,GAAG;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACpD,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAsBjC;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,IAAI,EAAE,gBAAgB,GAAG;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACpD,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAmBjC;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,IAAI,EAAE,gBAAgB,GAAG;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACpD,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAmBjC"}
@@ -0,0 +1,181 @@
1
+ import { sendMessage as sendMessageApi } from "./ilink-api.js";
2
+ import { logger } from "./ilink-logger.js";
3
+ import { generateId } from "./ilink-random.js";
4
+ import { MessageItemType, MessageState, MessageType } from "./ilink-types.js";
5
+ function generateClientId() {
6
+ return generateId("weixin-ilink");
7
+ }
8
+ /** Build a SendMessageReq containing a single text message. */
9
+ function buildTextMessageReq(params) {
10
+ const { to, text, contextToken, clientId } = params;
11
+ const item_list = text
12
+ ? [{ type: MessageItemType.TEXT, text_item: { text } }]
13
+ : [];
14
+ return {
15
+ msg: {
16
+ from_user_id: "",
17
+ to_user_id: to,
18
+ client_id: clientId,
19
+ message_type: MessageType.BOT,
20
+ message_state: MessageState.FINISH,
21
+ item_list: item_list.length ? item_list : undefined,
22
+ context_token: contextToken ?? undefined,
23
+ },
24
+ };
25
+ }
26
+ /** Build a SendMessageReq from a reply payload (text only; image send uses sendImageMessageWeixin). */
27
+ function buildSendMessageReq(params) {
28
+ const { to, contextToken, payload, clientId } = params;
29
+ return buildTextMessageReq({
30
+ to,
31
+ text: payload.text ?? "",
32
+ contextToken,
33
+ clientId,
34
+ });
35
+ }
36
+ /**
37
+ * Send a plain text message downstream.
38
+ */
39
+ export async function sendMessageWeixin(params) {
40
+ const { to, text, opts } = params;
41
+ if (!opts.contextToken) {
42
+ logger.warn(`sendMessageWeixin: contextToken missing for to=${to}, sending without context`);
43
+ }
44
+ const clientId = generateClientId();
45
+ const req = buildSendMessageReq({
46
+ to,
47
+ contextToken: opts.contextToken,
48
+ payload: { text },
49
+ clientId,
50
+ });
51
+ try {
52
+ await sendMessageApi({
53
+ baseUrl: opts.baseUrl,
54
+ token: opts.token,
55
+ timeoutMs: opts.timeoutMs,
56
+ body: req,
57
+ });
58
+ }
59
+ catch (err) {
60
+ logger.error(`sendMessageWeixin: failed to=${to} clientId=${clientId} err=${String(err)}`);
61
+ throw err;
62
+ }
63
+ return { messageId: clientId };
64
+ }
65
+ /**
66
+ * Send one or more MessageItems (optionally preceded by a text caption) downstream.
67
+ * Each item is sent as its own request so that item_list always has exactly one entry.
68
+ */
69
+ async function sendMediaItems(params) {
70
+ const { to, text, mediaItem, opts, label } = params;
71
+ const items = [];
72
+ if (text) {
73
+ items.push({ type: MessageItemType.TEXT, text_item: { text } });
74
+ }
75
+ items.push(mediaItem);
76
+ let lastClientId = "";
77
+ for (const item of items) {
78
+ lastClientId = generateClientId();
79
+ const req = {
80
+ msg: {
81
+ from_user_id: "",
82
+ to_user_id: to,
83
+ client_id: lastClientId,
84
+ message_type: MessageType.BOT,
85
+ message_state: MessageState.FINISH,
86
+ item_list: [item],
87
+ context_token: opts.contextToken ?? undefined,
88
+ },
89
+ };
90
+ try {
91
+ await sendMessageApi({
92
+ baseUrl: opts.baseUrl,
93
+ token: opts.token,
94
+ timeoutMs: opts.timeoutMs,
95
+ body: req,
96
+ });
97
+ }
98
+ catch (err) {
99
+ logger.error(`${label}: failed to=${to} clientId=${lastClientId} err=${String(err)}`);
100
+ throw err;
101
+ }
102
+ }
103
+ logger.info(`${label}: success to=${to} clientId=${lastClientId}`);
104
+ return { messageId: lastClientId };
105
+ }
106
+ /**
107
+ * Send an image message downstream using a previously uploaded file.
108
+ * Optionally include a text caption as a separate TEXT item before the image.
109
+ *
110
+ * ImageItem fields:
111
+ * - media.encrypt_query_param: CDN download param
112
+ * - media.aes_key: AES key, base64-encoded
113
+ * - mid_size: original ciphertext file size
114
+ */
115
+ export async function sendImageMessageWeixin(params) {
116
+ const { to, text, uploaded, opts } = params;
117
+ if (!opts.contextToken) {
118
+ logger.warn(`sendImageMessageWeixin: contextToken missing for to=${to}, sending without context`);
119
+ }
120
+ logger.info(`sendImageMessageWeixin: to=${to} filekey=${uploaded.filekey} fileSize=${uploaded.fileSize} aeskey=present`);
121
+ const imageItem = {
122
+ type: MessageItemType.IMAGE,
123
+ image_item: {
124
+ media: {
125
+ encrypt_query_param: uploaded.downloadEncryptedQueryParam,
126
+ aes_key: Buffer.from(uploaded.aeskey).toString("base64"),
127
+ encrypt_type: 1,
128
+ },
129
+ mid_size: uploaded.fileSizeCiphertext,
130
+ },
131
+ };
132
+ return sendMediaItems({ to, text, mediaItem: imageItem, opts, label: "sendImageMessageWeixin" });
133
+ }
134
+ /**
135
+ * Send a video message downstream using a previously uploaded file.
136
+ * VideoItem: media (CDN ref), video_size (ciphertext bytes).
137
+ * Includes an optional text caption sent as a separate TEXT item first.
138
+ */
139
+ export async function sendVideoMessageWeixin(params) {
140
+ const { to, text, uploaded, opts } = params;
141
+ if (!opts.contextToken) {
142
+ logger.warn(`sendVideoMessageWeixin: contextToken missing for to=${to}, sending without context`);
143
+ }
144
+ const videoItem = {
145
+ type: MessageItemType.VIDEO,
146
+ video_item: {
147
+ media: {
148
+ encrypt_query_param: uploaded.downloadEncryptedQueryParam,
149
+ aes_key: Buffer.from(uploaded.aeskey).toString("base64"),
150
+ encrypt_type: 1,
151
+ },
152
+ video_size: uploaded.fileSizeCiphertext,
153
+ },
154
+ };
155
+ return sendMediaItems({ to, text, mediaItem: videoItem, opts, label: "sendVideoMessageWeixin" });
156
+ }
157
+ /**
158
+ * Send a file attachment downstream using a previously uploaded file.
159
+ * FileItem: media (CDN ref), file_name, len (plaintext bytes as string).
160
+ * Includes an optional text caption sent as a separate TEXT item first.
161
+ */
162
+ export async function sendFileMessageWeixin(params) {
163
+ const { to, text, fileName, uploaded, opts } = params;
164
+ if (!opts.contextToken) {
165
+ logger.warn(`sendFileMessageWeixin: contextToken missing for to=${to}, sending without context`);
166
+ }
167
+ const fileItem = {
168
+ type: MessageItemType.FILE,
169
+ file_item: {
170
+ media: {
171
+ encrypt_query_param: uploaded.downloadEncryptedQueryParam,
172
+ aes_key: Buffer.from(uploaded.aeskey).toString("base64"),
173
+ encrypt_type: 1,
174
+ },
175
+ file_name: fileName,
176
+ len: String(uploaded.fileSize),
177
+ },
178
+ };
179
+ return sendMediaItems({ to, text, mediaItem: fileItem, opts, label: "sendFileMessageWeixin" });
180
+ }
181
+ //# sourceMappingURL=weixin-send.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weixin-send.js","sourceRoot":"","sources":["../src/weixin-send.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "@zhin.js/adapter-weixin-ilink",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Zhin.js adapter for WeChat personal account via Tencent iLink Bot API (ClawBot)",
6
+ "main": "./lib/index.js",
7
+ "types": "./lib/index.d.ts",
8
+ "keywords": [
9
+ "zhin",
10
+ "zhin.js",
11
+ "bot",
12
+ "adapter",
13
+ "wechat",
14
+ "weixin",
15
+ "ilink",
16
+ "clawbot"
17
+ ],
18
+ "author": {
19
+ "name": "lc-cn",
20
+ "email": "admin@liucl.cn",
21
+ "url": "https://github.com/lc-cn"
22
+ },
23
+ "license": "MIT",
24
+ "repository": {
25
+ "url": "git+https://github.com/zhinjs/zhin.git",
26
+ "type": "git",
27
+ "directory": "plugins/adapters/weixin-ilink"
28
+ },
29
+ "files": [
30
+ "src",
31
+ "lib",
32
+ "client",
33
+ "dist",
34
+ "skills",
35
+ "README.md",
36
+ "CHANGELOG.md"
37
+ ],
38
+ "publishConfig": {
39
+ "access": "public",
40
+ "registry": "https://registry.npmjs.org"
41
+ },
42
+ "exports": {
43
+ ".": {
44
+ "types": "./lib/index.d.ts",
45
+ "development": "./src/index.ts",
46
+ "import": "./lib/index.js"
47
+ }
48
+ },
49
+ "dependencies": {},
50
+ "devDependencies": {
51
+ "@radix-ui/themes": "^3.2.1",
52
+ "@types/react": "^19.2.15",
53
+ "@types/react-dom": "^19.2.3",
54
+ "lucide-react": "^1.17.0",
55
+ "radix-ui": "^1.4.3",
56
+ "typescript": "^6.0.3",
57
+ "@zhin.js/cli": "1.0.88",
58
+ "@zhin.js/host-api": "0.0.4",
59
+ "@zhin.js/contract": "1.0.1",
60
+ "zhin.js": "1.0.92"
61
+ },
62
+ "peerDependencies": {
63
+ "@zhin.js/agent": "0.1.31",
64
+ "@zhin.js/contract": "1.0.1",
65
+ "@zhin.js/client": "2.0.2",
66
+ "@zhin.js/core": "1.1.33",
67
+ "@zhin.js/host-api": "0.0.4",
68
+ "zhin.js": "1.0.92",
69
+ "@zhin.js/host-router": "0.0.3"
70
+ },
71
+ "peerDependenciesMeta": {
72
+ "@zhin.js/agent": {
73
+ "optional": true
74
+ },
75
+ "@zhin.js/client": {
76
+ "optional": true
77
+ },
78
+ "@zhin.js/host-api": {
79
+ "optional": true
80
+ },
81
+ "@zhin.js/host-router": {
82
+ "optional": true
83
+ }
84
+ },
85
+ "ilink_appid": "bot",
86
+ "scripts": {
87
+ "build": "zhin build",
88
+ "build:node": "tsc",
89
+ "clean": "rimraf lib dist"
90
+ }
91
+ }
package/src/adapter.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 微信 iLink(ClawBot)适配器
3
+ */
4
+ import { Adapter, type Plugin } from "zhin.js";
5
+ import { WeixinIlinkBot } from "./bot.js";
6
+ import type { WeixinIlinkBotConfig } from "./types.js";
7
+
8
+ export class WeixinIlinkAdapter extends Adapter<WeixinIlinkBot> {
9
+ constructor(plugin: Plugin) {
10
+ super(plugin, "weixin-ilink", []);
11
+ }
12
+
13
+ createBot(config: WeixinIlinkBotConfig): WeixinIlinkBot {
14
+ return new WeixinIlinkBot(this, config);
15
+ }
16
+ }