@kyan-du/agent-wechat-openclaw 0.12.0 → 0.13.1
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 +12 -12
- package/dist/index.js +368 -94
- package/openclaw.plugin.json +3 -3
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -81,19 +81,19 @@ If running alongside OpenClaw on the same Docker network, set `serverUrl` to `ht
|
|
|
81
81
|
# Pending P1-B (not available yet): openclaw plugins install @kyan-du/agent-wechat-openclaw
|
|
82
82
|
pnpm --filter @kyan-du/agent-wechat-openclaw build
|
|
83
83
|
node scripts/prepare-openclaw-plugin.mjs
|
|
84
|
-
openclaw plugins install
|
|
84
|
+
openclaw plugins install ./.artifacts/agent-wechat-openclaw.tgz
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
### 3. Configure the channel
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
This plugin registers as channel id `agent-wechat`. Do not use `wechat`, `weixin`, or `微信` — OpenClaw's official catalog maps those aliases to `@tencent-weixin/openclaw-weixin`, so outbound and cron delivery never reach this plugin.
|
|
90
90
|
|
|
91
91
|
```bash
|
|
92
|
-
openclaw config set channels.wechat.enabled true
|
|
93
|
-
openclaw config set channels.wechat.serverUrl http://localhost:6174
|
|
92
|
+
openclaw config set channels.agent-wechat.enabled true
|
|
93
|
+
openclaw config set channels.agent-wechat.serverUrl http://localhost:6174
|
|
94
94
|
# Optional when the server requires an explicit token:
|
|
95
|
-
openclaw config set channels.wechat.token <token>
|
|
96
|
-
openclaw plugins inspect wechat --runtime
|
|
95
|
+
openclaw config set channels.agent-wechat.token <token>
|
|
96
|
+
openclaw plugins inspect agent-wechat --runtime
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
Or edit `~/.openclaw/openclaw.json` directly:
|
|
@@ -101,7 +101,7 @@ Or edit `~/.openclaw/openclaw.json` directly:
|
|
|
101
101
|
```json
|
|
102
102
|
{
|
|
103
103
|
"channels": {
|
|
104
|
-
"wechat": {
|
|
104
|
+
"agent-wechat": {
|
|
105
105
|
"enabled": true,
|
|
106
106
|
"serverUrl": "http://localhost:6174",
|
|
107
107
|
"dmPolicy": "open",
|
|
@@ -130,7 +130,7 @@ Ask your bot to log in to WeChat:
|
|
|
130
130
|
Your bot should generate a QR code image. Alternatively, use the CLI:
|
|
131
131
|
|
|
132
132
|
```bash
|
|
133
|
-
openclaw channels login --channel wechat
|
|
133
|
+
openclaw channels login --channel agent-wechat
|
|
134
134
|
```
|
|
135
135
|
|
|
136
136
|
### 6. Scan the QR code
|
|
@@ -155,7 +155,7 @@ Once connected, configure how the bot handles direct messages and group chats. Y
|
|
|
155
155
|
|
|
156
156
|
## Configuration Reference
|
|
157
157
|
|
|
158
|
-
All config lives under `channels.wechat` in OpenClaw's config file
|
|
158
|
+
All config lives under `channels.agent-wechat` in OpenClaw's config file. `channels.wechat` is still read as a one-release fallback.
|
|
159
159
|
|
|
160
160
|
| Key | Type | Default | Description |
|
|
161
161
|
|-----|------|---------|-------------|
|
|
@@ -185,14 +185,14 @@ cd agent-wechat
|
|
|
185
185
|
pnpm install && pnpm build
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
-
###
|
|
188
|
+
### Install a private source build
|
|
189
189
|
|
|
190
190
|
```bash
|
|
191
191
|
node scripts/prepare-openclaw-plugin.mjs
|
|
192
|
-
openclaw plugins install
|
|
192
|
+
openclaw plugins install ./.artifacts/agent-wechat-openclaw.tgz
|
|
193
193
|
```
|
|
194
194
|
|
|
195
|
-
This
|
|
195
|
+
This creates a clean npm archive from the built plugin. OpenClaw installs its runtime dependencies, including the platform-specific `sharp` package, without relying on workspace-hoisted modules. Rebuild and repack after making changes, then restart the gateway.
|
|
196
196
|
|
|
197
197
|
## Architecture
|
|
198
198
|
|
package/dist/index.js
CHANGED
|
@@ -1102,8 +1102,10 @@ var DEFAULT_CATCH_UP_MAX_AGE_MS = 5 * 6e4;
|
|
|
1102
1102
|
var DEFAULT_CATCH_UP_CHAT_BUDGET = 5;
|
|
1103
1103
|
var DEFAULT_MEDIA_PART_DELAY_MS = 750;
|
|
1104
1104
|
var DEFAULT_ACCOUNT_ID = "default";
|
|
1105
|
+
var OPENCLAW_CHANNEL_ID = "agent-wechat";
|
|
1105
1106
|
function resolveWeChatAccount(cfg, accountId) {
|
|
1106
|
-
const
|
|
1107
|
+
const channels = cfg.channels;
|
|
1108
|
+
const wechat = channels?.[OPENCLAW_CHANNEL_ID] ?? channels?.wechat;
|
|
1107
1109
|
if (!wechat?.serverUrl) return null;
|
|
1108
1110
|
return {
|
|
1109
1111
|
accountId: accountId ?? DEFAULT_ACCOUNT_ID,
|
|
@@ -6154,7 +6156,7 @@ function normalizeWeChatId(raw) {
|
|
|
6154
6156
|
if (!trimmed) {
|
|
6155
6157
|
return "";
|
|
6156
6158
|
}
|
|
6157
|
-
return trimmed.replace(/^wechat:/i, "").trim();
|
|
6159
|
+
return trimmed.replace(/^(agent-wechat|wechat):/i, "").trim();
|
|
6158
6160
|
}
|
|
6159
6161
|
function normalizeWeChatAllowFrom(values) {
|
|
6160
6162
|
const normalized = (values ?? []).map((entry) => String(entry).trim()).filter(Boolean).map((entry) => entry === "*" ? "*" : normalizeWeChatId(entry)).filter(Boolean);
|
|
@@ -6240,7 +6242,7 @@ function resolveWeChatPolicyContext(params) {
|
|
|
6240
6242
|
const requireMention = firstDefined(groupEntry?.requireMention, wildcardEntry?.requireMention, true) !== false;
|
|
6241
6243
|
const defaultGroupPolicy = resolveDefaultGroupPolicy(params.cfg);
|
|
6242
6244
|
const { groupPolicy: fallbackGroupPolicy } = resolveAllowlistProviderRuntimeGroupPolicy({
|
|
6243
|
-
providerConfigPresent: params.cfg.channels?.wechat !== void 0,
|
|
6245
|
+
providerConfigPresent: params.cfg.channels?.["agent-wechat"] !== void 0 || params.cfg.channels?.wechat !== void 0,
|
|
6244
6246
|
groupPolicy: normalizeGroupPolicy2(params.account.groupPolicy),
|
|
6245
6247
|
defaultGroupPolicy: normalizeGroupPolicy2(defaultGroupPolicy)
|
|
6246
6248
|
});
|
|
@@ -6355,6 +6357,310 @@ function nextReconnectState(input) {
|
|
|
6355
6357
|
return { reconnect: true, dispatched: input.dispatched };
|
|
6356
6358
|
}
|
|
6357
6359
|
|
|
6360
|
+
// src/inbound-media.ts
|
|
6361
|
+
import sharp from "sharp";
|
|
6362
|
+
var MIME_BY_FORMAT = {
|
|
6363
|
+
jpeg: "image/jpeg",
|
|
6364
|
+
jpg: "image/jpeg",
|
|
6365
|
+
png: "image/png",
|
|
6366
|
+
webp: "image/webp",
|
|
6367
|
+
gif: "image/gif",
|
|
6368
|
+
mp3: "audio/mpeg",
|
|
6369
|
+
pdf: "application/pdf",
|
|
6370
|
+
doc: "application/msword",
|
|
6371
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
6372
|
+
xls: "application/vnd.ms-excel",
|
|
6373
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
6374
|
+
ppt: "application/vnd.ms-powerpoint",
|
|
6375
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
6376
|
+
zip: "application/zip",
|
|
6377
|
+
txt: "text/plain"
|
|
6378
|
+
};
|
|
6379
|
+
var MAX_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
6380
|
+
var MAX_IMAGE_PIXELS = 4e7;
|
|
6381
|
+
var MAX_IMAGE_DIMENSION = 32768;
|
|
6382
|
+
var MAX_DECODED_IMAGE_BYTES = MAX_IMAGE_PIXELS * 4;
|
|
6383
|
+
var MAX_GIF_FRAMES = 64;
|
|
6384
|
+
var GIF_MAX_DICTIONARY_SIZE = 4096;
|
|
6385
|
+
var MAX_GIF_LZW_OPERATIONS = 1e7;
|
|
6386
|
+
var IMAGE_DECODE_TIMEOUT_SECONDS = 5;
|
|
6387
|
+
var SHARP_INPUT_OPTIONS = {
|
|
6388
|
+
failOn: "warning",
|
|
6389
|
+
limitInputPixels: MAX_IMAGE_PIXELS,
|
|
6390
|
+
limitInputChannels: 5,
|
|
6391
|
+
sequentialRead: true,
|
|
6392
|
+
unlimited: false,
|
|
6393
|
+
page: 0,
|
|
6394
|
+
pages: 1,
|
|
6395
|
+
animated: false
|
|
6396
|
+
};
|
|
6397
|
+
sharp.cache({ files: 0, items: 0, memory: 32 });
|
|
6398
|
+
sharp.concurrency(1);
|
|
6399
|
+
function imageMimeFromMagic(buf) {
|
|
6400
|
+
if (buf.length >= 3 && buf[0] === 255 && buf[1] === 216 && buf[2] === 255) return "image/jpeg";
|
|
6401
|
+
if (buf.length >= 8 && buf.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) return "image/png";
|
|
6402
|
+
if (buf.length >= 12 && buf.toString("ascii", 0, 4) === "RIFF" && buf.toString("ascii", 8, 12) === "WEBP") return "image/webp";
|
|
6403
|
+
if (buf.length >= 6 && (buf.toString("ascii", 0, 6) === "GIF87a" || buf.toString("ascii", 0, 6) === "GIF89a")) return "image/gif";
|
|
6404
|
+
return void 0;
|
|
6405
|
+
}
|
|
6406
|
+
function validDimensions(width, height) {
|
|
6407
|
+
return Number.isInteger(width) && Number.isInteger(height) && width > 0 && height > 0 && width <= MAX_IMAGE_DIMENSION && height <= MAX_IMAGE_DIMENSION && width * height <= MAX_IMAGE_PIXELS;
|
|
6408
|
+
}
|
|
6409
|
+
var SHARP_FORMAT_BY_MIME = {
|
|
6410
|
+
"image/jpeg": "jpeg",
|
|
6411
|
+
"image/png": "png",
|
|
6412
|
+
"image/webp": "webp",
|
|
6413
|
+
"image/gif": "gif"
|
|
6414
|
+
};
|
|
6415
|
+
function skipGifSubBlocks(buffer, startOffset) {
|
|
6416
|
+
let offset = startOffset;
|
|
6417
|
+
while (offset < buffer.length) {
|
|
6418
|
+
const size = buffer[offset++];
|
|
6419
|
+
if (size === 0) return offset;
|
|
6420
|
+
if (offset + size > buffer.length) return void 0;
|
|
6421
|
+
offset += size;
|
|
6422
|
+
}
|
|
6423
|
+
return void 0;
|
|
6424
|
+
}
|
|
6425
|
+
var GifCodeReader = class {
|
|
6426
|
+
offset;
|
|
6427
|
+
blockRemaining = 0;
|
|
6428
|
+
datum = 0;
|
|
6429
|
+
bits = 0;
|
|
6430
|
+
ended = false;
|
|
6431
|
+
buffer;
|
|
6432
|
+
constructor(buffer, startOffset) {
|
|
6433
|
+
this.buffer = buffer;
|
|
6434
|
+
this.offset = startOffset;
|
|
6435
|
+
}
|
|
6436
|
+
nextByte() {
|
|
6437
|
+
if (this.ended) return void 0;
|
|
6438
|
+
if (this.blockRemaining === 0) {
|
|
6439
|
+
if (this.offset >= this.buffer.length) return void 0;
|
|
6440
|
+
this.blockRemaining = this.buffer[this.offset++];
|
|
6441
|
+
if (this.blockRemaining === 0) {
|
|
6442
|
+
this.ended = true;
|
|
6443
|
+
return void 0;
|
|
6444
|
+
}
|
|
6445
|
+
if (this.offset + this.blockRemaining > this.buffer.length) return void 0;
|
|
6446
|
+
}
|
|
6447
|
+
this.blockRemaining -= 1;
|
|
6448
|
+
return this.buffer[this.offset++];
|
|
6449
|
+
}
|
|
6450
|
+
read(codeSize) {
|
|
6451
|
+
while (this.bits < codeSize) {
|
|
6452
|
+
const byte = this.nextByte();
|
|
6453
|
+
if (byte === void 0) return void 0;
|
|
6454
|
+
this.datum |= byte << this.bits;
|
|
6455
|
+
this.bits += 8;
|
|
6456
|
+
}
|
|
6457
|
+
const code = this.datum & (1 << codeSize) - 1;
|
|
6458
|
+
this.datum >>>= codeSize;
|
|
6459
|
+
this.bits -= codeSize;
|
|
6460
|
+
return code;
|
|
6461
|
+
}
|
|
6462
|
+
finishSubBlocks() {
|
|
6463
|
+
if (this.ended) return this.offset;
|
|
6464
|
+
this.offset += this.blockRemaining;
|
|
6465
|
+
this.blockRemaining = 0;
|
|
6466
|
+
return skipGifSubBlocks(this.buffer, this.offset);
|
|
6467
|
+
}
|
|
6468
|
+
};
|
|
6469
|
+
function decodeExactGifLzwPixels(buffer, startOffset, minCodeSize, pixelCount, colorCount, operationBudget) {
|
|
6470
|
+
if (!Number.isInteger(minCodeSize) || minCodeSize < 2 || minCodeSize > 8) return void 0;
|
|
6471
|
+
const clearCode = 1 << minCodeSize;
|
|
6472
|
+
const endCode = clearCode + 1;
|
|
6473
|
+
const prefix = new Uint16Array(GIF_MAX_DICTIONARY_SIZE);
|
|
6474
|
+
const suffix = new Uint8Array(GIF_MAX_DICTIONARY_SIZE);
|
|
6475
|
+
const stack = new Uint8Array(GIF_MAX_DICTIONARY_SIZE + 1);
|
|
6476
|
+
for (let code = 0; code < clearCode; code += 1) suffix[code] = code;
|
|
6477
|
+
const reader = new GifCodeReader(buffer, startOffset);
|
|
6478
|
+
let codeSize = minCodeSize + 1;
|
|
6479
|
+
let available = clearCode + 2;
|
|
6480
|
+
let oldCode = -1;
|
|
6481
|
+
let first = 0;
|
|
6482
|
+
let produced = 0;
|
|
6483
|
+
let sawInitialClear = false;
|
|
6484
|
+
while (produced <= pixelCount && operationBudget.remaining > 0) {
|
|
6485
|
+
operationBudget.remaining -= 1;
|
|
6486
|
+
const next = reader.read(codeSize);
|
|
6487
|
+
if (next === void 0) return void 0;
|
|
6488
|
+
if (!sawInitialClear) {
|
|
6489
|
+
if (next !== clearCode) return void 0;
|
|
6490
|
+
sawInitialClear = true;
|
|
6491
|
+
continue;
|
|
6492
|
+
}
|
|
6493
|
+
if (next === clearCode) {
|
|
6494
|
+
codeSize = minCodeSize + 1;
|
|
6495
|
+
available = clearCode + 2;
|
|
6496
|
+
oldCode = -1;
|
|
6497
|
+
continue;
|
|
6498
|
+
}
|
|
6499
|
+
if (next === endCode) {
|
|
6500
|
+
return produced === pixelCount ? reader.finishSubBlocks() : void 0;
|
|
6501
|
+
}
|
|
6502
|
+
if (next > available || next >= GIF_MAX_DICTIONARY_SIZE) return void 0;
|
|
6503
|
+
if (oldCode === -1) {
|
|
6504
|
+
if (next >= clearCode || next >= colorCount) return void 0;
|
|
6505
|
+
produced += 1;
|
|
6506
|
+
oldCode = next;
|
|
6507
|
+
first = next;
|
|
6508
|
+
continue;
|
|
6509
|
+
}
|
|
6510
|
+
const inCode = next;
|
|
6511
|
+
let code = next;
|
|
6512
|
+
let top = 0;
|
|
6513
|
+
if (code === available) {
|
|
6514
|
+
stack[top++] = first;
|
|
6515
|
+
code = oldCode;
|
|
6516
|
+
} else if (code > available) {
|
|
6517
|
+
return void 0;
|
|
6518
|
+
}
|
|
6519
|
+
let links = 0;
|
|
6520
|
+
while (code >= clearCode + 2) {
|
|
6521
|
+
operationBudget.remaining -= 1;
|
|
6522
|
+
if (operationBudget.remaining < 0 || code >= available || top >= stack.length || links++ >= GIF_MAX_DICTIONARY_SIZE) return void 0;
|
|
6523
|
+
const value = suffix[code];
|
|
6524
|
+
if (value >= colorCount) return void 0;
|
|
6525
|
+
stack[top++] = value;
|
|
6526
|
+
code = prefix[code];
|
|
6527
|
+
}
|
|
6528
|
+
if (code >= clearCode || code >= colorCount || top >= stack.length) return void 0;
|
|
6529
|
+
first = suffix[code];
|
|
6530
|
+
stack[top++] = first;
|
|
6531
|
+
produced += top;
|
|
6532
|
+
if (produced > pixelCount) return void 0;
|
|
6533
|
+
if (available < GIF_MAX_DICTIONARY_SIZE) {
|
|
6534
|
+
prefix[available] = oldCode;
|
|
6535
|
+
suffix[available] = first;
|
|
6536
|
+
available += 1;
|
|
6537
|
+
if (available === 1 << codeSize && codeSize < 12) codeSize += 1;
|
|
6538
|
+
}
|
|
6539
|
+
oldCode = inCode;
|
|
6540
|
+
}
|
|
6541
|
+
return void 0;
|
|
6542
|
+
}
|
|
6543
|
+
function validateGifLzwStreams(buffer) {
|
|
6544
|
+
if (buffer.length < 14) return false;
|
|
6545
|
+
const canvasWidth = buffer.readUInt16LE(6);
|
|
6546
|
+
const canvasHeight = buffer.readUInt16LE(8);
|
|
6547
|
+
if (!validDimensions(canvasWidth, canvasHeight)) return false;
|
|
6548
|
+
const logicalPacked = buffer[10];
|
|
6549
|
+
const globalColorCount = (logicalPacked & 128) !== 0 ? 1 << (logicalPacked & 7) + 1 : 0;
|
|
6550
|
+
let offset = 13 + globalColorCount * 3;
|
|
6551
|
+
if (offset > buffer.length) return false;
|
|
6552
|
+
let frameCount = 0;
|
|
6553
|
+
const operationBudget = { remaining: MAX_GIF_LZW_OPERATIONS };
|
|
6554
|
+
while (offset < buffer.length) {
|
|
6555
|
+
const marker = buffer[offset++];
|
|
6556
|
+
if (marker === 59) return frameCount > 0 && offset === buffer.length;
|
|
6557
|
+
if (marker === 33) {
|
|
6558
|
+
if (offset >= buffer.length) return false;
|
|
6559
|
+
offset += 1;
|
|
6560
|
+
const nextOffset2 = skipGifSubBlocks(buffer, offset);
|
|
6561
|
+
if (nextOffset2 === void 0) return false;
|
|
6562
|
+
offset = nextOffset2;
|
|
6563
|
+
continue;
|
|
6564
|
+
}
|
|
6565
|
+
if (marker !== 44 || offset + 9 > buffer.length) return false;
|
|
6566
|
+
const left = buffer.readUInt16LE(offset);
|
|
6567
|
+
const top = buffer.readUInt16LE(offset + 2);
|
|
6568
|
+
const width = buffer.readUInt16LE(offset + 4);
|
|
6569
|
+
const height = buffer.readUInt16LE(offset + 6);
|
|
6570
|
+
const packed = buffer[offset + 8];
|
|
6571
|
+
offset += 9;
|
|
6572
|
+
if (!validDimensions(width, height)) return false;
|
|
6573
|
+
if (left + width > canvasWidth || top + height > canvasHeight) return false;
|
|
6574
|
+
frameCount += 1;
|
|
6575
|
+
if (frameCount > MAX_GIF_FRAMES) return false;
|
|
6576
|
+
const localColorCount = (packed & 128) !== 0 ? 1 << (packed & 7) + 1 : 0;
|
|
6577
|
+
if (localColorCount > 0) {
|
|
6578
|
+
offset += localColorCount * 3;
|
|
6579
|
+
if (offset > buffer.length) return false;
|
|
6580
|
+
}
|
|
6581
|
+
const colorCount = localColorCount || globalColorCount;
|
|
6582
|
+
if (colorCount === 0 || offset >= buffer.length) return false;
|
|
6583
|
+
const minCodeSize = buffer[offset++];
|
|
6584
|
+
const nextOffset = decodeExactGifLzwPixels(
|
|
6585
|
+
buffer,
|
|
6586
|
+
offset,
|
|
6587
|
+
minCodeSize,
|
|
6588
|
+
width * height,
|
|
6589
|
+
colorCount,
|
|
6590
|
+
operationBudget
|
|
6591
|
+
);
|
|
6592
|
+
if (nextOffset === void 0) return false;
|
|
6593
|
+
offset = nextOffset;
|
|
6594
|
+
}
|
|
6595
|
+
return false;
|
|
6596
|
+
}
|
|
6597
|
+
async function validateDecodedImage(buffer, mime) {
|
|
6598
|
+
if (buffer.length > MAX_IMAGE_BYTES) return "MEDIA_IMAGE_TOO_LARGE";
|
|
6599
|
+
if (mime === "image/gif" && !validateGifLzwStreams(buffer)) return "MEDIA_IMAGE_INVALID";
|
|
6600
|
+
try {
|
|
6601
|
+
const metadata = await sharp(buffer, SHARP_INPUT_OPTIONS).timeout({ seconds: IMAGE_DECODE_TIMEOUT_SECONDS }).metadata();
|
|
6602
|
+
const width = metadata.width;
|
|
6603
|
+
const height = metadata.pageHeight ?? metadata.height;
|
|
6604
|
+
if (metadata.format !== SHARP_FORMAT_BY_MIME[mime]) return "MEDIA_IMAGE_INVALID";
|
|
6605
|
+
if (!validDimensions(width, height)) return "MEDIA_IMAGE_TOO_LARGE";
|
|
6606
|
+
const decoded = await sharp(buffer, SHARP_INPUT_OPTIONS).timeout({ seconds: IMAGE_DECODE_TIMEOUT_SECONDS }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
6607
|
+
const decodedBytes = decoded.data.length;
|
|
6608
|
+
if (!validDimensions(decoded.info.width, decoded.info.height)) return "MEDIA_IMAGE_TOO_LARGE";
|
|
6609
|
+
if (decodedBytes !== decoded.info.width * decoded.info.height * decoded.info.channels) return "MEDIA_IMAGE_INVALID";
|
|
6610
|
+
if (decodedBytes > MAX_DECODED_IMAGE_BYTES) return "MEDIA_IMAGE_TOO_LARGE";
|
|
6611
|
+
return void 0;
|
|
6612
|
+
} catch {
|
|
6613
|
+
return "MEDIA_IMAGE_INVALID";
|
|
6614
|
+
}
|
|
6615
|
+
}
|
|
6616
|
+
async function validateInboundMedia(result) {
|
|
6617
|
+
if (!result.data) return { ok: false, code: "MEDIA_EMPTY_DATA" };
|
|
6618
|
+
const normalized = result.data.replace(/\s/g, "");
|
|
6619
|
+
if (!normalized || normalized.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(normalized)) {
|
|
6620
|
+
return { ok: false, code: "MEDIA_INVALID_BASE64" };
|
|
6621
|
+
}
|
|
6622
|
+
const buffer = Buffer.from(normalized, "base64");
|
|
6623
|
+
if (buffer.length === 0) return { ok: false, code: "MEDIA_EMPTY_DATA" };
|
|
6624
|
+
const declared = MIME_BY_FORMAT[result.format.toLowerCase()] ?? `application/${result.format || "octet-stream"}`;
|
|
6625
|
+
if (result.type === "image") {
|
|
6626
|
+
const detected = imageMimeFromMagic(buffer);
|
|
6627
|
+
if (!detected || detected !== declared) return { ok: false, code: "MEDIA_MAGIC_MISMATCH" };
|
|
6628
|
+
const invalidCode = await validateDecodedImage(buffer, detected);
|
|
6629
|
+
if (invalidCode) return { ok: false, code: invalidCode };
|
|
6630
|
+
}
|
|
6631
|
+
return { ok: true, value: { buffer, mime: declared } };
|
|
6632
|
+
}
|
|
6633
|
+
function safeBodyAfterKnownMediaFailure(baseType, content) {
|
|
6634
|
+
return baseType === 3 ? "[Image unavailable]" : content;
|
|
6635
|
+
}
|
|
6636
|
+
async function saveValidatedInboundMedia(result, save) {
|
|
6637
|
+
const validated = await validateInboundMedia(result);
|
|
6638
|
+
if (!validated.ok) return validated;
|
|
6639
|
+
try {
|
|
6640
|
+
const saved = await save(validated.value.buffer, validated.value.mime, result.filename);
|
|
6641
|
+
if (!saved?.path || typeof saved.path !== "string" || !saved.path.trim()) {
|
|
6642
|
+
return { ok: false, code: "MEDIA_SAVE_FAILED" };
|
|
6643
|
+
}
|
|
6644
|
+
return { ok: true, path: saved.path, mime: validated.value.mime };
|
|
6645
|
+
} catch {
|
|
6646
|
+
return { ok: false, code: "MEDIA_SAVE_FAILED" };
|
|
6647
|
+
}
|
|
6648
|
+
}
|
|
6649
|
+
|
|
6650
|
+
// src/inbound-media-poll.ts
|
|
6651
|
+
async function pollMedia(client, chatId, localId, log, maxAttempts = 15, intervalMs = 1e3) {
|
|
6652
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
6653
|
+
const result = await client.getMedia(chatId, localId);
|
|
6654
|
+
if (result.type === "unsupported") return null;
|
|
6655
|
+
if (result.data) return result;
|
|
6656
|
+
if (attempt < maxAttempts) {
|
|
6657
|
+
log?.info?.(`[wechat:media] pending attempt=${attempt}/${maxAttempts}`);
|
|
6658
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
6659
|
+
}
|
|
6660
|
+
}
|
|
6661
|
+
return null;
|
|
6662
|
+
}
|
|
6663
|
+
|
|
6358
6664
|
// src/monitor.ts
|
|
6359
6665
|
var MEDIA_TYPES = /* @__PURE__ */ new Set([3, 34, 43]);
|
|
6360
6666
|
var HISTORY_CONTEXT_MARKER = "[Chat messages since your last reply - for context]";
|
|
@@ -6371,22 +6677,6 @@ function sleep(ms, signal) {
|
|
|
6371
6677
|
});
|
|
6372
6678
|
});
|
|
6373
6679
|
}
|
|
6374
|
-
async function pollMedia(client, chatId, localId, log, maxAttempts = 15, intervalMs = 1e3) {
|
|
6375
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
6376
|
-
const result = await client.getMedia(chatId, localId);
|
|
6377
|
-
if (result.type === "unsupported") {
|
|
6378
|
-
return null;
|
|
6379
|
-
}
|
|
6380
|
-
if (result.data) {
|
|
6381
|
-
return result;
|
|
6382
|
-
}
|
|
6383
|
-
if (attempt < maxAttempts) {
|
|
6384
|
-
log?.info?.(`[media] Attempt ${attempt}/${maxAttempts} for ${chatId}:${localId} returned no data, retrying...`);
|
|
6385
|
-
await new Promise((r) => setTimeout(r, intervalMs));
|
|
6386
|
-
}
|
|
6387
|
-
}
|
|
6388
|
-
return null;
|
|
6389
|
-
}
|
|
6390
6680
|
function enqueueWeChatSystemEvent(text, contextKey) {
|
|
6391
6681
|
try {
|
|
6392
6682
|
const core = getWeChatRuntime();
|
|
@@ -6657,44 +6947,28 @@ async function prepareMessage(client, msg, chatId, chat, liveAccount, policy, lo
|
|
|
6657
6947
|
const result = await pollMedia(client, chatId, msg.localId, log);
|
|
6658
6948
|
if (result && result.data && result.type !== "unsupported") {
|
|
6659
6949
|
hasMedia = true;
|
|
6660
|
-
log?.info?.(`[wechat
|
|
6661
|
-
const
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
png: "image/png",
|
|
6665
|
-
gif: "image/gif",
|
|
6666
|
-
mp3: "audio/mpeg",
|
|
6667
|
-
pdf: "application/pdf",
|
|
6668
|
-
doc: "application/msword",
|
|
6669
|
-
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
6670
|
-
xls: "application/vnd.ms-excel",
|
|
6671
|
-
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
6672
|
-
ppt: "application/vnd.ms-powerpoint",
|
|
6673
|
-
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
6674
|
-
zip: "application/zip",
|
|
6675
|
-
txt: "text/plain"
|
|
6676
|
-
};
|
|
6677
|
-
mediaMime = mimeMap[result.format] ?? `application/${result.format || "octet-stream"}`;
|
|
6678
|
-
const buf = Buffer.from(result.data, "base64");
|
|
6679
|
-
const saved = await core.channel.media.saveMediaBuffer(
|
|
6680
|
-
buf,
|
|
6681
|
-
mediaMime,
|
|
6682
|
-
"inbound",
|
|
6683
|
-
void 0,
|
|
6684
|
-
result.filename
|
|
6950
|
+
log?.info?.(`[wechat:media] inbound media received type=${result.type} format=${result.format}`);
|
|
6951
|
+
const saved = await saveValidatedInboundMedia(
|
|
6952
|
+
result,
|
|
6953
|
+
async (buffer, mime, filename) => core.channel.media.saveMediaBuffer(buffer, mime, "inbound", void 0, filename)
|
|
6685
6954
|
);
|
|
6686
|
-
|
|
6687
|
-
|
|
6955
|
+
if (!saved.ok) {
|
|
6956
|
+
log?.error?.(`[wechat:media] inbound media rejected code=${saved.code}`);
|
|
6957
|
+
} else {
|
|
6958
|
+
mediaMime = saved.mime;
|
|
6959
|
+
mediaPath = saved.path;
|
|
6960
|
+
log?.info?.("[wechat:media] inbound media saved");
|
|
6961
|
+
}
|
|
6688
6962
|
} else if (MEDIA_TYPES.has(baseType)) {
|
|
6689
6963
|
hasMedia = true;
|
|
6690
|
-
log?.info?.(
|
|
6964
|
+
log?.info?.("[wechat:media] inbound media unavailable after retries");
|
|
6691
6965
|
}
|
|
6692
6966
|
} catch (err) {
|
|
6693
|
-
log?.error?.(
|
|
6967
|
+
log?.error?.("[wechat:media] inbound media failed code=MEDIA_DOWNLOAD_FAILED");
|
|
6694
6968
|
}
|
|
6695
6969
|
}
|
|
6696
6970
|
const timestamp = new Date(msg.timestamp).getTime();
|
|
6697
|
-
let rawBody = msg.content || "";
|
|
6971
|
+
let rawBody = mediaPath ? msg.content || "" : safeBodyAfterKnownMediaFailure(baseType, msg.content || "");
|
|
6698
6972
|
if (mediaPath && mediaMime) {
|
|
6699
6973
|
if (!rawBody) {
|
|
6700
6974
|
if (mediaMime.startsWith("audio/")) {
|
|
@@ -6762,7 +7036,7 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, polic
|
|
|
6762
7036
|
const mediaPath = mediaMsg?.mediaPath;
|
|
6763
7037
|
const mediaMime = mediaMsg?.mediaMime;
|
|
6764
7038
|
log?.info?.(
|
|
6765
|
-
`[wechat:${liveAccount.accountId}] Dispatching segment: ${segment.length} msg(s), last=${msg.localId}${mediaPath ?
|
|
7039
|
+
`[wechat:${liveAccount.accountId}] Dispatching segment: ${segment.length} msg(s), last=${msg.localId}${mediaPath ? " media=present" : ""}`
|
|
6766
7040
|
);
|
|
6767
7041
|
const hasControlCommand = allowTextCommands && core.channel.commands.isControlCommandMessage(commandBody, cfg);
|
|
6768
7042
|
const commandAuthorized = await resolveWeChatCommandAuthorization({
|
|
@@ -6802,7 +7076,7 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, polic
|
|
|
6802
7076
|
try {
|
|
6803
7077
|
const route = core.channel.routing.resolveAgentRoute({
|
|
6804
7078
|
cfg,
|
|
6805
|
-
channel:
|
|
7079
|
+
channel: OPENCLAW_CHANNEL_ID,
|
|
6806
7080
|
accountId: liveAccount.accountId,
|
|
6807
7081
|
peer: {
|
|
6808
7082
|
kind: isGroup ? "group" : "direct",
|
|
@@ -6874,21 +7148,21 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, polic
|
|
|
6874
7148
|
RawBody: rawBody,
|
|
6875
7149
|
CommandBody: commandBody,
|
|
6876
7150
|
InboundHistory: inboundHistory,
|
|
6877
|
-
From: isGroup ?
|
|
6878
|
-
To:
|
|
7151
|
+
From: isGroup ? `${OPENCLAW_CHANNEL_ID}:group:${chatId}` : `${OPENCLAW_CHANNEL_ID}:${senderId}`,
|
|
7152
|
+
To: `${OPENCLAW_CHANNEL_ID}:${chatId}`,
|
|
6879
7153
|
SessionKey: route.sessionKey,
|
|
6880
7154
|
AccountId: route.accountId,
|
|
6881
7155
|
ChatType: isGroup ? "group" : "direct",
|
|
6882
7156
|
ConversationLabel: fromLabel,
|
|
6883
7157
|
SenderName: senderName || void 0,
|
|
6884
7158
|
SenderId: senderId,
|
|
6885
|
-
Provider:
|
|
6886
|
-
Surface:
|
|
6887
|
-
MessageSid:
|
|
7159
|
+
Provider: OPENCLAW_CHANNEL_ID,
|
|
7160
|
+
Surface: OPENCLAW_CHANNEL_ID,
|
|
7161
|
+
MessageSid: `${OPENCLAW_CHANNEL_ID}:${chatId}:${msg.localId}`,
|
|
6888
7162
|
WasMentioned: isGroup ? mentionGate.effectiveWasMentioned : void 0,
|
|
6889
7163
|
CommandAuthorized: commandAuthorized,
|
|
6890
|
-
OriginatingChannel:
|
|
6891
|
-
OriginatingTo:
|
|
7164
|
+
OriginatingChannel: OPENCLAW_CHANNEL_ID,
|
|
7165
|
+
OriginatingTo: `${OPENCLAW_CHANNEL_ID}:${chatId}`,
|
|
6892
7166
|
...mediaPath ? { MediaPath: mediaPath, MediaUrl: mediaPath, MediaType: mediaMime } : {},
|
|
6893
7167
|
...msg.reply ? {
|
|
6894
7168
|
ReplyToBody: msg.reply.content.length > 50 ? msg.reply.content.slice(0, 50) + "..." : msg.reply.content,
|
|
@@ -6908,7 +7182,7 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, polic
|
|
|
6908
7182
|
const { onModelSelected, ...prefixOptions } = createChannelReplyPipeline({
|
|
6909
7183
|
cfg,
|
|
6910
7184
|
agentId: route.agentId,
|
|
6911
|
-
channel:
|
|
7185
|
+
channel: OPENCLAW_CHANNEL_ID,
|
|
6912
7186
|
accountId: liveAccount.accountId
|
|
6913
7187
|
});
|
|
6914
7188
|
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
@@ -6920,7 +7194,7 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, polic
|
|
|
6920
7194
|
const mediaList = payload.mediaUrls?.length ? payload.mediaUrls : payload.mediaUrl ? [payload.mediaUrl] : [];
|
|
6921
7195
|
const tableMode = core.channel.text.resolveMarkdownTableMode({
|
|
6922
7196
|
cfg,
|
|
6923
|
-
channel:
|
|
7197
|
+
channel: OPENCLAW_CHANNEL_ID,
|
|
6924
7198
|
accountId: liveAccount.accountId
|
|
6925
7199
|
});
|
|
6926
7200
|
const text = core.channel.text.convertMarkdownTables(
|
|
@@ -6989,7 +7263,7 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, polic
|
|
|
6989
7263
|
}
|
|
6990
7264
|
});
|
|
6991
7265
|
core.channel.activity?.record?.({
|
|
6992
|
-
channel:
|
|
7266
|
+
channel: OPENCLAW_CHANNEL_ID,
|
|
6993
7267
|
accountId: liveAccount.accountId,
|
|
6994
7268
|
direction: "inbound",
|
|
6995
7269
|
at: timestamp
|
|
@@ -7019,7 +7293,7 @@ async function processUnreadChat(client, chat, lastSeenId, equalCursorUnreadHand
|
|
|
7019
7293
|
const core = getWeChatRuntime();
|
|
7020
7294
|
const liveAccount = resolveWeChatAccount(cfg, account.accountId) ?? account;
|
|
7021
7295
|
const chatId = chat.username ?? chat.id;
|
|
7022
|
-
const storeAllowFrom = await core.channel.pairing.readAllowFromStore({ channel:
|
|
7296
|
+
const storeAllowFrom = await core.channel.pairing.readAllowFromStore({ channel: OPENCLAW_CHANNEL_ID, accountId: liveAccount.accountId, env: process.env }).catch(() => []);
|
|
7023
7297
|
const policy = resolveWeChatPolicyContext({
|
|
7024
7298
|
account: liveAccount,
|
|
7025
7299
|
cfg,
|
|
@@ -7028,7 +7302,7 @@ async function processUnreadChat(client, chat, lastSeenId, equalCursorUnreadHand
|
|
|
7028
7302
|
});
|
|
7029
7303
|
const allowTextCommands = core.channel.commands.shouldHandleTextCommands({
|
|
7030
7304
|
cfg,
|
|
7031
|
-
surface:
|
|
7305
|
+
surface: OPENCLAW_CHANNEL_ID
|
|
7032
7306
|
});
|
|
7033
7307
|
let opened = false;
|
|
7034
7308
|
const openChatIfNeeded = async () => {
|
|
@@ -7278,7 +7552,7 @@ async function processUnreadChat(client, chat, lastSeenId, equalCursorUnreadHand
|
|
|
7278
7552
|
}
|
|
7279
7553
|
if (decision.action === "defer") {
|
|
7280
7554
|
log?.info?.(
|
|
7281
|
-
`[wechat:${liveAccount.accountId}] Catch-up hold for ${chatId} (budget ${liveAccount.catchUpChatBudget}); raise channels.
|
|
7555
|
+
`[wechat:${liveAccount.accountId}] Catch-up hold for ${chatId} (budget ${liveAccount.catchUpChatBudget}); raise channels.${OPENCLAW_CHANNEL_ID}.catchUpChatBudget to continue`
|
|
7282
7556
|
);
|
|
7283
7557
|
return "deferred";
|
|
7284
7558
|
}
|
|
@@ -7344,7 +7618,7 @@ function collectWeChatStatusIssues(accounts) {
|
|
|
7344
7618
|
for (const snapshot of accounts) {
|
|
7345
7619
|
if (snapshot.connected === false) {
|
|
7346
7620
|
issues.push({
|
|
7347
|
-
channel: "wechat",
|
|
7621
|
+
channel: "agent-wechat",
|
|
7348
7622
|
accountId: snapshot.accountId,
|
|
7349
7623
|
kind: "runtime",
|
|
7350
7624
|
message: snapshot.lastError ? `Cannot reach agent-wechat server: ${snapshot.lastError}` : "Cannot reach agent-wechat server.",
|
|
@@ -7352,7 +7626,7 @@ function collectWeChatStatusIssues(accounts) {
|
|
|
7352
7626
|
});
|
|
7353
7627
|
} else if (snapshot.authStatus === "app_not_running") {
|
|
7354
7628
|
issues.push({
|
|
7355
|
-
channel: "wechat",
|
|
7629
|
+
channel: "agent-wechat",
|
|
7356
7630
|
accountId: snapshot.accountId,
|
|
7357
7631
|
kind: "runtime",
|
|
7358
7632
|
message: "WeChat application is not running. It should restart automatically.",
|
|
@@ -7360,11 +7634,11 @@ function collectWeChatStatusIssues(accounts) {
|
|
|
7360
7634
|
});
|
|
7361
7635
|
} else if (snapshot.linked === false) {
|
|
7362
7636
|
issues.push({
|
|
7363
|
-
channel: "wechat",
|
|
7637
|
+
channel: "agent-wechat",
|
|
7364
7638
|
accountId: snapshot.accountId,
|
|
7365
7639
|
kind: "auth",
|
|
7366
7640
|
message: "WeChat session not authenticated.",
|
|
7367
|
-
fix: "Run: openclaw channels login --channel wechat"
|
|
7641
|
+
fix: "Run: openclaw channels login --channel agent-wechat"
|
|
7368
7642
|
});
|
|
7369
7643
|
}
|
|
7370
7644
|
}
|
|
@@ -7783,7 +8057,7 @@ async function sendWeChatText(cfg, to, text) {
|
|
|
7783
8057
|
}
|
|
7784
8058
|
throw new Error(result.error ?? "Send failed");
|
|
7785
8059
|
}
|
|
7786
|
-
return
|
|
8060
|
+
return `${OPENCLAW_CHANNEL_ID}:${to}:${Date.now()}`;
|
|
7787
8061
|
}
|
|
7788
8062
|
async function sendWeChatMedia(cfg, to, text, mediaUrl) {
|
|
7789
8063
|
const account = resolveWeChatAccount(cfg);
|
|
@@ -7796,7 +8070,7 @@ async function sendWeChatMedia(cfg, to, text, mediaUrl) {
|
|
|
7796
8070
|
source: "openclaw"
|
|
7797
8071
|
});
|
|
7798
8072
|
if (!result.success) throw new Error(result.error ?? "Send failed");
|
|
7799
|
-
return
|
|
8073
|
+
return `${OPENCLAW_CHANNEL_ID}:${to}:${Date.now()}`;
|
|
7800
8074
|
}
|
|
7801
8075
|
const fsmod = await import("fs/promises");
|
|
7802
8076
|
const pathmod = await import("path");
|
|
@@ -7833,10 +8107,10 @@ async function sendWeChatMedia(cfg, to, text, mediaUrl) {
|
|
|
7833
8107
|
interPartDelayMs: account.mediaPartDelayMs,
|
|
7834
8108
|
source: "openclaw"
|
|
7835
8109
|
});
|
|
7836
|
-
return
|
|
8110
|
+
return `${OPENCLAW_CHANNEL_ID}:${to}:${requestId}`;
|
|
7837
8111
|
}
|
|
7838
8112
|
var wechatMessageAdapter = createChannelMessageAdapterFromOutbound({
|
|
7839
|
-
id:
|
|
8113
|
+
id: OPENCLAW_CHANNEL_ID,
|
|
7840
8114
|
capabilities: {
|
|
7841
8115
|
text: true,
|
|
7842
8116
|
media: true,
|
|
@@ -7844,26 +8118,26 @@ var wechatMessageAdapter = createChannelMessageAdapterFromOutbound({
|
|
|
7844
8118
|
},
|
|
7845
8119
|
outbound: {
|
|
7846
8120
|
sendText: async ({ cfg, to, text }) => ({
|
|
7847
|
-
channel:
|
|
8121
|
+
channel: OPENCLAW_CHANNEL_ID,
|
|
7848
8122
|
messageId: await sendWeChatText(cfg, to, text)
|
|
7849
8123
|
}),
|
|
7850
8124
|
sendMedia: async ({ cfg, to, text, mediaUrl }) => ({
|
|
7851
|
-
channel:
|
|
8125
|
+
channel: OPENCLAW_CHANNEL_ID,
|
|
7852
8126
|
messageId: await sendWeChatMedia(cfg, to, text, mediaUrl)
|
|
7853
8127
|
})
|
|
7854
8128
|
}
|
|
7855
8129
|
});
|
|
7856
8130
|
var meta = {
|
|
7857
|
-
id:
|
|
8131
|
+
id: OPENCLAW_CHANNEL_ID,
|
|
7858
8132
|
label: "WeChat",
|
|
7859
|
-
selectionLabel: "WeChat (
|
|
8133
|
+
selectionLabel: "WeChat (agent-wechat)",
|
|
7860
8134
|
blurb: "WeChat messaging via agent-wechat container.",
|
|
7861
8135
|
docsPath: "wechat",
|
|
7862
|
-
aliases: [
|
|
8136
|
+
aliases: [],
|
|
7863
8137
|
order: 80
|
|
7864
8138
|
};
|
|
7865
8139
|
var wechatPlugin = {
|
|
7866
|
-
id:
|
|
8140
|
+
id: OPENCLAW_CHANNEL_ID,
|
|
7867
8141
|
meta,
|
|
7868
8142
|
gatewayMethods: ["web.login.start", "web.login.wait"],
|
|
7869
8143
|
capabilities: {
|
|
@@ -7873,7 +8147,7 @@ var wechatPlugin = {
|
|
|
7873
8147
|
media: true,
|
|
7874
8148
|
reply: true
|
|
7875
8149
|
},
|
|
7876
|
-
reload: { configPrefixes: ["channels.wechat"] },
|
|
8150
|
+
reload: { configPrefixes: [`channels.${OPENCLAW_CHANNEL_ID}`, "channels.wechat"] },
|
|
7877
8151
|
configSchema: {
|
|
7878
8152
|
schema: {
|
|
7879
8153
|
type: "object",
|
|
@@ -7949,23 +8223,23 @@ var wechatPlugin = {
|
|
|
7949
8223
|
},
|
|
7950
8224
|
isEnabled: (account) => account.enabled && !!account.serverUrl,
|
|
7951
8225
|
isConfigured: (account) => !!account.serverUrl,
|
|
7952
|
-
unconfiguredReason: () => "No serverUrl configured. Run: openclaw channels setup wechat"
|
|
8226
|
+
unconfiguredReason: () => "No serverUrl configured. Run: openclaw channels setup agent-wechat"
|
|
7953
8227
|
},
|
|
7954
8228
|
// ---- Security adapter ----
|
|
7955
8229
|
security: {
|
|
7956
8230
|
resolveDmPolicy: ({ account }) => ({
|
|
7957
8231
|
policy: account.dmPolicy ?? "disabled",
|
|
7958
8232
|
allowFrom: account.allowFrom ?? [],
|
|
7959
|
-
allowFromPath:
|
|
7960
|
-
policyPath:
|
|
7961
|
-
approveHint:
|
|
7962
|
-
normalizeEntry: (raw) => raw.replace(/^wechat:/i, "").trim()
|
|
8233
|
+
allowFromPath: `channels.${OPENCLAW_CHANNEL_ID}.allowFrom`,
|
|
8234
|
+
policyPath: `channels.${OPENCLAW_CHANNEL_ID}.dmPolicy`,
|
|
8235
|
+
approveHint: `Add the wxid to channels.${OPENCLAW_CHANNEL_ID}.allowFrom`,
|
|
8236
|
+
normalizeEntry: (raw) => raw.replace(/^(agent-wechat|wechat):/i, "").trim()
|
|
7963
8237
|
})
|
|
7964
8238
|
},
|
|
7965
8239
|
// ---- Groups adapter ----
|
|
7966
8240
|
groups: {
|
|
7967
8241
|
resolveRequireMention: ({ cfg, groupId }) => {
|
|
7968
|
-
const wechat = cfg?.channels?.wechat;
|
|
8242
|
+
const wechat = cfg?.channels?.[OPENCLAW_CHANNEL_ID] ?? cfg?.channels?.wechat;
|
|
7969
8243
|
if (!wechat) return true;
|
|
7970
8244
|
if (!groupId) {
|
|
7971
8245
|
return wechat.groups?.["*"]?.requireMention ?? true;
|
|
@@ -7990,10 +8264,10 @@ var wechatPlugin = {
|
|
|
7990
8264
|
},
|
|
7991
8265
|
// ---- Messaging adapter ----
|
|
7992
8266
|
messaging: {
|
|
7993
|
-
normalizeTarget: (raw) => raw.replace(/^wechat:/i, "").trim() || void 0,
|
|
8267
|
+
normalizeTarget: (raw) => raw.replace(/^(agent-wechat|wechat):/i, "").trim() || void 0,
|
|
7994
8268
|
targetResolver: {
|
|
7995
8269
|
looksLikeId: (raw) => {
|
|
7996
|
-
const stripped = raw.replace(/^wechat:/i, "").trim();
|
|
8270
|
+
const stripped = raw.replace(/^(agent-wechat|wechat):/i, "").trim();
|
|
7997
8271
|
return stripped.includes("@chatroom") || stripped.startsWith("wxid_");
|
|
7998
8272
|
},
|
|
7999
8273
|
hint: "WeChat ID (wxid_xxx or xxx@chatroom)"
|
|
@@ -8003,11 +8277,11 @@ var wechatPlugin = {
|
|
|
8003
8277
|
outbound: {
|
|
8004
8278
|
deliveryMode: "direct",
|
|
8005
8279
|
sendText: async ({ cfg, to, text }) => ({
|
|
8006
|
-
channel:
|
|
8280
|
+
channel: OPENCLAW_CHANNEL_ID,
|
|
8007
8281
|
messageId: await sendWeChatText(cfg, to, text)
|
|
8008
8282
|
}),
|
|
8009
8283
|
sendMedia: async ({ cfg, to, text, mediaUrl }) => ({
|
|
8010
|
-
channel:
|
|
8284
|
+
channel: OPENCLAW_CHANNEL_ID,
|
|
8011
8285
|
messageId: await sendWeChatMedia(cfg, to, text, mediaUrl)
|
|
8012
8286
|
})
|
|
8013
8287
|
},
|
|
@@ -8034,7 +8308,7 @@ var wechatPlugin = {
|
|
|
8034
8308
|
accountId ?? void 0
|
|
8035
8309
|
);
|
|
8036
8310
|
if (!account?.serverUrl) {
|
|
8037
|
-
return { message: "No serverUrl configured. Run: openclaw channels setup wechat" };
|
|
8311
|
+
return { message: "No serverUrl configured. Run: openclaw channels setup agent-wechat" };
|
|
8038
8312
|
}
|
|
8039
8313
|
const client = new WeChatClient({ baseUrl: account.serverUrl, token: account.token });
|
|
8040
8314
|
try {
|
|
@@ -8075,7 +8349,7 @@ var wechatPlugin = {
|
|
|
8075
8349
|
);
|
|
8076
8350
|
if (!account?.serverUrl) {
|
|
8077
8351
|
throw new Error(
|
|
8078
|
-
"No serverUrl configured. Run: openclaw channels setup wechat"
|
|
8352
|
+
"No serverUrl configured. Run: openclaw channels setup agent-wechat"
|
|
8079
8353
|
);
|
|
8080
8354
|
}
|
|
8081
8355
|
const client = new WeChatClient({ baseUrl: account.serverUrl, token: account.token });
|
|
@@ -8235,8 +8509,8 @@ Error: ${event.message}`);
|
|
|
8235
8509
|
...cfg,
|
|
8236
8510
|
channels: {
|
|
8237
8511
|
...cfg.channels,
|
|
8238
|
-
|
|
8239
|
-
...cfg.channels?.wechat,
|
|
8512
|
+
[OPENCLAW_CHANNEL_ID]: {
|
|
8513
|
+
...cfg.channels?.[OPENCLAW_CHANNEL_ID] ?? cfg.channels?.wechat,
|
|
8240
8514
|
enabled: true,
|
|
8241
8515
|
serverUrl,
|
|
8242
8516
|
...token ? { token } : {}
|
|
@@ -8249,7 +8523,7 @@ Error: ${event.message}`);
|
|
|
8249
8523
|
|
|
8250
8524
|
// index.ts
|
|
8251
8525
|
var plugin = {
|
|
8252
|
-
id:
|
|
8526
|
+
id: OPENCLAW_CHANNEL_ID,
|
|
8253
8527
|
name: "WeChat",
|
|
8254
8528
|
description: "WeChat channel via agent-wechat container",
|
|
8255
8529
|
configSchema: emptyPluginConfigSchema(),
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "wechat",
|
|
3
|
-
"channels": ["wechat"],
|
|
2
|
+
"id": "agent-wechat",
|
|
3
|
+
"channels": ["agent-wechat"],
|
|
4
4
|
"configSchema": {
|
|
5
5
|
"type": "object",
|
|
6
6
|
"additionalProperties": false,
|
|
7
7
|
"properties": {}
|
|
8
8
|
},
|
|
9
9
|
"channelConfigs": {
|
|
10
|
-
"wechat": {
|
|
10
|
+
"agent-wechat": {
|
|
11
11
|
"label": "WeChat",
|
|
12
12
|
"description": "WeChat messaging via agent-wechat container.",
|
|
13
13
|
"schema": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kyan-du/agent-wechat-openclaw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"!dist/.release-metafile.json"
|
|
9
9
|
],
|
|
10
10
|
"scripts": {
|
|
11
|
-
"build": "esbuild index.ts --bundle --format=esm --platform=node --outfile=dist/index.js --metafile=dist/.release-metafile.json --external:openclaw",
|
|
11
|
+
"build": "esbuild index.ts --bundle --format=esm --platform=node --outfile=dist/index.js --metafile=dist/.release-metafile.json --external:openclaw --external:sharp",
|
|
12
12
|
"typecheck": "tsc --noEmit",
|
|
13
13
|
"test": "node --test --experimental-strip-types src/*.test.ts"
|
|
14
14
|
},
|
|
@@ -24,12 +24,10 @@
|
|
|
24
24
|
"./dist/index.js"
|
|
25
25
|
],
|
|
26
26
|
"channel": {
|
|
27
|
-
"id": "wechat",
|
|
27
|
+
"id": "agent-wechat",
|
|
28
28
|
"label": "WeChat",
|
|
29
|
-
"selectionLabel": "WeChat (
|
|
30
|
-
"aliases": [
|
|
31
|
-
"weixin"
|
|
32
|
-
],
|
|
29
|
+
"selectionLabel": "WeChat (agent-wechat)",
|
|
30
|
+
"aliases": [],
|
|
33
31
|
"order": 80
|
|
34
32
|
},
|
|
35
33
|
"install": {
|
|
@@ -48,5 +46,8 @@
|
|
|
48
46
|
"main": "./dist/index.js",
|
|
49
47
|
"exports": {
|
|
50
48
|
".": "./dist/index.js"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"sharp": "^0.35.3"
|
|
51
52
|
}
|
|
52
53
|
}
|