dasdasd
This commit is contained in:
parent
990172f559
commit
e4e9465bed
@ -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...');
|
||||
// 检测是否有图片挑战出现
|
||||
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 {
|
||||
const checkboxClicked = await this.page.evaluate(() => {
|
||||
const dialog = document.querySelector('[role="dialog"]');
|
||||
if (!dialog) return { success: false, reason: 'no-dialog' };
|
||||
|
||||
// 查找 checkbox 元素
|
||||
const checkbox = dialog.querySelector('input[type="checkbox"]') ||
|
||||
dialog.querySelector('[role="checkbox"]') ||
|
||||
dialog.querySelector('#checkbox');
|
||||
|
||||
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) }
|
||||
};
|
||||
}
|
||||
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})`);
|
||||
// 检查 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) {
|
||||
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);
|
||||
@ -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
|
||||
};
|
||||
|
||||
// 保存到数据库
|
||||
|
||||
Loading…
Reference in New Issue
Block a user