Fix xianyu QR login refresh

Exclude tiny QR-login icon candidates, expose captured QR dimensions in the Web UI, and refresh the portable Hermes skill release archives.
This commit is contained in:
chick
2026-05-07 15:29:05 +08:00
parent 6c8fdc861f
commit efd6eb8fb9
10 changed files with 264 additions and 22 deletions
+73 -13
View File
@@ -141,27 +141,53 @@ async function clickIfVisible(page, selector, timeout = 1000) {
return false;
}
async function isUsableQrCandidate(loc) {
try {
const box = await loc.boundingBox({ timeout: 1200 });
if (!box) return false;
// Goofish/Taobao pages contain tiny QR icons (for switching login modes).
// Those are visible canvases/images too, but screenshotting them produces a
// 24x24 icon instead of the actual scannable QR code. Only accept elements
// large enough to be a real QR/QR container.
return box.width >= 120 && box.height >= 120;
} catch {
return false;
}
}
async function findQrLikeLocator(page) {
const selectors = [
'canvas',
'img[src^="data:image"]',
'img[src*="qr"]',
'img[src*="qrcode"]',
'[class*="qrcode"] canvas',
'[class*="qrcode"] img',
'[class*="qr-code"] canvas',
'[class*="qr-code"] img',
'[class*="qrCode"] canvas',
'[class*="qrCode"] img',
'[class*="qr"] canvas',
'[class*="qr"] img',
'[class*="login"] canvas',
'[class*="login"] img',
'canvas',
'img[src^="data:image"]',
'img[src*="qr"]',
'img[src*="qrcode"]',
];
for (const selector of selectors) {
const loc = page.locator(selector).first();
if (await loc.isVisible({ timeout: 1200 }).catch(() => false)) return loc;
const locs = page.locator(selector);
const count = Math.min(await locs.count().catch(() => 0), 8);
for (let i = 0; i < count; i += 1) {
const loc = locs.nth(i);
if (await loc.isVisible({ timeout: 800 }).catch(() => false) && await isUsableQrCandidate(loc)) return loc;
}
}
for (const frame of page.frames()) {
for (const selector of selectors) {
const loc = frame.locator(selector).first();
if (await loc.isVisible({ timeout: 800 }).catch(() => false)) return loc;
const locs = frame.locator(selector);
const count = Math.min(await locs.count().catch(() => 0), 8);
for (let i = 0; i < count; i += 1) {
const loc = locs.nth(i);
if (await loc.isVisible({ timeout: 500 }).catch(() => false) && await isUsableQrCandidate(loc)) return loc;
}
}
}
return null;
@@ -197,16 +223,50 @@ export async function getLoginQr(emitLog = () => {}) {
let screenshotKind = '';
if (!status.loggedIn) {
const qrLike = await findQrLikeLocator(page);
const modal = page.locator('[class*="login-modal"], [class*="Login"], [class*="qrcode"], [class*="qr"], iframe').first();
const target = qrLike || (await modal.isVisible({ timeout: 1500 }).catch(() => false) ? modal : page.locator('body'));
screenshotKind = qrLike ? '二维码区域' : '登录面板截图';
const modalCandidates = [
'[class*="login-modal"]',
'[class*="Login"]',
'[class*="qrcode"]',
'[class*="qr-code"]',
'[class*="qrCode"]',
'[class*="qr"]',
'iframe',
];
let modal = null;
for (const selector of modalCandidates) {
const locs = page.locator(selector);
const count = Math.min(await locs.count().catch(() => 0), 8);
for (let i = 0; i < count; i += 1) {
const loc = locs.nth(i);
if (await loc.isVisible({ timeout: 500 }).catch(() => false) && await isUsableQrCandidate(loc)) {
modal = loc;
break;
}
}
if (modal) break;
}
const target = qrLike || modal || page.locator('body');
screenshotKind = qrLike ? '二维码区域' : (modal ? '登录面板截图' : '当前登录页面截图');
const buf = await target.screenshot({ type: 'png', timeout: 8000 }).catch(() => null);
if (buf) qrImage = `data:image/png;base64,${buf.toString('base64')}`;
}
let qrImageWidth = 0;
let qrImageHeight = 0;
if (qrImage) {
const match = qrImage.match(/^data:image\/png;base64,(.+)$/);
if (match) {
const raw = Buffer.from(match[1], 'base64');
if (raw.length >= 24 && raw.toString('ascii', 1, 4) === 'PNG') {
qrImageWidth = raw.readUInt32BE(16);
qrImageHeight = raw.readUInt32BE(20);
}
}
}
if (status.loggedIn) emitLog('✅ 当前账号已登录,无需扫码');
else emitLog(`已从${redirectedProvider}生成${screenshotKind || '登录截图'}${clicked ? '' : '(未找到明确登录按钮,已截取当前页面)'};请扫码后点“刷新账号状态”。当前 URL:${currentUrl}`);
return { ...status, qrImage, qrCheckedAt: new Date().toISOString(), qrSource: redirectedProvider, qrUrl: currentUrl, qrScreenshotKind: screenshotKind };
else emitLog(`已从${redirectedProvider}生成${screenshotKind || '登录截图'}${qrImageWidth && qrImageHeight ? `${qrImageWidth}×${qrImageHeight}` : ''}${clicked ? '' : '(未找到明确登录按钮,已截取当前页面)'};请扫码后点“刷新账号状态”。当前 URL:${currentUrl}`);
return { ...status, qrImage, qrCheckedAt: new Date().toISOString(), qrSource: redirectedProvider, qrUrl: currentUrl, qrScreenshotKind: screenshotKind, qrImageWidth, qrImageHeight };
}
export async function getLoginStatus(existingPage = null) {