200 lines
5.0 KiB
JavaScript
200 lines
5.0 KiB
JavaScript
import fs from 'node:fs/promises'
|
|
|
|
import { downloadRemoteQrImage, logBrowserSessionDebug, waitForFrame } from './session-login-shared.js'
|
|
|
|
const QQ_QR_TARGET_SIZE = 144
|
|
|
|
export async function ensureQqLoginReady(page) {
|
|
try {
|
|
const frame = await waitForFrame(
|
|
page,
|
|
(item) => item.url().includes('xui.ptlogin2.qq.com/cgi-bin/xlogin'),
|
|
10_000,
|
|
)
|
|
const switcher = frame.locator('#switcher_qlogin')
|
|
|
|
if (!(await switcher.count())) {
|
|
return
|
|
}
|
|
|
|
await switcher.click({ timeout: 3_000 }).catch(() => null)
|
|
await frame.waitForTimeout(250)
|
|
} catch {
|
|
// ignore qq inner tab switch failures; the screenshot retry path will try again
|
|
}
|
|
}
|
|
|
|
export async function captureQqQrImage(page, qrImagePath) {
|
|
const frame = await waitForFrame(
|
|
page,
|
|
(item) => item.url().includes('xui.ptlogin2.qq.com/cgi-bin/xlogin'),
|
|
10_000,
|
|
)
|
|
const qrUrl = await resolveQqQrImageUrl(frame)
|
|
const effectiveQrUrl = upgradeQqQrImageUrl(qrUrl)
|
|
|
|
logBrowserSessionDebug('qq.capture.qrUrl', {
|
|
qrUrl,
|
|
effectiveQrUrl,
|
|
frameUrl: frame.url(),
|
|
})
|
|
|
|
if (effectiveQrUrl) {
|
|
const body = await downloadRemoteQrImage(page, effectiveQrUrl, qrImagePath, frame.url(), 'qq')
|
|
logBrowserSessionDebug('qq.capture.downloadResult', {
|
|
qrUrl: effectiveQrUrl,
|
|
downloaded: Boolean(body?.length),
|
|
qrImagePath,
|
|
})
|
|
|
|
if (body?.length) {
|
|
await fs.writeFile(qrImagePath, body)
|
|
return
|
|
}
|
|
}
|
|
|
|
logBrowserSessionDebug('qq.capture.fallbackScreenshot', {
|
|
qrImagePath,
|
|
})
|
|
|
|
const qrLocator = await findQqQrLocator(page, frame)
|
|
|
|
try {
|
|
await qrLocator.screenshot({ path: qrImagePath })
|
|
return
|
|
} catch {
|
|
const iframeLocator = page.locator('#milo-qcwx-frame-qc').first()
|
|
await iframeLocator.waitFor({ state: 'visible', timeout: 15_000 })
|
|
await iframeLocator.screenshot({ path: qrImagePath })
|
|
}
|
|
}
|
|
|
|
export async function findQqQrLocator(page, frame = null) {
|
|
const effectiveFrame =
|
|
frame ||
|
|
await waitForFrame(page, (item) => item.url().includes('xui.ptlogin2.qq.com/cgi-bin/xlogin'))
|
|
const locator = effectiveFrame.locator('#qrlogin_img')
|
|
await locator.waitFor({ state: 'visible', timeout: 15_000 })
|
|
logBrowserSessionDebug('qq.findQrLocator.visibleReady', {
|
|
frameUrl: effectiveFrame.url(),
|
|
})
|
|
return locator
|
|
}
|
|
|
|
export async function extractQqQrState(page) {
|
|
const frame = page.frames().find((item) => item.url().includes('xui.ptlogin2.qq.com/cgi-bin/xlogin'))
|
|
|
|
if (!frame) {
|
|
return {
|
|
visible: false,
|
|
scanned: false,
|
|
expired: false,
|
|
message: '',
|
|
}
|
|
}
|
|
|
|
try {
|
|
const { qrVisible, bodyText } = await frame
|
|
.evaluate(() => {
|
|
const isVisible = (element) => {
|
|
if (!(element instanceof HTMLElement)) {
|
|
return false
|
|
}
|
|
|
|
const style = window.getComputedStyle(element)
|
|
const rect = element.getBoundingClientRect()
|
|
return (
|
|
style.display !== 'none' &&
|
|
style.visibility !== 'hidden' &&
|
|
style.opacity !== '0' &&
|
|
rect.width > 0 &&
|
|
rect.height > 0
|
|
)
|
|
}
|
|
|
|
const qrImage = document.querySelector('#qrlogin_img')
|
|
const bodyText = String(document.body?.textContent || '').replace(/\s+/g, ' ').trim()
|
|
|
|
return {
|
|
qrVisible:
|
|
qrImage instanceof HTMLImageElement &&
|
|
isVisible(qrImage) &&
|
|
Number(qrImage.naturalWidth || 0) > 0 &&
|
|
Number(qrImage.naturalHeight || 0) > 0,
|
|
bodyText,
|
|
}
|
|
})
|
|
.catch(() => ({
|
|
qrVisible: false,
|
|
bodyText: '',
|
|
}))
|
|
|
|
return {
|
|
visible: true,
|
|
qrVisible,
|
|
scanned: !qrVisible && /扫描成功|请在手机上确认登录/.test(bodyText),
|
|
expired: qrVisible ? false : /二维码失效|已失效/.test(bodyText),
|
|
message: bodyText.slice(0, 200),
|
|
frameUrl: frame.url(),
|
|
}
|
|
} catch {
|
|
return {
|
|
visible: true,
|
|
qrVisible: false,
|
|
scanned: false,
|
|
expired: false,
|
|
message: '',
|
|
frameUrl: frame.url(),
|
|
}
|
|
}
|
|
}
|
|
|
|
async function resolveQqQrImageUrl(frame) {
|
|
return frame
|
|
.evaluate(() => {
|
|
const qrImage = document.querySelector('#qrlogin_img')
|
|
|
|
if (!(qrImage instanceof HTMLImageElement)) {
|
|
return ''
|
|
}
|
|
|
|
return String(qrImage.currentSrc || qrImage.src || qrImage.getAttribute('src') || '').trim()
|
|
})
|
|
.then((src) => {
|
|
if (!src) {
|
|
return ''
|
|
}
|
|
|
|
try {
|
|
return new URL(src, frame.url()).toString()
|
|
} catch {
|
|
return ''
|
|
}
|
|
})
|
|
.catch(() => '')
|
|
}
|
|
|
|
function upgradeQqQrImageUrl(qrUrl) {
|
|
if (!qrUrl) {
|
|
return ''
|
|
}
|
|
|
|
try {
|
|
const url = new URL(qrUrl)
|
|
|
|
if (!/xui\.ptlogin2\.qq\.com$/i.test(url.hostname) || !/\/ptqrshow$/i.test(url.pathname)) {
|
|
return qrUrl
|
|
}
|
|
|
|
const currentSize = Number(url.searchParams.get('d') || 0)
|
|
|
|
if (!Number.isFinite(currentSize) || currentSize < QQ_QR_TARGET_SIZE) {
|
|
url.searchParams.set('d', String(QQ_QR_TARGET_SIZE))
|
|
}
|
|
|
|
return url.toString()
|
|
} catch {
|
|
return qrUrl
|
|
}
|
|
}
|