@sunnoy/wecom 1.9.0 → 2.0.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 +391 -845
- package/image-processor.js +30 -27
- package/index.js +10 -43
- package/package.json +5 -5
- package/think-parser.js +51 -11
- package/wecom/accounts.js +323 -189
- package/wecom/channel-plugin.js +543 -750
- package/wecom/constants.js +57 -47
- package/wecom/dm-policy.js +91 -0
- package/wecom/group-policy.js +85 -0
- package/wecom/onboarding.js +117 -0
- package/wecom/runtime-telemetry.js +330 -0
- package/wecom/sandbox.js +60 -0
- package/wecom/state.js +33 -35
- package/wecom/workspace-template.js +62 -5
- package/wecom/ws-monitor.js +1487 -0
- package/wecom/ws-state.js +160 -0
- package/crypto.js +0 -135
- package/stream-manager.js +0 -358
- package/webhook.js +0 -469
- package/wecom/agent-inbound.js +0 -541
- package/wecom/http-handler-state.js +0 -23
- package/wecom/http-handler.js +0 -395
- package/wecom/inbound-processor.js +0 -562
- package/wecom/media.js +0 -192
- package/wecom/outbound-delivery.js +0 -435
- package/wecom/response-url.js +0 -33
- package/wecom/stream-utils.js +0 -163
- package/wecom/webhook-targets.js +0 -28
- package/wecom/xml-parser.js +0 -126
package/wecom/constants.js
CHANGED
|
@@ -1,27 +1,49 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
|
|
3
|
+
export const CHANNEL_ID = "wecom";
|
|
3
4
|
export const DEFAULT_ACCOUNT_ID = "default";
|
|
5
|
+
export const DEFAULT_WEBSOCKET_URL = "wss://openws.work.weixin.qq.com";
|
|
6
|
+
export const DEFAULT_WS_URL = DEFAULT_WEBSOCKET_URL;
|
|
7
|
+
|
|
8
|
+
export const THINKING_MESSAGE = "<think></think>";
|
|
9
|
+
export const MEDIA_IMAGE_PLACEHOLDER = "<media:image>";
|
|
10
|
+
export const MEDIA_DOCUMENT_PLACEHOLDER = "<media:document>";
|
|
11
|
+
|
|
12
|
+
export const IMAGE_DOWNLOAD_TIMEOUT_MS = 30_000;
|
|
13
|
+
export const FILE_DOWNLOAD_TIMEOUT_MS = 60_000;
|
|
14
|
+
export const REPLY_SEND_TIMEOUT_MS = 15_000;
|
|
15
|
+
export const MESSAGE_PROCESS_TIMEOUT_MS = 5 * 60 * 1000;
|
|
16
|
+
export const WS_HEARTBEAT_INTERVAL_MS = 30_000;
|
|
17
|
+
export const WS_MAX_RECONNECT_ATTEMPTS = 100;
|
|
18
|
+
|
|
19
|
+
export const MESSAGE_STATE_TTL_MS = 10 * 60 * 1000;
|
|
20
|
+
export const MESSAGE_STATE_CLEANUP_INTERVAL_MS = 60_000;
|
|
21
|
+
export const MESSAGE_STATE_MAX_SIZE = 500;
|
|
22
|
+
export const REQID_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
23
|
+
export const REQID_MAX_SIZE = 200;
|
|
24
|
+
export const REQID_FLUSH_DEBOUNCE_MS = 1_000;
|
|
25
|
+
|
|
26
|
+
export const PENDING_REPLY_TTL_MS = 5 * 60 * 1000;
|
|
27
|
+
export const PENDING_REPLY_MAX_SIZE = 50;
|
|
28
|
+
|
|
29
|
+
export const DEFAULT_MEDIA_MAX_MB = 5;
|
|
30
|
+
export const TEXT_CHUNK_LIMIT = 4000;
|
|
31
|
+
export const DEFAULT_WELCOME_MESSAGE = ["你好,我是 AI 助手。", "", "可用命令:", "/new", "/compact", "/help", "/status"].join(
|
|
32
|
+
"\n",
|
|
33
|
+
);
|
|
4
34
|
|
|
5
|
-
// Placeholder shown while the LLM is processing or the message is queued.
|
|
6
|
-
export const THINKING_PLACEHOLDER = "思考中...";
|
|
7
|
-
|
|
8
|
-
// Image cache directory.
|
|
9
35
|
export const MEDIA_CACHE_DIR = join(process.env.HOME || "/tmp", ".openclaw", "media", "wecom");
|
|
10
36
|
|
|
11
|
-
// Slash commands that are allowed by default.
|
|
12
37
|
export const DEFAULT_COMMAND_ALLOWLIST = ["/new", "/compact", "/help", "/status"];
|
|
13
38
|
export const HIGH_PRIORITY_COMMANDS = new Set(["/stop", "/new"]);
|
|
14
|
-
|
|
15
|
-
// Default message shown when a command is blocked.
|
|
16
39
|
export const DEFAULT_COMMAND_BLOCK_MESSAGE = `⚠️ 该命令不可用。
|
|
17
40
|
|
|
18
41
|
支持的命令:
|
|
19
42
|
• **/new** - 新建会话
|
|
20
|
-
• **/compact** -
|
|
43
|
+
• **/compact** - 压缩会话
|
|
21
44
|
• **/help** - 查看帮助
|
|
22
45
|
• **/status** - 查看状态`;
|
|
23
46
|
|
|
24
|
-
// Files recognised by openclaw core as bootstrap files.
|
|
25
47
|
export const BOOTSTRAP_FILENAMES = new Set([
|
|
26
48
|
"AGENTS.md",
|
|
27
49
|
"SOUL.md",
|
|
@@ -30,60 +52,48 @@ export const BOOTSTRAP_FILENAMES = new Set([
|
|
|
30
52
|
"USER.md",
|
|
31
53
|
"HEARTBEAT.md",
|
|
32
54
|
"BOOTSTRAP.md",
|
|
55
|
+
"system-prompt.md",
|
|
33
56
|
]);
|
|
34
57
|
|
|
35
|
-
// Per-user message debounce buffer.
|
|
36
|
-
// Collects messages arriving within DEBOUNCE_MS into a single dispatch.
|
|
37
|
-
export const DEBOUNCE_MS = 2000;
|
|
38
|
-
|
|
39
|
-
export const MAIN_RESPONSE_IDLE_CLOSE_MS = 30 * 1000;
|
|
40
|
-
export const SAFETY_NET_IDLE_CLOSE_MS = 90 * 1000;
|
|
41
|
-
export const RESPONSE_URL_ERROR_BODY_PREVIEW_MAX = 300;
|
|
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
58
|
const DEFAULT_API_BASE = "https://qyapi.weixin.qq.com";
|
|
59
|
+
let apiBaseUrl = DEFAULT_API_BASE;
|
|
48
60
|
|
|
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
61
|
export function setApiBaseUrl(url) {
|
|
56
|
-
const trimmed = (url
|
|
57
|
-
|
|
62
|
+
const trimmed = String(url ?? "").trim().replace(/\/+$/, "");
|
|
63
|
+
apiBaseUrl = trimmed || DEFAULT_API_BASE;
|
|
58
64
|
}
|
|
59
65
|
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return env || _apiBase;
|
|
66
|
+
function resolveApiBaseUrl() {
|
|
67
|
+
const env = String(process.env.WECOM_API_BASE_URL ?? "").trim().replace(/\/+$/, "");
|
|
68
|
+
return env || apiBaseUrl;
|
|
64
69
|
}
|
|
65
70
|
|
|
66
|
-
// Agent API endpoints (self-built application mode).
|
|
67
71
|
export const AGENT_API_ENDPOINTS = {
|
|
68
|
-
get GET_TOKEN() {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
get
|
|
72
|
-
|
|
72
|
+
get GET_TOKEN() {
|
|
73
|
+
return `${resolveApiBaseUrl()}/cgi-bin/gettoken`;
|
|
74
|
+
},
|
|
75
|
+
get SEND_MESSAGE() {
|
|
76
|
+
return `${resolveApiBaseUrl()}/cgi-bin/message/send`;
|
|
77
|
+
},
|
|
78
|
+
get SEND_APPCHAT() {
|
|
79
|
+
return `${resolveApiBaseUrl()}/cgi-bin/appchat/send`;
|
|
80
|
+
},
|
|
81
|
+
get UPLOAD_MEDIA() {
|
|
82
|
+
return `${resolveApiBaseUrl()}/cgi-bin/media/upload`;
|
|
83
|
+
},
|
|
84
|
+
get DOWNLOAD_MEDIA() {
|
|
85
|
+
return `${resolveApiBaseUrl()}/cgi-bin/media/get`;
|
|
86
|
+
},
|
|
73
87
|
};
|
|
74
88
|
|
|
75
89
|
export const TOKEN_REFRESH_BUFFER_MS = 60 * 1000;
|
|
76
90
|
export const AGENT_API_REQUEST_TIMEOUT_MS = 15 * 1000;
|
|
77
|
-
export const MAX_REQUEST_BODY_SIZE = 1024 * 1024;
|
|
78
|
-
|
|
79
|
-
// Webhook Bot endpoints (group robot notifications).
|
|
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";
|
|
91
|
+
export const MAX_REQUEST_BODY_SIZE = 1024 * 1024;
|
|
82
92
|
|
|
83
|
-
// Dynamic getters so apiBaseUrl override applies to webhook bot too.
|
|
84
93
|
export function getWebhookBotSendUrl() {
|
|
85
|
-
return `${
|
|
94
|
+
return `${resolveApiBaseUrl()}/cgi-bin/webhook/send`;
|
|
86
95
|
}
|
|
96
|
+
|
|
87
97
|
export function getWebhookBotUploadUrl() {
|
|
88
|
-
return `${
|
|
98
|
+
return `${resolveApiBaseUrl()}/cgi-bin/webhook/upload_media`;
|
|
89
99
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { generateReqId } from "@wecom/aibot-node-sdk";
|
|
2
|
+
import { logger } from "../logger.js";
|
|
3
|
+
import { isSenderAllowed } from "./group-policy.js";
|
|
4
|
+
import { getRuntime } from "./state.js";
|
|
5
|
+
|
|
6
|
+
async function readStoredAllowFrom(pairing, accountId) {
|
|
7
|
+
const legacy = await pairing
|
|
8
|
+
.readAllowFromStore("wecom", undefined, accountId)
|
|
9
|
+
.catch(() => []);
|
|
10
|
+
const current = await pairing
|
|
11
|
+
.readAllowFromStore({ channel: "wecom", accountId })
|
|
12
|
+
.catch(() => []);
|
|
13
|
+
return [...legacy, ...current];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function sendPairingReply({ wsClient, frame, text, sendReply }) {
|
|
17
|
+
if (typeof sendReply === "function") {
|
|
18
|
+
await sendReply({
|
|
19
|
+
frame,
|
|
20
|
+
text,
|
|
21
|
+
finish: true,
|
|
22
|
+
streamId: generateReqId("pairing"),
|
|
23
|
+
});
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const streamId = generateReqId("pairing");
|
|
28
|
+
await wsClient.replyStream(frame, streamId, text, true);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function checkDmPolicy({ senderId, isGroup, account, wsClient, frame, core, sendReply }) {
|
|
32
|
+
if (isGroup) {
|
|
33
|
+
return { allowed: true };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let runtime = null;
|
|
37
|
+
try {
|
|
38
|
+
runtime = getRuntime();
|
|
39
|
+
} catch {}
|
|
40
|
+
const pairing = core?.pairing ?? runtime?.channel?.pairing ?? runtime?.pairing;
|
|
41
|
+
const dmPolicy = account?.config?.dmPolicy ?? "pairing";
|
|
42
|
+
const configAllowFrom = (account?.config?.allowFrom ?? []).map((entry) => String(entry));
|
|
43
|
+
|
|
44
|
+
if (dmPolicy === "disabled") {
|
|
45
|
+
return { allowed: false };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (dmPolicy === "open") {
|
|
49
|
+
return { allowed: true };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const storeAllowFrom = pairing ? await readStoredAllowFrom(pairing, account.accountId) : [];
|
|
53
|
+
const effectiveAllowFrom = [...configAllowFrom, ...storeAllowFrom];
|
|
54
|
+
if (isSenderAllowed(senderId, effectiveAllowFrom)) {
|
|
55
|
+
return { allowed: true };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (dmPolicy === "pairing" && pairing) {
|
|
59
|
+
const request = await pairing.upsertPairingRequest({
|
|
60
|
+
channel: "wecom",
|
|
61
|
+
id: senderId,
|
|
62
|
+
accountId: account.accountId,
|
|
63
|
+
meta: { name: senderId },
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (request?.created) {
|
|
67
|
+
try {
|
|
68
|
+
const replyText = pairing.buildPairingReply({
|
|
69
|
+
channel: "wecom",
|
|
70
|
+
idLine: `您的企业微信用户ID: ${senderId}`,
|
|
71
|
+
code: request.code,
|
|
72
|
+
});
|
|
73
|
+
await sendPairingReply({
|
|
74
|
+
wsClient,
|
|
75
|
+
frame,
|
|
76
|
+
text: replyText,
|
|
77
|
+
sendReply,
|
|
78
|
+
});
|
|
79
|
+
} catch (error) {
|
|
80
|
+
logger.warn(`[wecom] failed to send pairing reply: ${error.message}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
allowed: false,
|
|
86
|
+
pairingSent: Boolean(request?.created),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { allowed: false };
|
|
91
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { normalizeWecomAllowFromEntry } from "./allow-from.js";
|
|
2
|
+
|
|
3
|
+
function normalizeGroupEntry(entry) {
|
|
4
|
+
const trimmed = String(entry ?? "").trim();
|
|
5
|
+
if (!trimmed) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
if (trimmed === "*") {
|
|
9
|
+
return "*";
|
|
10
|
+
}
|
|
11
|
+
return trimmed.replace(/^wecom:/i, "").replace(/^(group|chat):/i, "").trim().toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function resolveGroupConfig(accountConfig, groupId) {
|
|
15
|
+
const groups = accountConfig?.groups;
|
|
16
|
+
if (!groups || typeof groups !== "object") {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const normalizedGroupId = String(groupId ?? "").trim().toLowerCase();
|
|
21
|
+
if (!normalizedGroupId) {
|
|
22
|
+
return groups["*"] ?? null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
for (const [key, value] of Object.entries(groups)) {
|
|
26
|
+
if (String(key).trim().toLowerCase() === normalizedGroupId) {
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return groups["*"] ?? null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isGroupAllowed(groupId, allowFrom, policy) {
|
|
35
|
+
if (policy === "disabled") {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (policy === "open") {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const normalizedGroupId = String(groupId ?? "").trim().toLowerCase();
|
|
43
|
+
const normalizedAllowFrom = (allowFrom ?? []).map(normalizeGroupEntry).filter(Boolean);
|
|
44
|
+
if (normalizedAllowFrom.includes("*")) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return normalizedAllowFrom.includes(normalizedGroupId);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function isSenderAllowed(senderId, allowFrom) {
|
|
51
|
+
const normalizedSender = normalizeWecomAllowFromEntry(senderId);
|
|
52
|
+
const normalizedAllowFrom = (allowFrom ?? []).map(normalizeWecomAllowFromEntry).filter(Boolean);
|
|
53
|
+
|
|
54
|
+
if (normalizedAllowFrom.includes("*")) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return normalizedAllowFrom.includes(normalizedSender);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function checkGroupPolicy({ chatId, senderId, account, config }) {
|
|
62
|
+
const groupPolicy =
|
|
63
|
+
account?.config?.groupPolicy ??
|
|
64
|
+
config?.channels?.wecom?.groupPolicy ??
|
|
65
|
+
"open";
|
|
66
|
+
|
|
67
|
+
const groupAllowFrom =
|
|
68
|
+
account?.config?.groupAllowFrom ??
|
|
69
|
+
config?.channels?.wecom?.groupAllowFrom ??
|
|
70
|
+
[];
|
|
71
|
+
|
|
72
|
+
if (!isGroupAllowed(chatId, groupAllowFrom, groupPolicy)) {
|
|
73
|
+
return { allowed: false };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const groupConfig = resolveGroupConfig(account?.config, chatId);
|
|
77
|
+
const senderAllowFrom = Array.isArray(groupConfig?.allowFrom) ? groupConfig.allowFrom : [];
|
|
78
|
+
if (senderAllowFrom.length === 0) {
|
|
79
|
+
return { allowed: true };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
allowed: isSenderAllowed(senderId, senderAllowFrom),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { addWildcardAllowFrom } from "openclaw/plugin-sdk";
|
|
2
|
+
import { DEFAULT_WS_URL, DEFAULT_ACCOUNT_ID } from "./constants.js";
|
|
3
|
+
import { resolveAccount, updateAccountConfig } from "./accounts.js";
|
|
4
|
+
|
|
5
|
+
const CHANNEL_ID = "wecom";
|
|
6
|
+
|
|
7
|
+
function resolveWizardAccountId(accountId) {
|
|
8
|
+
return String(accountId || DEFAULT_ACCOUNT_ID).trim() || DEFAULT_ACCOUNT_ID;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function resolveOnboardingAccountId(params) {
|
|
12
|
+
return resolveWizardAccountId(params?.accountId);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function setWecomDmPolicy(cfg, accountId, dmPolicy) {
|
|
16
|
+
const account = resolveAccount(cfg, accountId);
|
|
17
|
+
const existingAllowFrom = Array.isArray(account.config.allowFrom) ? account.config.allowFrom : [];
|
|
18
|
+
const nextAllowFrom =
|
|
19
|
+
dmPolicy === "open"
|
|
20
|
+
? addWildcardAllowFrom(existingAllowFrom.map((entry) => String(entry)))
|
|
21
|
+
: existingAllowFrom.map((entry) => String(entry));
|
|
22
|
+
|
|
23
|
+
return updateAccountConfig(cfg, accountId, {
|
|
24
|
+
dmPolicy,
|
|
25
|
+
allowFrom: nextAllowFrom,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function promptBotId(prompter, account) {
|
|
30
|
+
return String(
|
|
31
|
+
await prompter.text({
|
|
32
|
+
message: "企业微信机器人 Bot ID",
|
|
33
|
+
initialValue: account?.botId ?? "",
|
|
34
|
+
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
35
|
+
}),
|
|
36
|
+
).trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function promptSecret(prompter, account) {
|
|
40
|
+
return String(
|
|
41
|
+
await prompter.text({
|
|
42
|
+
message: "企业微信机器人 Secret",
|
|
43
|
+
initialValue: account?.secret ?? "",
|
|
44
|
+
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
|
|
45
|
+
}),
|
|
46
|
+
).trim();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function promptWebsocketUrl(prompter, account) {
|
|
50
|
+
return String(
|
|
51
|
+
await prompter.text({
|
|
52
|
+
message: "企业微信 WebSocket 地址(留空使用官方默认地址)",
|
|
53
|
+
initialValue: account?.websocketUrl ?? DEFAULT_WS_URL,
|
|
54
|
+
}),
|
|
55
|
+
).trim();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const dmPolicy = {
|
|
59
|
+
label: "企业微信",
|
|
60
|
+
channel: CHANNEL_ID,
|
|
61
|
+
policyKey: "channels.wecom.dmPolicy",
|
|
62
|
+
allowFromKey: "channels.wecom.allowFrom",
|
|
63
|
+
getCurrent: (cfg, accountId) => resolveAccount(cfg, resolveWizardAccountId(accountId)).config.dmPolicy ?? "pairing",
|
|
64
|
+
setPolicy: (cfg, policy, accountId) => setWecomDmPolicy(cfg, resolveWizardAccountId(accountId), policy),
|
|
65
|
+
promptAllowFrom: async ({ cfg, prompter, accountId }) => {
|
|
66
|
+
const resolvedAccountId = resolveOnboardingAccountId({ accountId });
|
|
67
|
+
const account = resolveAccount(cfg, resolvedAccountId);
|
|
68
|
+
const existingAllowFrom = Array.isArray(account.config.allowFrom) ? account.config.allowFrom : [];
|
|
69
|
+
const input = await prompter.text({
|
|
70
|
+
message: "企业微信 allowFrom(用户ID,每行一个)",
|
|
71
|
+
initialValue: existingAllowFrom.join("\n"),
|
|
72
|
+
placeholder: "user_a\nuser_b",
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const allowFrom = String(input ?? "")
|
|
76
|
+
.split(/[\n,;]+/g)
|
|
77
|
+
.map((entry) => entry.trim())
|
|
78
|
+
.filter(Boolean);
|
|
79
|
+
|
|
80
|
+
return updateAccountConfig(cfg, resolvedAccountId, { allowFrom });
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const wecomOnboardingAdapter = {
|
|
85
|
+
channel: CHANNEL_ID,
|
|
86
|
+
getStatus: async ({ cfg, accountId }) => {
|
|
87
|
+
const account = resolveAccount(cfg, resolveOnboardingAccountId({ accountId }));
|
|
88
|
+
const configured = Boolean(account.botId && account.secret);
|
|
89
|
+
return {
|
|
90
|
+
channel: CHANNEL_ID,
|
|
91
|
+
configured,
|
|
92
|
+
statusLines: [`企业微信: ${configured ? "已配置" : "需要 Bot ID 和 Secret"}`],
|
|
93
|
+
selectionHint: configured ? "已配置" : "需要设置",
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
configure: async ({ cfg, prompter, accountId }) => {
|
|
97
|
+
const resolvedAccountId = resolveWizardAccountId(accountId);
|
|
98
|
+
const account = resolveAccount(cfg, resolvedAccountId);
|
|
99
|
+
const botId = await promptBotId(prompter, account);
|
|
100
|
+
const secret = await promptSecret(prompter, account);
|
|
101
|
+
const websocketUrl = await promptWebsocketUrl(prompter, account);
|
|
102
|
+
|
|
103
|
+
const nextCfg = updateAccountConfig(cfg, resolvedAccountId, {
|
|
104
|
+
enabled: true,
|
|
105
|
+
botId,
|
|
106
|
+
secret,
|
|
107
|
+
websocketUrl: websocketUrl || DEFAULT_WS_URL,
|
|
108
|
+
sendThinkingMessage: account.sendThinkingMessage !== false,
|
|
109
|
+
dmPolicy: account.config.dmPolicy ?? "pairing",
|
|
110
|
+
allowFrom: account.config.allowFrom ?? [],
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return { cfg: nextCfg };
|
|
114
|
+
},
|
|
115
|
+
dmPolicy,
|
|
116
|
+
disable: (cfg, accountId) => updateAccountConfig(cfg, resolveWizardAccountId(accountId), { enabled: false }),
|
|
117
|
+
};
|