first commit: DuoPlus云手机协议注册工具 - 完整实现
This commit is contained in:
455
duoplus_register.py
Normal file
455
duoplus_register.py
Normal file
@@ -0,0 +1,455 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user