@yanhaidao/wecom 2.4.120 → 2.5.110
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 +4 -5
- package/dist/index.js +68 -0
- package/dist/src/accounts.js +20 -0
- package/dist/src/agent/handler.js +895 -0
- package/dist/src/agent/index.js +5 -0
- package/dist/src/app/account-runtime.js +216 -0
- package/dist/src/app/bootstrap.js +19 -0
- package/dist/src/app/index.js +118 -0
- package/dist/src/capability/agent/delivery-service.js +63 -0
- package/dist/src/capability/agent/fallback-policy.js +6 -0
- package/dist/src/capability/agent/ingress-service.js +33 -0
- package/dist/src/capability/agent/upstream-delivery-service.js +71 -0
- package/dist/src/capability/bot/dispatch-config.js +45 -0
- package/dist/src/capability/bot/fallback-delivery.js +147 -0
- package/dist/src/capability/bot/local-path-delivery.js +178 -0
- package/dist/src/capability/bot/sandbox-media.js +138 -0
- package/dist/src/capability/bot/service.js +49 -0
- package/dist/src/capability/bot/stream-delivery.js +321 -0
- package/dist/src/capability/bot/stream-finalizer.js +81 -0
- package/dist/src/capability/bot/stream-orchestrator.js +318 -0
- package/dist/src/capability/bot/types.js +1 -0
- package/{src/capability/calendar/client.ts → dist/src/capability/calendar/client.js} +118 -241
- package/{src/capability/calendar/schema.ts → dist/src/capability/calendar/schema.js} +0 -38
- package/dist/src/capability/calendar/tool.js +365 -0
- package/dist/src/capability/calendar/types.js +12 -0
- package/{src/capability/doc/client.ts → dist/src/capability/doc/client.js} +370 -605
- package/{src/capability/doc/schema.ts → dist/src/capability/doc/schema.js} +345 -394
- package/dist/src/capability/doc/tool.js +1556 -0
- package/dist/src/capability/doc/types.js +113 -0
- package/dist/src/capability/mcp/index.js +3 -0
- package/dist/src/capability/mcp/schema.js +102 -0
- package/dist/src/capability/mcp/tool.js +146 -0
- package/dist/src/capability/mcp/transport.js +293 -0
- package/dist/src/channel.js +224 -0
- package/dist/src/config/accounts.js +236 -0
- package/dist/src/config/derived-paths.js +31 -0
- package/dist/src/config/index.js +7 -0
- package/dist/src/config/media.js +110 -0
- package/dist/src/config/network.js +32 -0
- package/dist/src/config/routing.js +20 -0
- package/dist/src/config/runtime-config.js +25 -0
- package/dist/src/config/schema.js +4 -0
- package/{src/config-schema.ts → dist/src/config-schema.js} +1 -1
- package/dist/src/context-store.js +219 -0
- package/{src/crypto/aes.ts → dist/src/crypto/aes.js} +11 -28
- package/dist/src/crypto/index.js +9 -0
- package/{src/crypto/signature.ts → dist/src/crypto/signature.js} +3 -18
- package/{src/crypto/xml.ts → dist/src/crypto/xml.js} +3 -11
- package/dist/src/crypto.js +145 -0
- package/dist/src/domain/models.js +1 -0
- package/dist/src/domain/policies.js +32 -0
- package/{src/dynamic-agent.ts → dist/src/dynamic-agent.js} +36 -73
- package/dist/src/gateway-monitor.js +139 -0
- package/dist/src/http.js +114 -0
- package/{src/media.ts → dist/src/media.js} +21 -40
- package/dist/src/monitor/limits.js +7 -0
- package/dist/src/monitor/state.js +28 -0
- package/dist/src/monitor.js +84 -0
- package/dist/src/observability/audit-log.js +30 -0
- package/dist/src/observability/legacy-operational-event-store.js +22 -0
- package/dist/src/observability/raw-envelope-log.js +24 -0
- package/dist/src/observability/status-registry.js +9 -0
- package/dist/src/observability/transport-session-view.js +14 -0
- package/dist/src/onboarding.js +546 -0
- package/dist/src/outbound.js +557 -0
- package/dist/src/runtime/dispatcher.js +57 -0
- package/{src/runtime/index.ts → dist/src/runtime/index.js} +0 -1
- package/dist/src/runtime/outbound-intent.js +1 -0
- package/dist/src/runtime/reply-orchestrator.js +38 -0
- package/dist/src/runtime/routing-bridge.js +26 -0
- package/dist/src/runtime/session-manager.js +112 -0
- package/dist/src/runtime/source-registry.js +174 -0
- package/dist/src/runtime.js +1 -0
- package/dist/src/shared/command-auth.js +57 -0
- package/{src/shared/index.ts → dist/src/shared/index.js} +0 -1
- package/dist/src/shared/media-asset.js +65 -0
- package/dist/src/shared/media-service.js +59 -0
- package/dist/src/shared/media-types.js +1 -0
- package/{src/shared/xml-parser.ts → dist/src/shared/xml-parser.js} +72 -63
- package/dist/src/store/active-reply-store.js +41 -0
- package/dist/src/store/interfaces.js +1 -0
- package/dist/src/store/memory-store.js +33 -0
- package/dist/src/store/stream-batch-store.js +319 -0
- package/{src/target.ts → dist/src/target.js} +15 -48
- package/dist/src/transport/agent-api/client.js +168 -0
- package/dist/src/transport/agent-api/core.js +337 -0
- package/dist/src/transport/agent-api/delivery.js +28 -0
- package/dist/src/transport/agent-api/media-upload.js +4 -0
- package/dist/src/transport/agent-api/reply.js +24 -0
- package/dist/src/transport/agent-api/upstream-delivery.js +30 -0
- package/dist/src/transport/agent-api/upstream-media-upload.js +46 -0
- package/dist/src/transport/agent-api/upstream-reply.js +26 -0
- package/dist/src/transport/agent-callback/http-handler.js +30 -0
- package/dist/src/transport/agent-callback/inbound.js +4 -0
- package/dist/src/transport/agent-callback/reply.js +8 -0
- package/dist/src/transport/agent-callback/request-handler.js +189 -0
- package/dist/src/transport/agent-callback/session.js +15 -0
- package/dist/src/transport/bot-webhook/active-reply.js +27 -0
- package/dist/src/transport/bot-webhook/http-handler.js +31 -0
- package/dist/src/transport/bot-webhook/inbound-normalizer.js +496 -0
- package/dist/src/transport/bot-webhook/inbound.js +4 -0
- package/dist/src/transport/bot-webhook/message-shape.js +98 -0
- package/dist/src/transport/bot-webhook/protocol.js +124 -0
- package/dist/src/transport/bot-webhook/reply.js +9 -0
- package/dist/src/transport/bot-webhook/request-handler.js +285 -0
- package/dist/src/transport/bot-webhook/session.js +15 -0
- package/dist/src/transport/bot-ws/inbound.js +147 -0
- package/dist/src/transport/bot-ws/media.js +236 -0
- package/dist/src/transport/bot-ws/reply.js +310 -0
- package/dist/src/transport/bot-ws/sdk-adapter.js +257 -0
- package/dist/src/transport/bot-ws/session.js +15 -0
- package/dist/src/transport/http/common.js +78 -0
- package/dist/src/transport/http/registry.js +71 -0
- package/dist/src/transport/http/request-handler.js +51 -0
- package/{src/transport/index.ts → dist/src/transport/index.js} +2 -10
- package/dist/src/types/account.js +1 -0
- package/dist/src/types/config.js +1 -0
- package/dist/src/types/constants.js +28 -0
- package/dist/src/types/events.js +1 -0
- package/dist/src/types/index.js +1 -0
- package/dist/src/types/legacy-stream.js +1 -0
- package/dist/src/types/message.js +5 -0
- package/dist/src/types/runtime-context.js +1 -0
- package/dist/src/types/runtime.js +1 -0
- package/dist/src/types.js +1 -0
- package/dist/src/upstream/index.js +111 -0
- package/dist/src/wecom_msg_adapter/markdown_adapter.js +280 -0
- package/openclaw.plugin.json +15 -0
- package/package.json +18 -1
- package/.github/workflows/release.yml +0 -143
- package/GOVERNANCE.md +0 -26
- package/MENU_EVENT_CONF.md +0 -500
- package/MENU_EVENT_PLAN.md +0 -440
- package/SKILLS_CAL.md +0 -895
- package/SKILLS_DOC.md +0 -2288
- package/UPSTREAM_CONFIG.md +0 -170
- package/UPSTREAM_PLAN.md +0 -175
- package/assets/01.bot-add.png +0 -0
- package/assets/01.bot-setp2.png +0 -0
- package/assets/01.image.jpg +0 -0
- package/assets/02.agent.add.png +0 -0
- package/assets/02.agent.api-set.png +0 -0
- package/assets/02.image.jpg +0 -0
- package/assets/03.agent.page.png +0 -0
- package/assets/03.bot.page.png +0 -0
- package/assets/link-me.jpg +0 -0
- package/assets/register.png +0 -0
- package/changelog/v2.2.28.md +0 -70
- package/changelog/v2.3.10.md +0 -17
- package/changelog/v2.3.11.md +0 -19
- package/changelog/v2.3.12.md +0 -25
- package/changelog/v2.3.13.md +0 -19
- package/changelog/v2.3.14.md +0 -48
- package/changelog/v2.3.15.md +0 -15
- package/changelog/v2.3.16.md +0 -11
- package/changelog/v2.3.18.md +0 -22
- package/changelog/v2.3.19.md +0 -73
- package/changelog/v2.3.2.md +0 -28
- package/changelog/v2.3.26.md +0 -21
- package/changelog/v2.3.27.md +0 -33
- package/changelog/v2.3.4.md +0 -20
- package/changelog/v2.3.9.md +0 -22
- package/changelog/v2.4.12.md +0 -37
- package/compat-single-account.md +0 -148
- package/index.test.ts +0 -38
- package/scripts/test-proxy.ts +0 -70
- package/scripts/wecom/README.md +0 -123
- package/scripts/wecom/menu-click-help.js +0 -59
- package/scripts/wecom/menu-click-help.py +0 -55
- package/src/accounts.ts +0 -34
- package/src/agent/api-client.upload.test.ts +0 -109
- package/src/agent/event-router.test.ts +0 -421
- package/src/agent/event-router.ts +0 -272
- package/src/agent/handler.event-filter.test.ts +0 -135
- package/src/agent/handler.ts +0 -1250
- package/src/agent/index.ts +0 -12
- package/src/agent/script-runner.ts +0 -186
- package/src/agent/test-fixtures/invalid-json-script.mjs +0 -1
- package/src/agent/test-fixtures/reply-event-script.mjs +0 -29
- package/src/agent/test-fixtures/reply-event-script.py +0 -17
- package/src/app/account-runtime.ts +0 -276
- package/src/app/bootstrap.ts +0 -29
- package/src/app/index.ts +0 -192
- package/src/capability/agent/delivery-service.ts +0 -87
- package/src/capability/agent/fallback-policy.ts +0 -13
- package/src/capability/agent/ingress-service.ts +0 -38
- package/src/capability/agent/upstream-delivery-service.ts +0 -96
- package/src/capability/bot/dispatch-config.ts +0 -47
- package/src/capability/bot/fallback-delivery.ts +0 -178
- package/src/capability/bot/local-path-delivery.ts +0 -215
- package/src/capability/bot/sandbox-media.test.ts +0 -221
- package/src/capability/bot/sandbox-media.ts +0 -176
- package/src/capability/bot/service.ts +0 -56
- package/src/capability/bot/stream-delivery.ts +0 -379
- package/src/capability/bot/stream-finalizer.ts +0 -120
- package/src/capability/bot/stream-orchestrator.ts +0 -371
- package/src/capability/bot/types.ts +0 -8
- package/src/capability/calendar/SKILLS_CHECKLIST.md +0 -251
- package/src/capability/calendar/tool.ts +0 -417
- package/src/capability/calendar/types.ts +0 -309
- package/src/capability/doc/tool.ts +0 -1629
- package/src/capability/doc/types.ts +0 -792
- package/src/capability/mcp/index.ts +0 -10
- package/src/capability/mcp/schema.ts +0 -107
- package/src/capability/mcp/tool.ts +0 -174
- package/src/capability/mcp/transport.ts +0 -394
- package/src/channel.config.test.ts +0 -180
- package/src/channel.lifecycle.test.ts +0 -255
- package/src/channel.meta.test.ts +0 -26
- package/src/channel.ts +0 -256
- package/src/config/accounts.resolve.test.ts +0 -75
- package/src/config/accounts.ts +0 -312
- package/src/config/derived-paths.test.ts +0 -111
- package/src/config/derived-paths.ts +0 -41
- package/src/config/index.ts +0 -22
- package/src/config/media.test.ts +0 -113
- package/src/config/media.ts +0 -139
- package/src/config/network.ts +0 -20
- package/src/config/routing.test.ts +0 -88
- package/src/config/routing.ts +0 -26
- package/src/config/runtime-config.ts +0 -46
- package/src/config/schema.ts +0 -144
- package/src/context-store.ts +0 -297
- package/src/crypto/index.ts +0 -24
- package/src/crypto.test.ts +0 -32
- package/src/crypto.ts +0 -176
- package/src/domain/models.ts +0 -7
- package/src/domain/policies.ts +0 -36
- package/src/dynamic-agent.account-scope.test.ts +0 -17
- package/src/gateway-monitor.ts +0 -181
- package/src/http.ts +0 -137
- package/src/media.test.ts +0 -82
- package/src/monitor/limits.ts +0 -7
- package/src/monitor/state.queue.test.ts +0 -185
- package/src/monitor/state.ts +0 -34
- package/src/monitor.active.test.ts +0 -245
- package/src/monitor.inbound-filter.test.ts +0 -63
- package/src/monitor.integration.test.ts +0 -208
- package/src/monitor.ts +0 -121
- package/src/monitor.webhook.test.ts +0 -774
- package/src/observability/audit-log.ts +0 -48
- package/src/observability/legacy-operational-event-store.ts +0 -36
- package/src/observability/raw-envelope-log.ts +0 -28
- package/src/observability/status-registry.ts +0 -13
- package/src/observability/transport-session-view.ts +0 -14
- package/src/onboarding.test.ts +0 -336
- package/src/onboarding.ts +0 -704
- package/src/outbound.test.ts +0 -1271
- package/src/outbound.ts +0 -746
- package/src/runtime/dispatcher.ts +0 -71
- package/src/runtime/outbound-intent.ts +0 -4
- package/src/runtime/reply-orchestrator.test.ts +0 -71
- package/src/runtime/reply-orchestrator.ts +0 -67
- package/src/runtime/routing-bridge.test.ts +0 -115
- package/src/runtime/routing-bridge.ts +0 -44
- package/src/runtime/session-manager.test.ts +0 -174
- package/src/runtime/session-manager.ts +0 -139
- package/src/runtime/source-registry.ts +0 -249
- package/src/runtime.ts +0 -14
- package/src/shared/command-auth.ts +0 -87
- package/src/shared/media-asset.ts +0 -78
- package/src/shared/media-service.test.ts +0 -111
- package/src/shared/media-service.ts +0 -84
- package/src/shared/media-types.ts +0 -5
- package/src/shared/xml-parser.test.ts +0 -50
- package/src/store/active-reply-store.ts +0 -42
- package/src/store/interfaces.ts +0 -11
- package/src/store/memory-store.ts +0 -43
- package/src/store/stream-batch-store.ts +0 -350
- package/src/transport/agent-api/client.ts +0 -277
- package/src/transport/agent-api/core.ts +0 -463
- package/src/transport/agent-api/delivery.ts +0 -41
- package/src/transport/agent-api/media-upload.ts +0 -11
- package/src/transport/agent-api/reply.ts +0 -39
- package/src/transport/agent-api/upstream-delivery.ts +0 -45
- package/src/transport/agent-api/upstream-media-upload.ts +0 -70
- package/src/transport/agent-api/upstream-reply.ts +0 -43
- package/src/transport/agent-callback/http-handler.ts +0 -47
- package/src/transport/agent-callback/inbound.ts +0 -5
- package/src/transport/agent-callback/reply.ts +0 -13
- package/src/transport/agent-callback/request-handler.ts +0 -244
- package/src/transport/agent-callback/session.ts +0 -23
- package/src/transport/bot-webhook/active-reply.ts +0 -39
- package/src/transport/bot-webhook/http-handler.ts +0 -48
- package/src/transport/bot-webhook/inbound-normalizer.ts +0 -371
- package/src/transport/bot-webhook/inbound.ts +0 -5
- package/src/transport/bot-webhook/message-shape.ts +0 -89
- package/src/transport/bot-webhook/protocol.ts +0 -148
- package/src/transport/bot-webhook/reply.ts +0 -15
- package/src/transport/bot-webhook/request-handler.ts +0 -394
- package/src/transport/bot-webhook/session.ts +0 -23
- package/src/transport/bot-ws/inbound.test.ts +0 -96
- package/src/transport/bot-ws/inbound.ts +0 -116
- package/src/transport/bot-ws/media.test.ts +0 -44
- package/src/transport/bot-ws/media.ts +0 -321
- package/src/transport/bot-ws/reply.test.ts +0 -450
- package/src/transport/bot-ws/reply.ts +0 -365
- package/src/transport/bot-ws/sdk-adapter.test.ts +0 -187
- package/src/transport/bot-ws/sdk-adapter.ts +0 -314
- package/src/transport/bot-ws/session.ts +0 -28
- package/src/transport/http/common.ts +0 -109
- package/src/transport/http/registry.ts +0 -92
- package/src/transport/http/request-handler.ts +0 -84
- package/src/types/account.ts +0 -72
- package/src/types/config.ts +0 -166
- package/src/types/constants.ts +0 -31
- package/src/types/events.ts +0 -21
- package/src/types/global.d.ts +0 -9
- package/src/types/index.ts +0 -17
- package/src/types/legacy-stream.ts +0 -50
- package/src/types/message.ts +0 -187
- package/src/types/runtime-context.ts +0 -28
- package/src/types/runtime.ts +0 -165
- package/src/types.ts +0 -41
- package/src/upstream/index.ts +0 -150
- package/src/upstream.test.ts +0 -84
- package/src/wecom_msg_adapter/markdown_adapter.ts +0 -331
- package/tsconfig.json +0 -22
- package/vitest.config.ts +0 -26
- /package/{src/capability/agent/index.ts → dist/src/capability/agent/index.js} +0 -0
- /package/{src/capability/bot/index.ts → dist/src/capability/bot/index.js} +0 -0
- /package/{src/capability/calendar/index.ts → dist/src/capability/calendar/index.js} +0 -0
- /package/{src/capability/index.ts → dist/src/capability/index.js} +0 -0
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
export type WecomSourcePlane = "bot-ws" | "agent-callback";
|
|
2
|
-
|
|
3
|
-
export type WecomSourceSnapshot = {
|
|
4
|
-
accountId: string;
|
|
5
|
-
source: WecomSourcePlane;
|
|
6
|
-
recordedAt: number;
|
|
7
|
-
messageId?: string;
|
|
8
|
-
sessionKey?: string;
|
|
9
|
-
sessionId?: string;
|
|
10
|
-
peerKind?: "direct" | "group";
|
|
11
|
-
peerId?: string;
|
|
12
|
-
upstreamCorpId?: string;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const MAX_MESSAGE_FACTS = 2048;
|
|
16
|
-
const MAX_SESSION_SNAPSHOTS = 1024;
|
|
17
|
-
const MAX_CONVERSATION_SNAPSHOTS = 1024;
|
|
18
|
-
|
|
19
|
-
const messageFacts = new Map<string, WecomSourceSnapshot>();
|
|
20
|
-
const sessionSnapshotsByAccountKey = new Map<string, WecomSourceSnapshot>();
|
|
21
|
-
const sessionSnapshotsByLooseKey = new Map<string, WecomSourceSnapshot>();
|
|
22
|
-
const conversationSnapshotsByAccountKey = new Map<string, WecomSourceSnapshot>();
|
|
23
|
-
const conversationSnapshotsByLooseKey = new Map<string, WecomSourceSnapshot>();
|
|
24
|
-
|
|
25
|
-
function normalizeOptional(value: string | null | undefined): string | undefined {
|
|
26
|
-
const trimmed = String(value ?? "").trim();
|
|
27
|
-
return trimmed || undefined;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function messageFactKey(accountId: string, messageId: string): string {
|
|
31
|
-
return `${accountId}::${messageId}`;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function accountScopedSessionKey(
|
|
35
|
-
accountId: string,
|
|
36
|
-
kind: "sessionKey" | "sessionId",
|
|
37
|
-
value: string,
|
|
38
|
-
): string {
|
|
39
|
-
return `${accountId}::${kind}::${value}`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function normalizePeerId(value: string | null | undefined): string | undefined {
|
|
43
|
-
const trimmed = String(value ?? "").trim();
|
|
44
|
-
return trimmed ? trimmed.toLowerCase() : undefined;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function normalizePeerKind(value: string | null | undefined): "direct" | "group" | undefined {
|
|
48
|
-
const trimmed = String(value ?? "").trim().toLowerCase();
|
|
49
|
-
return trimmed === "direct" || trimmed === "group" ? trimmed : undefined;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function accountScopedConversationKey(
|
|
53
|
-
accountId: string,
|
|
54
|
-
peerKind: "direct" | "group",
|
|
55
|
-
peerId: string,
|
|
56
|
-
): string {
|
|
57
|
-
return `${accountId}::peer::${peerKind}::${peerId}`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function pruneOldest<T>(map: Map<string, T>, maxSize: number): void {
|
|
61
|
-
while (map.size > maxSize) {
|
|
62
|
-
const oldestKey = map.keys().next().value;
|
|
63
|
-
if (!oldestKey) return;
|
|
64
|
-
map.delete(oldestKey);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function writeSessionSnapshot(snapshot: WecomSourceSnapshot): void {
|
|
69
|
-
const sessionKey = normalizeOptional(snapshot.sessionKey);
|
|
70
|
-
const sessionId = normalizeOptional(snapshot.sessionId);
|
|
71
|
-
if (sessionKey) {
|
|
72
|
-
sessionSnapshotsByAccountKey.set(
|
|
73
|
-
accountScopedSessionKey(snapshot.accountId, "sessionKey", sessionKey),
|
|
74
|
-
snapshot,
|
|
75
|
-
);
|
|
76
|
-
sessionSnapshotsByLooseKey.set(`sessionKey::${sessionKey}`, snapshot);
|
|
77
|
-
}
|
|
78
|
-
if (sessionId) {
|
|
79
|
-
sessionSnapshotsByAccountKey.set(
|
|
80
|
-
accountScopedSessionKey(snapshot.accountId, "sessionId", sessionId),
|
|
81
|
-
snapshot,
|
|
82
|
-
);
|
|
83
|
-
sessionSnapshotsByLooseKey.set(`sessionId::${sessionId}`, snapshot);
|
|
84
|
-
}
|
|
85
|
-
pruneOldest(sessionSnapshotsByAccountKey, MAX_SESSION_SNAPSHOTS * 2);
|
|
86
|
-
pruneOldest(sessionSnapshotsByLooseKey, MAX_SESSION_SNAPSHOTS * 2);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function writeConversationSnapshot(snapshot: WecomSourceSnapshot): void {
|
|
90
|
-
const peerKind = normalizePeerKind(snapshot.peerKind);
|
|
91
|
-
const peerId = normalizePeerId(snapshot.peerId);
|
|
92
|
-
if (!peerKind || !peerId) {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
conversationSnapshotsByAccountKey.set(
|
|
96
|
-
accountScopedConversationKey(snapshot.accountId, peerKind, peerId),
|
|
97
|
-
{
|
|
98
|
-
...snapshot,
|
|
99
|
-
peerKind,
|
|
100
|
-
peerId,
|
|
101
|
-
},
|
|
102
|
-
);
|
|
103
|
-
conversationSnapshotsByLooseKey.set(`peer::${peerKind}::${peerId}`, {
|
|
104
|
-
...snapshot,
|
|
105
|
-
peerKind,
|
|
106
|
-
peerId,
|
|
107
|
-
});
|
|
108
|
-
pruneOldest(conversationSnapshotsByAccountKey, MAX_CONVERSATION_SNAPSHOTS);
|
|
109
|
-
pruneOldest(conversationSnapshotsByLooseKey, MAX_CONVERSATION_SNAPSHOTS);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function registerWecomSourceSnapshot(params: {
|
|
113
|
-
accountId: string;
|
|
114
|
-
source: WecomSourcePlane;
|
|
115
|
-
messageId?: string | null;
|
|
116
|
-
sessionKey?: string | null;
|
|
117
|
-
sessionId?: string | null;
|
|
118
|
-
peerKind?: "direct" | "group" | null;
|
|
119
|
-
peerId?: string | null;
|
|
120
|
-
upstreamCorpId?: string | null;
|
|
121
|
-
}): void {
|
|
122
|
-
const accountId = normalizeOptional(params.accountId);
|
|
123
|
-
if (!accountId) return;
|
|
124
|
-
|
|
125
|
-
const snapshot: WecomSourceSnapshot = {
|
|
126
|
-
accountId,
|
|
127
|
-
source: params.source,
|
|
128
|
-
recordedAt: Date.now(),
|
|
129
|
-
...(normalizeOptional(params.messageId)
|
|
130
|
-
? { messageId: normalizeOptional(params.messageId) }
|
|
131
|
-
: {}),
|
|
132
|
-
...(normalizeOptional(params.sessionKey)
|
|
133
|
-
? { sessionKey: normalizeOptional(params.sessionKey) }
|
|
134
|
-
: {}),
|
|
135
|
-
...(normalizeOptional(params.sessionId)
|
|
136
|
-
? { sessionId: normalizeOptional(params.sessionId) }
|
|
137
|
-
: {}),
|
|
138
|
-
...(normalizePeerKind(params.peerKind) ? { peerKind: normalizePeerKind(params.peerKind) } : {}),
|
|
139
|
-
...(normalizePeerId(params.peerId) ? { peerId: normalizePeerId(params.peerId) } : {}),
|
|
140
|
-
...(normalizeOptional(params.upstreamCorpId)
|
|
141
|
-
? { upstreamCorpId: normalizeOptional(params.upstreamCorpId) }
|
|
142
|
-
: {}),
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
if (snapshot.messageId) {
|
|
146
|
-
messageFacts.set(messageFactKey(accountId, snapshot.messageId), snapshot);
|
|
147
|
-
pruneOldest(messageFacts, MAX_MESSAGE_FACTS);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
writeSessionSnapshot(snapshot);
|
|
151
|
-
writeConversationSnapshot(snapshot);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function resolveWecomSourceSnapshot(params: {
|
|
155
|
-
accountId?: string | null;
|
|
156
|
-
sessionKey?: string | null;
|
|
157
|
-
sessionId?: string | null;
|
|
158
|
-
peerKind?: "direct" | "group" | null;
|
|
159
|
-
peerId?: string | null;
|
|
160
|
-
}): WecomSourceSnapshot | undefined {
|
|
161
|
-
const accountId = normalizeOptional(params.accountId);
|
|
162
|
-
const sessionKey = normalizeOptional(params.sessionKey);
|
|
163
|
-
const sessionId = normalizeOptional(params.sessionId);
|
|
164
|
-
const peerKind = normalizePeerKind(params.peerKind);
|
|
165
|
-
const peerId = normalizePeerId(params.peerId);
|
|
166
|
-
|
|
167
|
-
if (accountId && sessionKey) {
|
|
168
|
-
const scoped = sessionSnapshotsByAccountKey.get(
|
|
169
|
-
accountScopedSessionKey(accountId, "sessionKey", sessionKey),
|
|
170
|
-
);
|
|
171
|
-
if (scoped) return scoped;
|
|
172
|
-
}
|
|
173
|
-
if (accountId && sessionId) {
|
|
174
|
-
const scoped = sessionSnapshotsByAccountKey.get(
|
|
175
|
-
accountScopedSessionKey(accountId, "sessionId", sessionId),
|
|
176
|
-
);
|
|
177
|
-
if (scoped) return scoped;
|
|
178
|
-
}
|
|
179
|
-
if (sessionKey) {
|
|
180
|
-
const loose = sessionSnapshotsByLooseKey.get(`sessionKey::${sessionKey}`);
|
|
181
|
-
if (loose) return loose;
|
|
182
|
-
}
|
|
183
|
-
if (sessionId) {
|
|
184
|
-
const loose = sessionSnapshotsByLooseKey.get(`sessionId::${sessionId}`);
|
|
185
|
-
if (loose) return loose;
|
|
186
|
-
}
|
|
187
|
-
if (accountId && peerKind && peerId) {
|
|
188
|
-
const scoped = conversationSnapshotsByAccountKey.get(
|
|
189
|
-
accountScopedConversationKey(accountId, peerKind, peerId),
|
|
190
|
-
);
|
|
191
|
-
if (scoped) return scoped;
|
|
192
|
-
}
|
|
193
|
-
if (peerKind && peerId) {
|
|
194
|
-
const loose = conversationSnapshotsByLooseKey.get(`peer::${peerKind}::${peerId}`);
|
|
195
|
-
if (loose) return loose;
|
|
196
|
-
}
|
|
197
|
-
return undefined;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export function clearWecomSourceAccount(accountId: string): void {
|
|
201
|
-
const normalized = normalizeOptional(accountId);
|
|
202
|
-
if (!normalized) return;
|
|
203
|
-
|
|
204
|
-
for (const [key, value] of messageFacts) {
|
|
205
|
-
if (value.accountId === normalized || key.startsWith(`${normalized}::`)) {
|
|
206
|
-
messageFacts.delete(key);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
for (const [key, value] of sessionSnapshotsByAccountKey) {
|
|
210
|
-
if (value.accountId === normalized || key.startsWith(`${normalized}::`)) {
|
|
211
|
-
sessionSnapshotsByAccountKey.delete(key);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
for (const [key, value] of sessionSnapshotsByLooseKey) {
|
|
215
|
-
if (value.accountId === normalized) {
|
|
216
|
-
sessionSnapshotsByLooseKey.delete(key);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
for (const [key, value] of conversationSnapshotsByAccountKey) {
|
|
220
|
-
if (value.accountId === normalized || key.startsWith(`${normalized}::`)) {
|
|
221
|
-
conversationSnapshotsByAccountKey.delete(key);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
for (const [key, value] of conversationSnapshotsByLooseKey) {
|
|
225
|
-
if (value.accountId === normalized) {
|
|
226
|
-
conversationSnapshotsByLooseKey.delete(key);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export function isWecomBotWsSource(params: {
|
|
232
|
-
accountId?: string | null;
|
|
233
|
-
sessionKey?: string | null;
|
|
234
|
-
sessionId?: string | null;
|
|
235
|
-
peerKind?: "direct" | "group" | null;
|
|
236
|
-
peerId?: string | null;
|
|
237
|
-
}): boolean {
|
|
238
|
-
return resolveWecomSourceSnapshot(params)?.source === "bot-ws";
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
export function isWecomAgentSource(params: {
|
|
242
|
-
accountId?: string | null;
|
|
243
|
-
sessionKey?: string | null;
|
|
244
|
-
sessionId?: string | null;
|
|
245
|
-
peerKind?: "direct" | "group" | null;
|
|
246
|
-
peerId?: string | null;
|
|
247
|
-
}): boolean {
|
|
248
|
-
return resolveWecomSourceSnapshot(params)?.source === "agent-callback";
|
|
249
|
-
}
|
package/src/runtime.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
getActiveBotWsReplyHandle,
|
|
3
|
-
getAccountRuntime,
|
|
4
|
-
getAccountRuntimeSnapshot,
|
|
5
|
-
getBotWsPushHandle,
|
|
6
|
-
getWecomRuntime,
|
|
7
|
-
registerActiveBotWsReplyHandle,
|
|
8
|
-
registerAccountRuntime,
|
|
9
|
-
registerBotWsPushHandle,
|
|
10
|
-
setWecomRuntime,
|
|
11
|
-
unregisterActiveBotWsReplyHandle,
|
|
12
|
-
unregisterBotWsPushHandle,
|
|
13
|
-
unregisterAccountRuntime,
|
|
14
|
-
} from "./app/index.js";
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk";
|
|
2
|
-
|
|
3
|
-
import type { WecomAgentConfig, WecomBotConfig } from "../types/index.js";
|
|
4
|
-
import { isWecomSenderAllowed, normalizeWecomAllowFromEntry } from "../domain/policies.js";
|
|
5
|
-
|
|
6
|
-
type WecomCommandAuthAccountConfig = Pick<WecomBotConfig, "dm"> | Pick<WecomAgentConfig, "dm">;
|
|
7
|
-
|
|
8
|
-
export async function resolveWecomCommandAuthorization(params: {
|
|
9
|
-
core: PluginRuntime;
|
|
10
|
-
cfg: OpenClawConfig;
|
|
11
|
-
accountConfig: WecomCommandAuthAccountConfig;
|
|
12
|
-
rawBody: string;
|
|
13
|
-
senderUserId: string;
|
|
14
|
-
}): Promise<{
|
|
15
|
-
shouldComputeAuth: boolean;
|
|
16
|
-
dmPolicy: "pairing" | "allowlist" | "open" | "disabled";
|
|
17
|
-
senderAllowed: boolean;
|
|
18
|
-
authorizerConfigured: boolean;
|
|
19
|
-
commandAuthorized: boolean | undefined;
|
|
20
|
-
effectiveAllowFrom: string[];
|
|
21
|
-
}> {
|
|
22
|
-
const { core, cfg, accountConfig, rawBody, senderUserId } = params;
|
|
23
|
-
|
|
24
|
-
const dmPolicy = (accountConfig.dm?.policy ?? "pairing") as "pairing" | "allowlist" | "open" | "disabled";
|
|
25
|
-
const configAllowFrom = (accountConfig.dm?.allowFrom ?? []).map((v) => String(v));
|
|
26
|
-
|
|
27
|
-
const shouldComputeAuth = core.channel.commands.shouldComputeCommandAuthorized(rawBody, cfg);
|
|
28
|
-
// WeCom channel currently does NOT support the `openclaw pairing` CLI workflow
|
|
29
|
-
// ("Channel wecom does not support pairing"). So we must not rely on pairing
|
|
30
|
-
// store approvals for command authorization here.
|
|
31
|
-
//
|
|
32
|
-
// Policy semantics:
|
|
33
|
-
// - open: commands are allowed for everyone by default (unless higher-level access-groups deny).
|
|
34
|
-
// - allowlist: commands require allowFrom entries.
|
|
35
|
-
// - pairing: treated the same as allowlist for WeCom (since pairing CLI is unsupported).
|
|
36
|
-
const effectiveAllowFrom = dmPolicy === "open" ? ["*"] : configAllowFrom;
|
|
37
|
-
|
|
38
|
-
const senderAllowed = isWecomSenderAllowed(senderUserId, effectiveAllowFrom);
|
|
39
|
-
const allowAllConfigured = effectiveAllowFrom.some((entry) => normalizeWecomAllowFromEntry(entry) === "*");
|
|
40
|
-
const authorizerConfigured = allowAllConfigured || effectiveAllowFrom.length > 0;
|
|
41
|
-
const useAccessGroups = cfg.commands?.useAccessGroups !== false;
|
|
42
|
-
|
|
43
|
-
const commandAuthorized = shouldComputeAuth
|
|
44
|
-
? core.channel.commands.resolveCommandAuthorizedFromAuthorizers({
|
|
45
|
-
useAccessGroups,
|
|
46
|
-
authorizers: [{ configured: authorizerConfigured, allowed: senderAllowed }],
|
|
47
|
-
})
|
|
48
|
-
: undefined;
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
shouldComputeAuth,
|
|
52
|
-
dmPolicy,
|
|
53
|
-
senderAllowed,
|
|
54
|
-
authorizerConfigured,
|
|
55
|
-
commandAuthorized,
|
|
56
|
-
effectiveAllowFrom,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function buildWecomUnauthorizedCommandPrompt(params: {
|
|
61
|
-
senderUserId: string;
|
|
62
|
-
dmPolicy: "pairing" | "allowlist" | "open" | "disabled";
|
|
63
|
-
scope: "bot" | "agent";
|
|
64
|
-
}): string {
|
|
65
|
-
const user = params.senderUserId || "unknown";
|
|
66
|
-
const policy = params.dmPolicy;
|
|
67
|
-
const scopeLabel = params.scope === "bot" ? "Bot(智能机器人)" : "Agent(自建应用)";
|
|
68
|
-
const dmPrefix = params.scope === "bot" ? "channels.wecom.bot.dm" : "channels.wecom.agent.dm";
|
|
69
|
-
const allowCmd = (value: string) => `openclaw config set ${dmPrefix}.allowFrom '${value}'`;
|
|
70
|
-
const policyCmd = (value: string) => `openclaw config set ${dmPrefix}.policy "${value}"`;
|
|
71
|
-
|
|
72
|
-
if (policy === "disabled") {
|
|
73
|
-
return [
|
|
74
|
-
`无权限执行命令(${scopeLabel} 已禁用:dm.policy=disabled)`,
|
|
75
|
-
`触发者:${user}`,
|
|
76
|
-
`管理员:${policyCmd("open")}(全放开)或 ${policyCmd("allowlist")}(白名单)`,
|
|
77
|
-
].join("\n");
|
|
78
|
-
}
|
|
79
|
-
// WeCom 不支持 pairing CLI,因此这里统一给出“open / allowlist”两种明确的配置指令
|
|
80
|
-
return [
|
|
81
|
-
`无权限执行命令(入口:${scopeLabel},userid:${user})`,
|
|
82
|
-
`管理员全放开:${policyCmd("open")}`,
|
|
83
|
-
`管理员放行该用户:${policyCmd("allowlist")}`,
|
|
84
|
-
`然后设置白名单:${allowCmd(JSON.stringify([user]))}`,
|
|
85
|
-
`如果仍被拦截:检查 commands.useAccessGroups/访问组`,
|
|
86
|
-
].join("\n");
|
|
87
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
|
|
3
|
-
import { resolveWecomEgressProxyUrlFromNetwork } from "../config/index.js";
|
|
4
|
-
import { wecomFetch } from "../http.js";
|
|
5
|
-
import type { WecomNetworkConfig } from "../types/index.js";
|
|
6
|
-
|
|
7
|
-
function inferContentTypeFromFilePath(filePath: string): string {
|
|
8
|
-
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
9
|
-
const mimeTypes: Record<string, string> = {
|
|
10
|
-
jpg: "image/jpeg",
|
|
11
|
-
jpeg: "image/jpeg",
|
|
12
|
-
png: "image/png",
|
|
13
|
-
gif: "image/gif",
|
|
14
|
-
webp: "image/webp",
|
|
15
|
-
bmp: "image/bmp",
|
|
16
|
-
mp3: "audio/mpeg",
|
|
17
|
-
wav: "audio/wav",
|
|
18
|
-
amr: "audio/amr",
|
|
19
|
-
mp4: "video/mp4",
|
|
20
|
-
pdf: "application/pdf",
|
|
21
|
-
doc: "application/msword",
|
|
22
|
-
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
23
|
-
xls: "application/vnd.ms-excel",
|
|
24
|
-
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
25
|
-
ppt: "application/vnd.ms-powerpoint",
|
|
26
|
-
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
27
|
-
txt: "text/plain",
|
|
28
|
-
csv: "text/csv",
|
|
29
|
-
tsv: "text/tab-separated-values",
|
|
30
|
-
md: "text/markdown",
|
|
31
|
-
json: "application/json",
|
|
32
|
-
xml: "application/xml",
|
|
33
|
-
yaml: "application/yaml",
|
|
34
|
-
yml: "application/yaml",
|
|
35
|
-
zip: "application/zip",
|
|
36
|
-
rar: "application/vnd.rar",
|
|
37
|
-
"7z": "application/x-7z-compressed",
|
|
38
|
-
tar: "application/x-tar",
|
|
39
|
-
gz: "application/gzip",
|
|
40
|
-
tgz: "application/gzip",
|
|
41
|
-
rtf: "application/rtf",
|
|
42
|
-
odt: "application/vnd.oasis.opendocument.text",
|
|
43
|
-
};
|
|
44
|
-
return mimeTypes[ext] || "application/octet-stream";
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export async function resolveOutboundMediaAsset(params: {
|
|
48
|
-
mediaUrl: string;
|
|
49
|
-
network?: WecomNetworkConfig;
|
|
50
|
-
timeoutMs?: number;
|
|
51
|
-
}): Promise<{ buffer: Buffer; filename: string; contentType: string }> {
|
|
52
|
-
const { mediaUrl, network, timeoutMs = 30000 } = params;
|
|
53
|
-
if (/^https?:\/\//i.test(mediaUrl)) {
|
|
54
|
-
const response = await wecomFetch(
|
|
55
|
-
mediaUrl,
|
|
56
|
-
{ method: "GET" },
|
|
57
|
-
{
|
|
58
|
-
proxyUrl: resolveWecomEgressProxyUrlFromNetwork(network),
|
|
59
|
-
timeoutMs,
|
|
60
|
-
},
|
|
61
|
-
);
|
|
62
|
-
if (!response.ok) {
|
|
63
|
-
throw new Error(`Failed to download media: ${response.status}`);
|
|
64
|
-
}
|
|
65
|
-
const buffer = Buffer.from(await response.arrayBuffer());
|
|
66
|
-
const contentType = response.headers.get("content-type") || "application/octet-stream";
|
|
67
|
-
const filename = path.basename(new URL(mediaUrl).pathname) || "media";
|
|
68
|
-
return { buffer, filename, contentType };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const fs = await import("node:fs/promises");
|
|
72
|
-
const buffer = await fs.readFile(mediaUrl);
|
|
73
|
-
return {
|
|
74
|
-
buffer,
|
|
75
|
-
filename: path.basename(mediaUrl),
|
|
76
|
-
contentType: inferContentTypeFromFilePath(mediaUrl),
|
|
77
|
-
};
|
|
78
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { WecomMediaService } from "./media-service.js";
|
|
3
|
-
|
|
4
|
-
describe("WecomMediaService", () => {
|
|
5
|
-
const fetchRemoteMedia = vi.fn();
|
|
6
|
-
const saveMediaBuffer = vi.fn();
|
|
7
|
-
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
fetchRemoteMedia.mockReset();
|
|
10
|
-
saveMediaBuffer.mockReset();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it("passes configured wecom mediaMaxMb to remote attachment fetches and saves", async () => {
|
|
14
|
-
const service = new WecomMediaService(
|
|
15
|
-
{
|
|
16
|
-
channel: {
|
|
17
|
-
media: {
|
|
18
|
-
fetchRemoteMedia,
|
|
19
|
-
saveMediaBuffer,
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
} as never,
|
|
23
|
-
{
|
|
24
|
-
channels: {
|
|
25
|
-
wecom: {
|
|
26
|
-
mediaMaxMb: 24,
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
} as never,
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
fetchRemoteMedia.mockResolvedValue({
|
|
33
|
-
buffer: Buffer.from("file"),
|
|
34
|
-
contentType: "application/pdf",
|
|
35
|
-
fileName: "sample.pdf",
|
|
36
|
-
});
|
|
37
|
-
saveMediaBuffer.mockResolvedValue({
|
|
38
|
-
path: "/tmp/sample.pdf",
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const event = {
|
|
42
|
-
accountId: "default",
|
|
43
|
-
attachments: [{ remoteUrl: "https://example.com/sample.pdf" }],
|
|
44
|
-
} as never;
|
|
45
|
-
|
|
46
|
-
const attachment = await service.normalizeFirstAttachment(event);
|
|
47
|
-
|
|
48
|
-
expect(fetchRemoteMedia).toHaveBeenCalledWith({
|
|
49
|
-
url: "https://example.com/sample.pdf",
|
|
50
|
-
maxBytes: 24 * 1024 * 1024,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
await service.saveInboundAttachment(event, attachment!);
|
|
54
|
-
|
|
55
|
-
expect(saveMediaBuffer).toHaveBeenCalledWith(
|
|
56
|
-
expect.any(Buffer),
|
|
57
|
-
"application/pdf",
|
|
58
|
-
"inbound",
|
|
59
|
-
24 * 1024 * 1024,
|
|
60
|
-
"sample.pdf",
|
|
61
|
-
);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("prefers account-specific mediaMaxMb for inbound saves", async () => {
|
|
65
|
-
const service = new WecomMediaService(
|
|
66
|
-
{
|
|
67
|
-
channel: {
|
|
68
|
-
media: {
|
|
69
|
-
fetchRemoteMedia,
|
|
70
|
-
saveMediaBuffer,
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
} as never,
|
|
74
|
-
{
|
|
75
|
-
channels: {
|
|
76
|
-
wecom: {
|
|
77
|
-
mediaMaxMb: 24,
|
|
78
|
-
accounts: {
|
|
79
|
-
ops: {
|
|
80
|
-
mediaMaxMb: 36,
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
} as never,
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
saveMediaBuffer.mockResolvedValue({
|
|
89
|
-
path: "/tmp/account-specific.pdf",
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
await service.saveInboundAttachment(
|
|
93
|
-
{
|
|
94
|
-
accountId: "ops",
|
|
95
|
-
} as never,
|
|
96
|
-
{
|
|
97
|
-
buffer: Buffer.from("file"),
|
|
98
|
-
contentType: "application/pdf",
|
|
99
|
-
filename: "ops.pdf",
|
|
100
|
-
},
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
expect(saveMediaBuffer).toHaveBeenCalledWith(
|
|
104
|
-
expect.any(Buffer),
|
|
105
|
-
"application/pdf",
|
|
106
|
-
"inbound",
|
|
107
|
-
36 * 1024 * 1024,
|
|
108
|
-
"ops.pdf",
|
|
109
|
-
);
|
|
110
|
-
});
|
|
111
|
-
});
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk";
|
|
2
|
-
import { resolveWecomMediaMaxBytes } from "../config/index.js";
|
|
3
|
-
import { decryptWecomMediaWithMeta } from "../media.js";
|
|
4
|
-
import type { UnifiedInboundEvent } from "../types/index.js";
|
|
5
|
-
import type { NormalizedMediaAttachment } from "./media-types.js";
|
|
6
|
-
|
|
7
|
-
export class WecomMediaService {
|
|
8
|
-
constructor(
|
|
9
|
-
private readonly core: PluginRuntime,
|
|
10
|
-
private readonly cfg: OpenClawConfig,
|
|
11
|
-
) {}
|
|
12
|
-
|
|
13
|
-
private resolveInboundMaxBytes(accountId: string): number {
|
|
14
|
-
return resolveWecomMediaMaxBytes(this.cfg, accountId);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async downloadRemoteMedia(params: {
|
|
18
|
-
url: string;
|
|
19
|
-
maxBytes: number;
|
|
20
|
-
}): Promise<NormalizedMediaAttachment> {
|
|
21
|
-
const loaded = await this.core.channel.media.fetchRemoteMedia({
|
|
22
|
-
url: params.url,
|
|
23
|
-
maxBytes: params.maxBytes,
|
|
24
|
-
});
|
|
25
|
-
return {
|
|
26
|
-
buffer: loaded.buffer,
|
|
27
|
-
contentType: loaded.contentType,
|
|
28
|
-
filename: loaded.fileName,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Download and decrypt WeCom AES-encrypted media.
|
|
34
|
-
* Bot-ws: each message carries a unique per-URL aeskey in the message body.
|
|
35
|
-
* Bot-webhook: uses the account-level EncodingAESKey.
|
|
36
|
-
* Both use AES-256-CBC with PKCS#7 padding (32-byte block), IV = key[:16].
|
|
37
|
-
*/
|
|
38
|
-
async downloadEncryptedMedia(params: {
|
|
39
|
-
url: string;
|
|
40
|
-
aesKey: string;
|
|
41
|
-
maxBytes: number;
|
|
42
|
-
}): Promise<NormalizedMediaAttachment> {
|
|
43
|
-
const decrypted = await decryptWecomMediaWithMeta(params.url, params.aesKey, {
|
|
44
|
-
maxBytes: params.maxBytes,
|
|
45
|
-
});
|
|
46
|
-
return {
|
|
47
|
-
buffer: decrypted.buffer,
|
|
48
|
-
contentType: decrypted.sourceContentType,
|
|
49
|
-
filename: decrypted.sourceFilename,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async saveInboundAttachment(
|
|
54
|
-
event: UnifiedInboundEvent,
|
|
55
|
-
attachment: NormalizedMediaAttachment,
|
|
56
|
-
): Promise<string> {
|
|
57
|
-
const maxBytes = this.resolveInboundMaxBytes(event.accountId);
|
|
58
|
-
const saved = await this.core.channel.media.saveMediaBuffer(
|
|
59
|
-
attachment.buffer,
|
|
60
|
-
attachment.contentType,
|
|
61
|
-
"inbound",
|
|
62
|
-
maxBytes,
|
|
63
|
-
attachment.filename,
|
|
64
|
-
);
|
|
65
|
-
return saved.path;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async normalizeFirstAttachment(
|
|
69
|
-
event: UnifiedInboundEvent,
|
|
70
|
-
): Promise<NormalizedMediaAttachment | undefined> {
|
|
71
|
-
const first = event.attachments?.[0];
|
|
72
|
-
if (!first?.remoteUrl) {
|
|
73
|
-
return undefined;
|
|
74
|
-
}
|
|
75
|
-
// Keep fetch/decrypt/save on the same account-aware limit instead of falling back
|
|
76
|
-
// to the core media store default (5MB).
|
|
77
|
-
const maxBytes = this.resolveInboundMaxBytes(event.accountId);
|
|
78
|
-
// Bot-ws media is AES-encrypted; use decryption when aesKey is present
|
|
79
|
-
if (first.aesKey) {
|
|
80
|
-
return this.downloadEncryptedMedia({ url: first.remoteUrl, aesKey: first.aesKey, maxBytes });
|
|
81
|
-
}
|
|
82
|
-
return this.downloadRemoteMedia({ url: first.remoteUrl, maxBytes });
|
|
83
|
-
}
|
|
84
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { extractContent, extractFromUser, extractMediaId, extractMsgId, parseXml } from "./xml-parser.js";
|
|
4
|
-
|
|
5
|
-
describe("wecom xml-parser", () => {
|
|
6
|
-
test("extractContent is robust to non-string Content", () => {
|
|
7
|
-
const msg: any = { MsgType: "text", Content: { "#text": "hello", "@_foo": "bar" } };
|
|
8
|
-
expect(extractContent(msg)).toBe("hello");
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
test("extractContent handles array content", () => {
|
|
12
|
-
const msg: any = { MsgType: "text", Content: ["a", "b"] };
|
|
13
|
-
expect(extractContent(msg)).toBe("a\nb");
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test("extractContent handles file messages", () => {
|
|
17
|
-
const msg: any = { MsgType: "file", MediaId: "MEDIA123" };
|
|
18
|
-
expect(extractContent(msg)).toBe("[文件消息]");
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test("extractMediaId handles object MediaId", () => {
|
|
22
|
-
const msg: any = { MediaId: { "#text": "MEDIA123", "@_foo": "bar" } };
|
|
23
|
-
expect(extractMediaId(msg)).toBe("MEDIA123");
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("extractMsgId handles number MsgId", () => {
|
|
27
|
-
const msg: any = { MsgId: 123456789 };
|
|
28
|
-
expect(extractMsgId(msg)).toBe("123456789");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("parseXml preserves leading zero userid in FromUserName", () => {
|
|
32
|
-
const xml = `
|
|
33
|
-
<xml>
|
|
34
|
-
<FromUserName><![CDATA[0254571]]></FromUserName>
|
|
35
|
-
</xml>
|
|
36
|
-
`;
|
|
37
|
-
const msg = parseXml(xml);
|
|
38
|
-
expect(extractFromUser(msg)).toBe("0254571");
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test("parseXml preserves 64-bit MsgId as string", () => {
|
|
42
|
-
const xml = `
|
|
43
|
-
<xml>
|
|
44
|
-
<MsgId>1234567890123456</MsgId>
|
|
45
|
-
</xml>
|
|
46
|
-
`;
|
|
47
|
-
const msg = parseXml(xml);
|
|
48
|
-
expect(extractMsgId(msg)).toBe("1234567890123456");
|
|
49
|
-
});
|
|
50
|
-
});
|