455 lines
17 KiB
Python
455 lines
17 KiB
Python
import requests
|
||
import json
|
||
import time
|
||
import random
|
||
import string
|
||
import hashlib
|
||
from typing import Optional, Dict, Any
|
||
from dotenv import load_dotenv
|
||
import os
|
||
from captcha_solver import TwoCaptchaSolver
|
||
from tracker import DataTracker
|
||
from email_manager import EmailManager, AutoEmailVerification
|
||
from colorama import init, Fore, Style
|
||
|
||
# 初始化 colorama
|
||
init(autoreset=True)
|
||
|
||
class DuoPlusRegister:
|
||
"""DuoPlus 协议注册类"""
|
||
|
||
def __init__(self, captcha_api_key: str):
|
||
self.session = requests.Session()
|
||
self.captcha_solver = TwoCaptchaSolver(captcha_api_key)
|
||
self.tracker = DataTracker() # 初始化数据追踪器
|
||
self.base_url = "https://my.duoplus.cn"
|
||
self.api_url = "https://api.duoplus.cn"
|
||
self.captcha_app_id = None
|
||
|
||
# 生成设备指纹
|
||
self.device_fp = self._generate_device_fp()
|
||
|
||
# 设置请求头
|
||
self.session.headers.update({
|
||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36',
|
||
'Accept': 'application/json, text/plain, */*',
|
||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
||
'Accept-Encoding': 'gzip, deflate, br',
|
||
'Origin': 'https://my.duoplus.cn',
|
||
'Referer': 'https://my.duoplus.cn/',
|
||
'Content-Type': 'application/json',
|
||
'sec-ch-ua': '"Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138"',
|
||
'sec-ch-ua-mobile': '?0',
|
||
'sec-ch-ua-platform': '"Windows"',
|
||
'sec-fetch-site': 'same-site',
|
||
'sec-fetch-mode': 'cors',
|
||
'sec-fetch-dest': 'empty',
|
||
'pragma': 'no-cache',
|
||
'cache-control': 'no-cache',
|
||
'lang': 'zh-CN',
|
||
'duoplus-fp': self.device_fp,
|
||
'priority': 'u=1, i'
|
||
})
|
||
|
||
# 获取初始 cookies 和配置
|
||
self._init_session()
|
||
|
||
def _generate_device_fp(self) -> str:
|
||
"""生成设备指纹"""
|
||
# 简单的设备指纹生成,实际可能需要更复杂的算法
|
||
random_str = ''.join(random.choices(string.ascii_lowercase + string.digits, k=16))
|
||
return hashlib.md5(random_str.encode()).hexdigest()
|
||
|
||
def _init_session(self):
|
||
"""初始化会话,获取必要的 cookies 和配置"""
|
||
try:
|
||
# 先访问注册页面获取 cookies
|
||
response = self.session.get(f"{self.base_url}/sign-up")
|
||
print(f"{Fore.GREEN}[INFO] 初始化会话成功")
|
||
|
||
# 发送数据追踪请求(模拟真实浏览器行为)
|
||
self.tracker.track_registration_page()
|
||
|
||
# 获取腾讯验证码配置
|
||
self._get_captcha_config()
|
||
|
||
except Exception as e:
|
||
print(f"{Fore.RED}[ERROR] 初始化会话失败: {str(e)}")
|
||
|
||
def _get_captcha_config(self) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
获取腾讯验证码配置信息
|
||
|
||
Returns:
|
||
验证码配置信息
|
||
"""
|
||
try:
|
||
# 调用 API 获取验证码配置
|
||
config_url = f"{self.api_url}/common/tencentConfig"
|
||
|
||
# 更新请求头中的 referer
|
||
headers = self.session.headers.copy()
|
||
headers['referer'] = 'https://my.duoplus.cn/'
|
||
headers['authorization'] = '' # 注册时可能不需要 authorization
|
||
|
||
response = self.session.get(config_url, headers=headers)
|
||
|
||
if response.status_code == 200:
|
||
data = response.json()
|
||
if data.get('code') == 200:
|
||
self.captcha_app_id = data['data']['captcha_app_id']
|
||
print(f"{Fore.GREEN}[INFO] 获取验证码配置成功,app_id: {self.captcha_app_id}")
|
||
return {
|
||
"app_id": self.captcha_app_id,
|
||
"page_url": f"{self.base_url}/sign-up"
|
||
}
|
||
else:
|
||
print(f"{Fore.RED}[ERROR] 获取验证码配置失败: {data.get('message')}")
|
||
else:
|
||
print(f"{Fore.RED}[ERROR] 请求验证码配置失败: HTTP {response.status_code}")
|
||
|
||
except Exception as e:
|
||
print(f"{Fore.RED}[ERROR] 获取验证码配置异常: {str(e)}")
|
||
|
||
# 如果获取失败,返回默认值
|
||
return {
|
||
"app_id": "192789496", # 使用实际的 app_id 作为备份
|
||
"page_url": f"{self.base_url}/sign-up"
|
||
}
|
||
|
||
def send_verification_code(self, email: str) -> bool:
|
||
"""
|
||
发送验证码
|
||
|
||
Args:
|
||
email: 邮箱地址
|
||
|
||
Returns:
|
||
是否成功发送
|
||
"""
|
||
print(f"{Fore.CYAN}[INFO] 准备发送验证码到: {email}")
|
||
|
||
# 获取验证码配置
|
||
captcha_config = self._get_captcha_config()
|
||
if not captcha_config:
|
||
print(f"{Fore.RED}[ERROR] 无法获取验证码配置")
|
||
return False
|
||
|
||
# 使用 2captcha 解决验证码
|
||
print(f"{Fore.YELLOW}[INFO] 正在处理腾讯滑块验证码...")
|
||
captcha_result = self.captcha_solver.solve_tencent_captcha(
|
||
app_id=captcha_config["app_id"],
|
||
page_url=captcha_config["page_url"]
|
||
)
|
||
|
||
if not captcha_result:
|
||
print(f"{Fore.RED}[ERROR] 验证码处理失败")
|
||
return False
|
||
|
||
# 发送验证码请求 - 使用正确的 API 端点
|
||
send_code_url = f"{self.api_url}/email/verificationCode"
|
||
|
||
# 正确的请求体格式
|
||
payload = {
|
||
"type": 1, # 1 表示注册
|
||
"email": email,
|
||
"rand_str": captcha_result["randstr"],
|
||
"ticket": captcha_result["ticket"]
|
||
}
|
||
|
||
# 更新请求头
|
||
headers = self.session.headers.copy()
|
||
headers.update({
|
||
'referer': 'https://my.duoplus.cn/',
|
||
'authorization': '',
|
||
'accept': 'application/json'
|
||
})
|
||
|
||
try:
|
||
print(f"{Fore.YELLOW}[INFO] 发送请求到: {send_code_url}")
|
||
print(f"{Fore.YELLOW}[DEBUG] 请求体: {json.dumps(payload, ensure_ascii=False)}")
|
||
|
||
response = self.session.post(send_code_url, json=payload, headers=headers)
|
||
result = response.json()
|
||
|
||
if response.status_code == 200 and result.get("code") == 200:
|
||
print(f"{Fore.GREEN}[SUCCESS] 验证码发送成功: {result.get('message', 'Success')}")
|
||
return True
|
||
else:
|
||
print(f"{Fore.RED}[ERROR] 发送验证码失败: {result.get('message', 'Unknown error')}")
|
||
return False
|
||
|
||
except Exception as e:
|
||
print(f"{Fore.RED}[ERROR] 请求失败: {str(e)}")
|
||
return False
|
||
|
||
def register(self, email: str, password: str, verification_code: str) -> Dict[str, Any]:
|
||
"""
|
||
执行注册
|
||
|
||
Args:
|
||
email: 邮箱地址
|
||
password: 密码
|
||
verification_code: 邮箱验证码
|
||
|
||
Returns:
|
||
注册结果字典,包含 token 等信息
|
||
"""
|
||
print(f"{Fore.CYAN}[INFO] 开始注册账号...")
|
||
|
||
# 使用正确的注册端点
|
||
register_url = f"{self.api_url}/account/signup"
|
||
|
||
# 正确的请求体格式
|
||
payload = {
|
||
"username": email, # 使用邮箱作为用户名
|
||
"password": password,
|
||
"verification_code": verification_code,
|
||
"isAgress": True, # 同意条款
|
||
"from_landing": 0
|
||
}
|
||
|
||
# 更新请求头
|
||
headers = self.session.headers.copy()
|
||
headers.update({
|
||
'referer': 'https://my.duoplus.cn/',
|
||
'authorization': '',
|
||
'accept': 'application/json'
|
||
})
|
||
|
||
try:
|
||
print(f"{Fore.YELLOW}[INFO] 发送注册请求到: {register_url}")
|
||
print(f"{Fore.YELLOW}[DEBUG] 请求体: {json.dumps(payload, ensure_ascii=False)}")
|
||
|
||
response = self.session.post(register_url, json=payload, headers=headers)
|
||
result = response.json()
|
||
|
||
if response.status_code == 200 and result.get("code") == 200:
|
||
print(f"{Fore.GREEN}[SUCCESS] 注册成功!")
|
||
|
||
# 获取返回的数据
|
||
data = result.get("data", {})
|
||
if data:
|
||
print(f"{Fore.GREEN}[INFO] Access Token: {data.get('access_token')[:20]}...")
|
||
print(f"{Fore.GREEN}[INFO] Token 有效期: {data.get('expired_in')} 秒")
|
||
print(f"{Fore.GREEN}[INFO] 赠送余额: {data.get('gift_balance', '$0.00')}")
|
||
|
||
# 获取用户详细信息
|
||
self._get_user_profile(data.get('access_token'))
|
||
|
||
return {"success": True, "data": data}
|
||
else:
|
||
print(f"{Fore.RED}[ERROR] 注册失败: {result.get('message', 'Unknown error')}")
|
||
print(f"{Fore.RED}[DEBUG] 完整响应: {json.dumps(result, ensure_ascii=False)}")
|
||
return {"success": False, "message": result.get('message')}
|
||
|
||
except Exception as e:
|
||
print(f"{Fore.RED}[ERROR] 请求失败: {str(e)}")
|
||
return {"success": False, "message": str(e)}
|
||
|
||
def _get_user_profile(self, access_token: str) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
获取用户详细信息
|
||
|
||
Args:
|
||
access_token: 访问令牌
|
||
|
||
Returns:
|
||
用户信息字典
|
||
"""
|
||
profile_url = f"{self.api_url}/account/profile"
|
||
|
||
headers = self.session.headers.copy()
|
||
headers.update({
|
||
'authorization': access_token,
|
||
'accept': 'application/json'
|
||
})
|
||
|
||
try:
|
||
response = self.session.post(profile_url, json={}, headers=headers)
|
||
result = response.json()
|
||
|
||
if response.status_code == 200 and result.get("code") == 200:
|
||
profile = result.get("data", {})
|
||
print(f"{Fore.GREEN}[INFO] 用户ID: {profile.get('user_id')}")
|
||
print(f"{Fore.GREEN}[INFO] 邮箱: {profile.get('email')}")
|
||
print(f"{Fore.GREEN}[INFO] 团队ID: {profile.get('team_id')}")
|
||
print(f"{Fore.GREEN}[INFO] 团队名称: {profile.get('team_name')}")
|
||
return profile
|
||
|
||
except Exception as e:
|
||
print(f"{Fore.YELLOW}[WARNING] 获取用户信息失败: {str(e)}")
|
||
|
||
return None
|
||
|
||
def generate_random_password(self, length: int = 12) -> str:
|
||
"""生成随机密码"""
|
||
characters = string.ascii_letters + string.digits + "!@#$%^&*"
|
||
return ''.join(random.choice(characters) for _ in range(length))
|
||
|
||
def auto_register(self, email: str, password: Optional[str] = None) -> Dict[str, Any]:
|
||
"""
|
||
自动注册流程
|
||
|
||
Args:
|
||
email: 邮箱地址
|
||
password: 密码(如果不提供则自动生成)
|
||
|
||
Returns:
|
||
注册结果字典
|
||
"""
|
||
if not password:
|
||
password = self.generate_random_password()
|
||
print(f"{Fore.YELLOW}[INFO] 生成随机密码: {password}")
|
||
|
||
# 保存注册信息
|
||
print(f"{Fore.CYAN}{'='*60}")
|
||
print(f"{Fore.CYAN}注册信息:")
|
||
print(f"{Fore.CYAN}{'='*60}")
|
||
print(f" 📧 邮箱: {email}")
|
||
print(f" 🔑 密码: {password}")
|
||
print(f"{Fore.CYAN}{'='*60}\n")
|
||
|
||
# 发送验证码
|
||
if not self.send_verification_code(email):
|
||
return {"success": False, "message": "发送验证码失败"}
|
||
|
||
# 等待用户输入验证码
|
||
print(f"{Fore.CYAN}[INPUT] 请查看邮箱并输入验证码: ", end="")
|
||
verification_code = input().strip()
|
||
|
||
# 执行注册
|
||
result = self.register(email, password, verification_code)
|
||
|
||
if result.get("success"):
|
||
# 保存账号信息到文件
|
||
self._save_account_info(email, password, result.get("data", {}))
|
||
|
||
return result
|
||
|
||
def auto_register_with_temp_email(self) -> Dict[str, Any]:
|
||
"""
|
||
使用临时邮箱全自动注册
|
||
|
||
Returns:
|
||
注册结果字典
|
||
"""
|
||
print(f"{Fore.CYAN}[INFO] 使用自建邮箱进行全自动注册...")
|
||
|
||
# 创建邮箱管理器
|
||
email_manager = EmailManager()
|
||
auto_verifier = AutoEmailVerification(email_manager)
|
||
|
||
# 创建临时邮箱
|
||
print(f"{Fore.CYAN}[INFO] 创建临时邮箱...")
|
||
email_info = auto_verifier.create_temp_email(domain="cursor.edu.kg")
|
||
|
||
if not email_info.get("success"):
|
||
return {"success": False, "message": "创建临时邮箱失败"}
|
||
|
||
email_address = email_info["email"]
|
||
email_password = email_info["password"]
|
||
|
||
print(f"{Fore.GREEN}[INFO] 临时邮箱创建成功")
|
||
print(f"{Fore.GREEN}[INFO] 邮箱: {email_address}")
|
||
print(f"{Fore.GREEN}[INFO] 邮箱密码: {email_password}")
|
||
|
||
# 生成DuoPlus账号密码
|
||
duoplus_password = self.generate_random_password()
|
||
|
||
# 保存注册信息
|
||
print(f"\n{Fore.CYAN}{'='*60}")
|
||
print(f"{Fore.CYAN}DuoPlus 注册信息:")
|
||
print(f"{Fore.CYAN}{'='*60}")
|
||
print(f" 📧 邮箱: {email_address}")
|
||
print(f" 🔑 密码: {duoplus_password}")
|
||
print(f"{Fore.CYAN}{'='*60}\n")
|
||
|
||
# 发送验证码
|
||
if not self.send_verification_code(email_address):
|
||
return {"success": False, "message": "发送验证码失败"}
|
||
|
||
# 自动获取验证码
|
||
print(f"{Fore.CYAN}[INFO] 等待接收验证码...")
|
||
verification_code = auto_verifier.wait_for_code(
|
||
email_info,
|
||
sender_filter="duoplus",
|
||
timeout=60
|
||
)
|
||
|
||
if not verification_code:
|
||
print(f"{Fore.YELLOW}[WARNING] 自动获取验证码失败,请手动输入")
|
||
print(f"{Fore.CYAN}[INPUT] 请输入验证码: ", end="")
|
||
verification_code = input().strip()
|
||
|
||
# 执行注册
|
||
result = self.register(email_address, duoplus_password, verification_code)
|
||
|
||
if result.get("success"):
|
||
# 保存账号信息到文件,包括邮箱密码
|
||
data = result.get("data", {})
|
||
data["email_password"] = email_password # 保存邮箱密码
|
||
self._save_account_info(email_address, duoplus_password, data)
|
||
|
||
return result
|
||
|
||
def _save_account_info(self, email: str, password: str, data: Dict[str, Any]):
|
||
"""
|
||
保存账号信息到文件
|
||
|
||
Args:
|
||
email: 邮箱
|
||
password: 密码
|
||
data: 注册返回的数据
|
||
"""
|
||
try:
|
||
import datetime
|
||
filename = "registered_accounts.txt"
|
||
|
||
with open(filename, "a", encoding="utf-8") as f:
|
||
f.write(f"\n{'='*60}\n")
|
||
f.write(f"注册时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||
f.write(f"邮箱: {email}\n")
|
||
f.write(f"密码: {password}\n")
|
||
f.write(f"Access Token: {data.get('access_token', 'N/A')}\n")
|
||
f.write(f"Refresh Token: {data.get('refresh_token', 'N/A')}\n")
|
||
f.write(f"Token 有效期: {data.get('expired_in', 0)} 秒\n")
|
||
f.write(f"赠送余额: {data.get('gift_balance', '$0.00')}\n")
|
||
|
||
print(f"{Fore.GREEN}[INFO] 账号信息已保存到 {filename}")
|
||
|
||
except Exception as e:
|
||
print(f"{Fore.YELLOW}[WARNING] 保存账号信息失败: {str(e)}")
|
||
|
||
def main():
|
||
# 加载环境变量
|
||
load_dotenv()
|
||
|
||
# 获取 API Key
|
||
captcha_api_key = os.getenv('CAPTCHA_API_KEY')
|
||
if not captcha_api_key:
|
||
print(f"{Fore.RED}[ERROR] 请在 .env 文件中设置 CAPTCHA_API_KEY")
|
||
return
|
||
|
||
# 创建注册器
|
||
registrar = DuoPlusRegister(captcha_api_key)
|
||
|
||
# 获取用户输入
|
||
print(f"{Fore.CYAN}=== DuoPlus 自动注册工具 ===")
|
||
email = input(f"{Fore.YELLOW}请输入邮箱地址: ").strip()
|
||
|
||
use_random_password = input(f"{Fore.YELLOW}是否使用随机密码? (y/n): ").strip().lower() == 'y'
|
||
|
||
if use_random_password:
|
||
password = None
|
||
else:
|
||
password = input(f"{Fore.YELLOW}请输入密码: ").strip()
|
||
|
||
# 执行注册
|
||
if registrar.auto_register(email, password):
|
||
print(f"{Fore.GREEN}[SUCCESS] 注册流程完成!")
|
||
else:
|
||
print(f"{Fore.RED}[FAILED] 注册失败!")
|
||
|
||
if __name__ == "__main__":
|
||
main() |