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