import type { Page } from 'rebrowser-playwright'; import { XHH_SELECTORS } from './selectors.js'; import { detectCaptchaText } from './extractors.js'; function buildDetailUrl(linkId: string): string { return `https://www.xiaoheihe.cn/app/bbs/link/${encodeURIComponent(linkId)}`; } export async function setLikeState( page: Page, linkId: string, targetState: boolean, ): Promise<{ success: boolean; state: boolean; changed: boolean }> { await page.goto(buildDetailUrl(linkId), { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(1_000); const text = await page.textContent('body').catch(() => ''); if (text && detectCaptchaText(text)) { throw new Error('CAPTCHA_REQUIRED: captcha detected on interaction page'); } const current = await readButtonState(page, XHH_SELECTORS.detail.likeButton); if (current === targetState) { return { success: true, state: current, changed: false }; } const clicked = await clickAny(page, XHH_SELECTORS.detail.likeButton); if (!clicked) { return { success: false, state: current, changed: false }; } await page.waitForTimeout(700); const state = await readButtonState(page, XHH_SELECTORS.detail.likeButton); return { success: state === targetState, state, changed: state !== current, }; } export async function setFavoriteState( page: Page, linkId: string, targetState: boolean, ): Promise<{ success: boolean; state: boolean; changed: boolean }> { await page.goto(buildDetailUrl(linkId), { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(1_000); const text = await page.textContent('body').catch(() => ''); if (text && detectCaptchaText(text)) { throw new Error('CAPTCHA_REQUIRED: captcha detected on interaction page'); } const current = await readButtonState(page, XHH_SELECTORS.detail.favoriteButton); if (current === targetState) { return { success: true, state: current, changed: false }; } const clicked = await clickAny(page, XHH_SELECTORS.detail.favoriteButton); if (!clicked) { return { success: false, state: current, changed: false }; } await page.waitForTimeout(700); const state = await readButtonState(page, XHH_SELECTORS.detail.favoriteButton); return { success: state === targetState, state, changed: state !== current, }; } async function clickAny(page: Page, selectors: readonly string[]): Promise { for (const selector of selectors) { const ok = await page.locator(selector).first().click({ timeout: 2_000 }).then(() => true).catch(() => false); if (ok) return true; } return false; } async function readButtonState(page: Page, selectors: readonly string[]): Promise { for (const selector of selectors) { const state = await page .evaluate((sel) => { const node = document.querySelector(sel) as HTMLElement | null; if (!node) return null; if (node.getAttribute('aria-pressed') === 'true') return true; const cls = node.className.toString().toLowerCase(); if (cls.includes('active') || cls.includes('selected')) return true; const html = node.innerHTML.toLowerCase(); if (html.includes('filled') || html.includes('checked')) return true; return false; }, selector) .catch(() => null); if (typeof state === 'boolean') return state; } return false; }