feat(grok): 模拟 Chrome TLS 指纹以绕过 Cloudflare 检测
- 引入 TLS 模块并配置 HTTPS agent 以精确匹配 Chrome 136 的加密套件、签名算法和 ALPN 顺序 - 更新默认 User-Agent 至 Chrome 136 并动态生成更真实的 Statsig ID - 优化请求头,根据 UA 自动填充 sec-ch-ua 系列字段,增强指纹一致性 - 移除调试日志并调整 Cloudflare 令牌缺失的日志级别
This commit is contained in:
parent
3b345887d4
commit
f0445d96e4
1 changed files with 96 additions and 30 deletions
|
|
@ -2,6 +2,7 @@ import axios from 'axios';
|
|||
import logger from '../../utils/logger.js';
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import * as tls from 'tls';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { API_ACTIONS, isRetryableNetworkError } from '../../utils/common.js';
|
||||
import { getProviderModels } from '../provider-models.js';
|
||||
|
|
@ -12,18 +13,65 @@ import { ConverterFactory } from '../../converters/ConverterFactory.js';
|
|||
import * as readline from 'readline';
|
||||
import { getProviderPoolManager } from '../../services/service-manager.js';
|
||||
|
||||
// 配置 HTTP/HTTPS agent 限制连接池大小
|
||||
// Chrome 136 TLS cipher suites (精确匹配 Chrome 的 ClientHello 顺序)
|
||||
// 参考: https://tls.peet.ws/api/all (Chrome 136 fingerprint)
|
||||
const CHROME_CIPHERS = [
|
||||
'TLS_AES_128_GCM_SHA256',
|
||||
'TLS_AES_256_GCM_SHA384',
|
||||
'TLS_CHACHA20_POLY1305_SHA256',
|
||||
'ECDHE-ECDSA-AES128-GCM-SHA256',
|
||||
'ECDHE-RSA-AES128-GCM-SHA256',
|
||||
'ECDHE-ECDSA-AES256-GCM-SHA384',
|
||||
'ECDHE-RSA-AES256-GCM-SHA384',
|
||||
'ECDHE-ECDSA-CHACHA20-POLY1305',
|
||||
'ECDHE-RSA-CHACHA20-POLY1305',
|
||||
'ECDHE-RSA-AES128-SHA',
|
||||
'ECDHE-RSA-AES256-SHA',
|
||||
'AES128-GCM-SHA256',
|
||||
'AES256-GCM-SHA384',
|
||||
'AES128-SHA',
|
||||
'AES256-SHA',
|
||||
].join(':');
|
||||
|
||||
// Chrome 签名算法 (匹配 Chrome 的 signature_algorithms 扩展)
|
||||
const CHROME_SIGALGS = [
|
||||
'ecdsa_secp256r1_sha256',
|
||||
'rsa_pss_rsae_sha256',
|
||||
'rsa_pkcs1_sha256',
|
||||
'ecdsa_secp384r1_sha384',
|
||||
'rsa_pss_rsae_sha384',
|
||||
'rsa_pkcs1_sha384',
|
||||
'rsa_pss_rsae_sha512',
|
||||
'rsa_pkcs1_sha512',
|
||||
].join(':');
|
||||
|
||||
// 配置 HTTP Agent
|
||||
const httpAgent = new http.Agent({
|
||||
keepAlive: true,
|
||||
maxSockets: 100,
|
||||
maxFreeSockets: 5,
|
||||
timeout: 120000,
|
||||
});
|
||||
|
||||
// 配置 HTTPS Agent — 模拟 Chrome 136 TLS 指纹
|
||||
const httpsAgent = new https.Agent({
|
||||
keepAlive: true,
|
||||
maxSockets: 100,
|
||||
maxFreeSockets: 5,
|
||||
timeout: 120000,
|
||||
// TLS 指纹伪装
|
||||
ciphers: CHROME_CIPHERS,
|
||||
sigalgs: CHROME_SIGALGS,
|
||||
minVersion: 'TLSv1.2',
|
||||
maxVersion: 'TLSv1.3',
|
||||
// Chrome 的 ALPN 协商顺序: h2 优先
|
||||
ALPNProtocols: ['h2', 'http/1.1'],
|
||||
// Chrome 支持的 EC 曲线
|
||||
ecdhCurve: 'X25519:P-256:P-384',
|
||||
// 允许不安全的旧版协商 (Chrome 也允许)
|
||||
honorCipherOrder: false,
|
||||
// 启用 session ticket (Chrome 默认行为)
|
||||
sessionTimeout: 300,
|
||||
});
|
||||
|
||||
const DEFAULT_GROK_ENDPOINT = 'https://grok.com/rest/app-chat/conversations/new';
|
||||
|
|
@ -53,7 +101,7 @@ export class GrokApiService {
|
|||
this.uuid = config.uuid; // 存储 UUID 以便后续调用账号池方法
|
||||
this.token = config.GROK_COOKIE_TOKEN;
|
||||
this.cfClearance = config.GROK_CF_CLEARANCE;
|
||||
this.userAgent = config.GROK_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';
|
||||
this.userAgent = config.GROK_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';
|
||||
this.baseUrl = config.GROK_BASE_URL || 'https://grok.com';
|
||||
this.chatApi = `${this.baseUrl}/rest/app-chat/conversations/new`;
|
||||
this.isInitialized = false;
|
||||
|
|
@ -68,7 +116,7 @@ export class GrokApiService {
|
|||
logger.warn('[Grok] GROK_COOKIE_TOKEN is missing. Requests will fail if authorization is required.');
|
||||
}
|
||||
if (!this.cfClearance) {
|
||||
logger.warn('[Grok] GROK_CF_CLEARANCE is missing. This might cause Cloudflare challenges.');
|
||||
logger.debug('[Grok] GROK_CF_CLEARANCE not set. TLS/header fingerprinting should bypass Cloudflare without it.');
|
||||
}
|
||||
|
||||
// Initial usage sync
|
||||
|
|
@ -120,7 +168,6 @@ export class GrokApiService {
|
|||
try {
|
||||
const response = await axios(axiosConfig);
|
||||
const data = response.data;
|
||||
console.log("111111111111111111111111111", JSON.stringify(data))
|
||||
|
||||
let remaining = data.remainingTokens;
|
||||
if (remaining === undefined) {
|
||||
|
|
@ -176,8 +223,26 @@ export class GrokApiService {
|
|||
* Generate Statsig ID (StatsigGenerator)
|
||||
*/
|
||||
genStatsigId() {
|
||||
// Static Statsig ID from code
|
||||
return "ZTpUeXBlRXJyb3I6IENhbm5vdCByZWFkIHByb3BlcnRpZXMgb2YgdW5kZWZpbmVkIChyZWFkaW5nICdjaGlsZE5vZGVzJyk=";
|
||||
const randomString = (len, alphanumeric = false) => {
|
||||
const chars = alphanumeric
|
||||
? 'abcdefghijklmnopqrstuvwxyz0123456789'
|
||||
: 'abcdefghijklmnopqrstuvwxyz';
|
||||
let result = '';
|
||||
for (let i = 0; i < len; i++) {
|
||||
result += chars[Math.floor(Math.random() * chars.length)];
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
let msg;
|
||||
if (Math.random() < 0.5) {
|
||||
const rand = randomString(5, true);
|
||||
msg = `e:TypeError: Cannot read properties of null (reading 'children['${rand}']')`;
|
||||
} else {
|
||||
const rand = randomString(10);
|
||||
msg = `e:TypeError: Cannot read properties of undefined (reading '${rand}')`;
|
||||
}
|
||||
return Buffer.from(msg).toString('base64');
|
||||
}
|
||||
|
||||
buildHeaders() {
|
||||
|
|
@ -191,45 +256,46 @@ export class GrokApiService {
|
|||
cookie.push(`cf_clearance=${this.cfClearance}`);
|
||||
}
|
||||
|
||||
// Extract browser version and platform from UA for consistent fingerprinting
|
||||
const ua = this.userAgent;
|
||||
let brand = 'Google Chrome';
|
||||
if (ua.includes('Edg/')) brand = 'Microsoft Edge';
|
||||
const versionMatch = ua.match(/(?:Chrome|Chromium|Edg)\/(\d+)/);
|
||||
const version = versionMatch ? versionMatch[1] : '136';
|
||||
|
||||
let platform = 'macOS';
|
||||
if (ua.includes('Windows')) platform = 'Windows';
|
||||
else if (ua.includes('Android')) platform = 'Android';
|
||||
else if (ua.includes('iPhone') || ua.includes('iPad')) platform = 'iOS';
|
||||
else if (ua.includes('Linux') && !ua.includes('Android')) platform = 'Linux';
|
||||
|
||||
const isMobile = ua.toLowerCase().includes('mobile');
|
||||
|
||||
const headers = {
|
||||
'accept': '*/*',
|
||||
'accept-encoding': 'gzip, deflate, br',
|
||||
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
||||
'accept-language': 'zh-CN,zh;q=0.9',
|
||||
'baggage': 'sentry-environment=production,sentry-release=d6add6fb0460641fd482d767a335ef72b9b6abb8,sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c',
|
||||
'cache-control': 'no-cache',
|
||||
'content-type': 'application/json',
|
||||
'cookie': cookie.join('; '),
|
||||
'origin': this.baseUrl,
|
||||
'pragma': 'no-cache',
|
||||
'priority': 'u=1, i',
|
||||
'referer': `${this.baseUrl}/`,
|
||||
'sec-ch-ua': `"${brand}";v="${version}", "Chromium";v="${version}", "Not(A:Brand";v="24"`,
|
||||
'sec-ch-ua-arch': platform === 'macOS' ? 'arm' : 'x86',
|
||||
'sec-ch-ua-bitness': '64',
|
||||
'sec-ch-ua-mobile': isMobile ? '?1' : '?0',
|
||||
'sec-ch-ua-model': '',
|
||||
'sec-ch-ua-platform': `"${platform}"`,
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-origin',
|
||||
'user-agent': this.userAgent,
|
||||
'user-agent': ua,
|
||||
'x-statsig-id': this.genStatsigId(),
|
||||
'x-xai-request-id': uuidv4()
|
||||
};
|
||||
|
||||
// Sync Sec-Ch-Ua logic
|
||||
if (this.userAgent && (this.userAgent.includes("Chrome/") || this.userAgent.includes("Chromium/") || this.userAgent.includes("Edg/"))) {
|
||||
let brand = "Google Chrome";
|
||||
if (this.userAgent.includes("Edg/")) brand = "Microsoft Edge";
|
||||
|
||||
const versionMatch = this.userAgent.match(/(?:Chrome|Chromium|Edg)\/(\d+)/);
|
||||
const version = versionMatch ? versionMatch[1] : "133";
|
||||
|
||||
headers['sec-ch-ua'] = `"${brand}";v="${version}", "Chromium";v="${version}", "Not(A:Brand";v="24"`;
|
||||
headers['sec-ch-ua-mobile'] = this.userAgent.toLowerCase().includes("mobile") ? '?1' : '?0';
|
||||
|
||||
// Platform detection
|
||||
let platform = "Windows";
|
||||
if (this.userAgent.includes("Mac OS X")) platform = "macOS";
|
||||
else if (this.userAgent.includes("Android")) platform = "Android";
|
||||
else if (this.userAgent.includes("iPhone") || this.userAgent.includes("iPad")) platform = "iOS";
|
||||
else if (this.userAgent.includes("Linux")) platform = "Linux";
|
||||
|
||||
headers['sec-ch-ua-platform'] = `"${platform}"`;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue