From 057d4346fe624b83b77e9f4b0784a24853394a93 Mon Sep 17 00:00:00 2001 From: sevenhill Date: Mon, 6 Jul 2026 14:12:50 +0300 Subject: [PATCH] Reject wallpaper captures for stickers --- worker/src/server.js | 86 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/worker/src/server.js b/worker/src/server.js index 612836b..6c27b11 100644 --- a/worker/src/server.js +++ b/worker/src/server.js @@ -1378,6 +1378,88 @@ async function downloadDomStickerThroughScreenshot(p, request) { buffer: dataUrl.startsWith(prefix) ? Buffer.from(dataUrl.slice(prefix.length), "base64") : null }; }; + const inspectStickerPng = async (buffer) => { + const base64 = buffer.toString("base64"); + return await p.evaluate(async (dataUrl) => { + const image = new Image(); + image.src = dataUrl; + await image.decode(); + + const canvas = document.createElement("canvas"); + canvas.width = image.naturalWidth; + canvas.height = image.naturalHeight; + const context = canvas.getContext("2d", { willReadFrequently: true }); + if (!context || canvas.width <= 0 || canvas.height <= 0) { + return null; + } + + context.drawImage(image, 0, 0); + const data = context.getImageData(0, 0, canvas.width, canvas.height).data; + const totalPixels = canvas.width * canvas.height; + const step = Math.max(1, Math.floor(totalPixels / 20000)); + let sampled = 0; + let opaque = 0; + let dark = 0; + let white = 0; + let cyan = 0; + let sumR = 0; + let sumG = 0; + let sumB = 0; + + for (let pixel = 0; pixel < totalPixels; pixel += step) { + const offset = pixel * 4; + const r = data[offset]; + const g = data[offset + 1]; + const b = data[offset + 2]; + const a = data[offset + 3]; + sampled++; + if (a <= 8) { + continue; + } + + opaque++; + sumR += r; + sumG += g; + sumB += b; + if (Math.max(r, g, b) < 95) dark++; + if (Math.min(r, g, b) > 230) white++; + if (b > r + 25 && g > r + 20 && b > 150 && g > 130) cyan++; + } + + return { + width: canvas.width, + height: canvas.height, + sampled, + opaqueRatio: sampled ? opaque / sampled : 0, + darkRatio: opaque ? dark / opaque : 0, + whiteRatio: opaque ? white / opaque : 0, + cyanRatio: opaque ? cyan / opaque : 0, + avgR: opaque ? sumR / opaque : 0, + avgG: opaque ? sumG / opaque : 0, + avgB: opaque ? sumB / opaque : 0 + }; + }, `data:image/png;base64,${base64}`).catch(() => null); + }; + const isMaxWallpaperCapture = (metrics) => { + if (!metrics) { + return false; + } + + return metrics.opaqueRatio > 0.98 && + metrics.cyanRatio > 0.72 && + metrics.darkRatio < 0.03 && + metrics.whiteRatio < 0.08 && + metrics.avgB > metrics.avgR + 45 && + metrics.avgG > metrics.avgR + 30; + }; + const stickerCaptureResult = async (buffer) => { + const metrics = await inspectStickerPng(buffer); + if (isMaxWallpaperCapture(metrics)) { + throw new Error(`${kindLabel} capture matched MAX chat wallpaper instead of sticker.`); + } + + return { contentType: "image/png", buffer }; + }; const screenshotCandidate = async (handles, requestedIndex) => { const candidates = []; for (const handle of handles) { @@ -1522,7 +1604,7 @@ async function downloadDomStickerThroughScreenshot(p, request) { await target.handle.scrollIntoViewIfNeeded({ timeout: 3000 }).catch(() => {}); const canvasCapture = await captureCanvasPng(target.handle); if (canvasCapture.buffer) { - return { contentType: "image/png", buffer: canvasCapture.buffer }; + return await stickerCaptureResult(canvasCapture.buffer); } let buffer; @@ -1548,7 +1630,7 @@ async function downloadDomStickerThroughScreenshot(p, request) { throw error; } } - return { contentType: "image/png", buffer }; + return await stickerCaptureResult(buffer); } finally { for (const item of candidates) { await item.handle.dispose().catch(() => {});