Compare commits
No commits in common. "develop" and "master" have entirely different histories.
155
QUICKSTART.md
155
QUICKSTART.md
@ -1,155 +0,0 @@
|
|||||||
# 快速开始指南
|
|
||||||
|
|
||||||
## 🚀 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格式**:美化显示
|
|
||||||
- 适合:人工阅读、展示
|
|
||||||
- 多行格式化输出
|
|
||||||
12
README.md
12
README.md
@ -11,7 +11,6 @@
|
|||||||
- 🔧 **高度可配置** - 支持多种输出格式和自定义选项
|
- 🔧 **高度可配置** - 支持多种输出格式和自定义选项
|
||||||
- 📦 **轻量级** - 最小依赖,快速安装
|
- 📦 **轻量级** - 最小依赖,快速安装
|
||||||
- 🌍 **跨平台** - 支持 macOS、Linux、Windows
|
- 🌍 **跨平台** - 支持 macOS、Linux、Windows
|
||||||
- 🛡️ **反检测技术** - 使用最新的 rebrowser-puppeteer,降低被识别风险
|
|
||||||
|
|
||||||
## 📦 安装
|
## 📦 安装
|
||||||
|
|
||||||
@ -239,15 +238,12 @@ node src/index.js card -n 5
|
|||||||
|
|
||||||
## 📋 待办事项
|
## 📋 待办事项
|
||||||
|
|
||||||
- [x] 信用卡生成器(支持Luhn算法)
|
- [ ] 添加更多工具(邮箱生成器、用户名生成器等)
|
||||||
- [x] 账号注册工具(支持步骤化流程)
|
|
||||||
- [x] 反检测技术(rebrowser-puppeteer)
|
|
||||||
- [x] 人类行为模拟
|
|
||||||
- [ ] 添加更多网站注册脚本
|
|
||||||
- [ ] 添加单元测试
|
- [ ] 添加单元测试
|
||||||
- [ ] 验证码识别集成
|
- [ ] 添加配置文件支持
|
||||||
- [ ] 代理池管理
|
|
||||||
- [ ] 发布到 npm
|
- [ ] 发布到 npm
|
||||||
|
- [ ] 添加交互式模式
|
||||||
|
- [ ] 支持自定义卡号规则
|
||||||
|
|
||||||
## 🤝 贡献
|
## 🤝 贡献
|
||||||
|
|
||||||
|
|||||||
@ -1,259 +0,0 @@
|
|||||||
# 账号注册工具 (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)
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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}`);
|
|
||||||
});
|
|
||||||
@ -21,10 +21,7 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^11.0.0",
|
"commander": "^11.0.0"
|
||||||
"puppeteer-real-browser": "^1.3.12",
|
|
||||||
"imap": "^0.8.19",
|
|
||||||
"mailparser": "^3.6.5"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
|
|||||||
55
src/cli.js
55
src/cli.js
@ -44,51 +44,6 @@ 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')
|
||||||
@ -107,19 +62,11 @@ 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('');
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,170 +0,0 @@
|
|||||||
# 反检测技术说明
|
|
||||||
|
|
||||||
## 当前使用的技术
|
|
||||||
|
|
||||||
### 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/)
|
|
||||||
|
|
||||||
## 免责声明
|
|
||||||
|
|
||||||
本工具仅用于:
|
|
||||||
- 合法的自动化测试
|
|
||||||
- 个人账号管理
|
|
||||||
- 教育和研究目的
|
|
||||||
|
|
||||||
请遵守各网站的服务条款和适用法律。
|
|
||||||
滥用自动化工具可能导致账号封禁或法律后果。
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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
|
|
||||||
};
|
|
||||||
@ -1,232 +0,0 @@
|
|||||||
/**
|
|
||||||
* IMAP Connector - IMAP邮箱连接器
|
|
||||||
* 支持QQ邮箱、Gmail、Outlook等支持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;
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 // 每次获取的邮件数量
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,211 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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} 封未读邮件`);
|
|
||||||
|
|
||||||
// 打印最近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));
|
|
||||||
|
|
||||||
// 查找匹配的邮件并提取验证码
|
|
||||||
for (const email of emails) {
|
|
||||||
if (isResolved) return;
|
|
||||||
|
|
||||||
logger.info('EmailVerification', `检查邮件: 发件人="${email.from}", 主题="${email.subject}", 收件人="${email.to}", 时间="${email.date}"`);
|
|
||||||
|
|
||||||
// 收件人匹配:从收件人字段提取邮箱地址
|
|
||||||
// 格式可能是: "Name<email@example.com>" 或 "email@example.com"
|
|
||||||
const extractEmail = (str) => {
|
|
||||||
if (!str) return '';
|
|
||||||
const match = str.match(/<(.+?)>/) || str.match(/([^\s<>]+@[^\s<>]+)/);
|
|
||||||
return match ? match[1].toLowerCase() : str.toLowerCase();
|
|
||||||
};
|
|
||||||
|
|
||||||
const emailTo = extractEmail(email.to);
|
|
||||||
const targetEmail = recipientEmail.toLowerCase();
|
|
||||||
|
|
||||||
if (!emailTo.includes(targetEmail)) {
|
|
||||||
logger.info('EmailVerification', ` ✗ 跳过:收件人不匹配(期望:${targetEmail},实际:${emailTo})`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.success('EmailVerification', ` ✓ 收件人匹配!`);
|
|
||||||
|
|
||||||
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;
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
@ -1,124 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
@ -1,136 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
@ -1,139 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
@ -1,145 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
@ -1,147 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
@ -1,118 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
@ -1,171 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,229 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
@ -1,219 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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;
|
|
||||||
@ -9,7 +9,7 @@ const CARD_TYPES = {
|
|||||||
prefix: '622836754',
|
prefix: '622836754',
|
||||||
length: 16,
|
length: 16,
|
||||||
cvvLength: 3,
|
cvvLength: 3,
|
||||||
useLuhn: true // 使用Luhn算法校验
|
useLuhn: false // 可选:是否使用Luhn算法
|
||||||
},
|
},
|
||||||
visa: {
|
visa: {
|
||||||
name: 'Visa',
|
name: 'Visa',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user