@xuanyue202/wechat-mp 2026.3.21

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 (95) hide show
  1. package/README.md +74 -0
  2. package/dist/index.d.ts +20 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +59 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/src/api.d.ts +101 -0
  7. package/dist/src/api.d.ts.map +1 -0
  8. package/dist/src/api.js +142 -0
  9. package/dist/src/api.js.map +1 -0
  10. package/dist/src/channel.d.ts +296 -0
  11. package/dist/src/channel.d.ts.map +1 -0
  12. package/dist/src/channel.js +341 -0
  13. package/dist/src/channel.js.map +1 -0
  14. package/dist/src/config.d.ts +69 -0
  15. package/dist/src/config.d.ts.map +1 -0
  16. package/dist/src/config.js +167 -0
  17. package/dist/src/config.js.map +1 -0
  18. package/dist/src/crypto.d.ts +117 -0
  19. package/dist/src/crypto.d.ts.map +1 -0
  20. package/dist/src/crypto.js +270 -0
  21. package/dist/src/crypto.js.map +1 -0
  22. package/dist/src/crypto.test.d.ts +2 -0
  23. package/dist/src/crypto.test.d.ts.map +1 -0
  24. package/dist/src/crypto.test.js +76 -0
  25. package/dist/src/crypto.test.js.map +1 -0
  26. package/dist/src/dispatch.d.ts +15 -0
  27. package/dist/src/dispatch.d.ts.map +1 -0
  28. package/dist/src/dispatch.js +193 -0
  29. package/dist/src/dispatch.js.map +1 -0
  30. package/dist/src/dispatch.test.d.ts +2 -0
  31. package/dist/src/dispatch.test.d.ts.map +1 -0
  32. package/dist/src/dispatch.test.js +231 -0
  33. package/dist/src/dispatch.test.js.map +1 -0
  34. package/dist/src/inbound.d.ts +7 -0
  35. package/dist/src/inbound.d.ts.map +1 -0
  36. package/dist/src/inbound.js +82 -0
  37. package/dist/src/inbound.js.map +1 -0
  38. package/dist/src/onboarding.d.ts +25 -0
  39. package/dist/src/onboarding.d.ts.map +1 -0
  40. package/dist/src/onboarding.js +49 -0
  41. package/dist/src/onboarding.js.map +1 -0
  42. package/dist/src/outbound.d.ts +17 -0
  43. package/dist/src/outbound.d.ts.map +1 -0
  44. package/dist/src/outbound.js +55 -0
  45. package/dist/src/outbound.js.map +1 -0
  46. package/dist/src/outbound.test.d.ts +2 -0
  47. package/dist/src/outbound.test.d.ts.map +1 -0
  48. package/dist/src/outbound.test.js +175 -0
  49. package/dist/src/outbound.test.js.map +1 -0
  50. package/dist/src/probe.d.ts +15 -0
  51. package/dist/src/probe.d.ts.map +1 -0
  52. package/dist/src/probe.js +55 -0
  53. package/dist/src/probe.js.map +1 -0
  54. package/dist/src/runtime.d.ts +22 -0
  55. package/dist/src/runtime.d.ts.map +1 -0
  56. package/dist/src/runtime.js +33 -0
  57. package/dist/src/runtime.js.map +1 -0
  58. package/dist/src/send.d.ts +27 -0
  59. package/dist/src/send.d.ts.map +1 -0
  60. package/dist/src/send.js +103 -0
  61. package/dist/src/send.js.map +1 -0
  62. package/dist/src/state.d.ts +7 -0
  63. package/dist/src/state.d.ts.map +1 -0
  64. package/dist/src/state.js +109 -0
  65. package/dist/src/state.js.map +1 -0
  66. package/dist/src/text.d.ts +46 -0
  67. package/dist/src/text.d.ts.map +1 -0
  68. package/dist/src/text.js +192 -0
  69. package/dist/src/text.js.map +1 -0
  70. package/dist/src/text.test.d.ts +2 -0
  71. package/dist/src/text.test.d.ts.map +1 -0
  72. package/dist/src/text.test.js +110 -0
  73. package/dist/src/text.test.js.map +1 -0
  74. package/dist/src/token.d.ts +40 -0
  75. package/dist/src/token.d.ts.map +1 -0
  76. package/dist/src/token.js +154 -0
  77. package/dist/src/token.js.map +1 -0
  78. package/dist/src/token.test.d.ts +2 -0
  79. package/dist/src/token.test.d.ts.map +1 -0
  80. package/dist/src/token.test.js +74 -0
  81. package/dist/src/token.test.js.map +1 -0
  82. package/dist/src/types.d.ts +320 -0
  83. package/dist/src/types.d.ts.map +1 -0
  84. package/dist/src/types.js +2 -0
  85. package/dist/src/types.js.map +1 -0
  86. package/dist/src/webhook.d.ts +6 -0
  87. package/dist/src/webhook.d.ts.map +1 -0
  88. package/dist/src/webhook.js +381 -0
  89. package/dist/src/webhook.js.map +1 -0
  90. package/dist/src/webhook.test.d.ts +2 -0
  91. package/dist/src/webhook.test.d.ts.map +1 -0
  92. package/dist/src/webhook.test.js +737 -0
  93. package/dist/src/webhook.test.js.map +1 -0
  94. package/openclaw.plugin.json +83 -0
  95. package/package.json +103 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/send.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { WechatMpAccountState } from "./types.js";
2
+ export declare function markProcessedMessage(msgId: string): Promise<boolean>;
3
+ export declare function getAccountState(accountId: string): Promise<WechatMpAccountState>;
4
+ export declare function updateAccountState(accountId: string, patch: Partial<WechatMpAccountState>): Promise<WechatMpAccountState>;
5
+ export declare function flushWechatMpStateForTests(): Promise<void>;
6
+ export declare function setWechatMpStateFilePathForTests(nextPath?: string): void;
7
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/state.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAA0B,MAAM,YAAY,CAAC;AAqE/E,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAW1E;AAED,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAGtF;AAED,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,OAAO,CAAC,oBAAoB,CAAC,GACnC,OAAO,CAAC,oBAAoB,CAAC,CAO/B;AAED,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC,CAMhE;AAED,wBAAgB,gCAAgC,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAQxE"}
@@ -0,0 +1,109 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ const DEDUP_TTL_MS = 10 * 60 * 1000;
5
+ const DEFAULT_STATE_FILE = join(homedir(), ".openclaw", "wechat-mp", "data", "state.json");
6
+ let stateFilePath = DEFAULT_STATE_FILE;
7
+ let cachedState = null;
8
+ let loadingState = null;
9
+ let saveTimer = null;
10
+ function createEmptyState() {
11
+ return {
12
+ version: 1,
13
+ processedMsgIds: {},
14
+ accounts: {},
15
+ };
16
+ }
17
+ function pruneState(state) {
18
+ const cutoff = Date.now() - DEDUP_TTL_MS;
19
+ for (const [msgId, timestamp] of Object.entries(state.processedMsgIds)) {
20
+ if (timestamp < cutoff) {
21
+ delete state.processedMsgIds[msgId];
22
+ }
23
+ }
24
+ }
25
+ async function saveState() {
26
+ if (!cachedState)
27
+ return;
28
+ pruneState(cachedState);
29
+ await mkdir(dirname(stateFilePath), { recursive: true });
30
+ await writeFile(stateFilePath, `${JSON.stringify(cachedState, null, 2)}\n`, "utf8");
31
+ }
32
+ function scheduleSave() {
33
+ if (saveTimer)
34
+ return;
35
+ saveTimer = setTimeout(() => {
36
+ saveTimer = null;
37
+ void saveState();
38
+ }, 50);
39
+ }
40
+ async function loadState() {
41
+ if (cachedState)
42
+ return cachedState;
43
+ if (loadingState)
44
+ return loadingState;
45
+ loadingState = (async () => {
46
+ try {
47
+ const raw = await readFile(stateFilePath, "utf8");
48
+ const parsed = JSON.parse(raw);
49
+ cachedState = {
50
+ version: 1,
51
+ processedMsgIds: parsed.processedMsgIds ?? {},
52
+ accounts: parsed.accounts ?? {},
53
+ };
54
+ }
55
+ catch {
56
+ cachedState = createEmptyState();
57
+ }
58
+ pruneState(cachedState);
59
+ return cachedState;
60
+ })();
61
+ try {
62
+ return await loadingState;
63
+ }
64
+ finally {
65
+ loadingState = null;
66
+ }
67
+ }
68
+ export async function markProcessedMessage(msgId) {
69
+ const normalized = msgId.trim();
70
+ if (!normalized)
71
+ return false;
72
+ const state = await loadState();
73
+ pruneState(state);
74
+ if (state.processedMsgIds[normalized]) {
75
+ return false;
76
+ }
77
+ state.processedMsgIds[normalized] = Date.now();
78
+ scheduleSave();
79
+ return true;
80
+ }
81
+ export async function getAccountState(accountId) {
82
+ const state = await loadState();
83
+ return { ...(state.accounts[accountId] ?? {}) };
84
+ }
85
+ export async function updateAccountState(accountId, patch) {
86
+ const state = await loadState();
87
+ const current = state.accounts[accountId] ?? {};
88
+ const next = { ...current, ...patch };
89
+ state.accounts[accountId] = next;
90
+ scheduleSave();
91
+ return next;
92
+ }
93
+ export async function flushWechatMpStateForTests() {
94
+ if (saveTimer) {
95
+ clearTimeout(saveTimer);
96
+ saveTimer = null;
97
+ }
98
+ await saveState();
99
+ }
100
+ export function setWechatMpStateFilePathForTests(nextPath) {
101
+ stateFilePath = nextPath?.trim() || DEFAULT_STATE_FILE;
102
+ cachedState = null;
103
+ loadingState = null;
104
+ if (saveTimer) {
105
+ clearTimeout(saveTimer);
106
+ saveTimer = null;
107
+ }
108
+ }
109
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAI1C,MAAM,YAAY,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AACpC,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;AAE3F,IAAI,aAAa,GAAG,kBAAkB,CAAC;AACvC,IAAI,WAAW,GAAkC,IAAI,CAAC;AACtD,IAAI,YAAY,GAA2C,IAAI,CAAC;AAChE,IAAI,SAAS,GAAyC,IAAI,CAAC;AAE3D,SAAS,gBAAgB;IACvB,OAAO;QACL,OAAO,EAAE,CAAC;QACV,eAAe,EAAE,EAAE;QACnB,QAAQ,EAAE,EAAE;KACb,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,KAA6B;IAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,CAAC;IACzC,KAAK,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC;QACvE,IAAI,SAAS,GAAG,MAAM,EAAE,CAAC;YACvB,OAAO,KAAK,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;AACH,CAAC;AAED,KAAK,UAAU,SAAS;IACtB,IAAI,CAAC,WAAW;QAAE,OAAO;IACzB,UAAU,CAAC,WAAW,CAAC,CAAC;IACxB,MAAM,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,MAAM,SAAS,CAAC,aAAa,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACtF,CAAC;AAED,SAAS,YAAY;IACnB,IAAI,SAAS;QAAE,OAAO;IACtB,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;QAC1B,SAAS,GAAG,IAAI,CAAC;QACjB,KAAK,SAAS,EAAE,CAAC;IACnB,CAAC,EAAE,EAAE,CAAC,CAAC;AACT,CAAC;AAED,KAAK,UAAU,SAAS;IACtB,IAAI,WAAW;QAAE,OAAO,WAAW,CAAC;IACpC,IAAI,YAAY;QAAE,OAAO,YAAY,CAAC;IAEtC,YAAY,GAAG,CAAC,KAAK,IAAI,EAAE;QACzB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;YAClD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAoC,CAAC;YAClE,WAAW,GAAG;gBACZ,OAAO,EAAE,CAAC;gBACV,eAAe,EAAE,MAAM,CAAC,eAAe,IAAI,EAAE;gBAC7C,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,EAAE;aAChC,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,WAAW,GAAG,gBAAgB,EAAE,CAAC;QACnC,CAAC;QACD,UAAU,CAAC,WAAW,CAAC,CAAC;QACxB,OAAO,WAAW,CAAC;IACrB,CAAC,CAAC,EAAE,CAAC;IAEL,IAAI,CAAC;QACH,OAAO,MAAM,YAAY,CAAC;IAC5B,CAAC;YAAS,CAAC;QACT,YAAY,GAAG,IAAI,CAAC;IACtB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,KAAa;IACtD,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAChC,IAAI,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IAC9B,MAAM,KAAK,GAAG,MAAM,SAAS,EAAE,CAAC;IAChC,UAAU,CAAC,KAAK,CAAC,CAAC;IAClB,IAAI,KAAK,CAAC,eAAe,CAAC,UAAU,CAAC,EAAE,CAAC;QACtC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,KAAK,CAAC,eAAe,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC/C,YAAY,EAAE,CAAC;IACf,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,SAAiB;IACrD,MAAM,KAAK,GAAG,MAAM,SAAS,EAAE,CAAC;IAChC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;AAClD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,SAAiB,EACjB,KAAoC;IAEpC,MAAM,KAAK,GAAG,MAAM,SAAS,EAAE,CAAC;IAChC,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;IAChD,MAAM,IAAI,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;IACtC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;IACjC,YAAY,EAAE,CAAC;IACf,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0BAA0B;IAC9C,IAAI,SAAS,EAAE,CAAC;QACd,YAAY,CAAC,SAAS,CAAC,CAAC;QACxB,SAAS,GAAG,IAAI,CAAC;IACnB,CAAC;IACD,MAAM,SAAS,EAAE,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,gCAAgC,CAAC,QAAiB;IAChE,aAAa,GAAG,QAAQ,EAAE,IAAI,EAAE,IAAI,kBAAkB,CAAC;IACvD,WAAW,GAAG,IAAI,CAAC;IACnB,YAAY,GAAG,IAAI,CAAC;IACpB,IAAI,SAAS,EAAE,CAAC;QACd,YAAY,CAAC,SAAS,CAAC,CAAC;QACxB,SAAS,GAAG,IAAI,CAAC;IACnB,CAAC;AACH,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * WeChat MP text normalization utilities
3
+ *
4
+ * Provides markdown-friendly text normalization that can be shared between
5
+ * the reply pipeline (dispatch.ts) and direct outbound (outbound.ts).
6
+ *
7
+ * The normalization behavior is controlled by the `renderMarkdown` config:
8
+ * - true (default): Apply markdown-friendly formatting for WeChat MP
9
+ * - false: Minimal passthrough, preserving original text
10
+ */
11
+ /** WeChat MP text message byte limit (2048 bytes) */
12
+ export declare const WECHAT_TEXT_BYTE_LIMIT = 2048;
13
+ /**
14
+ * Calculate UTF-8 byte length of a string.
15
+ * @param str - The string to measure
16
+ * @returns The byte length in UTF-8 encoding
17
+ */
18
+ export declare function getUtf8ByteLength(str: string): number;
19
+ /**
20
+ * Split text by byte limit, respecting boundaries (paragraphs, sentences, spaces).
21
+ * Ensures no multi-byte characters are truncated.
22
+ *
23
+ * @param text - The text to split
24
+ * @param maxBytes - Maximum bytes per chunk (default: WECHAT_TEXT_BYTE_LIMIT)
25
+ * @returns Array of text chunks, each within the byte limit
26
+ */
27
+ export declare function splitTextByByteLimit(text: string, maxBytes?: number): string[];
28
+ /**
29
+ * Normalize text for WeChat MP delivery.
30
+ *
31
+ * When renderMarkdown is enabled (default), converts markdown to WeChat MP
32
+ * friendly plain text. When disabled, returns text with minimal changes.
33
+ *
34
+ * @param text - The raw text to normalize
35
+ * @param renderMarkdown - Whether to apply markdown-friendly formatting
36
+ * @returns Normalized text ready for WeChat MP delivery
37
+ */
38
+ export declare function normalizeWechatMpText(text: string, renderMarkdown: boolean): string;
39
+ /**
40
+ * Resolve the renderMarkdown setting from account config.
41
+ * Defaults to true if not explicitly set to false.
42
+ */
43
+ export declare function resolveRenderMarkdown(config: {
44
+ renderMarkdown?: boolean;
45
+ }): boolean;
46
+ //# sourceMappingURL=text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,qDAAqD;AACrD,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAE3C;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,MAA+B,GACxC,MAAM,EAAE,CAmCV;AA2CD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,MAAM,CAWnF;AAiGD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAEnF"}
@@ -0,0 +1,192 @@
1
+ /**
2
+ * WeChat MP text normalization utilities
3
+ *
4
+ * Provides markdown-friendly text normalization that can be shared between
5
+ * the reply pipeline (dispatch.ts) and direct outbound (outbound.ts).
6
+ *
7
+ * The normalization behavior is controlled by the `renderMarkdown` config:
8
+ * - true (default): Apply markdown-friendly formatting for WeChat MP
9
+ * - false: Minimal passthrough, preserving original text
10
+ */
11
+ /** WeChat MP text message byte limit (2048 bytes) */
12
+ export const WECHAT_TEXT_BYTE_LIMIT = 2048;
13
+ /**
14
+ * Calculate UTF-8 byte length of a string.
15
+ * @param str - The string to measure
16
+ * @returns The byte length in UTF-8 encoding
17
+ */
18
+ export function getUtf8ByteLength(str) {
19
+ return Buffer.byteLength(str, "utf-8");
20
+ }
21
+ /**
22
+ * Split text by byte limit, respecting boundaries (paragraphs, sentences, spaces).
23
+ * Ensures no multi-byte characters are truncated.
24
+ *
25
+ * @param text - The text to split
26
+ * @param maxBytes - Maximum bytes per chunk (default: WECHAT_TEXT_BYTE_LIMIT)
27
+ * @returns Array of text chunks, each within the byte limit
28
+ */
29
+ export function splitTextByByteLimit(text, maxBytes = WECHAT_TEXT_BYTE_LIMIT) {
30
+ if (getUtf8ByteLength(text) <= maxBytes) {
31
+ return [text];
32
+ }
33
+ const chunks = [];
34
+ let remaining = text;
35
+ while (remaining.length > 0) {
36
+ // Find the maximum character length that fits within maxBytes
37
+ let end = remaining.length;
38
+ while (end > 0 && getUtf8ByteLength(remaining.slice(0, end)) > maxBytes) {
39
+ end--;
40
+ }
41
+ if (end === 0) {
42
+ // Single character exceeds limit (should not happen with 2048 bytes)
43
+ // Force take at least 1 character to avoid infinite loop
44
+ end = 1;
45
+ }
46
+ // Try to find a good boundary (paragraph > sentence > space)
47
+ const boundary = findBestBoundary(remaining, end);
48
+ if (boundary > 0) {
49
+ end = boundary;
50
+ }
51
+ const chunk = remaining.slice(0, end).trim();
52
+ if (chunk) {
53
+ chunks.push(chunk);
54
+ }
55
+ remaining = remaining.slice(end).trim();
56
+ }
57
+ return chunks;
58
+ }
59
+ /**
60
+ * Find the best boundary position for splitting text.
61
+ * Prioritizes: paragraphs > horizontal rules > sentences > spaces.
62
+ *
63
+ * @param text - The text to search
64
+ * @param maxPos - Maximum position to consider
65
+ * @returns Best boundary position, or 0 if none found
66
+ */
67
+ function findBestBoundary(text, maxPos) {
68
+ const searchRegion = text.slice(0, maxPos);
69
+ // Priority: paragraph > horizontal rule > sentence > space
70
+ // Each tuple: [pattern, include pattern in chunk]
71
+ const boundaries = [
72
+ ["\n\n", true],
73
+ ["\n------------\n", true], // Converted markdown horizontal rule
74
+ ["\n---\n", true], // Markdown horizontal rule
75
+ ["\n***\n", true],
76
+ ["\n___\n", true],
77
+ ["\n", true],
78
+ ["。", true],
79
+ ["!", true],
80
+ ["?", true],
81
+ ["。 ", true],
82
+ ["! ", true],
83
+ ["? ", true],
84
+ [". ", true],
85
+ [" ", false],
86
+ ];
87
+ for (const [boundary, include] of boundaries) {
88
+ const pos = searchRegion.lastIndexOf(boundary);
89
+ // Ensure split point is not too early (at least 30% of maxPos)
90
+ if (pos > maxPos * 0.3) {
91
+ return include ? pos + boundary.length : pos;
92
+ }
93
+ }
94
+ return 0; // No suitable boundary found
95
+ }
96
+ /**
97
+ * Normalize text for WeChat MP delivery.
98
+ *
99
+ * When renderMarkdown is enabled (default), converts markdown to WeChat MP
100
+ * friendly plain text. When disabled, returns text with minimal changes.
101
+ *
102
+ * @param text - The raw text to normalize
103
+ * @param renderMarkdown - Whether to apply markdown-friendly formatting
104
+ * @returns Normalized text ready for WeChat MP delivery
105
+ */
106
+ export function normalizeWechatMpText(text, renderMarkdown) {
107
+ const raw = String(text ?? "").trim();
108
+ if (!raw)
109
+ return "";
110
+ // When renderMarkdown is disabled, pass through with minimal normalization
111
+ if (!renderMarkdown) {
112
+ return raw;
113
+ }
114
+ // Apply markdown-friendly normalization for WeChat MP
115
+ return stripMarkdownForWechatMp(raw);
116
+ }
117
+ /**
118
+ * Strip markdown formatting and convert to WeChat MP friendly plain text.
119
+ *
120
+ * WeChat MP text messages do not support markdown, so we convert to a
121
+ * readable plain-text format similar to the wecom-app implementation.
122
+ */
123
+ function stripMarkdownForWechatMp(text) {
124
+ let result = text;
125
+ // 1. Code blocks: extract content with indentation (preserve language label)
126
+ result = result.replace(/```(\w*)\n?([\s\S]*?)```/g, (_match, lang, code) => {
127
+ const trimmedCode = code.trim();
128
+ if (!trimmedCode)
129
+ return "";
130
+ const langLabel = lang ? `[${lang}]\n` : "";
131
+ const indentedCode = trimmedCode
132
+ .split("\n")
133
+ .map((line) => ` ${line}`)
134
+ .join("\n");
135
+ return `\n${langLabel}${indentedCode}\n`;
136
+ });
137
+ // 2. Headings: use brackets to mark
138
+ result = result.replace(/^#{1,6}\s+(.+)$/gm, "[$1]");
139
+ // 3. Bold/italic: preserve text (exclude underscores in URLs)
140
+ result = result
141
+ .replace(/\*\*(.*?)\*\*/g, "$1")
142
+ .replace(/\*(.*?)\*/g, "$1")
143
+ .replace(/__(.*?)__/g, "$1")
144
+ // Only replace standalone underscores (surrounded by space/punctuation), avoid URLs
145
+ .replace(/(?<![\w/])_(.+?)_(?![\w/])/g, "$1");
146
+ // 4. Unordered list items to bullet points
147
+ result = result.replace(/^[-*]\s+/gm, "- ");
148
+ // 5. Ordered lists keep numbering
149
+ result = result.replace(/^(\d+)\.\s+/gm, "$1. ");
150
+ // 6. Inline code: preserve content
151
+ result = result.replace(/`([^`]+)`/g, "$1");
152
+ // 7. Strikethrough
153
+ result = result.replace(/~~(.*?)~~/g, "$1");
154
+ // 8. Links: preserve text and URL
155
+ result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
156
+ // 9. Images: display alt text
157
+ result = result.replace(/!\[([^\]]*)\]\([^)]+\)/g, "[image: $1]");
158
+ // 10. Tables: simplify to aligned text (basic table support)
159
+ result = result.replace(/\|(.+)\|\n\|[-:| ]+\|\n((?:\|.+\|\n?)*)/g, (_match, header, body) => {
160
+ const headerCells = header.split("|").map((c) => c.trim()).filter(Boolean);
161
+ const rows = body.trim().split("\n").map((row) => row.split("|").map((c) => c.trim()).filter(Boolean));
162
+ // Calculate max width per column
163
+ const colWidths = headerCells.map((h, i) => {
164
+ const maxRowWidth = Math.max(...rows.map((r) => (r[i] || "").length));
165
+ return Math.max(h.length, maxRowWidth);
166
+ });
167
+ // Format header
168
+ const formattedHeader = headerCells
169
+ .map((h, i) => h.padEnd(colWidths[i]))
170
+ .join(" ");
171
+ // Format data rows
172
+ const formattedRows = rows
173
+ .map((row) => headerCells.map((_, i) => (row[i] || "").padEnd(colWidths[i])).join(" "))
174
+ .join("\n");
175
+ return `${formattedHeader}\n${formattedRows}\n`;
176
+ });
177
+ // 11. Blockquotes: remove > prefix
178
+ result = result.replace(/^>\s?/gm, "");
179
+ // 12. Horizontal rules
180
+ result = result.replace(/^[-*_]{3,}$/gm, "------------");
181
+ // 13. Collapse multiple blank lines
182
+ result = result.replace(/\n{3,}/g, "\n\n");
183
+ return result.trim();
184
+ }
185
+ /**
186
+ * Resolve the renderMarkdown setting from account config.
187
+ * Defaults to true if not explicitly set to false.
188
+ */
189
+ export function resolveRenderMarkdown(config) {
190
+ return config.renderMarkdown !== false;
191
+ }
192
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/text.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=text.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.test.d.ts","sourceRoot":"","sources":["../../src/text.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,110 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getUtf8ByteLength, splitTextByByteLimit, WECHAT_TEXT_BYTE_LIMIT, } from "./text.js";
3
+ describe("wechat-mp text utils", () => {
4
+ describe("getUtf8ByteLength", () => {
5
+ it("calculates ASCII byte length", () => {
6
+ expect(getUtf8ByteLength("hello")).toBe(5);
7
+ expect(getUtf8ByteLength("12345")).toBe(5);
8
+ });
9
+ it("calculates Chinese character byte length (3 bytes per char)", () => {
10
+ expect(getUtf8ByteLength("你好")).toBe(6); // 2 chars * 3 bytes
11
+ expect(getUtf8ByteLength("测试")).toBe(6);
12
+ });
13
+ it("calculates mixed content byte length", () => {
14
+ expect(getUtf8ByteLength("hello你好")).toBe(11); // 5 + 6
15
+ expect(getUtf8ByteLength("abc测试123")).toBe(12); // 3 + 6 + 3
16
+ });
17
+ it("handles empty string", () => {
18
+ expect(getUtf8ByteLength("")).toBe(0);
19
+ });
20
+ });
21
+ describe("splitTextByByteLimit", () => {
22
+ it("returns single chunk when within limit", () => {
23
+ const text = "a".repeat(100);
24
+ const chunks = splitTextByByteLimit(text, 200);
25
+ expect(chunks).toHaveLength(1);
26
+ expect(chunks[0]).toBe(text);
27
+ });
28
+ it("splits at exact byte limit boundary", () => {
29
+ const text = "a".repeat(3000);
30
+ const chunks = splitTextByByteLimit(text, 1000);
31
+ expect(chunks.length).toBeGreaterThan(1);
32
+ // Each chunk should be within limit
33
+ for (const chunk of chunks) {
34
+ expect(getUtf8ByteLength(chunk)).toBeLessThanOrEqual(1000);
35
+ }
36
+ });
37
+ it("splits at paragraph boundary (\\n\\n)", () => {
38
+ const part1 = "a".repeat(500);
39
+ const part2 = "b".repeat(500);
40
+ const text = `${part1}\n\n${part2}`;
41
+ const chunks = splitTextByByteLimit(text, 600);
42
+ expect(chunks.length).toBeGreaterThan(1);
43
+ expect(chunks[0]).toContain(part1);
44
+ expect(chunks[1]).toContain(part2);
45
+ });
46
+ it("splits at horizontal rule (---)", () => {
47
+ const part1 = "a".repeat(500);
48
+ const part2 = "b".repeat(500);
49
+ const text = `${part1}\n---\n${part2}`;
50
+ const chunks = splitTextByByteLimit(text, 600);
51
+ expect(chunks.length).toBeGreaterThan(1);
52
+ });
53
+ it("splits at Chinese sentence boundary", () => {
54
+ const part1 = "中".repeat(300); // 900 bytes
55
+ const part2 = "文".repeat(300); // 900 bytes
56
+ const text = `${part1}。${part2}`;
57
+ const chunks = splitTextByByteLimit(text, 1000);
58
+ expect(chunks.length).toBeGreaterThan(1);
59
+ });
60
+ it("does not truncate multi-byte characters", () => {
61
+ // Create text that would be exactly at boundary
62
+ const chinese = "你".repeat(683); // 2049 bytes, slightly over limit
63
+ const chunks = splitTextByByteLimit(chinese, WECHAT_TEXT_BYTE_LIMIT);
64
+ // Verify no characters are corrupted
65
+ const rejoined = chunks.join("");
66
+ expect(rejoined).toBe(chinese);
67
+ });
68
+ it("handles mixed ASCII and Chinese content", () => {
69
+ const part1 = "hello " + "中".repeat(300);
70
+ const part2 = "world " + "文".repeat(300);
71
+ const text = `${part1}\n\n${part2}`;
72
+ const chunks = splitTextByByteLimit(text, 1000);
73
+ // Each chunk should be within limit
74
+ for (const chunk of chunks) {
75
+ expect(getUtf8ByteLength(chunk)).toBeLessThanOrEqual(1000);
76
+ }
77
+ });
78
+ it("splits very long text into multiple chunks", () => {
79
+ const text = "a".repeat(10000);
80
+ const chunks = splitTextByByteLimit(text, WECHAT_TEXT_BYTE_LIMIT);
81
+ expect(chunks.length).toBeGreaterThan(4);
82
+ // Verify all content is preserved
83
+ expect(chunks.join("")).toBe(text);
84
+ });
85
+ it("handles text with multiple paragraph breaks", () => {
86
+ const paragraphs = [
87
+ "First paragraph with some content here.",
88
+ "Second paragraph with more content here.",
89
+ "Third paragraph with even more content here.",
90
+ ];
91
+ const text = paragraphs.join("\n\n");
92
+ const chunks = splitTextByByteLimit(text, 50);
93
+ // Should split into multiple chunks at boundaries
94
+ expect(chunks.length).toBeGreaterThanOrEqual(3);
95
+ // Verify content preserved
96
+ const rejoined = chunks.join(" ");
97
+ expect(rejoined).toContain("First paragraph");
98
+ expect(rejoined).toContain("Second paragraph");
99
+ expect(rejoined).toContain("Third paragraph");
100
+ });
101
+ it("handles leading/trailing whitespace in short text", () => {
102
+ const text = " hello world ";
103
+ const chunks = splitTextByByteLimit(text, 100);
104
+ // Short text within limit is returned as-is
105
+ expect(chunks).toHaveLength(1);
106
+ expect(chunks[0]).toBe(" hello world ");
107
+ });
108
+ });
109
+ });
110
+ //# sourceMappingURL=text.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.test.js","sourceRoot":"","sources":["../../src/text.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * access_token lifecycle management for wechat-mp
3
+ *
4
+ * Provides centralized token caching, proactive refresh, and error handling.
5
+ */
6
+ import type { ResolvedWechatMpAccount } from "./types.js";
7
+ /**
8
+ * Get a cached access_token or fetch a new one.
9
+ * @throws Error if appId/appSecret not configured or API call fails
10
+ */
11
+ export declare function getAccessToken(account: ResolvedWechatMpAccount): Promise<string>;
12
+ /**
13
+ * Clear token cache for an account
14
+ */
15
+ export declare function clearAccessTokenCache(account: ResolvedWechatMpAccount): void;
16
+ /**
17
+ * Clear all token caches
18
+ */
19
+ export declare function clearAllAccessTokenCache(): void;
20
+ /**
21
+ * Check if a token needs refresh (expires within 5 minutes)
22
+ */
23
+ export declare function shouldRefreshToken(account: ResolvedWechatMpAccount): boolean;
24
+ /**
25
+ * Get token cache status
26
+ */
27
+ export declare function getTokenCacheStatus(account: ResolvedWechatMpAccount): {
28
+ cached: boolean;
29
+ expiresAt?: number;
30
+ valid?: boolean;
31
+ };
32
+ /**
33
+ * Force refresh token (clear cache and fetch new)
34
+ */
35
+ export declare function refreshToken(account: ResolvedWechatMpAccount): Promise<string>;
36
+ /**
37
+ * Check if error code indicates invalid token
38
+ */
39
+ export declare function isInvalidTokenError(errcode: number): boolean;
40
+ //# sourceMappingURL=token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/token.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAyB,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAsBjF;;;GAGG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC,CA0BtF;AAgCD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAU5E;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAM/C;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAO5E;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,uBAAuB,GAC/B;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAW1D;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC,CAGpF;AA+BD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE5D"}