gewe-openclaw 2026.1.29 → 2026.1.31
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 +75 -1
- package/index.ts +1 -1
- package/openclaw.plugin.json +2 -2
- package/package.json +7 -6
- package/src/accounts.ts +5 -5
- package/src/channel.ts +48 -29
- package/src/config-schema.ts +4 -2
- package/src/constants.ts +10 -0
- package/src/delivery.ts +19 -16
- package/src/inbound.ts +11 -10
- package/src/normalize.ts +3 -4
- package/src/policy.ts +6 -5
- package/src/types.ts +1 -1
package/README.md
CHANGED
|
@@ -32,6 +32,81 @@ openclaw plugins install ./gewe-openclaw.tgz
|
|
|
32
32
|
|
|
33
33
|
> 安装或启用插件后需要重启 Gateway。
|
|
34
34
|
|
|
35
|
+
## 配置
|
|
36
|
+
|
|
37
|
+
插件配置放在 `~/.openclaw/openclaw.json` 的 `channels.gewe-openclaw`,并确保插件开启:
|
|
38
|
+
|
|
39
|
+
```json5
|
|
40
|
+
{
|
|
41
|
+
"plugins": {
|
|
42
|
+
"entries": {
|
|
43
|
+
"gewe-openclaw": { "enabled": true }
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"channels": {
|
|
47
|
+
"gewe-openclaw": {
|
|
48
|
+
"enabled": true,
|
|
49
|
+
"apiBaseUrl": "https://www.geweapi.com",
|
|
50
|
+
"token": "<gewe-token>",
|
|
51
|
+
"appId": "<gewe-app-id>",
|
|
52
|
+
"webhookHost": "0.0.0.0",
|
|
53
|
+
"webhookPort": 4399,
|
|
54
|
+
"webhookPath": "/webhook",
|
|
55
|
+
"mediaHost": "0.0.0.0",
|
|
56
|
+
"mediaPort": 4400,
|
|
57
|
+
"mediaPath": "/gewe-media",
|
|
58
|
+
"mediaPublicUrl": "https://your-public-domain/gewe-media",
|
|
59
|
+
"allowFrom": ["wxid_xxx"]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
说明:
|
|
66
|
+
- `webhookHost/webhookPort/webhookPath`:GeWe 回调入口(需公网可达,常配合 FRP)。
|
|
67
|
+
- `mediaPublicUrl`:公网访问地址,供微信拉取媒体。
|
|
68
|
+
- `allowFrom`:允许私聊触发的微信 ID(或在群里走 allowlist 规则)。
|
|
69
|
+
|
|
70
|
+
> 配置变更后需重启 Gateway。
|
|
71
|
+
|
|
72
|
+
## 在 onboarding 列表中显示(可选)
|
|
73
|
+
|
|
74
|
+
OpenClaw 支持外部插件目录(catalog)。放置到以下路径即可被 onboarding 读取:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
~/.openclaw/plugins/catalog.json
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
示例(只需添加一次):
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"entries": [
|
|
85
|
+
{
|
|
86
|
+
"name": "gewe-openclaw",
|
|
87
|
+
"openclaw": {
|
|
88
|
+
"channel": {
|
|
89
|
+
"id": "gewe-openclaw",
|
|
90
|
+
"label": "GeWe",
|
|
91
|
+
"selectionLabel": "WeChat (GeWe)",
|
|
92
|
+
"detailLabel": "WeChat (GeWe)",
|
|
93
|
+
"docsPath": "/channels/gewe-openclaw",
|
|
94
|
+
"docsLabel": "gewe-openclaw",
|
|
95
|
+
"blurb": "WeChat channel via GeWe API and webhook callbacks.",
|
|
96
|
+
"aliases": ["gewe-openclaw", "gewe", "wechat", "wx"],
|
|
97
|
+
"order": 72,
|
|
98
|
+
"quickstartAllowFrom": true
|
|
99
|
+
},
|
|
100
|
+
"install": {
|
|
101
|
+
"npmSpec": "gewe-openclaw",
|
|
102
|
+
"defaultChoice": "npm"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
35
110
|
## 依赖
|
|
36
111
|
|
|
37
112
|
### npm 依赖
|
|
@@ -53,4 +128,3 @@ openclaw plugins install ./gewe-openclaw.tgz
|
|
|
53
128
|
- GeWe API 服务
|
|
54
129
|
- Webhook 回调需要公网可达(可配合 FRP)
|
|
55
130
|
- 媒体对外地址(`mediaPublicUrl`)
|
|
56
|
-
|
package/index.ts
CHANGED
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gewe-openclaw",
|
|
3
|
-
"version": "2026.1.
|
|
3
|
+
"version": "2026.1.31",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw GeWe channel plugin",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,17 +9,18 @@
|
|
|
9
9
|
"./index.ts"
|
|
10
10
|
],
|
|
11
11
|
"channel": {
|
|
12
|
-
"id": "gewe",
|
|
12
|
+
"id": "gewe-openclaw",
|
|
13
13
|
"label": "GeWe",
|
|
14
14
|
"selectionLabel": "WeChat (GeWe)",
|
|
15
15
|
"detailLabel": "WeChat (GeWe)",
|
|
16
|
-
"docsPath": "/channels/gewe",
|
|
17
|
-
"docsLabel": "gewe",
|
|
16
|
+
"docsPath": "/channels/gewe-openclaw",
|
|
17
|
+
"docsLabel": "gewe-openclaw",
|
|
18
18
|
"blurb": "WeChat channel via GeWe API and webhook callbacks.",
|
|
19
19
|
"aliases": [
|
|
20
|
+
"gewe-openclaw",
|
|
21
|
+
"gewe",
|
|
20
22
|
"wechat",
|
|
21
|
-
"wx"
|
|
22
|
-
"gewe"
|
|
23
|
+
"wx"
|
|
23
24
|
],
|
|
24
25
|
"order": 72,
|
|
25
26
|
"quickstartAllowFrom": true
|
package/src/accounts.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
|
|
3
3
|
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk";
|
|
4
4
|
|
|
5
|
+
import { CHANNEL_CONFIG_KEY } from "./constants.js";
|
|
5
6
|
import type { CoreConfig, GeweAccountConfig, GeweAppIdSource, GeweTokenSource } from "./types.js";
|
|
6
7
|
|
|
7
8
|
const DEFAULT_API_BASE_URL = "http://api.geweapi.com";
|
|
@@ -18,7 +19,7 @@ export type ResolvedGeweAccount = {
|
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
function listConfiguredAccountIds(cfg: CoreConfig): string[] {
|
|
21
|
-
const accounts = cfg.channels?.
|
|
22
|
+
const accounts = cfg.channels?.[CHANNEL_CONFIG_KEY]?.accounts;
|
|
22
23
|
if (!accounts || typeof accounts !== "object") return [];
|
|
23
24
|
const ids = new Set<string>();
|
|
24
25
|
for (const key of Object.keys(accounts)) {
|
|
@@ -41,7 +42,7 @@ export function resolveDefaultGeweAccountId(cfg: CoreConfig): string {
|
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
function resolveAccountConfig(cfg: CoreConfig, accountId: string): GeweAccountConfig | undefined {
|
|
44
|
-
const accounts = cfg.channels?.
|
|
45
|
+
const accounts = cfg.channels?.[CHANNEL_CONFIG_KEY]?.accounts;
|
|
45
46
|
if (!accounts || typeof accounts !== "object") return undefined;
|
|
46
47
|
const direct = accounts[accountId] as GeweAccountConfig | undefined;
|
|
47
48
|
if (direct) return direct;
|
|
@@ -51,7 +52,7 @@ function resolveAccountConfig(cfg: CoreConfig, accountId: string): GeweAccountCo
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
function mergeGeweAccountConfig(cfg: CoreConfig, accountId: string): GeweAccountConfig {
|
|
54
|
-
const { accounts: _ignored, ...base } = (cfg.channels?.
|
|
55
|
+
const { accounts: _ignored, ...base } = (cfg.channels?.[CHANNEL_CONFIG_KEY] ?? {}) as GeweAccountConfig & {
|
|
55
56
|
accounts?: unknown;
|
|
56
57
|
};
|
|
57
58
|
const account = resolveAccountConfig(cfg, accountId) ?? {};
|
|
@@ -117,7 +118,7 @@ export function resolveGeweAccount(params: {
|
|
|
117
118
|
accountId?: string | null;
|
|
118
119
|
}): ResolvedGeweAccount {
|
|
119
120
|
const hasExplicitAccountId = Boolean(params.accountId?.trim());
|
|
120
|
-
const baseEnabled = params.cfg.channels?.
|
|
121
|
+
const baseEnabled = params.cfg.channels?.[CHANNEL_CONFIG_KEY]?.enabled !== false;
|
|
121
122
|
|
|
122
123
|
const resolve = (accountId: string): ResolvedGeweAccount => {
|
|
123
124
|
const merged = mergeGeweAccountConfig(params.cfg, accountId);
|
|
@@ -161,4 +162,3 @@ export function listEnabledGeweAccounts(cfg: CoreConfig): ResolvedGeweAccount[]
|
|
|
161
162
|
.map((accountId) => resolveGeweAccount({ cfg, accountId }))
|
|
162
163
|
.filter((account) => account.enabled);
|
|
163
164
|
}
|
|
164
|
-
|
package/src/channel.ts
CHANGED
|
@@ -15,6 +15,14 @@ import {
|
|
|
15
15
|
|
|
16
16
|
import { resolveGeweAccount, resolveDefaultGeweAccountId, listGeweAccountIds } from "./accounts.js";
|
|
17
17
|
import { GeweConfigSchema } from "./config-schema.js";
|
|
18
|
+
import {
|
|
19
|
+
CHANNEL_ALIASES,
|
|
20
|
+
CHANNEL_CONFIG_KEY,
|
|
21
|
+
CHANNEL_DOCS_LABEL,
|
|
22
|
+
CHANNEL_DOCS_PATH,
|
|
23
|
+
CHANNEL_ID,
|
|
24
|
+
stripChannelPrefix,
|
|
25
|
+
} from "./constants.js";
|
|
18
26
|
import { deliverGewePayload } from "./delivery.js";
|
|
19
27
|
import { monitorGeweProvider } from "./monitor.js";
|
|
20
28
|
import { looksLikeGeweTargetId, normalizeGeweMessagingTarget } from "./normalize.js";
|
|
@@ -24,14 +32,14 @@ import { sendTextGewe } from "./send.js";
|
|
|
24
32
|
import type { CoreConfig, ResolvedGeweAccount } from "./types.js";
|
|
25
33
|
|
|
26
34
|
const meta = {
|
|
27
|
-
id:
|
|
35
|
+
id: CHANNEL_ID,
|
|
28
36
|
label: "GeWe",
|
|
29
37
|
selectionLabel: "WeChat (GeWe)",
|
|
30
38
|
detailLabel: "WeChat (GeWe)",
|
|
31
|
-
docsPath:
|
|
32
|
-
docsLabel:
|
|
39
|
+
docsPath: CHANNEL_DOCS_PATH,
|
|
40
|
+
docsLabel: CHANNEL_DOCS_LABEL,
|
|
33
41
|
blurb: "WeChat channel via GeWe API and webhook callbacks.",
|
|
34
|
-
aliases: [
|
|
42
|
+
aliases: [...CHANNEL_ALIASES],
|
|
35
43
|
order: 72,
|
|
36
44
|
quickstartAllowFrom: true,
|
|
37
45
|
};
|
|
@@ -45,11 +53,11 @@ type GeweSetupInput = ChannelSetupInput & {
|
|
|
45
53
|
};
|
|
46
54
|
|
|
47
55
|
export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
|
|
48
|
-
id:
|
|
56
|
+
id: CHANNEL_ID,
|
|
49
57
|
meta,
|
|
50
58
|
pairing: {
|
|
51
59
|
idLabel: "wechatUserId",
|
|
52
|
-
normalizeAllowEntry: (entry) => entry
|
|
60
|
+
normalizeAllowEntry: (entry) => stripChannelPrefix(entry),
|
|
53
61
|
notifyApproval: async ({ cfg, id }) => {
|
|
54
62
|
const account = resolveGeweAccount({ cfg: cfg as CoreConfig });
|
|
55
63
|
if (!account.token || !account.appId) {
|
|
@@ -70,7 +78,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
|
|
|
70
78
|
nativeCommands: false,
|
|
71
79
|
blockStreaming: true,
|
|
72
80
|
},
|
|
73
|
-
reload: { configPrefixes: [
|
|
81
|
+
reload: { configPrefixes: [`channels.${CHANNEL_CONFIG_KEY}`] },
|
|
74
82
|
configSchema: buildChannelConfigSchema(GeweConfigSchema),
|
|
75
83
|
config: {
|
|
76
84
|
listAccountIds: (cfg) => listGeweAccountIds(cfg as CoreConfig),
|
|
@@ -79,7 +87,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
|
|
|
79
87
|
setAccountEnabled: ({ cfg, accountId, enabled }) =>
|
|
80
88
|
setAccountEnabledInConfigSection({
|
|
81
89
|
cfg,
|
|
82
|
-
sectionKey:
|
|
90
|
+
sectionKey: CHANNEL_CONFIG_KEY,
|
|
83
91
|
accountId,
|
|
84
92
|
enabled,
|
|
85
93
|
allowTopLevel: true,
|
|
@@ -87,7 +95,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
|
|
|
87
95
|
deleteAccount: ({ cfg, accountId }) =>
|
|
88
96
|
deleteAccountFromConfigSection({
|
|
89
97
|
cfg,
|
|
90
|
-
sectionKey:
|
|
98
|
+
sectionKey: CHANNEL_CONFIG_KEY,
|
|
91
99
|
accountId,
|
|
92
100
|
clearBaseFields: ["token", "tokenFile", "appId", "appIdFile", "name"],
|
|
93
101
|
}),
|
|
@@ -108,24 +116,24 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
|
|
|
108
116
|
allowFrom
|
|
109
117
|
.map((entry) => String(entry).trim())
|
|
110
118
|
.filter(Boolean)
|
|
111
|
-
.map((entry) => entry
|
|
119
|
+
.map((entry) => stripChannelPrefix(entry)),
|
|
112
120
|
},
|
|
113
121
|
security: {
|
|
114
122
|
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
|
115
123
|
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
116
124
|
const useAccountPath = Boolean(
|
|
117
|
-
cfg.channels?.
|
|
125
|
+
cfg.channels?.[CHANNEL_CONFIG_KEY]?.accounts?.[resolvedAccountId],
|
|
118
126
|
);
|
|
119
127
|
const basePath = useAccountPath
|
|
120
|
-
? `channels.
|
|
121
|
-
:
|
|
128
|
+
? `channels.${CHANNEL_CONFIG_KEY}.accounts.${resolvedAccountId}.`
|
|
129
|
+
: `channels.${CHANNEL_CONFIG_KEY}.`;
|
|
122
130
|
return {
|
|
123
131
|
policy: account.config.dmPolicy ?? "pairing",
|
|
124
132
|
allowFrom: account.config.allowFrom ?? [],
|
|
125
133
|
policyPath: `${basePath}dmPolicy`,
|
|
126
134
|
allowFromPath: basePath,
|
|
127
|
-
approveHint: formatPairingApproveHint(
|
|
128
|
-
normalizeEntry: (raw) => raw
|
|
135
|
+
approveHint: formatPairingApproveHint(CHANNEL_ID),
|
|
136
|
+
normalizeEntry: (raw) => stripChannelPrefix(raw),
|
|
129
137
|
};
|
|
130
138
|
},
|
|
131
139
|
collectWarnings: ({ account, cfg }) => {
|
|
@@ -136,11 +144,11 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
|
|
|
136
144
|
account.config.groups && Object.keys(account.config.groups).length > 0;
|
|
137
145
|
if (groupAllowlistConfigured) {
|
|
138
146
|
return [
|
|
139
|
-
`- GeWe groups: groupPolicy="open" allows any member in allowed groups to trigger (mention-gated). Set channels.
|
|
147
|
+
`- GeWe groups: groupPolicy="open" allows any member in allowed groups to trigger (mention-gated). Set channels.${CHANNEL_CONFIG_KEY}.groupPolicy="allowlist" + channels.${CHANNEL_CONFIG_KEY}.groupAllowFrom to restrict senders.`,
|
|
140
148
|
];
|
|
141
149
|
}
|
|
142
150
|
return [
|
|
143
|
-
`- GeWe groups: groupPolicy="open" with no channels.
|
|
151
|
+
`- GeWe groups: groupPolicy="open" with no channels.${CHANNEL_CONFIG_KEY}.groups allowlist; any group can add + ping (mention-gated). Set channels.${CHANNEL_CONFIG_KEY}.groupPolicy="allowlist" + channels.${CHANNEL_CONFIG_KEY}.groupAllowFrom or configure channels.${CHANNEL_CONFIG_KEY}.groups.`,
|
|
144
152
|
];
|
|
145
153
|
},
|
|
146
154
|
},
|
|
@@ -187,7 +195,10 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
|
|
|
187
195
|
}
|
|
188
196
|
return {
|
|
189
197
|
ok: false,
|
|
190
|
-
error: missingTargetError(
|
|
198
|
+
error: missingTargetError(
|
|
199
|
+
"GeWe",
|
|
200
|
+
`<wxid|@chatroom> or channels.${CHANNEL_CONFIG_KEY}.allowFrom[0]`,
|
|
201
|
+
),
|
|
191
202
|
};
|
|
192
203
|
}
|
|
193
204
|
return { ok: true, to: normalized };
|
|
@@ -198,7 +209,10 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
|
|
|
198
209
|
}
|
|
199
210
|
return {
|
|
200
211
|
ok: false,
|
|
201
|
-
error: missingTargetError(
|
|
212
|
+
error: missingTargetError(
|
|
213
|
+
"GeWe",
|
|
214
|
+
`<wxid|@chatroom> or channels.${CHANNEL_CONFIG_KEY}.allowFrom[0]`,
|
|
215
|
+
),
|
|
202
216
|
};
|
|
203
217
|
},
|
|
204
218
|
sendPayload: async ({ payload, cfg, to, accountId }) => {
|
|
@@ -210,7 +224,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
|
|
|
210
224
|
toWxid: to,
|
|
211
225
|
});
|
|
212
226
|
return {
|
|
213
|
-
channel:
|
|
227
|
+
channel: CHANNEL_ID,
|
|
214
228
|
messageId: result?.messageId ?? "ok",
|
|
215
229
|
timestamp: result?.timestamp,
|
|
216
230
|
meta: { newMessageId: result?.newMessageId },
|
|
@@ -225,7 +239,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
|
|
|
225
239
|
toWxid: to,
|
|
226
240
|
});
|
|
227
241
|
return {
|
|
228
|
-
channel:
|
|
242
|
+
channel: CHANNEL_ID,
|
|
229
243
|
messageId: result?.messageId ?? "ok",
|
|
230
244
|
timestamp: result?.timestamp,
|
|
231
245
|
meta: { newMessageId: result?.newMessageId },
|
|
@@ -240,7 +254,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
|
|
|
240
254
|
toWxid: to,
|
|
241
255
|
});
|
|
242
256
|
return {
|
|
243
|
-
channel:
|
|
257
|
+
channel: CHANNEL_ID,
|
|
244
258
|
messageId: result?.messageId ?? "ok",
|
|
245
259
|
timestamp: result?.timestamp,
|
|
246
260
|
meta: { newMessageId: result?.newMessageId },
|
|
@@ -307,7 +321,9 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
|
|
|
307
321
|
},
|
|
308
322
|
logoutAccount: async ({ accountId, cfg }) => {
|
|
309
323
|
const nextCfg = { ...cfg } as OpenClawConfig;
|
|
310
|
-
const nextSection = cfg.channels?.
|
|
324
|
+
const nextSection = cfg.channels?.[CHANNEL_CONFIG_KEY]
|
|
325
|
+
? { ...(cfg.channels?.[CHANNEL_CONFIG_KEY] as Record<string, unknown>) }
|
|
326
|
+
: undefined;
|
|
311
327
|
let cleared = false;
|
|
312
328
|
let changed = false;
|
|
313
329
|
|
|
@@ -377,7 +393,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
|
|
|
377
393
|
if (changed) {
|
|
378
394
|
nextCfg.channels = {
|
|
379
395
|
...nextCfg.channels,
|
|
380
|
-
|
|
396
|
+
[CHANNEL_CONFIG_KEY]: nextSection,
|
|
381
397
|
};
|
|
382
398
|
}
|
|
383
399
|
}
|
|
@@ -390,7 +406,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
|
|
|
390
406
|
applyAccountName: ({ cfg, accountId, name }) =>
|
|
391
407
|
applyAccountNameToChannelSection({
|
|
392
408
|
cfg: cfg as OpenClawConfig,
|
|
393
|
-
channelKey:
|
|
409
|
+
channelKey: CHANNEL_CONFIG_KEY,
|
|
394
410
|
accountId,
|
|
395
411
|
name,
|
|
396
412
|
}),
|
|
@@ -411,11 +427,14 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
|
|
|
411
427
|
const setupInput = input as GeweSetupInput;
|
|
412
428
|
const namedConfig = applyAccountNameToChannelSection({
|
|
413
429
|
cfg: cfg as OpenClawConfig,
|
|
414
|
-
channelKey:
|
|
430
|
+
channelKey: CHANNEL_CONFIG_KEY,
|
|
415
431
|
accountId,
|
|
416
432
|
name: setupInput.name,
|
|
417
433
|
});
|
|
418
|
-
const section = (namedConfig.channels?.
|
|
434
|
+
const section = (namedConfig.channels?.[CHANNEL_CONFIG_KEY] ?? {}) as Record<
|
|
435
|
+
string,
|
|
436
|
+
unknown
|
|
437
|
+
>;
|
|
419
438
|
const useAccountPath = accountId !== DEFAULT_ACCOUNT_ID;
|
|
420
439
|
const base = useAccountPath
|
|
421
440
|
? (section.accounts?.[accountId] as Record<string, unknown> | undefined) ?? {}
|
|
@@ -443,7 +462,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
|
|
|
443
462
|
...namedConfig,
|
|
444
463
|
channels: {
|
|
445
464
|
...namedConfig.channels,
|
|
446
|
-
|
|
465
|
+
[CHANNEL_CONFIG_KEY]: nextEntry,
|
|
447
466
|
},
|
|
448
467
|
};
|
|
449
468
|
}
|
|
@@ -451,7 +470,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
|
|
|
451
470
|
...namedConfig,
|
|
452
471
|
channels: {
|
|
453
472
|
...namedConfig.channels,
|
|
454
|
-
|
|
473
|
+
[CHANNEL_CONFIG_KEY]: {
|
|
455
474
|
...section,
|
|
456
475
|
accounts: {
|
|
457
476
|
...(section.accounts as Record<string, unknown> | undefined),
|
package/src/config-schema.ts
CHANGED
|
@@ -81,25 +81,27 @@ export const GeweAccountSchemaBase = z
|
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
export const GeweAccountSchema = GeweAccountSchemaBase.superRefine((value, ctx) => {
|
|
84
|
+
const pathHint = "channels.gewe-openclaw";
|
|
84
85
|
requireOpenAllowFrom({
|
|
85
86
|
policy: value.dmPolicy,
|
|
86
87
|
allowFrom: value.allowFrom,
|
|
87
88
|
ctx,
|
|
88
89
|
path: ["allowFrom"],
|
|
89
90
|
message:
|
|
90
|
-
|
|
91
|
+
`${pathHint}.dmPolicy="open" requires ${pathHint}.allowFrom to include "*"`,
|
|
91
92
|
});
|
|
92
93
|
});
|
|
93
94
|
|
|
94
95
|
export const GeweConfigSchema = GeweAccountSchemaBase.extend({
|
|
95
96
|
accounts: z.record(z.string(), GeweAccountSchema.optional()).optional(),
|
|
96
97
|
}).superRefine((value, ctx) => {
|
|
98
|
+
const pathHint = "channels.gewe-openclaw";
|
|
97
99
|
requireOpenAllowFrom({
|
|
98
100
|
policy: value.dmPolicy,
|
|
99
101
|
allowFrom: value.allowFrom,
|
|
100
102
|
ctx,
|
|
101
103
|
path: ["allowFrom"],
|
|
102
104
|
message:
|
|
103
|
-
|
|
105
|
+
`${pathHint}.dmPolicy="open" requires ${pathHint}.allowFrom to include "*"`,
|
|
104
106
|
});
|
|
105
107
|
});
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const CHANNEL_ID = "gewe-openclaw" as const;
|
|
2
|
+
export const CHANNEL_CONFIG_KEY = "gewe-openclaw" as const;
|
|
3
|
+
export const CHANNEL_DOCS_PATH = "/channels/gewe-openclaw" as const;
|
|
4
|
+
export const CHANNEL_DOCS_LABEL = "gewe-openclaw" as const;
|
|
5
|
+
export const CHANNEL_PREFIX_REGEX = /^(gewe-openclaw|gewe|wechat|wx):/i;
|
|
6
|
+
export const CHANNEL_ALIASES = ["gewe-openclaw", "gewe", "wechat", "wx"] as const;
|
|
7
|
+
|
|
8
|
+
export function stripChannelPrefix(value: string): string {
|
|
9
|
+
return value.replace(CHANNEL_PREFIX_REGEX, "");
|
|
10
|
+
}
|
package/src/delivery.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
|
|
6
6
|
import type { OpenClawConfig, ReplyPayload } from "openclaw/plugin-sdk";
|
|
7
7
|
import { extractOriginalFilename, extensionForMime } from "openclaw/plugin-sdk";
|
|
8
|
+
import { CHANNEL_ID } from "./constants.js";
|
|
8
9
|
import { getGeweRuntime } from "./runtime.js";
|
|
9
10
|
import {
|
|
10
11
|
sendFileGewe,
|
|
@@ -97,8 +98,10 @@ function resolveMediaMaxBytes(account: ResolvedGeweAccount): number {
|
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
function resolveGeweData(payload: ReplyPayload): GeweChannelData | undefined {
|
|
100
|
-
const data = payload.channelData as
|
|
101
|
-
|
|
101
|
+
const data = payload.channelData as
|
|
102
|
+
| { "gewe-openclaw"?: GeweChannelData; gewe?: GeweChannelData }
|
|
103
|
+
| undefined;
|
|
104
|
+
return data?.[CHANNEL_ID] ?? data?.gewe;
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
function isSilkAudio(opts: { contentType?: string; fileName?: string }): boolean {
|
|
@@ -145,7 +148,7 @@ async function probeVideoDurationSeconds(params: {
|
|
|
145
148
|
sourcePath: string;
|
|
146
149
|
}): Promise<number | null> {
|
|
147
150
|
const core = getGeweRuntime();
|
|
148
|
-
const logger = core.logging.getChildLogger({ channel:
|
|
151
|
+
const logger = core.logging.getChildLogger({ channel: CHANNEL_ID, module: "video" });
|
|
149
152
|
const ffmpegPath = resolveVideoFfmpegPath(params.account);
|
|
150
153
|
const ffprobePath = resolveVideoFfprobePath(params.account, ffmpegPath);
|
|
151
154
|
const args = [
|
|
@@ -180,7 +183,7 @@ async function generateVideoThumbBuffer(params: {
|
|
|
180
183
|
sourcePath: string;
|
|
181
184
|
}): Promise<Buffer | null> {
|
|
182
185
|
const core = getGeweRuntime();
|
|
183
|
-
const logger = core.logging.getChildLogger({ channel:
|
|
186
|
+
const logger = core.logging.getChildLogger({ channel: CHANNEL_ID, module: "video" });
|
|
184
187
|
const ffmpegPath = resolveVideoFfmpegPath(params.account);
|
|
185
188
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-gewe-video-"));
|
|
186
189
|
const thumbPath = path.join(tmpDir, "thumb.png");
|
|
@@ -247,7 +250,7 @@ async function convertAudioToSilk(params: {
|
|
|
247
250
|
sourcePath: string;
|
|
248
251
|
}): Promise<{ buffer: Buffer; durationMs: number } | null> {
|
|
249
252
|
const core = getGeweRuntime();
|
|
250
|
-
const logger = core.logging.getChildLogger({ channel:
|
|
253
|
+
const logger = core.logging.getChildLogger({ channel: CHANNEL_ID, module: "voice" });
|
|
251
254
|
if (!params.account.config.voiceAutoConvert) return null;
|
|
252
255
|
|
|
253
256
|
const sampleRate = resolveVoiceSampleRate(params.account);
|
|
@@ -441,7 +444,7 @@ async function resolveLinkThumbUrl(params: {
|
|
|
441
444
|
thumbUrl?: string;
|
|
442
445
|
}): Promise<string> {
|
|
443
446
|
const core = getGeweRuntime();
|
|
444
|
-
const logger = core.logging.getChildLogger({ channel:
|
|
447
|
+
const logger = core.logging.getChildLogger({ channel: CHANNEL_ID, module: "thumb" });
|
|
445
448
|
const fallbackBuffer = await fs.readFile(DEFAULT_LINK_THUMB_PATH);
|
|
446
449
|
const fallbackUrl = await stageThumbBuffer({
|
|
447
450
|
account: params.account,
|
|
@@ -591,7 +594,7 @@ export async function deliverGewePayload(params: {
|
|
|
591
594
|
thumbUrl,
|
|
592
595
|
});
|
|
593
596
|
core.channel.activity.record({
|
|
594
|
-
channel:
|
|
597
|
+
channel: CHANNEL_ID,
|
|
595
598
|
accountId: account.accountId,
|
|
596
599
|
direction: "outbound",
|
|
597
600
|
});
|
|
@@ -625,7 +628,7 @@ export async function deliverGewePayload(params: {
|
|
|
625
628
|
voiceDuration: declaredDuration,
|
|
626
629
|
});
|
|
627
630
|
core.channel.activity.record({
|
|
628
|
-
channel:
|
|
631
|
+
channel: CHANNEL_ID,
|
|
629
632
|
accountId: account.accountId,
|
|
630
633
|
direction: "outbound",
|
|
631
634
|
});
|
|
@@ -657,7 +660,7 @@ export async function deliverGewePayload(params: {
|
|
|
657
660
|
voiceDuration,
|
|
658
661
|
});
|
|
659
662
|
core.channel.activity.record({
|
|
660
|
-
channel:
|
|
663
|
+
channel: CHANNEL_ID,
|
|
661
664
|
accountId: account.accountId,
|
|
662
665
|
direction: "outbound",
|
|
663
666
|
});
|
|
@@ -674,7 +677,7 @@ export async function deliverGewePayload(params: {
|
|
|
674
677
|
imgUrl: staged.publicUrl,
|
|
675
678
|
});
|
|
676
679
|
core.channel.activity.record({
|
|
677
|
-
channel:
|
|
680
|
+
channel: CHANNEL_ID,
|
|
678
681
|
accountId: account.accountId,
|
|
679
682
|
direction: "outbound",
|
|
680
683
|
});
|
|
@@ -683,7 +686,7 @@ export async function deliverGewePayload(params: {
|
|
|
683
686
|
}
|
|
684
687
|
|
|
685
688
|
if (!forceFile && kind === "video") {
|
|
686
|
-
const logger = core.logging.getChildLogger({ channel:
|
|
689
|
+
const logger = core.logging.getChildLogger({ channel: CHANNEL_ID, module: "video" });
|
|
687
690
|
const video = geweData?.video;
|
|
688
691
|
let thumbUrl = video?.thumbUrl;
|
|
689
692
|
const fallbackThumbUrl = account.config.videoThumbUrl?.trim() || undefined;
|
|
@@ -755,7 +758,7 @@ export async function deliverGewePayload(params: {
|
|
|
755
758
|
videoDuration: Math.floor(videoDuration),
|
|
756
759
|
});
|
|
757
760
|
core.channel.activity.record({
|
|
758
|
-
channel:
|
|
761
|
+
channel: CHANNEL_ID,
|
|
759
762
|
accountId: account.accountId,
|
|
760
763
|
direction: "outbound",
|
|
761
764
|
});
|
|
@@ -780,7 +783,7 @@ export async function deliverGewePayload(params: {
|
|
|
780
783
|
videoDuration: Math.floor(videoDuration),
|
|
781
784
|
});
|
|
782
785
|
core.channel.activity.record({
|
|
783
|
-
channel:
|
|
786
|
+
channel: CHANNEL_ID,
|
|
784
787
|
accountId: account.accountId,
|
|
785
788
|
direction: "outbound",
|
|
786
789
|
});
|
|
@@ -803,7 +806,7 @@ export async function deliverGewePayload(params: {
|
|
|
803
806
|
fileName: fallbackName,
|
|
804
807
|
});
|
|
805
808
|
core.channel.activity.record({
|
|
806
|
-
channel:
|
|
809
|
+
channel: CHANNEL_ID,
|
|
807
810
|
accountId: account.accountId,
|
|
808
811
|
direction: "outbound",
|
|
809
812
|
});
|
|
@@ -814,7 +817,7 @@ export async function deliverGewePayload(params: {
|
|
|
814
817
|
if (trimmedText) {
|
|
815
818
|
const tableMode = core.channel.text.resolveMarkdownTableMode({
|
|
816
819
|
cfg,
|
|
817
|
-
channel:
|
|
820
|
+
channel: CHANNEL_ID,
|
|
818
821
|
accountId: account.accountId,
|
|
819
822
|
});
|
|
820
823
|
const content = core.channel.text.convertMarkdownTables(trimmedText, tableMode);
|
|
@@ -825,7 +828,7 @@ export async function deliverGewePayload(params: {
|
|
|
825
828
|
ats: geweData?.ats,
|
|
826
829
|
});
|
|
827
830
|
core.channel.activity.record({
|
|
828
|
-
channel:
|
|
831
|
+
channel: CHANNEL_ID,
|
|
829
832
|
accountId: account.accountId,
|
|
830
833
|
direction: "outbound",
|
|
831
834
|
});
|
package/src/inbound.ts
CHANGED
|
@@ -19,8 +19,7 @@ import {
|
|
|
19
19
|
} from "./policy.js";
|
|
20
20
|
import type { CoreConfig, GeweInboundMessage, ResolvedGeweAccount } from "./types.js";
|
|
21
21
|
import { extractAppMsgType, extractFileName, extractLinkDetails } from "./xml.js";
|
|
22
|
-
|
|
23
|
-
const CHANNEL_ID = "gewe" as const;
|
|
22
|
+
import { CHANNEL_ID } from "./constants.js";
|
|
24
23
|
|
|
25
24
|
type PreparedInbound = {
|
|
26
25
|
rawBody: string;
|
|
@@ -103,7 +102,7 @@ async function decodeSilkVoice(params: {
|
|
|
103
102
|
fileName?: string | null;
|
|
104
103
|
}): Promise<DecodedVoice | null> {
|
|
105
104
|
const core = getGeweRuntime();
|
|
106
|
-
const logger = core.logging.getChildLogger({ channel:
|
|
105
|
+
const logger = core.logging.getChildLogger({ channel: CHANNEL_ID, module: "voice" });
|
|
107
106
|
const decodeOutput = params.account.config.voiceDecodeOutput ?? "pcm";
|
|
108
107
|
const sampleRate = resolveVoiceDecodeSampleRate(params.account);
|
|
109
108
|
const ffmpegPath = params.account.config.voiceFfmpegPath?.trim() || "ffmpeg";
|
|
@@ -261,8 +260,10 @@ async function dispatchGeweInbound(params: {
|
|
|
261
260
|
Body: body,
|
|
262
261
|
RawBody: prepared.rawBody,
|
|
263
262
|
CommandBody: prepared.rawBody,
|
|
264
|
-
From: prepared.groupId
|
|
265
|
-
|
|
263
|
+
From: prepared.groupId
|
|
264
|
+
? `${CHANNEL_ID}:group:${prepared.groupId}`
|
|
265
|
+
: `${CHANNEL_ID}:${prepared.senderId}`,
|
|
266
|
+
To: `${CHANNEL_ID}:${prepared.toWxid}`,
|
|
266
267
|
SessionKey: prepared.route.sessionKey,
|
|
267
268
|
AccountId: prepared.route.accountId,
|
|
268
269
|
ChatType: prepared.isGroup ? "group" : "direct",
|
|
@@ -272,16 +273,16 @@ async function dispatchGeweInbound(params: {
|
|
|
272
273
|
SenderName: prepared.senderName || undefined,
|
|
273
274
|
SenderId: prepared.senderId,
|
|
274
275
|
CommandAuthorized: prepared.commandAuthorized,
|
|
275
|
-
Provider:
|
|
276
|
-
Surface:
|
|
276
|
+
Provider: CHANNEL_ID,
|
|
277
|
+
Surface: CHANNEL_ID,
|
|
277
278
|
MessageSid: prepared.messageSid,
|
|
278
279
|
MessageSidFull: prepared.messageSid,
|
|
279
280
|
MediaPath: media?.path,
|
|
280
281
|
MediaType: media?.contentType,
|
|
281
282
|
MediaUrl: media?.path,
|
|
282
283
|
GroupSystemPrompt: prepared.groupSystemPrompt,
|
|
283
|
-
OriginatingChannel:
|
|
284
|
-
OriginatingTo:
|
|
284
|
+
OriginatingChannel: CHANNEL_ID,
|
|
285
|
+
OriginatingTo: `${CHANNEL_ID}:${prepared.toWxid}`,
|
|
285
286
|
});
|
|
286
287
|
|
|
287
288
|
await core.channel.session.recordInboundSession({
|
|
@@ -523,7 +524,7 @@ export async function handleGeweInbound(params: {
|
|
|
523
524
|
};
|
|
524
525
|
|
|
525
526
|
core.channel.activity.record({
|
|
526
|
-
channel:
|
|
527
|
+
channel: CHANNEL_ID,
|
|
527
528
|
accountId: account.accountId,
|
|
528
529
|
direction: "inbound",
|
|
529
530
|
});
|
package/src/normalize.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
export function normalizeGeweMessagingTarget(target: string): string | null {
|
|
2
2
|
const trimmed = target.trim();
|
|
3
3
|
if (!trimmed) return null;
|
|
4
|
+
const prefix = /^(gewe-openclaw|gewe|wechat|wx):(group:|user:)?/i;
|
|
4
5
|
return trimmed
|
|
5
|
-
.replace(
|
|
6
|
-
.replace(/^wechat:(group:|user:)?/i, "")
|
|
7
|
-
.replace(/^wx:/i, "")
|
|
6
|
+
.replace(prefix, "")
|
|
8
7
|
.trim();
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
export function looksLikeGeweTargetId(id: string): boolean {
|
|
12
11
|
const trimmed = id?.trim();
|
|
13
12
|
if (!trimmed) return false;
|
|
14
|
-
if (/^gewe:/i.test(trimmed)) return true;
|
|
13
|
+
if (/^(gewe-openclaw|gewe):/i.test(trimmed)) return true;
|
|
15
14
|
if (/@chatroom$/i.test(trimmed)) return true;
|
|
16
15
|
if (/^wxid_/i.test(trimmed)) return true;
|
|
17
16
|
if (/^gh_/i.test(trimmed)) return true;
|
package/src/policy.ts
CHANGED
|
@@ -7,10 +7,11 @@ import {
|
|
|
7
7
|
resolveNestedAllowlistDecision,
|
|
8
8
|
} from "openclaw/plugin-sdk";
|
|
9
9
|
|
|
10
|
+
import { CHANNEL_CONFIG_KEY, CHANNEL_PREFIX_REGEX } from "./constants.js";
|
|
10
11
|
import type { GeweGroupConfig } from "./types.js";
|
|
11
12
|
|
|
12
13
|
function normalizeAllowEntry(raw: string): string {
|
|
13
|
-
return raw.trim().toLowerCase().replace(
|
|
14
|
+
return raw.trim().toLowerCase().replace(CHANNEL_PREFIX_REGEX, "");
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export function normalizeGeweAllowlist(values: Array<string | number> | undefined): string[] {
|
|
@@ -89,7 +90,7 @@ export function resolveGeweGroupToolPolicy(
|
|
|
89
90
|
): GeweGroupConfig["tools"] | undefined {
|
|
90
91
|
const cfg = params.cfg as {
|
|
91
92
|
channels?: {
|
|
92
|
-
gewe?: {
|
|
93
|
+
"gewe-openclaw"?: {
|
|
93
94
|
groups?: Record<string, GeweGroupConfig>;
|
|
94
95
|
accounts?: Record<string, { groups?: Record<string, GeweGroupConfig> }>;
|
|
95
96
|
};
|
|
@@ -99,10 +100,10 @@ export function resolveGeweGroupToolPolicy(
|
|
|
99
100
|
if (!groupId) return undefined;
|
|
100
101
|
const groupName = params.groupChannel?.trim() || undefined;
|
|
101
102
|
const accountGroups =
|
|
102
|
-
params.accountId && cfg.channels?.
|
|
103
|
-
? cfg.channels?.
|
|
103
|
+
params.accountId && cfg.channels?.[CHANNEL_CONFIG_KEY]?.accounts?.[params.accountId]?.groups
|
|
104
|
+
? cfg.channels?.[CHANNEL_CONFIG_KEY]?.accounts?.[params.accountId]?.groups
|
|
104
105
|
: undefined;
|
|
105
|
-
const groups = accountGroups ?? cfg.channels?.
|
|
106
|
+
const groups = accountGroups ?? cfg.channels?.[CHANNEL_CONFIG_KEY]?.groups;
|
|
106
107
|
const match = resolveGeweGroupMatch({
|
|
107
108
|
groups,
|
|
108
109
|
groupId,
|