重构为Monorepo:拆分xhs/xhh应用与core包并完成双服务部署改造
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
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<boolean> {
|
||||
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<boolean> {
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user