auto-account-machine/browser-automation-ts/configs/sites/windsurf-adapter.ts
2025-12-01 13:21:06 +08:00

385 lines
14 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Windsurf网站适配器
* 演示插件系统的使用
*/
import { BaseAdapter } from '../../src/adapters/BaseAdapter';
import { AccountGeneratorTool, AccountData } from '../../src/tools/AccountGeneratorTool';
import { DatabaseTool } from '../../src/tools/DatabaseTool';
import { EmailTool } from '../../src/tools/EmailTool';
import { CardGeneratorTool } from '../../src/tools/card/CardGeneratorTool';
export class WindsurfAdapter extends BaseAdapter {
readonly name = 'windsurf';
/**
* 声明必需的工具
*/
protected getRequiredTools(): string[] {
return ['account-generator', 'database', 'email', 'card-generator'];
}
/**
* 注册并配置工具
*/
protected registerTools(): void {
// 注册账号生成器Tool实例 + 配置)
this.registerTool(
new AccountGeneratorTool(),
{
email: {
domain: 'qichen.cloud'
},
password: {
strategy: 'email',
length: 12
},
includePhone: true
}
);
// 注册数据库工具(使用旧框架的真实配置)
this.registerTool(
new DatabaseTool(),
{
type: 'mysql',
host: process.env.MYSQL_HOST || '172.22.222.111',
port: parseInt(process.env.MYSQL_PORT || '3306'),
username: process.env.MYSQL_USER || 'windsurf-auto-register',
password: process.env.MYSQL_PASSWORD || 'Qichen5210523',
database: process.env.MYSQL_DATABASE || 'windsurf-auto-register'
}
);
// 注册邮箱工具使用QQ邮箱IMAP与旧框架配置一致
this.registerTool(
new EmailTool(),
{
type: 'imap',
user: process.env.EMAIL_USER || '1695551@qq.com',
password: process.env.EMAIL_PASS || 'iogmboamejdsbjdh', // QQ邮箱授权码
host: process.env.EMAIL_HOST || 'imap.qq.com',
port: parseInt(process.env.EMAIL_PORT || '993'),
tls: true,
tlsOptions: {
rejectUnauthorized: false
},
checkInterval: 3 // 每3秒检查一次与旧框架一致
}
);
// 注册卡片生成器
this.registerTool(
new CardGeneratorTool(),
{
type: 'unionpay'
// 注意:数据库工具会在初始化时自动传入
}
);
}
/**
* Workflow执行前 - 准备数据
*/
async beforeWorkflow(context: any): Promise<void> {
console.log('▶️ Windsurf workflow starting...');
// 如果没有账号数据,自动生成
if (!context.data.account || !context.data.account.email) {
console.log('📝 Generating account data...');
const accountGen = this.getTool<AccountGeneratorTool>('account-generator');
context.data.account = await accountGen.generate();
console.log(`✓ Generated: ${context.data.account.email}`);
}
}
/**
* Workflow执行后 - 保存数据
*/
async afterWorkflow(context: any, result: any): Promise<void> {
console.log('✅ Windsurf workflow completed');
// 数据库保存已在 yaml 中通过 saveToDatabase handler 完成
}
/**
* 提供custom action的处理函数
*/
getHandlers(): Record<string, (...args: any[]) => Promise<any>> {
return {
// 生成银行卡使用CardGeneratorTool与旧框架100%一致)
generateCard: async () => {
console.log('💳 Generating card...');
const cardGen = this.getTool<CardGeneratorTool>('card-generator');
const card = await cardGen.generate('unionpay'); // 使用银联卡
console.log(`✓ Generated card: ${card.number.slice(-4)} (${card.issuer})`);
this.context.data.card = card;
return { success: true, data: card };
},
// 处理Cloudflare Turnstile验证完全复制旧框架逻辑
handleTurnstile: async (params: any) => {
const {
timeout = 30000,
maxRetries = 3,
retryStrategy = 'refresh'
} = params;
const page = this.context.page;
for (let retryCount = 0; retryCount <= maxRetries; retryCount++) {
try {
if (retryCount > 0) {
console.warn(`Turnstile 超时,执行重试策略: ${retryStrategy} (${retryCount}/${maxRetries})...`);
// 根据策略执行不同的重试行为
await this.executeRetryStrategy(retryStrategy, retryCount);
}
console.log('🔐 Cloudflare Turnstile 人机验证');
// 等待 Turnstile 验证框出现
await new Promise(resolve => setTimeout(resolve, 2000));
// 检查是否有 Turnstile
const hasTurnstile = await page.evaluate(() => {
return !!document.querySelector('iframe[src*="challenges.cloudflare.com"]') ||
!!document.querySelector('.cf-turnstile') ||
document.body.textContent.includes('Please verify that you are human');
});
if (hasTurnstile) {
console.log('检测到 Turnstile 验证,等待自动完成...');
// 等待验证通过(检查按钮是否启用或页面是否变化)
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
const isPassed = await page.evaluate(() => {
// 检查是否有成功标记
const successMark = document.querySelector('svg[data-status="success"]') ||
document.querySelector('[aria-label*="success"]') ||
document.querySelector('.cf-turnstile-success');
// 或者检查 Continue 按钮是否启用
const continueBtn = Array.from(document.querySelectorAll('button')).find(btn =>
btn.textContent.trim() === 'Continue'
);
const btnEnabled = continueBtn && !continueBtn.disabled;
return !!successMark || btnEnabled;
});
if (isPassed) {
console.log('✅ Turnstile 验证通过');
// 点击 Continue 按钮
const continueBtn = await page.evaluateHandle(() => {
return Array.from(document.querySelectorAll('button')).find(btn =>
btn.textContent.trim() === 'Continue'
);
});
if (continueBtn) {
await continueBtn.asElement().click();
console.log('已点击 Continue 按钮');
await new Promise(resolve => setTimeout(resolve, 2000));
}
return { success: true };
}
await new Promise(resolve => setTimeout(resolve, 500));
}
// 超时了,如果还有重试次数就继续循环
if (retryCount < maxRetries) {
console.warn(`Turnstile 验证超时(${timeout}ms`);
continue; // 进入下一次重试
} else {
throw new Error('Turnstile 验证超时,已达最大重试次数');
}
} else {
console.log('未检测到 Turnstile跳过');
return { success: true, skipped: true };
}
} catch (error: any) {
if (retryCount >= maxRetries) {
console.error(`Turnstile 处理最终失败: ${error.message}`);
// Turnstile 是可选的,失败也继续(但记录错误)
return { success: true, error: error.message, failed: true };
}
// 否则继续重试
}
}
},
// 处理邮箱验证使用EmailTool与旧框架逻辑100%一致)
handleEmailVerification: async (params: any) => {
const { timeout = 120000 } = params;
const page = this.context.page;
console.log('开始邮箱验证');
const emailTool = this.getTool<EmailTool>('email');
try {
// 等待2秒让邮件到达
await new Promise(resolve => setTimeout(resolve, 2000));
// 获取验证码
const email = this.context.data.account.email;
console.log(`从邮箱获取验证码: ${email}`);
const code = await emailTool.getVerificationCode(
'windsurf',
email,
timeout / 1000
);
console.log(`✓ 验证码: ${code}`);
// 等待输入框出现
await page.waitForSelector('input[type="text"]', { timeout: 10000 });
// 获取所有输入框
const inputs = await page.$$('input[type="text"]');
console.log(`找到 ${inputs.length} 个输入框`);
if (inputs.length >= 6 && code.length === 6) {
// 填写6位验证码
console.log('填写6位验证码...');
for (let i = 0; i < 6; i++) {
await inputs[i].click();
await new Promise(resolve => setTimeout(resolve, 100));
await inputs[i].type(code[i].toUpperCase());
await new Promise(resolve => setTimeout(resolve, 300));
}
console.log('✓ 验证码已填写');
// 等待跳转到问卷页面
console.log('等待页面跳转...');
const startTime = Date.now();
while (Date.now() - startTime < 60000) {
const currentUrl = page.url();
if (currentUrl.includes('/account/onboarding') && currentUrl.includes('page=source')) {
console.log('✓ 邮箱验证成功');
return { success: true };
}
await new Promise(resolve => setTimeout(resolve, 500));
}
throw new Error('等待页面跳转超时');
} else {
throw new Error('输入框数量不正确');
}
} catch (error: any) {
console.error(`邮箱验证失败: ${error.message}`);
throw error;
}
},
// 处理hCaptcha
handleHCaptcha: async () => {
console.log('🤖 Waiting for hCaptcha...');
await new Promise(resolve => setTimeout(resolve, 60000));
return { success: true };
},
// 验证提交按钮点击
verifySubmitClick: async () => {
console.log('✓ Verifying submit button click...');
await new Promise(resolve => setTimeout(resolve, 2000));
return { success: true };
},
// 处理订阅数据
processSubscriptionData: async () => {
console.log('📊 Processing subscription data...');
const quotaData = this.context.data.quotaRaw;
const billingInfo = this.context.data.billingInfo;
console.log('Quota:', quotaData);
console.log('Billing:', billingInfo);
return { success: true };
},
// 保存到数据库(完全复制旧框架逻辑)
saveToDatabase: async () => {
console.log('保存到数据库');
try {
const db = this.getTool<DatabaseTool>('database');
const account = this.context.data.account;
const card = this.context.data.card;
const quotaInfo = this.context.data.quotaInfo;
const billingInfo = this.context.data.billingInfo;
// 准备账号数据(与旧框架字段完全一致)
const accountData = {
email: account.email,
password: account.password,
first_name: account.firstName,
last_name: account.lastName,
registration_time: new Date(),
quota_used: quotaInfo ? parseFloat(quotaInfo.used) : 0,
quota_total: quotaInfo ? parseFloat(quotaInfo.total) : 0,
billing_days: billingInfo ? parseInt(billingInfo.days) : null,
billing_date: billingInfo ? billingInfo.date : null,
payment_card_number: card ? card.number : null,
payment_card_expiry_month: card ? card.month : null,
payment_card_expiry_year: card ? card.year : null,
payment_card_cvv: card ? card.cvv : null,
payment_country: card ? card.country || 'MO' : 'MO',
status: 'active',
is_on_sale: false
};
// 保存到数据库(使用旧框架的表名)
console.log('保存账号信息...');
// 检查是否已存在
const exists = await db.exists('windsurf_accounts', { email: accountData.email });
let accountId;
if (exists) {
// 已存在,更新
await db.update('windsurf_accounts', { email: accountData.email }, accountData);
console.log(`✓ 账号信息已更新: ${accountData.email}`);
} else {
// 不存在,插入
const result = await db.insert('windsurf_accounts', accountData);
accountId = result.insertId;
console.log(`✓ 账号信息已保存到数据库 (ID: ${accountId})`);
}
console.log(` → 邮箱: ${accountData.email}`);
console.log(` → 配额: ${accountData.quota_used} / ${accountData.quota_total}`);
console.log(` → 卡号: ${accountData.payment_card_number}`);
return { success: true, accountId };
} catch (error: any) {
console.error(`保存到数据库失败: ${error.message}`);
// 数据库保存失败不影响注册流程
return { success: true, error: error.message };
}
}
};
}
}
// 导出adapter实例
export default WindsurfAdapter;