This commit is contained in:
dengqichen 2025-11-17 15:50:35 +08:00
parent d33fc7e53a
commit 1a61f5c917
2 changed files with 104 additions and 201 deletions

5
.env
View File

@ -1,2 +1,7 @@
# CapSolver API Key
CAPSOLVER_API_KEY=CAP-0FCDDA4906E87D9F4FF68EAECD34E320876FBA70E4F30EA1ADCD264EDB15E4BF
# AdsPower 指纹浏览器配置
ADSPOWER_USER_ID=k1728p8l
ADSPOWER_API_KEY=35de43696f6241f3df895f2f48777a99
# ADSPOWER_API=http://local.adspower.net:50325

View File

@ -30,7 +30,6 @@ class WindsurfRegister {
this.page = null;
this.currentStep = 0;
this.accountData = null;
this.sessionData = null; // 会话数据(用于浏览器切换)
// 初始化 CapSolver自动解决 Cloudflare Turnstile
this.capsolverKey = process.env.CAPSOLVER_API_KEY || null;
@ -186,123 +185,113 @@ class WindsurfRegister {
}
/**
* 初始化浏览器
* @param {Object} options - 选项
* @param {boolean} options.skipExtension - 是否跳过扩展加载用于支付步骤
* 初始化浏览器 - 使用 AdsPower 指纹浏览器
*/
async initBrowser(options = {}) {
const puppeteer = require('puppeteer');
const path = require('path');
const axios = require('axios');
const browserType = options.skipExtension ? '干净浏览器(无扩展)' : '浏览器(集成 CapSolver 扩展)';
logger.info(this.siteName, `启动${browserType}...`);
logger.info(this.siteName, '启动 AdsPower 指纹浏览器...');
// 随机视口大小
const viewports = [
{ width: 1920, height: 1080 },
{ width: 1366, height: 768 },
{ width: 1536, height: 864 },
{ width: 1440, height: 900 }
];
const viewport = viewports[Math.floor(Math.random() * viewports.length)];
// 检查 AdsPower 配置
const adspowerUserId = process.env.ADSPOWER_USER_ID;
if (!adspowerUserId) {
logger.error(this.siteName, '');
logger.error(this.siteName, '❌ 未配置 ADSPOWER_USER_ID');
logger.error(this.siteName, '');
logger.error(this.siteName, '请在 .env 文件中配置:');
logger.error(this.siteName, 'ADSPOWER_USER_ID=your_profile_id');
logger.error(this.siteName, '');
throw new Error('未配置 AdsPower 用户ID');
}
const launchOptions = {
headless: false,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
`--window-size=${viewport.width},${viewport.height}`
]
};
const apiBase = process.env.ADSPOWER_API || 'http://local.adspower.net:50325';
const apiKey = process.env.ADSPOWER_API_KEY;
// 如果配置了 CapSolver 且 不跳过扩展,加载扩展
if (!options.skipExtension && this.capsolverKey) {
const fs = require('fs');
const os = require('os');
const startUrl = `${apiBase}/api/v1/browser/start?user_id=${encodeURIComponent(adspowerUserId)}`;
// 配置请求头
const headers = {};
if (apiKey && apiKey.trim()) {
headers['Authorization'] = `Bearer ${apiKey}`;
logger.info(this.siteName, '✓ 使用 API Key 认证');
}
logger.info(this.siteName, ` → 启动 AdsPower 配置: ${adspowerUserId}`);
try {
const response = await axios.get(startUrl, { headers });
const data = response.data;
// 使用绝对路径:从当前文件向上找到项目根目录
const projectRoot = path.resolve(__dirname, '../../../../');
const extensionPath = path.join(projectRoot, 'extensions', 'capsolver');
if (fs.existsSync(extensionPath)) {
// 检查关键文件是否存在
const manifestPath = path.join(extensionPath, 'manifest.json');
const configPath = path.join(extensionPath, 'assets', 'config.js');
if (!fs.existsSync(manifestPath)) {
logger.error(this.siteName, '✗ manifest.json 不存在!扩展不完整');
throw new Error('扩展文件不完整');
}
if (!fs.existsSync(configPath)) {
logger.error(this.siteName, '✗ assets/config.js 不存在!扩展不完整');
throw new Error('扩展配置文件不存在');
}
logger.success(this.siteName, '✓ 扩展文件完整性检查通过');
// 关键修复Windows上路径需要转换为正斜杠
let normalizedPath = path.resolve(extensionPath);
if (process.platform === 'win32') {
// Windows: 将反斜杠转换为正斜杠Chrome要求
normalizedPath = normalizedPath.replace(/\\/g, '/');
logger.info(this.siteName, `Windows平台路径转换为正斜杠格式`);
}
logger.info(this.siteName, `扩展路径: ${normalizedPath}`);
// Windows特定使用临时用户数据目录
if (process.platform === 'win32') {
const tempUserDataDir = path.join(os.tmpdir(), 'chrome-capsolver-' + Date.now());
launchOptions.userDataDir = tempUserDataDir;
logger.info(this.siteName, `临时用户数据目录: ${tempUserDataDir}`);
// Windows必需参数
launchOptions.args.push(
'--disable-features=RendererCodeIntegrity',
'--disable-blink-features=AutomationControlled'
);
}
launchOptions.args.push(
`--disable-extensions-except=${normalizedPath}`,
`--load-extension=${normalizedPath}`
);
logger.info(this.siteName, '✓ CapSolver 扩展配置完成');
} else {
logger.warn(this.siteName, `⚠️ CapSolver 扩展未找到: ${extensionPath}`);
logger.warn(this.siteName, '请检查扩展目录是否存在');
if (data.code !== 0) {
logger.error(this.siteName, '');
logger.error(this.siteName, `AdsPower API 返回错误: ${JSON.stringify(data)}`);
logger.error(this.siteName, '');
logger.error(this.siteName, '解决方案:');
logger.error(this.siteName, '1. 确保 AdsPower 应用已启动并登录');
logger.error(this.siteName, '2. 检查配置文件 ID 是否正确: ' + adspowerUserId);
logger.error(this.siteName, '3. 如果需要 API Key请在 AdsPower 设置中生成');
logger.error(this.siteName, '4. 尝试在 AdsPower 中手动打开一次浏览器配置');
logger.error(this.siteName, '');
throw new Error(`AdsPower 启动失败: ${data.msg || JSON.stringify(data)}`);
}
} else if (options.skipExtension) {
logger.info(this.siteName, '✓ 跳过扩展加载(避免干扰支付流程)');
}
this.browser = await puppeteer.launch(launchOptions);
const pages = await this.browser.pages();
this.page = pages[0] || await this.browser.newPage();
await this.page.setViewport(viewport);
await this.page.setExtraHTTPHeaders({
'Accept-Language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7'
});
logger.success(this.siteName, `✓ 浏览器启动成功 (${viewport.width}x${viewport.height})`);
// API Key 应该已在 extensions/capsolver/assets/config.js 中配置
if (launchOptions.args.some(arg => arg.includes('load-extension'))) {
logger.success(this.siteName, '✓ CapSolver 扩展将自动处理 Turnstile 验证');
logger.info(this.siteName, '提示:请确保已在 extensions/capsolver/assets/config.js 中配置 API Key');
}
logger.info(this.siteName, '等待浏览器完全准备...');
await this.human.randomDelay(2000, 3000);
// 默认启用自动模式如果配置了CapSolver
if (this.capsolverKey) {
this.capsolverWorking = true;
logger.info(this.siteName, '✓ CapSolver自动模式已启用如失败会降级到手动模式');
// 获取 WebSocket 端点
const wsEndpoint = data.data.ws && (
data.data.ws.puppeteer ||
data.data.ws.selenium ||
data.data.ws.ws ||
data.data.ws
);
if (!wsEndpoint) {
throw new Error('AdsPower 未返回 WebSocket 端点');
}
logger.info(this.siteName, ` → WebSocket: ${wsEndpoint}`);
// 连接到 AdsPower 浏览器
this.browser = await puppeteer.connect({
browserWSEndpoint: wsEndpoint,
defaultViewport: null
});
// 获取已存在的页面
const pages = await this.browser.pages();
this.page = pages[0] || await this.browser.newPage();
// 关闭多余的标签页AdsPower 需要至少保留一个)
if (pages.length > 1) {
for (let i = 1; i < pages.length; i++) {
try {
await pages[i].close();
} catch (e) {
// 忽略关闭失败
}
}
}
logger.success(this.siteName, '✓ AdsPower 浏览器连接成功');
logger.info(this.siteName, '✓ 使用真实指纹,可同时绕过 Cloudflare 和 Stripe');
// 提示AdsPower 中需要手动安装 CapSolver 扩展
if (this.capsolverKey) {
logger.info(this.siteName, '');
logger.info(this.siteName, '💡 重要提示:');
logger.info(this.siteName, ' 请在 AdsPower 配置中手动安装 CapSolver 扩展');
logger.info(this.siteName, ' 扩展路径: extensions/capsolver');
logger.info(this.siteName, ' 这样可以自动处理 Cloudflare Turnstile 验证');
logger.info(this.siteName, '');
}
logger.info(this.siteName, '等待浏览器完全准备...');
await this.human.randomDelay(2000, 3000);
} catch (error) {
logger.error(this.siteName, '');
logger.error(this.siteName, `❌ AdsPower 连接失败: ${error.message}`);
logger.error(this.siteName, '');
throw error;
}
}
@ -316,91 +305,6 @@ class WindsurfRegister {
}
}
/**
* 保存会话数据用于浏览器切换
*/
async saveSession() {
logger.info(this.siteName, ' → 保存会话数据...');
this.sessionData = {
cookies: await this.page.cookies(),
localStorage: await this.page.evaluate(() => JSON.stringify(localStorage)),
sessionStorage: await this.page.evaluate(() => JSON.stringify(sessionStorage)),
url: this.page.url()
};
logger.success(this.siteName, ` → ✓ 会话数据已保存 (${this.sessionData.cookies.length} cookies)`);
}
/**
* 恢复会话数据用于浏览器切换
*/
async restoreSession() {
if (!this.sessionData) {
throw new Error('没有可恢复的会话数据');
}
logger.info(this.siteName, ' → 恢复会话数据...');
// 1. 先访问域名(必须在同一域名下才能设置 cookies
const url = new URL(this.sessionData.url);
await this.page.goto(url.origin, { waitUntil: 'networkidle2', timeout: 30000 });
// 2. 设置 cookies
await this.page.setCookie(...this.sessionData.cookies);
logger.info(this.siteName, ` → ✓ 已恢复 ${this.sessionData.cookies.length} 个 cookies`);
// 3. 访问原始 URL
await this.page.goto(this.sessionData.url, { waitUntil: 'networkidle2', timeout: 30000 });
// 4. 恢复 localStorage 和 sessionStorage
await this.page.evaluate((ls, ss) => {
// 恢复 localStorage
const localData = JSON.parse(ls);
for (let key in localData) {
localStorage.setItem(key, localData[key]);
}
// 恢复 sessionStorage
const sessionData = JSON.parse(ss);
for (let key in sessionData) {
sessionStorage.setItem(key, sessionData[key]);
}
}, this.sessionData.localStorage, this.sessionData.sessionStorage);
// 5. 刷新页面以应用所有存储
await this.page.reload({ waitUntil: 'networkidle2', timeout: 30000 });
logger.success(this.siteName, ' → ✓ 会话数据已恢复,登录状态保持');
}
/**
* 切换到干净的浏览器无扩展
*/
async switchBrowser() {
logger.info(this.siteName, '');
logger.info(this.siteName, '┌─────────────────────────────────────────────────────┐');
logger.info(this.siteName, '│ 🔄 切换到干净浏览器(避免扩展干扰支付) │');
logger.info(this.siteName, '└─────────────────────────────────────────────────────┘');
// 1. 保存会话
await this.saveSession();
// 2. 关闭当前浏览器
logger.info(this.siteName, ' → 关闭带扩展的浏览器...');
await this.browser.close();
// 3. 启动干净浏览器
logger.info(this.siteName, ' → 启动干净浏览器(无扩展)...');
await this.initBrowser({ skipExtension: true });
// 4. 恢复会话
await this.restoreSession();
logger.success(this.siteName, '✓ 浏览器切换完成');
logger.info(this.siteName, '');
}
/**
* 步骤1: 填写基本信息
*/
@ -1613,20 +1517,14 @@ class WindsurfRegister {
this.currentStep = 5;
logger.success(this.siteName, `步骤 5 完成 (共尝试 ${retryCount + 1} 次)`);
// 保存会话数据为步骤6的浏览器切换做准备
await this.saveSession();
}
/**
* 步骤6: 填写支付信息在干净浏览器中完成
* 步骤6: 填写支付信息
*/
async step6_fillPayment() {
logger.info(this.siteName, `[步骤 6/${this.getTotalSteps()}] 填写支付信息`);
// 切换到干净的浏览器(无扩展,避免干扰 Stripe
await this.switchBrowser();
try {
// 等待页面加载
await this.human.readPage(3, 5);