dasdasd
This commit is contained in:
parent
572b74afbd
commit
a3518c7054
@ -858,14 +858,35 @@ class WindsurfRegister {
|
||||
* 彻底清空输入框(通过DOM直接操作)
|
||||
*/
|
||||
async clearInputField(selector) {
|
||||
await this.page.evaluate((sel) => {
|
||||
const field = document.querySelector(sel);
|
||||
try {
|
||||
// 方法1: 通过 DOM 清空
|
||||
await this.page.evaluate((sel) => {
|
||||
const field = document.querySelector(sel);
|
||||
if (field) {
|
||||
// 聚焦
|
||||
field.focus();
|
||||
// 全选
|
||||
field.select();
|
||||
// 清空
|
||||
field.value = '';
|
||||
// 触发所有相关事件
|
||||
field.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
field.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
field.dispatchEvent(new Event('blur', { bubbles: true }));
|
||||
field.blur();
|
||||
}
|
||||
}, selector);
|
||||
|
||||
// 方法2: 使用 Puppeteer API 进一步清理
|
||||
const field = await this.page.$(selector);
|
||||
if (field) {
|
||||
field.value = '';
|
||||
field.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
field.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
await field.click({ clickCount: 3 }); // 三击全选
|
||||
await this.page.keyboard.press('Backspace');
|
||||
await this.human.randomDelay(100, 200);
|
||||
}
|
||||
}, selector);
|
||||
} catch (e) {
|
||||
logger.warn(this.siteName, ` → 清空输入框失败 (${selector}): ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1401,12 +1422,18 @@ class WindsurfRegister {
|
||||
document.querySelector('[name="g-recaptcha-response"]');
|
||||
const hasResponse = response && response.value && response.value.length > 20;
|
||||
|
||||
// 检查 iframe 中的图片挑战
|
||||
// 检查 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) {
|
||||
const style = window.getComputedStyle(iframe);
|
||||
const isVisible = style.display !== 'none' &&
|
||||
style.visibility !== 'hidden' &&
|
||||
style.opacity !== '0' &&
|
||||
rect.width > 300 && rect.height > 300;
|
||||
|
||||
if (isVisible) {
|
||||
hasImageChallenge = true;
|
||||
break;
|
||||
}
|
||||
@ -1423,14 +1450,142 @@ class WindsurfRegister {
|
||||
if (status.hasImageChallenge && !hasImageChallenge) {
|
||||
hasImageChallenge = true;
|
||||
logger.warn(this.siteName, ' → ⚠️ 检测到图片挑战!');
|
||||
logger.warn(this.siteName, ' → 请手动完成图片验证...');
|
||||
|
||||
// 尝试使用 2captcha 自动解决图片挑战
|
||||
if (this.twoCaptchaSolver) {
|
||||
try {
|
||||
logger.info(this.siteName, ' → 尝试使用 2Captcha 自动解决图片挑战...');
|
||||
|
||||
// 1. 获取挑战信息
|
||||
const challengeInfo = await this.page.evaluate(() => {
|
||||
// 查找挑战 iframe
|
||||
const iframes = document.querySelectorAll('iframe[src*="hcaptcha"]');
|
||||
for (const iframe of iframes) {
|
||||
const rect = iframe.getBoundingClientRect();
|
||||
if (rect.width > 300 && rect.height > 300) {
|
||||
// 尝试获取任务文本(可能在主页面或 iframe 外层)
|
||||
let taskText = '';
|
||||
const parentDiv = iframe.closest('div');
|
||||
if (parentDiv) {
|
||||
const textElements = parentDiv.querySelectorAll('h2, h3, div, span, label');
|
||||
for (const el of textElements) {
|
||||
const text = el.textContent?.trim();
|
||||
if (text && (text.includes('点击') || text.includes('选择') || text.includes('Select'))) {
|
||||
taskText = text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
found: true,
|
||||
taskText: taskText,
|
||||
iframeSrc: iframe.src,
|
||||
rect: {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
return { found: false };
|
||||
});
|
||||
|
||||
if (challengeInfo.found) {
|
||||
logger.info(this.siteName, ` → 挑战任务: ${challengeInfo.taskText || '未检测到任务文本'}`);
|
||||
|
||||
// 2. 截取挑战图片
|
||||
const challengeFrame = this.page.frames().find(f => f.url().includes('hcaptcha') && f.url().includes('challenge'));
|
||||
if (challengeFrame) {
|
||||
// 等待图片加载
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
|
||||
// 截取挑战区域的图片
|
||||
const screenshot = await this.page.screenshot({
|
||||
clip: {
|
||||
x: challengeInfo.rect.x,
|
||||
y: challengeInfo.rect.y,
|
||||
width: challengeInfo.rect.width,
|
||||
height: challengeInfo.rect.height
|
||||
},
|
||||
encoding: 'base64'
|
||||
});
|
||||
|
||||
logger.info(this.siteName, ' → 正在发送到 2Captcha 识别...');
|
||||
|
||||
// 3. 使用 2captcha coordinates API
|
||||
const result = await this.twoCaptchaSolver.coordinates({
|
||||
body: screenshot,
|
||||
textinstructions: challengeInfo.taskText || '点击正确的图片'
|
||||
});
|
||||
|
||||
logger.success(this.siteName, ` → ✓ 2Captcha 识别成功!耗时: ${result.cost}秒`);
|
||||
|
||||
// 4. 点击识别出的坐标
|
||||
const coords = result.data.split(',').map(coord => {
|
||||
const [key, value] = coord.split(':');
|
||||
return { [key]: parseInt(value) };
|
||||
}).reduce((acc, curr) => ({ ...acc, ...curr }), {});
|
||||
|
||||
if (coords.x && coords.y) {
|
||||
// 转换为屏幕坐标
|
||||
const clickX = challengeInfo.rect.x + coords.x;
|
||||
const clickY = challengeInfo.rect.y + coords.y;
|
||||
|
||||
logger.info(this.siteName, ` → 点击坐标: (${clickX}, ${clickY})`);
|
||||
await this.page.mouse.click(clickX, clickY);
|
||||
logger.success(this.siteName, ' → ✓ 已点击识别结果');
|
||||
|
||||
// 等待并点击提交按钮(通常是"验证"或"检查"按钮)
|
||||
await this.human.randomDelay(1000, 1500);
|
||||
|
||||
// 查找并点击提交按钮
|
||||
const submitClicked = await challengeFrame.evaluate(() => {
|
||||
const buttons = document.querySelectorAll('button, [role="button"]');
|
||||
for (const btn of buttons) {
|
||||
const text = btn.textContent?.trim();
|
||||
if (text && (text.includes('验证') || text.includes('检查') || text.includes('Verify') || text.includes('Submit'))) {
|
||||
btn.click();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (submitClicked) {
|
||||
logger.success(this.siteName, ' → ✓ 已提交验证');
|
||||
}
|
||||
|
||||
// 等待验证结果
|
||||
await this.human.randomDelay(2000, 3000);
|
||||
}
|
||||
} else {
|
||||
logger.warn(this.siteName, ' → 未找到挑战 iframe');
|
||||
logger.warn(this.siteName, ' → 请手动完成图片验证...');
|
||||
}
|
||||
} else {
|
||||
logger.warn(this.siteName, ' → 未找到挑战信息');
|
||||
logger.warn(this.siteName, ' → 请手动完成图片验证...');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(this.siteName, ` → 2Captcha 自动解决失败: ${error.message}`);
|
||||
logger.warn(this.siteName, ' → 请手动完成图片验证...');
|
||||
}
|
||||
} else {
|
||||
logger.warn(this.siteName, ' → 请手动完成图片验证...');
|
||||
}
|
||||
}
|
||||
|
||||
// 验证完成的条件:token 已填充 且 没有图片挑战
|
||||
if (status.verified && !status.hasImageChallenge) {
|
||||
// 验证完成的条件:token 已填充(不管 iframe 是否还存在)
|
||||
if (status.verified) {
|
||||
dialogClosed = true;
|
||||
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||
logger.success(this.siteName, ` → ✓ 验证完成(耗时${elapsed}秒)`);
|
||||
if (status.hasImageChallenge) {
|
||||
logger.info(this.siteName, ' → 图片挑战 iframe 仍存在,但 token 已填充,视为完成');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1448,7 +1603,9 @@ class WindsurfRegister {
|
||||
}
|
||||
|
||||
if (!dialogClosed) {
|
||||
logger.warn(this.siteName, ' → ⚠️ 验证超时,继续尝试支付...');
|
||||
logger.error(this.siteName, ' → ✗ 验证超时(120秒)!');
|
||||
logger.warn(this.siteName, ' → 验证码可能还在页面上,需要手动完成或重新提交');
|
||||
return false; // 返回 false,不要继续
|
||||
}
|
||||
|
||||
await this.human.randomDelay(1000, 2000);
|
||||
@ -1506,11 +1663,17 @@ class WindsurfRegister {
|
||||
const elements = document.querySelectorAll(selector);
|
||||
for (const el of elements) {
|
||||
const text = el.textContent || '';
|
||||
// 检查卡被拒绝或卡号无效
|
||||
if (text.includes('银行卡') && text.includes('拒绝') ||
|
||||
text.includes('您的银行卡被拒绝了') ||
|
||||
text.includes('card was declined') ||
|
||||
text.includes('被拒绝') ||
|
||||
text.includes('declined')) {
|
||||
text.includes('declined') ||
|
||||
text.includes('卡号无效') ||
|
||||
text.includes('您的卡号无效') ||
|
||||
text.includes('invalid card') ||
|
||||
text.includes('card number is invalid') ||
|
||||
text.includes('无效')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -1675,65 +1838,70 @@ class WindsurfRegister {
|
||||
// 等待 Stripe 开始处理
|
||||
await this.human.randomDelay(1000, 2000);
|
||||
|
||||
// 检查是否有验证码(只在第一次提交时检查)
|
||||
if (retryCount === 0) {
|
||||
// 等待可能的验证码弹窗/iframe 加载(最多等5秒)
|
||||
let captchaDetected = false;
|
||||
const checkStartTime = Date.now();
|
||||
const maxCheckTime = 5000; // 最多检查5秒
|
||||
let captchaCheckCount = 0;
|
||||
// 检查是否有验证码(每次提交都检查)
|
||||
// 等待可能的验证码弹窗/iframe 加载(最多等5秒)
|
||||
let captchaDetected = false;
|
||||
const checkStartTime = Date.now();
|
||||
const maxCheckTime = 5000; // 最多检查5秒
|
||||
let captchaCheckCount = 0;
|
||||
|
||||
logger.info(this.siteName, ` → 检查验证码状态(第 ${retryCount + 1} 次提交)...`);
|
||||
|
||||
while (Date.now() - checkStartTime < maxCheckTime && !captchaDetected) {
|
||||
captchaCheckCount++;
|
||||
|
||||
while (Date.now() - checkStartTime < maxCheckTime && !captchaDetected) {
|
||||
captchaCheckCount++;
|
||||
const captchaCheck = await this.page.evaluate(() => {
|
||||
// 检查多种可能的验证码 iframe 和对话框
|
||||
const stripeHCaptchaFrame = document.querySelector('iframe[src*="hcaptcha-inner"]');
|
||||
const hcaptchaFrame = document.querySelector('iframe[src*="hcaptcha.com"]');
|
||||
const hcaptchaDiv = document.querySelector('.h-captcha');
|
||||
|
||||
const captchaCheck = await this.page.evaluate(() => {
|
||||
// 检查多种可能的验证码 iframe 和对话框
|
||||
const stripeHCaptchaFrame = document.querySelector('iframe[src*="hcaptcha-inner"]');
|
||||
const hcaptchaFrame = document.querySelector('iframe[src*="hcaptcha.com"]');
|
||||
const hcaptchaDiv = document.querySelector('.h-captcha');
|
||||
|
||||
// 检查是否有"还需一步即可完成"的对话框
|
||||
const modalTexts = Array.from(document.querySelectorAll('*')).map(el => el.textContent);
|
||||
const hasCaptchaModal = modalTexts.some(text =>
|
||||
text && (text.includes('还需一步') || text.includes('我是真实访问者') || text.includes('hCaptcha'))
|
||||
);
|
||||
|
||||
return {
|
||||
hasStripeFrame: !!stripeHCaptchaFrame,
|
||||
hasHCaptchaFrame: !!hcaptchaFrame,
|
||||
hasDiv: !!hcaptchaDiv,
|
||||
hasModal: hasCaptchaModal,
|
||||
stripeFrameUrl: stripeHCaptchaFrame ? stripeHCaptchaFrame.src.substring(0, 100) : null
|
||||
};
|
||||
});
|
||||
// 检查是否有"还需一步即可完成"的对话框
|
||||
const modalTexts = Array.from(document.querySelectorAll('*')).map(el => el.textContent);
|
||||
const hasCaptchaModal = modalTexts.some(text =>
|
||||
text && (text.includes('还需一步') || text.includes('我是真实访问者') || text.includes('hCaptcha'))
|
||||
);
|
||||
|
||||
// 只要检测到任一验证码元素就退出循环
|
||||
if (captchaCheck.hasStripeFrame || captchaCheck.hasHCaptchaFrame ||
|
||||
captchaCheck.hasDiv || captchaCheck.hasModal) {
|
||||
captchaDetected = true;
|
||||
logger.info(this.siteName, ` → 检测到验证码(第${captchaCheckCount}次检查,${((Date.now() - checkStartTime) / 1000).toFixed(1)}秒)`);
|
||||
if (captchaCheck.hasModal) {
|
||||
logger.info(this.siteName, ` → 检测到验证码对话框`);
|
||||
}
|
||||
if (captchaCheck.stripeFrameUrl) {
|
||||
logger.info(this.siteName, ` → Stripe hCaptcha URL: ${captchaCheck.stripeFrameUrl}...`);
|
||||
}
|
||||
} else {
|
||||
// 每500ms检查一次
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
return {
|
||||
hasStripeFrame: !!stripeHCaptchaFrame,
|
||||
hasHCaptchaFrame: !!hcaptchaFrame,
|
||||
hasDiv: !!hcaptchaDiv,
|
||||
hasModal: hasCaptchaModal,
|
||||
stripeFrameUrl: stripeHCaptchaFrame ? stripeHCaptchaFrame.src.substring(0, 100) : null
|
||||
};
|
||||
});
|
||||
|
||||
// 只要检测到任一验证码元素就退出循环
|
||||
if (captchaCheck.hasStripeFrame || captchaCheck.hasHCaptchaFrame ||
|
||||
captchaCheck.hasDiv || captchaCheck.hasModal) {
|
||||
captchaDetected = true;
|
||||
logger.info(this.siteName, ` → 检测到验证码(第${captchaCheckCount}次检查,${((Date.now() - checkStartTime) / 1000).toFixed(1)}秒)`);
|
||||
if (captchaCheck.hasModal) {
|
||||
logger.info(this.siteName, ` → 检测到验证码对话框`);
|
||||
}
|
||||
if (captchaCheck.stripeFrameUrl) {
|
||||
logger.info(this.siteName, ` → Stripe hCaptcha URL: ${captchaCheck.stripeFrameUrl}...`);
|
||||
}
|
||||
}
|
||||
|
||||
if (captchaDetected) {
|
||||
logger.info(this.siteName, ' → 检测到验证码,使用 YesCaptcha API 处理...');
|
||||
// 使用 YesCaptcha API 处理
|
||||
await this.handleHCaptcha();
|
||||
await this.human.randomDelay(1000, 2000);
|
||||
} else {
|
||||
logger.info(this.siteName, ` → ✓ 无验证码(检查${captchaCheckCount}次,耗时${((Date.now() - checkStartTime) / 1000).toFixed(1)}秒)`);
|
||||
// 每500ms检查一次
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
}
|
||||
|
||||
if (captchaDetected) {
|
||||
logger.info(this.siteName, ' → 检测到验证码,使用 YesCaptcha API 处理...');
|
||||
// 使用 YesCaptcha API 处理
|
||||
const captchaSolved = await this.handleHCaptcha();
|
||||
if (!captchaSolved) {
|
||||
logger.error(this.siteName, ' → ✗ 验证码自动处理失败!');
|
||||
logger.warn(this.siteName, ' → 继续检测支付结果(可能用户已手动完成,或卡被拒绝需要重试)');
|
||||
// ✅ 不要直接返回 false,继续执行,检测支付结果
|
||||
} else {
|
||||
logger.success(this.siteName, ' → ✓ 验证码处理成功');
|
||||
}
|
||||
await this.human.randomDelay(1000, 2000);
|
||||
} else {
|
||||
logger.info(this.siteName, ` → 跳过验证码检查(重试 ${retryCount + 1})`);
|
||||
logger.info(this.siteName, ` → ✓ 无验证码(检查${captchaCheckCount}次,耗时${((Date.now() - checkStartTime) / 1000).toFixed(1)}秒)`);
|
||||
}
|
||||
|
||||
// 开始轮询检测:同时等待成功或失败
|
||||
|
||||
Loading…
Reference in New Issue
Block a user