@kyan-du/agent-wechat-openclaw 0.14.3 → 0.14.5
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/dist/index.js +377 -38
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5304,6 +5304,15 @@ var downloadFileParamsSchema = external_exports.object({
|
|
|
5304
5304
|
var downloadFileResultSchema = openChatResultSchema.extend({
|
|
5305
5305
|
clicked: external_exports.boolean().optional()
|
|
5306
5306
|
});
|
|
5307
|
+
var materializeChatHistoryParamsSchema = external_exports.object({
|
|
5308
|
+
chatId: external_exports.string().min(1),
|
|
5309
|
+
title: external_exports.string().min(1).optional(),
|
|
5310
|
+
localId: external_exports.number().int().optional(),
|
|
5311
|
+
executionTimeoutMs: external_exports.number().int().positive().optional()
|
|
5312
|
+
});
|
|
5313
|
+
var materializeChatHistoryResultSchema = openChatResultSchema.extend({
|
|
5314
|
+
clicked: external_exports.boolean().optional()
|
|
5315
|
+
});
|
|
5307
5316
|
var idempotencyKeySchema = external_exports.string().min(1).max(128).regex(/^[A-Za-z0-9._:-]+$/);
|
|
5308
5317
|
var forwardedMessageNodeSchema = external_exports.lazy(() => external_exports.object({
|
|
5309
5318
|
sender: external_exports.string().optional(),
|
|
@@ -5417,7 +5426,8 @@ var mediaResultSchema = external_exports.object({
|
|
|
5417
5426
|
format: external_exports.string(),
|
|
5418
5427
|
filename: external_exports.string(),
|
|
5419
5428
|
source: external_exports.enum(["original", "thumbnail"]).optional(),
|
|
5420
|
-
errorCode: external_exports.string().optional()
|
|
5429
|
+
errorCode: external_exports.string().optional(),
|
|
5430
|
+
items: external_exports.array(external_exports.lazy(() => mediaResultSchema)).optional()
|
|
5421
5431
|
});
|
|
5422
5432
|
var agentConfigSchema = external_exports.object({
|
|
5423
5433
|
maxTurns: external_exports.number().int().positive().default(30),
|
|
@@ -5501,6 +5511,17 @@ var WeChatClient = class {
|
|
|
5501
5511
|
await throwHttpError(res);
|
|
5502
5512
|
return res.json();
|
|
5503
5513
|
}
|
|
5514
|
+
async put(path, body, signal) {
|
|
5515
|
+
const res = await fetch(`${this.base}${path}`, {
|
|
5516
|
+
method: "PUT",
|
|
5517
|
+
headers: this.headers,
|
|
5518
|
+
body: body != null ? JSON.stringify(body) : void 0,
|
|
5519
|
+
signal
|
|
5520
|
+
});
|
|
5521
|
+
if (!res.ok)
|
|
5522
|
+
await throwHttpError(res);
|
|
5523
|
+
return res.json();
|
|
5524
|
+
}
|
|
5504
5525
|
// ---- Status ----
|
|
5505
5526
|
async status() {
|
|
5506
5527
|
return this.get("/api/status");
|
|
@@ -5522,12 +5543,18 @@ var WeChatClient = class {
|
|
|
5522
5543
|
return this.post("/api/status/auth/reset");
|
|
5523
5544
|
}
|
|
5524
5545
|
// ---- Chats ----
|
|
5525
|
-
async listChatsPage(limit, cursor, unreadOnly) {
|
|
5526
|
-
const page = await this.get(`/api/chats${qs({ limit, cursor, unreadOnly })}`);
|
|
5546
|
+
async listChatsPage(limit, cursor, unreadOnly, interestOnly) {
|
|
5547
|
+
const page = await this.get(`/api/chats${qs({ limit, cursor, unreadOnly, interestOnly })}`);
|
|
5527
5548
|
if (page.errorCode)
|
|
5528
5549
|
throw new WeChatHttpError(400, "Bad Request", page.errorCode, page.errorCode);
|
|
5529
5550
|
return page;
|
|
5530
5551
|
}
|
|
5552
|
+
async getMonitorInterest() {
|
|
5553
|
+
return this.get("/api/interest");
|
|
5554
|
+
}
|
|
5555
|
+
async setMonitorInterest(interest) {
|
|
5556
|
+
return this.put("/api/interest", interest);
|
|
5557
|
+
}
|
|
5531
5558
|
async listChats(limit) {
|
|
5532
5559
|
return (await this.listChatsPage(limit)).items;
|
|
5533
5560
|
}
|
|
@@ -5546,6 +5573,10 @@ var WeChatClient = class {
|
|
|
5546
5573
|
async downloadFile(chatId, filename, signal, executionTimeoutMs) {
|
|
5547
5574
|
return this.post(`/api/chats/${encodeURIComponent(chatId)}/download-file${qs({ filename, executionTimeoutMs })}`, void 0, signal);
|
|
5548
5575
|
}
|
|
5576
|
+
async materializeChatHistory(chatId, options, signal) {
|
|
5577
|
+
const { title, localId, executionTimeoutMs } = options ?? {};
|
|
5578
|
+
return this.post(`/api/chats/${encodeURIComponent(chatId)}/materialize-chat-history${qs({ title, localId, executionTimeoutMs })}`, void 0, signal);
|
|
5579
|
+
}
|
|
5549
5580
|
async listGroupMembersPage(groupId, limit, cursor) {
|
|
5550
5581
|
const page = groupMembersPageSchema.parse(await this.get(`/api/groups/${encodeURIComponent(groupId)}/members${qs({ limit, cursor })}`));
|
|
5551
5582
|
if (page.errorCode)
|
|
@@ -5671,6 +5702,65 @@ var WeChatClient = class {
|
|
|
5671
5702
|
// src/monitor.ts
|
|
5672
5703
|
import { createChannelMessageReplyPipeline as createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-outbound";
|
|
5673
5704
|
|
|
5705
|
+
// src/monitor-interest.ts
|
|
5706
|
+
function normalizeAllowFrom(values) {
|
|
5707
|
+
const out = [];
|
|
5708
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5709
|
+
for (const entry of values ?? []) {
|
|
5710
|
+
let id = String(entry).trim();
|
|
5711
|
+
if (!id) continue;
|
|
5712
|
+
if (id !== "*") {
|
|
5713
|
+
id = id.replace(/^(agent-wechat|wechat):/i, "").trim();
|
|
5714
|
+
}
|
|
5715
|
+
if (!id || seen.has(id)) continue;
|
|
5716
|
+
seen.add(id);
|
|
5717
|
+
out.push(id);
|
|
5718
|
+
}
|
|
5719
|
+
return out;
|
|
5720
|
+
}
|
|
5721
|
+
function buildMonitorInterest(account) {
|
|
5722
|
+
return {
|
|
5723
|
+
dmPolicy: account.dmPolicy ?? "disabled",
|
|
5724
|
+
dmAllowFrom: normalizeAllowFrom(account.allowFrom),
|
|
5725
|
+
groupPolicy: account.groupPolicy ?? "allowlist",
|
|
5726
|
+
groupAllowFrom: normalizeAllowFrom(account.groupAllowFrom),
|
|
5727
|
+
includeSystemFeeds: true
|
|
5728
|
+
};
|
|
5729
|
+
}
|
|
5730
|
+
function monitorInterestFingerprint(interest) {
|
|
5731
|
+
return JSON.stringify({
|
|
5732
|
+
dmPolicy: interest.dmPolicy,
|
|
5733
|
+
dmAllowFrom: [...interest.dmAllowFrom ?? []].sort(),
|
|
5734
|
+
groupPolicy: interest.groupPolicy ?? "allowlist",
|
|
5735
|
+
groupAllowFrom: [...interest.groupAllowFrom ?? []].sort(),
|
|
5736
|
+
includeSystemFeeds: interest.includeSystemFeeds !== false
|
|
5737
|
+
});
|
|
5738
|
+
}
|
|
5739
|
+
function shouldListChatsInterestOnly(interest) {
|
|
5740
|
+
return interest.dmPolicy === "allowlist" || interest.dmPolicy === "disabled";
|
|
5741
|
+
}
|
|
5742
|
+
async function ensureMonitorInterestSynced(client, account, log) {
|
|
5743
|
+
const interest = buildMonitorInterest(account);
|
|
5744
|
+
const desiredFp = monitorInterestFingerprint(interest);
|
|
5745
|
+
try {
|
|
5746
|
+
const current = await client.getMonitorInterest();
|
|
5747
|
+
const serverInterest = current.interest;
|
|
5748
|
+
if (serverInterest && monitorInterestFingerprint(serverInterest) === desiredFp) {
|
|
5749
|
+
return { interest, synced: true };
|
|
5750
|
+
}
|
|
5751
|
+
await client.setMonitorInterest(interest);
|
|
5752
|
+
log?.info?.(
|
|
5753
|
+
`[wechat:${account.accountId}] Synced monitor interest to agent-server (dmPolicy=${interest.dmPolicy}, dmAllowFrom=${interest.dmAllowFrom.length})`
|
|
5754
|
+
);
|
|
5755
|
+
return { interest, synced: true };
|
|
5756
|
+
} catch (err) {
|
|
5757
|
+
log?.error?.(
|
|
5758
|
+
`[wechat:${account.accountId}] Failed to sync monitor interest: ${err}`
|
|
5759
|
+
);
|
|
5760
|
+
return { interest, synced: false };
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5763
|
+
|
|
5674
5764
|
// src/catch-up.ts
|
|
5675
5765
|
function recoveryCursor(selection) {
|
|
5676
5766
|
return selection.cursor;
|
|
@@ -5824,7 +5914,7 @@ async function listInitialMonitorChatSnapshot(client, state, pageBudget = DEFAUL
|
|
|
5824
5914
|
const chats = [];
|
|
5825
5915
|
let cursor;
|
|
5826
5916
|
for (let pages = 0; pages < pageBudget; pages += 1) {
|
|
5827
|
-
const page = await client.listChatsPage(MONITOR_CHAT_PAGE_LIMIT, cursor);
|
|
5917
|
+
const page = await client.listChatsPage(MONITOR_CHAT_PAGE_LIMIT, cursor, void 0, state.interestOnly);
|
|
5828
5918
|
chats.push(...page.items);
|
|
5829
5919
|
cursor = page.nextCursor;
|
|
5830
5920
|
if (!cursor) {
|
|
@@ -5837,7 +5927,7 @@ async function listInitialMonitorChatSnapshot(client, state, pageBudget = DEFAUL
|
|
|
5837
5927
|
}
|
|
5838
5928
|
async function listNextMonitorChatPage(client, state) {
|
|
5839
5929
|
const isInitialSnapshot = !state.initialScanComplete;
|
|
5840
|
-
const page = await client.listChatsPage(MONITOR_CHAT_PAGE_LIMIT, state.cursor);
|
|
5930
|
+
const page = await client.listChatsPage(MONITOR_CHAT_PAGE_LIMIT, state.cursor, void 0, state.interestOnly);
|
|
5841
5931
|
state.cursor = page.nextCursor;
|
|
5842
5932
|
if (!page.nextCursor) {
|
|
5843
5933
|
state.initialScanComplete = true;
|
|
@@ -6520,9 +6610,12 @@ function safeInboundFilename(raw) {
|
|
|
6520
6610
|
const cleaned = [...base].filter((char) => char >= " " && char !== "\x7F" && char !== "/" && char !== "\\").slice(0, MAX_FILENAME_CHARS).join("").replace(/^[.\s]+|[.\s]+$/g, "");
|
|
6521
6611
|
return cleaned || void 0;
|
|
6522
6612
|
}
|
|
6523
|
-
var SUPPORTED_FILE_FORMATS = /* @__PURE__ */ new Set(["pdf"]);
|
|
6613
|
+
var SUPPORTED_FILE_FORMATS = /* @__PURE__ */ new Set(["pdf", "docx"]);
|
|
6524
6614
|
function documentMimeFromMagic(buf) {
|
|
6525
6615
|
if (buf.length >= 5 && buf.toString("ascii", 0, 5) === "%PDF-") return "application/pdf";
|
|
6616
|
+
if (buf.length >= 4 && buf[0] === 80 && buf[1] === 75 && (buf[2] === 3 || buf[2] === 5 || buf[2] === 7) && buf[3] === 4 && buf.includes(Buffer.from("word/document.xml", "ascii"))) {
|
|
6617
|
+
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
6618
|
+
}
|
|
6526
6619
|
return void 0;
|
|
6527
6620
|
}
|
|
6528
6621
|
function binaryMimeFromMagic(buf) {
|
|
@@ -6778,7 +6871,11 @@ async function validateInboundMedia(result) {
|
|
|
6778
6871
|
if (buffer.length > MAX_FILE_BYTES) return { ok: false, code: "MEDIA_FILE_TOO_LARGE" };
|
|
6779
6872
|
if (!safeInboundFilename(result.filename)) return { ok: false, code: "MEDIA_FILENAME_INVALID" };
|
|
6780
6873
|
if (!SUPPORTED_FILE_FORMATS.has(format)) return { ok: false, code: "MEDIA_FILE_TYPE_MISMATCH" };
|
|
6781
|
-
|
|
6874
|
+
const detected = documentMimeFromMagic(buffer);
|
|
6875
|
+
if (format === "pdf" && detected !== "application/pdf") {
|
|
6876
|
+
return { ok: false, code: "MEDIA_FILE_TYPE_MISMATCH" };
|
|
6877
|
+
}
|
|
6878
|
+
if (format === "docx" && detected !== "application/vnd.openxmlformats-officedocument.wordprocessingml.document") {
|
|
6782
6879
|
return { ok: false, code: "MEDIA_FILE_TYPE_MISMATCH" };
|
|
6783
6880
|
}
|
|
6784
6881
|
}
|
|
@@ -6787,20 +6884,71 @@ async function validateInboundMedia(result) {
|
|
|
6787
6884
|
function safeBodyAfterKnownMediaFailure(baseType, content) {
|
|
6788
6885
|
return baseType === 3 ? "[Image unavailable]" : content;
|
|
6789
6886
|
}
|
|
6887
|
+
function formatType49MediaFailureBody(content, mediaErrorCode) {
|
|
6888
|
+
const text = content || "File attachment";
|
|
6889
|
+
return `${text}
|
|
6890
|
+
[Attachment unavailable: ${mediaErrorCode}]`;
|
|
6891
|
+
}
|
|
6790
6892
|
|
|
6791
6893
|
// src/inbound-media-poll.ts
|
|
6894
|
+
var DOWNLOADABLE_MEDIA_TYPES = /* @__PURE__ */ new Set([3, 34, 43]);
|
|
6895
|
+
var CHAT_HISTORY_APPMSG_TYPE = 19;
|
|
6896
|
+
function isWeChatChatHistoryMessage(msg) {
|
|
6897
|
+
if (msg.forwarded) return true;
|
|
6898
|
+
const content = msg.content ?? "";
|
|
6899
|
+
if (content.startsWith("[Chat History]")) return true;
|
|
6900
|
+
const baseType = msg.type & 2147483647;
|
|
6901
|
+
if (baseType !== 49) return false;
|
|
6902
|
+
const packedSubtype = Math.floor(msg.type / 4294967296);
|
|
6903
|
+
if (packedSubtype === CHAT_HISTORY_APPMSG_TYPE) return true;
|
|
6904
|
+
return /<type>\s*19\s*<\/type>/.test(content);
|
|
6905
|
+
}
|
|
6906
|
+
function shouldPollInboundMedia(msg) {
|
|
6907
|
+
const baseType = msg.type & 2147483647;
|
|
6908
|
+
if (DOWNLOADABLE_MEDIA_TYPES.has(baseType)) return true;
|
|
6909
|
+
return baseType === 49;
|
|
6910
|
+
}
|
|
6911
|
+
function nestedChatHistoryMedia(result) {
|
|
6912
|
+
return (result?.items ?? []).filter(
|
|
6913
|
+
(item) => Boolean(item?.data) && Boolean(item?.type) && item.type !== "unsupported"
|
|
6914
|
+
);
|
|
6915
|
+
}
|
|
6916
|
+
function isUnsupportedInboundMedia(result) {
|
|
6917
|
+
return result?.type === "unsupported";
|
|
6918
|
+
}
|
|
6919
|
+
function mediaFlagsFromPollResult(result, baseType) {
|
|
6920
|
+
if (nestedChatHistoryMedia(result).length > 0) {
|
|
6921
|
+
return { hasMedia: true };
|
|
6922
|
+
}
|
|
6923
|
+
if (result && result.data && result.type !== "unsupported") {
|
|
6924
|
+
return { hasMedia: true };
|
|
6925
|
+
}
|
|
6926
|
+
if (isUnsupportedInboundMedia(result)) {
|
|
6927
|
+
return { hasMedia: false };
|
|
6928
|
+
}
|
|
6929
|
+
if (result?.errorCode || result && result.type !== "unsupported" || DOWNLOADABLE_MEDIA_TYPES.has(baseType)) {
|
|
6930
|
+
return {
|
|
6931
|
+
hasMedia: true,
|
|
6932
|
+
mediaErrorCode: result?.errorCode ?? "MEDIA_DOWNLOAD_UNAVAILABLE"
|
|
6933
|
+
};
|
|
6934
|
+
}
|
|
6935
|
+
return { hasMedia: false };
|
|
6936
|
+
}
|
|
6792
6937
|
var RETRYABLE_MEDIA_ERRORS = /* @__PURE__ */ new Set([
|
|
6793
6938
|
"MEDIA_NOT_DOWNLOADED",
|
|
6794
6939
|
"IMAGE_RESOURCE_UNAVAILABLE",
|
|
6795
6940
|
"FILE_NOT_DOWNLOADED",
|
|
6796
6941
|
"FILE_NOT_STABLE",
|
|
6797
6942
|
"VOICE_NOT_DOWNLOADED",
|
|
6798
|
-
"IMAGE_NOT_STABLE"
|
|
6943
|
+
"IMAGE_NOT_STABLE",
|
|
6944
|
+
"CHAT_HISTORY_NOT_MATERIALIZED"
|
|
6799
6945
|
]);
|
|
6800
6946
|
var DEFAULT_MEDIA_POLL_ATTEMPTS = 6;
|
|
6801
6947
|
var DEFAULT_MEDIA_POLL_INTERVAL_MS = 500;
|
|
6948
|
+
var CHAT_HISTORY_MEDIA_POLL_INTERVAL_MS = 1e3;
|
|
6802
6949
|
var IMAGE_MATERIALIZATION_OPEN_CHAT_TIMEOUT_MS = 400;
|
|
6803
6950
|
var FILE_MATERIALIZATION_CLICK_TIMEOUT_MS = 8e3;
|
|
6951
|
+
var CHAT_HISTORY_MATERIALIZATION_CLICK_TIMEOUT_MS = 25e3;
|
|
6804
6952
|
function shouldTriggerMediaMaterialization(result) {
|
|
6805
6953
|
return result.type === "image" || result.type === "file" || result.type === "pending";
|
|
6806
6954
|
}
|
|
@@ -6834,9 +6982,58 @@ function triggerFileBubbleClick(client, chatId, result, attempt, options) {
|
|
|
6834
6982
|
const clicked = Promise.resolve(client.downloadFile(chatId, filename, void 0, timeoutMs));
|
|
6835
6983
|
void clicked.catch(() => void 0);
|
|
6836
6984
|
}
|
|
6985
|
+
function shouldTriggerChatHistoryMaterialization(result) {
|
|
6986
|
+
if (result.errorCode === "CHAT_HISTORY_NOT_MATERIALIZED") return true;
|
|
6987
|
+
if (result.type === "pending" && result.errorCode === "IMAGE_RESOURCE_UNAVAILABLE") return true;
|
|
6988
|
+
if (result.type === "pending" && !result.items?.some((item) => item?.data)) return true;
|
|
6989
|
+
return false;
|
|
6990
|
+
}
|
|
6991
|
+
function triggerChatHistoryMaterialize(client, chatId, result, attempt, options) {
|
|
6992
|
+
const timeoutMs = options?.timeoutMs ?? CHAT_HISTORY_MATERIALIZATION_CLICK_TIMEOUT_MS;
|
|
6993
|
+
options?.log?.info?.(
|
|
6994
|
+
`[wechat:media] triggering chat-history materialize attempt=${attempt} title=${options?.title ?? ""} localId=${options?.localId ?? ""} skipOpen=${options?.skipOpen === true}`
|
|
6995
|
+
);
|
|
6996
|
+
if (!client.materializeChatHistory) {
|
|
6997
|
+
throw Object.assign(new Error("CHAT_HISTORY_MATERIALIZE_TRIGGER_UNAVAILABLE"), {
|
|
6998
|
+
code: "CHAT_HISTORY_MATERIALIZE_TRIGGER_UNAVAILABLE"
|
|
6999
|
+
});
|
|
7000
|
+
}
|
|
7001
|
+
const clicked = Promise.resolve(
|
|
7002
|
+
client.materializeChatHistory(
|
|
7003
|
+
chatId,
|
|
7004
|
+
{
|
|
7005
|
+
title: options?.title,
|
|
7006
|
+
localId: options?.localId,
|
|
7007
|
+
executionTimeoutMs: timeoutMs
|
|
7008
|
+
},
|
|
7009
|
+
void 0
|
|
7010
|
+
)
|
|
7011
|
+
);
|
|
7012
|
+
void clicked.catch(() => void 0);
|
|
7013
|
+
}
|
|
7014
|
+
function createChatHistoryMaterializationTrigger(client, chatId, options) {
|
|
7015
|
+
return async (result, attempt) => {
|
|
7016
|
+
if (!shouldTriggerChatHistoryMaterialization(result) || !options?.title?.trim()) return;
|
|
7017
|
+
triggerChatHistoryMaterialize(client, chatId, result, attempt, options);
|
|
7018
|
+
};
|
|
7019
|
+
}
|
|
7020
|
+
function chatHistoryTitleFromMessage(msg) {
|
|
7021
|
+
const fromForwarded = msg.forwarded?.title?.trim();
|
|
7022
|
+
if (fromForwarded) return fromForwarded;
|
|
7023
|
+
const content = msg.content ?? "";
|
|
7024
|
+
const en = content.match(/^\[Chat History\]\s*([^\n]+)/);
|
|
7025
|
+
if (en?.[1]?.trim()) return en[1].trim();
|
|
7026
|
+
const zh = content.match(/^\[聊天记录\]\s*([^\n]+)/);
|
|
7027
|
+
return zh?.[1]?.trim() || void 0;
|
|
7028
|
+
}
|
|
6837
7029
|
function createMediaMaterializationTrigger(client, chatId, options) {
|
|
6838
7030
|
const timeoutMs = options?.timeoutMs ?? IMAGE_MATERIALIZATION_OPEN_CHAT_TIMEOUT_MS;
|
|
6839
7031
|
return async (result, attempt) => {
|
|
7032
|
+
if (result.errorCode === "CHAT_HISTORY_NOT_MATERIALIZED") {
|
|
7033
|
+
if (!options?.title?.trim()) return;
|
|
7034
|
+
triggerChatHistoryMaterialize(client, chatId, result, attempt, options);
|
|
7035
|
+
return;
|
|
7036
|
+
}
|
|
6840
7037
|
if (!shouldTriggerMediaMaterialization(result)) return;
|
|
6841
7038
|
if (isFileMaterialization(result)) {
|
|
6842
7039
|
triggerFileBubbleClick(client, chatId, result, attempt, options);
|
|
@@ -6847,6 +7044,16 @@ function createMediaMaterializationTrigger(client, chatId, options) {
|
|
|
6847
7044
|
}
|
|
6848
7045
|
function mediaMaterializationTriggerForMessage(opts) {
|
|
6849
7046
|
const baseType = opts.messageType & 2147483647;
|
|
7047
|
+
if (opts.chatHistory) {
|
|
7048
|
+
const extra = typeof opts.chatHistory === "object" ? opts.chatHistory : {};
|
|
7049
|
+
return createChatHistoryMaterializationTrigger(opts.client, opts.chatId, {
|
|
7050
|
+
log: opts.log,
|
|
7051
|
+
timeoutMs: opts.timeoutMs ?? CHAT_HISTORY_MATERIALIZATION_CLICK_TIMEOUT_MS,
|
|
7052
|
+
skipOpen: opts.skipOpen,
|
|
7053
|
+
title: extra.title,
|
|
7054
|
+
localId: extra.localId
|
|
7055
|
+
});
|
|
7056
|
+
}
|
|
6850
7057
|
if (baseType !== 3 && baseType !== 49) return void 0;
|
|
6851
7058
|
return createMediaMaterializationTrigger(opts.client, opts.chatId, {
|
|
6852
7059
|
log: opts.log,
|
|
@@ -6878,6 +7085,15 @@ function isRetryable(result) {
|
|
|
6878
7085
|
if (result.errorCode !== void 0) return RETRYABLE_MEDIA_ERRORS.has(result.errorCode);
|
|
6879
7086
|
return result.type === "pending";
|
|
6880
7087
|
}
|
|
7088
|
+
function chatHistoryMediaPollWindow(opts) {
|
|
7089
|
+
const intervalMs = Math.max(1, opts?.intervalMs ?? CHAT_HISTORY_MEDIA_POLL_INTERVAL_MS);
|
|
7090
|
+
const timeoutMs = opts?.timeoutMs ?? CHAT_HISTORY_MATERIALIZATION_CLICK_TIMEOUT_MS;
|
|
7091
|
+
const maxAttempts = Math.max(
|
|
7092
|
+
DEFAULT_MEDIA_POLL_ATTEMPTS,
|
|
7093
|
+
Math.ceil(timeoutMs / intervalMs) + 1
|
|
7094
|
+
);
|
|
7095
|
+
return { maxAttempts, intervalMs };
|
|
7096
|
+
}
|
|
6881
7097
|
async function pollMedia(client, chatId, localId, log, maxAttempts = DEFAULT_MEDIA_POLL_ATTEMPTS, intervalMs = DEFAULT_MEDIA_POLL_INTERVAL_MS, onRetryTrigger) {
|
|
6882
7098
|
let lastResult;
|
|
6883
7099
|
let triggerUsed = false;
|
|
@@ -7159,6 +7375,9 @@ function loadFile(path) {
|
|
|
7159
7375
|
function inboundEventId(accountId, chatId, message) {
|
|
7160
7376
|
return createHash("sha256").update(`${accountId}\0${chatId}\0${cursorMessageKey(message)}`).digest("hex");
|
|
7161
7377
|
}
|
|
7378
|
+
function nestedMediaEventId(parentEventId, item) {
|
|
7379
|
+
return createHash("sha256").update(JSON.stringify(["nested-media", parentEventId, item.type ?? "", item.filename ?? ""])).digest("hex");
|
|
7380
|
+
}
|
|
7162
7381
|
var InboundEventLedger = class {
|
|
7163
7382
|
path;
|
|
7164
7383
|
entries = /* @__PURE__ */ new Map();
|
|
@@ -7533,9 +7752,29 @@ function applyEmptyUnreadSkip(chatId, opts) {
|
|
|
7533
7752
|
opts.backoff.set(chatId, { nextRetryAt: now + backoffMs, retryCount });
|
|
7534
7753
|
return { seededLastSeen, backoffMs };
|
|
7535
7754
|
}
|
|
7755
|
+
function stickyUnreadTip(chat) {
|
|
7756
|
+
return `${chat.lastMsgLocalId ?? ""}:${chat.unreadCount ?? 0}`;
|
|
7757
|
+
}
|
|
7758
|
+
function isStickyUnclearedAcked(chat, acked) {
|
|
7759
|
+
const chatId = chat.username ?? chat.id;
|
|
7760
|
+
return acked.get(chatId) === stickyUnreadTip(chat);
|
|
7761
|
+
}
|
|
7762
|
+
function ackStickyUnclearedUnread(chatId, chat, acked, backoff, now = Date.now()) {
|
|
7763
|
+
const tip = stickyUnreadTip(chat);
|
|
7764
|
+
acked.set(chatId, tip);
|
|
7765
|
+
const retryCount = (backoff.get(chatId)?.retryCount ?? 0) + 1;
|
|
7766
|
+
const backoffMs = Math.min(
|
|
7767
|
+
EMPTY_UNREAD_BACKOFF_MAX_MS,
|
|
7768
|
+
EMPTY_UNREAD_BACKOFF_MS * 2 ** Math.min(retryCount - 1, 6)
|
|
7769
|
+
);
|
|
7770
|
+
backoff.set(chatId, { nextRetryAt: now + backoffMs, retryCount });
|
|
7771
|
+
return { tip, backoffMs };
|
|
7772
|
+
}
|
|
7773
|
+
function isOpenImChat(chatId) {
|
|
7774
|
+
return chatId.includes("@openim");
|
|
7775
|
+
}
|
|
7536
7776
|
|
|
7537
7777
|
// src/monitor.ts
|
|
7538
|
-
var MEDIA_TYPES = /* @__PURE__ */ new Set([3, 34, 43]);
|
|
7539
7778
|
var HISTORY_CONTEXT_MARKER = "[Chat messages since your last reply - for context]";
|
|
7540
7779
|
var CURRENT_MESSAGE_MARKER = "[Current message - respond to this]";
|
|
7541
7780
|
function sleep(ms, signal) {
|
|
@@ -7589,6 +7828,7 @@ async function startWeChatMonitor(opts) {
|
|
|
7589
7828
|
};
|
|
7590
7829
|
trimPendingResetRetries(retryState);
|
|
7591
7830
|
const newsappHandled = /* @__PURE__ */ new Map();
|
|
7831
|
+
const stickyUnreadAck = /* @__PURE__ */ new Map();
|
|
7592
7832
|
const groupHistory = /* @__PURE__ */ new Map();
|
|
7593
7833
|
const GROUP_HISTORY_LIMIT = 50;
|
|
7594
7834
|
let lastAuthCheck = 0;
|
|
@@ -7604,6 +7844,13 @@ async function startWeChatMonitor(opts) {
|
|
|
7604
7844
|
while (!abortSignal.aborted) {
|
|
7605
7845
|
try {
|
|
7606
7846
|
const cfg = getWeChatRuntime().config.current();
|
|
7847
|
+
const liveAccountForInterest = resolveWeChatAccount(cfg, account.accountId) ?? account;
|
|
7848
|
+
const { interest: nextInterest, synced: interestSynced } = await ensureMonitorInterestSynced(
|
|
7849
|
+
client,
|
|
7850
|
+
liveAccountForInterest,
|
|
7851
|
+
log
|
|
7852
|
+
);
|
|
7853
|
+
chatScanState.interestOnly = shouldListChatsInterestOnly(nextInterest) && interestSynced;
|
|
7607
7854
|
const now = Date.now();
|
|
7608
7855
|
if (now - lastMediaCleanup >= MEDIA_RETENTION_MS / 4) {
|
|
7609
7856
|
lastMediaCleanup = now;
|
|
@@ -7689,6 +7936,7 @@ async function startWeChatMonitor(opts) {
|
|
|
7689
7936
|
const unreadChats = chats.filter((c) => {
|
|
7690
7937
|
const chatId = c.username ?? c.id;
|
|
7691
7938
|
if (isEmptyUnreadBackoffActive(emptyUnreadBackoff, chatId)) return false;
|
|
7939
|
+
if (isStickyUnclearedAcked(c, stickyUnreadAck)) return false;
|
|
7692
7940
|
if (c.unreadCount <= 0) return false;
|
|
7693
7941
|
if (isNewsappChat(c)) return true;
|
|
7694
7942
|
return !isOfficialAccount(chatId);
|
|
@@ -7703,16 +7951,23 @@ async function startWeChatMonitor(opts) {
|
|
|
7703
7951
|
chats,
|
|
7704
7952
|
retryState,
|
|
7705
7953
|
Date.now(),
|
|
7706
|
-
(chat) =>
|
|
7954
|
+
(chat) => {
|
|
7955
|
+
if (isStickyUnclearedAcked(chat, stickyUnreadAck)) return false;
|
|
7956
|
+
if (!isNewsappChat(chat)) return true;
|
|
7957
|
+
return newsappHandled.get(chat.username ?? chat.id) !== `${chat.lastMsgLocalId ?? ""}:${chat.lastMessagePreview?.trim() ?? ""}`;
|
|
7958
|
+
}
|
|
7707
7959
|
);
|
|
7708
7960
|
for (const chatId of chatsToProcess.keys()) {
|
|
7709
7961
|
if (isOfficialAccount(chatId) && chatId !== "newsapp" || isEmptyUnreadBackoffActive(emptyUnreadBackoff, chatId)) {
|
|
7710
7962
|
chatsToProcess.delete(chatId);
|
|
7711
7963
|
}
|
|
7712
7964
|
}
|
|
7713
|
-
|
|
7965
|
+
const actionableUnread = unreadChats.filter(
|
|
7966
|
+
(c) => chatsToProcess.has(c.username ?? c.id)
|
|
7967
|
+
);
|
|
7968
|
+
if (actionableUnread.length > 0) {
|
|
7714
7969
|
log?.info?.(
|
|
7715
|
-
`[wechat:${account.accountId}] ${
|
|
7970
|
+
`[wechat:${account.accountId}] ${actionableUnread.length} chat(s) with unreads`
|
|
7716
7971
|
);
|
|
7717
7972
|
}
|
|
7718
7973
|
let deferredThisTick = 0;
|
|
@@ -7731,6 +7986,7 @@ async function startWeChatMonitor(opts) {
|
|
|
7731
7986
|
inboundLedger,
|
|
7732
7987
|
mediaPipeline,
|
|
7733
7988
|
emptyUnreadBackoff,
|
|
7989
|
+
stickyUnreadAck,
|
|
7734
7990
|
account,
|
|
7735
7991
|
cfg,
|
|
7736
7992
|
log,
|
|
@@ -7780,6 +8036,7 @@ async function startWeChatMonitor(opts) {
|
|
|
7780
8036
|
inboundLedger,
|
|
7781
8037
|
mediaPipeline,
|
|
7782
8038
|
emptyUnreadBackoff,
|
|
8039
|
+
stickyUnreadAck,
|
|
7783
8040
|
account,
|
|
7784
8041
|
cfg,
|
|
7785
8042
|
log,
|
|
@@ -7849,30 +8106,75 @@ async function prepareMessage(client, mediaPipeline, msg, chatId, chat, liveAcco
|
|
|
7849
8106
|
let mediaMime;
|
|
7850
8107
|
let mediaHash;
|
|
7851
8108
|
let mediaPreviewPath;
|
|
8109
|
+
let mediaPaths;
|
|
8110
|
+
let mediaMimes;
|
|
7852
8111
|
let mediaSource;
|
|
7853
8112
|
let mediaErrorCode;
|
|
7854
8113
|
let hasMedia = false;
|
|
7855
8114
|
const baseType = msg.type & 2147483647;
|
|
7856
|
-
const
|
|
8115
|
+
const isChatHistory = isWeChatChatHistoryMessage(msg);
|
|
8116
|
+
const mayHaveMedia = shouldPollInboundMedia(msg);
|
|
7857
8117
|
if (mayHaveMedia) {
|
|
7858
8118
|
log?.info?.(`[wechat:${liveAccount.accountId}] Checking media for msg ${msg.localId} (type ${baseType})`);
|
|
7859
8119
|
try {
|
|
8120
|
+
const chatHistoryPoll = isChatHistory ? chatHistoryMediaPollWindow() : void 0;
|
|
7860
8121
|
const result = await pollMedia(
|
|
7861
8122
|
client,
|
|
7862
8123
|
chatId,
|
|
7863
8124
|
msg.localId,
|
|
7864
8125
|
log,
|
|
7865
|
-
|
|
7866
|
-
|
|
8126
|
+
chatHistoryPoll?.maxAttempts,
|
|
8127
|
+
chatHistoryPoll?.intervalMs,
|
|
7867
8128
|
mediaMaterializationTriggerForMessage({
|
|
7868
8129
|
client,
|
|
7869
8130
|
chatId,
|
|
7870
8131
|
messageType: baseType,
|
|
7871
8132
|
log,
|
|
7872
|
-
skipOpen
|
|
8133
|
+
skipOpen,
|
|
8134
|
+
chatHistory: isChatHistory ? {
|
|
8135
|
+
title: chatHistoryTitleFromMessage(msg),
|
|
8136
|
+
localId: msg.localId
|
|
8137
|
+
} : void 0
|
|
7873
8138
|
})
|
|
7874
8139
|
);
|
|
7875
|
-
|
|
8140
|
+
const nestedItems = nestedChatHistoryMedia(result);
|
|
8141
|
+
if (nestedItems.length > 0) {
|
|
8142
|
+
const savedPaths = [];
|
|
8143
|
+
const savedMimes = [];
|
|
8144
|
+
for (const [index, item] of nestedItems.entries()) {
|
|
8145
|
+
const saved = await mediaPipeline.process(
|
|
8146
|
+
item,
|
|
8147
|
+
{
|
|
8148
|
+
eventId: nestedMediaEventId(inboundEventId(liveAccount.accountId, chatId, msg), item),
|
|
8149
|
+
chatId,
|
|
8150
|
+
localId: msg.localId
|
|
8151
|
+
},
|
|
8152
|
+
async (buffer, mime, filename) => core.channel.media.saveMediaBuffer(buffer, mime, "inbound", void 0, filename),
|
|
8153
|
+
async (buffer, mime, filename) => core.channel.media.saveMediaBuffer(buffer, mime, "inbound", void 0, filename),
|
|
8154
|
+
async (path) => unlink(path)
|
|
8155
|
+
);
|
|
8156
|
+
if (!saved.ok) {
|
|
8157
|
+
log?.error?.(`[wechat:media] nested chat-history media rejected index=${index} code=${saved.code}`);
|
|
8158
|
+
continue;
|
|
8159
|
+
}
|
|
8160
|
+
if (!saved.originalPath || savedPaths.includes(saved.originalPath)) continue;
|
|
8161
|
+
savedPaths.push(saved.originalPath);
|
|
8162
|
+
savedMimes.push(saved.mime);
|
|
8163
|
+
if (!mediaPath) {
|
|
8164
|
+
mediaPath = saved.originalPath;
|
|
8165
|
+
mediaMime = saved.mime;
|
|
8166
|
+
mediaHash = saved.hash;
|
|
8167
|
+
mediaPreviewPath = saved.previewPath;
|
|
8168
|
+
mediaSource = item.source;
|
|
8169
|
+
}
|
|
8170
|
+
}
|
|
8171
|
+
if (savedPaths.length > 0) {
|
|
8172
|
+
hasMedia = true;
|
|
8173
|
+
mediaPaths = savedPaths;
|
|
8174
|
+
mediaMimes = savedMimes;
|
|
8175
|
+
log?.info?.(`[wechat:media] nested chat-history media saved count=${savedPaths.length}`);
|
|
8176
|
+
}
|
|
8177
|
+
} else if (result && result.data && result.type !== "unsupported") {
|
|
7876
8178
|
hasMedia = true;
|
|
7877
8179
|
mediaSource = result.source;
|
|
7878
8180
|
log?.info?.(`[wechat:media] inbound media received type=${result.type} format=${result.format}`);
|
|
@@ -7893,15 +8195,22 @@ async function prepareMessage(client, mediaPipeline, msg, chatId, chat, liveAcco
|
|
|
7893
8195
|
mediaPreviewPath = saved.previewPath;
|
|
7894
8196
|
log?.info?.(`[wechat:media] inbound media saved hash=${saved.hash}${saved.previewPath ? " preview=present" : ""}`);
|
|
7895
8197
|
}
|
|
7896
|
-
} else
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
|
|
8198
|
+
} else {
|
|
8199
|
+
const flags = mediaFlagsFromPollResult(result, baseType);
|
|
8200
|
+
hasMedia = flags.hasMedia;
|
|
8201
|
+
mediaErrorCode = isChatHistory ? void 0 : flags.mediaErrorCode;
|
|
8202
|
+
if (mediaErrorCode) {
|
|
8203
|
+
log?.info?.(`[wechat:media] inbound media unavailable code=${mediaErrorCode}`);
|
|
8204
|
+
}
|
|
7900
8205
|
}
|
|
7901
8206
|
} catch (err) {
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
8207
|
+
if (isChatHistory) {
|
|
8208
|
+
log?.error?.("[wechat:media] nested chat-history media failed code=MEDIA_DOWNLOAD_FAILED");
|
|
8209
|
+
} else {
|
|
8210
|
+
hasMedia = true;
|
|
8211
|
+
mediaErrorCode = "MEDIA_DOWNLOAD_FAILED";
|
|
8212
|
+
log?.error?.("[wechat:media] inbound media failed code=MEDIA_DOWNLOAD_FAILED");
|
|
8213
|
+
}
|
|
7905
8214
|
}
|
|
7906
8215
|
}
|
|
7907
8216
|
const timestamp = new Date(msg.timestamp).getTime();
|
|
@@ -7918,10 +8227,8 @@ async function prepareMessage(client, mediaPipeline, msg, chatId, chat, liveAcco
|
|
|
7918
8227
|
} else if (!mediaMime.startsWith("image/") && !mediaMime.startsWith("audio/")) {
|
|
7919
8228
|
rawBody = `[File: ${rawBody}]`;
|
|
7920
8229
|
}
|
|
7921
|
-
} else if (mediaErrorCode && baseType === 49) {
|
|
7922
|
-
|
|
7923
|
-
rawBody = `${text}
|
|
7924
|
-
[Attachment unavailable: ${mediaErrorCode}]`;
|
|
8230
|
+
} else if (mediaErrorCode && baseType === 49 && !isChatHistory) {
|
|
8231
|
+
rawBody = formatType49MediaFailureBody(msg.content || "", mediaErrorCode);
|
|
7925
8232
|
}
|
|
7926
8233
|
if (mediaSource === "thumbnail") {
|
|
7927
8234
|
rawBody = rawBody ? `${rawBody}
|
|
@@ -7950,6 +8257,8 @@ ${replyBlock}` : replyBlock;
|
|
|
7950
8257
|
mediaMime,
|
|
7951
8258
|
mediaHash,
|
|
7952
8259
|
mediaPreviewPath,
|
|
8260
|
+
mediaPaths,
|
|
8261
|
+
mediaMimes,
|
|
7953
8262
|
senderName,
|
|
7954
8263
|
senderId,
|
|
7955
8264
|
isGroup,
|
|
@@ -7981,9 +8290,11 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, polic
|
|
|
7981
8290
|
const core = getWeChatRuntime();
|
|
7982
8291
|
const lastMsg = segment[segment.length - 1];
|
|
7983
8292
|
const { isGroup, senderId, senderName, timestamp, rawBody, commandBody, msg } = lastMsg;
|
|
7984
|
-
const mediaMsg = segment.find((pm) => pm.mediaPath);
|
|
7985
|
-
const
|
|
7986
|
-
const
|
|
8293
|
+
const mediaMsg = segment.find((pm) => pm.mediaPath || pm.mediaPaths && pm.mediaPaths.length > 0);
|
|
8294
|
+
const mediaPaths = mediaMsg?.mediaPaths?.length ? mediaMsg.mediaPaths : mediaMsg?.mediaPath ? [mediaMsg.mediaPath] : [];
|
|
8295
|
+
const mediaMimes = mediaMsg?.mediaMimes?.length ? mediaMsg.mediaMimes : mediaMsg?.mediaMime ? [mediaMsg.mediaMime] : [];
|
|
8296
|
+
const mediaPath = mediaPaths[0];
|
|
8297
|
+
const mediaMime = mediaMimes[0];
|
|
7987
8298
|
log?.info?.(
|
|
7988
8299
|
`[wechat:${liveAccount.accountId}] Dispatching segment: ${segment.length} msg(s), last=${msg.localId}${mediaPath ? " media=present" : ""}`
|
|
7989
8300
|
);
|
|
@@ -8119,9 +8430,9 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, polic
|
|
|
8119
8430
|
MediaPath: mediaPath,
|
|
8120
8431
|
MediaUrl: mediaPath,
|
|
8121
8432
|
MediaType: mediaMime,
|
|
8122
|
-
MediaPaths:
|
|
8123
|
-
MediaUrls:
|
|
8124
|
-
MediaTypes:
|
|
8433
|
+
MediaPaths: mediaPaths,
|
|
8434
|
+
MediaUrls: mediaPaths,
|
|
8435
|
+
MediaTypes: mediaMimes,
|
|
8125
8436
|
...mediaMsg?.mediaHash ? { MediaHash: mediaMsg.mediaHash } : {},
|
|
8126
8437
|
...mediaMsg?.mediaPreviewPath ? { MediaPreviewPath: mediaMsg.mediaPreviewPath } : {}
|
|
8127
8438
|
} : {},
|
|
@@ -8250,7 +8561,7 @@ function bufferGroupHistory(groupHistory, chatId, pm, limit) {
|
|
|
8250
8561
|
if (first) groupHistory.delete(first);
|
|
8251
8562
|
}
|
|
8252
8563
|
}
|
|
8253
|
-
async function processUnreadChat(client, chat, lastSeenId, equalCursorUnreadHandled, startupBaselines, pendingMessageScans, persistRetryState, inboundLedger, mediaPipeline, emptyUnreadBackoff, account, cfg, log, skipOpen, groupHistory, groupHistoryLimit, catchup) {
|
|
8564
|
+
async function processUnreadChat(client, chat, lastSeenId, equalCursorUnreadHandled, startupBaselines, pendingMessageScans, persistRetryState, inboundLedger, mediaPipeline, emptyUnreadBackoff, stickyUnreadAck, account, cfg, log, skipOpen, groupHistory, groupHistoryLimit, catchup) {
|
|
8254
8565
|
const core = getWeChatRuntime();
|
|
8255
8566
|
const liveAccount = resolveWeChatAccount(cfg, account.accountId) ?? account;
|
|
8256
8567
|
const chatId = chat.username ?? chat.id;
|
|
@@ -8417,11 +8728,26 @@ async function processUnreadChat(client, chat, lastSeenId, equalCursorUnreadHand
|
|
|
8417
8728
|
);
|
|
8418
8729
|
}
|
|
8419
8730
|
if (chat.unreadCount > 0 && !isNewsappChat(chat)) {
|
|
8420
|
-
|
|
8731
|
+
const sticky = ackStickyUnclearedUnread(
|
|
8732
|
+
chatId,
|
|
8733
|
+
chat,
|
|
8734
|
+
stickyUnreadAck,
|
|
8735
|
+
emptyUnreadBackoff
|
|
8736
|
+
);
|
|
8737
|
+
log?.info?.(
|
|
8738
|
+
`[wechat:${liveAccount.accountId}] ${chatId}: ignoring sticky unread after catch-up (unreadCount=${chat.unreadCount}, tip=${sticky.tip}, openim=${isOpenImChat(chatId)}); badge kept; backoff=${sticky.backoffMs}ms`
|
|
8739
|
+
);
|
|
8421
8740
|
}
|
|
8422
8741
|
return "skipped";
|
|
8423
8742
|
}
|
|
8424
|
-
|
|
8743
|
+
const anyAllowlistedInbound = newMessages.some((msg) => {
|
|
8744
|
+
if (msg.isSelf) return false;
|
|
8745
|
+
if (shouldBypassNewsappAuthorization(chat)) return true;
|
|
8746
|
+
const isGroup2 = chatId.includes("@chatroom");
|
|
8747
|
+
const senderId = msg.sender ?? chatId;
|
|
8748
|
+
return resolveWeChatInboundAccessDecision({ isGroup: isGroup2, senderId, policy }).allowed;
|
|
8749
|
+
});
|
|
8750
|
+
if (anyAllowlistedInbound && !isNewsappChat(chat)) await openChatIfNeeded();
|
|
8425
8751
|
const catchUpLimits = {
|
|
8426
8752
|
maxMessages: liveAccount.catchUpMaxMessages,
|
|
8427
8753
|
maxAgeMs: liveAccount.catchUpMaxAgeMs
|
|
@@ -8517,6 +8843,8 @@ async function processUnreadChat(client, chat, lastSeenId, equalCursorUnreadHand
|
|
|
8517
8843
|
...processed[i],
|
|
8518
8844
|
mediaPath: void 0,
|
|
8519
8845
|
mediaMime: void 0,
|
|
8846
|
+
mediaPaths: void 0,
|
|
8847
|
+
mediaMimes: void 0,
|
|
8520
8848
|
hasMedia: false
|
|
8521
8849
|
};
|
|
8522
8850
|
}
|
|
@@ -8618,6 +8946,17 @@ async function processUnreadChat(client, chat, lastSeenId, equalCursorUnreadHand
|
|
|
8618
8946
|
}
|
|
8619
8947
|
const maxId = Math.max(...newMessages.map((m) => m.localId));
|
|
8620
8948
|
advanceLastSeen(newMessages.filter((message) => message.localId <= maxId));
|
|
8949
|
+
if (chat.unreadCount > 0 && !isNewsappChat(chat)) {
|
|
8950
|
+
const sticky = ackStickyUnclearedUnread(
|
|
8951
|
+
chatId,
|
|
8952
|
+
chat,
|
|
8953
|
+
stickyUnreadAck,
|
|
8954
|
+
emptyUnreadBackoff
|
|
8955
|
+
);
|
|
8956
|
+
log?.info?.(
|
|
8957
|
+
`[wechat:${liveAccount.accountId}] ${chatId}: ignoring sticky unread after filtered window (tip=${sticky.tip}, openim=${isOpenImChat(chatId)}); badge kept; backoff=${sticky.backoffMs}ms`
|
|
8958
|
+
);
|
|
8959
|
+
}
|
|
8621
8960
|
return "skipped";
|
|
8622
8961
|
}
|
|
8623
8962
|
|
|
@@ -9295,7 +9634,7 @@ var wechatPlugin = {
|
|
|
9295
9634
|
allowFrom: account.allowFrom ?? [],
|
|
9296
9635
|
allowFromPath: `channels.${OPENCLAW_CHANNEL_ID}.allowFrom`,
|
|
9297
9636
|
policyPath: `channels.${OPENCLAW_CHANNEL_ID}.dmPolicy`,
|
|
9298
|
-
approveHint: `Add the wxid to channels.${OPENCLAW_CHANNEL_ID}.allowFrom`,
|
|
9637
|
+
approveHint: `Add the wxid or enterprise @openim id to channels.${OPENCLAW_CHANNEL_ID}.allowFrom`,
|
|
9299
9638
|
normalizeEntry: (raw) => raw.replace(/^(agent-wechat|wechat):/i, "").trim()
|
|
9300
9639
|
})
|
|
9301
9640
|
},
|