dasdasd
This commit is contained in:
parent
990172f559
commit
e4e9465bed
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
// 保存到数据库
|
// 保存到数据库
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user