Fix DOM sticker screenshot selection
This commit is contained in:
+104
-23
@@ -875,38 +875,119 @@ async function downloadDomAudioThroughPlay(p, request) {
|
|||||||
|
|
||||||
async function downloadDomStickerThroughScreenshot(p, request) {
|
async function downloadDomStickerThroughScreenshot(p, request) {
|
||||||
const testId = String(request?.testId || "").trim();
|
const testId = String(request?.testId || "").trim();
|
||||||
if (testId) {
|
const timeText = String(request?.timeText || "").trim();
|
||||||
const selector = `[data-testid="${testId.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"]`;
|
const selectorForTestId = (value) => `[data-testid="${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"]`;
|
||||||
const sticker = p.locator(selector).first();
|
const screenshotCandidate = async (handles, requestedIndex) => {
|
||||||
if (await sticker.count()) {
|
const candidates = [];
|
||||||
await sticker.scrollIntoViewIfNeeded({ timeout: 3000 }).catch(() => {});
|
for (const handle of handles) {
|
||||||
const buffer = await sticker.screenshot({ type: "png", timeout: 10000 });
|
const meta = await handle.evaluate((node, requestedTimeText) => {
|
||||||
return { contentType: "image/png", buffer };
|
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 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");
|
if (testId) {
|
||||||
const visible = [];
|
const handles = await p.$$(selectorForTestId(testId));
|
||||||
for (const handle of handles) {
|
const result = await screenshotCandidate(handles, 0);
|
||||||
const box = await handle.boundingBox().catch(() => null);
|
if (result) {
|
||||||
if (box && box.width >= 24 && box.height >= 24) {
|
return result;
|
||||||
visible.push({ handle, box });
|
|
||||||
} else {
|
|
||||||
await handle.dispose().catch(() => {});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const target = visible[Math.max(0, Math.min(index, visible.length - 1))];
|
const handles = await p.$$("[data-testid^='sticker-'], button[class*='sticker' i], [class*='sticker' i] canvas, canvas");
|
||||||
if (!target) {
|
const result = await screenshotCandidate(handles, index);
|
||||||
|
if (!result) {
|
||||||
throw new Error("MAX sticker canvas was not found.");
|
throw new Error("MAX sticker canvas was not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = await target.handle.screenshot({ type: "png", timeout: 10000 });
|
return result;
|
||||||
for (const item of visible) {
|
|
||||||
await item.handle.dispose().catch(() => {});
|
|
||||||
}
|
|
||||||
return { contentType: "image/png", buffer };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function contentTypeFromFileName(fileName) {
|
function contentTypeFromFileName(fileName) {
|
||||||
|
|||||||
Reference in New Issue
Block a user