x
This commit is contained in:
404
register/register_worker.py
Normal file
404
register/register_worker.py
Normal file
@@ -0,0 +1,404 @@
|
||||
import asyncio
|
||||
import json
|
||||
import random
|
||||
import string
|
||||
from typing import Optional, Tuple
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from core.config import Config
|
||||
from core.exceptions import RegisterError
|
||||
from services.email_manager import EmailAccount, EmailManager
|
||||
from services.fetch_manager import FetchManager
|
||||
from services.uuid import ULID
|
||||
|
||||
|
||||
def extract_jwt(cookie_string: str) -> str:
|
||||
"""从cookie字符串中提取JWT token"""
|
||||
try:
|
||||
return cookie_string.split(';')[0].split('=')[1].split('%3A%3A')[1]
|
||||
except Exception as e:
|
||||
logger.error(f"[错误] 提取JWT失败: {str(e)}")
|
||||
return ""
|
||||
|
||||
class FormBuilder:
|
||||
@staticmethod
|
||||
def _generate_password() -> str:
|
||||
"""生成随机密码
|
||||
规则: 12-16位,包含大小写字母、数字和特殊字符
|
||||
"""
|
||||
length = random.randint(12, 16)
|
||||
lowercase = string.ascii_lowercase
|
||||
uppercase = string.ascii_uppercase
|
||||
digits = string.digits
|
||||
special = "!@#$%^&*"
|
||||
|
||||
# 确保每种字符至少有一个
|
||||
password = [
|
||||
random.choice(lowercase),
|
||||
random.choice(uppercase),
|
||||
random.choice(digits),
|
||||
random.choice(special)
|
||||
]
|
||||
|
||||
# 填充剩余长度
|
||||
all_chars = lowercase + uppercase + digits + special
|
||||
password.extend(random.choice(all_chars) for _ in range(length - 4))
|
||||
|
||||
# 打乱顺序
|
||||
random.shuffle(password)
|
||||
return ''.join(password)
|
||||
|
||||
@staticmethod
|
||||
def _generate_name() -> tuple[str, str]:
|
||||
"""生成随机的名字和姓氏
|
||||
Returns:
|
||||
tuple: (first_name, last_name)
|
||||
"""
|
||||
first_names = ["Alex", "Sam", "Chris", "Jordan", "Taylor", "Morgan", "Casey", "Drew", "Pat", "Quinn"]
|
||||
last_names = ["Smith", "Johnson", "Brown", "Davis", "Wilson", "Moore", "Taylor", "Anderson", "Thomas", "Jackson"]
|
||||
|
||||
return (
|
||||
random.choice(first_names),
|
||||
random.choice(last_names)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def build_register_form(boundary: str, email: str, token: str) -> tuple[str, str]:
|
||||
"""构建注册表单数据,返回(form_data, password)"""
|
||||
password = FormBuilder._generate_password()
|
||||
|
||||
fields = {
|
||||
"1_state": "{\"returnTo\":\"/settings\"}",
|
||||
"1_redirect_uri": "https://cursor.com/api/auth/callback",
|
||||
"1_bot_detection_token": token,
|
||||
"1_first_name": "wa",
|
||||
"1_last_name": "niu",
|
||||
"1_email": email,
|
||||
"1_password": password,
|
||||
"1_intent": "sign-up",
|
||||
"0": "[\"$K1\"]"
|
||||
}
|
||||
|
||||
form_data = []
|
||||
for key, value in fields.items():
|
||||
form_data.append(f'--{boundary}')
|
||||
form_data.append(f'Content-Disposition: form-data; name="{key}"')
|
||||
form_data.append('')
|
||||
form_data.append(value)
|
||||
|
||||
form_data.append(f'--{boundary}--')
|
||||
return '\r\n'.join(form_data), password
|
||||
|
||||
@staticmethod
|
||||
def build_verify_form(boundary: str, email: str, token: str, code: str, pending_token: str) -> str:
|
||||
"""构建验证表单数据"""
|
||||
fields = {
|
||||
"1_pending_authentication_token": pending_token,
|
||||
"1_email": email,
|
||||
"1_state": "{\"returnTo\":\"/settings\"}",
|
||||
"1_redirect_uri": "https://cursor.com/api/auth/callback",
|
||||
"1_bot_detection_token": token,
|
||||
"1_code": code,
|
||||
"0": "[\"$K1\"]"
|
||||
}
|
||||
|
||||
form_data = []
|
||||
for key, value in fields.items():
|
||||
form_data.append(f'--{boundary}')
|
||||
form_data.append(f'Content-Disposition: form-data; name="{key}"')
|
||||
form_data.append('')
|
||||
form_data.append(value)
|
||||
|
||||
form_data.append(f'--{boundary}--')
|
||||
return '\r\n'.join(form_data)
|
||||
|
||||
|
||||
class RegisterWorker:
|
||||
def __init__(self, config: Config, fetch_manager: FetchManager, email_manager: EmailManager):
|
||||
self.config = config
|
||||
self.fetch_manager = fetch_manager
|
||||
self.email_manager = email_manager
|
||||
self.form_builder = FormBuilder()
|
||||
self.uuid = ULID()
|
||||
|
||||
async def random_delay(self):
|
||||
delay = random.uniform(*self.config.register_config.delay_range)
|
||||
await asyncio.sleep(delay)
|
||||
|
||||
@staticmethod
|
||||
async def _extract_auth_token(response_text: str, email_account: EmailAccount, email_manager: EmailManager) -> str | None:
|
||||
"""从响应文本中提取pending_authentication_token"""
|
||||
res = response_text.split('\n')
|
||||
logger.debug(f"开始提取 auth_token,响应行数: {len(res)}")
|
||||
|
||||
# 检查邮箱是否可用
|
||||
for line in res:
|
||||
if '"code":"email_not_available"' in line:
|
||||
logger.error("不受支持的邮箱")
|
||||
await email_manager.update_account_status(email_account.id, 'unavailable')
|
||||
raise RegisterError("Email is not available")
|
||||
|
||||
try:
|
||||
for i, r in enumerate(res):
|
||||
if r.startswith('0:'):
|
||||
logger.debug(f"在第 {i+1} 行找到匹配")
|
||||
data = json.loads(r.split('0:')[1])
|
||||
auth_data = data[1][0][0][1]["children"][1]["children"][1]["children"][1]["children"][0]
|
||||
params_str = auth_data.split('?')[1]
|
||||
params_dict = json.loads(params_str)
|
||||
token = params_dict['pending_authentication_token']
|
||||
logger.debug(f"方法2提取成功: {token[:10]}...")
|
||||
return token
|
||||
except Exception as e:
|
||||
logger.error(f"提取token失败: {str(e)}")
|
||||
logger.debug("响应内容预览:", response_text[:200])
|
||||
|
||||
return None
|
||||
|
||||
async def register(self, proxy: str, token_pair: Tuple[str, str], email_account: EmailAccount):
|
||||
"""完整的注册流程"""
|
||||
token1, token2 = token_pair
|
||||
session_id = self.uuid.generate()
|
||||
try:
|
||||
logger.info(f"开始注册账号: {email_account.email}")
|
||||
|
||||
# 第一次注册请求
|
||||
try:
|
||||
email, pending_token, cursor_password = await self._first_register(
|
||||
proxy,
|
||||
token1,
|
||||
email_account.email,
|
||||
email_account,
|
||||
session_id=session_id
|
||||
)
|
||||
except RegisterError as e:
|
||||
if "Email is not available" in str(e):
|
||||
logger.warning(f"邮箱 {email_account.email} 不受支持,跳过处理")
|
||||
return None
|
||||
raise e
|
||||
|
||||
# 获取验证码的同时,可以开始准备下一步的操作
|
||||
verification_code_task = self._get_verification_code_with_retry(
|
||||
email_account.email,
|
||||
email_account.refresh_token,
|
||||
email_account.client_id,
|
||||
max_retries=3
|
||||
)
|
||||
|
||||
# 等待验证码
|
||||
verification_code = await verification_code_task
|
||||
if not verification_code:
|
||||
logger.error(f"账号 {email_account.email} 获取验证码失败")
|
||||
await self.email_manager.update_account_status(email_account.id, 'failed')
|
||||
raise RegisterError("Failed to get verification code")
|
||||
|
||||
logger.debug(f"邮箱 {email_account.email} 获取到验证码: {verification_code}")
|
||||
|
||||
await self.random_delay()
|
||||
|
||||
# 验证码验证
|
||||
redirect_url = await self._verify_code(
|
||||
proxy=proxy,
|
||||
token=token2,
|
||||
code=verification_code,
|
||||
pending_token=pending_token,
|
||||
email=email,
|
||||
session_id=session_id
|
||||
)
|
||||
|
||||
if not redirect_url:
|
||||
raise RegisterError("No redirect URL found")
|
||||
|
||||
await self.random_delay()
|
||||
|
||||
# callback请求
|
||||
cookies = await self._callback(proxy, redirect_url)
|
||||
if not cookies:
|
||||
raise RegisterError("Failed to get cookies")
|
||||
|
||||
logger.success(f"账号 {email_account.email} 注册成功")
|
||||
return {
|
||||
'account_id': email_account.id,
|
||||
'cursor_password': cursor_password,
|
||||
'cursor_cookie': cookies,
|
||||
'cursor_jwt': extract_jwt(cookies)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"账号 {email_account.email} 注册失败: {str(e)}")
|
||||
if not str(e).startswith("Email is not available"):
|
||||
await self.email_manager.update_account_status(email_account.id, 'failed')
|
||||
raise RegisterError(f"Registration failed: {str(e)}")
|
||||
|
||||
async def _first_register(
|
||||
self,
|
||||
proxy: str,
|
||||
token: str,
|
||||
email: str,
|
||||
email_account: EmailAccount,
|
||||
session_id: str
|
||||
) -> tuple[str, str, str]:
|
||||
"""第一次注册请求"""
|
||||
logger.debug(f"开始第一次注册请求 - 邮箱: {email}, 代理: {proxy}")
|
||||
|
||||
first_name, last_name = self.form_builder._generate_name()
|
||||
|
||||
# 在headers中定义boundary
|
||||
boundary = "----WebKitFormBoundary2rKlvTagBEhneWi3"
|
||||
headers = {
|
||||
"accept": "text/x-component",
|
||||
"next-action": "770926d8148e29539286d20e1c1548d2aff6c0b9",
|
||||
"content-type": f"multipart/form-data; boundary={boundary}",
|
||||
"origin": "https://authenticator.cursor.sh",
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-origin"
|
||||
}
|
||||
|
||||
params = {
|
||||
"first_name": first_name,
|
||||
"last_name": last_name,
|
||||
"email": email,
|
||||
"state": "%7B%22returnTo%22%3A%22%2Fsettings%22%7D",
|
||||
"redirect_uri": "https://cursor.com/api/auth/callback",
|
||||
}
|
||||
|
||||
# 构建form数据
|
||||
form_data, cursor_password = self.form_builder.build_register_form(boundary, email, token)
|
||||
|
||||
response = await self.fetch_manager.request(
|
||||
"POST",
|
||||
"https://authenticator.cursor.sh/sign-up/password",
|
||||
headers=headers,
|
||||
params=params,
|
||||
data=form_data,
|
||||
proxy=proxy
|
||||
)
|
||||
|
||||
if 'error' in response:
|
||||
raise RegisterError(f"First register request failed: {response['error']}")
|
||||
|
||||
text = response['body'].decode()
|
||||
pending_token = await self._extract_auth_token(text, email_account, self.email_manager)
|
||||
if not pending_token:
|
||||
raise RegisterError("Failed to extract auth token")
|
||||
|
||||
logger.debug(f"第一次请求完成 - pending_token: {pending_token[:10]}...")
|
||||
return email, pending_token, cursor_password
|
||||
|
||||
async def _verify_code(
|
||||
self,
|
||||
proxy: str,
|
||||
token: str,
|
||||
code: str,
|
||||
pending_token: str,
|
||||
email: str,
|
||||
session_id: str
|
||||
) -> str:
|
||||
"""验证码验证请求"""
|
||||
logger.debug(f"开始验证码验证 - 邮箱: {email}, 验证码: {code}")
|
||||
|
||||
boundary = "----WebKitFormBoundaryqEBf0rEYwwb9aUoF"
|
||||
headers = {
|
||||
"accept": "text/x-component",
|
||||
"content-type": f"multipart/form-data; boundary={boundary}",
|
||||
"next-action": "e75011da58d295bef5aa55740d0758a006468655",
|
||||
"origin": "https://authenticator.cursor.sh",
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-origin",
|
||||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
|
||||
}
|
||||
|
||||
params = {
|
||||
"email": email,
|
||||
"pending_authentication_token": pending_token,
|
||||
"state": "%7B%22returnTo%22%3A%22%2Fsettings%22%7D",
|
||||
"redirect_uri": "https://cursor.com/api/auth/callback",
|
||||
"authorization_session_id": session_id
|
||||
}
|
||||
|
||||
form_data = self.form_builder.build_verify_form(
|
||||
boundary=boundary,
|
||||
email=email,
|
||||
token=token,
|
||||
code=code,
|
||||
pending_token=pending_token,
|
||||
)
|
||||
|
||||
response = await self.fetch_manager.request(
|
||||
"POST",
|
||||
"https://authenticator.cursor.sh/email-verification",
|
||||
headers=headers,
|
||||
params=params,
|
||||
data=form_data,
|
||||
proxy=proxy
|
||||
)
|
||||
|
||||
redirect_url = response.get('headers', {}).get('x-action-redirect')
|
||||
if not redirect_url:
|
||||
raise RegisterError("未找到重定向URL,响应头: %s" % json.dumps(response.get('headers')))
|
||||
|
||||
return redirect_url
|
||||
|
||||
async def _callback(self, proxy: str, redirect_url: str) -> str:
|
||||
"""Callback请求"""
|
||||
logger.debug(f"开始callback请求 - URL: {redirect_url[:50]}...")
|
||||
|
||||
parsed = urlparse(redirect_url)
|
||||
code = parse_qs(parsed.query)['code'][0]
|
||||
logger.debug(f"从URL提取的code: {code[:10]}...")
|
||||
|
||||
headers = {
|
||||
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||
"accept-language": "zh-CN,zh;q=0.9",
|
||||
"sec-fetch-dest": "document",
|
||||
"sec-fetch-mode": "navigate",
|
||||
"sec-fetch-site": "cross-site",
|
||||
"upgrade-insecure-requests": "1",
|
||||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
|
||||
}
|
||||
|
||||
callback_url = "https://www.cursor.com/api/auth/callback"
|
||||
params = {
|
||||
"code": code,
|
||||
"state": "%7B%22returnTo%22%3A%22%2Fsettings%22%7D"
|
||||
}
|
||||
|
||||
response = await self.fetch_manager.request(
|
||||
"GET",
|
||||
callback_url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
proxy=proxy,
|
||||
allow_redirects=False
|
||||
)
|
||||
|
||||
if 'error' in response:
|
||||
raise RegisterError(f"Callback request failed: {response['error']}")
|
||||
|
||||
cookies = response['headers'].get('set-cookie')
|
||||
if cookies:
|
||||
logger.debug("成功获取到cookies")
|
||||
else:
|
||||
logger.error("未获取到cookies")
|
||||
return cookies
|
||||
|
||||
async def _get_verification_code_with_retry(self, email: str, refresh_token: str, client_id: str, max_retries: int = 3) -> Optional[str]:
|
||||
"""带重试的验证码获取"""
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
code = await self.email_manager.get_verification_code(
|
||||
email, refresh_token, client_id
|
||||
)
|
||||
if code:
|
||||
return code
|
||||
await asyncio.sleep(2) # 短暂延迟后重试
|
||||
except Exception as e:
|
||||
logger.warning(f"第 {attempt + 1} 次获取验证码失败: {str(e)}")
|
||||
if attempt == max_retries - 1: # 最后一次尝试
|
||||
return None
|
||||
await asyncio.sleep(2) # 失败后等待更长时间
|
||||
return None
|
||||
Reference in New Issue
Block a user