@pawastation/wechat-kf 0.1.2 → 0.2.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/README.md +34 -28
- package/README.zh-CN.md +34 -28
- package/dist/index.d.ts +5 -15
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/src/accounts.d.ts +2 -1
- package/dist/src/accounts.js +61 -19
- package/dist/src/accounts.js.map +1 -1
- package/dist/src/api.d.ts +31 -2
- package/dist/src/api.js +41 -13
- package/dist/src/api.js.map +1 -1
- package/dist/src/bot.d.ts +10 -8
- package/dist/src/bot.js +231 -78
- package/dist/src/bot.js.map +1 -1
- package/dist/src/channel.d.ts +7 -106
- package/dist/src/channel.js +208 -71
- package/dist/src/channel.js.map +1 -1
- package/dist/src/config-schema.d.ts +0 -6
- package/dist/src/config-schema.js +2 -7
- package/dist/src/config-schema.js.map +1 -1
- package/dist/src/constants.d.ts +20 -0
- package/dist/src/constants.js +29 -0
- package/dist/src/constants.js.map +1 -1
- package/dist/src/crypto.js +7 -6
- package/dist/src/crypto.js.map +1 -1
- package/dist/src/monitor.d.ts +27 -14
- package/dist/src/monitor.js +67 -120
- package/dist/src/monitor.js.map +1 -1
- package/dist/src/outbound.d.ts +10 -44
- package/dist/src/outbound.js +277 -92
- package/dist/src/outbound.js.map +1 -1
- package/dist/src/reply-dispatcher.d.ts +2 -6
- package/dist/src/reply-dispatcher.js +131 -32
- package/dist/src/reply-dispatcher.js.map +1 -1
- package/dist/src/runtime.d.ts +1 -119
- package/dist/src/runtime.js +2 -1
- package/dist/src/runtime.js.map +1 -1
- package/dist/src/send-utils.d.ts +13 -0
- package/dist/src/send-utils.js +56 -4
- package/dist/src/send-utils.js.map +1 -1
- package/dist/src/token.js +7 -3
- package/dist/src/token.js.map +1 -1
- package/dist/src/types.d.ts +68 -6
- package/dist/src/webhook.d.ts +16 -16
- package/dist/src/webhook.js +92 -75
- package/dist/src/webhook.js.map +1 -1
- package/dist/src/wechat-kf-directives.d.ts +132 -9
- package/dist/src/wechat-kf-directives.js +535 -24
- package/dist/src/wechat-kf-directives.js.map +1 -1
- package/index.ts +22 -12
- package/openclaw.plugin.json +1 -3
- package/package.json +3 -2
- package/dist/src/chunk-utils.d.ts +0 -18
- package/dist/src/chunk-utils.js +0 -58
- package/dist/src/chunk-utils.js.map +0 -1
package/dist/src/types.d.ts
CHANGED
|
@@ -4,15 +4,10 @@ export type WechatKfConfig = {
|
|
|
4
4
|
appSecret?: string;
|
|
5
5
|
token?: string;
|
|
6
6
|
encodingAESKey?: string;
|
|
7
|
-
webhookPort?: number;
|
|
8
7
|
webhookPath?: string;
|
|
9
8
|
dmPolicy?: "open" | "pairing" | "allowlist" | "disabled";
|
|
10
9
|
allowFrom?: string[];
|
|
11
10
|
};
|
|
12
|
-
export type OpenClawConfig = {
|
|
13
|
-
channels?: Record<string, unknown>;
|
|
14
|
-
[key: string]: unknown;
|
|
15
|
-
};
|
|
16
11
|
export type ResolvedWechatKfAccount = {
|
|
17
12
|
accountId: string;
|
|
18
13
|
enabled: boolean;
|
|
@@ -22,7 +17,6 @@ export type ResolvedWechatKfAccount = {
|
|
|
22
17
|
token?: string;
|
|
23
18
|
encodingAESKey?: string;
|
|
24
19
|
openKfId?: string;
|
|
25
|
-
webhookPort: number;
|
|
26
20
|
webhookPath: string;
|
|
27
21
|
config: WechatKfConfig;
|
|
28
22
|
};
|
|
@@ -42,6 +36,8 @@ export type WechatKfSyncMsgRequest = {
|
|
|
42
36
|
export type WechatKfMergedMsgItem = {
|
|
43
37
|
sender_name?: string;
|
|
44
38
|
msg_content?: string;
|
|
39
|
+
send_time?: number;
|
|
40
|
+
msgtype?: string;
|
|
45
41
|
};
|
|
46
42
|
export type WechatKfMessage = {
|
|
47
43
|
msgid: string;
|
|
@@ -53,6 +49,7 @@ export type WechatKfMessage = {
|
|
|
53
49
|
msgtype: string;
|
|
54
50
|
text?: {
|
|
55
51
|
content: string;
|
|
52
|
+
menu_id?: string;
|
|
56
53
|
};
|
|
57
54
|
image?: {
|
|
58
55
|
media_id: string;
|
|
@@ -103,6 +100,7 @@ export type WechatKfMessage = {
|
|
|
103
100
|
title?: string;
|
|
104
101
|
appid?: string;
|
|
105
102
|
pagepath?: string;
|
|
103
|
+
thumb_media_id?: string;
|
|
106
104
|
};
|
|
107
105
|
business_card?: {
|
|
108
106
|
userid?: string;
|
|
@@ -115,6 +113,22 @@ export type WechatKfMessage = {
|
|
|
115
113
|
}[];
|
|
116
114
|
tail_content?: string;
|
|
117
115
|
};
|
|
116
|
+
channels_shop_product?: {
|
|
117
|
+
product_id?: string;
|
|
118
|
+
head_image?: string;
|
|
119
|
+
title?: string;
|
|
120
|
+
sales_price?: string;
|
|
121
|
+
shop_nickname?: string;
|
|
122
|
+
shop_head_image?: string;
|
|
123
|
+
};
|
|
124
|
+
channels_shop_order?: {
|
|
125
|
+
order_id?: string;
|
|
126
|
+
product_titles?: string;
|
|
127
|
+
price_wording?: string;
|
|
128
|
+
state?: string;
|
|
129
|
+
image_url?: string;
|
|
130
|
+
shop_nickname?: string;
|
|
131
|
+
};
|
|
118
132
|
};
|
|
119
133
|
export type WechatKfSyncMsgResponse = {
|
|
120
134
|
errcode: number;
|
|
@@ -149,6 +163,54 @@ export type WechatKfSendMsgRequest = {
|
|
|
149
163
|
url: string;
|
|
150
164
|
thumb_media_id: string;
|
|
151
165
|
};
|
|
166
|
+
miniprogram?: {
|
|
167
|
+
appid: string;
|
|
168
|
+
title?: string;
|
|
169
|
+
thumb_media_id: string;
|
|
170
|
+
pagepath: string;
|
|
171
|
+
};
|
|
172
|
+
msgmenu?: {
|
|
173
|
+
head_content?: string;
|
|
174
|
+
list?: Array<{
|
|
175
|
+
type: "click";
|
|
176
|
+
click: {
|
|
177
|
+
id?: string;
|
|
178
|
+
content: string;
|
|
179
|
+
};
|
|
180
|
+
} | {
|
|
181
|
+
type: "view";
|
|
182
|
+
view: {
|
|
183
|
+
url: string;
|
|
184
|
+
content: string;
|
|
185
|
+
};
|
|
186
|
+
} | {
|
|
187
|
+
type: "miniprogram";
|
|
188
|
+
miniprogram: {
|
|
189
|
+
appid: string;
|
|
190
|
+
pagepath: string;
|
|
191
|
+
content: string;
|
|
192
|
+
};
|
|
193
|
+
} | {
|
|
194
|
+
type: "text";
|
|
195
|
+
text: {
|
|
196
|
+
content: string;
|
|
197
|
+
no_newline?: number;
|
|
198
|
+
};
|
|
199
|
+
}>;
|
|
200
|
+
tail_content?: string;
|
|
201
|
+
};
|
|
202
|
+
location?: {
|
|
203
|
+
name?: string;
|
|
204
|
+
address?: string;
|
|
205
|
+
latitude: number;
|
|
206
|
+
longitude: number;
|
|
207
|
+
};
|
|
208
|
+
business_card?: {
|
|
209
|
+
userid: string;
|
|
210
|
+
};
|
|
211
|
+
ca_link?: {
|
|
212
|
+
link_url: string;
|
|
213
|
+
};
|
|
152
214
|
};
|
|
153
215
|
export type WechatKfSendMsgResponse = {
|
|
154
216
|
errcode: number;
|
package/dist/src/webhook.d.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HTTP webhook
|
|
2
|
+
* HTTP webhook handler for WeChat KF callbacks
|
|
3
3
|
*
|
|
4
4
|
* Handles:
|
|
5
5
|
* - GET: URL verification (echostr decrypt)
|
|
6
6
|
* - POST: Event notification (decrypt XML → trigger sync_msg)
|
|
7
|
+
*
|
|
8
|
+
* Designed to be registered on the framework's shared gateway server
|
|
9
|
+
* via api.registerHttpHandler(handleWechatKfWebhook).
|
|
10
|
+
*/
|
|
11
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
12
|
+
export declare function parseQuery(url: string): Record<string, string>;
|
|
13
|
+
export declare function readBody(req: IncomingMessage, maxSize?: number): Promise<string>;
|
|
14
|
+
/** Extract a tag value from XML string */
|
|
15
|
+
export declare function xmlTag(xml: string, tag: string): string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Framework-compatible HTTP handler for WeChat KF webhooks.
|
|
18
|
+
*
|
|
19
|
+
* Returns `true` if the request was handled, `false` if the path doesn't
|
|
20
|
+
* match (so the framework can try other plugins).
|
|
7
21
|
*/
|
|
8
|
-
|
|
9
|
-
export type WebhookHandler = (openKfId: string, token: string) => void | Promise<void>;
|
|
10
|
-
export type WebhookOptions = {
|
|
11
|
-
port: number;
|
|
12
|
-
path: string;
|
|
13
|
-
callbackToken: string;
|
|
14
|
-
encodingAESKey: string;
|
|
15
|
-
corpId: string;
|
|
16
|
-
onEvent: WebhookHandler;
|
|
17
|
-
log?: {
|
|
18
|
-
info: (...a: unknown[]) => void;
|
|
19
|
-
error: (...a: unknown[]) => void;
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
export declare function createWebhookServer(opts: WebhookOptions): Server;
|
|
22
|
+
export declare function handleWechatKfWebhook(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
|
package/dist/src/webhook.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HTTP webhook
|
|
2
|
+
* HTTP webhook handler for WeChat KF callbacks
|
|
3
3
|
*
|
|
4
4
|
* Handles:
|
|
5
5
|
* - GET: URL verification (echostr decrypt)
|
|
6
6
|
* - POST: Event notification (decrypt XML → trigger sync_msg)
|
|
7
|
+
*
|
|
8
|
+
* Designed to be registered on the framework's shared gateway server
|
|
9
|
+
* via api.registerHttpHandler(handleWechatKfWebhook).
|
|
7
10
|
*/
|
|
8
|
-
import {
|
|
11
|
+
import { registerKfId } from "./accounts.js";
|
|
12
|
+
import { handleWebhookEvent } from "./bot.js";
|
|
13
|
+
import { formatError, logTag } from "./constants.js";
|
|
9
14
|
import { decrypt, verifySignature } from "./crypto.js";
|
|
10
|
-
|
|
15
|
+
import { getSharedContext } from "./monitor.js";
|
|
16
|
+
export function parseQuery(url) {
|
|
11
17
|
const idx = url.indexOf("?");
|
|
12
18
|
if (idx < 0)
|
|
13
19
|
return {};
|
|
@@ -23,7 +29,7 @@ function parseQuery(url) {
|
|
|
23
29
|
}
|
|
24
30
|
return params;
|
|
25
31
|
}
|
|
26
|
-
function readBody(req, maxSize = 64 * 1024) {
|
|
32
|
+
export function readBody(req, maxSize = 64 * 1024) {
|
|
27
33
|
return new Promise((resolve, reject) => {
|
|
28
34
|
const chunks = [];
|
|
29
35
|
let size = 0;
|
|
@@ -38,7 +44,7 @@ function readBody(req, maxSize = 64 * 1024) {
|
|
|
38
44
|
// can still write a response (413). Resume drains remaining data.
|
|
39
45
|
req.removeAllListeners("data");
|
|
40
46
|
req.resume();
|
|
41
|
-
reject(new Error(
|
|
47
|
+
reject(new Error(`${logTag()} request body too large`));
|
|
42
48
|
return;
|
|
43
49
|
}
|
|
44
50
|
chunks.push(c);
|
|
@@ -54,85 +60,96 @@ function readBody(req, maxSize = 64 * 1024) {
|
|
|
54
60
|
});
|
|
55
61
|
}
|
|
56
62
|
/** Extract a tag value from XML string */
|
|
57
|
-
function xmlTag(xml, tag) {
|
|
63
|
+
export function xmlTag(xml, tag) {
|
|
58
64
|
const re = new RegExp(`<${tag}><!\\[CDATA\\[(.+?)\\]\\]></${tag}>|<${tag}>(.+?)</${tag}>`);
|
|
59
65
|
const m = xml.match(re);
|
|
60
66
|
return m?.[1] ?? m?.[2];
|
|
61
67
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
const { message } = decrypt(encodingAESKey, echostr);
|
|
88
|
-
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
89
|
-
res.end(message);
|
|
90
|
-
return;
|
|
68
|
+
/**
|
|
69
|
+
* Framework-compatible HTTP handler for WeChat KF webhooks.
|
|
70
|
+
*
|
|
71
|
+
* Returns `true` if the request was handled, `false` if the path doesn't
|
|
72
|
+
* match (so the framework can try other plugins).
|
|
73
|
+
*/
|
|
74
|
+
export async function handleWechatKfWebhook(req, res) {
|
|
75
|
+
const ctx = getSharedContext();
|
|
76
|
+
if (!ctx)
|
|
77
|
+
return false;
|
|
78
|
+
const url = req.url ?? "/";
|
|
79
|
+
const pathname = url.split("?")[0];
|
|
80
|
+
if (pathname !== ctx.webhookPath)
|
|
81
|
+
return false;
|
|
82
|
+
const query = parseQuery(url);
|
|
83
|
+
try {
|
|
84
|
+
if (req.method === "GET") {
|
|
85
|
+
// URL verification
|
|
86
|
+
const { msg_signature, timestamp, nonce, echostr } = query;
|
|
87
|
+
if (!msg_signature || !timestamp || !nonce || !echostr) {
|
|
88
|
+
res.writeHead(400);
|
|
89
|
+
res.end("missing params");
|
|
90
|
+
return true;
|
|
91
91
|
}
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (!encryptedMsg || !msg_signature || !timestamp || !nonce) {
|
|
98
|
-
res.writeHead(400);
|
|
99
|
-
res.end("bad request");
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
if (!verifySignature(callbackToken, timestamp, nonce, encryptedMsg, msg_signature)) {
|
|
103
|
-
res.writeHead(403);
|
|
104
|
-
res.end("signature mismatch");
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
const { message } = decrypt(encodingAESKey, encryptedMsg);
|
|
108
|
-
// Parse decrypted XML for Token and OpenKfId
|
|
109
|
-
const eventToken = xmlTag(message, "Token") ?? "";
|
|
110
|
-
const openKfId = xmlTag(message, "OpenKfId") ?? "";
|
|
111
|
-
// Respond immediately, process async
|
|
112
|
-
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
113
|
-
res.end("success");
|
|
114
|
-
// Trigger message sync
|
|
115
|
-
Promise.resolve(onEvent(openKfId, eventToken)).catch((err) => {
|
|
116
|
-
(log?.error ?? console.error)("[wechat-kf] onEvent error:", err);
|
|
117
|
-
});
|
|
118
|
-
return;
|
|
92
|
+
if (!verifySignature(ctx.callbackToken, timestamp, nonce, echostr, msg_signature)) {
|
|
93
|
+
ctx.botCtx.log?.warn(`${logTag()} callback signature verification failed (GET)`);
|
|
94
|
+
res.writeHead(403);
|
|
95
|
+
res.end("signature mismatch");
|
|
96
|
+
return true;
|
|
119
97
|
}
|
|
120
|
-
|
|
121
|
-
res.
|
|
98
|
+
const { message } = decrypt(ctx.encodingAESKey, echostr);
|
|
99
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
100
|
+
res.end(message);
|
|
101
|
+
return true;
|
|
122
102
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
103
|
+
if (req.method === "POST") {
|
|
104
|
+
const { msg_signature, timestamp, nonce } = query;
|
|
105
|
+
const body = await readBody(req);
|
|
106
|
+
// Extract Encrypt from XML
|
|
107
|
+
const encryptedMsg = xmlTag(body, "Encrypt");
|
|
108
|
+
if (!encryptedMsg || !msg_signature || !timestamp || !nonce) {
|
|
109
|
+
res.writeHead(400);
|
|
110
|
+
res.end("bad request");
|
|
111
|
+
return true;
|
|
128
112
|
}
|
|
129
|
-
(
|
|
130
|
-
|
|
131
|
-
res.writeHead(
|
|
132
|
-
res.end("
|
|
113
|
+
if (!verifySignature(ctx.callbackToken, timestamp, nonce, encryptedMsg, msg_signature)) {
|
|
114
|
+
ctx.botCtx.log?.warn(`${logTag()} callback signature verification failed (POST)`);
|
|
115
|
+
res.writeHead(403);
|
|
116
|
+
res.end("signature mismatch");
|
|
117
|
+
return true;
|
|
133
118
|
}
|
|
119
|
+
const { message } = decrypt(ctx.encodingAESKey, encryptedMsg);
|
|
120
|
+
// Parse decrypted XML for Token and OpenKfId
|
|
121
|
+
const eventToken = xmlTag(message, "Token") ?? "";
|
|
122
|
+
const openKfId = xmlTag(message, "OpenKfId") ?? "";
|
|
123
|
+
// Respond immediately, process async
|
|
124
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
125
|
+
res.end("success");
|
|
126
|
+
// Fire-and-forget: register kfId and trigger message sync
|
|
127
|
+
Promise.resolve((async () => {
|
|
128
|
+
if (openKfId) {
|
|
129
|
+
await registerKfId(openKfId);
|
|
130
|
+
}
|
|
131
|
+
await handleWebhookEvent(ctx.botCtx, openKfId, eventToken);
|
|
132
|
+
})()).catch((err) => {
|
|
133
|
+
ctx.botCtx.log?.error(`${logTag()} webhook event processing error: ${formatError(err)}`);
|
|
134
|
+
});
|
|
135
|
+
return true;
|
|
134
136
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
res.writeHead(405);
|
|
138
|
+
res.end("method not allowed");
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
if (err instanceof Error && err.message.includes("body too large")) {
|
|
143
|
+
res.writeHead(413);
|
|
144
|
+
res.end("payload too large");
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
ctx.botCtx.log?.error(`${logTag()} webhook error: ${formatError(err)}`);
|
|
148
|
+
if (!res.headersSent) {
|
|
149
|
+
res.writeHead(500);
|
|
150
|
+
res.end("internal error");
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
137
154
|
}
|
|
138
155
|
//# sourceMappingURL=webhook.js.map
|
package/dist/src/webhook.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/webhook.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/webhook.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WeChat KF directive parser
|
|
3
3
|
*
|
|
4
|
-
* Parses [[
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Parses [[wechat_*: ...]] directives embedded in agent text replies.
|
|
5
|
+
* The framework doesn't recognize these directives, so the text arrives
|
|
6
|
+
* intact for plugin-level interception.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* [[wechat_link: title | url]]
|
|
10
|
-
* [[
|
|
11
|
-
* [[
|
|
8
|
+
* Supported directives:
|
|
9
|
+
* [[wechat_link: title | desc | url | thumbUrl]]
|
|
10
|
+
* [[wechat_location: name | address | lat | lng]]
|
|
11
|
+
* [[wechat_miniprogram: appid | title | pagepath | thumbUrl]]
|
|
12
|
+
* [[wechat_menu: header | Option1, Option2, Option3 | footer]]
|
|
13
|
+
* [[wechat_business_card: USERID]]
|
|
14
|
+
* [[wechat_ca_link: https://work.weixin.qq.com/ca/...]]
|
|
12
15
|
*/
|
|
13
16
|
export type WechatLinkDirective = {
|
|
14
17
|
title: string;
|
|
@@ -16,12 +19,78 @@ export type WechatLinkDirective = {
|
|
|
16
19
|
url: string;
|
|
17
20
|
thumbUrl?: string;
|
|
18
21
|
};
|
|
22
|
+
export type WechatLocationDirective = {
|
|
23
|
+
name: string;
|
|
24
|
+
address?: string;
|
|
25
|
+
latitude: number;
|
|
26
|
+
longitude: number;
|
|
27
|
+
};
|
|
28
|
+
export type WechatMenuItemDirective = {
|
|
29
|
+
type: "click";
|
|
30
|
+
id?: string;
|
|
31
|
+
content: string;
|
|
32
|
+
} | {
|
|
33
|
+
type: "view";
|
|
34
|
+
url: string;
|
|
35
|
+
content: string;
|
|
36
|
+
} | {
|
|
37
|
+
type: "miniprogram";
|
|
38
|
+
appid: string;
|
|
39
|
+
pagepath: string;
|
|
40
|
+
content: string;
|
|
41
|
+
} | {
|
|
42
|
+
type: "text";
|
|
43
|
+
content: string;
|
|
44
|
+
noNewline?: boolean;
|
|
45
|
+
};
|
|
46
|
+
export type WechatMenuDirective = {
|
|
47
|
+
headContent?: string;
|
|
48
|
+
items: WechatMenuItemDirective[];
|
|
49
|
+
tailContent?: string;
|
|
50
|
+
};
|
|
51
|
+
export type WechatMiniprogramDirective = {
|
|
52
|
+
appid: string;
|
|
53
|
+
title: string;
|
|
54
|
+
pagepath: string;
|
|
55
|
+
thumbUrl?: string;
|
|
56
|
+
};
|
|
57
|
+
export type WechatBusinessCardDirective = {
|
|
58
|
+
userid: string;
|
|
59
|
+
};
|
|
60
|
+
export type WechatCaLinkDirective = {
|
|
61
|
+
link_url: string;
|
|
62
|
+
};
|
|
63
|
+
export type WechatRawDirective = {
|
|
64
|
+
msgtype: string;
|
|
65
|
+
payload: Record<string, unknown>;
|
|
66
|
+
};
|
|
19
67
|
export type WechatDirectiveResult = {
|
|
20
68
|
text: string;
|
|
21
69
|
link?: WechatLinkDirective;
|
|
70
|
+
location?: WechatLocationDirective;
|
|
71
|
+
miniprogram?: WechatMiniprogramDirective;
|
|
72
|
+
menu?: WechatMenuDirective;
|
|
73
|
+
businessCard?: WechatBusinessCardDirective;
|
|
74
|
+
caLink?: WechatCaLinkDirective;
|
|
75
|
+
raw?: WechatRawDirective;
|
|
76
|
+
};
|
|
77
|
+
export type ProtectedRange = {
|
|
78
|
+
start: number;
|
|
79
|
+
end: number;
|
|
22
80
|
};
|
|
23
81
|
/**
|
|
24
|
-
*
|
|
82
|
+
* Scan text left-to-right and collect ranges that should be treated as
|
|
83
|
+
* "protected" — i.e. directive syntax inside them must be ignored.
|
|
84
|
+
*
|
|
85
|
+
* Three zone types (checked in priority order):
|
|
86
|
+
* 1. Fenced code blocks (``` or ~~~, with optional 0-3 leading spaces + lang tag)
|
|
87
|
+
* 2. Inline code spans (backtick sequences, matching equal-length closer)
|
|
88
|
+
* 3. Blockquote lines (line starting with optional whitespace + `>`)
|
|
89
|
+
*/
|
|
90
|
+
export declare function findProtectedRanges(text: string): ProtectedRange[];
|
|
91
|
+
/**
|
|
92
|
+
* Quick check whether text contains a `[[wechat_link:...]]` directive
|
|
93
|
+
* outside of markdown code blocks, inline code, and blockquotes.
|
|
25
94
|
*/
|
|
26
95
|
export declare function hasWechatLinkDirective(text: string): boolean;
|
|
27
96
|
/**
|
|
@@ -31,4 +100,58 @@ export declare function hasWechatLinkDirective(text: string): boolean;
|
|
|
31
100
|
* plus the parsed link fields. If parsing fails (e.g. invalid URL),
|
|
32
101
|
* returns the original text unchanged with no link.
|
|
33
102
|
*/
|
|
34
|
-
export declare function parseWechatLinkDirective(text: string): WechatDirectiveResult;
|
|
103
|
+
export declare function parseWechatLinkDirective(text: string, protectedRanges?: ProtectedRange[]): WechatDirectiveResult;
|
|
104
|
+
export declare function parseWechatLocationDirective(text: string, protectedRanges?: ProtectedRange[]): WechatDirectiveResult;
|
|
105
|
+
export declare function parseWechatMiniprogramDirective(text: string, protectedRanges?: ProtectedRange[]): WechatDirectiveResult;
|
|
106
|
+
export declare function parseWechatMenuDirective(text: string, protectedRanges?: ProtectedRange[]): WechatDirectiveResult;
|
|
107
|
+
/**
|
|
108
|
+
* Convert a parsed WechatMenuDirective into the API `msgmenu` payload.
|
|
109
|
+
*
|
|
110
|
+
* Click items get auto-incrementing IDs (only among click items that lack
|
|
111
|
+
* an explicit `id`). Explicit IDs are preserved as-is.
|
|
112
|
+
*/
|
|
113
|
+
export declare function buildMsgMenuPayload(menu: WechatMenuDirective): {
|
|
114
|
+
head_content?: string;
|
|
115
|
+
list: Array<{
|
|
116
|
+
type: "click";
|
|
117
|
+
click: {
|
|
118
|
+
id: string;
|
|
119
|
+
content: string;
|
|
120
|
+
};
|
|
121
|
+
} | {
|
|
122
|
+
type: "view";
|
|
123
|
+
view: {
|
|
124
|
+
url: string;
|
|
125
|
+
content: string;
|
|
126
|
+
};
|
|
127
|
+
} | {
|
|
128
|
+
type: "miniprogram";
|
|
129
|
+
miniprogram: {
|
|
130
|
+
appid: string;
|
|
131
|
+
pagepath: string;
|
|
132
|
+
content: string;
|
|
133
|
+
};
|
|
134
|
+
} | {
|
|
135
|
+
type: "text";
|
|
136
|
+
text: {
|
|
137
|
+
content: string;
|
|
138
|
+
no_newline?: number;
|
|
139
|
+
};
|
|
140
|
+
}>;
|
|
141
|
+
tail_content?: string;
|
|
142
|
+
};
|
|
143
|
+
export declare function parseWechatBusinessCardDirective(text: string, protectedRanges?: ProtectedRange[]): WechatDirectiveResult;
|
|
144
|
+
export declare function parseWechatCaLinkDirective(text: string, protectedRanges?: ProtectedRange[]): WechatDirectiveResult;
|
|
145
|
+
export declare function parseWechatRawDirective(text: string, protectedRanges?: ProtectedRange[]): WechatDirectiveResult;
|
|
146
|
+
/**
|
|
147
|
+
* Quick check whether text contains any `[[wechat_*:...]]` directive
|
|
148
|
+
* outside of markdown code blocks, inline code, and blockquotes.
|
|
149
|
+
*/
|
|
150
|
+
export declare function hasWechatDirective(text: string): boolean;
|
|
151
|
+
/**
|
|
152
|
+
* Parse the first matching directive from text.
|
|
153
|
+
* Tries parsers in order: link → location → miniprogram → menu → business_card → ca_link → raw.
|
|
154
|
+
* Returns the first successful parse result.
|
|
155
|
+
* Directives inside markdown code blocks, inline code, or blockquotes are ignored.
|
|
156
|
+
*/
|
|
157
|
+
export declare function parseWechatDirective(text: string): WechatDirectiveResult;
|