From 7f794f91851c44c6643fce662091538afaee4701 Mon Sep 17 00:00:00 2001
From: GeJiaXiang <353358601@qq.com>
Date: Sat, 7 Feb 2026 09:52:39 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=B5=8F=E8=A7=88?=
=?UTF-8?q?=E5=99=A8=E6=8C=87=E7=BA=B9=E9=85=8D=E7=BD=AE=E6=94=AF=E6=8C=81?=
=?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E9=85=8D=E7=BD=AE=E9=A1=B5=E9=9D=A2?=
=?UTF-8?q?=E5=88=86=E7=BB=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 新增 grok.browser 和 grok.user_agent 配置项,支持自定义 curl_cffi 指纹和 User-Agent
- 将 CF Clearance、curl_cffi、User-Agent 三个字段在前端独立为"反爬虫验证"分组
- 添加分组说明:首次请求 IP 需与获取 CF Clearance 时一致
- 调整"禁用记忆"字段位置至"临时对话"下方
- 移除 docker-compose.yml 中无效的 GROK_DISABLE_MEMORY 环境变量
---
app/services/grok/services/assets.py | 14 ++-
app/services/grok/services/chat.py | 9 +-
app/services/grok/services/media.py | 15 ++-
app/services/grok/services/nsfw.py | 12 +-
app/services/grok/services/usage.py | 9 +-
app/services/grok/services/voice.py | 5 +-
app/static/config/config.html | 2 +-
app/static/config/config.js | 177 +++++++++++++++++----------
config.defaults.toml | 4 +
9 files changed, 156 insertions(+), 91 deletions(-)
diff --git a/app/services/grok/services/assets.py b/app/services/grok/services/assets.py
index 7d80b5b..0a2a0e4 100644
--- a/app/services/grok/services/assets.py
+++ b/app/services/grok/services/assets.py
@@ -37,7 +37,8 @@ DOWNLOAD_API = "https://assets.grok.com"
LOCK_DIR = Path(__file__).parent.parent.parent.parent / "data" / ".locks"
TIMEOUT = 120
-BROWSER = "chrome136"
+DEFAULT_BROWSER = "chrome136"
+DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
DEFAULT_MIME = "application/octet-stream"
# 并发控制
@@ -179,6 +180,7 @@ class BaseService:
def _headers(self, token: str, referer: str = "https://grok.com/") -> dict:
"""构建请求头"""
+ user_agent = get_config("grok.user_agent", DEFAULT_USER_AGENT)
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br, zstd",
@@ -199,7 +201,7 @@ class BaseService:
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
+ "User-Agent": user_agent,
}
apply_statsig(headers)
@@ -354,7 +356,7 @@ class UploadService(BaseService):
UPLOAD_API,
headers=headers,
json=payload,
- impersonate=BROWSER,
+ impersonate=get_config("grok.browser", DEFAULT_BROWSER),
timeout=self.timeout,
proxies=self._proxies(),
)
@@ -448,7 +450,7 @@ class ListService(BaseService):
LIST_API,
headers=headers,
params=params,
- impersonate=BROWSER,
+ impersonate=get_config("grok.browser", DEFAULT_BROWSER),
timeout=self.timeout,
proxies=self._proxies(),
)
@@ -527,7 +529,7 @@ class DeleteService(BaseService):
response = await session.delete(
url,
headers=headers,
- impersonate=BROWSER,
+ impersonate=get_config("grok.browser", DEFAULT_BROWSER),
timeout=self.timeout,
proxies=self._proxies(),
)
@@ -663,7 +665,7 @@ class DownloadService(BaseService):
proxies=self._proxies(),
timeout=self.timeout,
allow_redirects=True,
- impersonate=BROWSER,
+ impersonate=get_config("grok.browser", DEFAULT_BROWSER),
stream=True,
)
diff --git a/app/services/grok/services/chat.py b/app/services/grok/services/chat.py
index 72be6e7..f1c8212 100644
--- a/app/services/grok/services/chat.py
+++ b/app/services/grok/services/chat.py
@@ -26,7 +26,8 @@ from app.services.token import get_token_manager, EffortType
CHAT_API = "https://grok.com/rest/app-chat/conversations/new"
TIMEOUT = 120
-BROWSER = "chrome136"
+DEFAULT_BROWSER = "chrome136"
+DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
@dataclass
@@ -162,6 +163,7 @@ class ChatRequestBuilder:
@staticmethod
def build_headers(token: str) -> Dict[str, str]:
"""构造请求头"""
+ user_agent = get_config("grok.user_agent", DEFAULT_USER_AGENT)
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br, zstd",
@@ -182,7 +184,7 @@ class ChatRequestBuilder:
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
+ "User-Agent": user_agent,
}
apply_statsig(headers)
@@ -311,7 +313,8 @@ class GrokChatService:
# 建立连接函数
async def establish_connection():
"""建立连接并返回 response 对象"""
- session = AsyncSession(impersonate=BROWSER)
+ browser = get_config("grok.browser", DEFAULT_BROWSER)
+ session = AsyncSession(impersonate=browser)
try:
response = await session.post(
CHAT_API,
diff --git a/app/services/grok/services/media.py b/app/services/grok/services/media.py
index da4f1af..7b0eab7 100644
--- a/app/services/grok/services/media.py
+++ b/app/services/grok/services/media.py
@@ -26,7 +26,8 @@ CREATE_POST_API = "https://grok.com/rest/media/post/create"
CHAT_API = "https://grok.com/rest/app-chat/conversations/new"
# 常量
-BROWSER = "chrome136"
+DEFAULT_BROWSER = "chrome136"
+DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
TIMEOUT = 300
DEFAULT_MAX_CONCURRENT = 50
_MEDIA_SEMAPHORE = asyncio.Semaphore(DEFAULT_MAX_CONCURRENT)
@@ -58,6 +59,7 @@ class VideoService:
self, token: str, referer: str = "https://grok.com/imagine"
) -> dict:
"""构建请求头"""
+ user_agent = get_config("grok.user_agent", DEFAULT_USER_AGENT)
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br, zstd",
@@ -78,7 +80,7 @@ class VideoService:
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
+ "User-Agent": user_agent,
}
apply_statsig(headers)
@@ -119,11 +121,12 @@ class VideoService:
payload = {"mediaType": media_type, "prompt": prompt}
async with AsyncSession() as session:
+ browser = get_config("grok.browser", DEFAULT_BROWSER)
response = await session.post(
CREATE_POST_API,
headers=headers,
json=payload,
- impersonate=BROWSER,
+ impersonate=browser,
timeout=30,
proxies=self._build_proxies(),
)
@@ -243,7 +246,8 @@ class VideoService:
prompt, post_id, aspect_ratio, video_length, resolution_name, preset
)
- session = AsyncSession(impersonate=BROWSER)
+ browser = get_config("grok.browser", DEFAULT_BROWSER)
+ session = AsyncSession(impersonate=browser)
response = await session.post(
CHAT_API,
headers=headers,
@@ -323,7 +327,8 @@ class VideoService:
prompt, post_id, aspect_ratio, video_length, resolution, preset
)
- session = AsyncSession(impersonate=BROWSER)
+ browser = get_config("grok.browser", DEFAULT_BROWSER)
+ session = AsyncSession(impersonate=browser)
response = await session.post(
CHAT_API,
headers=headers,
diff --git a/app/services/grok/services/nsfw.py b/app/services/grok/services/nsfw.py
index 2041ec1..be797a4 100644
--- a/app/services/grok/services/nsfw.py
+++ b/app/services/grok/services/nsfw.py
@@ -23,7 +23,8 @@ from app.services.grok.utils.headers import build_sso_cookie
NSFW_API = "https://grok.com/auth_mgmt.AuthManagement/UpdateUserFeatureControls"
BIRTH_DATE_API = "https://grok.com/rest/auth/set-birth-date"
-BROWSER = "chrome136"
+DEFAULT_BROWSER = "chrome136"
+DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
TIMEOUT = 30
@@ -61,12 +62,13 @@ class NSFWService:
def _build_headers(self, token: str) -> dict:
"""构造 gRPC-Web 请求头"""
cookie = build_sso_cookie(token, include_rw=True)
+ user_agent = get_config("grok.user_agent", DEFAULT_USER_AGENT)
return {
"accept": "*/*",
"content-type": "application/grpc-web+proto",
"origin": "https://grok.com",
"referer": "https://grok.com/",
- "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
+ "user-agent": user_agent,
"x-grpc-web": "1",
"x-user-agent": "connect-es/2.1.1",
"cookie": cookie,
@@ -75,12 +77,13 @@ class NSFWService:
def _build_birth_headers(self, token: str) -> dict:
"""构造设置出生日期请求头"""
cookie = build_sso_cookie(token, include_rw=True)
+ user_agent = get_config("grok.user_agent", DEFAULT_USER_AGENT)
return {
"accept": "*/*",
"content-type": "application/json",
"origin": "https://grok.com",
"referer": "https://grok.com/?_s=account",
- "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
+ "user-agent": user_agent,
"cookie": cookie,
}
@@ -127,7 +130,8 @@ class NSFWService:
proxies = {"http": self.proxy, "https": self.proxy} if self.proxy else None
try:
- async with AsyncSession(impersonate=BROWSER) as session:
+ browser = get_config("grok.browser", DEFAULT_BROWSER)
+ async with AsyncSession(impersonate=browser) as session:
ok, birth_status, birth_err = await self._set_birth_date(
session, token
)
diff --git a/app/services/grok/services/usage.py b/app/services/grok/services/usage.py
index e6b7c57..a339962 100644
--- a/app/services/grok/services/usage.py
+++ b/app/services/grok/services/usage.py
@@ -15,7 +15,8 @@ from app.services.grok.utils.retry import retry_on_status
LIMITS_API = "https://grok.com/rest/rate-limits"
-BROWSER = "chrome136"
+DEFAULT_BROWSER = "chrome136"
+DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
TIMEOUT = 10
DEFAULT_MAX_CONCURRENT = 25
_USAGE_SEMAPHORE = asyncio.Semaphore(DEFAULT_MAX_CONCURRENT)
@@ -45,6 +46,7 @@ class UsageService:
def _build_headers(self, token: str) -> dict:
"""构建请求头"""
+ user_agent = get_config("grok.user_agent", DEFAULT_USER_AGENT)
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br, zstd",
@@ -65,7 +67,7 @@ class UsageService:
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
+ "User-Agent": user_agent,
}
apply_statsig(headers)
@@ -103,13 +105,14 @@ class UsageService:
try:
headers = self._build_headers(token)
payload = {"requestKind": "DEFAULT", "modelName": model_name}
+ browser = get_config("grok.browser", DEFAULT_BROWSER)
async with AsyncSession() as session:
response = await session.post(
LIMITS_API,
headers=headers,
json=payload,
- impersonate=BROWSER,
+ impersonate=browser,
timeout=self.timeout,
proxies=self._build_proxies(),
)
diff --git a/app/services/grok/services/voice.py b/app/services/grok/services/voice.py
index 9e03880..8b87991 100644
--- a/app/services/grok/services/voice.py
+++ b/app/services/grok/services/voice.py
@@ -15,7 +15,7 @@ from app.services.grok.utils.headers import apply_statsig, build_sso_cookie
LIVEKIT_TOKEN_API = "https://grok.com/rest/livekit/tokens"
TIMEOUT = 30
-BROWSER = "chrome136"
+DEFAULT_BROWSER = "chrome136"
class VoiceService:
@@ -45,7 +45,8 @@ class VoiceService:
proxies = {"http": self.proxy, "https": self.proxy} if self.proxy else None
try:
- async with AsyncSession(impersonate=BROWSER) as session:
+ browser = get_config("grok.browser", DEFAULT_BROWSER)
+ async with AsyncSession(impersonate=browser) as session:
response = await session.post(
LIVEKIT_TOKEN_API,
headers=headers,
diff --git a/app/static/config/config.html b/app/static/config/config.html
index 76e4f7d..7fb207f 100644
--- a/app/static/config/config.html
+++ b/app/static/config/config.html
@@ -50,7 +50,7 @@
-
+