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'); logger.info(this.siteName, ' → Token 已注入,准备点击 checkbox');
} }
// 等待一下,让 token 生效 // 等待更长时间,让 hCaptcha 完全响应(可能降级为图片)
await this.human.randomDelay(1000, 2000); logger.info(this.siteName, ' → 等待 hCaptcha 响应5秒...');
await this.human.randomDelay(5000, 6000);
// 点击对话框中的 checkbox必须 // 检测是否有图片挑战出现
logger.info(this.siteName, ' → 查找并点击 checkbox...'); logger.info(this.siteName, ' → 检测验证码状态...');
const captchaStatus = await this.page.evaluate(() => {
// 检查所有 iframe
const iframes = document.querySelectorAll('iframe');
let hasImageChallenge = false;
let challengeText = '';
for (const iframe of iframes) {
try { try {
const checkboxClicked = await this.page.evaluate(() => { // 检查 iframe 的 src 是否包含 hcaptcha
const dialog = document.querySelector('[role="dialog"]'); if (iframe.src && iframe.src.includes('hcaptcha')) {
if (!dialog) return { success: false, reason: 'no-dialog' }; // 尝试访问 iframe 内容(跨域可能失败)
try {
// 查找 checkbox 元素 const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
const checkbox = dialog.querySelector('input[type="checkbox"]') || if (iframeDoc) {
dialog.querySelector('[role="checkbox"]') || const bodyText = iframeDoc.body ? iframeDoc.body.innerText : '';
dialog.querySelector('#checkbox'); if (bodyText.includes('点击') || bodyText.includes('选择') || bodyText.includes('Select')) {
hasImageChallenge = true;
if (checkbox) { challengeText = bodyText.substring(0, 50);
const rect = checkbox.getBoundingClientRect(); break;
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) }
};
}
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}`); // 跨域 iframe无法访问内容
}
// 即使无法访问内容,如果 iframe 存在且可见,也认为可能有挑战
const rect = iframe.getBoundingClientRect();
if (rect.width > 300 && rect.height > 300) {
hasImageChallenge = true;
break;
}
}
} catch (e) {
// 忽略错误
}
}
// 检查主页面的 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); await this.human.randomDelay(1000, 2000);
@ -1255,53 +1263,70 @@ class WindsurfRegister {
logger.info(this.siteName, ' → 等待验证完成...'); logger.info(this.siteName, ' → 等待验证完成...');
const startTime = Date.now(); const startTime = Date.now();
let dialogClosed = false; let dialogClosed = false;
let hasImageChallenge = false; let hasImageChallenge = captchaStatus.hasImageChallenge; // 使用前面检测的结果
const maxWaitTime = 60000; // 最多60秒包含图片挑战时间 const maxWaitTime = 120000; // 图片挑战需要更长时间最多120秒
if (hasImageChallenge) {
logger.warn(this.siteName, ' → ⚠️ 需要手动完成图片验证最多等待120秒...');
}
while (Date.now() - startTime < maxWaitTime) { 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 status = await this.page.evaluate(() => {
const dialog = document.querySelector('[role="dialog"]'); // 检查 token 是否已填充(主要判断依据)
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 是否已填充
const response = document.querySelector('[name="h-captcha-response"]') || const response = document.querySelector('[name="h-captcha-response"]') ||
document.querySelector('[name="g-recaptcha-response"]'); document.querySelector('[name="g-recaptcha-response"]');
const hasResponse = response && response.value && response.value.length > 20; const hasResponse = response && response.value && response.value.length > 20;
// 检查是否有图片挑战 // 检查 iframe 中的图片挑战
const dialogText = dialog.innerText || ''; let hasImageChallenge = false;
const hasImageTask = dialogText.includes('选择') || const iframes = document.querySelectorAll('iframe[src*="hcaptcha"]');
dialogText.includes('Select') || for (const iframe of iframes) {
dialog.querySelector('img[src*="hcaptcha"]') || const rect = iframe.getBoundingClientRect();
dialog.querySelector('[class*="task"]'); if (rect.width > 300 && rect.height > 300) {
hasImageChallenge = true;
break;
}
}
return { return {
dialogGone: !dialog || isHidden,
verified: hasResponse, verified: hasResponse,
hasImageChallenge: hasImageTask, hasImageChallenge: hasImageChallenge,
dialogText: dialogText.substring(0, 80) iframeCount: iframes.length
}; };
}); });
// 第一次检测到图片挑战 // 第一次检测到图片挑战(之前没检测到)
if (status.hasImageChallenge && !hasImageChallenge) { if (status.hasImageChallenge && !hasImageChallenge) {
hasImageChallenge = true; hasImageChallenge = true;
logger.warn(this.siteName, ' → ⚠️ Token 被降级,出现图片挑战!'); logger.warn(this.siteName, ' → ⚠️ 检测到图片挑战!');
logger.info(this.siteName, ` → 任务: ${status.dialogText}`);
logger.warn(this.siteName, ' → 请手动完成图片验证...'); logger.warn(this.siteName, ' → 请手动完成图片验证...');
} }
// 验证完成 // 验证完成的条件token 已填充 且 没有图片挑战
if (status.dialogGone || (status.verified && !status.hasImageChallenge)) { if (status.verified && !status.hasImageChallenge) {
dialogClosed = true; dialogClosed = true;
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
logger.success(this.siteName, ` → ✓ 验证完成(耗时${elapsed}秒)`); logger.success(this.siteName, ` → ✓ 验证完成(耗时${elapsed}秒)`);
break; 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; const interval = hasImageChallenge ? 2000 : 1000;
await new Promise(resolve => setTimeout(resolve, interval)); await new Promise(resolve => setTimeout(resolve, interval));
@ -1978,7 +2003,8 @@ class WindsurfRegister {
billingDate: this.billingInfo ? this.billingInfo.date : null, billingDate: this.billingInfo ? this.billingInfo.date : null,
paymentCardNumber: this.cardInfo ? this.cardInfo.number : null, paymentCardNumber: this.cardInfo ? this.cardInfo.number : null,
paymentCountry: this.cardInfo ? this.cardInfo.country : 'MO', paymentCountry: this.cardInfo ? this.cardInfo.country : 'MO',
status: 'active' status: 'active',
isOnSale: false
}; };
// 保存到数据库 // 保存到数据库