Handle rich emoji composer content
This commit is contained in:
+95
-24
@@ -2985,9 +2985,9 @@ async function sendTextAndConfirm(p, externalChatId, text) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const composerText = await readComposerText(p);
|
const composerState = await readComposerState(p);
|
||||||
if (composerText !== null && !messageTextMatches(composerText, wanted)) {
|
if (!composerStateMatchesText(composerState, text)) {
|
||||||
console.warn(`[send/text] composer mismatch after fill; attempt=${attempt.name} composerLength=${composerText.length} wantedLength=${wanted.length}`);
|
console.warn(`[send/text] composer mismatch after fill; attempt=${attempt.name} composerLength=${composerState?.text?.length ?? -1} richCount=${composerState?.richContentCount ?? -1} wantedLength=${wanted.length}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3010,8 +3010,8 @@ async function sendTextAndConfirm(p, externalChatId, text) {
|
|||||||
return externalId;
|
return externalId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentComposerText = await readComposerText(p);
|
const currentComposerState = await readComposerState(p);
|
||||||
if (currentComposerText !== null && currentComposerText.length === 0) {
|
if (composerStateIsEmpty(currentComposerState)) {
|
||||||
console.warn(`[send/text] MAX cleared composer but no outgoing message appeared; attempt=${attempt.name}`);
|
console.warn(`[send/text] MAX cleared composer but no outgoing message appeared; attempt=${attempt.name}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -3040,7 +3040,10 @@ async function fillComposerText(p, text, clearFirst) {
|
|||||||
await clearComposerInput(p);
|
await clearComposerInput(p);
|
||||||
}
|
}
|
||||||
if (await insertComposerTextViaDom(p, text, false)) {
|
if (await insertComposerTextViaDom(p, text, false)) {
|
||||||
return true;
|
await p.waitForTimeout(300);
|
||||||
|
if (composerStateMatchesText(await readComposerState(p), text)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clearFirst && await fillComposerViaLocator(p, text)) {
|
if (clearFirst && await fillComposerViaLocator(p, text)) {
|
||||||
@@ -3059,8 +3062,8 @@ async function fillComposerText(p, text, clearFirst) {
|
|||||||
await p.keyboard.insertText(text);
|
await p.keyboard.insertText(text);
|
||||||
await p.waitForTimeout(300);
|
await p.waitForTimeout(300);
|
||||||
|
|
||||||
const currentText = await readComposerText(p);
|
const state = await readComposerState(p);
|
||||||
if (currentText !== null && messageTextMatches(currentText, text)) {
|
if (composerStateMatchesText(state, text)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3087,8 +3090,8 @@ async function fillComposerText(p, text, clearFirst) {
|
|||||||
}
|
}
|
||||||
await p.keyboard.insertText(text);
|
await p.keyboard.insertText(text);
|
||||||
await p.waitForTimeout(250);
|
await p.waitForTimeout(250);
|
||||||
const currentText = await readComposerText(p);
|
const state = await readComposerState(p);
|
||||||
if (currentText !== null && messageTextMatches(currentText, text)) {
|
if (composerStateMatchesText(state, text)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -3108,16 +3111,14 @@ async function clearComposerInput(p) {
|
|||||||
await p.keyboard.press(process.platform === "darwin" ? "Meta+A" : "Control+A");
|
await p.keyboard.press(process.platform === "darwin" ? "Meta+A" : "Control+A");
|
||||||
await p.keyboard.press("Backspace");
|
await p.keyboard.press("Backspace");
|
||||||
await p.waitForTimeout(120);
|
await p.waitForTimeout(120);
|
||||||
const currentText = await readComposerText(p);
|
if (composerStateIsEmpty(await readComposerState(p))) {
|
||||||
if (currentText !== null && currentText.length === 0) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await insertComposerTextViaDom(p, "", true);
|
await insertComposerTextViaDom(p, "", true);
|
||||||
await p.waitForTimeout(150);
|
await p.waitForTimeout(150);
|
||||||
const currentText = await readComposerText(p);
|
return composerStateIsEmpty(await readComposerState(p));
|
||||||
return currentText !== null && currentText.length === 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fillComposerViaClipboard(p, text, clearFirst) {
|
async function fillComposerViaClipboard(p, text, clearFirst) {
|
||||||
@@ -3142,8 +3143,7 @@ async function fillComposerViaClipboard(p, text, clearFirst) {
|
|||||||
if (wroteClipboard) {
|
if (wroteClipboard) {
|
||||||
await p.keyboard.press(process.platform === "darwin" ? "Meta+V" : "Control+V");
|
await p.keyboard.press(process.platform === "darwin" ? "Meta+V" : "Control+V");
|
||||||
await p.waitForTimeout(300);
|
await p.waitForTimeout(300);
|
||||||
const pastedText = await readComposerText(p);
|
if (composerStateMatchesText(await readComposerState(p), text)) {
|
||||||
if (pastedText !== null && messageTextMatches(pastedText, text)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3164,8 +3164,7 @@ async function fillComposerViaKeyboard(p, text, clearFirst) {
|
|||||||
|
|
||||||
await p.keyboard.insertText(text);
|
await p.keyboard.insertText(text);
|
||||||
await p.waitForTimeout(300);
|
await p.waitForTimeout(300);
|
||||||
const currentText = await readComposerText(p);
|
return composerStateMatchesText(await readComposerState(p), text);
|
||||||
return currentText !== null && messageTextMatches(currentText, text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function insertComposerTextViaDom(p, text, clearFirst = false) {
|
async function insertComposerTextViaDom(p, text, clearFirst = false) {
|
||||||
@@ -3236,11 +3235,16 @@ async function insertComposerTextViaDom(p, text, clearFirst = false) {
|
|||||||
if ("value" in composer) {
|
if ("value" in composer) {
|
||||||
composer.value = "";
|
composer.value = "";
|
||||||
} else {
|
} else {
|
||||||
composer.textContent = "";
|
composer.replaceChildren();
|
||||||
}
|
}
|
||||||
composer.dispatchEvent(new InputEvent("input", { bubbles: true, inputType: "deleteContentBackward", data: null }));
|
composer.dispatchEvent(new InputEvent("input", { bubbles: true, inputType: "deleteContentBackward", data: null }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
composer.dispatchEvent(new Event("change", { bubbles: true }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const beforeInput = new InputEvent("beforeinput", {
|
const beforeInput = new InputEvent("beforeinput", {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
@@ -3281,8 +3285,7 @@ async function fillComposerViaLocator(p, text) {
|
|||||||
await composer.fill("", { timeout: 3000 });
|
await composer.fill("", { timeout: 3000 });
|
||||||
await composer.fill(text, { timeout: 3000 });
|
await composer.fill(text, { timeout: 3000 });
|
||||||
await p.waitForTimeout(200);
|
await p.waitForTimeout(200);
|
||||||
const currentText = await readComposerText(p);
|
if (composerStateMatchesText(await readComposerState(p), text)) {
|
||||||
if (currentText !== null && messageTextMatches(currentText, text)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -3385,8 +3388,7 @@ async function focusComposer(p) {
|
|||||||
async function waitForComposerToClear(p, timeoutMs = 4000) {
|
async function waitForComposerToClear(p, timeoutMs = 4000) {
|
||||||
const deadline = Date.now() + timeoutMs;
|
const deadline = Date.now() + timeoutMs;
|
||||||
while (Date.now() < deadline) {
|
while (Date.now() < deadline) {
|
||||||
const text = await readComposerText(p);
|
if (composerStateIsEmpty(await readComposerState(p))) {
|
||||||
if (text !== null && text.length === 0) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3397,6 +3399,11 @@ async function waitForComposerToClear(p, timeoutMs = 4000) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function readComposerText(p) {
|
async function readComposerText(p) {
|
||||||
|
const state = await readComposerState(p);
|
||||||
|
return state?.text ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readComposerState(p) {
|
||||||
return await p.evaluate(() => {
|
return await p.evaluate(() => {
|
||||||
const cleanText = (value) => String(value || "")
|
const cleanText = (value) => String(value || "")
|
||||||
.replace(/\u00a0/g, " ")
|
.replace(/\u00a0/g, " ")
|
||||||
@@ -3404,6 +3411,7 @@ async function readComposerText(p) {
|
|||||||
.replace(/\s+/g, " ")
|
.replace(/\s+/g, " ")
|
||||||
.trim();
|
.trim();
|
||||||
const isVisible = (element) => {
|
const isVisible = (element) => {
|
||||||
|
if (!(element instanceof Element)) return false;
|
||||||
const style = window.getComputedStyle(element);
|
const style = window.getComputedStyle(element);
|
||||||
const rect = element.getBoundingClientRect();
|
const rect = element.getBoundingClientRect();
|
||||||
return style.visibility !== "hidden" &&
|
return style.visibility !== "hidden" &&
|
||||||
@@ -3458,10 +3466,73 @@ async function readComposerText(p) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const value = "value" in composer ? composer.value : "";
|
const value = "value" in composer ? composer.value : "";
|
||||||
return cleanText(value || composer.innerText || composer.textContent);
|
const text = cleanText(value || composer.innerText || composer.textContent);
|
||||||
|
const richContentCount = Array.from(composer.querySelectorAll([
|
||||||
|
"img",
|
||||||
|
"canvas",
|
||||||
|
"svg",
|
||||||
|
"[class*='emoji' i]",
|
||||||
|
"[class*='sticker' i]",
|
||||||
|
"[class*='lottie' i]",
|
||||||
|
"[class*='emoticon' i]",
|
||||||
|
"[data-testid*='emoji' i]",
|
||||||
|
"[data-testid*='sticker' i]",
|
||||||
|
"[data-testid*='lottie' i]"
|
||||||
|
].join(",")))
|
||||||
|
.filter((element) => isVisible(element))
|
||||||
|
.filter((element) => !/placeholder/i.test(String(element.className || "")))
|
||||||
|
.length;
|
||||||
|
return { text, richContentCount };
|
||||||
}).catch(() => null);
|
}).catch(() => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function composerStateIsEmpty(state) {
|
||||||
|
return Boolean(state && !state.text && (state.richContentCount || 0) === 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function composerStateMatchesText(state, wantedText) {
|
||||||
|
if (!state) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messageTextMatches(state.text, wantedText)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((state.richContentCount || 0) === 0 || !containsEmojiSyntax(wantedText)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEmojiOnlyText(wantedText)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const composerText = stripEmojiForComparison(state.text);
|
||||||
|
const wanted = stripEmojiForComparison(wantedText);
|
||||||
|
return Boolean(composerText && wanted && (
|
||||||
|
composerText === wanted ||
|
||||||
|
composerText.endsWith(wanted) ||
|
||||||
|
wanted.endsWith(composerText)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
function containsEmojiSyntax(value) {
|
||||||
|
return /[\p{Extended_Pictographic}\u200d\ufe0f\ufe0e]/u.test(String(value || ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmojiOnlyText(value) {
|
||||||
|
const text = cleanComparableText(value, 1000);
|
||||||
|
return Boolean(text && containsEmojiSyntax(text) && !stripEmojiForComparison(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripEmojiForComparison(value) {
|
||||||
|
return cleanComparableText(value, 1000)
|
||||||
|
.replace(/[\p{Extended_Pictographic}\u200d\ufe0f\ufe0e]/gu, "")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
function messageTextMatches(value, wantedText) {
|
function messageTextMatches(value, wantedText) {
|
||||||
const text = cleanComparableText(value, 1000).toLowerCase();
|
const text = cleanComparableText(value, 1000).toLowerCase();
|
||||||
const wanted = cleanComparableText(wantedText, 1000).toLowerCase();
|
const wanted = cleanComparableText(wantedText, 1000).toLowerCase();
|
||||||
|
|||||||
Reference in New Issue
Block a user