This commit is contained in:
dengqichen 2025-11-18 11:23:58 +08:00
parent 990172f559
commit e4e9465bed

View File

@ -1192,61 +1192,69 @@ class WindsurfRegister {
logger.info(this.siteName, ' → Token 已注入,准备点击 checkbox');
}
// 等待一下,让 token 生效
await this.human.randomDelay(1000, 2000);
// 等待更长时间,让 hCaptcha 完全响应(可能降级为图片)
logger.info(this.siteName, ' → 等待 hCaptcha 响应5秒...');
await this.human.randomDelay(5000, 6000);
// 点击对话框中的 checkbox必须
logger.info(this.siteName, ' → 查找并点击 checkbox...');
try {
const checkboxClicked = await this.page.evaluate(() => {
const dialog = document.querySelector('[role="dialog"]');
if (!dialog) return { success: false, reason: 'no-dialog' };
// 检测是否有图片挑战出现
logger.info(this.siteName, ' → 检测验证码状态...');
const captchaStatus = await this.page.evaluate(() => {
// 检查所有 iframe
const iframes = document.querySelectorAll('iframe');
let hasImageChallenge = false;
let challengeText = '';
// 查找 checkbox 元素
const checkbox = dialog.querySelector('input[type="checkbox"]') ||
dialog.querySelector('[role="checkbox"]') ||
dialog.querySelector('#checkbox');
for (const iframe of iframes) {
try {
// 检查 iframe 的 src 是否包含 hcaptcha
if (iframe.src && iframe.src.includes('hcaptcha')) {
// 尝试访问 iframe 内容(跨域可能失败)
try {
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
if (iframeDoc) {
const bodyText = iframeDoc.body ? iframeDoc.body.innerText : '';
if (bodyText.includes('点击') || bodyText.includes('选择') || bodyText.includes('Select')) {
hasImageChallenge = true;
challengeText = bodyText.substring(0, 50);
break;
}
}
} catch (e) {
// 跨域 iframe无法访问内容
}
if (checkbox) {
const rect = checkbox.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
checkbox.click();
return {
success: true,
checked: checkbox.checked,
coords: { x: Math.round(rect.left + rect.width/2), y: Math.round(rect.top + rect.height/2) }
};
// 即使无法访问内容,如果 iframe 存在且可见,也认为可能有挑战
const rect = iframe.getBoundingClientRect();
if (rect.width > 300 && rect.height > 300) {
hasImageChallenge = true;
break;
}
}
return { success: false, reason: 'not-visible' };
}
return { success: false, reason: 'not-found' };
});
if (checkboxClicked.success) {
logger.success(this.siteName, ` → ✓ 已点击 checkbox`);
} else {
logger.warn(this.siteName, ` → ⚠️ 未找到 checkbox (${checkboxClicked.reason}),尝试坐标点击...`);
// 备用方案:通过坐标点击对话框左侧
const coords = await this.page.evaluate(() => {
const dialog = document.querySelector('[role="dialog"]');
if (!dialog) return null;
const rect = dialog.getBoundingClientRect();
// checkbox 通常在对话框左侧,距离顶部约 1/3 处
return {
x: rect.left + 40,
y: rect.top + rect.height * 0.4
};
});
if (coords) {
await this.page.mouse.click(coords.x, coords.y);
logger.success(this.siteName, ` → ✓ 已点击坐标 (${coords.x}, ${coords.y})`);
} catch (e) {
// 忽略错误
}
}
} catch (e) {
logger.warn(this.siteName, ` → 点击失败: ${e.message}`);
// 检查主页面的 response
const response = document.querySelector('[name="h-captcha-response"]') ||
document.querySelector('[name="g-recaptcha-response"]');
const hasResponse = response && response.value && response.value.length > 20;
return {
hasImageChallenge,
hasResponse,
challengeText,
iframeCount: iframes.length
};
});
logger.info(this.siteName, ` → 检测结果: iframe数=${captchaStatus.iframeCount}, 图片挑战=${captchaStatus.hasImageChallenge}, token已填充=${captchaStatus.hasResponse}`);
if (captchaStatus.hasImageChallenge) {
logger.warn(this.siteName, ' → ⚠️ 检测到图片挑战Token 可能被拒绝!');
if (captchaStatus.challengeText) {
logger.info(this.siteName, ` → 挑战内容: ${captchaStatus.challengeText}`);
}
}
await this.human.randomDelay(1000, 2000);
@ -1255,53 +1263,70 @@ class WindsurfRegister {
logger.info(this.siteName, ' → 等待验证完成...');
const startTime = Date.now();
let dialogClosed = false;
let hasImageChallenge = false;
const maxWaitTime = 60000; // 最多60秒包含图片挑战时间
let hasImageChallenge = captchaStatus.hasImageChallenge; // 使用前面检测的结果
const maxWaitTime = 120000; // 图片挑战需要更长时间最多120秒
if (hasImageChallenge) {
logger.warn(this.siteName, ' → ⚠️ 需要手动完成图片验证最多等待120秒...');
}
while (Date.now() - startTime < maxWaitTime) {
// 优先检查页面是否跳转(支付成功)
const currentUrl = this.page.url();
if (!currentUrl.includes('stripe.com') && !currentUrl.includes('checkout.stripe.com')) {
dialogClosed = true;
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
logger.success(this.siteName, ` → ✓ 验证完成,页面已跳转(耗时${elapsed}秒)`);
break;
}
const status = await this.page.evaluate(() => {
const dialog = document.querySelector('[role="dialog"]');
if (!dialog) return { dialogGone: true, verified: true, hasImageChallenge: false };
const style = window.getComputedStyle(dialog);
const isHidden = style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0';
// 检查 token 是否已填充
// 检查 token 是否已填充(主要判断依据)
const response = document.querySelector('[name="h-captcha-response"]') ||
document.querySelector('[name="g-recaptcha-response"]');
const hasResponse = response && response.value && response.value.length > 20;
// 检查是否有图片挑战
const dialogText = dialog.innerText || '';
const hasImageTask = dialogText.includes('选择') ||
dialogText.includes('Select') ||
dialog.querySelector('img[src*="hcaptcha"]') ||
dialog.querySelector('[class*="task"]');
// 检查 iframe 中的图片挑战
let hasImageChallenge = false;
const iframes = document.querySelectorAll('iframe[src*="hcaptcha"]');
for (const iframe of iframes) {
const rect = iframe.getBoundingClientRect();
if (rect.width > 300 && rect.height > 300) {
hasImageChallenge = true;
break;
}
}
return {
dialogGone: !dialog || isHidden,
verified: hasResponse,
hasImageChallenge: hasImageTask,
dialogText: dialogText.substring(0, 80)
hasImageChallenge: hasImageChallenge,
iframeCount: iframes.length
};
});
// 第一次检测到图片挑战
// 第一次检测到图片挑战(之前没检测到)
if (status.hasImageChallenge && !hasImageChallenge) {
hasImageChallenge = true;
logger.warn(this.siteName, ' → ⚠️ Token 被降级,出现图片挑战!');
logger.info(this.siteName, ` → 任务: ${status.dialogText}`);
logger.warn(this.siteName, ' → ⚠️ 检测到图片挑战!');
logger.warn(this.siteName, ' → 请手动完成图片验证...');
}
// 验证完成
if (status.dialogGone || (status.verified && !status.hasImageChallenge)) {
// 验证完成的条件token 已填充 且 没有图片挑战
if (status.verified && !status.hasImageChallenge) {
dialogClosed = true;
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
logger.success(this.siteName, ` → ✓ 验证完成(耗时${elapsed}秒)`);
break;
}
// 每10秒输出一次进度仅在有图片挑战时
if (hasImageChallenge) {
const elapsed = Date.now() - startTime;
if (elapsed > 0 && elapsed % 10000 === 0) {
logger.info(this.siteName, ` → 等待手动验证... (${(elapsed/1000).toFixed(0)}秒)`);
}
}
// 动态调整检查间隔
const interval = hasImageChallenge ? 2000 : 1000;
await new Promise(resolve => setTimeout(resolve, interval));
@ -1978,7 +2003,8 @@ class WindsurfRegister {
billingDate: this.billingInfo ? this.billingInfo.date : null,
paymentCardNumber: this.cardInfo ? this.cardInfo.number : null,
paymentCountry: this.cardInfo ? this.cardInfo.country : 'MO',
status: 'active'
status: 'active',
isOnSale: false
};
// 保存到数据库