From b047a731bfa57e99f8f846137f3d69a8e5a31563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=8E?= <95698662+wardforce@users.noreply.github.com> Date: Thu, 17 Sep 2026 02:03:42 +0800 Subject: [PATCH] fix: harden Liepin chat composer detection --- scripts/liepin-send-dom.node-test.mjs | 59 +++++++++++++++++++++++++++ src-tauri/src/rpa/liepin/send_text.js | 51 ++++++++++++++++------- 2 files changed, 96 insertions(+), 14 deletions(-) diff --git a/scripts/liepin-send-dom.node-test.mjs b/scripts/liepin-send-dom.node-test.mjs index dc4e9ae..546d624 100644 --- a/scripts/liepin-send-dom.node-test.mjs +++ b/scripts/liepin-send-dom.node-test.mjs @@ -97,6 +97,65 @@ test("do not mistake chat drawer history for a priority modal", async () => { assert.equal(result.success, true); }); +test("find chat composer when Liepin changes the container class", async () => { + const document = makeDocument(` +
+
+ + +
+
+ `); + const requestRecords = []; + const input = document.querySelector("textarea"); + document.querySelector(".ant-im-btn").addEventListener("click", () => { + input.value = ""; + requestRecords.push({ + url: "https://api-c.liepin.com/api/com.liepin.im.c.chat.send-push", + status: 200, + }); + }); + + const result = await runLiepinSend(document, { + message: "你好", + requestRecords, + sleep: async () => {}, + }); + + assert.equal(result.success, true); +}); + +test("ignore login text in chat history when the chat panel is a dialog", async () => { + const document = makeDocument(` + + `); + const requestRecords = []; + const input = document.querySelector("textarea"); + document.querySelector(".ant-im-btn").addEventListener("click", () => { + input.value = ""; + requestRecords.push({ + url: "https://api-c.liepin.com/api/com.liepin.im.c.chat.send-push", + status: 200, + }); + }); + + const result = await runLiepinSend(document, { + message: "你好", + requestRecords, + sleep: async () => {}, + }); + + assert.equal(result.blockedModal, false); + assert.equal(result.success, true); +}); + test("close only visible priority communication modal before sending", async () => { const document = makeDocument(`
diff --git a/src-tauri/src/rpa/liepin/send_text.js b/src-tauri/src/rpa/liepin/send_text.js index 9df3f4d..74327f7 100644 --- a/src-tauri/src/rpa/liepin/send_text.js +++ b/src-tauri/src/rpa/liepin/send_text.js @@ -91,7 +91,7 @@ async function closePriorityModal(modal, document, sleep) { } function classifyModal(modal) { - if (modal.querySelector(CHAT_CONTAINER_SELECTOR)) return "other"; + if (findChatContainer(modal)) return "other"; const text = textOf(modal); const hasPriorityTitle = Array.from(modal.querySelectorAll("h1, h2, h3, [class*='title'], [class*='header'], div, span")) .some((node) => textOf(node) === "优先沟通"); @@ -136,19 +136,6 @@ async function handleKnownModal(document, sleep) { }; } -function findChatContainer(document) { - const marker = findVisible(Array.from(document.querySelectorAll(CHAT_CONTAINER_SELECTOR))); - if (!marker) return null; - let candidate = marker; - for (let container = marker; container && container !== document.body; container = container.parentElement) { - if (findChatInput(container)) { - candidate = container; - if (findAnySendButton(container)) return container; - } - } - return candidate; -} - function findAnySendButton(container) { return container ? Array.from(container.querySelectorAll(SEND_BUTTON_SELECTOR)) @@ -163,6 +150,42 @@ function findChatInput(container) { return inputs.find((node) => !node.disabled && !node.readOnly) || null; } +function findInputContainer(input, boundary) { + for (let container = input?.parentElement; container; container = container.parentElement) { + if (findAnySendButton(container)) return container; + if (container === boundary) break; + } + return null; +} + +function findChatContainer(root) { + const document = root.nodeType === 9 ? root : root.ownerDocument; + const boundary = root.nodeType === 9 ? document.body : root; + const marker = findVisible(Array.from(root.querySelectorAll(CHAT_CONTAINER_SELECTOR))); + if (marker) { + let candidate = marker; + for (let container = marker; container && container !== boundary; container = container.parentElement) { + if (findChatInput(container)) { + candidate = container; + if (findAnySendButton(container)) return container; + } + } + return candidate; + } + + const markedInput = findVisible(Array.from(root.querySelectorAll(`[${CHAT_INPUT_MARKER}]`))); + const inputs = Array.from(root.querySelectorAll(INPUT_SELECTOR)).filter((node) => isVisible(node) + && !node.disabled && !node.readOnly); + if (markedInput) { + inputs.unshift(markedInput); + } + for (const input of inputs) { + const container = findInputContainer(input, boundary); + if (container) return container; + } + return null; +} + function markLiepinChatInput(document) { document.querySelectorAll(`[${CHAT_INPUT_MARKER}]`) .forEach((node) => node.removeAttribute(CHAT_INPUT_MARKER));