387 lines
14 KiB
TypeScript
387 lines
14 KiB
TypeScript
/**
|
||
* 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: 'qichen111.asia'
|
||
},
|
||
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');
|
||
|
||
// TODO: 保存数据到数据库
|
||
// const db = this.getTool<DatabaseTool>('database');
|
||
// await db.save('accounts', context.data.account);
|
||
}
|
||
|
||
/**
|
||
* 提供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;
|