aurora / internal /chatgpt /cookie_bootstrap.go
qwen2api's picture
Upload 113 files
6bc074c verified
Raw
History Blame Contribute Delete
3.79 kB
package chatgpt
import (
"io"
"net/http"
"sync"
"time"
"aurora/httpclient"
"aurora/internal/accounts"
)
// CookieBootstrap 负责把 Cloudflare 在 chatgpt.com 下发的
// cf_clearance / __cf_bm / session-token / oai-chat-web-route / oai-sc
// / __Secure-oai-is / _dd_s / _uasid / _umsid 等 cookie 引导进 CookieJar。
//
// 真实登录用户(浏览器)首次访问 chatgpt.com 时,CF 会通过 Set-Cookie 注入
// 这些关键 cookie,后续请求会自动带上。Go HTTP client 在没有这些 cookie 的
// 情况下,直接发 /f/conversation/prepare 会被 CF 当作机器人拦截。
//
// CookieBootstrap 不会"破解" Turnstile —— 那是 JS challenge,纯 HTTP 过不去。
// 它只是**接收** CF 在简单 GET 请求(非 challenge 路径)下发的 cookie,并维持
// CookieJar 的活跃度。当 cf_clearance 过期(15-30 分钟)时,需要重新走一次
// 浏览器登录刷新。
type CookieBootstrap struct {
mu sync.Mutex
bootstrapped bool
lastBootAt time.Time
}
// 全局引导器,所有 account 共享同一个 CookieJar(每个 client 各自有自己
// 的 jar,这里只负责"告诉 caller 何时需要重引导")。
var cookieBootstrap CookieBootstrap
// bootstrapCookieJar 主动访问 chatgpt.com 几个非 challenge 端点,
// 让 CF 把 cf_clearance / __cf_bm 等 cookie 写入 jar。
//
// 返回 true 表示 jar 已经有"基本可用"的 cookie,可以继续发请求。
// 返回 false 表示拿不到(被 CF 拦了)。
func bootstrapCookieJar(client httpclient.AuroraHttpClient, account *accounts.Account) bool {
cookieBootstrap.mu.Lock()
defer cookieBootstrap.mu.Unlock()
// 1) 快速路径:如果 jar 里已经有 cf_clearance 且 10 分钟内没刷新,跳过
if cookies := client.GetCookies("https://chatgpt.com"); hasUsableCookies(cookies) {
if time.Since(cookieBootstrap.lastBootAt) < 10*time.Minute {
cookieBootstrap.bootstrapped = true
return true
}
}
// 2) 拉首页 + 几个 warm-up 端点
header := createBaseHeader()
urls := []string{
"https://chatgpt.com/", // 首页 — CF 注入核心 cookie
"https://chatgpt.com/api/auth/session", // 拉 session 状态,触发更多 Set-Cookie
"https://chatgpt.com/cdn-cgi/trace", // CF trace,简单 200
}
for _, u := range urls {
resp, err := client.Request(http.MethodGet, u, header, nil, nil)
if err != nil || resp == nil {
continue
}
// 把响应里 Set-Cookie 的内容消费掉(让 jar 自动收集)
// tls-client 的 jar 在 Do() 时就会解析 Set-Cookie,这里只需 Drain body
if resp.Body != nil {
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}
_ = header
}
cookieBootstrap.lastBootAt = time.Now()
cookieBootstrap.bootstrapped = true
// 3) 校验:必须至少有 cf_clearance 或 session-token,否则视为引导失败
cookies := client.GetCookies("https://chatgpt.com")
if !hasUsableCookies(cookies) {
return false
}
return true
}
// hasUsableCookies 检查 jar 里是否有"能过 CF"的关键 cookie。
// 至少要有 cf_clearance 或 __Secure-next-auth.session-token.0 之一。
func hasUsableCookies(cookies []*http.Cookie) bool {
if len(cookies) == 0 {
return false
}
names := make(map[string]bool, len(cookies))
for _, c := range cookies {
names[c.Name] = true
}
return names["cf_clearance"] || names["__Secure-next-auth.session-token.0"]
}
// ensureBootstrapped 在请求前调用,jar 没初始化就主动引导一次。
// 失败也不 fatal —— 调用方自己判断后续请求是否被 CF 拦截。
func ensureBootstrapped(client httpclient.AuroraHttpClient, account *accounts.Account) {
cookies := client.GetCookies("https://chatgpt.com")
if hasUsableCookies(cookies) {
return
}
bootstrapCookieJar(client, account)
}