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 logger from '../../utils/logger.js';
|
||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
import * as https from 'https';
|
import * as https from 'https';
|
||||||
|
import * as tls from 'tls';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { API_ACTIONS, isRetryableNetworkError } from '../../utils/common.js';
|
import { API_ACTIONS, isRetryableNetworkError } from '../../utils/common.js';
|
||||||
import { getProviderModels } from '../provider-models.js';
|
import { getProviderModels } from '../provider-models.js';
|
||||||
|
|
@ -12,18 +13,65 @@ import { ConverterFactory } from '../../converters/ConverterFactory.js';
|
||||||
import * as readline from 'readline';
|
import * as readline from 'readline';
|
||||||
import { getProviderPoolManager } from '../../services/service-manager.js';
|
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({
|
const httpAgent = new http.Agent({
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
maxSockets: 100,
|
maxSockets: 100,
|
||||||
maxFreeSockets: 5,
|
maxFreeSockets: 5,
|
||||||
timeout: 120000,
|
timeout: 120000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 配置 HTTPS Agent — 模拟 Chrome 136 TLS 指纹
|
||||||
const httpsAgent = new https.Agent({
|
const httpsAgent = new https.Agent({
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
maxSockets: 100,
|
maxSockets: 100,
|
||||||
maxFreeSockets: 5,
|
maxFreeSockets: 5,
|
||||||
timeout: 120000,
|
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';
|
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.uuid = config.uuid; // 存储 UUID 以便后续调用账号池方法
|
||||||
this.token = config.GROK_COOKIE_TOKEN;
|
this.token = config.GROK_COOKIE_TOKEN;
|
||||||
this.cfClearance = config.GROK_CF_CLEARANCE;
|
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.baseUrl = config.GROK_BASE_URL || 'https://grok.com';
|
||||||
this.chatApi = `${this.baseUrl}/rest/app-chat/conversations/new`;
|
this.chatApi = `${this.baseUrl}/rest/app-chat/conversations/new`;
|
||||||
this.isInitialized = false;
|
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.');
|
logger.warn('[Grok] GROK_COOKIE_TOKEN is missing. Requests will fail if authorization is required.');
|
||||||
}
|
}
|
||||||
if (!this.cfClearance) {
|
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
|
// Initial usage sync
|
||||||
|
|
@ -120,7 +168,6 @@ export class GrokApiService {
|
||||||
try {
|
try {
|
||||||
const response = await axios(axiosConfig);
|
const response = await axios(axiosConfig);
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
console.log("111111111111111111111111111", JSON.stringify(data))
|
|
||||||
|
|
||||||
let remaining = data.remainingTokens;
|
let remaining = data.remainingTokens;
|
||||||
if (remaining === undefined) {
|
if (remaining === undefined) {
|
||||||
|
|
@ -176,8 +223,26 @@ export class GrokApiService {
|
||||||
* Generate Statsig ID (StatsigGenerator)
|
* Generate Statsig ID (StatsigGenerator)
|
||||||
*/
|
*/
|
||||||
genStatsigId() {
|
genStatsigId() {
|
||||||
// Static Statsig ID from code
|
const randomString = (len, alphanumeric = false) => {
|
||||||
return "ZTpUeXBlRXJyb3I6IENhbm5vdCByZWFkIHByb3BlcnRpZXMgb2YgdW5kZWZpbmVkIChyZWFkaW5nICdjaGlsZE5vZGVzJyk=";
|
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() {
|
buildHeaders() {
|
||||||
|
|
@ -191,45 +256,46 @@ export class GrokApiService {
|
||||||
cookie.push(`cf_clearance=${this.cfClearance}`);
|
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 = {
|
const headers = {
|
||||||
'accept': '*/*',
|
'accept': '*/*',
|
||||||
'accept-encoding': 'gzip, deflate, br',
|
'accept-language': 'zh-CN,zh;q=0.9',
|
||||||
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
|
||||||
'baggage': 'sentry-environment=production,sentry-release=d6add6fb0460641fd482d767a335ef72b9b6abb8,sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c',
|
'baggage': 'sentry-environment=production,sentry-release=d6add6fb0460641fd482d767a335ef72b9b6abb8,sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c',
|
||||||
|
'cache-control': 'no-cache',
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
'cookie': cookie.join('; '),
|
'cookie': cookie.join('; '),
|
||||||
'origin': this.baseUrl,
|
'origin': this.baseUrl,
|
||||||
|
'pragma': 'no-cache',
|
||||||
'priority': 'u=1, i',
|
'priority': 'u=1, i',
|
||||||
'referer': `${this.baseUrl}/`,
|
'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-dest': 'empty',
|
||||||
'sec-fetch-mode': 'cors',
|
'sec-fetch-mode': 'cors',
|
||||||
'sec-fetch-site': 'same-origin',
|
'sec-fetch-site': 'same-origin',
|
||||||
'user-agent': this.userAgent,
|
'user-agent': ua,
|
||||||
'x-statsig-id': this.genStatsigId(),
|
'x-statsig-id': this.genStatsigId(),
|
||||||
'x-xai-request-id': uuidv4()
|
'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;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue