Files
social-mcp/apps/xhh-mcp/src/platforms/xiaoheihe/interaction.ts
T

99 lines
3.3 KiB
TypeScript

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;
}