@sunnoy/wecom 1.7.1 → 1.8.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/index.js +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/wecom/accounts.js +9 -0
- package/wecom/channel-plugin.js +7 -0
- package/wecom/constants.js +38 -7
- package/wecom/http-handler.js +7 -1
- package/wecom/http.js +12 -1
- package/wecom/state.js +2 -2
- package/wecom/webhook-bot.js +2 -1
package/index.js
CHANGED
|
@@ -26,7 +26,7 @@ const plugin = {
|
|
|
26
26
|
id: "wecom",
|
|
27
27
|
name: "Enterprise WeChat",
|
|
28
28
|
description: "Enterprise WeChat AI Bot channel plugin for OpenClaw",
|
|
29
|
-
configSchema: { type: "object", additionalProperties:
|
|
29
|
+
configSchema: { type: "object", additionalProperties: true, properties: {} },
|
|
30
30
|
register(api) {
|
|
31
31
|
logger.info("WeCom plugin registering...");
|
|
32
32
|
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/wecom/accounts.js
CHANGED
|
@@ -56,6 +56,15 @@ const RESERVED_KEYS = new Set([
|
|
|
56
56
|
"allowFrom",
|
|
57
57
|
"commandAllowlist",
|
|
58
58
|
"commandBlockMessage",
|
|
59
|
+
// Top-level config keys that are NOT account IDs (issue #79).
|
|
60
|
+
"network",
|
|
61
|
+
"commands",
|
|
62
|
+
"dynamicAgents",
|
|
63
|
+
"dm",
|
|
64
|
+
"groupChat",
|
|
65
|
+
"adminUsers",
|
|
66
|
+
"workspaceTemplate",
|
|
67
|
+
"instances",
|
|
59
68
|
]);
|
|
60
69
|
|
|
61
70
|
// ── Helpers ─────────────────────────────────────────────────────────
|
package/wecom/channel-plugin.js
CHANGED
|
@@ -13,6 +13,7 @@ import { resolveWecomTarget } from "./target.js";
|
|
|
13
13
|
import { webhookSendImage, webhookSendText, webhookUploadFile, webhookSendFile } from "./webhook-bot.js";
|
|
14
14
|
import { normalizeWebhookPath, registerWebhookTarget } from "./webhook-targets.js";
|
|
15
15
|
import { wecomFetch, setConfigProxyUrl } from "./http.js";
|
|
16
|
+
import { setApiBaseUrl } from "./constants.js";
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
const AGENT_IMAGE_EXTS = new Set(["jpg", "jpeg", "png", "gif", "bmp"]);
|
|
@@ -164,6 +165,10 @@ export const wecomChannelPlugin = {
|
|
|
164
165
|
type: "string",
|
|
165
166
|
description: "HTTP(S) proxy URL for outbound WeCom API requests (e.g. http://proxy:8080). Env var WECOM_EGRESS_PROXY_URL takes precedence.",
|
|
166
167
|
},
|
|
168
|
+
apiBaseUrl: {
|
|
169
|
+
type: "string",
|
|
170
|
+
description: "Custom WeCom API base URL (default: https://qyapi.weixin.qq.com). Use when routing through a reverse-proxy or API gateway. Env var WECOM_API_BASE_URL takes precedence.",
|
|
171
|
+
},
|
|
167
172
|
},
|
|
168
173
|
},
|
|
169
174
|
webhooks: {
|
|
@@ -739,6 +744,8 @@ export const wecomChannelPlugin = {
|
|
|
739
744
|
// Wire proxy URL from config (env var takes precedence inside http.js).
|
|
740
745
|
const wecomCfg = ctx.cfg?.channels?.wecom ?? {};
|
|
741
746
|
setConfigProxyUrl(wecomCfg.network?.egressProxyUrl ?? "");
|
|
747
|
+
// Wire API base URL override (env var WECOM_API_BASE_URL takes precedence).
|
|
748
|
+
setApiBaseUrl(wecomCfg.network?.apiBaseUrl ?? "");
|
|
742
749
|
|
|
743
750
|
// Conflict detection: warn about duplicate tokens / agent IDs.
|
|
744
751
|
const conflicts = detectAccountConflicts(ctx.cfg);
|
package/wecom/constants.js
CHANGED
|
@@ -40,13 +40,36 @@ export const MAIN_RESPONSE_IDLE_CLOSE_MS = 30 * 1000;
|
|
|
40
40
|
export const SAFETY_NET_IDLE_CLOSE_MS = 90 * 1000;
|
|
41
41
|
export const RESPONSE_URL_ERROR_BODY_PREVIEW_MAX = 300;
|
|
42
42
|
|
|
43
|
+
// Default Agent API base URL (self-built application mode).
|
|
44
|
+
// Can be overridden via `channels.wecom.network.apiBaseUrl` config or
|
|
45
|
+
// `WECOM_API_BASE_URL` env var for users behind a reverse-proxy gateway
|
|
46
|
+
// that relays requests to qyapi.weixin.qq.com (issue #79).
|
|
47
|
+
const DEFAULT_API_BASE = "https://qyapi.weixin.qq.com";
|
|
48
|
+
|
|
49
|
+
let _apiBase = DEFAULT_API_BASE;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Set the API base URL from plugin config (called during plugin load).
|
|
53
|
+
* @param {string} url
|
|
54
|
+
*/
|
|
55
|
+
export function setApiBaseUrl(url) {
|
|
56
|
+
const trimmed = (url || "").trim().replace(/\/+$/, "");
|
|
57
|
+
_apiBase = trimmed || DEFAULT_API_BASE;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function apiBase() {
|
|
61
|
+
// Env var takes precedence over config.
|
|
62
|
+
const env = (process.env.WECOM_API_BASE_URL || "").trim().replace(/\/+$/, "");
|
|
63
|
+
return env || _apiBase;
|
|
64
|
+
}
|
|
65
|
+
|
|
43
66
|
// Agent API endpoints (self-built application mode).
|
|
44
67
|
export const AGENT_API_ENDPOINTS = {
|
|
45
|
-
GET_TOKEN
|
|
46
|
-
SEND_MESSAGE
|
|
47
|
-
SEND_APPCHAT
|
|
48
|
-
UPLOAD_MEDIA
|
|
49
|
-
DOWNLOAD_MEDIA
|
|
68
|
+
get GET_TOKEN() { return `${apiBase()}/cgi-bin/gettoken`; },
|
|
69
|
+
get SEND_MESSAGE() { return `${apiBase()}/cgi-bin/message/send`; },
|
|
70
|
+
get SEND_APPCHAT() { return `${apiBase()}/cgi-bin/appchat/send`; },
|
|
71
|
+
get UPLOAD_MEDIA() { return `${apiBase()}/cgi-bin/media/upload`; },
|
|
72
|
+
get DOWNLOAD_MEDIA() { return `${apiBase()}/cgi-bin/media/get`; },
|
|
50
73
|
};
|
|
51
74
|
|
|
52
75
|
export const TOKEN_REFRESH_BUFFER_MS = 60 * 1000;
|
|
@@ -54,5 +77,13 @@ export const AGENT_API_REQUEST_TIMEOUT_MS = 15 * 1000;
|
|
|
54
77
|
export const MAX_REQUEST_BODY_SIZE = 1024 * 1024; // 1 MB
|
|
55
78
|
|
|
56
79
|
// Webhook Bot endpoints (group robot notifications).
|
|
57
|
-
export const
|
|
58
|
-
export const
|
|
80
|
+
export const WEBHOOK_BOT_SEND_URL_DEFAULT = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send";
|
|
81
|
+
export const WEBHOOK_BOT_UPLOAD_URL_DEFAULT = "https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media";
|
|
82
|
+
|
|
83
|
+
// Dynamic getters so apiBaseUrl override applies to webhook bot too.
|
|
84
|
+
export function getWebhookBotSendUrl() {
|
|
85
|
+
return `${apiBase()}/cgi-bin/webhook/send`;
|
|
86
|
+
}
|
|
87
|
+
export function getWebhookBotUploadUrl() {
|
|
88
|
+
return `${apiBase()}/cgi-bin/webhook/upload_media`;
|
|
89
|
+
}
|
package/wecom/http-handler.js
CHANGED
|
@@ -48,7 +48,13 @@ export async function wecomHttpHandler(req, res) {
|
|
|
48
48
|
const targets = webhookTargets.get(path);
|
|
49
49
|
|
|
50
50
|
if (!targets || targets.length === 0) {
|
|
51
|
-
|
|
51
|
+
// Return a proper HTTP response instead of `false`. Returning false tells
|
|
52
|
+
// OpenClaw 3.x "not handled", which causes the SPA catch-all to serve the
|
|
53
|
+
// chat UI on webhook paths (issue #81).
|
|
54
|
+
logger.debug("WeCom: no webhook target registered for path", { path });
|
|
55
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
56
|
+
res.end(`No WeCom webhook target configured for ${path}`);
|
|
57
|
+
return true;
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
const query = Object.fromEntries(url.searchParams);
|
package/wecom/http.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* 3. Config: `channels.wecom.network.egressProxyUrl`
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { logger } from "../logger.js";
|
|
15
16
|
import { AGENT_API_REQUEST_TIMEOUT_MS } from "./constants.js";
|
|
16
17
|
|
|
17
18
|
// ── Lazy-loaded undici (optional dependency) ──────────────────────────
|
|
@@ -64,6 +65,7 @@ function mergeAbortSignal({ signal, timeoutMs }) {
|
|
|
64
65
|
// ── Proxy URL resolution ──────────────────────────────────────────────
|
|
65
66
|
|
|
66
67
|
let _configProxyUrl = "";
|
|
68
|
+
let _proxyWarningLogged = false;
|
|
67
69
|
|
|
68
70
|
/**
|
|
69
71
|
* Set the proxy URL from plugin config (called once during plugin load).
|
|
@@ -118,7 +120,16 @@ export async function wecomFetch(input, init, opts) {
|
|
|
118
120
|
dispatcher,
|
|
119
121
|
});
|
|
120
122
|
}
|
|
121
|
-
// undici not available — fall through to native fetch (no proxy)
|
|
123
|
+
// undici not available — log warning and fall through to native fetch (no proxy).
|
|
124
|
+
// This is a common cause of proxy misconfiguration (issue #79).
|
|
125
|
+
if (!_proxyWarningLogged) {
|
|
126
|
+
_proxyWarningLogged = true;
|
|
127
|
+
logger.error(
|
|
128
|
+
"[wecom/http] Proxy configured but undici is not available — requests will go DIRECT without proxy. " +
|
|
129
|
+
"Install undici (npm install undici) to enable proxy support.",
|
|
130
|
+
{ proxyUrl },
|
|
131
|
+
);
|
|
132
|
+
}
|
|
122
133
|
}
|
|
123
134
|
|
|
124
135
|
// Native fetch (no proxy)
|
package/wecom/state.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
import {
|
|
2
|
+
import { getWebhookBotSendUrl } from "./constants.js";
|
|
3
3
|
import { resolveAgentConfigForAccount, resolveAccount } from "./accounts.js";
|
|
4
4
|
|
|
5
5
|
const runtimeState = {
|
|
@@ -81,5 +81,5 @@ export function resolveWebhookUrl(name, accountId) {
|
|
|
81
81
|
if (!webhooks || !webhooks[name]) return null;
|
|
82
82
|
const value = webhooks[name];
|
|
83
83
|
if (value.startsWith("http")) return value;
|
|
84
|
-
return `${
|
|
84
|
+
return `${getWebhookBotSendUrl()}?key=${value}`;
|
|
85
85
|
}
|
package/wecom/webhook-bot.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import crypto from "node:crypto";
|
|
11
11
|
import { logger } from "../logger.js";
|
|
12
12
|
import { AGENT_API_REQUEST_TIMEOUT_MS } from "./constants.js";
|
|
13
|
+
import { getWebhookBotUploadUrl } from "./constants.js";
|
|
13
14
|
import { wecomFetch } from "./http.js";
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -76,7 +77,7 @@ export async function webhookSendImage({ url, base64, md5 }) {
|
|
|
76
77
|
*/
|
|
77
78
|
export async function webhookUploadFile({ url, buffer, filename }) {
|
|
78
79
|
const key = extractKey(url);
|
|
79
|
-
const uploadUrl =
|
|
80
|
+
const uploadUrl = `${getWebhookBotUploadUrl()}?key=${encodeURIComponent(key)}&type=file`;
|
|
80
81
|
|
|
81
82
|
const boundary = `----WebKitFormBoundary${crypto.randomBytes(16).toString("hex")}`;
|
|
82
83
|
const header = Buffer.from(
|