Initial project import
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
"""OpenAI Sentinel Token (PoW) 生成与请求工具函数。
|
||||
|
||||
用于密码登录、注册等需要 sentinel token 的流程。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import json
|
||||
import random
|
||||
import time
|
||||
import uuid
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from curl_cffi.requests import Session
|
||||
|
||||
|
||||
class SentinelTokenGenerator:
|
||||
"""Sentinel Token 生成器(PoW - Proof of Work)。"""
|
||||
MAX_ATTEMPTS = 500_000
|
||||
ERROR_PREFIX = "wQ8Lk5FbGpA2NcR9dShT6gYjU7VxZ4D"
|
||||
|
||||
def __init__(self, device_id: str, ua: str):
|
||||
self.device_id = device_id
|
||||
self.user_agent = ua
|
||||
self.sid = str(uuid.uuid4())
|
||||
|
||||
@staticmethod
|
||||
def _fnv1a_32(text: str) -> str:
|
||||
h = 2166136261
|
||||
for ch in text:
|
||||
h ^= ord(ch)
|
||||
h = (h * 16777619) & 0xFFFFFFFF
|
||||
h ^= h >> 16
|
||||
h = (h * 2246822507) & 0xFFFFFFFF
|
||||
h ^= h >> 13
|
||||
h = (h * 3266489909) & 0xFFFFFFFF
|
||||
h ^= h >> 16
|
||||
return format(h & 0xFFFFFFFF, "08x")
|
||||
|
||||
def _get_config(self) -> list:
|
||||
perf_now = random.uniform(1000, 50000)
|
||||
return [
|
||||
"1920x1080",
|
||||
time.strftime("%a %b %d %Y %H:%M:%S GMT+0000 (Coordinated Universal Time)", time.gmtime()),
|
||||
4294705152,
|
||||
random.random(),
|
||||
self.user_agent,
|
||||
"https://sentinel.openai.com/sentinel/20260124ceb8/sdk.js",
|
||||
None,
|
||||
None,
|
||||
"en-US",
|
||||
random.random(),
|
||||
random.choice(["vendorSub-undefined", "plugins-undefined", "mimeTypes-undefined", "hardwareConcurrency-undefined"]),
|
||||
random.choice(["location", "implementation", "URL", "documentURI", "compatMode"]),
|
||||
random.choice(["Object", "Function", "Array", "Number", "parseFloat", "undefined"]),
|
||||
perf_now,
|
||||
self.sid,
|
||||
"",
|
||||
random.choice([4, 8, 12, 16]),
|
||||
time.time() * 1000 - perf_now,
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def _b64(data) -> str:
|
||||
return base64.b64encode(json.dumps(data, separators=(",", ":"), ensure_ascii=False).encode("utf-8")).decode("ascii")
|
||||
|
||||
def generate_requirements_token(self) -> str:
|
||||
data = self._get_config()
|
||||
data[3] = 1
|
||||
data[9] = round(random.uniform(5, 50))
|
||||
return "gAAAAAC" + self._b64(data)
|
||||
|
||||
def generate_token(self, seed: str, difficulty: str) -> str:
|
||||
start = time.time()
|
||||
data = self._get_config()
|
||||
difficulty = str(difficulty or "0")
|
||||
for i in range(self.MAX_ATTEMPTS):
|
||||
data[3] = i
|
||||
data[9] = round((time.time() - start) * 1000)
|
||||
payload = self._b64(data)
|
||||
if self._fnv1a_32(seed + payload)[: len(difficulty)] <= difficulty:
|
||||
return "gAAAAAB" + payload + "~S"
|
||||
return "gAAAAAB" + self.ERROR_PREFIX + self._b64(str(None))
|
||||
|
||||
|
||||
# ── 默认 User-Agent 和 sec-ch-ua ──────────────────────────────
|
||||
DEFAULT_SENTINEL_USER_AGENT = (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/145.0.0.0 Safari/537.36"
|
||||
)
|
||||
DEFAULT_SENTINEL_SEC_CH_UA = '"Chromium";v="145", "Google Chrome";v="145", "Not/A)Brand";v="99"'
|
||||
|
||||
|
||||
def build_sentinel_token(
|
||||
session: "Session",
|
||||
device_id: str,
|
||||
flow: str,
|
||||
*,
|
||||
user_agent: str = "",
|
||||
sec_ch_ua: str = "",
|
||||
) -> tuple[str, str]:
|
||||
"""请求 sentinel token 并返回 (sentinel_header_value, oai_sc_cookie_value)。
|
||||
|
||||
Args:
|
||||
session: curl_cffi Session 实例
|
||||
device_id: 设备 ID
|
||||
flow: 流程标识(如 "password_verify", "username_password_create" 等)
|
||||
user_agent: 可选的 User-Agent 覆盖
|
||||
sec_ch_ua: 可选的 sec-ch-ua 覆盖
|
||||
|
||||
Returns:
|
||||
(openai-sentinel-token header value, oai-sc cookie value) 元组
|
||||
|
||||
Raises:
|
||||
RuntimeError: sentinel 请求失败
|
||||
"""
|
||||
ua = user_agent or DEFAULT_SENTINEL_USER_AGENT
|
||||
ch_ua = sec_ch_ua or DEFAULT_SENTINEL_SEC_CH_UA
|
||||
generator = SentinelTokenGenerator(device_id, ua)
|
||||
resp = session.post(
|
||||
"https://sentinel.openai.com/backend-api/sentinel/req",
|
||||
data=json.dumps({"p": generator.generate_requirements_token(), "id": device_id, "flow": flow}),
|
||||
headers={
|
||||
"Content-Type": "text/plain;charset=UTF-8",
|
||||
"Referer": "https://sentinel.openai.com/backend-api/sentinel/frame.html",
|
||||
"Origin": "https://sentinel.openai.com",
|
||||
"User-Agent": ua,
|
||||
"sec-ch-ua": ch_ua,
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"Windows"',
|
||||
},
|
||||
timeout=20,
|
||||
verify=False,
|
||||
)
|
||||
|
||||
try:
|
||||
data = resp.json() if resp.text else {}
|
||||
except Exception:
|
||||
fallback = json.dumps(
|
||||
{"p": generator.generate_requirements_token(), "t": "", "c": "", "id": device_id, "flow": flow},
|
||||
separators=(",", ":"),
|
||||
)
|
||||
return fallback, ""
|
||||
|
||||
token = str(data.get("token") or "").strip()
|
||||
if resp.status_code != 200 or not token:
|
||||
raise RuntimeError(f"sentinel_req_failed_{resp.status_code}")
|
||||
pow_data = data.get("proofofwork") or {}
|
||||
p_value = (
|
||||
generator.generate_token(str(pow_data.get("seed") or ""), str(pow_data.get("difficulty") or "0"))
|
||||
if pow_data.get("required") and pow_data.get("seed")
|
||||
else generator.generate_requirements_token()
|
||||
)
|
||||
sentinel_value = json.dumps({"p": p_value, "t": "", "c": token, "id": device_id, "flow": flow}, separators=(",", ":"))
|
||||
# oai-sc cookie = "0" + sentinel token "c" value (the challenge token from the server)
|
||||
oai_sc_value = "0" + token
|
||||
return sentinel_value, oai_sc_value
|
||||
Reference in New Issue
Block a user