From 438f7323d1a7cd19c9ccb1172fb6d40d8fb85430 Mon Sep 17 00:00:00 2001 From: sevenhill Date: Sat, 4 Jul 2026 14:34:59 +0300 Subject: [PATCH] Fix DOM sticker screenshot selection --- worker/src/server.js | 127 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 104 insertions(+), 23 deletions(-) diff --git a/worker/src/server.js b/worker/src/server.js index b96686b..9caf001 100644 --- a/worker/src/server.js +++ b/worker/src/server.js @@ -875,38 +875,119 @@ async function downloadDomAudioThroughPlay(p, request) { async function downloadDomStickerThroughScreenshot(p, request) { const testId = String(request?.testId || "").trim(); - if (testId) { - const selector = `[data-testid="${testId.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"]`; - const sticker = p.locator(selector).first(); - if (await sticker.count()) { - await sticker.scrollIntoViewIfNeeded({ timeout: 3000 }).catch(() => {}); - const buffer = await sticker.screenshot({ type: "png", timeout: 10000 }); - return { contentType: "image/png", buffer }; + const timeText = String(request?.timeText || "").trim(); + const selectorForTestId = (value) => `[data-testid="${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"]`; + const screenshotCandidate = async (handles, requestedIndex) => { + const candidates = []; + for (const handle of handles) { + const meta = await handle.evaluate((node, requestedTimeText) => { + const element = node instanceof HTMLElement ? node : node.parentElement; + if (!element) { + return null; + } + + const style = window.getComputedStyle(element); + const rect = element.getBoundingClientRect(); + if (style.visibility === "hidden" || + style.display === "none" || + rect.width <= 0 || + rect.height <= 0 || + rect.bottom < 0 || + rect.right < 0 || + rect.top > window.innerHeight || + rect.left > window.innerWidth) { + return null; + } + + const wrapper = element.closest("[class*='messageWrapper' i], [role='listitem'], [class*='item' i], [class*='bubbleContent' i], [class*='bordersWrapper' i]") || element; + const cleanText = (value) => String(value || "") + .replace(/\u00a0/g, " ") + .replace(/\s+/g, " ") + .trim(); + const wrapperText = cleanText(wrapper.innerText || wrapper.textContent); + const metaText = Array.from(wrapper.querySelectorAll("[class*='meta' i], time")) + .map((item) => cleanText( + item.getAttribute?.("datetime") || + item.getAttribute?.("aria-label") || + item.getAttribute?.("title") || + item.innerText || + item.textContent)) + .filter(Boolean) + .join(" "); + const timeMatches = requestedTimeText && + (wrapperText.includes(requestedTimeText) || metaText.includes(requestedTimeText)); + const semantic = [ + element.getAttribute("data-testid"), + element.getAttribute("aria-label"), + element.getAttribute("title"), + element.className, + wrapper.className, + wrapperText + ].filter(Boolean).join(" ").toLowerCase(); + const looksLikeSticker = /sticker|\u0441\u0442\u0438\u043a\u0435\u0440/.test(semantic) || + Boolean(element.querySelector("canvas,img,svg")) || + element.matches("canvas,img,svg"); + const area = rect.width * rect.height; + let score = 0; + if (looksLikeSticker) score += 4; + if (timeMatches) score += 40; + if (rect.width >= 72 && rect.height >= 72) score += 16; + if (rect.width >= 120 && rect.height >= 120) score += 8; + score += Math.min(12, area / 2500); + if (rect.width < 40 || rect.height < 40) score -= 30; + if (area < 4096) score -= 20; + return { + score, + width: rect.width, + height: rect.height, + y: rect.y, + area, + timeMatches: Boolean(timeMatches) + }; + }, timeText).catch(() => null); + + if (!meta || meta.score <= 0) { + await handle.dispose().catch(() => {}); + continue; + } + + candidates.push({ handle, meta }); } - } + + const strict = timeText + ? candidates.filter((candidate) => candidate.meta.timeMatches) + : candidates; + const sorted = (strict.length ? strict : candidates) + .sort((a, b) => b.meta.score - a.meta.score || b.meta.area - a.meta.area || b.meta.y - a.meta.y); + const target = sorted[Math.max(0, Math.min(requestedIndex, sorted.length - 1))]; + if (!target) { + return null; + } + + await target.handle.scrollIntoViewIfNeeded({ timeout: 3000 }).catch(() => {}); + const buffer = await target.handle.screenshot({ type: "png", timeout: 10000 }); + for (const item of candidates) { + await item.handle.dispose().catch(() => {}); + } + return { contentType: "image/png", buffer }; + }; const index = Number.isFinite(Number(request?.index)) ? Number(request.index) : 0; - const handles = await p.$$("[data-testid^='sticker-'], button[class*='sticker' i], [class*='sticker' i] canvas, canvas"); - const visible = []; - for (const handle of handles) { - const box = await handle.boundingBox().catch(() => null); - if (box && box.width >= 24 && box.height >= 24) { - visible.push({ handle, box }); - } else { - await handle.dispose().catch(() => {}); + if (testId) { + const handles = await p.$$(selectorForTestId(testId)); + const result = await screenshotCandidate(handles, 0); + if (result) { + return result; } } - const target = visible[Math.max(0, Math.min(index, visible.length - 1))]; - if (!target) { + const handles = await p.$$("[data-testid^='sticker-'], button[class*='sticker' i], [class*='sticker' i] canvas, canvas"); + const result = await screenshotCandidate(handles, index); + if (!result) { throw new Error("MAX sticker canvas was not found."); } - const buffer = await target.handle.screenshot({ type: "png", timeout: 10000 }); - for (const item of visible) { - await item.handle.dispose().catch(() => {}); - } - return { contentType: "image/png", buffer }; + return result; } function contentTypeFromFileName(fileName) {