Compare commits

...

15 Commits

Author SHA1 Message Date
dengqichen
c9e24bbb85 aaaaa 2025-11-17 00:46:25 +08:00
dengqichen
aa619cd3b4 aaaaa 2025-11-17 00:38:59 +08:00
dengqichen
10439f5af1 aaaaa 2025-11-16 23:19:40 +08:00
dengqichen
569a5d2a0f aaaaa 2025-11-16 22:49:54 +08:00
dengqichen
95572cb6e1 aaaaa 2025-11-16 22:45:31 +08:00
dengqichen
af26fab852 aaaaa 2025-11-16 22:39:36 +08:00
dengqichen
969f9cdb5d aaaaa 2025-11-16 21:27:33 +08:00
dengqichen
774a6c118a aaaaa 2025-11-16 21:15:06 +08:00
dengqichen
5d1d4e51ac aaaaa 2025-11-16 21:07:17 +08:00
dengqichen
740456ba56 aaaaa 2025-11-16 20:52:52 +08:00
dengqichen
7090744cf0 aaaaa 2025-11-16 20:34:50 +08:00
dengqichen
c1d1381edb aaaaa 2025-11-16 20:04:47 +08:00
dengqichen
bf55bcee27 aaaaa 2025-11-16 19:46:13 +08:00
dengqichen
f423f8b57f aaaaa 2025-11-16 19:23:20 +08:00
dengqichen
fe6d1b5d44 aaaaa 2025-11-16 19:13:07 +08:00
24 changed files with 3713 additions and 7 deletions

155
QUICKSTART.md Normal file
View File

@ -0,0 +1,155 @@
# 快速开始指南
## 🚀 5分钟上手
### 1. 基本使用
生成一张银联卡(默认):
```bash
node src/cli.js card
```
输出:
```
6228367546245545|08|29|783
```
### 2. 常用命令
```bash
# 生成Visa卡
node src/cli.js card -t visa
# 批量生成10张银联卡
node src/cli.js card -n 10
# JSON格式输出
node src/cli.js card -f json
# 美化格式
node src/cli.js card -f pretty
# 查看支持的卡类型
node src/cli.js card list-types
# 查看帮助
node src/cli.js --help
```
### 3. 便捷脚本
也可以使用 npm scripts
```bash
# 生成一张卡
npm run card
# 使用自定义参数(需要加 --
npm run card -- -t visa -n 5
```
### 4. 批量生成并保存
```bash
# 生成100张银联卡并保存
node src/cli.js card -n 100 > cards.txt
# 生成CSV格式
node src/cli.js card -n 50 -f csv > cards.csv
# 生成JSON格式每行一个JSON对象
node src/cli.js card -n 20 -f json > cards.json
```
### 5. 编程使用
创建脚本 `test.js`
```javascript
const CardGenerator = require('./src/tools/card-generator/generator');
const generator = new CardGenerator();
// 生成单张卡
const card = generator.generate('unionpay');
console.log(card);
// 批量生成
const cards = generator.generateBatch(10, 'visa');
console.log(cards);
```
运行:
```bash
node test.js
```
## 📊 输出格式对比
| 格式 | 命令 | 输出示例 |
|------|------|---------|
| **Pipe** | `node src/cli.js card` | `6228367546245545\|08\|29\|783` |
| **JSON** | `node src/cli.js card -f json` | `{"number":"6228367546245545","month":"08","year":"29","cvv":"783","type":"中国银联 (UnionPay)"}` |
| **CSV** | `node src/cli.js card -f csv` | `6228367546245545,08,29,783` |
| **Pretty** | `node src/cli.js card -f pretty` | 多行美化格式 |
## 🎯 常见使用场景
### 测试场景1: 支付系统测试
```bash
# 生成各种卡类型用于测试
node src/cli.js card -t visa -n 5
node src/cli.js card -t mastercard -n 5
node src/cli.js card -t amex -n 5
```
### 测试场景2: 前端表单测试
```bash
# 生成JSON格式用于mock数据
node src/cli.js card -n 20 -f json > mock-cards.json
```
### 测试场景3: 数据库填充
```bash
# 生成CSV格式导入数据库
node src/cli.js card -t unionpay -n 1000 -f csv > import.csv
```
## 🔧 运行示例代码
查看完整示例:
```bash
node examples/basic-usage.js
```
## ⚡ 性能提示
- 单次生成推荐不超过 10000 张卡
- 大批量生成建议分批进行
- 使用 pipe 格式性能最优
## ❓ 遇到问题?
1. 查看完整文档:`README.md`
2. 查看工具文档:`docs/tools/card-generator.md`
3. 运行示例:`node examples/basic-usage.js`
## 📝 输出格式说明
**Pipe格式** (默认)`卡号|月|年|CVV`
- 适合:文本处理、批量导入
- 分隔符:`|`
**JSON格式**完整JSON对象
- 适合API测试、前端mock
- 包含卡号、有效期、CVV、卡类型
**CSV格式**:逗号分隔
- 适合Excel、数据库导入
- 分隔符:`,`
**Pretty格式**:美化显示
- 适合:人工阅读、展示
- 多行格式化输出

View File

@ -11,6 +11,7 @@
- 🔧 **高度可配置** - 支持多种输出格式和自定义选项 - 🔧 **高度可配置** - 支持多种输出格式和自定义选项
- 📦 **轻量级** - 最小依赖,快速安装 - 📦 **轻量级** - 最小依赖,快速安装
- 🌍 **跨平台** - 支持 macOS、Linux、Windows - 🌍 **跨平台** - 支持 macOS、Linux、Windows
- 🛡️ **反检测技术** - 使用最新的 rebrowser-puppeteer降低被识别风险
## 📦 安装 ## 📦 安装
@ -238,12 +239,15 @@ node src/index.js card -n 5
## 📋 待办事项 ## 📋 待办事项
- [ ] 添加更多工具(邮箱生成器、用户名生成器等) - [x] 信用卡生成器支持Luhn算法
- [x] 账号注册工具(支持步骤化流程)
- [x] 反检测技术rebrowser-puppeteer
- [x] 人类行为模拟
- [ ] 添加更多网站注册脚本
- [ ] 添加单元测试 - [ ] 添加单元测试
- [ ] 添加配置文件支持 - [ ] 验证码识别集成
- [ ] 代理池管理
- [ ] 发布到 npm - [ ] 发布到 npm
- [ ] 添加交互式模式
- [ ] 支持自定义卡号规则
## 🤝 贡献 ## 🤝 贡献

View File

@ -0,0 +1,259 @@
# 账号注册工具 (Account Register)
自动化账号注册工具,支持步骤化流程和反检测技术。
## 功能特性
- ✅ 步骤化注册流程(每个网站独立定义)
- ✅ 反检测技术rebrowser-puppeteer
- ✅ 人类行为模拟
- ✅ 自动数据生成(姓名、邮箱、密码等)
- ✅ 支持部分步骤执行
- ✅ 干运行模式
## 快速开始
### 1. 生成账号数据(干运行)
```bash
node src/cli.js register -s windsurf --dry-run
```
输出示例:
```
firstName: John
lastName: Smith
fullName: John Smith
email: user_17001234_abc123@gmail.com
username: quickwolf456
password: Xy9#mK2$pL5@
timestamp: 2024-11-16T11:30:00.000Z
```
### 2. 执行第一步注册
```bash
node src/cli.js register -s windsurf --from-step 1 --to-step 1
```
这将:
1. 生成账号数据
2. 启动浏览器(反检测模式)
3. 打开注册页面
4. 自动填写 First Name, Last Name, Email
5. 点击 Continue 按钮
### 3. 执行完整注册流程
```bash
node src/cli.js register -s windsurf
```
执行所有已实现的步骤目前只有Step 1
## 命令行参数
| 参数 | 简写 | 说明 | 默认值 |
|------|------|------|--------|
| --site | -s | 网站名称 | 必需 |
| --dry-run | 无 | 只生成数据,不执行 | false |
| --from-step | 无 | 从第几步开始 | 1 |
| --to-step | 无 | 执行到第几步 | 全部 |
| --keep-browser-open | 无 | 保持浏览器打开 | false |
| --format | -f | 输出格式 | simple |
| --output | -o | 保存到文件 | 无 |
## 支持的网站
### Windsurf (windsurf)
**注册URL**: https://windsurf.com/account/register
**步骤总数**: 3步目前实现1步
**步骤详情**:
1. ✅ 填写基本信息First Name, Last Name, Email
2. ⏳ 设置密码(待实现)
3. ⏳ 邮箱验证(待实现)
## 使用示例
### 示例1测试数据生成
```bash
# 生成通用账号数据
node src/cli.js register generate
# 生成Windsurf专用数据
node src/cli.js register -s windsurf --dry-run
# JSON格式输出
node src/cli.js register generate -f json
```
### 示例2自动化注册
```bash
# 执行第一步
node src/cli.js register -s windsurf --from-step 1 --to-step 1
# 保持浏览器打开(手动完成后续步骤)
node src/cli.js register -s windsurf --keep-browser-open
# 保存账号数据到文件
node src/cli.js register -s windsurf -o account.json
```
### 示例3分步骤执行
```bash
# 只执行第一步
node src/cli.js register -s windsurf --to-step 1
# 从第二步继续(需要先完成第一步)
node src/cli.js register -s windsurf --from-step 2
```
## 反检测技术
### 使用的技术
1. **rebrowser-puppeteer**: 修补版Puppeteer修复CDP泄漏
2. **人类行为模拟**: 随机延迟、真实鼠标轨迹、自然输入节奏
3. **指纹随机化**: 随机视口、用户代理、语言设置
详细说明请查看: [ANTI-DETECTION.md](../account-register/ANTI-DETECTION.md)
### 测试反检测效果
访问以下网站测试是否被识别为bot
- https://bot-detector.rebrowser.net/
- https://arh.antoinevastel.com/bots/areyouheadless
- https://abrahamjuliot.github.io/creepjs
## 添加新网站
### 1. 创建网站脚本
`src/tools/account-register/sites/` 目录创建新文件,例如 `github.js`
```javascript
const AccountDataGenerator = require('../generator');
const HumanBehavior = require('../utils/human-behavior');
const logger = require('../../../shared/logger');
class GitHubRegister {
constructor() {
this.siteName = 'GitHub';
this.siteUrl = 'https://github.com/signup';
this.dataGen = new AccountDataGenerator();
this.human = new HumanBehavior();
this.currentStep = 0;
// 定义步骤
this.steps = [
{ id: 1, name: '填写邮箱', method: 'step1_fillEmail' },
{ id: 2, name: '设置密码', method: 'step2_setPassword' },
{ id: 3, name: '填写用户名', method: 'step3_fillUsername' },
// 更多步骤...
];
}
generateData(options = {}) {
return this.dataGen.generateAccount(options);
}
async initBrowser() {
// 使用Windsurf的实现
}
async step1_fillEmail() {
// 实现第一步
}
async register(options = {}) {
// 实现主流程
}
}
module.exports = GitHubRegister;
```
### 2. 使用新脚本
```bash
node src/cli.js register -s github
```
工具会自动发现并加载新脚本。
## 编程使用
```javascript
const WindsurfRegister = require('./src/tools/account-register/sites/windsurf');
(async () => {
const register = new WindsurfRegister();
try {
const result = await register.register({
fromStep: 1,
toStep: 1,
keepBrowserOpen: true
});
console.log('注册结果:', result);
} catch (error) {
console.error('注册失败:', error);
}
})();
```
## 注意事项
⚠️ **重要提醒**
1. **遵守服务条款**: 确保使用符合网站的服务条款
2. **频率限制**: 避免短时间内大量注册
3. **验证码**: 遇到验证码时需要手动处理
4. **IP地址**: 建议使用住宅代理避免数据中心IP
5. **法律合规**: 仅用于合法的测试和个人用途
## 故障排除
### 问题1: 浏览器无法启动
**解决方案**:
```bash
# 重新安装依赖
rm -rf node_modules package-lock.json
npm install
```
### 问题2: 元素找不到
**解决方案**:
- 检查网站是否更新了页面结构
- 增加等待时间
- 使用浏览器开发工具查看实际的选择器
### 问题3: 仍然被识别为bot
**解决方案**:
1. 使用住宅代理
2. 增加操作延迟
3. 添加更多人类行为(滚动、鼠标移动)
4. 考虑使用半自动模式
## 性能优化
- 使用 `--headless` 模式(但容易被检测)
- 禁用图片加载
- 使用更快的选择器
- 缓存浏览器实例
## 相关资源
- [反检测技术文档](../account-register/ANTI-DETECTION.md)
- [rebrowser-patches](https://github.com/rebrowser/rebrowser-patches)
- [人类行为模拟最佳实践](https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth)

47
examples/basic-usage.js Normal file
View File

@ -0,0 +1,47 @@
/**
* Basic Usage Examples
* 基本使用示例
*/
const CardGenerator = require('../src/tools/card-generator/generator');
const Formatter = require('../src/tools/card-generator/formatter');
const generator = new CardGenerator();
const formatter = new Formatter();
console.log('=== 信用卡生成器使用示例 ===\n');
// 示例1: 生成单张银联卡
console.log('1. 生成单张银联卡:');
const unionpayCard = generator.generate('unionpay');
console.log(formatter.format(unionpayCard, 'pipe'));
console.log('');
// 示例2: 生成Visa卡JSON格式
console.log('2. 生成Visa卡 (JSON格式):');
const visaCard = generator.generate('visa');
console.log(formatter.format(visaCard, 'json'));
console.log('');
// 示例3: 批量生成
console.log('3. 批量生成5张银联卡:');
const cards = generator.generateBatch(5, 'unionpay');
cards.forEach((card, index) => {
console.log(`${index + 1}. ${formatter.format(card, 'pipe')}`);
});
console.log('');
// 示例4: 不同格式输出
console.log('4. 不同格式输出同一张卡:');
const testCard = generator.generate('mastercard');
console.log('Pipe格式:', formatter.format(testCard, 'pipe'));
console.log('CSV格式:', formatter.format(testCard, 'csv'));
console.log('Pretty格式:\n' + formatter.format(testCard, 'pretty'));
console.log('');
// 示例5: 查看支持的类型
console.log('5. 支持的卡类型:');
const types = generator.getSupportedTypes();
types.forEach(type => {
console.log(` - ${type.id}: ${type.name}`);
});

View File

@ -21,7 +21,10 @@
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"commander": "^11.0.0" "commander": "^11.0.0",
"puppeteer": "npm:rebrowser-puppeteer@^23.9.0",
"imap": "^0.8.19",
"mailparser": "^3.6.5"
}, },
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"

View File

@ -44,6 +44,51 @@ if (cardTool) {
}); });
} }
// 注册account-register命令
const registerTool = registry.get('account-register');
if (registerTool) {
const registerCommand = program
.command('register')
.description('自动注册账号')
.option('-s, --site <site>', '网站名称 (windsurf, etc)')
.option('--dry-run', '干运行模式:只生成数据不执行注册', false)
.option('-f, --format <format>', '输出格式 (simple, json, table)', 'simple')
.option('-o, --output <file>', '保存账号数据到文件')
.option('--from-step <number>', '从第几步开始执行', '1')
.option('--to-step <number>', '执行到第几步(不指定则执行全部)')
.option('--password-strategy <strategy>', '密码策略 (email=使用邮箱, random=随机)', 'email')
.option('--keep-browser-open', '保持浏览器打开', false)
.action(async (options) => {
// 转换步骤参数为数字
if (options.fromStep) options.fromStep = parseInt(options.fromStep);
if (options.toStep) options.toStep = parseInt(options.toStep);
await registerTool.execute(options);
});
// 添加子命令
registerCommand
.command('list')
.description('列出所有支持的网站')
.action(() => {
registerTool.listSites();
});
registerCommand
.command('generate')
.description('只生成账号数据(不执行注册)')
.option('-f, --format <format>', '输出格式', 'simple')
.action((options) => {
registerTool.generate(options);
});
registerCommand
.command('list-formats')
.description('列出所有支持的输出格式')
.action(() => {
registerTool.listFormats();
});
}
// 列出所有工具 // 列出所有工具
program program
.command('list') .command('list')
@ -62,11 +107,19 @@ program
program.on('--help', () => { program.on('--help', () => {
console.log(''); console.log('');
console.log('Examples:'); console.log('Examples:');
console.log(' # 信用卡生成器');
console.log(' $ aam card # 生成一张默认银联卡'); console.log(' $ aam card # 生成一张默认银联卡');
console.log(' $ aam card -t visa # 生成一张Visa卡'); console.log(' $ aam card -t visa # 生成一张Visa卡');
console.log(' $ aam card -t unionpay -n 10 # 生成10张银联卡'); console.log(' $ aam card -t unionpay -n 10 # 生成10张银联卡');
console.log(' $ aam card -t visa -f json # JSON格式输出');
console.log(' $ aam card list-types # 查看支持的卡类型'); console.log(' $ aam card list-types # 查看支持的卡类型');
console.log('');
console.log(' # 账号注册工具');
console.log(' $ aam register -s windsurf --dry-run # 生成Windsurf账号数据');
console.log(' $ aam register -s windsurf # 执行Windsurf注册');
console.log(' $ aam register list # 列出支持的网站');
console.log(' $ aam register generate # 生成通用账号数据');
console.log('');
console.log(' # 其他');
console.log(' $ aam list # 列出所有工具'); console.log(' $ aam list # 列出所有工具');
console.log(''); console.log('');
}); });

View File

@ -0,0 +1,170 @@
# 反检测技术说明
## 当前使用的技术
### 1. rebrowser-puppeteer
我们使用 `rebrowser-puppeteer` 替代标准的 `puppeteer`,这是一个打了补丁的版本,修复了以下检测点:
#### 修复的主要泄漏点:
- **Runtime.enable 泄漏** - CDP命令检测
- **Console.enable 泄漏** - 控制台API检测
- **Command Flags 泄漏** - 启动参数检测
- **sourceURL 泄漏** - 脚本注入检测
- **navigator.webdriver** - 自动化标记
- **Chrome DevTools Protocol** - CDP特征
### 2. 人类行为模拟
实现了 `HumanBehavior` 类,模拟真实用户操作:
#### 输入行为:
- 随机输入速度80-180ms/字符)
- 随机停顿(模拟思考)
- 偶尔的快速输入段落
- 自然的输入节奏
#### 鼠标行为:
- 非线性移动轨迹
- 随机点击位置偏移
- 移动前的停顿
- 点击前的悬停
#### 页面交互:
- 阅读页面的停顿
- 随机滚动
- 视觉搜索模拟
- 自然的导航行为
### 3. 浏览器指纹随机化
- 随机视口大小
- 随机用户代理
- 随机语言设置
- 非无头模式运行
## 使用模式
### 全自动模式(高风险)
```bash
npm run install # 首先安装rebrowser-puppeteer
node src/cli.js register -s windsurf
```
适用场景:
- 测试环境
- 弱防护网站
- 已验证可用的网站
### 半自动模式(推荐)
```bash
node src/cli.js register -s windsurf --semi-auto
```
工作流程:
1. 工具生成账号数据
2. 自动打开浏览器到注册页面
3. 显示生成的数据供复制
4. 用户手动填写(最安全)
5. 工具监控进度
### 干运行模式(最安全)
```bash
node src/cli.js register -s windsurf --dry-run
```
只生成数据,不打开浏览器,完全手动注册。
## 检测测试
### 在线测试工具:
1. **rebrowser Bot Detector**
- https://bot-detector.rebrowser.net/
- 测试是否被识别为bot
2. **Are You Headless**
- https://arh.antoinevastel.com/bots/areyouheadless
- 检测headless特征
3. **CreepJS**
- https://abrahamjuliot.github.io/creepjs
- 浏览器指纹检测
4. **Browser Leaks**
- https://browserleaks.com
- 全面的泄漏检测
## 已知限制
### 仍然可能被检测的场景:
1. **行为分析**
- 过于规律的时间间隔
- 缺乏真实的页面交互
- 异常快的表单填写
2. **高级验证码**
- reCAPTCHA v3行为评分
- hCaptcha难以自动化
- 自定义验证码
3. **IP地址**
- 数据中心IP
- 频繁的注册尝试
- 地理位置异常
4. **设备指纹**
- Canvas指纹
- WebGL指纹
- Audio指纹
## 最佳实践
### 提高成功率的建议:
1. **使用住宅代理**
```javascript
// 配置代理
args: ['--proxy-server=http://your-residential-proxy:8080']
```
2. **延长操作间隔**
- 增加步骤之间的延迟
- 模拟更多的页面交互
- 随机浏览其他页面
3. **限制频率**
- 同一IP每天最多2-3次注册
- 使用不同的浏览器配置
- 轮换代理IP
4. **混合模式**
- 关键步骤手动完成
- 自动化只用于数据生成
- 遇到验证码立即转手动
## 持续改进
项目会持续关注:
- rebrowser-puppeteer 更新
- 新的反检测技术
- 社区最佳实践
- 实际测试反馈
## 参考资源
- [rebrowser-patches](https://github.com/rebrowser/rebrowser-patches)
- [Patchright](https://github.com/Kaliiiiiiiiii-Vinyzu/patchright)
- [nodriver](https://github.com/ultrafunkamsterdam/nodriver)
- [DataDome Detection Analysis](https://datadome.co/threat-research/)
## 免责声明
本工具仅用于:
- 合法的自动化测试
- 个人账号管理
- 教育和研究目的
请遵守各网站的服务条款和适用法律。
滥用自动化工具可能导致账号封禁或法律后果。

View File

@ -0,0 +1,96 @@
/**
* Account Register Configuration
* 账号注册工具配置
*/
/**
* 默认配置
*/
const DEFAULT_CONFIG = {
// 邮箱配置
email: {
domain: 'gmail.com',
pattern: null
},
// 用户名配置
username: {
type: 'word', // word, random, numeric
minLength: 8,
maxLength: 12,
pattern: null
},
// 密码配置
password: {
strategy: 'email', // 'email' = 使用邮箱作为密码, 'random' = 随机密码
length: 12,
includeUppercase: true,
includeLowercase: true,
includeNumbers: true,
includeSpecial: true
},
// 手机号配置
phone: {
country: 'CN',
withCountryCode: false
},
// 信用卡配置
card: {
type: 'visa'
},
// Cloudflare验证配置
cloudflare: {
mode: 'manual' // 'auto' = 全自动(实验性), 'manual' = 半自动(推荐)
}
};
/**
* 输出格式
*/
const OUTPUT_FORMATS = {
json: {
name: 'JSON格式',
formatter: (account) => JSON.stringify(account, null, 2)
},
table: {
name: '表格格式',
formatter: (account) => {
const lines = [];
lines.push('┌─────────────┬─────────────────────────────────────────┐');
for (const [key, value] of Object.entries(account)) {
if (typeof value === 'object' && value !== null) {
lines.push(`${key.padEnd(11)}${JSON.stringify(value).padEnd(39)}`);
} else {
lines.push(`${key.padEnd(11)}${String(value).padEnd(39)}`);
}
}
lines.push('└─────────────┴─────────────────────────────────────────┘');
return lines.join('\n');
}
},
simple: {
name: '简单格式',
formatter: (account) => {
const lines = [];
for (const [key, value] of Object.entries(account)) {
if (typeof value === 'object' && value !== null) {
lines.push(`${key}: ${JSON.stringify(value)}`);
} else {
lines.push(`${key}: ${value}`);
}
}
return lines.join('\n');
}
}
};
module.exports = {
DEFAULT_CONFIG,
OUTPUT_FORMATS
};

View File

@ -0,0 +1,232 @@
/**
* IMAP Connector - IMAP邮箱连接器
* 支持QQ邮箱GmailOutlook等支持IMAP的邮箱服务
*/
const Imap = require('imap');
const { simpleParser } = require('mailparser');
const logger = require('../../../../shared/logger');
class ImapConnector {
constructor(config) {
this.config = config;
this.imap = null;
this.connected = false;
}
/**
* 连接到邮箱
*/
async connect() {
return new Promise((resolve, reject) => {
this.imap = new Imap({
user: this.config.user,
password: this.config.password,
host: this.config.host,
port: this.config.port,
tls: this.config.tls,
tlsOptions: this.config.tlsOptions || { rejectUnauthorized: false }
});
this.imap.once('ready', () => {
this.connected = true;
logger.success('IMAP', `已连接到邮箱: ${this.config.user}`);
resolve();
});
this.imap.once('error', (err) => {
logger.error('IMAP', `连接失败: ${err.message}`);
reject(err);
});
this.imap.once('end', () => {
this.connected = false;
logger.info('IMAP', '连接已关闭');
});
this.imap.connect();
});
}
/**
* 断开连接
*/
disconnect() {
if (this.imap && this.connected) {
this.imap.end();
}
}
/**
* 获取最新的邮件
* @param {number} count - 获取数量
* @param {string} folder - 邮箱文件夹名称默认'INBOX'
* @returns {Promise<Array>}
*/
async getLatestEmails(count = 50, folder = 'INBOX') {
if (!this.connected) {
await this.connect();
}
return new Promise((resolve, reject) => {
this.imap.openBox(folder, false, (err, box) => {
if (err) {
reject(err);
return;
}
// 搜索条件:只搜索未读邮件
this.imap.search(['UNSEEN'], (err, results) => {
if (err) {
reject(err);
return;
}
if (!results || results.length === 0) {
resolve([]);
return;
}
logger.info('IMAP', `搜索到 ${results.length} 封未读邮件`);
// 只取最新的N封
const uids = results.slice(-count);
const emails = [];
let processedCount = 0;
const totalCount = uids.length;
const fetch = this.imap.fetch(uids, {
bodies: '',
markSeen: true
});
fetch.on('message', (msg) => {
msg.on('body', (stream) => {
simpleParser(stream, (err, parsed) => {
if (err) {
logger.warn('IMAP', `解析邮件失败: ${err.message}`);
} else {
emails.push({
uid: msg.uid,
from: parsed.from?.text || '',
to: parsed.to?.text || '',
subject: parsed.subject || '',
date: parsed.date,
text: parsed.text || '',
html: parsed.html || '',
headers: parsed.headers
});
}
processedCount++;
// 所有邮件都处理完后才resolve
if (processedCount === totalCount) {
logger.info('IMAP', `成功解析 ${emails.length} 封邮件`);
resolve(emails);
}
});
});
});
fetch.once('error', (err) => {
reject(err);
});
});
});
});
}
/**
* 搜索包含特定关键词的邮件
* @param {string} subject - 主题关键词
* @param {number} sinceDays - 几天内
* @returns {Promise<Array>}
*/
async searchBySubject(subject, sinceDays = 1) {
if (!this.connected) {
await this.connect();
}
return new Promise((resolve, reject) => {
this.imap.openBox('INBOX', false, (err, box) => {
if (err) {
reject(err);
return;
}
const sinceDate = new Date();
sinceDate.setDate(sinceDate.getDate() - sinceDays);
this.imap.search([['SINCE', sinceDate], ['SUBJECT', subject]], (err, results) => {
if (err) {
reject(err);
return;
}
if (!results || results.length === 0) {
resolve([]);
return;
}
const emails = [];
const fetch = this.imap.fetch(results, {
bodies: '',
markSeen: true
});
fetch.on('message', (msg) => {
msg.on('body', (stream) => {
simpleParser(stream, (err, parsed) => {
if (err) {
logger.warn('IMAP', `解析邮件失败: ${err.message}`);
return;
}
emails.push({
uid: msg.uid,
from: parsed.from?.text || '',
to: parsed.to?.text || '',
subject: parsed.subject || '',
date: parsed.date,
text: parsed.text || '',
html: parsed.html || '',
headers: parsed.headers
});
});
});
});
fetch.once('error', (err) => {
reject(err);
});
fetch.once('end', () => {
resolve(emails);
});
});
});
});
}
/**
* 标记邮件为已读
* @param {number} uid - 邮件UID
*/
async markAsRead(uid) {
if (!this.connected) {
return;
}
return new Promise((resolve, reject) => {
this.imap.addFlags(uid, ['\\Seen'], (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
}
module.exports = ImapConnector;

View File

@ -0,0 +1,33 @@
/**
* Email Configuration
* 邮箱配置
*/
module.exports = {
// 主邮箱配置QQ邮箱 - 接收所有验证码)
primary: {
type: 'imap',
host: 'imap.qq.com',
port: 993,
user: '1695551@qq.com',
password: 'iogmboamejdsbjdh', // QQ邮箱授权码
tls: true,
tlsOptions: {
rejectUnauthorized: false
}
},
// 可以配置备用邮箱(未来扩展)
backup: {
// type: 'imap',
// host: '...',
// ...
},
// 邮件搜索配置
search: {
maxWaitTime: 60, // 最长等待时间(秒)
checkInterval: 3, // 检查间隔(秒)
maxEmails: 10 // 每次获取的邮件数量
}
};

View File

@ -0,0 +1,202 @@
/**
* Email Verification Service
* 邮箱验证码服务 - 统一入口
*/
const ImapConnector = require('./connectors/imap-connector');
const WindsurfParser = require('./parsers/windsurf-parser');
const emailConfig = require('./email-config');
const logger = require('../../../shared/logger');
class EmailVerificationService {
constructor(config = null) {
this.config = config || emailConfig.primary;
this.connector = null;
this.parsers = [
new WindsurfParser()
// 未来添加更多解析器
// new GitHubParser(),
// new TwitterParser(),
];
}
/**
* 获取验证码
* @param {string} siteName - 网站名称 'windsurf'
* @param {string} recipientEmail - 接收验证码的邮箱地址
* @param {number} timeout - 超时时间
* @returns {Promise<string>} - 验证码
*/
async getVerificationCode(siteName, recipientEmail, timeout = 120) {
logger.info('EmailVerification', `开始获取 ${siteName} 的验证码...`);
logger.info('EmailVerification', `接收邮箱: ${recipientEmail}`);
return new Promise((resolve, reject) => {
const startTime = Date.now();
const maxWaitTime = timeout * 1000;
// 创建IMAP连接
this.connector = new ImapConnector(this.config);
let checkInterval;
let isResolved = false;
const checkMail = async () => {
if (Date.now() - startTime > maxWaitTime) {
clearInterval(checkInterval);
this.connector.disconnect();
if (!isResolved) {
isResolved = true;
reject(new Error('获取验证码超时'));
}
return;
}
try {
// 获取最新邮件
logger.info('EmailVerification', '正在搜索邮件...');
const emails = await this.connector.getLatestEmails(50, 'INBOX');
if (!emails || emails.length === 0) {
logger.info('EmailVerification', '暂无未读邮件');
return;
}
logger.info('EmailVerification', `✓ 找到 ${emails.length} 封未读邮件`);
// 按日期倒序排序(最新的在前)
emails.sort((a, b) => {
const dateA = a.date ? new Date(a.date).getTime() : 0;
const dateB = b.date ? new Date(b.date).getTime() : 0;
return dateB - dateA;
});
// 打印最近5条邮件信息
const recentEmails = emails.slice(0, 5);
logger.info('EmailVerification', '='.repeat(60));
logger.info('EmailVerification', '最近5条邮件');
recentEmails.forEach((email, index) => {
const dateStr = email.date ? new Date(email.date).toLocaleString('zh-CN') : 'N/A';
logger.info('EmailVerification', ` ${index + 1}. 时间: ${dateStr}`);
logger.info('EmailVerification', ` 发件人: ${email.from}`);
logger.info('EmailVerification', ` 主题: ${email.subject}`);
logger.info('EmailVerification', ` 收件人: ${email.to}`);
});
logger.info('EmailVerification', '='.repeat(60));
// 查找匹配的邮件并提取验证码
// 注意QQ邮箱转发邮件后收件人字段会被改写为QQ邮箱地址所以不能检查收件人
// 改为只检查发件人和主题,并按时间取最新的
for (const email of emails) {
if (isResolved) return;
logger.info('EmailVerification', `检查邮件: 发件人="${email.from}", 主题="${email.subject}", 时间="${email.date}"`);
for (const parser of this.parsers) {
if (parser.canParse(email)) {
logger.success('EmailVerification', ` ✓ 找到匹配的邮件: ${email.subject}`);
const code = parser.extractCode(email);
if (code) {
clearInterval(checkInterval);
this.connector.disconnect();
if (!isResolved) {
isResolved = true;
logger.success('EmailVerification', ` ✓ 成功提取验证码: ${code}`);
resolve(code);
}
return;
} else {
logger.warn('EmailVerification', ` 邮件匹配但无法提取验证码`);
}
}
}
}
logger.warn('EmailVerification', `未找到匹配的验证码邮件`);
} catch (err) {
logger.error('EmailVerification', `检查邮件失败: ${err.message}`);
}
};
// 连接成功后开始检查
this.connector.connect().then(() => {
logger.success('EmailVerification', 'IMAP连接成功开始监听验证码邮件...');
checkMail();
checkInterval = setInterval(checkMail, emailConfig.search.checkInterval * 1000);
}).catch((err) => {
if (!isResolved) {
isResolved = true;
reject(new Error(`IMAP连接失败: ${err.message}`));
}
});
});
}
/**
* 搜索特定主题的邮件并提取验证码
* @param {string} subject - 邮件主题关键词
* @param {number} timeout - 超时时间
* @returns {Promise<string>}
*/
async getCodeBySubject(subject, timeout = 60) {
logger.info('EmailVerification', `搜索主题包含 "${subject}" 的邮件...`);
try {
this.connector = new ImapConnector(this.config);
await this.connector.connect();
const startTime = Date.now();
const checkInterval = emailConfig.search.checkInterval * 1000;
while (Date.now() - startTime < timeout * 1000) {
const emails = await this.connector.searchBySubject(subject, 1);
if (emails && emails.length > 0) {
for (const email of emails.reverse()) {
for (const parser of this.parsers) {
if (parser.canParse(email)) {
const code = parser.extractCode(email);
if (code) {
logger.success('EmailVerification', `提取到验证码: ${code}`);
this.connector.disconnect();
return code;
}
}
}
}
}
await this.sleep(checkInterval);
}
this.connector.disconnect();
throw new Error(`获取验证码超时`);
} catch (error) {
if (this.connector) {
this.connector.disconnect();
}
throw error;
}
}
/**
* 添加新的解析器
* @param {BaseParser} parser - 解析器实例
*/
addParser(parser) {
this.parsers.push(parser);
}
/**
* 休眠
* @param {number} ms - 毫秒
*/
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
module.exports = EmailVerificationService;

View File

@ -0,0 +1,53 @@
/**
* Base Parser - 邮件解析器基类
* 所有网站的邮件解析器都继承此类
*/
class BaseParser {
constructor(siteName) {
this.siteName = siteName;
}
/**
* 判断是否能解析此邮件
* @param {Object} email - 邮件对象
* @returns {boolean}
*/
canParse(email) {
throw new Error('Must implement canParse() method');
}
/**
* 从邮件中提取验证码
* @param {Object} email - 邮件对象
* @returns {string|null} - 验证码或null
*/
extractCode(email) {
throw new Error('Must implement extractCode() method');
}
/**
* 通用的验证码提取方法
* @param {string} content - 邮件内容
* @param {RegExp} pattern - 正则表达式
* @returns {string|null}
*/
extractByRegex(content, pattern) {
if (!content) return null;
const match = content.match(pattern);
return match ? match[1] : null;
}
/**
* 从HTML中提取文本
* @param {string} html - HTML内容
* @returns {string}
*/
stripHtml(html) {
if (!html) return '';
return html.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
}
}
module.exports = BaseParser;

View File

@ -0,0 +1,124 @@
/**
* Windsurf Parser - Windsurf邮件解析器
* 用于解析Windsurf发送的验证码邮件
*/
const BaseParser = require('./base-parser');
class WindsurfParser extends BaseParser {
constructor() {
super('Windsurf');
// Windsurf邮件的特征
this.senderKeywords = ['windsurf', 'codeium', 'exafunction'];
this.subjectKeywords = ['verify', 'verification', 'code', '验证', 'welcome'];
// 验证码的正则表达式(根据实际邮件调整)
this.codePatterns = [
// HTML格式: <h1 class="code_xxx">866172</h1>
/<h1[^>]*class="code[^"]*"[^>]*>(\d{6})<\/h1>/i,
// 常见格式
/6 digit code[^0-9]*(\d{6})/i,
/verification code[^0-9]*(\d{6})/i,
/verify.*code:?\s*(\d{6})/i,
// 纯6位数字最后尝试
/\b(\d{6})\b/
];
}
/**
* 判断是否是Windsurf的验证码邮件
*/
canParse(email) {
if (!email) return false;
const from = (email.from || '').toLowerCase();
const subject = (email.subject || '').toLowerCase();
// 检查发件人
const hasSender = this.senderKeywords.some(keyword =>
from.includes(keyword)
);
// 检查主题
const hasSubject = this.subjectKeywords.some(keyword =>
subject.includes(keyword)
);
return hasSender || hasSubject;
}
/**
* 从邮件中提取验证码
*/
extractCode(email) {
if (!email) return null;
// 优先从HTML提取
let code = this.extractFromHtml(email.html);
if (code) return code;
// 其次从纯文本提取
code = this.extractFromText(email.text);
if (code) return code;
return null;
}
/**
* 从HTML内容提取验证码
*/
extractFromHtml(html) {
if (!html) return null;
// 先尝试直接从HTML提取保留HTML标签
for (const pattern of this.codePatterns) {
const code = this.extractByRegex(html, pattern);
if (code && this.validateCode(code)) {
return code;
}
}
// 如果HTML提取失败再去除标签后尝试
const text = this.stripHtml(html);
return this.extractFromText(text);
}
/**
* 从文本内容提取验证码
*/
extractFromText(text) {
if (!text) return null;
// 尝试所有正则表达式
for (const pattern of this.codePatterns) {
const code = this.extractByRegex(text, pattern);
if (code && this.validateCode(code)) {
return code;
}
}
return null;
}
/**
* 验证提取的验证码是否合理
*/
validateCode(code) {
if (!code) return false;
// Windsurf验证码是6位数字
if (code.length !== 6) {
return false;
}
// 应该是纯数字
if (!/^\d{6}$/.test(code)) {
return false;
}
return true;
}
}
module.exports = WindsurfParser;

View File

@ -0,0 +1,136 @@
/**
* Account Data Generator - 账号数据生成器
* 统一的数据生成接口
*/
const EmailGenerator = require('./generators/email-generator');
const UsernameGenerator = require('./generators/username-generator');
const PasswordGenerator = require('./generators/password-generator');
const PhoneGenerator = require('./generators/phone-generator');
const NameGenerator = require('./generators/name-generator');
const CardGenerator = require('../card-generator/generator');
class AccountDataGenerator {
constructor() {
this.emailGen = new EmailGenerator();
this.usernameGen = new UsernameGenerator();
this.passwordGen = new PasswordGenerator();
this.phoneGen = new PhoneGenerator();
this.nameGen = new NameGenerator();
this.cardGen = new CardGenerator();
}
/**
* 生成完整的账号数据
* @param {Object} options - 配置选项
* @returns {Object}
*/
generateAccount(options = {}) {
const name = this.generateName(options.name);
const email = this.generateEmail(options.email);
// 根据策略生成密码
let password;
const passwordStrategy = options.password?.strategy || 'email';
if (passwordStrategy === 'email') {
// 使用邮箱地址作为密码
password = email;
} else {
// 使用随机密码
password = this.generatePassword(options.password);
}
const account = {
firstName: name.firstName,
lastName: name.lastName,
fullName: name.fullName,
email: email,
username: this.generateUsername(options.username),
password: password,
passwordStrategy: passwordStrategy,
timestamp: new Date().toISOString()
};
// 可选字段
if (options.includePhone !== false) {
account.phone = this.generatePhone(options.phone);
}
if (options.includeCard) {
account.card = this.generateCard(options.card);
}
return account;
}
/**
* 生成邮箱
* @param {Object} options - 选项
* @returns {string}
*/
generateEmail(options = {}) {
return this.emailGen.generate(options);
}
/**
* 生成用户名
* @param {Object} options - 选项
* @returns {string}
*/
generateUsername(options = {}) {
return this.usernameGen.generate(options);
}
/**
* 生成密码
* @param {Object} options - 选项
* @returns {string}
*/
generatePassword(options = {}) {
return this.passwordGen.generate(options);
}
/**
* 生成手机号
* @param {Object} options - 选项
* @returns {string}
*/
generatePhone(options = {}) {
return this.phoneGen.generate(options);
}
/**
* 生成姓名
* @param {Object} options - 选项
* @returns {Object}
*/
generateName(options = {}) {
return this.nameGen.generateFullName(options);
}
/**
* 生成信用卡
* @param {Object} options - 选项
* @returns {Object}
*/
generateCard(options = {}) {
const type = options.type || 'visa';
return this.cardGen.generate(type);
}
/**
* 批量生成账号数据
* @param {number} count - 数量
* @param {Object} options - 选项
* @returns {Array}
*/
generateBatch(count, options = {}) {
const accounts = [];
for (let i = 0; i < count; i++) {
accounts.push(this.generateAccount(options));
}
return accounts;
}
}
module.exports = AccountDataGenerator;

View File

@ -0,0 +1,74 @@
/**
* Email Generator - 邮箱生成器
*/
const { randomInt } = require('../../../shared/utils');
class EmailGenerator {
constructor() {
this.domains = [
'qichen111.asia'
];
}
/**
* 生成邮箱地址
* @param {Object} options - 选项
* @returns {string}
*/
generate(options = {}) {
const domain = options.domain || this.getRandomDomain();
const prefix = this.generatePrefix(options.pattern);
return `${prefix}@${domain}`;
}
/**
* 生成邮箱前缀
* @param {string} pattern - 模式
* @returns {string}
*/
generatePrefix(pattern) {
if (pattern) {
// 支持模式替换,如 "user_{random}"
return pattern.replace('{random}', this.generateRandomString());
}
// 默认格式: 纯随机字符串不带user前缀
const length = randomInt(8, 12);
return this.generateRandomString(length);
}
/**
* 生成随机字符串
* @param {number} length - 长度
* @returns {string}
*/
generateRandomString(length = 8) {
return Math.random().toString(36).substring(2, 2 + length);
}
/**
* 获取随机域名
* @returns {string}
*/
getRandomDomain() {
return this.domains[randomInt(0, this.domains.length - 1)];
}
/**
* 批量生成
* @param {number} count - 数量
* @param {Object} options - 选项
* @returns {Array}
*/
generateBatch(count, options = {}) {
const emails = [];
for (let i = 0; i < count; i++) {
emails.push(this.generate(options));
}
return emails;
}
}
module.exports = EmailGenerator;

View File

@ -0,0 +1,139 @@
/**
* Name Generator - 姓名生成器
*/
const { randomInt } = require('../../../shared/utils');
class NameGenerator {
constructor() {
// 常见英文名字
this.firstNames = {
male: [
'James', 'John', 'Robert', 'Michael', 'William',
'David', 'Richard', 'Joseph', 'Thomas', 'Charles',
'Daniel', 'Matthew', 'Anthony', 'Mark', 'Donald',
'Steven', 'Paul', 'Andrew', 'Joshua', 'Kenneth'
],
female: [
'Mary', 'Patricia', 'Jennifer', 'Linda', 'Elizabeth',
'Barbara', 'Susan', 'Jessica', 'Sarah', 'Karen',
'Nancy', 'Lisa', 'Betty', 'Margaret', 'Sandra',
'Ashley', 'Kimberly', 'Emily', 'Donna', 'Michelle'
],
neutral: [
'Alex', 'Jordan', 'Taylor', 'Casey', 'Riley',
'Morgan', 'Parker', 'Avery', 'Quinn', 'Skyler'
]
};
// 常见英文姓氏
this.lastNames = [
'Smith', 'Johnson', 'Williams', 'Brown', 'Jones',
'Garcia', 'Miller', 'Davis', 'Rodriguez', 'Martinez',
'Hernandez', 'Lopez', 'Gonzalez', 'Wilson', 'Anderson',
'Thomas', 'Taylor', 'Moore', 'Jackson', 'Martin',
'Lee', 'Perez', 'Thompson', 'White', 'Harris',
'Sanchez', 'Clark', 'Ramirez', 'Lewis', 'Robinson'
];
// 中文姓名
this.chineseFirstNames = ['伟', '芳', '娜', '秀英', '敏', '静', '丽', '强', '磊', '军'];
this.chineseLastNames = ['王', '李', '张', '刘', '陈', '杨', '赵', '黄', '周', '吴'];
}
/**
* 生成名字
* @param {Object} options - 选项
* @returns {string}
*/
generateFirstName(options = {}) {
const { gender, locale } = options;
if (locale === 'zh-CN') {
return this.generateChineseFirstName();
}
// 英文名字
let pool;
if (gender === 'male') {
pool = this.firstNames.male;
} else if (gender === 'female') {
pool = this.firstNames.female;
} else {
// 随机选择性别或中性
const allNames = [
...this.firstNames.male,
...this.firstNames.female,
...this.firstNames.neutral
];
pool = allNames;
}
return pool[randomInt(0, pool.length - 1)];
}
/**
* 生成姓氏
* @param {Object} options - 选项
* @returns {string}
*/
generateLastName(options = {}) {
const { locale } = options;
if (locale === 'zh-CN') {
return this.generateChineseLastName();
}
return this.lastNames[randomInt(0, this.lastNames.length - 1)];
}
/**
* 生成完整姓名
* @param {Object} options - 选项
* @returns {Object}
*/
generateFullName(options = {}) {
const firstName = this.generateFirstName(options);
const lastName = this.generateLastName(options);
return {
firstName,
lastName,
fullName: options.locale === 'zh-CN'
? `${lastName}${firstName}`
: `${firstName} ${lastName}`
};
}
/**
* 生成中文名字
* @returns {string}
*/
generateChineseFirstName() {
return this.chineseFirstNames[randomInt(0, this.chineseFirstNames.length - 1)];
}
/**
* 生成中文姓氏
* @returns {string}
*/
generateChineseLastName() {
return this.chineseLastNames[randomInt(0, this.chineseLastNames.length - 1)];
}
/**
* 批量生成
* @param {number} count - 数量
* @param {Object} options - 选项
* @returns {Array}
*/
generateBatch(count, options = {}) {
const names = [];
for (let i = 0; i < count; i++) {
names.push(this.generateFullName(options));
}
return names;
}
}
module.exports = NameGenerator;

View File

@ -0,0 +1,145 @@
/**
* Password Generator - 密码生成器
*/
const { randomInt } = require('../../../shared/utils');
class PasswordGenerator {
constructor() {
this.lowercase = 'abcdefghijklmnopqrstuvwxyz';
this.uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
this.numbers = '0123456789';
this.special = '!@#$%^&*()_+-=[]{}|;:,.<>?';
}
/**
* 生成密码
* @param {Object} options - 选项
* @returns {string}
*/
generate(options = {}) {
const {
length = 12,
includeUppercase = true,
includeLowercase = true,
includeNumbers = true,
includeSpecial = true,
minUppercase = 1,
minLowercase = 1,
minNumbers = 1,
minSpecial = 1
} = options;
let chars = '';
let password = '';
// 构建字符集
if (includeLowercase) chars += this.lowercase;
if (includeUppercase) chars += this.uppercase;
if (includeNumbers) chars += this.numbers;
if (includeSpecial) chars += this.special;
if (chars.length === 0) {
throw new Error('At least one character type must be included');
}
// 确保满足最小要求
if (includeLowercase && minLowercase > 0) {
for (let i = 0; i < minLowercase; i++) {
password += this.lowercase.charAt(randomInt(0, this.lowercase.length - 1));
}
}
if (includeUppercase && minUppercase > 0) {
for (let i = 0; i < minUppercase; i++) {
password += this.uppercase.charAt(randomInt(0, this.uppercase.length - 1));
}
}
if (includeNumbers && minNumbers > 0) {
for (let i = 0; i < minNumbers; i++) {
password += this.numbers.charAt(randomInt(0, this.numbers.length - 1));
}
}
if (includeSpecial && minSpecial > 0) {
for (let i = 0; i < minSpecial; i++) {
password += this.special.charAt(randomInt(0, this.special.length - 1));
}
}
// 填充剩余长度
while (password.length < length) {
password += chars.charAt(randomInt(0, chars.length - 1));
}
// 打乱顺序
password = this.shuffle(password);
return password;
}
/**
* 打乱字符串
* @param {string} str - 字符串
* @returns {string}
*/
shuffle(str) {
const arr = str.split('');
for (let i = arr.length - 1; i > 0; i--) {
const j = randomInt(0, i);
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr.join('');
}
/**
* 生成简单密码只包含字母和数字
* @param {number} length - 长度
* @returns {string}
*/
generateSimple(length = 12) {
return this.generate({
length,
includeUppercase: true,
includeLowercase: true,
includeNumbers: true,
includeSpecial: false
});
}
/**
* 生成强密码
* @param {number} length - 长度
* @returns {string}
*/
generateStrong(length = 16) {
return this.generate({
length,
includeUppercase: true,
includeLowercase: true,
includeNumbers: true,
includeSpecial: true,
minUppercase: 2,
minLowercase: 2,
minNumbers: 2,
minSpecial: 2
});
}
/**
* 批量生成
* @param {number} count - 数量
* @param {Object} options - 选项
* @returns {Array}
*/
generateBatch(count, options = {}) {
const passwords = [];
for (let i = 0; i < count; i++) {
passwords.push(this.generate(options));
}
return passwords;
}
}
module.exports = PasswordGenerator;

View File

@ -0,0 +1,147 @@
/**
* Phone Generator - 手机号生成器
*/
const { randomInt, padZero } = require('../../../shared/utils');
class PhoneGenerator {
constructor() {
this.countryFormats = {
CN: {
name: '中国',
code: '+86',
format: '1##########',
prefixes: ['13', '14', '15', '16', '17', '18', '19']
},
US: {
name: '美国',
code: '+1',
format: '(###) ###-####',
prefixes: ['201', '202', '203', '212', '213', '214', '215']
},
UK: {
name: '英国',
code: '+44',
format: '7### ######',
prefixes: ['7400', '7500', '7600', '7700', '7800', '7900']
}
};
}
/**
* 生成手机号
* @param {Object} options - 选项
* @returns {string}
*/
generate(options = {}) {
const country = options.country || 'CN';
const withCountryCode = options.withCountryCode !== false;
const config = this.countryFormats[country];
if (!config) {
throw new Error(`Unsupported country: ${country}`);
}
let phone = this.generateByCountry(country);
if (withCountryCode) {
phone = `${config.code} ${phone}`;
}
return phone;
}
/**
* 根据国家生成手机号
* @param {string} country - 国家代码
* @returns {string}
*/
generateByCountry(country) {
const config = this.countryFormats[country];
switch (country) {
case 'CN':
return this.generateChinaPhone();
case 'US':
return this.generateUSPhone();
case 'UK':
return this.generateUKPhone();
default:
return this.generateChinaPhone();
}
}
/**
* 生成中国手机号
* @returns {string}
*/
generateChinaPhone() {
const config = this.countryFormats.CN;
const prefix = config.prefixes[randomInt(0, config.prefixes.length - 1)];
const suffix = this.generateDigits(9);
return `${prefix}${suffix}`;
}
/**
* 生成美国手机号
* @returns {string}
*/
generateUSPhone() {
const config = this.countryFormats.US;
const areaCode = config.prefixes[randomInt(0, config.prefixes.length - 1)];
const exchange = this.generateDigits(3);
const number = this.generateDigits(4);
return `(${areaCode}) ${exchange}-${number}`;
}
/**
* 生成英国手机号
* @returns {string}
*/
generateUKPhone() {
const config = this.countryFormats.UK;
const prefix = config.prefixes[randomInt(0, config.prefixes.length - 1)];
const suffix = this.generateDigits(6);
return `${prefix} ${suffix}`;
}
/**
* 生成指定长度的数字
* @param {number} length - 长度
* @returns {string}
*/
generateDigits(length) {
let result = '';
for (let i = 0; i < length; i++) {
result += randomInt(0, 9);
}
return result;
}
/**
* 获取支持的国家列表
* @returns {Array}
*/
getSupportedCountries() {
return Object.keys(this.countryFormats).map(code => ({
code,
name: this.countryFormats[code].name
}));
}
/**
* 批量生成
* @param {number} count - 数量
* @param {Object} options - 选项
* @returns {Array}
*/
generateBatch(count, options = {}) {
const phones = [];
for (let i = 0; i < count; i++) {
phones.push(this.generate(options));
}
return phones;
}
}
module.exports = PhoneGenerator;

View File

@ -0,0 +1,118 @@
/**
* Username Generator - 用户名生成器
*/
const { randomInt } = require('../../../shared/utils');
class UsernameGenerator {
constructor() {
this.adjectives = [
'quick', 'bright', 'swift', 'clever', 'smart',
'cool', 'happy', 'lucky', 'brave', 'wise',
'silent', 'dark', 'golden', 'silver', 'mystic'
];
this.nouns = [
'wolf', 'fox', 'eagle', 'tiger', 'dragon',
'phoenix', 'lion', 'bear', 'falcon', 'hawk',
'ninja', 'wizard', 'warrior', 'knight', 'hunter'
];
}
/**
* 生成用户名
* @param {Object} options - 选项
* @returns {string}
*/
generate(options = {}) {
const { pattern, minLength, maxLength } = options;
if (pattern) {
return this.generateFromPattern(pattern);
}
// 默认生成策略
const type = options.type || 'random';
switch (type) {
case 'word':
return this.generateWordBased();
case 'random':
return this.generateRandom(minLength, maxLength);
case 'numeric':
return this.generateNumeric();
default:
return this.generateWordBased();
}
}
/**
* 基于模式生成
* @param {string} pattern - 模式 "user_{random}", "player_{number}"
* @returns {string}
*/
generateFromPattern(pattern) {
return pattern
.replace('{random}', Math.random().toString(36).substring(2, 10))
.replace('{number}', randomInt(1000, 9999).toString())
.replace('{timestamp}', Date.now().toString().slice(-8));
}
/**
* 生成基于单词的用户名
* @returns {string}
*/
generateWordBased() {
const adj = this.adjectives[randomInt(0, this.adjectives.length - 1)];
const noun = this.nouns[randomInt(0, this.nouns.length - 1)];
const number = randomInt(10, 999);
return `${adj}${noun}${number}`;
}
/**
* 生成随机字符串用户名
* @param {number} minLength - 最小长度
* @param {number} maxLength - 最大长度
* @returns {string}
*/
generateRandom(minLength = 8, maxLength = 12) {
const length = randomInt(minLength, maxLength);
let username = '';
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
// 首字母必须是字母
username += chars.charAt(randomInt(0, 25));
// 其余字符
for (let i = 1; i < length; i++) {
username += chars.charAt(randomInt(0, chars.length - 1));
}
return username;
}
/**
* 生成数字用户名
* @returns {string}
*/
generateNumeric() {
return `user${randomInt(100000, 999999)}`;
}
/**
* 批量生成
* @param {number} count - 数量
* @param {Object} options - 选项
* @returns {Array}
*/
generateBatch(count, options = {}) {
const usernames = [];
for (let i = 0; i < count; i++) {
usernames.push(this.generate(options));
}
return usernames;
}
}
module.exports = UsernameGenerator;

View File

@ -0,0 +1,171 @@
/**
* Account Register Tool - 账号注册工具入口
*/
const fs = require('fs');
const path = require('path');
const AccountDataGenerator = require('./generator');
const logger = require('../../shared/logger');
const { OUTPUT_FORMATS } = require('./config');
const TOOL_NAME = 'account-register';
class AccountRegister {
constructor() {
this.sites = new Map();
this.dataGen = new AccountDataGenerator();
this.loadSites();
}
/**
* 加载所有网站脚本
*/
loadSites() {
const sitesDir = path.join(__dirname, 'sites');
try {
const files = fs.readdirSync(sitesDir);
files.forEach(file => {
if (file.endsWith('.js')) {
const siteName = file.replace('.js', '');
try {
const SiteClass = require(path.join(sitesDir, file));
this.sites.set(siteName, SiteClass);
logger.debug(TOOL_NAME, `Loaded site: ${siteName}`);
} catch (error) {
logger.warn(TOOL_NAME, `Failed to load site ${siteName}: ${error.message}`);
}
}
});
} catch (error) {
logger.warn(TOOL_NAME, `Failed to load sites: ${error.message}`);
}
}
/**
* 执行注册
*/
async execute(options) {
const { site, dryRun, format } = options;
try {
if (!site) {
logger.error(TOOL_NAME, '请指定网站名称');
this.listSites();
process.exit(1);
}
const SiteClass = this.sites.get(site);
if (!SiteClass) {
logger.error(TOOL_NAME, `未知的网站: ${site}`);
this.listSites();
process.exit(1);
}
const siteInstance = new SiteClass();
if (dryRun) {
// 干运行模式:只生成数据
logger.info(TOOL_NAME, '干运行模式:只生成账号数据');
const account = siteInstance.generateData(options);
const formatter = OUTPUT_FORMATS[format || 'simple'];
console.log('\n' + formatter.formatter(account) + '\n');
logger.success(TOOL_NAME, '账号数据生成完成');
} else {
// 真正执行注册
logger.info(TOOL_NAME, `开始注册 ${site} 账号...`);
const result = await siteInstance.register(options);
if (result.success) {
logger.success(TOOL_NAME, '注册流程完成');
if (options.output) {
// 保存到文件
fs.writeFileSync(
options.output,
JSON.stringify(result.account, null, 2)
);
logger.success(TOOL_NAME, `账号数据已保存到: ${options.output}`);
}
}
}
} catch (error) {
logger.error(TOOL_NAME, error.message);
process.exit(1);
}
}
/**
* 只生成数据不执行注册
*/
generate(options) {
logger.info(TOOL_NAME, '生成账号数据...');
const account = this.dataGen.generateAccount({
name: options.name,
email: options.email,
username: options.username,
password: options.password,
phone: options.phone,
includeCard: options.includeCard
});
const formatter = OUTPUT_FORMATS[options.format || 'simple'];
console.log('\n' + formatter.formatter(account) + '\n');
}
/**
* 列出支持的网站
*/
listSites() {
const sites = Array.from(this.sites.keys());
if (sites.length === 0) {
console.log('\n暂无可用网站\n');
return;
}
console.log('\n支持的网站:\n');
sites.forEach(site => {
console.log(` ${site}`);
});
console.log('');
}
/**
* 列出支持的格式
*/
listFormats() {
console.log('\n支持的输出格式:\n');
for (const [key, config] of Object.entries(OUTPUT_FORMATS)) {
console.log(` ${key.padEnd(15)} - ${config.name}`);
}
console.log('');
}
}
module.exports = {
name: TOOL_NAME,
alias: 'register',
description: '自动注册账号',
execute: async (options) => {
const tool = new AccountRegister();
await tool.execute(options);
},
listSites: () => {
const tool = new AccountRegister();
tool.listSites();
},
listFormats: () => {
const tool = new AccountRegister();
tool.listFormats();
},
generate: (options) => {
const tool = new AccountRegister();
tool.generate(options);
}
};

View File

@ -0,0 +1,897 @@
/**
* Windsurf Register - Windsurf网站注册
* https://windsurf.com/account/register
*
* 注册流程
* Step 1: 填写基本信息First Name, Last Name, Email
* Step 2: 设置密码
* Step 3: 邮箱验证
* Step 4: 完善个人信息
* ... 根据实际情况继续添加步骤
*/
const AccountDataGenerator = require('../generator');
const HumanBehavior = require('../utils/human-behavior');
const CloudflareHandler = require('../utils/cloudflare-handler');
const logger = require('../../../shared/logger');
const EmailVerificationService = require('../email-verification');
const { DEFAULT_CONFIG } = require('../config');
const CardGenerator = require('../../card-generator/generator');
class WindsurfRegister {
constructor() {
this.siteName = 'Windsurf';
this.siteUrl = 'https://windsurf.com/account/register';
this.dataGen = new AccountDataGenerator();
this.human = new HumanBehavior();
this.emailService = new EmailVerificationService();
this.browser = null;
this.page = null;
this.currentStep = 0;
this.accountData = null;
// 定义所有步骤
this.steps = [
{ id: 1, name: '填写基本信息', method: 'step1_fillBasicInfo' },
{ id: 2, name: '设置密码', method: 'step2_setPassword' },
{ id: 3, name: '邮箱验证', method: 'step3_emailVerification' },
{ id: 4, name: '跳过问卷', method: 'step4_skipSurvey' },
{ id: 5, name: '选择计划', method: 'step5_selectPlan' },
{ id: 6, name: '填写支付信息', method: 'step6_fillPayment' },
// 根据实际注册流程继续添加
];
}
/**
* 获取步骤总数
*/
getTotalSteps() {
return this.steps.length;
}
/**
* 获取当前步骤信息
*/
getCurrentStepInfo() {
if (this.currentStep === 0) {
return { id: 0, name: '未开始', total: this.getTotalSteps() };
}
const step = this.steps[this.currentStep - 1];
return {
...step,
current: this.currentStep,
total: this.getTotalSteps()
};
}
/**
* 通用方法点击按钮并等待页面跳转
* @param {Function} checkPageChanged - 检查页面是否已跳转的函数返回Promise<boolean>
* @param {number} maxAttempts - 最多尝试次数
* @param {string} actionName - 操作名称用于日志
* @returns {Promise<boolean>} - 是否成功跳转
*/
async clickButtonAndWaitForPageChange(checkPageChanged, maxAttempts = 5, actionName = '点击按钮') {
let pageChanged = false;
let attempts = 0;
while (!pageChanged && attempts < maxAttempts) {
attempts++;
// 查找未禁用的按钮
const button = await this.page.$('button:not([disabled])');
if (button) {
const text = await this.page.evaluate(el => el.textContent.trim(), button);
logger.info(this.siteName, ` → 第${attempts}${actionName}"${text}"...`);
await button.click();
await this.human.randomDelay(2000, 3000);
// 使用自定义检查函数判断页面是否跳转
const changed = await checkPageChanged();
if (changed) {
pageChanged = true;
logger.success(this.siteName, ` → ✓ 页面已跳转`);
break;
}
logger.warn(this.siteName, ` → 页面未跳转,${attempts}/${maxAttempts}`);
await this.human.randomDelay(2000, 3000);
} else {
logger.warn(this.siteName, ` → 未找到可点击的按钮`);
break;
}
}
if (!pageChanged) {
logger.error(this.siteName, `${maxAttempts}次尝试后页面仍未跳转`);
}
return pageChanged;
}
/**
* 生成账号数据干运行模式
*/
generateData(options = {}) {
logger.info(this.siteName, '生成账号数据...');
const account = this.dataGen.generateAccount({
name: options.name,
email: options.email,
username: options.username,
password: {
strategy: options.passwordStrategy || 'email',
...options.password
},
includePhone: false // Windsurf第一步不需要手机号
});
return account;
}
/**
* 初始化浏览器使用rebrowser-puppeteer自带反检测
*/
async initBrowser() {
// rebrowser-puppeteer 已经打好补丁,无需额外配置
const puppeteer = require('puppeteer');
logger.info(this.siteName, '启动浏览器(反检测模式)...');
// 随机视口大小(模拟不同设备)
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)];
this.browser = await puppeteer.launch({
headless: false, // 非无头模式更难被检测
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-blink-features=AutomationControlled',
'--disable-dev-shm-usage',
`--window-size=${viewport.width},${viewport.height}`
],
ignoreDefaultArgs: ['--enable-automation']
});
this.page = await this.browser.newPage();
// 设置随机视口
await this.page.setViewport(viewport);
// 随机用户代理
const userAgents = [
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36'
];
await this.page.setUserAgent(
userAgents[Math.floor(Math.random() * userAgents.length)]
);
// 设置语言和时区
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})`);
}
/**
* 关闭浏览器
*/
async closeBrowser() {
if (this.browser) {
await this.browser.close();
logger.info(this.siteName, '浏览器已关闭');
}
}
/**
* 步骤1: 填写基本信息First Name, Last Name, Email- 使用人类行为
*/
async step1_fillBasicInfo() {
logger.info(this.siteName, `[步骤 1/${this.getTotalSteps()}] 填写基本信息`);
// 打开注册页面
logger.info(this.siteName, `打开注册页面: ${this.siteUrl}`);
await this.page.goto(this.siteUrl, {
waitUntil: 'networkidle2',
timeout: 30000
});
// 模拟阅读页面1-3秒
await this.human.readPage(1, 3);
// 填写First Name使用人类行为
logger.info(this.siteName, ' → 填写First Name...');
await this.page.waitForSelector('#firstName', { timeout: 10000 });
await this.human.humanType(this.page, '#firstName', this.accountData.firstName);
// 填写Last Name
logger.info(this.siteName, ' → 填写Last Name...');
await this.page.waitForSelector('#lastName', { timeout: 10000 });
await this.human.humanType(this.page, '#lastName', this.accountData.lastName);
// 填写Email
logger.info(this.siteName, ' → 填写Email...');
await this.page.waitForSelector('#email', { timeout: 10000 });
await this.human.humanType(this.page, '#email', this.accountData.email);
// 勾选同意条款(如果有)
try {
const checkbox = await this.page.$('input[type="checkbox"]');
if (checkbox) {
logger.info(this.siteName, ' → 勾选同意条款...');
await this.human.humanCheckbox(this.page, 'input[type="checkbox"]');
}
} catch (error) {
logger.warn(this.siteName, ' → 未找到同意条款checkbox跳过');
}
// 点击Continue按钮并等待跳转到密码页面
logger.info(this.siteName, ' → 点击Continue按钮...');
// 等待按钮激活
await this.page.waitForSelector('button:not([disabled])', { timeout: 10000 });
logger.info(this.siteName, ' → 按钮已激活');
// 使用通用方法处理页面跳转
const checkPageChanged = async () => {
// 检查是否有密码输入框
const hasPasswordField = await this.page.evaluate(() => {
return !!document.querySelector('#password');
});
// 检查URL是否改变
const currentUrl = this.page.url();
const urlChanged = currentUrl !== this.siteUrl;
return hasPasswordField || urlChanged;
};
await this.clickButtonAndWaitForPageChange(checkPageChanged, 5, '点击Continue');
// 额外等待确保页面稳定
await this.human.randomDelay(2000, 3000);
this.currentStep = 1;
logger.success(this.siteName, `步骤 1 完成`);
}
/**
* 步骤2: 设置密码
*/
async step2_setPassword() {
logger.info(this.siteName, `[步骤 2/${this.getTotalSteps()}] 设置密码`);
// 等待密码页面加载
await this.human.readPage(1, 2);
// 填写密码
logger.info(this.siteName, ' → 填写密码...');
await this.page.waitForSelector('#password', { timeout: 10000 });
// 先清空密码框
await this.page.evaluate(() => {
const elem = document.querySelector('#password');
if (elem) elem.value = '';
});
await this.human.humanType(this.page, '#password', this.accountData.password);
// 填写确认密码
logger.info(this.siteName, ' → 填写确认密码...');
await this.page.waitForSelector('#passwordConfirmation', { timeout: 10000 });
// 先清空确认密码框(防止有残留)
await this.page.evaluate(() => {
const elem = document.querySelector('#passwordConfirmation');
if (elem) elem.value = '';
});
await this.human.humanType(this.page, '#passwordConfirmation', this.accountData.password);
// 等待验证通过
logger.info(this.siteName, ' → 等待密码验证...');
await this.human.randomDelay(1000, 2000);
// 移除旧的验证逻辑将在步骤3之前单独处理
// 查找并点击Continue按钮
logger.info(this.siteName, ' → 点击Continue按钮...');
// 等待按钮变为可点击状态不再disabled- 步骤2
try {
await this.page.waitForFunction(
() => {
const button = document.querySelector('button');
if (!button) return false;
const text = button.textContent.trim();
return text === 'Continue' && !button.disabled;
},
{ timeout: 20000 } // 增加超时时间,因为逐字符输入较慢
);
logger.info(this.siteName, ' → 按钮已激活');
// 使用更精确的选择器
const button = await this.page.$('button:not([disabled])');
if (button) {
const text = await this.page.evaluate(el => el.textContent.trim(), button);
if (text === 'Continue') {
// 同时等待导航和点击
await Promise.all([
this.page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 15000 }).catch(() => {}),
this.human.humanClick(this.page, 'button:not([disabled])')
]);
logger.success(this.siteName, ' → 已点击Continue按钮并等待跳转');
}
}
} catch (error) {
logger.warn(this.siteName, ' → 按钮等待超时尝试按Enter键');
await this.human.randomDelay(500, 1000);
await Promise.all([
this.page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 15000 }).catch(() => {}),
this.page.keyboard.press('Enter')
]);
}
// 额外等待确保页面稳定
await this.human.randomDelay(1000, 2000);
this.currentStep = 2;
logger.success(this.siteName, `步骤 2 完成`);
}
/**
* Cloudflare Turnstile验证步骤2.5- 使用通用处理器
*/
async handleCloudflareVerification() {
const cloudflareMode = DEFAULT_CONFIG.cloudflare.mode;
// 自定义检测函数检查Continue按钮是否激活
const customCheck = async () => {
try {
const buttonEnabled = await this.page.evaluate(() => {
const button = document.querySelector('button');
return button && !button.disabled;
});
return buttonEnabled;
} catch (e) {
return false;
}
};
const handler = new CloudflareHandler(this.page, this.human, this.siteName, cloudflareMode, customCheck);
const result = await handler.handle();
// 如果验证通过点击Continue按钮进入下一页
if (result === 'passed') {
logger.info(this.siteName, '[Cloudflare] 点击Continue按钮进入验证码页面...');
try {
let pageChanged = false;
let attempts = 0;
const maxAttempts = 10; // 最多尝试10次
while (!pageChanged && attempts < maxAttempts) {
attempts++;
// 查找并点击Continue按钮
const button = await this.page.$('button:not([disabled])');
if (button) {
logger.info(this.siteName, `[Cloudflare] 第${attempts}次点击Continue...`);
await button.click();
await this.human.randomDelay(2000, 3000);
// 检查是否有错误提示
const hasError = await this.page.evaluate(() => {
const errorMsg = document.querySelector('p.caption1.text-sk-error');
return errorMsg && errorMsg.textContent.includes('An error occurred');
});
if (hasError) {
logger.warn(this.siteName, '[Cloudflare] 检测到错误提示,重新尝试...');
await this.human.randomDelay(2000, 3000);
continue;
}
// 检查页面是否已改变
const checkResult = await this.page.evaluate(() => {
// 方法1: 检查是否有"Check your inbox"文本
const hasCheckInbox = document.body.textContent.includes('Check your inbox');
// 方法2: 检查按钮是否被禁用
const button = document.querySelector('button');
const buttonDisabled = button && button.disabled;
// 方法3: 检查是否还有"verify that you are human"文本
const stillOnVerifyPage = document.body.textContent.includes('verify that you are human');
return {
hasCheckInbox,
buttonDisabled,
stillOnVerifyPage
};
});
logger.info(this.siteName, `[Cloudflare] 页面状态: inbox=${checkResult.hasCheckInbox}, buttonDisabled=${checkResult.buttonDisabled}, stillVerify=${checkResult.stillOnVerifyPage}`);
// 判断是否成功跳转
if (checkResult.hasCheckInbox || (!checkResult.stillOnVerifyPage && checkResult.buttonDisabled)) {
pageChanged = true;
logger.success(this.siteName, '[Cloudflare] ✓ 已进入验证码页面');
break;
}
// 如果还在验证页面,继续等待
logger.info(this.siteName, '[Cloudflare] 页面未跳转,继续尝试...');
await this.human.randomDelay(2000, 3000);
} else {
logger.warn(this.siteName, '[Cloudflare] 未找到可点击的按钮');
break;
}
}
if (!pageChanged) {
logger.warn(this.siteName, `[Cloudflare] ${maxAttempts}次尝试后页面仍未跳转`);
}
// 额外等待确保页面稳定
await this.human.randomDelay(2000, 3000);
} catch (e) {
logger.warn(this.siteName, `[Cloudflare] 点击按钮失败: ${e.message}`);
}
}
return result;
}
/**
* 步骤3: 邮箱验证
*/
async step3_emailVerification() {
logger.info(this.siteName, `[步骤 3/${this.getTotalSteps()}] 邮箱验证`);
// 先处理Cloudflare验证如果有
const verifyResult = await this.handleCloudflareVerification();
if (verifyResult === 'failed' || verifyResult === 'error') {
throw new Error('Cloudflare验证失败无法继续');
}
if (verifyResult === 'manual') {
logger.info(this.siteName, 'Cloudflare需要手动验证已等待完成');
}
try {
// 等待验证码页面加载
await this.human.readPage(1, 2);
// 延迟2秒后再获取验证码让邮件有足够时间到达
logger.info(this.siteName, ' → 延迟2秒等待邮件到达...');
await new Promise(resolve => setTimeout(resolve, 2000));
// 获取验证码(从邮箱)
logger.info(this.siteName, ' → 正在从邮箱获取验证码...');
logger.info(this.siteName, ` → 接收邮箱: ${this.accountData.email}`);
logger.info(this.siteName, ' → 注意:如果长时间无响应,请检查:');
logger.info(this.siteName, ' 1. 邮件是否已发送到邮箱');
logger.info(this.siteName, ' 2. QQ邮箱IMAP配置是否正确');
logger.info(this.siteName, ' 3. 邮件是否被标记为垃圾邮件');
const code = await this.emailService.getVerificationCode(
'windsurf',
this.accountData.email,
120 // 增加到120秒超时
);
logger.success(this.siteName, ` → ✓ 验证码: ${code}`);
// 等待验证码输入框加载
await this.human.randomDelay(2000, 3000);
// Windsurf使用6个独立的输入框需要逐个填写
logger.info(this.siteName, ' → 查找验证码输入框...');
// 等待输入框出现
await this.page.waitForSelector('input[type="text"]', { timeout: 10000 });
// 获取所有文本输入框
const inputs = await this.page.$$('input[type="text"]');
logger.info(this.siteName, ` → 找到 ${inputs.length} 个输入框`);
if (inputs.length >= 6 && code.length === 6) {
// 逐个填写每一位验证码
logger.info(this.siteName, ' → 填写6位验证码...');
for (let i = 0; i < 6; i++) {
const char = code[i].toUpperCase(); // 确保大写
// 点击输入框获取焦点
await inputs[i].click();
await this.human.randomDelay(100, 200);
// 输入字符
await inputs[i].type(char);
await this.human.randomDelay(300, 500);
logger.info(this.siteName, ` → 已输入第 ${i + 1} 位: ${char}`);
}
logger.success(this.siteName, ' → 验证码已填写完成');
// 等待按钮激活(填完验证码后按钮会自动启用)
logger.info(this.siteName, ' → 等待按钮激活...');
await this.human.randomDelay(2000, 3000);
// 等待按钮激活
try {
await this.page.waitForSelector('button:not([disabled])', { timeout: 10000 });
logger.success(this.siteName, ' → 按钮已激活');
} catch (e) {
logger.warn(this.siteName, ` → 按钮等待超时: ${e.message}`);
}
// 点击按钮并等待页面跳转
const checkPageChanged = async () => {
// 检查是否离开了验证码页面URL改变或页面元素改变
const currentUrl = this.page.url();
return !currentUrl.includes('/register') ||
await this.page.$('input[type="text"]').then(el => !el).catch(() => true);
};
await this.clickButtonAndWaitForPageChange(checkPageChanged, 3, '点击Create account');
this.currentStep = 3;
logger.success(this.siteName, `步骤 3 完成`);
} else {
logger.error(this.siteName, ' → 未找到验证码输入框!');
logger.warn(this.siteName, ' → 请手动输入验证码: ' + code);
// 等待用户手动输入
await this.human.randomDelay(30000, 30000);
this.currentStep = 3;
}
} catch (error) {
logger.error(this.siteName, `邮箱验证失败: ${error.message}`);
throw error;
}
}
/**
* 步骤4: 跳过问卷调查
*/
async step4_skipSurvey() {
logger.info(this.siteName, `[步骤 4/${this.getTotalSteps()}] 跳过问卷`);
try {
// 等待页面加载
await this.human.readPage(2, 3);
// 查找并点击"Skip this step"按钮
logger.info(this.siteName, ' → 查找"Skip this step"按钮...');
// 方式1: 通过button文本查找
const buttons = await this.page.$$('button');
let skipButton = null;
for (const button of buttons) {
const text = await this.page.evaluate(el => el.textContent?.trim(), button);
if (text && text.toLowerCase().includes('skip')) {
skipButton = button;
logger.info(this.siteName, ` → 找到按钮: "${text}"`);
break;
}
}
if (skipButton) {
logger.info(this.siteName, ' → 点击"Skip this step"按钮...');
await skipButton.click();
// 等待页面跳转
await this.human.randomDelay(2000, 3000);
this.currentStep = 4;
logger.success(this.siteName, `步骤 4 完成`);
} else {
logger.warn(this.siteName, ' → 未找到Skip按钮可能已跳过此页面');
this.currentStep = 4;
}
} catch (error) {
logger.error(this.siteName, `跳过问卷失败: ${error.message}`);
throw error;
}
}
/**
* 步骤5: 选择计划
*/
async step5_selectPlan() {
logger.info(this.siteName, `[步骤 5/${this.getTotalSteps()}] 选择计划`);
try {
// 等待页面加载
await this.human.readPage(2, 3);
// 查找并点击"Select plan"按钮
logger.info(this.siteName, ' → 查找"Select plan"按钮...');
// 方式1: 通过按钮文本查找
const buttons = await this.page.$$('button');
let selectButton = null;
for (const button of buttons) {
const text = await this.page.evaluate(el => el.textContent?.trim(), button);
if (text && text.toLowerCase().includes('select plan')) {
selectButton = button;
logger.info(this.siteName, ` → 找到按钮: "${text}"`);
break;
}
}
if (selectButton) {
logger.info(this.siteName, ' → 点击"Select plan"按钮...');
// 点击后会跳转到 Stripe checkout 页面,需要等待导航完成
logger.info(this.siteName, ' → 等待跳转到支付页面...');
await Promise.all([
this.page.waitForNavigation({
waitUntil: 'networkidle2',
timeout: 30000
}).catch(e => {
logger.warn(this.siteName, ` → 导航等待异常: ${e.message}`);
}),
selectButton.click()
]);
// 额外等待页面稳定
await this.human.randomDelay(2000, 3000);
const currentUrl = this.page.url();
logger.info(this.siteName, ` → 当前URL: ${currentUrl}`);
if (currentUrl.includes('checkout.stripe.com')) {
logger.success(this.siteName, ' → ✓ 已跳转到 Stripe 支付页面');
}
this.currentStep = 5;
logger.success(this.siteName, `步骤 5 完成`);
} else {
logger.warn(this.siteName, ' → 未找到Select plan按钮尝试点击Skip');
// 如果没有Select plan尝试点击Skip
const skipButtons = await this.page.$$('button');
for (const btn of skipButtons) {
const text = await this.page.evaluate(el => el.textContent?.trim(), btn);
if (text && text.toLowerCase().includes('skip')) {
await btn.click();
logger.info(this.siteName, ' → 已点击Skip按钮');
break;
}
}
await this.human.randomDelay(2000, 3000);
this.currentStep = 5;
}
} catch (error) {
logger.error(this.siteName, `选择计划失败: ${error.message}`);
throw error;
}
}
/**
* 步骤6: 填写支付信息
*/
async step6_fillPayment() {
logger.info(this.siteName, `[步骤 6/${this.getTotalSteps()}] 填写支付信息`);
try {
// 等待页面加载
await this.human.readPage(3, 5);
// 1. 生成信用卡信息(使用银联卡)
logger.info(this.siteName, ' → 生成银联卡信息...');
const cardGen = new CardGenerator();
const card = cardGen.generate('unionpay');
logger.info(this.siteName, ` → 卡号: ${card.number}`);
logger.info(this.siteName, ` → 有效期: ${card.month}/${card.year}`);
logger.info(this.siteName, ` → CVV: ${card.cvv}`);
// 2. 点击选择"银行卡"支付方式
logger.info(this.siteName, ' → 选择银行卡支付方式...');
const cardRadio = await this.page.$('input[type="radio"][value="card"]');
if (cardRadio) {
await cardRadio.click();
logger.success(this.siteName, ' → ✓ 已点击银行卡选项');
// 等待支付表单完全加载(等待骨架屏消失,表单元素出现)
logger.info(this.siteName, ' → 等待支付表单加载...');
await this.human.randomDelay(3000, 5000);
// 等待所有必需的支付字段都加载完成
try {
await this.page.waitForFunction(
() => {
const cardNumber = document.querySelector('#cardNumber');
const cardExpiry = document.querySelector('#cardExpiry');
const cardCvc = document.querySelector('#cardCvc');
const billingName = document.querySelector('#billingName');
// 检查所有字段都存在且可见
return cardNumber && cardExpiry && cardCvc && billingName;
},
{ timeout: 30000 }
);
logger.success(this.siteName, ' → ✓ 支付表单加载完成');
} catch (e) {
logger.warn(this.siteName, ` → 等待表单超时: ${e.message}`);
}
}
// 3. 填写卡号
logger.info(this.siteName, ' → 填写卡号...');
await this.page.waitForSelector('#cardNumber', { visible: true, timeout: 10000 });
await this.page.click('#cardNumber');
await this.human.randomDelay(500, 1000);
await this.page.type('#cardNumber', card.number, { delay: 250 });
// 4. 填写有效期(月份/年份)
logger.info(this.siteName, ' → 填写有效期...');
await this.page.click('#cardExpiry');
await this.human.randomDelay(300, 500);
const expiry = `${card.month}${card.year}`; // 格式: MMYY
await this.page.type('#cardExpiry', expiry, { delay: 250 });
// 5. 填写CVC
logger.info(this.siteName, ' → 填写CVC...');
await this.page.click('#cardCvc');
await this.human.randomDelay(300, 500);
await this.page.type('#cardCvc', card.cvv, { delay: 250 });
// 6. 填写持卡人姓名
logger.info(this.siteName, ' → 填写持卡人姓名...');
await this.page.click('#billingName');
await this.human.randomDelay(300, 500);
const fullName = `${this.accountData.firstName} ${this.accountData.lastName}`;
await this.page.type('#billingName', fullName, { delay: 200 });
// 7. 选择地址:中国澳门特别行政区
logger.info(this.siteName, ' → 选择地址:中国澳门特别行政区...');
await this.page.select('#billingCountry', 'MO');
await this.human.randomDelay(1000, 2000);
// 8. 填写地址信息(如果需要)
// 等待地址字段加载
await this.human.randomDelay(1000, 2000);
// 检查是否需要填写地址行1和行2
const addressFields = await this.page.$$('input[placeholder*="地址"]');
if (addressFields.length > 0) {
logger.info(this.siteName, ' → 填写地址信息...');
// 填写简单的地址
await addressFields[0].type('Macau', { delay: 100 });
if (addressFields[1]) {
await this.human.randomDelay(300, 500);
await addressFields[1].type('Macao', { delay: 100 });
}
}
// 9. 点击订阅按钮
logger.info(this.siteName, ' → 点击订阅按钮...');
await this.human.randomDelay(2000, 3000);
const submitButton = await this.page.$('button[type="submit"][data-testid="hosted-payment-submit-button"]');
if (submitButton) {
await submitButton.click();
logger.success(this.siteName, ' → ✓ 已点击订阅按钮');
// 等待处理
await this.human.randomDelay(5000, 7000);
this.currentStep = 6;
logger.success(this.siteName, `步骤 6 完成`);
} else {
logger.warn(this.siteName, ' → 未找到订阅按钮');
this.currentStep = 6;
}
} catch (error) {
logger.error(this.siteName, `填写支付信息失败: ${error.message}`);
throw error;
}
}
/**
* 执行注册流程
* @param {Object} options - 选项
* @param {number} options.fromStep - 从第几步开始默认1
* @param {number} options.toStep - 执行到第几步默认全部
*/
async register(options = {}) {
const fromStep = options.fromStep || 1;
const toStep = options.toStep || this.getTotalSteps();
try {
// 1. 生成数据
this.accountData = this.generateData(options);
logger.success(this.siteName, '账号数据生成完成');
logger.info(this.siteName, `First Name: ${this.accountData.firstName}`);
logger.info(this.siteName, `Last Name: ${this.accountData.lastName}`);
logger.info(this.siteName, `Email: ${this.accountData.email}`);
logger.info(this.siteName, `Password: ${this.accountData.password}`);
// 2. 初始化浏览器
await this.initBrowser();
// 3. 执行指定范围的步骤
logger.info(this.siteName, `\n开始执行步骤 ${fromStep}${toStep} (共 ${this.getTotalSteps()} 步)\n`);
for (let i = fromStep; i <= toStep && i <= this.getTotalSteps(); i++) {
const step = this.steps[i - 1];
if (typeof this[step.method] === 'function') {
try {
await this[step.method]();
} catch (error) {
logger.error(this.siteName, `步骤 ${i} 执行失败: ${error.message}`);
throw error;
}
} else {
logger.warn(this.siteName, `步骤 ${i} (${step.name}) 未实现`);
break;
}
}
const stepInfo = this.getCurrentStepInfo();
logger.success(this.siteName, `\n已完成步骤 ${fromStep}${this.currentStep}`);
if (this.currentStep < this.getTotalSteps()) {
logger.info(this.siteName, `剩余步骤需要手动完成或等待后续开发`);
}
// 不自动关闭浏览器,让用户查看结果
if (!options.keepBrowserOpen) {
logger.info(this.siteName, '10秒后关闭浏览器...');
await this.page.waitForTimeout(10000);
await this.closeBrowser();
} else {
logger.info(this.siteName, '浏览器保持打开状态');
}
return {
success: true,
account: this.accountData,
completedSteps: this.currentStep,
totalSteps: this.getTotalSteps(),
message: `完成 ${this.currentStep}/${this.getTotalSteps()}`
};
} catch (error) {
logger.error(this.siteName, `注册失败: ${error.message}`);
// 截图保存错误状态
if (this.page) {
try {
const screenshotPath = `/tmp/windsurf-error-${Date.now()}.png`;
await this.page.screenshot({ path: screenshotPath });
logger.info(this.siteName, `错误截图已保存: ${screenshotPath}`);
} catch (e) {
// 忽略截图错误
}
}
await this.closeBrowser();
throw error;
}
}
}
module.exports = WindsurfRegister;

View File

@ -0,0 +1,229 @@
/**
* Cloudflare Turnstile 验证处理器
* 通用的Cloudflare人机验证处理支持全自动和半自动模式
*/
const logger = require('../../../shared/logger');
class CloudflareHandler {
/**
* @param {Page} page - Puppeteer page对象
* @param {HumanBehavior} human - 人类行为模拟对象
* @param {string} siteName - 网站名称用于日志
* @param {string} mode - 验证模式: 'auto' 'manual' (默认 'manual')
* @param {Function} customCheck - 自定义检测函数返回Promise<boolean>检测验证是否完成
*/
constructor(page, human, siteName, mode = 'manual', customCheck = null) {
this.page = page;
this.human = human;
this.siteName = siteName;
this.mode = mode; // 'auto' 或 'manual'
this.customCheck = customCheck; // 自定义检测函数
}
/**
* 处理Cloudflare Turnstile验证
* @returns {Promise<string>} 'passed' | 'failed' | 'manual'
*/
async handle() {
logger.info(this.siteName, '[Cloudflare] 检查人机验证...');
await this.human.randomDelay(2000, 3000);
// 检查页面内容,判断是否有验证
const pageText = await this.page.evaluate(() => document.body.textContent || '');
if (!pageText.includes('verify') && !pageText.includes('human')) {
logger.success(this.siteName, '[Cloudflare] 无验证,自动通过');
return 'passed';
}
logger.info(this.siteName, '[Cloudflare] 发现验证页面');
// 情况1等待自动验证某些情况下会自动通过
logger.info(this.siteName, '[Cloudflare] 等待自动验证5秒...');
await this.human.randomDelay(5000, 6000);
const afterWait = await this.page.evaluate(() => document.body.textContent || '');
if (!afterWait.includes('verify') && !afterWait.includes('human')) {
logger.success(this.siteName, '[Cloudflare] 自动验证通过');
return 'passed';
}
// 情况2需要点击验证框
if (this.mode === 'auto') {
return await this.autoHandle();
} else {
return await this.manualHandle();
}
}
/**
* 自动处理模式实验性
*/
async autoHandle() {
logger.info(this.siteName, '[Cloudflare] 自动模式:尝试点击验证框...');
try {
// 查找Turnstile iframe
const frames = await this.page.frames();
let turnstileFrame = null;
for (const frame of frames) {
const url = frame.url();
if (url.includes('challenges.cloudflare.com') || url.includes('turnstile')) {
turnstileFrame = frame;
logger.success(this.siteName, `[Cloudflare] 找到iframe: ${url.substring(0, 80)}...`);
break;
}
}
if (!turnstileFrame) {
logger.warn(this.siteName, '[Cloudflare] 未找到Turnstile iframe');
return 'failed';
}
// 等待iframe加载
await turnstileFrame.waitForSelector('body', { timeout: 5000 });
await this.human.randomDelay(8000, 8000);
// 查找并点击checkbox
const checkboxElement = await turnstileFrame.$('label.cb-lb');
if (!checkboxElement) {
logger.error(this.siteName, '[Cloudflare] 未找到验证框');
return 'failed';
}
// 使用真实鼠标点击
const box = await checkboxElement.boundingBox();
if (box) {
const clickX = box.x + box.width / 2;
const clickY = box.y + box.height / 2;
await this.page.mouse.move(clickX - 50, clickY - 50);
await this.human.randomDelay(100, 300);
await this.page.mouse.move(clickX, clickY, { steps: 10 });
await this.human.randomDelay(200, 400);
await this.page.mouse.click(clickX, clickY);
logger.success(this.siteName, '[Cloudflare] 已点击验证框');
}
// 等待验证处理
await this.human.randomDelay(8000, 12000);
// 检查是否通过
const finalCheck = await this.page.evaluate(() => document.body.textContent || '');
if (!finalCheck.includes('verify') && !finalCheck.includes('human')) {
logger.success(this.siteName, '[Cloudflare] ✓ 验证通过');
return 'passed';
}
return 'failed';
} catch (error) {
logger.error(this.siteName, `[Cloudflare] 自动处理失败: ${error.message}`);
return 'failed';
}
}
/**
* 半自动处理模式推荐
*/
async manualHandle() {
logger.info(this.siteName, '[Cloudflare] 半自动模式:等待用户手动点击...');
try {
// 查找Turnstile iframe
const frames = await this.page.frames();
let turnstileFrame = null;
for (const frame of frames) {
const url = frame.url();
if (url.includes('challenges.cloudflare.com') || url.includes('turnstile')) {
turnstileFrame = frame;
break;
}
}
if (turnstileFrame) {
await turnstileFrame.waitForSelector('body', { timeout: 5000 });
}
// 显示提示
logger.warn(this.siteName, '='.repeat(60));
logger.warn(this.siteName, '[Cloudflare] 🖱️ 请手动点击验证框!');
logger.warn(this.siteName, '[Cloudflare] 程序将自动检测验证完成超时10分钟...');
logger.warn(this.siteName, '='.repeat(60));
// 轮询检查验证是否完成最多10分钟
let verified = false;
let attempts = 0;
const maxAttempts = 600; // 10分钟 = 600秒
while (!verified && attempts < maxAttempts) {
attempts++;
await this.human.randomDelay(1000, 1000);
// 方法1: 检查页面内容
const pageText = await this.page.evaluate(() => document.body.textContent || '');
const hasVerifyText = pageText.includes('verify') || pageText.includes('human');
// 方法2: 检查URL是否改变
const currentUrl = this.page.url();
const urlChanged = !currentUrl.includes('register');
// 方法3: 检查iframe内成功标志
let iframeSuccess = false;
if (turnstileFrame) {
try {
iframeSuccess = await turnstileFrame.evaluate(() => {
const successDiv = document.querySelector('#success');
return successDiv && successDiv.style.display !== 'none';
});
} catch (e) {
// iframe可能已消失
}
}
// 方法4: 自定义检测(由网站自己定义)
let customCheckPassed = false;
if (this.customCheck) {
try {
customCheckPassed = await this.customCheck();
} catch (e) {
// 忽略错误
}
}
// 每15秒输出一次状态
if (attempts % 15 === 0) {
const minutes = Math.floor(attempts / 60);
const seconds = attempts % 60;
logger.info(this.siteName, `[Cloudflare] 等待中... (${minutes}${seconds}秒/${Math.floor(maxAttempts/60)}分钟) - custom=${customCheckPassed}, iframe=${iframeSuccess}`);
}
// 判断验证完成
if (customCheckPassed || iframeSuccess || (!hasVerifyText && attempts > 3) || urlChanged) {
verified = true;
logger.success(this.siteName, '[Cloudflare] ✓✓✓ 检测到验证完成!');
break;
}
}
if (!verified) {
logger.error(this.siteName, '[Cloudflare] ✗ 10分钟超时');
return 'failed';
}
// 验证完成后等待页面稳定
await this.human.randomDelay(2000, 3000);
return 'passed';
} catch (error) {
logger.error(this.siteName, `[Cloudflare] 半自动处理失败: ${error.message}`);
return 'failed';
}
}
}
module.exports = CloudflareHandler;

View File

@ -0,0 +1,219 @@
/**
* Human Behavior - 模拟人类行为
* 用于使自动化操作更像真实用户
*/
const { randomInt } = require('../../../shared/utils');
class HumanBehavior {
/**
* 人类般的输入
* @param {Page} page - Puppeteer page对象
* @param {string} selector - 选择器
* @param {string} text - 要输入的文本
*/
async humanType(page, selector, text) {
await page.waitForSelector(selector);
// 点击输入框获得焦点
await page.click(selector);
await this.randomDelay(200, 400);
// 清空现有内容
await page.evaluate((sel) => {
const element = document.querySelector(sel);
if (element) element.value = '';
}, selector);
// 逐字符输入 - 使用page.type
await page.type(selector, text, {
delay: this.randomInt(100, 180) // 每个字符的延迟
});
// 每隔几个字符停顿一下
await this.randomDelay(300, 600);
// 验证输入是否正确
const actualValue = await page.evaluate((sel) => {
const element = document.querySelector(sel);
return element ? element.value : '';
}, selector);
if (actualValue !== text) {
console.warn(`⚠️ 输入验证失败: 期望 "${text}", 实际 "${actualValue}"`);
}
// 触发blur事件触发表单验证
await page.evaluate((sel) => {
const element = document.querySelector(sel);
if (element) {
element.blur();
}
}, selector);
// 等待表单验证完成
await this.randomDelay(500, 800);
}
/**
* 人类般的点击
* @param {Page} page - Puppeteer page对象
* @param {string} selector - 选择器
*/
async humanClick(page, selector) {
const element = await page.$(selector);
if (!element) {
throw new Error(`Element not found: ${selector}`);
}
const box = await element.boundingBox();
if (!box) {
// 如果元素不可见,直接点击
await element.click();
return;
}
// 计算随机点击位置(在元素范围内)
const x = box.x + box.width / 2 + this.randomInt(-box.width * 0.3, box.width * 0.3);
const y = box.y + box.height / 2 + this.randomInt(-box.height * 0.3, box.height * 0.3);
// 先移动鼠标到附近(不是直接到目标)
const steps = this.randomInt(10, 20);
await page.mouse.move(
x + this.randomInt(-50, 50),
y + this.randomInt(-50, 50),
{ steps: Math.floor(steps / 2) }
);
await this.randomDelay(50, 150);
// 移动到目标位置
await page.mouse.move(x, y, { steps: Math.floor(steps / 2) });
// 短暂停顿
await this.randomDelay(50, 200);
// 点击(按下和释放之间有随机延迟)
await page.mouse.down();
await this.randomDelay(50, 120);
await page.mouse.up();
// 点击后短暂停顿
await this.randomDelay(100, 300);
}
/**
* 随机延迟
* @param {number} min - 最小毫秒
* @param {number} max - 最大毫秒
*/
async randomDelay(min, max) {
const delay = this.randomInt(min, max);
await new Promise(resolve => setTimeout(resolve, delay));
}
/**
* 随机整数
* @param {number} min - 最小值
* @param {number} max - 最大值
* @returns {number}
*/
randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/**
* 人类般的滚动
* @param {Page} page - Puppeteer page对象
* @param {string} direction - 方向 'down' 'up'
*/
async humanScroll(page, direction = 'down') {
const distance = this.randomInt(100, 300);
const steps = this.randomInt(3, 8);
for (let i = 0; i < steps; i++) {
await page.evaluate((dir, dist) => {
window.scrollBy(0, dir === 'down' ? dist : -dist);
}, direction, distance / steps);
await this.randomDelay(100, 300);
}
}
/**
* 模拟鼠标随机移动增加真实性
* @param {Page} page - Puppeteer page对象
*/
async randomMouseMovement(page) {
const viewport = await page.viewport();
const x = this.randomInt(100, viewport.width - 100);
const y = this.randomInt(100, viewport.height - 100);
await page.mouse.move(x, y, { steps: this.randomInt(10, 30) });
}
/**
* 模拟阅读页面随机停顿
* @param {number} minSeconds - 最小秒数
* @param {number} maxSeconds - 最大秒数
*/
async readPage(minSeconds = 2, maxSeconds = 5) {
const seconds = this.randomInt(minSeconds, maxSeconds);
await this.randomDelay(seconds * 1000, (seconds + 1) * 1000);
}
/**
* 检查并勾选checkbox人类方式
* @param {Page} page - Puppeteer page对象
* @param {string} selector - 选择器
*/
async humanCheckbox(page, selector) {
await page.waitForSelector(selector);
// 移动到checkbox附近
const element = await page.$(selector);
const box = await element.boundingBox();
if (box) {
await page.mouse.move(
box.x + box.width / 2,
box.y + box.height / 2,
{ steps: this.randomInt(5, 15) }
);
await this.randomDelay(200, 500);
}
// 点击
await element.click();
await this.randomDelay(300, 700);
}
/**
* 模拟查找元素视觉搜索行为
* @param {Page} page - Puppeteer page对象
* @param {string} selector - 目标元素选择器
*/
async visualSearch(page, selector) {
// 先随机移动几次鼠标(模拟眼睛在找)
for (let i = 0; i < this.randomInt(2, 4); i++) {
await this.randomMouseMovement(page);
await this.randomDelay(300, 800);
}
// 找到目标元素
await page.waitForSelector(selector);
// 移动到目标
const element = await page.$(selector);
const box = await element.boundingBox();
if (box) {
await page.mouse.move(
box.x + box.width / 2,
box.y + box.height / 2,
{ steps: this.randomInt(15, 25) }
);
}
}
}
module.exports = HumanBehavior;

View File

@ -9,7 +9,7 @@ const CARD_TYPES = {
prefix: '622836754', prefix: '622836754',
length: 16, length: 16,
cvvLength: 3, cvvLength: 3,
useLuhn: false // 可选是否使用Luhn算法 useLuhn: true // 使用Luhn算法校验
}, },
visa: { visa: {
name: 'Visa', name: 'Visa',