dasdasd
This commit is contained in:
parent
d33fc7e53a
commit
1a61f5c917
5
.env
5
.env
@ -1,2 +1,7 @@
|
|||||||
# CapSolver API Key
|
# CapSolver API Key
|
||||||
CAPSOLVER_API_KEY=CAP-0FCDDA4906E87D9F4FF68EAECD34E320876FBA70E4F30EA1ADCD264EDB15E4BF
|
CAPSOLVER_API_KEY=CAP-0FCDDA4906E87D9F4FF68EAECD34E320876FBA70E4F30EA1ADCD264EDB15E4BF
|
||||||
|
|
||||||
|
# AdsPower 指纹浏览器配置
|
||||||
|
ADSPOWER_USER_ID=k1728p8l
|
||||||
|
ADSPOWER_API_KEY=35de43696f6241f3df895f2f48777a99
|
||||||
|
# ADSPOWER_API=http://local.adspower.net:50325
|
||||||
|
|||||||
@ -30,7 +30,6 @@ class WindsurfRegister {
|
|||||||
this.page = null;
|
this.page = null;
|
||||||
this.currentStep = 0;
|
this.currentStep = 0;
|
||||||
this.accountData = null;
|
this.accountData = null;
|
||||||
this.sessionData = null; // 会话数据(用于浏览器切换)
|
|
||||||
|
|
||||||
// 初始化 CapSolver(自动解决 Cloudflare Turnstile)
|
// 初始化 CapSolver(自动解决 Cloudflare Turnstile)
|
||||||
this.capsolverKey = process.env.CAPSOLVER_API_KEY || null;
|
this.capsolverKey = process.env.CAPSOLVER_API_KEY || null;
|
||||||
@ -186,123 +185,113 @@ class WindsurfRegister {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化浏览器
|
* 初始化浏览器 - 使用 AdsPower 指纹浏览器
|
||||||
* @param {Object} options - 选项
|
|
||||||
* @param {boolean} options.skipExtension - 是否跳过扩展加载(用于支付步骤)
|
|
||||||
*/
|
*/
|
||||||
async initBrowser(options = {}) {
|
async initBrowser(options = {}) {
|
||||||
const puppeteer = require('puppeteer');
|
const puppeteer = require('puppeteer');
|
||||||
const path = require('path');
|
const axios = require('axios');
|
||||||
|
|
||||||
const browserType = options.skipExtension ? '干净浏览器(无扩展)' : '浏览器(集成 CapSolver 扩展)';
|
logger.info(this.siteName, '启动 AdsPower 指纹浏览器...');
|
||||||
logger.info(this.siteName, `启动${browserType}...`);
|
|
||||||
|
|
||||||
// 随机视口大小
|
// 检查 AdsPower 配置
|
||||||
const viewports = [
|
const adspowerUserId = process.env.ADSPOWER_USER_ID;
|
||||||
{ width: 1920, height: 1080 },
|
if (!adspowerUserId) {
|
||||||
{ width: 1366, height: 768 },
|
logger.error(this.siteName, '');
|
||||||
{ width: 1536, height: 864 },
|
logger.error(this.siteName, '❌ 未配置 ADSPOWER_USER_ID');
|
||||||
{ width: 1440, height: 900 }
|
logger.error(this.siteName, '');
|
||||||
];
|
logger.error(this.siteName, '请在 .env 文件中配置:');
|
||||||
const viewport = viewports[Math.floor(Math.random() * viewports.length)];
|
logger.error(this.siteName, 'ADSPOWER_USER_ID=your_profile_id');
|
||||||
|
logger.error(this.siteName, '');
|
||||||
|
throw new Error('未配置 AdsPower 用户ID');
|
||||||
|
}
|
||||||
|
|
||||||
const launchOptions = {
|
const apiBase = process.env.ADSPOWER_API || 'http://local.adspower.net:50325';
|
||||||
headless: false,
|
const apiKey = process.env.ADSPOWER_API_KEY;
|
||||||
args: [
|
|
||||||
'--no-sandbox',
|
|
||||||
'--disable-setuid-sandbox',
|
|
||||||
`--window-size=${viewport.width},${viewport.height}`
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
// 如果配置了 CapSolver 且 不跳过扩展,加载扩展
|
const startUrl = `${apiBase}/api/v1/browser/start?user_id=${encodeURIComponent(adspowerUserId)}`;
|
||||||
if (!options.skipExtension && this.capsolverKey) {
|
|
||||||
const fs = require('fs');
|
|
||||||
const os = require('os');
|
|
||||||
|
|
||||||
// 使用绝对路径:从当前文件向上找到项目根目录
|
// 配置请求头
|
||||||
const projectRoot = path.resolve(__dirname, '../../../../');
|
const headers = {};
|
||||||
const extensionPath = path.join(projectRoot, 'extensions', 'capsolver');
|
if (apiKey && apiKey.trim()) {
|
||||||
|
headers['Authorization'] = `Bearer ${apiKey}`;
|
||||||
|
logger.info(this.siteName, '✓ 使用 API Key 认证');
|
||||||
|
}
|
||||||
|
|
||||||
if (fs.existsSync(extensionPath)) {
|
logger.info(this.siteName, ` → 启动 AdsPower 配置: ${adspowerUserId}`);
|
||||||
// 检查关键文件是否存在
|
|
||||||
const manifestPath = path.join(extensionPath, 'manifest.json');
|
|
||||||
const configPath = path.join(extensionPath, 'assets', 'config.js');
|
|
||||||
|
|
||||||
if (!fs.existsSync(manifestPath)) {
|
try {
|
||||||
logger.error(this.siteName, '✗ manifest.json 不存在!扩展不完整');
|
const response = await axios.get(startUrl, { headers });
|
||||||
throw new Error('扩展文件不完整');
|
const data = response.data;
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(configPath)) {
|
if (data.code !== 0) {
|
||||||
logger.error(this.siteName, '✗ assets/config.js 不存在!扩展不完整');
|
logger.error(this.siteName, '');
|
||||||
throw new Error('扩展配置文件不存在');
|
logger.error(this.siteName, `AdsPower API 返回错误: ${JSON.stringify(data)}`);
|
||||||
}
|
logger.error(this.siteName, '');
|
||||||
|
logger.error(this.siteName, '解决方案:');
|
||||||
logger.success(this.siteName, '✓ 扩展文件完整性检查通过');
|
logger.error(this.siteName, '1. 确保 AdsPower 应用已启动并登录');
|
||||||
|
logger.error(this.siteName, '2. 检查配置文件 ID 是否正确: ' + adspowerUserId);
|
||||||
// 关键修复:Windows上路径需要转换为正斜杠
|
logger.error(this.siteName, '3. 如果需要 API Key,请在 AdsPower 设置中生成');
|
||||||
let normalizedPath = path.resolve(extensionPath);
|
logger.error(this.siteName, '4. 尝试在 AdsPower 中手动打开一次浏览器配置');
|
||||||
if (process.platform === 'win32') {
|
logger.error(this.siteName, '');
|
||||||
// Windows: 将反斜杠转换为正斜杠(Chrome要求)
|
throw new Error(`AdsPower 启动失败: ${data.msg || JSON.stringify(data)}`);
|
||||||
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, '请检查扩展目录是否存在');
|
|
||||||
}
|
}
|
||||||
} else if (options.skipExtension) {
|
|
||||||
logger.info(this.siteName, '✓ 跳过扩展加载(避免干扰支付流程)');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.browser = await puppeteer.launch(launchOptions);
|
// 获取 WebSocket 端点
|
||||||
|
const wsEndpoint = data.data.ws && (
|
||||||
|
data.data.ws.puppeteer ||
|
||||||
|
data.data.ws.selenium ||
|
||||||
|
data.data.ws.ws ||
|
||||||
|
data.data.ws
|
||||||
|
);
|
||||||
|
|
||||||
const pages = await this.browser.pages();
|
if (!wsEndpoint) {
|
||||||
this.page = pages[0] || await this.browser.newPage();
|
throw new Error('AdsPower 未返回 WebSocket 端点');
|
||||||
|
}
|
||||||
|
|
||||||
await this.page.setViewport(viewport);
|
logger.info(this.siteName, ` → WebSocket: ${wsEndpoint}`);
|
||||||
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})`);
|
// 连接到 AdsPower 浏览器
|
||||||
|
this.browser = await puppeteer.connect({
|
||||||
|
browserWSEndpoint: wsEndpoint,
|
||||||
|
defaultViewport: null
|
||||||
|
});
|
||||||
|
|
||||||
// API Key 应该已在 extensions/capsolver/assets/config.js 中配置
|
// 获取已存在的页面
|
||||||
if (launchOptions.args.some(arg => arg.includes('load-extension'))) {
|
const pages = await this.browser.pages();
|
||||||
logger.success(this.siteName, '✓ CapSolver 扩展将自动处理 Turnstile 验证');
|
this.page = pages[0] || await this.browser.newPage();
|
||||||
logger.info(this.siteName, '提示:请确保已在 extensions/capsolver/assets/config.js 中配置 API Key');
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(this.siteName, '等待浏览器完全准备...');
|
// 关闭多余的标签页(AdsPower 需要至少保留一个)
|
||||||
await this.human.randomDelay(2000, 3000);
|
if (pages.length > 1) {
|
||||||
|
for (let i = 1; i < pages.length; i++) {
|
||||||
|
try {
|
||||||
|
await pages[i].close();
|
||||||
|
} catch (e) {
|
||||||
|
// 忽略关闭失败
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 默认启用自动模式(如果配置了CapSolver)
|
logger.success(this.siteName, '✓ AdsPower 浏览器连接成功');
|
||||||
if (this.capsolverKey) {
|
logger.info(this.siteName, '✓ 使用真实指纹,可同时绕过 Cloudflare 和 Stripe');
|
||||||
this.capsolverWorking = true;
|
|
||||||
logger.info(this.siteName, '✓ CapSolver自动模式已启用(如失败会降级到手动模式)');
|
// 提示: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: 填写基本信息
|
* 步骤1: 填写基本信息
|
||||||
*/
|
*/
|
||||||
@ -1613,20 +1517,14 @@ class WindsurfRegister {
|
|||||||
|
|
||||||
this.currentStep = 5;
|
this.currentStep = 5;
|
||||||
logger.success(this.siteName, `步骤 5 完成 (共尝试 ${retryCount + 1} 次)`);
|
logger.success(this.siteName, `步骤 5 完成 (共尝试 ${retryCount + 1} 次)`);
|
||||||
|
|
||||||
// 保存会话数据(为步骤6的浏览器切换做准备)
|
|
||||||
await this.saveSession();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 步骤6: 填写支付信息(在干净浏览器中完成)
|
* 步骤6: 填写支付信息
|
||||||
*/
|
*/
|
||||||
async step6_fillPayment() {
|
async step6_fillPayment() {
|
||||||
logger.info(this.siteName, `[步骤 6/${this.getTotalSteps()}] 填写支付信息`);
|
logger.info(this.siteName, `[步骤 6/${this.getTotalSteps()}] 填写支付信息`);
|
||||||
|
|
||||||
// 切换到干净的浏览器(无扩展,避免干扰 Stripe)
|
|
||||||
await this.switchBrowser();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 等待页面加载
|
// 等待页面加载
|
||||||
await this.human.readPage(3, 5);
|
await this.human.readPage(3, 5);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user