Initial commit: Grok batch registration tool
- Multi-threaded account registration - Auto email verification via freemail API - Auto NSFW/Unhinged mode activation - Temporary email cleanup after registration
This commit is contained in:
9
g/__init__.py
Normal file
9
g/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
"""
|
||||
注册机配件
|
||||
"""
|
||||
from .email_service import EmailService
|
||||
from .turnstile_service import TurnstileService
|
||||
from .user_agreement_service import UserAgreementService
|
||||
from .nsfw_service import NsfwSettingsService
|
||||
|
||||
__all__ = ['EmailService', 'TurnstileService', 'UserAgreementService', 'NsfwSettingsService']
|
||||
66
g/email_service.py
Normal file
66
g/email_service.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""邮箱服务类 - 适配 freemail API"""
|
||||
import os
|
||||
import time
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
|
||||
|
||||
class EmailService:
|
||||
def __init__(self):
|
||||
load_dotenv()
|
||||
self.worker_domain = os.getenv("WORKER_DOMAIN")
|
||||
self.freemail_token = os.getenv("FREEMAIL_TOKEN")
|
||||
if not all([self.worker_domain, self.freemail_token]):
|
||||
raise ValueError("Missing: WORKER_DOMAIN or FREEMAIL_TOKEN")
|
||||
self.base_url = f"https://{self.worker_domain}"
|
||||
self.headers = {"Authorization": f"Bearer {self.freemail_token}"}
|
||||
|
||||
def create_email(self):
|
||||
"""创建临时邮箱 GET /api/generate"""
|
||||
try:
|
||||
res = requests.get(
|
||||
f"{self.base_url}/api/generate",
|
||||
headers=self.headers,
|
||||
timeout=10
|
||||
)
|
||||
if res.status_code == 200:
|
||||
email = res.json().get("email")
|
||||
return email, email # 兼容原接口 (jwt, email)
|
||||
print(f"[-] 创建邮箱失败: {res.status_code} - {res.text}")
|
||||
return None, None
|
||||
except Exception as e:
|
||||
print(f"[-] 创建邮箱失败: {e}")
|
||||
return None, None
|
||||
|
||||
def fetch_verification_code(self, email, max_attempts=30):
|
||||
"""轮询获取验证码 GET /api/emails?mailbox=xxx"""
|
||||
for _ in range(max_attempts):
|
||||
try:
|
||||
res = requests.get(
|
||||
f"{self.base_url}/api/emails",
|
||||
params={"mailbox": email},
|
||||
headers=self.headers,
|
||||
timeout=10
|
||||
)
|
||||
if res.status_code == 200:
|
||||
emails = res.json()
|
||||
if emails and emails[0].get("verification_code"):
|
||||
code = emails[0]["verification_code"]
|
||||
return code.replace("-", "")
|
||||
except:
|
||||
pass
|
||||
time.sleep(1)
|
||||
return None
|
||||
|
||||
def delete_email(self, address):
|
||||
"""删除邮箱 DELETE /api/mailboxes?address=xxx"""
|
||||
try:
|
||||
res = requests.delete(
|
||||
f"{self.base_url}/api/mailboxes",
|
||||
params={"address": address},
|
||||
headers=self.headers,
|
||||
timeout=10
|
||||
)
|
||||
return res.status_code == 200 and res.json().get("success")
|
||||
except:
|
||||
return False
|
||||
164
g/nsfw_service.py
Normal file
164
g/nsfw_service.py
Normal file
@@ -0,0 +1,164 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from curl_cffi import requests
|
||||
|
||||
DEFAULT_USER_AGENT = (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/120.0.0.0 Safari/537.36"
|
||||
)
|
||||
|
||||
|
||||
class NsfwSettingsService:
|
||||
"""开启 NSFW 相关设置(线程安全,无全局状态)。"""
|
||||
|
||||
def __init__(self, cf_clearance: str = ""):
|
||||
self.cf_clearance = (cf_clearance or "").strip()
|
||||
|
||||
def enable_nsfw(
|
||||
self,
|
||||
sso: str,
|
||||
sso_rw: str,
|
||||
impersonate: str,
|
||||
user_agent: Optional[str] = None,
|
||||
cf_clearance: Optional[str] = None,
|
||||
timeout: int = 15,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
启用 always_show_nsfw_content。
|
||||
返回: {
|
||||
ok: bool,
|
||||
hex_reply: str,
|
||||
status_code: int | None,
|
||||
grpc_status: str | None,
|
||||
error: str | None
|
||||
}
|
||||
"""
|
||||
if not sso:
|
||||
return {
|
||||
"ok": False,
|
||||
"hex_reply": "",
|
||||
"status_code": None,
|
||||
"grpc_status": None,
|
||||
"error": "缺少 sso",
|
||||
}
|
||||
if not sso_rw:
|
||||
return {
|
||||
"ok": False,
|
||||
"hex_reply": "",
|
||||
"status_code": None,
|
||||
"grpc_status": None,
|
||||
"error": "缺少 sso-rw",
|
||||
}
|
||||
|
||||
url = "https://grok.com/auth_mgmt.AuthManagement/UpdateUserFeatureControls"
|
||||
|
||||
cookies = {
|
||||
"sso": sso,
|
||||
"sso-rw": sso_rw,
|
||||
}
|
||||
clearance = (cf_clearance if cf_clearance is not None else self.cf_clearance).strip()
|
||||
if clearance:
|
||||
cookies["cf_clearance"] = clearance
|
||||
|
||||
headers = {
|
||||
"content-type": "application/grpc-web+proto",
|
||||
"origin": "https://grok.com",
|
||||
"referer": "https://grok.com/?_s=data",
|
||||
"x-grpc-web": "1",
|
||||
"user-agent": user_agent or DEFAULT_USER_AGENT,
|
||||
}
|
||||
|
||||
data = (
|
||||
b"\x00\x00\x00\x00"
|
||||
b"\x20"
|
||||
b"\x0a\x02\x10\x01"
|
||||
b"\x12\x1a"
|
||||
b"\x0a\x18"
|
||||
b"always_show_nsfw_content"
|
||||
)
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
url,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
data=data,
|
||||
impersonate=impersonate or "chrome120",
|
||||
timeout=timeout,
|
||||
)
|
||||
hex_reply = response.content.hex()
|
||||
grpc_status = response.headers.get("grpc-status")
|
||||
|
||||
error = None
|
||||
ok = response.status_code == 200 and (grpc_status in (None, "0"))
|
||||
if response.status_code == 403:
|
||||
error = "403 Forbidden"
|
||||
elif response.status_code != 200:
|
||||
error = f"HTTP {response.status_code}"
|
||||
elif grpc_status not in (None, "0"):
|
||||
error = f"gRPC {grpc_status}"
|
||||
|
||||
return {
|
||||
"ok": ok,
|
||||
"hex_reply": hex_reply,
|
||||
"status_code": response.status_code,
|
||||
"grpc_status": grpc_status,
|
||||
"error": error,
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"ok": False,
|
||||
"hex_reply": "",
|
||||
"status_code": None,
|
||||
"grpc_status": None,
|
||||
"error": str(e),
|
||||
}
|
||||
|
||||
def enable_unhinged(
|
||||
self,
|
||||
sso: str,
|
||||
impersonate: str = "chrome120",
|
||||
user_agent: Optional[str] = None,
|
||||
timeout: int = 30,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
使用帖子方法开启 Unhinged 模式(二次验证)。
|
||||
"""
|
||||
import struct
|
||||
|
||||
url = "https://grok.com/auth_mgmt.AuthManagement/UpdateUserFeatureControls"
|
||||
|
||||
headers = {
|
||||
"accept": "*/*",
|
||||
"content-type": "application/grpc-web+proto",
|
||||
"origin": "https://grok.com",
|
||||
"referer": "https://grok.com/",
|
||||
"user-agent": user_agent or DEFAULT_USER_AGENT,
|
||||
"x-grpc-web": "1",
|
||||
"x-user-agent": "connect-es/2.1.1",
|
||||
"cookie": f"sso={sso}; sso-rw={sso}"
|
||||
}
|
||||
|
||||
payload = bytes([0x08, 0x01, 0x10, 0x01])
|
||||
data = b'\x00' + struct.pack('>I', len(payload)) + payload
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
url,
|
||||
headers=headers,
|
||||
data=data,
|
||||
impersonate=impersonate,
|
||||
timeout=timeout,
|
||||
)
|
||||
return {
|
||||
"ok": response.status_code == 200,
|
||||
"status_code": response.status_code,
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"ok": False,
|
||||
"error": str(e),
|
||||
}
|
||||
105
g/turnstile_service.py
Normal file
105
g/turnstile_service.py
Normal file
@@ -0,0 +1,105 @@
|
||||
"""
|
||||
Turnstile验证服务类
|
||||
"""
|
||||
import os
|
||||
import time
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
class TurnstileService:
|
||||
"""Turnstile验证服务类"""
|
||||
|
||||
def __init__(self, solver_url="http://127.0.0.1:5072"):
|
||||
"""
|
||||
初始化Turnstile服务
|
||||
"""
|
||||
self.yescaptcha_key = os.getenv('YESCAPTCHA_KEY', '').strip()
|
||||
self.solver_url = solver_url
|
||||
self.yescaptcha_api = "https://api.yescaptcha.com"
|
||||
|
||||
def create_task(self, siteurl, sitekey):
|
||||
"""
|
||||
创建Turnstile验证任务
|
||||
"""
|
||||
if self.yescaptcha_key:
|
||||
# 使用 YesCaptcha API
|
||||
url = f"{self.yescaptcha_api}/createTask"
|
||||
payload = {
|
||||
"clientKey": self.yescaptcha_key,
|
||||
"task": {
|
||||
"type": "TurnstileTaskProxyless",
|
||||
"websiteURL": siteurl,
|
||||
"websiteKey": sitekey
|
||||
}
|
||||
}
|
||||
response = requests.post(url, json=payload)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
if data.get('errorId') != 0:
|
||||
raise Exception(f"YesCaptcha创建任务失败: {data.get('errorDescription')}")
|
||||
return data['taskId']
|
||||
else:
|
||||
# 使用本地 Turnstile Solver
|
||||
url = f"{self.solver_url}/turnstile?url={siteurl}&sitekey={sitekey}"
|
||||
response = requests.get(url)
|
||||
response.raise_for_status()
|
||||
return response.json()['taskId']
|
||||
|
||||
def get_response(self, task_id, max_retries=30, initial_delay=5, retry_delay=2):
|
||||
"""
|
||||
获取Turnstile验证响应
|
||||
"""
|
||||
time.sleep(initial_delay)
|
||||
|
||||
for _ in range(max_retries):
|
||||
try:
|
||||
if self.yescaptcha_key:
|
||||
# 使用 YesCaptcha API
|
||||
url = f"{self.yescaptcha_api}/getTaskResult"
|
||||
payload = {
|
||||
"clientKey": self.yescaptcha_key,
|
||||
"taskId": task_id
|
||||
}
|
||||
response = requests.post(url, json=payload)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
if data.get('errorId') != 0:
|
||||
print(f"YesCaptcha获取结果失败: {data.get('errorDescription')}")
|
||||
return None
|
||||
|
||||
if data.get('status') == 'ready':
|
||||
token = data.get('solution', {}).get('token')
|
||||
if token:
|
||||
return token
|
||||
else:
|
||||
print("YesCaptcha返回结果中没有token")
|
||||
return None
|
||||
elif data.get('status') == 'processing':
|
||||
time.sleep(retry_delay)
|
||||
else:
|
||||
print(f"YesCaptcha未知状态: {data.get('status')}")
|
||||
time.sleep(retry_delay)
|
||||
else:
|
||||
# 使用本地 Turnstile Solver
|
||||
url = f"{self.solver_url}/result?id={task_id}"
|
||||
response = requests.get(url)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
captcha = data.get('solution', {}).get('token', None)
|
||||
|
||||
if captcha:
|
||||
if captcha != "CAPTCHA_FAIL":
|
||||
return captcha
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
time.sleep(retry_delay)
|
||||
except Exception as e:
|
||||
print(f"获取Turnstile响应异常: {e}")
|
||||
time.sleep(retry_delay)
|
||||
|
||||
return None
|
||||
115
g/user_agreement_service.py
Normal file
115
g/user_agreement_service.py
Normal file
@@ -0,0 +1,115 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from curl_cffi import requests
|
||||
|
||||
DEFAULT_USER_AGENT = (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/120.0.0.0 Safari/537.36"
|
||||
)
|
||||
|
||||
|
||||
class UserAgreementService:
|
||||
"""处理账号协议同意流程(线程安全,无全局状态)。"""
|
||||
|
||||
def __init__(self, cf_clearance: str = ""):
|
||||
self.cf_clearance = (cf_clearance or "").strip()
|
||||
|
||||
def accept_tos_version(
|
||||
self,
|
||||
sso: str,
|
||||
sso_rw: str,
|
||||
impersonate: str,
|
||||
user_agent: Optional[str] = None,
|
||||
cf_clearance: Optional[str] = None,
|
||||
timeout: int = 15,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
同意 TOS 版本。
|
||||
返回: {
|
||||
ok: bool,
|
||||
hex_reply: str,
|
||||
status_code: int | None,
|
||||
grpc_status: str | None,
|
||||
error: str | None
|
||||
}
|
||||
"""
|
||||
if not sso:
|
||||
return {
|
||||
"ok": False,
|
||||
"hex_reply": "",
|
||||
"status_code": None,
|
||||
"grpc_status": None,
|
||||
"error": "缺少 sso",
|
||||
}
|
||||
if not sso_rw:
|
||||
return {
|
||||
"ok": False,
|
||||
"hex_reply": "",
|
||||
"status_code": None,
|
||||
"grpc_status": None,
|
||||
"error": "缺少 sso-rw",
|
||||
}
|
||||
|
||||
url = "https://accounts.x.ai/auth_mgmt.AuthManagement/SetTosAcceptedVersion"
|
||||
|
||||
cookies = {
|
||||
"sso": sso,
|
||||
"sso-rw": sso_rw,
|
||||
}
|
||||
clearance = (cf_clearance if cf_clearance is not None else self.cf_clearance).strip()
|
||||
if clearance:
|
||||
cookies["cf_clearance"] = clearance
|
||||
|
||||
headers = {
|
||||
"content-type": "application/grpc-web+proto",
|
||||
"origin": "https://accounts.x.ai",
|
||||
"referer": "https://accounts.x.ai/accept-tos",
|
||||
"x-grpc-web": "1",
|
||||
"user-agent": user_agent or DEFAULT_USER_AGENT,
|
||||
}
|
||||
|
||||
data = (
|
||||
b"\x00\x00\x00\x00" # 头部
|
||||
b"\x02" # 长度
|
||||
b"\x10\x01" # Field 2 = 1
|
||||
)
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
url,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
data=data,
|
||||
impersonate=impersonate or "chrome120",
|
||||
timeout=timeout,
|
||||
)
|
||||
hex_reply = response.content.hex()
|
||||
grpc_status = response.headers.get("grpc-status")
|
||||
|
||||
error = None
|
||||
ok = response.status_code == 200 and (grpc_status in (None, "0"))
|
||||
if response.status_code == 403:
|
||||
error = "403 Forbidden"
|
||||
elif response.status_code != 200:
|
||||
error = f"HTTP {response.status_code}"
|
||||
elif grpc_status not in (None, "0"):
|
||||
error = f"gRPC {grpc_status}"
|
||||
|
||||
return {
|
||||
"ok": ok,
|
||||
"hex_reply": hex_reply,
|
||||
"status_code": response.status_code,
|
||||
"grpc_status": grpc_status,
|
||||
"error": error,
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"ok": False,
|
||||
"hex_reply": "",
|
||||
"status_code": None,
|
||||
"grpc_status": None,
|
||||
"error": str(e),
|
||||
}
|
||||
Reference in New Issue
Block a user