189 lines
4.8 KiB
JavaScript
189 lines
4.8 KiB
JavaScript
// @ts-nocheck
|
|
|
|
import fs from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
|
|
import { isRetryableQrCaptureError, logBrowserSessionDebug } from './session-login-shared.js'
|
|
import { captureQqQrImage, ensureQqLoginReady, extractQqQrState } from './session-qq.js'
|
|
import { captureWxQrImage, ensureWxLoginReady, extractWxQrState } from './session-wx.js'
|
|
|
|
export async function captureSessionQr(
|
|
session,
|
|
{
|
|
ensureLoginReady = true,
|
|
ensureLoginTab: ensureLoginTabImpl = ensureLoginTab,
|
|
captureQrImageWithRetry: captureQrImageWithRetryImpl = captureQrImageWithRetry,
|
|
readFile = fs.readFile,
|
|
now = () => new Date().toISOString(),
|
|
} = {},
|
|
) {
|
|
if (ensureLoginReady) {
|
|
await ensureLoginTabImpl(session.page, session.loginType)
|
|
}
|
|
|
|
const qrImagePath = path.join(session.sessionDir, `${session.loginType}-qr.png`)
|
|
await captureQrImageWithRetryImpl(session.page, session.loginType, qrImagePath, {
|
|
ensureLoginTab: ensureLoginTabImpl,
|
|
})
|
|
|
|
session.qrImagePath = qrImagePath
|
|
session.qrImageBase64 = await readFile(qrImagePath, 'base64')
|
|
session.qrUpdatedAt = now()
|
|
session.updatedAt = session.qrUpdatedAt
|
|
}
|
|
|
|
export async function captureQrImageWithRetry(
|
|
page,
|
|
loginType,
|
|
qrImagePath,
|
|
{
|
|
captureQqQrImage: captureQqQrImageImpl = captureQqQrImage,
|
|
captureWxQrImage: captureWxQrImageImpl = captureWxQrImage,
|
|
ensureLoginTab: ensureLoginTabImpl = ensureLoginTab,
|
|
isRetryableQrCaptureError: isRetryableQrCaptureErrorImpl = isRetryableQrCaptureError,
|
|
logBrowserSessionDebug: logBrowserSessionDebugImpl = logBrowserSessionDebug,
|
|
} = {},
|
|
) {
|
|
const maxAttempts = loginType === 'qq' ? 4 : 3
|
|
let lastError = null
|
|
|
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
try {
|
|
logBrowserSessionDebugImpl('captureQrImageWithRetry.start', {
|
|
loginType,
|
|
attempt,
|
|
maxAttempts,
|
|
qrImagePath,
|
|
})
|
|
|
|
if (loginType === 'qq') {
|
|
await captureQqQrImageImpl(page, qrImagePath)
|
|
} else {
|
|
await captureWxQrImageImpl(page, qrImagePath)
|
|
}
|
|
|
|
logBrowserSessionDebugImpl('captureQrImageWithRetry.success', {
|
|
loginType,
|
|
attempt,
|
|
qrImagePath,
|
|
})
|
|
|
|
return
|
|
} catch (error) {
|
|
lastError = error
|
|
logBrowserSessionDebugImpl('captureQrImageWithRetry.error', {
|
|
loginType,
|
|
attempt,
|
|
message: error instanceof Error ? error.message : String(error),
|
|
})
|
|
|
|
if (!isRetryableQrCaptureErrorImpl(error) || attempt === maxAttempts) {
|
|
throw error
|
|
}
|
|
|
|
await page.waitForTimeout(500)
|
|
await ensureLoginTabImpl(page, loginType)
|
|
}
|
|
}
|
|
|
|
if (lastError) {
|
|
throw lastError
|
|
}
|
|
|
|
throw new Error('二维码截图失败')
|
|
}
|
|
|
|
export async function ensureLoginTab(
|
|
page,
|
|
loginType,
|
|
{
|
|
ensureQqLoginReady: ensureQqLoginReadyImpl = ensureQqLoginReady,
|
|
ensureWxLoginReady: ensureWxLoginReadyImpl = ensureWxLoginReady,
|
|
} = {},
|
|
) {
|
|
const tabSelector = loginType === 'wx' ? '.wx-tab' : '.qc-tab'
|
|
const loginButton = page.locator('#unlogin')
|
|
let interacted = false
|
|
|
|
if (await loginButton.count()) {
|
|
try {
|
|
await loginButton.first().click({ timeout: 2_000 })
|
|
interacted = true
|
|
} catch {
|
|
// ignore when the login layer is already open
|
|
}
|
|
}
|
|
|
|
const tab = page.locator(tabSelector).first()
|
|
|
|
if (await tab.count()) {
|
|
try {
|
|
await tab.click({ timeout: 2_000 })
|
|
interacted = true
|
|
} catch {
|
|
// ignore tab click failures
|
|
}
|
|
}
|
|
|
|
if (interacted) {
|
|
await page.waitForTimeout(250)
|
|
}
|
|
|
|
if (loginType === 'qq') {
|
|
await ensureQqLoginReadyImpl(page)
|
|
return
|
|
}
|
|
|
|
if (loginType === 'wx') {
|
|
await ensureWxLoginReadyImpl(page)
|
|
}
|
|
}
|
|
|
|
export async function extractHostState(page) {
|
|
return page.evaluate(() => {
|
|
const isVisible = (element) => {
|
|
if (!element) {
|
|
return false
|
|
}
|
|
|
|
const style = window.getComputedStyle(element)
|
|
|
|
return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'
|
|
}
|
|
|
|
const unlogin = document.querySelector('#unlogin')
|
|
const logined = document.querySelector('#logined')
|
|
|
|
return {
|
|
unloginVisible: isVisible(unlogin),
|
|
loginedVisible: isVisible(logined),
|
|
loginText: String(logined?.textContent || '').trim(),
|
|
}
|
|
})
|
|
}
|
|
|
|
export async function reloadActivityPageForPresentation(page, activityUrl) {
|
|
try {
|
|
await page.reload({ waitUntil: 'domcontentloaded' })
|
|
} catch {
|
|
await page.goto(activityUrl, { waitUntil: 'domcontentloaded' })
|
|
}
|
|
|
|
await page.waitForTimeout(2_500)
|
|
}
|
|
|
|
export async function extractQrState(
|
|
page,
|
|
loginType,
|
|
{
|
|
extractQqQrState: extractQqQrStateImpl = extractQqQrState,
|
|
extractWxQrState: extractWxQrStateImpl = extractWxQrState,
|
|
} = {},
|
|
) {
|
|
if (loginType === 'qq') {
|
|
return extractQqQrStateImpl(page)
|
|
}
|
|
|
|
return extractWxQrStateImpl(page)
|
|
}
|