@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
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # @xuanyue202/wechat-mp
2
+
3
+ `wechat-mp` 是一个独立的微信公众号(订阅号 / 服务号)渠道插件,用于把公众号消息接入 OpenClaw。
4
+
5
+ ## P0 已实现范围
6
+
7
+ - 单账号配置
8
+ - `GET` / `POST` 回调接入
9
+ - `plain / safe / compat` 基础处理边界
10
+ - `access_token` 获取、缓存、刷新
11
+ - 文本消息入站
12
+ - 基础事件(`subscribe / unsubscribe / scan / click / view`)标准化
13
+ - 统一 routing / session / buffered reply dispatch 接入
14
+ - 5 秒内 passive reply 主路径
15
+ - active outbound skeleton
16
+ - 超长消息自动分割(2048 字节限制,智能边界分割)
17
+ - Markdown 渲染与格式转换
18
+ - setup / aggregate / install hint 接线
19
+
20
+ ## P1 / P2 暂未完成
21
+
22
+ - 完整媒体消息收发
23
+ - OAuth / JS-SDK / 菜单 / 二维码全量业务能力
24
+ - 多账号运行时完善
25
+ - 更完整的主动发送能力与运营接口
26
+
27
+ ## 配置示例
28
+
29
+ ```json
30
+ {
31
+ "channels": {
32
+ "wechat-mp": {
33
+ "enabled": true,
34
+ "webhookPath": "/wechat-mp",
35
+ "appId": "wx1234567890abcdef",
36
+ "appSecret": "your-app-secret",
37
+ "token": "your-callback-token",
38
+ "encodingAESKey": "your-43-char-encoding-aes-key",
39
+ "messageMode": "safe",
40
+ "replyMode": "active",
41
+ "activeDeliveryMode": "split",
42
+ "renderMarkdown": true,
43
+ "welcomeText": "你好,欢迎关注。"
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ## 联调要点
50
+
51
+ 1. 公众号后台把服务器地址指向你的网关回调地址。
52
+ 2. `plain` 模式可先做最小链路验证;`safe/compat` 需要 `encodingAESKey`。
53
+ 3. 如果需要 active outbound,必须额外配置 `appSecret`。
54
+ 4. `replyMode=active` 时可用 `activeDeliveryMode` 控制主动发送行为:
55
+ - `split`:每个日志 / chunk 单独发一条消息
56
+ - `merged`:等待 reply pipeline 结束后合并成一条消息发送
57
+ 5. `replyMode=passive` 时始终单次 HTTP 回包,`activeDeliveryMode` 不生效。
58
+ 6. `renderMarkdown` 控制 Markdown 格式渲染:
59
+ - `true`(默认):自动将 Markdown 转换为公众号友好的纯文本格式
60
+ - `false`:保留原始 Markdown 格式,不做转换
61
+ 7. **消息长度限制**:公众号客服消息 API 有 2048 字节限制,超长消息会自动分割:
62
+ - 优先在自然边界处分割:段落 `\n\n` → 分割线 `---` → 换行 `\n` → 句末标点 → 空格
63
+ - 分割后的消息逐条发送,确保不截断中文字符
64
+ 8. 推荐先用:
65
+
66
+ ```bash
67
+ pnpm -F @xuanyue202/wechat-mp build
68
+ pnpm -F @xuanyue202/wechat-mp test
69
+ ```
70
+
71
+ ## 文档入口
72
+
73
+ - 开发计划:`doc/guides/wechat-mp/doc/开发计划.md`
74
+ - 配置指南:`doc/guides/wechat-mp/configuration.md`
@@ -0,0 +1,20 @@
1
+ import type { WechatMpConfig, WechatMpAccountConfig, ResolvedWechatMpAccount, WechatMpDmPolicy, WechatMpMessageMode, WechatMpReplyMode, WechatMpActiveDeliveryMode, PluginConfig, MoltbotPluginApi } from "./src/types.js";
2
+ export type { WechatMpConfig, WechatMpAccountConfig, ResolvedWechatMpAccount, WechatMpDmPolicy, WechatMpMessageMode, WechatMpReplyMode, WechatMpActiveDeliveryMode, PluginConfig, MoltbotPluginApi, };
3
+ export { wechatMpPlugin, DEFAULT_ACCOUNT_ID } from "./src/channel.js";
4
+ export { setWechatMpRuntime, getWechatMpRuntime } from "./src/runtime.js";
5
+ export { sendWechatMpActiveText } from "./src/send.js";
6
+ /**
7
+ * Collect webhook paths for all configured accounts.
8
+ * Used to register HTTP routes with the host.
9
+ */
10
+ export declare function collectWechatMpRoutePaths(config: PluginConfig): string[];
11
+ /**
12
+ * Plugin register function - entry point for host integration.
13
+ * Follows the thin entry pattern with setup CLI, install hint, runtime, and registerChannel.
14
+ */
15
+ export declare function register(api: MoltbotPluginApi): void;
16
+ declare const _default: {
17
+ register: typeof register;
18
+ };
19
+ export default _default;
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EACjB,0BAA0B,EAC1B,YAAY,EACZ,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAGxB,YAAY,EACV,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EACjB,0BAA0B,EAC1B,YAAY,EACZ,gBAAgB,GACjB,CAAC;AAEF,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAEvD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,EAAE,CAmBxE;AAGD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,gBAAgB,GAAG,IAAI,CA6BpD;;;;AAED,wBAA4B"}
package/dist/index.js ADDED
@@ -0,0 +1,59 @@
1
+ import { registerChinaSetupCli, showChinaInstallHint } from "@xuanyue202/shared";
2
+ import { wechatMpPlugin } from "./src/channel.js";
3
+ import { setWechatMpRuntime } from "./src/runtime.js";
4
+ import { handleWechatMpWebhookRequest } from "./src/webhook.js";
5
+ export { wechatMpPlugin, DEFAULT_ACCOUNT_ID } from "./src/channel.js";
6
+ export { setWechatMpRuntime, getWechatMpRuntime } from "./src/runtime.js";
7
+ export { sendWechatMpActiveText } from "./src/send.js";
8
+ /**
9
+ * Collect webhook paths for all configured accounts.
10
+ * Used to register HTTP routes with the host.
11
+ */
12
+ export function collectWechatMpRoutePaths(config) {
13
+ const paths = [];
14
+ const cfg = config?.channels?.["wechat-mp"];
15
+ if (!cfg)
16
+ return paths;
17
+ const defaultPath = cfg.webhookPath ?? "/wechat-mp";
18
+ paths.push(defaultPath);
19
+ if (cfg.accounts) {
20
+ for (const [accountId, account] of Object.entries(cfg.accounts)) {
21
+ const accountPath = account.webhookPath ?? defaultPath;
22
+ if (!paths.includes(accountPath)) {
23
+ paths.push(accountPath);
24
+ }
25
+ }
26
+ }
27
+ return paths;
28
+ }
29
+ /**
30
+ * Plugin register function - entry point for host integration.
31
+ * Follows the thin entry pattern with setup CLI, install hint, runtime, and registerChannel.
32
+ */
33
+ export function register(api) {
34
+ const config = api.config;
35
+ if (api.registerCli) {
36
+ registerChinaSetupCli(api, { channels: ["wechat-mp"] });
37
+ }
38
+ showChinaInstallHint(api);
39
+ if (api.runtime) {
40
+ setWechatMpRuntime(api.runtime);
41
+ }
42
+ api.registerChannel({ plugin: wechatMpPlugin });
43
+ const routePaths = collectWechatMpRoutePaths(config ?? {});
44
+ for (const path of routePaths) {
45
+ if (api.registerHttpRoute) {
46
+ api.registerHttpRoute({
47
+ path,
48
+ auth: "plugin",
49
+ match: "exact",
50
+ handler: handleWechatMpWebhookRequest,
51
+ });
52
+ }
53
+ else if (api.registerHttpHandler) {
54
+ api.registerHttpHandler(handleWechatMpWebhookRequest);
55
+ }
56
+ }
57
+ }
58
+ export default { register };
59
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAEjF,OAAO,EAAE,cAAc,EAAsB,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAsB,MAAM,kBAAkB,CAAC;AAE1E,OAAO,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAC;AA4BhE,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAEvD;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,MAAoB;IAC5D,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAG,MAAM,EAAE,QAAQ,EAAE,CAAC,WAAW,CAA+B,CAAC;IAE1E,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IAEvB,MAAM,WAAW,GAAG,GAAG,CAAC,WAAW,IAAI,YAAY,CAAC;IACpD,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAExB,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;QACjB,KAAK,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAChE,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,WAAW,CAAC;YACvD,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;gBACjC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAGD;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAqB;IAC5C,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;IAE1B,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;QACpB,qBAAqB,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,oBAAoB,CAAC,GAAG,CAAC,CAAC;IAE1B,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;QAChB,kBAAkB,CAAC,GAAG,CAAC,OAAkC,CAAC,CAAC;IAC7D,CAAC;IAED,GAAG,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC;IAEhD,MAAM,UAAU,GAAG,yBAAyB,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IAE3D,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,IAAI,GAAG,CAAC,iBAAiB,EAAE,CAAC;YAC1B,GAAG,CAAC,iBAAiB,CAAC;gBACpB,IAAI;gBACJ,IAAI,EAAE,QAAQ;gBACd,KAAK,EAAE,OAAO;gBACd,OAAO,EAAE,4BAA4B;aACtC,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,GAAG,CAAC,mBAAmB,EAAE,CAAC;YACnC,GAAG,CAAC,mBAAmB,CAAC,4BAA4B,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;AACH,CAAC;AAED,eAAe,EAAE,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * WeChat MP HTTP API adapter layer
3
+ *
4
+ * Provides unified HTTP client for WeChat MP API calls with:
5
+ * - Token injection and refresh on expiry
6
+ * - Error code handling and retry logic
7
+ * - Type-safe request/response handling
8
+ */
9
+ import type { ResolvedWechatMpAccount } from "./types.js";
10
+ export interface WechatMpApiResponse<T = unknown> {
11
+ errcode?: number;
12
+ errmsg?: string;
13
+ data?: T;
14
+ }
15
+ export interface WechatMpApiError extends Error {
16
+ errcode: number;
17
+ errmsg: string;
18
+ accountId: string;
19
+ }
20
+ export declare class WechatMpApiErrorImpl extends Error implements WechatMpApiError {
21
+ errcode: number;
22
+ errmsg: string;
23
+ accountId: string;
24
+ constructor(message: string, errcode: number, errmsg: string, accountId: string);
25
+ }
26
+ interface RequestConfig {
27
+ method: "GET" | "POST";
28
+ path: string;
29
+ params?: Record<string, string | number | boolean>;
30
+ body?: unknown;
31
+ requireToken?: boolean;
32
+ }
33
+ /**
34
+ * Make API call with error handling
35
+ * @throws WechatMpApiError if API returns an error
36
+ */
37
+ export declare function callWechatMpApi<T = unknown>(account: ResolvedWechatMpAccount, config: RequestConfig): Promise<T>;
38
+ export interface SendMessageParams {
39
+ touser: string;
40
+ msgtype: "text" | "image" | "voice" | "video" | "music" | "news" | "mpnews" | "msgmenu";
41
+ text?: {
42
+ content: string;
43
+ };
44
+ image?: {
45
+ media_id: string;
46
+ };
47
+ voice?: {
48
+ media_id: string;
49
+ };
50
+ video?: {
51
+ media_id: string;
52
+ thumb_media_id: string;
53
+ title?: string;
54
+ description?: string;
55
+ };
56
+ }
57
+ export interface SendMessageResult {
58
+ errcode: number;
59
+ errmsg: string;
60
+ msgid?: number;
61
+ }
62
+ /**
63
+ * Send customer service message (active send)
64
+ */
65
+ export declare function sendWechatMpMessage(account: ResolvedWechatMpAccount, params: SendMessageParams): Promise<SendMessageResult>;
66
+ export interface UploadMediaResult {
67
+ type: string;
68
+ media_id: string;
69
+ created_at: number;
70
+ }
71
+ /**
72
+ * Upload temporary media
73
+ */
74
+ export declare function uploadWechatMpMedia(account: ResolvedWechatMpAccount, type: "image" | "voice" | "video" | "thumb", media: Buffer, filename?: string): Promise<UploadMediaResult>;
75
+ export interface MenuButton {
76
+ type: "click" | "view" | "scancode_push" | "scancode_waitmsg" | "pic_sysphoto" | "pic_photo_or_album" | "pic_weixin" | "location_select" | "media_id" | "view_limited";
77
+ name: string;
78
+ key?: string;
79
+ url?: string;
80
+ media_id?: string;
81
+ sub_button?: MenuButton[];
82
+ }
83
+ export interface MenuConfig {
84
+ button: MenuButton[];
85
+ }
86
+ /**
87
+ * Create custom menu
88
+ */
89
+ export declare function createWechatMpMenu(account: ResolvedWechatMpAccount, menu: MenuConfig): Promise<{
90
+ errcode: number;
91
+ errmsg: string;
92
+ }>;
93
+ /**
94
+ * Delete custom menu
95
+ */
96
+ export declare function deleteWechatMpMenu(account: ResolvedWechatMpAccount): Promise<{
97
+ errcode: number;
98
+ errmsg: string;
99
+ }>;
100
+ export {};
101
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * WeChat MP HTTP API adapter layer
3
+ *
4
+ * Provides unified HTTP client for WeChat MP API calls with:
5
+ * - Token injection and refresh on expiry
6
+ * - Error code handling and retry logic
7
+ * - Type-safe request/response handling
8
+ */
9
+ import { getAccessToken, isInvalidTokenError, clearAccessTokenCache } from "./token.js";
10
+ export class WechatMpApiErrorImpl extends Error {
11
+ errcode;
12
+ errmsg;
13
+ accountId;
14
+ constructor(message, errcode, errmsg, accountId) {
15
+ super(message);
16
+ this.errcode = errcode;
17
+ this.errmsg = errmsg;
18
+ this.accountId = accountId;
19
+ this.name = "WechatMpApiError";
20
+ }
21
+ }
22
+ // ============================================================================
23
+ // API Client
24
+ // ============================================================================
25
+ const API_BASE = "https://api.weixin.qq.com";
26
+ /**
27
+ * Make an API call to WeChat MP
28
+ */
29
+ async function callApi(account, config) {
30
+ const url = new URL(`${API_BASE}${config.path}`);
31
+ // Add query params
32
+ if (config.params) {
33
+ for (const [key, value] of Object.entries(config.params)) {
34
+ url.searchParams.set(key, String(value));
35
+ }
36
+ }
37
+ // Add access_token if required
38
+ if (config.requireToken !== false) {
39
+ const token = await getAccessToken(account);
40
+ url.searchParams.set("access_token", token);
41
+ }
42
+ const fetchOptions = {
43
+ method: config.method,
44
+ headers: {
45
+ "Content-Type": "application/json",
46
+ },
47
+ };
48
+ if (config.body && config.method === "POST") {
49
+ fetchOptions.body = JSON.stringify(config.body);
50
+ }
51
+ const response = await fetch(url.toString(), fetchOptions);
52
+ const data = (await response.json());
53
+ // Handle token expiry
54
+ if (data.errcode && isInvalidTokenError(data.errcode)) {
55
+ clearAccessTokenCache(account);
56
+ // Retry once with fresh token
57
+ const newToken = await getAccessToken(account);
58
+ url.searchParams.set("access_token", newToken);
59
+ const retryResponse = await fetch(url.toString(), fetchOptions);
60
+ return (await retryResponse.json());
61
+ }
62
+ return data;
63
+ }
64
+ /**
65
+ * Make API call with error handling
66
+ * @throws WechatMpApiError if API returns an error
67
+ */
68
+ export async function callWechatMpApi(account, config) {
69
+ const response = await callApi(account, config);
70
+ if (response.errcode !== undefined && response.errcode !== 0) {
71
+ throw new WechatMpApiErrorImpl(`WeChat MP API error: ${response.errmsg ?? "unknown error"}`, response.errcode, response.errmsg ?? "unknown error", account.accountId);
72
+ }
73
+ return response.data;
74
+ }
75
+ /**
76
+ * Send customer service message (active send)
77
+ */
78
+ export async function sendWechatMpMessage(account, params) {
79
+ const response = await callApi(account, {
80
+ method: "POST",
81
+ path: "/cgi-bin/message/custom/send",
82
+ body: params,
83
+ });
84
+ return {
85
+ errcode: response.errcode ?? 0,
86
+ errmsg: response.errmsg ?? "ok",
87
+ msgid: response.data?.msgid,
88
+ };
89
+ }
90
+ /**
91
+ * Upload temporary media
92
+ */
93
+ export async function uploadWechatMpMedia(account, type, media, filename) {
94
+ const token = await getAccessToken(account);
95
+ const url = new URL(`${API_BASE}/cgi-bin/media/upload`);
96
+ url.searchParams.set("access_token", token);
97
+ url.searchParams.set("type", type);
98
+ const formData = new FormData();
99
+ const blob = new Blob([media]);
100
+ formData.append("media", blob, filename ?? `media.${type === "image" ? "jpg" : "mp3"}`);
101
+ const response = await fetch(url.toString(), {
102
+ method: "POST",
103
+ body: formData,
104
+ });
105
+ const data = (await response.json());
106
+ if (data.errcode && isInvalidTokenError(data.errcode)) {
107
+ clearAccessTokenCache(account);
108
+ return uploadWechatMpMedia(account, type, media, filename);
109
+ }
110
+ if (data.errcode !== undefined && data.errcode !== 0) {
111
+ throw new WechatMpApiErrorImpl(`Upload media failed: ${data.errmsg ?? "unknown error"}`, data.errcode, data.errmsg ?? "unknown error", account.accountId);
112
+ }
113
+ return data.data;
114
+ }
115
+ /**
116
+ * Create custom menu
117
+ */
118
+ export async function createWechatMpMenu(account, menu) {
119
+ const response = await callApi(account, {
120
+ method: "POST",
121
+ path: "/cgi-bin/menu/create",
122
+ body: menu,
123
+ });
124
+ return {
125
+ errcode: response.errcode ?? 0,
126
+ errmsg: response.errmsg ?? "ok",
127
+ };
128
+ }
129
+ /**
130
+ * Delete custom menu
131
+ */
132
+ export async function deleteWechatMpMenu(account) {
133
+ const response = await callApi(account, {
134
+ method: "GET",
135
+ path: "/cgi-bin/menu/delete",
136
+ });
137
+ return {
138
+ errcode: response.errcode ?? 0,
139
+ errmsg: response.errmsg ?? "ok",
140
+ };
141
+ }
142
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":"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"}